Write-Ups

Reverse Engineering

Friend (400 pts)​

Frodo and his party have reached a big door.

Help them say the magic words for the door to open.
 

Because the door cannot talk, you might try asking

the Door BodyGuard for some assistance.

Also, when you are stuck, try restarting.
 

P.S. Turtles cannot enter the door.

Step 0) Load binary into a disassmbler such as IDA.

Step 1) From WinMain, go into the Window Proc function

windowproc.png

Step 2) From the Window Proc function, we see that when Msg 273 (WM_COMMAND) and wParam is 0 (a button is clicked) is triggered, then the input (obtained from GetWindowTextW) is parsed and sent to another function. Note that the input is obtained as a Windows-Unicode string, and then some sort of custom parsing is done on it. It basically converts the input to a ascii string, except for when a character is Unicode, then it will be copied over.

callFuncPtr.png

The function next calls a function pointer, IDA cannot see what is it. With a debugger, we can tell what is the next function.

Step 3) In the next function, it starts by XOR-decrypting a binary blob with the key that the user passed in. Following that, there is a check that the first characters in the decrypted blob are the characters "BOWSER". Logically, this would let people think that the key is 6 characters long. Now, the description text above the input box says "Speak friend and enter". This suggests that the key is "friend". In fact, the key is the 8 characters string "friend\r\n", obtained from literally entering after typing the "friend". The description did say "and enter", referring to the "enter" key. There is no way to deduce this (presence of additional two characters), thus people will probably get stuck here and give up on this challenge.

replaceFunc.png

The replacement of off_1400482C0 to a new function sub_140001740 is to have the subsequent decode execute a different function.

Step 4) After the "BOWSER" check, another binary blob is decoded. This binary blob contains the subsequent function that is called. That function runs through the first decrypted binary blob, and does some parsing on it. The function has been made difficult to statically analyse by having parts of it call to another page, which is marked as PAGE_NO_ACCESS, that is an invalid page. When control flow reaches that function, it triggers an access violation. A vectored exception handler was earlier installed, which will handle this exception by XOR-ing the page with some key and then marking it as PAGE_EXECUTE. Then execution will go back, and now the code can run. Once it is done, the page is again XOR-ed back, and marked as PAGE_NO_ACCESS again.

This function is really just an interpreter of the the Brainf*** (BF) language, which the challengers might or might not be familiar with. Even if they are not aware of this language, the interpreter implements most of the language, except for I/O. This is good enough to let them know of the existence of this language, which will be seen again later.

The interpreter parses the blob, and writes out a key in some offset in the blob (in fact at offset 37).

Step 5) The GUI program has some slight feedback. When the "BOWSER" check succeeds, the text on the GUI will change. (So both the right "friend\r\n" and the wrong "friend" will trigger this). Other than that, there is no other feedback. This is intentional. The idea is that the challenger has to attach a debugger to it to read the memory. With a debugger, the memory at the blob can be read.

hex.png

The string at offset 37 is "to \x22\x1e and bey0nd".

Alternatively, one can throw the decoded binary blob, viewed as characters, into any online BF interpreter, and get back the output. Note that hex editors normally print unprintable hex bytes with the '.' character, which is a valid character in the BF language. This makes it not possible to directly throw in the hex output. However, one can first replace all unprintable bytes to some printable irrelevant character.

replaceUnprintables.png
replaceUnprintables2.png

If one were to use the wrong key "friend", then the binary blob will decode into something else, which also starts with "BOWSER", hence passing the check at the start. This is still a valid BF input which decodes some other string "stop killing the turtles", this is a red herring.

Step 5) There is no feedback in the GUI program. The trick is to insert this key into the input box and click the button again. However, one cannot just type in this key in, as it contains an unprintable character (\x1e). The user is expected to be familiar with the phrase "To infinity and beyond" spoken by Buzz Lightyear of Toy Story. The unprintable character \x1e is part of the two bytes Unicode character \x221e, which is the infinity symbol in Unicode.

infinitysym.png

