Wednesday, February 20, 2013

Ghost in the Shellcode 2013 - Imgception Writeup

Back at it with UTDCSG (http://csg.utdallas.edu), we competed in Ghost in the Shellcode 2013. We had a great turnout Friday night with lots of old and new members working together shoulder-to-shoulder in ECSS 4.619!

Still relatively new to CTFs but armed with new techniques, new tools and a sharpened approach, I set off to tackle question 9 - 'Imgception', a forensics problem worth 150 points. Given the name of the problem it's probably safe to assume there are multiple images buried inside of each other.

We are given 'imgception-ce4fae066ffabd57aeb4a4d29faa1de1cf4c988f.png' and simply told 'find the key'. Since the file format is a PNG (a CTF favorite), of course the first thought is steganography. I gave it a quick run through my defacto stego tools with no luck, along with exiftool, foremost, scalpel and some others.

OK, let's take a look at it through pngcheck with the -v (verbose) flag set.



Nothing stands out at first glance, at least as far as tEXt or tEXt-esque chunks are concerned. Let's take a moment to review the PNG specification (http://en.wikipedia.org/wiki/Portable_Network_Graphics). The first thing that caught my were the 'unknown private, ancillary' chunks. According to the specification, PNGs can contain a variety of data 'chunks' that are optional (non-critical) as far as rendering is concerned.

Let's take an inventory of this file's chunk types - we have IHDR, sRGB, pHYs, giTs, ITDAT and IEND. All of these seem pretty typical, but let's compare them to a list of standard PNG chunk types.
  • bKGD gives the default background color. It is intended for use when there is no better choice available, such as in standalone image viewers (but not web browsers; see below for more details)
  • cHRM gives the chromaticity coordinates of the display primaries and white point
  • gAMA specifies gamma
  • hIST can store the histogram, or total amount of each color in the image
  • iCCP is an ICC color profile
  • iTXt contains UTF-8 text, compressed or not, with an optional language tag. iTXt chunk with the keyword
  • pHYs holds the intended pixel size and/or aspect ratio of the image
  • sBIT (significant bits) indicates the color-accuracy of the source data
  • sPLT suggests a palette to use if the full range of colors is unavailable
  • sRGB indicates that the standard sRGB color space is used
  • sTER stereo-image indicator chunk for stereoscopic images
  • tEXt can store text that can be represented in ISO/IEC 8859-1, with one name=value pair for each chunk
  • tIME stores the time that the image was last changed
  • tRNS contains transparency information. For indexed images, it stores alpha channel values for one or more palette entries. For truecolor and grayscale images, it stores a single pixel value that is to be regarded as fully transparent
  • zTXt contains compressed text with the same limits as tEXt
The one that stands out as not being in the list is giTs, and while it seems obvious after making the connection, giTs = Ghost in the Shellcode. OK, that's encouraging, let's look closer. Opening the file up in WinHex, we can search for the giTs chunk header.


If we examine the other giTs headers closely, we notice something interesting - the standard 4 byte chunk header (giTs) is immediately succeeded by 3 ASCII characters and then some spaces - a clue?



Let's take a closer look at what all of those letters are by pulling out the 8 byte chunk header and stripping out 'giTs' as it's a constant. Note: I originally performed this by copying and pasting, but since I wrote a similar script to pull out of data later in this problem, I re-purposed it a bit for use in this problem - feel free to use it for similar tasks, unless you're PPP in which case you probably have something way better kicking around.



Checking the program's output, we are left with the following text:

ger low ndT The edA heD uns heG ed  lin lac Man ese ssT kFl rtA Fol cro InB

No way that's a mistake - let's rearrange and see what we come up with:

The Man In Black Fled Across The Desert And The Gunslinger Followed

Apparently this is a quote from a famous Stephen King novel as pointed out to us by one of the CSG folks (and it explains the original PNG we were given). OK, so now what? During our initial recon, one of our team members ran Foremost against the original image and we were greeted with something that looked like this:



We can make out what looks to be a cat but the data is clearly malformed. It looks like there are sections that are out of order (particularly interesting considering BMPs are more or less just a raw stream of pixel data) and the color channels are wacked. We know that each of the giTs chunks are directly related to a section of the resulting rearranged quote - what if we rearranged each of the data chunks (32356 bytes) to match the order that they appear in the quote?

I didn't script this part and I'm not going to (though it would be good practice) - it was accomplished by hand pretty easily with the help of TweakPNG (http://entropymine.com/jason/tweakpng/) which allowed us to quickly export the chunks, stripping the footer bytes in the process (header bytes remain). Assign each of the chunks a name based on their position in the sentence (eg: chunk1, chunk2) and then just:

#: cat chunk1 > reassembled.bin
#: cat chunk2 > reassembled.bin
...

and repeat until they're all lined up and arranged properly. Strip the 8 header bytes out (simple find/replace RegEx for 'giTs....') and we are left with a proper looking 24bit Windows BMP file!


Now what? This part stumped us - we had lots of eyes on it but unfortunately ran out of time before we discovered the solution. Come to find out, the BMP file format (http://en.wikipedia.org/wiki/BMP_file_format#Pixel_array_.28bitmap_data.29) allows for padding at the end of each row:
Padding bytes (not necessarily 0) must be appended to the end of the rows in order to bring up the length of the rows to a multiple of four bytes. When the pixel array is loaded into memory, each row must begin at a memory address that is a multiple of 4. This address/offset restriction is mandatory only for Pixel Arrays loaded in memory. For file storage purposes, only the size of each row must be a multiple of 4 bytes while the file offset can be arbitrary.
A 24-bit bitmap with Width=1, would have 3 bytes of data per row (blue, green, red) and 1 byte of padding, while Width=2 would have 2 bytes of padding, Width=3 would have 3 bytes of padding, and Width=4 would not have any padding at all.
So let's take a look at our resultant bitmap (cat_rearranged.bmp) in WinHex:


According to the file format spec, we can examine byte 0xA (highlighted in yellow) to determine where the actual image data starts (highlighted in blue). We know the image width is 319px and the bit-depth is 24, so we know that each row is 319*(24/8) or 957 bytes in length. So starting at offset 0x36 and skipping ahead 957 bytes, we have skipped the row pixel data and arrive at the padding.


Ahhh, good old 0xFFD8! Forensics professionals and general purpose nerds will recognize this pattern instantly - JPEG file header! You only live once, that's the motto nigga ÿØÿà....

Examining the padding from the next few rows, it looks like that a JPEG image has been stuffed into the 3 padding bytes of each row - let's write a Python program to traverse the file and extract it.



Run it and we get get the following output - we have our key and the answer to Imgception for 150 points.


No comments:

Post a Comment