首页 > 系统相关 >以修改内存的方式实现导入表动态注入

以修改内存的方式实现导入表动态注入

时间:2022-12-06 12:23:53浏览次数:71  
标签:return IMAGE char 修改 导入 内存 IMPORT DWORD hProcess

简介

搜索"导入表注入", 网上大堆的博客和代码, 统统都是修改PE文件实现的. 这里将介绍exe加载到内存后, 修改主模块映像, 而不必去改变本地的exe文件的注入方法.

原理

注入原理

简单来说, exe被加载时, 会遍历导入表, 依次使用LoadLibrary加载导入表的依赖dll, 而我们要做的是, 在exe被加载前, 就给导入表新增一个项, 使其能够加载我们指定的dll.

PE知识

这一章, 我们要用到PE文件的解析, 导入表的遍历, 导入表项的添加等功能.下面来复习一下:

Dos头固定占0x40大小, 其最后4字节是一个叫AddressOfNewExeHeader的成员, 指示的是Nt头相对于文件的偏移.

image

在Nt头中又分为三部分, 分别是Signature,FileHeader,OptionalHeader, Signature的值固定为0x4550, 翻译成ASCII是PE两个字符, FileHeader文件头中比较重要的成员是SizeOfOptionalHeader, 这决定了我们要如何去解析扩展头. OptionalHeader扩展头中主要用到的成员DataDirArray, 该成员是扩展头的最后一个成员,它是一个数组, 里面记录了各种表, 如导出表,导入表, 资源表, 重定位表等, 这一章主要要用到的是导入表, 即DataDirArray[1], DataDirArray[1]里面有两个成员, 分别是VirtualAddressSize,指定了导入表的RVA和大小, 后面我们建立新的导入表后, 这两个成员也需要更改.

image

导入表的VirtualAddress指向的是IMAGE_IMPORT_DESCRIPTOR数组, 这个数组的最后一项要置空, 因为PE文件是靠IMAGE_IMPORT_DESCRIPTOR的最后一个成员FirstThunk是否为NULL来判断是否为最后一个导入表项的, 而不是DataDirArray[1].Size.

实现细节

启动进程

前面提到, 由于导入表被加载的时机比较早, 所以为实现动态导入表注入, 我们要以挂起的方式来创建目标进程, 代码如下

HANDLE createProcessSuspendly(wstring processFileName)
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi = { 0 };

	if (CreateProcessW(processFileName.c_str(), NULL, NULL, NULL, FALSE, IDLE_PRIORITY_CLASS | CREATE_SUSPENDED, NULL, NULL, &si, &pi))
	{
		CloseHandle(pi.hProcess);
		return pi.hThread;
	}
	return INVALID_HANDLE_VALUE;
}
获取进程句柄
#include <TlHelp32.h>
DWORD getProcessIdByName(const wchar_t* name)
{
	PROCESSENTRY32 entry = { sizeof(PROCESSENTRY32) };
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	DWORD id = 0;
	if (INVALID_HANDLE_VALUE == hSnapshot)
	{
		return 0;
	}
	if (!Process32FirstW(hSnapshot, &entry))
	{
		return 0;
	}
	do
	{
		if (wcscmp(entry.szExeFile, name) == 0)
		{
			return entry.th32ProcessID;
		}
	} while (Process32Next(hSnapshot, &entry));
	return 0;
}

......
    
DWORD pid = getProcessIdByName(processName);
if (pid == 0) return false;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
if (hProcess == INVALID_HANDLE_VALUE) return false;
遍历模块

一般遍历模块的方法是使用EnumProcessModulesEx来遍历, 但这种方式对部分程序不起作用. 没有深究原因, 我更换了另一种遍历模块的方案, 即通过内存搜索的方式, 来寻找所有的模块, 这种方式会找到很多在模块列表看不到的dll, 但这不妨碍我们找到主模块的基址.

// 这里代码从IDA copy的
unsigned __int64 __fastcall findNextModule(HANDLE hProcess, __int64 base, DWORD *buffer)
{
	unsigned __int64 address; // rbx
	DWORD v6; // eax
	struct _MEMORY_BASIC_INFORMATION mbi; // [rsp+30h] [rbp-C8h] BYREF
	HANDLE v9; // [rsp+60h] [rbp-98h]
	DWORD *v10; // [rsp+70h] [rbp-88h]
	__int16 Buffer[32]; // [rsp+80h] [rbp-78h] BYREF

	mbi.BaseAddress = 0;
	mbi.AllocationBase = 0;
	mbi.AllocationProtect = 0;
	mbi.RegionSize = 0;
	mbi.State = 0;
	mbi.Type = 0;
	address = 0x10000;
	if (base)
		address = base + 0x10000;
	while (VirtualQueryEx(hProcess, (LPCVOID)address, &mbi, 0x30ui64)
		&& (mbi.RegionSize & 0xFFF) != 0xFFFi64
		&& (char *)mbi.BaseAddress + mbi.RegionSize >= (PVOID)address)
	{
		if (mbi.State == 4096)
		{
			v6 = mbi.Protect;
            // 这里Buffer即Dos头
			if (LOBYTE(mbi.Protect) != 1
				&& !_bittest((const long *)&v6, 8u)
				&& ReadProcessMemory(hProcess, (LPCVOID)address, Buffer, 0x40ui64, 0i64)
				&& Buffer[0] == 0x5A4D
				&& *(unsigned int *)&Buffer[30] <= mbi.RegionSize
				&& *(DWORD *)&Buffer[30] >= 0x40u
				&& ReadProcessMemory(hProcess, (LPCVOID)(address + *(int *)&Buffer[30]), buffer, 0xF8ui64, 0i64)
				&& *buffer == 0x4550)
			{
				return address;
			}
		}
		address = (unsigned __int64)mbi.BaseAddress + mbi.RegionSize;
	}
	return 0i64;
}


HMODULE getModuleByName(string moduleName)
{
    // 这里用于存放PE文件的头0x140个字节
    char buf[0x140] = { 0 };
	// 通过内存检索的方式遍历模块,并通过判断Characteristics的IMAGE_FILE_DLL位是否为1来判断是否为主模块
	unsigned long long moduleBase = findNextModule(hProcess, (ULONG_PTR)GetModuleHandle(moduleName.c_str()), (DWORD*)buf);
	char * finalModuleBase = NULL;
	do
	{
		if ((*(WORD*)(buf + 0x16) & IMAGE_FILE_DLL) == 0)
		{
			finalModuleBase = (char *)moduleBase;
			break;
		}
		moduleBase = findNextModule(hProcess, moduleBase, (DWORD*)buf);
	} while (moduleBase);
}

获取主模块PE结构信息
// 获取Dos头
IMAGE_DOS_HEADER dosHeader = { 0 };
if (!readMemory(hProcess, iTunesModuleBase, (char*)&dosHeader, sizeof(dosHeader)))
    return false;

// 获取Signature
DWORD signature = { 0 };
char* signatureAddress = iTunesModuleBase + dosHeader.e_lfanew;
if (!readMemory(hProcess, signatureAddress, (char*)&signature, sizeof(DWORD)))
    return 0;

// 获取文件头
IMAGE_FILE_HEADER fileHeader = { 0 };
char* fileHeaderAddress = signatureAddress + sizeof(signature);
if (!readMemory(hProcess, fileHeaderAddress, (char*)&fileHeader, sizeof(IMAGE_FILE_HEADER)))
    return 0;

// 获取扩展头
IMAGE_OPTIONAL_HEADER optHeader = { 0 };
char* optHeaderAddress = fileHeaderAddress + sizeof(IMAGE_FILE_HEADER);
if (!readMemory(hProcess, optHeaderAddress, (char*)&optHeader, fileHeader.SizeOfOptionalHeader))
    return 0;

// 读取节区表
IMAGE_SECTION_HEADER* sectionHeader = new IMAGE_SECTION_HEADER[fileHeader.NumberOfSections];
char* sectionHeaderAddress = optHeaderAddress + fileHeader.SizeOfOptionalHeader;
if (!readMemory(hProcess, sectionHeaderAddress, (char*)sectionHeader, fileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)))
    return 0;
申请内存

在目标进程中申请内存, 用于存放新的导入表和字符串等信息. 这里我只申请了一次内存, 并将这部分内存分为多段来使用.需要注意的是, 后面会有许多地方要填主模块相对于这块内存的偏移, 即RVA, 且数据结构为4字节, 所以此处申请的内存地址的位置与主模块首地址的差值不能超过4GB, 至于这段内存的地址是否可以比主模块小, 没有尝试过, 感兴趣的可自行尝试.

// 用于对齐内存
unsigned long long align(unsigned long long value)
{
	unsigned long long v = value % 0x10;
	if (v != 0)
	{
		value += (0x10-v);
	}
	return value;
}

