[软件名称及版本]:EditPlus Text Editor 4.1(32位)。
[官网下载地址]:https://www.editplus.com/。
[软件介绍]:由于官网已经对该软件的功能及特点有了权威而详细的介绍,故不再描述。
[分析环境]:Windos XP SP3。
[分析工具]:OLLYDBG V1.10、IDA V6.8。
[本文目的]:本文针对目前官网最新版的 EditPlus 分析了其注册算法,也是一次学习过程,在这里将分析过程写出来,希望能与大家多多交流,互相学习。本次分析过中有些地方分析的不是很透彻,但是关键点都力所能及的分析了,如有错误和疏漏之处,还请大家多多批评指正。
注:本次分析以32位版本为对象,关于64位版本,本文结尾处有提及。在分析过程中以
UserName:"LocalHostUser", RegCode:"63AC4-6654R-QWED5"做为参数进行分析。
***********************************************************************************************************************************
[正文]:
软件安装完毕,直接运行软件并输入注册码,报错误提示,弹出一个对话框。上OD调试启动,并在MessageBox相关函数处下断点,在MessageBoxW处断下来后,回溯即可找到关键函数。回溯到函数
sub_004D8D80 处:
.text:004D8D80 ; int __thiscall Fun1(CDialog *this)
.text:004D8D80 Fun1 proc near ; DATA XREF:
.rdata:00598BA4o
.text:004D8D80
.text:004D8D80 var_8 = dword ptr -8
.text:004D8D80 var_4 = dword ptr -4
.text:004D8D80
.text:004D8D80 sub esp, 8
.text:004D8D83 push esi
.text:004D8D84 lea eax, [esp+0Ch+var_8]
.text:004D8D88 push eax
.text:004D8D89 mov esi, ecx
.text:004D8D8B mov [esp+10h+var_8], 0
.text:004D8D93 call Fun_KeyFun ; 按照 Unicode 格式的数据做校验
.text:004D8D98 test eax, eax
.text:004D8D9A jnz short loc_4D8DD9
.text:004D8D9C lea ecx, [esp+0Ch+var_4]
.text:004D8DA0 push ecx
.text:004D8DA1 mov ecx, esi
.text:004D8DA3 mov [esp+10h+var_4], eax
.text:004D8DA7 call Fun_KeyFun1 ; 按照 ASCII 格式的数据做校验
.text:004D8DAC mov ecx, [esp+0Ch+var_4]
.text:004D8DB0 mov edx, [esp+0Ch+var_8]
.text:004D8DB4 test ecx, ecx
.text:004D8DB6 jbe short loc_4D8DC2
.text:004D8DB8 cmp edx, 4E7Fh
.text:004D8DBE jz short loc_4D8DC2
.text:004D8DC0 mov edx, ecx
.text:004D8DC2
.text:004D8DC2 loc_4D8DC2: ; CODE XREF: Fun1+36j
.text:004D8DC2 ; Fun1+3Ej
.text:004D8DC2 test eax, eax
.text:004D8DC4 jnz short loc_4D8DD9
.text:004D8DC6 test edx, edx
.text:004D8DC8 jz short loc_4D8DD4
.text:004D8DCA push 0FFFFFFFFh ; unsigned int
.text:004D8DCC push 10h ; uType
.text:004D8DCE push edx ; unsigned int
.text:004D8DCF call ?AfxMessageBox@@YGHIII@Z ; AfxMessageBox(uint,uint,uint)
.text:004D8DD4 loc_4D8DD4: ; CODE XREF: Fun1+48j
.text:004D8DD4 pop esi
.text:004D8DD5 add esp, 8
.text:004D8DD8 retn
.text:004D8DD9 ;
---------------------------------------------------------------------------
.text:004D8DD9
.text:004D8DD9 loc_4D8DD9: ; CODE XREF: Fun1+1Aj
.text:004D8DD9 ; Fun1+44j
.text:004D8DD9 mov edx, [esi+134h]
.text:004D8DDF mov ecx, esi ; this
.text:004D8DE1 mov dword ptr [edx], 0
.text:004D8DE7 pop esi
.text:004D8DE8 add esp, 8
.text:004D8DEB jmp ?OnOK@CDialog@@MAEXXZ ; CDialog::OnOK(void)
.text:004D8DEB Fun1 endp[/COLOR]
由以上代码及后续分析可得出,
Fun_KeyFun 会对输入的 UserName 和 RegCode 按照 Unicode 格式的字符串进行校验,如果校验失败会调用 Fun_KeyFun1 再次进行校验,而 Fun_KeyFun1 是对输入的 UserName 和 RegCode 按照 ASCII 格式的字符串做校验,两个函数的内部校验流程是一样的,所以仅对 Fun_KeyFun 进行较为详细的分析。
Fun_KeyFun 和 Fun_KeyFun1 如果有任何一个校验成功,即提示注册成功并要求重启程序,否则就会弹出对话框提示注册码错误,应此重点分析 Fun_KeyFun 校验成功的条件。
下面详细看:
Fun_KeyFun
.text:004D8AF0 Fun_KeyFun proc near ; CODE XREF:
Fun1+13p
.text:004D8AF0
.text:004D8AF0 var_45C = dword ptr -45Ch
.text:004D8AF0 var_458 = dword ptr -458h
.text:004D8AF0 var_454 = dword ptr -454h
.text:004D8AF0 var_450 = word ptr -450h
.text:004D8AF0 Dst = word ptr -3ECh
.text:004D8AF0 var_4 = dword ptr -4
.text:004D8AF0 arg_0 = dword ptr 4
.text:004D8AF0
.text:004D8AF0 sub esp, 45Ch
.text:004D8AF6 mov eax, dword_5CCE34
.text:004D8AFB xor eax, esp
.text:004D8AFD mov [esp+45Ch+var_4], eax
.text:004D8B04 mov eax, [esp+45Ch+arg_0]
.text:004D8B0B push ebx
.text:004D8B0C push ebp
.text:004D8B0D mov ebp, ecx
.text:004D8B0F lea ecx, [esp+464h+var_45C]
.text:004D8B13 push ecx ; int
.text:004D8B14 push 1F4h ; SizeInWords
.text:004D8B19 lea edx, [esp+46Ch+Dst]
.text:004D8B20 mov [esp+46Ch+var_458], eax
.text:004D8B24 push edx ; Dst
.text:004D8B25 lea eax, [ebp+0C8h]
.text:004D8B2B push eax ; int
.text:004D8B2C call Fun_GetInputString ; eax = UserName(Unicode)
.text:004D8B31 add esp, 10h
.text:004D8B34 cmp [esp+464h+var_45C], 0
.text:004D8B39 mov ebx, eax
.text:004D8B3B mov [esp+464h+var_454], ebx
.text:004D8B3F jnz short loc_4D8B48
.text:004D8B41 xor eax, eax
.text:004D8B43 jmp loc_4D8C17
.text:004D8B48 ;
---------------------------------------------------------------------------
.text:004D8B48
.text:004D8B48 loc_4D8B48: ; CODE XREF: Fun_KeyFun+4Fj
.text:004D8B48 push edi
.text:004D8B49 lea ecx, [esp+468h+var_45C]
.text:004D8B4D push ecx ; int
.text:004D8B4E push 32h ; SizeInWords
.text:004D8B50 lea edx, [esp+470h+var_450]
.text:004D8B54 push edx ; Dst
.text:004D8B55 lea eax, [ebp+74h]
.text:004D8B58 push eax ; int
.text:004D8B59 call Fun_GetInputString ; eax = RegCode(Unicode)
.text:004D8B5E mov edi, eax
.text:004D8B60 mov eax, [esp+478h+var_45C]
.text:004D8B64 add esp, 10h
.text:004D8B67 test eax, eax
.text:004D8B69 jz short loc_4D8BE9
.text:004D8B6B push esi
.text:004D8B6C xor esi, esi
.text:004D8B6E test eax, eax
.text:004D8B70 jle short loc_4D8BB6 ; 密钥字符串(Unicode)
.text:004D8B72
.text:004D8B72 loc_4D8B72: ; CODE XREF: Fun_KeyFun+C0j
.text:004D8B72 movzx ebx, word ptr [edi+esi*2]
.text:004D8B76 mov ecx, 100h
.text:004D8B7B cmp bx, cx
.text:004D8B7E jnb short loc_4D8B8D
.text:004D8B80 movzx edx, bl
.text:004D8B83 movzx eax, word_5F2890[edx*2]
.text:004D8B8B jmp short loc_4D8BA7
.text:004D8B8D ;
---------------------------------------------------------------------------
.text:004D8B8D
.text:004D8B8D loc_4D8B8D: ; CODE XREF: Fun_KeyFun+8Ej
.text:004D8B8D movzx eax, bx
.text:004D8B90 push eax ; lpsz
.text:004D8B91 call ds:CharUpperW
.text:004D8B97 movzx eax, ax
.text:004D8B9A test ax, ax
.text:004D8B9D jnz short loc_4D8BA4
.text:004D8B9F movzx eax, bx
.text:004D8BA2 jmp short loc_4D8BA7
.text:004D8BA4 ;
---------------------------------------------------------------------------
.text:004D8BA4
.text:004D8BA4 loc_4D8BA4: ; CODE XREF: Fun_KeyFun+ADj
.text:004D8BA4 movzx eax, ax
.text:004D8BA7
.text:004D8BA7 loc_4D8BA7: ; CODE XREF: Fun_KeyFun+9Bj
.text:004D8BA7 ; Fun_KeyFun+B2j
.text:004D8BA7 mov [edi+esi*2], ax
.text:004D8BAB inc esi
.text:004D8BAC cmp esi, [esp+46Ch+var_45C]
.text:004D8BB0 jl short loc_4D8B72 ; 将密钥中的小写字母转换为大写
.text:004D8BB2 mov ebx, [esp+46Ch+var_454]
.text:004D8BB6
.text:004D8BB6 loc_4D8BB6: ; CODE XREF: Fun_KeyFun+80j
.text:004D8BB6 push edi ; 密钥字符串(Unicode)
.text:004D8BB7 push ebx ; 用户名字符串(Unicode)
.text:004D8BB8 call Fun2_UserName_RegCode ; 参数(UserName, RegCode),对UserName 和 RegCode 进行校验
.text:004D8BBD add esp, 8
.text:004D8BC0 pop esi
.text:004D8BC1 test eax, eax ; 校验成功时这里返回1
.text:004D8BC3 jnz short loc_4D8BD1
.text:004D8BC5 mov ecx, [esp+468h+var_458] ; 校验成功时这里不执行
.text:004D8BC9 mov dword ptr [ecx], 5F81h
.text:004D8BCF jmp short loc_4D8C16 ; 校验失败时跳转
.text:004D8BD1 ;
---------------------------------------------------------------------------
.text:004D8BD1
.text:004D8BD1 loc_4D8BD1: ; CODE XREF: Fun_KeyFun+D3j
.text:004D8BD1 push edi
.text:004D8BD2 push ebx
.text:004D8BD3 call Fun_WriteInfo2RegAndIniFile ; 校验成功之后执行这里,参数仍然为(UserName, RegCode), 向注册表和ini文件中写入一些信息
.text:004D8BD8 add esp, 8
.text:004D8BDB test eax, eax
.text:004D8BDD jnz short loc_4D8BED
.text:004D8BDF mov edx, [esp+468h+var_458]
.text:004D8BE3 mov dword ptr [edx], 4E7Fh
.text:004D8BE9
.text:004D8BE9 loc_4D8BE9: ; CODE XREF: Fun_KeyFun+79j
.text:004D8BE9 xor eax, eax
.text:004D8BEB jmp short loc_4D8C16
.text:004D8BED ;
---------------------------------------------------------------------------
.text:004D8BED
.text:004D8BED loc_4D8BED: ; CODE XREF: Fun_KeyFun+EDj
.text:004D8BED mov eax, [ebp+120h]
.text:004D8BF3 push ebx ; Src
.text:004D8BF4 push 1F4h ; SizeInWords
.text:004D8BF9 push eax ; Dst
.text:004D8BFA call _wcscpy_s
.text:004D8BFF mov ecx, [ebp+124h]
.text:004D8C05 push edi ; Src
.text:004D8C06 push 32h ; SizeInWords
.text:004D8C08 push ecx ; Dst
.text:004D8C09 call _wcscpy_s
.text:004D8C0E add esp, 18h
.text:004D8C11 mov eax, 1
.text:004D8C16
.text:004D8C16 loc_4D8C16: ; CODE XREF: Fun_KeyFun+DFj
.text:004D8C16 ; Fun_KeyFun+FBj
.text:004D8C16 pop edi
.text:004D8C17
.text:004D8C17 loc_4D8C17: ; CODE XREF: Fun_KeyFun+53j
.text:004D8C17 mov ecx, [esp+464h+var_4]
.text:004D8C1E pop ebp
.text:004D8C1F pop ebx
.text:004D8C20 xor ecx, esp
.text:004D8C22 call Fun_UnKnow ;未总结功能
.text:004D8C27 add esp, 45Ch
.text:004D8C2D retn 4
.text:004D8C2D Fun_KeyFun endp
由以上代码及后续分析可得出,Fun_KeyFun 中做了以下几件事:
1:获取 Unicode 格式的 UserName 和 RegCode;
2:将 RegCode 中的小写字母转换为大写字母;
3:将 UserName 和 RegCode 做为参数传递到函数 Fun2_UserName_RegCode 中进行校验;
4:如果 Fun2_UserName_RegCode 的校验成功,返回值为1,然后继续调用 Fun_WriteInfo2RegAndIniFile,Fun_WriteInfo2RegAndIniFile 利用 UserName 和 RegCode 以及当前的磁盘信息生成一些加密信息,写入注册表和文件(.ini文件)。
以上流程中1和2不用详细解释,
下面重点看 Fun2_UserName_RegCode 利用 UserName 和 RegCode 进行校验的过程,重点看如何才能使该函数返回1。
Fun2_UserName_RegCode
.text:004D8250 Fun2_UserName_RegCode proc near ; CODE XREF:
sub_460B80+17Ep
.text:004D8250 ; Fun_KeyFun+C8p
.text:004D8250
.text:004D8250 Dst = word ptr -0BD0h
.text:004D8250 var_BCE = word ptr -0BCEh
.text:004D8250 var_BBC = byte ptr -0BBCh
.text:004D8250 var_4 = dword ptr -4
.text:004D8250 arg_0 = dword ptr 4
.text:004D8250 arg_4 = dword ptr 8
.text:004D8250
.text:004D8250 sub esp, 0BD0h
.text:004D8256 mov eax, dword_5CCE34
.text:004D825B xor eax, esp
.text:004D825D mov [esp+0BD0h+var_4], eax
.text:004D8264 push ebx
.text:004D8265 push ebp
.text:004D8266 mov ebp, [esp+0BD8h+arg_0] ; ebp = UserName
.text:004D826D push esi
.text:004D826E mov esi, [esp+0BDCh+arg_4] ; esi = RegCode
.text:004D8275 mov eax, ebp
.text:004D8277 push edi
.text:004D8278 lea edx, [eax+2]
.text:004D827B jmp short loc_4D8280
.text:004D827B ;
---------------------------------------------------------------------------
.text:004D827D align 10h
.text:004D8280
.text:004D8280 loc_4D8280: ; CODE XREF: Fun2_UserName_RegCode+2Bj
.text:004D8280 ; Fun2_UserName_RegCode+39j
.text:004D8280 mov cx, [eax]
.text:004D8283 add eax, 2
.text:004D8286 test cx, cx
.text:004D8289 jnz short loc_4D8280
.text:004D828B sub eax, edx
.text:004D828D sar eax, 1 ; eax = UserName 长度
.text:004D828F mov ebx, eax
.text:004D8291 mov eax, esi
.text:004D8293 lea edx, [eax+2]
.text:004D8296
.text:004D8296 loc_4D8296: ; CODE XREF: Fun2_UserName_RegCode+4Fj
.text:004D8296 mov cx, [eax]
.text:004D8299 add eax, 2
.text:004D829C test cx, cx
.text:004D829F jnz short loc_4D8296
.text:004D82A1 sub eax, edx
.text:004D82A3 sar eax, 1 ; eax = RegCode 长度
.text:004D82A5 mov edi, eax
.text:004D82A7 call Fun_CreateGlobalTable_005F38A0_ ; 在地址0X005F38A0处生成一个0X200大小的数组
.text:004D82AC push 0BB8h ; Arg4 常量0x0BB8
.text:004D82B1 lea eax, [esp+0BE4h+var_BBC]
.text:004D82B5 push eax ; Arg3 栈地址,用于保存格式化后数据的地址
.text:004D82B6 push ebx ; Arg2 Unicode 字符串长度(此处为UserName长度)
.text:004D82B7 push ebp ; Arg1 Unicode 字符串(此处为 UserName)
.text:004D82B8 call Fun_FroamtString ; 对Unicode字符串进行格式化,对每个Unicode字符按照 %04X进行格式化
.text:004D82BD push eax ; Arg3 格式化后字符串的长度
.text:004D82BE lea ecx, [esp+0BF4h+var_BBC]
.text:004D82C2 push ecx ; Arg2 格式化后的字符串
.text:004D82C3 push 0 ; Arg1 常量 0
.text:004D82C5 call Fun_CalcStringyValue ; 对Unicode字符串(这里是 UserName) 进行计算,得出一个值,在计算的过程中使用了之前的全局表数据
.text:004D82CA movzx edx, ax ; 计算的结果为 WORD 类型
.text:004D82CD push edx ; ArgList
.text:004D82CE lea eax, [esp+0C00h+Dst]
.text:004D82D2 push offset a04x ; "%04X"
.text:004D82D7 push eax ; Dst
.text:004D82D8 call Fun_FormatCalcRes ; 将计算的结果格式化为字符串
.text:004D82DD mov cx, [esi+4]
.text:004D82E1 lea eax, [esi+4]
.text:004D82E4 add esp, 28h
.text:004D82E7 cmp cx, [esp+0BE0h+Dst] ; 格式化后的字符串的第一个字符和 RegCode的第三个字符比较
.text:004D82EC jz short loc_4D82F2
.text:004D82EE
.text:004D82EE loc_4D82EE: ; CODE XREF: Fun2_UserName_RegCode+ABj
.text:004D82EE xor eax, eax
.text:004D82F0 jmp short loc_4D834C
.text:004D82F2 ;
---------------------------------------------------------------------------
.text:004D82F2
.text:004D82F2
loc_4D82F2:; CODE XREF: Fun2_UserName_RegCode+9Cj
.text:004D82F2 mov dx, [esi+6]
.text:004D82F6 cmp dx, [esp+0BE0h+var_BCE] ; 格式化后的字符串的第二个字符和RegCode的四个字符比较
.text:004D82FB jnz short loc_4D82EE
.text:004D82FD push 0BB8h
.text:004D8302 lea ecx, [esp+0BE4h+var_BBC]
.text:004D8306 push ecx
.text:004D8307 add edi, -2 ; RegCode 长度减2
.text:004D830A push edi
.text:004D830B push eax
.text:004D830C call Fun_FroamtString ; 将RegCode从第三个字符开始,按照 %4X 的格式对每个字符进行格式化
.text:004D8311 push eax
.text:004D8312 lea edx, [esp+0BF4h+var_BBC]
.text:004D8316 push edx
.text:004D8317 push 0
.text:004D8319 call Fun_CalcStringyValue ; 对Unicode字符串(这里是从RegCode 的第三个字符开始)进行计算,得出一个值,在计算的过程中使用了之前的全局表的数据
.text:004D831E movzx eax, ax ; 计算结果为 DWORD 类型
.text:004D8321 push eax ; ArgList
.text:004D8322 lea ecx, [esp+0C00h+Dst]
.text:004D8326 push offset a04x ; "%04X"
.text:004D832B push ecx ; Dst
.text:004D832C call Fun_FormatCalcRes ; 格式化计算的结果
.text:004D8331 mov dx, [esi] ; 取出 RegCode 第一位字符串
.text:004D8334 add esp, 28h
.text:004D8337 xor eax, eax
.text:004D8339 cmp dx, [esp+0BE0h+Dst] ; 格式化字符串的第一个和 RegCode 的第一个字符比较
.text:004D833E jnz short loc_4D834C
.text:004D8340 mov cx, [esi+2] ; 取出 RegCode 第二位字符串
.text:004D8344 cmp cx, [esp+0BE0h+var_BCE] ; 格式化字符串的第二个和 RegCode 的第二个字符比较
.text:004D8349 setz al
.text:004D834C
.text:004D834C loc_4D834C: ; CODE XREF: Fun2_UserName_RegCode+A0j
.text:004D834C ; Fun2_UserName_RegCode+EEj
.text:004D834C mov ecx, [esp+0BE0h+var_4]
.text:004D8353 pop edi
.text:004D8354 pop esi
.text:004D8355 pop ebp
.text:004D8356 pop ebx
.text:004D8357 xor ecx, esp
.text:004D8359 call Fun_UnKnow ; 暂时没看出来该函数的功能
.text:004D835E add esp, 0BD0h
.text:004D8364 retn
.text:004D8364 Fun2_UserName_RegCode endp
由以上代码及后续分析可得出, 校验过程如下:
1.通过函数 Fun_CreateGlobalTable_005F38A0 在全局地址 0x005F38A0 处生成了一张大小为 0x200 且数据固定的表;
2.通过函数 Fun_FroamtString 对 UserName 进行格式化;
3.通过函数 Fun_CalcStringyValue 对格式化后的 UserName 数据进行计算,在计算的过程中使用到了之前生成的全局表数据;
4.对3中计算得出的 DWORD 数据值按照"%04X"的格式进行格式化得出一个字符串,其中字符串的前两位需要与 RegCode 的第3、4位字符相同;
5.取 RegCode 的第 3-结束 部分作为新的字符串通过 3、4 的步骤也计算出一个 DWORD 值并按照 "%04X" 的格式进行格式化得出一个字符串,并且该字符串的前两位需要与 RegCode 的前 2 位字符相等。以上执行过程中如果比较条件都能满足, 则 Fun2_UserName_RegCode 的校验就是成功的,返回1,否则返回0。
举个例子说明上述过程:采用 UserName:"LocalHostUser", RegCode:"63AC4-6654R-QWED5"时,由于
"LocalHostUser"通过 Fun_FroamtString 后得出的内存数据值如下:
0012E4E8 30 30 34 43 30 30 36 46 30 30 36 33 30 30 36 31 004C006F00630061
0012E4F8 30 30 36 43 30 30 34 38 30 30 36 46 30 30 37 33 006C0048006F0073
0012E508 30 30 37 34 30 30 35 35 30 30 37 33 30 30 36 35 0074005500730065
0012E518 30 30 37 32 00 0072.
对上述内存数据通过 Fun_CalcStringyValue 得出的DWORD值是:0xDBAA;对该DWORD值调用 Fun_FroamtString 得到的字符串是:"DBAA", 所以 RegCode 的第3、4位字符需要是'D'和'B', 因此手动修改 RegCode 为"63DB4-6654R-QWED5"。
取 RegCode 的第 3-结尾 字符作为一个新的字符串 "DB4-6654R-QWED5",调用 Fun_FroamtString 得出的内存数据如下:
0012E4E8 30 30 34 34 30 30 34 32 30 30 33 34 30 30 32 44 004400420034002D
0012E4F8 30 30 33 36 30 30 33 36 30 30 33 35 30 30 33 34 0036003600350034
0012E508 30 30 35 32 30 30 32 44 30 30 35 31 30 30 35 37 0052002D00510057
0012E518 30 30 34 35 30 30 34 34 30 30 33 35 00 004500440035.
对上述内存数据通过 Fun_CalcStringyValue 得出的 DWORD值是:0x8122;对该DWORD值调用 Fun_FroamtString 得到的字符串是:"8122", RegCode的第1、2位需要是'8'、'1',因此手动修改 RegCode 为"81DB4-6654R-QWED5", 至此验证函数 Fun2_UserName_RegCode 即可返回1。
下面关键看验证过程中的几个算法和关键函数:
算法1: Fun_CreateGlobalTable_005F38A0 在全局地址 0x005F38A0 处生成了一张大小为 0x200 且数据固定的表:
.text:004D7560 Fun_CreateGlobalTable_005F38A0_ proc near
.text:004D7560 ; CODE XREF: Fun_GetVolumeSerialAndCalc+D6p
.text:004D7560 ; Fun2_UserName_RegCode+57p ...
.text:004D7560 push 200h ; size_t
.text:004D7565 push 0 ; int
.text:004D7567 push offset word_5F38A0 ; void *
.text:004D756C call _memset
.text:004D7571 add esp, 0Ch
.text:004D7574 xor edx, edx
.text:004D7576 jmp short loc_4D7580
.text:004D7576 ;
---------------------------------------------------------------------------
.text:004D7578 align 10h
.text:004D7580
.text:004D7580 loc_4D7580: ; CODE XREF: Fun_CreateGlobalTable_005F38A0_+16j
.text:004D7580 ; Fun_CreateGlobalTable_005F38A0_+57j
.text:004D7580 mov eax, 0C0C1h
.text:004D7585 mov ecx, 1
.text:004D758A lea ebx, [ebx+0]
.text:004D7590
.text:004D7590 loc_4D7590: ; CODE XREF: Fun_CreateGlobalTable_005F38A0_+4Ej
.text:004D7590 test edx, ecx
.text:004D7592 jz short loc_4D759C
.text:004D7594 xor word_5F38A0[edx*2], ax
.text:004D759C
.text:004D759C loc_4D759C: ; CODE XREF: Fun_CreateGlobalTable_005F38A0_+32j
.text:004D759C add eax, eax
.text:004D759E xor eax, 4003h
.text:004D75A3 add ecx, ecx
.text:004D75A5 cmp ecx, 100h
.text:004D75AB movzx eax, ax
.text:004D75AE jl short loc_4D7590
.text:004D75B0 inc edx
.text:004D75B1 cmp edx, 100h
.text:004D75B7 jl short loc_4D7580
.text:004D75B9 retn
.text:004D75B9 Fun_CreateGlobalTable_005F38A0_ endp
将上述代码直接还原为 C 代码如下:
/********************************************
函数功能:
在固定地址处 g_dwAddr(0x005F38A0) 处
依据函数内的算法生成数据, 生成的数据
是固定的, 大小为 g_dwSize(0X200)
**********************************************/
void Fun_CreateGlobalTable_005F38A0()
{
//空间置0
memset((void*)g_dwAddr, 0, g_dwSize);
DWORD dwEDX = 0;
DWORD dwEAX;
DWORD dwECX;
DWORD dwEBX;
do
{
dwEAX = 0xC0C1;
dwECX = 0x01;
dwEBX = 0x0A;
do
{
if(0 != (dwEDX&dwECX))
{
*(WORD*)((char*)g_dwAddr + dwEDX*2) ^= (WORD)(dwEAX&0x0000FFFF);
}
dwEAX *= 2;
dwEAX ^= 0x4003;
dwECX *= 2;
dwEAX &= 0x0000FFFF;
}while(dwECX < 0x100);
dwEDX++;
}while(dwEDX < 0x100);
}
字符串格式化函数 Fun_FroamtString:
.text:004D7600 Fun_FroamtString proc near ; CODE XREF:
Fun2_UserName_RegCode+68p
.text:004D7600 ; Fun2_UserName_RegCode+BCp
.text:004D7600
.text:004D7600 arg_0 = dword ptr 4
.text:004D7600 arg_4 = dword ptr 8
.text:004D7600 arg_8 = dword ptr 0Ch
.text:004D7600 arg_C = dword ptr 10h
.text:004D7600
.text:004D7600 push ebp
.text:004D7601 mov ebp, [esp+4+arg_4]
.text:004D7605 push esi
.text:004D7606 push edi
.text:004D7607 xor esi, esi
.text:004D7609 xor edi, edi
.text:004D760B test ebp, ebp
.text:004D760D jle short loc_4D7648
.text:004D760F push ebx
.text:004D7610 mov ebx, [esp+10h+arg_8]
.text:004D7614
.text:004D7614 loc_4D7614: ; CODE XREF: Fun_FroamtString+3Bj
.text:004D7614 mov eax, [esp+10h+arg_0]
.text:004D7618 movzx ecx, word ptr [eax+edi*2]
.text:004D761C mov edx, [esp+10h+arg_C]
.text:004D7620 push ecx
.text:004D7621 push offset a04x_0 ; "%04X"
.text:004D7626 sub edx, esi
.text:004D7628 push edx ; SizeInBytes
.text:004D7629 lea eax, [esi+ebx]
.text:004D762C push eax ; DstBuf
.text:004D762D call _sprintf_s
.text:004D7632 inc edi
.text:004D7633 add esp, 10h
.text:004D7636 add esi, 4
.text:004D7639 cmp edi, ebp
.text:004D763B jl short loc_4D7614
.text:004D763D mov byte ptr [esi+ebx], 0
.text:004D7641 pop ebx
.text:004D7642 pop edi
.text:004D7643 mov eax, esi
.text:004D7645 pop esi
.text:004D7646 pop ebp
.text:004D7647 retn
.text:004D7648 ;
---------------------------------------------------------------------------
.text:004D7648
.text:004D7648
loc_4D7648: ; CODE XREF: Fun_FroamtString+Dj
.text:004D7648 mov ecx, [esp+0Ch+arg_8]
.text:004D764C pop edi
.text:004D764D mov byte ptr [esi+ecx], 0
.text:004D7651 mov eax, esi
.text:004D7653 pop esi
.text:004D7654 pop ebp
.text:004D7655 retn
.text:004D7655 Fun_FroamtString endp
将上述代码直接还原为 C 代码如下:
/****************************************************************************
参数意义:
Arg1: IN g_wChar Unicode 字符串首地址
Arg2: IN g_dwAddrFormatDataBuff 接收格式化数据的地址
Arg3: IN dwLength 接收格式化数据的缓冲区长度
Arg4: IN dwConstValue 常量 0xBB8
函数功能: 格式化 Unicode字符串,格式化后的数据存放到 g_dwAddrFormatDataBuff
函数返回值: 返回格式化后缓冲区的字节数
****************************************************************************/
DWORD Fun_FroamtString(IN WCHAR *g_wChar, IN DWORD g_dwAddrFormatDataBuff, IN DWORD dwLength, IN DWORD dwConstValue)
{
DWORD dwRet = 0;
WORD wValue;
for(int iIndex = 0; iIndex < dwLength; iIndex++)
{
wValue = *(WORD*)((DWORD)g_wChar + iIndex*2); //取出每个Unicode字符的值
::sprintf((char*)(g_dwAddrFormatDataBuff + dwRet), "%04X", wValue);
dwRet += 4;
}
*(char*)(g_dwAddrFormatDataBuff + dwRet) = 0;
return dwRet;
}
算法2:Fun_CalcStringyValue,利用格式化后的内存字符串数据和之前生成的全局表数据来计算出一个DWORD值:
.text:004D75C0 Fun_CalcStringyValue proc near ; CODE XREF: Fun_GetVolumeSerialAndCalc+E4p
.text:004D75C0 ; Fun2_UserName_RegCode+75p ...
.text:004D75C0
.text:004D75C0 arg_0 = dword ptr 4
.text:004D75C0 arg_4 = dword ptr 8
.text:004D75C0 arg_8 = dword ptr 0Ch
.text:004D75C0
.text:004D75C0 mov ecx, [esp+arg_4]
.text:004D75C4 mov eax, [esp+arg_8]
.text:004D75C8 lea edx, [ecx+eax]
.text:004D75CB cmp ecx, edx
.text:004D75CD jnb short loc_4D75F4
.text:004D75CF mov eax, [esp+arg_0]
.text:004D75D3 push esi
.text:004D75D4 push edi
.text:004D75D5
.text:004D75D5 loc_4D75D5: ; CODE XREF: Fun_CalcStringyValue+2Fj
.text:004D75D5 movzx edi, byte ptr [ecx]
.text:004D75D8 movzx esi, al
.text:004D75DB shr ax, 8
.text:004D75DF xor esi, edi
.text:004D75E1 xor ax, word_5F38A0[esi*2]
.text:004D75E9 inc ecx
.text:004D75EA movzx eax, ax
.text:004D75ED cmp ecx, edx
.text:004D75EF jb short loc_4D75D5
.text:004D75F1 pop edi
.text:004D75F2 pop esi
.text:004D75F3 retn
.text:004D75F4 ;
---------------------------------------------------------------------------
.text:004D75F4
.text:004D75F4 loc_4D75F4: ; CODE XREF: Fun_CalcStringyValue+Dj
.text:004D75F4 mov ax, word ptr [esp+arg_0]
.text:004D75F9 retn
.text:004D75F9 Fun_CalcStringyValue endp
将上述代码直接还原为 C 代码如下:
/***************************************************************************************
参数意义:
Arg1: IN dwArg1 传入的值为 0, 内部并没有使用
Arg2: IN 传入的是对输入的数据进行格式化之后的内存数据首地址
Arg3: IN 传入的是 Arg2 指向的 ASCII 字符串的长度
函数功能: 对 Arg2 处的数据进行计算, 计算的过程中使用到了之前生成的 0x005F38A0 处的数据
函数返回值: 返回计算的结果 DWORD值
***************************************************************************************/
DWORD Fun_004D75C0(IN DWORD dwArg1, IN char *lpszAddr, IN DWORD dwLength)
{
DWORD dwStartAddr = (DWORD)lpszAddr;
DWORD dwESI = 0;
DWORD dwEDI = 0;
DWORD dwEAX = 0;
WORD wAX = 0;
WORD wValue = 0;
//比较字符串首位地址高低
if((dwStartAddr + dwLength) <= (DWORD)lpszAddr)
{
return dwArg1;
}
for(int iIndex = 0; iIndex < dwLength; iIndex++)
{
dwEDI = *(lpszAddr + iIndex);
dwESI = dwEAX&0x000000FF;
wAX = dwEAX&0x0000FFFF;
wAX = wAX >> 8;
dwESI = dwESI^dwEDI;
wValue = *(WORD*)(g_dwAddr + dwESI*2);
wAX = wAX^wValue;
dwEAX = wAX;
}
return dwEAX;
}
至此 Fun2_UserName_RegCode 函数中的关键算法和函数都已还原完。现在函数流程回到 Fun_KeyFun 中,如果校验成功,则会调用 Fun_WriteInfo2RegAndIniFile 利用能够校验成功的 UserName 和 RegCode 以及硬盘信息生成一些加密后的数据写到注册表以及程序安装目录下的 "reg_u.ini" 文件中。由于在整个注册算法分析的过程中该部分并不是很关键的部分,并且这部分内容实在过多,所以如果有兴趣的朋友可以参见idb文件中的注释内容,这里就不再详细描述。
在这个地方如果你在OD里面直接F9运行,会弹出 UserName 和 RegCode 注册成功的对话框,但是如果你以为到此整个注册过程就分析完了,那后面就会把自己坑死,因为还有一个巨坑。
注意看 Fun_KeyFun 的 Fun_WriteInfo2RegAndIniFile 之后, 还有两个 _wcscpy_s 函数,而拷贝的内容一个是 UserName 一个是 RegCode, 如果注册算法已经还原完毕,那这里还要拷贝 UserName 和 RegCode 做什么呢?所以坑就在后面。
在OD中注意观察拷贝过程
拷贝 UserName
拷贝 RegCode
所以在内存00CBA120和00CBA508处都下上Byte类型的硬件访问断点,然后直接F9运行,看看后续哪里还在使用 UserName 和RegCode。通过硬件断点可以发现后续第一次使用 UserName 的地方位于 sub_004D7FE0 函数内部,代码如下:
Fun_ReChecRegCodekBit5:
.text:004D7FE0 Fun_ReChecRegCodekBit5 proc near ; CODE XREF:
sub_4D8850+3p
.text:004D7FE0
.text:004D7FE0 var_2C = dword ptr -2Ch
.text:004D7FE0 var_28 = dword ptr -28h
.text:004D7FE0 Dst = word ptr -24h
.text:004D7FE0 var_10 = dword ptr -10h
.text:004D7FE0 var_C = dword ptr -0Ch
.text:004D7FE0 var_4 = dword ptr -4
.text:004D7FE0
.text:004D7FE0 push 0FFFFFFFFh
.text:004D7FE2 push offset loc_577E73
.text:004D7FE7 mov eax, large fs:0
.text:004D7FED push eax
.text:004D7FEE sub esp, 20h
.text:004D7FF1 mov eax, dword_5CCE34
.text:004D7FF6 xor eax, esp
.text:004D7FF8 mov [esp+2Ch+var_10], eax
.text:004D7FFC push ebx
.text:004D7FFD push ebp
.text:004D7FFE push esi
.text:004D7FFF push edi
.text:004D8000 mov eax, dword_5CCE34
.text:004D8005 xor eax, esp
.text:004D8007 push eax
.text:004D8008 lea eax, [esp+40h+var_C]
.text:004D800C mov large fs:0, eax
.text:004D8012 mov ebp, ecx
.text:004D8014 mov [esp+40h+var_28], ebp
.text:004D8018 mov dword ptr [ebp+0], offset off_598A4C
.text:004D801F mov edx, [ebp+120h] ; edx = UserName(Unicode)首地址
.text:004D8025 mov eax, edx ; eax = edx = UserName(Unicode)首地址
.text:004D8027 mov [esp+40h+var_4], 1 ; Var4 = 1
.text:004D802F lea esi, [eax+2] ; esi 指向 UserName 第二个字符
.text:004D8032
.text:004D8032 loc_4D8032: ; CODE XREF: Fun_ReChecRegCodekBit5+5Bj
.text:004D8032 mov cx, [eax] ; 取出 UserName 中的当前字符
.text:004D8035 add eax, 2
.text:004D8038 test cx, cx
.text:004D803B jnz short loc_4D8032 ; 计算UserName的长度
.text:004D803D sub eax, esi
.text:004D803F sar eax, 1
.text:004D8041 mov ebx, eax ; eax = ebx = UserName 长度
.text:004D8043 mov [esp+14h], ebx ; 保存UserName长度
.text:004D8047 test ebx, ebx
.text:004D8049 jle loc_4D8104 ; UserName 长度 <= 0 跳转
.text:004D804F xor esi, esi ; esi = 0
.text:004D8051 xor edi, edi ; edi = 0
.text:004D8053 xor eax, eax ; eax = 0
.text:004D8055 cmp ebx, 2
.text:004D8058 lea ecx, [esi+1] ; ecx = 1
.text:004D805B jl short loc_4D807C ; UserName 长度 < 2 时跳转
.text:004D805D dec ebx ; ebx = UserName 长度 - 1
.text:004D805E mov edi, edi
.text:004D8060
.text:004D8060 loc_4D8060: ; CODE XREF: Fun_ReChecRegCodekBit5+92j
.text:004D8060 movzx ebp, word ptr [edx+eax*2] ; ebp = UserName 中的第奇数个字符
.text:004D8064 add esi, ebp ; esi = UserName 中的第奇数个字符的累加和(不包含最后一个字符(如果最后一个字符是第奇数个字符))
.text:004D8066 movzx ebp, word ptr [edx+eax*2+2] ; ebp = UserName 中的第偶数个字符
.text:004D806B add eax, 2
.text:004D806E add edi, ebp ; edi = UserName 中的第偶数个字符的累加和
.text:004D8070 cmp eax, ebx
.text:004D8072 jl short loc_4D8060 ; ebp = UserName 中的第奇数个字符
.text:004D8074 mov ebx, [esp+40h+var_2C] ; ebx = UserName 长度
.text:004D8078 mov ebp, [esp+40h+var_28]
.text:004D807C
.text:004D807C loc_4D807C: ; CODE XREF: Fun_ReChecRegCodekBit5+7Bj
.text:004D807C cmp eax, ebx
.text:004D807E jge short loc_4D8085 ; esi = UserName 处最后一个第奇数个字符外其他字符的累加和
.text:004D8080 movzx ecx, word ptr [edx+eax*2] ; ecx = UserName 最后一个字符
.text:004D8084 inc ecx ; 最后一个字符累加 1
.text:004D8085
.text:004D8085 loc_4D8085: ; CODE XREF: Fun_ReChecRegCodekBit5+9Ej
.text:004D8085 add esi, edi ; esi = UserName 处最后一个第奇数个字符外其他字符的累加和
.text:004D8087 add ecx, esi ; ecx = UserName 所有字母累加和 + 1
.text:004D8089 lea ecx, [ecx+ecx*8+0Ah] ; ecx = ecx*9 + 0A
.text:004D808D mov eax, 55555556h
.text:004D8092 imul ecx
.text:004D8094 mov eax, edx
.text:004D8096 shr eax, 1Fh
.text:004D8099 lea ecx, [edx+eax+24h] ; ecx = ecx/3 + 0x24
.text:004D809D and ecx, 8000000Fh ; ecx = ecx&0x8000000F
.text:004D80A3 jns short loc_4D80AA ; ecx为正数时跳转
.text:004D80A5 dec ecx
.text:004D80A6 or ecx, 0FFFFFFF0h
.text:004D80A9 inc ecx ; ecx 为负数时 ecx =((ecx - 1)|0XFFFFFFF0) + 1
.text:004D80AA
.text:004D80AA loc_4D80AA: ; CODE XREF: Fun_ReChecRegCodekBit5+C3j
.text:004D80AA push ecx ; ArgList
.text:004D80AB lea edx, [esp+44h+Dst]
.text:004D80AF push offset a1x ; "%1X"
.text:004D80B4 push edx ; Dst
.text:004D80B5 call Fun_FormatCalcRes ; 按照"%1x"的格式格式化计算结果
.text:004D80BA mov edx, [ebp+124h] ; edx 指向 RegCode
.text:004D80C0 mov eax, edx ; eax = edx
.text:004D80C2 add esp, 0Ch
.text:004D80C5 lea esi, [eax+2] ; esi 指向 RegCode 的第二个字符
.text:004D80C8 jmp short loc_4D80D0
.text:004D80C8 ;
---------------------------------------------------------------------------
.text:004D80CA align 10h
.text:004D80D0
.text:004D80D0 loc_4D80D0: ; CODE XREF: Fun_ReChecRegCodekBit5+E8j
.text:004D80D0 ; Fun_ReChecRegCodekBit5+F9j
.text:004D80D0 mov cx, [eax]
.text:004D80D3 add eax, 2
.text:004D80D6 test cx, cx
.text:004D80D9 jnz short loc_4D80D0
.text:004D80DB sub eax, esi
.text:004D80DD sar eax, 1 ; eax 得出的是 RegCode 长度
.text:004D80DF cmp eax, 5 ; RegCode 长度小于 5 时跳转
.text:004D80E2 jb short loc_4D80F3
.text:004D80E4 mov ax, [edx+8] ; ax = RegCode 的第五个字符
.text:004D80E8 cmp ax, [esp+40h+Dst] ; 比较 RegCode 的第五个字符和之前格式化得出的一个字符
.text:004D80ED jz loc_4D81CF ; 相等时跳转
.text:004D80F3
.text:004D80F3 loc_4D80F3: ; CODE XREF: Fun_ReChecRegCodekBit5+102j
.text:004D80F3 mov ecx, [ebp+134h]
.text:004D80F9 mov dword ptr [ecx], 1 ; 标志位置1, 表示 RegCode 长度 < 5 或者 RegCode 的第五位没有校验成功
.text:004D80FF jmp loc_4D81CF
.text:004D8104 ;
---------------------------------------------------------------------------
.text:004D8104
.text:004D8104
loc_4D8104: ; CODE XREF: Fun_ReChecRegCodekBit5+69j
.text:004D8104 mov edi, [ebp+128h]
.text:004D810A mov eax, edi
.text:004D810C lea edx, [eax+1]
.text:004D810F nop
.text:004D8110
.text:004D8110 loc_4D8110: ; CODE XREF: Fun_ReChecRegCodekBit5+135j
.text:004D8110 mov cl, [eax]
.text:004D8112 inc eax
.text:004D8113 test cl, cl
.text:004D8115 jnz short loc_4D8110
.text:004D8117 sub eax, edx
.text:004D8119 mov ebx, eax
.text:004D811B mov [esp+40h+var_2C], ebx
.text:004D811F test ebx, ebx
.text:004D8121 jle loc_4D81CF
.text:004D8127 xor edx, edx
.text:004D8129 xor esi, esi
.text:004D812B xor eax, eax
.text:004D812D cmp ebx, 2
.text:004D8130 lea ecx, [edx+1]
.text:004D8133 jl short loc_4D815C
.text:004D8135 dec ebx
.text:004D8136 jmp short loc_4D8140
.text:004D8136 ;
---------------------------------------------------------------------------
.text:004D8138 align 10h
.text:004D8140
.text:004D8140 loc_4D8140: ; CODE XREF: Fun_ReChecRegCodekBit5+156j
.text:004D8140 ; Fun_ReChecRegCodekBit5+172j
.text:004D8140 movzx ebp, byte ptr [edi+eax]
.text:004D8144 add edx, ebp
.text:004D8146 movzx ebp, byte ptr [edi+eax+1]
.text:004D814B add eax, 2
.text:004D814E add esi, ebp
.text:004D8150 cmp eax, ebx
.text:004D8152 jl short loc_4D8140
.text:004D8154 mov ebp, [esp+40h+var_28]
.text:004D8158 mov ebx, [esp+40h+var_2C]
.text:004D815C
.text:004D815C loc_4D815C: ; CODE XREF: Fun_ReChecRegCodekBit5+153j
.text:004D815C cmp eax, ebx
.text:004D815E jge short loc_4D8165
.text:004D8160 movzx ecx, byte ptr [eax+edi]
.text:004D8164 inc ecx
.text:004D8165
.text:004D8165 loc_4D8165: ; CODE XREF: Fun_ReChecRegCodekBit5+17Ej
.text:004D8165 add esi, edx
.text:004D8167 add ecx, esi
.text:004D8169 lea ecx, [ecx+ecx*8+0Ah]
.text:004D816D mov eax, 55555556h
.text:004D8172 imul ecx
.text:004D8174 mov eax, edx
.text:004D8176 shr eax, 1Fh
.text:004D8179 lea ecx, [edx+eax+24h]
.text:004D817D and ecx, 8000000Fh
.text:004D8183 jns short loc_4D818A
.text:004D8185 dec ecx
.text:004D8186 or ecx, 0FFFFFFF0h
.text:004D8189 inc ecx
.text:004D818A
.text:004D818A loc_4D818A: ; CODE XREF: Fun_ReChecRegCodekBit5+1A3j
.text:004D818A push ecx ; ArgList
.text:004D818B lea edx, [esp+44h+Dst]
.text:004D818F push offset off_589878 ; Format
.text:004D8194 push edx ; DstBuf
.text:004D8195 call sub_4D7840
.text:004D819A mov ecx, [ebp+12Ch]
.text:004D81A0 mov eax, ecx
.text:004D81A2 add esp, 0Ch
.text:004D81A5 lea esi, [eax+1]
.text:004D81A8
.text:004D81A8 loc_4D81A8: ; CODE XREF: Fun_ReChecRegCodekBit5+1CDj
.text:004D81A8 mov dl, [eax]
.text:004D81AA inc eax
.text:004D81AB test dl, dl
.text:004D81AD jnz short loc_4D81A8
.text:004D81AF sub eax, esi
.text:004D81B1 cmp eax, 5
.text:004D81B4 jb short loc_4D81C3
.text:004D81B6 movzx eax, byte ptr [ecx+4]
.text:004D81BA movsx ecx, byte ptr [esp+40h+Dst]
.text:004D81BF cmp eax, ecx
.text:004D81C1 jz short loc_4D81CF
.text:004D81C3
.text:004D81C3 loc_4D81C3: ; CODE XREF: Fun_ReChecRegCodekBit5+1D4j
.text:004D81C3 mov edx, [ebp+134h]
.text:004D81C9 mov dword ptr [edx], 1
.text:004D81CF
.text:004D81CF loc_4D81CF: ; CODE XREF: Fun_ReChecRegCodekBit5+10Dj
.text:004D81CF ; Fun_ReChecRegCodekBit5+11Fj ...
.text:004D81CF lea ecx, [ebp+0C8h]
.text:004D81D5 call sub_518CCA
.text:004D81DA lea ecx, [ebp+74h]
.text:004D81DD mov byte ptr [esp+40h+var_4], 0
.text:004D81E2 call sub_518CCA
.text:004D81E7 mov ecx, ebp
.text:004D81E9 mov [esp+40h+var_4], 0FFFFFFFFh
.text:004D81F1 mov dword ptr [ebp+0], offset off_580E24
.text:004D81F8 call sub_5108ED
.text:004D81FD mov ecx, [esp+40h+var_C]
.text:004D8201 mov large fs:0, ecx
.text:004D8208 pop ecx
.text:004D8209 pop edi
.text:004D820A pop esi
.text:004D820B pop ebp
.text:004D820C pop ebx
.text:004D820D mov ecx, [esp+2Ch+var_10]
.text:004D8211 xor ecx, esp
.text:004D8213 call Fun_UnKnow
.text:004D8218 add esp, 2Ch
.text:004D821B retn
.text:004D821B Fun_ReChecRegCodekBit5 endp
由以上代码及后续的分析可得出:
Fun_ReChecRegCodekBit5 函数内部对UserName长度和RegCode的长度都做了检查,
如果 UserName长度 < 2 或 RegCode的长度 < 5 均直接校验失败,在校验过程中使用了一段算法对 UserName 重新计算得出一个DWORD数据,然后对这个DWORD数据按照"%1X"进行格式化得到一个字符,
如果该字符和 RegCode 的第5位字符不匹配,会执行
.text:004D80F3 loc_4D80F3: ; CODE XREF: Fun_ReChecRegCodekBit5+102j
.text:004D80F3 mov ecx, [ebp+134h]
.text:004D80F9 mov dword ptr [ecx], 1 ; 标志位置1, 表示 RegCode 长度 < 5 或者 RegCode 的第五位没有校验成功
.text:004D80FF jmp loc_4D81CF
这3句。通过后面的分析得知,其实这个函数的功能就在于执行不执行这3句上,因为 004D80F9 实际上是一个置标志位为1的动作,该位为1表示RegCode的第5位校验失败,该位为0表示RegCode的第5位校验成功。
下面把利用 UserName 计算 RegCode 第5位的算法还原为 C 代码,如下:
/*****************************************************************
参数意义:
无。
函数功能: 对 UserName 再次进行计算,得出的结果格式化为 1 个字符。
函数返回值: 返回格式化得出的字符。
******************************************************************/
char Fun_GetBit5ByUserName(const char *lpUserName)
{
char szBuff[4];
int iRes = 0;
for(int iIndex = 0; iIndex < strlen(lpUserName); iIndex++)
{
iRes += *(lpUserName + iIndex);
}
iRes++;
iRes = iRes*9 + 0xA;
iRes = iRes/3 + 0x24;
iRes &= 0X8000000F;
if(iRes < 0)
{
iRes = ((iRes - 1) | 0XFFFFFFF0) + 1;
}
::wsprintf(szBuff, "%1X", iRes);
return szBuff[0];
}
利用该算法对 UserName:"LocalHostUser"得出的字符是'2'。所以 RegCode 的第5位需要是 '2'。
该算法可称为算法3。
上面的内容分析完毕之后直接F9,会发现弹出一个对话提示重新启动程序,关闭弹出的对话框,并且在关闭程序主窗口界面之后,会有断点断到对 RegCode 的使用上,可以发现断点位于函数 sub_00469210 内:
.text:00469210 ; int __thiscall Fun_ModifyRegCodeByFlag(CFrameWnd *this)
.text:00469210 Fun_ModifyRegCodeByFlag proc near ; CODE XREF:
sub_46A9E0+3p
.text:00469210
.text:00469210 var_10 = dword ptr -10h
.text:00469210 var_C = dword ptr -0Ch
.text:00469210 var_4 = dword ptr -4
.text:00469210
.text:00469210 push 0FFFFFFFFh
.text:00469212 push offset loc_573A82
.text:00469217 mov eax, large fs:0
.text:0046921D push eax
.text:0046921E push ecx
.text:0046921F push esi
.text:00469220 mov eax, dword_5CCE34
.text:00469225 xor eax, esp
.text:00469227 push eax
.text:00469228 lea eax, [esp+18h+var_C]
.text:0046922C mov large fs:0, eax
.text:00469232 mov esi, ecx
.text:00469234 mov [esp+18h+var_10], esi
.text:00469238 mov dword ptr [esi], offset off_58F584
.text:0046923E cmp dword ptr [esi+1EECh], 0 ; 比较标志位,如果该位为 0 表示之前的 RegCode 的第 5 位验证成功,否者表示之前的验证失败
.text:00469245 mov [esp+18h+var_4], 0Bh
.text:0046924D jz short loc_4692A1
.text:0046924F cmp byte ptr [esi+1EA8h], 30h
.text:00469256 lea eax, [esi+1EA8h]
.text:0046925C setz cl
.text:0046925F push eax ; void *
.text:00469260 lea edx, [esi+1CB4h]
.text:00469266 add cl, 30h
.text:00469269 push edx ; int
.text:0046926A mov [eax], cl
.text:0046926C call sub_4D7970
.text:00469271 lea eax, [esi+1C50h]
.text:00469277 mov ecx, 30h
.text:0046927C add esp, 8
.text:0046927F cmp cx, [eax] ; 判断 RegCode 的第一个字符是否为 '0'
.text:00469282 jnz short loc_46928E ; RegCode 的第一个字符不为 '0'时跳转
.text:00469284 mov edx, 31h
.text:00469289 mov [eax], dx
.text:0046928C jmp short loc_469291
.text:0046928E ;
---------------------------------------------------------------------------
.text:0046928E
.text:0046928E
loc_46928E: ; CODE XREF: Fun_ModifyRegCodeByFlag+72j
.text:0046928E mov [eax], cx ; RegCode 的第一个字符置 '0'
.text:00469291
.text:00469291 loc_469291: ; CODE XREF: Fun_ModifyRegCodeByFlag+7Cj
.text:00469291 push eax
.text:00469292 lea edx, [esi+1868h]
.text:00469298 push edx
.text:00469299 call Fun_WriteInfo2RegAndIniFile ; 修改 RegCode 之后重新加密,生成新的信息,写入注册表和文件
.text:0046929E add esp, 8
.text:004692A1
.text:004692A1 loc_4692A1: ; CODE XREF: Fun_ModifyRegCodeByFlag+3Dj
.text:004692A1 mov eax, [esi+1F44h]
.text:004692A7 mov byte ptr [esp+18h+var_4], 0Ah
.text:004692AC test eax, eax
.text:004692AE jz short loc_4692B8
.text:004692B0 mov ecx, [eax]
.text:004692B2 mov edx, [ecx+8]
.text:004692B5 push eax
.text:004692B6 call edx
.text:004692B8
.text:004692B8 loc_4692B8: ; CODE XREF: Fun_ModifyRegCodeByFlag+9Ej
.text:004692B8 mov eax, [esi+1864h]
.text:004692BE sub eax, 10h
.text:004692C1 mov byte ptr [esp+18h+var_4], 9
.text:004692C6 lea ecx, [eax+0Ch]
.text:004692C9 or edx, 0FFFFFFFFh
.text:004692CC lock xadd [ecx], edx
.text:004692D0 dec edx
.text:004692D1 test edx, edx
.text:004692D3 jg short loc_4692DF
.text:004692D5 mov ecx, [eax]
.text:004692D7 mov edx, [ecx]
.text:004692D9 push eax
.text:004692DA mov eax, [edx+4]
.text:004692DD call eax
.text:004692DF
.text:004692DF loc_4692DF: ; CODE XREF: Fun_ModifyRegCodeByFlag+C3j
.text:004692DF lea ecx, [esi+1794h]
.text:004692E5 mov byte ptr [esp+18h+var_4], 8
.text:004692EA call sub_40EE30
.text:004692EF mov eax, [esi+176Ch]
.text:004692F5 mov dword ptr [esi+1768h], offset off_58E7EC
.text:004692FF test eax, eax
.text:00469301 jz short loc_46930C
.text:00469303 push eax ; void *
.text:00469304 call ??3@YAXPAX@Z ; operator delete(void *)
.text:00469309 add esp, 4
.text:0046930C
.text:0046930C loc_46930C: ; CODE XREF: Fun_ModifyRegCodeByFlag+F1j
.text:0046930C lea ecx, [esi+15DCh] ; this
.text:00469312 mov byte ptr [esp+18h+var_4], 6
.text:00469317 call sub_469180
.text:0046931C lea ecx, [esi+0C2Ch]
.text:00469322 mov byte ptr [esp+18h+var_4], 5
.text:00469327 call sub_4EADC0
.text:0046932C lea ecx, [esi+5FCh]
.text:00469332 mov byte ptr [esp+18h+var_4], 4
.text:00469337 call sub_4C0ED0
.text:0046933C lea ecx, [esi+480h] ; this
.text:00469342 mov byte ptr [esp+18h+var_4], 3
.text:00469347 call sub_4690D0
.text:0046934C lea ecx, [esi+304h] ; this
.text:00469352 mov byte ptr [esp+18h+var_4], 2
.text:00469357 call sub_4690D0
.text:0046935C lea ecx, [esi+188h] ; this
.text:00469362 mov byte ptr [esp+18h+var_4], 1
.text:00469367 call sub_4690D0
.text:0046936C lea ecx, [esi+0ECh] ; this
.text:00469372 mov byte ptr [esp+18h+var_4], 0
.text:00469377 call ??1CStatusBar@@UAE@XZ ; CStatusBar::~CStatusBar(void)
.text:0046937C mov ecx, esi ; this
.text:0046937E mov [esp+18h+var_4], 0FFFFFFFFh
.text:00469386 call ??1CFrameWnd@@UAE@XZ ; CFrameWnd::~CFrameWnd(void)
.text:0046938B mov ecx, [esp+18h+var_C]
.text:0046938F mov large fs:0, ecx
.text:00469396 pop ecx
.text:00469397 pop esi
.text:00469398 add esp, 10h
.text:0046939B retn
.text:0046939B Fun_ModifyRegCodeByFlag endp
通过对上述代码的分析可以发现,ModifyRegCodeByFlag 函数会根据由之前对 RegCode 第5位的校验结果而置的标志位的情况来选择操作,如果之前校验成功,这里就什么都不做,如果校验失败,该函数会修改 RegCode的第一个字符为'0', 并且依据UserName 和修改后的 RegCode 再次调用 Fun_WriteInfo2RegAndIniFile 函数来生成相关信息并写入注册表和文件。
到此,整个注册过程才算分析完毕。
下面总结一下整体的注册流程:
1.利用算法1在全局数据区生成一个固定大小为 0X200,且数据固定的表;
2.利用算法2对 UserName 计算出结果,利用计算结果对 RegCode 的第3、4位进行校验;
3.利用算法2对 RegCode 的3-结束 字符串计算出结果,利用计算结果对 RegCode 的第1、2位进行验证;
4.利用算法3对 UserName 计算出一个结果,利用该结果对 RegCode 的第5位的校验情况置一个标志位;
5.在关闭弹出的重启验证对话框,并且关闭程序主界面之后,程序利用步骤4中所置的标志位来决定是否修改 RegCode 并重新写注册信息到注册表和文件(修改后的 UserName 和 RegCode 肯定是过不了3步的验证的)。
依据上述的注册过程,经过再次的调试和修改,可以得出一组可用的UserName和RegCode如下:
UserName:"LocalHostUser"
RegCode:"6FDB2-6654R-QWED5"。
由于在整个分析过程中并没有注意到 RegCode 的严格的格式要求,所以这里就随便采用了一种格式。
关于该软件的ASCII字符串验证过程:由于该软件默认的先直接采用Unicode字符串进行校验,所以分析过程以Unicode格式的字符串进行,对于ASCII格式的校验过程在IDA中看了一下流程和调用的函数,发现和Unicode格式校验过程调用的一样,所以就没有详细分析。
关于该软件的64位版本:利用上面的UserName和RegCode注册 Win7 SP1 下的64位4.1版本的软件时能够直接成功,同时用
IDA查看了64位程序的情况,发现注册流程和算法与32位版本相似,所以这里也不再分析。
最后注册机我就不放出来了(其实如果你仔细看完此文你已经可以自己写了),上面给出了一组 UserName 和 RegCode 大家可以试一下。
[课程]Linux pwn 探索篇!