Run-PE-In-Memory(The update is not complete)

该代码实现了在内存中加载PE文件并执行的功能,包括映射文件到内存、处理重定位、修复导入地址表(IAT)以及替换特定函数(如`GetCommandLine`)以伪装命令行。此外,还涉及到了如何通过`LoadLibrary`和`GetProcAddress`获取API地址。
摘要由CSDN通过智能技术生成
#include "stdafx.h"
#include <Windows.h>
#include "peBase.hpp"
#include "fixIAT.hpp"
#include "fixReloc.hpp"

bool peLoader(const char *exePath, const wchar_t* cmdline)
{
	LONGLONG fileSize = -1;
	BYTE *data = MapFileToMemory(exePath, fileSize);//读取该文件到内存
	BYTE* pImageBase = NULL;
	LPVOID preferAddr = 0;//LPVOID是一个没有类型的指针,也就是说你可以将任意类型的指针赋值给LPVOID类型的变量(一般作为参数传递),然后在使用的时候在转换回来
	IMAGE_NT_HEADERS *ntHeader = (IMAGE_NT_HEADERS *)getNtHdrs(data);//得到NT头的地址
	if (!ntHeader) 
	{
		printf("[+] File %s isn't a PE file.", exePath);
		return false;
	}

	IMAGE_DATA_DIRECTORY* relocDir = getPeDir(data, IMAGE_DIRECTORY_ENTRY_BASERELOC);//对PE文件的地址进行重定位
	preferAddr = (LPVOID)ntHeader->OptionalHeader.ImageBase;//内存预先加载的目标基址(RAV)
	printf("[+] Exe File Prefer Image Base at %x\n", preferAddr);

	HMODULE dll = LoadLibraryA("ntdll.dll");//便利API函数
	((int(WINAPI*)(HANDLE, PVOID))GetProcAddress(dll, "NtUnmapViewOfSection"))((HANDLE)-1, (LPVOID)ntHeader->OptionalHeader.ImageBase);//这是啥?
	
	pImageBase = (BYTE *)VirtualAlloc(preferAddr, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if (!pImageBase && !relocDir)//检测基址和重定位
	{
		printf("[-] Allocate Image Base At %x Failure.\n", preferAddr);
		return false;
	}
	if (!pImageBase && relocDir)//如果基址被占用则重新申请一块地址
	{
		printf("[+] Try to Allocate Memory for New Image Base\n");
		pImageBase = (BYTE *)VirtualAlloc(NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
		if (!pImageBase)
		{
			printf("[-] Allocate Memory For Image Base Failure.\n");
			return false;
		}
	}
	
	puts("[+] Mapping Section ...");
	ntHeader->OptionalHeader.ImageBase = (size_t)pImageBase;
	memcpy(pImageBase, data, ntHeader->OptionalHeader.SizeOfHeaders);//复制确认PE文件头部的大小

	IMAGE_SECTION_HEADER * SectionHeaderArr = (IMAGE_SECTION_HEADER *)(size_t(ntHeader) + sizeof(IMAGE_NT_HEADERS));//扩展头的地址
	for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++)
	{
		printf("    [+] Mapping Section %s\n", SectionHeaderArr[i].Name);
		memcpy
		(
			LPVOID(size_t(pImageBase) + SectionHeaderArr[i].VirtualAddress),
			LPVOID(size_t(data) + SectionHeaderArr[i].PointerToRawData),
			SectionHeaderArr[i].SizeOfRawData
		);
	}

	// for demo usage: 
	// masqueradeCmdline(L"C:\\Windows\\RunPE_In_Memory.exe Demo by aaaddress1");
	masqueradeCmdline(cmdline);
	fixIAT(pImageBase);//导入IAT表,同时输出了每个API调用的地址

	if (pImageBase != preferAddr) //判断文件是否加载到目标地址,否则进行重定位
		if (applyReloc((size_t)pImageBase, (size_t)preferAddr, pImageBase, ntHeader->OptionalHeader.SizeOfImage))
		puts("[+] Relocation Fixed.");
	size_t retAddr = (size_t)(pImageBase)+ntHeader->OptionalHeader.AddressOfEntryPoint;
	printf("Run Exe Module: %s\n", exePath);

	((void(*)())retAddr)();
}

int main(int argc, char **argv)
{
	 if (argc != 2)//运行的命令参数是两个,也就是自身运行文件,和另外一个运行文件
	{
		printf("Usage: %s [Exe Path]", strrchr(argv[0], '\\') ? strrchr(argv[0], '\\') + 1 : argv[0]);/*C 库函数 char* strrchr(const char* str, int c)
		在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置该函数  
		返回值:
		返回 str 中最后一次出现字符 c 的位置。如果未找到该值,则函数返回一个空指针*/
		getchar();
		return 0;
	}
 

	peLoader(argv[1], NULL);//读取PE文件的路径
	getchar();
    return 0;
}


Headfile

fixIAT,hpp
#include <string>
#include <windows.h>
using namespace std;

bool hijackCmdline = false;
char* sz_masqCmd_Ansi = NULL, *sz_masqCmd_ArgvAnsi[100] = {  };
wchar_t* sz_masqCmd_Widh = NULL, *sz_masqCmd_ArgvWidh[100] = { };
int int_masqCmd_Argc = 0;
/*LPCSTR是Win32和VC++所使用的一种字符串数据类型。LPCSTR被定义成是一个指向以'\0'结尾的常量字符的指针。

LPWSTR是wchar_t字符串

LPCWSTR是一个指向unicode编码字符串的32位指针,所指向字符串是wchar型,而不是char型。

LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的32位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的64位双字节字符数组指针
*/
LPWSTR hookGetCommandLineW() { return sz_masqCmd_Widh; }//检索当前进程的命令行字符串,返回值是指向当前进程的命令行字符串的指针
LPSTR hookGetCommandLineA() { return sz_masqCmd_Ansi;  }

int __wgetmainargs(int* _Argc, wchar_t*** _Argv, wchar_t*** _Env, int _useless_, void* _useless) {
	*_Argc = int_masqCmd_Argc;
	*_Argv = (wchar_t **)sz_masqCmd_ArgvWidh;//和下面的编码不一样
	return 0;//得到主函数地址
}
int __getmainargs(int* _Argc, char*** _Argv, char*** _Env, int _useless_, void* _useless) {
	*_Argc = int_masqCmd_Argc;
	*_Argv = (char **)sz_masqCmd_ArgvAnsi;
	return 0;
}

void masqueradeCmdline(const wchar_t* cmdline) {//用来检测文件路径
	if (!cmdline) return;
	auto sz_wcmdline = wstring(cmdline);//强制转换字符

	// 
	sz_masqCmd_Widh = new wchar_t[sz_wcmdline.size() + 1];
	lstrcpyW(sz_masqCmd_Widh, sz_wcmdline.c_str());//将字符串复制到缓冲区

	/*返回值
	类型: LPTSTR

   如果函数成功,则返回值是指向缓冲区的指针。

   如果函数失败,则返回值为 NULL,并且 lpString1 可能不会以 null 结尾。*/
	//
	auto k = string(sz_wcmdline.begin(), sz_wcmdline.end());
	sz_masqCmd_Ansi = new char[k.size() + 1];
	lstrcpyA(sz_masqCmd_Ansi, k.c_str());

	wchar_t** szArglist = CommandLineToArgvW(cmdline, &int_masqCmd_Argc);//分析 Unicode 命令行字符串,并返回指向命令行参数的指针数组以及此类参数的计数,
	//其方式类似于标准 C 运行时 argv 和 argc 值。参数1:指向包含完整命令行的 以 null 结尾的 Unicode 字符串的指针。 
	//如果此参数为空字符串,则函数返回当前可执行文件的路径。
	//参数2指向接收返回的数组元素数的 int 的指针,类似于 argc
	//返回值:
	//指向 LPWSTR 值数组的指针,类似于 argv。
   //如果函数失败,则返回值为 NULL。 要获得更多的错误信息,请调用 GetLastError。
	for (size_t i = 0; i < int_masqCmd_Argc; i++) {
		sz_masqCmd_ArgvWidh[i] = new wchar_t[lstrlenW(szArglist[i]) + 1];
		lstrcpyW(sz_masqCmd_ArgvWidh[i], szArglist[i]);
    //lstrlenW 函数:确定指定字符串的长度 (不包括终止 null 字符) 。
		auto b = string(wstring(sz_masqCmd_ArgvWidh[i]).begin(), wstring(sz_masqCmd_ArgvWidh[i]).end());
		sz_masqCmd_ArgvAnsi[i] = new char[b.size() + 1];
		lstrcpyA(sz_masqCmd_ArgvAnsi[i], b.c_str());
	}

	hijackCmdline = true;
}


bool fixIAT(PVOID modulePtr)//对导入表进行检索
{
	printf("[+] Fix Import Address Table\n");
	IMAGE_DATA_DIRECTORY *importsDir = getPeDir(modulePtr, IMAGE_DIRECTORY_ENTRY_IMPORT);//文件运行的虚拟地址
	if (importsDir == NULL) return false;

	size_t maxSize = importsDir->Size;//大小
	size_t impAddr = importsDir->VirtualAddress;//相对虚拟地址

	IMAGE_IMPORT_DESCRIPTOR* lib_desc = NULL;//导入表参数,引导系统找到真正保存有导入信息的其他两个结构
	//IMAGE_THUNK_DATA
	//IMAGE_IMPORT_BY_NAME
	size_t parsedSize = 0;

	for (; parsedSize < maxSize; parsedSize += sizeof(IMAGE_IMPORT_DESCRIPTOR)) {
		lib_desc = (IMAGE_IMPORT_DESCRIPTOR*)(impAddr + parsedSize + (ULONG_PTR)modulePtr);

		if (lib_desc->OriginalFirstThunk == NULL && lib_desc->FirstThunk == NULL) break;//检测指向输入名称表的表(INT)的RVA
		LPSTR lib_name = (LPSTR)((ULONGLONG)modulePtr + lib_desc->Name);//DLL文件函数名
		printf("    [+] Import DLL: %s\n", lib_name);

		size_t call_via = lib_desc->FirstThunk;//指向输入地址表的表(IAT)的RVA
		size_t thunk_addr = lib_desc->OriginalFirstThunk;
		if (thunk_addr == NULL) thunk_addr = lib_desc->FirstThunk;//先测测再赋值地址

		size_t offsetField = 0;
		size_t offsetThunk = 0;
		while (true)
		{
			IMAGE_THUNK_DATA* fieldThunk = (IMAGE_THUNK_DATA*)(size_t(modulePtr) + offsetField + call_via);//文件导入表地址IAT
			IMAGE_THUNK_DATA* orginThunk = (IMAGE_THUNK_DATA*)(size_t(modulePtr) + offsetThunk + thunk_addr);//文件导入表INT地址

			if (orginThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32 || orginThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64) // 检查是否使用序号(x86 和 x64)
            {
                size_t addr = (size_t)GetProcAddress(LoadLibraryA(lib_name), (char *)(orginThunk->u1.Ordinal & 0xFFFF));
                printf("        [V] API %x at %x\n", orginThunk->u1.Ordinal, addr);//显示出地址
                fieldThunk->u1.Function = addr;
            }
			
			if (fieldThunk->u1.Function == NULL) break;//检测导入表函数的RVA

			if (fieldThunk->u1.Function == orginThunk->u1.Function) {
				
				PIMAGE_IMPORT_BY_NAME by_name = (PIMAGE_IMPORT_BY_NAME)(size_t(modulePtr) + orginThunk->u1.AddressOfData);

				LPSTR func_name = (LPSTR)by_name->Name;
				size_t addr = (size_t)GetProcAddress(LoadLibraryA(lib_name), func_name);//遍历函数
				printf("        [V] API %s at %x\n", func_name, addr);//显示

				if (hijackCmdline && strcmpi(func_name, "GetCommandLineA") == 0)//逐一检测拥有运行当前进程的函数
					fieldThunk->u1.Function = (size_t)hookGetCommandLineA;
				else if (hijackCmdline && strcmpi(func_name, "GetCommandLineW") == 0)
					fieldThunk->u1.Function = (size_t)hookGetCommandLineW;
				else if (hijackCmdline && strcmpi(func_name, "__wgetmainargs") == 0)
					fieldThunk->u1.Function = (size_t)__wgetmainargs;
				else if (hijackCmdline && strcmpi(func_name, "__getmainargs") == 0)
					fieldThunk->u1.Function = (size_t)__getmainargs;
				else
					fieldThunk->u1.Function = addr;

			}
			offsetField += sizeof(IMAGE_THUNK_DATA);//计算文件运行地址
			offsetThunk += sizeof(IMAGE_THUNK_DATA);//计算RVA地址
		}
	}
	return true;
}
peBase.hpp
#include <windows.h>
#include <fstream>
#define _CRT_SECURE_NO_WARNINGS
#pragma warning( disable : 4996 )


BYTE* MapFileToMemory(LPCSTR filename, LONGLONG &filelen)
{
	FILE *fileptr;
	BYTE *buffer;

	fileptr = fopen(filename, "rb");  //以二进制模式打开文件
	fseek(fileptr, 0, SEEK_END);          // 跳转到文件末尾
	filelen = ftell(fileptr);             // 获取文件中的当前字节偏移量
	rewind(fileptr);                      // 跳回到文件的开头

	buffer = (BYTE *)malloc((filelen + 1) * sizeof(char)); // 文件内存不足 + 0
	fread(buffer, filelen, 1, fileptr); // 读入整个文件
	fclose(fileptr); // 关闭文件

	return buffer;
}


BYTE* getNtHdrs(BYTE *pe_buffer)//读取PE文件的NT头
{
	if (pe_buffer == NULL) return NULL;

	IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER*)pe_buffer;//DOS文件头
	if (idh->e_magic != IMAGE_DOS_SIGNATURE) {
		return NULL;//检查DOS头e_magic成员的格式
	}
	const LONG kMaxOffset = 1024;
	LONG pe_offset = idh->e_lfanew;//NT头的偏移位置
	if (pe_offset > kMaxOffset) return NULL;
	IMAGE_NT_HEADERS32 *inh = (IMAGE_NT_HEADERS32 *)((BYTE*)pe_buffer + pe_offset);//得到运行文件的位置
	if (inh->Signature != IMAGE_NT_SIGNATURE) return NULL;//对位置进行检测
	return (BYTE*)inh;
}

IMAGE_DATA_DIRECTORY* getPeDir(PVOID pe_buffer, size_t dir_id)//得到相对虚拟地址
{
	if (dir_id >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) return NULL;//扩展文件头

	BYTE* nt_headers = getNtHdrs((BYTE*)pe_buffer);
	if (nt_headers == NULL) return NULL;//检测运行文件地址正不正确

	IMAGE_DATA_DIRECTORY* peDir = NULL;//PE文件的目录

	IMAGE_NT_HEADERS* nt_header = (IMAGE_NT_HEADERS*)nt_headers;
	peDir = &(nt_header->OptionalHeader.DataDirectory[dir_id]);

	if (peDir->VirtualAddress == NULL) {
		return NULL;//检测相对虚拟地址
	}
	return peDir;//最后返回
}
// stdafx.h : 可在此標頭檔中包含標準的系統 Include 檔,
// 或是經常使用卻很少變更的
// 專案專用 Include 檔案
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>



// TODO:  在此參考您的程式所需要的其他標頭
#pragma once

// 加上 SDKDDKVer.h 可定義最高可用的 Windows 平台。

// 如果要針對先前的 Windows 平台建置應用程式,請加上 WinSDKVer.h,
// 並在加上 SDKDDKVer.h 之前將 _WIN32_WINNT 巨集設為要支援的平台。