// 远程申请内存
void* remoteAllocMemory(HANDLE hProcess, PVOID beginAddress, int size)
{
	MEMORY_BASIC_INFORMATION mbi = { 0 };
	if (VirtualQueryEx(hProcess, beginAddress, &mbi, 0x30))
	{
		while (true)
		{
			if ((mbi.RegionSize & 0xFFF) == 0xFFF)
				break;
			if (mbi.State == 0x10000)
			{
				for (char* address = (char*)mbi.BaseAddress; address < (char*)mbi.BaseAddress + mbi.RegionSize; address +=0x10000)
				{
					if (VirtualAllocEx(hProcess, address, size, MEM_RESERVE, PAGE_READWRITE))
					{
						PVOID result = VirtualAllocEx(hProcess, address, size, MEM_COMMIT, PAGE_READWRITE);
						if (result) return result;
					}
				}
			}
			char* baseAddress = (char*)mbi.BaseAddress;
			size_t regionSize = mbi.RegionSize;
			if (!VirtualQueryEx(hProcess, &baseAddress[regionSize], &mbi, 0x30))
			{
				int e = GetLastError();
				return nullptr;
			}

		}
	}
}

size_t newImportTableSize = align(optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size + sizeof(IMAGE_IMPORT_DESCRIPTOR));
size_t newStringTableSize = align(strlen(dllName));
size_t newImportByNameSize = align(sizeof(WORD)+strlen(exportName));
size_t newThunkDataSize = align(sizeof(IMAGE_THUNK_DATA) * 2);
size_t newMemorySize = newImportTableSize + newStringTableSize + newImportByNameSize+ newThunkDataSize;

char* queryAddress = iTunesModuleBase + optHeader.SizeOfCode + optHeader.SizeOfInitializedData + optHeader.SizeOfUninitializedData + optHeader.BaseOfCode;
IMAGE_IMPORT_DESCRIPTOR* newImportTable = (IMAGE_IMPORT_DESCRIPTOR*)remoteAllocMemory(hProcess, queryAddress, newMemorySize);

writeZeroMemory(hProcess, newImportTable, newMemorySize);
// 用于存放字符串
// dllName\0+Hint+exportName\0
char* newStringTable = (char*)newImportTable + newImportTableSize;

// 存放IMAGE_IMPORT_BY_NAME结构体, IMAGE_THUNK_DATA里面唯一的成员Ordinal就是该结构体的RVA
IMAGE_IMPORT_BY_NAME* newImportByName = (IMAGE_IMPORT_BY_NAME*)(newStringTable + newStringTableSize);
// 用于INT和IAT
IMAGE_THUNK_DATA* newThunkData = (IMAGE_THUNK_DATA*)((char*)newImportByName + newImportByNameSize);
修改导入表

这里有几步操作

  1. 将dll的名字写入到目标进程. 由于系统加载PE文件的导入表项时, 也是使用LoadLibrary函数,而它支持指定绝对路径和省略路径两种模式, 故此处也是一样. 如果将要注入的dll放到exe同目录下, 便可只往目标进程写入dll的名字, 否则要指定路径+名字.

  2. 拷贝原导入表到新的内存. 由于要扩展导入表, 所以需要将原导入表放到新申请的内存中, 并将旧的导入表拷贝过来. 需要注意的是, 导入表最后一定要留一个空的导入表项(即IMAGE_IMPORT_DESCRIPTOR), PE文件是靠IMAGE_IMPORT_DESCRIPTOR的最后一个成员FirstThunk是否为NULL来判断是否为最后一个导入表项的, 而不是扩展头中的optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size.

  3. 填充导出函数. 结构为IMAGE_IMPORT_BY_NAME, 定义如下:

    typedef struct _IMAGE_IMPORT_BY_NAME {
        WORD    Hint;
        CHAR   Name[1];
    } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
    

    前两字节为导出函数的序号, 后面为导出函数的名字

  4. 在新的导入表后面写入新的导入表项(IMAGE_IMPORT_DESCRIPTOR). 需要填充OriginalFirstThunk,Name,FirstThunk三个成员. 其中OriginalFirstThunkFirstThunk分别用于INT和IAT, 这两个地方填充IMAGE_THUNK_DATA结构体的RVA.

    IMAGE_THUNK_DATA又是什么呢?这是ms的定义:

    typedef struct _IMAGE_THUNK_DATA64 {
        union {
            ULONGLONG ForwarderString;  // PBYTE 
            ULONGLONG Function;         // PDWORD
            ULONGLONG Ordinal;
            ULONGLONG AddressOfData;    // PIMAGE_IMPORT_BY_NAME
        } u1;
    } IMAGE_THUNK_DATA64;
    

    只有一个DWORD_PTR类型的成员, 它存放的是一个叫IMAGE_IMPORT_BY_NAME结构体的RVA, 这便是第3步中所创建的结构体.

    // 填充导出函数
    // 导出函数的结构为IMAGE_IMPORT_BY_NAME
    WORD Hint = 1;
    writeMemory(hProcess, (char*)newImportByName, (char*)&Hint, sizeof(WORD));
    writeMemory(hProcess, (char*)newImportByName + sizeof(WORD), (char*)exportName, strlen(exportName));
    
    // 填充INT
    DWORD_PTR Ordinal = (DWORD_PTR)((char*)newImportByName - iTunesModuleBase);
    writeMemory(hProcess, newThunkData, &Ordinal, sizeof(DWORD_PTR));
    DWORD OriginalFirstThunk = 0;
    DWORD FirstThunk = (DWORD_PTR)newThunkData - (DWORD_PTR)iTunesModuleBase;
    if (boundImport)
    {
        OriginalFirstThunk = 0;
    }
    else
    {
        OriginalFirstThunk = (DWORD_PTR)newThunkData - (DWORD_PTR)iTunesModuleBase;
    }
    
    // 填充注入dll的improt descriptor, 其后要紧跟一个空的描述符, 用于判定描述符表是否结束, 由于在申请内存后, 将内存置0了,故此处无需额外处理
    writeMemory(hProcess, &newImportTable[i].OriginalFirstThunk, &OriginalFirstThunk, sizeof(DWORD));
    DWORD Name = (DWORD)(newStringTable-iTunesModuleBase);
    writeMemory(hProcess, &newImportTable[i].Name, &Name, sizeof(DWORD));
    writeMemory(hProcess, &newImportTable[i].FirstThunk, &FirstThunk, sizeof(DWORD));
    
  5. 将新的导入表信息应用到扩展头.

    // 修改导入表的大小
    DWORD importSizeOffset = (DWORD_PTR)&optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size - (DWORD_PTR)&optHeader;
    // 这里i为原导入表的数量(包括结尾置空的那一项)
    optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size += (i + 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR);
    writeMemory(hProcess, optHeaderAddress + importSizeOffset, &optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size, sizeof(DWORD));
    // 修改导入表的VA
    DWORD importVirtualAdressOffset = (DWORD_PTR)&optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - (DWORD_PTR)&optHeader;
    // 这里的VirtualAddress为4字节类型, 所以VA不能超过4GB
    optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = (DWORD)newImportTable - (DWORD)iTunesModuleBase;
    writeMemory(hProcess, optHeaderAddress + importVirtualAdressOffset, &(optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress), sizeof(DWORD));
    

调试

由于本例中大量操作其他进程的内存, 所以传统的调试方法就不是那么的有用, 需要与x64dbg/od等调试器,010Edit结合起来使用, 并灵活使用二分删除法来缩减问题的规模.

如, 当代码写好后, 如果程序不能按预期运行, 使用调试器附加目标进程, 并检查申请的内存地址与主模块的RVA是否超过了4GB, 如果申请内存没问题, 那么我们继续检查其他部分. 将填充字符串, 增加新的导入表项等功能删掉, 只保留将原导入表拷贝到新内存的流程. 如果这一步也没问题, 再慢慢加上其他部分的代码, 一步步的缩小问题的规模. 如果猜测可能是导入表填充的有误, 那么, 你可以在调试器中进行检查.

举例: 如图所示, 此处我导入表项填充的有问题, 那么我可以通过调试器的内存搜索功能来进行排错.

image

首先, 我们将这些二进制数据, 与结构体对应起来. 导入表项的结构为

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

5个成员, 共20字节. 上图上红色标记比是我填充的导入表项, 绿色标记笔是原导入表的最后一个有值的项. 我们分别去检查这两个IMAGE_IMPORT_DESCRIPTOR结构的每一个成员, 其中OriginalFirstThunk, Name, FirstThunk三个成员存放的都是RVA, 使用他们加上主模块基址便可找到对应的结构. 最后通过比较两个导入表项的结构, 发现是FirstThunk指向的IMAGE_THUNK_DATA填充有误. 同理, 如果存在其他问题, 也很容易就可以分析出来了.

完整源码

注入器
#include <iostream>
#include <Windows.h>
#include <mutex>
using namespace std;


#include <windows.h>
#include <iostream>
#include <exception>
#include <string>
#include <TlHelp32.h>
#include <psapi.h>
#pragma comment(lib,"psapi.lib")

using namespace std;

struct ModuleInfo
{
	TCHAR szExeFile[MAX_PATH];	// 模块文件名
	DWORD ImageBase;
	DWORD SizeOfImage;
};

