0x00 前言

补:当年高考结束第一天写的文章...不要太关心当初的词藻...

众所周知,破解流程多以注册机或是补丁告终,而破解补丁(此处破解补丁不局限于传统意义的某一补丁)无非就那么几种,像文件补丁,内存注册机以及水平更高些的手工编写的注册机。

这次练习破解的软件比较典型,分析了几天,有了一些感触,就写下来,文章有些长,不过比较基础,对于分析注册流程一部分,黑客字典II是小榕前辈写的,软件明确指出,注册只是为了看有多少人用,而我今日的目的也只是分析一下。因为注册不是为了盈利,软件并没有加壳保护。如图一。

img

0x01 套路分析

下面还是老套路,载入Ollyice中,依旧用很普通但也很实用的方法,ASCII字符串参考,如图二。

img

上下翻或者右键查找字符串参考,找到如图三的地方,双击进入CPU的反汇编窗口。

img

经验告诉我,注册部分必定在上方不远处,时时注意上方的跳转,向上滚动鼠标一段时间后,会找到如下部分。

img

在图四的最下一行往下会看到:

00401CC7   .  8DB424 840000>lea     esi, dword ptr [esp+84]
00401CCE   .  8D8424 480100>lea     eax, dword ptr [esp+148]         
00401CD5   >  8A10          mov     dl, byte ptr [eax]
00401CD7   .  8ACA          mov     cl, dl
00401CD9   .  3A16          cmp     dl, byte ptr [esi]
00401CDB   .  75 1C         jnz     short 00401CF9
00401CDD   .  3ACB          cmp     cl, bl
00401CDF   .  74 14         je      short 00401CF5
00401CE1   .  8A50 01       mov     dl, byte ptr [eax+1]
00401CE4   .  8ACA          mov     cl, dl
00401CE6   .  3A56 01       cmp     dl, byte ptr [esi+1]
00401CE9   .  75 0E         jnz     short 00401CF9
00401CEB   .  83C0 02       add     eax, 2
00401CEE   .  83C6 02       add     esi, 2
00401CF1   .  3ACB          cmp     cl, bl
00401CF3   .^ 75 E0         jnz     short 00401CD5
00401CF5   >  33C0          xor     eax, eax
00401CF7   .  EB 05         jmp     short 00401CFE

这部分跳转密集,并且有较多的比较指令像cmp,所以基本可以断定此处即跳转向注册成功或失败的地方了。

向上找关键Call,因为上面只有两个比较靠谱的call,可以在这种情况下在上面的call上按F2下断点。下面继续很基本的Ctrl+F2重新载入程序,运行,输入用户名与注册码点确定,Ollyice自动将软件停在已设的断点处。

为了方便单步跟踪,我输入的用户名要比较简单,像我用的lengye而不输入汉字。在断点处我们继续按F8单步跟踪,把注册流程的反汇编窗口里关键的代码看下。我用lengye的用户名,123456为注册码,进行单步跟踪,下面一部分我都加了注释,本想省些的,不过这部分都是完整的注册流程,对后面编写注册机有很大作用,也是分析很久得到的,我就没删去,拣我认为的关键部分分析一下。

