买回《加密与解密 第三版》已经半个多月了,每天晚上下班都会抽出3个小时来学习,用“拨开云雾”四个字来形容这半个月的感觉最恰当不过,一层一层的知识让我应接不暇,现在才发现原来用了好几年的EXE等文件竟存在着这么多的秘密! 虽然我是做嵌入式系统的,目前软件安全性应用得不多,但是能学到这么多感兴趣的东西,毕竟也是一件幸事,不敢独享,在此与众同乐!
附件为我周末时用VC++编写的CRECKME系列,由于只有两天时间,目前只完成了前6个难度,即难度0--难度5,我后面会陆续再贴出后面的难度6---难度9,也将陆续贴出源码以及我个人的破解方法,望各位能一起参与,多多指教!!
本来还想贴出各难度的帮助,但想想还是让大家先试着破解,等过一段时间贴出具体源代码和破解流程时再一起贴出好了!
PS:由于本人用VC++ MFC 编译,所以如果文件打不开的话,请依照提示下载MFC71.dll等文件,如电脑中有装VC++则无需下载。发现CRECKME 4程序中漏了一句话,导致与我原来的想法不符,现在补上名称为"CRECKME 4修正版",有兴趣的可以从附件中下载尝试。
CRECKME 6 已经上传! 期待分析和注册码,能爆破也行! 结果:
CRECKME 0:明码,无算法,有字符串可下断点,可爆破,无各项反调试措施,无加壳。 难易程度:最简单。 目的:明码典型性。
CRECKME 1:非明码,算法简单,有字符串可下断点,可爆破,无各项反调试措施,无加壳。 难易程度:简单。 目的:非明码的实现。
CRECKME 2:非明码,算法简单,无字符串和敏感API函数可下断点,可爆破,无各项反调试措施,无加壳。 难易程度:简单。 目的:OllyDbg消息断点或其它断点尝试。
CRECKME 3:非明码,算法简单,无字符串和敏感API函数可下断点,有防爆破,无各项反调试措施,无加壳。 难易程度:简单。 目的:尝试防爆破。
CRECKME 4,CRECKME 4修正版:Cyane已爆破,但尚未能得出注册码。 等待注册码后贴解析和源代码。期待中……
CRECKME 5,6:已有爆破,但尚未有注册码。期待中……
CRECKME 7:由于CRECKME 5,6用了别人的VM PROTECT,导致某些人的反感,我这里再次诚挚道歉,并感谢你们的关注。 CRECKME 7中不再使用VM,但有成熟算法和其它一些反调试的手段,代码里也有自己的一些思路,虽然很多是前人走过的,但毕竟也有自己的想法和创新,这个CRECKME花了我几天时间,感谢你们的继续关注!
CRACKME 7实现流程和源码在编程模块已详细说明,有兴趣的可以看以下链接:http://bbs.pediy.com/showthread.php?p=663830#post663830, 特别感谢sessiondiy的提醒和关注! ----------------------------------------------------------------------------------------------------------------------------------------CRECKME 0 解析(初次解析,请多多指教):
工具:OllyDbg,
查壳:peid,
1,查壳显示为Microsoft Visual C++ 7.0 Method2 [Debug],可知无加壳(当然,一些壳也能伪装这些信息,以OllyDbg入口点为准)。
2,调试MFC程序,需先倒入MFC的LIB文件,否则MFC里面的函数将无法解析。deg--->select import libraries--->MFC42.LIB,MFC71.LIB ,然后点击process.
3,通过测试,我们知道有字符串提醒,因此可以用字符串下断点,用OllyDbg打开程序后,OPlugins---->Ultra String Reference -->Find AScill,找到Congratulation! Correct Serial Num,do next one,双击查看,即可找到验证代码处,我在004015A0 . 56 push esi 行处下断点。
4,按F9运行代码,在NAME和SERIAL中随便输入,我这里输入为NAME:zenghw, SERIAL:5705312 (个人习惯)。点确定,程序中断。
5,以下内容中,//后面内容即为我的注释
004015A0 . 56 push esi
004015A1 . 57 push edi
004015A2 . 6A 01 push 1 ; //UpdateDate的参数,为1,即为TRUE
004015A4 . 8BF1 mov esi, ecx
004015A6 . E8 6B030000 call <jmp.&MFC71.#6236_CWnd::UpdateData> ; //UpdateDate,结合前面的参数TRUE,可知为把对话框中的内容保存起来
004015AB . 8D7E 74 lea edi, dword ptr [esi+74] ; //由信息窗口可知ptr[esi+74]为地址0013FEF8,再由右下角堆栈串口可知0013FEF8为我们输入的假NAME,我这里为zenghw
004015AE . 8BCF mov ecx, edi ; //把我们输入的假NAME zenghw 赋给 ecx
004015B0 . FF15 D0314000 call dword ptr [<&MFC71.#2902_ATL::CSimpleString>; MFC71.ATL::CSimpleStringT<wchar_t,1>::GetLength
004015B6 . 83F8 06 cmp eax, 6 ; //在上面跟入可以很明显的知道eax即为返回的string长度值,即假NAME zenghw的长度值
004015B9 . 7D 1A jge short 004015D5 ; //如果长度值>=6的话就跳,否则往下跑
004015BB . 817E 78 A0860>cmp dword ptr [esi+78], 186A0
004015C2 . 7D 11 jge short 004015D5
004015C4 . 6A 00 push 0
004015C6 . 6A 00 push 0
004015C8 . 68 8C394000 push 0040398C ; name or serial is too short!
004015CD . E8 3E030000 call <jmp.&MFC71.#1123_AfxMessageBox>
004015D2 > 5F pop edi
004015D3 . 5E pop esi
004015D4 . C3 retn
004015D5 > 68 78394000 push 00403978 ; //把字符串"indolentafternoon"推进栈
004015DA . 8BCF mov ecx, edi ; //从信息窗口和堆栈窗口可知,edi即为假NAME
004015DC . FF15 C0314000 call dword ptr [<&MFC71.#1482_ATL::CStringT<char>; //Compare,跟进去可知为比较函数,其实看名字不跟也知道大概结果
004015E2 . 85C0 test eax, eax ; //eax为返回值,如果相等为0,不等为1
004015E4 .^ 75 EC jnz short 004015D2
004015E6 . 817E 78 D7C75>cmp dword ptr [esi+78], 56C7D7 ; //把假SERIAL 5705312 与0X56C7D7比较,即十进制5687255
004015ED .^ 75 E3 jnz short 004015D2 ; //如果不等的话就跳走
004015EF . 6A 00 push 0
004015F1 . 6A 00 push 0
004015F3 . 68 44394000 push 00403944 ; congratulation! correct serial num,do next one? :)
004015F8 . E8 13030000 call <jmp.&MFC71.#1123_AfxMessageBox>
004015FD . 8B06 mov eax, dword ptr [esi]
004015FF . 5F pop edi
00401600 . 8BCE mov ecx, esi
00401602 . 5E pop esi 再附上我VC里的代码:
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(true);
if(m_Name.GetLength()<6 && m_Serial< 100000)
{
AfxMessageBox("Name or Serial is too short!");
return;
}
if(m_Name == "IndolentAfternoon" && m_Serial == 5687255)
{
AfxMessageBox("Congratulation! Correct Serial Num,do next one? :)");
OnOK();
}
else
return;
} CREAKME 0 内容是最简单最初步的,很多程序员也犯这样的错误:明码,不用算法,没有各种防范措施,就直接比较,相同则注册成功。
明天继续贴CREME 1,谢谢大家关注。------------------------------------------------------------------------------------------------------------------------------------
继续贴CRECKME 1, 1,还是用字符串下断点。运行后,我输入NAME为zenghw,SERIAL为5705312.
2,分析代码:
004013E8 . 55 push ebp
004013E9 . 56 push esi
004013EA . 57 push edi
004013EB . 33FF xor edi, edi
004013ED . 6A 01 push 1 ; //UpdateDate的参数,为1
004013EF . 8BF1 mov esi, ecx
004013F1 . 897C24 18 mov dword ptr [esp+18], edi
004013F5 . E8 A2040000 call <jmp.&MFC71.#6236_CWnd::UpdateDa>; //UpdateData函数,参数为1时,表示把当前控件内容更新入变量中
004013FA . 8D6E 78 lea ebp, dword ptr [esi+78] ; //由信息窗口可知ptr[esi+78]为地址0013FEF8,再由右下角堆栈串口可知0013FEF8为我们输入的假NAME,我这里为zenghw,在command中输入D 0013FEF8为什么不能显示?
004013FD . 8BCD mov ecx, ebp
004013FF . 896C24 18 mov dword ptr [esp+18], ebp ; //把让ptr[esp+18]指针指向假NAME :zenghw
00401403 . FF15 C0214000 call dword ptr [<&MFC71.#2902_ATL::CS>; //获得假NAME的输入长度
00401409 . 83F8 06 cmp eax, 6
0040140C . 0F8C 71010000 jl 00401583 ; //如果输入长度<6,就跳向失败
00401412 . 83C6 7C add esi, 7C ; //做什么用?给参数分配空间吧?
00401415 . 8BCE mov ecx, esi ; //由信心窗口可知ESI地址为0013FEFC,由堆栈窗口可知其为假SERIAL:5705312
00401417 . FF15 C0214000 call dword ptr [<&MFC71.#2902_ATL::CS>; //获得假SERIAL的输入长度
0040141D . 83F8 06 cmp eax, 6
00401420 . 0F8C 5D010000 jl 00401583 ; //如果输入长度<6,就跳向失败
00401426 . 53 push ebx
00401427 . 8BCE mov ecx, esi ; //由上面分析知道ESI为假SERIAL:5705312
00401429 . 897C24 14 mov dword ptr [esp+14], edi
0040142D . FF15 C0214000 call dword ptr [<&MFC71.#2902_ATL::CS>; MFC71.ATL::CSimpleStringT<wchar_t,1>::GetLength
00401433 . 85C0 test eax, eax
00401435 . 7E 4E jle short 00401485
00401437 > 6A 01 push 1 ; //由后面分析,可知其为mid函数的第二个参数,nCount
00401439 . 8D043F lea eax, dword ptr [edi+edi] ; //相当于edi x 2
0040143C . 50 push eax ; //为mid第一个参数,nFirst,
0040143D . 8D4C24 20 lea ecx, dword ptr [esp+20]
00401441 . 51 push ecx
00401442 . 8BCE mov ecx, esi ; //ECX为Mid函数的句柄,如string.mid(0,1)表示从string中从第0个字符开始,提取1个字符
00401444 . FF15 BC214000 call dword ptr [<&MFC71.#4109_ATL::CS>; //由注释知为mid函数,其原型大概为CString Mid( int nFirst, int nCount ),再往回看,可知1为第二个参数,eax为第一个参数
0040144A . 8BC8 mov ecx, eax
0040144C . FF15 B8214000 call dword ptr [<&MFC71.#876_ATL::CSi>; MFC71._CIP<IMoniker,&IID_IMoniker>::operator IMoniker *
00401452 . 50 push eax ; ///eax即为上面mid函数得回的返回值
00401453 . FF15 B0224000 call dword ptr [<&MSVCR71.atoi>] ; \//atoi 函数,int atoi(const char *nptr); 即把字符指针转换为整数,返回其转换后的值
00401459 . 83C4 04 add esp, 4
0040145C . 8D4C24 18 lea ecx, dword ptr [esp+18]
00401460 . 8BE8 mov ebp, eax ; //eax即为上面mid函数得回的返回值
00401462 . FF15 68204000 call dword ptr [<&MFC71.#578_ATL::CSt>; MFC71.ATL::CSimpleStringT<char,1>::~CSimpleStringT<char,1>
00401468 . 8B5C24 14 mov ebx, dword ptr [esp+14]
0040146C . 03DD add ebx, ebp ; //有上面分析可知,ebp即为mid函数的返回值
0040146E . 8BCE mov ecx, esi ; //ESI为假SERIAL 5705312,由后面分析可知,赋给ecx是为了得到它的长度
00401470 . 895C24 14 mov dword ptr [esp+14], ebx ; //从00401468,0040146C两行可以知道,ebx为ebx+dword ptr [esp+14]的值,在从本句,可知其为一个累加
00401474 . 47 inc edi ; //edi加1,可以推测edi为控制累加次数
00401475 . FF15 C0214000 call dword ptr [<&MFC71.#2902_ATL::CS>; //得到假SERIAL 5705312的长度,跟进去知道其用到ecx
0040147B . 3BF8 cmp edi, eax ; //EDI 与 假SERIAL 570531的长度比较,如果小于假SERIAL长度,就跳
0040147D .^ 7C B8 jl short 00401437 ; //从这个JL可推知,这是一个循环,循环以与假码长度的比较为结束,得到累加值赋给ptr [esp+14]
0040147F . 8B6C24 1C mov ebp, dword ptr [esp+1C] ; //由信息窗口可知ptr[esp+1c]为地址0013FEF8,再由右下角堆栈串口可知0013FEF8为我们输入的假NAME:zenghw
00401483 . 33FF xor edi, edi ; //edi清0
00401485 > 6A 01 push 1
00401487 . 57 push edi
00401488 . 8D5424 2C lea edx, dword ptr [esp+2C]
0040148C . 52 push edx
0040148D . 8BCE mov ecx, esi ; //有上面的分析或从堆栈窗口可知esi为假SERIAL:5705312
0040148F . FF15 BC214000 call dword ptr [<&MFC71.#4109_ATL::CS>; //仍旧为mid函数,由参数可知为SERIAL.MID(0,1)
00401495 . BB 01000000 mov ebx, 1
0040149A . 68 8C264000 push 0040268C ; 5
0040149F . 8BC8 mov ecx, eax
004014A1 . 897C24 34 mov dword ptr [esp+34], edi
004014A5 . 895C24 1C mov dword ptr [esp+1C], ebx
004014A9 . FF15 84204000 call dword ptr [<&MFC71.#1482_ATL::CS>; //跟进去看看,可知为为字符SERIAL.MID(0,1)与‘5’比较,返回值为eax.
004014AF . 85C0 test eax, eax ; //eax即为返回的比较是否相等的标记位,以上程序意思为取假SERAL的第一位,是否=="5"
004014B1 . 75 64 jnz short 00401517 ; //不等则跳向失败
004014B3 . 6A 02 push 2
004014B5 . 6A 04 push 4
004014B7 . 8D4424 28 lea eax, dword ptr [esp+28]
004014BB . 50 push eax
004014BC . 8BCE mov ecx, esi
004014BE . FF15 BC214000 call dword ptr [<&MFC71.#4109_ATL::CS>; MFC71.ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > >::Mid
004014C4 . 895C24 30 mov dword ptr [esp+30], ebx
004014C8 . BB 03000000 mov ebx, 3
004014CD . 68 88264000 push 00402688 ; 31
004014D2 . 8BC8 mov ecx, eax
004014D4 . 895C24 1C mov dword ptr [esp+1C], ebx
004014D8 . FF15 84204000 call dword ptr [<&MFC71.#1482_ATL::CS>; MFC71.ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > >::Compare
004014DE . 85C0 test eax, eax ; //同样道理,以上程序意思为取假SERAL的第4,5两位,是否=="31'"
004014E0 . 75 35 jnz short 00401517 ; //不等则跳向失败
004014E2 . 6A 01 push 1
004014E4 . 6A 01 push 1
004014E6 . 8D4C24 24 lea ecx, dword ptr [esp+24]
004014EA . 51 push ecx
004014EB . 8BCE mov ecx, esi
004014ED . FF15 BC214000 call dword ptr [<&MFC71.#4109_ATL::CS>; MFC71.ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char> > >::Mid
004014F3 . 8BC8 mov ecx, eax
004014F5 . BB 07000000 mov ebx, 7
004014FA . FF15 B8214000 call dword ptr [<&MFC71.#876_ATL::CSi>; MFC71._CIP<IMoniker,&IID_IMoniker>::operator IMoniker *
00401500 . 50 push eax ; /s
00401501 . FF15 B0224000 call dword ptr [<&MSVCR71.atoi>] ; \//同样分析,以上程序意思为:取假SERIAL:5705312的第一位(即为7),并atoi转换为整数,由此函数还可知,SERIAL中需为数字,因为如果为字母的话,经函数转换会为0
00401507 . 8B4C24 18 mov ecx, dword ptr [esp+18] ; //ptr [esp+18]即为刚刚那个循环中计算出来的累加值
0040150B . 83C4 04 add esp, 4
0040150E . 3BC8 cmp ecx, eax ; //把其累加值和假SERIAL的第一位比较
00401510 . C64424 13 00 mov byte ptr [esp+13], 0
00401515 . 74 05 je short 0040151C ; //上面的比较中,相等则跳
00401517 > C64424 13 01 mov byte ptr [esp+13], 1
0040151C > F6C3 04 test bl, 4 ; //为什么要test bl,4我搞不清楚,请知道的解释下下面这一小段代码!
0040151F . 74 0D je short 0040152E
00401521 . 8D4C24 1C lea ecx, dword ptr [esp+1C]
00401525 . 83E3 FB and ebx, FFFFFFFB
00401528 . FF15 68204000 call dword ptr [<&MFC71.#578_ATL::CSt>; MFC71.ATL::CSimpleStringT<char,1>::~CSimpleStringT<char,1>
0040152E > F6C3 02 test bl, 2
00401531 . 74 0D je short 00401540
00401533 . 8D4C24 20 lea ecx, dword ptr [esp+20]
00401537 . 83E3 FD and ebx, FFFFFFFD
0040153A . FF15 68204000 call dword ptr [<&MFC71.#578_ATL::CSt>; MFC71.ATL::CSimpleStringT<char,1>::~CSimpleStringT<char,1>
00401540 > F6C3 01 test bl, 1
00401543 . C74424 30 FFF>mov dword ptr [esp+30], -1
0040154B . 5B pop ebx
0040154C . 74 0A je short 00401558
0040154E . 8D4C24 20 lea ecx, dword ptr [esp+20]
00401552 . FF15 68204000 call dword ptr [<&MFC71.#578_ATL::CSt>; //以上代码为对释放字符串
00401558 > 8A4424 0F mov al, byte ptr [esp+F]
0040155C . 84C0 test al, al
0040155E . 57 push edi
0040155F . 74 08 je short 00401569
00401561 . 57 push edi
00401562 > 68 60264000 push 00402660 ; name or serial is wrong,try again !
00401567 . EB 21 jmp short 0040158A
00401569 > 68 58264000 push 00402658 ; zeng
0040156E . 8BCD mov ecx, ebp
00401570 . FF15 B4214000 call dword ptr [<&MFC71.#2272_ATL::CS>; //Find函数 int Find( LPCTSTR lpszSub ) ,在某字符串中查找另一字符串,并返回在第几位找到
00401576 . 85C0 test eax, eax ; //eax即为返回值,即在第几位找到其字符串
00401578 . 57 push edi
00401579 . 57 push edi
0040157A .^ 7E E6 jle short 00401562 ; //没找到的话,即跳向失败
0040157C . 68 10264000 push 00402610 ; congratulation ! correct serial number,good job,do next one? :)
00401581 . EB 07 jmp short 0040158A
00401583 > 57 push edi
00401584 . 57 push edi
00401585 . 68 F4254000 push 004025F4 ; name or serial is too short
0040158A > E8 07030000 call <jmp.&MFC71.#1123_AfxMessageBox>
0040158F . 8B4C24 24 mov ecx, dword ptr [esp+24]
00401593 . 5F pop edi
00401594 . 5E pop esi
00401595 . 5D pop ebp
00401596 . 64:890D 00000>mov dword ptr fs:[0], ecx
0040159D . 83C4 24 add esp, 24
004015A0 . C3 retn
再贴上程序中对应代码:
UpdateData(true);
if(m_sName.GetLength() < 6 || m_sSerial.GetLength() < 6)
{
AfxMessageBox(_T("NAME or SERIAL is too short"));
return;
}
int isum=0,itemp1,itemp2,itemp3;
for(int i=0;i<m_sSerial.GetLength();i++)
{
itemp1 = atoi(m_sSerial.Mid(2*i,1));
isum += itemp1;
}
if(m_sSerial.Mid(0,1) != "5" || m_sSerial.Mid(4,2) != "31" || isum != atoi(m_sSerial.Mid(1,1)))
{
AfxMessageBox(_T("NAME or Serial is wrong,Try again !"));
return;
}
if(m_sName.Find("ZENG")>0 )//&& m_sSerial.Mid(3,1) ==itemp1)
{
AfxMessageBox(_T("CONGRATULATION ! CORRECT SERIAL NUMBER,GOOD JOB,DO NEXT ONE? :)"));
}else
{
AfxMessageBox(_T("NAME or Serial is wrong,Try again !"));
}
return; CRECKME 1较CRECKME 0有一点不同就是代码中已经没有出现明码,但是算法仍然不强,且没有防爆破,字符串提醒无加密容易被下断等缺点。------------------------------------------------------------------------------------------------------------------------------------
继续贴CRECKME 2,
1,用插件查看是否有敏感字符串可下断,无。 ctrl+N 查看,MFC函数太多,对用bpx getwindowitem等敏感API函数下段并无作用。
2,消息下段。F9运行程序,出现注册对话框,切回OllyDbg,点快捷菜单中的W,出现控件窗口,选择到“确定”行--->右键--->选择message breakpoint on classproc--->messages中选择到202 WM_LBOTTONUP,确定。 在注册对话框中输入假NAME:zenghw ,假SERIAL: 5705312,点确定,此时OllyDbg被中断,按ALT+M,调出内存窗口,选择到如下一行,并按下F2,
然后再按F9让程序运行,可见程序运行后又马上中断在CRECKME中,按F8单步走,但是否真正运行在CRECKME 2空间中,如不是,再调出内存窗口,重新下断点,按F9运行,如此反复,知道单步走时的确运行在CRECKME 2空间中,这个时候仔细查找,可以发现已到校验代码处,注意那个函数updatadate(当然了,别人程序不一定有这个函数)。
注:用消息断点跟TRACE跟踪跟方便,请参考http://bbs.pediy.com/showthread.php?t=67866&highlight=OLLYDBG
3,代码分析:
00401581 |. 6A 01 push 1
00401583 |. 8BCB mov ecx, ebx
00401585 |. AA stos byte ptr es:[edi]
00401586 |. E8 CB030000 call <jmp.&MFC71.#6236_CWnd::Upd>
0040158B |. 8D4B 74 lea ecx, dword ptr [ebx+74] ; //把有信息窗口可知ptr[ebx+74] = 0013FEF8,由堆栈窗口可知为假NAME:zenghw 赋给ecx
0040158E |. FF15 9C314000 call dword ptr [<&MFC71.#876_ATL>; //把ecx里面的内容赋给eax
00401594 |. 8D5424 10 lea edx, dword ptr [esp+10]
00401598 |> 8A08 /mov cl, byte ptr [eax] ; //eax派上用场了
0040159A |. 40 |inc eax
0040159B |. 880A |mov byte ptr [edx], cl ; //ptr [edx] 初地址为0013F788,注意,后面会用到
0040159D |. 42 |inc edx
0040159E |. 84C9 |test cl, cl ; //当跳转了到字符串末尾时,为0x00,下面语句就不满足
004015A0 |.^ 75 F6 \jnz short 00401598 ; //上面这个循环,为把假NAME:zenghw 字符串赋到ptr[edx]地址
004015A2 |. 8A4424 16 mov al, byte ptr [esp+16] ; //由信息窗口可以看出ptr[esp+16]为地址 0013F78E,由上面循环语句可知此为字符串里的第6个字节(从0字节算起)
004015A6 |. 84C0 test al, al
004015A8 |. 75 50 jnz short 004015FA ; //如果第6个字节不等于0的话,则跳向失败
004015AA |. 8A5424 15 mov dl, byte ptr [esp+15] ; //字符串的第5个字节
004015AE |. 84D2 test dl, dl
004015B0 |. 74 48 je short 004015FA ; //如果第5个字节==0的话,则跳向失败
004015B2 |. 8B43 78 mov eax, dword ptr [ebx+78] ; //由信息窗口知 此地址值为0x00570E60,即为十进制5705312,为假SERIAL
004015B5 |. 3D A0860100 cmp eax, 186A0 ; 与十进制100000比较
004015BA |. 7C 3E jl short 004015FA ; //如果小于则跳向失败
004015BC |. 0FBE7424 12 movsx esi, byte ptr [esp+12] ; //假NAME :zenghw 的第2位 n
004015C1 |. 0FBE4C24 11 movsx ecx, byte ptr [esp+11] ; //假NAME :zenghw 的第1位 e
004015C6 |. 0FBE7C24 14 movsx edi, byte ptr [esp+14] ; //假NAME :zenghw 的第4位 h
004015CB |. 03CE add ecx, esi ; //第2位 加 上第1位,结果放在ecx中
004015CD |. 0FBE7424 10 movsx esi, byte ptr [esp+10] ; //假NAME :zenghw 的第0位 z
004015D2 |. 03CE add ecx, esi ; //把上面加出的结果ecx再加上第0位
004015D4 |. 0FBE7424 13 movsx esi, byte ptr [esp+13] ; //假NAME :zenghw 的第3位 g
004015D9 |. 0FBED2 movsx edx, dl ; //由004015AA行可知,dl即为/假NAME :zenghw 的第5位 w
004015DC |. 03F7 add esi, edi ; //第4位加第3位,结果放在 esi中
004015DE |. 03F2 add esi, edx ; //把上行相加的结果再加上第5位
004015E0 |. 99 cdq ; //扩展,把edx扩展为eax的高位,也就是说变为64位
004015E1 |. BF E8030000 mov edi, 3E8 ; //0X3E8即为1000
004015E6 |. F7FF idiv edi ; //edx eax /edi 注意右边寄存器的变化,此时edx eax为00570E60,即为假SERIAL:5705312
004015E8 |. 3BC8 cmp ecx, eax ; //除完的结果,商放在eax,余数放在edx,这里把除得的商与ecx比较。往回看,ecx即为假NAME第0,1,2位之和,这里显示为0X14D,即为333,把SERIAL改为333312(因为上面是/1000的商,所以333后面只跟3位),重来
004015EA |. 75 0E jnz short 004015FA
004015EC |. 3BF2 cmp esi, edx ; //余数与esi比较,esi即为上面假NAME 3,4,5位的和,这里显示值为 0x146,即326,因此把SERIAL改为333326,重来,测试通过
004015EE |. 75 0A jnz short 004015FA
004015F0 |. 8B03 mov eax, dword ptr [ebx]
004015F2 |. 8BCB mov ecx, ebx
004015F4 |. FF90 54010000 call dword ptr [eax+154]
004015FA |> 8B8C24 940000>mov ecx, dword ptr [esp+94]
00401601 |. E8 43040000 call 00401A49
00401606 |. 5F pop edi
00401607 |. 5E pop esi
00401608 |. 5B pop ebx
00401609 |. 8BE5 mov esp, ebp
0040160B |. 5D pop ebp
0040160C \. C3 retn
附上VC上校验的源代码:
char cTmep[128] = {0xFF};
int iCount1 =0,iCount2 =0;
UpdateData(true);
int i =0;
_tcscpy(cTmep, m_Name);
if(cTmep[6] != 0x00 || cTmep[5] == 0x00) //用于判断长度,故意不用你API的GETLENGTH
return;
if(m_Serial< 100000)//用于判断长度
return;
for(i=0;i< 3;i++)
iCount1 += cTmep[i];
for(i= 3;i< 6;i++)
iCount2 += cTmep[i];
if(iCount1 == m_Serial/1000 && iCount2 == (m_Serial % 1000))//随便算法
{
OnOK();
}
else
return; CRECKME 2较CRECKME 1有一点不同就是代码中不用字符串提醒,但是算法仍然偏简单,且没有防爆破,在004015E8 行等处直接用jmp语句或nop语句或更改为jz,都可以直接爆破。------------------------------------------------------------------------------------------------------------------------------------
继续贴CRECKME 3,
CRECKME 3 较CRECKME 2只是在内存上多做了校验以防止爆破,算法依然简单,因此,在这里就不多讲解了,主要实践下ollydbg的万能断点的下法!
1,用ollydbg加载CRECKME 3.按F9 运行,输入NAME:zenghw,SERIAL:5705312,先不点确定.
2,切回ollydbg,按快捷键ALT+E,在NAME那列随便找到USER32行,选中,按快捷键CTRL+N;
3,选中TranslateMessage行。如下图 ,按SHIFT F4,如下图设置:
4,切至注册窗口,按“确定”,此时ollydbg中断。
5,切回ollydbg,按快捷键ALT+M打开内存窗口,选中第一行,按CTRL+B打开搜索窗口,在ASCILL行输入NAME或SERIAL内容,我这里输入5705312,点确定,会搜索到5705312在内存中的位置,选中5705312,右键,选择break point -->memory access,按F9运行,ollydbg会马上再被中断6,此时右边寄存器窗口可看到5705312,按F8单步走,再一直按CTRL+F9知道程序跑回CHECK ME的领空,此时往上找一点点即为验证序列号的代码。
附上源代码:
char cTmep[128] = {0xFF};
int iCount1 =2,iCount2 =3;
UpdateData(true);
int i =0;
_tcscpy(cTmep, m_Name);
for(int i=0;i<7;i++)
{
if(cTmep[i] == _T('0'))
return;
}
if(cTmep[6] != 0x00 || cTmep[5] == 0x00) //用于判断长度,故意不用你API的GETLENGTH
return;
if(m_Serial< 100000)
return;
for(i=0;i< 3;i++)
iCount1 *= cTmep[i];
for(i= 3;i< 5;i++)
iCount2 *= cTmep[i];
if(iCount1 == m_Serial/100000 && iCount2 == (m_Serial % 100000))
{
OnOK();
}
else
return;
关于防爆破代码的解析由于CRECKME 4也有其内容,因此放在CRECKME 4一起讲。CRECKME 4修正版的注册码有人解析出来了吗? 期盼中!
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: