[Forensics] ASIS CTF finals 2016 – p1ng

Average: 3.60
Rating Count: 5
You Rated: Not rated
Points
121
Solves
24
Category
Forensic

Description

p1ng is ASIS hand-drawn PNG.
http://asis-ctf.ir/tasks/p1ng.txz_76eca77720a65d95557a3850929abd0a8a18c636

We have a png file inspecting with binwalk we can see this strange compressed data:

kinyabitch@Debian ~/h/c/a/f/p1ng> binwalk p1ng/p1ng

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 180 x 76, 8-bit/color RGBA, non-interlaced
99            0x63            Zlib compressed data, best compression
4987          0x137B          Zlib compressed data, best compression
9484          0x250C          Zlib compressed data, best compression
17713         0x4531          Zlib compressed data, best compression
22512         0x57F0          Zlib compressed data, best compression
29380         0x72C4          Zlib compressed data, best compression
36947         0x9053          Zlib compressed data, best compression
43723         0xAACB          Zlib compressed data, best compression
51878         0xCAA6          Zlib compressed data, best compression
58000         0xE290          Zlib compressed data, best compression
65549         0x1000D         Zlib compressed data, best compression
72231         0x11A27         Zlib compressed data, best compression
79133         0x1351D         Zlib compressed data, best compression
85159         0x14CA7         Zlib compressed data, best compression
92012         0x1676C         Zlib compressed data, best compression
98842         0x1821A         Zlib compressed data, best compression
105524        0x19C34         Zlib compressed data, best compression
113043        0x1B993         Zlib compressed data, best compression
119801        0x1D3F9         Zlib compressed data, best compression
127259        0x1F11B         Zlib compressed data, best compression
134259        0x20C73         Zlib compressed data, best compression
139926        0x22296         Zlib compressed data, best compression
146983        0x23E27         Zlib compressed data, best compression

if we inspect with a hex editor or even easier using pngsplit to split the png chunks we can find some unusual type chunks like fdAT, fcTL and acTL:

kinyabitch@Debian ~/h/c/a/f/p1ng> ls p1ng/qwe/
p1ng.0000.sig   p1ng.0004.IDAT  p1ng.0008.fdAT  p1ng.0012.fdAT  p1ng.0016.fdAT  p1ng.0020.fdAT  p1ng.0024.fdAT  p1ng.0028.fdAT  p1ng.0032.fdAT  p1ng.0036.fdAT  p1ng.0040.fdAT  p1ng.0044.fdAT  p1ng.0048.fdAT
p1ng.0001.IHDR  p1ng.0005.fcTL  p1ng.0009.fcTL  p1ng.0013.fcTL  p1ng.0017.fcTL  p1ng.0021.fcTL  p1ng.0025.fcTL  p1ng.0029.fcTL  p1ng.0033.fcTL  p1ng.0037.fcTL  p1ng.0041.fcTL  p1ng.0045.fcTL  p1ng.0049.IEND
p1ng.0002.acTL  p1ng.0006.fdAT  p1ng.0010.fdAT  p1ng.0014.fdAT  p1ng.0018.fdAT  p1ng.0022.fdAT  p1ng.0026.fdAT  p1ng.0030.fdAT  p1ng.0034.fdAT  p1ng.0038.fdAT  p1ng.0042.fdAT  p1ng.0046.fdAT
p1ng.0003.fcTL  p1ng.0007.fcTL  p1ng.0011.fcTL  p1ng.0015.fcTL  p1ng.0019.fcTL  p1ng.0023.fcTL  p1ng.0027.fcTL  p1ng.0031.fcTL  p1ng.0035.fcTL  p1ng.0039.fcTL  p1ng.0043.fcTL  p1ng.0047.fcTL

After some search on google I found this links https://wiki.mozilla.org/APNG_Specification and http://fileformats.wikia.com/wiki/Animated_Portable_Network_Graphics , this is a APNG it’s a png but animated! So our job here is to split the animation images and then maybe we can find the flag. There is a lot of tools online to do this but for the curiosity I managed to write one in python, some images were broken I needed to do some adjustments to the IDHR header to fix it:

import os
import sys
import struct
import binascii

directory = "outp/"
signature = ""
ihdr_header = ""
fdat_chunk = ""
idat_chunk = ""
iend_chunk = ""
ihdrs = []

parts = []

PNG_SIGN = b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"


def is_png(png):
    """Test if @png is valid png file by checking signature

    @png can be str of the filename, a file-like object, or a bytes object.
    """
    if isinstance(png, str):
        with open(png, "rb") as f:
            png = f.read(8)

    if hasattr(png, "read"):
        png = png.read(8)

    return png[:8] == PNG_SIGN


def chunks(png):
    """Yield chunks from png.

    @png can be a string of filename, a file-like object, or a bytes bject.
    """
    if not is_png(png):
        # convert to png
        if isinstance(png, bytes):
            with io.BytesIO(png) as f:
                with io.BytesIO() as f2:
                    PIL.Image.open(f).save(f2, "PNG", optimize=True)
                    png = f2.getvalue()
        else:
            with io.BytesIO() as f2:
                PIL.Image.open(png).save(f2, "PNG", optimize=True)
                png = f2.getvalue()

    if isinstance(png, str):
        # file name
        with open(png, "rb") as f:
            png = f.read()

    if hasattr(png, "read"):
        # file like
        png = png.read()

    return chunks_read(png)


def make_chunk(type, data):
    """Create chunk with @type and chunk data @data.
    
    It will calculate length and crc for you. Return bytes.
    
    @type is str and @data is bytes.
    """
    out = struct.pack("!I", len(data))
    data = type.encode("latin-1") + data
    a = '%08x' % (binascii.crc32(data) % (1<<32))
    out += data + a.decode('hex')
    return out


def chunks_read(b):
    """Parse PNG bytes into different chunks, yielding (type, data). 

    @type is a string of chunk type.
    @data is the bytes of the chunk. Including length, type, data, and crc.
    """
    # skip signature
    i = 8
    # yield chunks

    while i < len(b):
        data_len, = struct.unpack("!I", b[i:i + 4])
        type = b[i + 4:i + 8].decode("latin-1")
        yield type, b[i:i + data_len + 12]
        i += data_len + 12
# 6 
if __name__ == '__main__':
    i = 0
    t = 0
    frame_chunks = []
    frames = []
    for ctype, data in list(chunks('p1ng/p1ng')):
        if ctype == "IHDR":
            ihdr_header = data
            hdr = ihdr_header
        elif ctype == "acTL": # ignore Animation Control Chunk
            continue    
        elif ctype == "fcTL": # https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
            """
            """
            c = struct.unpack("!IIIIHHbb", data[12:-4])
            width = 180
            height = 76
            if i in [11,12,13,2,9]:
                width = c[0]
                height = c[1]
            if i in [7,13,21,22]:
                width -= 1
                #height = c[1]
            if i == 6:
                width -= 2
            print c
            ihdr = make_chunk("IHDR", struct.pack("!II", width + c[2], height+ c[3]) + hdr[16:-4])
            ihdrs.append(ihdr)
            #i += 1
        elif ctype == "IDAT":
            parts.append(("IDAT", data))
            i += 1
        elif ctype == "fdAT": # https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk
            parts.append(("IDAT", make_chunk("IDAT", data[12:-4])))
            i += 1
        elif ctype == "IEND":
            iend_chunk= data
            break

    if not os.path.exists(directory):
        os.makedirs(directory)
    for i in range(len(parts)):
        #parts[i] = PNG_SIGN + parts[0] + pallets[i] + parts[i] + parts[-1]
        f = open(directory + 'p1ng%d.png' % i, 'w+')
        if i == 0:
            ihdrs[i] = ihdr_header
        f.write(PNG_SIGN + ihdrs[i] + parts[i][1] + iend_chunk)
        f.close()

After running the script you can get 22 imgs splited into a directory, after joining them you can construct the flag:
ASIS{As_l0n9_4s_CTF_3x1sts_th3r3_w1ll_b3_ASIS_4nd_4s_l0n9_4s_ASIS_3x1sts_th3r3_w1ll_b3_PNG!}

Leave a comment