Quals NdH 2015 Clark Kent - re150

Published on Monday, 06 April 2015 in CTF, Security, Reverse Engineering ; tagged with quals, nuit du hack, ctf, write-up, re, 100, challenge, security, hackgyver ; text version

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.

Clark Kent Reboot

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
# . . .
# . . .
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!!!
) = 29

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:

Clark Kent Reboot

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:

IDA Remote Options

I put a breakpoint on the mprotect call and checked the heap:

Clark Kent Flag

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


contactdepier.re License WTFPL2