wswm 发表于 2010-5-29 14:42

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一起讲



justu 发表于 2010-5-30 20:06

把答案贴出来干嘛~

太虚伪了 发表于 2010-5-30 20:14

这不是看雪那边的老帖子了么。。。

听海 发表于 2010-7-27 10:20

哦。。。

我喜欢

perfectiris 发表于 2010-7-27 11:04

CRECKME 5 没搞动。。被VMP的乱七八糟。

咖啡没拿铁 发表于 2014-3-14 08:47

这本书确实值得深入研究
页: [1]
查看完整版本: CRACKME系列 难度由0--9,看你属于哪一级?