00401BF5   .  E8 1CE40100   call    00420016
00401BFA   .  8D7C24 20     lea     edi, dword ptr [esp+20]          ;  出现ascii lengye
00401BFE   .  83C9 FF       or      ecx, FFFFFFFF
00401C01   .  33C0          xor     eax, eax
00401C03   .  F2:AE         repne   scas byte ptr es:[edi]           ;  注册名依次传送,一次一个字母
00401C05   .  F7D1          not     ecx
00401C07   .  49            dec     ecx
00401C08   .  0F84 16020000 je      00401E24                         ;  这一部分是控制计算注册码的循环次数的
00401C0E   .  8DBC24 840000>lea     edi, dword ptr [esp+84]          ;  传送用户自行输入的注册码
00401C15   .  83C9 FF       or      ecx, FFFFFFFF
00401C18   .  F2:AE         repne   scas byte ptr es:[edi]
00401C1A   .  F7D1          not     ecx
00401C1C   .  49            dec     ecx                              ;  控制程序是否跳出循环
00401C1D   .  0F84 01020000 je      00401E24
00401C23   .  8D7C24 20     lea     edi, dword ptr [esp+20]          ;  l 正式开始
00401C27   .  83C9 FF       or      ecx, FFFFFFFF
00401C2A   .  33F6          xor     esi, esi                         ;  esi清零
00401C2C   .  F2:AE         repne   scas byte ptr es:[edi]           ;  l传入
00401C2E   .  F7D1          not     ecx
00401C30   .  49            dec     ecx                              ;  循环次数减少一次
00401C31   .  74 79         je      short 00401CAC                   ;  zf=0  即ecx不为1,不跳  此例第一次时ecx为00000006
00401C33   >  0FBE7C34 20   movsx   edi, byte ptr [esp+esi+20]       ;  edi=l的ascii十进制
00401C38   .  8BC7          mov     eax, edi                         ;  EAX=108 l的ascii
00401C3A   .  B9 0A000000   mov     ecx, 0A                          ;  ecx=0A即十六进制10
00401C3F   .  99            cdq                                      ;  双字扩展,把EAX的字的符号扩到EDX去,edx归零
00401C40   .  F7F9          idiv    ecx                              ;  整数除法  eax108 ecx 10
00401C42   .  8BCA          mov     ecx, edx                         ;  余数edx=8 eax=10
00401C44   .  81E2 01000080 and     edx, 80000001                    ;  and运算,可理解为余数为1则ZF为0跳,此时ecx=8。注意同则ZF标志位为1不同为零
00401C4A   .  79 05         jns     short 00401C51                   ;  正号则转移 看SF标志位为零
00401C4C   .  4A            dec     edx
00401C4D   .  83CA FE       or      edx, FFFFFFFE
00401C50   .  42            inc     edx
00401C51   >  75 16         jnz     short 00401C69                   ;  and运算结果为零ZF=1结果不为零ZF=0,为零时候跳转
00401C53   .  8BC7          mov     eax, edi                         ;  eax=108
00401C55   .  B9 1A000000   mov     ecx, 1A                          ;  ecx=26  1A对应十进制26
00401C5A   .  99            cdq                                      ;  双字扩展
00401C5B   .  F7F9          idiv    ecx                              ;  除求余数
00401C5D   .  80C2 41       add     dl, 41                           ;  余数是4,与十六进制41即十进制65相加得到十进制ascii
00401C60   .  889434 480100>mov     byte ptr [esp+esi+148], dl
00401C67   .  EB 2E         jmp     short 00401C97
00401C69   >  8BC1          mov     eax, ecx                         ;  e的征程  eax=余数1
00401C6B   .  BB 03000000   mov     ebx, 3                           ;  ebx=3
00401C70   .  99            cdq                                      ;  双字扩展,把EAX的字的符号扩到EDX去,edx归零
00401C71   .  F7FB          idiv    ebx                              ;  eax除以ebx商eax==0  余数edx=1  eax=1  edx==0
00401C73   .  85D2          test    edx, edx                         ;  edx为0则test结果为ZF=1
00401C75   .  75 16         jnz     short 00401C8D                   ;  ZF标志位为0时需要眺
00401C77   .  8BC7          mov     eax, edi                         ;  eax=103(g的十进制)
00401C79   .  B9 1A000000   mov     ecx, 1A                          ;  ecx=26
00401C7E   .  99            cdq                                      ;  双字扩展EAX
00401C7F   .  F7F9          idiv    ecx                              ;  eax除以ecx
00401C81   .  80C2 61       add     dl, 61                           ;  eax商为3  edx余数为19  余数十六进制加上十六进制61即十进制97得到122对应z
00401C84   .  889434 480100>mov     byte ptr [esp+esi+148], dl
00401C8B   .  EB 0A         jmp     short 00401C97
00401C8D   >  80C1 31       add     cl, 31                           ;  余数cl=1  加上十六进制31(十进制49)得到十进制ascii
00401C90   .  888C34 480100>mov     byte ptr [esp+esi+148], cl
00401C97   >  8D7C24 20     lea     edi, dword ptr [esp+20]
00401C9B   .  83C9 FF       or      ecx, FFFFFFFF
00401C9E   .  33C0          xor     eax, eax
00401CA0   .  46            inc     esi
00401CA1   .  F2:AE         repne   scas byte ptr es:[edi]           ;  esi由零变一加一操作,进入下一字符
00401CA3   .  F7D1          not     ecx
00401CA5   .  49            dec     ecx                              ;  自减
00401CA6   .  3BF1          cmp     esi, ecx                         ;  比较esi与ecx
00401CA8   .^ 72 89         jb      short 00401C33                   ;  继续循环,并决定是否跳出循环
00401CAA   .  33DB          xor     ebx, ebx
00401CAC   >  55            push    ebp
00401CAD   .  8D8C24 EC0000>lea     ecx, dword ptr [esp+EC]
00401CB4   .  889C34 4C0100>mov     byte ptr [esp+esi+14C], bl
00401CBB   .  E8 F0010000   call    00401EB0
00401CC0   .  899C24 B40100>mov     dword ptr [esp+1B4], ebx
00401CC7   .  8DB424 840000>lea     esi, dword ptr [esp+84]
00401CCE   .  8D8424 480100>lea     eax, dword ptr [esp+148]         ;   (lengye对应的注册码ASCII “E2Gz22”)

