haunte'sCrackme#1破解+算法

Mail随手拿了个CrackMe研究了一下,很简单的CrackMe,打算写篇详细点的教学文章,结果竟然写文章比破解更费时间,但最终总算写完了,贴在这里吧。


【文章标题】: haunte's Crackme #1 破解+算法
【文章作者】: megadeath
【作者邮箱】: massacre@163.com
【作者QQ号】: 84227716
【软件名称】: haunte's Crackme #1
【软件大小】: 14,336
【下载地址】: http://rapidshare.com/files/68507917/Crackme.rar.html
【加壳方式】: 无
【保护方式】: 无
【编写语言】: ASM
【使用工具】: OllyICE
【操作平台】: WinXPsp2
【软件介绍】: 用户名+密码 验证
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
MD5:39e1b194a26009aa91f6dd42e326453fCrackme.exe

首先拿到这个CrackMe,初次执行竟然没有反映,将在XP中设置为兼容win95模式后可以启动,程序很小,只有14k,但结构比我想象中的复杂,有不少干扰代码,花了一些时间读懂,破解过程如下。

程序是老套,输入用户名和密码,点击确认,但是错误注册码不会弹出对话框,用OD加载,在串式参考中找不到注册失败或成功的注册码,但是刚才在输入错误注册信息时弹出的对话框的标题是“crackmebyhaunted”,所以直接从这里找,在程序段往上一点点可以找到注册成功或失败的字样(幸运),可以断定注册判注册在这附近,再往上翻看,可以查到从004010D0处开始进行注册码判断,我们从这里开始逐步分析,重新下断点在004010D0 处,然后往下走


