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