首页
社区
课程
招聘
[原创]一个Crackme的简单算法分析
发表于: 2009-2-6 14:53 7500

[原创]一个Crackme的简单算法分析

2009-2-6 14:53
7500

一个crackme的简单算法分析

o(∩_∩)o...首先声明一下,这是本人第一个破解文章,曾经在一蓑烟雨论坛上发表过,这里发表的与之有少量的改动。o(∩_∩)o...

peid检查,无壳,Microsoft Visual C++ 6.0程序。

运行程序,随便输入注册信息,比如asdf,12345,点击注册,弹出出错对话框Registration fail。有出错提示,真是太好了。(没有提示的话,断点不好下啊)

OD载入,选择 插件->字符串参考->Find ASCII,再找到Registration fail。双击就到达了弹出出错提示的位置。向上找到关键call和关键跳。

00401064   .  E8 C7010000   call    00401230                         ;  关键call。在这里下断点。
00401069   .  85C0          test    eax, eax                         ;  检验eax是否等于0。等于0则注册成功。
0040106B   .  6A 00         push    0                                ; /Style = MB_OK|MB_APPLMODAL
0040106D   .  68 80504000   push    00405080                         ; |ncrackme
00401072   .  75 1B         jnz     short 0040108F                   ; |关键跳。要爆破就在这里动手了。
00401074   .  A1 B8564000   mov     eax, dword ptr [4056B8]          ; |
00401079   .  68 64504000   push    00405064                         ; |registration successful.
0040107E   .  50            push    eax                              ; |hOwner => NULL
0040107F   .  FF15 C0404000 call    dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA
00401085   .  E8 A6020000   call    00401330
0040108A   .  33C0          xor     eax, eax
0040108C   .  C2 1000       retn    10
0040108F   >  8B0D B8564000 mov     ecx, dword ptr [4056B8]          ; |
00401095   .  68 50504000   push    00405050                         ; |registration fail.
0040109A   .  51            push    ecx                              ; |hOwner => NULL
0040109B   .  FF15 C0404000 call    dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA

在00401064处按下F2下断点。
F9运行程序,00401064处程序暂停,按F7跟进去。然后F8单步执行,

00401245  |.  6A 10         push    10                               ; /Count = 10 (16.)
00401247  |.  50            push    eax                              ; |Buffer
00401248  |.  68 E8030000   push    3E8                              ; |ControlID = 3E8 (1000.)
0040124D  |.  51            push    ecx                              ; |hWnd => NULL
0040124E  |.  33DB          xor     ebx, ebx                         ; |
00401250  |.  FFD6          call    esi                              ; \GetDlgItemTextA
00401252  |.  83F8 03       cmp     eax, 3                           ;  eax=用户名长度(字符数)
00401255  |.  73 0B         jnb     short 00401262                   ;  eax>=3,否则eax=1,然后返回,注册失败了。
00401257  |.  5E            pop     esi
00401258  |.  B8 01000000   mov     eax, 1
0040125D  |.  5B            pop     ebx
0040125E  |.  83C4 30       add     esp, 30
00401261  |.  C3            retn
00401262  |>  A1 BC564000   mov     eax, dword ptr [4056BC]

继续跟踪,
00401276  |.  0FBE4424 08   movsx   eax, byte ptr [esp+8]            ;  字节name[0]
0040127B  |.  0FBE4C24 09   movsx   ecx, byte ptr [esp+9]            ;  字节name[1]
00401280  |.  99            cdq
00401281  |.  F7F9          idiv    ecx
00401283  |.  8BCA          mov     ecx, edx                         ; ecx=name[1]%name[0]
00401285  |.  83C8 FF       or      eax, FFFFFFFF
00401288  |.  0FBE5424 0A   movsx   edx, byte ptr [esp+A]            ;  字节name[2]
0040128D  |.  0FAFCA        imul    ecx, edx                         ; ecx=(name[1]%name[0])*name[2]
00401290  |.  41            inc     ecx
00401291  |.  33D2          xor     edx, edx
00401293  |.  F7F1          div     ecx                 ; eax=0xffffffff/((name[1]%name[0])*name[2]+1)
00401295  |.  50            push    eax
00401296  |.  E8 A5000000   call    00401340            ;  将eax存入地址4050ac,实际上设置了rand函数的种子。

继续,
0040129E  |.  33F6          xor     esi, esi
004012A0  |> /E8 A5000000   /call    0040134A                       ;跟进去,可以知道此处调用的就是rand函数。
004012A5  |. |99            |cdq
004012A6  |. |B9 1A000000   |mov     ecx, 1A
004012AB  |. |F7F9          |idiv    ecx
004012AD  |. |80C2 41       |add     dl, 41
004012B0  |. |885434 18     |mov     byte ptr [esp+esi+18], dl       ;  存入(eax % 26) + 'A'
004012B4  |. |46            |inc     esi
004012B5  |. |83FE 0F       |cmp     esi, 0F                         ;  循环15次
004012B8  |.^\72 E6         \jb      short 004012A0

快到核心的地方了。继续努力。
004012CD  |> /8A4434 0C     /mov     al, byte ptr [esp+esi+C]        ;  用户名的某个字节
004012D1  |. |C0F8 05       |sar     al, 5                           ;  eax=name[i]>>5
004012D4  |. |0FBEC0        |movsx   eax, al
004012D7  |. |8D1480        |lea     edx, dword ptr [eax+eax*4]
004012DA  |. |8D04D0        |lea     eax, dword ptr [eax+edx*8]
004012DD  |. |8D0440        |lea     eax, dword ptr [eax+eax*2]      ;  eax=eax*123(Dec)
004012E0  |. |85C0          |test    eax, eax
004012E2  |. |7E 0A         |jle     short 004012EE
004012E4  |. |8BF8          |mov     edi, eax                        ;  edi=(name[i]>>5)*123
004012E6  |> |E8 5F000000   |/call    0040134A                       ;  调用rand函数
004012EB  |. |4F            ||dec     edi
004012EC  |.^|75 F8         |\jnz     short 004012E6                 ;  循环edi次
004012EE  |> |E8 57000000   |call    0040134A                        ;  调用rand函数
004012F3  |. |99            |cdq
004012F4  |. |B9 1A000000   |mov     ecx, 1A
004012F9  |. |8D7C24 0C     |lea     edi, dword ptr [esp+C]          ;  edi指向用户名
004012FD  |. |F7F9          |idiv    ecx
004012FF  |. |0FBE4C34 2C   |movsx   ecx, byte ptr [esp+esi+2C]      ;  序列号的某个字节
00401304  |. |80C2 41       |add     dl, 41                          ;  dl=(eax % 26) + 'A'
00401307  |. |0FBEC2        |movsx   eax, dl                         ;  eax=(eax % 26) + 'A'
0040130A  |. |2BC1          |sub     eax, ecx                        ;  只有eax=ecx,才可能注册成功。
0040130C  |. |885434 1C     |mov     byte ptr [esp+esi+1C], dl
00401310  |. |99            |cdq                                     ;  edx=0
00401311  |. |33C2          |xor     eax, edx
00401313  |. |83C9 FF       |or      ecx, FFFFFFFF
00401316  |. |2BC2          |sub     eax, edx
00401318  |. |03D8          |add     ebx, eax                        ;  ebx 累加
0040131A  |. |33C0          |xor     eax, eax
0040131C  |. |46            |inc     esi
0040131D  |. |F2:AE         |repne   scas byte ptr es:[edi]
0040131F  |. |F7D1          |not     ecx
00401321  |. |49            |dec     ecx
00401322  |. |3BF1          |cmp     esi, ecx
00401324  |.^\72 A7         \jb      short 004012CD
00401326  |> \5F            pop     edi
00401327  |.  8BC3          mov     eax, ebx
00401329  |.  5E            pop     esi
0040132A  |.  5B            pop     ebx
0040132B  |.  83C4 30       add     esp, 30
0040132E  \.  C3            retn

