吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 13902|回复: 32
收起左侧

[原创] 160 个 CrackMe 之 082 - ultraschall 自带微型VM的跟踪和分析

  [复制链接]
solly 发表于 2019-11-24 12:16
本帖最后由 solly 于 2019-11-27 09:36 编辑

160 个 CrackMe 之 082 - ultraschall 是一个自带了包含10个指令的微型VM的 Crackme 程序,并且其VM部分的代码带有“花指令”干扰。
我们先来看看文件信息:
00.png
显示是用汇编语言写的,没有加壳。重新用 “Scan /t” 扫描一下:
01.png
这次显示有一个 PE Diminisher v0.1 处理过了,应该就是加了一些“花指令”,我们在后面会看到。


但是这个程序在 Win10 下不能直接运行,会报兼容性问题,如下图所示,Windows10不允许其运行:
10.png

这个错误是由于程序的“节”的参数不严谨造成的,是“.rsrc”节的大小超出了文件结尾,在 Windows 9x 下没有问题,在 Win10下则不认可,不过可以修正(本论坛有贴有详细説明,修改原理这里不重复了),修改方法如下面的两图所示:
11.png
上图中的 0x00004A00 需要修改,改成 0x00004200 即可,如下图所示:
12.png
修改后即可在 Windows 10 下正常运行了。


启动后界面如下:
13.png
需要找出其 SN 或写出注册机。


接下来我们用 OD 加载 Crackme,先进行静态分析。
20.png
加载后,如上图所示,用汇编写的代码比较精简整洁,结构清晰。
上图中红框中的代码就是 WinMain() 入口函数的代码,直接生成一个基于“对话框”的程序,没有一句多余代码。
由上图可以看到对话框的事件处理回调函数 DlgProc() 的入口为 0x0040103E,也是直接就是在 WinMain() 函数的后面,如上图蓝框所标示的部分。
下图所示就是 DlgProc() 的部分代码:
21.png
最前面的代码是处理 WM_SHOWWINDOW 消息,对文本框的输入长度进行了一些限制,防止后面接收字符串时溢出。同时将光标(输入焦点)定位到输入用户名的文本框内。
图中红框中所示代码是发送一个自定义的消息,不过这个消息是用来退出 CrackMe 的,也就是将 WM_CLOSE 消息,转换成自定义的 WM_COMMAND  消息,再由 WM_COMMAND  消息处理代码来关闭 Crackme。


下面来看看 DlgProc() 中对 WM_COMMAND 处理的代码,如下图所示:
22.png

上图中,蓝框中的代码就是处理自定义的 WM_COMMAND 消息的,用来关闭对话框程序。
主要的处理代码在红框中,这就是处理“Register”按钮事件的代码。关键就是一个 call 调用: call 004010FF,这里调用了一个微型 VM 系统。往下看,就可以看到这个调用的代码,如下图所示:
23.png
可以看到,这个函数内部的代码很乱,这是由于有“花指令”的干扰,往下拖,看看函数结尾在哪里,如下图所示,在 IAT 的跳转表前面就是其结尾:
24.png

下面先去除“花指令”,在 OD 的“插件”菜单中,选择“5. 花指令去除器”==>“去除花指令”,在弹出的对话框中输入搜索地址和搜索大小,如下图所示:
25.png
搜索地址为函数地址:0x004010FF,搜索大小为 0x31F = 0x0040141E - 0x004010FF。再点“确定”就可以了,如下图所示:
26.png
去除了 112 条花指令。后面OD代码区的花指令变成了大量的 nop 指令了,如下图所示,就是 VM 的入口,先将Host机器的上下文(寄存器信息)保存。
27.png

同时在地址 0x0040110D 处的 NOP 指令,是 VM 机器的执行标志,后面有说明,当这个字节由 0x90 变成 0xCC 时,VM 结束。
静态分析到这里,下面进行动态跟踪,分析注册机制。

我们先在下图所示位置(0x004010CE),下一个“断点”:
30.png

然后按 “F9” 运行 CrackMe,显示主界面,并输入注册假码数据,如下图所示:
31.png
输入完注册信息,点“Register”按钮,OD 马上断下程序,如下图所示,定位到了我们前面下的断点处:
32.png
可以看到,按钮的 ControlID = 0x000003F4,按几次 F8,来到那个 call 004010FF 处,如下图所示,同时在 call 后面下一个断点(防止退出CrackMe):
33.png

