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.
<Span style = "font-size: 18px;"> 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
);
</Span>
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:
<Span style = "font-size: 18px;"> lpvoid winapi VirtualAllocEx (
_ In HANDLE hProcess,
_ In_opt LPVOID lpAddress,
_ In SIZE_T dwSize,
_ In DWORD flAllocationType,
_ In DWORD flProtect
);
</Span>
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
<Span style = "font-size: 18px;"> bool winapi VirtualFreeEx (
_ In HANDLE hProcess,
_ In LPVOID lpAddress,
_ In SIZE_T dwSize,
_ In DWORD dwFreeType
);
</Span>
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.
<Span style = "font-size: 18px;"> bool winapi ReadProcessMemory (
_ In HANDLE hProcess,
_ In LPCVOID lpBaseAddress,
_ Out LPVOID lpBuffer,
_ In SIZE_T nSize,
_ Out SIZE_T * lpNumberOfBytesRead
);
</Span>
<Span style = "font-size: 18px;"> bool winapi WriteProcessMemory (
_ In HANDLE hProcess,
_ In LPVOID lpBaseAddress,
_ In LPCVOID lpBuffer,
_ In SIZE_T nSize,
_ Out SIZE_T * lpNumberOfBytesWritten
);
</Span>
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: www.2cto.com
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.
<Span style = "font-size: 18px;"> farproc winapi GetProcAddress (
_ In HMODULE hModule,
_ In LPCSTR lpProcName
);
</Span>
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.
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.
From ithzhang's column