Taken from Wikipedia: https://en.wikipedia.org/wiki/Infinity_symbol

Therefore the trick is to insert the infinity symbol into the input box and submit.

toinfinityandbeyond.png

Alternatively, one could put a breakpoint at the call to the function pointer, write some other stuff there and submit. At the breakpoint, change the input to the one with the \x22 and \x1e.

Step 6) After clicking the button, there is again no feedback. From the disassembler, we see that the new function that is executed, decrypts another binary blob.

cryptdecrypt.png

Since there is no feedback, the user has no choice but to use the debugger to look at the binary blob. The initial part of the binary blob has the magic string "PNG". Further along the blob, there are many more occurences of the "PNG" string.

dbpolyglot.png

PNG files (images) start with those same 8 characters.

png_signature.png

This indicates that the binary blob contains a PNG image file. However if one continues looking through the binary blob, which is pretty big, one can see that there are multiple occurences of this PNG signature. What is happening is that the decoded binary blob contains multiple images. Each image is conveniently separated by 8 null bytes, although this is not necessary as a PNG file is always ended with a certain chunk (IEND then a 4 bytes checksum)

One could dump out the entire memory blob and then use a script to split out all the images.

writemem_dump.png
writemem_dump2.png

This dumps out 97 files, of which the first 96 are images, and the last one is some gibberish due to the decryption process.

Looking through the images, one sees that they seem to be little parts of a big image, which seem to be a source code of some kind. In fact, there are these symbols, that seem to be there for the sake of helping to align the images. One could use some form of optical character recognition to convert the images to characters. However, it might be easier to just do it by hand.

After restitching the images back to a single big image, one finds that it is a source code. If one has seen the polyglot entry in wikipedia, one will see that this is almost the exact same code. However, knowing that this is a polyglot code, one must next figure out what is the real language. In fact, it is the "alignment symbols", which is a modified BF language. It still includes the same 7 characters (there is no output symbol), but they have been substituted to different characters. Since both this BF code and the earlier BF code was handwritten, they have exactly the same starting point, which means the user can just pattern match. So if the user can actually figure out that the alignment symbols are modified BF symbols, then he might have a chance to solve this and move on. Either by replacing the modified BF symbols with the original symbols, or using a custom interpreter to interpret the modified symbols, one can take the output, which is yet another key "the quick white hare loses to the lazy tortoise".

Step 6) The key is to be inserted into the input box. Note that the action that the button does is modified each time the previous decryption is done. So all the keys must be keyed in, in order. Otherwise, the entire program must be restarted. Again, there will not be any feedback. It decrypts yet another binary blob. From the memory view, we see that the decrypted blob starts with IDAT.

another_file.png

This magic signature is part of a PNG file, specifically a IDAT chunk which stores the data of an image. The challenge here is to reconstruct the headers that will get back the original file.

One way is to take one of the earlier PNG image files, remove its IDAT chunk and replace it with this one. Care must be taken to copy exactly how many bytes make up the chunk, because the decompression will introduce a few extra junk bytes at the end. The starting part of any PNG chunk is the length of the chunk, the ending part of any PNG chunk is a crc, so they can be used to help align. Finally the dimensions of the picture must be modified correctly, in the IHDR chunk.

One way is to just do a trial and error process. Alternatively, the IDAT chunkhas been compressed with DEFLATE, at least in the earlier PNG images. It can be safe to assume that this is the same case in this IDAT chunk. This chunk can be uncompressed with the zlib package in python.

zlib_decompress.png

The number of bytes that each pixel takes up is 4 (red, green, blue, alpha) and there is an additional byte per row. With some trial and error, one can get (4 * 300 + 1) * 300 = 360300.

After fixing up the width and the height in the IHDR chunk, the picture is viewable by say the standard Windows photo viewer, giving a QR code.

qrcode_flag.png

This is just a normal QR code, which can be scanned or uploaded to an online QR code viewer, giving the flag.

DSO-NUS{b8403d208e673a5ded550004fec27f4d6f629fd31755d894af6752f9b29b51ff}