📜 ⬆️ ⬇️

Windows hook: just about complicated

image What is a hook?
What is a function hook and what is it for? In English, “hook” is a trap. Therefore, the purpose of the function hooks in Windows can be guessed - this is a trap for the function. In other words, we catch the function and take control. After this definition, tempting prospects open up for us: we can intercept the call of any function, replace the code with our own, thereby changing the behavior of any program with the one we need (of course, within certain limitations).

The purpose of this article is to demonstrate the installation of the hook and its direct implementation.

- You can not believe in the impossible!
“You just have little experience,” said the Queen. - At your age, I gave it half an hour every day! On other days, I managed to believe in a dozen of impossibilities before breakfast!

Where did this knowledge really come in handy to me
')
This knowledge is very highly specialized, and in everyday development practice it is unlikely that they will be useful, but it is highly desirable to know about them, even if this knowledge is purely theoretical. In my practice, this knowledge was useful to me for the following tasks:

• Control of incoming http-traffic and substitution of “adult” content for more harmless.
• Logging information in case of copying any files from the network folder under control.
• Minor modification of the code in the project from which the source code was lost (yes, and this also happens)

Hook installation methods

Let's move on from general phrases to a more detailed look at hooks. I know of several types of hook implementations:

● Using the SetWindowsHookEx function. This is a very simple, and therefore limited, method. It allows you to intercept only certain functions, mainly related to the window (for example, intercepting events that a window receives, mouse clicks, keyboard input). The advantage of this method is the ability to install global hooks (for example, to intercept keyboard input on all applications at once).
● Using address spoofing in the DLL import section. The essence of the method is that any module has an import section, which lists all other modules used in it, as well as addresses in memory for the functions exported by this module. It is necessary to replace the address in this module with your own and control will be transferred to the specified address.
● Using the registry key HKEY_LOCAL_MACHINE \ Software \ Microsoft \ Windows NT \ CurrentVersion \ Windows \ AppInit_Dlls . It is necessary to register the path to the DLL, but only users with administrator rights can do this. This method is good if the application does not use kernel32.dll (you cannot call the LoadLibrary function).
● Use injection DLL in the process. In my opinion, this is the most flexible and most indicative way. We will consider it in more detail.

Injection method

Injection is possible because the ThreadStart function, which is passed to the CreateThread function, has a similar signature with the LoadLibrary function (and indeed the structure of the dll and the executable file are very similar). This allows you to specify the LoadLibrary method as an argument when creating a stream.

The DLL injection algorithm looks like this:

1. Find the address of the LoadLibrary function from Kernel32.dll for the thread where we want to inject the DLL.
2. Allocate memory to write the arguments of this function.
3. Create a thread and specify the LoadLibrary and its argument as the ThreadStart function.
4. The thread goes to execution, loads the library and ends.
5. Our library is injected into the address space of a foreign thread. At the same time, when loading a DLL, the DllMain method with the PROCESS_ATTACH flag will be called. This is exactly the place where you can install hooks on the desired functions. Next, consider the installation itself hook.

Hook installation

The approach used when installing the hook can be divided into the following parts:

1. Find the address of the function whose call we want to intercept (for example, MessageBox in user32.dll).
2. Store the first few bytes of this function in another memory location.
3. In their place, we insert the machine command JUMP to go to the address of the dummy function. Naturally, the signature of the function must be the same as the original, that is, all parameters, the return value, and the call rules must match.
4. Now, when the thread calls the intercepted function, the JUMP command will redirect it to our function. At this stage we can execute any necessary code.

Then you can remove the trap, returning the first bytes of paragraph 2 in place.

So, now we understand how to inject the DLL we need into the address space of the stream and how to install the hook on the function. Now we will try to combine these approaches in practice.

Test application

Our test application will be pretty simple and written in C #. It will contain a button to display the MessageBox. For example, set the hook to this function. Test application code:

public partial class MainForm : Form { [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type); public MainForm() { InitializeComponent(); this.Text = "ProcessID: " + Process.GetCurrentProcess().Id; } private void btnShowMessage_Click(Object sender, EventArgs e) { MessageBox(new IntPtr(0), "Hello World!", "Hello Dialog", 0); } } 

Injector

