万里长征第二步:LC4注册算法分析

解密者:冲天剑@pediy.com
工具:peid 0.94, Ollyice 1.10


0. 导言

  近来本地机器系统密码丢失,为了将其破解出来,到网上下载了据说很著名
的LC4,但却要求注册,否则暴力破解功能不可用。LC4顾名思义是4代的
版本,目前据说5代都出来了,官网上肯定下不到了。可下载的页面位于:

  hxxp://www.hackchina.cn/Soft/hacktools/jmjm/200511/54.html

(老规矩,要访问这个网页的请自行改成正确格式URL。)


1. 反向工程

  (1) 用PEID查壳,显示为Microsoft Visual C++ 6.0。

  (2) 运行程序试注册,可知注册流程是程序自己显示机器号然后要求输入序
列号的类型。机器号显示:15859a29d,输入假码1234567试注册,显示字符串:

   You have entered an invalid code. Please try again.

然后Ollyice载入,查找此字串。(首次运行会遇到异常,应在调试设置选项中
忽略所有异常并将所遇到的异常添加进去,重新运行)


//////////////  以下是代码  ////////////////

00411BF3  |.  50            |push    eax                             ;  断点
00411BF4  |.  E8 B722FFFF   |call    00403EB0
00411BF9  |.  8B07          |mov     eax, [edi]                      ;  真码指针
00411BFB >|.  8D5424 28     |lea     edx, [esp+28]                   ;  假码指针
00411BFF  |.  52            |push    edx
00411C00  |.  50            |push    eax
00411C01  |.  E8 955C0100   |call    0042789B
00411C06  |.  83C4 10       |add     esp, 10
00411C09  |.  85C0          |test    eax, eax
00411C0B  |.  75 23         |jnz     short 00411C30                  ;  关键跳转
00411C0D  |.  8B07          |mov     eax, [edi]
00411C0F  |.  50            |push    eax                             ; /Arg3
00411C10  |.  68 50134700   |push    00471350                        ; |Arg2 = 00471350 ASCII "Unlock Code"
00411C15  |.  68 5C134700   |push    0047135C                        ; |Arg1 = 0047135C ASCII "Registration"
00411C1A  |.  8BCE          |mov     ecx, esi                        ; |
00411C1C  |.  899E 3C010000 |mov     [esi+13C], ebx                  ; |
00411C22  |.  E8 9AB20300   |call    0044CEC1                        ; \lc4.0044CEC1
00411C27  |.  53            |push    ebx
00411C28  |.  53            |push    ebx
00411C29  |.  68 34144700   |push    00471434                        ;  ASCII "You have successfully registered LC4."
00411C2E  |.  EB 07         |jmp     short 00411C37
00411C30  |>  53            |push    ebx                             ; /Arg3
00411C31  |.  53            |push    ebx                             ; |Arg2
00411C32  |.  68 00144700   |push    00471400                        ; |Arg1 = 00471400 ASCII "You have entered an invalid code. Please try again."
00411C37  |>  E8 D3720300   |call    00448F0F                        ; \lc4.00448F0F

//////////////  以上是代码  ////////////////


这个关键跳转太显眼了!那么接下去call 0042789B应该是判断两个字符串是否相
同,而call 00403EB0应该就是算注册码的过程了吧!于是在00411BF3处下断点,
单步步过00411BF4以后,果然出现了真正注册码!而接下来的两个push指令应该
是为call 0042789B准备参数的,它们所推入的果然是真码和假码的指针!不用再
多说了,赶快跟进00403EB0这个过程里去吧。


//////////////  以下是代码  ////////////////