struct ProcessInfo
{
	ModuleInfo MainModuleInfo;	// 主模块信息
	DWORD dwPID;				// 进程ID
	ModuleInfo *modules;		// 子模块数组
	DWORD dwModules;			// 子模块数量
};


// 枚举进程地址空间内的模块句柄,返回数组长度
DWORD EnumModulesHandle(HANDLE hProcess, HMODULE **lpModule)
{
	DWORD cbBytesNeeded = 0;
	// 备注:EnumProcessModules 函数无法枚举64位进程的模块,除非程序以64位编译
	int ret = EnumProcessModulesEx(hProcess, NULL, 0, &cbBytesNeeded, LIST_MODULES_64BIT); // 计算数组大小
	int error = GetLastError();
	*lpModule = (HMODULE *)malloc(cbBytesNeeded + 0x1000);
	ret = EnumProcessModulesEx(hProcess, *lpModule, cbBytesNeeded + 0x1000, &cbBytesNeeded, LIST_MODULES_64BIT); // 枚举模块句柄
	return cbBytesNeeded / sizeof(HMODULE);
}

unsigned __int64 __fastcall findNextModule(HANDLE hProcess, __int64 base, DWORD *buffer)
{
	unsigned __int64 address; // rbx
	DWORD v6; // eax
	struct _MEMORY_BASIC_INFORMATION mbi; // [rsp+30h] [rbp-C8h] BYREF
	HANDLE v9; // [rsp+60h] [rbp-98h]
	DWORD *v10; // [rsp+70h] [rbp-88h]
	__int16 Buffer[32]; // [rsp+80h] [rbp-78h] BYREF

	mbi.BaseAddress = 0;
	mbi.AllocationBase = 0;
	mbi.AllocationProtect = 0;
	mbi.RegionSize = 0;
	mbi.State = 0;
	mbi.Type = 0;
	address = 0x10000;
	if (base)
		address = base + 0x10000;
	while (VirtualQueryEx(hProcess, (LPCVOID)address, &mbi, 0x30ui64)
		&& (mbi.RegionSize & 0xFFF) != 0xFFFi64
		&& (char *)mbi.BaseAddress + mbi.RegionSize >= (PVOID)address)
	{
		if (mbi.State == 4096)
		{
			v6 = mbi.Protect;
			if (LOBYTE(mbi.Protect) != 1
				&& !_bittest((const long *)&v6, 8u)
				&& ReadProcessMemory(hProcess, (LPCVOID)address, Buffer, 0x40ui64, 0i64)
				&& Buffer[0] == 0x5A4D
				&& *(unsigned int *)&Buffer[30] <= mbi.RegionSize
				&& *(DWORD *)&Buffer[30] >= 0x40u
				&& ReadProcessMemory(hProcess, (LPCVOID)(address + *(int *)&Buffer[30]), buffer, 0xF8ui64, 0i64)
				&& *buffer == 0x4550)
			{
				return address;
			}
		}
		address = (unsigned __int64)mbi.BaseAddress + mbi.RegionSize;
	}
	return 0i64;
}

int readMemory(HANDLE hProcess, LPVOID address, PVOID buffer, int size)
{
	size_t read;
	BOOL result = ReadProcessMemory(hProcess, address, buffer, size, &read);
	if (!result || read == 0)
	{
		int e = GetLastError();
		return 0;
	}
	return read;
}

int writeMemory(HANDLE hProcess, LPVOID address, PVOID buffer, int size)
{
	DWORD oldProtect;
	if (!VirtualProtectEx(hProcess, address, size, PAGE_READWRITE, &oldProtect))
		return 0;
	size_t written;
	BOOL result = WriteProcessMemory(hProcess, address, buffer, size, &written);
	if (!result || written == 0)
	{
		int e = GetLastError();
		return 0;
	}
	if (!VirtualProtectEx(hProcess, address, size, oldProtect, &oldProtect))
		return 0;
	return written;
}

int writeZeroMemory(HANDLE hProcess, LPVOID address, int size)
{
	char* zeroMemory = new char[size];
	memset(zeroMemory, 0, size);
	size_t written;
	BOOL result = WriteProcessMemory(hProcess, address, zeroMemory, size, &written);
	if (written == 0)
	{
		int e = GetLastError();
		return 0;
	}
	delete[] zeroMemory;
	return written;
}

DWORD getProcessIdByName(const wchar_t* name)
{
	PROCESSENTRY32 entry = { sizeof(PROCESSENTRY32) };
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	DWORD id = 0;
	if (INVALID_HANDLE_VALUE == hSnapshot)
	{
		return 0;
	}
	if (!Process32FirstW(hSnapshot, &entry))
	{
		return 0;
	}
	do
	{
		if (wcscmp(entry.szExeFile, name) == 0)
		{
			return entry.th32ProcessID;
		}
	} while (Process32Next(hSnapshot, &entry));
	return 0;
}

