PE(Portable Executable)导入表是Windows操作系统中可执行文件(如DLL和EXE)的一部分,用于记录该文件所依赖的外部模块和函数。导入表是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的位置此处也要修正。
修正后直接打补丁,此时即可正常读取出附加数据了。