DLL injection technology

1. Explicitly load and unload DLL modules

At any time, a thread in the process can call the following two functions to map a DLL to the address space of the process:

HMODULE LoadLibrary(PCTSTR pszDLLPathName);

HMODULE LoadLibraryEx(
	PCTSTR pszDLLPathName,
	HANDLE hFile,  //Default NULL
   	DWORD  dwFlags //Default 0
);

Unload function

BOOL FreeLibrary(HMODULE hInstDll);

VOID FreeLibraryAndExitThraed(
	HMODULE hInstDll,
	DWORD   dwExitCode
);

2. Get the DLL symbol address

FARPROC GetProcAddress(
	HMODULE hInstDll,
	PCSTR  	pszSymbolName
);

3.Dll entry point function

BOOL WINAPI DllMain(HINSTANCE hInstDll,DWORD fdwReason,PVOID fImpLoad){
	switch(fdwReason){
		//For the first time, DLL is mapped to the process address space.
		case DLL_PROCESS_ATTACH:
			break;
		//Only after the dll has been mapped to the process address space can a new thread call this function
		case DLL_THREAD_ATTACH:
			break;
		case DLL_THREAD_DETACH:
			break;
		//Called when the DLL is freed from the process address space
		case DLL_PROCESS_DETACH:
			break;
	}
	return TRUE;
}

4.DLL injection

4.1. Injecting DLL s using registry

First, find the following entries in the registry:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\

The table entry contains AppInit_DLLs and LoadAppInit_DLLs two keys
All we have to do is write the DLL path to AppInit_DLLs and loadappinit_ Set DLLs to 1

If you want to inject multiple DLLs, pay attention to putting the DLL into the system path, appinit_ Each DLL in DLLs is separated by a space or comma, and the path cannot be written from the beginning of the second DLL.

Principle: user32 DLL will be affected when it is mapped to a new process_ PROCESS_ Attach notification when user32 When DLL processes it, it will obtain the above registry value and call LoadLibrary to load the DLL.

Advantages: most convenient

Disadvantages:
(1) DLLs will only be mapped to those that use user32 DLL in process
(2) The DLL will be mapped to all that use user32 DLL.

4.2. Using windows hooks to inject DLL s

First, let's look at the hook function

HHOOK hHook = SetwindowHookEx(
	int idHook,  //Type of hook installed
	HOOKPROC,		//Address of a function
	HINSTANCE hMod,		//A DLL that contains functions
	DWORD dwThreadId				//Which thread installs hook? 0 represents all threads
);

Principle: if the last parameter is set to 0, the global message hook is installed. At this time, hookproc must be in the DLL and the third parameter hMod must be specified. In this way, when the system calls HOOKPROC in other processes, if the target DLL is not loaded, the KeUserModeCallback function will be used to callback User32.. DLL__ ClientLoadLibrary() function, by user32 DLL loads the DLL into the target process, so as to achieve the purpose of injecting the DLL into other processes.

4.3. Using remote threads to inject DLL s

Remote thread API

HANDLE CreateRemoteThread(
HANDLE hProcess,                            //Used to represent the process to which the newly created thread belongs
LPSECURITY_ATTRIBUTES 1pThreadAttributes,   //Thread safe property, NULL
SIZE_T dwStacksize,                         //Stack space size of new thread
LPTHREAD_START_ROUTINE lpStartAddress       //Function address of the new thread
LPVOID lpParameter,                         //Data passed to the new thread
DWORD dwCreationFlags,						//Method of creating thread
LPDWORD lpThreadId                          //It is used to receive the ID of the new thread. If it is NULL, the thread ID will not be returned
);

Two questions:
(1) LoadLibrary cannot be directly put into CreateRemoteThread, because the other process does not know the base address. We should first get the mechanism of LoadLibrary in the memory where the other process is located
(2) The dynamic library string cannot be passed directly because the other party's memory address does not have the string. The string should be passed to the other party's process first.

