管理员
|
阅读:2376回复:0
Crack之初体验-第三课
楼主#
更多
发布于:2011-12-16 17:59
 | |  |  | 现在已经是凌晨两点了,不知道该不该在这个时候来做这个第三课的教程。想睡觉了,可是寝室里的兄弟都在dota开黑,他们吵着,我肯定睡不着;想说来写吧,害怕一个不小心多说了些话,鸡就叫了、天就亮了。重庆这几天只有几度,冷得蛋紧蛋紧的,准备盖两床被子了,幸好我把笔记本搬到床上了,窝床里要稍微暖和点。要是有个媳妇一起睡,那样就更暖和了,哎,不提这事也罢。。。。借看雪宝地打个广告,本男常年求媳妇,有意者请联系XXXXXXX,此广告长期有效。(似乎这论坛里没几个女人吧。。。。。????)
书接上文,言归正传。第一课的时候我说过,第二课的终极目标是写出注册机,或者起码要整理出注册代码,用你所熟悉的编程语言表述出来。可是实际上我们在第二课中并没有完成这个任务。
上一课我们仅仅是“侥幸”在寄存器以及堆栈中得知了正确的注册码,有个细节不知道大家注意到没有,就是程序其实只取了我们输入进去的字符的前9个字符,不管你在输入框中输入多少东西,它都只取了前9个字符进去,呵呵,细心点就应该发现了。对于这个程序来说,也许取几个字符进去并不重要,但是以后也许我们会遇到这种比较重要的问题。
因为上次那个crackme是非常very很简单的,它只有一个输入框,我们输入注册码后程序直接拿进去与正确的注册码比较,这种比较方式我们称之为:明码比较!现在我们初学的时候都先学习这种明码比较的例子,等技术成熟了我们再学习暗码比较(明码比较的对立面是不是叫暗码比较??不清楚。。。)。
这节课我们将学习一个name-serial的例子,我个人比较倾向于不讲纯理论,读了一二十年的书了,每次一读到理论性的东西就想睡觉,每次一看到举了个例子或者故事就马上看看。不知道大家是不是跟我一个情况,反正我就是这样,似乎没救了。。。。
引用: 所以呢,我们的课程始终都会以一个(或者几个)具体的例子为载体,通过对具体实例的分析来学知识,用不到的知识我绝对不会讲,如果你有兴趣,自己去学习。所有知识我都只在需要用得地方才讲(这句话似乎上节课就说过一次了。。。)。
OK,今天我们这个小白鼠呢,要比前面那个稍微大一点。如下图。它有两个输入框,一个是name,一个是serial。它们两个之间肯定不是独立的,肯定会有某种关系把它们联系起来(有可能是搞基...)。
图片:37_3710_ad04e06398e6240.png 
老套路,首先运行来看看。运行后什么也不输入直接点check it,会弹出一个“Wrong serial-try again”的对话框。点击确定返回后name输入框中会出现nameless的字符串。 再随便输入点什么看看,就输入name:iloveswu serial:1234567吧,再check下,还是弹出跟刚才一样的错误对话框。
第二步:查壳。PEID查出是:EXECryptor 1.x.x -> SoftComplete Developement,这个是什么壳,没见过。。。不过没事,我们没见过的东西多了,大爷不稀罕,是吧? 引用: 查壳这儿我说几句。前两课我们都没有涉及到壳,因为怕大家不懂,但是这节课我们也不会讲关于壳的知识,只算是跟壳这个东西打个照面,先熟悉一下,为以后我们学习壳奠个基础(前提是我学会了。。)!
第三步,用OD载入。载入后的样子我就不截图了,大家用OD打开看看就是了,都是一样的。
OK。我们先别忙着破解,先在程序里面晃悠几圈,看看有什么值得注意的地方,顺便看看里面有苍老师的作品没有。。。(这个可以有!可是这个还真没有!!!)
好,大家都转回来吧,别转晕了。我相信大家眼神再不好,就算是1000度近视,都应该看到程序门口那句赤裸裸的挑衅了吧?
代码: [pre] 00401045 |. 6A 30 PUSH 30 ; /Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL00401047 |. 68 00604000 PUSH crackme2.00406000 ; |crackme20040104C |. 68 7A604000 PUSH crackme2.0040607A ; |what the hell are you doing in my app with a debugger?00401051 |. 6A 00 PUSH 0 ; |hOwner = NULL00401053 |. E8 9C030000 CALL <JMP.;user32.MessageBoxA> ; MessageBoxA00401058 |. 6A 00 PUSH 0 ; /ExitCode = 00040105A . E8 A7030000 CALL <JMP.;kernel32.ExitProcess> ; ExitProcess[/pre] what the hell are you doing in my app with a debugger? 这不是赤裸裸的挑衅是什么?它怎么知道我在用debugger拿它做实验? 这还不是关键,关键在于,这是个死程序,不信大家一步一步F8试试,试到最好什么结果,是不是下面这样的?
图片:37_3710_4e6c5d2ba0e5543.png  诶,这个比较新奇哦,从来没见过啊。用OD载入后居然只运行一个具有十足挑衅味道的对话框就完事,这算什么啊?
好吧,我猜,这就是传说中的anti-debugger,所谓的反调试是也。 那怎么办? 在这能够运行的仅仅几句代码上,我们该怎么来想办法破解呢? 额,大家想想办法啊? 我觉得办法有几个: 1.放弃吧,我连壳是什么东西都不知道,连汇编代码都认不完,这个难度肯定不适合初学者。 2.找找看,有什么线索没有,或许万一说不定有可能瞎猫碰到死耗子呢? 如果要放弃,我们还写这个教程干嘛? 所以,永不放弃! 那好,我们来个比较死板的办法,先查下参考字符串吧。 右键->超级字串参考->查找ASCII(或者右键->查找->所有参考文本字串) 哈哈,里面居然能找“Wrong serial-try again“这串字符,太神奇了,还有nameless 当然,还有注册成功的语句:valid serial - now write a keygen! 好,我们双击”valid serial - now write a keygen!“所在行跟随来到反汇编窗口,找到了这几句关键的代码。
代码: [pre] 00401246 |. 68 B1604000 PUSH crackme2.004060B1 ; / nameless0040124B |. 68 B6624000 PUSH crackme2.004062B6 ; |String1 = ""00401250 |. E8 DB010000 CALL <JMP.;kernel32.lstrcmpA> ; lstrcmpA这儿是比较函数00401255 75 16 JNZ SHORT crackme2.0040126D ;这儿根据比较结果进行相应的跳转00401257 |. 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL00401259 |. 68 00604000 PUSH crackme2.00406000 ; |crackme20040125E |. 68 3D604000 PUSH crackme2.0040603D ; |valid serial - now write a keygen!这儿是验证成功时执行的代码,其实就是弹出一个对话框00401263 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner00401266 |. E8 89010000 CALL <JMP.;user32.MessageBoxA> ; MessageBoxA0040126B |. EB 14 JMP SHORT crackme2.004012810040126D |> 6A 10 PUSH 10 ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL0040126F |. 68 00604000 PUSH crackme2.00406000 ; |crackme200401274 |. 68 60604000 PUSH crackme2.00406060 ; |wrong serial - try again!这儿是验证失败00401279 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner0040127C |. E8 73010000 CALL <JMP.;user32.MessageBoxA> ; MessageBoxA00401281 |> 5B POP EBX00401282 |. 5E POP ESI00401283 |. 5F POP EDI00401284 |. C9 LEAVE00401285 . C2 0400 RETN 4[/pre] 大家是不是觉得这段代码很眼熟啊,呵呵,这跟上一个crackme的关键代码几乎一模一样啊,都是一个比较字符串是否相同的函数后接一个跳转语句,然后下面就分别是验证成功和失败的代码。
所以,就算它有anti-debugger那又如何?就算我们不能在OD里动态调试又如何?我们不是一样找到了它的关键代码吗?并且如果你愿意暴力破解,现在不改代码,更待何时?就像第一节课我们讲得那样,直接nop掉那句跳转指令也行,或者将JNZ改为JZ(JE也一样)也行。爆破就那么简单。
但是爆破已经不能满足我们的欲望了(此时我欲火焚身ing......),我们来完全破解它。 但是因为有anti-debugger我们不能在OD里面动态调试程序,也就不可能像上节课那样在CALL上下断点,然后拦截到正确的注册码。那既然不能动态调试,我们就彻底搞清楚它是怎么计算注册码的,这样的话我们就不用在OD里运行它,只需要分析出算法,然后算出正确的注册码就行了啊!
好,说到做到!我们就来学学如何分析算法。 今天我们要讲一个非常重要的函数,名字叫GetDlgItemTextA,这个函数是干嘛的呢?以下文字从百度上copy过来的:
引用: 调用这个函数以获得与对话框中的控件相关的标题或文本。GetDlgItemText成员函数将文本拷贝到lpStr指向的位置并返回拷贝的字节的数目。
我们关心的地方是:1.这个函数会提取输入框中输入的文本内容并保存在什么地方 2.函数的返回值是其拷贝的字节数目。 通常来说,程序就是通过这个函数将我们输入的东西读入进去的。我们通过跟踪这个函数,继而发现它把读入进去的name和serial怎么样了,然后就能找到其算法了,此所谓顺藤摸瓜也! 那么,我们怎么知道什么时候这个函数被调用了呢? OK,要找到什么时候这个函数被调用了还是很简单的。 首先,在反汇编窗口中点击右键->查找->当前模块中得名称(标签),然后在名称窗口中找到这个函数GetDlgItemTextA,找到后在这个函数所在行点击右键->查找输入函数参考,快捷键为Enter。 如下图所示:
图片:37_3710_5335407d763b445.png 
在我们这个例子中,我们看到,有两处调用了这个函数,一处是004011CD,另一处是00401241。很明显这两处就是读入name和serial的时候。 不妨先来看看004011CD。在”参考位于“那个小窗口里双击第一行就来到了反汇编窗口中004011CD这儿。
代码: [pre] 004011C3 |. 68 84624000 |PUSH crackme2.00406284 ; |Buffer = crackme2.00406284004011C8 |. 6A 01 |PUSH 1 ; |ControlID = 1004011CA |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hWnd004011CD |. E8 16020000 |CALL <JMP.;user32.GetDlgItemTextA> ; GetDlgItemTextA004011D2 |. 64:8B15 18000>|MOV EDX,DWORD PTR FS:[18]004011D9 |. 8B52 30 |MOV EDX,DWORD PTR DS:[EDX+30]004011DC |. 0FB652 02 |MOVZX EDX,BYTE PTR DS:[EDX+2]004011E0 |. 83F8 00 |CMP EAX,0004011E3 |. 7F 11 |JG SHORT crackme2.004011F6[/pre] 上下看看,大家看到前面几个PUSH语句,后面一个CALL,典型的函数调用啊!!! 还有一个问题就是,这个函数把读入后的字符串放到哪里了?关于函数的基本知识就只能靠大家自己去学了,我在这里只是告诉你们,这个函数的第三个参数(还记得参数入栈的顺序是从后向前吗?),也就是这一行:
代码: [pre] 004011C3 |. 68 84624000 |PUSH crackme2.00406284 ; |Buffer = crackme2.00406284[/pre] 后面的注释是Buffer,说明这是程序定义的一个缓冲区,字符读入后就放在这个地方,要用得时候到这个地方来拿,这个缓冲区的地址是crackme2.00406284,大家只要知道这个地址就是放得第一次读入的字符就是了。
函数返回之后有几个MOV语句,这都不是我们关心的,关键是大家看到后面那两句了吗?如下:
代码: [pre] 004011E0 |. 83F8 00 |CMP EAX,0004011E3 |. 7F 11 |JG SHORT crackme2.004011F6[/pre] 刚刚我们不是说了吗,GetDlgItemTextA这个函数会返回其拷贝的字符数目,还记得函数返回值一定是在EAX里面吧,也就是说这儿的EAX里面装的是读取的字符的个数。然后与0进行比较,如果大于0,就跳转到下面的004011F6去执行。如果等于0,不用怀疑,肯定是将name的输入框中填入"nameless"字符串。
OK。我们关心的是,如果输入的字符串个数大于0,然后怎么样了,那我们就到004011F6去看看。
代码: [pre] 004011F6 |> 8D35 84624000 LEA ESI,DWORD PTR DS:[406284] ;将存放name的缓冲区地址传给ESI004011FC |. 33C9 XOR ECX,ECX ;清空ECX004011FE |> 0FBE06 /MOVSX EAX,BYTE PTR DS:[ESI] ;将name的第一个字符放进EAX中 循环体开始00401201 |. 8BD8 |MOV EBX,EAX ;这中间这段就是对name的每个字符进行处理00401203 |. 2BF2 |SUB ESI,EDX00401205 |. C1E0 04 |SHL EAX,400401208 |. C1EB 05 |SHR EBX,50040120B |. 33C3 |XOR EAX,EBX0040120D |. 83C0 26 |ADD EAX,2600401210 |. 33C1 |XOR EAX,ECX00401212 |. 03C8 |ADD ECX,EAX00401214 |. 46 |INC ESI ;将指针移向name的下一个字符00401215 |. 803E 00 |CMP BYTE PTR DS:[ESI],0 ;判断是否为空,是则说明name中得每个字符都处理过了,否则跳到开头继续处理下一个字符00401218 |.^ 75 E4 JNZ SHORT crackme2.004011FE ;循环体结束0040121A |. B8 EF0D0C00 MOV EAX,0C0DEF0040121F |. 2BC1 SUB EAX,ECX00401221 |. 0FAFC0 IMUL EAX,EAX[/pre] 大家初学的时候,看不懂这几句是什么意思一点也不奇怪,其实,告诉大家,上面这段代码才是这个程序的灵魂所在。它就是我们千辛万苦在寻找的”算法“。 这里就引用前辈的文字:
引用: 算法总结:将用户名每位取出ascii码值计算,设每位为x,C为ecx的值 A=shl(x) B=shr(x) A=A XOR B A=A+26 A=A XOR C C=C+A 然后在用户名长度之内进行循环计算 A=0C0DEF A=A-C A=A*A
然后后面就是对计算出来的结果进行格式化处理,具体格式是: 最后将CM2,A,C连解成注册码,一组可行的注册码:CM2-C-A 比如:name:iloveswu serial:CM2-4879-9072D264
虽然说现在我们知道了算法,但是当真要人工来算这个注册码还真比较难,所以注册机是必需的。但是我们今天好像讲得有点多了,注册机怎么写就不讲了。
以上算是比较完全地破解了这个crackme,并且我们也知道了它的算法。 但是假如我要你在一分钟内将它破解呢,而且不能改掉判断注册码正确与否的那几行关键代码?有没有办法?
办法其实是有的。那就是,想办法让OD能够运行它。 前面我们已经看到了,这个程序有一个anti-debugger,它在OD里面不会运行到任何有用的代码处,只会显示一个”what the hell are you doing in my app with a debugger?“的对话框。那怎么样才能让它运行它的主功能那些代码呢?JMP?还是CALL?
我们仔细看看,在OD里面能够运行的几个语句里面没有跳转指令,但是有CALL指令,我们 可以用它来作为跳转。但是解决这个问题还得解决另外一个问题,跳到哪儿?到底哪儿才是我们需要的程序的起始点呢?呵呵,大家来看看这个程序的代码:
代码: [pre] 00401000 > E8 24000000 CALL crackme2.00401029 ; (初始 cpu 选择)00401005 |. 8B4C24 0C MOV ECX,DWORD PTR SS:[ESP+C]00401009 |. C701 17000100 MOV DWORD PTR DS:[ECX],100170040100F |. C781 B8000000>MOV DWORD PTR DS:[ECX+B8],000401019 |. 31C0 XOR EAX,EAX0040101B |. 8941 14 MOV DWORD PTR DS:[ECX+14],EAX0040101E |. 8941 18 MOV DWORD PTR DS:[ECX+18],EAX00401021 |. 806A 00 E8 SUB BYTE PTR DS:[EDX],0E800401025 |. 33C0 XOR EAX,EAX00401027 |. 33DB XOR EBX,EBX00401029 |$ 68 60104000 PUSH crackme2.00401060 ; SE 处理程序安装0040102E |. 64:FF35 00000>PUSH DWORD PTR FS:[0]00401035 |. 64:8925 00000>MOV DWORD PTR FS:[0],ESP0040103C |. 9C PUSHFD0040103D |. 813424 540100>XOR DWORD PTR SS:[ESP],15400401044 |. 9D POPFD00401045 |. 6A 30 PUSH 30 ; /Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL00401047 |. 68 00604000 PUSH crackme2.00406000 ; |crackme20040104C |. 68 7A604000 PUSH crackme2.0040607A ; |what the hell are you doing in my app with a debugger?00401051 |. 6A 00 PUSH 0 ; |hOwner = NULL00401053 |. E8 9C030000 CALL <JMP.;user32.MessageBoxA> ; MessageBoxA00401058 |. 6A 00 PUSH 0 ; /(初始 cpu 选择)0040105A . E8 A7030000 CALL <JMP.;kernel32.ExitProcess> ; ExitProcess0040105F . C3 RETN00401060 /$ 64:8F05 00000>POP DWORD PTR FS:[0] ; 结构异常处理程序00401067 |. 83C4 04 ADD ESP,40040106A |. 6A 00 PUSH 0 ; /pModule = NULL0040106C |. E8 A1030000 CALL <JMP.;kernel32.GetModuleHandleA> ; GetModuleHandleA00401071 |. A3 35604000 MOV DWORD PTR DS:[406035],EAX00401076 |. 6A 00 PUSH 0 ; /lParam = NULL00401078 |. 68 95104000 PUSH crackme2.00401095 ; |DlgProc = crackme2.004010950040107D |. 6A 00 PUSH 0 ; |hOwner = NULL0040107F |. 68 00604000 PUSH crackme2.00406000 ; |crackme200401084 |. FF35 35604000 PUSH DWORD PTR DS:[406035] ; |hInst = NULL0040108A |. E8 4D030000 CALL <JMP.;user32.DialogBoxParamA> ; DialogBoxParamA0040108F |. 50 PUSH EAX ; /ExitCode00401090 . E8 71030000 CALL <JMP.;kernel32.ExitProcess> ; ExitProcess [/pre] 你们如果见过几个程序的代码的话,就会很轻易地发现,在00401060到00401090这一段中得几行代码的意义!也许,如果你足够聪明的话(比如像我这样的....),当你第一次打开这个程序的时候你就会发现这段其实很想某些程序的开始部分,所以,我们可以试试。
这个程序的第一条语句就是CALL,那我们就不客气了,直接将CALL后面的地址改成00401060,也就是说,程序一进来就会去执行00401060那段程序。这种想法是好的,但是这样改了这个程序还能工作吗?试试就知道。大家把第一句那个CALL改了以后存盘试一下,如何保存修改过的程序不用我说吧,如果想不起来了,翻翻第一课,里面就有。 保存好以后运行一下刚刚改过的程序,呵呵居然能运行,而且一切正常,那么,接下来,要破解它就容易了。 直接OD载入,用上节课讲到的方法来吧(还记得上节课我们是怎么得到注册码的吗?不记得了我们再温习一遍) 我们在lstrcmpA这个函数上下断点。即在 00401250 |. E8 DB010000 CALL <JMP.;kernel32.lstrcmpA> ; lstrcmpA 这行上下断点。如下图:
图片:37_3710_3de816483e8ee2d.png 
然后F9运行,输入name:iloveswu serial:随便输点什么或者干脆不输入也行,然后点击check,回到OD,程序就被断下来了,此时,在反汇编窗口中和对战窗口中看到一串类似CM2-4879-9072D264的字符串了吧,这就是我们的注册码了。
这个方法我说了那么多,其实总结一下,确实只要一分钟就可以破解了。首先观察到程序可能的起始点,然后直接将CALL指向这个起始点,然后OD载入,下断点,运行,输入name和serial后点击check,程序被断下,看到注册码。 就这么简单,当然,这个属于爆破!
啊,我要崩溃了,童鞋们,你们知道现在是几点吗?告诉你们,北京时间5:40,我想睡觉了,累啊,从两点写到快六点,爱学习的人你们伤不起啊!有责任心的人你们伤不起啊!长得帅的人你们也伤不起啊!单身的人你们更伤不起啊!
| |  | |  |
|