这里解释一下“只有eax=ecx,才可能注册成功”:关键call返回后,只有eax=0才会注册成功。而
00401327  |.  8BC3          mov     eax, ebx
ebx是累加而得。因此每次累加值都应该是0。继续分析就会得到“只有eax=ecx,才可能注册成功”。

最后,总结一下:
name至少3个字符。key的长度不小于name的长度,且前面长度为name长度的部分必须是大写英文字母,后面部分对注册无影响。rand函数的种子设定为0xffffffff/((name[1]%name[0])*name[2]+1)。
然后调用15次rand函数。
然后调用rand函数 (name[0]>>5)*123 次,
满足条件key[0] = (char)(rand()%26 + 'A') 时才可能注册成功。(注意,这里又调用了一次rand函数)
然后调用rand函数 (name[1]>>5)*123 次,
满足条件key[1] = (char)(rand()%26 + 'A') 时才可能注册成功。
。。。。。。。
直至取完name的所有字节。

这里给一个正确的注册码:reg name:asdf,
                        reg key :YNMK.

另外,需要特别说明一点:
首先,给出产生随机数的rand函数代码:

static long holdrand = 1;
void srand(unsigned seed)
{
    holdrand = seed;
}
int rand()
{
    return (holdrand = holdrand * 214013 + 2531011) >> 16 & 0x7fff;
}
这是在网络上搜索到的代码。

不过我在visual c++ 6.0下做过实验,直接使用头文件中的rand()函数,发现做出的注册机并不能注册成功。而如果使用如下的自定义函数,则可以成功做出注册机。

unsigned long seed;

unsigned long myrand()
{
  return (seed=seed*0x343fd+0x269ec3)>>16 & 0x7fff;
}
直接使用头文件中的rand()函数做出的注册机,用OD查看这个函数的汇编代码,发现与crackme中的rand函数代码相同。
然而做出的注册机并不能注册成功。  

本人水平有限,实在不明白这是为什么???望高人指点!

附visual c++ 6.0源代码:
#include <iostream>
#include <string.h>
#include <windows.h>
//#define myrand rand
using namespace std;

unsigned long seed;

unsigned long myrand()
{
  return (seed=seed*0x343fd+0x269ec3)>>16 & 0x7fff;
}

int main()
{
  char name[50];
re_input:
  cout<<"请输入用户名(至少3个字符):"<<endl;
  cin>>name;
  if (strlen(name)<3)
  {
    cout<<"用户名至少3个字符!"<<endl
      <<"请重新输入。"<<endl;
    goto re_input;
  }
  unsigned long qq;
  qq = (unsigned long)(name[0] % name[1]) * name[2] + 1;
  seed = 0xffffffff/qq;
  qq = 0xf;
  while (qq--)
    myrand();

  char key[50];
  for (int i=0; i<strlen(name); i++)
  {
    qq=(unsigned long)(name[i]>>5) * 123;
    while (qq--)
      myrand();
    key[i] = (char)(myrand()%26 + 'A');
  }
  key[i] = '\0';
  cout<<key<<endl;
  system("PAUSE");
  return 0;
}


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 7
支持
分享
最新回复 (4)
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
用的着这么麻烦吗,直接在
00401069   .  85C0          test    eax, eax                         ;  检验eax是否等于0。等于0则注册成功
这里把test改成xor就可以了
哈哈
2009-2-7 10:43
0
雪    币: 47147
活跃值: (20450)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
3
这个CrackMe设计主要是让他人分析其算法。没有防爆破。
2009-2-7 13:47
0
雪    币: 212
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
不错,学习了,楼主分析得很详细~赞一个!
2009-2-7 15:41
0
雪    币: 17
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
不错,学习了,楼主分析得很详细~赞一个!
2014-3-23 09:28
0
游客
登录 | 注册 方可回帖
返回
//