一个crackme的算法分析
一个crackme的算法分析o(∩_∩)o...首先声明一下,这是本人第一个破解文章。o(∩_∩)o...
本人属于菜鸟级的,这个crackme也没什么技术含量。这个crackme是一个chm破解教程中的附件,虽然教程已经分析了这个程序,但是可能嫌麻烦(也可能是教程的目的仅仅是练习爆破),没有分析出具体的注册算法,仅仅给出了爆破点。本人耐心地跟踪了这个程序,才有了这个破解的文章,其中给出了注册算法。
废话说完了,下面转入正题。
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 ; |
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 ; |
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
继续跟踪,
00401276|.0FBE4424 08 movsx eax, byte ptr ;字节name
0040127B|.0FBE4C24 09 movsx ecx, byte ptr ;字节name
00401280|.99 cdq
00401281|.F7F9 idiv ecx
00401283|.8BCA mov ecx, edx ; ecx=name%name
00401285|.83C8 FF or eax, FFFFFFFF
00401288|.0FBE5424 0A movsx edx, byte ptr ;字节name
0040128D|.0FAFCA imul ecx, edx ; ecx=(name%name)*name
00401290|.41 inc ecx
00401291|.33D2 xor edx, edx
00401293|.F7F1 div ecx ; eax=0xffffffff/((name%name)*name+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 , 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 ;用户名的某个字节
004012D1|. |C0F8 05 |sar al, 5 ;eax=name>>5
004012D4|. |0FBEC0 |movsx eax, al
004012D7|. |8D1480 |lea edx, dword ptr
004012DA|. |8D04D0 |lea eax, dword ptr
004012DD|. |8D0440 |lea eax, dword ptr ;eax=eax*123(Dec)
004012E0|. |85C0 |test eax, eax
004012E2|. |7E 0A |jle short 004012EE
004012E4|. |8BF8 |mov edi, eax ;edi=(name>>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 ;edi指向用户名
004012FD|. |F7F9 |idiv ecx
004012FF|. |0FBE4C34 2C |movsx ecx, byte ptr ;序列号的某个字节
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 , 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:
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%name)*name+1)。
然后调用15次rand函数。
然后调用rand函数 (name>>5)*123 次,
满足条件key = (char)(rand()%26 + 'A') 时才可能注册成功。(注意,这里又调用了一次rand函数)
然后调用rand函数 (name>>5)*123 次,
满足条件key = (char)(rand()%26 + 'A') 时才可能注册成功。
。。。。。。。
直至取完name的所有字节。
这里给一个正确的注册码:asdf,YNMK.
另外,给出产生随机数的rand函数代码:
static long holdrand = 1;
void srand(unsigned seed)
{
holdrand = seed;
}
int rand()
{
return (holdrand = holdrand * 214013 + 2531011) >> 16 & 0x7fff;
}
????附件发不了??? 学习算法。 /:good 分析得很好。 好文章,学习下~
页:
[1]