Thanks
@kram7750, I try to simplify what is a hook.
A hook is used to "snap" Windows messages or any other application. With the global hook can catch such as all the pressed keys, in any application, when the user moves a any window when an application starts, and any other message generated from the operating system to/from any application.
The antivirus use a global hook, as well as many malware (for obvious reasons): thanks to hooking, hidden rootkits and keyloggers steal informations. ...
There are several ways to create a hook, one of the most used is the
dll injection, i.e. the insertion of code we want to run in a dll created ad hoc.
For example the GetAsyncKeyState function is not used to log what is typed from the keyboard, you have to set a hook.
The class (which can also be static) allows you to record a Hook or to delete it.
When registered, SetWindowsHookEx is invoked and passed a delegate that will serve the function of Windows to invoke LowLevelKeyboardHandler (callback), which will receive the message (WM_KEYUP or WM_KEYDOWN) and print on the console one of two messages (button pressed or released).
EH of course is the method that keylogger use LOL
There are more ways to make undetected a code like this. One such example is not to use the Windows API but fortunately is not at all easy ...
Well there are different types of hooks. The hook you are mentioning in the above post is a bit different to how Antvirus products work; e.g. using SetWindowsHookEx to monitor the keyboard keystrokes is a different type of hook to how Antivirus software works.
If an Antivirus is hooking functions from user-mode, it will work via IAT (Import Address Table) hooking. A better way for doing this is via what you can call a "trampoline hook". However, that's more advanced. Personally, I prefer trampoline hooks from user-mode for IAT hooking. You still place a JMP instruction for example, but it can get very advanced.
However, I would say for x86 processes, and even better method for hooking would be to just hook a deep function called KiFastSystemCall. I'm aware that maybe 100 out of thousands of software developers would actually know about this function; unless you are very advanced in security or malware programming/cracking, I doubt you'd ever learn about this function. A friend of mine,
@Cowpipe, knows other malware analysts who he checked with, they did not know about this function.
@Cowpipe only knows about it from his old cracking days (he turned onto the good side).
You can hook a function like KiFastSystemCall via a simple method such as placing a JMP instruction. However, it involves other things related to memory (changes). It's not something which a new person to hooking can do at least, it takes time to learn. I've been doing hooking for awhile (after I studied other things), I'm still not a master... Although I'd like to be, sooner or later I should fully master it, haha. (then I'll focus on mastering hooking from kernel-mode drivers, getting better everyday).
If you hook a function like KiFastSystemCall, you won't even require to hook every function you need too. For example, if you had a list of functions in the Native or Windows API you wanted to hook, for example:
- ZwTerminateProcess
- ZwSuspendProcess
- ZwOpenProcess
- ZwCreateFile
- ZwCreateKey
- ZwOpenKey
- ZwAllocateVirtualMemory
And you hooked KiFastSystemCall, then you can determine if those functions were being called via the System ID. Each function in the Native/Windows API has something called a System ID (system identifier), and it is used to differentiate between which function was being called. For example, if we hooked a function locally (or via DLL/Code injection), a very simple IAT hook which can be done in just a few lines of code with a bit of typecasting, in our callback we would move the system ID value into the EAX register. That same system ID value we move into the EAX register would be used in the KiFastSystemCall hook.
So we can check which function was being called by that, and then we can progress to more advanced things... Such as comparing the parameters and then returning the correct values. (ZwLoadDriver system call is 01Fh, memorized them haha).
If we wanted to monitor the use of ZwTerminateProcess via a KiFastSystemCall hook to protect a specific process (well Antivirus software can do this, but so can a rootkit (malware type)), we would start off by hooking KiFastSystemCall, and then we would check the system ID. The system ID for ZwTerminateProcess is e.g. 7002Bh. Then if the system ID is the same as that from the KiFastSystemCall hook, we can then start to check the parameters (comparison). If it's trying to terminate notepad, then the HANDLE parameter (_In_ HANDLE ProcessHandle) would be for notepad.exe. If so, we can return STATUS_ACCESS_DENIED.
From User-Mode, by default, you'll get an error if you try to: return STATUS_ACCESS_DENIED. The reason for this is because we are not in kernel-mode where this is defined by default. The NTSTATUS return code for this is 0xC0000022 (STATUS_ACCESS_DENIED), so we would: return 0xC0000022. This will generate the Access Denied error. Please bear in mind that you cannot do this for every function, only if the return value for this is supported and should be returned. For example, a WINAPI function (not Native API) like SetWinEventHook, you would return the integer 0 to represent that it's failed. If you wanted to block this function from successfully completing, in your hook, you would just: return 0. This means that since you had your callback function executed instead of the what was meant to happen (e.g. IAT hook, you place a JMP instruction to your memory address so your callback function code will be executed instead), if you just return 0, it will tell the process which called it that it's failed to complete the call.
Code:
if (!SetWinEventHook(...))
{
}
If we returned 0, then the code inside of those curly braces will be executed. Because we are checking if the function failed, and then execute the code beneath it wrapped in the curly braces. Since we return 0, it tells the process that the operation failed to successfully complete.
Back onto the KiFastSystemCall topic, I will explain a bit how it works - when you call a NTAPI/WinAPI function from a user-mode process (well pretty much everything evolves around a function call (when you terminate a process, create a file, delete a file, create a registry key, connect to the internet, open a process, write to a file), and then of course as we know, the CPU executes instructions in the memory address it's Instruction Pointer is at), the function ends up executing in kernel-mode (completing). Basically what happens in short is you call the function, and then in the end, it's received by KiFastSystemCall (which is why hooking this on x86 processes gives us the power to filter out which API functions a user-mode process can complete or what should be denied). Once KiFastSystemCall receives this API call request, it will attempt to move this onto kernel-mode via the use of a function called SYSENTER. Therefore, if we set IAT hooks in user-mode or filter out certain API requests from KiFastSystemCall, we can prevent the function executing from kernel-mode in the first place.
To create a hook (hook a NTAPI function for example via IAT hooking techniques), you'll need to get the target address in which you want to hook (the address of the function in the Native API, let's say ZwOpenProcess). Then you'll need to unprotect the memory, 10 bytes is probably okay, via the WINAPI function VirtualProtect (takes in a few parameters for the target address, bytes to unprotect, the new protection setting and where to store the old protection setting). After you've unprotected the memory, you can start writing to the memory to even overwrite the instruction if you'd like. You can write your instruction, feel free! So for a trampoline hook (IAT), you overwrite a few bytes of the function. Anyway, stepping away from that, you can use simple hooking methods to start with, such as using typecasting to convert between data types to write the JMP instruction to the target address to your own memory address (which contains the the callback function).
As an example:
Code:
void KlipshTheMaster(LPVOID targetAddr)
{
DWORD oldProt;
VirtualProtect(targetAddr, 10, PAGE_EXECUTE_READWRITE, &oldProt);
*(BYTE*)(targetAddr) = 0xE9;
// now we do the rest to place the address to our callback, we placed JMP instruction above already
VirtualProtect(targetAddr, 10, oldProt, &oldProt);
}
The above code, the first line in the function will create a variable (of DWORD) to store the protection settings. In the Windows API library (<windows.h>), the VirtualProtect protection settings are already defined for us. PAGE_EXECUTE_READWRITE represents 0x40 (DWORD). We can use 0x40 instead of PAGE_EXECUTE_READWRITE if we'd like.
The line:
Code:
*(BYTE*)(targetAddr) = 0xE9;
uses typecasting to BYTE to write the 0xE9 JMP instruction (byte). The reason we have the asteriks before the (BYTE) and after BYTE before the end paranthesis for (BYTE) is because it represents we are working with memory. So without the asteriks, we wouldn't be writing to memory. But since we have the asteriks there, it means that it will write to memory.
The last line in that function
Code:
VirtualProtect(targetAddr, 10, oldProt, &oldProt);
will set the protection settings back to how they originally were... It was stored in the DWORD variable oldProt (&oldProt means it will store the old protection settings to that DWORD variable), therefore we use that instead of PAGE_EXECUTE_READWRITE this time to re-protect the memory (since the old settings was protected memory).
0x40 (PAGE_EXECUTE_READWRITE) allows us to read and write to the memory.
However, there is one line missing from that code. That's the line to write to the memory to place the address to JMP too (we place it + 1 from the original address to make sure it's after the JMP instruction has been placed).
As a better alternate explanation to hooking (for me at least): hooking (IAT especially, in the sense where you write to the memory of the process) is essentially changing the memory to how you want it. What I mean is, we change instructions/write to the memory addresses to place in our instructions, therefore we are 'changing the memory'.
For trampoline hooking, we have a bit more than a few lines. If we are using DLL injection, we need to unprotect the memory as usual via VirtualProtect(...). Then, we need to allocate memory in the address space of the target process we injected into. We can allocate memory in the process we have already injected into via VirtualAlloc. If we want to allocate memory in another process, we can use VirtualAllocEx as MSDN mentions, however since we are injecting our DLL, our code is now executing in the address space of that process already, so we use VirtualAlloc. After this, we can copy memory to destination via a function called memcpy(...). After this, we can start doing things such as placing the JMP instruction to the addresses we want to JMP too (we'll need to work out the offset for this), when were done, reprotect the memory up.
I've mentioned hooking for x86 processes in these examples. However, for x64, the JMP instruction is 0x48. On x86, it is 0xE9. For hooking x86 processes via DLL, we use x86 hooking. For a x64 process, x64 hooking.
DLL injection methods (not all work on newer OS versions of Windows):
- CreateRemoteThread (no longer supported, Windows 7 had issues with it, didn't work for some people)
- SetWindowsHookEx (this can be used for DLL injection, not just setting hooks on the keyboard)
- ZwCreateThread (I like this one, I always prefer the Native API)
Those are 3 methods.
The popular Antivirus vendors who have experienced staff usually resort to kernel-mode drivers. For example, they have ObRegisterCallback for process protection, they can hook the SSDT on x86 (I know vendors do use this nowadays probably, I know vendors like AVG used too on x86 systems, not sure about now though - I think Kaspersky also did it, cannot remember what the other vendor was, I think it was Kaspersky. I remember checking the SSDT and seeing AVG doing it for sure, they used to hook functions in the SSDT like ZwTerminateProcess for sure). SSDT stands for System Service Descriptor Table, it holds the addresses to the kernel-mode functions (Zw* functions). In kernel-mode we use Zw* functions. In user-mode, we use Nt* functions. In user-mode, Zw* and Nt* are the same.
Avast uses ObRegisterCallback as an example, to protect their process. On x64, if you go to terminate the process, it should generate Access Denied error. They used to protect their process from ring3 I believe. Some AVs still do.
Hooking is incredibly powerful (and kernel-mode callbacks) for features like HIPS/Behaviour Blocker, dynamic analysis... Any zero-day prevention system. It's essential, and it's very easy to get started and learn to become a master eventually. All you need to start is the very basics of C++ and Assembly. Then you get better as you learn about Windows API functions, Native API functions, learn more hooking techniques, etc. You can make real-time protection either via a driver, or by hooking functions like ZwCreateFile, ZwWriteFile. You can even make a user-mode firewall via user-mode hooking. Although, a driver is better due to the control for a firewall to sniff the network, etc.
Bear in mind, there's also code injection... Accomplished via ZwWriteVirtualMemory as an example. (write to the process memory, although that function is for the Native API). (Nt* functions). I said a Zw* function, but on user-mode level, Nt* and Zw* are the same, so no issue.
Keyloggers (.NET for example) especially use functions like SetWindowsHookEx to set a hook and then invoke to a function to use a function like GetAsyncKeyState. They use the pair for monitoring the keystrokes on the system, SetWindowsHookEx can also be used to steal the cursor information (when you move the cursor, it can track). However, more advanced keyloggers in lower level languages like C, will install keyboard drivers to perform theft of the keystrokes. And if there is a user-mode process for it or the driver, rootkit activity can come into place to help hide the process/driver or the files on disk from the user/antivirus software.
Very detailed post I think this is, been writing for some time... Started writing earlier when I saw your post, maybe it helped educate you and many others. In the future, I'll probably discuss x64 hooking instead of x86 like in this post and kernel-mode hooking.
@Klipsh I'll give your post a like - attempted to help, included informaton regarding keylogger activity (SetWindowsHookEx) and tried to make the explanation more basic.
Cheers.