本次所用的crackme来自160个crackme系列的第5个,是上一个crackme的进阶版,要困难很多,主要通过这个crackme来学习一次PE逆向中各个步骤的对应操作和知识,以及相应的Delphi框架的基本知识
使用一个常用的查壳工具:exeinfope,直接拖进去看一下:
可以看机下面有一个upx.exe,说明这是个upx壳,然后拖进OD里面看一下:
开头一个pushad,很明显的加壳操作(将寄存器状态入栈,后面会将这些寄存器状态出栈,然后进入程序真正的代码部分)
这展开说一下壳这个机制和对应的解决办法
简单来说壳就是在程序被执行之前获得程序控制权并自动执行的一段指令,它的作用根据情况分为两种:加密和压缩,这里先讲压缩
顾名思义,压缩壳就是让程序的大小变小但不影响正常的装入内存。
压缩壳在解压时会有一个问题,我们的代码在被解压时是从被压缩的地址开始解压,但是由于文件被压缩,那么紧邻这被解压地址的代码的可能会被正在解压的代码给覆盖掉,那么程序是先解压还是先覆盖?
其实在压缩之前,程序会记录原PE文件的节的个数和大小,此时我们生成新的带壳的文件,就会获取这个两个数据,在装载时只需要在壳解压缩的代码后面占用等大的空间,将解压出来的代码放入后面的空间中即可。
加密壳的种类相较于压缩壳就要多很多,每个加密壳的侧重点不同,它们有些可能只是单纯的保护程序和避免反编译,有些可能还会提供注册验证,使用次数等功能,加密壳比较复杂这里就不展开说了,后面遇到了再仔细说。
后面解题时会用专门的脱壳机,但是可以通过这道题来尝试一下自己手动脱壳下面是实际操作:
主要步骤:
本次的crackme在前面的查壳中发现了有一个UPX的壳的存在,UPX是一个压缩壳,它的特征非常明显,就是在程序开头会有一个pushad,然后按一下F8看一下ESP的情况:
只有ESP发生了改变,大概率是可以使用ESP定律,直接在这里下一个硬件断点,然后F9运行起来:
这里有一个jmp,特征非常明显,这里就是跳入程序实际执行的代码部分,可以先F7进入这个jmp看一下:
开头一个push ebp和mov ebp,esp 这是一个非常明显的程序在执行主函数前开栈的操作,基本可以确定这里就是程序的OEP了,那么直接右键选择OD自带的脱壳插件进行脱壳:
由于壳基址会抹去程序原本的IAT,所以OD脱壳后的程序是无法正常运行的,需要修复一下脱壳后程序的IAT,这里使用importREC进行修复:
点击获取导入表后在修复转储即可。之后得到的修复后的文件再拖入OD中调试就会是下面这个样子:
程序的开头就已经是push ebp,即主函数的开栈操作,到这里手动脱壳就完成了。
但是本次的解题为了后面的对齐准确和代码准确性使用的是专门的脱壳机进行脱壳,但是通过手动脱壳可以更深的了解壳机制和积累经验。
将脱壳后的程序再次放入exeinfope看一下:
这里已经解析出来了是一个Delphi框架下编写的程序,那么我们就可以使用Delphi的框架分析软件来先静态分析一下这个程序,这里使用的框架分析软件是DarkDe4,直接将脱壳后的程序拖进去:
首先是程序中各个模块的信息,但是我们先关注一下窗体信息,在窗体信息中有一个叫TForm1的窗体,双击它看一下细节:
发现这里其实有两个文本编辑框,但是当我们运行这个程序时只显示出来一个,说明有一个文本编辑框是被隐藏了,这是通过DarkDe4获得的第一个信息。然后看一下过程信息:
事件信息:
这里可以总结出在窗口中有以下几种时间:
控件信息:
注意如果前面的脱壳部分采取的是手动脱壳或者通用脱壳机进行脱壳,那么这里的空间消息可能是空白的
为了方便后面对程序的分析,可以将程序的所有符号签名信息在IDA中导出为MAP文件并加载到OD中,首先将程序拖到IDA中:
然后在File选项中找到Produce file,然后在里面找到,create map file
然后出现这个界面后选择生成信息:
然后在目标文件夹中会生成这样一个map文件:
在OD中将这个文件进行导入:
在OD上面的菜单中找到插件,插件菜单中选择LoadMapEx就可以导入map文件了
导入后:
导入map文件后的OD在调试时会多出来很多注释,并且有些函数的名称会发生改变(编程IDA静态分析出来的sig),在后面对代码的分析中会有很大的帮助,所以在以后的程序分析中都可以先尝试使用IDA生成map文件后导入OD
(注意有些OD中可能没有这个插件,这就需要自己到网上下载一个插件之后把它放进OD的插件文件夹中,也就是下面这个文件夹里)
下面正式进入对于这个程序的分析,首先还是按照以前的经验查一下程序里面的字符串:
找到了一些比较关键的字符串,这里选注册成功后的提示字符串点进去看一下:
在显示成功的字符串上面的代码中可以找到一个很明显的判断跳转,有四个与常量的判断跳转(也就是cmp jcc组合),这里就说明了程序中有四个对于程序是否成功的判断检测,下面分别分析这四个检测以及对应的绕过方式。
注意第一个CMP与JCC的组合,CMP是与一个常量0xC34进行比较,所以这里的ds:[ebx+0x304]就应该是一个与0xC34有关的变量,我们想要绕过这个判断就得让这个变量中的值不为0xC34,那么可以再OD中搜索有关这个0xC34的变量(右键->查找->所有变量->搜索0xC34)
可以看见在这次判断之前还有两次赋值操作,选择第一个点进去看一下:
这里有两个对ds:[ebx+0x304]进行赋值的操作,将程序往上拉,分析一下这段程序的执行逻辑:
程序的开头是创建并初始化表单,并设置程序中的控件为可见(SetVisible)
但是这个流程并不是中单,需要继续往下看:
往下翻一点就会看见一个文件路径注释,还有一个指向赋值语句的跳转。
在跳转语句前还有三个CALL,这三个CALL对应的是Delphi框架下的一次文件IO操作,下面分别说一下:
上面三个操作连起来就是打开对应位置路径上的文件并读取里面的内容,如果读取内容为空或者读取错误的话就会执行下面那个jnz跳转到赋值操作的部分,等于第一层检查就直接检测失败了,那么这里对应的操作就是要在对应的目标路径下先创建一个ok.txt文件。
这里要注意一下,在最原始的题目当中这个路径是X盘,但是根据每个人的情况不一样可能并没有X盘,所以可以在OD中的数据区域将这个路径改成C盘或者其他电脑上有的盘
接着往下看:
这里会发现一个名字很奇怪的函数:system StrCmp,在C/C++编程中经常会用到这个名字的函数:strcmp,这里CALL的这个函数功能就是字符串比对,根据结果判断是否je跳过赋值语句,那么这个地方要进行比较的字符串和目标字符串是什么,在调用函数前要进行参数压栈(或通过寄存器传参),这里就要看调用函数前面的某些压栈或者寄存器操作,可以发现有个mov指令是往EDX里面传递了一个参数地址,在数据窗口中找到这个指针指向的数据:
这里正好就是一个字符串,也是作为被比较字符串传入函数的参数。
这里就可以得出绕过第一层防护的方法:在对应路径下创建文件,并将被比较字符串数据用十六进制编辑器写入文件即可。
可以看见被隐藏的编辑框已经可视化了。
那么这里回到前面的四个CMP与JCC的组合判断中,看一下第二个防护是什么:
还是前面那个办法,找一下这个常量0x230D在程序中的赋值操作:
这里找到了一个赋值操作,点进去看一下:
开头的注释说明了这是一个对应按钮1被点击时的响应事件,这里没有特别值得注意的操作,动态调试一步一步跟进后会发现这个ds:[eax+0x308]会被初始化为0x28E,然后最终与0x294比较来决定是否给它赋值为0x230D,在判断之前有一个add操作,但是要执行这个add操作之前要通过一个cmp,条件是让CL为1,这个的CL是否为1的意义如果单步跟踪下来其实就是是否右键点击了按钮1(也就是注册了按钮),如果点击了右键则让这个值加3,但是如果直接调试到这里点击一次右键会发现这个变量只加了3,是无法绕过的,这个地方由于分析还不充分所以还无法找到正确的解决办法。所以这里继续往下看。
回到JMP与JCC的嵌套判断的地方观察一下下一个判断:
还是直接去搜索这个变量:
发现了一个赋值语句,点进去后找到函数最开始的地方看一下对应的控件操作:
很明显这个函数试管与鼠标移动的,接着往下看:
后面就是一连串的cmp和jcc的判断跳转组合,大多数使用edx和eax进行比较,那么这两个寄存器里面装了什么数据就要往前找一下了:
这个两个参数是什么呢?这个就要动态调试了,我们F9执行到这个部分,然后将鼠标移到程序表单中发现程序被断住了,之后edx和eax就有了值:
这里我们就可以大概猜到这两个寄存器里装的就是鼠标的横纵坐标,再多调试几次就会发现坐标系的远点在左上角,eax装的是横坐标,edx装的是纵坐标。那么后面对于edx和eax的判断操作就是对鼠标所处的位置进行判断。
但是在对eax和edx进行判断之前还有一个判断,这个判断要结合前面通过Delphi框架解析软件中的信息来分析:
我们可以发现程序中有四张图(image1~image4),每张图的ID是一一对应的,而这个ID在前面的判断中出现过:
那么这里就可以看出来这个判断是要对引入的图像进行判断,前面的是判断image3 后面是判断image2,根据程序的执行流程可以知道这里的image1~image4的对应图片就是对应的“人之初,性本善,性相近,习相远”的图片顺序。
这里可以总结一下绕过这个防护的方法:
但是在真正进入赋值语句之前还有两个判断:
)
第一个判断变量0x310是否等于0x10在前面判断绕过后就可以跳过跳转,但是第二个变量0x30C是否等于0x9则是嵌套在这个判断里的又一个判断,其实就是第四层防护。
前面说到的嵌套在第三层防护中的防护也可以用查找变量的方式:
有两个赋值语句,第一个是在创建表单时的赋值语句,没有意义,所以这里进入第二个语句:
由于我们要让变量0x30C不等于0x9来绕过判断,所以这里是符合要求的,向上找到函数头部,看一下这个函数是在完成什么功能:
根据注释可以知道这个函数用来管理编辑框2的双击事件的,但是这个时候的编辑框2是禁用的,为了让这个点击事件生效,我们就要先解禁编辑框2,那么回到DeDePark中看一下编辑框2对应的控件ID是2F0,回到OD中查一下这个有关这个控件的常量:
很多条,这就需要一条一条的点进去看,这里找到有关编辑框2启用的操作函数是第四条,点进去看一下:
这里panel1控件的双击操作,什么是panel1呢,这个还是到DeDePark中去看一下窗体的分布:
发现panel1就是下面这个大方框,回到程序中看,在解禁编辑框之前有一个CMP和JCC的组合判断跳转,当变量0x308不等于0x29D时会跳过解禁操作,这变量0x308我们在前面见到过,就在第二层防护中,这个变量的作用是用来记录鼠标点击注册按钮的次数的(到这里忘了的话可以倒回去看看),变量0x308在第二层防护中被初始化为0x28E,当右键点击一次注册按钮时这个值会加3,那么我们符合条件就要右键点击注册按钮:(0x29D-0x28E)/3 = 5次。
这里总结一下解禁编辑框2的操作:
解禁编辑框2后回到第四层防护对于编辑框2的双击事件,直接来到它的验证流程:
验证流程总结如下:
到这里就完全绕过了前面的第三层防护。下面回到第三层防护的地方,因为它的流程还没有执行完。
这是第三层防护的后半部分:
流程也比较简单,就是获取编辑框1中的内容,然后与字符串"ajj"进行比较,相同则判断成功,不相同则判断失败,所以这里就知道第一个编辑框中的内容需要为"ajj",然后这个函数就完全执行完成了。下面总结一下这个嵌套了三层防护的函数判断:
回到计时器空间的五个CMP与JCC的组合判断中,到这里已经过掉了三个验证,来看下一个:
这里要让变量0x318和变量0x314相等,那么就要先知道这两个变量中装的值是什么,仍然是用搜索常量的方法:
可以看到除了第一个是赋初值之外(初值为0),其它全是在给这个变量做加法,这里点进第二句指令中去:
这里先判断点击的是鼠标左键还是鼠标右键,左键数值加2,右键数值加0x11,将程序拉倒函数开头的地方看一下这是处理什么控件的函数:
这里处理的是Image1,也就是图片1的鼠标点击事件。那么再换到上面搜索出来的第四个加法语句中去:
与上面几乎是一样的流程,只是点击左右键后所加的数值不相同,这里就可以看出来这个流程的规律,根据点击的图片和左右键不同对变量的加的数值不同
这里查找到五句赋值指令,其中第一个是创建表单时的赋值语句,没有什么意义,后面四句的地址相隔很近,可以推测应该是同一个函数里的操作,点进第二个去看一下:
在这里有一个Switch case的选择执行结构,这个选择结构依据的值是变量0x30C中的值,关于这个变量中的值要往上找:
这里就是给变量0x30C赋值的地方,第一个call是获取磁盘剩余空间,第二个call是一个算法,很复杂但是不用特别注意,因为主要关注的是后面是Switch case中对变量0x314的赋值
注意不是那句mov 0x30C,0x9,我们在绕过第三层防护时说过我们不能使这个变量里面的值为0x9。
结合上面的选择操作,可以总结出下面这个选择赋值:
根据这里获得的变量0x314的值,为了保证前面的变量0x318与0x314值的相同,根据显示数字的不同可以总结出下面的操作:
这个是160个crackme中四星难度的一个crackme,跟着流程走一遍可以收获很多关于壳机制与脱壳,逆向分析的知识(诸如嵌套判断与选择执行的流程),以及一些关于Delphi程序框架以及操作函数的名称和流程。