Join the discord

crackmes.de : CrackMe v1.1 by Greedy_Fly

16 Oct, 2013 22:33
Here's how i defeated that challenge.

Let's see what we have here:


The crackme is seems to be written in assembly language (MASM/FASM?) so the code is pretty clear.
As usual I've loaded the EXE in ollydbg to take a quick look over the code.

I will split the code in few parts.
First of all we have the Serial number length validation:

asm / disassembly004012FE  |. 0F87 45010000	JA CrackMe.00401449
00401304  |. 68 78304000	PUSH CrackMe.00403078		; Serial number buffer
00401309  |. E8 22FEFFFF	CALL CrackMe.00401130		; strlen routine
0040130E  |. BA C6E10000	MOV EDX,0E1C6			; / Serial length validation
00401313  |. BB 86E30000	MOV EBX,0E386			; |
00401318  |. A3 B8304000	MOV DWORD PTR DS:[4030B8],EAX	; | EAX holds the Serial length
0040131D  |. 9B 		WAIT				; | NO, YOU WAIT!
0040131E  |. DBE3	 	FINIT				; |
00401320  |. DB05 00304000	FILD DWORD PTR DS:[403000]	; | Hardcoded to 2
00401326  |. DB05 B8304000	FILD DWORD PTR DS:[4030B8]	; | Serial length X
0040132C  |. D9F1		FYL2X				; | ST1*Log2(ST0) => 2*Log2(X)
0040132E  |. D9C0		FLD ST0				; | copy ST0 to ST1, so both are now equal
00401330  |. D9FC		FRNDINT				; | Convert ST0 to integer
00401332  |. D9C9		FXCH ST1			; | Exchange ST0 with ST1
00401334  |. D8E1		FSUB ST0,ST1			; | ST0-ST1
00401336  |. D9F0		F2XM1				; | 2pow(ST0-ST1)
00401338  |. D9E8		FLD1				; | Push 1 to the ST stack
0040133A  |. DEC1		FADDP ST1,ST0			; | Add ST1 to ST0
0040133C  |. D9FD		FSCALE 				; | ST0*2pow(ST1)
0040133E  |. DB1D BC304000	FISTP DWORD PTR DS:[4030BC]	; | POP the result to addr 004030BC
00401344  |. A1 BC304000	MOV EAX,DWORD PTR DS:[4030BC]	; |
00401349  |. 33C2		XOR EAX,EDX			; | EAX^EDX => result^0xE1C6
0040134B  |. 2BC3		SUB EAX,EBX			; \ EAX-EBX => EAX should be 0
0040134D  |. 74 06		JE SHORT CrackMe.00401355	; We must jump here!
0040134F  |. 68 49144000	PUSH CrackMe.00401449
00401354  |. C3 		RETN				; Bad RET.

Using the FPU to calculate the valid length is quite fancy.
I've "cheated" here since I'm lazy (and bad in math), so I write a bruteforce to determine the right value.
After all, the textbox can handle only 51 characters and the algorithm is using only simple math operations, sooo... yeah.

C / Code::Blocks#include <math.h>
#include <stdio.h>

int main() {

    double result;
    for(int i = 0; i < 100; i++) {
        result = 2*log2(i);
        result = pow(2, result-(int)result) * pow(2, (int)result);
        if ((0xE1C6 ^ (int)round(result)) == 0xE386) {
            printf("Valid length: %d\n", i);
        }
    }

    return 0;
}

The result is 24, so the only valid serial number length is 24 bytes.

asm / disassembly00401355  |> 33C0		XOR EAX,EAX
00401357  |. 68 6C134000	PUSH CrackMe.0040136C
0040135C  |. 64:FF30		PUSH DWORD PTR FS:[EAX]
0040135F  |. 64:8920		MOV DWORD PTR FS:[EAX],ESP
00401362  |. 68 429C0000	PUSH 9C42				; / WHY?!
00401367  |. E8 34020000	CALL <JMP.&kernel32.CloseHandle>	; \

I don't know why this CloseHandle is here, but if it's not added by incident, then it's probably here to freak out the debugger.

Next is the actual validation, and I'm again splitting this code in parts:

asm / disassembly0040136C  |. 68 98304000	 PUSH CrackMe.00403098
00401371  |. 68 78304000	 PUSH CrackMe.00403078
00401376  |. E8 2B010000	 CALL CrackMe.004014A6		; Convert the Serial number string to three DWORD values
0040137B  |. 8B1D 98304000	 MOV EBX,DWORD PTR DS:[403098]	; Serial[0-8]
00401381  |. 8B35 9C304000	 MOV ESI,DWORD PTR DS:[40309C]	; Serial[8-16]
00401387  |. 8B3D A0304000	 MOV EDI,DWORD PTR DS:[4030A0]	; Serial[16-24]
0040138D  |. 8B0D B8304000	 MOV ECX,DWORD PTR DS:[4030B8]	; Serial number length - 24