我们按 F7 进入 call 调用,跟踪 VM 的执行。


一路按 F8 执行,来到如下图所示位置,这个 VM 内的 call dword ptr [eax+esi],就是解释执行 VM 指令的调用:
34.png
其中 al 为操作码,esi 为操作码解释执行程序的入口基址,当前的 cx 为下一条指令的地址,EBX 为第一个操作数,EDX 为第二个操作数,后面再对指令结构详细説明,在 OD 的数据区,就显示了当前正在执行的VM指令。
eax+esi = 0x00403199,这个位置保存的是解释执行当前VM指令的子过程(0x0040128A),如下图所示,在 OD 的数据区就是这些子过程的入口表,共 10 个入口,表示这个小小的 VM 只有 10 条指令。
35.png

具体入口代码如下:
[Asm] 纯文本查看 复制代码
;  Func[0] ~ Func[9], 共 10 个指令解释程序入口地址
00403189: 004011DF    00401202    00401224    00401256
00403199: 0040128A    004012E0    00401343    0040136F
004031A9: 00401380    004013C0

VM 执行的第一条指令的 opcode 为 0x10,这是一条从界面取文本数据的指令,其第2个参数就是 ControlID,如下图所示,取到了用户名:

36.png
本 VM 机器执行的 VM 程序全部指令序列如下所示:
[Asm] 纯文本查看 复制代码
 0040300B  10 E0 00 00 00 F0 03 00 00 0B 00   ; Func[4] GetText,取用户名 存于 [004030EB], "solly", API:  User32.GetDlgItemTextA()
00403016  10 F5 00 00 00 F1 03 00 00 16 00   ; Func[4] GetText,取序列号(文本) 存于 [00403100], "78787878", API:  User32.GetDlgItemTextA()
00403021  08 DC 00 00 00 DC 00 00 00 21 00   ; Func[2] Sub,计算 [004030E7] = [004030E7] - [004030E7] = 0 - 0 = 0,[004030E7]变量初始化为0
0040302C  04 DC 00 00 00 5A 01 00 00 2C 00   ; Func[1] Add,计算 [004030E7] = [004030E7] + [00403165] = 0 + 0 = 0 
00403037  20 E0 00 00 00 4E 01 00 00 37 00   ; Func[8] Equ,赋值 [004030E7] = *((dword *)&name[j]) = 0x6C6C6F73("soll"),("olly"),......, 下面Func[6]跳转到这里执行循环。
00403042  04 4E 01 00 00 56 01 00 00 42 00   ; Func[1] Add,计算 [00403159] = [00403159] + [00403161] = 0x00 + 0x01 = 0x01, j++(char索引)
0040304D  04 4A 01 00 00 DC 00 00 00 4D 00   ; Func[1] Add,计算 [00403155] = [00403155] + [004030E7] = 0x00 + 0x6C6C6F73 = 0x6C6C6F73, sum += *((dword *)&name[j]), 循环结束时 sum = 0x5589C233
00403058  08 52 01 00 00 56 01 00 00 58 00   ; Func[2] Sub,计算 [0040315D] = [0040315D] - [00403161] = 0x14 - 0x01 = 0x13,[0040315D] 为循环次数, i--(char索引)
00403063  18 52 01 00 00 00 00 2C 00 63 00   ; Func[6] Jnz,不为0跳转 (操作数1[0040315D] != 0) 结果(IP) == 操作数2:0x002C0000 ==> bswap(0x00002C00) ==> (xchg)0x0000(002C),跳回去循环
0040306E  20 66 01 00 00 52 01 00 00 6E 00   ; Func[8] Equ,赋值 [004030E7] = xor_arr[i] = [00403171](0xD4E1C094), 下面Func[6]跳转到这里执行循环。
00403079  0C 4A 01 00 00 DC 00 00 00 79 00   ; Func[3] Xor,计算 [00403155] = [00403155] ^ [004030E7],  sum ^= *((dword *)&xor_arr[j]),  循环结束时 sum = 0x061F4FD2
00403084  04 52 01 00 00 62 01 00 00 84 00   ; Func[1] Add,计算 [0040315D] = [0040315D] + [0040316D] = 0x00 + 0x04 = 0x04, i++(int索引) 
0040308F  08 4E 01 00 00 62 01 00 00 8F 00   ; Func[2] Sub,计算 [00403159] = [00403159] - [0040316D] = 0x14 - 0x04 = 0x10, j--(int索引)
0040309A  18 4E 01 00 00 00 00 63 00 9A 00   ; Func[6] Jnz,不为0跳转 (操作数1[00403159] != 0) 结果(IP) == 操作数2:0x00630000 ==> bswap(0x00006300) ==> (xchg)0x0000(0063),跳回去循环
004030A5  24 7A 01 00 00 F1 03 00 00 A5 00   ; Func[9] GetInt,取序列号(整数) 存于 [00403185], API:  User32.GetDlgItemInt(), SN = eax == 0x04B23526(78787878)
004030B0  08 4A 01 00 00 7A 01 00 00 B0 00   ; Func[2] Sub,计算 [00403155] = [00403155] - [00403185] = sum - SN,sum -= SN,结果为0时表示序列号正确
004030BB  18 4A 01 00 00 00 00 C6 00 BB 00   ; Func[6] Jnz,不为0跳转 (操作数1(sum) != 0) 结果(IP) == 操作数2:0x00C60000 ==> bswap(0x0000C600) ==> (xchg)0x0000(00C6),跳转去显示注册错误
004030C6  14 2C 01 00 00 00 00 00 00 D1 00   ; Func[5] Msg,显示注册正确,MsgText == [00403137] ===> "Registration Code is CORRECT!",API: USER32.MessageBoxExA()
004030D1  14 0A 01 00 00 00 00 00 00 D1 00   ; Func[5] Msg,显示注册错误,MsgText == [00403115] ===> "Registration Code is not correct!",API: USER32.MessageBoxExA()
004030DC  1C 00 00 00 00 00 00 00 00 00 00   ; Func[7] End,设置程序结束标志,[0040110D] = 0xCC;  int3, 设置程序结束标志

可以看出,一共20条指令代码,每个指令占用 11 个字节,具体结构如下所示:
[C++] 纯文本查看 复制代码
/// 指令结构 
struct Instruction {
        char opcode;
        DWORD operand1; /// and result
        DWORD operand2;
        WORD next_ip;
};

VM 伪指令的主要执行流程如下:
1、首先取得用户名和字符串形式的注册码;
2、循环计算用户名中字符的ASCII码的累加和(每次读取4个字符,合并其ASCII码组成一个 DWORD进行累加运算),但索引指针还是一个字节一个字节的移动;
3、计算得到的ASCII码的累加和再循环与5个DWORD常量依次进行异或运算后得到的数值,就是最终的序列号;
4、重新从界面取得无符号整数形式的注册码,与前面计算的序列号进行比较(相减运算),如果相等,则显示序列号正确的提示,否则显示序列号不正确的提示。
5、最后设置结束标志,结束VM主循环地执行,退出VM。

参与异或运算的常量数组如下:
[Asm] 纯文本查看 复制代码
;第二次循环进行异或的操作数,5个整数常量:
00403171  94 C0 E1 D4 11 98 1F FF C8 BB FA 91 10 94 1D 78 
00403181  BC FA 8F 91 

以16进制形式表示:0xD4E1C094, 0xFF1F9811, 0x91FABBC8, 0x781D9410, 0x918FFABC;


