Understanding SysCalls Manipulation

I previously wrote an article on how to potentially detect SysCall hooking from C# so the operator can make an informed decision on whether to migrate processes or whether EDR will have a good chance at detecting their malicious activities. In order to write this post I have read through many great resources by other people in an aim to understand and write an injector based on SysCalls to better understand C and really grasp the SysCall functions within Windows x64 predominantly and how they can be called and detected by EDR products. The main blog which peeked my interest and left me spending hours on a weekend was by @passthehashbrwn – if you haven’t read this its a really great piece of work.

The problem – why do we care about trying to hide our SysCalls and better understand the mechanics behind it. Well its pretty simple, the defensive side is really upping their game and as offensive security engineers we need to stay ahead of the curve if we want to battle through next-gen EDR techniques. The current methods to hand to battle these problems have been two fold, either take your SysCalls with you in your dropper which was really well documented in the SysWhispers project below.

Or the next option is to unhook or patch NTDLL.dll inside your own process to remove the jmp instructions put in from the EDR vendors that has been a well documented technique and used by many attackers in the wild. As we now know, EDR vendors and other defensive tooling is becoming more savvy and can detect both of these techniques using different mechanisms behind the scenes, e.g. DLL tampering if we re-patch NTDLL or Kernel CallBacks if we’re jumping to SysCall land from anything other than NTDLL.

The solution – so what can we do to avoid these detections right now. This is where the the trampoline method comes in that I posted above. Instead of doing either one of the above options, we do a combination thereof and I’ve created a little POC based on their original code in the post above. The SysCall function is split into two main parts, the bit which has the SysCall ID for the type of SysCall, e.g. NtWriteVirtualMemory and the actual call to Kernel land which is always the same for each call (0f05 in hex). When EDR usually hooks a SysCall they put the jmp instruction right at the top then come back to NTDLL to perform the SysCall (0f05 in hex) as per the below images. In a nut shell, we steal the original code which hasnt been patched with a jmp from EDR, using NTDLL.dll on disk (this has its own OPSEC considerations of course) or take the SysCall IDs with us like in the SysWhispers project (I prefer to grab this from disk as its much safer as if you get the SysCalls wrong it could crash the process). Now we have the SysCall IDs we can do that code from anywhere in the process avoiding the jmp instruction, but before calling Kernel land we actually jmp back to NTDLL after the EDR jmp instuction so Kernel CallBacks still see the move to Kernel being done from NTDLL.dll in the process not a random bit of memory. The code I have modified and written is quite verbose so I could easily follow each step in a debugger while watching the memory flows and allocations throughout the whole process on both a system with and without EDR installed. For demonstration purposes I am using some simple MessageBox shellcode generated from Metasploit but you can use any shellcode you like and it will read it from disk if you uncomment the lines. If you want to read more, I would highly recommend the blogs above to give more information but this is my full understanding of the situation and how we can attempt to bypass this.

If successful, you should get a MessageBox

One thing I wanted to implement was to actually check if the SysCalls were being updated from the original code. To do this I begin reading the memory location of NtCreateThreadEx, NtWriteVirtualMemory and ZwAllocateVirtualMemory. If the bytes don’t start with a 0x4c which is usually the start of the functions (see below) it prints the bytes and lets the user know its probably being hooked.

HMODULE ntdllHandle = GetModuleHandleA("ntdll.dll");

ULONG_PTR NtCreateThreadEx = (ULONG_PTR)GetProcAddress(ntdllHandle, "NtCreateThreadEx");

char startBytes[1] = { 0x4c };

if (((PBYTE)NtCreateThreadEx)[0] != ((char*)startBytes)[0]) {
    printf("[-] NtCreateThreadEx is being hooked!\n > ");
    int i;
    for (i = 0; i < 10; i++)
        unsigned char c = ((char*)NtCreateThreadEx)[i];
        printf("%02x ", c);

As the bytes at that memory location are printed, the operator can then convert the code similarly to the other blog I posted using something like ODA.

ODA will then convert the code if its a jump instruction to something like this:

Another small modification which was made was instead of creating an array with the bytes on our heap, I create a new memory region that holds the functions for the separate code stubs. This is only really for debugging purposes but also allows for easier clean up later should you wish to do so. You can easily check the bytes for that function and that they are at the right memory location and contain the right SysCall instruction and jump location.

@m0rv4i kindly reviewed the code and tidied up some of the elements as well (as he always needs to on my code :D) so big shout out to him for his help as always.

One final thing I’m not sure on and haven’t done enough testing is @passthehashbrwn uses a clean SysCall instruction to jump to so that it evades detection but in my modified code I jump back to the same SysCall than the original as we have already bypassed the jump instruction set out by the EDR solution. Therefore in theory, it shouldn’t matter if you use the same SysCall but I have only done limited testing on this theory and wanted to make it clear as the code can be changed back to use a clean SysCall if preferable. My only other thought process was if the kernel land was performing and CallBacks or checks from where the SysCall originated but unconfirmed at this point.

There is not much else to add to this post other than trying to read and understand the code yourself if you want to better understand your knowledge of SysCalls. I would definitely recommend reading the blog post at the top of this page too as it goes into detail about how SysCalls are usually detected and how jumping back into NTDLL as a trampoline evades that detection which is also incorporated into the code snippet here. In the mean time i’m trying to also understand SysCalls on x86 bit systems and x86 bit processes on x86 bit systems which is both confusing and mind boggling but there is another SysWhispers project which helps below. I’ll hopefully include x86 into the POC above when i’ve got time.

Published by benpturner

Red Teamer (CCSAS|CCSAM) | Creator of PoshC2 | Powershell / C# Enthusiast | Passionate about Security!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: