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