BOOL WINAPI InjectDLLToProcess(DWORD dwTargetPid, LPCSTR DLLPath){
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid);
    if (hProc == NULL) {
        printf("OpenProcess Faild\n");
        return FALSE; 
    }
    //Use the VirtualAllocEx function to allocate the DLL file name buffer at the memory address of the remote process
    LPTSTR psLibFileRemote = (LPTSTR)VirtualAllocEx(hProc, NULL, lstrlen((LPTSTR)DLLPath) + 1, MEM_COMMIT, PAGE_READWRITE);
    if (psLibFileRemote == NULL) {
        printf("VirtualAllocEx Faild\n");
        return FALSE;
    }
    //Use the WriteProcessMemory function to copy the DLL pathname to the remote memory space
    if (WriteProcessMemory(hProc, psLibFileRemote, (LPCVOID)DLLPath, lstrlen((LPTSTR)DLLPath) + 1, NULL) == 0) {
        printf("WriteMemory Failed\n");
        return FALSE;
    }
    //Get the LoadLibrary address of the remote process
    PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");

    HANDLE hThread = CreateRemoteThread(hProc, NULL, 0, pfnThreadRtn,psLibFileRemote, 0, NULL);
    if (hThread == NULL) {
        printf("CreateRemoteThread Failed\n");
        return FALSE;
    }
    printf("Inject Successfull\n");
    return TRUE;
}

4.4.QueueUserApc/NtQueueAPCThread APC injection method

When a thread wakes up from the waiting state (the thread calls SleepEx, singleobjectandwait, MsgWaitForMultiple, etc.), it will detect whether APC is delivered to itself. If so, it will execute these APC processes. In the user layer, we can use QueueUserAPC to add the APC process to the APC queue of the target thread like creating a remote thread. When the thread resumes execution, the APC process we inserted will be executed.

DWORD WINAPI QueueUserAPC(
	PAPCFUNC pfnAPC,    //Address of APC function
	HANDLE   hThread,
	ULONG_PTR dwData    //Address of APC function
);

After adding user mode APC, the thread will not directly call APC function, so in order to increase the call opportunity, APC should be inserted into all threads.

BOOL WINAPI InjectDLLToProcessAPC(DWORD dwTargetPid, LPCSTR DLLPath) {
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid);
    if (hProc == NULL) {
        printf("OpenProcess Faild\n");
        return FALSE;
    }
    //Use the VirtualAllocEx function to allocate the DLL file name buffer at the memory address of the remote process
    LPTSTR psLibFileRemote = (LPTSTR)VirtualAllocEx(hProc, NULL, lstrlen((LPTSTR)DLLPath) + 1, MEM_COMMIT, PAGE_READWRITE);
    if (psLibFileRemote == NULL) {
        printf("VirtualAllocEx Faild\n");
        return FALSE;
    }
    //Use the WriteProcessMemory function to copy the DLL pathname to the remote memory space
    if (WriteProcessMemory(hProc, psLibFileRemote, (LPCVOID)DLLPath, lstrlen((LPTSTR)DLLPath) + 1, NULL) == 0) {
        printf("WriteMemory Failed\n");
        return FALSE;
    }
    CloseHandle(hProc);
    BOOL status = FALSE;
    //Define thread information structure
    THREADENTRY32 te32 = { sizeof(te32) };
    //Create a snapshot of the current system thread
    HANDLE hThreadShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hThreadShot == INVALID_HANDLE_VALUE)
        return FALSE;
    //Loop enumerating thread information
    if (Thread32First(hThreadShot, &te32)) {
        do {
            //Determine whether it is a child process of the target process
            if (te32.th32OwnerProcessID == dwTargetPid) {
                HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
                if (hThread){
                    //Add APC to the specified thread
                    DWORD dwRet = QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)psLibFileRemote);
                    if (dwRet > 0)
                        status = TRUE;
                    CloseHandle(hThread);
                }
            }
        } while (Thread32Next(hThreadShot, &te32));
    }
    CloseHandle(hThreadShot);
    return status;
}

In practical use, due to the harsh conditions, there are few opportunities for successful utilization. However, if the driver can be loaded, you can insert APC into the target process in the driver and directly modify some fields of the thread object to make the thread meet the conditions for calling APC.

4.5.SetThreadContext method

When injecting DLL, you can pause the thread in the target process, then write shellcode to it, and set the eip of the thread's context to the address of shellcode, so that when the thread resumes execution, it will execute our shellcode first. Load the target DLL in ShellCode and then go back to the original eip execution.

typedef struct INJECT_DATA {
    BYTE ShellCode[0x30];         //0x00
    ULONG_PTR AddrofLoadLibraryA; //0x30
    PBYTE lpDLLPath;              //0x34
    ULONG_PTR OriginalEIP;        //0x38
    char szDllPath[MAX_PATH];     //0x3C
};

__declspec(naked)
VOID ShellCodeFun(VOID) {
    __asm {
        push eax
        pushad
        popfd
        call L001
     L001:
        pop ebx
        sub ebx,8
        push dword ptr ds:[ebx+0x34]
        call dword ptr ds:[ebx+0x30]
        mov eax,dword ptr ds:[eax+0x38]
        xchg eax,[esp+0x24]
        popfd
        popad
        retn
    }
}

BOOL WINAPI InjectDLLToProcessSTC(DWORD dwTargetPid, LPCSTR DLLPath) {
    DWORD dwTidList[1024] = { 0 };
    BOOL status = FALSE;
    int index = 0;
    //First, get the thread ID in the process
    //Define thread information structure
    THREADENTRY32 te32 = { sizeof(te32) };
    //Create a snapshot of the current system thread
    HANDLE hThreadShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hThreadShot == INVALID_HANDLE_VALUE)
        return FALSE;
    //Loop enumerating thread information
    if (Thread32First(hThreadShot, &te32)) {
        do {
            //Determine whether it is a child process of the target process
            if (te32.th32OwnerProcessID == dwTargetPid) {
                status = TRUE;
                dwTidList[index++] = te32.th32ThreadID;
            }
        } while (Thread32Next(hThreadShot, &te32));
    }
    CloseHandle(hThreadShot);
    if (!status) {
        printf("The process has no threads");
        return status;
    }
    //Open processes and threads, and pause threads
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid);
    HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwTidList[0]);
    if (hThread == NULL) {
        printf("Failed to open thread");
        return FALSE;
    }
    DWORD swSuppendCnt = SuspendThread(hThread);
    //Gets the CONTEXT of the thread
    CONTEXT context;
    ULONG_PTR uEIP = 0;
    ZeroMemory(&context, sizeof(CONTEXT));
    context.ContextFlags = CONTEXT_FULL;
    GetThreadContext(hThread, &context);
    uEIP = context.Eip;
    //Request memory to write ShellCode
    PBYTE lpData = (PBYTE)VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    INJECT_DATA data;
    PBYTE pShellCode = (PBYTE)ShellCodeFun;
    if (pShellCode[0] == 0xE9) {
        pShellCode = pShellCode + *(ULONG*)(pShellCode + 1) + 5;
    }
    memcpy(data.ShellCode, pShellCode, 0x30);
    lstrcpyA(data.szDllPath,DLLPath);
    PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
    data.AddrofLoadLibraryA = (ULONG_PTR)pfnThreadRtn;
    data.OriginalEIP = uEIP;
    data.lpDLLPath = lpData + FIELD_OFFSET(INJECT_DATA,szDllPath);
    printf("Shellcode Filling completed");
    if (!WriteProcessMemory(hProcess, lpData, &data, sizeof(INJECT_DATA), NULL)) {
        printf("Write failed!");
        return FALSE;
    }
    context.Eip = (ULONG)lpData;
    SetThreadContext(hThread, &context);
    ResumeThread(hThread);
    CloseHandle(hProcess);
    CloseHandle(hThread);
    printf("Dynamic library injection succeeded!");
    return TRUE;
}

4.6. Input table entry DLL replacement method (DLL hijacking method)

The registry has a setting key called KnownDLLs

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

The existing libraries cannot be replaced, because the directory will be searched first when loading. If the directory does not exist, it can be replaced under the exe root directory.

Added by zeth369 on Tue, 01 Feb 2022 17:38:28 +0200