【文章标题】: Thigo's keygenme简单分析
【下载地址】: 自己搜索下载
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
呵呵,这个keygenMe有点意思.
让我们开始吧,运行一下CrackMe,有如下提示信息:
You should put a file named key.dat in this dir...
哦,听作者的话吧,把CrackMe关掉,然后在同一个目录下建一个名字叫key.dat的文件
里面随便输点东西,我写的信息是字符串"12345678".
再次打开CrackMe,看到提示信息变成了:
Are u sure it's a good keyfile ??
呀,还是错的呢.
W32Dasm反汇编,看看有没有什么有用的信息.
W32DASM String Data References
------------
" ?"
""
""
"?"
"]_^[?]?L$髁"
"Are u sure it's a good keyfile "
"key.dat"
"U??-"
"You should put a file named key.dat "
"竣??NK4?8?2?8?5C泪B?LE?G?H:?8?0R?
------------
似乎都是提示keyfile的信息,没成功的提示信息.
双击"Are u sure it's a good keyfile "这一行字符串,来到这里
:004011EA 3DF48B2A00 cmp eax, 002A8BF4
:004011EF 7407 je 004011F8
* Possible StringData Ref from Data Obj ->"Are u sure it's a good keyfile "
->"??"
|
:004011F1 68E4514000 push 004051E4
:004011F6 EB60 jmp 00401258
一直往上看
:00401142 50 push eax
:00401143 56 push esi
* Reference To: KERNEL32.ReadFile, Ord:0218h
|
:00401144 FF1518404000 call dword ptr ds:[<&KERNEL32.ReadFile>] ; 读取keyfile文件
:0040114A 56 push esi
* Reference To: KERNEL32.CloseHandle, Ord:001Bh
|
:0040114B FF151C404000 call dword ptr ds:[<&KERNEL32.CloseHandle>] ; 关闭文件句炳
:00401151 8B4DFC mov ecx, dword ptr [ebp-04]
:00401154 33C0 xor eax, eax
:00401156 3BCB cmp ecx, ebx
:00401158 760C jbe 00401166
就从这里下手吧,用OD载入CrackME,在00401144处下个断点,Let's go
0040115A 308C05 A8FEFFFF xor byte ptr ss:[ebp+eax-158],cl ; ---------
00401161 40 inc eax ; key.dat内容逐字节异或文件的长度
00401162 3BC1 cmp eax,ecx
00401164 ^ 72 F4 jb short crackme.0040115A ; ---------
00401166 80B5 A8FEFFFF 54 xor byte ptr ss:[ebp-158],54 ; 文件第1字节XOR 0x54
0040116D 80B5 A9FEFFFF 4D xor byte ptr ss:[ebp-157],4D ; 文件第2字节XOR 0x4D
00401174 80B5 AAFEFFFF 47 xor byte ptr ss:[ebp-156],47 ; 文件第3字节XOR 0x47
0040117B 3BCF cmp ecx,edi ; /
0040117D 8BF7 mov esi,edi ; key.dat文件长度<=3则跳转
0040117F 76 27 jbe short crackme.004011A8 ; \
00401181 8A95 A8FEFFFF mov dl,byte ptr ss:[ebp-158] ; /
00401187 8D8435 A9FEFFFF lea eax,dword ptr ss:[ebp+esi-157] ; n为循环计数器,n > 0
0040118E 03F7 add esi,edi
00401190 3050 FF xor byte ptr ds:[eax-1],dl ; 第1位XOR第3n+1位->第3n+1位
00401193 8A95 A9FEFFFF mov dl,byte ptr ss:[ebp-157]
00401199 3010 xor byte ptr ds:[eax],dl ; 第2位XOR第3n+2位->第3n+2位
0040119B 8A95 AAFEFFFF mov dl,byte ptr ss:[ebp-156]
004011A1 3050 01 xor byte ptr ds:[eax+1],dl ; 第3位XOR第3(n+1)位->第3(n+1)位
004011A4 3BF1 cmp esi,ecx ; 循环直到3(n+1) > length
004011A6 ^ 72 D9 jb short crackme.00401181 ; \
004011A8 BE 30504000 mov esi,crackme.00405030 ; 要处理的数据开始地址(0405030)放到ESI
004011AD 889C0D A8FEFFFF mov byte ptr ss:[ebp+ecx-158],bl ; 把0写入上面最后一次循环的3(n+1)位
004011B4 33FF xor edi,edi
004011B6 8BC6 mov eax,esi
004011B8 8A8C3D A8FEFFFF mov cl,byte ptr ss:[ebp+edi-158] ; /
004011BF 3008 xor byte ptr ds:[eax],cl
004011C1 40 inc eax ; 用前面对key.dat内容变换后得到的数据
004011C2 47 inc edi
004011C3 83FF 03 cmp edi,3 ; 的前3位循环XOR从地址0403050开始的数据,
004011C6 75 02 jnz short crackme.004011CA
004011C8 33FF xor edi,edi ; 直到碰到要处理数据段的结束标志0xFF
004011CA 8038 FF cmp byte ptr ds:[eax],0FF
004011CD ^ 75 E9 jnz short crackme.004011B8 ; \
004011CF 0FB605 32504000 movzx eax,byte ptr ds:[405032] ; /
004011D6 0FB60D 31504000 movzx ecx,byte ptr ds:[405031]
004011DD 0FAFC1 imul eax,ecx
004011E0 0FB60D 30504000 movzx ecx,byte ptr ds:[405030] ; 0403050开始3位字节的乘积=0x2A8BF4 ?
004011E7 0FAFC1 imul eax,ecx
004011EA 3D F48B2A00 cmp eax,2A8BF4 ; 如不相等则显示下面的错误信息
004011EF 74 07 je short crackme.004011F8 ; \
004011F1 68 E4514000 push crackme.004051E4 ; ASCII "Are u sure it's a good keyfile ??"
004011F6 EB 60 jmp short crackme.00401258
004011F8 8A8D A8FEFFFF mov cl,byte ptr ss:[ebp-158]
004011FE 33C0 xor eax,eax
00401200 884C05 D0 mov byte ptr ss:[ebp+eax-30],cl ; /
00401204 8A8C05 A9FEFFFF mov cl,byte ptr ss:[ebp+eax-157]
0040120B 40 inc eax ; 在处理后的key.dat中查找第一个空格字符(0x20)
0040120C 80F9 20 cmp cl,20
0040120F ^ 75 EF jnz short crackme.00401200 ; \
00401211 885C05 D0 mov byte ptr ss:[ebp+eax-30],bl ;
00401215 40 inc eax
00401216 33D2 xor edx,edx
00401218 8D8C05 A8FEFFFF lea ecx,dword ptr ss:[ebp+eax-158]
0040121F 8A8405 A8FEFFFF mov al,byte ptr ss:[ebp+eax-158]
00401226 41 inc ecx ; /---------A-------
00401227 884415 A8 mov byte ptr ss:[ebp+edx-58],al ; 将key.dat文件中第一个空格字符
0040122B 42 inc edx ; 后1位起一直到末尾的数据
0040122C 8A01 mov al,byte ptr ds:[ecx] ; COPY到某处内存
0040122E 3AC3 cmp al,bl ; 这段数据最后要用到
00401230 ^ 75 F4 jnz short crackme.00401226 ; \---------A-------
00401232 8D45 F8 lea eax,dword ptr ss:[ebp-8]
00401235 885C15 A8 mov byte ptr ss:[ebp+edx-58],bl
00401239 50 push eax
0040123A 6A 40 push 40
0040123C 68 7F010000 push 17F
00401241 56 push esi
00401242 FF15 68404000 call dword ptr ds:[<&KERNEL32.VirtualProte>; kernel32.VirtualProtect
0012FA88 00401248 /CALL 到 VirtualProtect 来自 crackme.00401242
0012FA8C 00405030 |Address = crackme.00405030
0012FA90 0000017F |Size = 17F (383.)
0012FA94 00000040 |NewProtect = PAGE_EXECUTE_READWRITE
0012FA98 0012FBF8 \pOldProtect = 0012FBF8
修改地址为0403050,长度为0x17F内存区域的属性,从这里可以猜测到前面做变换的使用的数据段的长度
00401248 8D45 D0 lea eax,dword ptr ss:[ebp-30]
0040124B 50 push eax
0040124C 8D45 A8 lea eax,dword ptr ss:[ebp-58]
0040124F 50 push eax
00401250 FFD6 call esi ; /*整个CrackMe的精彩部分*/ESI=403050
00401252 59 pop ecx
00401253 8D45 D0 lea eax,dword ptr ss:[ebp-30]
00401256 59 pop ecx
00401257 50 push eax
00401258 68 EC030000 push 3EC
0040125D FF75 08 push dword ptr ss:[ebp+8]
00401260 FF15 C0404000 call dword ptr ds:[<&USER32.SetDlgItemText>; 根据CALL ESI的结果设置提示信息
00401266 5F pop edi
00401267 5E pop esi
00401268 5B pop ebx
00401269 C9 leave
我是用内容为字符串"12345678"的keyfile,通过把前面的两处跳转
1:
004011EA 3D F48B2A00 cmp eax,2A8BF4 ;
004011EF 74 07 je short crackme.004011F8 ;这里要修改
2:
0040120C 80F9 20 cmp cl,20
0040120F ^ 75 EF jnz short crackme.00401200 ; 这里要修改
修改后来到这里的:
程序运行到这里的时候ESI=0403050
00401250 FFD6 call esi
进入CALL
00405030 - 73 C8 jnb short crackme.00404FFA
00405032 DE77 84 fidiv word ptr ds:[edi-7C]
00405035 ^ 77 DA ja short crackme.00405011
00405037 43 inc ebx
00405038 3226 xor ah,byte ptr ds:[esi]
0040503A 43 inc ebx
0040503B B9 634FF426 mov ecx,26F44F63
......
......
一大段的乱码,呵呵,这是怎么回事呀?
这里正是整个CrackMe最精彩的部分,慢慢分析一下:
地址0403050开始的这段数据在我们前面的分析中曾出现两次
1:
004011B8 8A8C3D A8FEFFFF mov cl,byte ptr ss:[ebp+edi-158] ; /
004011BF 3008 xor byte ptr ds:[eax],cl
004011C1 40 inc eax ; 用前面对key.dat内容变换后得到的数据
004011C2 47 inc edi
004011C3 83FF 03 cmp edi,3 ; 的前3位循环XOR从地址0403050开始的数据,
004011C6 75 02 jnz short crackme.004011CA
004011C8 33FF xor edi,edi ; 直到碰到要处理数据段的结束标志0xFF
004011CA 8038 FF cmp byte ptr ds:[eax],0FF
004011CD ^ 75 E9 jnz short crackme.004011B8 ; \
2:
004011E0 0FB60D 30504000 movzx ecx,byte ptr ds:[405030] ; 0403050开始3位字节的乘积=0x2A8BF4 ?
004011E7 0FAFC1 imul eax,ecx
004011EA 3D F48B2A00 cmp eax,2A8BF4 ; 如不相等则显示下面的错误信息
004011EF 74 07 je short crackme.004011F8 ; \
004011F1 68 E4514000 push crackme.004051E4 ; ASCII "Are u sure it's a good keyfile ??"
而现在它又是一段CALL的代码,这里面一定有作者的某种提示.
让我们看看一般的CALL开头是什么样的:
kernel32.GetModuleHandleA
7C80B529 > 8BFF mov edi,edi
7C80B52B 55 push ebp ; ******
7C80B52C 8BEC mov ebp,esp ; ******
7C80B52E 837D 08 00 cmp dword ptr ss:[ebp+8],0
7C80B532 74 18 je short kernel32.7C80B54C
7C80B534 FF75 08 push dword ptr ss:[ebp+8]
7C80B537 E8 682D0000 call kernel32.7C80E2A4
7C80B53C 85C0 test eax,eax
7C80B53E 74 08 je short kernel32.7C80B548
7C80B540 FF70 04 push dword ptr ds:[eax+4]
7C80B543 E8 F4300000 call kernel32.GetModuleHandleW
7C80B548 5D pop ebp
7C80B549 C2 0400 retn 4
kernel32.TerminateProcess
7C801E16 > 8BFF mov edi,edi
7C801E18 55 push ebp ; ******
7C801E19 8BEC mov ebp,esp ; ******
7C801E1B 837D 08 00 cmp dword ptr ss:[ebp+8],0
7C801E1F 75 09 jnz short kernel32.7C801E2A
7C801E21 6A 06 push 6
7C801E23 E8 98740000 call kernel32.7C8092C0
7C801E28 EB 1B jmp short kernel32.7C801E45
7C801E2A FF75 0C push dword ptr ss:[ebp+C]
7C801E2D FF75 08 push dword ptr ss:[ebp+8]
7C801E30 FF15 FC13807C call dword ptr ds:[<&ntdll.NtTerminateProc>; ntdll.ZwTerminateProcess
7C801E36 85C0 test eax,eax
7C801E38 7C 05 jl short kernel32.7C801E3F
7C801E3A 33C0 xor eax,eax
7C801E3C 40 inc eax
7C801E3D EB 08 jmp short kernel32.7C801E47
7C801E3F 50 push eax
7C801E40 E8 36750000 call kernel32.7C80937B
7C801E45 33C0 xor eax,eax
7C801E47 5D pop ebp
7C801E48 C2 0800 retn 8
注意打了'*'号的语句
0x55 * 0x8B * 0xEC = 0x2A8BF4
结合:
1:
004011B8 8A8C3D A8FEFFFF mov cl,byte ptr ss:[ebp+edi-158] ; /
004011BF 3008 xor byte ptr ds:[eax],cl
004011C1 40 inc eax ; 用前面对key.dat内容变换后得到的数据
004011C2 47 inc edi
004011C3 83FF 03 cmp edi,3 ; 的前3位循环XOR从地址0403050开始的数据,
004011C6 75 02 jnz short crackme.004011CA
004011C8 33FF xor edi,edi ; 直到碰到要处理数据段的结束标志0xFF
004011CA 8038 FF cmp byte ptr ds:[eax],0FF
004011CD ^ 75 E9 jnz short crackme.004011B8 ; \
2:
004011E0 0FB60D 30504000 movzx ecx,byte ptr ds:[405030] ; 0403050开始3位字节的乘积=0x2A8BF4 ?
004011E7 0FAFC1 imul eax,ecx
004011EA 3D F48B2A00 cmp eax,2A8BF4 ; 如不相等则显示下面的错误信息
004011EF 74 07 je short crackme.004011F8 ; \
004011F1 68 E4514000 push crackme.004051E4 ; ASCII "Are u sure it's a good keyfile ??"
原来作者要告诉我们的就是:
地址0403050开始的这段数据最后变换得到的结果应该是:
7C801E18 55 push ebp
7C801E19 8BEC mov ebp,esp
这样的形式.
因为异或运算满足:
A XOR B = C
A XOR C = B
从而可以推导出前面key.dat变换后得到的前3位的结果,
0403050处的原始数据(程序内置数据):
---------------------------
00405030 1E BF A2 1A F3 0B B7 34 4E 4B 34 C5 0E 38 88 4B 竣??NK4?8?
00405040 32 C5 06 38 88 0A 35 43 C0 61 42 8D 76 4C 45 BF 2?8?5C泪B?LE
.....
.....
00405190 35 C7 0E C8 C5 06 3C 4D 06 C8 41 F5 25 CB 99 41 5?扰<M攘??A
004051A0 97 C0 71 42 48 71 B2 8D 74 41 4B BF AB 16 F7 FF ?qBHq?tAK揩?
0x1E XOR 0x55 = 0x4B
0xBF XOR 0x8B = 0x34
0xA2 XOR 0xEC = 0x4E
把得到的结果代替
004011B8 8A8C3D A8FEFFFF mov cl,byte ptr ss:[ebp+edi-158]
处的key.dat数据,就可以把0403050处的原始数据还原成正确的指令代码了:
还原后的指令:
00405030 55 push ebp
00405031 8BEC mov ebp,esp
00405033 51 push ecx
00405034 C745 FC 00000000 mov dword ptr ss:[ebp-4],0
0040503B 8B45 0C mov eax,dword ptr ss:[ebp+C]
0040503E C600 06 mov byte ptr ds:[eax],6
......
......
前面一大段的指令做的只是很简单的变换:
把固定的数据还原成以'\0'结尾的字符串:
registed to:
0040517A 8B45 0C mov eax,dword ptr ss:[ebp+C] ; /
0040517D 0345 FC add eax,dword ptr ss:[ebp-4]
00405180 8B4D 08 mov ecx,dword ptr ss:[ebp+8] ; 把前面标记为A出得到的数据
00405183 034D FC add ecx,dword ptr ss:[ebp-4]
00405186 8A11 mov dl,byte ptr ds:[ecx]
00405188 8850 0F mov byte ptr ds:[eax+F],dl
0040518B 8B45 FC mov eax,dword ptr ss:[ebp-4] ; 连接在字符串"registed to:"后面
0040518E 83C0 01 add eax,1
00405191 8945 FC mov dword ptr ss:[ebp-4],eax
00405194 8B4D 08 mov ecx,dword ptr ss:[ebp+8]
00405197 034D FC add ecx,dword ptr ss:[ebp-4] ; 得到registed to:XXXXX形势的字符串
0040519A 0FBE11 movsx edx,byte ptr ds:[ecx]
0040519D 85D2 test edx,edx ; 做为提示信息
0040519F ^ 75 D9 jnz short crackme.0040517A ; \
这个CrackMe总的来说还是比较简单的,因为这个CrackMe本来就是for newbies,专门为我们菜鸟写的嘛:).
算法大体过程如上所叙,根据用户名逆推回去就可以得到正确的keyFile了,说的不清楚的部分,请参看注册机代码(C语言).
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
string userName;
string tmp;
userName.push_back(0x4B ^ 0x54);
userName.push_back(0x34 ^ 0x4D);
userName.push_back(0x4E ^ 0x47);
userName.push_back(0x4B ^ 0x20);
userName.push_back(0x34 ^ 0x20);
userName.push_back(0x4E ^ 0x20);
cout << "Please Enter your name:" << endl;
cin >> tmp;
userName += tmp;
int len = userName.length();
if((userName.length() % 3)!= 0)
{
int i =3 * (userName.length() / 3 + 1);
for(; i > len; i--)
{
userName.push_back(0x0);
}
}
for(int i = 2;i < userName.length() / 3; i ++)
{
userName[i * 3] ^= 0x4B;
userName[i * 3 + 1] ^= 0x34;
userName[i *3 + 2] ^= 0x4E;
}
for(int i = 0; i < userName.length(); i++)
{
userName[i] ^= userName.length();
}
ofstream outFile;
outFile.open("key.dat",ios_base::out | ios_base::binary);
outFile << userName;
outFile.close();
system("pause");
return 0;
}
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2006年08月10日 12:01:57
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: