入口点介绍:
一个程序从最开头往下算大致可以分为DOS头,DOS头相关数据,PE头,然后才是文件数据,我们要找的入口点地址写在了PE头里面。DOS头的长度是固定的,但其相关数据是不固定的,好在DOS头里有一个LONG类型的e_lfanew,这个变量记载了PE头相对DOS头的偏移。
IMAGE_NT_HEADERS是PE头结构体的宏,在32位和64位下分别对应_IMAGE_NT_HEADERS和_IMAGE_NT_HEADERS64。
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
Signature是任何程序里都是固定不变的,就是ASCII的“PE”两个字
FileHeader对应的东西用不到不作说明
OptionalHeader结构体里的AddressOfEntryPoint是入口点。
获取入口点:
获取到入口点,要么你自己一层层调用得到入口点地址,要么就用API。
1. API——ImageLoad()函数
头文件ImageHlp.h
ImageLoad(
_In_ PCSTR DllName,
_In_opt_ PCSTR DllPath
);
就你填一个.exe文件地址和一个NULL,他最后返回给你一个LOADED_IMAGE结构体的指针,通过如下调用:
PLOADED_IMAGE image = ImageLoad("多字节字符集可执行文件地址",NULL);
DWORD dEnterPointer = image->FileHeader->OptionalHeader.AddressOfEntryPointer;
关于LOADED_IMAGE可以参考msdn介绍。
这个dEnterPointer就是入口点地址了。
最后记得释放image:
ImageUnLoad(Image);
因为加载文件头的本质就是把人家内存拷贝过来一份,用完了及时释放省的占地儿。
2. 不使用API的话就自己写一个ImageLoad
头文件:fstream.h
void* INJECT::ImageLoad(const wchar_t* filename){
std::ifstream streamReader(filename, std::ios::binary); //以二进制的形式读取文件
streamReader.seekg(0, std::ios::end); //跳转到尾部
unsigned filesize = streamReader.tellg(); //得到文件大小
streamReader.seekg(0, std::ios::beg); //跳转到开始
char* _data = new char[filesize];
streamReader.read(_data, filesize);
streamReader.close();
return _data;
}
void INJECT::UnloadImage(void* _data){
delete[] _data;
}
函数使用:
DWORD INJECT::GetEntryPoint(const wchar_t* filename){
void* image = ImageLoad(filename);//得到DOS头
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)image;//以IMAGE_DOS_HEADER解析信息
unsigned PEAddress = dosHeader->e_lfanew + (unsigned)image;//得到PE头
IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)PEAddress; //以IMAGE_NT_HEADERS解析PE头
DWORD dEnterPoint = ntHeader->OptionalHeader.AddressOfEntryPoint;//获取PE头内的入口点地址
UnloadImage(image);//关闭文件
return dEnterPoint;
}
几个结构体的关系:
LOADED_IMAGE 包含PIMAGE_NT_HEADERS32(PE头)
IMAGE_NT_HEADERS32 包含IMAGE_OPTIONAL_HEADER32(包含入口点位置的结构体)
IMAGE_OPTIONAL_HEADER32 包含DWORD类型的AddressOfEntryPoint(入口点)
API版的ImageLoad和手写的ImageLoad的区别且不谈,就获取到数据后的操作:
API:
DWORD dEnterPointer = image->FileHeader->OptionalHeader.AddressOfEntryPointer;
手写:
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)image; //以IMAGE_DOS_HEADER的形式解析信息
unsigned PEAddress = dosHeader->e_lfanew + (unsigned)image; //得到PE头
IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)PEAddress; //以解析PE头特有的结构解析PE头
DWORD dEnterPoint = ntHeader->OptionalHeader.AddressOfEntryPoint; //获取PE头内的入口点地址
可以看出手写的函数,代码传回的是指向DOS头的指针而非PLOADED_IMAGE,这个LOADED_IMAGE结构体内部就有PE头的已经计算好的地址,而我们手写的话,得到了DOS头地址,需要用DOS头地址加上e_lfanew才能得到真正的PE头。