编译过的二进制代码本身是一种数据结构,在代码被载入内存执行的时候,由操作系统对这种数据结构进行操作,具体到Win32平台,就是所谓的PE文件头。
对于Windows系统,源代码是如何转化为二进制代码?全局变量存储在什么地方,如何初始化?共享变量是如何工作?理解PE文件格式能更好地理解上面的问题.举个例子,我们使用C++编写源代码,这些源代码被编译器翻译成obj格式的目标文件,每一个目标文件都包含着全局变量、常量数据、资源、可执行代码、用于链接的符号名以及调试信息。模块的目标文件通过链接器与库链接在一起,而库自身是一种将目标文件组合在一起的格式。此外,链接器将所有已经初始化的全局变量合并到一个段中,将所有未初始化的全局变量合并到另一个段中,再将所有的执行代码放入到另一个段中等。为什么要将目标文件的不同部分进行分组分段呢?主要是保护资源和优化资源的使用。例如常数和可执行代码是只读的,如果操作系统检测到对这部分内存进行写操作时,就会发出错误。又例如,所有的Win32程序都是用同一个gdi32.dll,这样就优化了内存的使用,gdi32.dll的可执行代码段自在内存中存储一次。
PE文件格式
PE格式保留了多个目录,通常可以看到的目录有导入、绑定导入、延迟导入、到处、重定位、资源和调试信息。将这些段和目录组合在一起,
加上两个头,就成为了PE文件。
-----------------
|IMAGE_DOS_HEADER | : 兼容DOS程序运行
|-----------------|
|DOS 存根程序 | : 小程序,用于显示错误信息的软件中断然后退出,
|-----------------|
|PE Header | : 真正的PE头
|-----------------|
|Section Table | : 段表
|-----------------|
|Section 1 |
|-----------------|
|Section 2 |
|-----------------|
|Section ... |
|-----------------|
|Section n |
--------------
对于DOS存根程序,由于存根程序长度不定,所以要DOS头的e_lfanew确定偏移。
段的名称和作用如下:
节名 作用
.arch 最初的构建信息(Alpha Architecture Information)
.bss 未经初始化的数据
.CRT C运行期只读数据
.data 已经初始化的数据
.debug 调试信息
.didata 延迟输入文件名表
.edata 导出文件名表
.idata 导入文件名表
.pdata 异常信息(Exception Information)
.rdata 只读的初始化数据
.reloc 重定位表信息
.rsrc 资源
.text .exe或.dll文件的可执行代码
.tls 线程的本地存储器
.xdata 异常处理表
PE文件存储的地址有些以虚拟地址的形式存储,不过一旦其相应模块被加载,它们就可以使用。下面编写一个简单的PEFile的C++类,来说明
PE文件的使用。
我们要实现一个小小的钩子程序,将MessageBoxA改成我们自己设定的程序中去,由于程序比较简单,不再赘述,详见程序中的注释。
PEFile.h
#ifndef PE_FILE_H
#define PE_FILE_H
class PEFile
{
public:
PEFile(HMODULE _hModule);
const void* getDirectory(int _id);
PIMAGE_IMPORT_DESCRIPTOR getImportDescriptor(LPCSTR _pDllName);
const unsigned * getFunctionPtr(PIMAGE_IMPORT_DESCRIPTOR _pImport,LPCSTR _pProcName);
FARPROC setImportAddress(LPCSTR _pDllName,LPCTSTR _pProcName,FARPROC _pNewProc);
FARPROC setExportAddress(LPCSTR _pProcName,FARPROC _pNewProc);
private:
const char* m_pModule;//模块
PIMAGE_DOS_HEADER m_pDOSHeader;//DOS头
PIMAGE_NT_HEADERS m_pNTHeader;//NT头
};
#endif
PEFile.cpp
#include <Windows.h>
#include "PEFile.h"
PEFile::PEFile(HMODULE _hModule)
{
m_pModule = (const char*)_hModule;
if (IsBadReadPtr(m_pModule,sizeof(IMAGE_DOS_HEADER)))
{
m_pDOSHeader = 0;
m_pNTHeader = 0;
}
else
{
m_pDOSHeader = (PIMAGE_DOS_HEADER)m_pModule;
if (IsBadReadPtr(m_pModule + m_pDOSHeader->e_lfanew,sizeof(IMAGE_NT_HEADERS)))
{
m_pNTHeader = 0;
}
else
{
m_pNTHeader = (PIMAGE_NT_HEADERS)(m_pModule + m_pDOSHeader->e_lfanew);
}
}
}
const void* PEFile::getDirectory(int _id)
{
return m_pModule + (m_pNTHeader->OptionalHeader.DataDirectory[_id].VirtualAddress);
}
//返回引入的模块
PIMAGE_IMPORT_DESCRIPTOR PEFile::getImportDescriptor(LPCSTR _pDllName)
{
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)getDirectory(IMAGE_DIRECTORY_ENTRY_IMPORT);
if (pImport == 0)
{
return 0;
}
//一个一个查找
while(pImport->FirstThunk)
{
if (stricmp(_pDllName,m_pModule + pImport->Name) == 0)
{
return pImport;
}
++pImport;
}
return 0;
}
//返回引入的函数
const unsigned * PEFile::getFunctionPtr(PIMAGE_IMPORT_DESCRIPTOR _pImport,LPCSTR _pProcName)
{
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(m_pModule + _pImport->OriginalFirstThunk);
//一个一个查找
for (int i = 0; pThunk->u1.Function ; ++i)
{
bool isMatch = false;
if (pThunk->u1.Ordinal & 0x80000000)
{
isMatch = (pThunk->u1.Ordinal & 0xFFFF) == ((DWORD)_pProcName);
}
else
{
isMatch = stricmp(_pProcName,(m_pModule + ((unsigned)pThunk->u1.AddressOfData +2 ))) == 0;
}
if (isMatch)
{
return ((unsigned *)(m_pModule + _pImport->FirstThunk)) + i;
}
++pThunk;
}
return 0;
}
//设定导入的函数地址
FARPROC PEFile::setImportAddress(LPCSTR _pDllName,LPCTSTR _pProcName,FARPROC _pNewProc)
{
PIMAGE_IMPORT_DESCRIPTOR pImport = getImportDescriptor(_pDllName);
if (pImport)
{
//拿到某模块的某函数地址
const unsigned* pfn = getFunctionPtr(pImport,_pProcName);
if (IsBadReadPtr(pfn,sizeof(DWORD)))
{
return 0;
}
FARPROC oldproc = (FARPROC)*pfn;
//写入自定的函数地址
DWORD dwWritten;
WriteProcessMemory(GetCurrentProcess(),(void*)pfn,&_pNewProc,sizeof(DWORD),&dwWritten);
return oldproc;
}
return 0;
}
//导出目录
FARPROC PEFile::setExportAddress(LPCSTR _pProcName,FARPROC _pNewProc)
{
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)getDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT);
if (pExport == 0)
{
return 0;
}
unsigned ord = 0;
if ((unsigned)_pProcName < 0xFFFF)
{
ord = (unsigned)_pProcName;
}
else
{
const DWORD* pNames = (const DWORD*)(m_pModule + pExport->AddressOfNames);
const WORD* pOrds = (const WORD*)(m_pModule + pExport->AddressOfNameOrdinals);
for (unsigned i = 0; i < pExport->AddressOfNames; ++i)
{
if (stricmp(_pProcName,m_pModule + pNames[i]) == 0)
{
ord = pExport->Base + pOrds[i];
break;
}
}
}
if ( (ord < pExport->Base) || (ord > pExport->NumberOfFunctions))
{
return 0;
}
DWORD *pRVA = ((DWORD*)(m_pModule + pExport->AddressOfFunctions)) + ord - pExport->Base;
DWORD rslt = *pRVA;
DWORD dwWritten = 0;
DWORD newRVA = (DWORD)_pNewProc - (DWORD)m_pModule;
WriteProcessMemory(::GetCurrentProcess(),pRVA,&newRVA,sizeof(DWORD),&dwWritten);
return (FARPROC)(m_pModule + rslt);
}
测试代码:
#include <Windows.h>
#include "PEFile.h"
int WINAPI MyMessageBoxA(HWND hWnd,LPCSTR pText,LPCSTR pCaption,UINT uType)
{
char buf1[128];
char buf2[128];
strcpy(buf1,pCaption);
strcat(buf1," - catch!");
strcpy(buf2,pText);
strcat(buf2," - catch!");
return MessageBoxExA(hWnd,buf2,buf1,uType,0);
}
int WINAPI WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd )
{
{
PEFile pe(hInstance);
pe.setImportAddress("user32.dll","MessageBoxA",(FARPROC)MyMessageBoxA);
MessageBoxA(0,"Test","SetImportAddresss",MB_OK);
}
HMODULE hUser = GetModuleHandle("user32.dll");
PEFile user32(hUser);
FARPROC oldproc = GetProcAddress(hUser,"MessageBoxA");
user32.setExportAddress("MessageBoxA",(FARPROC)MyMessageBoxA);
FARPROC newproc = GetProcAddress(hUser,"MessageBoxA");
char temp[64];
wsprintf(temp, "GetProcAddress(MessageBoxA)\n"
"changes from %x to %x", oldproc, newproc);
MessageBoxA(NULL, temp, "SetExportAddress", MB_OK);
return 0;
}
运行一下可以发现,MessageBoxA已经变成自定义的函数,并且使用导出目录的方法导出函数,GetProcAddress将返回新函数的地址,将来DLL载入此进程时也会和新函数链接。
posted on 2011-09-02 20:18
bennycen 阅读(2331)
评论(2) 编辑 收藏 引用 所属分类:
Windows Programming