通过修改PE
文件导入导出表
来更改函数。单纯分析PE文件是一件比较无聊的事,通过修改可以更加灵活的利用和理解PE文件。这就像生活一样,总要有一些特别的事,才能更加有灵感。学习本身就是生活的一部分,如何让程序更加灵活,是一个比较值得去实践的事,可以更加接近计算机的一些思想。
VC++ 6.0
为了减少复杂度,用VC++ 6.0
编译sum.cpp sub.cpp
生成sum.dll sub.dll
,用dll_main.cpp
静态链接sum.dll
,要把dll_main
程序中的sum.dll的函数换成sub.dll的函数,改成两数想减,代码如下:
程序: https://github.com/sv4us/binary/blob/master/dll分析.zip
sub.cpp
extern "C" _declspec(dllexport) int sub(int a,int b);
int sub(int a,int b)
{
return a-b;
}
sum.cpp
extern "C" _declspec(dllexport) int sum(int a,int b);
int sum(int a,int b)
{
return a+b;
}
主函数dll_main.cpp
#include
#include
using namespace std;
extern "C" _declspec(dllimport) int sum(int a,int b);
int main(int argc,char *argv[])
{
int a;
a = sum(8,10);
cout << "a=" <<a <<endl;
system("pause");
return 0;
}
环境
010 Editor , CFF Explorer
,PE结构图和说明文档
用010 Editor
打开dll_main.exe
这边只关心IMAGE_NT_HEADERS
,010 Ediotr
的插件看出(直接搜索PE
字符串也可以看出,实际上是(IMAGE_DOS_HEADER->AddressOfNewExeHeader)处的内容,括号为取出地址上的内容,DOS头的最后一个成员),由于IMAGE
和RVA
均指的是PE加载到内存时的用语,文件中用RAW
,但PE结构一样,以下的IMAGE
和RVA
均指文件未加载到内存时的情况。
NT头的起始地址
: E8h
,内容如下 Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
000000E0 00 00 00 00 00 00 00 00 50 45 00 00 4C 01 03 00 ........PE..L.
函数与导入导出表有关,查看EXE的数据目录结构 IMAGE_DATA_DIRECTORY距IMAGE_NT_HEADERS
偏移量+78H
处,
IMAGE_DATA_DIRECTORY地址
= E8H + 78H = 160H ,内容如下 Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
00000160 00 00 00 00 00 00 00 00 68 53 01 00 3C 00 00 00 ........hS.<...
NtHeader下面是(Signature, IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER32)
, FileHeader
距NtHeader偏移量+4H
处, OptionalHeader
距NtHeader偏移量+18H
处
IMAGE_DATA_DIRECTORY
为OptionalHeader
的最后一个字段(IMAGE_OPTIONAL_HEADER32->IMAGE_DATA_DIRECTORY),距NtHeader偏移量+78H
处,是一个结构数组(16个)。
typedef struct IMAGE_DATA_DIRECTORY{
DWORD VirtualAddress ;//数据起始地址RVA
DWORD Size ;//数据块长度
}
IMAGE_DATA_DIRECTORY
结构数组第一项 :IMAGE_DATA_DIRECTORY_Export
输出表,上面EXE没有输出表,为8Byte的0x00
IMAGE_DATA_DIRECTORY
结构数组第二项 :IMAGE_DATA_DIRECTORY_Import
输入表,RVA = 0X0001 5368H Size = 0x0000 003CH
DWORD
,双字4字节32位(与机器字长不同,x86_64机器字长64位,64/8 = 8字节,1字8字节)
IMAGE_DATA_DIRECTORY_Import
地址 = (NtHeader + 80H) VA = 0X0001 5368H ,查看此处地址,15368H ~ 153A3H Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
00015360 FF FF FF FF F8 20 41 00 A4 54 01 00 00 00 00 00 ?A......
00015370 00 00 00 00 B2 54 01 00 00 31 01 00 A4 53 01 00 ....睺..1..
00015380 00 00 00 00 00 00 00 00 24 59 01 00 00 30 01 00 ........$Y..0.
00015390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000153A0 00 00 00 00 0E 57 01 00 12 59 01 00 BA 54 01 00 ....W.Y.篢.
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //指向INT(输入名称表),RVA
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; //指向DLL名称,RVA
DWORD FirstThunk; //指向IAT(输入地址表),RVA
},
//IMAGE_IMPORT_DESCRIPTOR为一个数组,没有字段指出项数,但最后一个单元为NULL,IID结构长度5个字,20Byte,14H。15390H ~ 153A3H 20个0,共2项输入,sum.dll,KERNEL32.dll
Name的RVA = 0x0001 54B2H 距IMAGE_IMPORT_DESCRIPTO偏移3个字(union算1字),+CH
FirstThunk的RVA = 0x0001 3100H
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
000154B0 6D 00 73 75 6D 2E 64 6C 6C 00 E4 01 4D 75 6C 74 m.sum.dll.?Mult
Name的RVA = 0x0001 5924H 距IMAGE_IMPORT_DESCRIPTO偏移3个字(union算1字),+CH
FirstThunk的RVA = 0x0001 3000H
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
00015920 6F 57 00 00 4B 45 52 4E 45 4C 33 32 2E 64 6C 6C oW..KERNEL32.dll
typedef struct IMAGE_THUNK_DATA { //指针大小联合,长度一个DWORD
union u1 {
ForwarderString dd; //指向一个转向字符的RVA
Function dd; //被输入函数的内存地址
Oridinal dd; //被输入函数API序列值
AddressOfData dd; //指向IMAGE_IMPORT_BY_NAME
}
}
//IMAGE_THUNK_DATA最高位为1时,函数以序号导出,Oridinal低31位为输出函数在其DLL的导出序号, 最高位为0时,函数以字符串导出, AddressOfData指向用来导入函数名称的IMAGE_IMPORT_BY_NAME的数据结构RVA
typedef struct IMAGE_IMPORT_BY_NAME {
HINT WORD ; //函数序号,非必须
NAME BYTE; //导入函数的名称,大小可变,以0结尾的ascii字符串
}
FirstThunk的RVA = 0x0001 3100H,查看13100处的内容
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
00013100 AC 54 01 00 00 00 00 00 00 00 00 00 FF FF FF FF 琓.........
IAT(sum.dll,RVA) = 0x0001 54ACH
0x0001 54ACH处的内容
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
000154A0 00 00 00 00 AC 54 01 00 00 00 00 00 00 00 73 75 ....琓.......su
000154B0 6D 00 73 75 6D 2E 64 6C 6C 00 E4 01 4D 75 6C 74 m.sum.dll.?Mult
HINT = 0X0000 NAME = sum
FirstThunk的RVA = 0x0001 3000H, sum.dll RVA = 0x00013100H查看13000~131F0处的内容
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
00013000 0E 57 01 00 12 59 01 00 BA 54 01 00 D0 54 01 00 W.Y.篢.蠺.
000130F0 DC 58 01 00 E8 58 01 00 F8 58 01 00 00 00 00 00 躕.鑈.鳻.....
可以看出IMAGE_THUNK_DATA以0x00000000结束
第一个 IAT(KERNEL32.dll,RVA) = 0x0001 570EH
0x0001 570EH处的内容
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
00015700 15 01 47 65 74 46 69 6C 65 54 79 70 65 00 50 01 GetFileType.P
00015710 47 65 74 53 74 61 72 74 75 70 49 6E 66 6F 41 00 GetStartupInfoA.
HINT = 0X0150 NAME = GetStartupInfoA
通过CFF EXPLORER
import directory -> 查看输入表 ,hex editor -> 查看十六进制
NtHeader
(‘PE00’) ->
IMAGE_DATA_DIRECTORY
(NT+78H,第二项)) ->
DATA_DIRECTORY_Import
(NT+80H,DDI->VirtualAddress,rva) ->
IMAGE_IMPORT_DESCRIPTOR
(IID -> FirstThunk(IID+4字),rva) ->
IMAGE_THUNK_DATA
(IAT,ITD -> AddressOfData) ->
IMAGE_IMPORT_BY_NAME
(IIBN -> NAME)
DLL名称 = IMAGE_IMPORT_DESCRIPTOR -> NAME
函数名 = IMAGE_IMPORT_BY_NAME -> NAME
NtHeader = E8h
Import = NtHeader + 80H = 168H = (0001 5368 0000 003c)
IMAGE_IMPORT_DESCRIPTOR->name = Import(15368H) + 3字 = 15374H = (0001 54b2)
DLL名称: 154b2H = (73 73 6d 2e 64 6c 6c sum.dll) 改为 (73 73 62 2e 64 6c 6c sub.dll)
IMAGE_IMPORT_DESCRIPTOR->FirstThunk = Import(15368H) + 4字 = 15378H = (0001 3100)
IMAGE_THUNK_DATA->AddressOfData = 13100H = (0001 54ac)
函数名: 154acH = (00 00 73 75 6d) 改为 (00 00 73 75 62)
原来 8+10 = 18 改完后 8-10= -2
(可以发现函数名和DLL名在154b0h
这一行,用CFF Explorer查看输出表: sub.dll和sum.dll的Oridinal Function RVA Name RVA都一样)
程序: https://github.com/sv4us/binary/blob/master/dll分析.zip