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;
}