bool closeProcessByName(const wchar_t* name)
{
	DWORD pid = getProcessIdByName(name);
	if (pid == 0) return false;
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
	if (hProcess == INVALID_HANDLE_VALUE) return false;
	return TerminateProcess(hProcess, 0);
}



HANDLE createProcessSuspendly(wstring processFileName)
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi = { 0 };

	if (CreateProcessW(processFileName.c_str(), NULL, NULL, NULL, FALSE, IDLE_PRIORITY_CLASS | CREATE_SUSPENDED, NULL, NULL, &si, &pi))
	{
		CloseHandle(pi.hProcess);
		return pi.hThread;
	}
	return INVALID_HANDLE_VALUE;
}


void* remoteAllocMemory(HANDLE hProcess, PVOID beginAddress, int size)
{
	MEMORY_BASIC_INFORMATION mbi = { 0 };
	if (VirtualQueryEx(hProcess, beginAddress, &mbi, 0x30))
	{
		while (true)
		{
			if ((mbi.RegionSize & 0xFFF) == 0xFFF)
				break;
			if (mbi.State == 0x10000)
			{
				for (char* address = (char*)mbi.BaseAddress; address < (char*)mbi.BaseAddress + mbi.RegionSize; address +=0x10000)
				{
					if (VirtualAllocEx(hProcess, address, size, MEM_RESERVE, PAGE_READWRITE))
					{
						PVOID result = VirtualAllocEx(hProcess, address, size, MEM_COMMIT, PAGE_READWRITE);
						if (result) return result;
					}
				}
			}
			char* baseAddress = (char*)mbi.BaseAddress;
			size_t regionSize = mbi.RegionSize;
			if (!VirtualQueryEx(hProcess, &baseAddress[regionSize], &mbi, 0x30))
			{
				int e = GetLastError();
				return nullptr;
			}

		}
	}
}

unsigned long long align(unsigned long long value)
{
	unsigned long long v = value % 0x10;
	if (v != 0)
	{
		value += (0x10-v);
	}
	return value;
}


