Khallenge 2007 is now over, I can write something about the levels.
A debugger provides the fastest solution for the level because you can sniff the serial in few seconds. That’s why the first solution took only 1 minute to arrive. I used a disassembler approach for the level.
It’s a console application, printf is used to print the string on the console while scanf is used to read the serial:
69001056 push offset aAssembly2007Re ; "Assembly 2007 Reverse-Engineering"...
6900105B call esi ; printf
6900105D push offset Fake_Serial
69001062 push offset aS ; "%s"
69001067 call ds:scanf ; Read the serial, the fake one
Now, the core of the level:
6900106D movsx ecx, byte ptr aAssembly2007Re+22h
69001074 movsx edx, byte ptr aAssembly2007Re+16h
6900107B movsx eax, byte ptr aAssembly2007Re+0Eh
69001082 mov edi, ds:sprintf
69001088 push ecx
69001089 movsx ecx, byte ptr aAssembly2007Re+0Ch
69001090 push edx
69001091 movsx edx, byte ptr aAssembly2007Re+0Bh
69001098 push eax
69001099 movsx eax, byte ptr aAssembly2007Re+4
690010A0 push ecx
690010A1 movsx ecx, byte ptr aAssembly2007Re+1
690010A8 push edx
690010A9 movsx edx, byte ptr aAssembly2007Re ; "Assembly 2007 Reverse-Engineering Chall"...
690010B0 push eax
690010B1 push ecx
690010B2 push edx
690010B3 push offset aCCCCCCCC ; "%c%c%c%c%c%c%c%c"
690010B8 push offset Right_Serial ; char *
690010BD call edi ; sprintf : build the right serial
690010BF push offset Right_Serial ; char *
690010C4 push offset Fake_Serial ; char *
690010C9 call ds:_stricmp ; Compare between the right and the fake serial
690010CF add esp, 3Ch
690010D2 test eax, eax
690010D4 jnz short Error_Message ; Jump to error if the serial is wrong
It’s all in the stricmp function. The function is used to compare two strings, the fake and the right serial. stricmp returns zero if the strings are equal. If the serials are not equal the conditional jump at 690010D4 will bring you directly to the error message, otherwise you’ll see the congratulations box. The serial is not clearly visible, it’s build using sprintf function. Just some lines above the sprintf call there are some push instructions. These are the bytes used to create the right serial. As you can see they are taken directly from an existing string: “‘Assembly 2007 Reverse-Engineering Challenge – Level…”. You can identify the right characters looking at the offsets: “
movsx eax, byte ptr
aAssembly2007Re+4" represents the 5° character of the above string, ‘m’. For a better visualization you can undefine the string “Assembly 2007…” obtaining the right characters without using the offets. Since of the challenge uses stricmp you don’t have to use capital letters, ‘asm07rec’ will do the job.
I’m guilty? Don’t know but I feel a little bit guilty about this level.
This time the challenge consists of a console application which needs a correct parameter in order to be solved. When you provide the right parameter the application shows the right mail address, which is unique.
The image represents the block diagram of the checking routine. The challenge needs something from the command line, the block “Scan command line string” scans the entire string until the end beause it needs a pointer to the end of the command line string. After that there are 3 checks, if you complete check #1, #2 and #3_A you’ll see the congratulations box showing the right mail address. If you don’t provide a valid parameter you’ll only get an error message. A classical challenge with two ways, the right and the wrong way.
This is not totally true because there’s another way for reaching the right box, it’s via “Hmmm” block. If you pass check #1, #3_B but don’t pass check #2 you arrive to this piece of code:
004012EF push esp
004012F0 mov [esp+24h+var_24], eax
004012F3 mov eax, offset loc_401245
004012F8 xchg eax, [esp+24h+var_24]
401245 is the starting address of the block named “Congratulations message”, seems like there’s another way to solve the level. Yes and no. If you reach this piece of code you’ll see the congratulations box, but unfortunatly the mail address is totally wrong. Guided by the enthusiasm I didn’t take care of the checking process, I had a congratulations box with a mail address in front of me… do I need something else? Yes, a valid string to pass to the program, nothing else.
The end of the story: I passed the level providing two parameters instead of one and using a wrong mail address. I think that the guys who did check my solution approve it just because the message box appears. I’m terribly sorry about that, I feel stupid. They didn’t ask me to re-try the challenge again because when the author knew it I had just complete challenge#3…Thanks to Kamil and the guys at F-Secure for approving my non-solution, I could have given them the valid solution before the end of the challenge as well :p
Try to find out two valid parameters and look with your eyes btw, it doesn’t have much sense really. So, the challenge is not bugged for sure, but I would like to know if it’s an intentional trick or what?
Now, back to the original problem: how to solve this level playing the right way? As I said some lines above I have to pass 3 checks.
004011C2 mov al, [edx-5] ; edx points to the end of the command line string
004011C5 xchg eax, [esp+24h+var_24] ; Store the value in the stack
004011D1 xor eax, eax
004011D3 mov al, 20h
004011D5 xchg eax, [esp+28h+var_28] ; Store 0x20 in the stack
004011D8 xchg eax, [esp+28h+var_24]
004011DC cmp eax, [esp+28h+var_28] ; compare between a byte in the command line and 0x20
004011DF pop eax
004011E0 pop eax
004011E1 jnz loc_4012FC ; They have to be the same
The filename doesn’t have a space in it so I have to pass something from the command line. At the moment I can say that I have 5 bytes after filename, the first one is the ‘space’.
004011FE mov al, [edx-6] ; Pretty similar to 4011C2
00401201 xchg eax, [esp+24h+var_24] ; Store the value in the stack
0040120D xor eax, eax
0040120F mov al, 22h
00401211 xchg eax, [esp+28h+var_28] ; Store 0x22 in the stack
00401214 xchg eax, [esp+28h+var_24]
00401218 cmp eax, [esp+28h+var_28] ; compare between a byte in the command line and 0x22
0040121B pop eax
0040121C pop eax
0040121D jnz loc_4012D1 ; They have to be the same
It’s more or less like check#1. In this case it checks the presence of ‘”‘. Do I have to insert this character after the filename? No. If you look at the value returned by GetCommandLine you see something like: “C:\Rev\tmp\FSC_Level2_unp.exe”. The ‘”‘ at the begining and at the end are not added by me.
004011E9 mov eax, [edx-4] ; v1 = dword pointed by [edx-4]
004011EC xor eax, 5528566Dh ; v1 ^ 0x5528566D
004011F1 xor ah, bh ; Antidebug trick in action!
00401227 mov eax, [edx-0Ah] ; v2: dword pointed by [edx-0Ah]
0040123A cmp eax, [esp+28h+var_28] ; compare between v1 and v2
0040123D pop eax
0040123E pop eax
0040123F jnz loc_4012FC ; They have to be the same
2 dwords are taken from the command line string. They are used in this final check. At this point you have two options:
1. you can use two params (like I did..)
2. you can use one param only
The right solution (the one to the right mail message) is represented by the second option.
In the snippet above there is a little antidebug trick in action. The trick is not really visible but the result of the trick resides in the ‘xor ah, bh’ instruction. The antidebug trick is stored inside a TLS:
004070AC mov eax, large fs:18h ; TEB
004070B2 mov eax, [eax+30h] ; PEB
004070B5 movzx eax, word ptr [eax+2] ; PEB.BeingDebugged
004070B9 cmp eax, 0
004070BC setz al
004070BF imul eax, 8
004070C2 sub byte ptr ds:loc_4063BC+1, al
At 4070C2, AL is 0x00 if the process is being debugged while AL is 0x08 if not. The value is used to patch a byte at 4063BD, the jump instruction at the end of the upx layer. There are two possible entry points (401352 and 40135A), depending on the use of a debugger or not:
00401352 xor ebx, ebx ; First entry point: EBX = 0
00401354 push 40135A ; Go to the other entry point
0040135A push eax ; Second entry point
The only difference is the xor instruction, the only one capable of changing the value of EBX. Starting from here EBX is never changed. Now you should understand why ‘xor ah, bh’ was putted inside the code.
Now that I know this information I can calculate the right value.
v2 is obtained from the bytes at the end of the filename (“.exe”, 0x6578652E), I have:
v2 == v1 ^ 0x5528566D
v1 = 0x30503343
In the end, C3P0 is the parameter. To solve the challenge you have to run the challenge in this way: FSC_Level2.exe C3P0
Well, at this point you should understand why I sent a mail to the team. I was sure to have a valid address just because I didn’t study the whole protection routine. Yes, I’m guilty!
The last level, the hardest one. Another console application, to complete the level you have to find the right password.
The protection is pretty easy, but the author tried to make our reversing session a little bit hard. This challenge reminds me of a crackme I wrote some years ago. The name/serial check was really simple, but I used an implementation of a Turing machine for checking the name/serial combination. It increases the level of difficulty a little.
Using this kind of tricks an easy algorithm becomes an evil algorithm for sure.
Well, everything starts at 40E95B. Don’t think to step the code instruction by instruction! The routine is a sequence of blocks, in general each block has from 2 to 4 instructions. Some examples:
mov eax, [ebx+8]
add [ebx+78h], eax
mov eax, [ebx+78h]
mov eax, [eax]
mov [ebx+68h], eax
mov dword ptr [ebx+78h], 0
cmp dword ptr [ebx+64h], 0
jz short loc_40FABD
What to do in this case? There are two valid ways: trying to use some clever breakpoints or approaching the target with a sort of static analysis. I used the first way, breakpoints.
I tried to step some instructions at first, just to see how the algo works. There are some memory dwords used inside the protection routine, they are used to store/change values of the protection routine. Everything pass from these dwords, but only one seems to be the most interesting: the one pointed by [ebx+64]. Why? Look at the cmp instructions inside the routine, you’ll see [ebx+64] only. Searching for [ebx+64] inside the routine I got a lot of references, here are the instruction’s type (each one is called many times…):
1. add [ebx+64h], al
2. add [ebx+64h], eax
3. and byte ptr [ebx+64h], 1
4. and dword ptr [ebx+64h], 1
5. cmp [ebx+64h], cl
6. cmp [ebx+64h], ecx
7. cmp dword ptr [ebx+64h], 0
8. mov [ebx+64h], al
9. mov [ebx+64h], eax
10. mov al, [ebx+64h]
11. mov dword ptr [ebx+64h], 0
12. mov dword ptr [ebx+64h], 0Ah
13. mov dword ptr [ebx+64h], 0BAh
14. mov dword ptr [ebx+64h], 0Bh
15. mov dword ptr [ebx+64h], 0CEh
16. mov dword ptr [ebx+64h], 0Ch
17. mov dword ptr [ebx+64h], 0E0AC5C4Eh
18. mov dword ptr [ebx+64h], 0E0h
19. mov dword ptr [ebx+64h], 0FFFFFDF4h
20. mov dword ptr [ebx+64h], 0FFFFFFFCh
21. mov dword ptr [ebx+64h], 0FFFFFFFFh
22. mov dword ptr [ebx+64h], 0FFh
23. mov dword ptr [ebx+64h], 1
24. mov dword ptr [ebx+64h], 1000000h
25. mov dword ptr [ebx+64h], 10000h
26. mov dword ptr [ebx+64h], 100h
27. mov dword ptr [ebx+64h], 100h
28. mov dword ptr [ebx+64h], 2
29. mov dword ptr [ebx+64h], 208h
30. mov dword ptr [ebx+64h], 3
31. mov dword ptr [ebx+64h], 32303038h
32. mov dword ptr [ebx+64h], 33h
33. mov dword ptr [ebx+64h], 3766h
34. mov dword ptr [ebx+64h], 4
35. mov dword ptr [ebx+64h], 48h
36. mov dword ptr [ebx+64h], 5
37. mov dword ptr [ebx+64h], 53h
38. mov dword ptr [ebx+64h], 6
39. mov dword ptr [ebx+64h], 61h
40. mov dword ptr [ebx+64h], 6Ah
41. mov dword ptr [ebx+64h], 7
42. mov dword ptr [ebx+64h], 76h
43. mov dword ptr [ebx+64h], 8
44. mov dword ptr [ebx+64h], 9
45. mov eax, [ebx+64h]
46. xor [ebx+64h], al
47. xor [ebx+64h], eax
48. xor byte ptr [ebx+64h], 1
49. xor dword ptr [ebx+64h], 1
At first I wanted to select few of them, just to set some strategic breakpoint. It’s impossible to select the most interesting and I can’t put a breakpoint on all of them.
One step at a time, let’s start breakpointing on the serial. Ollydbg stops at 412848. Take a look at some instructions that are part of the code flow starting from 412848:
00412848 mov al, [eax] ; serial
0041284A mov [ebx+64h], al ; store serial
0040F4D0 mov al, [ebx+64h] ; retrieve serial
0040FD55 mov dword ptr [ebx+64h], 0 ; clear the dword for future use
00414783 mov eax, [ebx+64h] ; dword is 0 due to execution of 40FD55
00413E43 cmp [ebx+64h], cl ; [ebx+64h] = serial, cl = 0x00
00413E49 jnz short loc_413E4C ; is there a serial ?
00413E4B inc eax ; no other byte in the serial
00413E4C mov [ebx+64h], al
00413E52 jmp loc_40ECD2
413E4B is a good place for a breakpoint. Let’s try. Yes, I’m on the right way. The algo scans the entered serial, in general this is done when you want to check the length of the serial. I am almost sure there will be a compare instruction sooner or later. I added some more breakpoints on instruction’s type #5 and #6, but not #7 because I suppose that the compare should be between the length of the serial and a fixed values. Let’s try:
00413D74 cmp [ebx+64h], ecx ; compare between the serial length and 0x10
00413D7A jnz short loc_413D7D
Seems like the serial should be 0x10 bytes. I suggest you to use a serial like “0123456789abcdef”, it helps me when I have to know the offset of a certain byte inside the buffer.
At this point I could try to add some more breakpoints but which one exactly? Don’t know. Let’s try running the program with the same breakpoints setted. Olly breaks, the program reads serial and stores it inside [ebx+64].
Ok, it’s time to add a new breakpoint, a memory breakpoint on dword at [ebx+64]. Olly will break some times, but only one seems to be interesting:
004144DB mov dword ptr [ebx+64h], 53h
Hmm, the next interesting break occours at 412896:
00412896 cmp [ebx+64h], cl ; cl = 0x53h and [ebx+64h] = serial
This is the first check, the first useful information: serial = 0x53 (‘S’)
Believe it or not this is a fast approach, using Ollydbg it’s only a matter of F9 trying to be concentrate for some minutes. While running/stopping you only have to discard instructions like #2, #7, #8, #9, #10,#45, #48, #49 and some more. You can find out the right serial without restarting the program again and again, just one session is needed. Here is the information gained debugging the program.
serial = 0x61 (‘a’)
serial = 0x6A (‘j’)
serial + serial = 0xBA
serial + serial = 0xCE
serial + serial = 0xE0
3 equations in 3 variables, the result is:
serial = 0x54 (‘T’)
serial = 0x7A (‘z’)
serial= 0x66 (‘f’)
serial = serial + serial + serial = 0x54 + 0x7A + 0x66 = 0x134
serial= 0x34 (‘4’)
serial = 0x48 (‘H’)
serial[A] = 0x33 (‘3’)
serial[B] = 0x76 (‘v’)
serial ^ serial = 0x53
You have some valid combinations here, just choose one:
serial = 0x63 (‘c’)
serial = 0x30 (‘0’)
(serial + serial[F]) ^ 0x32 = 0xE0 –> serial[F] = 0x71 (‘q’)
(serial + serial[D]) ^ 0x30 = 0xAC –> serial[D] = 0x48 (‘H’)
(serial[A] + serial[E]) ^ 0x30 = 0x5C –> serial[E] = 0x39 (‘9’)
serial[C] ^ 0x38 = 0x4E –> serial[C] = 0x76 (‘v’)
To sum up, a possible right serial is: SacjTzf40H3vvH9q
Thanks to Kamil for the nice challenges and congratulations to the solvers.