前几天群里有人给了个病毒样本
拿来一看很奇怪,是个exe文件,但入口点显示却是0
用OD加载后会提示出错:
之后问了一下同事,大概了解了一下原理:
Windows系统加载PE文件后,会通过PE文件的特定结构读取各种信息。
而该PE文件的各种信息都是完整的,可以正常被读取。
相关的PE结构捡主要的在这里大概说一下: 文件开始是一个IMAGE_DOS_HEADER的结构,以数据0x4D5A(ASCII字符为"MZ")开头的e_magic元素。从该结构体开头开始计算偏移量0x3c处为一个DWORD值——被称为e_lfanew,该值为IMAGE_NT_HEADER结构体(即俗称的PE头)的偏移量。PE头分三部分:第一部分是一个名为Signature的字段,固定为0x4550(ASCII字符为"PE");第二部分从PE头开始偏移0x4,名为FileHeader的结构;第三部分从PE头开始偏移0x18,名为OptionalHeader。这个OptionalHeader中的AddressOfEntryPoint与ImageBase两元素之和即是PE文件被加载到内存中运行时,正式开始执行功能的代码起始点。 |
该文件的AddressOfEntryPoint为0,而ImageBase为400000.
那么尝试在OD下用Ctrl+G查找该地址:
可见固定的e_magic元素被识别为了汇编指令dec ebp和pop edx
而后面的8个字节则是被人为修改的。
push edx是为了恢复前面"字符Z"被误认为是指令所造成的出栈操作
而后jmp到自己的修改过的一段代码处:
在运行到这个retn指令后,栈中出现的才是原本的程序入口点:
执行retn之后,程序会自动运行到这个入口点(本身还有一层UPX加壳):
总结一下:
其实就是将程序原本的入口点(AddressOfEntryPoint)修改为0,这样在加载PE文件的时候就会从文件的DOS头开始作为二进制指令开始执行。只要固定的e_magic不动就不会影响整体的PE结构识别,这样修改之后的几个字节,来实现向程序原本入口点的直接或间接跳转。
用以下代码可以轻松实现任意一个PE文件的修改(跳转方法没有用jmp,比较麻烦。采用了push+retn的方法,很直接):
- VOID ChangeExeOEP( PVOID pBuffer)
- {
- /*将文件映射到内存,通过内存中的句柄获取文件DOS头*/
- PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;
- /*通过DOS头获取PE头*/
- PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + (DWORD)pBuffer);
- /*通过PE头中OptionalHeader结构中的AddressOfEntryPoint和ImageBase相加获取程序入口点*/
- DWORD dwOEP = pNtHeader->OptionalHeader.AddressOfEntryPoint + pNtHeader->OptionalHeader.ImageBase;
- /*初始化跳转数组,其中0x52和0x45分别为push edx指令和inc ebp,作用前面解释过。
- 0x68为push指令,后面四个0x00为预留的原始入口点地址,0xC3为retn指令*/
- BYTE JmpArray[] = {0x4D,0x5A,0x52,0x45,0x68,0x00,0x00,0x00,0x00,0xC3};
- /*数组的第五位起填入之前预留的原始入口点地址*/
- *(DWORD*)(JmpArray + 5) = dwOEP;
- memcpy( pBuffer,JmpArray,10 );
- /*将AddressOfEntryPoint元素置零*/
- pNtHeader->OptionalHeader.AddressOfEntryPoint = 0;
- }
用该方法修改notepad.exe,以下是修改前和修改后的notepad.exe的对比。仅此两处修改,其余均未变:
修改之后notepad照常运行,而入口点却是0了:
就先写到这了,权当学习记录了。