English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Windows x86/ x64 Ring3Summary of Layer Injection Dll

0x01.Introduction

  When mentioning Dll injection, many methods can be thought of immediately, such as using remote threads, Apc, etc., here I focus on Ring3Summary of the learning of layer-level Dll injection.

  I divide the injection methods into six categories, namely:1.Create a new thread,2.Set thread context, modify registers,3.Insert Apc queue,4.Modify the registry,5.Hook window messages,6.Remote manual implementation of LoadLibrary.

  So let's embark on the learning journey!

0x02.Preparation work

  In the program involved in injection, elevating the program's privileges is naturally indispensable, here I provide two encapsulated functions that can be used for privilege escalation. The first one is to adjust privileges through the permission token; the second one is to adjust privileges through the undocumented export function RtlAdjustPrivilege of ntdll.dll.

// Passing the parameter SE_DEBUG_NAME, elevate to debugging privileges
BOOL GrantPriviledge(WCHAR* PriviledgeName)
{
TOKEN_PRIVILEGES TokenPrivileges, OldPrivileges;
DWORD dwReturnLength = sizeof(OldPrivileges);
HANDLE TokenHandle = NULL;
LUID uID;
// Open the permission token
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &TokenHandle))
{
if (GetLastError() != ERROR_NO_TOKEN)
{
return FALSE;
}
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle))
{
return FALSE;
}
}
if (!LookupPrivilegeValue(NULL, PriviledgeName, &uID)) // Find uID by permission name
{
CloseHandle(TokenHandle);
return FALSE;
}
TokenPrivileges.PrivilegeCount = 1; // The number of permissions to elevate
TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // Dynamic array, the size of the array is based on the number of Count
TokenPrivileges.Privileges[0].Luid = uID;
// Here we adjust the permissions
if (!AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), &OldPrivileges, &dwReturnLength))
{
CloseHandle(TokenHandle);
return FALSE;
}
// 成功了
CloseHandle(TokenHandle);
return TRUE;
}

  Since we need to inject Dll into the target process, obtaining the target process's Id is indispensable, because OpenProcess will definitely be used, and here I also provide two methods to obtain the process Id through the target process image name. The first one is the most common method, using TlHelp to create the system's process snapshot; the second one is to use the Psapi enumeration series functions, but this method has some shortcomings in my implementation.32below cannot be obtained64for the process Id.

// Using ToolHelp series functions
#include <TlHelp32.h>
BOOL GetProcessIdByProcessImageName(IN PWCHAR wzProcessImageName, OUT PUINT32 ProcessId)
{
HANDLE ProcessSnapshotHandle = INVALID_HANDLE_VALUE;
PROCESSENTRY32 ProcessEntry32 = { 0 };
ProcessEntry32.dwSize = sizeof(PROCESSENTRY32); // Initialize PROCESSENTRY32Structure
ProcessSnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // Provide snapshots of all system processes
if (ProcessSnapshotHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}
if (Process32First(ProcessSnapshotHandle, &ProcessEntry32)) // Find the first
{
do
{
if (lstrcmpi(ProcessEntry32.szExeFile, wzProcessImageName) == 0) // Case-insensitive
{
*ProcessId = ProcessEntry32.th32ProcessID;
break;
}
} while (Process32Next(ProcessSnapshotHandle, &ProcessEntry32));
}
CloseHandle(ProcessSnapshotHandle);
ProcessSnapshotHandle = INVALID_HANDLE_VALUE;
if (*ProcessId == 0)
{
return FALSE;
}
return TRUE;
}
// Using Psapi enumeration functions
#include <Psapi.h>
BOOL GetProcessIdByProcessImageName(IN PWCHAR wzProcessImageName, OUT PUINT32 ProcessId)
{
DWORD dwProcessesId[1024]= { 0 };
DWORD BytesReturned = 0;
UINT32 ProcessCount = 0;
// Obtaining all process IDs in the current operating system and storing them in the dwProcessesId array
if (!EnumProcesses(dwProcessesId, sizeof(dwProcessesId), &BytesReturned))
{
return FALSE;
}
ProcessCount = BytesReturned / sizeof(DWORD);
// Traversal
for (INT i = 0; i < ProcessCount; i++)
{
HMODULE ModuleBase = NULL;
WCHAR wzModuleBaseName[MAX_PATH] = { 0 };
HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessesId[i]);
if (ProcessHandle == NULL)
{
continue;
}
if (EnumProcessModulesEx(ProcessHandle, &ModuleBase, sizeof(HMODULE), &BytesReturned, LIST_MODULES_ALL))
{
// Obtaining the first module name of the process
GetModuleBaseName(ProcessHandle, ModuleBase, wzModuleBaseName, MAX_PATH * , sizeof(WCHAR));
}
CloseHandle(ProcessHandle);
ProcessHandle = NULL;
if (lstrcmpi(wzModuleBaseName, wzProcessImageName) == 0) // Case-insensitive
{
*ProcessId = dwProcessesId[i];
break;
}
}
if (*ProcessId == 0)
{
return FALSE;
}
return TRUE;
}

  Then in operations such as inserting into the Apc queue, suspending threads, and so on, thread operations on the target process are required, so obtaining the thread ID is also necessary. Similarly, I have also provided two methods to obtain the thread ID through the process ID. The first one is still using TlHelp to create a system thread snapshot, storing all threads in the vector template (for Apc injection use); the second one is to make use of the great art of ZwQuerySystemInformation, enumerating system process information, and this method only returns a thread ID, which is enough.

// Enumerate all threads of the specified process ID and push them into the template
#include <vector>
#include <TlHelp32.h>
using namespace std;
BOOL GetThreadIdByProcessId(IN UINT32 ProcessId, OUT vector<UINT32>& ThreadIdVector)
{
HANDLE ThreadSnapshotHandle = NULL;
THREADENTRY32 ThreadEntry32 = { 0 };
ThreadEntry32.dwSize = sizeof(THREADENTRY32);
ThreadSnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); // Take snapshots of all threads in the system
if (ThreadSnapshotHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}
if (Thread32First(ThreadSnapshotHandle, &ThreadEntry32))
{
do
{
if (ThreadEntry32.th32OwnerProcessID == ProcessId)
{
ThreadIdVector.emplace_back(ThreadEntry32.th32ThreadID); // Push all thread IDs of the process into the template
}
}) while (Thread32Next(ThreadSnapshotHandle, &ThreadEntry32));
}
CloseHandle(ThreadSnapshotHandle);
ThreadSnapshotHandle = NULL;
return TRUE;
}
// ZwQuerySystemInformation+SystemProcessInformation
typedef
NTSTATUS(NTAPI * pfnZwQuerySystemInformation)(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN UINT32 SystemInformationLength,
OUT PUINT32 ReturnLength OPTIONAL);
BOOL GetThreadIdByProcessId(IN UINT32 ProcessId, OUT PUINT32 ThreadId)
{
BOOL bOk = FALSE;
NTSTATUS Status = 0;
PVOID BufferData = NULL;
PSYSTEM_PROCESS_INFO spi = NULL;
pfnZwQuerySystemInformation ZwQuerySystemInformation = NULL;
ZwQuerySystemInformation = (pfnZwQuerySystemInformation)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "ZwQuerySystemInformation");
if (ZwQuerySystemInformation == NULL)
{
return FALSE;
}
BufferData = malloc(1024 * 1024);
if (!BufferData)
{
return FALSE;
}
// In the QuerySystemInformation series of functions, memory must be allocated in advance when querying SystemProcessInformation, and it cannot be queried for length first and then called again
Status = ZwQuerySystemInformation(SystemProcessInformation, BufferData, 1024 * 1024, NULL);
if (!NT_SUCCESS(Status))
{
free(BufferData);
return FALSE;
}
spi = (PSYSTEM_PROCESS_INFO)BufferData;
// Traverse the process to find our target process
while (TRUE)
{
bOk = FALSE;
if (spi->UniqueProcessId == (HANDLE)ProcessId)
{
bOk = TRUE;
break;
}
else if (spi->NextEntryOffset)
{
spi = (PSYSTEM_PROCESS_INFO)((PUINT8)spi + spi->NextEntryOffset);
}
else
{
break;
}
}
if (bOk)
{
for (INT i = 0; i < spi->NumberOfThreads; i++)
{
// return the found thread Id
*ThreadId = (UINT32)spi->Threads[i].ClientId.UniqueThread;
break;
}
}
if (BufferData != NULL)
{
free(BufferData);
}
return bOk;
}

  Well, so far, the preparatory work is almost done, so let's get down to business!

0x03.Injection method one -- Create a new thread

  Create a new thread, which is to create a thread in the target process to serve us, and the methods I found to create a thread are three:1.CreateRemoteThread;2.NtCreateThreadEx;3.RtlCreateUserThread.

  The basic idea is:1.Apply for memory in the target process memory space;2.Write the complete path of Dll in the newly allocated memory;3.Create a new thread to execute LoadLibrary to complete the injection of Dll.

  ps: here we use the kernel loaded from ourselves directly32The address of LoadLibrary obtained from the module export table is because, in general, all processes load such system libraries at the same address in memory!

  Because the functions used to create threads are different, the following code can be successful by randomly uncommenting one step of creating a thread and masking the other two; the one I uncommented is NtCreateThreadEx.

typedef NTSTATUS(NTAPI* pfnNtCreateThreadEx)
(
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer);
#define NT_SUCCESS(x) ((x) >= 0)
typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
typedef NTSTATUS(NTAPI * pfnRtlCreateUserThread)(
IN HANDLE ProcessHandle,
IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
IN BOOLEAN CreateSuspended,
IN ULONG StackZeroBits OPTIONAL,
IN SIZE_T StackReserve OPTIONAL,
IN SIZE_T StackCommit OPTIONAL,
IN PTHREAD_START_ROUTINE StartAddress,
IN PVOID Parameter OPTIONAL,
OUT PHANDLE ThreadHandle OPTIONAL,
OUT PCLIENT_ID ClientId OPTIONAL);
BOOL InjectDll(UINT32 ProcessId)
{
HANDLE ProcessHandle = NULL;
ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
// Apply memory in the target process space to store the full path of the Dll
UINT32 DllFullPathLength = (strlen(DllFullPath + 1);
PVOID DllFullPathBufferData = VirtualAllocEx(ProcessHandle, NULL, DllFullPathLength, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (DllFullPathBufferData == NULL)
{
CloseHandle(ProcessHandle);
return FALSE;
}
// Write DllFullPath into the memory just applied for
SIZE_T ReturnLength;
BOOL bOk = WriteProcessMemory(ProcessHandle, DllFullPathBufferData, DllFullPath, strlen(DllFullPath) + 1, &ReturnLength);
LPTHREAD_START_ROUTINE LoadLibraryAddress = NULL;
HMODULE Kernel32Module = GetModuleHandle(L"Kernel32");
LoadLibraryAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(Kernel32Module, "LoadLibraryA");
pfnNtCreateThreadEx NtCreateThreadEx = (pfnNtCreateThreadEx)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateThreadEx");
if (NtCreateThreadEx == NULL)
{
CloseHandle(ProcessHandle);
return FALSE;
}
HANDLE ThreadHandle = NULL;
// 0x1FFFFF #define THREAD_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF)
NtCreateThreadEx(&ThreadHandle, 0x1FFFFF, NULL, ProcessHandle, (LPTHREAD_START_ROUTINE)LoadLibraryAddress, DllFullPathBufferData, FALSE, NULL, NULL, NULL, NULL);
/*
pfnRtlCreateUserThread RtlCreateUserThread = (pfnRtlCreateUserThread)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlCreateUserThread");
HANDLE ThreadHandle = NULL;
NTSTATUS Status = RtlCreateUserThread(ProcessHandle, NULL, FALSE, 0, 0, 0, LoadLibraryAddress, DllFullPathBufferData, &ThreadHandle, NULL); 
*/
/*
HANDLE ThreadHandle = CreateRemoteThread(ProcessHandle, NULL, 0, LoadLibraryAddress, DllFullPathBufferData, 0, NULL); // CreateRemoteThread function
*/
if (ThreadHandle == NULL)
{
CloseHandle(ProcessHandle);
return FALSE;
}
if (WaitForSingleObject(ThreadHandle, INFINITE) == WAIT_FAILED)
{
return FALSE;
}
CloseHandle(ProcessHandle);
CloseHandle(ThreadHandle);
return TRUE;
}

0x04.Injection method two -- Set thread context

  The main purpose of setting the thread context is to let a certain thread of the target process execute our code, and then come back to do what it should do, and our code is a string of ShellCode composed of assembly hard coding.

  This ShellCode does three things:1.Passes the complete path parameter of the Dll;2.Calls the address of the LoadLibrary function;3.Returns the original Eip or Rip.

  The call instruction I have chosen is ff 15 and ff 25, in32Below is the jump to15(25The byte code address stored in the byte code corresponding to the instruction behind it is in64Below15(25) The four bytes after the instruction store the offset, which is used to jump to the address stored at the calculated address. Here, I write the offset as 0 for calculation convenience.

#ifdef _WIN64
// Test 64 Bit the dll has been injected, the bug has been fixed
/*
0:019> u 0x000002b5d5f80000
000002b5`d5f80000 4883ec28 sub rsp,28h
000002b5`d5f80004 488d0d20000000 lea rcx,[000002b5`d5f8002b]
000002b5`d5f8000b ff1512000000 call qword ptr [000002b5`d5f80023]
000002b5`d5f80011 4883c428 add rsp,28h
000002b5`d5f80015 ff2500000000 jmp qword ptr [000002b5`d5f8001b]
*/
UINT8 ShellCode[0x100] = {
0x48,0x83,0xEC,0x28, // sub rsp ,28h
0x48,0x8D,0x0d, // [+4] lea rcx,
0x00,0x00,0x00,0x00, // [+7] DllNameOffset = [+43] - [+4] - 7
// call jump offset, to address, to resolve*Number
0xff,0x15, // [+11]
0x00,0x00,0x00,0x00, // [+13] 
0x48,0x83,0xc4,0x28, // [+17] add rsp,28h
// ] jmp jump offset, to address, to resolve*Number
0xff,0x25, // [+21]
0x00,0x00,0x00,0x00, // [+23] LoadLibraryAddressOffset
// Store the original rip
0x00,0x00,0x00,0x00, // [+27]
0x00,0x00,0x00,0x00, // [+31]
// Jumper loadlibrary address
0x00,0x00,0x00,0x00, // [+35] 
0x00,0x00,0x00,0x00, // [+39]
// Store the full path of the dll
// 0x00,0x00,0x00,0x00, // [+43]
// 0x00,0x00,0x00,0x00 // [+47]
// ......
};
#else
// Test 32 Bit to match the new Dll for repeated injection
/*
0:005> u 0x00ca0000
00000000`00ca0000 60 pusha
00000000`00ca0001 9c pushfq
00000000`00ca0002 681d00ca00 push 0CA001Dh
00000000`00ca0007 ff151900ca00 call qword ptr [00000000`01940026]
00000000`00ca000d 9d popfq
00000000`00ca000e 61 popa
00000000`00ca000f ff251500ca00 jmp qword ptr [00000000`0194002a]
*/
UINT8 ShellCode[0x100] = {
0x60, // [+0] pusha
0x9c, // [+1] pushf
0x68, // [+2] push
0x00,0x00,0x00,0x00, // [+3] ShellCode + 
0xff,0x15, // [+7] call 
0x00,0x00,0x00,0x00, // [+9] LoadLibrary Addr Addr
0x9d, // [+13] popf
0x61, // [+14] popa
0xff,0x25, // [+15] jmp
0x00,0x00,0x00,0x00, // [+17] jmp eip
// eip Address
0x00,0x00,0x00,0x00, // [+21]
// LoadLibrary Address
0x00,0x00,0x00,0x00, // [+25] 
// DllFullPath 
0x00,0x00,0x00,0x00 // [+29] 
};
#endif

  The entire injection process consists of these steps: Applying memory (executable memory) in the target process ---> Filling the address code required for ShellCode ---> Write ShellCode to the allocated memory ---> SuspendThread(Suspend the thread)---> GetThreadContext(Get the thread context)---> Modify the Eip or Rip of the Context to the starting address of ShellCode ---> SetThreadContext(Set the just modified Context)---> ResumeThread(Restore thread execution).

BOOL Inject(IN UINT32 ProcessId, IN UINT32 ThreadId)
{
BOOL bOk = FALSE;
CONTEXT ThreadContext = { 0 };
PVOID BufferData = NULL;
HANDLE ThreadHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadId);
HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
// Firstly, suspend the thread
SuspendThread(ThreadHandle);
ThreadContext.ContextFlags = CONTEXT_ALL;
if (GetThreadContext(ThreadHandle, &ThreadContext) == FALSE)
{
CloseHandle(ThreadHandle);
CloseHandle(ProcessHandle);
return FALSE;
}
BufferData = VirtualAllocEx(ProcessHandle, NULL, sizeof(ShellCode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (BufferData != NULL)
{
if (LoadLibraryWAddress != NULL)
{
#ifdef _WIN64
// ShellCode + 43Store the full path at the location
PUINT8 v1 = ShellCode + 43;
memcpy(v1, DllFullPath, (wcslen(DllFullPath) + 1) * , sizeof(WCHAR));
UINT32 DllNameOffset = (UINT32)(((PUINT8)BufferData + 43) - ((PUINT8)BufferData + 4) - 7);
*(PUINT32)(ShellCode + 7) = DllNameOffset;
// ShellCode + 35Place the LoadLibrary function address at the location
*(PUINT64)(ShellCode + 35) = (UINT64)LoadLibraryWAddress;
UINT32 LoadLibraryAddressOffset = (UINT32)(((PUINT8)BufferData + 35) - ((PUINT8)BufferData + 11) - 6);
*(PUINT32)(ShellCode + 13) = LoadLibraryAddressOffset;
// Place the rip address
*(PUINT64)(ShellCode + 27) = ThreadContext.Rip;
if (!WriteProcessMemory(ProcessHandle, BufferData, ShellCode, sizeof(ShellCode), NULL))
{
return FALSE;
}
ThreadContext.Rip = (UINT64)BufferData;
#else
PUINT8 v1 = ShellCode + 29;
memcpy((char*)v1, DllFullPath, (wcslen(DllFullPath) + 1) * , sizeof(WCHAR)); //Here is the name of the DLL to be injected
*(PUINT32)(ShellCode + 3) = (UINT32)BufferData + 29;
*(PUINT32)(ShellCode + 25) = LoadLibraryWAddress; //LoadLibrary address is placed in the shellcode
*(PUINT32)(ShellCode + 9) = (UINT32)BufferData + 25;//Modify the address after call to the target space storage of loaddlladdr
//////////////////////////////////
*(PUINT32)(ShellCode + 21) = ThreadContext.Eip;
*(PUINT32)(ShellCode + 17) = (UINT32)BufferData + 21;//Modify jmp after the original eip address
if (!WriteProcessMemory(ProcessHandle, BufferData, ShellCode, sizeof(ShellCode), NULL))
{
printf("write Process Error\n");
return FALSE;
}
ThreadContext.Eip = (UINT32)BufferData;
#endif 
if (!SetThreadContext(ThreadHandle, &ThreadContext))
{
printf("set thread context error\n");
return FALSE;
}
ResumeThread(ThreadHandle);
printf("ShellCode injection completed\r\n");
}
}
CloseHandle(ThreadHandle);
CloseHandle(ProcessHandle);
return TRUE;
}

0x05.Insert Apc queue

  Ring3Layer's Apc injection is not very stable, so I force the Apc object into the UserMode Apc queue (threads have two Apc queues: Kernel and User) of all threads in the target process, waiting for it to execute the function registered in the Apc. And only when the thread is in an alterable state, will it check if there are any registered functions to be executed in the Apc queue.

  PS: It is because I don't know which thread will handle the Apc that I feel that Ring3Layer Apc injection is not as effective as other methods, but Ring0 layer Apc injection is relatively stable. Before testing xp and win10All succeeded, win7The explorer process always crashes, after fiddling around for a while, I found that inserting from the end of the thread traversal will not crash Orz

int main()
{
......
ThreadCount = ThreadIdVector.size();
for (INT i = ThreadCount - 1; i >= 0; i--)
{
UINT32 ThreadId = ThreadIdVector[i];
InjectDllByApc(ProcessId, ThreadId);
}
......
}
BOOL InjectDllByApc(IN UINT32 ProcessId, IN UINT32 ThreadId)
{
BOOL bOk = 0;
HANDLE ThreadHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadId);
HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
UINT_PTR LoadLibraryAddress = 0;
SIZE_T ReturnLength = 0;
UINT32 DllFullPathLength = (strlen(DllFullPath + 1);
// Global, apply memory once
if (DllFullPathBufferData == NULL)
{
//Apply memory
DllFullPathBufferData = VirtualAllocEx(ProcessHandle, NULL, DllFullPathLength, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (DllFullPathBufferData == NULL)
{
CloseHandle(ProcessHandle);
CloseHandle(ThreadHandle);
return FALSE;
}
}
// To avoid the failure of previous write operations, rewrite it repeatedly each time
bOk = WriteProcessMemory(ProcessHandle, DllFullPathBufferData, DllFullPath, strlen(DllFullPath) + 1,
&ReturnLength);
if (bOk == FALSE)
{
CloseHandle(ProcessHandle);
CloseHandle(ThreadHandle);
return FALSE;
}
LoadLibraryAddress = (UINT_PTR)GetProcAddress(GetModuleHandle(L"Kernel32.dll), "LoadLibraryA");
if (LoadLibraryAddress == NULL)
{
CloseHandle(ProcessHandle);
CloseHandle(ThreadHandle);
return FALSE;
}
__try
{
QueueUserAPC((PAPCFUNC)LoadLibraryAddress, ThreadHandle, (UINT_PTR)DllFullPathBufferData);
}
__except (EXCEPTION_CONTINUE_EXECUTION)
{
}
CloseHandle(ProcessHandle);
CloseHandle(ThreadHandle);
return TRUE;
}

0x06.Modify the registry

  Registry injection can be considered a global Hook, after all, the newly created process loads User32When loading .dll, LoadLibrary will be automatically called to load the Dll path written in the registry item key value.

  The registry item key we are concerned about is: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows, and the key value we need to set is AppInit_DLLs = "Full path of Dll", LoadAppInit_Dlls = 1(Let the system use this registry item)

  PS: Since the injected Dll is in the early stage of process creation, special care must be taken when using functions in the Dll, as some libraries may not have been loaded yet.

int main()
{
LSTATUS Status = 0;
WCHAR* wzSubKey = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows";
HKEY hKey = NULL;
// Open the registry
Status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, // The main key to be opened
wzSubKey, // The address of the name of the subkey to be opened
0, // Reserved, pass 0
KEY_ALL_ACCESS, // The way to open
&hKey); // The returned subkey handle
if (Status != ERROR_SUCCESS)
{
return 0;
}
WCHAR* wzValueName = L"AppInit_DLLs";
DWORD dwValueType = 0;
UINT8 ValueData[MAX_PATH] = { 0 };
DWORD dwReturnLength = 0;
// Query the registry
Status = RegQueryValueExW(hKey, // Subkey handle
wzValueName, // The name of the key to be queried
NULL, // Reserved
&dwValueType, // Data type
ValueData, // Key value
&dwReturnLength);
WCHAR wzDllFullPath[MAX_PATH] = { 0 };
GetCurrentDirectoryW(MAX_PATH, wzDllFullPath);
#ifdef _WIN64
wcscat_s(wzDllFullPath, L"\\x64NormalDll.dll");
#else
wcscat_s(wzDllFullPath, L"\\x86NormalDll.dll");
#endif
// Set key value
Status = RegSetValueExW(hKey,
wzValueName,
NULL,
dwValueType,
(CONST BYTE*)wzDllFullPath,
(lstrlen(wzDllFullPath) + 1) * , sizeof(WCHAR));
if (Status != ERROR_SUCCESS)
{
return 0;
}
wzValueName = L"LoadAppInit_DLLs";
DWORD dwLoadAppInit = 1;
// Query the registry
Status = RegQueryValueExW(hKey, wzValueName, NULL, &dwValueType, ValueData, &dwReturnLength);
// Set key value
Status = RegSetValueExW(hKey, wzValueName, NULL, dwValueType, (CONST BYTE*)&dwLoadAppInit, sizeof(DWORD));
if (Status != ERROR_SUCCESS)
{
return 0;
}
printf("Input Any Key To Resume\r\n");
getchar();
getchar();
// Restore key value
dwLoadAppInit = 0;
Status = RegQueryValueExW(hKey, wzValueName, NULL, &dwValueType, ValueData, &dwReturnLength);
Status = RegSetValueExW(hKey, wzValueName, NULL, dwValueType, (CONST BYTE*)&dwLoadAppInit, sizeof(DWORD));
wzValueName = L"AppInit_DLLs";
ZeroMemory(wzDllFullPath, (lstrlen(wzDllFullPath) + 1) * , sizeof(WCHAR));
Status = RegQueryValueExW(hKey, wzValueName, NULL, &dwValueType, ValueData, &dwReturnLength);
Status = RegSetValueExW(hKey, wzValueName, NULL, dwValueType, (CONST BYTE*)wzDllFullPath, 0);
return 0;
}

0x07.Hook window messages

  .Use the API interface SetWindowsHookEx provided by MS to hook the window message. Its working principle is to hook our Dll exported function to the message of a certain thread in a certain window process with a window. Once the message is triggered, the exported function will be called. In essence, the several methods learned earlier all call LoadLibrary, but this method does not.

// Inject the critical code of the exe into the specified message hook of the target thread, and enter the Dll export function
BOOL Inject(IN UINT32 , OUT HHOOK& HookHandle)
{
HMODULE DllModule = LoadLibraryA(DllFullPath);
FARPROC FunctionAddress = GetProcAddress(DllModule, "Sub_1");
HookHandle = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)FunctionAddress, DllModule, ThreadId);
if (HookHandle == NULL)
{
return FALSE;
}
return TRUE;
}
// Exported functions in the dynamic library
extern "C"
__declspec(dllexport)
VOID Sub_1() // Exported function
{
MessageBox(0, 0, 0, 0);
}

0x08.Manually implement LoadLibrary remotely

  This method is learned from the github called ReflevtiveDllInjection, which is roughly divided into two parts: exe and dll, which will be described briefly below.

  exe: As the injection startup program, it applies for a block of PAGE_EXECUTE_READWRITE memory in the target process, writes the Dll directly into the target process memory space in file format, then obtains the offset of the export function "LoadDllByOEP" in the file, and uses CreateRemoteThread to directly make the target process execute the LoadDllByOEP function.

  Dll: The most critical export function LoadDllByOEP, in this function, first obtain the address of the function NtFlushInstructionCache from the export table of ntdll.dll in the target process, in Kernel32The export table of the .dll can obtain the addresses of the functions LoadLibraryA, GetProcAddress, and VirtualAlloc; then apply for memory in the process memory space, copy my own PE structure into memory, then correct the IAT and redirection block, and finally call the module OEP to complete the manual implementation of LoadLibrary!

  ps: When writing code, refer to "Windows PE Authority Guide", and I have a new understanding of the entire PE structure. I have a compulsion to use for loops. This code is all pasted here.

// InjectDllByOEP.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
using namespace std;
BOOL GrantPriviledge(WCHAR* PriviledgeName);
UINT32 GetLoadDllByOEPOffsetInFile(PVOID DllBuffer);
UINT32 RVAToOffset(UINT32 RVA, PIMAGE_NT_HEADERS NtHeader);
BOOL GetProcessIdByProcessImageName(IN WCHAR* wzProcessImageName, OUT UINT32* TargetProcessId);
HANDLE WINAPI LoadRemoteDll(HANDLE ProcessHandle, PVOID ModuleFileBaseAddress, UINT32 ModuleFileSize, LPVOID lParam);
CHAR DllFullPath[MAX_PATH] = { 0 };
int main()
{
// Firstly, get some privileges
if (GrantPriviledge(SE_DEBUG_NAME) == FALSE)
{
printf("GrantPriviledge Error\r\n");
}
// Then obtain the process ID through the process name
UINT32 ProcessId = 0;
GetCurrentDirectoryA(MAX_PATH, DllFullPath);
#ifdef _WIN64
// GetProcessIdByProcessImageName(L"Taskmgr.exe", &ProcessId);
GetProcessIdByProcessImageName(L"explorer.exe", &ProcessId);
strcat_s(DllFullPath, "\\x64LoadRemoteDll.dll");
#else
GetProcessIdByProcessImageName(L"notepad++.exe", &ProcessId);
strcat_s(DllFullPath, "\\x86LoadRemoteDll.dll");
#endif
// Obtain the dll handle
HANDLE FileHandle = CreateFileA(DllFullPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (FileHandle == INVALID_HANDLE_VALUE)
{
printf("Open File Error\r\n");
return 0;
}
// Obtain the length of the dll file
UINT32 FileSize = GetFileSize(FileHandle, NULL);
if (FileSize == INVALID_FILE_SIZE || FileSize == 0)
{
printf("Get File Size Error\r\n");
CloseHandle(FileHandle);
return 0;
}
// Apply for memory and save
PVOID FileData = HeapAlloc(GetProcessHeap(), 0, FileSize);
if (FileData == NULL)
{
printf("HeapAlloc Error\r\n");
CloseHandle(FileHandle);
return 0;
}
DWORD ReturnLength = 0;
BOOL bOk = ReadFile(FileHandle, FileData, FileSize, &ReturnLength, NULL);
CloseHandle(FileHandle);
if (bOk == FALSE)
{
printf("ReadFile Error\r\n");
HeapFree(GetProcessHeap(), 0, FileData);
return 0;
}
HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
if (ProcessHandle == NULL)
{
printf("OpenProcess Error\r\n");
HeapFree(GetProcessHeap(), 0, FileData);
return 0;
}
// Execute the export function LoadDllByOEP in the Dll to enable the target process to implement the LoadLibrary function
HANDLE ThreadHandle = LoadRemoteDll(ProcessHandle, FileData, FileSize, NULL);
if (ThreadHandle == NULL)
{
goto _Clear;
}
WaitForSingleObject(ThreadHandle, INFINITE);
_Clear:
if (FileData)
{
HeapFree(GetProcessHeap(), 0, FileData);
}
if (ProcessHandle)
{
CloseHandle(ProcessHandle);
}
return 0;
}
/************************************************************************
* Name : LoadRemoteDll
* Param: ProcessHandle process handle (IN)
* Param: ModuleBaseAddress module base address
* Param: ModuleLength size of the module in the file
* Param: lParam module handle
* Ret : HANDLE
* Write the Dll in file format to the target process memory and execute the export function LoadDllByOEP of the Dll
************************************************************************/
HANDLE WINAPI LoadRemoteDll(HANDLE ProcessHandle, PVOID ModuleFileBaseAddress, UINT32 ModuleFileSize, LPVOID lParam)
{
HANDLE ThreadHandle = NULL;
__try
{
if (ProcessHandle == NULL || ModuleFileBaseAddress == NULL || ModuleFileSize == 0)
{
return NULL;
}
// Offset of the export function relative to ModuelBaseAddress
UINT32 FunctionOffset = GetLoadDllByOEPOffsetInFile(ModuleFileBaseAddress);
if (FunctionOffset == 0)
{
return NULL;
}
// Apply memory in the target process
PVOID RemoteBufferData = VirtualAllocEx(ProcessHandle, NULL, ModuleFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (RemoteBufferData == NULL)
{
return NULL;
}
// Write the Dll file into the target process memory space
BOOL bOk = WriteProcessMemory(ProcessHandle, RemoteBufferData, ModuleFileBaseAddress, ModuleFileSize, NULL);
if (bOk == FALSE)
{
return NULL;
}
// Execute Dll's LoadDllByOEP in file format
LPTHREAD_START_ROUTINE RemoteThreadCallBack = (LPTHREAD_START_ROUTINE)((PUINT8)RemoteBufferData + FunctionOffset);
ThreadHandle = CreateRemoteThread(ProcessHandle, NULL, 1024 * 1024, RemoteThreadCallBack, lParam, 0, NULL);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
ThreadHandle = NULL;
}
return ThreadHandle;
}
/************************************************************************
* Name : LoadRemoteDll
* Param: ProcessHandle process handle
* Ret : HANDLE
* Obtain the offset of LoadDllByOEP in the Dll file
************************************************************************/
UINT32 GetLoadDllByOEPOffsetInFile(PVOID DllBuffer)
{
UINT_PTR BaseAddress = (UINT_PTR)DllBuffer;
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NtHeader = NULL;
DosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
NtHeader = (PIMAGE_NT_HEADERS)((PUINT8)BaseAddress + DosHeader->e_lfanew);
/*
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC 0x107
*/
if (NtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) // pe32
{
}
else if (NtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) // pe64
{
}
else
{
return 0;
}
UINT32 ExportDirectoryRVA = NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PIMAGE_EXPORT_DIRECTORY ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PUINT8)BaseAddress + RVAToOffset(ExportDirectoryRVA, NtHeader));
UINT32 AddressOfNamesRVA = ExportDirectory->AddressOfNames;
PUINT32 AddressOfNames = (PUINT32)((PUINT8)BaseAddress + RVAToOffset(AddressOfNamesRVA, NtHeader));
UINT32 AddressOfFunctionsRVA = ExportDirectory->AddressOfFunctions;
PUINT32 AddressOfFunctions = (PUINT32)((PUINT8)BaseAddress + RVAToOffset(AddressOfFunctionsRVA, NtHeader));
UINT32 AddressOfNameOrdinalsRVA = ExportDirectory->AddressOfNameOrdinals;
PUINT16 AddressOfNameOrdinals = (PUINT16)((PUINT8)BaseAddress + RVAToOffset(AddressOfNameOrdinalsRVA, NtHeader));
for (UINT32 i = 0; i < ExportDirectory->NumberOfFunctions; i++)
{
CHAR* ExportFunctionName = (CHAR*)((PUINT8)BaseAddress + RVAToOffset(*AddressOfNames, NtHeader));
if (strstr(ExportFunctionName, "LoadDllByOEP") != NULL)
{
UINT16 ExportFunctionOrdinals = AddressOfNameOrdinals[i];
return RVAToOffset(AddressOfFunctions[ExportFunctionOrdinals], NtHeader);
}
}
return 0;
}
/************************************************************************
* Name : RVAToOffset
* Param: RVA memory offset
* Param: NtHeader Nt header
* Ret : UINT32
* Convert memory offset to file offset
************************************************************************/
UINT32 RVAToOffset(UINT32 RVA, PIMAGE_NT_HEADERS NtHeader)
{
UINT32 i = 0;
PIMAGE_SECTION_HEADER SectionHeader = NULL;
SectionHeader = IMAGE_FIRST_SECTION(NtHeader);
if (RVA < SectionHeader[0].PointerToRawData)
{
return RVA;
}
for (i = 0; i < NtHeader->FileHeader.NumberOfSections; i++)
{
if (RVA >= SectionHeader[i].VirtualAddress && RVA < (SectionHeader[i].VirtualAddress + SectionHeader[i].SizeOfRawData))
{
return (RVA - SectionHeader[i].VirtualAddress + SectionHeader[i].PointerToRawData);
}
}
return 0;
}
/************************************************************************
* Name: GetProcessIdByProcessImageName
* Param: wzProcessImageName Process image name (IN)
* Param: TargetProcessId Process ID (OUT)
* Ret : BOOLEAN
* Obtain the process ID using the ToolHelp series of functions through the process image name
************************************************************************/
BOOL GetProcessIdByProcessImageName(IN WCHAR* wzProcessImageName, OUT UINT32* TargetProcessId)
{
HANDLE ProcessSnapshotHandle = NULL;
PROCESSENTRY32 ProcessEntry32 = { 0 };
ProcessEntry32.dwSize = sizeof(PROCESSENTRY32); // Initialize PROCESSENTRY32Structure
ProcessSnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // Provide snapshots of all system processes
if (ProcessSnapshotHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}
Process32First(ProcessSnapshotHandle, &ProcessEntry32); // Find the first
do
{
if (lstrcmpi(ProcessEntry32.szExeFile, wzProcessImageName) == 0) // Case-insensitive
{
*TargetProcessId = ProcessEntry32.th32ProcessID;
break;
}
} while (Process32Next(ProcessSnapshotHandle, &ProcessEntry32));
CloseHandle(ProcessSnapshotHandle);
ProcessSnapshotHandle = NULL;
return TRUE;
}
/************************************************************************
* Name : GrantPriviledge
* Param: PriviledgeName The permission you want to elevate
* Ret : BOOLEAN
* Elevate the permissions you want
************************************************************************/
BOOL GrantPriviledge(WCHAR* PriviledgeName)
{
TOKEN_PRIVILEGES TokenPrivileges, OldPrivileges;
DWORD dwReturnLength = sizeof(OldPrivileges);
HANDLE TokenHandle = NULL;
LUID uID;
// Open the permission token
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &TokenHandle))
{
if (GetLastError() != ERROR_NO_TOKEN)
{
return FALSE;
}
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle))
{
return FALSE;
}
}
if (!LookupPrivilegeValue(NULL, PriviledgeName, &uID)) // Find uID by permission name
{
CloseHandle(TokenHandle);
return FALSE;
}
TokenPrivileges.PrivilegeCount = 1; // The number of permissions to elevate
TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // Dynamic array, the size of the array is based on the number of Count
TokenPrivileges.Privileges[0].Luid = uID;
// Here we adjust the permissions
if (!AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), &OldPrivileges, &dwReturnLength))
{
CloseHandle(TokenHandle);
return FALSE;
}
// 成功了
CloseHandle(TokenHandle);
return TRUE;
}
// LoadRemoteDll.h
#include <Windows.h>
#include <intrin.h>
#ifdef LOADREMOTEDLL_EXPORTS
#define LOADREMOTEDLL_API __declspec(dllexport)
#else
#define LOADREMOTEDLL_API __declspec(dllimport)
#endif
#define KERNEL32DLL_HASH 0x6A4ABC5B
#define NTDLLDLL_HASH 0x3CFA685D
#define LOADLIBRARYA_HASH 0xEC0E4E8E
#define GETPROCADDRESS_HASH 0x7C0DFCAA
#define VIRTUALALLOC_HASH 0x91AFCA54
#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
#define IMAGE_REL_BASED_ARM_MOV32A 5
#define IMAGE_REL_BASED_ARM_MOV32T 7
#define HASH_KEY 13
#pragma intrinsic( _rotr )
__forceinline UINT32 ror(UINT32 d)
{
return _rotr(d, HASH_KEY);
}
__forceinline UINT32 hash(char * c)
{
register UINT32 h = 0;
do
{
h = ror(h);
h += *c;
} while (*++c);
return h;
}
//////////////////////////////////////////////////////////////////////////
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
}{UNICODE_STRING}, *PUNICODE_STRING;
typedef struct _PEB_LDR_DATA_WIN7_X64
{
UINT32 Length;
UINT8 Initialized;
UINT8 _PADDING0_[0x3];
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
UINT8 ShutdownInProgress;
UINT8 _PADDING1_[0x7];
PVOID ShutdownThreadId;
}{PEB_LDR_DATA_WIN}7_X64, *PPEB_LDR_DATA_WIN7_X64;
typedef struct _PEB_LDR_DATA_WINXP_X86
{
UINT32 Length;
UINT8 Initialized;
UINT8 _PADDING0_[0x3];
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
}{PEB_LDR_DATA_WINXP_X}86, *PPEB_LDR_DATA_WINXP_X86;
#ifdef _WIN64
#define PPEB_LDR_DATA PPEB_LDR_DATA_WIN7_X64
#define PEB_LDR_DATA PEB_LDR_DATA_WIN7_X64
#else 
#define PPEB_LDR_DATA PPEB_LDR_DATA_WINXP_X86
#define PEB_LDR_DATA PEB_LDR_DATA_WINXP_X86
#endif
typedef struct _CURDIR
{
UNICODE_STRING DosPath;
HANDLE Handle;
}{ CURDIR,} *PCURDIR;
typedef struct _RTL_USER_PROCESS_PARAMETERS_WINXP_X86 {
UINT32 MaximumLength;
UINT32 Length;
UINT32 Flags;
UINT32 DebugFlags;
HANDLE ConsoleHandle;
UINT32 ConsoleFlags;
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
CURDIR CurrentDirectory; // ProcessParameters
UNICODE_STRING DllPath; // ProcessParameters
UNICODE_STRING ImagePathName; // ProcessParameters
UNICODE_STRING CommandLine; // ProcessParameters
PVOID Environment;
UINT32 StartingX;
UINT32 StartingY;
UINT32 CountX;
UINT32 CountY;
UINT32 CountCharsX;
UINT32 CountCharsY;
UINT32 FillAttribute;
UINT32 WindowFlags;
UINT32 ShowWindowFlags;
UNICODE_STRING WindowTitle;
UNICODE_STRING DesktopInfo;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeData;
UINT32 CurrentDirectores[8];
}{RTL_USER_PROCESS_PARAMETERS_WINXP_X}86, *PRTL_USER_PROCESS_PARAMETERS_WINXP_X86;
typedef struct _RTL_USER_PROCESS_PARAMETERS_WIN7_X64 {
UINT32 MaximumLength;
UINT32 Length;
UINT32 Flags;
UINT32 DebugFlags;
HANDLE ConsoleHandle;
UINT32 ConsoleFlags;
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
CURDIR CurrentDirectory; // ProcessParameters
UNICODE_STRING DllPath; // ProcessParameters
UNICODE_STRING ImagePathName; // ProcessParameters
UNICODE_STRING CommandLine; // ProcessParameters
PVOID Environment;
UINT32 StartingX;
UINT32 StartingY;
UINT32 CountX;
UINT32 CountY;
UINT32 CountCharsX;
UINT32 CountCharsY;
UINT32 FillAttribute;
UINT32 WindowFlags;
UINT32 ShowWindowFlags;
UNICODE_STRING WindowTitle;
UNICODE_STRING DesktopInfo;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeData;
UINT32 CurrentDirectores[8];
UINT64 EnvironmentSize;
UINT64 EnvironmentVersion;
}RTL_USER_PROCESS_PARAMETERS_WIN7_X64, *PRTL_USER_PROCESS_PARAMETERS_WIN7_X64;
#ifdef _WIN64
#define PRTL_USER_PROCESS_PARAMETERS PRTL_USER_PROCESS_PARAMETERS_WIN7_X64
#define RTL_USER_PROCESS_PARAMETERS RTL_USER_PROCESS_PARAMETERS_WIN7_X64
#else 
#define PRTL_USER_PROCESS_PARAMETERS PRTL_USER_PROCESS_PARAMETERS_WINXP_X86
#define RTL_USER_PROCESS_PARAMETERS RTL_USER_PROCESS_PARAMETERS_WINXP_X86
#endif
#define GDI_HANDLE_BUFFER_SIZE32 34
#define GDI_HANDLE_BUFFER_SIZE64 60
#ifndef _WIN64
#define GDI_HANDLE_BUFFER_SIZE GDI_HANDLE_BUFFER_SIZE32
#else
#define GDI_HANDLE_BUFFER_SIZE GDI_HANDLE_BUFFER_SIZE64
#endif
typedef UINT32 GDI_HANDLE_BUFFER[GDI_HANDLE_BUFFER_SIZE];
// PEB structure
typedef struct _PEB
{
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
union
{
BOOLEAN BitField;
struct
{
BOOLEAN ImageUsesLargePages : 1;
BOOLEAN IsProtectedProcess : 1;
BOOLEAN IsLegacyProcess : 1;
BOOLEAN IsImageDynamicallyRelocated : 1;
BOOLEAN SkipPatchingUser :32Forwarders : 1;
BOOLEAN IsPackagedProcess : 1;
BOOLEAN IsAppContainer : 1;
BOOLEAN SpareBits : 1;
};
};
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID SubSystemData;
PVOID ProcessHeap;
PRTL_CRITICAL_SECTION FastPebLock;
PVOID AtlThunkSListPtr;
PVOID IFEOKey;
union
{
UINT32 CrossProcessFlags;
struct
{
UINT32 ProcessInJob : 1;
UINT32 ProcessInitializing : 1;
UINT32 ProcessUsingVEH : 1;
UINT32 ProcessUsingVCH : 1;
UINT32 ProcessUsingFTH : 1;
UINT32 ReservedBits0 : 27;
};
UINT32 EnvironmentUpdateCount;
};
union
{
PVOID KernelCallbackTable;
PVOID UserSharedInfoPtr;
};
UINT32 SystemReserved[1];
UINT32 AtlThunkSListPtr32;
PVOID ApiSetMap;
UINT32 TlsExpansionCounter;
PVOID TlsBitmap;
UINT32 TlsBitmapBits[2];
PVOID ReadOnlySharedMemoryBase;
PVOID HotpatchInformation;
PVOID* ReadOnlyStaticServerData;
PVOID AnsiCodePageData;
PVOID OemCodePageData;
PVOID UnicodeCaseTableData;
UINT32 NumberOfProcessors;
UINT32 NtGlobalFlag;
LARGE_INTEGER CriticalSectionTimeout;
SIZE_T HeapSegmentReserve;
SIZE_T HeapSegmentCommit;
SIZE_T HeapDeCommitTotalFreeThreshold;
SIZE_T HeapDeCommitFreeBlockThreshold;
UINT32 NumberOfHeaps;
UINT32 MaximumNumberOfHeaps;
PVOID* ProcessHeaps;
PVOID GdiSharedHandleTable;
PVOID ProcessStarterHelper;
UINT32 GdiDCAttributeList;
PRTL_CRITICAL_SECTION LoaderLock;
UINT32 OSMajorVersion;
UINT32 OSMinorVersion;
UINT16 OSBuildNumber;
UINT16 OSCSDVersion;
UINT32 OSPlatformId;
UINT32 ImageSubsystem;
UINT32 ImageSubsystemMajorVersion;
UINT32 ImageSubsystemMinorVersion;
UINT_PTR ImageProcessAffinityMask;
GDI_HANDLE_BUFFER GdiHandleBuffer;
PVOID PostProcessInitRoutine;
PVOID TlsExpansionBitmap;
UINT32 TlsExpansionBitmapBits[32];
UINT32 SessionId;
ULARGE_INTEGER AppCompatFlags;
ULARGE_INTEGER AppCompatFlagsUser;
PVOID pShimData;
PVOID AppCompatInfo;
UNICODE_STRING CSDVersion;
PVOID ActivationContextData;
PVOID ProcessAssemblyStorageMap;
PVOID SystemDefaultActivationContextData;
PVOID SystemAssemblyStorageMap;
SIZE_T MinimumStackCommit;
PVOID* FlsCallback;
LIST_ENTRY FlsListHead;
PVOID FlsBitmap;
UINT32 FlsBitmapBits[FLS_MAXIMUM_AVAILABLE / (sizeof(UINT32) * 8);
UINT32 FlsHighIndex;
PVOID WerRegistrationData;
PVOID WerShipAssertPtr;
PVOID pContextData;
PVOID pImageHeaderHash;
union
{
UINT32 TracingFlags;
struct
{
UINT32 HeapTracingEnabled : 1;
UINT32 CritSecTracingEnabled : 1;
UINT32 LibLoaderTracingEnabled : 1;
UINT32 SpareTracingBits : 29;
};
};
UINT64 CsrServerReadOnlySharedMemoryBase;
} PEB, *PPEB;
// Ldr three link list structure
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
UINT32 SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
UINT32 Flags;
UINT16 LoadCount;
UINT16 TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
UINT32 CheckSum;
};
};
union {
struct {
UINT32 TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
struct _ACTIVATION_CONTEXT * EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedef const struct _LDR_DATA_TABLE_ENTRY *PCLDR_DATA_TABLE_ENTRY;
LOADREMOTEDLL_API UINT_PTR WINAPI LoadDllByOEP(PVOID lParam);
// LoadRemoteDll.cpp
// LoadRemoteDll.cpp : Defines the export functions of the DLL application.
//
#include "stdafx.h"
#include "LoadRemoteDll.h"
#pragma intrinsic(_ReturnAddress)
__declspec(noinline)
UINT_PTR caller()
{
return (UINT_PTR)_ReturnAddress(); // #include <intrin.h>
}
typedef
HMODULE
(WINAPI * pfnLoadLibraryA)(LPCSTR lpLibFileName);
typedef
FARPROC
(WINAPI * pfnGetProcAddress)(HMODULE hModule, LPCSTR lpProcName);
typedef
LPVOID
(WINAPI * pfnVirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
typedef
LONG // NTSTATUS
(NTAPI * pfnNtFlushInstructionCache)(HANDLE ProcessHandle, PVOID BaseAddress, SIZE_T Length);
typedef
BOOL
(APIENTRY * pfnDllMain)(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved);
LOADREMOTEDLL_API UINT_PTR WINAPI LoadDllByOEP(PVOID lParam)
{
UINT_PTR LibraryAddress = 0;
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NtHeader = NULL;
pfnLoadLibraryA LoadLibraryAAddress = NULL;
pfnGetProcAddress GetProcAddressAddress = NULL;
pfnVirtualAlloc VirtualAllocAddress = NULL;
pfnNtFlushInstructionCache NtFlushInstructionCacheAddress = NULL;
LibraryAddress = caller(); // Obtain the address of the next instruction, which is actually to obtain the current instruction address, providing a starting point for finding the PE header later
DosHeader = (PIMAGE_DOS_HEADER)LibraryAddress;
while (TRUE)
{
if (DosHeader->e_magic == IMAGE_DOS_SIGNATURE &&
DosHeader->e_lfanew >= sizeof(IMAGE_DOS_HEADER) &&
DosHeader->e_lfanew < 1024)
{
NtHeader = (PIMAGE_NT_HEADERS)((PUINT8)LibraryAddress + DosHeader->e_lfanew);
if (NtHeader->Signature == IMAGE_NT_SIGNATURE)
{
break;
}
}
LibraryAddress--;
DosHeader = (PIMAGE_DOS_HEADER)LibraryAddress;
}
// Obtain PEB
#ifdef _WIN64
PPEB Peb = (PPEB)__readgsqword(0x60);
#else
PPEB Peb = (PPEB)__readfsdword(0x30);
#endif
PPEB_LDR_DATA Ldr = Peb->Ldr;
// 1.Get function addresses from the Dll export table
for (PLIST_ENTRY TravelListEntry = (PLIST_ENTRY)Ldr->InLoadOrderModuleList.Flink;
TravelListEntry != &Ldr->InLoadOrderModuleList; // Empty node
TravelListEntry = TravelListEntry->Flink)
{
PLDR_DATA_TABLE_ENTRY LdrDataTableEntry = (PLDR_DATA_TABLE_ENTRY)TravelListEntry;
UINT32 FunctionCount = 0;
// WCHAR* DllName = (WCHAR*)LdrDataTableEntry->BaseDllName.Buffer;
UINT_PTR DllName = (UINT_PTR)LdrDataTableEntry->BaseDllName.Buffer;
UINT32 DllLength = LdrDataTableEntry->BaseDllName.Length;
UINT_PTR DllBaseAddress = (UINT_PTR)LdrDataTableEntry->DllBase;
DosHeader = (PIMAGE_DOS_HEADER)DllBaseAddress;
NtHeader = (PIMAGE_NT_HEADERS)((PUINT8)DllBaseAddress + DosHeader->e_lfanew);
IMAGE_DATA_DIRECTORY ExportDataDirectory = (IMAGE_DATA_DIRECTORY)(NtHeader)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
PIMAGE_EXPORT_DIRECTORY ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PUINT8)DllBaseAddress + ExportDataDirectory.VirtualAddress);
PUINT32 AddressOfFunctions = (PUINT32)((PUINT8)DllBaseAddress + ExportDirectory->AddressOfFunctions);
PUINT32 AddressOfNames = (PUINT32)((PUINT8)DllBaseAddress + ExportDirectory->AddressOfNames);
PUINT16 AddressOfNameOrdinals = (PUINT16)((PUINT8)DllBaseAddress + ExportDirectory->AddressOfNameOrdinals);
UINT16 Ordinal = 0;
UINT_PTR ExportFunctionAddress = 0;
UINT32 HashValue = 0;
// Convert the DLL name to a Hash value
do
{
HashValue = ror((UINT32)HashValue);
if (*((PUINT8)DllName) >= 'a'
{
HashValue += *((PUINT8)DllName) - 0x20;
}
else
{
HashValue += *((PUINT8)DllName);
}
DllName++;
} while (--DllLength);
if (HashValue == KERNEL)32DLL_HASH)
{
FunctionCount = 3;
for (INT i = 0; i < ExportDirectory->NumberOfFunctions; i++)
{
if (FunctionCount == 0)
{
break;
}
CHAR* szExportFunctionName = (CHAR*)((PUINT8)DllBaseAddress + AddressOfNames[i]);
HashValue = hash(szExportFunctionName);
if (HashValue == LOADLIBRARYA_HASH)
{
Ordinal = AddressOfNameOrdinals[i];
LoadLibraryAAddress = (pfnLoadLibraryA)((PUINT8)DllBaseAddress + AddressOfFunctions[Ordinal]);
FunctionCount--;
}
else if (HashValue == GETPROCADDRESS_HASH)
{
Ordinal = AddressOfNameOrdinals[i];
GetProcAddressAddress = (pfnGetProcAddress)((PUINT8)DllBaseAddress + AddressOfFunctions[Ordinal]);
FunctionCount--;
}
else if (HashValue == VIRTUALALLOC_HASH)
{
Ordinal = AddressOfNameOrdinals[i];
VirtualAllocAddress = (pfnVirtualAlloc)((PUINT8)DllBaseAddress + AddressOfFunctions[Ordinal]);
FunctionCount--;
}
}
}
else if (HashValue == NTDLLDLL_HASH)
{
FunctionCount = 1;
for (INT i = 0; i < ExportDirectory->NumberOfFunctions; i++)
{
if (FunctionCount == 0)
{
break;
}
CHAR* szExportFunctionName = (CHAR*)((PUINT8)DllBaseAddress + AddressOfNames[i]);
HashValue = hash(szExportFunctionName);
if (HashValue == NTFLUSHINSTRUCTIONCACHE_HASH)
{
Ordinal = AddressOfNameOrdinals[i];
NtFlushInstructionCacheAddress = (pfnNtFlushInstructionCache)((PUINT8)DllBaseAddress + AddressOfFunctions[Ordinal]);
FunctionCount--;
}
}
}
if (LoadLibraryAAddress != NULL &&
GetProcAddressAddress != NULL &&
VirtualAllocAddress != NULL &&
NtFlushInstructionCacheAddress != NULL)
{
break;
}
}
// 2. Allocate memory, reload our Dll
// Update DosHeader and NtHeader again
DosHeader = (PIMAGE_DOS_HEADER)LibraryAddress;
NtHeader = (PIMAGE_NT_HEADERS)((PUINT8)LibraryAddress + DosHeader->e_lfanew);
// Reallocate memory (SizeOfImage is the size of PE in memory)
/* _asm
{
int 3;
}
*/
// This header that is newly allocated should not be moved arbitrarily, use a variable to replace it
UINT_PTR NewBaseAddress = (UINT_PTR)VirtualAllocAddress(NULL, NtHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
UINT_PTR OldPtr = LibraryAddress;
UINT_PTR BasePtr = NewBaseAddress;
// 2.1First, copy the header + Section Table
UINT32 SizeOfHeaders = NtHeader->OptionalHeader.SizeOfHeaders;
while (SizeOfHeaders--)
{
*(PUINT8)BasePtr++ = *(PUINT8)OldPtr++;
}
// memcpy((PVOID)NewBaseAddress, (PVOID)LibraryAddress, NtHeader->OptionalHeader.SizeOfHeaders);
/*
PIMAGE_SECTION_HEADER SectionHeader = (PIMAGE_SECTION_HEADER)((PUINT8)&NtHeader->OptionalHeader + NtHeader->FileHeader.SizeOfOptionalHeader);
UINT32 NumberOfSections = NtHeader->FileHeader.NumberOfSections;
while (NumberOfSections--)
{
UINT_PTR NewSectionAddress = (UINT_PTR)((PUINT8)NewBaseAddress + SectionHeader->VirtualAddress);
UINT_PTR OldSectionAddress = (UINT_PTR)((PUINT8)LibraryAddress + SectionHeader->PointerToRawData);
UINT32 SizeOfRawData = SectionHeader->SizeOfRawData;
while (SizeOfRawData--)
{
*(PUINT8)NewSectionAddress++ = *(PUINT8)OldSectionAddress++;
}
SectionHeader = (PIMAGE_SECTION_HEADER)((PUINT8)SectionHeader + sizeof(IMAGE_SECTION_HEADER));
}
*/
// 2.2Copy section area
PIMAGE_SECTION_HEADER SectionHeader = IMAGE_FIRST_SECTION(NtHeader);
for (INT i = 0; i < NtHeader->FileHeader.NumberOfSections; i++)
{
if (SectionHeader[i].VirtualAddress == 0 || SectionHeader[i].SizeOfRawData == 0) // There is no data in the section block
{
continue;
}
// Locate the position of the section block in memory
UINT_PTR NewSectionAddress = (UINT_PTR)((PUINT8)NewBaseAddress + SectionHeader[i].VirtualAddress);
UINT_PTR OldSectionAddress = (UINT_PTR)((PUINT8)LibraryAddress + SectionHeader[i].PointerToRawData);
// Copy the section block data to virtual memory
UINT32 SizeOfRawData = SectionHeader[i].SizeOfRawData;
while (SizeOfRawData--)
{
*(PUINT8)NewSectionAddress++ = *(PUINT8)OldSectionAddress++;
}
//memcpy(SectionAddress, (PVOID)((PUINT8)LibraryAddress + SectionHeader[i].PointerToRawData), SectionHeader[i].SizeOfRawData);
}
// 2.3Correct the import table (IAT)
IMAGE_DATA_DIRECTORY ImportDataDirectory = (IMAGE_DATA_DIRECTORY)(NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]);
PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((PUINT8)NewBaseAddress + ImportDataDirectory.VirtualAddress);
/* 
_asm
{
int 3;
}
*/
/*
while (ImportDescriptor->Characteristics != 0)
{
PIMAGE_THUNK_DATA FirstThunk = (PIMAGE_THUNK_DATA)((PUINT8)NewBaseAddress + ImportDescriptor->FirstThunk);
PIMAGE_THUNK_DATA OriginalFirstThunk = (PIMAGE_THUNK_DATA)((PUINT8)NewBaseAddress + ImportDescriptor->OriginalFirstThunk);
// Get the name of the imported module
// char szModuleName[MAX_PATH] = { 0 };
PCHAR ModuleName = (PCHAR)((PUINT8)NewBaseAddress + ImportDescriptor->Name);
HMODULE Dll = LoadLibraryAAddress(ModuleName);
UINT_PTR FunctionAddress = 0;
for (INT i = 0; OriginalFirstThunk[i].u1.Function != 0; i++)
{
if (IMAGE_SNAP_BY_ORDINAL(OriginalFirstThunk[i].u1.Ordinal))
{
FunctionAddress = (UINT_PTR)GetProcAddressAddress(Dll, MAKEINTRESOURCEA((IMAGE_ORDINAL(OriginalFirstThunk[i].u1.Ordinal))));
}
else
{
PIMAGE_IMPORT_BY_NAME ImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PUINT8)NewBaseAddress + OriginalFirstThunk[i].u1.AddressOfData);
FunctionAddress = (UINT_PTR)GetProcAddressAddress(Dll, (CHAR*)ImageImportByName->Name); // Get Function Address by Function Name
}
FirstThunk[i].u1.Function = FunctionAddress;
}
ImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((PUINT8)ImportDescriptor + sizeof(IMAGE_IMPORT_DESCRIPTOR));
}
*/
for (INT i = 0; ImportDescriptor[i].Name != NULL; i)++)
{
// Load the imported dynamic library
HMODULE Dll = LoadLibraryAAddress((const CHAR*)((PUINT8)NewBaseAddress + ImportDescriptor[i].Name));
PIMAGE_THUNK_DATA OriginalFirstThunk = (PIMAGE_THUNK_DATA)((PUINT8)NewBaseAddress + ImportDescriptor[i].OriginalFirstThunk);
PIMAGE_THUNK_DATA FirstThunk = (PIMAGE_THUNK_DATA)((PUINT8)NewBaseAddress + ImportDescriptor[i].FirstThunk);
UINT_PTR FunctionAddress = 0;
// Traverse each function of the imported module
for (INT j = 0; OriginalFirstThunk[j].u1.Function; j++)
{
if (&OriginalFirstThunk[j] && IMAGE_SNAP_BY_ORDINAL(OriginalFirstThunk[j].u1.Ordinal))
{
// Ordinal import---->Here, the function address is directly found from the export table of Dll
// FunctionAddress = (UINT_PTR)GetProcAddressAddress(Dll, MAKEINTRESOURCEA((IMAGE_ORDINAL(OriginalFirstThunk[j].u1.Ordinal)))); // Removing the highest bit is the sequence number
DosHeader = (PIMAGE_DOS_HEADER)Dll;
NtHeader = (PIMAGE_NT_HEADERS)((PUINT8)Dll + DosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PUINT8)Dll + NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
// Export function address RVA array
PUINT32 AddressOfFunctions = (PUINT32)((PUINT8)Dll + ExportDirectory->AddressOfFunctions);
UINT16 Ordinal = IMAGE_ORDINAL(OriginalFirstThunk[j].u1.Ordinal - ExportDirectory->Base); // Export Function Number - Base(Start Value of Export Function Number) = Sequence Number of Export Function in Function Address Table
FunctionAddress = (UINT_PTR)((PUINT8)Dll + AddressOfFunctions[Ordinal]);
}
else
{
// Name Import
PIMAGE_IMPORT_BY_NAME ImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PUINT8)NewBaseAddress + OriginalFirstThunk[j].u1.AddressOfData);
FunctionAddress = (UINT_PTR)GetProcAddressAddress(Dll, (CHAR*)ImageImportByName->Name); // Get Function Address by Function Name
}
// Update IAT
FirstThunk[j].u1.Function = FunctionAddress;
}
}
// 2.4Correct Redirect Table
DosHeader = (PIMAGE_DOS_HEADER)LibraryAddress;
NtHeader = (PIMAGE_NT_HEADERS)((PUINT8)LibraryAddress + DosHeader->e_lfanew);
// UINT_PTR Delta = NewBaseAddress - NtHeader->OptionalHeader.ImageBase;
IMAGE_DATA_DIRECTORY BaseRelocDataDirectory = (IMAGE_DATA_DIRECTORY)(NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]);
// Redirect Table Existence
if (BaseRelocDataDirectory.Size != 0)
{
PIMAGE_BASE_RELOCATION BaseRelocation = (PIMAGE_BASE_RELOCATION)((PUINT8)NewBaseAddress + BaseRelocDataDirectory.VirtualAddress);
while (BaseRelocation->SizeOfBlock != 0)
{
typedef struct _IMAGE_RELOC
{
UINT16 Offset : 12; // Low12Bit---Offset
UINT16 Type : 4; // High4Bit---Type
} *PIMAGE_RELOC;
// Locate to the relocation block
PIMAGE_RELOC RelocationBlock = (PIMAGE_RELOC)((PUINT8)BaseRelocation + sizeof(IMAGE_BASE_RELOCATION));
// Calculate the number of relocation bits to be corrected
UINT32 NumberOfRelocations = (BaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(UINT16);
for (INT i = 0; i < NumberOfRelocations; i++)
{
if (RelocationBlock[i].Type == IMAGE_REL_BASED_DIR)64)
{
// 64 Bit
PUINT64 Address = (PUINT64)((PUINT8)NewBaseAddress + BaseRelocation->VirtualAddress + RelocationBlock[i].Offset);
UINT64 Delta = (UINT64)NewBaseAddress - NtHeader->OptionalHeader.ImageBase;
*Address += Delta;
}
else if (RelocationBlock[i].Type == IMAGE_REL_BASED_HIGHLOW)
{
// 32 Bit
PUINT32 Address = (PUINT32)((PUINT8)NewBaseAddress + BaseRelocation->VirtualAddress + (RelocationBlock[i].Offset));
UINT32 Delta = (UINT32)NewBaseAddress - NtHeader->OptionalHeader.ImageBase;
*Address += Delta;
}
}
// Go to the next redirection table
BaseRelocation = (PIMAGE_BASE_RELOCATION)((PUINT8)BaseRelocation + BaseRelocation->SizeOfBlock);
}
}
// 3.Get module OEP
UINT_PTR AddressOfEntryPoint = (UINT_PTR)((PUINT8)NewBaseAddress + NtHeader->OptionalHeader.AddressOfEntryPoint);
NtFlushInstructionCacheAddress(INVALID_HANDLE_VALUE, NULL, 0);
// Call through OEP to call DllMain
((pfnDllMain)AddressOfEntryPoint)((HMODULE)NewBaseAddress, DLL_PROCESS_ATTACH, lParam);
/* _asm
{
int 3;
}
*/
return AddressOfEntryPoint;
}
// dllmain.cpp : Defines the entry point of the DLL application.
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
MessageBoxA(0, 0, 0, 0);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

0x09Summary

  Maybe there are some things I haven't learned yet, Ring3The method of injecting Dll, as the saying goes, the road is long and arduous, I will seek to the end!

  Here is the code download address: https://github.com/YouArekongqi/InjectCollection.git

The following is what the editor introduces to everyone about Windows x86/ x64 Ring3Summary of layer injection Dll, hoping it will be helpful to everyone!

Declaration: The content of this article is from the Internet, the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously, this website does not own the copyright, does not undergo manual editing, and does not assume relevant legal liability. If you find any content suspected of copyright infringement, please send an email to notice#w3Please report any violations by email to codebox.com (replace # with @), and provide relevant evidence. Once verified, this site will immediately delete the suspected infringing content.

You May Also Like