首页 技术 正文
技术 2022年11月16日
0 收藏 527 点赞 2,520 浏览 23410 个字

某pdf转word v6.3.0.2算法分析

【文章标题】某pdf转word v6.3.0.2算法分析 
【文章作者】jieliuhouzi
【原版下载】www.pdfcword.cn 
【保护方式】序列号 
【分析过程】

一. 去掉随机基址

直接OD载入程序,入口是“一call一jmp”,基本上就是VS高版本编译的

某pdf转word v6.3.0.2算法分析 
为了避免随机基址的影响,先去除随机基址。找到“PE”下一行偏移为6的字节处,将“02”修改为“03”,可去掉随机基址

某pdf转word v6.3.0.2算法分析

二. 注册码分析

随便输入一串注册码

某pdf转word v6.3.0.2算法分析 
此处MessageBox是断不下来的,使用“F12大法”:OD中按F12暂停程序,Alt+K查看调用堆栈

某pdf转word v6.3.0.2算法分析 
“黄框”部分是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,
.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+]
.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,
.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+]
.text:00467FAF push eax
.text:00467FB0 lea ecx, [ebp+var_3A0]
.text:00467FB6 push ecx
.text:00467FB7 call sub_40D670 ; 弹出验证邮箱对话框
.text:00467FBC add esp,
.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+]
.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: lea ecx, [ebp+var_144]
.text: call sub_4013B0
.text:0046800E lea ecx, [ebp+var_148]
.text: call sub_4013B0
.text: lea ecx, [ebp+var_10]
.text:0046801C call sub_4013B0
.text: jmp loc_4683F1
.text: ; ---------------------------------------------------------------------------
.text:
.text: loc_468026: ; CODE XREF: sub_467EA0+140↑j
.text: mov [ebp+var_14],
.text:0046802D
.text:0046802D loc_46802D: ; CODE XREF: sub_467EA0+100↑j
.text:0046802D lea ecx, [ebp+var_1C]
.text: call ds:??0CWaitCursor@DuiLib@@QAE@XZ
.text: push ecx
.text: mov ecx, esp
.text: lea edx, [ebp+var_4]
.text:0046803C push edx ; 邮箱
.text:0046803D call sub_4069F0
.text: mov ecx, offset unk_4CB420
.text: call sub_46CC70 ; 网络验证:验证注册邮箱是否合法
.text:0046804C mov [ebp+var_C], eax
.text:0046804F cmp [ebp+var_C],
.text: jnz short loc_4680B4 ; 跳转到“升级成功,谢谢您的支持!”
.text: push offset word_4B2D6C
.text:0046805A call sub_433800
.text:0046805F add esp,
.text: push 40h
.text: mov eax, [ebp+var_3B0]
.text:0046806A mov ecx, [eax+]
.text:0046806D push ecx
.text:0046806E push offset aVipnotfoundema ; "VIPNotFoundEmail"
.text: call sub_409A20 ; 弹出“邮箱验证失败,无法注册软件”
.text: add esp, 0Ch
.text:0046807B lea ecx, [ebp+var_1C]
.text:0046807E call ds:??1CWaitCursor@DuiLib@@QAE@XZ
.text: lea ecx, [ebp+var_4]
.text: call sub_4013B0
.text:0046808C lea ecx, [ebp+var_144]
.text: call sub_4013B0
.text: 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],

此处重点分析注册码生成部分:

.text:004325E0                 lea     ecx, [ebp+arg_4] ; 机器码
.text:004325E3 call sub_4017A0 ; 将机器码转换为字符串
.text:004325E8 mov [ebp+lpString], eax
.text:004325EB cmp [ebp+lpString],
.text:004325EF jnz short loc_4325FD
.text:004325F1 mov [ebp+var_C4],
.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: push eax
.text: call ds:lstrlenW
.text: add eax,
.text:0043260A mov [ebp+var_8], eax
.text:0043260D cmp [ebp+var_8], 3FFFFFFFh
.text: jle short loc_432622
.text: mov [ebp+var_C8],
.text: jmp short loc_432655
.text: ; ---------------------------------------------------------------------------
.text:
.text: loc_432622: ; CODE XREF: sub_4325C0+54↑j
.text: mov eax, [ebp+var_8]
.text: shl eax,
.text: call __alloca_probe_16 ; alloc申请空间
.text:0043262C mov [ebp+lpMultiByteStr], esp
.text: mov ecx, [ebp+CodePage]
.text: push ecx
.text: mov edx, [ebp+var_8]
.text:0043263C shl edx,
.text:0043263E push edx
.text:0043263F mov eax, [ebp+lpString]
.text: push eax ; 机器码UNICODE字符串
.text: mov ecx, [ebp+lpMultiByteStr]
.text: push ecx ; 接收转换完的ASCII字符串
.text:0043264A call sub_408BC0 ; Unicode机器码转Ascii
.text:0043264F mov [ebp+var_C8], eax
.text:
.text: loc_432655: ; CODE XREF: sub_4325C0+60↑j
.text: mov edx, [ebp+var_C8]
.text:0043265B mov [ebp+var_C4], edx
.text:
.text: loc_432661: ; CODE XREF: sub_4325C0+3B↑j
.text: mov eax, [ebp+var_C4]
.text: mov [ebp+var_4], eax
.text:0043266A push 0FFFFFFFFh
.text:0043266C lea ecx, [ebp+arg_4] ; 机器码地址
.text:0043266F call sub_409B30 ; 获取长度
.text: mov cl, ds:byte_4A5912
.text:0043267A mov [ebp+var_98], cl
.text: push 3Fh
.text: push
.text: lea edx, [ebp+var_97]
.text:0043268A push edx
.text:0043268B call _memset
.text: add esp, 0Ch
.text: push 40h
.text: push
.text: 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,
.text:004326CA mov cl, ds:byte_4A5913
.text:004326D0 mov [ebp+MultiByteStr], cl
.text:004326D3 push 3Fh
.text:004326D5 push
.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
.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: call Get_MD5 ; 将机器码的MD5字符串再次计算MD5
.text: add esp, 0Ch
.text: lea eax, [ebp+MultiByteStr]
.text:0043270B push eax
.text:0043270C lea ecx, [ebp+obj_A8]
.text: call sub_430200 ; 将ASCII转为UNICODE
.text: push
.text: lea ecx, [ebp+var_AC]
.text:0043271F push ecx ; 传出参数:用来存储计算完的注册码的第4段
.text: lea ecx, [ebp+obj_A8] ; MD5字符串
.text: call RegistrationCode_4 ; 注册码的第四段
.text:0043272B push eax
.text:0043272C push
.text:0043272E push 16h
.text: lea edx, [ebp+var_B0]
.text: push edx
.text: lea ecx, [ebp+obj_A8]
.text:0043273D call RegistrationCode_2_3 ; 注册码的第三段
.text: push eax
.text: push
.text: push 0Ah
.text: lea eax, [ebp+var_B4]
.text:0043274D push eax
.text:0043274E lea ecx, [ebp+obj_A8]
.text: call RegistrationCode_2_3 ; 注册码的第二段
.text: push eax
.text:0043275A push
.text:0043275C lea ecx, [ebp+var_B8]
.text: push ecx
.text: lea ecx, [ebp+obj_A8]
.text: call RegistrationCode_1 ; 注册码的第一段
.text:0043276E push eax
.text:0043276F lea edx, [ebp+var_BC]
.text: push edx
.text: call MyStrCat ; 类似于strcat功能
.text:0043277B add esp, 0Ch
.text:0043277E push eax
.text:0043277F lea eax, [ebp+var_C0]
.text: push eax
.text: call MyStrCat
.text:0043278B add esp, 0Ch
.text:0043278E push eax
.text:0043278F lea ecx, [ebp+var_A4]
.text: push ecx
.text: 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,
.text:0042C3E6 mov [ebp+var_8], ecx
.text:0042C3E9 cmp [ebp+arg_4],
.text:0042C3ED jge short loc_42C3F6
.text:0042C3EF mov [ebp+arg_4],
.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*] ; edx指针:移动到字符串的尾部
.text:0042C435 mov eax, [ebp+arg_4]
.text:0042C438 shl eax,
.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
.text:0042C44B RegistrationCode_4 endp

注册码的第2和3段,其他几段大同小异

三. 网络验证去除

网络验证部分的代码:

.text:                 lea     edx, [ebp+var_4]
.text:0046803C push edx ; 邮箱
.text:0046803D call sub_4069F0
.text: mov ecx, offset unk_4CB420
.text: call sub_46CC70 ; 网络验证:验证注册邮箱是否合法
.text:0046804C mov [ebp+var_C], eax
.text:0046804F cmp [ebp+var_C],
.text: jnz short loc_4680B4 ; 跳转到“升级成功,谢谢您的支持!”
.text: push offset word_4B2D6C
.text:0046805A call sub_433800
.text:0046805F add esp,
.text: push 40h
.text: mov eax, [ebp+var_3B0]
.text:0046806A mov ecx, [eax+]
.text:0046806D push ecx
.text:0046806E push offset aVipnotfoundema
.text: call sub_409A20 ; 弹出“邮箱验证失败,无法注册软件”

网络验证函数sub_46CC70()返回1表示验证失败,打补丁修改返回值即可去除网络验证

四. 机器码获取分析

编写注册机,其实没必要分析机器码的生成,由于我不了解一般软件如何获取机器码的,我就慢慢的从头到尾跟一遍,这一跟才发现机器码的生成比注册码的生成麻烦多了,不过也学到了不少东西,如何获取硬件信息,如何获取物理内存信息等等

1. 流程分析

打开软件,点击:注册–>购买序列号,在下图弹窗找到本机的机器码“89354AF54032753D”

某pdf转word v6.3.0.2算法分析 
问题:这个机器码是在那里生成的? 
最挫的方法:一步一步跟,注册码是根据机器码算出来的,所以检验注册码的上面一定会有注册码 
找到出现机器码的那段代码,一步一步的回溯,代码如下:

.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],
.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+]
.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,因为第一个位置是虚表指针,后推一个才是第一个数据成员

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

某pdf转word v6.3.0.2算法分析 
一次一次的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
.text:004694EE push offset aPdfcword_2 ; Pdfcword
.text:004694F3 push offset aSoftwareMicros_16 ;
.text:004694F8 push 80000002h ; hkey
.text:004694FD call ds:SHGetValueW ; 该函数获取注册表的键值
.text: mov [ebp+var_210], eax
.text: cmp [ebp+var_210],
.text: jnz short loc_469527
.text: lea ecx, [ebp+pvData]
.text: push ecx
.text: mov ecx, [ebp+var_214]
.text:0046951F add ecx,
.text: call sub_4013D0
.text:
.text: loc_469527: ; CODE XREF: GetMachineID+2C↑j
.text: ; GetMachineID+80↑j
.text: mov edx, [ebp+var_214]

遇到SHGetValueW就说明了机器码被写到注册表了。可以通过参数定位到注册表路径 
路径:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Pdfcword

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

某pdf转word v6.3.0.2算法分析 
返回到0x0043A08,并在段首下断点 
下面函数生成字符串: “27fd2a8ad66d99c944a52b0a2f9b6ff1”

 004328B3   .  6A          push 0x40
004328B5 . 8D95 68FDFFFF lea edx,dword ptr ss:[ebp-0x298]
004328BB . push edx
004328BC . E8 9F5D0300 call PDFConve. ;计算出字符串

下面函数生成字符串: “e3a725d4be6392fae82a71339d6e381b”

004329B2   > \6A          push 0x40
004329B4 . 8D95 20FDFFFF lea edx,dword ptr ss:[ebp-0x2E0]
004329BA . push edx
004329BB . 8B85 14FDFFFF mov eax,dword ptr ss:[ebp-0x2EC]
004329C1 . push eax
004329C2 . E8 A9EBFFFF call <PDFConve.Get_MD5>

最后截取并转换为大写就是机器码:”E3A725D4BE6392FA” 
机器码生成的大致流程:

某pdf转word v6.3.0.2算法分析

2. 取硬盘信息

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

某pdf转word v6.3.0.2算法分析

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],
.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,
.text:0040D22D mov [ebp+var_1010], edx
.text:0040D233
.text:0040D233 loc_40D233:
.text:0040D233 cmp [ebp+var_1010],
.text:0040D23A jge loc_40D2DF
.text:0040D240 mov [ebp+var_1030],
.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,
.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 = ;
CHAR temp = '\0';
for (i = ; i < uscStrSize; i += )
{
temp = szString[i];
szString[i] = szString[i + ];
szString[i + ] = temp;
}
}
//--------------------------------------------------------------
//功能:硬盘序列号
//参数:
// lpszHD:传出参数,存储最终计算的硬盘信息
// len:默认参128
//--------------------------------------------------------------
BOOL GetHDSerial(char *lpszHD, int len/*=128*/)
{
BOOL bRet = FALSE;
DWORD bytesRtn = ;
char szhd[] = { };
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 = ; j < ; j++)
{
sprintf(szhd, "\\\\.\\PhysicalDrive%d", j);
hDrive = CreateFileA(szhd,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
,
OPEN_EXISTING,
,
);
if (NULL == hDrive)
{
continue;
}
if (!DeviceIoControl(hDrive, DFP_GET_VERSION, , , &vers, sizeof(vers), &bytesRtn, ))
{
CloseHandle(hDrive);
hDrive = NULL;
continue;
}
//If IDE identify command not supported, fails
if (!(vers.fCapabilities & ))
{
CloseHandle(hDrive);
hDrive = NULL;
continue;
}
//Identify the IDE drives
if (j & )
{
in.irDriveRegs.bDriveHeadReg = 0xb0;
}
else
{
in.irDriveRegs.bDriveHeadReg = 0xa0;
}
if (vers.fCapabilities&( >> j))
{
//We don't detect a ATAPI device.
CloseHandle(hDrive);
hDrive = NULL;
continue;
}
else
{
in.irDriveRegs.bCommandReg = 0xec;
}
in.bDriveNumber = j;
in.irDriveRegs.bSectorCountReg = ;
in.irDriveRegs.bSectorNumberReg = ;
in.cBufferSize = ;
if (!DeviceIoControl(hDrive, DFP_RECEIVE_DRIVE_DATA, &in, sizeof(in), &out, sizeof(out), &bytesRtn, ))
{
//"DeviceIoControl failed:DFP_RECEIVE_DRIVE_DATA"<<endl;
CloseHandle(hDrive);
hDrive = NULL;
continue;
}
phdinfo = (PIDSECTOR)out.bBuffer;
char s[] = { };
memcpy(s, phdinfo->sSerialNumber, );
s[] = ;
ChangeByteOrder(s, );
memcpy(lpszHD, s, );
if (strlen(lpszHD) != )
{
bRet = TRUE;
}
break;
}
CloseHandle(hDrive);
hDrive = NULL;
return bRet;
}

2. 取物理内存信息

获取物理内存信息:

 int  g_nTable[] = {  };       //全局数组,作为表使用
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[] = 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 = ;
ObjectAttributes.ObjectName = &PhysicalMemoryUnicodeString;
ObjectAttributes.Attributes = ;
ObjectAttributes.SecurityDescriptor = NULL;
ObjectAttributes.SecurityQualityOfService = NULL;
NTSTATUS status = ZwOpenSection(&hPhysicalMemoryHandle,
,
&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)-,
&pvVirtualAddress,
,
dwLength,
&base,
&dwOutLenth,
ViewShare,
,
PAGE_READONLY);
if (!NT_SUCCESS(status))
return FALSE;
//当前进程的虚地址空间中,复制数据到输出缓冲区
memcpy(pvDataBuffer, pvVirtualAddress, dwLength);
//完成访问,取消地址映射
return ZwUnmapViewOfSection((HANDLE)-, pvVirtualAddress) >= ;
}
//关闭内核对象,释放空间
BOOL sub_40CF70()
{
BOOL bRet = FALSE;
if (&hPhysicalMemoryHandle)
bRet = CloseHandle(&hPhysicalMemoryHandle);
if (hLibModule)
bRet = FreeLibrary(hLibModule);
return bRet;
}
//填表函数:将数据进行一系列运算,填入全局数组
int sub_40D030()
{
int nResult = ;
signed int j;
unsigned int uNum = ;
signed int i;
for (i = ; i < ; ++i)
{
uNum = i;
for (j = ; j > ; --j)
{
if (uNum & )
uNum = (uNum >> ) ^ 0xEDB88320;
else
uNum >>= ;
}
g_nTable[i] = uNum;
nResult = i + ;
}
return nResult;
}
//查表计算
int __cdecl sub_40D300(BYTE bArg1, int* pArg2)
{
int nResult = ;
nResult = *pArg2 & 0xFF;
*pArg2 = g_nTable[nResult ^ bArg1] ^ ((DWORD)*pArg2 >> );
return nResult;
}
int __cdecl sub_40D0B0(char* pStr, int nLen)
{
BYTE *bPTmp = NULL;
int nResult = -;
bPTmp = (BYTE*)pStr;
for (int i = ; i < nLen; ++i)
sub_40D300(*bPTmp++, &nResult);
return ~nResult;
}
//获取第二部分字符串
//地址:0040D28C call sub_40D0B0
//说明:参数是我自己后来添加的
BOOL GetBiosSerial(char* pBios)
{
char szBuf[0x1000] = { };
char szTmp[] = { };
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 = ; i < ; ++i)
{
char szTmp[] = { };
int nRet = sub_40D0B0(pTmp, );
sprintf(szTmp, "%04X", nRet);
strcat(pBios, szTmp);
pTmp += ;
}
bRet = TRUE;
}
return bRet;
}

2. 生成机器码

生成机器码:

 //获取机器码:计算MD5的函数实现本处省略
char* GetMachineID(char* pMac, char* pBios, char* pMachineID)
{
BYTE uMD5Buf[] = { };
BYTE uTmpBuf[] = { };
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, ); //将MD5数据转为16进制字符串
strlwr((char*)uTmpBuf); //将字符串转为小写
}
//尾部追加#
strcat((char*)uTmpBuf, "#");
//计算MD5值
MDString((char*)uTmpBuf, uMD5Buf);
HexToStr(uTmpBuf, uMD5Buf, ); //将MD5数据转为16进制字符串
//截取前半部分字符串
uTmpBuf[] = '\0';
memcpy(pMachineID, uTmpBuf, );
return pMachineID;
}

3.生成注册码

生成注册码:

 //获取注册码的第一段
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 < )
{
nStart = ;
}
if (nLen < )
{
nLen = ;
}
//....
memcpy(pDest, pSrc + nStart, nLen);
return pDest;
}
//获取注册码的第四段
char* RegistrationCode_4(char* pSrc, char* pDest, int nLen)
{
if (nLen < )
{
nLen = ;
}
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[] = { };
BYTE uMD5Buf[CODE_LEN] = { };
BYTE uTmpBuf[CODE_LEN] = { };
//计算机器码的MD5值
MDString(pMachineID, uMD5Buf);
//将MD5数据转为16进制字符串
HexToStr(uTmpBuf, uMD5Buf, );
//将机器码的MD5字符串再次计算MD5
MDString((char*)uTmpBuf, uMD5Buf);
//将MD5数据转为16进制字符串
HexToStr(uTmpBuf, uMD5Buf, );
//将字符串转为小写
strlwr((char*)uTmpBuf);
//注册码的第一段
RegistrationCode_1((char*)uTmpBuf, szTmpBuf, );
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, );
sprintf(pKey, "%s-%s", pKey, szTmpBuf);
return pKey;
}

总结

之前从来没有写过KeyGen,该软件的注册码生成基本上没啥难度,倒是追机器码搞得我头大

说明:

 1.KeyGen在虚拟机中可能无法获取硬盘信息和物理内存信息,我在XP虚拟机下正常获取,Win7下获取不到硬盘信息和物理内存信息(具体细节没研究),不过该软件在Win7虚拟机下也是获取不到硬盘信息和物理内存信息,KeyGen可以正常获取注册码

 2.Win10下需要管理员权限运行才能拿到硬件信息

 3.工程中的KeyGen不能直接实现软件注册(只用于学习,务作它用),因为该软件除了本地验证外还有网络验证(Windows Defender会报毒直接杀掉,介意的务尝试),只要输入注册码出现以下截图就说明本地注册完成,填写邮箱是网络验证的事情某pdf转word v6.3.0.2算法分析KeyGen代码地址:https://pan.baidu.com/s/1i5peytJ 密码:ip5q(失效请练习我)

考文档: 
获取硬盘序列号参考代码:http://blog.csdn.net/tody_guo/article/details/26084143 
获取物理内存信息参考代码:http://blog.csdn.net/wangxvfeng101/article/details/7394725

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,084
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,559
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,408
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,181
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,818
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,901