|=-----------------------------------------------------------------------=| |=------------------=[ GitS 2013 - Imgception Writeup ]=-----------------=| |=----------------------------=[ wwong@RPISEC ]=-------------------------=| |=-----------------------------------------------------------------------=| -- [ Intro / Part 1 For this challenge, we were given a PNG file with some data hidden inside. While poking around the file in a hex editor, we found a few PNG chunks that had types which were outside of the normal PNG spec. We found 19 chunks with "giTs" in the type field. If we examine the data inside each of these chunks, we see a common pattern: a 4-byte string followed by binary data. We were able to extract the data from these chunks and write them to some file, named with respect to their 4-byte string headers. While we used the hex editor to manually splice each chunk out during the competition, a script to do so has been attached below as part1.rb Once all the chunks were in their own files, we needed to recombine them into a single bitmap again. Looking at the file names, we see there are a few capitalized letters, which would seem to signify the beginnings of words. Doing a reverse-image search on the original image gives us results related to a book, The Dark Tower: The Gunslinger by Stephen King. Using a quote from that book to order our chunks, we concatenate the 19 chunks into a single bitmap image. The process to extract the hidden file went something like this: % ruby part1.rb % cd extracted/ % cat The Man InB lac kFl edA cro ssT heD ese rtA ndT heG uns lin ger Fol low ed > part1.bmp Now that we have a result from part 1, we can move onto part 2! -- [ Part 2 Once we saw the bitmap picture, we thought that we had completed the challenge and made a few attempts to submit the book quote to the scoreboard. No dice. Guess we need to go deeper. After some more poking around, we found that BMP's pixel array has a padding value to it to keep things aligned to 4-byte boundaries. We were expecting the padding values to be zeroes, but there was a good number of places where that wasn't the case. Even more revealing was the value of the first padding byte: \xff\xd8. Looks like we might have a JPEG on our hands! Luckily, the padding bytes are all at very consistent offsets from each other. Using a simple script like part2.rb, we can pull a JPEG out of the padding. No need to worry about pulling too much data out; any extra data after a JPEG footer is ignored by the viewer. % part2.rb % feh flag.jpg A winner is you. -- [ Conclusion This was a pretty sweet challenge. We made a lot of silly mistakes along the way to the solution, but it was nice to see a forensics challenge that did not rely on heavy guessing as part of the process. My thanks go out to the GitS organizers; you guys put on a hell of a CTF. Can't wait to see what you have in store for next year! -- [ part1.rb #!/usr/bin/ruby require 'bindata' class PngHeader < BinData::Record endian :big string :header, :read_length => 8 end class PngChunk < BinData::Record endian :big uint32 :len string :type, :read_length =>4 string :data, :read_length => :len uint32 :checksum end img_fn = 'imgception-ce4fae066ffabd57aeb4a4d29faa1de1cf4c988f.png' io = File.open(img_fn) chunks = {} #Skip past the header PngHeader.read(io) begin chunk = PngChunk.read(io) if chunk.type == "giTs" chunks[ chunk.data[0..2] ] = chunk.data[4..-1] end end until chunk.type == "IEND" puts "Dumping chunks:" Dir.mkdir('extracted') chunks.each do |k,v| filename = "extracted/" + k.rstrip f = open(filename, 'wb') f.write(v) f.close puts " * Created #{filename}" end puts "Done\n" -- [ part2.rb #!/usr/bin/ruby bmp = open('part1.bmp','rb').read offset = 0x3f3 out = "" while bmp[offset] out += bmp[offset..offset+2] offset += 0x3c0 end open('flag.jpg','w').write(out)