一个crackme的简单算法分析
o(∩_∩)o...首先声明一下,这是本人第一个破解文章,曾经在一蓑烟雨论坛上发表过,这里发表的与之有少量的改动。o(∩_∩)o...
peid检查,无壳,Microsoft Visual C++ 6.0程序。
运行程序,随便输入注册信息,比如asdf,12345,点击注册,弹出出错对话框Registration fail。有出错提示,真是太好了。(没有提示的话,断点不好下啊)
OD载入,选择 插件->字符串参考->Find ASCII,再找到Registration fail。双击就到达了弹出出错提示的位置。向上找到关键call和关键跳。
00401064 . E8 C7010000 call 00401230 ; 关键call。在这里下断点。
00401069 . 85C0 test eax, eax ; 检验eax是否等于0。等于0则注册成功。
0040106B . 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
0040106D . 68 80504000 push 00405080 ; |ncrackme
00401072 . 75 1B jnz short 0040108F ; |关键跳。要爆破就在这里动手了。
00401074 . A1 B8564000 mov eax, dword ptr [4056B8] ; |
00401079 . 68 64504000 push 00405064 ; |registration successful.
0040107E . 50 push eax ; |hOwner => NULL
0040107F . FF15 C0404000 call dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA
00401085 . E8 A6020000 call 00401330
0040108A . 33C0 xor eax, eax
0040108C . C2 1000 retn 10
0040108F > 8B0D B8564000 mov ecx, dword ptr [4056B8] ; |
00401095 . 68 50504000 push 00405050 ; |registration fail.
0040109A . 51 push ecx ; |hOwner => NULL
0040109B . FF15 C0404000 call dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA
在00401064处按下F2下断点。
F9运行程序,00401064处程序暂停,按F7跟进去。然后F8单步执行,
00401245 |. 6A 10 push 10 ; /Count = 10 (16.)
00401247 |. 50 push eax ; |Buffer
00401248 |. 68 E8030000 push 3E8 ; |ControlID = 3E8 (1000.)
0040124D |. 51 push ecx ; |hWnd => NULL
0040124E |. 33DB xor ebx, ebx ; |
00401250 |. FFD6 call esi ; \GetDlgItemTextA
00401252 |. 83F8 03 cmp eax, 3 ; eax=用户名长度(字符数)
00401255 |. 73 0B jnb short 00401262 ; eax>=3,否则eax=1,然后返回,注册失败了。
00401257 |. 5E pop esi
00401258 |. B8 01000000 mov eax, 1
0040125D |. 5B pop ebx
0040125E |. 83C4 30 add esp, 30
00401261 |. C3 retn
00401262 |> A1 BC564000 mov eax, dword ptr [4056BC]
继续跟踪,
00401276 |. 0FBE4424 08 movsx eax, byte ptr [esp+8] ; 字节name[0]
0040127B |. 0FBE4C24 09 movsx ecx, byte ptr [esp+9] ; 字节name[1]
00401280 |. 99 cdq
00401281 |. F7F9 idiv ecx
00401283 |. 8BCA mov ecx, edx ; ecx=name[1]%name[0]
00401285 |. 83C8 FF or eax, FFFFFFFF
00401288 |. 0FBE5424 0A movsx edx, byte ptr [esp+A] ; 字节name[2]
0040128D |. 0FAFCA imul ecx, edx ; ecx=(name[1]%name[0])*name[2]
00401290 |. 41 inc ecx
00401291 |. 33D2 xor edx, edx
00401293 |. F7F1 div ecx ; eax=0xffffffff/((name[1]%name[0])*name[2]+1)
00401295 |. 50 push eax
00401296 |. E8 A5000000 call 00401340 ; 将eax存入地址4050ac,实际上设置了rand函数的种子。
继续,
0040129E |. 33F6 xor esi, esi
004012A0 |> /E8 A5000000 /call 0040134A ;跟进去,可以知道此处调用的就是rand函数。
004012A5 |. |99 |cdq
004012A6 |. |B9 1A000000 |mov ecx, 1A
004012AB |. |F7F9 |idiv ecx
004012AD |. |80C2 41 |add dl, 41
004012B0 |. |885434 18 |mov byte ptr [esp+esi+18], dl ; 存入(eax % 26) + 'A'
004012B4 |. |46 |inc esi
004012B5 |. |83FE 0F |cmp esi, 0F ; 循环15次
004012B8 |.^\72 E6 \jb short 004012A0
快到核心的地方了。继续努力。
004012CD |> /8A4434 0C /mov al, byte ptr [esp+esi+C] ; 用户名的某个字节
004012D1 |. |C0F8 05 |sar al, 5 ; eax=name[i]>>5
004012D4 |. |0FBEC0 |movsx eax, al
004012D7 |. |8D1480 |lea edx, dword ptr [eax+eax*4]
004012DA |. |8D04D0 |lea eax, dword ptr [eax+edx*8]
004012DD |. |8D0440 |lea eax, dword ptr [eax+eax*2] ; eax=eax*123(Dec)
004012E0 |. |85C0 |test eax, eax
004012E2 |. |7E 0A |jle short 004012EE
004012E4 |. |8BF8 |mov edi, eax ; edi=(name[i]>>5)*123
004012E6 |> |E8 5F000000 |/call 0040134A ; 调用rand函数
004012EB |. |4F ||dec edi
004012EC |.^|75 F8 |\jnz short 004012E6 ; 循环edi次
004012EE |> |E8 57000000 |call 0040134A ; 调用rand函数
004012F3 |. |99 |cdq
004012F4 |. |B9 1A000000 |mov ecx, 1A
004012F9 |. |8D7C24 0C |lea edi, dword ptr [esp+C] ; edi指向用户名
004012FD |. |F7F9 |idiv ecx
004012FF |. |0FBE4C34 2C |movsx ecx, byte ptr [esp+esi+2C] ; 序列号的某个字节
00401304 |. |80C2 41 |add dl, 41 ; dl=(eax % 26) + 'A'
00401307 |. |0FBEC2 |movsx eax, dl ; eax=(eax % 26) + 'A'
0040130A |. |2BC1 |sub eax, ecx ; 只有eax=ecx,才可能注册成功。
0040130C |. |885434 1C |mov byte ptr [esp+esi+1C], dl
00401310 |. |99 |cdq ; edx=0
00401311 |. |33C2 |xor eax, edx
00401313 |. |83C9 FF |or ecx, FFFFFFFF
00401316 |. |2BC2 |sub eax, edx
00401318 |. |03D8 |add ebx, eax ; ebx 累加
0040131A |. |33C0 |xor eax, eax
0040131C |. |46 |inc esi
0040131D |. |F2:AE |repne scas byte ptr es:[edi]
0040131F |. |F7D1 |not ecx
00401321 |. |49 |dec ecx
00401322 |. |3BF1 |cmp esi, ecx
00401324 |.^\72 A7 \jb short 004012CD
00401326 |> \5F pop edi
00401327 |. 8BC3 mov eax, ebx
00401329 |. 5E pop esi
0040132A |. 5B pop ebx
0040132B |. 83C4 30 add esp, 30
0040132E \. C3 retn
这里解释一下“只有eax=ecx,才可能注册成功”:关键call返回后,只有eax=0才会注册成功。而
00401327 |. 8BC3 mov eax, ebx
ebx是累加而得。因此每次累加值都应该是0。继续分析就会得到“只有eax=ecx,才可能注册成功”。
最后,总结一下:
name至少3个字符。key的长度不小于name的长度,且前面长度为name长度的部分必须是大写英文字母,后面部分对注册无影响。rand函数的种子设定为0xffffffff/((name[1]%name[0])*name[2]+1)。
然后调用15次rand函数。
然后调用rand函数 (name[0]>>5)*123 次,
满足条件key[0] = (char)(rand()%26 + 'A') 时才可能注册成功。(注意,这里又调用了一次rand函数)
然后调用rand函数 (name[1]>>5)*123 次,
满足条件key[1] = (char)(rand()%26 + 'A') 时才可能注册成功。
。。。。。。。
直至取完name的所有字节。
这里给一个正确的注册码:reg name:asdf,
reg key :YNMK.
另外,需要特别说明一点:
首先,给出产生随机数的rand函数代码:
static long holdrand = 1;
void srand(unsigned seed)
{
holdrand = seed;
}
int rand()
{
return (holdrand = holdrand * 214013 + 2531011) >> 16 & 0x7fff;
}
这是在网络上搜索到的代码。
不过我在visual c++ 6.0下做过实验,直接使用头文件中的rand()函数,发现做出的注册机并不能注册成功。而如果使用如下的自定义函数,则可以成功做出注册机。
unsigned long seed;
unsigned long myrand()
{
return (seed=seed*0x343fd+0x269ec3)>>16 & 0x7fff;
}
直接使用头文件中的rand()函数做出的注册机,用OD查看这个函数的汇编代码,发现与crackme中的rand函数代码相同。
然而做出的注册机并不能注册成功。
本人水平有限,实在不明白这是为什么???望高人指点!
附visual c++ 6.0源代码:
#include <iostream>
#include <string.h>
#include <windows.h>
//#define myrand rand
using namespace std;
unsigned long seed;
unsigned long myrand()
{
return (seed=seed*0x343fd+0x269ec3)>>16 & 0x7fff;
}
int main()
{
char name[50];
re_input:
cout<<"请输入用户名(至少3个字符):"<<endl;
cin>>name;
if (strlen(name)<3)
{
cout<<"用户名至少3个字符!"<<endl
<<"请重新输入。"<<endl;
goto re_input;
}
unsigned long qq;
qq = (unsigned long)(name[0] % name[1]) * name[2] + 1;
seed = 0xffffffff/qq;
qq = 0xf;
while (qq--)
myrand();
char key[50];
for (int i=0; i<strlen(name); i++)
{
qq=(unsigned long)(name[i]>>5) * 123;
while (qq--)
myrand();
key[i] = (char)(myrand()%26 + 'A');
}
key[i] = '\0';
cout<<key<<endl;
system("PAUSE");
return 0;
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!