Keygen 2: adrianbn’s lincrackme3
Link: http://crackmes.cf/users/adrianbn/lincrackme3/ (binary)
The description for this binary refers to antidebugging techniques, and suggests building a keygen for it rather than just bypassing the check.
# ./lincrackme3-32
**********************************************************************
* Welcome to lincrackme3! This is a simple linux crackme. The goal *
* is to understand the key checking method and write your own keygen *
* for it. Keep an eye on antidebugging techniques, because thats the *
* reason for this crackmes, knowing them and the way to bypass them. *
* Key format: XXXX-YYYY-WWWW-ZZZZ with all numbers. Good luck! *
* @author: Adrian adrianbn[at]gmail.com http://securityetalii.es *
* ********************************************************************
Enter the key, you fool: 1234-1234-1234-1234
Wrong key! I've seen monkeys smarter than you...
If we run it under ltrace
, we see a call to ptrace
and an error message:
ptrace(0, 0, 1, 0) = 0xffffffff
puts("Debugger detected. Bye!\n") = 24
As usual, this is an easy fix:
080485d9 call j_ptrace
080485de test eax, eax
080485e0 jns loc_80485fa
The call instruction takes 5 bytes, plenty of space to insert XOR EAX, EAX
followed by NOPs. After changing [E8 86 FE FF FF] to [31 C0 90 90 90], Hopper sees it as:
080485d9 xor eax, eax
080485db nop
080485dc nop
080485dd nop
080485de test eax, eax
We save the binary and continue. sub_804862c
is interesting. It reads the input character by character, validating that it contains only digits and hyphens:
08048643 cmp byte [ebp+var_9], 0xa ; compare to '\n'
08048647 je loc_8048688 ; -> end of buffer
08048649 cmp byte [ebp+var_9], 0x0 ; null byte
0804864d je loc_8048688 ; -> end of buffer
0804864f cmp byte [ebp+var_9], 0x2d ; '-'
08048653 je loc_804867c ; -> continue
08048655 cmp byte [ebp+var_9], 0x2f ; compare to '/' which is just before '0'
08048659 jle loc_8048661 ; -> error
0804865b cmp byte [ebp+var_9], 0x39 ; compare to '9'
0804865f jle loc_8048668 ; -> continue
After the call to this procedure, we verify that we got 0x10 (16) characters right, and print an error message otherwise:
080487de cmp dword [esp+0xe0+var_B4], 0x10
080487e3 je loc_80487fd
080487e5 mov dword [esp+0xe0+var_E0], aWrongKeyFormat
080487ec call j_puts
Next, a trick comparison:
080487fd mov dword [esp+0xe0+var_D8], 0x10 ; argument "__n" for method j_strncmp, CODE XREF=sub_80486b4+303
08048805 mov dword [esp+0xe0+var_DC], aItsNotThatEasy ; argument "__s2" for method j_strncmp, "It's not that easy, dude"
0804880d lea eax, dword [esp+0xe0+var_24]
08048814 mov dword [esp+0xe0+var_E0], eax ; argument "__s1" for method j_strncmp
08048817 call j_strncmp
This call to strcmp
would be obvious in ltrace
, but its return value is not even used so it’s just there to fool us.
A few local variables are set up, and then follows a very short call:
08048841 call sub_80486b4+403
08048846 jmp 0x8a18da3
The call is to 0x08048847, which is one byte after what Hopper decoded as a JMP. We need to change it to data and then code again to reveal the real instructions:
08048841 call sub_80486b4+403 ; jumps to 0x08048847
08048846 db 0xe9
08048847 pop eax
08048848 add eax, 0x9d
0804884d push eax
0804884e ret
We ret back to 0x80488e3, where a function pointer is read and called. At this point, ebx
is 0x080485B4, which we use in a call
:
080488e3 mov ebx, dword [esp+40]
080488e7 call ebx
0x080485B4 is where we broke the ptrace
call, we’ll pass through without failing. We return from the previous two calls and end up in 0x08048850. A variable at esp
+0x24 is initialized to zero, before we jump to the end of the block and compare it to 3: this is some sort of counter.
08048852 mov dword [esp+0x24], 0x0
0804885a jmp sub_804884f+135
[...]
080488d6 cmp dword [esp+0x24], 0x3
080488db jle sub_804884f+13
In between, we successively load the integer values for our input digits, and add them into multiple local variables. First with the integer value of input[counter] into esp
+0x36 (let’s call it var_36
). So as the counter is incremented, the value of var_36
will be the sum of counter[0] to counter[3] included:
0804885c mov eax, dword [esp+0x24]
08048860 movzx eax, byte [esp+eax+0xbc]
08048868 cbw
0804886a add ax, word [esp+0x36]
0804886f sub eax, 0x30 ; 0x30 is ascii for '0'
08048872 mov word [esp+0x36], ax
Then input[counter+4] is added to esp
+0x34 (var_34
):
08048877 mov eax, dword [esp+0x24]
0804887b add eax, 0x4
0804887e movzx eax, byte [esp+eax+0xbc]
08048886 cbw
08048888 add ax, word [esp+0x34]
0804888d sub eax, 0x30
08048890 mov word [esp+0x34], ax
Then input[counter+8] to esp
+0x32 (var_32
):
08048895 mov eax, dword [esp+0x24]
08048899 add eax, 0x8
0804889c movzx eax, byte [esp+eax+0xbc]
080488a4 cbw
080488a6 add ax, word [esp+0x32]
080488ab sub eax, 0x30
080488ae mov word [esp+0x32], ax
And finally input[counter+12] to esp
+0x30 (var_30
):
080488b3 mov eax, dword [esp+0x24]
080488b7 add eax, 0xc
080488ba movzx eax, byte [esp+eax+0xbc]
080488c2 cbw
080488c4 add ax, word [esp+0x30]
080488c9 sub eax, 0x30
080488cc mov word [esp+0x30], ax
We do this 4 times, from counter = 0 to 3. We then jump to a checksum verification block at 0x080488ef. The 4 checksums are involved, computing ecx
= var_36
+var_34
, eax
= (var_32
+ var_30
) * 2, and comparing the two:
080488ef movzx edx, word [esp+0x36]
080488f4 movzx eax, word [esp+0x34]
080488f9 lea ecx, dword [edx+eax]
080488fc movzx edx, word [esp+0x32]
08048901 movzx eax, word [esp+0x30]
08048906 lea eax, dword [edx+eax]
08048909 add eax, eax
0804890b cmp ecx, eax
0804890d jne verify_checksum+92 ; assuming a failure jump
Then we need var_34
> var_32
or we’d fail:
0804890f movzx eax, word [esp+0x34]
08048914 cmp ax, word [esp+0x32]
08048919 jbe verify_checksum+92
We continue with a check on whether (var_36
+ var_30
) & 1 == 0:
0804891b movzx edx, word [esp+0x36]
08048920 movzx eax, word [esp+0x30]
08048925 lea eax, dword [edx+eax]
08048928 and eax, 0x1
0804892b test al, al
0804892d jne verify_checksum+92
Followed by the requirements that var_36
> 5 and <= 0x18:
0804892f cmp word [esp+0x36], 0x5
08048935 jbe verify_checksum+92 ; jump if below or equal
08048937 cmp word [esp+0x36], 0x18
0804893d ja verify_checksum+92 ; jump if above
Finally, var_30
must be odd:
0804893f movzx eax, word [esp+0x30]
08048944 and eax, 0x1
08048947 test eax, eax
08048949 jne verify_checksum+151
; right here is verify_checksum + 92, the failure message:
We initialize a variable at esp
+0x20 to zero and loop until it’s 0x31:
0804894b mov dword [esp+0x20], 0x0
08048953 jmp verify_checksum+132
08048955 mov eax, dword [esp+0x20]
08048959 movzx eax, byte [esp+eax+0x89]
08048961 movsx eax, al
08048964 xor al, 0xe9
08048966 mov dword [esp], eax
08048969 call j_putchar
0804896e add dword [esp+0x20], 0x1
08048973 cmp dword [esp+0x20], 0x31
08048978 jle verify_checksum+102
The address at esp
+0x89 was initialized above, pointing to 0x8048d84. We XOR 0x31 bytes at that address with 0xe9, to produce the following message: “Wrong key! I’ve seen monkeys smarter than you…\n”
If we had jumped to verify_checksum
+151:
08048986 mov dword [esp+0x1c], 0x0
0804898e jmp sub_8048990+27 ; just below
[...]
sub_8048990:
08048990 mov eax, dword [esp+28]
08048994 movzx eax, byte [esp+eax+56]
08048999 movsx eax, al
0804899c xor al, 0xf9
0804899e mov dword [esp+0], eax ; argument "__c" for method j_putchar
080489a1 call j_putchar ; putchar
080489a6 add dword [esp+28], 0x1
080489ab cmp dword [esp+28], 0x4f
080489b0 jle sub_8048990
The loop decrypts a string using XOR 0xf9, eventually printing: “You got it, congratz. Have you tried to keygen me? Next crackme soon ^_^\n”.
To recap the rules:
- the format is AAAA-BBBB-CCCC-DDDD where A,B,C,D are digits
var_36
is the sum of the A digitsvar_34
is the sum of the B digitsvar_32
is the sum of the C digitsvar_30
is the sum of the D digitsvar_36
+var_34
== 2 * (var_32
+var_30
)var_34
>var_32
var_36
+var_30
% 2 == 0- 5 <
var_36
<= 24 var_30
% 2 == 1
A simple keygen gives us a random serial to try:
$ ./keygen.py
4630-6843-8020-1204
$ ./lincrackme3-32
**********************************************************************
* Welcome to lincrackme3! This is a simple linux crackme. The goal *
* is to understand the key checking method and write your own keygen *
* for it. Keep an eye on antidebugging techniques, because thats the *
* reason for this crackmes, knowing them and the way to bypass them. *
* Key format: XXXX-YYYY-WWWW-ZZZZ with all numbers. Good luck! *
* @author: Adrian adrianbn[at]gmail.com http://securityetalii.es *
* ********************************************************************
Enter the key, you fool: 4630-6843-8020-1204
Good! You got it, congratz. Have you tried to keygen me? Next crackme soon ^_^