004010CB > B9 1E000000mov ecx, 1E
004010D0 .31C0xor eax,eax; 这里是开始点
004010D2 . BF B8204000mov edi,004020B8; 这里是最后放入真正注册码的
004010D7 .F3:AArep stos byte ptres:[edi]
004010D9 .31FFxor edi, edi
004010DB .47inc edi
004010DC . C1E70B shledi, 0B
004010DF . 6A2Dpush2D; 用户名最长不能超过0x2d
004010E1 . 68 4F204000push0040204F; 这个指针用于存放用户名
004010E6 .57push edi
004010E7 .56push esi
004010E8 . FF15 90214000 calldword ptr[402190]; USER32.GetDlgItemTextA
004010EE .09C0or eax,eax; 判断用户名长度
004010F0 . 0F85 01000000jnz004010F7; 如果用户名长度不为0则跳转
004010F6 .C3retn; 否则就结束
004010F7 >D1EFshr edi, 1
004010F9 . 6A1Epush1E; 输入的注册码最长不能超过0x1E
004010FB . 68 7C204000push0040207C; 这个指针用于存放注册码
00401100 .57push edi
00401101 .56push esi
00401102 . FF15 90214000 calldword ptr[402190]; USER32.GetDlgItemTextA
00401108 .09C0or eax, eax
0040110A . 0F85 01000000jnz00401111; 判断注册码长度
00401110 .C3retn; 长度如果为0就Over
00401111 > B8 4F204000mov eax,0040204F; 这里放的是输入的用户名
00401116 . E8 1D020000call00401338; 进到里面看,这是计算字符串长度的
0040111B . A3 A0214000mov dword ptr [4021A0],eax ;字符串长度放到[4021A0],从后面来看,这步操作似乎没有太多作用,只是暂存了字符串长度
00401120 . BB 4F204000mov ebx,0040204F; 输入的用户名字符串
00401125 .0FB613 movzxedx, byte ptr[ebx]; 取用户名的第一个字符放到edx中
00401128 . 0FB64C03 FFmovzx ecx, byte ptr[ebx+eax-1] ;取用户名的最后一个字符到ecx中
0040112D .0FAFD1imul edx,ecx; edx = edx * ecx,即第一个字和最后一个字的乘积
00401130 .50push eax
00401131 . B9 02000000mov ecx, 2
00401136 .52push edx
00401137 .31D2xor edx,edx; edx清零
00401139 .F7F9idivecx; eax = 用户名长度 / ecx
0040113B .5Apopedx; edx是刚才的乘积
0040113C . 0FB60C03movzx ecx, byte ptr[ebx+eax]; 取用户名[用户名长度/2]的字符
00401140 .0FAFD1imul edx,ecx; 刚才的乘积再乘以上面的字符
00401143 . BB B8204000mov ebx,004020B8; 这个地址是放真正注册码的,现在是空的
00401148 . C703 484E542Dmov dword ptr [ebx],2D544E48 ; 将2D544E48 放到 数据区 004020B8 处,即字符串“HNT-”
0040114E .89D0mov eax,edx; 将刚才的乘积放到eax
00401150 . 83C304 addebx,4; 这里ebx是放注册码的新位置,就是在 HNT- 这几个字符的后面
00401153 . E8 F7010000call0040134F; 对eax进行一系列运算,算出四个值,添加到注册码尾部
00401158 . C70500204000>mov dword ptr[402000],1E; 这步的确不知道是做什么用的,可能是迷惑代码
00401162 . 68 00204000push00402000; /pBufferSize = Crackme.00402000
00401167 . 68 9A204000push0040209A; |Buffer = Crackme.0040209A
0040116C . E8 B34E0000call<jmp.&KERNEL32.GetComputerNameA>; GetComputerNameA
00401171 . B8 9A204000mov eax,0040209A; 这里eax中放的是计算机的名称
00401176 . E8 F2010000call0040136D; 对计算机名称每个字符进行累加,结果返回eax
0040117B . BB 4F204000mov ebx,0040204F; 这里是用户名,地址传给ebx
00401180 . E8 00020000call00401385; 对字符串一系列运算
00401185 .50pusheax; 对刚才的计算结果压栈,后面还有用到这个值
00401186 .31C0xor eax,eax; eax清零
00401188 .0FA2cpuid; 取得CPUID
0040118A .58popeax; eax是刚才计算后在 00401185 处压栈的结果
0040118B .51push ecx
0040118C .52push edx
0040118D .21D8and eax,ebx; eax 的值更新为 eax 和CPUID后ebx的与结果
0040118F . BB B8204000mov ebx,004020B8; 这是放注册码的地方
00401194 .89DFmov edi,ebx; 取得注册码首地址位置放到edi中
00401196 .50pusheax; 将刚才eax的计算结果保存起来
00401197 .66:31C0xor ax,ax; ax清零
0040119A .F2:AErepne scas byte ptres:[edi]; 统计现在注册码的长度,edi步进到字符串的末尾
0040119C .89FBmov ebx,edi; 字符串末尾位置赋值给ebx
0040119E .58popeax; 回复eax的值
0040119F .4Bdecebx; 注意这里的ebx自减1
004011A0 . C6032D movbyte ptr [ebx],2D; 在注册码中增加 - 这个分割符号
004011A3 .43incebx; 步进新的位置填充注册码
004011A4 . E8 A6010000call0040134F; eax的值是0040118D时的eax值,计算后增加注册码
004011A9 . B8 4F204000mov eax,0040204F; 这里是输入的用户名
004011AE . E8 BA010000call0040136D; 对输入的用户名的每个字符进行累加,结果返回eax
004011B3 .59popecx; ecx中是刚才CPUID调用时取得的edx返回的值
004011B4 .31C8xor eax,ecx; eax 异或 ecx
004011B6 .59popecx; 这个返回的是刚才CPUID调用时ecx的值
004011B7 .09C8or eax,ecx; eax 或 ecx
004011B9 .50pusheax; 保存eax
004011BA . BB 4F204000mov ebx,0040204F; 这是放输入用户名的地方
004011BF .0FB603 movzxeax, byte ptr[ebx]; 注册码的第一个字符
004011C2 . 0FB64B 01movzx ecx, byte ptr[ebx+1]; 注册码的第二个字符
004011C6 .0FAFC1imul eax,ecx; 两者相乘结果放入到eax
004011C9 .59popecx; ecx 是刚才上面计算的结果
004011CA .0FAFC1imul eax, ecx
004011CD . BF B8204000mov edi,004020B8; 放注册码的地方
004011D2 .50push eax
004011D3 .30C0xor al, al
004011D5 .F2:AErepne scas byte ptres:[edi]; 扫描注册码字符串
004011D7 .58pop eax
004011D8 .89FBmov ebx, edi
004011DA .4Bdec ebx
004011DB . C6032D movbyte ptr [ebx],2D; 再次增加 - 分隔符
004011DE .43inc ebx
004011DF . E8 6B010000call0040134F; 对eax进行运算得到 - 后的新注册码
004011E4 . B8 4F204000mov eax,0040204F; 这里存放的是用户名
004011E9 . E8 7F010000call0040136D; 累加用户名值
004011EE . B9 1A000000mov ecx, 1A
004011F3 .31D2xor edx,edx; edx清零
004011F5 .F7F9idivecx; 累加值除以1a
004011F7 . B8 41000000mov eax,41; eax 放入 41
004011FC .01D0add eax,edx; 再和刚才的余数相加
004011FE .89C7mov edi,eax; 将结果存入到edi,一会儿要用到
00401200 . BB 4F204000mov ebx,0040204F; 这里存放的是用户名
00401205 . E8 7B010000call00401385; 对用户名进行一系列计算
0040120A .31D2xor edx,edx; edx清零
0040120C .F7F9idivecx; 得到的结果除以1A
0040120E . 83C241 addedx,41; 将余数加0x41
00401211 . BB B8204000mov ebx,004020B8; 这是存放注册码
00401216 .50push eax
00401217 .31C0xor eax, eax
00401219 .57push edi
0040121A .89DFmov edi, ebx
0040121C .F2:AErepne scas byte ptres:[edi]; 到注册码的末尾处
0040121E .58pop eax
0040121F .89FBmov ebx, edi
00401221 .5Fpop edi
00401222 .4Bdec ebx
00401223 . C6032D movbyte ptr [ebx],2D; 注册码增加短线分隔符
00401226 . 884301 movbyte ptr [ebx+1],al; 把al作为字符增加
00401229 . 885302 movbyte ptr [ebx+2],dl; 把dl作为字符增加
0040122C . C643 03 00mov byte ptr [ebx+3],0; 最后添加结束符,这时注册码已经完整
00401230 .58pop eax
00401231 .56push esi
00401232 . B8 B8204000mov eax,004020B8; 真正注册码
00401237 . E8 FC000000call00401338; 统计真正注册码长度
0040123C .89C2mov edx,eax; 将长度放到edx中
0040123E . B8 7C204000mov eax,0040207C; 输入的伪注册码
00401243 . E8 F0000000call00401338; 统计输入注册码长度,将注册码长度放到eax
00401248 .39D0cmp eax,edx; 比较一下
0040124A . 0F85 5F000000jnz004012AF; 长度不相等就跳转到失败处
00401250 . BE B8204000mov esi,004020B8; 真正注册码
00401255 . BF 7C204000mov edi,0040207C; 输入的伪注册码
0040125A .89C1mov ecx, eax
0040125C >8A06mov al, byte ptr [esi]
0040125E .8A27mov ah, byte ptr [edi]
00401260 .46inc esi
00401261 .47inc edi
00401262 .30C4xor ah,al; 两个注册码逐个比较
00401264 . 0F85 45000000jnz004012AF; 如果不相等则跳到失败处
0040126A .49dec ecx
0040126B .^ 75EFjnz short0040125C; 两个注册码逐个字符比较,循环
0040126D .5Epopesi; 后面就是显示正确注册的提示信息了
0040126E . B9 C8000000mov ecx, 0C8
00401273 . B8 DC000000mov eax, 0DC
00401278 . BF 23204000mov edi,00402023; ASCII "Right Code , Well Done",CR,LF,"-hari code eka-?
0040127D .F2:AErepne scas byte ptr es:[edi]
0040127F .4Fdec edi
00401280 .8827mov byte ptr [edi], ah
00401282 . B8 DD204000mov eax, 004020DD
00401287 . 83C064 addeax, 64
0040128A . 6A40push 40
0040128C .50push eax
0040128D . 68 23204000push00402023; ASCII "Right Code , Well Done",CR,LF,"-hari code eka-?
00401292 .56push esi
00401293 . FF15 08204000 calldword ptr[402008]; USER32.MessageBoxA
00401299 . B9 C8000000mov ecx, 0C8
0040129E . B8 00000000mov eax, 0
004012A3 . BF 23204000mov edi,00402023; ASCII "Right Code , Well Done",CR,LF,"-hari code eka-?
004012A8 .F2:AErepne scas byte ptr es:[edi]
004012AA .4Fdec edi
004012AB . C607DC movbyte ptr [edi], 0DC
004012AE .C3retn
004012AF > B9 C8000000mov ecx, 0C8
004012B4 . B8 DC000000mov eax, 0DC
004012B9 . BF 0C204000mov edi,0040200C; ASCII "Wrong Code",CR,LF,"-weradiyi-躌ight Code , WellDone",CR,LF," -hari code eka-?
004012BE .F2:AErepne scas byte ptr es:[edi]
004012C0 .4Fdec edi
004012C1 .8827mov byte ptr [edi], ah
004012C3 . B8 DD204000mov eax, 004020DD
004012C8 . 83C064 addeax, 64
004012CB . 6A30push 30
004012CD .50push eax
004012CE . 68 0C204000push0040200C; ASCII "Wrong Code",CR,LF,"-weradiyi-躌ight Code , WellDone",CR,LF," -hari code eka-?
004012D3 .56push esi
004012D4 . FF15 08204000 calldword ptr[402008]; USER32.MessageBoxA
004012DA . B9 C8000000mov ecx, 0C8
004012DF . B8 00000000mov eax, 0
004012E4 . BF 0C204000mov edi,0040200C; ASCII "Wrong Code",CR,LF,"-weradiyi-躌ight Code , WellDone",CR,LF," -hari code eka-?
004012E9 .F2:AErepne scas byte ptr es:[edi]
004012EB .4Fdec edi
004012EC . C607DC movbyte ptr [edi], 0DC
004012EF .5Epop esi
004012F0 .C3retn
004012F1 > 6A30push 30
004012F3 . 68 41214000push00402141; ASCII "Crackme By Haunted"
004012F8 . 68 06214000push00402106; ASCII "Crackme",CR,CR,"Rules :",CR,"Find aserial(Write a keygen if you want)"
004012FD .56push esi
004012FE . FF15 04204000 calldword ptr[402004]; USER32.MessageBoxA
00401304 .C3retn

