Points: 160 Solves: 68 Category: Reverse Engineering Description:
It’s-a me, Mario! We’re there. All I can see are turtle tracks. Whaddaya say we give Bowser the old Brooklyn one-two? hello-joe
Write-up
$ file hellojoe-d4fc787734bc9d0fb867aa7ade904f07
hellojoe-d4fc787734bc9d0fb867aa7ade904f07: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped
If we start the binary, we see usage information. It takes the FLAG / password as command line argument.
➜ 9447 ./hellojoe-d4fc787734bc9d0fb867aa7ade904f07
Usage: ./hellojoe-d4fc787734bc9d0fb867aa7ade904f07 FLAG
➜ 9447
When we look at the disassembly we see memcpy() being called several times in main() to copy chunks of data from the Data Section to the Stack Section.
Little after that we see a random chunk being called and executed from the stack.
What happens here is data from /dev/urandom is used as seed for rand(), followed by with modulo 6 to determine which of the previously copied chunks of data is going to be executed. Each of those chunks are individual flag checking functions. Because it will take too long to step through all 6 of the functions I will just give you an example snippet of how they check for right flag. Each function checks a specific index character from our input against 3 valid characters like so:
00000000006014e6 movzx rax, byte [ds:rdi] # Pick char from input
00000000006014ea inc rdi
00000000006014ed cmp al, 0x30 # '0' is valid char
00000000006014ef je 0x601508
00000000006014f5 cmp al, 0x37 # '7' is valid char
00000000006014f7 je 0x601508
00000000006014fd cmp al, 0x38 # '8' is valid char
00000000006014ff je 0x601508
0000000000601505 xor eax, eax # If incorrect return 0 (fail)
0000000000601507 ret
As you can see 0, 7 and 8 are all valid characters at that index. None of the functions check all of the characters at once, some indexes are going to be skipped but all indexes are going to be checked at least twice against different values but a single common character. Here is a table of which function checks which index with what characters.
char | function 2 | function 3 | function 4 | function 5 | function 6 |
-----|------------|------------|------------|------------|------------|
1 | ? | ? | ? | ? | ? |
2 | ? | ? | ? | ? | ? |
3 | ? | ? | ? | ? | ? |
4 | ? | ? | ? | ? | ? |
5 | ? | ? | ? | ? | ? |
6 | ? | 29f | 79b | ? | 189 |
7 | ? | 14c | 47f | ? | 04f |
8 | 5de | 16e | ? | 7ef | ? |
9 | aef | ? | ? | 02a | 9ae |
10 | ? | 59a | 5ab | 57b | ? |
11 | ? | 19e | ade | ? | 5ae |
12 | 023 | ? | ? | 038 | 36e |
13 | 27d | 278 | ? | 02e | ? |
14 | ? | 78f | 57f | ? | 8cf |
15 | ? | ? | 23b | 2bf | 269 |
16 | bdf | 57b | bdf | ? | ? |
17 | ? | ? | 358 | 35d | 5ef |
18 | ? | abe | abf | 7be | ? |
19 | ? | 35d | ? | 35b | 349 |
20 | ? | 7af | 67f | 7de | ? |
21 | 25d | ? | bdf | ? | 6cd |
22 | ? | 49b | 9ae | ? | 39e |
23 | 24a | ? | 49a | ? | 14d |
24 | 17d | 67f | ? | 79a | ? |
25 | ? | 2ce | 9de | ? | 25e |
26 | 3ef | ? | 16e | 47e | ? |
27 | 18a | 7ae | 13a | ? | ? |
28 | ? | 39b | 34a | ? | 3bf |
29 | 59a | 1ad | ? | ? | 2af |
30 | ? | 37a | 136 | 3ad | ? |
31 | 078 | ? | ? | 28c | 078 |
32 | 49a | 29d | ? | ? | 79b |
33 | 39f | 037 | ? | ? | 023 |
34 | 236 | ? | 26e | ? | 24b |
35 | ? | 7ad | 34a | ? | ade |
36 | 8ce | 78e | ? | 2ce | ? |
37 | 145 | ? | 179 | ? | 1be |
38 | ? | ? | ? | ? | ? |
39 | nul | nul | nul | nul | nul |
Question marks are characters that are being checked at that index by each specific function. Function 1 checks if the input starts with ‘9447{‘, consists only of chars ‘0123456789abcdef’ and ends with ‘}’. Knowing each valid pair characters at each index we can build the only valid flag ‘9447{94ea5e32f2b5b37d947eea3a38932ae1}’.
To hinder our analysis there were anti-stepping instruction after each character check/skip using the following method:
00000000006015e4 rdtsc
00000000006015e6 test eax, 0xfffff
00000000006015eb jne 0x6015e4
Instruction ‘rdtsc’ returns CPU time-stamp counter, which is being checked if it ends with 0xfffff, if not it jumps back 9 bytes making it infinite loop. As we single-step our chances of hitting 0xfffff are probably less than winning the lotto :). So for the most part I just used static analysis by converting the functions in the Data Segment to executable code.
Overall it was a fun challenge. My thanks to the guys from 9447.