VM的具体代码如下所示(去除了大量的 nop 指令,所以指令前的地址值不是连贯的了):
[Asm] 纯文本查看 复制代码
004010FF      55                 push    ebp
00401100      8BEC               mov     ebp, esp
00401107      60                 pushad
00401108      9C                 pushfd
0040110B      90                 nop
0040110C      90                 nop
0040110D      90                 nop                                   ;  程序开始(nop)/结束(int3)标志存放点
0040110E      90                 nop
00401112      BB 0B304000        mov     ebx, 0040300B                 ;  初始化数据区基址
0040111B      BE 89314000        mov     esi, 00403189                 ;  初始化指令解释执行函数dispatcher表基址
00401124      33C0               xor     eax, eax                      ;  初始化指令指针寄存器IP = 0
00401128      90                 nop
00401129      90                 nop
0040112A      50                 push    eax                           ;  开始循环执行VM指令
0040112B      53                 push    ebx
00401131      8BF8               mov     edi, eax                      ;  IP
00401137      50                 push    eax                           ;  IP
0040113C      3D FF000000        cmp     eax, 0FF                      ;  IP>255
00401145      0F8F 88000000      jg      004011D3                      ;  IP大于255则退出VM执行循环
00401153      90                 nop
00401154      D7                 xlat    byte ptr [ebx+al]             ;  取指令 AL=opcode, 相当于指令 mov al, byte ptr [ebx+al]
00401155      50                 push    eax
0040115E      90                 nop
0040115F      8B541F 05          mov     edx, dword ptr [edi+ebx+5]    ;  取操作数2
00401163      90                 nop
00401167      59                 pop     ecx                           ;  指令
0040116C      58                 pop     eax                           ;  IP
00401172      51                 push    ecx                           ;  指令
00401176      90                 nop
00401177      66:8B4C18 09       mov     cx, word ptr [eax+ebx+9]      ;  取下一条指令IP
0040117C      90                 nop
00401180      8B5C1F 01          mov     ebx, dword ptr [edi+ebx+1]    ;  取操作数1,结果缓冲区
00401184      58                 pop     eax                           ;  指令
00401188      90                 nop
00401189      FF1430             call    dword ptr [eax+esi]           ;  执行指令
0040118C      90                 nop
00401190      5B                 pop     ebx
00401195      58                 pop     eax
0040119A      8BC1               mov     eax, ecx                      ;  eax ==>下一条指令结构
0040119F      90                 nop
004011A0      803D 0D114000 CC   cmp     byte ptr [40110D], 0CC        ;  int3, 程序是否结束,0xCC表示VM程序结束
004011AB    ^ 0F85 79FFFFFF      jnz     0040112A                      ;  没有结束则继续执行下一条指令
004011B1      90                 nop
004011B5      C605 0D114000 90   mov     byte ptr [40110D], 90         ;  改成 nop,可重新开始执行的标志
004011C0      90                 nop
004011C1      9D                 popfd
004011C6      61                 popad
004011CB      C9                 leave
004011CC      C2 0400            retn    4                             ;  返回,退出VM程序
004011CF      90                 nop
004011D0      90                 nop
004011D3      8A0418             mov     al, byte ptr [eax+ebx]        ;  IP>255 时跳转到这里,取指令并重新返回循环执行指令
004011DA    ^ E9 76FFFFFF        jmp     00401155
;===============================================================================================================================
004011DF      90                 nop                                   ;  Func[0] 入口,Equ 指令
004011E3      C605 7A124000 89   mov     byte ptr [40127A], 89         ;  修改为 Mov 指令
004011EF      68 46124000        push    00401246                      ;  ret 返回地址
004011F8      68 56124000        push    00401256                      ;  ret 返回地址
00401201      C3                 retn                                  ;  jmp 00401256
;===============================================================================================================================
00401202      90                 nop                                   ;  Func[1] 入口,Add 指令
00401206      C605 7A124000 01   mov     byte ptr [40127A], 1          ;  修改为 Add 指令
00401211      68 46124000        push    00401246                      ;  ret 返回地址
0040121A      68 56124000        push    00401256                      ;  ret 返回地址
00401223      C3                 retn                                  ;  jmp 00401256
;===============================================================================================================================
00401224      90                 nop                                   ;  Func[2] 入口,Sub 指令
00401228      C605 7A124000 29   mov     byte ptr [40127A], 29         ;  修改为 Sub 指令,修改运算符
00401233      68 46124000        push    00401246                      ;  ret 返回地址
0040123C      68 56124000        push    00401256                      ;  ret 返回地址
00401245      C3                 retn                                  ;  jmp 00401256, call Func[3]
;===============================================================================================================================
00401246      90                 nop                                   ;  Func_Restore()
0040124A      C605 7A124000 31   mov     byte ptr [40127A], 31         ;  恢复指令
00401255      C3                 retn
;===============================================================================================================================
00401256      90                 nop                                   ;  Func[3] 入口,Xor 指令,同时兼VM的加法器或EU
0040125A      51                 push    ecx
0040125F      81C3 0B304000      add     ebx, 0040300B                 ;  ebx ===> 操作数1,以及结果
00401269      81C2 0B304000      add     edx, 0040300B                 ;  edx ===> 操作数2
00401274      8B12               mov     edx, dword ptr [edx]          ;  取得操作数2的值
0040127A      3113               xor     dword ptr [ebx], edx          ;  执行操作,这里的指令会是可变的,Mov, Add, Sub, Xor 等
00401280      59                 pop     ecx
00401285      C3                 retn                                  ;  返回 00401246 恢复指令, 由 00401233 处指令压入
;===============================================================================================================================
00401289      90                 nop
0040128A      51                 push    ecx                           ;  Func[4] 入口, GetDlgItemText(),ControlID为参数
00401294      81C3 0B304000      add     ebx, 0040300B                 ;  ebx 为存放用户名和序列号的buffer地址
004012A7      6A 15              push    15                            ;  length
004012AD      53                 push    ebx                           ;  buffer
004012B2      52                 push    edx                           ;  ControlID
004012B3      FF35 B8314000      push    dword ptr [4031B8]            ;  hWnd
004012BE      68 D5124000        push    004012D5                      ;  API调用的返回地址
004012C7      68 3C144000        push    <jmp.&USER32.GetDlgItemTextA>
004012D0      C3                 retn                                  ;  调用 API
004012D4      90                 nop
004012D5      59                 pop     ecx                           ;  API 返回位置
004012DA      C3                 retn
;===============================================================================================================================
004012DF      90                 nop
004012E0      51                 push    ecx                           ;  Func[5] 入口,MessageBoxA()
004012E9      81C3 0B304000      add     ebx, 0040300B                 ;  MsgText
004012F3      6A 00              push    0
004012F9      6A 00              push    0
004012FF      68 00304000        push    00403000                      ;  MsgTitle ==> "CrackME #1"
00401308      53                 push    ebx                           ;  MsgText
00401312      FF35 B8314000      push    dword ptr [4031B8]            ;  hWnd
00401321      68 39134000        push    00401339                      ;  API 调用返回地址
0040132A      68 48144000        push    <jmp.&USER32.MessageBoxExA>
00401334      C3                 retn                                  ;  调用 API
00401338      90                 nop
00401339      59                 pop     ecx                           ;  API 返回位置
0040133E      C3                 retn
;===============================================================================================================================
00401343      90                 nop                                   ;  Func[6] 入口,Jnz 指令
00401348      81C3 0B304000      add     ebx, 0040300B
0040134E      8B1B               mov     ebx, dword ptr [ebx]
00401350      85DB               test    ebx, ebx                      ;  检测条件
00401356      74 12              je      short 0040136A
0040135C      8BCA               mov     ecx, edx                      ; 不等于0,则改变IP,执行跳转
00401362      0FC9               bswap   ecx
00401368      86CD               xchg    ch, cl                        ;  IP == cl
0040136A      90                 nop                                   ;  如果等于0,则直接跳转到此,不改变IP。
0040136E      C3                 retn
;===============================================================================================================================
0040136F      90                 nop                                   ;  Func[7] 入口,End 指令
00401373      C605 0D114000 CC   mov     byte ptr [40110D], 0CC        ;  int3, 设置程序结束标志
0040137F      C3                 retn
;===============================================================================================================================
00401380      90                 nop                                   ;  Func[8] 入口, Equ& 指令
00401384      51                 push    ecx
00401389      81C3 0B304000      add     ebx, 0040300B                 ;  操作数1
00401393      81C2 0B304000      add     edx, 0040300B                 ;  操作数2
0040139E      8B12               mov     edx, dword ptr [edx]          ;  取操作数2的值
004013A4      8B0C1A             mov     ecx, dword ptr [edx+ebx]
004013AB      890D E7304000      mov     dword ptr [4030E7], ecx       ;  返回结果
004013B5      59                 pop     ecx
004013BF      C3                 retn
;===============================================================================================================================
004013C0      51                 push    ecx                           ;  Func[9] 入口,GetDlgItemInt()
004013CA      81C3 0B304000      add     ebx, 0040300B
004013D9      6A 00              push    0
004013DF      6A 00              push    0
004013E5      52                 push    edx                           ;  ControlID, 操作数2
004013E6      FF35 B8314000      push    dword ptr [4031B8]            ;  hWnd
004013F1      68 08144000        push    00401408                      ;  API 调用返回地址
004013FA      68 42144000        push    <jmp.&USER32.GetDlgItemInt>
00401403      C3                 retn                                  ;  调用 API
00401407      90                 nop
00401408      90                 nop                                   ;  API 返回位置
0040140C      8903               mov     dword ptr [ebx], eax          ;  取得的界面整数数据
00401412      59                 pop     ecx
00401417      C3                 retn
0040141C      90                 nop
0040141D      CC                 int3