The serial number is split into three dwords. So, if i use a 111111112222222233333333 as serial, the three values will be 0x11111111, 0x22222222, 0x33333333.
It's important to mention here, that the valid code can contain only numbers and the letters A B C D E and F.

Next is this code.
asm / disassembly00401393  |. 0F6EC7		MOVD MM0,EDI			; MM0 = Serial[16-24]
00401396  |. BF 225A78E1	MOV EDI,E1785A22
0040139B  |. 2BD2		SUB EDX,EDX			;/ EDX = 0
0040139D  |. 42			INC EDX				;\ EDX = 1
0040139E  |. 0F6ECE		MOVD MM1,ESI			; MM1 = Serial[8-16]
004013A1  |. 0F6ED3		MOVD MM2,EBX			; MM2 = Serial[0-8]
004013A4  |. 0F6EDA		MOVD MM3,EDX			; MM3 = 1
004013A7  |. 0F6EE7		MOVD MM4,EDI			; MM4 = 0xE1785A22
004013AA  |> 0FEFD0		PXOR MM2,MM0			;/ loop
004013AD  |. 0FFECB		PADDD MM1,MM3			;|
004013B0  |. 0FFAC1		PSUBD MM0,MM1			;|
004013B3  |. 0FEFC4		PXOR MM0,MM4			;|
004013B6  |.^E2 F2		LOOPD SHORT CrackMe.004013AA	;\
004013B8  |. 0F7EC7		MOVD EDI,MM0
004013BB  |. 0F7ECE		MOVD ESI,MM1
004013BE  |. 0F7ED3		MOVD EBX,MM2
004013C1  |. 57			PUSH EDI			; / MM0 note these!
004013C2  |. 56			PUSH ESI			; | MM1
004013C3  |. 53			PUSH EBX			; \ MM2

The loop will make some MMX math and binary operations and will run exactly 24 times, since that's the length of the valid serial, and is set in ECX.
At first I tough that this will be the hard part, but then again, if you look closer, you will see that MM1 here is changed by only adding 1 every time the loop runs.
So, at the end of the loop, the original MM1 (serial[8-16]) will be MM1+24.
Another important thing here is the final PUSH of all the calculated parts, so keep that in mind.

This here is the first of all the three validations:

asm / disassembly004013C4  |. 8D34B5 00000000	LEA ESI,DWORD PTR DS:[ESI*4]	; ESI *= 4
004013CB  |. D1E3		SHL EBX,1			; EBX <<= 1
004013CD  |. 03FE		ADD EDI,ESI			; ESI += ESI
004013CF  |. 2BFB		SUB EDI,EBX			; EDI -= EBX
004013D1  |. 83EC 04		SUB ESP,4			;/ Technically a PUSH EDI
004013D4  |. 893C24		MOV DWORD PTR SS:[ESP],EDI	;\
004013D7  |. 81EF 9C59F016	SUB EDI,16F0599C		;/ EDI must equal to 0x16F0599C
004013DD  |. 5F			POP EDI				;|
004013DE     74 06		JE SHORT CrackMe.004013E6	;\ We should jump here
004013E0  |. 68 49144000	PUSH CrackMe.00401449
004013E5  |. C3			RETN				; Bad RET.

If we simplify this code, we will get the following equation:

(EDI + (ESI * 4)) - (EBX << 1) = 0x16F0599C

Cool. Lets get to the next validation:
asm / disassembly004013E6  |> 5E			POP ESI				;/ MM1, again note these
004013E7  |. 5F			POP EDI				;| MM0
004013E8  |. 5B			POP EBX				;\ MM2
004013E9  |. 57			PUSH EDI
004013EA  |. 03FB		ADD EDI,EBX
004013EC  |. 8D3C3E		LEA EDI,DWORD PTR DS:[ESI+EDI]
004013EF  |. 81EF 72F36681	SUB EDI,8166F372		; EDI must equal to 0x8166F372
004013F5  |. 97			XCHG EAX,EDI
004013F6  |. 85C0		TEST EAX,EAX
004013F8     75 4F		JNZ SHORT CrackMe.00401449	; Bad Jump, we should continue here instead of jumping!
004013FA  |. 5F 		POP EDI

At the very beginning we have the POPs, so these values here are the original values from the LOOPD, before the previous equation.
So, lets simplify the code again:

(ESI + EDI + EBX) = 0x8166F372

That was simple, and note again the PUSH EDI at the beginning and the POP EDI at the end.

The final check is here:

asm / disassembly004013FB  |. 8D1C5D 00000000	LEA EBX,DWORD PTR DS:[EBX*2]
00401402  |. 895C24 FC		MOV DWORD PTR SS:[ESP-4],EBX	;/ Technically a PUSH EBX
00401406  |. 83EC 04		SUB ESP,4			;\
00401409  |. 2BDE		SUB EBX,ESI			; EBX should be equal to ESI
0040140B  |. 5B			POP EBX
0040140C     75 3B		JNZ SHORT CrackMe.00401449	; Bad jump, we should continue here instead of jumping!

