史上最全最完整,最详细,软件保护技术-程序脱壳篇-逆向工程学习记录(一)
https://developer.aliyun.com/article/1618653
欢迎大家访问我的原站!(乙太小屋)[https://www.oisec.cn/index.php/archives/520/]
三· 重建IAT输入表(Import Address Table)
1 . 啥是输入表?
在各种不同版本的Windows系统中,DLL的版本各不相同,同一函数在不同版本DLL中的位置也可能不同;另外一个原因在于DLL的重定位,DLL文件的ImageBase值一般为10000000,但当两个DLL尝试装载到同一个地址时会发生冲突,因此有一个DLL得寻找另一个地址装载,这就是所谓的DLL重定位。
因此当我们要调用某个DLL中的函数时,需要借助IAT(Import Address Table,导入地址表)作为中间桥梁。PE文件在装载时由PE装载器将导入函数的真实地址写入到IAT时,函数调用时则需要从IAT中获取函数的真实地址。
对于每一个引入的可执行文件(例如dll),有一个镜像引入描述符(IMAGE_IMPORT_DESCRIPTOR)。 typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) }; DWORD TimeDateStamp; // 0 if not bound, // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders DWORD Name; // RVA,指向字符串,是这个可执行文件的名字。例如"ACE.dll" DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) } IMAGE_IMPORT_DESCRIPTOR;
IAT是一个IMAGE_THUNK_DATA类型的数组。有多少个函数被导入,这个数组就有多少个成员。该数组以0结尾。 typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // 一个RVA地址,指向forwarder string DWORD Function; // PDWORD,被导入的函数的入口地址 DWORD Ordinal; // 该函数的序数 DWORD AddressOfData; // 一个RVA地址,指向IMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32;
PIMAGE_IMPORT_BY_NAME是一个非常简单的结构,就两个成员。 typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; // 该函数的导出序数 BYTE Name[1]; // 该函数的名字 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
2 . OD跟踪输入表
例程序:
这是个简单的MessgeBoxA对话框程序,用OD打开:
虽然OD注释可以识别到这是MessgeBox,但点开这条指令是call 004011EA,那为什么它知道内存地址0x004011EA地址是一个MessgeBoxA的API函数呢?
那我们就在这行按下回车Enter键:
可以看到这是个跳转,其实周围还很多都是跳转,查看下面按是跳转到哪里:
上面显示的是取[00402048]内存地址里面的值=77D507EA跳转,为了验证一下,我们在内存窗口Ctrl+G输入00402048,就来到了这个地址:
根据小端存储,从右往左读,发现正是77D507EA!
所以我们可以知道在这整个程序无论到在个位置调用MessgeBoxA,它都相当于call 004011EA,在这里集中跳转到MessgeBoxA的动态链接库!
可以看到上面这些都是OD分析给我的,但是OD为什么会知道呢,其实就是根据IAT得出的!
接下来我们继续跟踪输入表,Alt+M到内存双击PE文件头,也就是00400000区块:
然后我们就可以看到标准PE头文件格式: 标志: Image Base:
偏移地址,这是输入表的地址Import Table address:
回到内存窗口,其实输入表所在地差不多就在这个位置:
接下来我们在代码窗口Ctrl+G搜索00402050,我们就来到了这里,但是发现没办法分析,因为它认为我们这不是在代码段:
所以我们要像之前一样安装一个新的插件:
安装好插件后重新打开,就可以点击Analyze This!
可见左边黑色数据是小端存储,右边绿色是OD简化成了我们方便阅读的字节形式,都是相对400000
的偏移地址,可见总共有两个IMAGE_IMPORT_DESCRIPTOR,最后一个全是0是作为结束的标识。我们先尝试Ctrl+G搜第一个参数OriginalFilrstThunk地址00402098:
然后就走到了INT的第一个参数IMACE_THUNK_DATA又是一个偏移地址00402122,继续搜:
可见我们就来到了IMAGE_IMPORT_BY_NAME内,可找到函数的名字(都是由ASCII码转换翻译的)!
然后看到IMAGE_IMPORT_DESCRIPTOR第五个参数FirstThunk,这才是真正指向IAT的偏移地址:
搜索到:
可见其实这就是IAT输入表!
3 . HOOK-API
壳为了防止输入表被还原,强化壳自身的保护功能,会在IAT大量加密。
例如:源程序要调用一个函数的时候需要找IAT,然后却进到了壳程序进行监控般的跳转才能最终回到IAT!
四· FSG压缩壳和ImportREC的使用
程序可以正常运行:
查看壳:
可见这是用Delphi7.0写的程序,API比较多,所以IAT修复比较麻烦!
安装壳程序,点击install.exe,或者自行解压fsg.rar:
给程序FishC加壳,拖拽即可!这是一个压缩壳,压缩44% 加壳后程序也可正常运行!
现在查看壳就可看到壳了:
把加FSG壳后的程序用OD打开:
此时用老方法较为复杂,目前使用一种新方法,步骤如上图1234,选项/调试选项/SFX/字节方式跟踪真正入口处/重新运行!
在这之后,还是回到了原来的地方,然后再带点击选项/调试选项/SFX/停在自解压器的入口/重新运行/,就可以看见左下角在疯狂的运行,这是在模拟程序的运行,自解压:
然后就来到了OEP,接下来就是右键/分析/从模块中删除分析:
接下来就可以dump操作了。由于SFG壳破坏了IAT,所以不要勾选重建输入表,因为勾选了也没用!我们接下来还要借助ImpREC.exe神奇工具修复IAT!
如图,要确保要修改的程序被OD打开中或者与运行中,ImpREC才能检测到,然后根据我们刚才跟踪到了SFX代码真正入口点的地址,提取偏移地址,修改上面红色框起来的OEP,然后点击IAT AutoSearch就弹出一个提示框:
ImportREC会自动搜索所有的IAT!
翻译:好像找到了一些东西,但也有可能不不正确,如果不正确,建议把RAV改成:00001000 Size改成:00094000
接下俩我们就来验证一下,在内存搜索RAV地址,看它是不是IAT的入口点:
就看到这里:
继续往下滑就可以看见熟悉的API函数:
可见00461BEC上面还包含一些数据其实也是,所以我们还要是要把RVA改成:0046B12C,把这些数据包含进去,这才能确保我们的IAT修复的是完整的,才能正常打开程序!
可见IAT最后到了这里:
所以Size也要修改,范围大了可以,范围小了就不可以,只要把全部IAT包含到了就可以!我们修改成大概10000吧:
然后我们就可以点击Get Imports按钮了!
加载一会儿,可以看到很多的valid:NO这些就都是不合法的,总共有6642条都是不合法:
所以我们点击Show Invalid按钮把不合法的都展示出来,可见这些不合法的数据穿插在各个IAT里面,只要程序加载到这里的时候,加载不到,就会报错!所以我们右键/Cut thunk(s),把这些不合法的东西全部都给砍掉!
然后就可见我们见到的都是valid:YES的了,然后我们就点击Fix Dump按钮,选择覆盖刚才OD的dump文件:
然后它就帮我们多生成了一个文件dump_可以正常运行!
然后再用PEiD查看壳,如果有时候查看不出来
可以选择选项/深度扫描:
就出来了!也可以观察到,脱壳后的文件变大了!
五 . UPX和WinUpacx压缩壳
UPX官网
UPX壳是目前最流行的,最稳定的壳,更新也较块!
1 . upx309w命令行加\脱壳
win+R打开控制台,cd到壳程序目录下,upx.exe加壳,后面的才参数是我们待加壳地程序,加壳后可以看到,压缩到了原来的51.93%,大小从582656变成了302592字节!
其实也可以不用命令行,直接拖拽到upx.exe加壳!
然后我们就能查到壳:
然后查阅文档可知,命令加上-d参数,即可脱壳:
如下:
2 . Free UPX
点击Add files按钮选择程序,COMPRESS是加壳,DECOMPRESS脱壳,下面的Backup file可以选择create可以创建一个副本,备份原程序!
3 . OD脱UPX壳
OD打开加UPX壳后的fishc程序:
通过堆栈平衡原理寻找OEP过程中,硬件断点来到这里:
由于这是3.09的版本壳,在之前的版本有可能是jmp,但这里jmp在下面,所以我们先取消硬件断点,然后在下面的jmp加断点/运行至此/去掉断点/F8/分析/分析代码,就来到了OEP:
然后dump操作,注意不要勾选重建输入表,接下来我们要用ImportRE恢复!
5 . 使用ImportREC修复IAT
- 先点击小三角选择用OD正在运行的原程序文件
- 更具OD找到的OEP,修改ImportREC的OEP里面的偏移地址
- 点击IAT AutoSearch按钮确定
- 根据给出的RAV偏移地址去OD内存搜索验证,保证输入表没被修改,如果被修改了,就要把RAV的偏移地址调整过来,把IAT包含进去!这里可见上滑上面没有了,是可以的准确的,下面的Size也把IAT包含了,OK
- 点击Get Imports按钮,点击Show Invalid按钮,可见没有错误的IAT
- 就可以直接Fix Dump选择刚才OD的dumpupx文件了!
6 . WinUpacx壳
该壳压缩效率更高,但已不更新!
界面中文,比较友好!建议勾选3.清除导出表/4.清除重定位表
然后压缩之后会出现一个文件: 这个文件去掉.bak后缀之后就是我们的原程序备份!
然后OD打开:
入过一开始不是这里,那也可以Alt+M,双击fishc在数据窗口找到偏移地址,即可搜索:
接下来就是根据堆栈平衡寻找OEP:
就可进行dump操作,由于输入表已被破坏所以记得不用勾选重建,重建也是错误的!
然后其实和上一个程序一样操作ImportREC,不多赘述!
然后如果还不满意可以再进行LordPE操作:
最后这就就完美啦!恭喜结业!!!芜湖!!!