主頁 >  其他 > 以修改記憶體的方式實作匯入表動態注入dll

以修改記憶體的方式實作匯入表動態注入dll

2022-12-07 08:04:18 其他

簡介

搜索"匯入表注入", 網上大堆的博客和代碼, 統統都是修改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 =https://www.cnblogs.com/FeJQ/p/= 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 = https://www.cnblogs.com/FeJQ/p/(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 =https://www.cnblogs.com/FeJQ/p/= 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 = https://www.cnblogs.com/FeJQ/p/(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;
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/539334.html

標籤:其他

上一篇:HCIA學習筆記四十九:PPPOE原理及配置

下一篇:Jenkins實踐指南-02-Groovy基礎知識01

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more