BOOL addNewSectionInMemory(const wchar_t* processName, const char* dllName, const char* exportName)
{
	wstring processFileName = LR"(C:\Program Files\iTunes\iTunes.exe)";
	closeProcessByName(processName);
	HANDLE hThread = createProcessSuspendly(processFileName);
	if (hThread == INVALID_HANDLE_VALUE) return false;

	DWORD pid = getProcessIdByName(processName);
	if (pid == 0) return false;
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
	if (hProcess == INVALID_HANDLE_VALUE) return false;

	char buf[0x140] = { 0 };
	// 通过内存检索的方式遍历模块,并通过判断Characteristics的IMAGE_FILE_DLL位是否为1来判断是否为iTunes主模块
	unsigned long long moduleBase = findNextModule(hProcess, (ULONG_PTR)GetModuleHandleA("iTunes.exe"), (DWORD*)buf);
	char * iTunesModuleBase = NULL;
	do
	{
		
		if ((*(WORD*)(buf + 0x16) & IMAGE_FILE_DLL) == 0)
		{
			iTunesModuleBase = (char *)moduleBase;
			break;
		}
		moduleBase = findNextModule(hProcess, moduleBase, (DWORD*)buf);
	} while (moduleBase);

	  
	// 获取Dos头
	IMAGE_DOS_HEADER dosHeader = { 0 };
	if (!readMemory(hProcess, iTunesModuleBase, (char*)&dosHeader, sizeof(dosHeader)))
		return false;

	// 获取Signature
	DWORD signature = { 0 };
	char* signatureAddress = iTunesModuleBase + dosHeader.e_lfanew;
	if (!readMemory(hProcess, signatureAddress, (char*)&signature, sizeof(DWORD)))
		return 0;

	// 获取文件头
	IMAGE_FILE_HEADER fileHeader = { 0 };
	char* fileHeaderAddress = signatureAddress + sizeof(signature);
	if (!readMemory(hProcess, fileHeaderAddress, (char*)&fileHeader, sizeof(IMAGE_FILE_HEADER)))
		return 0;

	// 获取扩展头
	IMAGE_OPTIONAL_HEADER optHeader = { 0 };
	char* optHeaderAddress = fileHeaderAddress + sizeof(IMAGE_FILE_HEADER);
	if (!readMemory(hProcess, optHeaderAddress, (char*)&optHeader, fileHeader.SizeOfOptionalHeader))
		return 0;

	// 读取节区表
	IMAGE_SECTION_HEADER* sectionHeader = new IMAGE_SECTION_HEADER[fileHeader.NumberOfSections];
	char* sectionHeaderAddress = optHeaderAddress + fileHeader.SizeOfOptionalHeader;
	if (!readMemory(hProcess, sectionHeaderAddress, (char*)sectionHeader, fileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)))
		return 0;



	size_t newImportTableSize = align(optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size + sizeof(IMAGE_IMPORT_DESCRIPTOR));
	size_t newStringTableSize = align(strlen(dllName));
	size_t newImportByNameSize = align(sizeof(WORD)+strlen(exportName));
	size_t newThunkDataSize = align(sizeof(IMAGE_THUNK_DATA) * 2);
	size_t newMemorySize = newImportTableSize + newStringTableSize + newImportByNameSize+ newThunkDataSize;

	char* queryAddress = iTunesModuleBase + optHeader.SizeOfCode + optHeader.SizeOfInitializedData + optHeader.SizeOfUninitializedData + optHeader.BaseOfCode;
	IMAGE_IMPORT_DESCRIPTOR* newImportTable = (IMAGE_IMPORT_DESCRIPTOR*)remoteAllocMemory(hProcess, queryAddress, newMemorySize);

	// 申请一块内存, 用于存放新的导入表
	//IMAGE_IMPORT_DESCRIPTOR* newImportTable = (IMAGE_IMPORT_DESCRIPTOR*)VirtualAllocEx(hProcess, NULL, newImportTableSize + newStringTableSize + newThunkDataSize, MEM_COMMIT, PAGE_READWRITE);
	writeZeroMemory(hProcess, newImportTable, newMemorySize);
	// 用于存放字符串
	// dllName\0+Hint+exportName\0
	char* newStringTable = (char*)newImportTable + newImportTableSize;

	// 存放IMAGE_IMPORT_BY_NAME结构体, IMAGE_THUNK_DATA里面唯一的成员Ordinal就是该结构体的RVA
	IMAGE_IMPORT_BY_NAME* newImportByName = (IMAGE_IMPORT_BY_NAME*)(newStringTable + newStringTableSize);
	// 用于INT和IAT
	IMAGE_THUNK_DATA* newThunkData = (IMAGE_THUNK_DATA*)((char*)newImportByName + newImportByNameSize);

	// 将要注入dll名称写入到新内存中
	writeMemory(hProcess, newStringTable, (void*)dllName, strlen(dllName));


	// 拷贝原导入表内容
	IMAGE_IMPORT_DESCRIPTOR* oldImportTable = (IMAGE_IMPORT_DESCRIPTOR*)new char[optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size];

	readMemory(hProcess, (IMAGE_IMPORT_DESCRIPTOR*)(iTunesModuleBase + optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress), oldImportTable, optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size);
	bool boundImport = false;

	// 判断是否使用了绑定导入表
	if (oldImportTable->Characteristics == 0 && oldImportTable->FirstThunk != 0)
	{
		boundImport = true;
		optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;
		optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress = 0;
	}

	int i = 0;
	while (oldImportTable[i].FirstThunk != 0 || oldImportTable[i].Characteristics != 0)
	{
		writeMemory(hProcess, &newImportTable[i], &oldImportTable[i], sizeof(IMAGE_IMPORT_DESCRIPTOR));
		i++;
	}
	


	// 填充导出函数
	// 导出函数的结构为IMAGE_IMPORT_BY_NAME
	WORD Hint = 1;
	writeMemory(hProcess, (char*)newImportByName, (char*)&Hint, sizeof(WORD));
	writeMemory(hProcess, (char*)newImportByName + sizeof(WORD), (char*)exportName, strlen(exportName));

	// 填充INT
	DWORD_PTR Ordinal = (DWORD_PTR)((char*)newImportByName - iTunesModuleBase);
	writeMemory(hProcess, newThunkData, &Ordinal, sizeof(DWORD_PTR));
	DWORD OriginalFirstThunk = 0;
	DWORD FirstThunk = (DWORD_PTR)newThunkData - (DWORD_PTR)iTunesModuleBase;
	if (boundImport)
	{
		OriginalFirstThunk = 0;
	}
	else
	{
		OriginalFirstThunk = (DWORD_PTR)newThunkData - (DWORD_PTR)iTunesModuleBase;
	}

	// 填充注入dll的improt descriptor, 其后要紧跟一个空的描述符, 用于判定描述符表是否结束, 由于在申请内存后, 将内存置0了,故此处无需额外处理
	writeMemory(hProcess, &newImportTable[i].OriginalFirstThunk, &OriginalFirstThunk, sizeof(DWORD));
	DWORD Name = (DWORD)(newStringTable-iTunesModuleBase);
	writeMemory(hProcess, &newImportTable[i].Name, &Name, sizeof(DWORD));
	writeMemory(hProcess, &newImportTable[i].FirstThunk, &FirstThunk, sizeof(DWORD));




	// 修改导入表的大小
	DWORD importSizeOffset = (DWORD_PTR)&optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size - (DWORD_PTR)&optHeader;
	optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size += (i + 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR);
	writeMemory(hProcess, optHeaderAddress + importSizeOffset, &optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size, sizeof(DWORD));
	// 修改导入表的VA
	DWORD importVirtualAdressOffset = (DWORD_PTR)&optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - (DWORD_PTR)&optHeader;
	optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = (DWORD)newImportTable - (DWORD)iTunesModuleBase;
	writeMemory(hProcess, optHeaderAddress + importVirtualAdressOffset, &(optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress), sizeof(DWORD));

	ResumeThread(hThread);
	CloseHandle(hProcess);
	return 0;
}





int main()
{
	//getchar();
	wstring processName = L"iTunes.exe";
	string dllName = "dlltest.dll";
	string dllFileName = R"(D:\WorkStation\Project\Test\cpptest\x64\Debug\dlltest.dll)";
    // 导出的函数名字
	string exportName = "test";

	addNewSectionInMemory(processName.c_str(), dllFileName.c_str(), exportName.c_str());
	//AddImportTable(R"(C:\Program Files\iTunes\iTunes.exe)", dllFileName, exportName);

	system("pause");
	return true;
}
被注入的dll

必须要导出至少一个函数

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"


extern "C" __declspec(dllexport) void test(){}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
		MessageBoxA(0,"dll加载成功",0,0);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

标签:return,IMAGE,char,修改,导入,内存,IMPORT,DWORD,hProcess
From: https://www.cnblogs.com/FeJQ/p/16954873.html

相关文章

  • 034-MemoryStream内存数据读写
    位与字节计算机中最小的单位时“位”,用bit表示,数据是用二进制来表示。但是bit过小,因此用字节来作为存储单位,用“Byte”表示,其中8位组成一个字节。所以一个字节表示的数的......
  • ubuntu安装stardict并导入词典
    de.google.com/p/stardict-3/downloads/list下载文件:stardict_3.0.1-1_i386.deb或者是在终端中输入:sudoapt-getinstallstardict.启动stardic会出现一下界面: star......
  • 修改win10系统右键新建
    一、win+R打开CMD输入regedit回车进入“注册表”  二、找到此目录“计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Discardable\Po......
  • ILSpy使用reflexil修改dll中文件
    这里介绍修改dll中的资源文件ILSpy下载:ILSpy_binaries_7.2.1.6856.zipreflexil下载:reflexil.for.ILSpy.2.7.AIO.bin.zip 1、解压reflexil.for.ILSpy.2.7.AIO.bin.zip......
  • 【深入理解java虚拟机】 - HotSpot虚拟机中对象的创建、内存布局和访问
    文章目录​​对象的创建​​​​对象的内存布局​​​​对象头​​​​实例数据​​​​对齐填充​​​​对象的访问定位​​​​句柄​​​​直接指针​​​​优缺点​​对......
  • wps word 批量修改表格样式
    https://www.ngui.cc/el/1276218.html?action=onClick Sub统一表格样式()''批量修改表格Macro'宏由zsz录制,时间:2020/05/07'DimtempTableAsTable......
  • docker 镜像、容器的导入导出
    1.本地镜像的载入载出1.镜像载出tar包dockersavemysql:8>/root/mysql-8.tardockersavecd3ed0dfff7e-o/root/mysql-8.targzip包dockersavemysql:8|g......
  • Vue.js:v-charts根据E-charts修改样式
    以饼状图为例子:首先importimportVeRingfrom'v-charts/lib/ring'在<template>加上<ve-ring><ve-ringstyle="background:#F9F9F9":data="chartDataRing":sett......
  • JVM内存结构
    JVM内存结构  线程共享的由:堆和方法区线程隔离:虚拟机栈、本地方法栈和程序计数器 堆:  虚拟机栈:......
  • 修改远程登录3389端口
    【一】win+R,输入Regedit  【二】 修改端口1,按照路径打开,HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TerminalServer\Wds\rdpwd\Tds\tcp,双击文件,切......