写这篇文章的目的,是为了加强对系统的了解,强化一下编程的技术。
这篇文章是“
”的升级版本。所以这篇文章的代码是在之前文章的基础上增加的。
我把这篇文章分成如下几部分。
一.PE基础知识
二.基本思路
三.修改引入表
四.修改代码段
一.PE基础知识
手工打造可执行程序.rar 这是一个高人写的关于PE基础知识的资料,看了能详细的了解PE结构的各个部分。
二.基本思路
有了“
”这篇文章做为基础,下面要做的就是,
1)设计一个名为my.dll的DLL,同时导出一个MyFun的函数;
2)修改引入表,将my.dll插入到引入表中;
3)在代码段的最后部分增加一条jmp MyFun指令和一条jmp 原EntryPoint指令;
4)修改选项头(Optional Header)的EntryPoint,使其指向 Jmp MyFun的地址;
三.修改引入表
参考“
”
四.修改代码段
需要了解call,jmp指令的编码格式。
call指令编码格式如下:E8 偏移量的小字节序排列
偏移量 = 目标地址 - ( call指令所在地址 + call指令长度 )
call指令长度 一般为5个字节。
例如,当前call指令的地址是 0x2d0028,call指令长度为5个字节,那么下一条指令的地址就是0x2d0028 + 5 = 0x2d0033,假如 Jmp MyFun函数的起始地址是0x2d0043,那么我们call后边的数就是 0x2d0043- 0x2d0033 = 0x10,那么 call MyFun的实际的机器码就是 E8 10 00 00 00
jmp指令的使用比较复杂:
1)机器码为E9的JMP指令,后面的4字节是跳转的相对偏移。相对偏移 = 目标地址 - ( jmp指令所在地址 + jmp指令长度 )。intel处理器使用的是小尾序,所以这里的字节1B80FFFF 就是偏移FFFF801B,由此可以推算出这条JMP指令的地址是00401020 - FFFF801B -5 = 409000
所以说应该是在409000这里写了这条JMP指令,这样就好理解了。在409000这里第一个字节是JMP机器码E9,后面的就是JMP指令的下一条指令的地址409005 到 你的跳转目的地址00401020的偏移。
00401020 - (409000+5) = FFFF801B =小尾序> 1B80FFFF
所以这条jmp指令的机器码为: E9 1B 80 FF FF
2)
跳转指令有三种方式:短(short),近(near)和远(far)。短是指要跳至的目标地址与当前地址前后相差不超过128字节。近是指跳转的目标地址与当前地址在用一个段内,即CS的值不变,只改变EIP的值。远指跳到另一个代码段去执行,CS/EIP都要改变。短和近在编码上有所不同,在汇编指令中一般很少显式指定,只要写 "jmp
目标地址",几乎任何汇编器都会根据目标地址的距离采用适当的编码。远转移在32位系统中很少见到,原因前面已经讲过,由于有足够的线性空间,一个程序很少需要两个代码段,就连用到的系统模块也被映射到同一个地址空间。
jmp的操作数自然是目标地址,这个指令支持直接寻址和间接寻址。间接寻址又可分为寄存器间接寻址和内存间接寻址。举例如下(32位系统):
jmp 8E347D60 ;直接寻址段内跳转
jmp EBX ;寄存器间接寻址:只能段内跳转
jmp dword ptr [EBX] ;内存间接寻址,段内跳转
jmp dword ptr [00903DEC] ;同上
jmp fward ptr [00903DF0] ;内存间接寻址,段间跳转
解释:
在32位系统中,完整目标地址由16位段选择子和32位偏移量组成。因为寄存器的宽度是32位,因此寄存器间接寻址只能给出32位偏移量,所以只能是段内近转移。在内存间接寻址时,指令后面是方括号内的有效地址,在这个地址上存放跳转的目标地址。比如,在[00903DEC]处有如下数据:7C 82
59 00 A7 01 85 65 9F 01 把指定存储单元的字内容送到IP寄存器(0x827C),并把下一个字的内容送到CS寄存器(0x0059);
例子
jmp dword ptr [00903DEC] 的指令编码为: FF 25 EC 3D 90 00 在代码段的VirtualSize的末尾添加代码,首先调用MyFun函数,然后再跳转到 旧的Entry Point处执行。
//变量声明
const BYTE jmpOpcode[2]= {0xFF,0x25};
BYTE jmpOperand[4]={0x0};
DWORD dwRvaMyFun = 0;
unsigned long ulOldEntryPoint = 0;
//修改代码段
//old EntryPoint
ulOldEntryPoint = ntHeader->OptionalHeader.AddressOfEntryPoint;
dwRvaMyFun = myImport.FirstThunk;
int nIdx=sectionNum(lpBase,ntHeader->OptionalHeader.AddressOfEntryPoint);
//通过entry point 找到代码段的section Header
sectionHeader=(IMAGE_SECTION_HEADER*)((BYTE*)lpBase+dosHeader->e_lfanew+sizeof(IMAGE_NT_HEADERS))+nIdx;
BYTE *pAddr =((BYTE*)lpBase+sectionHeader->PointerToRawData+sectionHeader->Misc.VirtualSize);
//找到代码段的末尾
DWORD dwNewEntryPoint = sectionHeader->VirtualAddress + sectionHeader->Misc.VirtualSize;
//新的 Entrypoint,是一个file offset 需要变成 Rva;
//call 的地址为jmp MyFun函数所在的地址,
//call 的操作码为call的下一条指令到 jmp MyFun 的地址的Offset
*pAddr = 0xE8;
*(DWORD*)(pAddr +1 ) = 5 ; //到Jmp Myfun的Offset 是5个字节
//jmp oldEntryPoint
pAddr = pAddr + 5;
//jmp的相对偏移 = 目标地址- ( jmp当前地址 + 5)
*pAddr = 0xE9;
*(DWORD*)(pAddr+1) = ((BYTE*)lpBase+sectionHeader->PointerToRawData) - (pAddr + 5 );
//jmp MyFun
pAddr = pAddr + 5;
*pAddr = jmpOpcode[0];
*(pAddr+1) = jmpOpcode[1];
*(DWORD*)(pAddr+2) = dwRvaMyFun + ntHeader->OptionalHeader.ImageBase;
//填充新的entry point
ntHeader->OptionalHeader.AddressOfEntryPoint = dwNewEntryPoint;
sectionHeader->Misc.VirtualSize = 0X150; //修改代码段的size;
//修改.rdata头结构的size
nIdx=sectionNum(lpBase,ntHeader->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
sectionHeader=(IMAGE_SECTION_HEADER*)((BYTE*)lpBase+dosHeader->e_lfanew+sizeof(IMAGE_NT_HEADERS))+nIdx;
sectionHeader->Misc.VirtualSize = 0X192;
//---------------------------------
IIDMod.rar 此种方法加载my.dll时,如果MyFun的加载地址比较高,则容易失败。还需要继续查找原因。
阅读(1117) | 评论(0) | 转发(0) |