Each process in Windows has its own address space. They are independent of each other, ensuring system security. However, windows also designed some functions for the debugger or other tools that allow one process to operate on another process. Although they are designed for debuggers, any application can call them. Next we will talk about using remote threads to inject DLL.
Basically, DLL injection is to inject a DLL into the address space of a process. A thread in this process calls loadlibrary to load the DLL to be injected. Since we cannot directly control threads in other processes, we must create a thread of our own in other processes. We can control the newly created thread so that he can call loadlibrary to load the DLL. Windows provides a function that allows us to create a thread in another process:
A thread created in another process is called a remote thread, which is called a remote process.
HANDLE WINAPI CreateRemoteThread( __in HANDLE hProcess, __in LPSECURITY_ATTRIBUTES lpThreadAttributes, __in SIZE_T dwStackSize, __in LPTHREAD_START_ROUTINE lpStartAddress, __in LPVOID lpParameter, __in DWORD dwCreationFlags, __out LPDWORD lpThreadId );
It's easy. In addition to the first hprocess parameter, this function identifies the process of the thread to be created. Other parameters are identical with those of createthread.
The lpstartaddress parameter is the address of the thread function. Because it is created in a remote process, the function must be in the address space of the remote process.
Now that we know how to create a thread in another process, how can we load this thread into our DLL?
Don't rush to let the thread call loadlibrary to load the DLL. Now you need to consider how to run the thread, that is, selecting the thread function for the thread. Because the thread runs in other processes, the thread function must meet the following conditions:
1: The function conforms to the prototype of the thread function,
2: it exists in the remote thread address space.
After careful analysis, there is only one remote thread task. Call loadlibray to load the DLL.
In this case, can loadlibrary be directly used as a thread function?
First, check whether the function signature is the same. Not to mention, except that the parameter types are a bit different, the others are the same. Since the parameter type can be achieved through forced conversion, the first condition is met.
Let's look at the second condition: whether the function is in the remote process address space. We all know it is. In addition, they all have the same function call Convention, that is, their parameter transfer is from right to left, and there is a subroutine to balance the stack. OK, great. Using loadlibrary as a thread function is really convenient.
Is Microsoft designed for us like this? Unknown. But here, I would like to thank the cool man who found this skill.
According to msdn, loadlibrary is not an API, but a macro.
In WINBASE. H, we can find the following sentence:
# Ifdef Unicode
# Define loadlibrary loadlibraryw
# Else
# Define loadlibrary loadlibrarya
# Endif
Do you understand? There are actually two load * functions. The only difference between them is that the parameter types are different. If the DLL file name is saved in ANSI format, we must call loadlibrarya. If it is saved in unicode format, we must call loadlibraryw.
The next step is simple. You only need to call the createthread function and pass it to the parameter loadlibrarya or loadlibraryw that identifies the thread function. Then pass the path name of the DLL to be loaded by the Remote Process as a parameter to it. Haha, so excited! Everything is so smooth!
Don't be happy too early. Didn't you find anything wrong? The parameter passed to the thread function is the dll path name address. But the address is in our city. If the remote process references the data of this address, it is likely to cause access violations and the remote process is terminated. How is it serious. However, this also gives us an idea of undermining other processes. Haha. Do it yourself!
To solve this problem, we should put the string in the address space of the remote address. Are there any corresponding functions? Of course!
First, allocate a piece of memory in the address space of the remote process. How can this problem be solved! You may be familiar with virtualalloc, but it does not. His brother virtualallocex can solve this problem. View prototype:
LPVOID WINAPI VirtualAllocEx( __in HANDLE hProcess, __in_opt LPVOID lpAddress, __in SIZE_T dwSize, __in DWORD flAllocationType, __in DWORD flProtect );
Hprocess should know what it is. It is the process handle that identifies the address space of the process in which you want to apply for memory. Other parameters are identical to virtualalloc. We will not describe it here.
Of course, I know how to apply and how to release it! Look at his partner: virtualfreeex
BOOL WINAPI VirtualFreeEx( __in HANDLE hProcess, __in LPVOID lpAddress, __in SIZE_T dwSize, __in DWORD dwFreeType );
The difference from virtualfree is that there is only one more process handle.
Now that the task of applying for space is completed, how can we copy the data of this process to another process? Readprocessmemory and writeprocessmemory can be used.
BOOL WINAPI ReadProcessMemory( __in HANDLE hProcess, __in LPCVOID lpBaseAddress, __out LPVOID lpBuffer, __in SIZE_T nSize, __out SIZE_T *lpNumberOfBytesRead );
BOOL WINAPI WriteProcessMemory( __in HANDLE hProcess, __in LPVOID lpBaseAddress, __in LPCVOID lpBuffer, __in SIZE_T nSize, __out SIZE_T *lpNumberOfBytesWritten );
Because their signatures are similar, we will introduce them here.
Hprocess is used to identify a remote process.
Lpbaseaddress is the address in the remote process address space and is the returned value of virtualallocex.
Lpbuffer is the memory address of the current process. This is the address of the dll path name.
Nsize is the string to be transmitted.
Lpnumberofbyteread and lpnumberofbytewrite are the actual bytes transmitted.
Note: Calling writeprocessmemory sometimes causes failure. In this case, you can call virtualprotect to modify the attribute of the page to be written, and then change it back.
So far, there seems to be nothing, but there is another obscure problem. If you do not know the PE file format and DLL loading method, it is very difficult to find out.
We know that the actual address of the import function is obtained when the DLL is loaded. The loader obtains the function name (string) of each import function from the import table, and then fills in the corresponding position (IAT) of the import table after being queried in the DLL loaded into the process address space). That is to say, we do not know the address of the import function before running (unless the module is bound ). How does one call the imported function in the program code? Have you ever thought about this problem.
You may think it should be: Call dword ptr [004020108] ([] only indicates the address of the imported function, without practical significance ).
The program code has been determined after compilation and connection, and the address of the imported table, for example, 00402010, is obtained when the program is running. Therefore, this cannot be implemented when the program calls the import function. How is it implemented?
[] There is a definite address in it, which is beyond doubt, but its value is not the address of the imported function, but the address of a sub-program. This subroutine is called a conversion function (Thunk ). These conversion functions are used to jump to the import function. When the program calls the import function, it first calls the conversion function. When the conversion function obtains the real address of the import function from the IAT of the import table, it calls the corresponding address.
Therefore, the call form for the import function is as follows:
Call 00401164; the address of the Conversion Function. ...... : 00401164 ..... Call dword ptr [00402010]; call the import function.
After analysis, we can also understand why we need to add the _ decllpec (dllimport) prefix when declaring an export function.
The reason is: the compiler cannot distinguish whether an application calls a common function or an imported function. When we add this prefix before a function, it tells the compiler that the function comes from the imported function, and the compiler will generate the preceding command. Instead of calling XXXXXXXX.
Therefore, when writing an output function, you must add the modifier _ decllpec (dllimport) before the function declaration ).
Let's get down to the truth. The reason for this is that the thread function loadlibrary * We passed to createremotethread will be parsed into the address of the Conversion Function in our process. If the address of the conversion function is used as the starting address of the thread function, access violations may occur. Solution: force the code to skip the Conversion Function and directly call loadlibrary *.
This can be achieved through getproaddress.
FARPROC WINAPI GetProcAddress( __in HMODULE hModule, __in LPCSTR lpProcName );
Hmodule is the module handle. Mark a module.
Lpprocname is the name of a function in this module.
It returns the address of the function in the process address space of the module.
For example, getprocaddress (getmodulehandle ("kernel. dll", "loadlibraryw "));
This statement obtains the real address of loadlibrary in the process space where kernel. dll is located. Note that only the address of the kernel. dll in this process and the address of loadlibraryw are obtained. Is the same in a remote process?
"From the author's experience, the address mapped from kernel. DLL to each process is the same. "Based on this, we can think that we call this statement to obtain the address of kernel. dll and loadlibraryw in the remote address space.
The following is an example. Inject DLL to the assumer.exe process through a remote thread.
Assumer.exe: Resource Manager process. It starts with the system and runs continuously. Therefore, it is often used as the host of a remote thread.
Steps:
1: Get the handle of the explorer process.
You can call createhlpsnapshot to obtain a snapshot of the system. Then, the snapshot is traversed. Find the process named assumer.exe. And get the process object handle.
Check the Code:
Processentry32 pe32; pe32.dwsize = sizeof (pe32); handle hsnapshot = round (th32cs_snapall, 0); int ret = process32first (hsnapshot, & pe32); cstring A; updatedata (); if (-1 = m_processtofind.find (". EXE ", 0) m_processtofind + = ". EXE "; while (RET) {If (pe32.szexefile = m_processtofind) {. format ("process: % s found, its process ID is: % d", m_processtofind, pe32.th32processid); MessageBox (a); break;} ret = process32next (hsnapshot, & pe32 );}
HANDLE WINAPI CreateToolhelp32Snapshot( __in DWORD dwFlags, __in DWORD th32ProcessID);
This function is used to obtain the snapshot of a specified process and the heap and thread used by the process.
Dwflags is used to indicate the items contained in this snapshot. For more information, see msdn.
Here, th32cs_snapall is passed in, indicating that this snapshot includes information about all processes and threads in the system, as well as the modules and threads of the processes specified in th32processid.
Th32proessid specifies the ID of the process to be included in this snapshot. When 0 is passed in, it indicates the current process.
The Snapshot handle is returned. Otherwise, invalid_handle_value is returned. You can call getlasterror to view more error information.
BOOL WINAPI Process32First( __in HANDLE hSnapshot, __inout LPPROCESSENTRY32 lppe);
BOOL WINAPI Process32Next( __in HANDLE hSnapshot, __out LPPROCESSENTRY32 lppe);
The preceding two functions are used to traverse items in createhlpsnapshot. For usage, see the previous example. For other information, see msdn.
2: After obtaining the process ID of explorer, you must call OpenProcess to obtain the process handle. If the function is successfully executed, the Process Handle is returned.
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,pe32.th32ProcessID);
3: In the address space of explorer, apply for a space to store the path name of the DLL to be injected.
Pvoid ADDR = virtualallocex (hprocess, null, 50, mem_commit, page_readwrite); If (ADDR = NULL) {cstring A; int ret = getlasterror ();. format ("failed to apply for space in remote process! Error code: % d ", RET); MessageBox (a);} else {MessageBox (" the remote process address space has been successfully applied for space! ");}
4: Write the path name to the space requested by the explorer process.
Char path [50] = "F: \ injectdll. DLL "; int retval = writeprocessmemory (hprocess, ADDR, (lpvoid) path, sizeof (PATH), null); If (retval) {MessageBox (" written successfully! ");} Elsemessagebox (" Write failed! ");
5: Create a remote thread.
// Obtain the address of loadlibrarya in the remote process. (Same as the address of the current process .)
PTHREAD_START_ROUTINE pfnThread=(PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle("kernel32.DLL"),"LoadLibraryA");
HANDLE hRemoteThread=CreateRemoteThread( hProcess,//in HANDLE hProcess, NULL, 0,//__in SIZE_T dwStackSize, pfnThread, addr, 0, NULL);
If (hremotethread = invalid_handle_value) {MessageBox ("remote thread wearing failed! ");} Else {MessageBox (" the remote thread is created successfully! ");}
6: Create the DLL to be injected into the remote process. We will not describe it here. You can refer to Windows core programming series to talk about the basics of DLL.
If you create a thread in the injected DLL, You can execute the work we want it to do.
For example, to monitor the running of a program, once the program runs, load another DLL to this process. This dll will be mounted to a global hook to obtain the user's keyboard action. This is how the keyboard steals QQ. You can do it yourself.
So far, the introduction of remote threads has been completed.
Refer to Chapter 5 of Windows core programming, section 2 of encryption and decryption, and chapter 10
The above is only summarized based on the reference of various books. In case of any errors, please do not give me your advice.