ASIS CTF Quals 2015: Broken Heart

This is a write up for the first ASIS CTF 2015 Quals forensics challenge. It was worth 100 points and consisted in recovering a broken image.

We start by uncompressing the XZ compressed file.

$ file myheart_7cb6daec0c45b566b9584f98642a7123
myheart_7cb6daec0c45b566b9584f98642a7123: XZ compressed data
$ unxz < myheart_7cb6daec0c45b566b9584f98642a7123 > myheart

The resulting file is a packet capture, which we can open with Wireshark.

$ file myheart
myheart: pcap-ng capture file - version 1.0

There are a bunch of HTTP 206 Partial Content responses with different Content-Range bytes values, so the file we are interested in is broken into pieces (e.g: bytes 1080486-1345387/2347916).

We can list all the TCP streams in Wireshark by going to Statistics - Conversations List - TCP. There are 23 chunks and all of them seem to be part of the same file.

We extract each server response into its own file, take note of the content range, and strip the HTTP headers. In my case, I named these files from s_dump0 to s_dump22.

Let’s write a dirty python script that puts everything in place so we don’t have to worry about the overlapping ranges.

data = [
    {"file":"s_dump0",
    "start":1080486,
    "end":1345387},
    {"file":"s_dump1",
    "start":986065,
    "end":1150874},
    {"file":"s_dump2",
    "start":1397670,
    "end":1593207},
    {"file":"s_dump3",
    "start":337541,
    "end":500782},
    {"file":"s_dump4",
    "start":2001846,
    "end":2202904},
    {"file":"s_dump5",
    "start":467298,
    "end":648929},
    {"file":"s_dump6",
    "start":1507903,
    "end":1694032},
    {"file":"s_dump7",
    "start":552789,
    "end":781321},
    {"file":"s_dump8",
    "start":1276598,
    "end":1432659},
    {"file":"s_dump9",
    "start":1888311,
    "end":1938509},
    {"file":"s_dump10",
    "start":13,
    "end":179538},
    {"file":"s_dump11",
    "start":2106781,
    "end":2347915},
    {"file":"s_dump12",
    "start":1540792,
    "end":1639406},
    {"file":"s_dump13",
    "start":145550,
    "end":198027},
    {"file":"s_dump14",
    "start":905781,
    "end":1032111},
    {"file":"s_dump15",
    "start":1987909,
    "end":2044321},
    {"file":"s_dump16",
    "start":694834,
    "end":905770},
    {"file":"s_dump17",
    "start":27943,
    "end":132132},
    {"file":"s_dump18",
    "start":1774960,
    "end":1959007},
    {"file":"s_dump19",
    "start":892465,
    "end":1067354},
    {"file":"s_dump20",
    "start":1904693,
    "end":2059434},
    {"file":"s_dump21",
    "start":188923,
    "end":359924},
    {"file":"s_dump22",
    "start":1672374,
    "end":1872648},
]

with file('entire_file', 'r+b') as fh:
    for i in data:
        patch = file(i["file"]).read()
        fh.seek(i["start"])
        fh.write(patch)

The scripts assumes we have a file named entire_file that is 2347916 bytes in size, so we need to create it first.

$ dd if=/dev/zero of=entire_file bs=2347916 count=1

Opening the file with a hex editor shows that the first 13 bytes are set to zero, which makes sense since that’s the minimum content range present in the chunks. There are several hints to the file being a PNG, such as a pHYs chunk and a HDR right next to the bytes we are missing, which is probably a IHDR chunk.

Patching the header and the missing I character resulted in a valid PNG image. I used the bless hex editor to set the first 13 bytes to 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49. The image contains the flag.

broken_heart

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s