[软件名称]: CRECKME 0  CRECKME 0 程序和分析源码.zip.


[应用平台]:Win9X/NT/2000/XP


[软件大小]:8K


[破解声明]:为了检验自己的逆向破解能力,请求斑竹给个邀请码,鼓励我继续写下去。顺便感谢一下党,感谢国家,感谢我们家的曾曾。


[破解工具]:PEiD,IDA 5.5,PE Explore


[备注] 破解软件来源于看雪论坛中的帖子 (本人原创CRACKME系列 难度由0--9,看你属于哪一级)


[作者]:INightElf


[破解日期]:2012-08-06


前言:


   之前一段时间都在破解MASM32 / TASM32之类的软件,由于比较简单,很快就无法引起我的兴趣,在看雪论坛闲逛,发现有网友发(原创CRACKME系列 难度由0--9,看你属于哪一级)帖子,该破解软件由MFC 编译的,刚好用来检验本人掌握的MFC原理是否扎实,也来检验自己的破解水平。


言归正传!下面开始我们的破解之旅



1.查壳,用PEid 检测,编译器为编译器Microsoft Visual C++ 7.0 Method2 [调试],无壳。




2.用PE Explore 查看软件的资源,如图。可见该软件有三个对话框,其中编号为129的对话框为主要窗口, 验证输入的Name 和Serial是否正确。请记下Name控件的ID 为1000,Serial 控件的ID 为1001,确定按钮控件ID为1,取消按钮ID 为2。当输入的Name和Serial 正确,则弹出编号为102的对话框。编号为100则是关于对话框.请记下上面描述的ID号。


这是为了后面的反汇编代码中处理按钮消息做准备,请注意这里的ID 号是10进制





3.用IDA 反汇编CrackMe0,得到CrackMe0反汇编清单,熟悉MFC编程的人都知道,一般程序都会把初始化放到CXXXApp::InitInstance和CXXXApp::IInitApplication 这两个函数,而CXXXApp则继承了CWinApp,并在CXXXApp::InitInstance 中调用父类的CWinApp::InitInstance,在CXXXApp:: IInitApplication 中调用父类的CWinApp:: IInitApplication。所以逆向的一步,则是找到CWinApp::InitInstance和CWinApp:: IInitApplication这两个函数,然后通过交叉引用注释,查找到CXXXApp::InitInstance和CWinApp:: IInitApplication函数所在地址。





由于IDA强大的反汇编库函数,只要对Function Name 列表进行排序,很容易就找到了,在CrackMe0函数中,程序的初始化放到CXXXApp::InitInstance中。导航到交叉引用函数sub_401090,该函数就是CXXXApp::InitInstance。并对该函数进行重命名为CXXXWinApp::InitInstance。




4.在CXXXWinApp::InitInstance函数中仔细观察,看到了地址0x004010EE 调用了CDialog::DoModal(void),表明在该地址前面创建了对话框。在text:004010DA行有一个Call 函数指令,表明了该函数创建了对话框 


.text:004010EE call?DoModal@CDialog@@UAEHXZ ; CDialog::DoModal(void)


导航进去该函数查看。果然验证了之前的推测. .text:004014CE 地址的ID为129,根据步骤2的ID比较,可以断定创建的窗口为即将破解的主窗口, 其中CrackMeMainDialogVirtualFunctionTable为主窗口的虚函数表


.text:004014EC   mov     dword ptr [esi], offset CrackMeMainDialogVirtualFunctionTable ; 虚函数表格


CrackMeMainDialogVirtualFunctionTable 函数名为本人重新命名的,请注意。为什么在这里要特别强调主窗口的虚函数表呢?这是非常关键的一步。猜猜!嘻嘻。


当当当……….谜底是(为了获取确定按钮的消息处理函数) 。是不是觉得我很无聊呢。






.text:004014B0 CreateCrackMeMainDialog proc near       ; CODE XREF: CXXXWinApp::InitInstance(void)+4A p


.text:004014B0


.text:004014B0 var_10          = dword ptr -10h


.text:004014B0 var_C           = dword ptr -0Ch


.text:004014B0 var_4           = dword ptr -4


.text:004014B0 arg_0           = dword ptr  4


.text:004014B0


.text:004014B0                 push    0FFFFFFFFh


