PE格式:手工实现各种脱壳后的修复
手工修复导入表结构
实现手工修复导入表结构
1.首先需要找到加壳后程序的导入表以及导入了那些函数,使用PETools工具解析导入表结构,如下。
2.发现目录FOA地址为0x00000800
的位置,长度是0x000000A8
定位过去看看,程序中只保留了一个LoadLibraryA和GetProcAddress这两个关键函数,通过这两个关键函数即可定位到所有的函数入口,一般壳都会只加载这两个API函数。
3.首先我们先来实现手工脱壳,使用ESP定律,开头运行F8一次,在ESP寄存器上右键选择内存窗口中转到,选择断点硬件访问四字节,脱壳环节省略,OEP位置如下。
4.找到了程序的OEP位置以后,我们可以找到以下代码,通常是在程序的最底部,我们可以顺藤摸瓜的找到内存IAT表的所在位置。
将地址转为地址,能够看到脱壳后的程序导入的函数,该程序导入了三个函数,分别在两个动态链接库中存储着。
而我们编写的PETOOLS工具并没有那么智能,他只能识别出文件中的导入表结构,也就是在没有装载入内存时的状态,很明显,此处识别的是外壳的导入表结构
我们接着脱壳,使用内置的脱壳工具进行内存转储即可,如下所示。
正常我们脱壳后,程序输入表会保留原始的带壳状态下的结构,如下。
使用X64DBG对其进行FixDump修复后,其结构表现如下,看样子是完全重构了它的输入表结构。
既然知道了解决方案,我们也来自己重构一下输入表结构,我们可以任意选择一处具有可读可写属性的内存,这里以2E00为例
首先我们先在20E0处构建一些导入字符串,格式如下。
00002118 => 指向ExitProcess字符串
00002126 => 指向CreateFileA字符串
00000000 => 代表换行符,将两个模块隔开
00002134 => 指向MessageBoxA字符串
之所以中间需要隔开,是因为前两个函数属于Kernel32.dll最后一个函数属于User32.dll 两者之间使用一个DWORD来分隔开,使用PETools工具解析后可以清晰的看出来。
构建的字符串结构如下所示
接着我们继续来构建IID结构数组,IID数组则选在2010得位置,以此类推。
000020E0 代表的是OriginFristThunk字段 -> 指向00002118 -> ExitProcess字符串
00002100 代表的是Name字段 -> 指向Kernel32.dll字符串
00002000 指向FristThunk -> 此处可指向一个空白空间,有PE装载器自动填充。
接着我们需要找到脱壳后的程序输入表位置,并填入。
最后的结构对应关系如下。
最后跳转到0x130
处,修正地址为0x2010
大小则是0x28
手工脱壳完成了。
处理不连续的输入表结构
有些输入表结构在内存中是不连续的,例如下面案例,我们使用PETools解析出来,首先目录FOA=0x0000A800
其次大小是0x000005E8
将FOA转换为VA地址,0x0040E000 长度是5E8,也就是40e000 - 40E5E8
这个范围内。
其中导入函数开始位置是 40e0ec 结束位置是 40e22C 长度是 00000140
脱壳修复时,填入对应地址,删除无效指针,即可自动新建一个新的导入表。
手工修正重定位表
重定位表一般出现在DLL中,因为DLL都是动态加载,所以地址不固定,DLL的入口点在整个执行过程中至少要执行2次,一次是在开始时执行初始化工作,一次则是在结束时做最后的收尾工作,重定位表则是解决DLL的地址问题,默认情况下,重定位表是如下方式构建的。
1000 表示重定位RVA地址,011c则表示重定位块的长度,后面则是每两个字节代表一个重定位块,1D是重定位地址,30则是重定位的类型,以此向下排列。
重定位表也是分页排列的,每一页大小都是1000字节,如下我们解析一下看看。
我们以第一个为例,查询一下1000页上的重定位结构。
重定位RVA: 0000101D是用 1000 加上 1D得到的。
重定位地址: 0040702C 则是建议装入地址,修正RVA: 0000702C 则是程序被PE加载器修正后的RVA地址,通常与基地址相加得到,如下。
接下来以 UPX3.01为例,我们来手工脱壳,DLL的脱壳往往需要经过两部,第一步修正导入表地址,第二部则是修正重定位表,UPX壳会破坏这两个表结构,我们需要自己修正一下,首先看一下加壳后的Section节。
upx壳,我们可以搜索popad命令来快速到达壳尾部的位置上,然后可看到如下,jmp语句则是跳转到解密后的地址处,由于壳没运行起来,这里是空的。
我们让壳单步运行一段距离,观察尾部的jmp所指向的地址处是否解码(不能让壳跑到这里,我们手动定位过来观察)发现解码后,直接找一处可能会重定位的地址。
例如:call 0x401167 此处在载入时必然会发生重定位,我们就数据窗口跟随。
跟随40127B ,然后在第一个四字节出右键,选择断点,内存写入断点,设置好以后,运行1-4次左右,就会停到重定位地址处,如下所示。
我们来到程序OEP处,将内存转储,并修正镜像。
接着我们将UPX外壳的重定位数据提取出来,依次循环拿到ebx中的地址,如下0017106E - 00171000 = 6E
得到的6E就是需要修正的地址。
再次循环结果变为了0017108D - 00171000 = 8D
得到8D这条数据。
最终我们需要手动创建一个新节,然后写入我们得到的重定位数据,自己手动重建一个重定位表,这个过程很麻烦,我就不在演示了。
最后不要忘记调整,重定位表的位置,第一处为相对RVA偏移,第二处则为重定位块大小。
关于附加数据的修正
附加数据就是在最后一个节的后面增加的一段数据,这段数据没有节区属性,所以附加数据不会被动态装入内存,附加数据一般起点是最后一个区块的末尾,终点则是文件的末尾字节,例如下面的一个案例中,附加数据就在文件偏移 0x00003200 + 0x00000600 = 3800
的位置处。
使用WinHex定位过去看看,会发现数据,这段数据由于没有被载入内存,所以我们是不可能通过PETools工具对其进行分析的,当然专业的PE工具依然可以识别出来。
我们首先使用X64DBG,并配合ESP定律,快速脱壳并修复程序,保存后,接着就是在文件末尾创建一段空款区域。
将附加数据拷贝过来,有时附加数据并没有在程序中引用,这种的可以不复制,有的不行,程序运行会引用这些数据块,我们需要修正。
当我们打开程序时,程序会自动调用CreateFile打开自身,并将文件指针移动到附加数据位置,我们需要手动修正读取偏移,下一个CreateFile断点,运行程序会断下,回溯一层。
向下跟进,修改0x3800这个是脱壳前默认附加数据的位置,此时我们脱壳后附加数据改到了,B400的位置此处也要修正。
修正后直接打补丁,此时即可正常读取出附加数据了。
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!