Reverse engineering the HITB binary 100 CTF challenge
Disclaimer for legal people: “I” and “me” are nicknames in this blog post. They refer to a person who may or may not be me, myself, or I.
During the HITB conference (Hack In The Box) in Amsterdam last week, a Capture The Flag challenge was organised. Six categories were available of which you could solve challenges: Web, Binary, Network, Crypto, Misc and Special. Together with Kinine and Flunk, team hDs secured a 7th place in the CTF ranking.
Binary analysis is not exactly the field I feel most comfortable at right now, but it has certainly captured my interest lately. The binary for the first challenge we were confronted with (bin100), simply outputted a lyric line once every second. After a certain amount of time (too long), the binary will eventually display the solution.
Download the binary: bin100.elf
Analysing the binary
Here’s an overview of a strings run on the binary. It may not seem like many lyrics, but they’re just repeated over and over again. The line “OK YOU WIN. HERE’S YOUR FLAG: “, is not actually followed by the solution we’re looking for (would be a bit too easy, wouldn’t it). So how do we get the actual key?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
/lib64/ld-linux-x86-64.so.2 __gmon_start__ libc.so.6 __printf_chk srand time __stack_chk_fail putchar sleep __ctype_toupper_loc __ctype_tolower_loc __libc_start_main GLIBC_2.3.4 GLIBC_2.4 GLIBC_2.2.5 GLIBC_2.3 AVAUATUSH |$4H D<Yi D<YI []A\A]A^A_ l$ L t$(L |$0H %80s KEY: %02x OK YOU WIN. HERE'S YOUR FLAG: ,7"rmn~uJ im the defacto leader of a movement screaming "hack the planet" back in 99 hacktivism in its prime globalHell had the .mil rooted alphabet soup and their troops in the suits kid kicking down doors and seizing my equipment blocking all my shipments sitting on their hitlist 0day radical emphatic beat addict and that stab hit i envelope the game call me rabbit hop to hop i run the internet equivocally bitch i be hit em with the bytestyle symmetry digi g digital gangster repping till im dead steady grep apache logs when im looking for the feds fast forward now the internet anonymous and captains of the lulzboat raise the mast prominent dominant hacks - antisec on that new new dropping tables in mysql like it was some poo poo pound antisec - pounding through your speakers pound antisec - pound it to the bleachers pound antisec - if youre sitting below deck in the lulzboat salute bitch and show some respect lulzsec bitch they fold ya hacked sony got that md5 we rocked ya macaroni cook coke crack then boil like a noodle while hbgary stay toast like a streudel rootshell on ya bootstrap - now whos that? botnet mjoin and drop your whole c class see class? it is evident we flossing ion cannon in the proc list ddossing lean back bitch we be sending an injection magic quotes off JOIN TABLE intersection put it up on pastebin it wont get erased then 20 million hits to your dome like some cavemen ask cnn - you want a interview? send a PRIVMSG to the nickname sabu on irc - man we convening this some 99 throwback shit that im screaming ;*3$" |
Time is of the essence, so let’s get going (literally, you’ll see). First, we ran the binary through IDA Pro to see which function names are in there. Here you see an overview of .symtab and some externally imported functions from GLIBC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# nm bin100.elf | egrep " T " 0000000000400a34 T _fini 0000000000400600 T _init 0000000000400a30 T __libc_csu_fini 00000000004009a0 T __libc_csu_init 00000000004006c0 T main 0000000000400890 T _start # objdump -T bin100.elf | awk '{print $1, $NF}' bin100.elf: elf64-x86-64 DYNAMIC TABLE: 0000000000000000 __ctype_toupper_loc 0000000000000000 putchar 0000000000000000 __stack_chk_fail 0000000000000000 __libc_start_main 0000000000000000 srand 0000000000000000 __gmon_start__ 0000000000000000 time 0000000000000000 __printf_chk 0000000000000000 sleep 0000000000000000 rand 0000000000000000 __ctype_tolower_loc |
Not much interesting. It seems like the whole program’s functionality has been compiled into the single main() function. Some functions from the standard library are being called (time, srand, putchar, sleep, rand). The main function consists of a gazillion branches.
GDB jumping
Here you see an excerpt of two locations that were of specific interest: 0x4007E8 and 0x400803.
The first (0x4007E8) seems to display a key, while the second (0x400803) seems to output our flag. Let’s try jumping to those addresses using GDB:
It was a long shot, but the outputted flag is still garbage when we jump to 0x4007E8. Jumping to 0x400803 immediately segfaults. Not what we want.
Overriding time() and sleep()
We then proceeded by attaching a debugger and follow the program flow dynamically. You can do as such using gdbserver and remotely attaching to the process with IDA’s Remote GDB Debugger tool. Doing that, revealed that the lyric lines were being looped (no surprise there). At the end of each iteration the line is printf’ed, followed by a call to the sleep() function. The special characters you see in the screenshot are the music symbols.
At first thought you would want to NOP the sleep() function, or replace the MOV edi, 1 by MOV edi, 0 to disable the sleep function. This is not possible because the program keeps track of the elapsed time, and the needed key to output the flag relies on this time (correct me if I’m wrong). Since time is limited and my knowledge of assembly isn’t all that great, another approach was taken rather than trying to analyse the file any further.
What if you could fake both the sleep calls and elapsed time, tricking the program into thinking it has actually slept for the required number of seconds? You can do that by overriding GLIBC’s sleep() and time() functions very easily: create a C file that contains the functions you want to override, and make them have the same signature. Just increment the time with one second on every sleep() call, without invoking the underlying sleep() function. Then compile it into a shared object and preload it using the LD_PRELOAD trick. When you’d run the binary, the sleep function will be NOP’ed, while it still increases the elapsed time.
In the above screenshot, you can see the C file being compiled into a shared object and then preloaded. It does all the necessary iterations, followed by the printout of our flag, p4ul_1z_d34d_1z_wh4t_th3_r3c0rd_s4ys!
NOP’ing the printf statements
For the attentive readers: you don’t see any lyric lines because we NOP’ed the aforementioned foregoing printf calls (printing all the lines takes a lot of time).
This can be done in IDA (although definitely not its best developed function), by first undefining the instruction call ___printf_chk in 0x7B7 (0x4007B7 if you like). You can then use the patch function (Edit > Patch > Change byte), and replace the call with 0x90, which is a NOP. Newer IDA versions don’t display the Patch submenu at this moment, but you can bring it back by editing idagui.cfg and setting DISPLAY_PATCH_SUBMENU = YES. Then produce a DIF file (File > Produce file > DIF), and patch your binary with it. It doesn’t work with the Unix patch utility, but you can use this gist.
That seems rather difficult for applying a simple patch. Surely there are better solutions (comments are welcome). You could just as well use a hex editor. P.S. A nice tool that can also help you getting the correct assembly for your instruction, is radare2.
Download:
original bin100.elf binary, printf patch file, patched bin100.elf binary, time.c preload