#include <SDKDDKVer.h>

fixReloc
#include <windows.h>
//这段代码用于修复基地址重定位表。函数 applyReloc 接受四个参数:新基地址 newBase,旧基地址 oldBase,模块指针 modulePtr,以及模块大小 moduleSize。它会遍历基地址

typedef struct _BASE_RELOCATION_ENTRY {
	WORD Offset : 12;//重新定位条目与页面开头的偏移量
	WORD Type : 4;//搬迁条目的类型
} BASE_RELOCATION_ENTRY;

#define RELOC_32BIT_FIELD 3//32 位重定位条目的类型

bool applyReloc(ULONGLONG newBase, ULONGLONG oldBase, PVOID modulePtr, SIZE_T moduleSize)
{
	IMAGE_DATA_DIRECTORY* relocDir = getPeDir(modulePtr, IMAGE_DIRECTORY_ENTRY_BASERELOC);
	if (relocDir == NULL) /* Cannot relocate - application have no relocation table 无法重新定位 - 应用程序没有重新定位表*/
		return false;

	size_t maxSize = relocDir->Size;//重新定位目录的大小
	size_t relocAddr = relocDir->VirtualAddress;//重定位目录的虚拟地址
	IMAGE_BASE_RELOCATION* reloc = NULL;

	size_t parsedSize = 0;
	for (; parsedSize < maxSize; parsedSize += reloc->SizeOfBlock) {//遍历重定位块
		reloc = (IMAGE_BASE_RELOCATION*)(relocAddr + parsedSize + size_t(modulePtr));
		if (reloc->VirtualAddress == NULL || reloc->SizeOfBlock == 0)
			break;

		size_t entriesNum = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(BASE_RELOCATION_ENTRY);//区块中的搬迁条目数
		size_t page = reloc->VirtualAddress;//块引用的页面的虚拟地址

		BASE_RELOCATION_ENTRY* entry = (BASE_RELOCATION_ENTRY*)(size_t(reloc) + sizeof(IMAGE_BASE_RELOCATION));// 块中的第一个搬迁条目
		for (size_t i = 0; i < entriesNum; i++) {//循环访问重定位条目
			size_t offset = entry->Offset;//重新定位条目与页面开头的偏移量
			size_t type = entry->Type;//重新定位条目的类型
			size_t reloc_field = page + offset;//需要重新定位的字段的虚拟地址
			if (entry == NULL || type == 0)
				break;
			if (type != RELOC_32BIT_FIELD) {
				printf("    [!] Not supported relocations format at %d: %d\n", (int)i, (int)type);
				return false;
			}
			if (reloc_field >= moduleSize) {
				printf("    [-] Out of Bound Field: %lx\n", reloc_field);
				return false;
			}

			size_t* relocateAddr = (size_t*)(size_t(modulePtr) + reloc_field);//需要重新定位的字段的地址
			printf("    [V] Apply Reloc Field at %x\n", relocateAddr);
			(*relocateAddr) = ((*relocateAddr) - oldBase + newBase);//将重定位应用于字段
			entry = (BASE_RELOCATION_ENTRY*)(size_t(entry) + sizeof(BASE_RELOCATION_ENTRY));// 移动到下一个搬迁条目
		}
	}
	return (parsedSize != 0);
}

https://github.com/aaaddress1/RunPE-In-Memory

Figure out all the code above and write comments to deeply understand the operation of PE files in memory
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Back~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值