.text:004014B2                 push    offset SEH_4014B0


.text:004014B7                 mov     eax, large fs:0


.text:004014BD                 push    eax


.text:004014BE                 mov     large fs:0, esp


.text:004014C5                 push    ecx


.text:004014C6                 mov     eax, [esp+10h+arg_0]


.text:004014CA                 push    esi


.text:004014CB                 push    eax


.text:004014CC                 mov     esi, ecx


.text:004014CE                 push    129             ; hDlgTemplateId 对话框模板ID,在这里也是破解的主窗口


.text:004014D3                 mov     [esp+1Ch+var_10], esi


.text:004014D7                 call    ??0CDialog@@QAE@IPAVCWnd@@@Z ; CDialog::CDialog(uint,CWnd *)


.text:004014DC                 push    offset unk_403940


.text:004014E1                 lea     ecx, [esi+74h]


.text:004014E4                 mov     [esp+18h+var_4], 0


.text:004014EC                 mov     dword ptr [esi], offset CrackMeMainDialogVirtualFunctionTable ; 虚函数表格


.text:004014F2                 call    ds:??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@PBD@Z ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>(char const *)


.text:004014F8                 mov     ecx, [esp+14h+var_C]


.text:004014FC                 mov     dword ptr [esi+78h], 0


.text:00401503                 mov     eax, esi


.text:00401505                 pop     esi


.text:00401506                 mov     large fs:0, ecx


.text:0040150D                 add     esp, 10h


.text:00401510                 retn    4


.text:00401510 CreateCrackMeMainDialog endp




5.导航到CrackMeMainDialogVirtualFunctionTable 虚函数表格。


在地址


.rdata:0040380C dd offset ?GetTypeLib@CCmdTarget@@UAEJKPAPAUITypeLib@@@Z ; CCmdTarget::GetTypeLib(ulong,ITypeLib * *)


和地址 


.rdata:00403814 dd offset ?GetCommandMap@CCmdTarget@@MBEPBUAFX_OLECMDMAP@@XZ ; CCmdTarget::GetCommandMap(void)


之间的函数就是主窗口消息栈区函数。请注意该函数已经被本人重新命名过。


.rdata:00403810                 dd offset j_?GetMessageMap@CrackMeMainDialog@@MBEPBUAFX_MSGMAP@@XZ ; CrackMeMainDialog::GetMessageMap(void)




6.导航到CrackMeMainDialog::GetMessageMap(void) 函数,在该函数里面的地址.text:00401545 设置断点,并执行程序。


text:00401540 ; protected: virtual struct AFX_MSGMAP const * __thiscall CrackMeMainDialog::GetMessageMap(void)const


.text:00401540 j_?GetMessageMap@CrackMeMainDialog@@MBEPBUAFX_MSGMAP@@XZ proc near


.text:00401540                 mov     eax, offset off_403780


.text:00401545                 retn


.text:00401545 j_?GetMessageMap@CrackMeMainDialog@@MBEPBUAFX_MSGMAP@@XZ endp


则程序中断在地址text:00401545,查看此时的EAX寄存器值,[EAX+4] == 0x00403784 的值就是MFC 对话框的消息处理函数列表。





在栈区Jump到地址0x00403784





在栈区Jump到地址0x00403788




在这里必须说明以下MFC消息处理函数的结构,定义如下


#define ON_CONTROL(wNotifyCode, id, memberFxn) \


  { WM_COMMAND, (WORD)wNotifyCode, (WORD)id, (WORD)id, AfxSigCmd_v, \


    (static_cast< AFX_PMSG > (memberFxn)) },



#define WM_COMMAND     0x0111  发送命令消息,一般控件按钮消息都是通过这个命令发送的。


Id 就是按钮的ID号, memberFxn 就是按钮的消息处理函数。


从上图和步骤二可以知道,确定按钮的ID号为1,所以地址0x004015A0 为确定按钮的消息处理函数。本人把该函数重新命名为BtnOK



7.  十万长征终于完成了一半,导航到函数地址,剩下的工作就是分析算法了。


.text:004015A0 ; 确定按钮事件


.text:004015A0


.text:004015A0 BtnOK           proc near


.text:004015A0


.text:004015A0 ; FUNCTION CHUNK AT .text:004015D5 SIZE 00000034 BYTES


.text:004015A0


