t0ad_k3yg3n
13 April, 2018
t0ad_k3yg3n is a crackme imported from crackmes.de, with a difficulty level of 1 in C/C++. The aim is to create a keygen to generate valid passwords.
The main function we are interested in is the one that starts at 0x4006fd
,
which we can find by seeing all the printed strings, such as “< T0AD K4YG3N >”.
400715: 48 b8 20 20 20 20 20 movabs rax,0x2020202020202020
40071c: 20 20 20
40071f: 48 89 45 b0 mov QWORD PTR [rbp-0x50],rax
400723: c6 45 b8 20 mov BYTE PTR [rbp-0x48],0x20
400727: 48 b8 20 20 20 20 20 movabs rax,0x2020202020202020
40072e: 20 20 20
400731: 48 89 45 c0 mov QWORD PTR [rbp-0x40],rax
400735: c6 45 c8 20 mov BYTE PTR [rbp-0x38],0x20
400739: 48 b8 20 20 20 20 20 movabs rax,0x2020202020202020
400740: 20 20 20
400743: 48 89 45 d0 mov QWORD PTR [rbp-0x30],rax
To begin with, we see that the function writes 0x202020
… to three buffers.
0x20 is the space character in ASCII, so these are likely character buffers.
Later down the line, this is confirmed.
400764: 48 8b 15 fd 08 20 00 mov rdx,QWORD PTR [rip+0x2008fd] # 601068 <stdin@@GLIBC_2.2.5>
40076b: 48 8d 45 b0 lea rax,[rbp-0x50]
40076f: be 09 00 00 00 mov esi,0x9
400774: 48 89 c7 mov rdi,rax
400777: e8 64 fe ff ff call 4005e0 <fgets@plt>
Here we see a function call to scanf()
that reads from STDIN to rbp-0x50
,
one of the buffers from earlier. Before this, “Username: “ is printed, so this
must be the buffer for our username. We can rename it in our disassembler.
rbp-0x50
=username
40077c: c7 45 a0 00 00 00 00 mov DWORD PTR [rbp-0x60],0x0
400783: eb 17 jmp 40079c
400785: 8b 45 a0 mov eax,DWORD PTR [rbp-0x60]
400788: 48 98 cdqe
40078a: 0f b6 44 05 b0 movzx eax,BYTE PTR [rbp+rax*1-0x50]
40078f: 3c 20 cmp al,0x20
400791: 75 05 jne 400798
400793: e9 f4 00 00 00 jmp 40088c
400798: 83 45 a0 01 add DWORD PTR [rbp-0x60],0x1
40079c: 83 7d a0 08 cmp DWORD PTR [rbp-0x60],0x8
4007a0: 7e e3 jle 400785
This section initialises a value rbp-0x60
to 0. It then checks if this value
is 8, and jumps to 400785
if it’s less than or equal. At the end of this
section, it is incremented by 1. Clearly, rbp-0x60
is a loop variable.
rbp-0x60
=i
The body of this loop is simple. It takes our username buffer and goes through
every character to check if it is 0x20 - space. If it is, it jumps to 400798
,
where it prints “Access Denied.” and leaves. Since the buffer was initially set
to spaces, if any remain (or the user input contains a space), then the program
exits. As a result, the user input must be exactly 7 characters long, plus the
newline to make 8 characters.
4007c0: 48 8b 15 a1 08 20 00 mov rdx,QWORD PTR [rip+0x2008a1] # 601068 <stdin@@GLIBC_2.2.5>
4007c7: 48 8d 45 c0 lea rax,[rbp-0x40]
4007cb: be 09 00 00 00 mov esi,0x9
4007d0: 48 89 c7 mov rdi,rax
4007d3: e8 08 fe ff ff call 4005e0 <fgets@plt>
Another user input from STDIN, this time to rbp-0x40
. This time, it’s the
password.
rbp-0x40
=password
4007d8: 0f b6 45 b1 movzx eax,BYTE PTR [rbp-0x4f]
4007dc: 66 0f be d0 movsx dx,al
4007e0: 6b d2 56 imul edx,edx,0x56
4007e3: 66 c1 ea 08 shr dx,0x8
4007e7: c0 f8 07 sar al,0x7
4007ea: 89 d1 mov ecx,edx
4007ec: 29 c1 sub ecx,eax
4007ee: 89 c8 mov eax,ecx
4007f0: 0f be c0 movsx eax,al
4007f3: 89 45 9c mov DWORD PTR [rbp-0x64],eax
The process of generating the correct password from the username starts here.
The byte rbp-0x4f
is grabbed into eax. Since the username
buffer is at
rbp-0x50
, rbp-0x4f
is the second character of the username input
(username[1]
). Through some twiddling, this value is copied to edx, where it
is multiplied by 0x56 and bit shifted to the right by 8. The copy in eax is
rotated to the right by 7. The value in edx is copied to ecx, and the value in
eax is subtracted from it. This is saved to rbp-0x64
, which we will name
magic
for lack of a better term.
rbp-0x64
=magic
= ((username[1] * 0x56) » 8) - (username[1] » 7)
4007f6: c7 45 a4 00 00 00 00 mov DWORD PTR [rbp-0x5c],0x0
4007fd: eb 37 jmp 400836
4007ff: 8b 45 a4 mov eax,DWORD PTR [rbp-0x5c]
400802: 48 98 cdqe
400804: 0f b6 44 05 b0 movzx eax,BYTE PTR [rbp+rax*1-0x50]
400809: 0f be c0 movsx eax,al
40080c: 33 45 9c xor eax,DWORD PTR [rbp-0x64]
40080f: 83 e0 3c and eax,0x3c
400812: 89 45 ac mov DWORD PTR [rbp-0x54],eax
400815: 8b 45 ac mov eax,DWORD PTR [rbp-0x54]
400818: 83 c0 30 add eax,0x30
40081b: 89 c2 mov edx,eax
40081d: 8b 45 a4 mov eax,DWORD PTR [rbp-0x5c]
400820: 48 98 cdqe
400822: 88 54 05 d0 mov BYTE PTR [rbp+rax*1-0x30],dl
400826: 8b 55 ac mov edx,DWORD PTR [rbp-0x54]
400829: 89 d0 mov eax,edx
40082b: 01 c0 add eax,eax
40082d: 01 d0 add eax,edx
40082f: 89 45 9c mov DWORD PTR [rbp-0x64],eax
400832: 83 45 a4 01 add DWORD PTR [rbp-0x5c],0x1
400836: 83 7d a4 08 cmp DWORD PTR [rbp-0x5c],0x8
40083a: 7e c3 jle 4007ff
Using the same logic as last time, we have another loop variable at rbp-0x5c
.
rbp-0x5c
=i2
This loop once again goes over the username
buffer, placing the character at
username[i2]
into eax. It then XORs this value with magic
, and ANDs the
result of this with 0x3c. This value is saved to rbp-0x54
, a temporary value
we will come back to later.
rbp-0x54
=temp
= (username[i2] ^ magic) & 0x3c
The value in temp
is added to 0x30, and this value is stored in our third
buffer rbp-0x30
plus i2
. We will later see that this buffer is used to
contain the real password.
rbp-0x30
=real_password
real_password[i2]
=temp
+ 0x30
temp
is then retrieved from memory, and added to itself twice. This is the
same as multiplying by three. The result is saved back into magic
.
magic
=temp
* 3
This loop continues over the entire username
buffer, and the end result is
thus the target password. This loop, and the initial magic
value, is
therefore the algorithm to recreate within our keygen.
There is one final loop in the code, that simply checks the user-supplied
password
buffer with the generated real_password
. If they match, the user
is granted access, otherwise they are not.
The final keygen code is as follows
#include <stdint.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char username[9] = " ";
char password[9] = " ";
uint8_t magic, i;
if (argc != 2)
{
fprintf(stderr, "%s <username>\n", argv[0]);
return 1;
}
if (strlen(argv[1]) != 7)
{
fprintf(stderr, "Username must be exactly 7 characters\n");
return 1;
}
strncpy(username, argv[1], 7);
username[7] = '\n';
username[8] = '\0';
// Calculate initial magic (loc_4007d8)
magic = ((username[i] * 0x56) >> 8) - (username[1] >> 7);
// Calculate password (loc_0x400836)
for (i = 0; i < 8; i++)
{
password[i] = (((username[i] ^ magic) & 0x3c) + 0x30);
magic = (password[i] - 0x30) * 3;
}
password[8] = '\0';
printf("%s\n", password);
return 0;
}