首页
社区
课程
招聘
Crackme中的游戏-----贪吃蛇(菜鸟的第一次发文)
2006-3-23 15:33 7126

Crackme中的游戏-----贪吃蛇(菜鸟的第一次发文)

2006-3-23 15:33
7126
Crackme中的游戏-----贪吃蛇
   某日,闲来无聊,从C盘浏览到F盘,又从F盘浏览到C盘。无意中翻出个CrackMe。反正无事,就当复习好了。不看不知道,一看还真不错,不只是单调的函数拉,贪吃蛇的小游戏一定玩过吧?注册的过程就是个贪吃蛇游戏。(本文适合和我一样入门级的朋友,所以注释得很罗嗦,高手莫笑。)
废话不多说~,FI检查,无壳。OllyDbg载入。这种CrackMe就是很适合我们菜鸟,无壳无花样。下断,到下面地址:
00401092        >>pushad                         //从这里开始,取得我们添入的信息,开始处理。
00401093        .>push 7F                       
00401095        .>push Snake.00401E00            
0040109A        .>push 3E8                       
0040109F        .>push dword ptr ds:[401F00]        
004010A5        .>call dword ptr ds:[<&USER32.Get>  ; \GetDlgItemTextA
004010AB        .>test eax,eax        ;下面一小段是判断是否输入姓名和注册码的,我们跳过一些代码
004010AD        .>jnz short Snake.004010CB
…………
00401103        >>call Snake.004012AA  ; 这个CALL对输入的注册码进行形式上判断
00401108        .>test al,al ; 跟进很容易知道注册码只能是0-9,A-F
0040110A        .>jnz short Snake.00401128  ;注册码不合要求,不跳出错
0040110C        .>push 30                        
0040110E        .>push Snake.00401400            ; |Title = "Error"
00401113        .>push Snake.00401464            ; |Text = "There is something wrong with your Serial..."
00401118        .>push dword ptr ds:[401F00]      ; |hOwner = 000B01A8 ('Snake ',class='#32770')
0040111E        .>call dword ptr ds:[<&USER32.Mes>; \MessageBoxA
00401124        .>popad
00401125        .>xor eax,eax
00401127        .>retn
00401128        >>call Snake.00401173             ;  这个CALL是将401B00到401BFF清零,在这区域两边添加16个FF,作为边界
0040112D        .>call Snake.0040119B   ;对名字进行处理的关键call
00401132        .>call Snake.0040120C   ;对注册码进行处理的关键call
00401137        .>test al,al
00401139        .>jnz short Snake.00401157 ; 跳则成功,不跳OVER。爆破这里OK,不过我们的目的是找算法。
我们先跟进对注册名处理的CALL:
0040119B       /$>push eax
0040119C       |.>push ecx
0040119D       |.>push edx
0040119E       |.>push esi
0040119F       |.>push edi
004011A0       |.>xor ecx,ecx
004011A2       |.>mov esi,Snake.00401E00       ; 这是保存注册名的地址
004011A7       |.>mov edi,Snake.00401B00      ;记的前面1个CALL清零的区域不?这就是这区域的开始地址,也就是我们贪吃蛇的游戏范围
004011AC       |.>push esi
004011AD       |.>mov dl,0
004011AF       |>>/mov al,byte ptr ds:[esi]  ;[esi]保存的注册名
004011B1       |.>|inc esi
004011B2       |.>|add dl,al
004011B4       |.>|test al,al    ; 对名字各位求和,取低8位,记为S,即求和后与0x000000FF作与运算
004011B6       |.>\jnz short Snake.004011AF
004011B8       |.>pop esi
循环1:这个循环作用是产生名字长度个“CC”标志
004011B9       |>>/movzx eax,byte ptr ds:[esi]    ;  取名字的1位
004011BC       |.>|xor al,dl                      ;  记T=Name[n] XOR S;
004011BE       |>>|/sub dl,al           ;  S=(S-T)AND 000000FF(注意是低8位运算,始终只取低8位,所以我们算的时候要与0x000000FF作与运算
004011C0       |.>||or byte ptr ds:[edi+eax],0CC  ;  [401b00+T]=CC;
004011C4       |.>||jnz short Snake.004011CA   ;??上一步结果不可能为0
004011C6       |.>||dec dl
004011C8       |.>|\jmp short Snake.004011BE
004011CA       |>>|inc ecx    ;记数器,注册名长度为几,ecx+几次
004011CB       |.>|inc esi  
004011CC       |.>|cmp byte ptr ds:[esi],0  ; 注册名是否取完
004011CF       |.>\jnz short Snake.004011B9
上面这循环,在401B00-401BFF这段地址产生CC标志,即我们这游戏中蛇要吃的果子,其实,只要多看下就知道这段程序有问题,我在文末还会说明下。
循环2:这个循环是找个位置放‘DD’标志
004011D1       |.>mov dword ptr ds:[401F04],ecx   ;  名字长度
004011D7       |.>xor dl,al                       ;  S=S XOR T;
004011D9       |>>/sub al,dl                    ; T=(T-S) AND 0x000000FF;
004011DB       |.>|cmp byte ptr ds:[edi+eax],0CC  ;  比较是不是已经放了CC标志了
004011DF       |.>|jnz short Snake.004011E5       ; 不是就存放DD标志
004011E1       |.>|dec dl                         ;  是CC标志就再找位置
004011E3       |.>\jmp short Snake.004011D9       ;   
004011E5       |>>mov byte ptr ds:[edi+eax],0DD   ;   [401B00+T]=DD
004011E9       |.>mov al,dl                     ;  T=S;
这个小循环是找个没存放CC标志的位置放DD标志,后面我们可以知道,DD是最后1个果子
循环3:找个位置放99标志,从下面可知,99是蛇头
004011EB       |>>/cmp byte ptr ds:[edi+eax],0CC  ;  
004011EF       |.>|je short Snake.004011F7        ;  寻找1个既没存放CC,也没存放DD标志的位置
004011F1       |.>|cmp byte ptr ds:[edi+eax],0DD  ;  
004011F5       |.>|jnz short Snake.004011FB       ;  
004011F7       |>>|dec al
004011F9       |.>\jmp short Snake.004011EB
004011FB       |>>lea eax,dword ptr ds:[edi+eax]
004011FE       |.>mov byte ptr ds:[eax],99        ;  [401BDD+T]=99;
00401201       |.>mov dword ptr ds:[401700],eax
00401206       |.>pop edi
00401207       |.>pop esi
00401208       |.>pop edx
00401209       |.>pop ecx
0040120A       |.>pop eax
0040120B       \.>retn
从上面的分析可以知道,对注册名的处理结果是在401B00至401BFF的空间中产生数量与注册名长度相等的’CC’标志以及1个’DD’标志和1个’99’标志.
下面我们再跟进对注册码进行处理的CALL:
0040120C       /$>pushad                        ;  游戏开始,准备好吃果子了。
0040120D       |.>mov esi,Snake.00401D00          ;  注册码地址
00401212       |.>mov edi,Snake.00401700          ;  "99"标志的地址
00401217       |>>/movzx eax,byte ptr ds:[esi]    ;  取注册码的1位 记作PASS[n]
0040121A       |.>|test al,al                     ;
0040121C       |.>|je short Snake.00401260  
0040121E       |.>|sub al,30                  ;这几句ASCII码变成0-15的数字
00401220       |.>|cmp al,0A                    ;  
00401222       |.>|jb short Snake.00401226        ;  PASS[n]<10在401226处理
00401224       |.>|sub al,7                      ;  若是字母,就-7,结果是A=10,B=11…F=15
00401226       |>>|mov ecx,eax                 ;  
00401228       |.>|and al,3                      ;  即PASS[n] MOD 4
0040122A       |.>|shr cl,2                      ;  PASS[n]/4,是整数相除,ECX即下面的K
0040122D       |.>|mov edx,10     ;edx是每次蛇移动的距离
00401232       |.>|test al,al          ;看PASS[n] MOD 4是否为0
00401234       |.>|je short Snake.00401241        ;  4的倍数跳
00401236       |.>|dec eax
00401237       |.>|je short Snake.0040123F        ;  4的倍数+1 跳
00401239       |.>|shr edx,4          ;若PASS[n]=4*K+2或PASS[n]=4*K+3,则edx==1
0040123C       |.>|dec eax                  ;  
0040123D       |.>|jnz short Snake.00401241       ;  PASS[n]= 4*K+3跳
0040123F       |>>|neg edx                     ;  蛇移动距离取反
上面这段是根据PASS[n]计算出每次蛇移动的距离,具体是:(PASS[n]=0~F,即0~15)
PASS[n]=4*K,edx=10,移动(K+1)*10     PASS[n]=4*K+1,edx=-10,位移-(K+1)*10
PASS[n]=4*K+2,edx=-1,位移-(K+1)      PASS[n]=4*K+3,edx=1,位移K+1
00401241       |>>|mov eax,dword ptr ds:[edi]     ;  ‘99’标志地址,这个‘99’始终是蛇头
00401243       |.>|add eax,edx                 ;蛇头原来位置+移动距离
00401245       |.>|mov al,byte ptr ds:[eax]       ; 取蛇头将要移动到的位置的值
00401247       |.>|test al,al             ;是否为0?
00401249       |.>|je short Snake.0040127A   ;是0则跳到下面移动蛇身
0040124B       |.>|cmp al,99       ;“99”标志代表蛇身的,看蛇头是否吃到蛇身了
0040124D       |.>|je short Snake.00401260        ;  是,则跳,OVER
0040124F       |.>|cmp al,0CC          ;是否是果子?
00401251       |.>|je short Snake.00401268    ;是则跳到下面增长蛇身
00401253       |.>|cmp al,0DD             ;是否是最后一个果子
00401255       |.>|jnz short Snake.00401260       ;  跳则OVER
00401257       |.>|cmp dword ptr ds:[401F04],0    ;[401F04]即剩余果子数,在处理注册名的那CALL里被初始化为注册名长度
0040125E       |.>|je short Snake.00401264        ; 不=0,即果子没吃完,不跳OVER
00401260       |>>|popad                          
00401261       |.>|mov al,0           ;失败标志位
00401263       |.>|retn
00401264       |>>|popad
00401265       |.>|mov al,1       ;成功标志位
00401267       |.>|retn
00401268       |>>|dec dword ptr ds:[401F04]    ; 吃到果子跳到这里处理。剩余果子个数-1。
0040126E       |.>|call Snake.00401289   ;
00401273       |.>|mov dword ptr ds:[eax],ebx     ;  这是吃中果子时执行的语句
00401275       |.>|mov byte ptr ds:[ebx],99       ;  将果子所在地址变为蛇身
00401278       |.>|jmp short Snake.0040127F     ;
0040127A       |>>|call Snake.00401289
0040127F       |>>|test ecx,ecx                   ;  ecx即上面的K
00401281       |.>|je short Snake.00401286        ;  看K=0不?不就再移动EDX距离。即每个字符移动EDX*(K+1)距离。
00401283       |.>|dec ecx  ;K=K-1
00401284       |.>|jmp short Snake.00401241
00401286       |>>|inc esi   ;取下一个注册码
00401287       \.>\jmp short Snake.00401217
上面有个CALL是用来处理蛇身移动的。
00401289       /$>push edi  ; 蛇身各节地址,从EDI开始为蛇头有几个地址蛇身就有几节
0040128A       |.>mov eax,dword ptr ds:[edi]      ;
0040128C       |.>mov ebx,eax               
0040128E       |.>add eax,edx                     ;  蛇身位置+EDX
00401290       |>>/mov dword ptr ds:[edi],eax        ;保存新的蛇身位置
00401292       |.>|mov byte ptr ds:[eax],99         ;
00401295       |.>|mov byte ptr ds:[ebx],0        ;  将‘99’移位,原蛇身位清零
00401298       |.>|add edi,4           ; edi+4,取蛇身的下一节
0040129B       |.>|cmp dword ptr ds:[edi],0  ;看蛇身是否还有下一节。
0040129E       |.>|je short Snake.004012A6  ;没有下一节即返回
004012A0       |.>|mov eax,ebx         ; ebx即上一节蛇身移动前位置(99标志已被清零),下一节蛇身位置将移动到这里
004012A2       |.>|mov ebx,dword ptr ds:[edi]   ;将要移动的下一节蛇身位置
004012A4       |.>\jmp short Snake.00401290   ;跳上去进行移动
004012A6       |>>mov eax,edi
004012A8       |.>pop edi
004012A9       \.>retn

   到此,我们已经弄清楚该程序的验证过程了。先清零出255个空位,对注册名处理,在空位种,生成注册名长度个CC标志,再生成DD和99标志。对注册码处理,每一位注册码都代表蛇的一次移动。只要先把CC标志吃完,再吃DD标志,就OK啦,当然吃到蛇身自己(99标志)也OVER咯~~

   注意上面生成CC标志的那几句程序。多看几眼就知道这几句是有问题的。因为,在生成CC标志时没有检查要生成的位置是否已经有标志了,结果是可能导致原CC标志被覆盖,所以CC标志个数也不=注册名长度,在这种情况下永远也不可能成功(如:注册名为aaaaaaa,长度为7,但算出CC标志只有3位。 )。所以,这里也就不给出注册机了。当然了,我只要弄清楚过程就行了。下面对生成CC,DD,99标志这段过程给出C语言描述

   int main(int argc, char* argv[])
{
        int num[256]={0},s=0,t=0,i;
        char name[20];
        printf("Enter the name:\n");
                scanf("%s",name);
        for(i=0;name[i]!='\0';i++)s+=name[i];
        s=s&255;
        for(i=0;name[i]!='\0';i++)
        {
                t=name[i]^s;
                s=(s-t)&255;
                num[t]=1;
        }
        s=s^t;
        t=(t-s)&255;
        while(num[t]==1)
        {
                s=(s-1)&255;  
                t=(t-s)&255;
        }
        num[t]=2;
        t=s;
        while(num[t]==1||num[t]==2)t-=1;
        num[t]=99;//µ½´Ë´¦Î»ÎªÖ¹,ΪÉú³ÉCC£DD£99±êÖ¾
    for(i=0,t=0;i<256;i++)
        {
                switch(num[i])
                {
                case 0:continue;
                case 1:printf("[%p]=CC  ",4201216+i);t++;continue;
                case 2:printf("[%p]=DD  ",4201216+i);continue;
                case 99:printf("[%p]=99  ",4201216+i);continue;
                }
        }
        printf("\ntotal of \"CC\":%d",t);
        scanf("%s",name);
        return 0;
}

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞7
打赏
分享
最新回复 (2)
雪    币: 225
活跃值: (40)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
push_eax 3 2006-3-23 15:42
2
0
补上crackme.
上传的附件:
雪    币: 434
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sinlncout 2012-9-4 00:12
3
0
对于注册名长度不等于CC个数的问题,可以在填充CC的时候先做下判断,如果此位置已经有CC了,那么我们可以给al加一个随机的偏移,跳到另外一个地方填充CC,这样保证CC的个数等于注册名的长度。不然,如作者所说,当填充CC个数和用户名长度不等时,永远不能成功。

004011B9  |> /0FB606        /movzx   eax, byte ptr [esi]
004011BC  |. |32C2          |xor     al, dl
004011BE  |> |2AD0         |/sub     dl, al

L_add:
|cmp  byte ptr [edi+eax],0CC
    |jnz short 004011C0

    |add al,3C

    |jmp short L_add


004011C0  |. |800C07 CC  ||or      byte ptr [edi+eax], 0CC
004011C4  |. |75 04         ||jnz     short 004011CA
004011C6  |. |FECA          ||dec     dl
004011C8  |.^|EB F4        |\jmp     short 004011BE
004011CA  |> |41            |inc     ecx
004011CB  |. |46             |inc     esi
004011CC  |. |803E 00     |cmp     byte ptr [esi], 0
004011CF  |.^\75 E8        \jnz     short 004011B9

考虑了下注册机,感觉没有很好的算法~~哎,悲催啊
游客
登录 | 注册 方可回帖
返回