I like puzzles, they keep your mind up2date! So I’ve just registered over at crackmes.de because it really looks like a lot of fun – and I like fun especially when it comes to reversing things. But isn’t it Off-Topic? No, because analyzing applications and breaking protections in AppSec is something that is closely connected to exploiting flaws: You need to find a way through the application flow, probably evading protection mechanisms and understand what the dev did wrong ;-), which leads – in the end – to an application level compromise of the protection and grants access for all those badboy pirates. These information can help the dev to implement a more secure registration/authentication process. Hardening is something that can be fun, even if you keep in mind that nothing is 100% secure!
Let’s reverse the keygeneration process of an entry-level KeyGenMe. To be solved: Greedy Fly’s KeyGenMe v1.6
Where does the KeyGen – process start ?
Since there is no badboy – message (if you enter a wrong serial, the KeyGenMe exits silently) and the goodboy – message is unknown yet, you need to look for something else. A good starting point is the “Hi!!!” string: Having a look at the IDA strings window reveals:
.rdata:0040227C 0000000B C user32.dll .rdata:004022BA 0000000D C kernel32.dll .rdata:004022DE 0000000D C comctl32.dll .rdata:00402348 0000000A C ole32.dll .rdata:004023C8 0000000A C gdi32.dll .rdata:004023FA 0000000D C oleaut32.dll .data:00403000 0000001C C KeygenMe v1.6 by Greedy Fly .data:0040301C 00000006 C Hi!!! .data:00403050 00000006 C IMAGE
The string can be found @ 0x0040301C which is located in the function “DialogFunc”:
; INT_PTR __stdcall DialogFunc(HWND, UINT, WPARAM, LPARAM) DialogFunc proc near
Find the correct Control handles!
Scrolling down a bit below the function head, you discover the first interesting part:
.text:0040124D push 68h ; nIDDlgItem .text:0040124F push [ebp+hDlg] ; hDlg .text:00401252 call GetDlgItem
The GetDlgItem function retrieves the handle to an control within the GUI. The famous tool ResourceHacker is able to reveal all assigned control handles:
The Edit-Controls (called textboxes in the further article) have the handles 104 up to 107 but in a muddled order (from left to right):
- Textbox = 107
- Textbox = 106
- Textbox = 104
- Textbox = 105
This will become important later 😉 …and btw: the tool also reveals the goodboy message: “Yeaaaaah!!! Congratulations!!! You’re the best cracker!”
According to the first code snippet the third textbox is queried first by the GetDlgItem function which returns the handle for the control and stores it into EAX. The handle is copied to another location in memory, because EAX is used in the following SendMessageA CALL:
.text:00401257 mov dword_403070, eax .text:0040125C lea eax, lParam .text:00401262 push eax .text:00401263 push 8 ; wParam .text:00401265 push 0Dh ; Msg .text:00401267 push dword_403070 ; hWnd .text:0040126D call SendMessageA .text:00401272    mov   ebx, eax
Using this SendMessage CALL the application is able to issue commands such as
.text:00401265 push 0Dh ; Msg
…which is a WM_GETTEXT CALL directly to the handle “dword_403070” which contains the handle for the third textbox. After the SendMessage CALL, EAX contains the length of the string loaded from the third textbox which is afterwards MOVed to EBX. The next CALL is much more interesting:
.text:00401274 call sub_4014A9
The first challenge!
It’s the first “real” part of the Keygeneration process:
.text:004014A9 sub_4014A9 proc near ; CODE XREF: DialogFunc+138 .text:004014A9 inc ebx .text:004014AA shl ebx, 4 .text:004014AD mov esi, 64 .text:004014B2 lea edi, [ebx+esi] .text:004014B5 imul ebx, edi, 386 .text:004014BB xor esi, esi .text:004014BD xchg esi, ebx .text:004014BF sub ebx, ebx .text:004014C1 retn .text:004014C1 sub_4014A9 endp
Deciphered: EBX holds the strlen of the 3rd part of the serial. At this point it’s incremented by 1:
.text:004014A9 inc ebx
Afterwards shifted left by 4, which is basically the same like multipliying with 16:
.text:004014AA shl ebx, 4
64 is added on top:
.text:004014AD mov esi, 64 .text:004014B2 lea edi, [ebx+esi]
multiplied by 386:
.text:004014B5 imul ebx, edi, 386
and finally XCHGed with / saved in ESI:
.text:004014BB xor esi, esi .text:004014BD xchg esi, ebx .text:004014BF sub ebx, ebx .text:004014C1 retn
Back in the DialogFunc application flow, ESI is compared to the value “67936”:
.text:00401279 cmp esi, 67936 .text:0040127F jz short loc_401286
But what’s that value ? To find out you have to reverse the last few commands which represent the following calculation:
((((strlen+1)*16)+64)*368)
Mathematically reversed:
((((67936/368)-64)/16)-1) = ~6
This means: if strlen equals 6 the following jump will be taken, which is our goal:
The next two parts are very similar. The strlen of the first textbox is compared to 6 (EDX, that is also pushed onto the Stack):
.text:00401286 push 6Bh ; nIDDlgItem ("107"=1st textbox) .text:00401288 push [ebp+hDlg] ; hDlg .text:0040128B call GetDlgItem .text:00401290 mov dword_403074, eax .text:00401295 lea eax, unk_403090 .text:0040129B push eax ; lParam .text:0040129C push 8 ; wParam .text:0040129E push 0Dh ; Msg (WM_GETTEXT-0x000D) .text:004012A0 push dword_403074 ; hWnd .text:004012A6 call SendMessageA .text:004012AB mov edx, eax .text:004012AD push edx .text:004012AE cmp edx, 6 .text:004012B1 jnz loc_40144C
and strlen of the fourth textbox is compared to EDX, that contains the POPed value 6:
.text:004012B7 push 69h ; nIDDlgItem ("105"=4th textbox) .text:004012B9 push [ebp+hDlg] ; hDlg .text:004012BC call GetDlgItem .text:004012C1 mov dword_403078, eax .text:004012C6 lea eax, unk_4030A0 .text:004012CC push eax ; lParam (pointer to the buffer that is to receive the text) .text:004012CD push 8 ; wParam .text:004012CF push 0Dh ; Msg (WM_GETTEXT-0x000D) .text:004012D1 push dword_403078 ; hWnd .text:004012D7 call SendMessageA .text:004012DC pop edx ; get EDX back from stack .text:004012DD cmp eax, edx .text:004012DF jz short loc_4012E6 .text:004012E1 jmp loc_40144C
This means the strings from textboxes 3,1 and 4 (in this order) are compared in its lengths to 6. If that matches, the application takes “the right” jump. Up next: The string from the 4th textbox is PUSHed to the stack and the function “sub_4015D0” is CALLed. Thanks to IDA’s great possibility to rename functions, I’ll use the “addup_int_from_char” name for this function, because it’s used several times later again:
.text:004012E6 push offset unk_4030A0 ; push string from 4th textbox to stack .text:004012EB call addup_int_from_char ; sub_4015D0
This functions addups all single chars in a loop and returns the value to EAX. To better visualize what this function does, I’ve commented my IDA dump:
The value from EAX is then compared against decimal 500200, which means that the value from the 4th textbox has to be less or equal to this value:
.text:004012F0 cmp eax, 7A1E8h ; Sum (ECX) must be less than 500200 .text:004012F5 jbe short loc_4012FC .text:004012F7 jmp loc_40144C
The following four lines
.text:004012FC cmp byte_4030A3, 32h ; 4th char of 4th textbox must be 32h (2) .text:00401303 jz short loc_40130A
and
.text:0040130A cmp byte_4030A5, 30h ; 6th char of 4th textbox must be 30h (0) .text:00401311 jz short loc_401318
compare the 4th and 6th char of the 4th textbox to two specific values 2 and 0. So what do we have so far for the 4th textbox:
- It must have a strlen of 6
- It must be less than 500200
- Its 4th char must be 2
- Its 6th char must be 0
The next part is quite simple. The strlen from the 2nd textbox is first decremented, then substituted by 5 and EAX is tested, which leads to a true condition if the strlen is again 6:
.text:00401318 push 6Ah ; nIDDlgItem ("106"=2nd textbox) .text:0040131A push [ebp+hDlg] ; hDlg .text:0040131D call GetDlgItem .text:00401322 mov dword_40307C, eax .text:00401327 lea eax, byte_403098 .text:0040132D push eax ; lParam (pointer to the buffer that is to receive the text) .text:0040132E push 8 ; wParam .text:00401330 push 0Dh ; Msg (WM_GETTEXT-0x000D) .text:00401332 push dword_40307C ; hWnd .text:00401338 call SendMessageA .text:0040133D dec eax ; EAX is decremented by 1 .text:0040133E sub eax, 5 .text:00401341 test eax, eax .text:00401343 jnz loc_40144C
After that the values from the textboxes are pushed onto the stack:
.text:00401349 push offset unk_4030A0 ; 1st char of 4th textbox .text:0040134E push offset lParam ; contains str from 2nd textbox .text:00401353 push offset byte_403098 ; 1st char of 2nd textbox .text:00401358 push offset unk_403090 ; 1st char of 1st textbox .text:0040135D push offset unk_4030A8 ; NULL byte .text:00401362 push 4 .text:00401364 call sub_401610 ; builds string from all chars .text:00401369 add esp, 18h .text:0040136C lea esi, unk_4030A8 ; loads the key-string .text:00401372 mov ecx, 18h
The following loop tests if each char is between 0 and 9, which means only numeric chars are allowed in the serial:
Then some specific single chars are compared against single values:
.text:0040139F cmp byte_403091, 39h ; 2nd char of 1st textbox must be 9 .text:004013A6 jz short loc_4013AD .text:004013A8 jmp loc_40144C .text:004013AD ; --------------------------------------------------------------------------- .text:004013AD .text:004013AD loc_4013AD: ; CODE XREF: DialogFunc+26A .text:004013AD cmp byte_403093, 37h ; 4th char of 1st textbox must be 7 .text:004013B4 jz short loc_4013BB .text:004013B6 jmp loc_40144C .text:004013BB ; --------------------------------------------------------------------------- .text:004013BB .text:004013BB loc_4013BB: ; CODE XREF: DialogFunc+278 .text:004013BB cmp byte_403098, 38h ; 1st char of 2nd textbox must be 8 .text:004013C2 jnz loc_40144C
and
.text:004013C8 push offset unk_403090 ; points to 1st char of 1st textbox .text:004013CD call addup_int_from_char .text:004013D2 cmp eax, 7A057h ; decimal: 499799 .text:004013D7 jbe short loc_4013DB
The final challenge!
Great work until here. But wait something is missing! Correct: 3rd textbox. Remember: The value from the first textbox is already present in EAX. So this is first saved (MOVed) to ESI for later usage:
.text:004013DB mov esi, eax
The value from the 4th textbox is pushed onto stack for further usage in the addup_int_from_char function:
.text:004013DD push offset unk_4030A0 ; points to 1st char of 4 th textbox .text:004013E2 call addup_int_from_char
The function saves its return value to EAX, that is then MOVed to EDI:
.text:004013E7 mov edi, eax
Afterwards the values from ESI (contains value from first textbox) and EDI (contains value from 4th textbox) are ADDed and saved to ESI:
.text:004013E9 add esi, edi ; add value from 4th textbox to value from 1st textbox
Last but not least: the value from the third textbox is compared against the addition from the 1st and 4th textboxes. If those values are the same the jump is taken to the successful – message:
.text:004013EB push offset lParam ; points to 1st char of 3rd textbox .text:004013F0 call addup_int_from_char .text:004013F5 cmp esi, eax ; compare value from 3rd textbox to 4th+1st .text:004013F7 jnz short loc_40144C
The KeyGen!
So that’s the puzzle. Let’s write a KeyGen for it. What do we have so far?
1st textbox:
- Its 2nd char must be 9
- its 4th char must be 7
- It must be less or equal decimal 499799
2nd textbox:
- Its 1st char must be 8
3rd textbox:
- Its value is the addition of the first and fourth textboxes
4th textbox:
- It must have a strlen of 6
- It must be less than 500200
- Its 4th char must be 2
- Its 6th char must be 0
The normal keygenner would write a small and nice-music-driven application,but since this is just a small KeyGenMe, I’ll waive ;-). Here’s a Python script as a PoC:
#!/usr/bin/python import random # first part with schema: x9x7xx part1=str(random.randint(100000,499799)) part1=part1[0]+ "9" + part1[2] + "7" + part1[4:] # second part with schema: 8xxxxx part2=str(random.randint(800000,899999)) # fourth part with schema: xxx2x0 part4=str(random.randint(100000,500200)) part4=part4[:3] + "2" + part2[5] + "0" # third part with schema: part1+part4 part3=str(int(part1) + int(part4)) print "Serial: " + part1 + "-" + part2 + "-" + part3 + "-" + part4
…and finally pwned: