the_thing
18 April, 2018
the_thing is a crackme imported from crackmes.de, with a difficulty level of 3 in C/C++. The aim is to create a keygen to generate valid passwords.
A way to attack this challenge is to effectively work backwards. Before we do that, we need to label the inputs.
8048715: 8d 85 e8 fe ff ff lea eax,[ebp-0x118]
804871b: 50 push eax
804871c: 68 36 8d 04 08 push 0x8048d36 "%s"
8048721: e8 02 fe ff ff call 0x8048528 <scanf>
This is the code called after the printf()
that prints the string “Username:”.
This call is to the scanf()
function, so the pointer to the buffer stored in
ebp-0x118
is our username buffer.
ebp-0x118
=username
804873c: 8d 85 e8 fd ff ff lea eax,[ebp-0x218]
8048742: 50 push eax
8048743: 68 36 8d 04 08 push 0x8048d36 "%s"
8048748: e8 db fd ff ff call 0x8048528 <scanf>
Similarly, the same thing happens when we input the serial.
ebp-0x218
=serial
804881c: 8d 85 e8 fd ff ff lea eax,[ebp-0x218]
8048822: 8d 95 e8 fc ff ff lea edx,[ebp-0x318]
8048828: 83 ec 08 sub esp,0x8
804882b: 50 push eax
804882c: 52 push edx
804882d: e8 b6 fc ff ff call 0x80484e8 <strcmp>
8048832: 83 c4 10 add esp,0x10
8048835: 85 c0 test eax,eax
8048837: 75 12 jne 0x804884b
Now we’re going to skip most of the code and move towards the bottom. We see a
split 0x8048837, where one path prints the success message, and the other
prints the failure. Clearly, the comparison against the password happens around
here. Indeed, moving up a bit, we see the strcmp()
function, and the
user-input serial
is one of the arguments. This must mean that the other
argument is our target serial value.
ebp-0x318
=target_serial
So how is this value generated? Going up a little futher, we see this is passed
to an sprintf()
call.
8048807: 50 push eax
8048808: 68 ad 8d 04 08 push 0x8048dad "%d"
804880d: 8d 85 e8 fc ff ff lea eax,[ebp-0x318]
8048813: 50 push eax
8048814: e8 9f fd ff ff call 0x80485b8 <sprintf>
Here, a value in eax is passed to sprintf()
, with the format string “%d”, and
the result is stored in our target_serial
buffer. So our serial is a number,
and that number is stored in eax at the time of this call. Going up a tiny
bit further
80487f6: 8d 85 e8 fe ff ff lea eax,[ebp-0x118]
80487fc: 50 push eax
80487fd: a1 38 9f 04 08 mov eax,ds:0x8049f38
8048802: ff d0 call eax
8048804: 83 c4 0c add esp,0xc
Here, our username
function is pushed as an argument to a function, but which
function? The address is stored in 0x8049f38, and called from the register.
This function must be taking the username as an argument, performing some
operation, and returning a number that is stringified to form the serial.
But what is this function? For this, we can use a debugger. We find that the function is at the address 0x08048f18. Where did this address come from? For the purposes of developing a keygen, the answer actually isn’t important. Some disassemblers will not realise this area is a function, since it is never referenced by its actual address, however we can define one here ourselves. Let’s look at the contents of this function:
8048f18 55 push ebp
8048f19 89e5 mov ebp, esp
8048f1b 53 push ebx
8048f1c 8b5d08 mov ebx, dword [ebp + 8]
8048f1f c00320 rol byte [ebx], 0x20
8048f22 c0430110 rol byte [ebx + 1], 0x10
8048f26 c0430208 rol byte [ebx + 2], 8
8048f2a c0430304 rol byte [ebx + 3], 4
8048f2e 8b03 mov eax, dword [ebx]
8048f30 5b pop ebx
8048f31 c9 leave
8048f32 c3 ret
This is quite a simple function, and forms the entire key generation algorithm.
The first and only argument, the username
buffer is retrieved. The first
character is rotate left 32 bits. The second is rotated left by 16. The third
by 8, and the fourth by 4. You may notice that all but the last of these don’t
actually do anything, they give the same output as the input. Let’s try with
an example username, ‘~kirby’
rol(0x7E, 32) = 0x7E rol(0x6B, 16) = 0x6B rol(0x69, 8) = 0x69 rol(0x62, 4) = 0x27
This forms the 32-bit integer 0x27696B7E. In decimal this is 661220222, and this is our serial. This also means only the first four characters of the name is actually used or relevant. Building a keygen from this is therefore quite simple - just perform the operations, construct the integer, and stringise it. Our final keygen is as follows:
#include <limits.h>
#include <string.h>
#include <stdio.h>
/* <https://stackoverflow.com/questions/16387745> */
char rol(char n, int shift)
{
return (n << shift) | (n >> (sizeof(n) * CHAR_BIT - shift));
}
int main(int argc, char **argv)
{
char user[4];
unsigned int n;
if (argc != 2)
{
fprintf(stderr, "%s <username>\n", argv[0]);
return 1;
}
/*
* Although usernames of less than three characters are possible, in my
* experience it relied on uninitialised memory, which was unpredictable
*/
if (strlen(argv[1]) < 3)
{
fprintf(stderr, "Username must be 3 characters or longer\n");
return 1;
}
strncpy(user, argv[1], 4);
n = user[0];
n |= user[1] << 8;
n |= user[2] << 16;
n |= rol(user[3], 0x04) << 24;
printf("%u\n", n);
return 0;
}