查看原文
其他

PE加载器的简单实现

VicZ 看雪学院 2019-05-25

模拟PE加载器加载PE文件,对导入表以及重定位表的操作过程。

 

PE加载器的步骤:

 


1. 将PE文件用ReadFile读取数据


char szFileName[] = "1.exe";

//打开文件,设置属性可读可写
HANDLE hFile = CreateFileA(szFileName,
   GENERIC_READ | GENERIC_WRITE,
   FILE_SHARE_READ | FILE_SHARE_WRITE,
   NULL,
   OPEN_EXISTING,
   FILE_ATTRIBUTE_ARCHIVE,
   NULL);

if (INVALID_HANDLE_VALUE == hFile)
{
   printf("文件打开失败\n");
   return 1;
}

//获取文件大小
DWORD dwFileSize = GetFileSize(hFile, NULL);

//申请空间将exe读取到内存中
char *pData = new char[dwFileSize];
if (NULL == pData)
{
   printf("空间申请失败\n");
   return 2;
}

DWORD dwRet = 0;
ReadFile(hFile, pData, dwFileSize, &dwRet, NULL);
CloseHandle(hFile);


2. 根据PE结构获取镜像大小,在自己的程序中申请可读可写可执行的内存。


char* chBaseAddress = NULL;

//获取镜像大小
DWORD dwSizeOfImage = GetSizeOfImage(pFileBuff);

//在进程中开辟一块内存空间
chBaseAddress = (char*)VirtualAlloc(NULL,
   dwSizeOfImage,
   MEM_COMMIT | MEM_RESERVE,
   PAGE_EXECUTE_READWRITE);
if (NULL == chBaseAddress)
{
   printf("申请进程空间失败\n");
   return NULL;
}



3. 将申请的空间全部填为0


RtlZeroMemory(chBaseAddress, dwSizeOfImage);



4. 将用ReadFile读取的数据映射到内存中

 



由上图可知,PE文件在加载到内存中对齐粒度不同,因此在写代码时要注意这个问题:

PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuff;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pFileBuff + pDos->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
//所有头 + 结表头的大小
DWORD dwSizeOfHeaders = pNt->OptionalHeader.SizeOfHeaders;
//获取区段数量
int nNumerOfSections = pNt->FileHeader.NumberOfSections;

// 将前一部分都拷贝过去
RtlCopyMemory(chBaseAddress, pFileBuff, dwSizeOfHeaders);

char* chSrcMem = NULL;
char* chDestMem = NULL;
DWORD dwSizeOfRawData = 0;
for (int i = 0; i < nNumerOfSections; i++)
{
   if ((0 == pSection->VirtualAddress) ||
       (0 == pSection->SizeOfRawData))
   {
       pSection++;
       continue;
   }

   chSrcMem = (char*)((DWORD)pFileBuff + pSection->PointerToRawData);
   chDestMem = (char*)((DWORD)chBaseAddress + pSection->VirtualAddress);
   dwSizeOfRawData = pSection->SizeOfRawData;
   RtlCopyMemory(chDestMem, chSrcMem, dwSizeOfRawData);

   pSection++;
}

return TRUE;



5. 修复重定位

 

重定位后的地址 = 需要重定位的地址  -  默认加载基址  +  当前加载基址

PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)chBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(chBaseAddress + pDos->e_lfanew);
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)(chBaseAddress + pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

//判断是否有重定位表
if ((char*)pLoc == (char*)pDos)
{
   return TRUE;
}

while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //开始扫描重定位表
{
   WORD *pLocData = (WORD *)((PBYTE)pLoc + sizeof(IMAGE_BASE_RELOCATION));
   //计算需要修正的重定位项(地址)的数目
   int nNumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

   for (int i = 0; i < nNumberOfReloc; i++)
   {
       // 每个WORD由两部分组成。高4位指出了重定位的类型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定义了重定位类型的取值。
       // 低12位是相对于VirtualAddress域的偏移,指出了必须进行重定位的位置。

       if ((DWORD)(pLocData[i] & 0x0000F000) == 0x00003000) //这是一个需要修正的地址
       {
           DWORD* pAddress = (DWORD *)((PBYTE)pDos + pLoc->VirtualAddress +                                                                    (pLocData[i] & 0x0FFF));  
           DWORD dwDelta = (DWORD)pDos - pNt->OptionalHeader.ImageBase;
           *pAddress += dwDelta;
       }
   }

   //转移到下一个节进行处理
   pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
}

return TRUE;



6. 根据PE结构的导入表,加载所需的dll,并获取导入函数的地址并写入导入表中


PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)chBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(chBaseAddress + pDos->e_lfanew);
PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDos +
   pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

// 循环遍历DLL导入表中的DLL及获取导入表中的函数地址
char *lpDllName = NULL;
HMODULE hDll = NULL;
PIMAGE_THUNK_DATA lpImportNameArray = NULL;
PIMAGE_IMPORT_BY_NAME lpImportByName = NULL;
PIMAGE_THUNK_DATA lpImportFuncAddrArray = NULL;
FARPROC lpFuncAddress = NULL;
DWORD i = 0;

while (TRUE)
{
   if (0 == pImportTable->OriginalFirstThunk)
   {
       break;
   }

   // 获取导入表中DLL的名称并加载DLL
   lpDllName = (char *)((DWORD)pDos + pImportTable->Name);
   hDll = GetModuleHandleA(lpDllName);
   if (NULL == hDll)
   {
       hDll = LoadLibraryA(lpDllName);
       if (NULL == hDll)
       {
           pImportTable++;
           continue;
       }
   }

   i = 0;
   // 获取OriginalFirstThunk以及对应的导入函数名称表首地址
   lpImportNameArray = (PIMAGE_THUNK_DATA)((DWORD)pDos + pImportTable->OriginalFirstThunk);
   // 获取FirstThunk以及对应的导入函数地址表首地址
   lpImportFuncAddrArray = (PIMAGE_THUNK_DATA)((DWORD)pDos + pImportTable->FirstThunk);
   while (TRUE)
   {
       if (0 == lpImportNameArray[i].u1.AddressOfData)
       {
           break;
       }

       // 获取IMAGE_IMPORT_BY_NAME结构
       lpImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pDos + lpImportNameArray[i].u1.AddressOfData);

       // 判断导出函数是序号导出还是函数名称导出
       if (0x80000000 & lpImportNameArray[i].u1.Ordinal)
       {
           // 序号导出
           lpFuncAddress = GetProcAddress(hDll, (LPCSTR)                           (lpImportNameArray[i].u1.Ordinal & 0x0000FFFF));
       }
       else
       {
           // 名称导出
           lpFuncAddress = GetProcAddress(hDll, (LPCSTR)lpImportByName->Name);
       }
       lpImportFuncAddrArray[i].u1.Function = (DWORD)lpFuncAddress;
       i++;
   }

   pImportTable++;
}



7. 修改PE文件的加载基址


PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)chBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(chBaseAddress + pDos->e_lfanew);
pNt->OptionalHeader.ImageBase = (ULONG32)chBaseAddress;



8.跳转到PE的入口点处执行


PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)chBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(chBaseAddress + pDos->e_lfanew);
char* ExeEntry = (char*)(chBaseAddress + pNt->OptionalHeader.AddressOfEntryPoint);
// 跳转到入口点处执行
__asm
{
   mov eax, ExeEntry
   jmp eax
}


通过上述代码,成功加载并运行程序:




- End -


看雪ID:VicZ                

https://bbs.pediy.com/user-829513.htm


本文由看雪论坛 VicZ 原创

转载请注明来自看雪社区



热门图书推荐:

立即购买!


(点击图片即可进入)



热门技术文章:        






公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com



点击下方“阅读原文”

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存