.text:004015A0                 push    esi


.text:004015A1                 push    edi


.text:004015A2                 push    1


.text:004015A4                 mov     esi, ecx


.text:004015A6                 call    ?UpdateData@CWnd@@QAEHH@Z ; CWnd::UpdateData(int)


.text:004015AB                 lea     edi, [esi+74h]  ; 获取Name 字段的内容


.text:004015AE                 mov     ecx, edi


.text:004015B0                 call    ds:?GetLength@?$CSimpleStringT@D$00@ATL@@QBEHXZ ; ATL::CSimpleStringT<char,1>::GetLength(void)


.text:004015B6                 cmp     eax, 6          ; name 字段的字符串的长度必须大于等于6


.text:004015B9                 jge     short loc_4015D5


.text:004015BB                 cmp     dword ptr [esi+78h], 186A0h


.text:004015C2                 jge     short loc_4015D5


.text:004015C4                 push    0               ; unsigned int


.text:004015C6                 push    0               ; unsigned int


.text:004015C8                 push    offset aNameOrSerialIs ; "Name or Serial is too short!"


.text:004015CD                 call    ?AfxMessageBox@@YGHPBDII@Z ; AfxMessageBox(char const *,uint,uint)


.text:004015D2


.text:004015D2 loc_4015D2:


.text:004015D2                 pop     edi


.text:004015D3                 pop     esi


.text:004015D4                 retn


.text:004015D4 BtnOK           endp


.text:004015D4


.text:004015D5 ; ---------------------------------------------------------------------------


.text:004015D5 ; START OF FUNCTION CHUNK FOR BtnOK


.text:004015D5


.text:004015D5 loc_4015D5:                             ; Name 字段的内容与"IndolentAfternoon" 字符串比较,


.text:004015D5                 push    offset aIndolentaftern ; 一致则继续比较Serial,从这里就说明了Name 必须=='IndolentAfternoon'


.text:004015DA                 mov     ecx, edi


.text:004015DC                 call    ds:?Compare@?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QBEHPBD@Z ; ATL::CStringT<char,StrTraitMFC_DLL<char,ATL::ChTraitsCRT<char>>>::Compare(char const *)


.text:004015E2                 test    eax, eax


.text:004015E4                 jnz     short loc_4015D2


.text:004015E6                 cmp     dword ptr [esi+78h], 5687255 ; 获取Serial 字符串与5687255 常量比较,一致则说明了输入的Serial


.text:004015E6                                         ; 是正确的,并给出成功的提示符,证明了 Serial  ==5687255


.text:004015ED                 jnz     short loc_4015D2


.text:004015EF                 push    0               ; unsigned int


.text:004015F1                 push    0               ; unsigned int


.text:004015F3                 push    offset aCongratulation ; "Congratulation! Correct Serial Num,do n"...


.text:004015F8                 call    ?AfxMessageBox@@YGHPBDII@Z ; AfxMessageBox(char const *,uint,uint)


.text:004015FD                 mov     eax, [esi]


.text:004015FF                 pop     edi


.text:00401600                 mov     ecx, esi


.text:00401602                 pop     esi


.text:00401603                 jmp     dword ptr [eax+154h]


.text:00401603 ; END OF FUNCTION CHUNK FOR BtnOK



8.前面分析了那么多步骤只是为了获取按钮消息事件而已,感觉有点学院派的作风,其实可以用很多简单的方法来获取。但是就无法理解MFC消息处理机制了。总结前面的步骤


第1步获取: CWinApp::InitInstance函数地址


第2步获取: CXXXWinApp::InitInstance函数地址


第3步获取: 主对话框的虚函数表CrackMeMainDialogVirtualFunctionTable


第4步获取: 主对话框的CrackMeMainDialog::GetMessageMap(void)const 函数


第5步获取: 获取确定按钮事件.text:004015A0  BtnOK 


很多MFC程序都可以用前面的5个步骤来获取按钮的消息,这个是本人总结出来的宝贵经验,现在无偿奉献给大家。明天把算法的步骤补上,其实该算法特别简单。但是写到现在我已经没有精力写下去了。累!



9.算法太简单了,在BtnOK函数中直接给出明文比较,通过分析可以得出 Name ==” IndolentAfternoon” ,Serial == 5687255   不需要注册机。