[反汇编练习] 160个CrackMe之011.
本系列文章的目的是从一个没有任何经验的新手的角度(其实就是我自己),一步步尝试将160个CrackMe全部破解,如果可以,通过任何方式写出一个类似于注册机的东西。
其中,文章中按照如下逻辑编排(解决如下问题):
1、使用什么环境和工具
2、程序分析
3、思路分析和破解流程
4、注册机的探索
----------------------------------
提醒各位看客: 如果文章中的逻辑看不明白,那你一定是没有亲手操刀!OD中的跳转提示很强大,只要你跟踪了,不用怎么看代码就理解了!
----------------------------------
1、工具和环境:
WinXP SP3 + 52Pojie六周年纪念版OD + PEID + 汇编金手指。
160个CrackMe的打包文件。
下载地址: http://pan.baidu.com/s/1xUWOY 密码: jbnq
注:
1、Win7系统对于模块和程序开启了随机初始地址的功能,会给分析带来很大的负担,所以不建议使用Win7进行分析。
2、以上工具都是在52PoJie论坛下的原版程序,NOD32不报毒,个人承诺绝对不会进行任何和木马病毒相关内容。
2、程序分析:
想要破解一个程序,必须先了解这个程序。所以,在破解过程中,对最初程序的分析很重要,他可以帮助我们理解作者的目的和意图,特别是对于注册码的处理细节,从而方便我们反向跟踪和推导。
和上一节一样,打开CHM,选择第11个Andrénalin.4,保存下来。运行程序,程序界面如下:
3、思路分析和破解流程:
随意点击几个数字,发现没有提示。但是我们可以在右侧的Status: UNREGISTRIERT,我们可以大胆地猜测,如果序列号正常,这个文本应该会变为成功一类的提示。没有什么线索,直接放到OD里进行分析。
1、使用PEID查下壳,Microsoft Visual Basic 5.0 / 6.0,没有壳。
2、打开OD,将exe拖到OD当中,F9运行。
3、由于找不到任何线索,我们还是用“最笨的”查找字符串办法,点击E图标,选中Andrénalin.4.exe,右键->Follow Entry,这样我们就进入了主模块。
4、在反汇编窗口任意位置,右键->中文搜索引擎->智能搜索,发现了0-9的数字和很多类似0817E747D7AFF7C7F82836D74RR7A7F7E7B7C7D826D81KE7B7C和REGISTRIERT的字符串。
5、我们随意选择一个数字,双击进去发现都是类似如下的代码:
00404439 . /7D 12 jge short 0040444D 0040443B . |68 A0000000 push 0xA0 00404440 . |68 B81D4000 push 00401DB8 00404445 . |56 push esi 00404446 . |50 push eax 00404447 . |FF15 2C104000 call dword ptr ds:[<&MSVBVM60.__vbaHresu>; msvbvm60.__vbaHresultCheckObj 0040444D > \8B55 E8 mov edx,dword ptr ss:[ebp-0x18] 00404450 . 8B37 mov esi,dword ptr ds:[edi] 00404452 . 52 push edx 00404453 . 68 1C1E4000 push 00401E1C ; 8 00404458 . FF15 28104000 call dword ptr ds:[<&MSVBVM60.__vbaStrCa>; msvbvm60.__vbaStrCat 0040445E . 8BD0 mov edx,eax 00404460 . 8D4D E4 lea ecx,dword ptr ss:[ebp-0x1C] 00404463 . FF15 BC104000 call dword ptr ds:[<&MSVBVM60.__vbaStrMo>; msvbvm60.__vbaStrMove 00404469 . 50 push eax 0040446A . 57 push edi 0040446B . FF96 A4000000 call dword ptr ds:[esi+0xA4] 00404471 . 85C0 test eax,eax 00404473 . DBE2 fclex 00404475 . 7D 12 jge short 00404489
上下查找,找不到任何与算法跳转相关的东西。
6、字符串0817E747D7AFF7C7F82836D74RR7A7F7E7B7C7D826D81KE7B7C和REGISTRIERT反而更像是注册码相关的东西,只是….这些注册码是不是有些多的太过了!
7、选择第一个REGISTRIERT文本,双击进去,向上浏览代码。哇,似乎和之前的三个都一样啊!太好了,我们大概分析一下:
00404A5B .^\E9 CFFEFFFF jmp 0040492F 00404A60 > 8D55 CC lea edx,dword ptr ss:[ebp-0x34] 00404A63 . 8D85 4CFFFFFF lea eax,dword ptr ss:[ebp-0xB4] 00404A69 . 52 push edx 00404A6A . 50 push eax 00404A6B . C785 54FFFFFF>mov dword ptr ss:[ebp-0xAC],00401E50 ; 0817E747D7AFF7C7F82836D74RR7A7F7E7B7C7D826D81KE7B7C 00404A75 . C785 4CFFFFFF>mov dword ptr ss:[ebp-0xB4],0x8008 ; // 修改eax指针的内容 00404A7F . FF15 5C104000 call dword ptr ds:[<&MSVBVM60.__vbaVarTs>; msvbvm60.__vbaVarTstEq // 字符串比较 00404A85 . 66:85C0 test ax,ax 00404A88 . 74 4C je short 00404AD6 ; // 爆破的关键跳转 00404A8A . 8B45 08 mov eax,dword ptr ss:[ebp+0x8] 00404A8D . 50 push eax 00404A8E . 8B08 mov ecx,dword ptr ds:[eax] 00404A90 . FF91 38030000 call dword ptr ds:[ecx+0x338] 00404A96 . 8D55 AC lea edx,dword ptr ss:[ebp-0x54] 00404A99 . 50 push eax 00404A9A . 52 push edx 00404A9B . FF15 3C104000 call dword ptr ds:[<&MSVBVM60.__vbaObjSe>; msvbvm60.__vbaObjSet 00404AA1 . 8B08 mov ecx,dword ptr ds:[eax] 00404AA3 . 68 BC1E4000 push 00401EBC ; REGISTRIERT 00404AA8 . 50 push eax 00404AA9 . 8985 30FFFFFF mov dword ptr ss:[ebp-0xD0],eax 00404AAF . FF51 54 call dword ptr ds:[ecx+0x54]
哈哈, msvbvm60.__vbaVarTstEq 进行字符串比较test ax,ax和je short 00404AD6完成是否跳转的判断,爆破的关键就是这里啦!选中je语句,右键->Binary->Fill with NOPs。回到程序,神马?已经提示成功了!
4、算法探索
根据msvbvm60.__vbaVarTstEq进行文本比较,我们很容易想到就是通过一个算法生成饿了一个字符串,然后这个字符串与“0817E747D7AFF7C7F82836D74RR7A7F7E7B7C7D826D81KE7B7C”进行比较,若果相等就成功。
我们按照这个思路继续向上分析代码:
004048FD . FF15 30104000 call dword ptr ds:[<&MSVBVM60.__vbaLenVa>; msvbvm60.__vbaLenVar 00404903 . 50 push eax ; // "12321" 00404904 . 8D95 3CFFFFFF lea edx,dword ptr ss:[ebp-0xC4] 0040490A . 8D85 08FFFFFF lea eax,dword ptr ss:[ebp-0xF8] 00404910 . 52 push edx ; 1 00404911 . 8D8D 18FFFFFF lea ecx,dword ptr ss:[ebp-0xE8] 00404917 . 50 push eax ; // 5 00404918 . 8D55 DC lea edx,dword ptr ss:[ebp-0x24] ; UNICODE "0817E747D7A7D7C7F82836D74G47A7F7E7B7C7D826D817E7B7" 0040491B . 51 push ecx ; ecx=1 0040491C . 52 push edx ; 0 0040491D . FF15 38104000 call dword ptr ds:[<&MSVBVM60.__vbaVarFo>; msvbvm60.__vbaVarForInit 00404923 . 8B35 80104000 mov esi,dword ptr ds:[<&MSVBVM60.__vbaSt>; msvbvm60.__vbaStrVarVal 00404929 . 8B1D B4104000 mov ebx,dword ptr ds:[<&MSVBVM60.#617>] ; msvbvm60.rtcLeftCharVar 0040492F > 85C0 test eax,eax 00404931 . 0F84 29010000 je 00404A60 00404937 . 8D45 BC lea eax,dword ptr ss:[ebp-0x44] 0040493A . 6A 01 push 0x1 0040493C . 8D4D 8C lea ecx,dword ptr ss:[ebp-0x74] 0040493F . 50 push eax ; // ‘12321‘ 00404940 . 51 push ecx ; // ‘1‘ 00404941 . FFD3 call ebx ; msvbvm60.rtcLeftCharVar 00404943 . 8D55 8C lea edx,dword ptr ss:[ebp-0x74] 00404946 . 8D45 B0 lea eax,dword ptr ss:[ebp-0x50] 00404949 . 52 push edx 0040494A . 50 push eax 0040494B . FFD6 call esi ; msvbvm60.__vbaStrVarVal 0040494D . 50 push eax 0040494E . FF15 D8104000 call dword ptr ds:[<&MSVBVM60.#581>] ; msvbvm60.rtcR8ValFromBstr 00404954 . DD9D 34FFFFFF fstp qword ptr ss:[ebp-0xCC] 0040495A . 8D4D 9C lea ecx,dword ptr ss:[ebp-0x64] 0040495D . 8D55 DC lea edx,dword ptr ss:[ebp-0x24] 00404960 . 51 push ecx 00404961 . 52 push edx 00404962 . C745 A4 01000>mov dword ptr ss:[ebp-0x5C],0x1 00404969 . C745 9C 02000>mov dword ptr ss:[ebp-0x64],0x2 00404970 . FF15 AC104000 call dword ptr ds:[<&MSVBVM60.__vbaI4Var>; msvbvm60.__vbaI4Var 00404976 . 50 push eax ; eax=1 2 3 4 00404977 . 8D45 BC lea eax,dword ptr ss:[ebp-0x44] 0040497A . 8D4D B8 lea ecx,dword ptr ss:[ebp-0x48] 0040497D . 50 push eax 0040497E . 51 push ecx 0040497F . FFD6 call esi ; msvbvm60.__vbaStrVarVal 00404981 . 50 push eax ; // ‘12321‘ 00404982 . FF15 4C104000 call dword ptr ds:[<&MSVBVM60.#631>] ; msvbvm60.rtcMidCharBstr 00404988 . 8BD0 mov edx,eax 0040498A . 8D4D B4 lea ecx,dword ptr ss:[ebp-0x4C] 0040498D . FF15 BC104000 call dword ptr ds:[<&MSVBVM60.__vbaStrMo>; msvbvm60.__vbaStrMove 00404993 . 50 push eax 00404994 . FF15 20104000 call dword ptr ds:[<&MSVBVM60.#516>] ; msvbvm60.rtcAnsiValueBstr 0040499A . 0FBFD0 movsx edx,ax ; // 取字符的ansii,0x31 0x32 0x33 0x32 0040499D . 8995 FCFCFFFF mov dword ptr ss:[ebp-0x304],edx 004049A3 . C785 7CFFFFFF>mov dword ptr ss:[ebp-0x84],0x5 004049AD . DB85 FCFCFFFF fild dword ptr ss:[ebp-0x304] ; // 转为浮点数,压栈st0 004049B3 . DD9D F4FCFFFF fstp qword ptr ss:[ebp-0x30C] ; // 出栈 004049B9 . DD85 F4FCFFFF fld qword ptr ss:[ebp-0x30C] ; // 浮点加载数,拉取数据 004049BF . DC85 34FFFFFF fadd qword ptr ss:[ebp-0xCC] ; //从堆栈中弹出这二个操作数,然后把计算的“和”压入堆栈,即:ST=ST(1)+ST 004049C5 . DD5D 84 fstp qword ptr ss:[ebp-0x7C] ; // 出栈,50.0,51, 52,51 004049C8 . DFE0 fstsw ax ; // ax=0x100 004049CA . A8 0D test al,0xD ; // 不知道为什么是和0xD比较,但是不用咱关心 004049CC . 0F85 FA1F0000 jnz 004069CC ; 跳到异常处理,正常不跳 004049D2 . 8D85 7CFFFFFF lea eax,dword ptr ss:[ebp-0x84] 004049D8 . 50 push eax 004049D9 . FF15 94104000 call dword ptr ds:[<&MSVBVM60.#572>] ; msvbvm60.rtcHexBstrFromVar 004049DF . 8D4D CC lea ecx,dword ptr ss:[ebp-0x34] 004049E2 . 8985 74FFFFFF mov dword ptr ss:[ebp-0x8C],eax 004049E8 . 8D95 6CFFFFFF lea edx,dword ptr ss:[ebp-0x94] 004049EE . 51 push ecx ; // ‘0‘ ‘032‘ // 上一次保存的值 004049EF . 8D85 5CFFFFFF lea eax,dword ptr ss:[ebp-0xA4] 004049F5 . 52 push edx ; // ‘33‘ = 0x32 + 1 // 浮点数的hex值 004049F6 . 50 push eax ; // ‘032‘ 004049F7 . C785 6CFFFFFF>mov dword ptr ss:[ebp-0x94],0x8 00404A01 . FF15 84104000 call dword ptr ds:[<&MSVBVM60.__vbaVarCa>; msvbvm60.__vbaVarCat 00404A07 . 8BD0 mov edx,eax ; // ‘0323334‘ 00404A09 . 8D4D CC lea ecx,dword ptr ss:[ebp-0x34] 00404A0C . FFD7 call edi ; msvbvm60.__vbaVarMove // 将结果存到[ebp-0x34] 00404A0E . 8D4D B0 lea ecx,dword ptr ss:[ebp-0x50] 00404A11 . 8D55 B4 lea edx,dword ptr ss:[ebp-0x4C] 00404A14 . 51 push ecx 00404A15 . 8D45 B8 lea eax,dword ptr ss:[ebp-0x48] 00404A18 . 52 push edx 00404A19 . 50 push eax 00404A1A . 6A 03 push 0x3 00404A1C . FF15 9C104000 call dword ptr ds:[<&MSVBVM60.__vbaFreeS>; msvbvm60.__vbaFreeStrList 00404A22 . 8D8D 6CFFFFFF lea ecx,dword ptr ss:[ebp-0x94] 00404A28 . 8D95 7CFFFFFF lea edx,dword ptr ss:[ebp-0x84] 00404A2E . 51 push ecx 00404A2F . 8D45 8C lea eax,dword ptr ss:[ebp-0x74] 00404A32 . 52 push edx 00404A33 . 8D4D 9C lea ecx,dword ptr ss:[ebp-0x64] 00404A36 . 50 push eax 00404A37 . 51 push ecx 00404A38 . 6A 04 push 0x4 00404A3A . FF15 14104000 call dword ptr ds:[<&MSVBVM60.__vbaFreeV>; msvbvm60.__vbaFreeVarList 00404A40 . 83C4 24 add esp,0x24 00404A43 . 8D95 08FFFFFF lea edx,dword ptr ss:[ebp-0xF8] 00404A49 . 52 push edx 00404A4A . 8D85 18FFFFFF lea eax,dword ptr ss:[ebp-0xE8] 00404A50 . 8D4D DC lea ecx,dword ptr ss:[ebp-0x24] 00404A53 . 50 push eax 00404A54 . 51 push ecx 00404A55 . FF15 C8104000 call dword ptr ds:[<&MSVBVM60.__vbaVarFo>; msvbvm60.__vbaVarForNext 00404A5B .^ E9 CFFEFFFF jmp 0040492F 00404A60 > 8D55 CC lea edx,dword ptr ss:[ebp-0x34] 00404A63 . 8D85 4CFFFFFF lea eax,dword ptr ss:[ebp-0xB4] 00404A69 . 52 push edx 00404A6A . 50 push eax 00404A6B . C785 54FFFFFF>mov dword ptr ss:[ebp-0xAC],00401E50 ; 0817E747D7AFF7C7F82836D74RR7A7F7E7B7C7D826D81KE7B7C 00404A75 . C785 4CFFFFFF>mov dword ptr ss:[ebp-0xB4],0x8008 ; // 修改eax指针的内容 00404A7F . FF15 5C104000 call dword ptr ds:[<&MSVBVM60.__vbaVarTs>; msvbvm60.__vbaVarTstEq // 字符串比较 00404A85 . 66:85C0 test ax,ax 00404A88 . 74 4C je short 00404AD6 ; // 爆破的关键跳转 00404A8A . 8B45 08 mov eax,dword ptr ss:[ebp+0x8] 00404A8D . 50 push eax 00404A8E . 8B08 mov ecx,dword ptr ds:[eax] 00404A90 . FF91 38030000 call dword ptr ds:[ecx+0x338] 00404A96 . 8D55 AC lea edx,dword ptr ss:[ebp-0x54] 00404A99 . 50 push eax 00404A9A . 52 push edx 00404A9B . FF15 3C104000 call dword ptr ds:[<&MSVBVM60.__vbaObjSe>; msvbvm60.__vbaObjSet 00404AA1 . 8B08 mov ecx,dword ptr ds:[eax] 00404AA3 . 68 BC1E4000 push 00401EBC ; REGISTRIERT
具体VB函数请参考前几节的说明,这里只针对算法部分分析。算法其实内容也很简单,就是在代码:
00404994 . FF15 20104000 call dword ptr ds:[<&MSVBVM60.#516>] ; msvbvm60.rtcAnsiValueBstr 0040499A . 0FBFD0 movsx edx,ax ; // 取字符的ansii,0x31 0x32 0x33 0x32 0040499D . 8995 FCFCFFFF mov dword ptr ss:[ebp-0x304],edx 004049A3 . C785 7CFFFFFF>mov dword ptr ss:[ebp-0x84],0x5 004049AD . DB85 FCFCFFFF fild dword ptr ss:[ebp-0x304] ; // 转为浮点数,压栈st0 004049B3 . DD9D F4FCFFFF fstp qword ptr ss:[ebp-0x30C] ; // 出栈 004049B9 . DD85 F4FCFFFF fld qword ptr ss:[ebp-0x30C] ; // 浮点加载数,拉取数据 004049BF . DC85 34FFFFFF fadd qword ptr ss:[ebp-0xCC] ; //从堆栈中弹出这二个操作数,然后把计算的“和”压入堆栈,即:ST=ST(1)+ST 004049C5 . DD5D 84 fstp qword ptr ss:[ebp-0x7C] ; // 出栈,50.0,51, 52,51 004049C8 . DFE0 fstsw ax ; // ax=0x100 004049CA . A8 0D test al,0xD ; // 不知道为什么是和0xD比较,但是不用咱关心 004049CC . 0F85 FA1F0000 jnz 004069CC ; 跳到异常处理,正常不跳 004049D2 . 8D85 7CFFFFFF lea eax,dword ptr ss:[ebp-0x84] 004049D8 . 50 push eax 004049D9 . FF15 94104000 call dword ptr ds:[<&MSVBVM60.#572>] ; msvbvm60.rtcHexBstrFromVar
进行浮点数加法,具体内容是每个字符的ANSII码,加上一个固定的值1,然后格式化为开头为0,然后每个字符加之后的值格式化为2位十六进制文本,最后组合起来的文本与之前的固定文本进行比较,如果成功则注册成功!
似乎很简单啊!我们尝试C/CPP反计算序列号:
#include "stdafx.h" #include "iostream" int hex2int(char cAnsi) { int nRet = ‘*‘; if ( cAnsi >= ‘0‘ && cAnsi <= ‘9‘) { nRet = cAnsi - ‘0‘; }else if ( cAnsi >= ‘A‘ && cAnsi<= ‘Z‘ ) { nRet = cAnsi - ‘A‘ + 10; }else if ( cAnsi >= ‘a‘ && cAnsi<= ‘z‘ ) { nRet = cAnsi - ‘A‘ + 10; } return nRet; } int _tmain(int argc, _TCHAR* argv[]) { char pCmp[] = "0817E747D7AFF7C7F82836D74RR7A7F7E7B7C7D826D81KE7B7C"; // 第一个一定是0,不做处理 char pKey[100] = {0}; char cHex[3] = {0}; // 转换为整数,然后减一的值作为ANSII码组成字符串 int nLen = strlen(pCmp); printf("Len: %d\r\n",nLen); int nCode = 0; for( int i=0;i<nLen/2;i++ ) { cHex[0] = pCmp[1+i*2]; cHex[1] = pCmp[1+i*2+1]; nCode = hex2int(pCmp[1+i*2])*0x10 + hex2int(pCmp[1+i*2+1]); pKey[i] = nCode - 1; // 1,12, } printf("Key:%s\r\n",pKey); system("pause"); return 0; }
计算出来一堆乱七八糟的文本,不管了,…..可是我们无法输入进去啊!坑啊!
回想之前的步骤,是不是还有很多比较的文本和“REGISTR”字样的文本?好吧,我们也尝试一下….
我直接说结果吧!他们的代码几乎就一样的,只是中间加的固定值不太一样,所以,我们多换几个(其实C/CPP中也已经试过了!),试过之后发现,还是无法计算出一个使0-9组成的字符串,真心无语了!
到了这里,说实在的,真的没什么办法了!我的算法似乎也找到了,但是为什么不对呢?本着【相信CracMe作者,怀疑自己的态度】,到网上搜索还有没有更好的反汇编VB的程序,答案是:没有!
但是,找到了一个可以跟踪VB事件的程序,叫做VB程序调试工具(SmartCheck),抱着试一试的态度,还真看出来了一些门道:
建议感兴趣的自己去下一个看看,一定要再输入完成后停止捕捉事件,然后等待日志完成。拉到最后几个,随意选择一个事件,按照顺序查看,你会很惊奇地发现:
比如我输入的注册码为12321,程序进行了如下步骤:
1、 取第1个字符‘1’,转换为ANSII为0x31,然后加上1
2、 取第2个字符‘2’,转换为ANSII为0x32,然后加上1
3、 取第3个字符‘3’,转换为ANSII为0x33,然后加上1
4、 取第4个字符‘2’,转换为ANSII为0x32,然后加上1
5、 取第5个字符‘1’,转换为ANSII为0x31,然后加上1
6、 取第1个字符‘1’,转换为ANSII为0x31,然后加上12
7、 取第2个字符‘2’,转换为ANSII为0x32,然后加上12
8、 取第3个字符‘3’,转换为ANSII为0x33,然后加上12
…….
然后每个字符都加上 123,再一次每个字符加上1232,再一次每个字符加上12321,结束。
我们可以大胆地猜测,每次循环都将我们之前分析的算法使用上,进行字符串比较,然后相等的才是正确的注册码!
我实在无语了!多么坑的作者才会做出这么OOXX的程序,反正按照这个思路我是找不到的。暴力遍历的思路是有的,但是一想到一共51个字符,算下来这个注册码应该的长度是25个数字或者*#号,我就有一种蛋蛋的忧伤!ByeBye 不伺候你了!活该被标记了一个大大的问号!
BY 笨笨D幸福
[反汇编练习] 160个CrackMe之011