欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

第一阶段第一题分析+完整逆向代码(看雪金山2007逆向分析挑战赛)

第一阶段第一题分析+完整逆向代码(看雪金山2007逆向分析挑战赛)

第一阶段第一题分析+完整逆向代码(看雪金山2007逆向分析挑战赛)
by aker
8:31 2007-8-24
第一题已经结束了,放出分析和逆向代码先;)
我很少做crackme分析,第一次写逆向分析文章,希望没有什么错误。给了个还原出来的crackme的代码,见附件,样子,行为和原来的一模一样;)
od载入,没有什么说的,总共才1.6k的程序,载入就看到下面接受输入的代码,检查名字和序列号是否为空,空则重新接受输入

代码:

00400499   |.  8D45 EC        lea eax,dword ptr ss:[ebp-14]        ;  name
0040049C   |.  6A 10          push 10                              ; /Count = 10 (16.)
0040049E   |.  50             push eax                             ; |Buffer
0040049F   |.  68 E9030000    push 3E9                             ; |ControlID = 3E9 (1001.)
004004A4   |.  FF75 08        push dword ptr ss:[ebp+8]            ; |hWnd
004004A7   |.  FFD6           call esi                             ; \GetDlgItemTextA
004004A9   |.  85C0           test eax,eax      
004004AB   |.  74 6C          je short CrackMe.00400519
004004AD   |.  8D85 E8EFFFFF  lea eax,dword ptr ss:[ebp-1018]      ;  serial
004004B3   |.  68 00100000    push 1000                            ; /Count = 1000 (4096.)
004004B8   |.  50             push eax                             ; |Buffer
004004B9   |.  68 EA030000    push 3EA                             ; |ControlID = 3EA (1002.)
004004BE   |.  FF75 08        push dword ptr ss:[ebp+8]            ; |hWnd
004004C1   |.  FFD6           call esi                             ; \GetDlgItemTextA
004004C3   |.  85C0           test eax,eax
004004C5   |.  74 52          je short CrackMe.00400519

下面代码计算用户名字符数,用的repne scas指令,ecx保存字符数,我们还原代码时要不用strlen,要不保存GetDlgItemTextA的返回值都可以达到该效果。
代码:

004004C7   |.  8D7D EC        lea edi,dword ptr ss:[ebp-14]        ;  name
004004CA   |.  83C9 FF        or ecx,FFFFFFFF                      ;  ecx = ffffffff
004004CD   |.  33C0           xor eax,eax                          ;  eax = 0
004004CF   |.  33D2           xor edx,edx                          ;  edx = 0
004004D1   |.  F2:AE          repne scas byte ptr es:[edi]
004004D3   |.  F7D1           not ecx
004004D5   |.  49             dec ecx
004004D6   |.  85C9           test ecx,ecx                         ;  计数用户名字符数
004004D8   |.  7E 23          jle short CrackMe.004004FD

下面004004DA代码计算用户名特征码,这个后面有用,注意ebx在前面0040046F处已经初始化了一个常数,发现一个未知寄存器一定要往上找,看什么地方修改过。
代码:

0040046F   |.  BB 68245713    mov ebx,13572468                     ;  ebx = 13572468
代码:

004004DA   |> /0FBE4415 EC    /movsx eax,byte ptr ss:[ebp+edx-14]  ;  对用户名每个字符操作
004004DF   |. |03C3           |add eax,ebx
004004E1   |. |69C0 73127203  |imul eax,eax,3721273
004004E7   |. |05 57136824    |add eax,24681357
004004EC   |. |8BF0           |mov esi,eax
004004EE   |. |C1E6 19        |shl esi,19
004004F1   |. |C1F8 07        |sar eax,7
004004F4   |. |0BF0           |or esi,eax
004004F6   |. |42             |inc edx                             ;  edx -- i
004004F7   |. |3BD1           |cmp edx,ecx
004004F9   |. |8BDE           |mov ebx,esi
004004FB   |.^\7C DD          \jl short CrackMe.004004DA

代码还原c如下,其中name为接受输入的用户名,对等起来看的话,addr相当于开始的eax,adder2相当于esi,ebx保存namecalc,这个值以后用。
代码:

    for(i=0; i<namelen; i++)
    {   // 计算名字特征
        adder = (name[i]+ namecalc)*0x3721273+0x24681357;
        adder2 = adder<<0x19;
        __asm sar adder,7
        namecalc = adder2|adder;
    }

好,上面计算出了输入用户名的特征码,底下就到了第一个关键函数调用。该调用将用户名特征作为第一个参数,序列号数组做为第二个参数。
代码:

004004FD   |> \8D85 E8EFFFFF  lea eax,dword ptr ss:[ebp-1018]      ;  serial
00400503   |.  50             push eax
00400504   |.  53             push ebx                             ;  ebx
00400505   |.  E8 C2FDFFFF    call CrackMe.004002CC

跟进去一看,这个函数好长,开始是老规矩,压栈,申请空间,数据初始化。
首先找找,什么地方对我刚刚调用的参数操作了,因为我传进来的参数才是和用户名,序列号相关的,也就是ebp+8,和ebp+c,分别发现两个。
代码:

00400306   |.  8B7D 0C        mov edi,dword ptr ss:[ebp+C]         ;  edi == 输入的序列号
00400367   |> /8B45 08        /mov eax,dword ptr ss:[ebp+8]        ;  namecalc
00400383   |> /8B45 0C        /mov eax,dword ptr ss:[ebp+C]        ;  eax = &serial[0]
004003A4   |.  8B45 08        |mov eax,dword ptr ss:[ebp+8]        ;  eax = namecalc

分析一下第一个地方00400306是计算序列号长度,最后ecx = 序列号长度,还原该处和上面说的一样,我们不需要这样做了。另外该处将ebx置1,这个ebx以后都不会动了,就把他当作1看。
第二个地方是个小关键,根据名字特征值namecalc即[ebp+8]的1-8位构造名字表
代码:

//汇编代码如下:
00400365   |.  8BFB           mov edi,ebx                          ;  edi = 1
00400367   |>  8B45 08        /mov eax,dword ptr ss:[ebp+8]        ;  namecalc
0040036A   |.  8BCF           |mov ecx,edi
0040036C   |.  D3E8           |shr eax,cl                          ;  namecalc >>= i
0040036E   |.  22C3           |and al,bl                           ;  (byte)namecalc &= 1
00400370   |.  88443D DC      |mov byte ptr ss:[ebp+edi-24],al
00400374   |.  47             |inc edi
00400375   |.  83FF 09        |cmp edi,9
00400378   |.^ 7C ED          \jl short CrackMe.00400367
0040037A   |.  33FF           xor edi,edi                          ;  edi = 0
0040037C   |.  885D E5        mov byte ptr ss:[ebp-1B],bl          ;  bl == 1
    // 还原c代码如下
    for (i=1; i<9; i++)
    {   //根据名字特征值的1-8位构造名字表
        nametable[i] = (unsigned char)((unsigned char)(namecalc>>i)&1);
    }
    nametable[9] = 1;
    

第三和第四在一个大段里面,其他地方没有了,不用说,肯定就是这儿判断是否序列号为真。这一段比较长,我们先放一下,看看到底是什么地方判断成功的。下翻看到一个msgbox,看看就这一个地方调用msgbox的,那肯定是他跳出判断的,再看到0040041B处文本是个eax,而eax指向[ebp-128],所以[ebp-128]存放判断成功失败的文本。看代码发现和[ebp-128]相关的地方在这一段有4个,开始置0,刚刚msgbox要取数据,另外两个分别在004003FC,0040042B,可以看到最后都是作为call CrackMe.00400240的第二个参数,而第一个参数不一样,但是第一个参数都是一个11字节的数组,而且第一个字节为FF,只是做检查用的。
代码:

