Point Value: 200
Description: It slices, it dices, it juliennes fries!
Tags: forensics
The challenge is a binary file with a forensics tags, so I assumed it would be pulling some data out of some other file format.
Step 1: Unpack
$ file diskomatic-98757b0043b8aea77e9014cff8ead7b1
diskomatic-98757b0043b8aea77e9014cff8ead7b1: gzip compressed data, last modified: Mon Jun 22 00:08:37 2015, max compression, from Unix
diskomatic-98757b0043b8aea77e9014cff8ead7b1: gzip compressed data, last modified: Mon Jun 22 00:08:37 2015, max compression, from Unix
Simple enough.
$ zcat diskomatic-98757b0043b8aea77e9014cff8ead7b1 > diskomatic.dat
$ file diskomatic.dat diskomatic.dat:
DOS/MBR boot sector, code offset 0x58+2, OEM-ID "mkdosfs", FAT 1,
Media descriptor 0xf8, sectors/track 32, heads 64, sectors 250368
(volumes > 32 MB) , FAT (32 bit), sectors/FAT 1941, serial number
0x20550574, label: "DISKOMATIC "
Step 2: Mount Filesystem
So we have a FAT filesystem image. Let's try mounting it and seeing what's inside.
$ sudo mount -t vfat ./diskomatic.dat ~/mnt/dos/
$ ls -la ~/mnt/dos
total 5
drwxr-xr-x 2 root root 512 Dec 31 1969 .
drwxr-xr-x 3 grind grind 4096 Aug 6 12:41 ..
$ df -h | grep dos
/dev/loop0 122M 512 122M 1% /home/grind/mnt/dos
$ sudo umount ~/mnt/dos
$ ls -la ~/mnt/dos
total 5
drwxr-xr-x 2 root root 512 Dec 31 1969 .
drwxr-xr-x 3 grind grind 4096 Aug 6 12:41 ..
$ df -h | grep dos
/dev/loop0 122M 512 122M 1% /home/grind/mnt/dos
$ sudo umount ~/mnt/dos
Interesting so from the filesystem's perspective there are no valid files, but we have 122MB of something.
Step 3: Analyze the Binary
$ binwalk -e diskomatic.dat
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
17787904 0x10F6C00 PNG image, 1600 x 900, 8-bit/color RGB, non-interlaced
34088942 0x20827EE Minix filesystem, V1, little endian, 30 char names, 23429 zones
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
17787904 0x10F6C00 PNG image, 1600 x 900, 8-bit/color RGB, non-interlaced
34088942 0x20827EE Minix filesystem, V1, little endian, 30 char names, 23429 zones
binwalk found a PNG file, but was unable to extract it. Interesting... perhaps it is corrupted somehow. I don't know where the Minix FS detection came from, but it's a false positive.
So let's open up the file in a hex editor and take a look at location 0x10F6C00.
Step 4: Hex Editor
Here's the PNG header, with the first IDAT section but it is noticeably quite short. Searching for IDAT we find many more of these sections. Each one is exactly 512B, which I believe is the block size for FAT32. So it seems we have a PNG file which has been fragmented and we need to piece it back together. Well let's try the naieve approach, and just concatenate all the chunks. We can do this from within the hex editor by cropping the file from location 0x10F6C00 onward and then doing a find and replace on all the \x00\x00\x00\x00 words with nothing.
We'll save this file as diskomatic.png and open it.
Length | Chunk type | Chunk data | CRC |
---|---|---|---|
4 bytes | 4 bytes | Length bytes | 4 bytes |
Looking at the 512B blocks in the hex editor again, we see that the PNG chunks are split in the middle of the data section, leaving some data and the CRC for that data at the front of the next block. So we have a fragmented file, but we have a method of determining in which order the pieces are supposed to fit back together by combining two blocks and then checking whether the CRC for the PNG chunk is correct. So now we have an idea of the problem and a solution, but first we'll need to extract each block of the PNG file.
Step 5: Programmatically Extracting FAT32 Blocks
$ grep -abc IDAT diskomatic.dat
4976
4976
FILE = "diskomatic.dat" def get_parts(): fh = open(FILE, 'rb') parts = [] # we know the location of the first part from `binwalk` fh.seek(0x010F6C00) p_idx = 0 part = "" try: while True: block = fh.read(512) if len(block) == 0: break if block[0:4] != "\x00\x00\x00\x00": fh2 = open("parts/p"+str(p_idx)+".png.part", "wb") fh2.write(block) fh2.close() p_idx += 1 offset = fh.tell() if offset % 0x100000 == 0: print "0x%x" % offset except EOFError: fh.close()
This writes all the blocks/parts out, 1 per file, and puts them in a directory called parts. After running this code we have 6238 files.
$ cd parts
$ ls | wc
6238 56144 354468
$ ls | wc
6238 56144 354468
Step 6: Reassemble the Blocks
From that code, I moved on to checking every block against every other block, and constructing the correct ordering of blocks in a dictionary. Then writing that string of blocks out into a single PNG file. The full code, excluding the get_parts() function is:
#!/usr/bin/env python import binascii import struct FILE = "diskomatic.dat" NUM_CHUNKS = 6238 FIRST_PART = 0 def tokenize_chunk(data): length = struct.unpack("!I", data[0:4])[0] chunk = data[0:4+4+length+4] return chunk def png_chunk_verify_crc(chunk): length = struct.unpack("!I", chunk[0:4])[0] ctype = chunk[4:8] data = chunk[8:8+length] crc = struct.unpack("!I", chunk[8+length:8+length+4])[0] # The CRC is a network-byte-order CRC-32 computed over the chunk type and # chunk data, but not the length. crc_c = (binascii.crc32(chunk[4:-4]) & 0xFFFFFFFF) valid = (crc == crc_c) return valid def read_parts(): parts = [] # XXX: magic number: that's how many chunks were in the original file for i in xrange(0, NUM_CHUNKS): fh = open("parts/p"+str(i)+".png.part", "rb") data = bytearray(fh.read()) parts.append(data) fh.close() return parts def compare_parts(parts): parts_dict = {} for i in xrange(1, NUM_CHUNKS): first_two = parts[0] + parts[i] # XXX: magic number, for first one, because there's the PNG header chunk = tokenize_chunk(first_two[0x2E:]) matched = png_chunk_verify_crc(chunk) if matched: print "Chunk 0 and %d matched!" % i parts_dict[0] = i break for i in xrange(1, NUM_CHUNKS): matched = False for j in xrange(1, NUM_CHUNKS): if i == j: continue two_parts = parts[i] + parts[j] # XXX: magic number, most other parts seem to have the same offset offset_to_chunk = 0x2E chunk = tokenize_chunk(two_parts[offset_to_chunk:])
matched = png_chunk_verify_crc(chunk) if matched: if i % 100 == 0: print "Chunk %d and %d matched!" % (i, j) parts_dict[i] = j break if not matched: print "Chunk %d had no match!" % (i) return parts_dict def write_parts(parts, parts_dict): fh = open("final.png", "wb") next_key = 0 for i in parts_dict.iterkeys(): fh.write(parts[next_key]) try: next_key = parts_dict[next_key] except KeyError: print "Hit KeyError" fh.close() def main(): parts = read_parts() parts_dict = compare_parts(parts) write_parts(parts, parts_dict) if __name__ == "__main__": main()
The nested for loops do some unnecessary work, but the whole thing runs in under 5 minutes on my laptop.
could you share file diskomatic-98757b0043b8aea77e9014cff8ead7b1 :)
ReplyDeleteThanks you
Your blog is very useful, I am truly to this blog which is specially design about the def con 24.
ReplyDeleteGreat job.
How to Overthrow a Government
Your blog is very attractive and the presenting style is awesome. I liked your blog.
ReplyDeleteDEFCON Conference