名字:
Triangle KeyGenMe
下载地址:
见附件
界面:

加壳情况:

定位注册算法位置:
下GetDlgItemTextA断点,返回到Alt+F9程序领空
输入的信息
aaaaaa
bbbbbb
分析算法:
004012B0 push 32 ; /Count = 32 (50.)
004012B2 push 0040315C ; |Buffer = KeyGenMe.0040315C
004012B7 push 0FA1 ; |ControlID = FA1 (4001.)
004012BC push dword ptr [ebp+8] ; |hWnd
004012BF call <jmp.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
004012C4 push 12 ; /Count = 12 (18.)
004012C6 push 004031D2 ; |Buffer = KeyGenMe.004031D2
004012CB push 0FA2 ; |ControlID = FA2 (4002.)
004012D0 push dword ptr [ebp+8] ; |hWnd
004012D3 call <jmp.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
004012D8 cmp eax, 10
004012DB jnz 00401371
获取账号密码,分别放在[0040315C]和[004031D2]
密码必须等于16个字符,所以要将输入的信息更换如下
aaaaaa
bbbbbbbbbbbbbbbb
004012E1 push 0040315C ; ASCII "aaaaaa"
004012E6 call 004013EB
{
004013EB push ebp
004013EC mov ebp, esp
004013EE pushad
004013EF push dword ptr [ebp+8]
004013F2 call 004010F3 ; 计算用户名的长度
004013F7 mov ecx, 63
004013FC sub ecx, eax ; 63-6 in this case
004013FE mov edi, dword ptr [ebp+8]
00401401 add edi, eax
00401403 inc edi
00401404 /stos byte ptr es:[edi]
00401405 |inc al
00401407 \loopd short 00401404 ; 这个循环初始化一个表
00401409 finit ; 初始化浮点数寄存器
0040140B push dword ptr [ebp+8]
0040140E call 004010F3 ; 计算用户名的长度
00401413 mov dword ptr [4031E3], eax
00401418 fild dword ptr [4031E3] ; st(0)=用户名长度=6
0040141E fst qword ptr [4031E7] ; [4031E7]=st(0)
00401424 fst qword ptr [4031F7] ; [4031F7]=st(0)
0040142A fstp qword ptr [4031FF] ; [4031FF]=st(0) 注意fstp会出栈
00401430 mov eax, 2
00401435 mov dword ptr [4031E3], eax
0040143A fild dword ptr [4031E3]
00401440 fstp qword ptr [40323F] ; 即[40323F]=[4031E3]=eax=2
00401446 xor eax, eax
00401448 mov dword ptr [4031E3], eax
0040144D fild dword ptr [4031E3]
00401453 fst qword ptr [403237] ; [403237]=eax=0
00401459 fstp qword ptr [40324F] ; [40324F]=eax=0 注意fstp会出栈
0040145F xor ecx, ecx
00401461 mov ebx, dword ptr [ebp+8] ; ebx->用户名 "aaaaaa"
00401464 /cmp ecx, 63
00401467 |jnb 00401556 ; 不小于63
0040146D |movzx eax, byte ptr [ebx]
00401470 |mov dword ptr [4031E3], eax
00401475 |fild dword ptr [4031E3]
0040147B |fsqrt
0040147D |fst qword ptr [4031EF] ; [4031EF]=用户名第一个字母开平方
00401483 |fstp qword ptr [403217] ; [403217]=用户名第一个字母开平方 出栈
00401489 |inc ebx
0040148A |movzx eax, byte ptr [ebx]
0040148D |mov dword ptr [4031E3], eax
00401492 |fild dword ptr [4031E3]
00401498 |fsqrt
0040149A |fst qword ptr [403207] ; [403207]=用户名第二个字母开平方
004014A0 |fstp qword ptr [40320F] ; [40320F]=用户名第二个字母开平方
004014A6 |fld qword ptr [403217] ; 第一个字母开平方
004014AC |fld qword ptr [4031F7] ; 用户名长度
004014B2 |faddp st(1), st ; 两个加起来放到st(0) [随便起个名字:长方]
004014B4 |fld qword ptr [40320F]
004014BA |fld qword ptr [4031EF]
004014C0 |fsubp st(1), st ; 1,2个字母的平方相减
004014C2 |fmulp st(1), st ; 长方(就是现在的st(1))*st(0)
004014C4 |fdiv qword ptr [40323F] ; 除以2
004014CA |fstp qword ptr [403247] ; [403247]=st(0)/2
004014D0 |fld qword ptr [403207] ; 第二个字母平方
004014D6 |fld qword ptr [403217] ; 第一个字母平方
004014DC |faddp st(1), st ; 加起来
004014DE |fld qword ptr [4031FF] ; 用户名长度
004014E4 |fld qword ptr [40320F] ; 第二个字母开方
004014EA |fsubp st(1), st
004014EC |fmulp st(1), st ; 相减再相乘
004014EE |fdiv qword ptr [40323F] ; 除以2
004014F4 |fld qword ptr [403247] ; 装入之前算的[403247]
004014FA |fadd st, st(1)
004014FC |fstp qword ptr [403247] ; 加起来又放回[403247]
00401502 |ffree st ; invalidate st(0)
00401504 |fld qword ptr [403207] ; 第二个字母平方
0040150A |fld qword ptr [4031F7] ; 用户名长度
00401510 |faddp st(1), st ; 加起来
00401512 |fld qword ptr [4031FF] ; 用户名长度
00401518 |fld qword ptr [4031EF] ; 第一个字母
0040151E |fsubp st(1), st
00401520 |fmulp st(1), st
00401522 |fdiv qword ptr [40323F] ; 相减,相乘 再除以2
00401528 |fld qword ptr [403247] ; 装入之前算的[403247]
0040152E |fsub st, st(1) ; 相减
00401530 |fcom qword ptr [403237] ; st(0)跟[403237]比较,影响对应标记位
00401536 |fstsw ax ; 复制浮点标记寄存器(应该是这么叫吧)到ax
00401538 |test ax, 100
0040153C |je short 00401540
0040153E |fchs ; 不等于则取相反值
00401540 |fld qword ptr [40324F] ; 载入0
00401546 |faddp st(1), st
00401548 |fstp qword ptr [40324F] ; 相加放回[40324F],可以看出这个call主要就是计算这个地址放的东西啦
0040154E |ffree st
00401550 |inc ecx
00401551 \jmp 00401464
00401556 popad
00401557 leave
00401558 retn 4
}
吓我一跳,都是浮点操作的汇编指令 照着书看就行了。。
004012EB push 1
004012ED push 8
004012EF push 0040324F
004012F4 push 004031C1 ; ASCII "A376957E38A17340"
004012F9 call 004010B1
{
004010B1 push ebp ; 很简单,就是查表,弄一条字符串出来放到4031C1
004010B2 mov ebp, esp
004010B4 pushad
004010B5 xor eax, eax
004010B7 mov ecx, dword ptr [ebp+10]
004010BA mov ebx, dword ptr [ebp+8]
004010BD mov edx, dword ptr [ebp+C]
004010C0 cmp dword ptr [ebp+14], 1
004010C4 jnz short 004010CD
004010C6 mov esi, 00403000 ; ASCII "0123456789ABCDEF0123456789abcdef+-Need at least "
004010CB jmp short 004010D2
004010CD mov esi, 00403010 ; ASCII "0123456789abcdef+-Need at least "
004010D2 /mov al, byte ptr [edx]
004010D4 |shr al, 4
004010D7 |mov al, byte ptr [eax+esi]
004010DA |mov byte ptr [ebx], al
004010DC |inc ebx
004010DD |mov al, byte ptr [edx]
004010DF |and al, 0F
004010E1 |mov al, byte ptr [eax+esi]
004010E4 |mov byte ptr [ebx], al
004010E6 |inc ebx
004010E7 |inc edx
004010E8 \loopd short 004010D2
004010EA xor al, al
004010EC mov byte ptr [ebx], al
004010EE popad
004010EF leave
004010F0 retn 10
}
004012FE push 004031D2 ; ASCII "bbbbbbbbbbbbbbbb"
00401303 push 004031C1 ; ASCII "A376957E38A17340"
00401308 call 00401378 ; 这个是关键call
{
00401378 push ebp ; 比较神奇的校验用户和密码的方法。。
00401379 mov ebp, esp
0040137B pushad
0040137C mov ecx, 10
00401381 xor edx, edx
00401383 xor ebx, ebx
00401385 mov esi, dword ptr [ebp+8] ; User Magic String
00401388 mov edi, dword ptr [ebp+C] ; password
0040138B mov ah, byte ptr [ebx+esi]
0040138E mov al, byte ptr [ebx+edi]
00401391 cmp ah, 39
00401394 jnz short 0040139A
00401396 jmp short 004013C7
00401398 jmp short 004013C5
0040139A cmp ah, 46
0040139D jnz short 004013A3
0040139F jmp short 004013C7 ; 异或重来
004013A1 jmp short 004013C5 ; 减一异或重来
004013A3 cmp al, 30
004013A5 jb short 004013C3
004013A7 cmp al, 39
004013A9 ja short 004013AF
004013AB jmp short 004013C5 ; 减一异或重来
004013AD jmp short 004013C5 ; 减一异或重来
004013AF cmp al, 41
004013B1 jb short 004013BF
004013B3 cmp al, 46
004013B5 ja short 004013BB
004013B7 jmp short 004013C5 ; 减一异或重来
004013B9 jmp short 004013C5 ; 减一异或重来
004013BB jmp short 004013D7 ; 错误
004013BD jmp short 004013C5 ; 减一异或重来
004013BF jmp short 004013D7 ; 错误
004013C1 jmp short 004013C5 ; 减一异或重来
004013C3 jmp short 004013D7 ; 错误
004013C5 dec al
004013C7 xor al, ah
004013C9 jnz short 004013CC ; al-1必须等于ah
004013CB inc edx
004013CC inc ebx
004013CD dec ecx
004013CE jnz short 0040138B
004013D0 cmp edx, 10
004013D3 jnz short 004013D7 ; edx必须等于16
004013D5 jmp short 004013E1 ; 正确
004013D7 popad
004013D8 mov eax, 0
004013DD leave
004013DE retn 8
004013E1 popad
004013E2 mov eax, 1
004013E7 leave
004013E8 retn 8
}
重上面看下来没看出什么端倪,但是从最后面回溯可以发现edx必须等于16(十进制),进而推出al-1必须等于ah,所以我们根据A376957E38A17340,把密码改成这样:B487@68F49B28451
即每个字母的ASCII都减1。
现在再运行,发现到最后edx竟然是F,就是还差一个. 调试发现是因为”A376957E38A17340”中第五个字符’9’使得地址00401396处的jmp得以执行,跳过了dec al这步,所以,当”A376957E38A17340”中字符’9’出现的时候,密码处也应该有一个相同的字符’9’
最终密码为B487968F49B28451
0040130D cmp eax, 1
00401310 jnz short 00401328
00401312 push 40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
00401314 push 004030D3 ; |Title = "Goodboy"
00401319 push 0040306C ; |Text = "Yes! You made it! I hope you didn't patch anything and you'll write a nice tutorial for crackmes.de ;)"
0040131E push dword ptr [ebp+8] ; |hOwner
00401321 call <jmp.&user32.MessageBoxA> ; \MessageBoxA
这里是比较了,很简单。
注册机编写:
将这两个CALL(call 004013EB和call 004010B1)摘下来,再自己写一个函数将生成的字符串的ASCII全部减1(当然了,遇到字符’9’就不需要减1了)
CrackMe流程总结:
经验积累:
如果从上到下无法得出什么结论,可以从后面向前面回溯得出正确密码所需的特征
备注:后来看了下老外的solution,其实遇到字符'F'的时候ASCII也会减一
[培训]科锐逆向工程师培训第53期2025年7月8日开班!