#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