VM 解释执行程序的分析説明都包含在上面的代码内了。

以第1条指令为例説明指令的解释执行过程:
[Asm] 纯文本查看 复制代码
; 第1条指令
0040300B  10 E0 00 00 00 F0 03 00 00 0B 00   ; Func[4] GetText,取用户名 存于 [004030EB], "solly", API:  User32.GetDlgItemTextA()

1、指令的 opcode 为 0x10,operand1 为 0x000000E0,operand2 为 0x000003F0,next_ip 为 0x000B,执行该指令时,将 0x10 解码成微代码函数索引,通过该索引调用执行代码完成指令功能,host_func = opcode + entres_base = opcode + ESI = 0x10 + 0x00403189 = 0x00403199,在 0x00403199 处保存的是这条指令的解释程序(微代码)的入口地址,相当于执行 call [0x00403199]。
2、操作数 operand1 = 0x000000E0,是接收字符串的缓冲区地址,与物理内存的联系是 host_address = operand1 + base_addr = operand1 + ebx = 0x000000E0 + 0x0040300B = 0x004030EB,这个0x004030EB就是实际的物理地址。这个地址是调用 Windows API:GetDlgItemTextA() 的 lpString 参数。
3、操作数 operand2 是一个立即数,表示 ControlID = 0x000003F0,这个数据是调用 Windows API:GetDlgItemTextA() 的 nIDDlgItem 参数 。
4、最后的 next_ip 的计算为 host_ip = next_ip + base_addr = next_ip + ebx = 0x000B + 0x0040300B = 0x00403016,所以下一条指令存贮在 0x00403016 的位置。