00403EB0  /$  83EC 0C       sub     esp, 0C                          ;  预留局部变量空间(dword*3)
00403EB3  |.  8B4424 10     mov     eax, [esp+10]                    ;  指向机器码字串的指针
00403EB7  |.  6A 08         push    8
00403EB9  |.  40            inc     eax                              ;  掐掉机器码头字符
00403EBA  |.  50            push    eax
00403EBB  |.  8D4C24 08     lea     ecx, [esp+8]                     ;  局部变量1的指针
00403EBF  |.  51            push    ecx
00403EC0  |.  E8 AB1E0200   call    00425D70
00403EC5  |.  8D5424 1C     lea     edx, [esp+1C]
00403EC9  |.  52            push    edx
00403ECA  |.  8D4424 10     lea     eax, [esp+10]
00403ECE  |.  68 88FA4600   push    0046FA88                         ;  ASCII "%08x"
00403ED3  |.  50            push    eax
00403ED4  |.  C64424 20 00  mov     byte ptr [esp+20], 0
00403ED9  |.  E8 65240200   call    00426343
00403EDE  |.  8B4424 28     mov     eax, [esp+28]
00403EE2  |.  35 52AE376F   xor     eax, 6F37AE52                    ;  EAX=机器码后8位对应的16进位字符
00403EE7  |.  8BC8          mov     ecx, eax
00403EE9  |.  C1E1 1B       shl     ecx, 1B
00403EEC  |.  C1E8 05       shr     eax, 5
00403EEF  |.  03C8          add     ecx, eax
00403EF1  |.  51            push    ecx                              ;  ECX=注册码
00403EF2  |.  894C24 2C     mov     [esp+2C], ecx
00403EF6  |.  8B4C24 30     mov     ecx, [esp+30]
00403EFA  |.  68 80FA4600   push    0046FA80                         ;  ASCII "%04x"
00403EFF  |.  51            push    ecx
00403F00  |.  E8 EC230200   call    004262F1
00403F05  |.  33C0          xor     eax, eax
00403F07  |.  83C4 30       add     esp, 30
00403F0A  \.  C3            retn

//////////////  以上是代码  ////////////////


机器码现在是9个字符,去掉首字符后所剩8个字符为:5859a29d。我一开始还在
一句一句地分析指令功能,等到步过00403EDE这句时,眼前忽然一亮:

                     EAX 5859A29D

不会这么简单吧!跟到00403EF1查看ECX的值为79BB7066。从这个子程序返回,到
外面一看,真码就是79bb7066!捏到软柿子了!这么说来,只需要额外编写一个
16进制数值与对应的ASCII字串相互转换的函数,而注册码计算的功能完全由上面
00403EE2到00403EEF这几句所标明了。


  (3)注册函数

void Hextoasc(unsigned long int uValue, char *lpTransformedBuffer)

/* 实现把uValue所对应的16进制数值转化为ASCII字符串。16进制字母采用小写 */
/* 入口参数:uValue——16进制数值
             lpTransformedBuffer——用于存放转化串的缓冲区             */
/* 出口参数:lpTransformedBuffer——ASCII串的指针                      */
/* 注意:对输入不作合法性检测!                                        */

{
   char i = 8, temp;
   lpTransformedBuffer[i]='\0';
   do{
       temp = uValue % 16;
       lpTransformedBuffer[i - 1] = temp + (temp < 10 ? '0': 87);
       uValue = uValue >> 4;
       i = i - 1;
     }while(i != 0);
   
}       

unsigned long int uAsctohex(char *lpString)

/* 实现把lpString对应的字符串转换为16进制数值。 */
/* 注意:对输入不作合法性检测!                 */ 

{
   unsigned long int result = 0;
   char a, i;
   i = 0;
   while(i < 8 && lpString[i] != '\0'){
           if (lpString[i] > '9')
             a = (lpString[i] & 0x1f) + 9;
           else
             a = lpString[i] - '0';
           result = (result << 4) + a;      
           i = i + 1;
           }
   return result;
   
}

void KeyGen(char *lpFingerPrint, char *lpRegCodeBuffer)

/*  注册码计算程序。                                  */
/*  入口参数:lpFingerPrint——机器码字串          
              lpRegCodeBuffer——存放注册码的缓冲区   */
/*  出口参数:lpRegCodeBuffer——注册码字串           */

{
    unsigned long int m;  
    m = uAsctohex(lpFingerPrint + 1);
    m = m ^ 0x6F37AE52;
    m = (m << 27) + (m >> 5);
    Hextoasc(m, lpRegCodeBuffer);
}

  • 标 题: 答复
  • 作 者:BlueT
  • 时 间:2006-04-12 10:02

感觉注册机写得是不是有些复杂了?

我来一个:

  UpdateData(true);
  char serial[10];
  unsigned long tmp;

  strcpy(serial,m_ser);
  tmp=strtoul(serial+1,0,16);
  tmp^=0x6F37AE52;
  tmp=(tmp<<0x1B)+(tmp>>5);
  m_sn.Format("%08x",tmp);
  UpdateData(false);