一、前言
通过前文对逆向原理的说明,想必对逆向这个方向已经有了初步的了解。那么工欲善其事,必先利其器,本篇主要介绍一下我所接触到得相关逆向工具。也作为我上阶段学习的一个记录。
话说,逆向真的是很有意思啊hhhhhh~
二、静态调试与动态调试
对于软件的逆向分析,可以分为静态调试和动态调试。与之相对应的是各有擅长的静态调试工具(IDA、C32ASM、Win32Dasm等),和动态调试工具(Ollydbg、X64dbg等),当然大部分工具都是兼具这两种能力的,根据自己的习惯选用合适的即可。
所谓静态调试,就是在本地对其程序结构进行分析,此时程序并未在运行状态,某种程度上类似于源代码审计。
所谓动态调试,就是在程序运行过程中,以断点的方式使程序运行到想要的位置,以此来分析程序的运行逻辑,寻找可能的突破点,某种程度上相当于渗透测试。
三、从Ollydbg开始
为什么从Ollydbg开始,因为IDA_Pro实在是让小白留下了无奈的泪水。。。
Ollydbg运行在windows平台上,是Ring 3级调试器,可以对程序进行动态调试和附加调试,支持对线程的调试同时还支持插件扩展功能,它可以分析函数过程、循环语句、选择语句、表[tables]、常量、代码中的字符串、欺骗性指令、API调用、函数中参数的数目,import表等等;支持调试标准动态链接库(Dlls),目前已知OllyDbg 可以识别2300多个C和Windows API中的常用函数及其使用的参数,是Ring3级功能最强大的一款动态调试工具。
其中 Ring 3级指的是Intel将CPU的特权级别分为四个级别:RING0,RING1,RING2,RING3。在windows下,只可使用其中两个级别RING0和RING3,其中为RING0为仅供操作系统使用的特权指令。
在各大论坛均可下载获取到OD的汉化版安装包,打开之后可看到如下界面:
数据窗口可查看指定内存数据
堆栈窗口则包含程序运行的堆栈信息,可以帮助进行动态调试
我们可以直接将要逆向的程序拖入OD的窗口中实现加载,在前文曾经提到,所谓逆向,就是把程序还原到源代码级别。也就是汇编语言级别,作为初学者,可以把汇编语言理解为机器语言的封装。那么我们的程序也在运行时,也是通过编译器&解释器来讲高级语言代码还原为机器码,实现成功运行。
基于此,那么我们逆向的思路就是,通过修改OD分析得到的汇编指令,在程序运行的关键位置(如判断语句,跳转语句,生成key子程序等)对其进行修改,以实现程序以我们想要的方式运行,最终成功破解。
四、尝试一下
成功源于自信,自信造就成功。在漫漫长的学习路上,实现一个小小的成就,可以很好的激励自己,获得继续学习下去的动力。这样你可以清楚的知道,通过一段时间的学习,具体掌握了写什么,可以实现到什么地步。
故而,我们尝试对第一个程序进行破解,在论坛上可以找到很多demo程序,用以初学者体验逆向。解压之后得到:
首先,我们尝试对CM1(无壳)程序尝试进行破解。通过运行该程序,可以了解到该程序通过校验账号和密码,如验证成功则成功登录。
将该程序导入到OD:
那么接下来应该怎么办呢,我们注意到,如果我们输入错误的账号和密码,会触发登录失败的报错提示。也就是说,这个字符串对应的内存地址,是在源程序校验用户口令的子程序之后的。这个实现校验的子程序,自然就是我们所寻找的关键位置。那么如何找到呢,我们可以通过动态调试的方式,使程序运行到报错的位置。在字符串未被加密的情况下,也可以通过搜索提示信息,定位到该行代码。
在反汇编子窗口中点击右键-->中文搜索引擎-->智能搜索,进入字符串子窗口:
通过搜索功能找到报错提示,点击右键跟随,成功定位到包含该注释的内存地址及对应的汇编代码:
那么问题又来了,就算找到了对应的汇编代码,可他们是什么意思呢?在此简单介绍一下相关的几个汇编指令:
MOV 传输指令,把字或字节从一个地址传输到另一个地址
CALL 子程序的入口,在OD中可以通过Enter/Esc或者+/-进入或退出子程序
JE/JZ 跳转指令,若值相等则跳转
JMP 跳转指令,在任何情况下均进行跳转
JNE/JNZ 跳转指令,若值不相等则跳转
PUSH 传输指令,把字压入堆栈
CMP 比较指令,根据比较结果设置标志寄存器的值
NOP 空指令,可以理解为空过
再回到我们定位到的地址,我们发现,这是一个PUSH指令。然后上面几行,有一个CALL子程序。那么也就是说,下面一连串的压栈动作,都是这个子程序处理的结果。再看这个子程序上有面以一个MOV指令,通过我们刚才的了解,我们知道了MOV指令的具体含义。再联想到下一条CALL子程序,在信息窗口中可以看到这个地址的跳转地址。那么我们猜测,这个MOV指令是把验证CALL的结果传递给了下一条指令,输出最终的结果。
在信息窗口中使用右键,跟随到跳转之前的地址,发现这是一个验证跳转,若值相等则跳转。分析一下运行流程:用户输入口令-->比较指令得出结果-->跳转指令进行验证跳转-->根据是否跳转判断是否成功。
那么我们的逆向思路也就变得清晰,既然是经过跳转指令验证之后,才会到下面输出错误结果的子程序,那么我们不让他跳转不就行了。于是,之前说到过的空指令就尤为重要了,我们可以把这个跳转指令使用空指令填充,这样的话代码会一直往下运行,也就不会跳转到登录失败的错误提示了。这也就是大佬们说的,把它给“NOP”掉。
于是我们选中这条指令,使用右键-->二进制-->使用NOP填充,即可插入NOP指令。然后我们点击F9使程序运行,再次输入任意口令,就会发现已经破解成功了:
如何,自己成功尝试的之后,是不是获得了一些小小的成就感呢,有没有体会到逆向成功的开心。那么再仔细想想,除了使用空指令填充,还有什么其他的方法嘛,自然是有的。
回到刚才的运行流程:用户输入口令-->比较指令得出结果-->跳转指令进行验证跳转-->根据是否跳转判断是否成功
那也就是说,我们是不是可以追寻正确的口令,或是修改比较指令的结果或是可以。。。
黑客,这个词是用来形容那些热衷于解决问题、克服限制的人的。