本帖最后由 Peace 于 2011-8-5 14:59 编辑
【暑假活动后续】"最后的CM"详细解析(追码+爆破) CM的破解探讨
作者:Kris
感谢指导:qaz大大,小生吾怕怕大大
帖子链接:http://bbs.52pojie.cn/thread-101488-1-1.html
废话不多说,俺是菜菜,第一次写破文,写的不对的地方还请大大指教!
如果觉得做得好,求加分!!!!!!!!!!!!!!!!~~~~
首先,我们打开CM看一下
看到这个CM的界面以后,可以明确2点:
1.该CM应该的单机单号,有机器码和注册码,也就是说在点注册按钮的时候应该会计算出真码
2.该CM存在按钮,应该是按下按钮以后才会检测Key是否正确,而不是在输入Key的时候就检测或者用其他方式检测的
明确了这2点后,把CM放到E-Debug中运行,并点击按钮,E-Debug告诉我们按钮事件的位置为004026B7
为什么有那么多个事件,却说按钮事件为004026B7呢?因为我们只点击了一次按钮,在这些事件中只有一次的就只有
事件发生: 00401A11
事件发生: 004026B7
注意,事件发生: 00401A11是在窗体创建的时候就已经发生的事件,而不是在我们点击按钮之后才发生的,因此,按钮事件就为004026B7了
得到了按钮事件的地址,我们可以关闭掉E-Debug了,现在打开今天的主角OD,把CM拖入OD后运行,在按钮事件004026B7处下断点,然后点击按钮,就中断住了
F7单步跟踪,跟到0044F223的时候发现关键代码
调用CreateThread创建线程,堆栈窗口信息如下
线程的地址是40340A,我们在40340A处下断,然后继续让程序运行,很快,OD就中断在40340A了,F8继续跟踪,执行完
00403438 E8 0AF3FFFF call 00402747
出现了可疑字符串,有可能是真码
因此,我们把断点清除一下,然后在00403438处下断,重新载入运行并点击按钮,然后会中断在00403438,这个时候我们F7跟进这个CALL
发现了一大堆算法指令,果断无视它们,写注册机以我的水平简直就是天方夜谭,况且这个CM还是qaz大大写的,因此我们不研究算法,只研究追码和爆破
在算法指令跑完之后,好像没有发现什么可疑的判断真码和假码是否相同的代码,但是在算法结束后,发现了这样一句指令
004028A4 C705 1CFE4A00 01000000 mov dword ptr ds:[0x4AFE1C],0x1
把4AFE1C这个位置赋值为1,至于为什么要这样做,程序里面肯定有用到这个的地方,
因此,我们执行这一句指令的时候,在004AFE1C处下内存访问断点,然后点运行按钮,继续让程序运行
不久后OD中断在
00401F4B 833D 1CFE4A00 01 cmp dword ptr ds:[0x4AFE1C],0x1
00401F52 0F85 4A000000 jnz 00401FA2
这两句的作用是用[4AFE1C]与1作比较,如果不同则跳,
我们知道,在执行完刚才的生成真码的函数以后这里才会变为1,否则这里一直是为0的,因此这里判断它是否为1,很可能就是判断是否已经产生了真码,
也就是说这里很可能将会对真码和假码做比较,看是否相同,所以我们这里继续F7单步步入,不久后又看到了关键代码
以下回复可见
又调用CreateThread创建线程,堆栈窗口信息如下
线程的地址是402612,我们在402612处下断,然后继续让程序运行,很快,OD就中断在402612了,F8继续跟踪,当执行
00402638 E8 41FAFFFF call 0040207E
的时候,OD卡死了,因此这里可能是一个关键CALL,把断点清除一下,然后在这里下断,重新载入运行并点击按钮,不久后中断在这里,F7单步步入,
进入这个函数以后,发现里面有一些CALL一样会导致OD卡死,因此,这里F7单步步入的时候,碰到CALL,全部在CALL的下一句指令下断,然后直接点运行,执行到如下命令的时候会发现
eax=真码(如果要追码的话,这里就可以追到咯^_^),我的真注册码是
185?193?28?51?214?75?60?225?~72?216?51?11?76?117?205?46?133?120?237?7?132?180?113?198?~214?145?46?17?227?174?147?109?13?69?67?72?247?184?72?172?~128?147?0?108?147?136?240?34?203?254?21?237?107?72?81?188?~145?123?116?91?136?92?253?114?110?131?161?9?209?64?160?115?~229?245?181?64?249?155?197?109?161?253?240?171?123?110?43?19?~19?12?130?170?241?95?77?84?252?150?2?31?208?97?11?210?~180?27?9?57?49?207?100?99?87?69?224?179?144?231?73?65?~124?181?30?228?153?206?141?184?144?41?7?43?186?195?95?217?~13?136?198?245?219?216?127?128?206?198?75?4?84?250?20?231?~1?199?126?41?8?114?8?177?
[ebp-0x8]=假码,并且调用函数比较它们,这里就是我们要找的检测Key是否正确的关键代码了,那么我们继续往下执行,注意当执行到
004020DA 0F84 6A000000 je 0040214A
的时候,跳转是未实现的,并且这个跳转是取决于刚才那个比较函数的返回值,因此,这个跳转是一个关键跳,
把断点清除一下,并在这里设置断点,然后先按着程序原来的流程继续跟踪一下,发现不久后弹出了框框,注册失败就会弹出这种框框了,
因此这里就更加清楚了,如果这个跳转是未实现的就是注册失败了,因此我们要爆破它,这个跳转就应该改为JMP
重新载入运行并点击按钮,断在刚才的004020DA处,把je改为JMP,完成第一处关键跳的修改,
继续单步跟踪,执行到402184的时候发现二次效验
很明显00402196 0F85 ED000000 jnz 00402289是关键跳,把它用NOP代替,即可完成爆破
总结一下"最后的CM"注册流程:
1.按下按钮后,触发Button,调用线程,线程再调用BuildKey
2.BuildKey函数生成了Key之后,把CheckKey标志赋值为TRUE
3.在窗体启动的时候,有一个时钟(Timer)始终检测CheckKey标志是否为TRUE,检测到后创建CheackKeyThread线程
4.CheackKeyThread线程调用函数Check,Check为最终判断Key是否正确的函数
//下面是我模拟该程序的C代码
//设置Key已经产生的标志,高级语言里面可以这样写:
//(由于我没学E语言,所以用C语言代替)
//(注:在C中FALSE宏为0,TRUE宏为1)
BOOL CheckKey=FALSE;
//CheckKey为布尔型,并且初始化值为FALSE
void Check()
{
......
效验①
004020AC 50 push eax eax=真码
004020AD FF75 F8 push dword ptr ss:[ebp-0x8] dword ptr ss:[ebp-0x8]=假码
004020B0 E8 2CFFFFFF call 00401FE1 判断是否相同,相同返回0
004020DA 0F84 6A000000 je 0040214A 如果返回不为0,则说明不相同,那么这个je跳转是不实现的,就会无限弹窗了,明显,这个应该改为JMP
......
效验②
00402184 50 push eax eax=假码
00402185 FF35 20FE4A00 push dword ptr ds:[0x4AFE20] dword ptr ss:[ebp-0x8]=真码
0040218B E8 51FEFFFF call 00401FE1 判断是否相同,相同返回0
00402196 0F85 ED000000 jnz 00402289 如果返回不为0,则说明不相同,那么这个je跳转是实现的,那么窗口就会无限乱弹,明显,这个应该改为NOP
......
}
DWORD WINAPI CheackKeyThread()//00402612,检测Key是否正确的线程
{
......
00402638 E8 41FAFFFF call 0040207E ;//调用一个子程序来判断
......
}
Timer()//00401F3D,检测CheckKey是否为TRUE的时钟
{
//00401F4B 833D 1CFE4A00 01 cmp dword ptr ds:[0x4AFE1C],0x1
if(CheckKey==TRUE)//如果CheckKey标志为TRUE
{
CreateThread(NULL,NULL,NULL,(LPTHREAD_START_ROUTINE)CheackKeyThread,NULL,NULL,NULL);
//创建线程创建线程来判断输入的注册码和真码是否相同
}
}
char * BuildKey()//004028ED
{
char Key[256];
BuildKey Code....
CheckKey=TRUE;
//004028A4 C705 1CFE4A00 01000000 mov dword ptr ds:[0x4AFE1C],0x1
//算出Key以后把检测标志改为TRUE,让Timer对Key进行判断
return Key;
}
void Button()//004026B7
{
......//这里调用了一大堆的子程序,最后CALL BuildKey函数
}
演示教程:http://u.115.com/file/e6yps6cn如果觉得做得好,求加分!!!!!!!!!!!!!!!!~~~~
|