skip to content
secrary[dot]com

[A]dvanced Keygenme by sd333221 - Crackme

/

Introduction

CrackMes are executable programs designed to teach reverse engineering concepts. In this walkthrough, we will analyze [A]dvanced Keygenme, a challenge that employs various anti-analysis techniques, including anti-debugging and custom encoding schemes.

Challenge Details

  • Name: [A]dvanced Keygenme
  • Author: sd333221 (haxx0r)
  • Release Date: February 24, 2007
  • Difficulty: Medium/Advanced

Analysis

Download the Sample

You can download the sample from:

Note: Try solving this challenge yourself before reading the solution. Variable names used in this analysis are chosen for clarity.

Program Interface

Screenshot of the CrackMe program interface

Static Analysis

Using PeStudio, we can identify two important characteristics:

  • Author signature: haxx0r
  • Thread Local Storage (TLS): This is a common anti-debugging technique used to hinder reverse engineering efforts.
PeStudio Analysis

Dynamic Analysis

Let’s open the program in IDA Pro to analyze its behavior:

In the tls-callback, it calls GetTickCount and computes the value of HIGH_pp from the result: TLS Callback Analysis

In GetRand_CPUID_getTick, it retrieves the CPU vendor ID using cpuid (EAX=0) and uses HIGH_pp to compute a new value: GetRand_CPUID_getTick Analysis

After that, it converts the result to a string using _itoa.

Function Analysis

The modify_str function contains two parts: mix_two_list and modify_cpuid_time:

Mix Two List Analysis

In mix_two_list, it builds a list of bytes (renamed as alpha) from 0x0 to 0xFF and modifies it using another list of 13337....133371:

def mix_two_list():
nums = b"13337"
alpha = [n for n in range(256)]
edi = 0
for n in range(256):
edi = (edi + alpha[n] + nums[n % 5]) % 256
tmp = alpha[n]
alpha[n] = alpha[edi]
alpha[edi] = tmp
return alpha

In modify_cpuid_time, it uses the value converted from integer to string and modifies this value using the alpha list:

def modify_cpuid_time(from_cpu_and_time):
alpha = mix_two_list()
index = 0
edx = 0
res = []
for x in range(len(from_cpu_and_time)):
index += 1
index = index & 0xFF
edx = (alpha[index] + edx) & 0xFF
tmp = alpha[index]
alpha[index] = alpha[edx]
alpha[edx] = tmp
eax = (alpha[index] + tmp) & 0x800000FF
if from_cpu_and_time[x] ^ alpha[eax] == 0:
return res
res.append(from_cpu_and_time[x] ^ alpha[eax])
return res

The modify_str function is used to encrypt/decrypt the computed value before using it.

At the end of tls-callback, it uses two methods to detect debuggers, named chckDbgr and anotherCheck. chckDbgr uses guard pages to detect debuggers, while anotherCheck uses FindWindow() and CreateFile() functions. Both tricks are explained in The Ultimate Anti-Reversing Reference by Peter Ferrie (must read!).

Debugger Detection

The CrackMe uses a custom base64 alphabet: /+9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA. If it detects a debugger, it calls the ifDebugg__ function, which reverses the alphabet:

Custom Base64 Alphabet

In WinMain, it sets the result of our old friend GetRand_CPUID_getTick as the username, which is exactly the same value that we have in encrypted form.

CheckSerial Function

Let’s analyze the CheckSerial function (we are trying to write a keygen, so don’t patch it):

CheckSerial Function

In the CheckSerial function, it decrypts the value obtained from tls-callback. The decrypted version of the text is the same as the username, both derived from GetRand_CPUID_getTick. After decryption, it encodes the result with base64 using a custom alphabet, encrypts the result again, and encodes it once more. The final result is saved in the esi register.

SerialComp Function

The SerialComp function receives our input as a parameter and returns a value that should match the one in esi.

Python Implementation of SerialComp

Here’s the Python reverse implementation of SerialComp:

def get_serial_number(enc2):
serial__number = [0 for n in range(0x400)]
i = 0
j = 0
done_1 = False
done_2 = False
done_3 = False
while j < len(enc2):
if done_1 or done_2 or done_3:
break
for a in my_base64chars:
if done_2:
break
if done_1:
break
for b in my_base64chars:
if done_2:
done_1 = True
break
if enc2[j] == ((my_base64chars.find(a) << 2) & 0xFF) | ((my_base64chars.find(b) >> 4) & 0xFF):
serial__number[i] = a
if j + 1 >= len(enc2):
serial__number[i + 1] = b
done_1 = True
break
for c in my_base64chars:
if enc2[j + 1] == ((my_base64chars.find(b) << 4) & 0xF0) | ((my_base64chars.find(c) >> 2) & 0xFF):
serial__number[i + 1] = b
if j + 2 >= len(enc2):
serial__number[i + 2] = c
done_2 = True
break
for d in my_base64chars:
if enc2[j + 2] == ((my_base64chars.find(c) << 6) & 0xC0) | (my_base64chars.find(d) & 0xFF):
serial__number[i + 2] = c
serial__number[i + 3] = d
i += 4
j += 3
return bytearray(serial__number).strip(b'\x00').decode()

Conclusion

We have everything we need to write a keygen:

import base64
my_base64chars = b"/+9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA"
STANDARD_ALPHABET = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
ENCODE_TRANS = bytes.maketrans(STANDARD_ALPHABET, my_base64chars)
DECODE_TRANS = bytes.maketrans(my_base64chars, STANDARD_ALPHABET)
def encode(clear):
return base64.b64encode(clear).translate(ENCODE_TRANS)
def decode(encoded):
return base64.b64decode(encoded).translate(DECODE_TRANS)
# Additional functions here...
if __name__ == "__main__":
username = input("Username (Locked): ")
from_CPUID = username.encode()
enc1 = encode(bytearray(from_CPUID))
sm = modify_cpuid_time(enc1)
encoded2 = encode(bytearray(sm))
serial_number = get_serial_number(encoded2)
print("Serial: {}".format(serial_number))