0040040D   |.  8D85 D8FEFFFF  lea eax,dword ptr ss:[ebp-128]       ;  此处为判断成功失败的文本
00400413   |.  59             pop ecx
00400414   |.  6A 00          push 0                               ; /Style = MB_OK|MB_APPLMODAL
00400416   |.  68 70054000    push CrackMe.00400570                ; |Title = ""
0040041B   |.  50             push eax                             ; |Text
0040041C   |.  6A 00          push 0                               ; |hOwner = NULL
0040041E   |.  FF15 34024000  call dword ptr ds:[<&USER32.MessageB>; \MessageBoxA
//[ebp-128] 相关1,判断成功
004003FC   |.  8D85 D8FEFFFF  lea eax,dword ptr ss:[ebp-128]       ;  该处返回OK!!
00400402   |.  50             push eax
00400403   |.  8D45 E8        lea eax,dword ptr ss:[ebp-18]        ;  成功
00400406   |>  50             push eax
00400407   |.  E8 34FEFFFF    call CrackMe.00400240
//[ebp-128] 相关2,判断失败
0040042B   |> \8D85 D8FEFFFF  lea eax,dword ptr ss:[ebp-128]       ;  不是数字,或者其他什么都是失败Fail!
00400431   |.  50             push eax                             ;  第二个参数
00400432   |.  8D45 F4        lea eax,dword ptr ss:[ebp-C]         ;  第一个参数
00400435   \.^ EB CF          jmp short CrackMe.00400406

跟到call CrackMe.00400240里面看了下,很简单,就是异或还原数据。此处可以不看,只要知道数据传送进去,会异或出fail!和OK!!字样的字符串就好了。
代码:


//c还原代码
void calc_alpha(char *xor, char *result_str)
{
    int i;
    unsigned char xor0[10] = {0x25,0x9a,0xf3,0x6f,0x82,0xda,0x72,0xfe,0xc9,0xb7};
    for(i=0; i<10; i++)  result_str[i] = xor0[i]^xor[i];
}
//下面是调用者准备好的数据。
    char result_str[0x40];
    unsigned char xorfail[10] = {0x63,0xfb,0x9a,0x03,0xa3,0xda,0x72,0xfe,0xc9,0xb7};  //fail!
    unsigned char xorok[10] = {0x6a,0xd1,0xd2,0x4e,0x82,0xda,0x72,0xfe,0xc9,0xb7};  //OK!!
//调用calc_alpha(xorfail,result_str);会得到fail!字样,calc_alpha(xorok,result_str);会得到OK!!
////注意此处代码没有还原,该处检查标志FF
00400240   /$  57             push edi
00400241   |.  8B7C24 08      mov edi,dword ptr ss:[esp+8]         ;  edi = 第一个参数,一已定义数组
00400245   |.  803F FF        cmp byte ptr ds:[edi],0FF            ;  测试0012EB30是否为0xff
00400248   |.  75 5F          jnz short CrackMe.004002A9

好了,上面说了一大通嗦的话,下面到了真正关键的代码了,也就是上面说到的第三和第四在一个大段里面,肯定就是这儿判断是否序列号为真。该段是个循环,判断序列号[ebp+C]是否为真。
循环操作如下:
1.依次加载序列号的每一个字节。
2.判断是否为数字字符if(serial[i]<'0' || serial[i]>'9') ,不是数字字符就跳转。
3.循环计算32位整数就是刚才计算的用户名特征码的1/2模10的余数,每次都多计算其1/2,所以可以看到这个数是以32位为循环的。
4.该数字加上刚刚的数字字符串代表的数字再模10,以这个余数为索引到开始计算的名字特征值的名字表中作变换。
5.下面是变换过程
5.1如果为1,则一号位异或1,见004003BE,cmp edx,ebx
5.2否则异或该位置,但是需要该位置的前一个位置为1,再前面所有数据为0,不满足就报错
关键代码就这些
代码:

00400383   |> /8B45 0C        /mov eax,dword ptr ss:[ebp+C]        ;  eax = &serial[0]
00400386   |. |8A0407         |mov al,byte ptr ds:[edi+eax]        ;  
00400389   |. |3C 30          |cmp al,30                           ;  if(serial[i]<'0' || serial[i]>'9') goto fail;
0040038B   |. |8845 FF        |mov byte ptr ss:[ebp-1],al          ;  
0040038E   |. |0F8C 97000000  |jl CrackMe.0040042B
00400394   |. |3C 39          |cmp al,39
00400396   |. |0F8F 8F000000  |jg CrackMe.0040042B                 ;  如果不是数字跳转到该处,一定要是数字,否则fail
0040039C   |. |8BC7           |mov eax,edi                         ;  
0040039E   |. |6A 1F          |push 1F
004003A0   |. |99             |cdq
004003A1   |. |59             |pop ecx                             ;  
004003A2   |. |F7F9           |idiv ecx                            ;  i/0x1f
004003A4   |. |8B45 08        |mov eax,dword ptr ss:[ebp+8]        ;  eax = namecalc
004003A7   |. |6A 0A          |push 0A
004003A9   |. |8BCA           |mov ecx,edx                         ;  ecx = 余数
004003AB   |. |33D2           |xor edx,edx                         ;  
004003AD   |. |D3E8           |shr eax,cl                          ;  
004003AF   |. |59             |pop ecx                             ;  
004003B0   |. |F7F1           |div ecx                             ;  remainder = (namecalc >>= (byte)ecx)%0xa
004003B2   |. |0FBE45 FF      |movsx eax,byte ptr ss:[ebp-1]       ;  
004003B6   |. |8D4402 D0      |lea eax,dword ptr ds:[edx+eax-30]   ;  eax = serial[i]数字值+余数
004003BA   |. |33D2           |xor edx,edx                         ;  
004003BC   |. |F7F1           |div ecx                             ;  
004003BE   |. |3BD3           |cmp edx,ebx                         ;  ebx 一直为1
004003C0   |. |75 05          |jnz short CrackMe.004003C7          ;  如果remainder != 1则跳转
004003C2   |. |305D DD        |xor byte ptr ss:[ebp-23],bl         ;  
004003C5   |. |EB 22          |jmp short CrackMe.004003E9
004003C7   |> |385C15 DB      |cmp byte ptr ss:[ebp+edx-25],bl     ;  nametable[remainder-1]!=1>fail!
004003CB   |. |75 5E          |jnz short CrackMe.0040042B          ;  fail!
004003CD   |. |8D42 FE        |lea eax,dword ptr ds:[edx-2]        ;  eax = remainder-2; //calc
004003D0   |. |8BCB           |mov ecx,ebx                         ;  ecx = 1;
004003D2   |. |3BC3           |cmp eax,ebx                         ;  calc >= 1
004003D4   |. |7C 0B          |jl short CrackMe.004003E1
004003D6   |> |385C0D DC      |/cmp byte ptr ss:[ebp+ecx-24],bl
004003DA   |. |74 4F          ||je short CrackMe.0040042B          ;  fail!
004003DC   |. |41             ||inc ecx
004003DD   |. |3BC8           ||cmp ecx,eax
004003DF   |.^|7E F5          |\jle short CrackMe.004003D6
004003E1   |> |305C15 DC      |xor byte ptr ss:[ebp+edx-24],bl     ;  nametable[remainder] ^= 1;
004003E5   |. |8D4415 DC      |lea eax,dword ptr ss:[ebp+edx-24]   ;  多余操作
004003E9   |> |47             |inc edi
004003EA   |. |3BFE           |cmp edi,esi
004003EC   |.^\7C 95          \jl short CrackMe.00400383

逆向出来的c代码如下
代码:

    // check
    for (i=0; i<seriallen; i++)
    {
      if(serial[i]<'0' || serial[i]>'9') goto failinput;
      remainder = (serial[i]-0x30+ ((namecalc>>(unsigned char)(i%(TABLE_SIZE-1)))%10) )%10;//remainder 在 0-9之间
    
         if(remainder != 1)
      {
        if(nametable[remainder-1]==1) 
        { 
          int calc = remainder-2;
          while(calc >=1)
              if(nametable[calc--]==1) goto fail; 
                nametable[remainder]^=1; 
        }   
        else goto fail;
      }
      else nametable[1] ^=1;
    }

最后一个判断是,判断特征表中所有数字都为0,比较简单。
代码:

004003EE   |> \8BC3           mov eax,ebx                          ;  ebx = 1
004003F0   |>  385C05 DC      /cmp byte ptr ss:[ebp+eax-24],bl     ;  此时需要table中全为0
004003F4   |.  74 35          |je short CrackMe.0040042B           ;  fail!
004003F6   |.  40             |inc eax
004003F7   |.  83F8 0A        |cmp eax,0A
004003FA   |.^ 7C F4          \jl short CrackMe.004003F0
// c代码
    for (i=1; i<10; i++)
        if(nametable[i]==1) goto failedcheck;

最后给出所有的逆向出来的检查用户名和序列号的c代码,没有什么需要注释的,名字都很清楚了,窗体部分见附件。至此,整个代码的逆向过程都出来了。
过会儿给出写注册机的分析,这个咚咚写注册机有些麻烦,而且不太说的清楚。各位要不先自己试着考虑下象这样的过程该怎么写注册机。昨天我看了半天才想出来怎么写注册机,而且开始走了很大的弯路.
代码:


#define TABLE_SIZE      32
#define SERIAL_SIZE     0x1000
char            name[0x10];
char            serial[SERIAL_SIZE];
unsigned char   nametable[10];

int namelen = 0, seriallen = 0;
void calc_alpha(unsigned char *xor, char *result_str)
{// 其实这个地方就是为了好玩才逆向的;)不需要,可以直接sprintf();
    int i;
    unsigned char xor0[10] = {0x25,0x9a,0xf3,0x6f,0x82,0xda,0x72,0xfe,0xc9,0xb7};
    for(i=0; i<10; i++)  result_str[i] = xor0[i]^xor[i];
}
char buf[0x20];
int checkserial( char *name,char *serial)
{
    int i;
    int remainder;
    unsigned namecalc = 0x13572468;
    unsigned adder,adder2;
    unsigned char xorfail[10] = {0x63,0xfb,0x9a,0x03,0xa3,0xda,0x72,0xfe,0xc9,0xb7};  //fail!
    unsigned char xorok[10] = {0x6a,0xd1,0xd2,0x4e,0x82,0xda,0x72,0xfe,0xc9,0xb7};  //OK!!
    memset(nametable,0,0xa);
    //namelen = strlen(name);
    //seriallen= strlen(serial);// 我采用GetDlgItemText的返回值计算的。
    for(i=0; i<namelen; i++)
    {   // 计算名字特征
        adder = (name[i]+ namecalc)*0x3721273+0x24681357;
        adder2 = adder<<0x19;
        __asm sar adder,7
        namecalc = adder2|adder;
    }
    for (i=1; i<9; i++)
        nametable[i] = (unsigned char)((unsigned char)(namecalc>>i)&1);
    nametable[9] = 1;
    
    // check
    for (i=0; i<seriallen; i++)
    {
      if(serial[i]<'0' || serial[i]>'9') goto failinput;
      remainder = (serial[i]-0x30+ ((namecalc>>(unsigned char)(i%(TABLE_SIZE-1)))%10) )%10;
    
         if(remainder != 1)
      {
        if(nametable[remainder-1]==1) 
        {   
          int calc = remainder-2;
          while(calc >=1)
              if(nametable[calc--]==1) goto fail; 
                nametable[remainder]^=1; 
        }   
        else goto fail;
      }
      else nametable[1] ^=1;
    }
    for (i=1; i<10; i++)
        if(nametable[i]==1) goto failedcheck;
    goto success;
failinput:
fail:
failedcheck:    
    calc_alpha(xorfail,buf);        return 1;
success:
    calc_alpha(xorok,buf);         return 0;
}

稍微总结一下验证过程,循环读入序列号,根据序列号对nametable的对应位置取反(其中位置判断是否序列号有效),最后序列号结束后判断是否nametable全为0,否则失败。
另外这个题逆推回去有些不容易.

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功