Solution Detten crackme 10

Saturday, August 16 2008 @ 10:55 AM CEST

Contributed by: detten

Level : beginner

DETTEN CRACKME #10 [file:20080313183208535 download here]
******************

Tools: Softice, Procdump, LordPE, Hexeditor, Filescanner
Level 1-10: 4



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:

0167:00413063 53 PUSH EBX
0167:00413064 50 PUSH EAX
0167:00413065 FF95284B4400 CALL [EBP+00444B28]
0167:0041306B 898528404400 MOV [EBP+00444028],EAX ; si pops up here
0167:00413071 8D9D4A4A4400 LEA EBX,[EBP+00444A4A]
0167:00413077 53 PUSH EBX
0167:00413078 57 PUSH EDI
0167:00413079 FF95284B4400 CALL [EBP+00444B28]
0167:0041307F 89852C404400 MOV [EBP+0044402C],EAX
0167:00413085 8D85C1394400 LEA EAX,[EBP+004439C1]
0167:0041308B FFE0 JMP EAX
0167:0041308D 00F0 ADD AL,DH

We start tracing with <F10>, always looking out for a call to address 40xxxx. Soon there's a call edi, with edi = 401000:

0167:0041311A 8B3E MOV EDI,[ESI]
0167:0041311C 03BD284A4400 ADD EDI,[EBP+00444A28]
0167:00413122 FF37 PUSH DWORD PTR [EDI]
0167:00413124 C607C3 MOV BYTE PTR [EDI],C3
0167:00413127 FFD7 CALL EDI ; call 401000
0167:00413129 8F07 POP DWORD PTR [EDI]

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


The first call checks this:

00401197 55 push ebp
00401198 8B EC mov ebp, esp
0040119A 8B 45 08 mov eax, [ebp+8]
0040119D 0F BE 10 movsx edx, byte ptr [eax] ; s[0]
004011A0 83 FA 41 cmp edx, 41h ; >= 41h ?
004011A3 7D 04 jge short loc_4011A9 ; yes, jump
004011A5 33 C0 xor eax, eax
004011A7 5D pop ebp
004011A8 C3 retn
004011A9 ; ---------------------------------------------------------------------------
004011A9 8A 50 01 mov dl, [eax+1] ; s[1]
004011AC 32 50 05 xor dl, [eax+5] ; xor s[5]
004011AF 0F BE D2 movsx edx, dl
004011B2 0F BE 48 02 movsx ecx, byte ptr [eax+2]
004011B6 33 D1 xor edx, ecx ; xor s[2]
004011B8 83 F2 16 xor edx, 16h ; xor 16h
004011BB 3A 50 0C cmp dl, [eax+0Ch] ; == s[12] ?
004011BE 74 04 jz short loc_4011C4 ; yes, jmp to set flag
004011C0 33 C0 xor eax, eax
004011C2 5D pop ebp
004011C3 C3 retn
004011C4 ; ---------------------------------------------------------------------------
004011C4 B8 01 00 00 00 mov eax, 1 ; set flag
004011C9 5D pop ebp
004011CA C3 retn

Two checks are performed here:

s[0] >= 41h
s[1] xor s[5] xor s[2] xor 16h = s[12]

The second call is similar:

0040115A 55 push ebp
0040115B 8B EC mov ebp, esp
0040115D 8B 55 08 mov edx, [ebp+8]
00401160 8A 42 05 mov al, [edx+5] ; s[5]
00401163 32 42 01 xor al, [edx+1] ; xor s[1]
00401166 32 42 06 xor al, [edx+6] ; xor s[6]
00401169 0F BE C8 movsx ecx, al
0040116C 0F BE 42 02 movsx eax, byte ptr [edx+2]
00401170 0F AF C8 imul ecx, eax ; * s[2]
00401173 8B C1 mov eax, ecx
00401175 32 42 02 xor al, [edx+2] ; xor s[2]
00401178 3A 42 06 cmp al, [edx+6] ; == s[6] ?
0040117B 74 04 jz short loc_401181 ; yes, go on
0040117D 33 C0 xor eax, eax
0040117F 5D pop ebp
00401180 C3 retn
00401181 ; ---------------------------------------------------------------------------
00401181 32 42 07 xor al, [edx+7] ; s[6] xor s[7]
00401184 32 42 0A xor al, [edx+0Ah] ; xor s[10]
00401187 3A 42 08 cmp al, [edx+8] ; == s[8] ?
0040118A 74 04 jz short loc_401190 ; yes, jmp to set flag
0040118C 33 C0 xor eax, eax
0040118E 5D pop ebp
0040118F C3 retn
00401190 ; ---------------------------------------------------------------------------
00401190 B8 01 00 00 00 mov eax, 1 ; set flag
00401195 5D pop ebp
00401196 C3 retn

Two more checks are performed:

((s[5] xor s[1] xor s[6]) * s[2]) xor s[2] == s[6]
s[6] xor s[7] xor s[10] = s[8]

That sounds interesting already, but let's have a look at the last call:

