Collection of Thread Hijacking malware using direct/syscalls, ntdll and low level utilities, undetected by Windows Defender and BitDefender.
This code is for educational purposes only, do not use it for any malicious or unauthorized activity.
More advanced version of this malware collection, it uses system calls resolved dynamically from ntdll.dll functions to hijack one of the target process threads and inject it with shellcode. This bypasses usermode AV hooks, EDRs, and reduces static IAT footprint. Using direct syscalls i made a process injection version and a Dll version, then i did the same thing but with indirect syscalls.
On Windows, many native API functions inside ntdll.dll eventually execute a syscall that transitions execution from user mode to kernel mode. This code extracts the system service number (SSN) of some native api functions and puts it in their new unhooked structs. The ssn changes from a windows version to another, so it has to be resolved at runtime. Here is the struct the code looks for:
mov r10, rcx
mov eax, 0x00001BE ; SSN
syscall ; found and replaced with "qword ptr [Syscall addr]"This is not ntdll unhooking because we do not directly modify the function but simply bypass the hook placed at the beginning of the function (jmp ...). In the indirect syscall version the code also locates the "syscall" instruction inside that function and jumps to that after setting the SSN.
So basically this code:
- Creates custom typedef structs for each function (+ internal structures and objects that they need);
- Manually resolves their RVA in ntdll with
GetProcAddressManual(ex); - Replaces the instructions with the assembly stubs containing the SSN and syscall address;
- Uses the new nt functions to hijack an existing thread (or to create and hijack a new thread) inside a target process;
To get the RVA of each nt procedure (located in the export directory) i used GetProcAddressManualEx(), a custom function made by chatgp- ehm i mean me, that parses the PE headers and walks the export directory instead of calling the Windows api. So the process looks like this:
- Read the DOS header and use
e_lfanewto get to nt headers; - At
base + e_lfanewfind the NT headers (signature + IMAGE_FILE_HEADER + IMAGE_OPTIONAL_HEADER), the OptionalHeader contains the DataDirectory array. - OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] gives the RVA/size of the Export Directory, so
base + rvagoes to IMAGE_EXPORT_DIRECTORY in memory. - Export Directory contains 3 lists:
- AddressOfNames: array of RVAs to ASCII names, for example
nameRvas[i] = "LoadLibraryA"; - AddressOfNameOrdinals: each index corresponds to a function name and tells you the ordinal for that name,
ordinals[i] = index into AddressOfFunctions; - AddressOfFunctions: array of RVAs of the real exported functions,
funcRvas[ordinal] = RVA of the actual function.
- AddressOfNames: array of RVAs to ASCII names, for example
So to resolve a name we find the name in the AddressOfNames array, get its ordinals[i] value (index), read funcRvas[ordinals[i]], and convert the function RVA to an absolute address by adding the module base: funct_add = base + funcRvas[ordinals[i]].
If funcRva points back inside the export directory region then the entry is a forwarded export, in this case and in other special cases like failing to validates the DOS header magic (MZ) and the PE signature, or if the RVA is too big, the function simply returns null.
This technique is a simple remote process injection with encrypted shellcode, you can find this pattern in most of my malware samples and the shellcode is most of the times just for a msgbox that says "xd". On the right are the raw direct proc-inj detections, and below the indirect ones:
- The shellcode is decrypted, for a better view of the encryption mechanism check it out here;
- The code then uses
NtQuerySystemInformation()to list all existing processes, find the one that matches the name, save its pid and take a handle to it; - After this the code does the same thing but iterating through all the threads of our system process to find one whose owner is the process we targeted earlier;

- Once its saves its Tid and gets a handle to it, the program will allocate memory for the payload, upload the shellcode in it, and change its permissions to RWX.
- Finally it will suspend the thread, change its instruction pointer to make it point to the payload, and resume it.
Because the thread in question might hold important operations, there is a high chance that after executing our new payload the process might crash, because it cannot function without the thread we hijacked.
The alternative version is a bit different, it does not iterate through every thread but simply creates an already suspended one (whose base address points to a dummy function) so that when hijacking it it's already suspended.
The DLL version is the same exact process but it's a DLL that operates in the same process it was injected into, stealthier than the first version. On the right are the detections for the direct and indirect dlls.
The difference is simply that once the dll is attached to a process, it will start a new thread that will function as the "main" function of the previous samples. In order to target the current process we use a harmless GetCurrentProcessId(), the rest is the same as the Process Injection version. 
The new thread outside the loader lock is essential to avoid deadlock. You also might want to disable precompiled headers to remove the pch files.
Undetected by Windows Defender and Bitdefender, although sometimes the Bitdefender Advanced threat defense system blocks some random operations like suspending the process or opening it... it does not warn the user, logs the activity or whatsoever, it just blocks the execution.... idk
Here you can find the detections for the direct/indirect process injections obfuscated with some metadata or wrapped in msi files. 
If an EDR replaced the syscall instruction itself with a trampoline, the jump may land in the hook/trampoline and will not bypass detection. So this method does not bypass any kernel-level monitoring, ETW, callbacks, filtering, or advanced trampolines.
This one on the left shows the detections for the obfuscated dll versions, which drop to 0 by simply injecting ntdll metadata, keep in mind that the injector must be undetected too.




