I found this crackme really interesting and I've learned a couple of stuff that will come useful within my long way into the reverse engineering learning process. I thought it would be a good idea to save somewhere a few notes that I took during the static analysis of these executables.
The "4N006135" by borismilner, is zip file that contains a total of 4 x86 binaries, each of those with an increasing level of difficulty. level-0 an level-1 are pretty straight forward while level-2 and level-3 took me some time and the Intel Software Developer's Manual constantly at hand.
In this blog post I'll focus my attention on (some aspects of) level-2 and how I built the relative keygen in Python.
.text:004013E0 mov edi, 40D020h .text:004013E5 mov ecx, 20h .text:004013EA mov al, 4Fh; 4Fh is the letter "O" .text:004013EC rep stosb
REP executes an instruction (STOS) and decrease the value of ECX until ECX is 0. The value of ECX is 20h so 32 times.
STOS(B) copies the value (1 byte) contained in AL to [EDI] and increments/decrements (depending on the Direction Flag) EDI by 1. B can be replaced by W or D to copy value of 2 or 4 bytes respectively.
s = bytearray(chr(0x4f) * 32)
I'm using bytearray instead of string because we'll need to modify part of this value (strings are immutable in Python).
Skip a few lines of useless code until:
.text:00401401 rdtsc .text:00401403 mov ebx, eax .text:00401405 push ebx .text:00401406 push 40903Dh .text:0040140B call _printf
From the Intel manual:
The RDPMC (read performance-monitoring counter) and RDTSC (read time-stamp counter) instructions allow application programs to read the processor’s performance-monitoring and time-stamp counters, respectively.
The RDTSC instruction loads the current count of the time-stamp counter into the EDX:EAX registers.
Basically in this case the instruction is used as a pseudorandom number generator. The EAX part of the result is used as an User ID and stored in EBX for later usage.
Our keygen will ask this value:
uid = int(raw_input("Enter User ID: "))
.text:00401413 mov edi, 40D020h .text:00401418 bt ebx, 0 .text:0040141C jnb short zero_bit_set .text:0040141E mov byte ptr [edi], 2Ah .text:00401421 zero_bit_set: [...]
The value 40D020h already came up, but what it is? By observing all the remaining instructions of the program we can deduct that this is the memory location that will hold the final key (that we have to
guess calculate) computed using a custom algorithm and the user ID random value.
So, quick recap:
EDI - pointer to the memory location where the key is stored. This at the moment contains a string of 32 "O"s
EBX - the random User ID
The BT instruction takes the bit 0 of EBX and stores the result in the Carry Flag (CF). JNB jumps if CF is 0.
We are basically checking if the user ID number is even or not. If the number is odd the first value of the key will be 2Ah (symbol "*") otherwise remains "O".
Let's add this into our python script:
if ((uid % 2) != 0): s = chr(0x2a)
Then the program checks if the number of user ID is bigger than 0B16B00B5h :))
; zero_bit_set: .text:00401421 inc edi .text:00401422 cmp ebx, 0B16B00B5h .text:00401428 ja short above_b16b00b5h .text:0040142A mov byte ptr [edi], 2Ah .text:0040142D above_b16b00b5h: [...]
if not, change the second value of the key to "*"
if (uid <= 0x0b16b00b5): s = chr(0x2a)
Then the Parity Flag (PF) is evaluated. This is, however, done over the value of EDI, which now will be 40D022h (we "inc EDI" twice since now). 404D022h in binary is "10000001101000000100000" and so the PF will always be 0.
; above_b16b00b5h: .text:0040142D inc edi .text:0040142E jnp short no_parity .text:00401430 mov byte ptr [edi], 2Ah .text:00401433 no_parity: [...]
the jump won't be taken and the third value of the key will always be "*".
Same for the 4th as shown in the next two lines:
; no_parity: .text:00401433 inc edi .text:00401434 mov byte ptr [edi], 2Ah
Let's implement this in Python:
s = chr(0x2a) s = chr(0x2a)
and analyze the next instructions:
.text:00401437 mov ecx, 1Ch .text:0040143C get_byte: .text:0040143C shr ebx, 1 .text:0040143E mov edx, 0 .text:00401443 mov eax, ebx .text:00401445 mov esi, 1Ah .text:0040144A div esi .text:0040144C test ecx, 1 .text:00401452 jz short add_97 .text:00401454 add edx, 41h .text:00401457 mov [edi], dl .text:00401459 inc edi .text:0040145A loop get_byte .text:0040145C jmp short go_on .text:0040145E add_97: .text:0040145E add edx, 61h .text:00401461 mov [edi], dl .text:00401463 inc edi .text:00401464 loop get_byte .text:00401466 .text:00401466 go_on: [...]
This is obviously a loop as we can see from instruction at address 00401464.
LOOP decreases ECX and jumps to the specified address until ECX is 0. This is done 28 times (ECX is 1Ch).
PS.: remember that our EDI register still points to the third value of our key, since EDI was not increased after the last mov instruction at 00401434
What every cycle does is:
- shift right the value of User ID by 1 bit
- take the remainder of the division between User ID and 1Ah
- if the value of ECX is even, add 61h to the remainder; else, add 41h to the remainder
- save the remainder value to EDI (our key, remember?)
- increase EDI
- decrease ECX
for x in range(28,0,-1): uid = uid >> 1 z = uid % 26 if ((x % 2) == 0): z += 0x61 else: z += 0x41 s[31-x] = chr(z) # starts from s
At the end we'll have the final key value.
The remaining instructions of the program ask the operator to insert the key, and the value entered is compared with the one just computed in memory.
If they are equal... WIN!
Our final python script:
s = bytearray(chr(0x4f)*32) uid = int(raw_input("Enter User ID: ")) if ((uid % 2) != 0): s = chr(0x2a) if (uid <= 0x0b16b00b5): s = chr(0x2a) s = chr(0x2a) s = chr(0x2a) for x in range(28,0,-1): uid = uid >> 1 z = uid % 26 if ((x % 2) == 0): z += 0x61 else: z += 0x41 s[31-x] = chr(z) # starts from s print ("Password: %s" % s )