一、工具及壳介绍
使用工具:Ollydbg,PEID,ImportREC,LoadPE,010Editor
UPX壳 3.94:
有了上篇ASPack壳的经验,先查看数据目录表:
可知是upx壳通过PE加载器修复自我重定位信息。
二、脱壳
1、ESP定律
入口:
通过ESP定律,ESP下硬件断点。
断下的第二次:
跳过循环,单步几次。
OEP:
2、Dump内存
查看基址
OPE - 加载基址 = OEP RVA
4BA9E1- 390000= 12A9E1
双击运行。
查看出错位置。
加载基址 + 出错RVA = 出错地址
900000 + 131d90 = A31D90
可见,重定位信息并没有修复。
三、寻找重定位表数据
目标:找到原本重定位表数据。
1、解压数据、解密重定位点、修复重定位信息 代码块
预判:程序先解压后 再修复重定位。
找到解压后的OEP入口处,还有附近的exe重定位点
程序重新加载,现加载基址 + OEP_RVA(12A9ED) == 现OEP。
断在了壳的解压代码块。
解密还未完成。
但是找到了似加密过的重定位点,找此点0xC0411A00再下硬件断点。
断下来的地方,应该是修复exe重定位信息 代码块了。
似做了混淆。
锁定疑似修改重定位点的代码块,
再次在原重定位点0xC0411A00下硬件断点
断下的地方进行分析,发现:
原重定位点的数据的确被加密。
详细分析:
EBX取出到EAX(0x88FC1A00地址),解密,得到RVA,加上text段地址,再放回去,完成修复exe重定位。
2、回溯分析 寻重定位数据
它是怎么锁定当前重定位点的呢,往上分析,追踪EBX,找出重定位表。
从【EDI】中取出1字节的偏移值给AL,后续在将EBX和EAX相加。
它的算法是:
EBX为当前重定位点的地址,【EDI】 --> EAX为下一重定位点的偏移,
EBX+EAX方可定位到下一重定位点地址。
查看EDI内存,内存储存着不同的偏移值。
查看取出偏移值代码块:
可以看出代码有两处机制,可以推出偏移值有1字节和2字节之分。
锁定分析EDI地址数据来源,也就是偏移值来源。
最终分析,发现UPX其算法非常特别。
对偏移值区域数据的操作:
也就是说EDX在加4之前,是0x15690F3,查看地址,发现真的是数据覆盖。
3、总结及实施方案
总结upx的几点:
- 对原需要 修复重定位点 进行加密。
- 采用重定位点地址 加 偏移值 修复下一重定位点。
- 算出偏移值的算法 奇特。
而且通过原demo的重定位表数据,搜索字节码,发现无论在脱壳前还是脱壳后,都搜寻不到.reloc的数据。
可推断,upx在加壳时就已经将.reloc Section加密了,在壳中再通过其算法解析出偏移值,再通过偏移值修复重定位点。
4.加壳时已对重定位表数据加密。
加壳 --> .reloc加密,重定位点加密 --> 壳运行 --> .reloc解密 --> 算出偏移值 -->
对加密的重定位点解密 --> 采用偏移值修复重定位点信息。
现在有两种方案:
- UPX是开源的,研究其源码算法,对偏移值数据逆向处理,得到原本重定位表。
- 分析算法代码块,抠出偏移值数据,模拟算法,将偏移值转换为重定位表数据。
最后重建重定位表,为程序添加.reloc Section。
这里时间紧迫,选择方案2。
四、模拟算法转换数据
1、分析算法代码块:
可见是从EDI所指向的地址,取出偏移值给EAX,再进行JA判断,最终加上EBX。
编写C++代码 进行模拟算法计算,转换,并输出。
2、取偏移值区块数据:
数据首:
数据末:
抠出数据:
3、写C++代码转换数据
//模拟重定位表结构,转换重定位表数据。 //参考,重定位表结构体: /*typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION; */ typedef vector<WORD> _TypeOffset; //自实现重定位结构体 typedef struct _MyRELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; _TypeOffset TypeOffset; } MyRELOCATION,*PMyRELOCATION; int _tmain(int argc, _TCHAR* argv[]) { /////////////////////////////////////////////////////////////////////////////////////// //////前期工作 //打开文件 FILE* DataFile = NULL; DWORD error = fopen_s(&DataFile, "Section_Data", "rb"); if (error != NULL) { printf("失败1"); getchar(); return 0; } //重建重定位表 PMyRELOCATION relocData = new MyRELOCATION(); //新建重定位表数据文件 HANDLE HNewFile = CreateFile(L"reloc_Section", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (HNewFile == INVALID_HANDLE_VALUE) { printf("失败2"); getchar(); return 0; } //读取数据 fseek(DataFile, 0, SEEK_END); DWORD FileSize = ftell(DataFile); fseek(DataFile, 0, SEEK_SET); //存放偏移值缓冲区 BYTE* FileBuff = new BYTE[FileSize](); fread_s(FileBuff, FileSize, FileSize, 1, DataFile); /////////////////////////////////////////////////////////////////////////////////////// /////////模拟算法 BYTE* BuffPos = FileBuff; //Buff指针 DWORD Curshifting = 0; //当前偏移值 用于运算 DWORD AddShifting = 0; //本区段累计偏移值 用于判断跨区段 DWORD Count = 1; //跨区段个数 //由重复逆向分析可可知,TEXE段的RVA恒定0x1000 DWORD RelocOfNumber = 0; //重定位数量 for (DWORD i = 0; BuffPos[i] != 0;) { Curshifting = BuffPos[i]; if (Curshifting > 0xEF) //取出两字节 { Curshifting &= 0xF; Curshifting << 0x10; Curshifting = *(WORD*)&BuffPos[i + 1]; if (i == 0) Curshifting -= 4; //首字节减去4字节 i += 1 + 2; } else { //取出1字节 if (i == 0) Curshifting -= 4; //首字节减去4字节 ++i; } /////////////////////////////////////////////////////////////////////////////////////// //////////转换成重定位表数据 //判断是否跨区段 ////当本区段累计值 与 当前偏移值 之和 超过0x1000时,表示跨区段 if (AddShifting + Curshifting > 0x1000) { //跨区段 Count += 1; relocData->VirtualAddress = 0x1000 * Count; relocData->SizeOfBlock = ((relocData->TypeOffset.size()*sizeof(WORD)) + sizeof(_IMAGE_BASE_RELOCATION)); //输出到重定位表数据文件 DWORD Relity = 0; WriteFile(HNewFile, &(relocData->VirtualAddress), 4, (LPDWORD)&Relity, NULL); WriteFile(HNewFile, &(relocData->SizeOfBlock), 4, (LPDWORD)&Relity, NULL); RelocOfNumber += relocData->TypeOffset.size(); for (DWORD j = 0; j < relocData->TypeOffset.size(); j++) { WriteFile(HNewFile, &(relocData->TypeOffset[j]), 2, (LPDWORD)&Relity, NULL); } //清空结构体 //计算新区段累加值 AddShifting = (AddShifting + Curshifting) % 0x1000; } AddShifting += Curshifting; //添加进本区段累计偏移值 //记录每个重定位区域 重定位点 的偏移值,高4位重定位属性3,低12位偏移值。 WORD y = (0x3000 | *(WORD*)&AddShifting); //位运算符是把数据加载,运算后,再小端存储 relocData->TypeOffset.push_back((0x3000 | *(WORD*)&AddShifting)); } //最后重定位表结构体数组以0结尾 DWORD Relity2 = 0; WriteFile(HNewFile, &Relity2, 4, (LPDWORD)&Relity2, NULL); //打印重定位数量: printf("%X", RelocOfNumber); /////////////////////////////////////////////////////////////////////////////////////// fclose(DataFile); CloseHandle(HNewFile); delete[]relocData; delete FileBuff; getchar(); }
重定位数量:
转换后数据:
4、添加.reloc区段
先填充一个区段头表为零:
最末尾粘贴上.reloc数据。
.reloc数据一共0xE99CD8大小。
LoadPE添加区段,填上相应数值。
成功!
个人总结:
1. 四部分的难度是逐步上升的,多休息,多吃苹果,保持好心态(这是我对自己说的)。
2. 实现随机基址,主要是想尽量还原程序的运行环境,觉得改掉随机标志没什么意思,但是,很累压...
3. 静态编译的MFC,有点大...
附件:
KID