The first part of this tutorial is about unpacking Aspack 1.08.04 by hand to get the program's listing. Then we reverse the algorithm and finally code a keygenerator.
a) Manual unpacking
-------------------
As usual, we load the target into our favourite Fileanalyzer - i always use viper's FileinsPEctor XL. We see that the program is packed with Aspack 1.08.04. We also have a look at the imports. There are only four, among them GetProcAddress. The lazy cracker takes Caspr to automatically unpack the program, but we're going to do it the hard way. I always try to unpack by hand first, and only if i fail, i'll take a generic unpacker.
We load Icedump, set a bpx GetProcAddress in Softice and start the crackme. Softice pops up here:
We step into the call with <F8> but see a ret command only - this seems to be a little trick to distract crackers. There are a couple of "backward-jumps", in this case we set a bpx to the instruction following the jump and let Softice run with <F5>. But we have to disable all other breakpoints to prevent Softice from popping up at the same address several times. Eventually we come to the jump to the original entry point (OEP). Well, actually it's not a jump but a return, but with the same effect:
0167:004133A0 B800100000 MOV EAX,00001000
0167:004133A5 50 PUSH EAX
0167:004133A6 0385284A4400 ADD EAX,[EBP+00444A28]
0167:004133AC 59 POP ECX
0167:004133AD 0BC9 OR ECX,ECX
0167:004133AF 8985F13C4400 MOV [EBP+00443CF1],EAX
0167:004133B5 61 POPAD
0167:004133B6 7508 JNZ 004133C0
0167:004133B8 B801000000 MOV EAX,00000001
0167:004133BD C20C00 RET 000C
0167:004133C0 6800104000 PUSH 00401000 ; address of OEP
0167:004133C5 C3 RET ; jump to OEP
Now we dump the file. Before we can do that, we put the program in a endless loop, so it doesn't mess up the unpacked program in memory. Type: "a <enter> jmp eip <enter> <esc> <f5>". Now let's do a fulldump with Procdump. In the Options - Import section we mark "rebuild new import table", this makes it usually much easier for us to get a fully working program and a listing that shows all import functions correctly.
We're almost done: we only have to correct the entry point. Load the dumped file in Procdump's PE-Editor and set the EP to:
OEP - Image Base = 401000h - 400000h = 1000h
We start the program and IT WORKS! The listing looks good as well, there are all import functions visible.
Now let's crack the crackme...
b) Reversing the algo
---------------------
Now that we can disassemble the crackme we have a look at the listing, especially the part with the function GetDlgItemTextA. We set a bpx GetDlgItemTextA in Softice:
004011E3 6A 0F push 0Fh
004011E5 FF 75 0C push dword ptr [ebp+0Ch]
004011E8 6A 67 push 67h
004011EA 56 push esi
004011EB E8 70 8C 00 00 call j_GetDlgItemTextA ; get serial
004011F0 83 FB 0E cmp ebx, 0Eh ; length ls == 0Eh ?
004011F3 7F 05 jg short loc_4011FA ; jmp if ls > 0Eh
004011F5 83 FB 0E cmp ebx, 0Eh ; ls == 0Eh ?
004011F8 7D 04 jge short loc_4011FE ; yes, go on
004011FA 33 C0 xor eax, eax
004011FC EB 05 jmp short loc_401203
004011FE B8 01 00 00 00 mov eax, 1 ; set flag
00401203 5E pop esi
The serials' length has to be 0Eh. After the return we come here:
00401286 8D 4D F0 lea ecx, [ebp-10h]
00401289 51 push ecx
0040128A FF 75 08 push dword ptr [ebp+8]
0040128D E8 39 FF FF FF call loc_4011CB ; get serial
00401292 83 C4 08 add esp, 8
00401295 85 C0 test eax, eax ; flag == 0 ?
00401297 75 07 jnz short loc_4012A0 ; no, go on
00401299 33 C0 xor eax, eax
0040129B E9 83 00 00 00 jmp loc_401323 ; leave procedure
004012A0 ; ---------------------------------------------------------------------------
004012A0 0F BE 55 F4 movsx edx, byte ptr [ebp-0Ch] ; s[4]
004012A4 83 FA 2D cmp edx, 2Dh ; == 2Dh ?
004012A7 75 09 jnz short loc_4012B2 ; no, error
004012A9 0F BE 4D F9 movsx ecx, byte ptr [ebp-7] ; s[9]
004012AD 83 F9 2D cmp ecx, 2Dh ; == 2Dh ?
004012B0 74 07 jz short loc_4012B9 ; yes, go on
004012B2 33 C0 xor eax, eax
004012B4 EB 6D jmp short loc_401323 ; leave procedure
004012B4 ; ---------------------------------------------------------------------------
The fifth (s[4]) and tenth (s[9]) char must be '-' (2Dh). Let's type in a (valid) serial, e.g. ABCD-EFGH-IJKL and try again. The above checks should be ok, but what happens next:
004012B6 90 nop
004012B7 90 nop
004012B8 90 nop
004012B9 8D 55 F0 lea edx, [ebp-10h]
004012BC 52 push edx
004012BD E8 D5 FE FF FF call loc_401197 ; call 1
004012C2 59 pop ecx
004012C3 85 C0 test eax, eax ; flag == 0 ?
004012C5 75 07 jnz short loc_4012CE ; no, go on
004012C7 33 C0 xor eax, eax
004012C9 EB 58 jmp short loc_401323 ; leave procedure
004012C9 ; ---------------------------------------------------------------------------
004012CB 90 nop
004012CC 90 nop
004012CD 90 nop
004012CE 8D 55 F0 lea edx, [ebp-10h]
004012D1 52 push edx
004012D2 E8 83 FE FF FF call loc_40115A ; call 2
004012D7 59 pop ecx
004012D8 85 C0 test eax, eax ; flag == 0 ?
004012DA 75 07 jnz short loc_4012E3 ; no, go on
004012DC 33 C0 xor eax, eax
004012DE EB 43 jmp short loc_401323 ; leave procedure
004012DE ; ---------------------------------------------------------------------------
004012E0 90 nop
004012E1 90 nop
004012E2 90 nop
004012E3 8D 55 F0 lea edx, [ebp-10h]
004012E6 52 push edx
004012E7 E8 1C FE FF FF call loc_401108 ; call 3
004012EC 59 pop ecx
004012ED 85 C0 test eax, eax ; flag == 0 ?
004012EF 75 0C jnz short loc_4012FD ; no, go on
004012F1 90 nop
004012F2 90 nop
004012F3 90 nop
004012F4 90 nop
004012F5 33 C0 xor eax, eax
004012F7 EB 2A jmp short loc_401323 ; leave procedure
004012F7 ; ---------------------------------------------------------------------------
004012F9 90 nop
004012FA 90 nop
004012FB 90 nop
004012FC 68 db 68h ; h
004012FD 68 F2 A0 40 00 push offset aYouDidItURTheM ; "You did it ! U r the man !"
00401302 6A 67 push 67h
00401304 FF 75 08 push dword ptr [ebp+8]
00401307 E8 78 8B 00 00 call j_SetDlgItemTextA
0040130C EB 10 jmp short loc_40131E
Well, this looks like a math-crackme! There are two ways to solve this: plain brute forcing or solving the equations. Of course we solve the equations and see what else can be done to kill the beast.
We can't solve it like this, because there are more unknown variables than equations. We try to simplify the whole stuff a bit. We set s[1] = s[5] to erase a couple of xors in eq. 3 and 4 (x xor x = 0; and x xor 0 = x):
That's it, we don't need more information for the keygen. As i found out there are plenty of solutions, so we can type in s[0], s[1], s[6] and s[7] and calculate the rest. That way we only have to brute force s[2].
In my keygen you can type in a 4-char string, whom it calculates the serial from. But there isn't a serial for every combination. Is this the case, i increase the chars of the string until i find a valid serial. Here the code in c without explanation:
/*------------------------------------------------------------------------
Procedure: CalculateSerial
Purpose: Calculate a valid serial
Input: hWnd: Handle of the Dialogbox
Output: None
Errors: None
------------------------------------------------------------------------*/
void CalculateSerial (HWND hWnd)
{
char szSerial[16] = "";
char szBuffer[6];
unsigned char i, iSum;
int iStrLen;