从00401BF5到00401C31与00401CA1到00401CA8部分是控制循环的,可以注意到,每一个注册名的字符对应一个注册码。而这一部分的作用就是遍历用户名,并计算对应的注册码。另外强调一下JNZ跳转,JNZ是比较的是ZF标志位,ZF标志位前相应命令执行后,若结果为0,则ZF标志位为1,反之为0,这是个很易弄混的地方。其他的命令朋友们若有不懂得建议谷歌一下。

0x02 补丁制作

现在已经分析到注册流程,先看一下目前已掌握信息,已经可以用来尝试补丁制作了。想了以下几种方法:

  • 直接修改文件

    这个就是很传统的暴破了,针对这个软件可以有不同方法,这里仅举两种来起抛砖之意。

    • 一种是可以看到00401D00 . /0F85 DF000000 jnz 00401DE5处跳转到注册码错误的提示处,于是就将JNZ修改为JZ。
    • 还有一种方法,注意上面最后几行代码。

      00401CC7 . 8DB424 840000>lea esi, dword ptr [esp+84]
      00401CCE . 8D8424 480100>lea eax, dword ptr [esp+148]

    这两行可以看用户输入的注册码与软件计算出的注册码在此处被传送到esi和eax,然后进行比较,所以我们可以修改为

    00401CC7   .  8DB424 840000>lea     esi,  dword ptr [esp+148]  
    00401CCE   .  8D8424 480100>lea     eax, dword ptr [esp+148]  

    这样就让程序自己去比较吧!

  • 文件补丁

    已经有了修改好的文件了,那么就可以制作比较简单的文件补丁了。

    拿CodeFusion为例(keymake等也可以制作)。打开界面如图五,

    img

    输入必要的信息后单击下一步,在要“补丁的文件”部分有个绿色的“+”,单击选择文件如图六

    img

    然后单击“要补丁的数据”部分的绿色“+”,选择“文件比较”,选好暴破后的文件后单击左下角的比较按钮,然后就可以自动比较了,如图七。

    img

    确定后回到程序单击“下一步”,单击“创建Win32可执行程序”,然后单击“完成”即可,制作好后将文件补丁与原程序放在同一目录下,运行文件补丁即可,如图八。

    img

    单击开始后原程序即可改为暴破后的文件了。

  • 内存注册机

    这个曾在论坛发过类似的,数据库清理了,就简单提一下,用keymake里内存注册机与内存补丁原理及制作是一个思路。

    00401CD5   > /8A10                mov     dl, byte ptr [eax]

    这里就是把软件计算出的注册码存到寄存器eax中

    中断地址      中断次数    第一字节     指令长度     
    00401CD5      1          8A           2  

    用keymake这样添加即可,不再赘述。程序如图九。

    img

    至于C代码,以MessageBox显示的:

#include <windows.h>
void main()
{
    PROCESS_INFORMATION pi;
    STARTUPINFO si;
    DEBUG_EVENT devent;
    CONTEXT Regs;
    Regs.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS | CONTEXT_CONTROL;
    ZeroMemory(&pi, sizeof(pi));
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    LPVOID breakAddr = (LPVOID)0x00401CD5 ;  //这是断点地址,就是用工具生成注册机时要填的那个地址
    BYTE firstByte = 0x8A;      //同样是生成注册机时要填的那个第一字节
    BYTE bp = 0xCC;
    DWORD real;
    BYTE buf[64];
    int nCount = 0;
    DWORD way;
    //用调试方式创建需要破解的程序进程,此处进程为字典.exe
    if(CreateProcess("字典.exe", NULL, NULL, NULL, FALSE, 
        DEBUG_ONLY_THIS_PROCESS | DEBUG_PROCESS, NULL, NULL, &si, &pi))
    {
        //调试循环体
        while(TRUE)
        {
            //等待调试事件
            if(WaitForDebugEvent(&devent, INFINITE))
            {
                way = DBG_EXCEPTION_NOT_HANDLED;
                switch(devent.dwDebugEventCode)
                {
                case EXIT_PROCESS_DEBUG_EVENT:  //进程退出事件
                    //return;
                    break;
                case EXCEPTION_DEBUG_EVENT:     //异常事件
                    {
                        if(devent.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT)
                        {
                            nCount++;
                            //创建进程后会产生第一次异常中断,此时可以设置断点
                            if(nCount == 1)
                            {
                                //读取第一个字节进行效验
                                ReadProcessMemory(pi.hProcess, breakAddr, buf, 1, &real);
                                if(buf[0] == firstByte)                
                                {
                                    //设置断点
                                    DWORD oldProtect;
                                    VirtualProtectEx(pi.hProcess, breakAddr, 1, PAGE_READWRITE, &oldProtect);
                                    WriteProcessMemory(pi.hProcess, breakAddr, &bp, 1, &real);
                                    VirtualProtectEx(pi.hProcess, breakAddr, 1, oldProtect, NULL);
                                }
                                way = DBG_CONTINUE;
                            }
                            //如果是在断点处暂停,就可以读取内存中的注册码
                            else if(devent.u.Exception.ExceptionRecord.ExceptionAddress == breakAddr)
                            {
                                GetThreadContext(pi.hThread, &Regs);
                                ReadProcessMemory(pi.hProcess, (char *)Regs.Eax, buf, 64, &real);  //从寄存器Eax中读取注册码
                                MessageBox(NULL, (char *)buf, "注册机 By 冷夜 [B.H.S.T]        ", 0);
                                //去除断点
                                DWORD oldProtect;
                                VirtualProtectEx(pi.hProcess, breakAddr, 1, PAGE_READWRITE, &oldProtect);
                                WriteProcessMemory(pi.hProcess, breakAddr, &firstByte, 1, NULL);
                                VirtualProtectEx(pi.hProcess, breakAddr, 1, oldProtect, NULL);
                                //回到断点处继续执行
                                Regs.Eip = (DWORD)breakAddr;
                                SetThreadContext(pi.hThread, &Regs);
                                way = DBG_CONTINUE;
                            }
                        }
                        break;
                    }
                }
                ContinueDebugEvent(pi.dwProcessId, pi.dwThreadId, way);
            }
        }
    }
    else
        MessageBox(NULL, "请将注册机和要破解程序放在同一目录", NULL, 0);
}

0x03 注册机编写

就以上对反汇编代码的分析,得到注册码的计算流程如下:

输入用户名,进入计算注册码的循环

  • 传送第一个字符的十六进制
    • 字符ascii十进制与十进制10相除,余数与800000001进行and与运算,而800000001即十进制无符号1,进行十进制and运算可发现奇数则结果为一,偶数则结果为零,若结果为零, 跳入1,否则跳入2。
      • 1:ascii对应十进制与十进制26相除得到余数x,再用x加上65(十六进制41)得到十进制数,对应注册码ascii可得。
      • 2:余数与3相除,若余数不为零,进入3,若余数为零,进入4。
      • 3:ascii十进制与10相除的余数和十进制49(十六进制31)相加,得到十进制数,找到对应的ascii。
      • 4:取该字母的ascii十进制与26相除,余数与十进制97相加,得到十进制数,找到对应ascii。

以上便是整理的注册流程,下面编写相应的注册机了。我用比较习惯的Delphi编写,其他高级语言类似。

关键代码如下:

procedure TForm1.Button1Click(Sender: TObject);
   var s:string;
   i,e,t,y,u:integer;
begin
  edit2.Text:='';
  s:=edit1.text;
  for i:=1 to length(s) do
    begin
    e:= ord(s[i]);
    t:= e mod 10;
    if  odd(t) then
       begin
        y:= t mod 3 ;
        if y = 0 then
          begin
          u:=e mod 26;
          u:= u+97 ;
          end
        else
          begin
          u:= t+49 ;
          end
       end
    else
        begin
         y:= e mod  26;
         u:= y + 65;
        end;
   edit3.Text:= chr(u);
   edit2.text:= edit2.Text +edit3.Text;
   end;
end;

编写好注册机如图十

img

Comments
Write a Comment