00401108 55 push ebp
00401109 8B EC mov ebp, esp
0040110B 8B 4D 08 mov ecx, [ebp+8]
0040110E 8A 41 0A mov al, [ecx+0Ah] ; s[10]
00401111 32 41 07 xor al, [ecx+7] ; xor s[7]
00401114 2A 41 0B sub al, [ecx+0Bh] ; - s[11]
00401117 0F BE D0 movsx edx, al
0040111A 0F BE 41 0C movsx eax, byte ptr [ecx+0Ch]
0040111E 0F AF D0 imul edx, eax ; * s[12] --> (a)
00401121 8B C2 mov eax, edx
00401123 8A 11 mov dl, [ecx] ; s[0]
00401125 2A 51 0B sub dl, [ecx+0Bh] ; - s[11]
00401128 32 C2 xor al, dl ; xor a
0040112A 3A 41 03 cmp al, [ecx+3] ; == s[3] ?
0040112D 74 04 jz short loc_401133 ; yes, go on
0040112F 33 C0 xor eax, eax
00401131 5D pop ebp
00401132 C3 retn
00401133 ; ---------------------------------------------------------------------------
00401133 33 D2 xor edx, edx ; ------ start loop
00401135 B0 36 mov al, 36h ; al = 36h
00401137 02 04 11 add al, [ecx+edx] ; al += s[i] --> (b)
0040113A 42 inc edx ; i++
0040113B 83 FA 0D cmp edx, 0Dh ; i < 0Dh ?
0040113E 7C F7 jl short loc_401137 ; yes, next char
00401140 32 41 08 xor al, [ecx+8] ; b xor s[8]
00401143 0F BE D0 movsx edx, al
00401146 8B C2 mov eax, edx
00401148 F7 D8 neg eax ; * (-1)
0040114A 3A 41 0D cmp al, [ecx+0Dh] ; == s[13] ?
0040114D 74 04 jz short loc_401153 ; yes, jmp to set flag
0040114F 33 C0 xor eax, eax
00401151 5D pop ebp
00401152 C3 retn
00401153 ; ---------------------------------------------------------------------------
00401153 B8 01 00 00 00 mov eax, 1 ; set flag
00401158 5D pop ebp
00401159 C3 retn

Again there are two checks:

(((s[10] xor s[7]) - s[11]) * s[12]) xor (s[0] - s[11]) = s[3]
- ((36h + sum (s[0]...s[12])) xor s[8]) = s[13]


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.

Let's see what we have:

1. ls = 0Eh
2. s[4] = s[9] = 2Dh
3. s[12] = s[1] xor s[2] xor s[5] xor 16h
4. s[6] = ((s[5] xor s[1] xor s[6]) * s[2]) xor s[2]
5. s[8] = s[6] xor s[7] xor s[10]
6. s[3] = (((s[10] xor s[7]) - s[11]) * s[12]) xor (s[0] - s[11])
7. s[13] = - ((36h + sum (s[0]...s[12])) xor s[8])

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):

3a. s[12] = s[2] xor 16h
4a. s[6] = (s[6] * s[2]) xor s[2] --> s[2] xor s[6] = s[2] * s[6]

We set s[6] = s[8] to simplify equations 5 and 6:

5a: s[8] = s[10]
6a: s[3] = (- s[11] * s[12]) xor (s[0] - s[11])

To make equation 6a even simpler, we set s[0] = s[11]:

6b: s[3] = - s[11] * s[12]


Now we have the following:

s[4] = s[9] = 2Dh
s[0] = s[11]
s[1] = s[5]
s[6] = s[8]
s[7] = s[10]
s[12] = s[2] xor 16h
s[2] xor s[6] = s[2] * s[6]
s[3] = - s[11] * s[12]
s[13] = - ((36h + sum (s[0]...s[12])) xor s[8])

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;

iStrLen = GetDlgItemTextA (hWnd, EDF_NAME, szBuffer, 6);

if ((iStrLen < 4) || (szBuffer[0] < 'A'))
SetDlgItemTextA (hWnd, EDF_SERIAL, NULL);
else
{
szSerial[4] = '-';
szSerial[9] = '-';

for (szSerial[1] = szBuffer[0]; szSerial[1] <= 'z'; szSerial[1]++)
{
for (szSerial[6] = szBuffer[1]; szSerial[6] <= 'z'; szSerial[6]++)
{
for (szSerial[7] = szBuffer[2]; szSerial[7] <= 'z'; szSerial[7]++)
{
for (szSerial[11] = szBuffer[3]; szSerial[11] <= 'z'; szSerial[11]++)
{
for (szSerial[2] = 'A'; szSerial[2] <= 'z'; szSerial[2]++)
{
szSerial[12] = szSerial[2] ^ 0x16;
if ((((szSerial[2] * szSerial[6]) & 0xFF) == (szSerial[2] ^ szSerial[6])) && (szSerial[12] >= 0x20))
{
szSerial[3] = (szSerial[11] * (-1) * szSerial[12]) & 0xFF;
szSerial[0] = szSerial[11];
szSerial[5] = szSerial[1];
szSerial[8] = szSerial[6];
szSerial[10] = szSerial[7];
for (iSum = 0x36, i = 0; i < 13; i++)
{
iSum += szSerial[i];
}
szSerial[13] = (-1) * (szSerial[8] ^ iSum);
SetDlgItemTextA (hWnd, EDF_SERIAL, szSerial);
break;
}
}
}
}
}
}
}
return;
}

Tutorial by figugegl.

0 comments



http://www.reversing.be/article.php?story=2008081610554529