Crack之初体验-第一课链接:http://bbs.pediy.com/showthread.php?t=143858
Crack之初体验-第三课链接:http://bbs.pediy.com/showthread.php?t=144053
鉴于众位专家评审团一致认为上一篇文章(Crack之初体验-第一课)言语累赘,读之太累,俗称B话太多,所以这篇文章我会尽量少说点话。但是我看了各位的评论,还是有很多人(当然,基本是菜鸟)认为说仔细点好,因为他们刚刚接触这个东西,很多地方都不懂,所以我也认为能说详细点就说详细点。但是,这节课我再说得详细,也不可能再做那种:让你把OD添加进右键菜单还给你截个图这种傻事。大家既然都看了我讲得第一节课了,对OD,对各种操作应该还是有一定了解了,以后这种事情我最多是把步骤贴出来,具体位置大家自己去找。比如,我让大家把OD调试设置里面的异常全部忽略,你就不要来问我这个设置在哪儿,从哪儿打开这种问题。
OK。这回真的开始了!
上一节课我们讲了一些关于CRACK的基本知识,以及通过一个简单的例子稍微了解了一下crack。这节课我们将更深入地来学习。所有需要的知识我都不会先讲,都是在要用到的地方才讲,免得大家看了又忘了。还有,任何事情你看到别人做觉得很简单,但是正当自己要做的时候却感觉无从下手,所有呢,动手,多练“左右互博”,呵呵
上节课我们把那个小白鼠(点击下载)的五脏六腑都搞乱了,话说暴力破解(以后我们都简称爆破)真的很暴力,但我们又岂能满足于爆破呢?所有这节课我们来点有高科技含量的破解技术。
首先,把这个小白鼠用OD载入。OD的主界面不是有四个大框框吗?上一节课我们只用到了左上角那个反汇编窗口,其他窗口正闲得蛋疼呢,所有今天我们将用到右上角这个窗口,它的名字叫“寄存器窗口”。
话说,寄存器是什么?说到寄存器,话可就多了去了,不过我们今天只讲我们将用到的几个寄存器,学名“通用寄存器”,一般我们就叫它寄存器,下面是百度上抄来的。
还有呢,在这个寄存器窗口中,你会看到除了EAX,ECX,EDX,EBX这几个之外的其他一些东西,我们暂时不管,只看前面那4个。如果这个寄存器中的值变化了,表明上一条运行的语句改变了它的值,这个不难理解吧,只要它变化了,它就会变红(害羞了还是怎么的???)。
OK,我们再来看看小白鼠那几行关键的代码:(大家不会找不到了吧?赶紧翻翻上一课,看看我们如何才能找到这几句关键代码所在的位置)
代码:
0040155F |. 50 PUSH EAX ; /String 00401560 |. FF15 04204000 CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>] ; \lstrlenA 00401566 |. 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX 00401569 |. 837D F0 01 CMP DWORD PTR SS:[EBP-10],1 0040156D |. 73 16 JNB SHORT crackme.00401585 0040156F |. 6A 40 PUSH 40 00401571 |. 68 2C304000 PUSH crackme.0040302C ; crackme 00401576 |. 68 34304000 PUSH crackme.00403034 ; enter registration number 0040157B |. 8B4D E0 MOV ECX,DWORD PTR SS:[EBP-20] 0040157E |. E8 7B050000 CALL <JMP.&MFC42.#4224_?MessageBoxA@CWnd> 00401583 |. EB 3C JMP SHORT crackme.004015C1 00401585 |> 8D4D E4 LEA ECX,DWORD PTR SS:[EBP-1C] 00401588 |. 51 PUSH ECX ; /String2 00401589 |. 8D55 F4 LEA EDX,DWORD PTR SS:[EBP-C] ; | 0040158C |. 52 PUSH EDX ; |String1 0040158D |. FF15 00204000 CALL DWORD PTR DS:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA 00401593 |. 85C0 TEST EAX,EAX 00401595 |. 75 16 JNZ SHORT crackme.004015AD 00401597 |. 6A 40 PUSH 40 00401599 |. 68 50304000 PUSH crackme.00403050 ; crackme 0040159E |. 68 58304000 PUSH crackme.00403058 ; correct way to go!! 004015A3 |. 8B4D E0 MOV ECX,DWORD PTR SS:[EBP-20] 004015A6 |. E8 53050000 CALL <JMP.&MFC42.#4224_?MessageBoxA@CWnd> 004015AB |. EB 14 JMP SHORT crackme.004015C1 004015AD |> 6A 40 PUSH 40 004015AF |. 68 6C304000 PUSH crackme.0040306C ; crackme 004015B4 |. 68 74304000 PUSH crackme.00403074 ; incorrect try again!! 004015B9 |. 8B4D E0 MOV ECX,DWORD PTR SS:[EBP-20] 004015BC |. E8 3D050000 CALL <JMP.&MFC42.#4224_?MessageBoxA@CWnd> 004015C1 |> 8BE5 MOV ESP,EBP 004015C3 |. 5D POP EBP 004015C4 \. C3 RETN
讲之前,我先简单介绍一下在汇编里面典型的函数调用是怎么回事,以下摘抄自本论坛:
那么,看我们那个小白鼠的代码。比如这两行:
代码:
0040155F |. 50 PUSH EAX ; /String 00401560 |. FF15 04204000 CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>] ; \lstrlenA
OK,说明上面这两句是在调用一个函数,这个函数是干什么的呢?不知道!但是从它后面的注释里,我们依稀看到几个字母:lstrlenA。这几个字母什么意思?编过程的都知道,字符常量string通常我们都会简写为str,而长度的英文是length,我们一般也简写为len,那么是不是我们就可以猜测这个函数的作用是:检测字符串长度的!
对了,其实这就是一个检测字符串长度的函数。
那么检测长度干嘛呢?当然是判断你输没输东西进去了!如果你任何东西都不输入,直接点击check,看看会出现是什么。那肯定会弹出来一个对话框,上面有一句话:enter registration number,意思是叫你输入注册码。
所以,这个函数就是判断你有没有输入东西,如果没有输入,它就提醒你输入。
刚刚我们讲到,函数一般都会有返回值,而且返回值一般都保存在EAX这个寄存器里面,那我们来瞧瞧。
代码:
0040155F |. 50 PUSH EAX ; /String 00401560 |. FF15 04204000 CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>] ; \lstrlenA 00401566 |. 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX 00401569 |. 837D F0 01 CMP DWORD PTR SS:[EBP-10],1 0040156D |. 73 16 JNB SHORT crackme.00401585
CMP这个命令就是比较compare的简写,命令我就不解释了,不清楚的查看8088 汇编速查手册。
我们刚刚说到,函数返回后一般会将返回值放在EAX中,并且上面的函数返回后将EAX和1比较,然后后面紧接着一条JNB语句(关于汇编跳转语句请查看-8088 汇编跳转),说明它会根据比较的结果进行相应的跳转,那么这儿的意思再明显不过了:如果EAX大于或者等于1,那么就跳转,否则就不跳。也就是说,如果你没有输入任何东西,它就不跳,继续执行下一条语句。那我们看看下几条语句是什么呢。。。
代码:
0040156F |. 6A 40 PUSH 40 00401571 |. 68 2C304000 PUSH crackme.0040302C ; crackme 00401576 |. 68 34304000 PUSH crackme.00403034 ; enter registration number 0040157B |. 8B4D E0 MOV ECX,DWORD PTR SS:[EBP-20] 0040157E |. E8 7B050000 CALL <JMP.&MFC42.#4224_?MessageBoxA@CWnd> 00401583 |. EB 3C JMP SHORT crackme.004015C1
刚刚是假设我们没输入东西,现在如果我们输入了东西了呢,还是比如iloveswu吧,是不是经过那个检查长度的函数检查,哦,原来我们已经输入了东西,并且长度是8个字符。所以它就会执行那句 JNB SHORT crackme.00401585。
这个跳转的目的地是0401585,大家应该注意到每行代码前面都有一个地址,比如00401585,这就好比人的住宿地址一样,只有通过地址才能找到人,不然你上哪儿找去。
OK,闲话不多说,来看看00401585这行是什么东西。
代码:
00401585 |> 8D4D E4 LEA ECX,DWORD PTR SS:[EBP-1C] 00401588 |. 51 PUSH ECX ; /String2 00401589 |. 8D55 F4 LEA EDX,DWORD PTR SS:[EBP-C] ; | 0040158C |. 52 PUSH EDX ; |String1 0040158D |. FF15 00204000 CALL DWORD PTR DS:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA 00401593 |. 85C0 TEST EAX,EAX 00401595 |. 75 16 JNZ SHORT crackme.004015AD
也就是说:它把我们输入进去的注册码和正确的注册码进行比较,如果相同说明注册码正确,弹出correct way to go 的对话框。如果不相同,说明输入错误,就弹出incorrect try again 的对话框。
现在大家明白了它运行的原理了吧?
好,既然知道了它是怎么工作的,那就可以想解决办法了。
上一课中,我们解决的办法是暴利破解,就是将验证完注册码后准备跳转的那条语句改了。本来,如果比较发现注册码错误了,他就会跳走,那我们就偏不要它跳走,索性直接将跳转语句NOP掉,或者将JNZ改为JZ,上次我们就是这样处理的。
这一课我们当然不会爆破了,那怎么解决呢?
那只有一个办法,找到正确的注册码!!!
怎么找?在哪儿找?
呵呵,童鞋们现在可别蒙了,我们现在开始吧。
我们的目的是要找到正确的注册码,那么这个正确的注册码到底在哪儿呢?
嘿嘿,不知道大家还有没有印象,刚刚我们讲到了一个函数,它的作用是比较我们输入的注册码是不是和真实的注册码相同。那么,现在知道了在哪儿找了吧?
好,现在我们讲一讲怎样把这个正确的注册码搞到手。
这就必须讲到OD的一个功能了,叫做:下断点。
那么什么是断点呢?哦,这可不是张敬轩唱的那个歌:
在OD里面,下断点的快捷键是F2,当某条语句被下来 断点以后,这条语句的地址会变成红色(顺便提一句,正在运行的那条语句地址会是黑色),就像这样:
OK。关于断点我就讲这么多,如果大家想知道更多,自己搜索去。。。。
好,既然我们想得到正确的注册码,再看这几条语句:
代码:
00401585 |> 8D4D E4 LEA ECX,DWORD PTR SS:[EBP-1C] 00401588 |. 51 PUSH ECX ; /String2 00401589 |. 8D55 F4 LEA EDX,DWORD PTR SS:[EBP-C] ; | 0040158C |. 52 PUSH EDX ; |String1 0040158D |. FF15 00204000 CALL DWORD PTR DS:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA 00401593 |. 85C0 TEST EAX,EAX 00401595 |. 75 16 JNZ SHORT crackme.004015AD
但是不妨来看看就知道了。
OK,我们在0040158D |. FF15 00204000 CALL DWORD PTR DS:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA 这句上下断点,如上图所示。
我说一句,很多人都会问,在这句上下断点,那当程序停下来的时候,下断点这句到底执没执行呢?我很明确地告诉大家,下断点这句是没有执行的,程序在执行完下断点的前一句,并且准备执行被下断点这句的时候,被中断了。不信你们自己试试。
OK,大家将光标移动到0040158D |. FF15 00204000 CALL DWORD PTR DS:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA这句上,然后按F2,或者在这句上点击 右键->断点->切换,也可以。下了断点以后,这句的地址就会变红。
然后,我们来运行一下这个小白鼠。按F9运行(关于OD的快捷键,大家自己去记就是了,其实也不用记,只要你经常用,自然就记住了)
OK,我们看到任务栏出现了这个crackme的程序。如下图:
我们随便输入点什么吧,比如还是iloveswu吧,输完以后点击check,诶,大家奇怪为什么没有弹出那个错误对话框了吧?因为我们下了断点,现在对断点了解多一点了吧,它会把程序拦下来,不管你要做什么。
赶紧切回OD来看看,大家看到什么了,不可能什么都没发现吧?
大家再仔细看看反汇编窗口中我们下了断点的那行!有没有发现那一行的地址背景变成了黑色?那说明什么?
说明程序已经运行到这一行上了!
OK。我们现在再来看看右上角的寄存器窗口,在ECX和EDX后面分别有一个字符串,而且其中一个居然是我们刚刚输入的iloveswu!
那,我们是否有理由相信那另外一个字符串就是我们要找的正确的注册码?
不确定?那我们试试!这里注意:如果你想在刚刚那个被调试的程序里直接输入<BrD-SoB>来验证,那是绝对不可能的,因为它虽然被拦住了,但人家本来应该做的事还没做完,所以你怎么点击那个程序的输入框都是无济于事的,所以必须重新打开一个这个crackme程序,输入<BrD-SoB>:
看到没有,这就是正确的注册码!
OK,刚刚那个程序已经被拦下来了,那我们怎么让它再次运行呢?
按F9,它就会再运行起来,现在,那个提示注册码错误的对话框出来了吧?
好吧,正确的注册码已经找到了,但是我相信你们心中肯定有万千个疑问,为什么它会在这里,而不是那里?是不是每个crackme都是这么被破解的?
所以,首先我要说的是,不要怕困难,其实crack就是这么简单。其次,这只是我们第一个crackme,相对来说,它是最简单的,以后肯定会有更难的,但没什么好怕的。
OK,我们来总结一下,我们是怎么找到这个正确的注册码的。
首先,我们分析程序代码后知道程序的运行流程是这样的:
1.程序会首先读入我们输入的注册码
2.随后调用函数判断我们输入的注册码长度
2.1如果长度小于1,弹出“请输入注册码”的对话框
2.2如果长度大于或等于1,就调用字符串比较函数,比较我们输入的注册码和正确的注册码
2.2.1如果相同,弹出正确的对话框
2.2.2如果不相同,弹出错误对话框
所以,我们要想得到正确的注册码,就必须到步骤2.2那儿去找,也就是比较两个注册码的函数那儿。我们要得到原滋原味的正确注册码,就必须在这个函数执行之前去,不然等这个函数执行完以后再去看,谁知道这个函数有没有毁尸灭迹啊?保不定什么也找不到,这就是我们要在CALL这句下断点的原因。这也算一个经验吧,大家以后记得,要找什么重要的数据,一定要在CALL上(或者之前)下断点,说不定有什么收获哦。
其实我们看到,在这个CALL之前,有两个参数string1和string2被压入了堆栈,PUSH ECX 和 PUSH EDX ,这ECX和EDX里面就保存了两个真假注册码,不信来看看OD 右下角的堆栈窗口,大家看到了那个正确的注册码和我们输入的假注册码了吧?至于堆栈是什么东西,不懂的童鞋请自行查阅资料,我就不讲了。这些知识都是死的,只有我们的智慧是活的,我只负责把活的东西讲死,不负责把死的东西讲活,哼。。。。
大家今天收获还多吧,我可是打字都打累了。
今天的课就讲到这个地方。大家看完后再去翻翻论坛的一些基础文章,顺便温习一下我今天讲的课程,积极的童鞋请自己往前学,比较懒的(如我这种)就等着我的下一课吧。
本人夜观星象,推测我下一课会讲一个name-serial的例子,比今天这个肯定要高级一点,不过道理都一样,别灰心,我们一起进步。