------------------------------------------------------------------------------------------

几点说明:

1.在00401188处调用的CPUID指令,在AMD的CPU上也有这个指令,但求得的结果和Intel的不同,这里按照Intel的来写
如果eax初始值为0x00,调用CPUID后会往ebx,ecx和edx中写入一些值,查Intel手册的话可以了解到是寄存器中放的是一些字符串,ebx写的是“Genu”,ecx是“ntel”,edx中是“ineI”,也就是说如果是Intel的CPU的话必然是ebx=756E6547h,ecx=6c65746eh,edx=49656e69h。


2.说明一下几个call的调用,这几个call基本上都是靠eax和ebx传递参数的,刚开始看的时候觉得很混乱,多看几次就能理解了,不是很难

call 00401338
00401338 /$51push ecx
00401339 |.57push edi
0040133A |. B9 64000000mov ecx,64; 最多扫描64个字符
0040133F |.89C7mov edi,eax; eax中放的是字符串,赋值给edi,为后面的扫描
00401341 |.31C0xor eax,eax; eax 清零,al 也是0
00401343 |.F2:AErepne scas byte ptres:[edi]; 对注册码扫描,碰到al就结束
00401345 |.F7D1notecx; 反转ecx,ecx是减少的次数
00401347 |. 83C1 64add ecx,64; ecx 再加上64,ok,这样ecx中存放的是字符串的长度
0040134A |.89C8mov eax,ecx; 结果就是用户名长度,放到eax中
0040134C |.5Fpop edi
0040134D |.59pop ecx
0040134E .C3retn; 返回

这个是个很简单的函数,目的是统计输入字符串的长度,我们只要记得 00401338 是统计字符串长度的函数就可以了

call 0040134F
0040134F /$51push ecx
00401350 |.56push esi
00401351 |.4Bdec ebx
00401352 |. B9 04000000mov ecx,4; ecx赋值4,要计算4个新值
00401357 |. BE 0A000000mov esi,0A; esi赋值0xA,就是10进制的10
0040135C |.52push edx
0040135D |>31D2xor edx,edx; edx清零
0040135F |.F7FEidivesi; eax = eax / esi,等于除以10
00401361 |. 80C2 30add dl,30; 余数加30
00401364 |.88140Bmov byte ptr [ebx+ecx],dl; 放到一个数据区中
00401367 |.^ E2F4loopd short 0040135D
004————01369 |.5Apop edx
0040136A |.5Epop esi
0040136B |.59pop ecx
0040136C .C3retn

这段是要计算四个值,将刚才的eax传入后不停除以0xA,每次得到的余数转换成数字字符,然后在刚才的注册码尾部增加上去,但要注意的是增加数字字符是倒着着加进去的,也就是从后往前加的


call 0040136D
0040136D /$56push esi
0040136E |.51push ecx
0040136F |.89C6mov esi,eax; 将字符串传给esi
00401371 |.31C9xor ecx, ecx
00401373 |.31C0xor eax, eax
00401375 |>8A06/mov al, byte ptr[esi]; 取得字符串的每个字符
00401377 |.84C0|test al,al; 看看是否有值
00401379 |. 7405|je short 00401380
0040137B |.46|inc esi
0040137C |.01C1|add ecx,eax; 然后进行累加
0040137E |.^ EBF5jmp short 00401375
00401380 |>89C8mov eax,ecx; 将结果返回到eax返回
00401382 |.59pop ecx
00401383 |.5Epop esi
00401384 .C3retn

直接就是对计算机名称进行累加计算,结果放到eax后返回


call 00401385
00401385 /$51pushecx;
00401386 |.52push edx
00401387 |.89D8mov eax,ebx; 这里把eax清除掉了
00401389 |. E8 AAFFFFFFcall00401338; 计算字符串长度的函数
0040138E |.89C1mov ecx,eax; 字符串长度放到ecx
00401390 |.4Bdec ebx
00401391 |.31D2xor edx,edx; edx清零
00401393 |>8A140B/mov dl, byte ptr[ebx+ecx]; 取得字符串每个字符,倒序
00401396 |.0FAFD1|imul edx,ecx; edx = edx * ecx
00401399 |.01D0|add eax,edx; eax = edx + eax
0040139B |.49|dec ecx
0040139C |.^ 75F5jnz short 00401393
0040139E |.5Apop edx
0040139F |.59pop ecx
004013A0 .C3retn

这段代码就比较有意思,这里是把用户名传入,然后先计算长度用来做循环次数,循环是倒序的,也是倒序取得用户名字符,然后将用户名字符乘以循环循环值,和eax累积,eax中初始值为用户名长度,最终返回eax的值,但有意思的是每次取得用户名的一个字符时是要放到edx中的,edx初始值为0x00,但每次循环都是将取得的字符是放在edx的低8位上的,其余位不变,这个要注意

最后到00401232 处的时候可以直接看内存中004020B8,这时注册码已经被计算出来了,爆破就从这边下手就可以了。

注册名:megadeath
注册码:HNT-3600-4642-9887-SQ

到这里我们把重新把思路整理一下,在计算注册码的时候总共有4个关键的call,00401338、0040134F、0040136D和00401385,其中00401338是统计字符串长度的,0040134F是将eax的值进行运算求得4个0-9的数字字符并增加到注册码末尾的,0040136D是对字符串的每个字符进行累加求和,00401385是对字符串每个字符求一个特殊的累积值。知道这四个函数的功能,我们把注册机写出来:

#include <iostream>
#include <string>
using namespace std;

//将字符串转换为4个数字字符并增加到注册码末尾,注意是倒序放入的
void Func0040134F(string & strCode, unsignediNumber)
{
strCode.resize(4 + strCode.length());
for (int ii = 0; ii < 4; ++ii)
{
strCode[strCode.length() - ii - 1] =(char)((iNumber % 0x0A) + 0x30);
iNumber /= 0xA;
}
return;
}

//对字符串的每个字符进行一系列特殊的计算,返回一个特殊的乘积结果
unsigned Func00401385(const string &strString)
{
unsigned iResult = 0;
unsigned iStringLength = strString.length();
unsigned iAccumulate = iStringLength;

for (int ii = iStringLength - 1; ii >= 0;--ii)
{
iResult = iResult &0xFFFFFF00;
iResult = iResult + (((int)strString[ii])& 0xFF);
iResult = iResult * (ii + 1);
iAccumulate = iResult + iAccumulate;
}

return iAccumulate;
}

