下载地址:http://www.crackmes.de/users/taliesin/kgm1tal
使用工具:
反编译及调试工具:IDA,OllyDbg
程序开发包:MASM32 1. 界面的生成
这是一个汇编写的SDK程序,用IDA载入,找到入口点,可以轻易发现
GetModuleHandle,ExitProcess这些函数。出人意料的是这个程序竟然没
有静态资源,加载到内存才用自己的代码动态创建资源,因而Resource
Hacker这些工具无法派上用场。主界面是在资源创建完毕以后用
DialogBoxIndirectParam(好复杂的名字@_@)建立的对话框。这就是
说,我们能看到的资源全部是二进制形式的。如果知道资源的二进制与脚
本形式的对应关系,应该可以根据这些二进制资源重建一个资源脚本,以
便能够使用习惯的DialogBoxParam来建立界面。可惜鄙人目前还不会做这
种手工反编译资源的工作,创建资源这部分代码只好让它保持原样了。这
些代码在原程序中从地址401017开始到40122F结束。
对话框的回调过程位于原程序中地址401230到401296,其中处理了
WM_COMMAND,WM_INITDIALOG,WM_CLOSE三种消息。由于后两种消息的处
理比较常规,分析起来也很简单,这里就不详述了。我们的重点是放在
WM_COMMAND的处理上,这涉及到程序的核心算法,也就是下一小节将要叙
述的内容。 2. 注册算法分析
现在定位到对话框回调过程中的WM_COMMAND分支上: ===================================================
loc_40125A: ; CODE XREF: DialogFunc+Aj
cmp [ebp+uMsg], WM_COMMAND
jnz short loc_401280
cmp [ebp+wParam], ID_REGISTER ; 按下Register按钮
jnz short loc_401290
call _Detect_Int3_1 ; 检测过程401296中的Int3断点
call _Detect_Int3_2 ; 检测GetDlgItemTextA起始处的Int3断点
push [ebp+hDlg] ; hDlg
call sub_401296
jmp short loc_401290
=================================================== 其中_Detect_Int3_1和_Detect_Int3_2是自定义的名字,这是由于这两个
过程中采用检查代码字节是否为0CCh的方法检测Int3断点。如果在调试器
中对所检测区域内的位置下断,可能会立即跳到验证失败的分支。但是采
用OD的F4键“执行到此处”功能则可以轻易避开这一检测,因为这个功能
本质是基于SingleStep而不是断点中断。以下跟进过程sub_401296中: ===================================================
; Attributes: bp-based frame
; int __stdcall sub_401296(HWND hDlg)
sub_401296 proc near ; CODE XREF: DialogFunc+49p
; DATA XREF: _Detect_Int3_1o
hDlg = dword ptr 8
push ebp
mov ebp, esp
pusha
mov esi, offset loc_4012FE
push esi
push large dword ptr fs:0 ; uType
mov large fs:0, esp
push nMaxCharName ; nMaxCount
push offset szNameString ; lpString
push EDT_NAME ; nIDDlgItem
push [ebp+hDlg] ; hDlg
call GetDlgItemTextA ; Name取30字符
push nMaxCharPwd ; nMaxCount
push offset szPwdString ; lpString
push EDT_PASSWORD ; nIDDlgItem
push [ebp+hDlg] ; hDlg
call GetDlgItemTextA ; Password取20字符
call _1stTestOfPwd
push offset szTestTable ; "ZWATRQLCGHPSXYENVBJDFKMU"
call sub_4013B6
call sub_4014CE
push 0 ; nResult
push [ebp+hDlg] ; hDlg
call EndDialog
jmp short loc_401324
loc_4012FE: ; DATA XREF: sub_401296+4o
mov esp, [esp+8]
pop large dword ptr fs:0
add esp, 4
popa
push MB_OK
push offset aSehInAssembly ; "SEH in Assembly"
push offset aExceptionHasBe ; "Exception has been handled !!\r\nPress OK"...
push NULL ; hWnd
call MessageBoxA
jmp short locret_40132E
loc_401324: ; CODE XREF: sub_401296+66j
pop large dword ptr fs:0
add esp, 24h
locret_40132E: ; CODE XREF: sub_401296+8Aj
; sub_401296+8Cj
leave
retn 4
sub_401296 endp
=================================================== 这个过程包含了获取输入的用户名及Password、以及验证的整个流程,正
常情况下应该在调用EndDialog以后结束。还有一段SEH,由于有地方没看
懂,在还原的时候把它删掉了(关键在于按照SEH的设置,产生异常的时
候流程转到loc_4012FE处执行,但为什么产生异常时的[esp+8]中存储的
恰好就是SEH设置完毕的esp值呢?)。其中EDT_NAME,EDT_PASSWORD这些
常数的等值定义都是根据创建控件资源的代码猜出来的。由此得知输入的
用户名只取前30字符,而密码只取前20字符。
过程_1stTestOfPwd主要是对输入的密码格式作简单的前期合法性检
测,其代码如下: ===================================================
_1stTestOfPwd proc near ; CODE XREF: sub_401296+48p
xor eax, eax
mov ecx, 0
mov esi, offset szPwdString
mov al, [esi]
jmp short loc_401352
loc_401342: ; CODE XREF: _1stTestOfPwd+22j
movzx eax, al
cmp byte_403150[eax], 2 ; 实质是检测eax中是否大写字母
jnz short loc_401358 ; 不是则跳
inc ecx
mov al, [ecx+esi]
loc_401352: ; CODE XREF: _1stTestOfPwd+Ej
cmp al, 0 ; 已达串尾否
ja short loc_401342
jmp short loc_40135F
loc_401358: ; CODE XREF: _1stTestOfPwd+1Aj
mov LegalFlagOfPwd, 40h ; 置密码非法标志
loc_40135F: ; CODE XREF: _1stTestOfPwd+24j
mov esi, offset szNameString
xor ecx, ecx
mov eax, 1
xor edx, edx
mov byteChkSumName, 0
loc_401377: ; CODE XREF: _1stTestOfPwd+59j
mov ecx, 0
mov cl, [edx+esi]
cmp cl, 0
jz short loc_40138D
inc edx
add byte ptr byteChkSumName, cl
jmp short loc_401377
loc_40138D: ; CODE XREF: _1stTestOfPwd+50j
mov eax, byteChkSumName
mov ecx, 18h
cdq
idiv ecx
mov ChkSumNameMod24, dl
mov cl, LegalFlagOfPwd
cmp cl, 40h
jnz short loc_4013B0
jmp loc_4014F5 ; 如果Password中有非大写字母则为非法
loc_4013B0: ; CODE XREF: _1stTestOfPwd+77j
jmp loc_401480
loc_401480: ; CODE XREF: _1stTestOfPwd:loc_4013B0j
; DATA XREF: _1stTestOfPwd+155o
; .text:004014C7o
call sub_401510 ; 检测Password第二个字符是否为'E'
xor ebx, ebx
mov edi, offset loc_401480
sub edi, 60h
mov eax, 0DEh
xor eax, 12h ; 检测关键比较处的Int3断点
mov ecx, 59h
repne scasb
test ecx, ecx
jz short locret_4014A8
pop esi
xor esi, esi
push edi
jmp short loc_4014F5
locret_4014A8: ; CODE XREF: _1stTestOfPwd+16Ej
retn
=================================================== 这个过程有三个功能:第一,检验密码是否全由大写字母组成,以及
第二个字符是否为'E',过滤不满足这些条件的输入(地址403150起始的
地方有一个256字节的表,表中只有索引为65到90的字节等于2,正好是大
写字母ASCII码的范围);第二,计算出用户名字符串的字节校验和模24
的值(注意不是用户名各字符之和模24,要先模256再模24),该值存放
于变量ChkSumNameMod24中;第三,检测关键比较处的Int3断点。
前期检测通过后,接下来就是关键比较了。该段代码在过程
sub_4013B6中: ===================================================
sub_4013B6 proc near ; CODE XREF: sub_401296+52p
arg_0 = dword ptr 8
push ebp
mov ebp, esp
push offset szPwdString
call sub_401540 ; 求长度的过程
cmp eax, 0Ah
jnz loc_4014F5 ; Password必须为10个字符
mov esi, offset szPwdString
mov eax, 0
mov ebx, 0
xor ecx, ecx
jmp short loc_4013E5
loc_4013DF: ; CODE XREF: sub_4013B6+32j
mov cl, [eax+esi]
add ebx, ecx ; 将password前9字符之和累加放到ebx
inc eax
loc_4013E5: ; CODE XREF: sub_4013B6+27j
cmp eax, 9
jb short loc_4013DF
mov eax, ebx
mov ecx, 9
cdq
idiv ecx
mov SumPwdDiv9, eax
mov edi, [ebp+arg_0] ; edi---->"ZWATRQLCGHPSXYENVBJDFKMU"
mov dl, ChkSumNameMod24
mov al, dl
cmp al, 18h
jbe short loc_40140A
sub al, 18h
loc_40140A: ; CODE XREF: sub_4013B6+50j
mov byte_40304E, al
xor eax, eax
mov al, byte_40304E
mov ah, [eax+edi]
mov dh, [esi] ; esi(password)的第一个字符必须等于以chksumNameMod24做索引找到的字符
cmp ah, dh
jnz loc_4014F5
sub dh, 'A'
mov dh, dl ; 这一句是否打错?
mov ah, 0
mov byte_40304E, al
xor eax, eax
mov al, byte_40304E
add al, dl
cmp al, 18h
jbe short loc_40143E
sub al, 18h
loc_40143E: ; CODE XREF: sub_4013B6+84j
mov ecx, 2
mov ah, [eax+edi] ; esi(password)的第三个字符必须等于以chksumNameMod24*2做索引找到的字符
mov dh, [ecx+esi]
cmp ah, dh
jnz loc_4014F5
jmp short loc_401477
loc_401453: ; CODE XREF: sub_4013B6+C4j
mov byte_40304E, al
xor eax, eax
mov al, byte_40304E
sub dh, 'A' ; 取得此字母在正规字母表中的索引
mov dl, dh
inc ecx
add al, dl ; 加到刚才查表的索引
cmp al, 18h
jbe short loc_40146D
sub al, 18h
loc_40146D: ; CODE XREF: sub_4013B6+B3j
mov ah, [eax+edi]
mov dh, [ecx+esi] ; 继续实施比较
cmp ah, dh
jnz short loc_4014F5
loc_401477: ; CODE XREF: sub_4013B6+9Bj
cmp ecx, 8 ; 直到比较完password的前9个字符
jb short loc_401453
leave
retn 4
sub_4013B6 endp
=================================================== 根据这段程序,有效的password必须是10字符长。而确定password前9个
字符的算法可总结如下:
预先给定一个字符表szTestTable = "ZWATRQLCGHPSXYENVBJDFKMU"
根据用户名字节检验和模24的值a = ChkSumNameMod24
在该表中找到szTestTable[a]作为密码的第一个字符
密码的第二个字符必须为'E',如上一段所述。
a = (a + a) mod 24
找到szTestTable[a]作为密码的第三个字符
然后取得该字符在顺序字母表中的索引,加到a的现行值上,
作为新的索引(当然要模24)
也就是说:
a = (a + szTestTable[a] - 'A') mod 24
找到szTestTable[a]作为密码的第四个字符
a = (a + szTestTable[a] - 'A') mod 24
找到szTestTable[a]作为密码的第五个字符
……
依次类推直到确定前九个字符。
确定密码最后一个字符的代码在过程sub_4014CE中,兹从略。最后一个字
符等于前9个字符之和除以9的商。
例:假设用户名为PEDIY
计算byteChkSumName = ('P'+'E'+'D'+'I'+'Y') mod 256 = 123
ChkSumNameMod24 = 123 mod 24 = 3
TestTable[3] = 'T'
Password的第二个字符必须为'E'
3 + 3 = 6
TestTable[6] = 'L', 'L' - 'A' = 11, 6 + 11 = 17
TestTable[17] = 'B', 'B' - 'A' = 1, 17 + 1 = 18
TestTable[18] = 'J', 'J' - 'A' = 9, 18 + 9 - 24 = 3
TestTable[3] = 'T', 'T' - 'A' = 19, 3 + 19 = 22
TestTable[22] = 'M', 'M' - 'A' = 12, 22 + 12 - 24 = 10
TestTable[10] = 'P', 'P' - 'A' = 15, 10 + 15 - 24 = 1
TestTable[1] = 'W'
'T' + 'E' + 'L' + 'B' + 'J' + 'T' + 'M' + 'P' + 'W' = 697
697/9 = 77 = 'M'
password为TELBJTMPWM 至此可以还原出程序大部分功能的源代码。程序中对Int3断点的检测
涉及到具体机器码地址范围,不可能精确还原;而SEH的部分由于笔者水
平有限而不得不删掉了。在还原的代码中,把比较方式改成了明码,以便
书写生成真码的过程以及制作注册机。
上传文件内容:Noname6.asm 还原的MASM源码(含注册机条件汇编)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: