逆向一个非常简单的crackme(2)

再来看一个。

reverseMe

首先打开程序

wpsB4A2.tmp

提示说版本过期之类的。。

od载入

程序很短,不需要跑,直接观察就行

往下拉来到这段

wpsB4E1.tmp

可以看到,程序在初始的一些操作之后,用CreateFile的OPEN_EXISTING这种方式打开一个叫Keyfile.dat的文件,实际上就是检查这个文件是否存在

再往下

wpsB4F2.tmp

发现是读该文件的前70个字节pBytesRead这个指针指向的内存地址存放的是实际读取的字符数

下面一段是关键

004010A9   call    <jmp.&KERNEL32.ReadFile>            ; \ReadFile

004010AE   test    eax, eax

004010B0   jnz     short 004010B4                               ;ReadFile成功时返回非零值

004010B2   jmp     short 004010F7                             ;跳过去则会提示Keyfile is not valid. Sorry.”

004010B4   xor     ebx, ebx

004010B6   xor     esi, esi

004010B8   cmp     dword ptr [402173], 10                 ;key的长度必须>=10h,即16

004010BF   jl      short 004010F7

004010C1   mov     al, byte ptr [ebx+40211A]              ;循环操作读取进来的key

004010C7   cmp     al, 0                                                ;字符串以0结尾,也就是处理到结束

004010C9   je      short 004010D3

004010CB   cmp     al, 47                                              ;ascii码47h=’G’

004010CD   jnz     short 004010D0

004010CF   inc     esi                                                     ;esi是在计算’G’的出现次数

004010D0   inc     ebx

004010D1   jmp     short 004010C1

004010D3   cmp     esi, 8                                                ;’G’至少出现8次才行

004010D6   jl      short 004010F7

004010D8   jmp     00401205                                         ;调到提示成功的地方

由上面的分析可知key的内容是:(1)长度>=16    (2)’G’至少出现8次

这样就可以写出Keyfile.dat了。

wpsB512.tmp

逆向一个非常简单的crackme

 

今天看了下逆向破解的内容,发现大一时学的东西已经忘得差不多了。。本来对这个是挺感兴趣的,但是大二开始入了acm队,专心搞acm去了,所以逆向工程这一块就荒废了,因而至今在逆向破解这一块仍是什么也不懂的弱渣。正好这段时间有空,所以想重新拾起来,然后写下过程,给自己也给想学crack的初学者。

还是先从最简单的开始。

TraceMe
(注:本文写给crack初学者,写的会比较详细。。大牛请直接右上角。。)

K[T{S0{R6HKB5X3H]W%HHBT[4]

发现要输入用户名和序列号,先随意输入

4KZ6(%HY79CY36Q%PZUO5LB

错误时弹出一个messagebox,上面有字符串”序列号错误,再来一次!”

好,od载入,看下字符串参考

U~`DIDY`C6X6Q$B__CPXOKQ

好像没这个字符串。。。

然后ctrl+n看下导入表

6YE~1C{HU4_S951SW82G~2P

发现有GetDlgItemTextA,读入文本框输入不是这个就是GetWindowText,所以在这个函数上下断。

}PIJ2M3M207(AH6K32W9ZTY

重新载入,运行,输入用户名”abcde”

和密码123456.点确认。好,进程断在了刚刚下的断点处,F8单步几下回到程序领空。如下图

Z2IHI5SAX}_1VQN3C7J~IN7

好,继续F8往下

004011CA   .  8A4424 4C     mov     al, byte ptr [esp+4C];数据窗口找一下会发现esp+4c是用户名的地址
004011CE   .  84C0          test    al, al
004011D0   .  74 76         je      short 00401248
004011D2   .  83FB 05       cmp     ebx, 5
004011D5   .  7C 71         jl      short 00401248;到这里为止是判断用户名超度是否合法
004011D7   .  8D5424 4C     lea     edx, dword ptr [esp+4C]
004011DB   .  53            push    ebx;用户名长度
004011DC   .  8D8424 A00000>lea     eax, dword ptr [esp+A0];
004011E3   .  52            push    edx;用户名的内存首地址
004011E4   .  50            push    eax;输入的序列号的内存首地址
004011E5   .  E8 56010000   call    00401340 ;由上面的参数可知这个是关键call,目测是生成序列号并比较,看下下面的代码也可以推断

call    00401340,F7跟进这个call

L{HRFFGEZT0V2)5%N[%3ZKH

先来看最下面这段

00401379  |> \56            push    esi                              ; /<%ld>
0040137A  |.  68 78504000   push    00405078                         ; |Format = “%ld”
0040137F  |.  55            push    ebp                              ; |s
00401380  |.  FF15 9C404000 call    dword ptr [<&USER32.wsprintfA>]  ; \wsprintfA;将esi的内容写入到ebp指向的内容
00401386  |.  8B4424 1C     mov     eax, dword ptr [esp+1C]
0040138A  |.  83C4 0C       add     esp, 0C
0040138D  |.  55            push    ebp                              ; /String2
0040138E  |.  50            push    eax                              ; |String1;我们输入的序列号
0040138F  |.  FF15 04404000 call    dword ptr [<&KERNEL32.lstrcmpA>] ; \lstrcmpA;注意这里
00401395  |.  F7D8          neg     eax
00401397  |.  1BC0          sbb     eax, eax
00401399  |.  5F            pop     edi
0040139A  |.  5E            pop     esi
0040139B  |.  40            inc     eax
0040139C  |.  5D            pop     ebp
0040139D  \.  C3            retn

显然edi是正确的序列号,strcmpA将我们输入的序列号与正确的序列号比较。可见上面得出edi的那部分代码就是关键的生成正确序列号的部分

来看一下

00401340  /$  55            push    ebp
00401341  |.  8B6C24 0C     mov     ebp, dword ptr [esp+C];用户名首址
00401345  |.  56            push    esi
00401346  |.  57            push    edi
00401347  |.  8B7C24 18     mov     edi, dword ptr [esp+18];用户名长度
0040134B  |.  B9 03000000   mov     ecx, 3
00401350  |.  33F6          xor     esi, esi
00401352  |.  33C0          xor     eax, eax
00401354  |.  3BF9          cmp     edi, ecx;用户名长度<=3?
00401356  |.  7E 21         jle     short 00401379
00401358  |.  53            push    ebx
00401359  |>  83F8 07       /cmp     eax, 7;这里开始是关键
0040135C  |.  7E 02         |jle     short 00401360
0040135E  |.  33C0          |xor     eax, eax;eax>7时清零
00401360  |>  33D2          |xor     edx, edx
00401362  |.  33DB          |xor     ebx, ebx
00401364  |.  8A1429        |mov     dl, byte ptr [ecx+ebp];可见,每次取用户名的一个字符,而且是第3个开始
00401367  |.  8A98 30504000 |mov     bl, byte ptr [eax+405030];这里是一组常数,eax在0~7循环,所以是8个常数
0040136D  |.  0FAFD3        |imul    edx, ebx
00401370  |.  03F2          |add     esi, edx
00401372  |.  41            |inc     ecx
00401373  |.  40            |inc     eax
00401374  |.  3BCF          |cmp     ecx, edi;直到用户名的字符取完
00401376  |.^ 7C E1         \jl      short 00401359

这里单步运行一下,观察各个寄存器的值,会发现ebp指向用户名,ecx=3,所以其实是每次把用户名从第3个字符(编号从0开始)开始,每个字符和405030这个地址开始的一组常数分别相乘再相加,结果放在esi里面。

数据窗口查看405030这个地址

X`F%%}HR600UR%8]U6@YZ~3

好,这8个数找到了,接下来写出注册机,代码如下:

#include<stdio.h>
#include<string.h>
int b[]={0x0c,0x0a,0x13,0x09,0x0c,0x0b,0x0a,0x08};
int ans;
int main()
{
    int i,j;
    char s[110];
    while(~scanf("请输入用户名:%s",s))
    {
        ans=0;j=0;
        int len=strlen(s);
        if(len<=3){printf("用户名长度至少为4\n");continue;}
        for(i=3;i<=len-1;i++)
        {
            ans+=s[i]*b[j];
            j++;
            if(j==7) j=0;
        }
        printf("序列号是:%d\n",ans);

    }
    return 0;
}

这样就OK了。