//累加字符串操作
unsigned Func0040136D(const string &strString)
{
unsigned iSum = 0;

for (int ii = 0; ii < strString.length();++ ii)
{
iSum += (int)strString[ii];
}
return iSum;
}

int main(int,char **)
{
string strName;
string strSn;
strSn = "HNT-";//注册码的第一部分
unsigned iNameLength = 0;
unsigned iResult = 0;
unsigned iEax, iEbx, iEcx, iEdx;
iEax = iEbx = iEcx = iEdx = 0;

do
{
cout << "Pleaseinput your name:" <<endl;
cin >>strName;

if((0 != (iNameLength = strName.length()))&& (0x2D >=iNameLength))
{
goto KeyGen;
}

} while(1);

KeyGen:
//增加注册码的第二部分
iResult = strName[0] * strName[iNameLength - 1];
iResult *= strName[iNameLength/2];
Func0040134F(strSn, iResult);

//增加注册码的第三部分
iResult = Func00401385(strName);
__asm
{
pushad
xor eax, eax
cpuid
mov iEax, eax
mov iEbx, ebx
mov iEcx, ecx
mov iEdx, edx
popad
}
iResult = iResult & iEbx;
strSn.push_back('-');
Func0040134F(strSn, iResult);

//增加注册码的第四部分
iResult = Func0040136D(strName);
iResult ^= iEdx;
iResult |= iEcx;
iResult *= (strName[0] * strName[1]);
strSn.push_back('-');
Func0040134F(strSn, iResult);

//增加注册码的第五部分
strSn.push_back('-');
iResult = Func0040136D(strName);
iResult = (iResult % 0x1A) + 0x41;
strSn.push_back((char)iResult);

iResult = (Func00401385(strName) % 0x1A) + 0x41;
strSn.push_back((char)iResult);

cout << strSn<<endl;

cout << "all right!"<< endl;
cin.get();
return 0;
}

--------------------------------------------------------------------------------
【经验总结】
其中在找注册码的过程中,有一些迷惑代码,比如GetComputerNameAPI的调用,还有在 00401158处存放0x1E,这个到最终也没有用上,在对CPUID调用后压栈后再重新取值进行计算也要小心,需要记下压栈的顺序以便后面运算的,否则很容易被搞乱计算步骤。在函数调用的时候也不是一般的压栈传参数,而是通过eax和ebx来传值,这个在开始的时候被弄得很迷惑,但多看几次就能明白怎么回事了。

另外对于明码比较的CrackMe还是比较好破解的,用爆破是最简单的办法,而且注册码也比较容易得到,今后将多练习看看非明码比对的CrackMe来学习学习。

--------------------------------------------------------------------------------
【版权声明】: 转载请注明作者并保持文章的完整, 谢谢!

2007年11月04日 22:50:15

  

爱华网本文地址 » http://www.413yy.cn/a/25101015/267082.html

更多阅读

如何破解新浪邮箱密码 如何破解邮箱密码软件

? ? ? ? ? ?生活中,我们都会使用由各种服务商提供的邮箱,但在发邮件的时要知道,电子邮件并不是安全的,在邮件的发送、传送和接收整个过程中的每个环节都可能存在薄弱环节,恶意用户如果利用其漏洞,就能够轻易的破解出账号,获得邮件内容。他

RHCE_RHEL6_复习总结1.破解root密码_网络运维

破解root密码,顺利进入系统核心提示:破解root密码,才能顺利进入系统。破解root密码,必须进入单用户模式。如何保护单用户模式?加密!实验环境:一台已经安装好在VMwareWorkstation上的RHCE6虚拟机。

MyEclipse10.0的破解过程详细及图解 碗莲种植过程详细图解

MyEclipse10.0的破解过程详细图解准备阶段?:1.?破解软件(网上有下载)2.?JDK软件(免费软件)开始破解:1.?关闭MyEclipse?10.02.?安装?JDK然后解压破解软件并运行run.bat(批处理文件)看到出现破解工具界面框。第一步:在Usercode栏中输

声明:《haunte'sCrackme#1破解+算法》为网友勋章分享!如侵犯到您的合法权益请联系我们删除