通过分析 VM 所执行的伪指令序列(见前面的指令表), 可以看出,用了 20 条伪指令就组成了注册算法程序,并由 VM 解释执行。

通过第1条指令的执行过程解释,我们对其指令有所了解,下面我们可以通过patch方式修改伪指令来通过注册验证(Crackme 不允许这种操作,这里只是演示,注册机源码在后面有)。需要修改的伪指令如下所示,就是一条 Jnz 指令,下面显示的第1条指令:
[Asm] 纯文本查看 复制代码
004030BB  18 4A 01 00 00 00 00 C6 00 BB 00   ; Func[6] Jnz,不为0跳转 (操作数1(sum) != 0) 结果(IP) == 操作数2:0x00C60000 ==> bswap(0x0000C600) ==> (xchg)0x0000(00C6),跳转去显示注册错误
004030C6  14 2C 01 00 00 00 00 00 00 D1 00   ; Func[5] Msg,显示注册正确,MsgText == [00403137] ===> "Registration Code is CORRECT!",API: USER32.MessageBoxExA()
004030D1  14 0A 01 00 00 00 00 00 00 D1 00   ; Func[5] Msg,显示注册错误,MsgText == [00403115] ===> "Registration Code is not correct!",API: USER32.MessageBoxExA()

可以看到,其 opcode = 0x18,操作数  operand2 = 0x00C60000,通过VM 的 IP 转换算法,最后会变成 0xC6,这就是 Jnz 执行时,如果条件为 True(不相等),则将 next_ip 改为 0x00C6,而原来的 next_ip = 0x00BB,即条件为 False,则继续执行 0x00BB 处的指令,通过上面的 host_ip 计算方法可知,这两个地址分别对应的指令实际地址如下:
[Asm] 纯文本查看 复制代码
0x00C6 ===> 0x0040300B + 0x00C6 = 0x004030D1,执行显示 "Registration Code is not correct!" 的指令。
0x00BB ===> 0x0040300B + 0x00BB = 0x004030C6,执行显示 "Registration Code is CORRECT!" 的指令。

