Last week-end I participated to the qualifications of NdH with HackGyver (scoreboard). We gathered at the hackerspace's local with futex, kiwhacks and pastrep (a new member met at the SecuRT 2015). We finished 32 over more than 200 teams that validated at least one challenge. It is not that bad, knowing that some of our key comrades where not available this Saturday.
I had to do some stuff that I never did before and it was really fun. That is why I wanted to do the write-up of the Clark Kent reverse engineering challenge.
Clark Kent - The challenge
On its web page:
"There's a shadow inside all of us. But that doesn't mean you need to embrace it. You decide who you really are. And I know you'll make the right choice and become the hero you're destined to be." (Clark Kent)
Become that hero you're destined to be. Discover and evolve your reversing powers.
ultra-depierre :: ctf/ndh2k15/clarkkent » file clark clark: ELF 32-bit LSB executable, Intel 80386, invalid version (SYSV), for GNU/Linux 2.6.24, dynamically linked, interpreter \004, corrupted section header size
Looks like they had fun with the ELF header.
ultra-depierre :: ctf/ndh2k15/clarkkent » strings clark # . . . mprotect ptrace # . . . Booh! Don't debug me! Welcome to NDH2k15 No no no! Don't patch me! # . . .
And also that they added a couple of anti-reverse stuffs.
Basic protections
First clark
run ptrace
in order to check if it was currently being debugged. Indeed, ptrace
will fail if
the process is already ptraced.
LOAD:080484DD main proc near ; DATA XREF: start+17o ; [. . .] LOAD:080484E6 mov dword ptr [esp+0Ch], 0 LOAD:080484EE mov dword ptr [esp+8], 1 LOAD:080484F6 mov dword ptr [esp+4], 0 LOAD:080484FE mov dword ptr [esp], 0 ; request LOAD:08048505 call ptrace ; Anti-debug ptrace trick LOAD:0804850A cmp eax, 0FFFFFFFFh ; ptrace returns -1 when process is already ptraced LOAD:0804850D jnz short loc_8048527 LOAD:0804850F mov dword ptr [esp], offset s ; "Booh! Don't debug me!" LOAD:08048516 call puts LOAD:0804851B mov dword ptr [esp], 1 ; status LOAD:08048522 call exit
Then the binary computed a checksum and decrypted a chunk of code with that checksum:
LOAD:08048527 loc_8048527: ; CODE XREF: main+30j LOAD:08048527 mov dword ptr [esp], offset aWelcomeToNdh2k ; "Welcome to NDH2k15" LOAD:0804852E call puts LOAD:08048533 mov edx, offset loc_80485E1 LOAD:08048538 mov eax, offset loc_80484E6 LOAD:0804853D sub edx, eax ; size to checksum LOAD:0804853F mov eax, edx LOAD:08048541 mov [esp+4], eax LOAD:08048545 mov dword ptr [esp], offset loc_80484E6 ; start offset of what to checksum LOAD:0804854C call compute_checksum LOAD:08048551 mov [esp+14h], eax LOAD:08048555 mov eax, [esp+14h] LOAD:08048559 mov [esp+8], eax ; result of the checksum LOAD:0804855D mov dword ptr [esp+4], 186h LOAD:08048565 mov dword ptr [esp], offset enc_chunk LOAD:0804856C call decrypt_chunk
After it computed another checksum and checked if the binary had been patched:
LOAD:08048571 mov [esp+18h], eax LOAD:08048575 mov dword ptr [esp+4], 186h LOAD:0804857D mov eax, [esp+18h] LOAD:08048581 mov [esp], eax LOAD:08048584 call compute_checksum LOAD:08048589 cmp eax, 5780C882h LOAD:0804858E jz short loc_80485A8 LOAD:08048590 mov dword ptr [esp], offset aNoNoNoDonTPatc ; "No no no! Don't patch me!" LOAD:08048597 call puts LOAD:0804859C mov dword ptr [esp], 1 ; status LOAD:080485A3 call exit
Finally, it changed the protection of the allocated buffer that contained the decrypted code and jumped in it:
LOAD:080485A8 loc_80485A8: ; CODE XREF: main+B1j LOAD:080485A8 mov eax, [esp+18h] LOAD:080485AC and eax, 0FFFFF000h LOAD:080485B1 mov dword ptr [esp+8], 5 ; prot LOAD:080485B9 mov dword ptr [esp+4], 186h ; len LOAD:080485C1 mov [esp], eax ; addr LOAD:080485C4 call mprotect LOAD:080485C9 mov [esp+1Ch], eax LOAD:080485CD cmp dword ptr [esp+1Ch], 0 LOAD:080485D2 jns short loc_80485DB LOAD:080485D4 mov eax, 1 LOAD:080485D9 jmp short locret_80485E6 ; Jump to exit. LOAD:080485DB loc_80485DB: ; CODE XREF: main+F5j LOAD:080485DB mov eax, [esp+18h] LOAD:080485DF call eax ; Jump into the decrypted buffer.
Patch like a noob
First thing I wanted to extract was the checksum algorithm to decrypt the chunk of code locally.
int __cdecl compute_checksum(int start_offset, int size) { int i; // [sp+8h] [bp-8h]@1 int checksum; // [sp+Ch] [bp-4h]@1 checksum = 0; for ( i = 0; i < size; ++i ) checksum = 0x1000193 * (*(_BYTE *)(i + start_offset) ^ checksum); return checksum; }
It gave me 0xB4DF7F49
but sadly, I could not make the decrypt function work properly. I changed my mind and decided
to patch the program instead.
So I patched the ptrace
call with mov eax, 1; cmp eax, -1; jnz
. Then I hardcoded the checksum with the one I
extracted mov eax, 0xB4DF7F49
and hardcoded the last checksum as well mov eax, 0x5780C882, cmp eax, 0x5780C882
.
Film the strace
Now I was ready to run the program. Using strace
I was able to see what the binary was trying to do:
; . . . mprotect(0x9f11000, 390, PROT_READ|PROT_EXEC) = 0 getuid() = 1000 write(1, "Need supercow power!!!\nBye!\n\0", 29Need supercow power!!! Bye! ) = 29 _exit(1)
Are you asking to run you as root? No way!. After hearing jvoisin's idea to booby-trap CTF binaries, there is no way I would run a binary as root. Instead I fired-up a 32bit Kali machine.
The problem I had was that I could not read the program output when run as root. It was too fast and the VM kept rebooting. I had the idea to film the VM thanks to VirtualBox's Video Capture feature. It gave me the following output:
I tried to LD_PRELOAD
the binary and intercept the reboot
function with no success until I realized that the
binary was rebooting the VM via a system call.
Now that the CTF is over, I could have LD_PRELOAD
the binary to hook the mprotect
function and dump the code
chunk instead of remote-debugged the binary with IDA... But I did not think about that :P
Remote debugging via IDA
Instead, I chose a less obvious way. Since gdb would fail everytime (change one byte in the headers and gdb is completely lost...) I did not know what tool to use. Then futex told me about one article he wrote a couple of month ago: Remote debugging with IDA.
I copied IDA/dbgsrv/linux_server
onto my Kali VM:
root@kali32:~# ./linux_server IDA Linux 32-bit remote debug server(ST) v1.17. Hex-Rays (c) 2004-2014 Listening on port #23946...
And configured IDA via Debugger -> Process options
like below:
I put a breakpoint on the mprotect
call and checked the heap:
Luckily, the flag was in clear right here: WhyN0P4tch?
Note: Using rasm2
(or IDA who cares?) on the heap chunk
0x0000004d 5 ba67452301 mov edx, 0x1234567 0x00000052 5 b969191228 mov ecx, 0x28121969 0x00000057 5 bbaddee1fe mov ebx, 0xfee1dead 0x0000005c 5 b858000000 mov eax, 0x58 ; reboot syscall 0x00000061 2 cd80 int 0x80
Bastards!