CRACKME系列 难度由0--9,看你属于哪一级?
标 题: 【转载】本人原创CRACKME系列 难度由0--9,看你属于哪一级? (更新CRACKME 7的程序实现,附源码)作 者: zenghw
时 间: 2009-07-20,17:26:52
链 接: http://bbs.pediy.com/showthread.php?t=93936
买回《加密与解密 第三版》已经半个多月了,每天晚上下班都会抽出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 ,可知无加壳(当然,一些壳也能伪装这些信息,以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 ;//由信息窗口可知ptr为地址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 , 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 , 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
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 , edi
004013F5 .E8 A2040000 call <jmp.&MFC71.#6236_CWnd::UpdateDa>;//UpdateData函数,参数为1时,表示把当前控件内容更新入变量中
004013FA .8D6E 78 lea ebp, dword ptr ;//由信息窗口可知ptr为地址0013FEF8,再由右下角堆栈串口可知0013FEF8为我们输入的假NAME,我这里为zenghw,在command中输入D 0013FEF8为什么不能显示?
004013FD .8BCD mov ecx, ebp
004013FF .896C24 18 mov dword ptr , ebp ;//把让ptr指针指向假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 , 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 x 2
0040143C .50 push eax ;//为mid第一个参数,nFirst,
0040143D .8D4C24 20 lea ecx, dword ptr
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
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
0040146C .03DD add ebx, ebp ;//有上面分析可知,ebp即为mid函数的返回值
0040146E .8BCE mov ecx, esi ;//ESI为假SERIAL 5705312,由后面分析可知,赋给ecx是为了得到它的长度
00401470 .895C24 14 mov dword ptr , ebx ;//从00401468,0040146C两行可以知道,ebx为ebx+dword ptr 的值,在从本句,可知其为一个累加
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
0040147F .8B6C24 1C mov ebp, dword ptr ;//由信息窗口可知ptr为地址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
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 , edi
004014A5 .895C24 1C mov dword ptr , 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
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 , ebx
004014C8 .BB 03000000 mov ebx, 3
004014CD .68 88264000 push 00402688 ;31
004014D2 .8BC8 mov ecx, eax
004014D4 .895C24 1C mov dword ptr , 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
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 ;//ptr 即为刚刚那个循环中计算出来的累加值
0040150B .83C4 04 add esp, 4
0040150E .3BC8 cmp ecx, eax ;//把其累加值和假SERIAL的第一位比较
00401510 .C64424 13 00mov byte ptr , 0
00401515 .74 05 je short 0040151C ;//上面的比较中,相等则跳
00401517 >C64424 13 01mov byte ptr , 1
0040151C >F6C3 04 test bl, 4 ;//为什么要test bl,4我搞不清楚,请知道的解释下下面这一小段代码!
0040151F .74 0D je short 0040152E
00401521 .8D4C24 1C lea ecx, dword ptr
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
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 , -1
0040154B .5B pop ebx
0040154C .74 0A je short 00401558
0040154E .8D4C24 20 lea ecx, dword ptr
00401552 .FF15 68204000 call dword ptr [<&MFC71.#578_ATL::CSt>;//以上代码为对释放字符串
00401558 >8A4424 0F mov al, byte ptr
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
00401593 .5F pop edi
00401594 .5E pop esi
00401595 .5D pop ebp
00401596 .64:890D 00000>mov dword ptr fs:, 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:
00401586|.E8 CB030000 call <jmp.&MFC71.#6236_CWnd::Upd>
0040158B|.8D4B 74 lea ecx, dword ptr ;//把有信息窗口可知ptr = 0013FEF8,由堆栈窗口可知为假NAME:zenghw 赋给ecx
0040158E|.FF15 9C314000 call dword ptr [<&MFC71.#876_ATL>;//把ecx里面的内容赋给eax
00401594|.8D5424 10 lea edx, dword ptr
00401598|>8A08 /mov cl, byte ptr ;//eax派上用场了
0040159A|.40 |inc eax
0040159B|.880A |mov byte ptr , cl ;//ptr 初地址为0013F788,注意,后面会用到
0040159D|.42 |inc edx
0040159E|.84C9 |test cl, cl ;//当跳转了到字符串末尾时,为0x00,下面语句就不满足
004015A0|.^ 75 F6 \jnz short 00401598 ;//上面这个循环,为把假NAME:zenghw 字符串赋到ptr地址
004015A2|.8A4424 16 mov al, byte ptr ;//由信息窗口可以看出ptr为地址 0013F78E,由上面循环语句可知此为字符串里的第6个字节(从0字节算起)
004015A6|.84C0 test al, al
004015A8|.75 50 jnz short 004015FA ;//如果第6个字节不等于0的话,则跳向失败
004015AA|.8A5424 15 mov dl, byte ptr ;//字符串的第5个字节
004015AE|.84D2 test dl, dl
004015B0|.74 48 je short 004015FA ;//如果第5个字节==0的话,则跳向失败
004015B2|.8B43 78 mov eax, dword ptr ;//由信息窗口知 此地址值为0x00570E60,即为十进制5705312,为假SERIAL
004015B5|.3D A0860100 cmp eax, 186A0 ;与十进制100000比较
004015BA|.7C 3E jl short 004015FA ;//如果小于则跳向失败
004015BC|.0FBE7424 12 movsx esi, byte ptr ;//假NAME :zenghw 的第2位 n
004015C1|.0FBE4C24 11 movsx ecx, byte ptr ;//假NAME :zenghw 的第1位 e
004015C6|.0FBE7C24 14 movsx edi, byte ptr ;//假NAME :zenghw 的第4位 h
004015CB|.03CE add ecx, esi ;//第2位 加 上第1位,结果放在ecx中
004015CD|.0FBE7424 10 movsx esi, byte ptr ;//假NAME :zenghw 的第0位 z
004015D2|.03CE add ecx, esi ;//把上面加出的结果ecx再加上第0位
004015D4|.0FBE7424 13 movsx esi, byte ptr ;//假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
004015F2|.8BCB mov ecx, ebx
004015F4|.FF90 54010000 call dword ptr
004015FA|>8B8C24 940000>mov ecx, dword ptr
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 = {0xFF};
int iCount1 =0,iCount2 =0;
UpdateData(true);
int i =0;
_tcscpy(cTmep, m_Name);
if(cTmep != 0x00 || cTmep == 0x00) //用于判断长度,故意不用你API的GETLENGTH
return;
if(m_Serial< 100000)//用于判断长度
return;
for(i=0;i< 3;i++)
iCount1 += cTmep;
for(i= 3;i< 6;i++)
iCount2 += cTmep;
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 = {0xFF};
int iCount1 =2,iCount2 =3;
UpdateData(true);
int i =0;
_tcscpy(cTmep, m_Name);
for(int i=0;i<7;i++)
{
if(cTmep == _T('0'))
return;
}
if(cTmep != 0x00 || cTmep == 0x00) //用于判断长度,故意不用你API的GETLENGTH
return;
if(m_Serial< 100000)
return;
for(i=0;i< 3;i++)
iCount1 *= cTmep;
for(i= 3;i< 5;i++)
iCount2 *= cTmep;
if(iCount1 == m_Serial/100000 && iCount2 == (m_Serial % 100000))
{
OnOK();
}
else
return;
关于防爆破代码的解析由于CRECKME 4也有其内容,因此放在CRECKME 4一起讲
把答案贴出来干嘛~ 这不是看雪那边的老帖子了么。。。 哦。。。
我喜欢 CRECKME 5 没搞动。。被VMP的乱七八糟。 这本书确实值得深入研究
页:
[1]