[原创]某pdf转word v6.3.0.2算法分析
某pdf转word v6.3.0.2算法分析
【文章标题】某pdf转word v6.3.0.2算法分析
【文章作者】JieLiuHouZi
【原版下载】www.pdfcword.cn
【保护方式】序列号
【分析过程】
一. 去掉随机基址
直接OD载入程序,入口是“一call一jmp”,基本上就是VS高版本编译的

为了避免随机基址的影响,先去除随机基址。找到“PE”下一行偏移为6的字节处,将“02”修改为“03”,可去掉随机基址

二. 注册码分析
随便输入一串注册码

此处MessageBox是断不下来的,使用“F12大法”:OD中按F12暂停程序,Alt+K查看调用堆栈

“黄框”部分是ycomuiu.DuiLib::CWindowWnd::ShowModal,搞这个软件之前我也没用过这个框架,百度了一下“在duilib中,可以调用CWindowWnd::ShowModal()来实现模态框的显示”, 在段尾retn处下断点
注意:消息循环是在CWindowWnd::ShowModal()中实现的,假如在ShowModal下面的几个函数返回处下断点,基本上就迷失在消息循环中了,由于不了解DuiLib我在这里耗了很久才跳出来的
然后点击“确定”按钮,会在刚才下断的地方段下来,F8
.text:00467F03 lea ecx, [ebp+var_148] .text:00467F09 push ecx .text:00467F0A mov ecx, [ebp+var_3B0] .text:00467F10 call sub_467D30 .text:00467F15 push ecx .text:00467F16 mov ecx, esp .text:00467F18 lea edx, [ebp+var_10] .text:00467F1B push edx .text:00467F1C call sub_4069F0 .text:00467F21 lea eax, [ebp+var_144] .text:00467F27 push eax ; 机器码 .text:00467F28 call sub_4325C0 ; 计算出注册码 .text:00467F2D add esp, 8 .text:00467F30 lea ecx, [ebp+var_148] .text:00467F36 call unknown_libname_1 ; Microsoft VisualC 2-14/net runtime .text:00467F3B push eax ; 我们自己输入的假码 .text:00467F3C lea ecx, [ebp+var_144] .text:00467F42 call sub_42C270 ; 注册码比较 .text:00467F47 test eax, eax .text:00467F49 jz short loc_467F87 ; 跳过注册码错误 .text:00467F4B push 40h .text:00467F4D mov ecx, [ebp+var_3B0] .text:00467F53 mov edx, [ecx+4] .text:00467F56 push edx .text:00467F57 push offset aSnerror .text:00467F5C call sub_409A20 ; 弹窗提示“序列号错误,请重新输入” .text:00467F61 add esp, 0Ch .text:00467F64 lea ecx, [ebp+var_144] .text:00467F6A call sub_4013B0 .text:00467F6F lea ecx, [ebp+var_148] .text:00467F75 call sub_4013B0 .text:00467F7A lea ecx, [ebp+var_10] .text:00467F7D call sub_4013B0 .text:00467F82 jmp loc_4683F1 .text:00467F87 ; --------------------------------------------------------------------------- .text:00467F87 .text:00467F87 loc_467F87: ; CODE XREF: sub_467EA0+A9↑j .text:00467F87 lea eax, [ebp+var_4] .text:00467F8A push eax .text:00467F8B call sub_433840 .text:00467F90 add esp, 4 .text:00467F93 lea ecx, [ebp+var_4] .text:00467F96 call sub_4070F0 .text:00467F9B movzx ecx, al .text:00467F9E test ecx, ecx .text:00467FA0 jz loc_46802D .text:00467FA6 mov edx, [ebp+var_3B0] .text:00467FAC mov eax, [edx+4] .text:00467FAF push eax .text:00467FB0 lea ecx, [ebp+var_3A0] .text:00467FB6 push ecx .text:00467FB7 call sub_40D670 ; 弹出验证邮箱对话框 .text:00467FBC add esp, 8 .text:00467FBF push eax .text:00467FC0 lea ecx, [ebp+var_4] .text:00467FC3 call sub_4068A0 .text:00467FC8 lea ecx, [ebp+var_3A0] .text:00467FCE call sub_4013B0 .text:00467FD3 lea ecx, [ebp+var_4] ; 检查邮箱是否合法 .text:00467FD6 call sub_4070F0 .text:00467FDB movzx edx, al .text:00467FDE test edx, edx .text:00467FE0 jz short loc_468026 ; 跳过“不提供购买邮箱的错误提示” .text:00467FE2 push 40h .text:00467FE4 mov eax, [ebp+var_3B0] .text:00467FEA mov ecx, [eax+4] .text:00467FED push ecx .text:00467FEE push offset aVipnotemail ; .text:00467FF3 call sub_409A20 .text:00467FF8 add esp, 0Ch .text:00467FFB lea ecx, [ebp+var_4] .text:00467FFE call sub_4013B0 .text:00468003 lea ecx, [ebp+var_144] .text:00468009 call sub_4013B0 .text:0046800E lea ecx, [ebp+var_148] .text:00468014 call sub_4013B0 .text:00468019 lea ecx, [ebp+var_10] .text:0046801C call sub_4013B0 .text:00468021 jmp loc_4683F1 .text:00468026 ; --------------------------------------------------------------------------- .text:00468026 .text:00468026 loc_468026: ; CODE XREF: sub_467EA0+140↑j .text:00468026 mov [ebp+var_14], 1 .text:0046802D .text:0046802D loc_46802D: ; CODE XREF: sub_467EA0+100↑j .text:0046802D lea ecx, [ebp+var_1C] .text:00468030 call ds:??0CWaitCursor@DuiLib@@QAE@XZ .text:00468036 push ecx .text:00468037 mov ecx, esp .text:00468039 lea edx, [ebp+var_4] .text:0046803C push edx ; 邮箱 .text:0046803D call sub_4069F0 .text:00468042 mov ecx, offset unk_4CB420 .text:00468047 call sub_46CC70 ; 网络验证:验证注册邮箱是否合法 .text:0046804C mov [ebp+var_C], eax .text:0046804F cmp [ebp+var_C], 1 .text:00468053 jnz short loc_4680B4 ; 跳转到“升级成功,谢谢您的支持!” .text:00468055 push offset word_4B2D6C .text:0046805A call sub_433800 .text:0046805F add esp, 4 .text:00468062 push 40h .text:00468064 mov eax, [ebp+var_3B0] .text:0046806A mov ecx, [eax+4] .text:0046806D push ecx .text:0046806E push offset aVipnotfoundema ; "VIPNotFoundEmail" .text:00468073 call sub_409A20 ; 弹出“邮箱验证失败,无法注册软件” .text:00468078 add esp, 0Ch .text:0046807B lea ecx, [ebp+var_1C] .text:0046807E call ds:??1CWaitCursor@DuiLib@@QAE@XZ .text:00468084 lea ecx, [ebp+var_4] .text:00468087 call sub_4013B0 .text:0046808C lea ecx, [ebp+var_144] .text:00468092 call sub_4013B0 .text:00468097 lea ecx, [ebp+var_148] .text:0046809D call sub_4013B0 .text:004680A2 lea ecx, [ebp+var_10] .text:004680A5 call sub_4013B0 .text:004680AA jmp loc_4683F1 .text:004680AF ; --------------------------------------------------------------------------- .text:004680AF jmp loc_468140 .text:004680B4 ; --------------------------------------------------------------------------- .text:004680B4 .text:004680B4 loc_4680B4: ; CODE XREF: sub_467EA0+1B3↑j .text:004680B4 cmp [ebp+var_C], 0
此处重点分析注册码生成部分:
.text:004325E0 lea ecx, [ebp+arg_4] ; 机器码 .text:004325E3 call sub_4017A0 ; 将机器码转换为字符串 .text:004325E8 mov [ebp+lpString], eax .text:004325EB cmp [ebp+lpString], 0 .text:004325EF jnz short loc_4325FD .text:004325F1 mov [ebp+var_C4], 0 .text:004325FB jmp short loc_432661 .text:004325FD ; --------------------------------------------------------------------------- .text:004325FD .text:004325FD loc_4325FD: ; CODE XREF: sub_4325C0+2F↑j .text:004325FD mov eax, [ebp+lpString] .text:00432600 push eax .text:00432601 call ds:lstrlenW .text:00432607 add eax, 1 .text:0043260A mov [ebp+var_8], eax .text:0043260D cmp [ebp+var_8], 3FFFFFFFh .text:00432614 jle short loc_432622 .text:00432616 mov [ebp+var_C8], 0 .text:00432620 jmp short loc_432655 .text:00432622 ; --------------------------------------------------------------------------- .text:00432622 .text:00432622 loc_432622: ; CODE XREF: sub_4325C0+54↑j .text:00432622 mov eax, [ebp+var_8] .text:00432625 shl eax, 1 .text:00432627 call __alloca_probe_16 ; alloc申请空间 .text:0043262C mov [ebp+lpMultiByteStr], esp .text:00432632 mov ecx, [ebp+CodePage] .text:00432638 push ecx .text:00432639 mov edx, [ebp+var_8] .text:0043263C shl edx, 1 .text:0043263E push edx .text:0043263F mov eax, [ebp+lpString] .text:00432642 push eax ; 机器码UNICODE字符串 .text:00432643 mov ecx, [ebp+lpMultiByteStr] .text:00432649 push ecx ; 接收转换完的ASCII字符串 .text:0043264A call sub_408BC0 ; Unicode机器码转Ascii .text:0043264F mov [ebp+var_C8], eax .text:00432655 .text:00432655 loc_432655: ; CODE XREF: sub_4325C0+60↑j .text:00432655 mov edx, [ebp+var_C8] .text:0043265B mov [ebp+var_C4], edx .text:00432661 .text:00432661 loc_432661: ; CODE XREF: sub_4325C0+3B↑j .text:00432661 mov eax, [ebp+var_C4] .text:00432667 mov [ebp+var_4], eax .text:0043266A push 0FFFFFFFFh .text:0043266C lea ecx, [ebp+arg_4] ; 机器码地址 .text:0043266F call sub_409B30 ; 获取长度 .text:00432674 mov cl, ds:byte_4A5912 .text:0043267A mov [ebp+var_98], cl .text:00432680 push 3Fh .text:00432682 push 0 .text:00432684 lea edx, [ebp+var_97] .text:0043268A push edx .text:0043268B call _memset .text:00432690 add esp, 0Ch .text:00432693 push 40h .text:00432695 push 0 .text:00432697 lea eax, [ebp+var_98] .text:0043269D push eax .text:0043269E call _memset .text:004326A3 add esp, 0Ch .text:004326A6 push 40h .text:004326A8 lea ecx, [ebp+var_98] .text:004326AE push ecx ; szBuffer .text:004326AF mov edx, [ebp+var_4] .text:004326B2 push edx ; ASCII格式机器码字符串 .text:004326B3 call Get_MD5 ; 计算机器码的MD5值 .text:004326B8 add esp, 0Ch .text:004326BB lea eax, [ebp+var_98] .text:004326C1 push eax .text:004326C2 call __strupr ; 机器码的MD5字符串转换为大写字符串 .text:004326C7 add esp, 4 .text:004326CA mov cl, ds:byte_4A5913 .text:004326D0 mov [ebp+MultiByteStr], cl .text:004326D3 push 3Fh .text:004326D5 push 0 .text:004326D7 lea edx, [ebp+var_4F] .text:004326DA push edx .text:004326DB call _memset .text:004326E0 add esp, 0Ch .text:004326E3 push 40h .text:004326E5 push 0 .text:004326E7 lea eax, [ebp+MultiByteStr] .text:004326EA push eax .text:004326EB call _memset .text:004326F0 add esp, 0Ch .text:004326F3 push 40h .text:004326F5 lea ecx, [ebp+MultiByteStr] .text:004326F8 push ecx .text:004326F9 lea edx, [ebp+var_98] .text:004326FF push edx .text:00432700 call Get_MD5 ; 将机器码的MD5字符串再次计算MD5 .text:00432705 add esp, 0Ch .text:00432708 lea eax, [ebp+MultiByteStr] .text:0043270B push eax .text:0043270C lea ecx, [ebp+obj_A8] .text:00432712 call sub_430200 ; 将ASCII转为UNICODE .text:00432717 push 4 .text:00432719 lea ecx, [ebp+var_AC] .text:0043271F push ecx ; 传出参数:用来存储计算完的注册码的第4段 .text:00432720 lea ecx, [ebp+obj_A8] ; MD5字符串 .text:00432726 call RegistrationCode_4 ; 注册码的第四段 .text:0043272B push eax .text:0043272C push 4 .text:0043272E push 16h .text:00432730 lea edx, [ebp+var_B0] .text:00432736 push edx .text:00432737 lea ecx, [ebp+obj_A8] .text:0043273D call RegistrationCode_2_3 ; 注册码的第三段 .text:00432742 push eax .text:00432743 push 4 .text:00432745 push 0Ah .text:00432747 lea eax, [ebp+var_B4] .text:0043274D push eax .text:0043274E lea ecx, [ebp+obj_A8] .text:00432754 call RegistrationCode_2_3 ; 注册码的第二段 .text:00432759 push eax .text:0043275A push 4 .text:0043275C lea ecx, [ebp+var_B8] .text:00432762 push ecx .text:00432763 lea ecx, [ebp+obj_A8] .text:00432769 call RegistrationCode_1 ; 注册码的第一段 .text:0043276E push eax .text:0043276F lea edx, [ebp+var_BC] .text:00432775 push edx .text:00432776 call MyStrCat ; 类似于strcat功能 .text:0043277B add esp, 0Ch .text:0043277E push eax .text:0043277F lea eax, [ebp+var_C0] .text:00432785 push eax .text:00432786 call MyStrCat .text:0043278B add esp, 0Ch .text:0043278E push eax .text:0043278F lea ecx, [ebp+var_A4] .text:00432795 push ecx .text:00432796 call MyStrCat .text:0043279B add esp, 0Ch
注册码分为4小段,其实算法基本上都一样,只是取的区间不同,注册码的第4段
.text:0042C3E0 RegistrationCode_4 proc near ; CODE XREF: sub_4277C0+B1↑p .text:0042C3E0 .text:0042C3E0 push ebp .text:0042C3E1 mov ebp, esp .text:0042C3E3 sub esp, 8 .text:0042C3E6 mov [ebp+var_8], ecx .text:0042C3E9 cmp [ebp+arg_4], 0 .text:0042C3ED jge short loc_42C3F6 .text:0042C3EF mov [ebp+arg_4], 0 .text:0042C3F6 .text:0042C3F6 loc_42C3F6: ; CODE XREF: RegistrationCode_4+D↑j .text:0042C3F6 mov ecx, [ebp+var_8] .text:0042C3F9 call sub_4017C0 ; 获取MD5字串长度 .text:0042C3FE mov [ebp+var_4], eax .text:0042C401 mov eax, [ebp+arg_4] .text:0042C404 cmp eax, [ebp+var_4] .text:0042C407 jl short loc_42C41A .text:0042C409 mov ecx, [ebp+var_8] .text:0042C40C push ecx .text:0042C40D mov ecx, [ebp+arg_0] .text:0042C410 call sub_4069F0 .text:0042C415 mov eax, [ebp+arg_0] .text:0042C418 jmp short loc_42C448 .text:0042C41A ; --------------------------------------------------------------------------- .text:0042C41A .text:0042C41A loc_42C41A: ; CODE XREF: RegistrationCode_4+27↑j .text:0042C41A mov ecx, [ebp+var_8] .text:0042C41D call sub_408D50 .text:0042C422 push eax ; int .text:0042C423 mov edx, [ebp+arg_4] .text:0042C426 push edx ; int .text:0042C427 mov ecx, [ebp+var_8] .text:0042C42A call GetString ; 获取字符串的首地址 .text:0042C42F mov ecx, [ebp+var_4] ; 其实关键的就这几行代码 .text:0042C432 lea edx, [eax+ecx*2] ; edx指针:移动到字符串的尾部 .text:0042C435 mov eax, [ebp+arg_4] .text:0042C438 shl eax, 1 .text:0042C43A sub edx, eax .text:0042C43C push edx ; 截取的注册码的一部分 .text:0042C43D mov ecx, [ebp+arg_0] .text:0042C440 call memcpy ; 内联的memcpy,这东西坑了我很久 .text:0042C445 mov eax, [ebp+arg_0] .text:0042C448 .text:0042C448 loc_42C448: ; CODE XREF: RegistrationCode_4+38↑j .text:0042C448 mov esp, ebp .text:0042C44A pop ebp .text:0042C44B retn 8 .text:0042C44B RegistrationCode_4 endp
注册码的第2和3段,其他几段大同小异
三. 网络验证去除
网络验证部分的代码:
.text:00468039 lea edx, [ebp+var_4] .text:0046803C push edx ; 邮箱 .text:0046803D call sub_4069F0 .text:00468042 mov ecx, offset unk_4CB420 .text:00468047 call sub_46CC70 ; 网络验证:验证注册邮箱是否合法 .text:0046804C mov [ebp+var_C], eax .text:0046804F cmp [ebp+var_C], 1 .text:00468053 jnz short loc_4680B4 ; 跳转到“升级成功,谢谢您的支持!” .text:00468055 push offset word_4B2D6C .text:0046805A call sub_433800 .text:0046805F add esp, 4 .text:00468062 push 40h .text:00468064 mov eax, [ebp+var_3B0] .text:0046806A mov ecx, [eax+4] .text:0046806D push ecx .text:0046806E push offset aVipnotfoundema .text:00468073 call sub_409A20 ; 弹出“邮箱验证失败,无法注册软件”
网络验证函数sub_46CC70()返回1表示验证失败,打补丁修改返回值即可去除网络验证
四. 机器码获取分析
编写注册机,其实没必要分析机器码的生成,由于我不了解一般软件如何获取机器码的,我就慢慢的从头到尾跟一遍,这一跟才发现机器码的生成比注册码的生成麻烦多了,不过也学到了不少东西,如何获取硬件信息,如何获取物理内存信息等等
1. 流程分析
打开软件,

问题:这个机器码是在那里生成的?
最挫的方法:一步一步跟,注册码是根据机器码算出来的,所以检验注册码的上面一定会有注册码
找到出现机器码的那段代码,一步一步的回溯,代码如下:
.text:00467EA0 push ebp .text:00467EA1 mov ebp, esp .text:00467EA3 sub esp, 3B0h .text:00467EA9 mov eax, ___security_cookie .text:00467EAE xor eax, ebp .text:00467EB0 mov [ebp+var_20], eax .text:00467EB3 mov [ebp+var_3B0], ecx .text:00467EB9 mov [ebp+var_14], 0 .text:00467EC0 lea eax, [ebp+var_10] .text:00467EC3 push eax .text:00467EC4 mov ecx, offset unk_4CB400 .text:00467EC9 call GetMachineID .text:00467ECE lea ecx, [ebp+var_10] .text:00467ED1 call sub_4070F0 .text:00467ED6 movzx ecx, al .text:00467ED9 test ecx, ecx .text:00467EDB jz short loc_467F03 .text:00467EDD push 40h ; int .text:00467EDF mov edx, [ebp+var_3B0] .text:00467EE5 mov eax, [edx+4] .text:00467EE8 push eax ; int .text:00467EE9 push offset aNomachineid_0 ; "NoMachineId" .text:00467EEE call sub_409A20 .text:00467EF3 add esp, 0Ch .text:00467EF6 lea ecx, [ebp+var_10] .text:00467EF9 call sub_4013B0 .text:00467EFE jmp loc_4683F1 .text:00467F03 ; --------------------------------------------------------------------------- .text:00467F03 .text:00467F03 loc_467F03: ; CODE XREF: sub_467EA0+3B↑j .text:00467F03 lea ecx, [ebp+var_148] .text:00467F09 push ecx .text:00467F0A mov ecx, [ebp+var_3B0] .text:00467F10 call sub_467D30 .text:00467F15 push ecx .text:00467F16 mov ecx, esp .text:00467F18 lea edx, [ebp+var_10] .text:00467F1B push edx ; 出现机器码 .text:00467F1C call sub_4069F0
已知的出现机器码的是.text:00467F1B push edx,其中edx存放机器码的地址一层一层的往上回溯
00467F1B:edx //edx存放机器码地址 00467F18:var_10 //var_10存储机器码 00467EC9:GetMachineID //函数中进行一系列处理得到机器码 00467EC4:offset unk_4CB400 //这是个全局对象,第一个成员是一个堆地址,其中存储机器码
到这里定位到了全局对象,但是全局对象是在那里获取机器码的,只能下内存断点了。对全局对象的第一个成员的位置下硬件写入断点,注意是004CB404而不是004CB400,因为第一个位置是虚表指针,后推一个才是第一个数据成员

重新载入程序,会断下来好几次,断下来之后再对堆地址指向的内容下硬件写入断点,其中有一次来到下面这个位置,此时恰巧刚填写机器码的前两位“89”

一次一次的retn之后来到下面这段代码中00469527地址处
.text:004694D6 call _memset .text:004694DB add esp, 0Ch .text:004694DE lea edx, [ebp+pcbData] .text:004694E4 push edx .text:004694E5 lea eax, [ebp+pvData] .text:004694EB push eax .text:004694EC push 0 .text:004694EE push offset aPdfcword_2 ; Pdfcword .text:004694F3 push offset aSoftwareMicros_16 ; .text:004694F8 push 80000002h ; hkey .text:004694FD call ds:SHGetValueW ; 该函数获取注册表的键值 .text:00469503 mov [ebp+var_210], eax .text:00469509 cmp [ebp+var_210], 0 .text:00469510 jnz short loc_469527 .text:00469512 lea ecx, [ebp+pvData] .text:00469518 push ecx .text:00469519 mov ecx, [ebp+var_214] .text:0046951F add ecx, 4 .text:00469522 call sub_4013D0 .text:00469527 .text:00469527 loc_469527: ; CODE XREF: GetMachineID+2C↑j .text:00469527 ; GetMachineID+80↑j .text:00469527 mov edx, [ebp+var_214]
遇到SHGetValueW就说明了机器码被写到注册表了。可以通过参数定位到注册表路径
路径:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Pdfcword

此处只是读取注册表中的机器码,并不是直接计算出机器码,我们先把注册表项给删掉,重新载入程序,对SHSetValueA下断
注意:此处不能对RegSetValue(A)、RegSetValueEx(A)下断,不然会很痛苦的,由于自个的无知被这里也折磨了好久
软件跑起来后,点击“注册-->购买序列号”就会断下来,查看堆栈窗口此时正准备向注册表写入机器码,那么写入之前一定有地方获取机器码

返回到0x0043A08,并在段首下断点
下面函数生成字符串: "27fd2a8ad66d99c944a52b0a2f9b6ff1"
004328B3 . 6A 40 push 0x40 004328B5 . 8D95 68FDFFFF lea edx,dword ptr ss:[ebp-0x298] 004328BB . 52 push edx 004328BC . E8 9F5D0300 call PDFConve.00468660 ;计算出字符串
下面函数生成字符串: "e3a725d4be6392fae82a71339d6e381b"
004329B2 > \6A 40 push 0x40 004329B4 . 8D95 20FDFFFF lea edx,dword ptr ss:[ebp-0x2E0] 004329BA . 52 push edx 004329BB . 8B85 14FDFFFF mov eax,dword ptr ss:[ebp-0x2EC] 004329C1 . 50 push eax 004329C2 . E8 A9EBFFFF call <PDFConve.Get_MD5>
最后截取并转换为大写就是机器码:"E3A725D4BE6392FA"
机器码生成的大致流程:

2. 取硬盘信息
定位到:004686AE call sub_40C8B0 ;取硬盘序列号
继续向里面跟进来到:0040C8DB call sub_40BCE0
IDA中F5的代码如下所示:
用CreateFile函数打开\.\PhysicalDrive%d
然后用DeviceIoControl来获取硬盘的信息(扇区数,磁头数,柱面数)等

3. 取物理内存信息
定位到:004686C3 call sub_40D160 ;取物理内存信息
限于篇幅原因,函数就不跟进去了
.text:0040D1D1 call sub_40CFA0 ; 文件映射,拷贝数据 .text:0040D1D6 add esp, 0Ch .text:0040D1D9 test eax, eax .text:0040D1DB jnz short loc_40D1EC .text:0040D1DD call sub_40CF70 ; 关闭内核对象,释放空间 .text:0040D1E2 mov eax, offset byte_4C9B40 .text:0040D1E7 jmp loc_40D2E9 .text:0040D1EC ; --------------------------------------------------------------------------- .text:0040D1EC .text:0040D1EC loc_40D1EC: .text:0040D1EC jmp short loc_40D207 .text:0040D1EE ; --------------------------------------------------------------------------- .text:0040D1EE .text:0040D1EE loc_40D1EE: .text:0040D1EE push 1000h ; size_t .text:0040D1F3 push 0FE000h ; void * .text:0040D1F8 lea eax, [ebp+var_1008] .text:0040D1FE push eax ; void * .text:0040D1FF call _memcpy .text:0040D204 add esp, 0Ch .text:0040D207 .text:0040D207 loc_40D207: .text:0040D207 call sub_40D030 ; 填表函数 .text:0040D20C lea ecx, [ebp+var_1008] .text:0040D212 mov [ebp+var_100C], ecx .text:0040D218 mov [ebp+var_1010], 0 .text:0040D222 jmp short loc_40D233 .text:0040D224 ; --------------------------------------------------------------------------- .text:0040D224 .text:0040D224 loc_40D224: .text:0040D224 mov edx, [ebp+var_1010] .text:0040D22A add edx, 1 .text:0040D22D mov [ebp+var_1010], edx .text:0040D233 .text:0040D233 loc_40D233: .text:0040D233 cmp [ebp+var_1010], 2 .text:0040D23A jge loc_40D2DF .text:0040D240 mov [ebp+var_1030], 0 .text:0040D247 xor eax, eax .text:0040D249 mov [ebp+var_102F], eax .text:0040D24F mov [ebp+var_102B], eax .text:0040D255 mov [ebp+var_1027], eax .text:0040D25B mov [ebp+var_1023], eax .text:0040D261 mov [ebp+var_101F], eax .text:0040D267 mov [ebp+var_101B], eax .text:0040D26D mov [ebp+var_1017], eax .text:0040D273 mov [ebp+var_1013], ax .text:0040D27A mov [ebp+var_1011], al .text:0040D280 push 800h .text:0040D285 mov ecx, [ebp+var_100C] .text:0040D28B push ecx .text:0040D28C call sub_40D0B0 ; 关键函数:通过物理内存的信息计算出一串字符串 .text:0040D291 add esp, 8 .text:0040D294 mov [ebp+var_4], eax .text:0040D297 mov edx, [ebp+var_4] .text:0040D29A push edx .text:0040D29B push offset a04x ; "%04X" .text:0040D2A0 lea eax, [ebp+var_1030] .text:0040D2A6 push eax ; char * .text:0040D2A7 call _sprintf .text:0040D2AC add esp, 0Ch .text:0040D2AF push 1000h ; size_t .text:0040D2B4 lea ecx, [ebp+var_1030] .text:0040D2BA push ecx ; char * .text:0040D2BB push offset byte_4C9B40 ; char * .text:0040D2C0 call _strncat ;拼接字符串 .text:0040D2C5 add esp, 0Ch .text:0040D2C8 mov edx, [ebp+var_100C] .text:0040D2CE add edx, 400h .text:0040D2D4 mov [ebp+var_100C], edx .text:0040D2DA jmp loc_40D224
五. 注册机编写
不能算是注册机,因为还有网络验证,虽然本地的注册算法验证可以通过,但是绕不过去网络验证,对于网络验证只能打补丁
1. 取硬盘信息
取硬盘信息部分:
void ChangeByteOrder(PCHAR szString, USHORT uscStrSize)
{
USHORT i = 0;
CHAR temp = '\0';
for (i = 0; i < uscStrSize; i += 2)
{
temp = szString[i];
szString[i] = szString[i + 1];
szString[i + 1] = temp;
}
}
//--------------------------------------------------------------
//功能:硬盘序列号
//参数:
// lpszHD:传出参数,存储最终计算的硬盘信息
// len:默认参128
//--------------------------------------------------------------
BOOL GetHDSerial(char *lpszHD, int len/*=128*/)
{
BOOL bRet = FALSE;
DWORD bytesRtn = 0;
char szhd[80] = { 0 };
PIDSECTOR phdinfo;
HANDLE hDrive = NULL;
GETVERSIONOUTPARAMS vers;
SENDCMDINPARAMS in;
SENDCMDOUTPARAMS out;
ZeroMemory(&vers, sizeof(vers));
ZeroMemory(&in, sizeof(in));
ZeroMemory(&out, sizeof(out));
//搜索四个物理硬盘,取第一个有数据的物理硬盘
for (int j = 0; j < 4; j++)
{
sprintf(szhd, "\\\\.\\PhysicalDrive%d", j);
hDrive = CreateFileA(szhd,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
0,
0);
if (NULL == hDrive)
{
continue;
}
if (!DeviceIoControl(hDrive, DFP_GET_VERSION, 0, 0, &vers, sizeof(vers), &bytesRtn, 0))
{
CloseHandle(hDrive);
hDrive = NULL;
continue;
}
//If IDE identify command not supported, fails
if (!(vers.fCapabilities & 1))
{
CloseHandle(hDrive);
hDrive = NULL;
continue;
}
//Identify the IDE drives
if (j & 1)
{
in.irDriveRegs.bDriveHeadReg = 0xb0;
}
else
{
in.irDriveRegs.bDriveHeadReg = 0xa0;
}
if (vers.fCapabilities&(16 >> j))
{
//We don't detect a ATAPI device.
CloseHandle(hDrive);
hDrive = NULL;
continue;
}
else
{
in.irDriveRegs.bCommandReg = 0xec;
}
in.bDriveNumber = j;
in.irDriveRegs.bSectorCountReg = 1;
in.irDriveRegs.bSectorNumberReg = 1;
in.cBufferSize = 512;
if (!DeviceIoControl(hDrive, DFP_RECEIVE_DRIVE_DATA, &in, sizeof(in), &out, sizeof(out), &bytesRtn, 0))
{
//"DeviceIoControl failed:DFP_RECEIVE_DRIVE_DATA"<<endl;
CloseHandle(hDrive);
hDrive = NULL;
continue;
}
phdinfo = (PIDSECTOR)out.bBuffer;
char s[21] = { 0 };
memcpy(s, phdinfo->sSerialNumber, 20);
s[20] = 0;
ChangeByteOrder(s, 20);
memcpy(lpszHD, s, 20);
if (strlen(lpszHD) != 0)
{
bRet = TRUE;
}
break;
}
CloseHandle(hDrive);
hDrive = NULL;
return bRet;
}
2. 取物理内存信息
获取物理内存信息:
int g_nTable[256] = { 0 }; //全局数组,作为表使用
PFNZwOpenSection ZwOpenSection = NULL;
PFNZwMapViewOfSection ZwMapViewOfSection = NULL;
PFNZwUnmapViewOfSection ZwUnmapViewOfSection = NULL;
PFNRtlInitUnicodeString RtlInitUnicodeString = NULL;
HMODULE hLibModule = NULL;
HANDLE hPhysicalMemoryHandle = NULL;
//从NTDLL获取我们需要的几个函数指针,并调用ZwOpenSection
BOOL sub_40CE30()
{
UNICODE_STRING PhysicalMemoryUnicodeString;
OBJECT_ATTRIBUTES ObjectAttributes;
wchar_t szBuffer[100] = L"\\Device\\PhysicalMemory";
//获取函数指针
hLibModule = LoadLibraryA("ntdll.dll");
if (!hLibModule)
return FALSE;
ZwOpenSection = (PFNZwOpenSection)GetProcAddress(hLibModule, "ZwOpenSection");
if (!ZwOpenSection)
return FALSE;
ZwMapViewOfSection = (PFNZwMapViewOfSection)GetProcAddress(hLibModule, "ZwMapViewOfSection");
if (!ZwMapViewOfSection)
return FALSE;
ZwUnmapViewOfSection = (PFNZwUnmapViewOfSection)GetProcAddress(hLibModule, "ZwUnmapViewOfSection");
if (!ZwUnmapViewOfSection)
return FALSE;
RtlInitUnicodeString = (PFNRtlInitUnicodeString)GetProcAddress(hLibModule, "RtlInitUnicodeString");
if (!RtlInitUnicodeString)
return FALSE;
RtlInitUnicodeString(&PhysicalMemoryUnicodeString, szBuffer);
ObjectAttributes.Length = sizeof(OBJECT_ATTRIBUTES);
ObjectAttributes.RootDirectory = 0;
ObjectAttributes.ObjectName = &PhysicalMemoryUnicodeString;
ObjectAttributes.Attributes = 0;
ObjectAttributes.SecurityDescriptor = NULL;
ObjectAttributes.SecurityQualityOfService = NULL;
NTSTATUS status = ZwOpenSection(&hPhysicalMemoryHandle,
4,
&ObjectAttributes);
if (NT_SUCCESS(status))
return TRUE;
return FALSE;
}
//文件映射,拷贝数据
BOOL __cdecl sub_40CFA0(PVOID pvDataBuffer, DWORD dwAddress, DWORD dwLength)
{
PVOID pvVirtualAddress; // 映射的虚地址
LARGE_INTEGER base; // 物理内存地址
DWORD dwOutLenth = dwLength;
base.QuadPart = (ULONGLONG)(dwAddress);
pvVirtualAddress = NULL;
NTSTATUS status = ZwMapViewOfSection(hPhysicalMemoryHandle,
(HANDLE)-1,
&pvVirtualAddress,
0,
dwLength,
&base,
&dwOutLenth,
ViewShare,
0,
PAGE_READONLY);
if (!NT_SUCCESS(status))
return FALSE;
//当前进程的虚地址空间中,复制数据到输出缓冲区
memcpy(pvDataBuffer, pvVirtualAddress, dwLength);
//完成访问,取消地址映射
return ZwUnmapViewOfSection((HANDLE)-1, pvVirtualAddress) >= 0;
}
//关闭内核对象,释放空间
BOOL sub_40CF70()
{
BOOL bRet = FALSE;
if (&hPhysicalMemoryHandle)
bRet = CloseHandle(&hPhysicalMemoryHandle);
if (hLibModule)
bRet = FreeLibrary(hLibModule);
return bRet;
}
//填表函数:将数据进行一系列运算,填入全局数组
int sub_40D030()
{
int nResult = 0;
signed int j;
unsigned int uNum = 0;
signed int i;
for (i = 0; i < 256; ++i)
{
uNum = i;
for (j = 8; j > 0; --j)
{
if (uNum & 1)
uNum = (uNum >> 1) ^ 0xEDB88320;
else
uNum >>= 1;
}
g_nTable[i] = uNum;
nResult = i + 1;
}
return nResult;
}
//查表计算
int __cdecl sub_40D300(BYTE bArg1, int* pArg2)
{
int nResult = 0;
nResult = *pArg2 & 0xFF;
*pArg2 = g_nTable[nResult ^ bArg1] ^ ((DWORD)*pArg2 >> 8);
return nResult;
}
int __cdecl sub_40D0B0(char* pStr, int nLen)
{
BYTE *bPTmp = NULL;
int nResult = -1;
bPTmp = (BYTE*)pStr;
for (int i = 0; i < nLen; ++i)
sub_40D300(*bPTmp++, &nResult);
return ~nResult;
}
//获取第二部分字符串
//地址:0040D28C call sub_40D0B0
//说明:参数是我自己后来添加的
BOOL GetBiosSerial(char* pBios)
{
char szBuf[0x1000] = { 0 };
char szTmp[16] = { 0 };
char *pTmp = NULL;
//获取文件映射的函数指针
BOOL bRet = sub_40CE30();
if (bRet)
{
//创建文件映射,拷贝数据
bRet = sub_40CFA0(szBuf, 0x0FE000, 0x1000);
if (!bRet)
{
//关闭内核对象,释放资源
sub_40CF70();
return FALSE;
}
//填表函数
sub_40D030();
//计算最终的字符串
pTmp = szBuf;
for (int i = 0; i < 2; ++i)
{
char szTmp[32] = { 0 };
int nRet = sub_40D0B0(pTmp, 2048);
sprintf(szTmp, "%04X", nRet);
strcat(pBios, szTmp);
pTmp += 1024;
}
bRet = TRUE;
}
return bRet;
}
3. 生成机器码
生成机器码:
//获取机器码:计算MD5的函数实现本处省略
char* GetMachineID(char* pMac, char* pBios, char* pMachineID)
{
BYTE uMD5Buf[32] = { 0 };
BYTE uTmpBuf[32] = { 0 };
char* pTmp = NULL;
//连接字符串
if (pMac == NULL)
{
if (pBios != NULL)
{
pTmp = pBios;
}
}
else
{
if (pBios != NULL)
{
strcat(pMac, pBios);
pTmp = pMac;
}
pTmp = pMac;
}
//计算MD5值:这个MD5不能出来空字符串,需要单独提出来处理
if (pTmp == NULL)
{
strcpy((char*)uTmpBuf, "d41d8cd98f00b204e9800998ecf8427e");
}
else
{
MDString(pTmp, uMD5Buf);
HexToStr(uTmpBuf, uMD5Buf, 16); //将MD5数据转为16进制字符串
strlwr((char*)uTmpBuf); //将字符串转为小写
}
//尾部追加#
strcat((char*)uTmpBuf, "#");
//计算MD5值
MDString((char*)uTmpBuf, uMD5Buf);
HexToStr(uTmpBuf, uMD5Buf, 16); //将MD5数据转为16进制字符串
//截取前半部分字符串
uTmpBuf[16] = '\0';
memcpy(pMachineID, uTmpBuf, 16);
return pMachineID;
}
4. 生成注册码
生成注册码:
//获取注册码的第一段
char* RegistrationCode_1(char* pSrc, char* pDest, int nLen)
{
memcpy(pDest, pSrc, nLen);
return pDest;
}
//获取注册码的第二/三段
char* RegistrationCode_2_3(char* pSrc, char* pDest, int nStart, int nLen)
{
if (nStart < 0)
{
nStart = 0;
}
if (nLen < 0)
{
nLen = 0;
}
//....
memcpy(pDest, pSrc + nStart, nLen);
return pDest;
}
//获取注册码的第四段
char* RegistrationCode_4(char* pSrc, char* pDest, int nLen)
{
if (nLen < 0)
{
nLen = 0;
}
int nSrcLen = strlen(pSrc);
if (nLen < nSrcLen)
{
pSrc += nSrcLen;
pSrc -= nLen;
memcpy(pDest, pSrc, nLen);
}
return pDest;
}
//功能:根据机器码获取注册码
//参数:
// pMachineID:传入参数,机器码
// pKey:传出参数,存储计算完的注册码
char* GetSerialNumber(char* pMachineID, char* pKey)
{
char szTmpBuf[5] = { 0 };
BYTE uMD5Buf[CODE_LEN] = { 0 };
BYTE uTmpBuf[CODE_LEN] = { 0 };
//计算机器码的MD5值
MDString(pMachineID, uMD5Buf);
//将MD5数据转为16进制字符串
HexToStr(uTmpBuf, uMD5Buf, 16);
//将机器码的MD5字符串再次计算MD5
MDString((char*)uTmpBuf, uMD5Buf);
//将MD5数据转为16进制字符串
HexToStr(uTmpBuf, uMD5Buf, 16);
//将字符串转为小写
strlwr((char*)uTmpBuf);
//注册码的第一段
RegistrationCode_1((char*)uTmpBuf, szTmpBuf, 4);
strcat(pKey, szTmpBuf);
//注册码的第二段
RegistrationCode_2_3((char*)uTmpBuf, szTmpBuf, 0x0A, 0x4);
sprintf(pKey, "%s-%s", pKey, szTmpBuf);
//注册码的第三段
RegistrationCode_2_3((char*)uTmpBuf, szTmpBuf, 0x16, 0x4);
sprintf(pKey, "%s-%s", pKey, szTmpBuf);
//注册码的第四段
RegistrationCode_4((char*)uTmpBuf, szTmpBuf, 4);
sprintf(pKey, "%s-%s", pKey, szTmpBuf);
return pKey;
}
总结
之前从来没有写过KeyGen,该软件的注册码生成基本上没啥难度,倒是追机器码搞得我头大
说明:
1.KeyGen在虚拟机中可能无法获取硬盘信息和物理内存信息,我在XP虚拟机下正常获取,Win7下获取不到硬盘信息和物理内存信息(具体细节没研究),不过该软件在Win7虚拟机下也是获取不到硬盘信息和物理内存信息,KeyGen可以正常获取注册码 2.Win10下需要管理员权限运行才能拿到硬件信息 3.工程中的KeyGen不能直接实现软件注册(只用于学习,务作它用),因为该软件除了本地验证外还有网络验证(Windows Defender会报毒直接杀掉,介意的务尝试),只要输入注册码出现以下截图就说明本地注册完成,填写邮箱是网络验证的事情
参考文档:
获取硬盘序列号参考代码:http://blog.csdn.net/tody_guo/article/details/26084143
获取物理内存信息参考代码:http://blog.csdn.net/wangxvfeng101/article/details/7394725

