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.