WindowsPE文件格式入门05.PE加载器LoadPE
https://bpsend.net/thread-316-1-1.htmlLoadPE - pe 加载器 壳的前身
如果想访问一个程序运行起来的内存,一种方法就是跨进程读写内存,但是跨进程读写内存需要来回调用api,不如直接访问地址来得方便,那么如果我们需要直接访问地址,该怎么做呢?.需要把dll注进程,注进去的代码跟他在同一块进程里面,这样我们就可以直接访问这个进程的地址,但是不想注入,也不想跨进程读写地址,就直接读进程地址,有什么方法呢?如果把导入表里面加一个dll,这样很容易检查出来,模块里一遍历,就可容易看到了, 其实我们可以反其道行之,换种思路,不是往他进程里面塞东西,而是把他加载到我们进程里面.,这个时候再去访问进程内存其实就是访问我们自己的内存.1.系统加载exe的流程
[*]准备一个新的内存;
[*]按照顺序把节表内容映射进内存;
[*]填写导入表。
2.LoadPE的目的
[*]可以在自己进程更改目标PE的内存。
3.LoadPE的重点
[*]1.在自己代码前留出足够空间--目标进程的SizeofImage;
[*]2.更改自己程序的ImageBase加载到目标ImageBase处:/base:0x。
这样,目标进程的PE头就占据了我们自己进程的 PE头,他的数据节就覆盖了我们自己的数据节,即目标进程的数据就会覆盖我们自己进程的数据,因此我们需要 把我们的代码往后移,给 目标进程 留出足够的空间
4.LoadPE的实现思路
[*]设置我们进程的 ImageBase 和 目标进程 ImageBase 保持一致
[*]我们需要把我们的代码往后移,腾出来的内存可以放目标程序
[*]读目标进程的数据,按照节表拷贝数据帮他处理导入表
[*]执行目标进程代码。
5.LoadPE的汇编实现
[*]用winhex查看目标进程的 ImageBase 和 SizeofImage
[*]新建工程,并且在工程选项设置 自己工程 的 ImageBase 与目标进程的一致
[*]后移自己的代码,可以在开头定义 SizeofImage 大小的全局变量 或者通过指令 org 偏移调整指令
注意,很多 C 库函数 并不会 保存 ecx , edx 环境,自己使用前记得先保存
进程的模块基址和 数据大小可以通过 winhex看
.586
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
include kernel32.inc
include msvcrt.inc
includelib user32.lib
includelib kernel32.lib
includelib msvcrt.lib
IMAGE_SIZE equ 20000h ;往后移的大小,即目标进程的SizeofImage
.data
g_szFile db "winmine.exe", 0 ;要读取的进程
.code
org IMAGE_SIZE
LoadPe proc
LOCAL @dwImageBase:DWORD ;自己进程的模块基址
LOCAL @hFile:HANDLE ;文件句柄
LOCAL @hFileMap:HANDLE ;映射句柄
LOCAL @pPEBuf:LPVOID ;映射文件的缓冲地址
LOCAL @pDosHdr:ptr IMAGE_DOS_HEADER ;目标进程的dos头
LOCAL @pNTHdr:ptr IMAGE_NT_HEADERS ;目标进程的NT头
LOCAL @pSecHdr:ptr IMAGE_SECTION_HEADER ;目标进程的节表
LOCAL @dwNumOfSecs:DWORD ;目标进程的节表数量
LOCAL @pImpHdr:ptr IMAGE_IMPORT_DESCRIPTOR ;目标进程的导入表
LOCAL @dwSizeOfHeaders:DWORD ;目标进程的选项头大小
LOCAL @dwOldProc:DWORD ;旧的内存属性
LOCAL @hdrZeroImp:IMAGE_IMPORT_DESCRIPTOR ;导入表结束标志,所有项全0
LOCAL @hDll:HMODULE ;加载dll的句柄
LOCAL @dwOep:DWORD ;进程的入口地址
;判断导入表结束的标志清0
invoke RtlZeroMemory, addr @hdrZeroImp, size IMAGE_IMPORT_DESCRIPTOR
;自己的模块基址
invoke GetModuleHandle, NULL
mov @dwImageBase, eax
;解析PE文件,获取表
;打开文件
invoke CreateFile, offset g_szFile, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL
;check ....
mov @hFile, eax ;保存文件句柄
invoke CreateFileMapping, @hFile, NULL, PAGE_READONLY, 0, 0, NULL ;创建文件映射
;check
mov @hFileMap, eax ;创建文件映射句柄
invoke MapViewOfFile, @hFileMap, FILE_MAP_READ, 0, 0, 0 ;将整个文件映射进内存
;check
mov @pPEBuf, eax ;保存映射文件内存的地址
;解析目标进程
;目标进程的 dos 头
mov eax, @pPEBuf
mov @pDosHdr, eax
;目标进程的 nt头
mov esi, @pDosHdr
assume esi:ptr IMAGE_DOS_HEADER
mov eax, @pPEBuf
add eax, .e_lfanew ;获取nt头的偏移地址
mov @pNTHdr, eax
mov esi, @pNTHdr
assume esi:ptr IMAGE_NT_HEADERS
;选项头信息
mov eax, .OptionalHeader.SizeOfHeaders ;获取选项头大小
mov @dwSizeOfHeaders, eax
;进程的入口地址 = 进程的内存偏移地址 + 模块基址
mov eax, .OptionalHeader.AddressOfEntryPoint
add eax, @dwImageBase
mov @dwOep, eax
;节表 地址: 选项头地址+大小
movzx eax, .FileHeader.NumberOfSections
mov @dwNumOfSecs,eax
lea ebx, .OptionalHeader
;获取选项头大小:用于定位节表位置=选项头地址+选项头大小
movzx eax, .FileHeader.SizeOfOptionalHeader ;把 word 转为 dword
add eax, ebx
mov @pSecHdr, eax ;保存节表地址
;修改内存属性
invoke VirtualProtect, @dwImageBase, IMAGE_SIZE, PAGE_EXECUTE_READWRITE, addr @dwOldProc
;拷贝PE头 从映射内存拷贝到 自己进程的最开始处
invoke crt_memcpy, @dwImageBase, @pPEBuf, @dwSizeOfHeaders
;按照节表,拷贝节区数据
mov esi, @pSecHdr
assume esi:ptr IMAGE_SECTION_HEADER
xor ecx, ecx
.while ecx < @dwNumOfSecs ;遍历节表
;目标
mov edi, @dwImageBase
add edi, .VirtualAddress ;获取节的内存地址 + 模块地址 就是内存中的绝对地址
;源
mov ebx, @pPEBuf
add ebx, .PointerToRawData ;获取指定进程的节数据的偏移地址 映射的首地址 + 文件偏移地址
;大小.SizeOfRawData
;拷贝 注意,很多 C 库函数 并不会 保存 ecx ,edx 环境,自己使用前记得先保存
push ecx
push edx
invoke crt_memcpy, edi, ebx, .SizeOfRawData ;将目标进程的节数据拷贝进自己的进程
pop edx
pop ecx
inc ecx ;计数++
add esi, size IMAGE_SECTION_HEADER ;指针移动
.endw
;获取导入表 如果在前面获取导入表信息,那么就需要对内存地址和文件地址做转化比较麻烦
;但是把数据拷贝到我们进程之后只需要访问内存进程就可以了
mov esi, @pNTHdr
assume esi:ptr IMAGE_NT_HEADERS
;获取导入表地址 ,数组的第二个元素的第一个成员
mov eax, .OptionalHeader.DataDirectory.VirtualAddress
add eax, @dwImageBase ;获取导入表在进程的绝对地址 内存偏移 + 模块基址
mov @pImpHdr, eax ;保存导入表的地址
;处理导入表
mov esi, @pImpHdr
assume esi:ptr IMAGE_IMPORT_DESCRIPTOR
.while TRUE ;遍历导入表
;判断结束,全0项结束
invoke crt_memcmp, esi, addr @hdrZeroImp
.if eax == 0
.break
.endif
;判断字段,为空则结束
.if .Name1 == NULL || .FirstThunk == NULL
.break
.endif
;加载dll
mov eax, .Name1
add eax, @dwImageBase
push ecx
push edx
invoke LoadLibrary, eax ;根据dll名加载 dll
pop edx
pop ecx
;check 如果此时为空加说明无法找到dll
mov @hDll, eax ;保存dll的模句柄
;获取导入地址表,IAT
mov ebx, .FirstThunk
add ebx, @dwImageBase
;获取导入名称表,INT
mov edi, ebx
.if .OriginalFirstThunk != NULL
mov edi, .OriginalFirstThunk
add edi, @dwImageBase
.endif
;遍历导入名称表
.while dword ptr != 0
.if dword ptr & 80000000h ;判断最高位是否为1
;序号导入,获取序号
mov edx, dword ptr
and edx, 0ffffh ;获取低 word
.else
;名称导入
mov edx, dword ptr
add edx, @dwImageBase
add edx, 2 ;名称前面有2个无用字节
.endif
;获取dll导入函数进程加载后地址
push ecx
push edx
invoke GetProcAddress, @hDll, edx
pop edx
pop ecx
;check
;把地址存入 INT 表
mov dword ptr , eax
add ebx, 4
add edi, 4
.endw
add esi, size IMAGE_IMPORT_DESCRIPTOR
.endw
;清理
invoke UnmapViewOfFile,@pPEBuf
invoke CloseHandle,@hFileMap
invoke CloseHandle,@hFile
; 执行加载的pe的代码
jmp @dwOep
ret
LoadPe endp
start:
invoke LoadPe
invoke ExitProcess,0
end start#include <windows.h>
#include <concrt.h>
#include <iostream>
using namespace std;
#define IMAGE_SIZE 0x5000
//留空的全局变量
char szBuff = { 1 };
#if 1
char g_szExeName[] = "PE.exe"; //需要加载的PE路径
LPVOID lpMapAddr = NULL;
HANDLE LoadPe()
{
DWORD dwOldProc = 0;
PIMAGE_IMPORT_DESCRIPTOR pImpHdr = NULL;
IMAGE_IMPORT_DESCRIPTOR zeroImp = { 0 };
HANDLE hDll = NULL;
PIMAGE_THUNK_DATA pTmpThunk = NULL;
DWORD dwPFNAddr = 0;
DWORD dwIAT = 0;
// 获取模块句柄
HANDLE hInst = GetModuleHandle(NULL);
// 读取文件
HANDLE hFile = CreateFile(g_szExeName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return 0;
}
// 创建文件映射对象
HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hFileMap == NULL)
{
CloseHandle(hFile);
return 0;
}
void* lpMapAddr = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (lpMapAddr == NULL)
{
CloseHandle(hFileMap);
CloseHandle(hFile);
return 0;
}
// 拷贝PE头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpMapAddr;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)lpMapAddr + pDosHeader->e_lfanew);
DWORD dwSizeOfHeaders = pNtHeader->OptionalHeader.SizeOfHeaders;
DWORD dwNumOfSection = pNtHeader->FileHeader.NumberOfSections;
//获取节表首地址 (选项头地址+ 大小)
PIMAGE_SECTION_HEADER pSecHdr = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeader->FileHeader.SizeOfOptionalHeader + (DWORD)(&pNtHeader->OptionalHeader));
DWORD dwOep = (DWORD)hInst + pNtHeader->OptionalHeader.AddressOfEntryPoint;
// 更改内存属性
VirtualProtect(hInst, IMAGE_SIZE, PAGE_EXECUTE_READWRITE, &dwOldProc);
//拷贝PE头
memcpy(hInst, lpMapAddr, dwSizeOfHeaders);
// 拷贝拷贝节区数据
DWORD i = 0;
while (dwNumOfSection > i)
{
//拷贝数据
DWORD pDst = (DWORD)hInst + (DWORD)pSecHdr->VirtualAddress;
DWORD pSrc = (DWORD)lpMapAddr + (DWORD)pSecHdr->PointerToRawData;
//DWORD pSrc = (DWORD)pSecHdr;
memcpy((void*)pDst, (void*)pSrc, pSecHdr->SizeOfRawData);
int nSecHdrSzie = sizeof(IMAGE_SECTION_HEADER);
pSecHdr = (PIMAGE_SECTION_HEADER)((DWORD)pSecHdr + (DWORD)nSecHdrSzie);
i++;
}
//获取导入表地址
pImpHdr = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pNtHeader->OptionalHeader.DataDirectory.VirtualAddress
+ (DWORD)hInst);
// 处理导入表
while (TRUE)
{
// 遇到全0项,遍历结束
int nRet = memcmp(pImpHdr, &zeroImp, sizeof(IMAGE_IMPORT_DESCRIPTOR));
if (nRet == 0)
{
break;
}
//判断字段, 为空则结束
if (pImpHdr->Name == NULL || pImpHdr->FirstThunk == NULL)
{
break;
}
DWORD pNameAddre = (DWORD)pImpHdr->Name + (DWORD)hInst;
// 加载dll
HMODULE hDll = LoadLibrary((LPCSTR)pNameAddre);
if (hDll == NULL)
{
break;
}
DWORD pFunAddr = 0;
DWORD pIAT = (DWORD)pImpHdr->FirstThunk + (DWORD)hInst;
DWORD pINT = (DWORD)pImpHdr->OriginalFirstThunk;
if (pINT != 0)
{
pFunAddr = pINT + (DWORD)hInst;
}
else
{
pFunAddr = pIAT;
}
// 遍历导入名称表
while (true){
DWORD pAddr = *(DWORD*)pFunAddr;
if (pAddr == 0)
{
break;
}
DWORD pFun = 0;
if (pAddr & 0x80000000)
{
//序号导入, 获取序号
pFun = pAddr & 0xffff;
}
else
{
pFun = pAddr + 2 + (DWORD)hInst;
}
DWORD dwPFNAddr = (DWORD)GetProcAddress(hDll, (LPCSTR)LOWORD(pFun));
*(DWORD*)pIAT = dwPFNAddr;
pIAT += 4;
pFunAddr+=4;
}
pImpHdr = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImpHdr + (DWORD)sizeof(IMAGE_IMPORT_DESCRIPTOR));
}
// 关闭句柄
UnmapViewOfFile(lpMapAddr);
CloseHandle(hFileMap);
CloseHandle(hFile);
// 返回地址
return &dwOep;
}
#endif // 0
int main()
{
#if 1
// 加载PE文件,返回原OEP
void* pOep = LoadPe();
if (pOep != NULL)
{
__asm jmp pOep
}
#endif // 0
return 0;
}思路: 主要难点是代码后移,留出空间
方法1: 内联 汇编 开始 nop
方法2: 把代码放到dll中
方法3: 合并节,全局变量是放在未初始化的节内合并后就会在第一个节
合并节代码:
#include <iostream>
#include <Windows.h>
#include <stdio.h>
using namespace std;
#define FILESIZE 0x00020000 //目标进程的SizeofImage
#define IMAGEBASE 0x01000000
char szExeDataBuff = {1};
char g_szExeName[] = "winmine.exe"; //要加载的进程路径
DWORD g_oep = 0;
void LoadPe()
{
// 创建文件映射
//打开文件
HANDLE hFile = CreateFile(g_szExeName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return ;
}
// 创建文件映射对象
HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hFileMap == NULL)
{
CloseHandle(hFile);
return;
}
//将文件映射进内存
LPVOID pView = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (pView == NULL)
{
CloseHandle(hFileMap);
CloseHandle(hFile);
return;
}
// 修改代码节属性
DWORD nOldProtect = 0;
VirtualProtect((LPVOID)IMAGEBASE, FILESIZE, PAGE_EXECUTE_READWRITE, &nOldProtect);
// 处理PE
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pView;
// 获取pe头大小
PIMAGE_NT_HEADERS32 pNt = (PIMAGE_NT_HEADERS32)((DWORD)pView + pDos->e_lfanew);
memcpy((void*)IMAGEBASE, (void*)pDos, pNt->OptionalHeader.SizeOfHeaders);
// OEP
g_oep = pNt->OptionalHeader.AddressOfEntryPoint + IMAGEBASE;
// 解析节
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((DWORD) & (pNt->OptionalHeader.Magic) + pNt->FileHeader.SizeOfOptionalHeader);
// 节数量
unsigned int nNumberOfSections = pNt->FileHeader.NumberOfSections;
while (nNumberOfSections != 0)
{
void* pVirtualAddress = (void*)(pSec->VirtualAddress + IMAGEBASE);
void* pFileAddress = (void*)(pSec->PointerToRawData + (DWORD)pDos);
memcpy(pVirtualAddress, pFileAddress, pSec->SizeOfRawData);
nNumberOfSections--;
pSec++;
}
if (pNt->OptionalHeader.NumberOfRvaAndSizes > 2)
{
// 处理导入表
PIMAGE_IMPORT_DESCRIPTOR pIm = (PIMAGE_IMPORT_DESCRIPTOR)(pNt->OptionalHeader.DataDirectory.VirtualAddress + IMAGEBASE);
while (pIm->Name && pIm->FirstThunk)
{
HMODULE hModule = LoadLibraryA((char*)(pIm->Name + IMAGEBASE));
if (hModule == NULL)
{
pIm++;
continue;
}
DWORD dwThunkData = pIm->OriginalFirstThunk ? pIm->OriginalFirstThunk : pIm->FirstThunk;
if (dwThunkData == 0)
{
return ;
}
PIMAGE_THUNK_DATA pThunkData = (PIMAGE_THUNK_DATA)(dwThunkData + IMAGEBASE);
DWORD* pPfn = (DWORD*)(pIm->FirstThunk + IMAGEBASE);
while (pThunkData->u1.AddressOfData)
{
FARPROC farProc = 0;
LPCSTR lpParam2 = 0;
// 最高位判断
if (pThunkData->u1.AddressOfData & 0x80000000)
{
// 序号
lpParam2 = LPCSTR(LOWORD(pThunkData->u1.AddressOfData));
}
else
{
// 函数名
lpParam2 = LPCSTR(pThunkData->u1.AddressOfData + IMAGEBASE + 2);
}
farProc = GetProcAddress(hModule, lpParam2);
// 填充IAT
*pPfn = (DWORD)farProc;
pThunkData++;
pPfn++;
}
pIm++;
}
}
//取消映射
UnmapViewOfFile(pView);
//关闭文件映射对象
CloseHandle(hFileMap);
//关闭文件
CloseHandle(hFile);
}
int main()
{
LoadPe();
if (g_oep != 0)
{
__asm
{
jmp g_oep;
}
}
return 0;
}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]