Crackmes.de – s!mple Crack Me v0.3 by simple_re
- April 23, 2015
- reverse engineering
- crackmes
- no comments
- Anti-Debugging
- Trick 1: IsDebuggerPresent
- Trick 2: ProcessDebugPort
- Trick 3: Format String Vulnerability
- Trick 4: Timing Check
- Patching
- Valid name/serials combos
- Name must have at least 3 characters
- Last serial character must be first hostname character
- Name must have at most 6 characters
- First serial character must be fourth character of MAC address
- Second serial character must be second IP character
- Remaining serial characters
- Keygen
The crackme s!mple Crack Me v0.3 by simple_re has been published February 14, 2013. It is rated at 3 - Getting harder. The crackme is written in C/C++ and runs on Windows. The description reads:
Hey reversers, welcome to my third crack me.
Rules: Patching/Serial Phish/Keygen
Goal is to get to correct input, thanks for reversing!
Anti-Debugging
The crackme has a couple of anti-debugging checks that need addressing first.
Trick 1: IsDebuggerPresent
The first anti-debugging check is in subroutine AntiRevIDP(void), at offset 00409B8E. The routine dynamically loads the kernel32.dll:
00409BCD mov dword ptr [esp+8], 0 ; dwFlags
00409BD5 mov dword ptr [esp+4], 0 ; hFile
00409BDD mov dword ptr [esp], offset LibFileName ; "kernel32.dll"
00409BE4 mov [ebp+var_70], 0FFFFFFFFh
00409BEB call _LoadLibraryExA@12 ; LoadLibraryExA(x,x,x)
Next, the subroutine calls IDB(void). This nasty routine builds the String IsDebuggerPresent by sampling characters from the hardcoded string:
.rdata:00453000 unicode 0, <simplecortn!BUTONRgIDbuPC SakvQyf>
The AntiRevIDP(void) then loads the procedure IsDebuggerPresent:
00409C20 mov [ebp+lpProcName], eax
00409C23 mov eax, [ebp+lpProcName]
00409C26 mov [esp+4], eax ; lpProcName
00409C2A mov eax, [ebp+hModule]
00409C2D mov [esp], eax ; hModule
00409C30 call _GetProcAddress@8 ; GetProcAddress(x,x)
00409C30 call _GetProcAddress@8 ; GetProcAddress(x,x)
00409C35 sub esp, 8
00409C38 mov [ebp+IsDebuggerPresent], eax
Immediately after that, the routine then calls IsDebuggerPresent and sets a flag if a debugger is detected:
00409C41 mov eax, [ebp+IsDebuggerPresent]
00409C44 call eax
00409C46 test eax, eax
00409C48 jz short loc_409C51
00409C4A mov [ebp+there_is_a_debugger], 1
Finally, if the flag is set, the crackme kills the registration window:
00409CB9 mov dword ptr [esp], 0 ; hWnd
00409CC0 mov [ebp+var_70], 0FFFFFFFFh
00409CC7 call _DestroyWindow@4 ; DestroyWindow(x)
Trick 2: ProcessDebugPort
The next anti-debugger check we find in CallNtInformationProcess(void) at offset 0x00409CEC. This routine loads the ntdll.dll library:
00409D34 mov [ebp+NtQueryInformationProcess], 0
00409D3B mov [ebp+processdebugport], 0
00409D42 mov dword ptr [esp], offset aNtdll_dll ; "ntdll.dll"
00409D49 mov [ebp+var_9C], 0FFFFFFFFh
00409D53 call _LoadLibraryA@4 ; LoadLibraryA(x)
After that follows a call to NTQIP(void) at offset 407376. This routine — like the routine IDB from the first anti-debugging measure — samples characters from “simplecortn!BUTONRgIDbuPC SakvQyf” to generate the following string: NtQueryInformationProcess. This procedure is then called after loading the address with GetProcAddress:
00409D84 mov eax, [ebp+lpProcName]
00409D87 mov [esp+4], eax ; lpProcName
00409D8B mov eax, [ebp+hModule]
00409D8E mov [esp], eax ; hModule
00409D91 call _GetProcAddress@8 ; GetProcAddress(x,x)
00409D96 sub esp, 8
00409D99 mov [ebp+NtQueryInformationProcess], eax
00409D9C call _GetCurrentProcess@0 ; GetCurrentProcess()
00409DA1 mov dword ptr [esp+10h], 0
00409DA9 mov dword ptr [esp+0Ch], 4
00409DB1 lea edx, [ebp+processdebugport]
00409DB4 mov [esp+8], edx
00409DB8 mov dword ptr [esp+4], 7 ; ProcessDebugPort
00409DC0 mov [esp], eax
00409DC3 mov eax, [ebp+NtQueryInformationProcess]
00409DC6 call eax
The first three arguments to NtQueryInformationProcess are:
- ProcessHandle: Handle to current process, retrieved by GetCurrentProcess.
- ProcessInformationClass: Value 7, which stands for ProcessDebugPort
- ProcessInformation: A pointer that will receive the debug port information in processdebugport.
If processdebugport is nonzero, this means that there is a debugger attached. In this case the crackme shows a message and quits:
00409DD8 cmp [ebp+processdebugport], 0
00409DDC jz ok_your_no_reverser
00409DE2 lea eax, [ebp+var_68]
00409DE5 mov [esp], eax
00409DE8 call __Z5ULLARv ; ULLAR(void)
00409DED sub esp, 4
00409DF0 lea eax, [ebp+var_68]
00409DF3 mov [esp], eax ; this
00409DF6 mov [ebp+var_9C], 1
00409E00 call __ZNKSs5c_strEv ; std::string::c_str(void)
00409E05 mov [ebp+you_look_like_a_reverser], eax
00409E08 mov dword ptr [esp+0Ch], 0 ; uType
00409E10 mov eax, [ebp+you_look_like_a_reverser]
00409E13 mov [esp+8], eax ; lpCaption
00409E17 mov eax, [ebp+you_look_like_a_reverser]
00409E1A mov [esp+4], eax ; lpText
00409E1E mov dword ptr [esp], 0 ; hWnd
00409E25 call _MessageBoxA@16 ; MessageBoxA(x,x,x,x)
00409E2A sub esp, 10h
00409E2D mov dword ptr [esp], 0 ; uExitCode
00409E34 call _ExitProcess@4 ; ExitProcess(x)
00409E39 ; ------------------------------------------------------------
The displayed message is:
If you passed the first two tests, you can finally enter a name and serial. But after hitting “Register” you face two additional anti-debugging checks.
Trick 3: Format String Vulnerability
At various locations in the crackme there are calls to CrashOlly(void)
at offset 0040A7EA. The disassembly of the routine is:
0040A7EA ; _DWORD CrashOlly(void)
0040A7EA public __Z9CrashOllyv
0040A7EA __Z9CrashOllyv proc near
0040A7EA push ebp
0040A7EB mov ebp, esp
0040A7ED sub esp, 8
0040A7F0 mov dword ptr [esp], offset OutputString ; "%s%s%s%s%s%s%s%s%s%s%s"
0040A7F7 call _OutputDebugStringA@4 ; OutputDebugStringA(x)
0040A7FC sub esp, 4
0040A7FF leave
0040A800 retn
0040A800 __Z9CrashOllyv endp
This subroutine tries to exploit a format string vulnerability in version 1.1 of OllyDbg. See page 365 of the book Practical Malware Analysis by Michael Sikorski and Andrew Honig for more information.
Trick 4: Timing Check
Lastly, there is a timing check:
0040B126 call _GetTickCount@0 ; GetTickCount()
0040B12B mov [ebp+tick_count_start], eax
0040B131 mov dword ptr [esp], 250 ; dwMilliseconds
0040B138 call _Sleep@4 ; Sleep(x)
0040B13D sub esp, 4
0040B140 call _GetTickCount@0 ; GetTickCount()
0040B145 mov [ebp+tick_count_after], eax
0040B14B mov eax, [ebp+tick_count_start]
0040B151 add eax, 250
0040B156 cmp eax, [ebp+tick_count_after]
0040B15C jz short pass
0040B15E mov dword ptr [esp], 0 ; uExitCode
0040B165 call _ExitProcess@4
This code performs this check:
tick_count_start = GetTickCount()
sleep(250 ms)
tick_count_end = GetTickCount()
IF tick_count_end - tick_count_start != 250 THEN
exit
END
The reasoning here is that the debugger slows down the process and it should take more than 250 ms between the to calls to GetTickCount. Unfortunately, GetTickCount is not really a reliable way to measure time. On my machine the crackme often quit even when no debugger was attached.
Patching
The easiest way to deal with the anti-debugging checks is to replace the conditional jumps at 0x409c58, 0x409ddc and 0x40b15c with unconditional jumps. For instance, the jump after the timing check:
becomes
Valid name/serials combos
The name and serial need to meet various criteria to be valid.
Name must have at least 3 characters
First, the name must be longer than 3 characters:
0040B414 cmp [ebp+name_length], 3
0040B41B jle invalid_name_len
Last serial character must be first hostname character
The subroutine GetHostName retrieve the host name of the machine:
0040B421 lea eax, [ebp+hostname_string]
0040B427 mov [esp], eax
0040B42A call __Z11GetHostNamev ; GetHostName(void)
The crackme extracts the first letter of the hostname with the [] operator:
0040B432 mov dword ptr [esp+4], 0 ; int
0040B43A lea eax, [ebp+hostname_string]
0040B440 mov [esp], eax ; std::string *
0040B443 mov [ebp+var_408], 29h
0040B44D call __ZNSsixEj ; std::string::operator[](uint)
The code then takes the last character of the serial by first calculating the length of the serial, the referencing the address [ebp+serial + length - 1]
:
0040B46C lea eax, [ebp+serial]
0040B472 mov [esp], eax ; char *
0040B475 call _strlen
0040B47A movzx eax, byte ptr [eax+ebp-139h] ; =[ebp+serial+eax-1]
0040B482 mov [ebp+serial_last_char], al
0040B488 lea eax, [ebp+some_string]
Both the first letter of the hostname and the last letter of the serial are converted to C++ strings, before they are compared with the compare method:
0040B5BA loc_40B5BA: ; first char
0040B5BA lea eax, [ebp+hostname_string]
0040B5C0 mov [esp+4], eax ; std::string *
0040B5C4 lea eax, [ebp+serial_last_char_string_]
0040B5CA mov [esp], eax ; this
0040B5CD mov [ebp+var_408], 27h
0040B5D7 call __ZNKSs7compareERKSs ; std::string::compare(std::string const&)
0040B5DC test eax, eax
0040B5DE jnz badboymessage
So we know that the first character of the serial is the first character of the hostname.
Name must have at most 6 characters
Next follows another simple check:
0040B694 cmp [ebp+name_length], 6
0040B69B jg badboymessage2 ; name length must be no more than 6
So in summary, names must be between 3 and 6 characters in length.
First serial character must be fourth character of MAC address
The next check is based on the MAC address:
0040B6A1 lea eax, [ebp+hostname_later_mac]
0040B6A7 mov [esp], eax
0040B6AA call __Z6GetMacv ; GetMac(void)
This time the chrackme extracts the fourth character with mac[3]
:
0040B6B2 mov dword ptr [esp+4], 3 ; int
0040B6BA lea eax, [ebp+mac]
0040B6C0 mov [esp], eax ; std::string *
0040B6C3 mov [ebp+var_408], 25h
0040B6CD call __ZNSsixEj ; std::string::operator[](uint)
Similarly, the code reads the first character of the serial with serial[0]
:
0040B6F5 mov dword ptr [esp+4], 0 ; int
0040B6FD lea eax, [ebp+serial_string]
0040B703 mov [esp], eax ; std::string *
0040B706 mov [ebp+var_408], 24h
0040B710 call __ZNSsixEj ; std::string::operator[](uint)
Both characters are converted to strings and compared:
0040B7D1 lea eax, [ebp+mac]
0040B7D7 mov [esp+4], eax ; std::string *
0040B7DB lea eax, [ebp+first_serial_character]
0040B7E1 mov [esp], eax ; this
0040B7E4 call __ZNKSs7compareERKSs ; std::string::compare(std::string const&)
0040B7E9 test eax, eax
0040B7EB jnz fail
So we know that the first character of the serial is the fourth character of the MAC address.
Second serial character must be second IP character
The crackme then strips the first and last character from the serial:
0040B811 mov [esp], eax ; this
0040B814 mov [ebp+var_408], 22h
0040B81E call __ZNKSs4sizeEv ; std::string::size(void)
0040B823 dec eax
0040B824 mov [esp+0Ch], eax ; length(serial)-1
0040B828 mov dword ptr [esp+8], 0 ; unsigned int
0040B830 lea eax, [ebp+serial_string]
0040B836 mov [esp+4], eax ; unsigned int
0040B83A mov edx, [ebp+serial_without_last_char_string]
0040B840 mov [esp], edx ; this
0040B843 call __ZNKSs6substrEjj ; std::string::substr(uint,uint)
Then the IP of the machine is determined and the second letter retrieved with []:
0040BA7B lea eax, [ebp+ip_address_serial]
0040BA81 mov [esp], eax
0040BA84 call __Z9GetIpAddyv ; GetIpAddy(
...
0040BAA4 mov dword ptr [esp+4], 1 ; int
0040BAAC lea eax, [ebp+hostname]
0040BAB2 mov [esp], eax ; std::string *
0040BAB5 mov [ebp+var_408], 1Dh
0040BABF call __ZNSsixEj ; std::string::operator[](uint)
The second digit of the IP is compared to the second character of the serial:
0040BAD9 lea eax, [ebp+mac_]
0040BADF mov [esp+4], eax ; std::string *
0040BAE3 lea eax, [ebp+second_letter_of_ip_string]
0040BAE9 mov [esp], eax ; this
0040BAEC call __ZNKSs7compareERKSs ; std::string::compare(std::string const&)
0040BAF1 test eax, eax
0040BAF1 test eax, eax
0040BAF3 jnz fail2 ; serial[1] != ip[1]
So we know that the second character of the serial is the second character of the IP address.
Remaining serial characters
The crackme converts the name to the ASCII string representation with subroutine CreateSerialFromName:
0040BB67 mov edx, [ebp+name22]
0040BB6D mov [esp], edx ; name
0040BB70 mov [ebp+var_408], 1Ch
0040BB7A call __Z20CreateSerialFromNameSs ; CreateSerialFromName
So for example “bixby” becomes “6269786279”. The crackme also converts the first serial character to integer (if the character happens to be A-F, the value 0 is stored):
0040BB2B mov [esp], eax ; char *
0040BB2E call _atoi ; int(serial[0])
0040BB33 mov [ebp+serial_zero_atoi], eax
The name as the hex string and the integer of the first serial are then passed to GetFinalSerialFromHexedName:
0040BBFF lea edx, [ebp+name_as_ascii_hex]
0040BC05 mov eax, [ebp+serial_zero_atoi]
0040BC0B mov [esp+8], eax
0040BC0F mov [esp+4], edx
0040BC13 mov edx, [ebp+result]
0040BC19 mov [esp], edx ; result
0040BC1C mov [ebp+var_408], 1Ah
0040BC26 call __Z27GetFinalSerialFromHexedNameSsi ; GetFinalSerialFromHexedName
The routine GetFinalSerialFromHexedName calculates an integer value:
0040923C mov ecx, [ebp+name_as_ascii_hex]
0040923F mov [esp], ecx ; this
00409242 mov [ebp+var_138], 0FFFFFFFFh
0040924C call __ZNKSs5c_strEv ; std::string::c_str(void)
00409251 mov [esp], eax ; char *
00409254 call _atoi
00409259 mov [ebp+a], eax
0040925C mov [ebp+s1], 0ABh
00409263 mov edx, [ebp+a]
00409266 lea eax, [ebp+s1]
00409269 add [eax], edx ; eax = name_atoi + 0xab
0040926B mov [ebp+s2], 0EEEEEEh
00409272 mov edx, [ebp+a]
00409275 lea eax, [ebp+s2]
00409278 add [eax], edx ; name_atoi + 0xEEEEEE
0040927A mov edx, [ebp+a]
0040927D lea eax, [ebp+s1]
00409280 mov [ebp+s3], eax
00409286 mov eax, edx
00409288 mov ecx, [ebp+s3]
0040928E cdq
0040928F idiv dword ptr [ecx]
00409291 mov [ebp+s3], eax ; name_atoi / (0xab + name_atoi)
00409297 mov eax, [ebp+s3]
0040929D mov [ebp+s4], eax
004092A0 mov edx, [ebp+serial_zero_integer]
004092A3 lea eax, [ebp+s4]
004092A6 add [eax], edx
004092A8 mov eax, [ebp+serial_zero_integer]
004092AB xor eax, [ebp+s4]
004092AE mov [ebp+s5], eax
004092B1 mov eax, [ebp+s5]
004092B4 imul eax, [ebp+s2]
004092B8 mov [ebp+s5], eax
004092BB mov edx, [ebp+s4]
004092BE xor edx, 23h
004092C1 lea eax, [ebp+s5]
004092C4 add [eax], edx
004092C6 mov eax, [ebp+s2]
004092C9 add eax, [ebp+serial_zero_integer]
004092CC xor eax, 33838Dh
004092D1 mov [ebp+s6], eax
004092D4 mov eax, [ebp+s1]
004092D7 mov edx, [ebp+serial_zero_integer]
004092DA xor edx, eax
004092DC lea eax, [ebp+s6]
004092DF add [eax], edx
004092E1 mov eax, [ebp+serial_zero_integer]
004092E4 imul eax, 0EEEEEh
004092EA mov [ebp+s7], eax
004092F0 mov edx, [ebp+name_as_ascii_hex]
004092F3 mov [esp], edx ; this
004092F6 call __ZNKSs5c_strEv ; std::string::c_str(void)
004092FB mov [esp], eax ; char *
004092FE call _atoi
00409303 add eax, [ebp+s7]
00409309 add eax, [ebp+s6]
0040930C mov [ebp+final_value], eax
The calculated value is
a = int(name)
b = int(mac[3])
s1 = (a + 171)
s2 = (a + 15658734)
s3 = a // s1
s4 = b + s3
s5 = (s4 ^ 35) + s2*(s4 ^ b)
s6 = (s1 ^ b) + ((b + s2) ^ 0x33838D)
s7 = 978670 * b
final_value = s6 + s7 + a
This value is then converted to a string, and compared to the remaining serial characters.
Keygen
To summarize, this is what we know:
- Name between 3 and 6 letters
- Serial[0] = MAC[3]
- Serial[1] = IP[1]
- Serial[-1] = Hostname[0]
- Serial[2:-1] = Result of GetFinalSerialFromHexedName
The following Python code retrieves the necessary host information and generates the serial:
import socket, uuid
import sys
def keygen(name):
hostname = socket.gethostname()
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("gmail.com",80))
ip = s.getsockname()[0]
mac = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0,8*6,8)][::-1])
print("hostname: {}".format(hostname))
print("ip: {}".format(ip))
print("mac: {}".format(mac))
hex_name = ''.join(["{:02x}".format(ord(x)) for x in name])
calc = 0
i_str = ""
for x in hex_name:
try:
int(x)
i_str += x
except ValueError:
break
a = int(i_str) & 0xFFFFFFFF
try:
b = int(mac[3])
except ValueError:
b = 0
s1 = (a + 171) & 0xFFFFFFFF
s2 = (a + 15658734) & 0xFFFFFFFF
s3 = a // s1
s4 = b + s3
s5 = (s4 ^ 35) + s2*(s4 ^ b) & 0xFFFFFFFF
s6 = (s1 ^ b) + ((b + s2) ^ 0x33838D) & 0xFFFFFFFF
s7 = 978670 * b & 0xFFFFFFFF
s8 = s6 + s7 + a & 0xFFFFFFFF
serial = "{}{}{}{}".format(mac[3], ip[1], s8, hostname[0])
print("name: {}".format(name))
print("serial: {}".format(serial))
keygen(sys.argv[1])
For example
hostname: hp
ip: 192.168.73.46
mac: a0:d3:c1:6f:62:48
name: bixby
serial: d91644328465h
Entering a valid serial you should see the following screenshot:
If the crackme crashes after entering the serial, patch away the timing check at offset 0x40B15C. If the serial is not accepted, then the Python script might have failed to retrieve the MAC, hostname or IP of you PC. In this case enter the information manually. Maybe you also need to change the first letter (from the mac address) from lowercase to uppercase or vice-versa.