{
	"id": "cf0df4d9-f8f3-4c3c-8661-d880d9769905",
	"created_at": "2026-04-06T00:10:52.371968Z",
	"updated_at": "2026-04-10T03:24:39.772338Z",
	"deleted_at": null,
	"sha1_hash": "1e6d3a11597d85088f4588cca063af51b4956c4f",
	"title": "Red Team Tactics: Combining Direct System Calls and sRDI to bypass AV/EDR | Outflank",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 114042,
	"plain_text": "Red Team Tactics: Combining Direct System Calls and sRDI to\r\nbypass AV/EDR | Outflank\r\nBy Cornelis\r\nPublished: 2019-06-19 · Archived: 2026-04-05 17:57:58 UTC\r\nIn this blog post we will explore the use of direct system calls, restore hooked API calls and ultimately combine\r\nthis with a shellcode injection technique called sRDI. We will combine these techniques in proof of concept code\r\nwhich can be used to create a LSASS memory dump using Cobalt Strike, while not touching disk and evading\r\nAV/EDR monitored user-mode API calls.\r\nAs companies grow in their cybersecurity maturity level, attackers also evolve in their attacking capabilities. As a\r\nred team we try to offer the best value to our customers, so we also need to adapt to more advanced tactics and\r\ntechniques attackers are using to bypass modern defenses and detection mechanisms. Recent malware research\r\nshows that there is an increase in malware that is using direct system calls to evade user-mode API hooks used by\r\nsecurity products. So time for us to sharpen our offensive tool development skills.\r\nSource code of the PoC can be found here:\r\nhttps://github.com/outflanknl/Dumpert\r\nWhat are direct system calls?\r\nIn order to understand what system calls really are, we first have to dig a little bit into Operating System\r\narchitecture, specifically Windows.\r\nIf you are old (not like me… 😉 ) and have a MS-DOS background, you probably remember that a simple\r\napplication crash could result in a complete system crash. This was due to the fact that the operating system was\r\nrunning in real mode, which means that the processor is running in a mode in which no memory isolation and\r\nprotection is applied. A bad program or bug could result in a complete crash of the Operating System due to\r\ncritical system memory corruption, as there was no restriction in what memory regions could be accessed or not.\r\nThis all changed with newer processors and Operating Systems supporting the so-called protected mode. This\r\nmode introduced many safeguards and could protect the system from crashes by isolating running programs from\r\neach other using virtual memory and privilege levels or rings. On a Windows system two of these rings are\r\nactually used. Application are running in user-mode, which is the equivalent of ring 3 and critical system\r\ncomponents like the kernel and device drivers are running in kernel-mode which corresponds to ring 0. \r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 1 of 12\n\nUsing these protection rings makes sure that applications are isolated and cannot directly access critical memory\r\nsections and system resources running in kernel-mode. When an application needs to execute a privileged system\r\noperation, the processor first needs to switch into ring 0 to handover the execution flow into kernel-mode. This is\r\nwhere system calls come into place.\r\nLet’s demonstrate this privilege mode switching while monitoring a notepad.exe process and saving a simple text\r\nfile:\r\nWriteFile call stack in Process Monitor .\r\nThe screenshot shows the program flow (call stack) from the notepad.exe process when we save a file. We can see\r\nthe Win32 API WriteFile call following the Native API NtWriteFile call (more on APIs later).\r\nFor a program to save a file on disk, the Operating System needs access to the filesystem and device drivers.\r\nThese are privileged operations and not something the application itself should be allowed to do. Accessing device\r\ndrivers directly from an application could result in very bad things. So, the last API call before entering kernel-mode is responsible for pulling the dip switch into kernel land.\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 2 of 12\n\nThe CPU instruction for entering kernel-mode is the syscall instruction (at least on x64 architecture, which we will\r\ndiscuss in this blog only). We can see this in the following WinDBG screenshot, which shows the unassembled\r\nNtWriteFile instruction:\r\nDisassembled NtWriteFile API call in WinDBG.\r\nThe NtWriteFile API from ntdll.dll is responsible for setting up the relevant function call arguments on the stack,\r\nthen moving the system call number from the NtWriteFile call in the EAX register and executing the syscall\r\ninstruction. After that, the CPU will jump into kernel-mode (ring 0). The kernel uses the dispatch table (SSDT) to\r\nfind the right API call belonging to the system call number, copies the arguments from the user-mode stack into\r\nthe kernel-mode stack and executes the kernel version of the API call (in this case ZwWriteFile). When the kernel\r\nroutines are finished, the program flow will return back into user-mode almost the same way, but will return the\r\nreturn values from the kernel API calls (for example a pointer to received data, or a handle to a file).\r\nThis (user-mode) is also the place where many security products like AV, EDR and sandbox software put their\r\nhooks, so they can detour the execution flow into their engines to monitor and intercept API calls and block\r\nanything suspicious. As you have seen in the disassembled view of the NtWriteFile instruction you may have\r\nnoticed that it only uses a few assembly instructions, from which the syscall number and the syscall instruction\r\nitself are the most important. The only important thing before executing a direct system call is that the stack is\r\nsetup correctly with the expected arguments and using the right calling convention.\r\nSo, having this knowledge… why not execute the system calls directly and bypass the Windows and Native API,\r\nso that we also bypass any user-mode hooks that might be in place? Well this is exactly what we are going to do,\r\nbut first a little bit more about the Windows programming interfaces.\r\nThe Windows programming interfaces\r\nIn the following screenshot we see a high-level overview of the Windows OS Architecture:\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 3 of 12\n\nFor a user-mode application to interface with the underlying operating system, it uses an application programming\r\ninterface (API). If you are a Windows developer writing C/C++ application, you would normally use the Win32\r\nAPI. This is Microsoft’s documented programming interfaces which consists of several DLLs (so called Win32\r\nsubsystem DLLs).\r\nUnderneath the Win32 API sits the Native API (ntdll.dll), which is actually the real interface between the user-mode applications and the underlying operating system. This is the most important programming interface but\r\n“not officially” documented and should be avoided by programmers in most circumstances.\r\nThe reason why Microsoft has put another layer on top of the Native API is that the real magic occurs within this\r\nNative API layer as it is the lowest layer between user-mode and the kernel. Microsoft probably decided to shield\r\noff the documented APIs using an extra layer, so they could make architectural OS changes without affecting the\r\nWin32 programming interface.\r\nSo now we know a bit more about system calls and the Windows programming APIs, let’s see how we can\r\nactually skip the programming APIs and invoke the APIs directly using their system call number or restore\r\npotentially hooked API calls.\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 4 of 12\n\nUsing system calls directly\r\nWe already showed how to disassemble native API calls to identify the corresponding system call numbers. Using\r\na debugger this could take a lot of time. So, the same can be done using IDA (or Ghidra) by opening a copy of\r\nntdll.dll and lookup the needed function:\r\nDisassembled NtWriteFile API call in IDA.\r\nOne slight problem… system call numbers change between OS versions and sometimes even between service\r\npack/built numbers.\r\nFortunately @j00ru from Google project Zero comes to the rescue with his online system call tables.\r\nj00ru did an amazing job keeping up with all system call numbers in use by different Windows versions and\r\nbetween builds. So now we have a great resource to look up all the system calls we want to use.\r\nIn our code, we want to invoke the system calls directly using assembly. Within Visual Studio we can enable\r\nassembly code support using the masm build dependency, which allows us to add .asm files and code within our\r\nproject.\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 5 of 12\n\nAssembly system call functions in .asm file.\r\nAll we need to do is gather OS version information from the system we are using and create references between\r\nthe native API function definitions and OS version specific system call functions in assembly language. For this\r\nwe can use the Native API RtlGetVersion routine and save this information into a version info structure.\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 6 of 12\n\nReference function pointers based on OS info.\r\nExported OS specific assembly functions + native API function definitions.\r\nNow we can use the system call functions in our code as if they are normal native API functions:\r\nUsing ZwOpenProcess systemcall function as a Native API call.\r\nRestoring hooked API calls with direct system calls\r\nWriting advanced malware that only uses direct system calls and completely evades user-mode API calls is\r\npractically impossible or at least extremely cumbersome. Sometimes you just want to use an API call in your\r\nmalicious code. But what if somewhere in the call stack there is a user-mode hook by AV/EDR? Let’s have a look\r\nhow we can remove the hook using direct system calls.\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 7 of 12\n\nBasic user-mode API hooks by AV/EDR are often created by modifying the first 5 bytes of the API call with a\r\njump (JMP) instruction to another memory address pointing to the security software. The technique of unhooking\r\nthis method has already been documented within two great blog posts by @SpecialHoang, and by MDsec’s Adam\r\nChester and Dominic Chell.\r\nIf you study these methods carefully, you will notice the use of API calls such as VirtualProtectEx and\r\nWriteProcessMemory to unhook Native API functions. But what if the first API calls are hooked and monitored\r\nalready somewhere in the call stack? Inception, get it? Direct system calls to the rescue!\r\nIn the PoC code we created we basically use the same unhooking technique by restoring the first 5 bytes with the\r\noriginal assembly instructions, including the system call number. The only difference is that the API calls we use\r\nto unhook the APIs are direct systems call functions (ZwProtectVirtualMemory and ZwWriteVirtualMemory).\r\nUsing direct system call function to unhook APIs.\r\nProof of concept\r\nIn our operations we sometimes need Mimikatz to get access to credentials, hashes and Kerberos tickets on a\r\ntarget system. Endpoint detection software and threat hunting instrumentation are pretty good in detection and\r\nprevention of Mimikatz nowadays. So, if you are in an assessment and your scenario requires to stay under the\r\nradar as much as possible, using Mimikatz on an endpoint is not best practice (even in-memory). Also, dumping\r\nLSASS memory with tools such as procdump is often caught by modern AV/EDR using API hooks.\r\nSo, we need an alternative to get access to LSASS memory and one option is to create a memory dump of the\r\nLSASS process after unhooking relevant API functions. This technique was also documented in @SpecialHoang\r\nblog. \r\nAs a proof of concept, we created a LSASS memory dump tool called “Dumpert”. This tool combines direct\r\nsystem calls and API unhooking and allows you to create a LSASS minidump. This might help bypassing defenses\r\nof modern AV and EDR products.\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 8 of 12\n\nThe minidump file can be used in Mimikatz to extract credential information without running Mimikatz on the\r\ntarget system.\r\nMimikatz minidump import.\r\nOf course, dropping executable files on a target is probably something you want to avoid during an engagement,\r\nso let’s take this a step further…\r\nsRDI – Shellcode Reflective DLL Injection\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 9 of 12\n\nIf we do not want to touch disk, we need some sort of injection technique. We can create a reflective loadable\r\nDLL from our code, but reflective DLL injection leaves memory artefacts behind that can be detected. My\r\ncolleague @StanHacked recently pointed me to an interesting DLL injection technique called shellcode Reflective\r\nDLL Injection.\r\nsRDI allows for the conversion of DLL files to position independent shellcode. This technique is developed by\r\nNick Landers (@monoxgas) from Silent Break Security and is basically a new version of RDI. \r\nSome advantages of using sRDI instead of standard RDI:\r\nYou can convert any DLL to position independent shellcode and use standard shellcode injection\r\ntechniques.\r\nYour DLL does not need to be reflection-aware as the reflective loader is implemented in shellcode outside\r\nof your DLL.\r\nUses proper Permissions, no massive RWX blob.\r\nOptional PE Header Cleaning.\r\nMore detailed information about sRDI can be found in this blog.\r\nLet our powers combine!\r\nOkay with all the elements in place, let’s see if we can combine these elements and techniques and create\r\nsomething more powerful that could be useful during Red Team operations:\r\nWe created a DLL version of the “dumpert” tool using the same direct system calls and unhooking\r\ntechniques. This DLL can be run standalone using the following command line: “rundll32.exe\r\nC:\\Dumpert\\Outflank-Dumpert.dll,Dump”, but in this case we are going to convert it to a sRDI shellcode.\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 10 of 12\n\nCompile the DLL version using Visual Studio and turn it into a position independent shellcode. This can be\r\ndone using the ConvertToShellcode.py script from the sRDI project: “python3 ConvertToShellcode.py\r\nOutflank-Dumpert.dll”\r\nTo inject the shellcode into a remote target, we can use Cobalt Strike’s shinject command. Cobalt Strike\r\nhas a powerful scripting language called aggressor script which allows you to automate this step.  To make\r\nthis easier we provided an aggressor script which enables a “dumpert” command in the beacon menu to do\r\nthe dirty job.\r\nThe dumpert script uses shinject to inject the sRDI shellcode version of the dumpert DLL into the current\r\nprocess (to avoid CreateRemoteThread API). Then it waits a few seconds for the lsass minidump to finish\r\nand finally download the minidump file from the victim host.\r\nNow you can use Mimikatz on another host to get access to the lsass memory dump including credentials,\r\nhashes e.g. from our target host. For this you can use the following command: “sekurlsa::minidump\r\nC:\\Dumpert\\dumpert.dmp”\r\nConclusion\r\nMalware that evades security product hooks is increasing and we need to be able to embed such techniques in our\r\nprojects.\r\nIn this blog we used references between the native API function definitions and OS version specific system call\r\nfunctions in assembly. This allows us to use direct system calls function as if they were normal Native API\r\nfunctions. We combined this technique together with an API unhooking technique to create a minidump from the\r\nLSASS process and used sRDI in combination with Cobalt Strike to inject the dumpert shellcode into memory of\r\na target system.\r\nDetecting malicious use of system calls is difficult. Because user-mode programming interfaces are bypassed, the\r\nonly place to look for malicious activity is the kernel itself. But with kernel PatchGuard protection it is infeasible\r\nfor security products to create hooks or modification in the running kernel.\r\nI hope this blogpost is useful in understanding the advanced techniques attackers are using nowadays and provides\r\na useful demonstration on how to emulate these techniques during Red Team operations.If you have any feedback\r\nor additional ideas, let me know.\r\nLastly, in order to help other red teams easily implement these techniques and more, we’ve developed Outflank\r\nSecurity Tooling (OST), a broad set of evasive tools that allow users to safely and easily perform complex tasks. If\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 11 of 12\n\nyou’re interested in seeing the diverse offerings in OST, we recommend scheduling an expert led demo.\r\nSource: https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nhttps://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/\r\nPage 12 of 12",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/"
	],
	"report_names": [
		"red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr"
	],
	"threat_actors": [
		{
			"id": "610a7295-3139-4f34-8cec-b3da40add480",
			"created_at": "2023-01-06T13:46:38.608142Z",
			"updated_at": "2026-04-10T02:00:03.03764Z",
			"deleted_at": null,
			"main_name": "Cobalt",
			"aliases": [
				"Cobalt Group",
				"Cobalt Gang",
				"GOLD KINGSWOOD",
				"COBALT SPIDER",
				"G0080",
				"Mule Libra"
			],
			"source_name": "MISPGALAXY:Cobalt",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "77b28afd-8187-4917-a453-1d5a279cb5e4",
			"created_at": "2022-10-25T15:50:23.768278Z",
			"updated_at": "2026-04-10T02:00:05.266635Z",
			"deleted_at": null,
			"main_name": "Inception",
			"aliases": [
				"Inception Framework",
				"Cloud Atlas"
			],
			"source_name": "MITRE:Inception",
			"tools": [
				"PowerShower",
				"VBShower",
				"LaZagne"
			],
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775434252,
	"ts_updated_at": 1775791479,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/1e6d3a11597d85088f4588cca063af51b4956c4f.pdf",
		"text": "https://archive.orkl.eu/1e6d3a11597d85088f4588cca063af51b4956c4f.txt",
		"img": "https://archive.orkl.eu/1e6d3a11597d85088f4588cca063af51b4956c4f.jpg"
	}
}