Simplifying it will result to this:

(EBX * 2) = ESI

So, enough code reading lets sum up what we have:

We know that correct serial length should be 24 characters and we also have those three equation rules:

(EDI + (ESI * 4)) - (EBX << 1) = 0x16F0599C
(ESI + EDI + EBX) = 0x8166F372
(EBX * 2) = ESI


The correct serial should match all these rules, but remember those "note this" comments in the code?
Let's update the equations so everything gets even clearer:

(MM0 + (MM1 * 4)) - (MM2 << 1) = 0x16F0599C
(MM1 + MM0 + MM2) = 0x8166F372
(MM2 * 2) = MM1


Now we're cookin'!

After trying to make a logical equation that can be solved, I've decided to brute the serial. Now, don't get frustrated, it's going to be a simple brute force.

So what I've done was to write this:

C / Code::Blocks#include <math.h>
#include <stdio.h>

int main() {

    unsigned int MM0, MM1, MM2;

    for (MM0 = 0; MM0 < 0xFFFFFFFF; MM0++) {
        MM2 = MM0*2;
        if ((MM0*2) == MM2) {
            MM1 = 0x8166F372-(MM0+MM2);
            if (((MM0+(MM1*4))-(MM2<<1)) == 0x16F0599C) {
                printf("correct values for:\nMM0: %08X\nMM1: %08X\nMM2: %08X\n", MM0, MM1, MM2);
            }
        }
    }

    return 0;
}

On my machine this code will take about 3 seconds to complete:

consolecorrect values for:
MM0: 20FA5D14
MM1: 1E77DC36
MM2: 41F4BA28

These three are the correct values for MM0, MM1 and MM2, BUT after the LOOPD, so we have to reverse that loop to get the valid serial number parts.
I literally take the LOOPD loop and reverse it like this:

C / Code::Blocksfor(i = 24; i > 0; i--) {
   MM0 ^= 0xE1785A22;		// PXOR MM0,MM4
   MM0 += MM1;			// PSUBD MM0,MM1
   MM1--;			// PADDD MM1,MM3
   MM2 ^= MM0;			// PXOR MM2,MM0
}

At this point I have these values:
consolecorrect values for:
MM0: 7306FDD0
MM1: 1E77DC1E
MM2: 46041818

We are getting closer to the valid serial number. Finally, because the MMX MOVD instruction flipped the dword values and we need to BSWAP them (7306FDD0 become D0FD0673 and so on...), some inline assembly will help:
C / Code::Blocks__asm__("bswap %0" : "+r" (MM0));
__asm__("bswap %0" : "+r" (MM1));
__asm__("bswap %0" : "+r" (MM2));

And finally to group the serial:
C / Code::Blocksprintf("SERIAL: %08X%08X%08X\n", MM2, MM1, MM0);

And here's what i get:


The whole code should look like this:

C / Code::Blocks#include <math.h>
#include <stdio.h>

int main() {

    unsigned int MM0, MM1, MM2, i;

    char nfo[] = "===============================================================================\n"\
                 "= CrackMe v1.1, by Greedy_Fly @ crackmes.de                     [09. Oct, 2013]\n"\
                 "= Solution by XpoZed @ nullsecurity.org                         [16. Oct, 2013]\n"\
                 "===============================================================================\n\n";
    printf(nfo);

    printf("Calculating, please wait...\n\n");

    for (MM0 = 0; MM0 < 0xFFFFFFFF; MM0++) {
        MM2 = MM0*2;
        if ((MM0*2) == MM2) {
            MM1 = 0x8166F372-(MM0+MM2);
            if (((MM0+(MM1*4))-(MM2<<1)) == 0x16F0599C) {

                for(i = 24; i > 0; i--) {
                    MM0 ^= 0xE1785A22;
                    MM0 += MM1;
                    MM1--;
                    MM2 ^= MM0;
                }

                __asm__("bswap %0" : "+r" (MM0));
                __asm__("bswap %0" : "+r" (MM1));
                __asm__("bswap %0" : "+r" (MM2));

                printf("VALID SERIAL: %08X%08X%08X\n", MM2, MM1, MM0);
                break;
            }
        }
    }

    return 0;
}

I've added a break after the first valid code, but my method will find only one valid number anyways.
Well that's that. The CrackMe really make my gears grind, since like I mention, I really suck at math... also in coding, RE, social life, etc, etc...

Oh, and here's something funny from the Manifest section:
XML/Manifest<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity version="1.5.0.0" processorArchitecture="x86" name="Canterwood.Keygen" type="win32"/>
    <description>Canterwood's Keygen</description>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0"
		                processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*"/>
        </dependentAssembly>
    </dependency>
</assembly>

Comments

* You have an opinion? Let us all hear it!

There's no comments on this article yet. Be the first!
© nullsecurity.org 2011-2024 | legal | terms & rules | contacts