160 个 CrackMe 之 082 - ultraschall 自带微型VM的跟踪和分析
本帖最后由 solly 于 2019-11-27 09:36 编辑160 个 CrackMe 之 082 - ultraschall 是一个自带了包含10个指令的微型VM的 Crackme 程序,并且其VM部分的代码带有“花指令”干扰。
我们先来看看文件信息:
显示是用汇编语言写的,没有加壳。重新用 “Scan /t” 扫描一下:
这次显示有一个 PE Diminisher v0.1 处理过了,应该就是加了一些“花指令”,我们在后面会看到。
但是这个程序在 Win10 下不能直接运行,会报兼容性问题,如下图所示,Windows10不允许其运行:
这个错误是由于程序的“节”的参数不严谨造成的,是“.rsrc”节的大小超出了文件结尾,在 Windows 9x 下没有问题,在 Win10下则不认可,不过可以修正(本论坛有贴有详细説明,修改原理这里不重复了),修改方法如下面的两图所示:
上图中的 0x00004A00 需要修改,改成 0x00004200 即可,如下图所示:
修改后即可在 Windows 10 下正常运行了。
启动后界面如下:
需要找出其 SN 或写出注册机。
接下来我们用 OD 加载 Crackme,先进行静态分析。
加载后,如上图所示,用汇编写的代码比较精简整洁,结构清晰。
上图中红框中的代码就是 WinMain() 入口函数的代码,直接生成一个基于“对话框”的程序,没有一句多余代码。
由上图可以看到对话框的事件处理回调函数 DlgProc() 的入口为 0x0040103E,也是直接就是在 WinMain() 函数的后面,如上图蓝框所标示的部分。
下图所示就是 DlgProc() 的部分代码:
最前面的代码是处理 WM_SHOWWINDOW 消息,对文本框的输入长度进行了一些限制,防止后面接收字符串时溢出。同时将光标(输入焦点)定位到输入用户名的文本框内。
图中红框中所示代码是发送一个自定义的消息,不过这个消息是用来退出 CrackMe 的,也就是将 WM_CLOSE 消息,转换成自定义的 WM_COMMAND消息,再由 WM_COMMAND消息处理代码来关闭 Crackme。
下面来看看 DlgProc() 中对 WM_COMMAND 处理的代码,如下图所示:
上图中,蓝框中的代码就是处理自定义的 WM_COMMAND 消息的,用来关闭对话框程序。
主要的处理代码在红框中,这就是处理“Register”按钮事件的代码。关键就是一个 call 调用: call 004010FF,这里调用了一个微型 VM 系统。往下看,就可以看到这个调用的代码,如下图所示:
可以看到,这个函数内部的代码很乱,这是由于有“花指令”的干扰,往下拖,看看函数结尾在哪里,如下图所示,在 IAT 的跳转表前面就是其结尾:
下面先去除“花指令”,在 OD 的“插件”菜单中,选择“5. 花指令去除器”==>“去除花指令”,在弹出的对话框中输入搜索地址和搜索大小,如下图所示:
搜索地址为函数地址:0x004010FF,搜索大小为 0x31F = 0x0040141E - 0x004010FF。再点“确定”就可以了,如下图所示:
去除了 112 条花指令。后面OD代码区的花指令变成了大量的 nop 指令了,如下图所示,就是 VM 的入口,先将Host机器的上下文(寄存器信息)保存。
同时在地址 0x0040110D 处的 NOP 指令,是 VM 机器的执行标志,后面有说明,当这个字节由 0x90 变成 0xCC 时,VM 结束。
静态分析到这里,下面进行动态跟踪,分析注册机制。
我们先在下图所示位置(0x004010CE),下一个“断点”:
然后按 “F9” 运行 CrackMe,显示主界面,并输入注册假码数据,如下图所示:
输入完注册信息,点“Register”按钮,OD 马上断下程序,如下图所示,定位到了我们前面下的断点处:
可以看到,按钮的 ControlID = 0x000003F4,按几次 F8,来到那个 call 004010FF 处,如下图所示,同时在 call 后面下一个断点(防止退出CrackMe):
我们按 F7 进入 call 调用,跟踪 VM 的执行。
一路按 F8 执行,来到如下图所示位置,这个 VM 内的 call dword ptr ,就是解释执行 VM 指令的调用:
其中 al 为操作码,esi 为操作码解释执行程序的入口基址,当前的 cx 为下一条指令的地址,EBX 为第一个操作数,EDX 为第二个操作数,后面再对指令结构详细説明,在 OD 的数据区,就显示了当前正在执行的VM指令。
eax+esi = 0x00403199,这个位置保存的是解释执行当前VM指令的子过程(0x0040128A),如下图所示,在 OD 的数据区就是这些子过程的入口表,共 10 个入口,表示这个小小的 VM 只有 10 条指令。
具体入口代码如下:
;Func ~ Func, 共 10 个指令解释程序入口地址
00403189: 004011DF 00401202 00401224 00401256
00403199: 0040128A 004012E0 00401343 0040136F
004031A9: 00401380 004013C0
VM 执行的第一条指令的 opcode 为 0x10,这是一条从界面取文本数据的指令,其第2个参数就是 ControlID,如下图所示,取到了用户名:
本 VM 机器执行的 VM 程序全部指令序列如下所示:
0040300B10 E0 00 00 00 F0 03 00 00 0B 00 ; Func GetText,取用户名 存于 , "solly", API:User32.GetDlgItemTextA()
0040301610 F5 00 00 00 F1 03 00 00 16 00 ; Func GetText,取序列号(文本) 存于 , "78787878", API:User32.GetDlgItemTextA()
0040302108 DC 00 00 00 DC 00 00 00 21 00 ; Func Sub,计算 = - = 0 - 0 = 0,变量初始化为0
0040302C04 DC 00 00 00 5A 01 00 00 2C 00 ; Func Add,计算 = + = 0 + 0 = 0
0040303720 E0 00 00 00 4E 01 00 00 37 00 ; Func Equ,赋值 = *((dword *)&name) = 0x6C6C6F73("soll"),("olly"),......, 下面Func跳转到这里执行循环。
0040304204 4E 01 00 00 56 01 00 00 42 00 ; Func Add,计算 = + = 0x00 + 0x01 = 0x01, j++(char索引)
0040304D04 4A 01 00 00 DC 00 00 00 4D 00 ; Func Add,计算 = + = 0x00 + 0x6C6C6F73 = 0x6C6C6F73, sum += *((dword *)&name), 循环结束时 sum = 0x5589C233
0040305808 52 01 00 00 56 01 00 00 58 00 ; Func Sub,计算 = - = 0x14 - 0x01 = 0x13, 为循环次数, i--(char索引)
0040306318 52 01 00 00 00 00 2C 00 63 00 ; Func Jnz,不为0跳转 (操作数1 != 0) 结果(IP) == 操作数2:0x002C0000 ==> bswap(0x00002C00) ==> (xchg)0x0000(002C),跳回去循环
0040306E20 66 01 00 00 52 01 00 00 6E 00 ; Func Equ,赋值 = xor_arr = (0xD4E1C094), 下面Func跳转到这里执行循环。
004030790C 4A 01 00 00 DC 00 00 00 79 00 ; Func Xor,计算 = ^ ,sum ^= *((dword *)&xor_arr),循环结束时 sum = 0x061F4FD2
0040308404 52 01 00 00 62 01 00 00 84 00 ; Func Add,计算 = + = 0x00 + 0x04 = 0x04, i++(int索引)
0040308F08 4E 01 00 00 62 01 00 00 8F 00 ; Func Sub,计算 = - = 0x14 - 0x04 = 0x10, j--(int索引)
0040309A18 4E 01 00 00 00 00 63 00 9A 00 ; Func Jnz,不为0跳转 (操作数1 != 0) 结果(IP) == 操作数2:0x00630000 ==> bswap(0x00006300) ==> (xchg)0x0000(0063),跳回去循环
004030A524 7A 01 00 00 F1 03 00 00 A5 00 ; Func GetInt,取序列号(整数) 存于 , API:User32.GetDlgItemInt(), SN = eax == 0x04B23526(78787878)
004030B008 4A 01 00 00 7A 01 00 00 B0 00 ; Func Sub,计算 = - = sum - SN,sum -= SN,结果为0时表示序列号正确
004030BB18 4A 01 00 00 00 00 C6 00 BB 00 ; Func Jnz,不为0跳转 (操作数1(sum) != 0) 结果(IP) == 操作数2:0x00C60000 ==> bswap(0x0000C600) ==> (xchg)0x0000(00C6),跳转去显示注册错误
004030C614 2C 01 00 00 00 00 00 00 D1 00 ; Func Msg,显示注册正确,MsgText == ===> "Registration Code is CORRECT!",API: USER32.MessageBoxExA()
004030D114 0A 01 00 00 00 00 00 00 D1 00 ; Func Msg,显示注册错误,MsgText == ===> "Registration Code is not correct!",API: USER32.MessageBoxExA()
004030DC1C 00 00 00 00 00 00 00 00 00 00 ; Func End,设置程序结束标志, = 0xCC;int3, 设置程序结束标志
可以看出,一共20条指令代码,每个指令占用 11 个字节,具体结构如下所示:
/// 指令结构
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。
参与异或运算的常量数组如下:
;第二次循环进行异或的操作数,5个整数常量:
0040317194 C0 E1 D4 11 98 1F FF C8 BB FA 91 10 94 1D 78
00403181BC FA 8F 91
以16进制形式表示:0xD4E1C094, 0xFF1F9811, 0x91FABBC8, 0x781D9410, 0x918FFABC;
VM的具体代码如下所示(去除了大量的 nop 指令,所以指令前的地址值不是连贯的了):
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 ;取指令 AL=opcode, 相当于指令 mov al, byte ptr
00401155 50 push eax
0040115E 90 nop
0040115F 8B541F 05 mov edx, dword ptr ;取操作数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 ;取下一条指令IP
0040117C 90 nop
00401180 8B5C1F 01 mov ebx, dword ptr ;取操作数1,结果缓冲区
00401184 58 pop eax ;指令
00401188 90 nop
00401189 FF1430 call dword ptr ;执行指令
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 , 0CC ;int3, 程序是否结束,0xCC表示VM程序结束
004011AB ^ 0F85 79FFFFFF jnz 0040112A ;没有结束则继续执行下一条指令
004011B1 90 nop
004011B5 C605 0D114000 90 mov byte ptr , 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 ;IP>255 时跳转到这里,取指令并重新返回循环执行指令
004011DA ^ E9 76FFFFFF jmp 00401155
;===============================================================================================================================
004011DF 90 nop ;Func 入口,Equ 指令
004011E3 C605 7A124000 89 mov byte ptr , 89 ;修改为 Mov 指令
004011EF 68 46124000 push 00401246 ;ret 返回地址
004011F8 68 56124000 push 00401256 ;ret 返回地址
00401201 C3 retn ;jmp 00401256
;===============================================================================================================================
00401202 90 nop ;Func 入口,Add 指令
00401206 C605 7A124000 01 mov byte ptr , 1 ;修改为 Add 指令
00401211 68 46124000 push 00401246 ;ret 返回地址
0040121A 68 56124000 push 00401256 ;ret 返回地址
00401223 C3 retn ;jmp 00401256
;===============================================================================================================================
00401224 90 nop ;Func 入口,Sub 指令
00401228 C605 7A124000 29 mov byte ptr , 29 ;修改为 Sub 指令,修改运算符
00401233 68 46124000 push 00401246 ;ret 返回地址
0040123C 68 56124000 push 00401256 ;ret 返回地址
00401245 C3 retn ;jmp 00401256, call Func
;===============================================================================================================================
00401246 90 nop ;Func_Restore()
0040124A C605 7A124000 31 mov byte ptr , 31 ;恢复指令
00401255 C3 retn
;===============================================================================================================================
00401256 90 nop ;Func 入口,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 ;取得操作数2的值
0040127A 3113 xor dword ptr , edx ;执行操作,这里的指令会是可变的,Mov, Add, Sub, Xor 等
00401280 59 pop ecx
00401285 C3 retn ;返回 00401246 恢复指令, 由 00401233 处指令压入
;===============================================================================================================================
00401289 90 nop
0040128A 51 push ecx ;Func 入口, 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 ;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 入口,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 ;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 入口,Jnz 指令
00401348 81C3 0B304000 add ebx, 0040300B
0040134E 8B1B mov ebx, dword ptr
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 入口,End 指令
00401373 C605 0D114000 CC mov byte ptr , 0CC ;int3, 设置程序结束标志
0040137F C3 retn
;===============================================================================================================================
00401380 90 nop ;Func 入口, 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 ;取操作数2的值
004013A4 8B0C1A mov ecx, dword ptr
004013AB 890D E7304000 mov dword ptr , ecx ;返回结果
004013B5 59 pop ecx
004013BF C3 retn
;===============================================================================================================================
004013C0 51 push ecx ;Func 入口,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 ;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 , eax ;取得的界面整数数据
00401412 59 pop ecx
00401417 C3 retn
0040141C 90 nop
0040141D CC int3
VM 解释执行程序的分析説明都包含在上面的代码内了。
以第1条指令为例説明指令的解释执行过程:
; 第1条指令
0040300B10 E0 00 00 00 F0 03 00 00 0B 00 ; Func GetText,取用户名 存于 , "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 。
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条指令:
004030BB18 4A 01 00 00 00 00 C6 00 BB 00 ; Func Jnz,不为0跳转 (操作数1(sum) != 0) 结果(IP) == 操作数2:0x00C60000 ==> bswap(0x0000C600) ==> (xchg)0x0000(00C6),跳转去显示注册错误
004030C614 2C 01 00 00 00 00 00 00 D1 00 ; Func Msg,显示注册正确,MsgText == ===> "Registration Code is CORRECT!",API: USER32.MessageBoxExA()
004030D114 0A 01 00 00 00 00 00 00 D1 00 ; Func Msg,显示注册错误,MsgText == ===> "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 计算方法可知,这两个地址分别对应的指令实际地址如下:
0x00C6 ===> 0x0040300B + 0x00C6 = 0x004030D1,执行显示 "Registration Code is not correct!" 的指令。
0x00BB ===> 0x0040300B + 0x00BB = 0x004030C6,执行显示 "Registration Code is CORRECT!" 的指令。
所以,只要把这条 Jnz 的 operand2 修改为 next_ip 一样就可以通过注册。修改结果如下:
; Patch 后的 Jnz 指令
004030BB 18 4A 01 00 00 00 00 BB 00 BB 00 ; Func Jnz,不为0跳转
这样,不管条件如何,都是跳转到(0x00BB),显示注册码正确的提示了。
另外,需要説明一下,如下图所示:
用户名和注册码的都是21个字节的 char 数组,存贮是连续的,中间没有间隙,所以,在对用户名进行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)。
#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;
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;
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); //// 将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;
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 = '0'; /// CrackMe 中对 name 操作时取到了 SN 最前面两个字符
buffer = '0'; /// CrackMe 中对 name 操作时取到了 SN 最前面两个字符
long sum = 0;
for(int i=0; i<20; i++) {
sum += * ((long *)&buffer); //// 将char* 当作 long* 取 name,一次取4个字符
}
// long xor_check = 0;
// for(int i=0; i<5; i++) {
// xor_check ^= xor_base;
// }
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;
//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) == k) && ((sn) ==m)) {
//printf("%c%c: %s\n", k, m, sn);
printf("SN: %s\n", sn);
n++;
}
}
}
return n;
}
代码由 Dev-C ++ 调试通过。计算结果如下:
Input your name: solly
SN: 0420761554
SN: 00353652690
--------------------------------
Process exited after 4.457 seconds with return value 0
请按任意键继续. . .
再来一个:
Input your name: 52pojie.cn
SN: 1557972636
SN: 01507837596
SN: 001524614812
--------------------------------
Process exited after 1.438 seconds with return value 0
请按任意键继续. . .
返回界面输入上面的注册码,再次点“Register”,CrackMe 提示如下:
表示注册通过,分析完毕!!!
yijian 发表于 2020-1-6 15:20
如何学校Cmake呢?cmake与makefile什么关系呀
它俩的关系类似于 CMD.exe 与 *.BAT 文件的关系。 有朝一日我也能看懂并会操作就好了,,唉太菜 好东西感谢分享 好东西感谢分享 这个一直没研究明白,有时间会好好研读,这回哦了。谢谢楼主的教程。 学到了,花指令和VM的东西{:1_893:} 你的帖子好牛逼,可惜我学不会 liphily 发表于 2019-11-28 09:46
asm后就不能下断了
不明白你的意思??? 虚心学习! 谢谢楼主,学习了{:1_927:}