所以,只要把这条 Jnz 的 operand2 修改为 next_ip 一样就可以通过注册。修改结果如下:
[Asm] 纯文本查看 复制代码
; Patch 后的 Jnz 指令
004030BB 18 4A 01 00 00 00 00 BB 00 BB 00 ; Func[6] Jnz,不为0跳转

这样,不管条件如何,都是跳转到(0x00BB),显示注册码正确的提示了。

另外,需要説明一下,如下图所示:
37.png
用户名和注册码的都是21个字节的 char[21] 数组,存贮是连续的,中间没有间隙,所以,在对用户名进行ASCII码求累加和计算时,由于每次都取4个字节,当在进行最后2个字节计算时,就会超出用户名的存贮区域,读取到序列号的最前面2个字节的内容。
如上图所示,在最后两次计算,取得的数据分别是:0x37000000 (00,00,'\0', '7')和 0x38370000(00,'\0','7','8'),所以在进行注册机编写时也要考虑这个问题。

下面是注册机代码,通过两种方式计算注册码,第一种是碰撞测试注册码的前2字节(00~99),测试注册码是否合法,测试00时,效果与第二种方式一致。
第二种方式,是在注册码前加上两个前导 ‘0’,因为前导 0 不影响注册码的大小,可以一次性计算出注册码(第二种算法本论坛中已有说明,参考了“反沉沦”的帖子:https://www.52pojie.cn/forum.php ... &page=5#pid11840041)。
[Asm] 纯文本查看 复制代码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
   
int getSN1(char * name);
int getSN2(char * name);
int testSN1(long sum_name);
  
/// 伪指令结构 
struct Instruction {
        char opcode;
        DWORD operand1; /// and result
        DWORD operand2;
        WORD next_ip;
};
  
int main(int argc, char** argv) {
        char name[256];
        printf("Input your name: ");
        gets(name);
          
        getSN1(name);  //// 方式1, 碰撞测试 
        //getSN2(name);  //// 方式2,加双前导 0 
          
        return 0;
}
  
long xor_base[] = {0xD4E1C094, 0xFF1F9811, 0x91FABBC8, 0x781D9410, 0x918FFABC};
  
int getSN1(char * name) {
    char buffer[48];
      
    memset(buffer, 0, 48);
      
    /// name: 52pojie.cn
    ///   SN: 1557972636
      
    int n = strlen(name);
    if (n>20) {
        n = 20;
    }
    strncpy(buffer, name, n);
          
    long sum_name = 0;
          
    for(int i=0; i<20; i++) {
        sum_name += * ((long *)&buffer[i]); //// 将char* 当作 long* 取 name,一次取4个字符 
    }
          
    int count = testSN1(sum_name);
    if(count == 0) {
        printf("SN: no result.\n");
    }
          
    return 0;
}
  
int getSN2(char * name) {
    char buffer[48];
      
    memset(buffer, 0, 48);
      
    /// name: 52pojie.cn
    ///   SN: 001524614812
      
    int n = strlen(name);
    if (n>20) {
            n = 20;
        }
        strncpy(buffer, name, n);
  
    //// SN处理,SN 前导 0 不影响数值大小 
    buffer[21] = '0'; /// CrackMe 中对 name 操作时取到了 SN 最前面两个字符 
    buffer[22] = '0'; /// CrackMe 中对 name 操作时取到了 SN 最前面两个字符 
          
    long sum = 0;
          
    for(int i=0; i<20; i++) {
        sum += * ((long *)&buffer[i]); //// 将char* 当作 long* 取 name,一次取4个字符 
    }
  
//        long xor_check = 0;
//        for(int i=0; i<5; i++) {
//                xor_check ^= xor_base[i];
//        }
    long xor_check = 0x53968DE1; //// 5 个整数的 Xor 结果值 
    // printf("xor check: %08X\n", xor_check);
    sum ^= xor_check;
  
    //// SN处理,SN 前导 0 不影响数值大小 
    printf("SN: 00%lu\n", (unsigned long)sum);
          
    return 0;
}
  
/*
 * SN 前两位碰撞测试
 */
int testSN1(long sum_name) {
    char sn[16];
    //long sum0 = 0xE652C233;
    int n = 0;
    long sum0 = sum_name;
    for(int i=9; i>=0; i--) {
        long k = i+0x30;
        long sum1 = (k<<24) + (k<<16);
        for(int j=9; j>=0; j--) {
            long m = j + 0x30;
            long sum2 = (m<<24);
            long sum3 = sum0 + sum1 + sum2;
            long sum = sum3 ^ 0x53968DE1; /// xor_check 
            //// 有前导 0 时, 分情况格式化 
            if(i==0) {
                if(j==0) {
                    sprintf(sn, "00%lu", (unsigned long)sum); /// 这一种情况与 getSN2() 方式一致 
                } else {
                    sprintf(sn, "0%lu", (unsigned long)sum);
                }
            } else {
                //// 没有前导 0 时,直接输出 
                sprintf(sn, "%lu", (unsigned long)sum);
            }
            //printf("%c%c: %s\n", k, m, sn);
            if(((sn[0]) == k) && ((sn[1]) ==m)) {
                //printf("%c%c: %s\n", k, m, sn);
                printf("SN: %s\n", sn);
                                 
                n++;
            }
        }
    }
          
    return n;
}

代码由 Dev-C ++ 调试通过。计算结果如下:
[Shell] 纯文本查看 复制代码
Input your name: solly
SN: 0420761554
SN: 00353652690
--------------------------------
Process exited after 4.457 seconds with return value 0
请按任意键继续. . .

再来一个:
[Shell] 纯文本查看 复制代码
Input your name: 52pojie.cn
SN: 1557972636
SN: 01507837596
SN: 001524614812

--------------------------------
Process exited after 1.438 seconds with return value 0
请按任意键继续. . .




返回界面输入上面的注册码,再次点“Register”,CrackMe 提示如下:
40.png
表示注册通过,分析完毕!!!


免费评分

参与人数 28吾爱币 +29 热心值 +25 收起 理由
lemon__star + 1 + 1 我很赞同!
我还没长大 + 1 + 1 nb兄弟
Lixinist + 1 + 1 我很赞同!
springkang + 1 + 1 我很赞同!
侃遍天下无二人 + 1 大佬太强了,一直想学学
bullshit + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
N0LL + 1 + 1 谢谢@Thanks!
ttao88 + 1 谢谢@Thanks!
poisonbcat + 1 + 1 用心讨论,共获提升!
山郭 + 1 + 1 我很赞同!
梁萧 + 1 + 1 用心讨论,共获提升!
独一无② + 1 + 1 谢谢@Thanks!
yixi + 1 + 1 我很赞同!
gongjiankk + 1 用心讨论,共获提升!
gaosld + 1 + 1 用心讨论,共获提升!
aileki + 1 + 1 谢谢@Thanks!
jnez112358 + 1 + 1 谢谢@Thanks!
qingyise + 1 + 1 我很赞同!
梦之浅伤 + 1 + 1 谢谢@Thanks!
22222 + 1 + 1 我很赞同!
mjzz8023 + 1 + 1 我很赞同!
liphily + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
daniel7785 + 1 用心讨论,共获提升!
oxsho + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
CHILAS_LEE + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
pk8900 + 3 + 1 写的超详细,有时间再好好研读。支持楼主~~~~
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
天空藍 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| solly 发表于 2020-1-6 16:56
yijian 发表于 2020-1-6 15:20
如何学校Cmake呢?cmake与makefile什么关系呀

它俩的关系类似于 CMD.exe 与 *.BAT 文件的关系。
2019ghua 发表于 2019-11-24 12:51
有朝一日我也能看懂并会操作就好了,,唉太菜
zhang321412 发表于 2019-11-24 13:09
thatup 发表于 2019-11-24 18:57
好东西感谢分享
pk8900 发表于 2019-11-25 11:10
这个一直没研究明白,有时间会好好研读,这回哦了。谢谢楼主的教程。
CHILAS_LEE 发表于 2019-11-25 13:17
学到了,花指令和VM的东西
头像被屏蔽
一只小木木 发表于 2019-11-25 20:50
你的帖子好牛逼,可惜我学不会
 楼主| solly 发表于 2019-11-28 10:13
liphily 发表于 2019-11-28 09:46
asm后就不能下断了

不明白你的意思???
richens 发表于 2019-11-28 11:38
虚心学习!
果果没有糖葫芦 发表于 2019-11-28 18:52
谢谢楼主,学习了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 15:45

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表