As an injector, we consider two options. Injectors written in C ++ and C #. Why bilingual? The fact is that many people believe that C # is a language in which system things cannot be used, this is a myth, you can :). So, the code of the C ++ injector:

 #include "stdafx.h" #include <iostream> #include <Windows.h> #include <cstdio> int Wait(); int main() { //   ,   . DWORD processId = 55; char* dllName = "C:\\_projects\\CustomHook\\Hooking\\Debug\\HookDll.dll"; //  PID    . printf("Enter PID to inject dll: "); std::cin >> processId; //    . HANDLE openedProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); if (openedProcess == NULL) { printf("OpenProcess error code: %d\r\n", GetLastError()); return Wait(); } //  kernel32.dll HMODULE kernelModule = GetModuleHandleW(L"kernel32.dll"); if (kernelModule == NULL) { printf("GetModuleHandleW error code: %d\r\n", GetLastError()); return Wait(); } //  LoadLibrary ( A     ANSI,    ) LPVOID loadLibraryAddr = GetProcAddress(kernelModule, "LoadLibraryA"); if (loadLibraryAddr == NULL) { printf("GetProcAddress error code: %d\r\n", GetLastError()); return Wait(); } //     LoadLibrary,   -     DLL LPVOID argLoadLibrary = (LPVOID)VirtualAllocEx(openedProcess, NULL, strlen(dllName), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (argLoadLibrary == NULL) { printf("VirtualAllocEx error code: %d\r\n", GetLastError()); return Wait(); } //     . int countWrited = WriteProcessMemory(openedProcess, argLoadLibrary, dllName, strlen(dllName), NULL); if (countWrited == NULL) { printf("WriteProcessMemory error code: %d\r\n", GetLastError()); return Wait(); } //  ,   LoadLibrary     HANDLE threadID = CreateRemoteThread(openedProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryAddr, argLoadLibrary, NULL, NULL); if (threadID == NULL) { printf("CreateRemoteThread error code: %d\r\n", GetLastError()); return Wait(); } else { printf("Dll injected!"); } //  . CloseHandle(openedProcess); return 0; } int Wait() { char a; printf("Press any key to exit"); std::cin >> a; return 0; } 

Now the same, but only in C #. Consider how much more compact the code is; there is no riot of types (HANDLE, LPVOID, HMODULE, DWORD, which, in essence, mean the same thing).

 public class Exporter { [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, UIntPtr nSize, out IntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out IntPtr lpThreadId); [DllImport("kernel32.dll", SetLastError = true)] public static extern Int32 CloseHandle(IntPtr hObject); } public class Injector { public static void Inject(Int32 pid, String dllPath) { IntPtr openedProcess = Exporter.OpenProcess(ProcessAccessFlags.All, false, pid); IntPtr kernelModule = Exporter.GetModuleHandle("kernel32.dll"); IntPtr loadLibratyAddr = Exporter.GetProcAddress(kernelModule, "LoadLibraryA"); Int32 len = dllPath.Length; IntPtr lenPtr = new IntPtr(len); UIntPtr uLenPtr = new UIntPtr((uint)len); IntPtr argLoadLibrary = Exporter.VirtualAllocEx(openedProcess, IntPtr.Zero, lenPtr, AllocationType.Reserve | AllocationType.Commit, MemoryProtection.ReadWrite); IntPtr writedBytesCount; Boolean writed = Exporter.WriteProcessMemory(openedProcess, argLoadLibrary, System.Text.Encoding.ASCII.GetBytes(dllPath), uLenPtr, out writedBytesCount); IntPtr threadIdOut; IntPtr threadId = Exporter.CreateRemoteThread(openedProcess, IntPtr.Zero, 0, loadLibratyAddr, argLoadLibrary, 0, out threadIdOut); Exporter.CloseHandle(threadId); } } 

Injectable Library

Now the most interesting is the code of the library that installs the hooks. This library is written in C ++, while without an analog in C #.

 // dllmain.cpp : Defines the entry point for the DLL application. #include "stdafx.h" #include <Windows.h> #define SIZE 6 //      typedef int (WINAPI *pMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT); int WINAPI MyMessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT); void BeginRedirect(LPVOID); pMessageBoxW pOrigMBAddress = NULL; BYTE oldBytes[SIZE] = { 0 }; BYTE JMP[SIZE] = { 0 }; DWORD oldProtect, myProtect = PAGE_EXECUTE_READWRITE; BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: //       . MessageBoxW(NULL, L"I hook MessageBox!", L"Hello", MB_OK); //   MessageBox pOrigMBAddress = (pMessageBoxW)GetProcAddress(GetModuleHandleW(L"user32.dll"), "MessageBoxW"); if (pOrigMBAddress != NULL) { BeginRedirect(MyMessageBoxW); } break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; } return TRUE; } void BeginRedirect(LPVOID newFunction) { // -     BYTE tempJMP[SIZE] = { 0xE9, 0x90, 0x90, 0x90, 0x90, 0xC3 }; memcpy(JMP, tempJMP, SIZE); //      DWORD JMPSize = ((DWORD)newFunction - (DWORD)pOrigMBAddress - 5); //     VirtualProtect((LPVOID)pOrigMBAddress, SIZE, PAGE_EXECUTE_READWRITE, &oldProtect); //    memcpy(oldBytes, pOrigMBAddress, SIZE); //  4 . ,     x86 memcpy(&JMP[1], &JMPSize, 4); //    memcpy(pOrigMBAddress, JMP, SIZE); //     VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); } int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType) { //     VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, NULL); //    (   ) memcpy(pOrigMBAddress, oldBytes, SIZE); //   ,    int retValue = MessageBoxW(hWnd, lpText, L"Hooked", uiType); //    memcpy(pOrigMBAddress, JMP, SIZE); //     VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); return retValue; } 

Well, a few pictures in the end. Before installing the hook:

image

And after installation:

image

In our next article, we will try to write the library code that sets the hooks to C #, since the mechanism for injecting managed code deserves a separate article.

Article authors: nikitam , ThoughtsAboutProgramming

Source: https://habr.com/ru/post/324718/


All Articles