Giter VIP home page Giter VIP logo

syswhispers3's Introduction

SysWhispers3

SysWhispers helps with evasion by generating header/ASM files implants can use to make direct system calls.

Official Discord Channel

Come hang out on Discord!

Inceptor Server

Why on earth didn't I create a PR to SysWhispers2?

The reason for SysWhispers3 to be a standalone version are many, but the most important are:

  • SysWhispers3 is the de-facto "fork" used by Inceptor, and implements some utils class which are not relevant to the original version of the tool.
  • SysWhispers2 is moving towards supporting NASM compilation (for gcc/mingw), while this version is specifically designed and tested to support MSVC (because Inceptor will stay a Windows-only framework for the near future).
  • SysWhispers3 contains partially implemented features (such as egg-hunting) which would not be sensible to include in the original version of the tool.

Differences with SysWhispers2

The usage is pretty similar to SysWhispers2, with the following exceptions:

  • It also supports x86/WoW64
  • It supports syscalls instruction replacement with an EGG (to be dynamically replaced)
  • It supports direct jumps to syscalls in x86/x64 mode (in WOW64 it's almost standard)
  • It supports direct jumps to random syscalls (borrowing @ElephantSeal's idea)

A better explanation of these features are better outlined i the blog post SysWhispers is dead, long live SysWhispers!

Introduction

Security products, such as AVs and EDRs, usually place hooks in user-mode API functions to analyse a program execution flow, in order to detect potentially malicious activities.

SysWhispers2 is a tool designed to generate header/ASM pairs for any system call in the core kernel image (ntoskrnl.exe), which can then be integrated and called directly from C/C++ code, evading user-lands hooks.

The tool, however, generates some patters which can be included in signatures, or behaviour which can be detected at runtime.

SysWhispers3 is built on top of SysWhispers2, and integrates some helpful features to bypass these forms of detection.

Installation

C:\> git clone https://github.com/klezVirus/SysWhispers3.git
C:\> cd SysWhispers3
C:\> python .\syswhispers.py --help

Usage and Examples

The help shows all the available commands and features of the tool:

C:\>python syswhispers.py -h

usage: syswhispers.py [-h] [-p PRESET] [-a {x86,x64}] [-m {embedded,egg_hunter,jumper,jumper_randomized}] [-f FUNCTIONS] -o OUT_FILE [--int2eh] [--wow64] [-v] [-d]

SysWhispers3 - SysWhispers on steroids

optional arguments:
  -h, --help            show this help message and exit
  -p PRESET, --preset PRESET
                        Preset ("all", "common")
  -a {x86,x64}, --arch {x86,x64}
                        Architecture
  -c {msvc,mingw,all}, --compiler {msvc,mingw,all}
                        Compiler
  -m {embedded,egg_hunter,jumper,jumper_randomized}, --method {embedded,egg_hunter,jumper,jumper_randomized}
                        Syscall recovery method
  -f FUNCTIONS, --functions FUNCTIONS
                        Comma-separated functions
  -o OUT_FILE, --out-file OUT_FILE
                        Output basename (w/o extension)
  --int2eh              Use the old `int 2eh` instruction in place of `syscall`
  --wow64               Use Wow64 to run x86 on x64 (only usable with x86 architecture)
  -v, --verbose         Enable debug output
  -d, --debug           Enable syscall debug (insert software breakpoint)

Command Lines

Standard SysWhispers, embedded system calls (x64)

# 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

SysWhispers3-only samples

# Normal SysWhispers, 32-bits mode
py .\syswhispers.py --preset all -o syscalls_all -m jumper --arch x86

# Normal SysWhispers, using WOW64 in 32-bits mode (only specific functions)
py .\syswhispers.py --functions NtProtectVirtualMemory,NtWriteVirtualMemory -o syscalls_mem --arch x86 --wow64

# Egg-Hunting SysWhispers, to bypass the "mark of the sycall" (common function)
py .\syswhispers.py --preset common -o syscalls_common -m egg_hunter

# Jumping/Jumping Randomized SysWhispers, to bypass dynamic RIP validation (all functions) using MinGW as the compiler
py .\syswhispers.py --preset all -o syscalls_all -m jumper -c mingw

Script Output

PS C:\Projects\SysWhispers2> py .\syswhispers.py --preset common --out-file temp\syscalls_common -v 
                                                       
                  .                         ,--.
,-. . . ,-. . , , |-. o ,-. ,-. ,-. ,-. ,-.  __/
`-. | | `-. |/|/  | | | `-. | | |-' |   `-. .  \
`-' `-| `-' ' '   ' ' ' `-' |-' `-' '   `-'  '''
     /|                     |  @Jackson_T
    `-'                     '  @modexpblog, 2021

                      Edits by @klezVirus,  2022
SysWhispers3: Why call the kernel when you can whisper?


Common functions selected.

Complete! Files written to:
        temp\syscalls_common.h
        temp\syscalls_common.c
        temp\syscalls_common_.asm
Press a key to continue...

Importing into Visual Studio

  1. Copy the generated H/C/ASM files into the project folder.
  2. In Visual Studio, go to Project โ†’ Build Customizations... and enable MASM.
  3. In the Solution Explorer, add the .h and .c/.asm files to the project as header and source files, respectively.
  4. Go to the properties of the ASM file, and set the Item Type to Microsoft Macro Assembler.

Compiling outside of Visual Studio

Windows

Makefile for 64 bits:

Makefile.msvc

OPTIONS = -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS-
LIBS = libvcruntime.lib libcmt.lib ucrt.lib kernel32.lib

program:
  ML64 /c syscalls-asm.x64.asm /link /NODEFAULTLIB /RELEASE /MACHINE:X64
  cl.exe $(OPTIONS) syscalls.c  program.c
  link.exe /OUT:program.x64.exe -nologo $(LIBS) /MACHINE:X64 -subsystem:console -nodefaultlib syscalls-asm.x64.obj syscalls.obj program.obj

Makefile for 32 bits:

Makefile.msvc

OPTIONS = -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS-
LIBS = libvcruntime.lib libcmt.lib ucrt.lib kernel32.lib

program:
  ML /c syscalls-asm.x86.asm /link /NODEFAULTLIB /RELEASE /MACHINE:X86
  cl.exe $(OPTIONS) syscalls.c  program.c
  link.exe /OUT:program.x86.exe -nologo $(LIBS) /MACHINE:X86 -subsystem:console -nodefaultlib syscalls-asm.x86.obj syscalls.obj program.obj

Compile with nmake:

nmake -f Makefile.msvc

Linux

Makefile for both 64 and 32 bits:

Makefile.mingw

CC_x64 := x86_64-w64-mingw32-gcc
CC_x86 := i686-w64-mingw32-gcc
OPTIONS := -masm=intel -Wall

program:
  $(CC_x64) syscalls.c program.c -o program.x64.exe $(OPTIONS)
  $(CC_x86) syscalls.c program.c -o program.x86.exe $(OPTIONS)

Compile with make:

make -f Makefile.mingw

Caveats and Limitations

  • The Egg-Hunter functionality is not implemented within this tool, it is in Inceptor.
  • System calls from the graphical subsystem (win32k.sys) are not supported.
  • Tested on Visual Studio 2019/2022 with Windows 10 SDK.
  • Support for NASM is not guaranteed.
  • Support for GCC and MinGW is not guaranteed.

Troubleshooting

From SysWhispers2

  • 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.

New

  • With --verbose, it is possible to enable troubleshooting output during code generation.
  • With --debug, the tool will insert a software breakpoint in the syscall stub, to ease the debugging in WinDbg.
  • If you get a error A2084:constant value too large during compilation, regenerates the stubs.

Credits

SysWhispers2

Developed by @Jackson_T and @modexpblog, but builds upon the work of many others:

SysWhispers2 (x86/WOW64)

  • @rooster for creating a sample x86/WOW64 compatible fork.

Others

Licence

As the original, this project is also licensed under the Apache License 2.0.

syswhispers3's People

Contributors

klezvirus avatar s4ntiagop avatar scriptidiot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

syswhispers3's Issues

question

supports WINDOWS 11 PRO 22H2??

Potentially unexpected behaviour by using r15 for sysenter

Hi,

Problem

I'm using the jumper_randomized method for generating the syscalls. The generated stubs are in the following form:

NtAllocateVirtualMemory PROC
	mov [rsp +8], rcx          ; Save registers.
	mov [rsp+16], rdx
	mov [rsp+24], r8
	mov [rsp+32], r9
	sub rsp, 28h
	mov ecx, 0C1D1ED76h        ; Load function hash into ECX.
	call SW3_GetRandomSyscallAddress        ; Get a syscall offset from a different api.
	mov r15, rax                           ; Save the address of the syscall
	mov ecx, 0C1D1ED76h        ; Re-Load function hash into ECX (optional).
	call SW3_GetSyscallNumber              ; Resolve function hash into syscall number.
	add rsp, 28h
	mov rcx, [rsp+8]                      ; Restore registers.
	mov rdx, [rsp+16]
	mov r8, [rsp+24]
	mov r9, [rsp+32]
	mov r10, rcx
	jmp r15                                ; Jump to -> Invoke system call.
NtAllocateVirtualMemory ENDP

Now I had some code in the following form:

//Doing some other stuff beforehand

NtProtectVirtualMemory(hCurrentProc, &pEventWrite, (PSIZE_T)&size,oldprotect, &oldprotect);
void* address = NULL;
NtAllocateVirtualMemory(GetCurrentProcess(), &address, 0, &newSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

Which workes fine when compiled in Debug mode, but crashed when compiled as a Release build. In Realease mode, address was not set to 0, but to a value pointing to some instruction in ntdll.dll. After viewing the code in disassembly, the instruction to zero out the value of address was optimized by the compiler to this code: mov qword ptr [address],r15, which of course loads the value from our previous syscall into the variable, which in turn leads to problems with the next NtAllocateVirtualMemory call.

The problem only occurs when some additional code is executed before doing the syscalls. But I don't really want to post the full code here in the issue :D . When the additional code is removed, to program works without problems. I hope that the workarounds are enough for people who are running into the same problem.

Workaround

There are two possible workarounds I could find while debugging:

  • add/remove code to your application until the compiler optimizes in some other way
  • change the optimization in the project settings (Project -> Settings -> C++ -> Optimization)

@klezVirus Do you think there is any other way to circumvent this? I guess I ran in a super specific edge case here because of the code optimization. Registers r12-r15 however should be saved and restored before a ret.
R12..R15 are call-preserved registers you can use for whatever you want, as long as your save/restore them before returning. (https://stackoverflow.com/questions/53290932/what-are-r10-r15-registers-used-for-in-the-windows-x64-calling-convention)

However, I don't really see a good solution in how this could be done in the stubs.

Cheers

Flaw in concept

I starred your repo because your idea is nice, however because you are using ReadProcessMemory which is a user mode function (in fact any API called...), you already lost.

Besides this you should use a SEED like PRNG or something.

Good idea again, but you should rework it a bit.

WOW64 does not work on Windows 11 x64 22H2

All calls to Nt-api via wow64 on Windows 11 x64 22H2 return status 0xC000000B, on win 10 x64 everything works correctly.

NtOpenProcess PROC
push ebp
mov ebp, esp
push 0D59FFE03h ; Load function hash into ECX.
call SW3_GetRandomSyscallAddress ; Get a syscall offset from a different api.
mov edi, eax ; Save the address of the syscall
push 0D59FFE03h ; Re-Load function hash into ECX (optional).
call SW3_GetSyscallNumber
lea esp, [esp+4]
mov ecx, 04h
push_argument:
dec ecx
push [ebp + 8 + ecx * 4]
jnz push_argument
mov ecx, eax
mov eax, ecx
push ret_address_epilog
call do_sysenter_interrupt
lea esp, [esp+4]
ret_address_epilog:
mov esp, ebp
pop ebp
ret
do_sysenter_interrupt:
mov edx, esp
jmp edi
ret
NtOpenProcess ENDP

x86 cross compile under Linux with mingw assembler errors

Hi @klezVirus,

Not quite sure what I am doing wrong here but I cannot seem to assemble under Linux mingw. I am doing x86 with mingw target from syswhispers. Seems like the assembler cannot workout some symbol defs. See below.

$ python3 syswhispers.py -a x86 -c mingw -f NtCreateThreadEx,NtAllocateVirtualMemory,NtProtectVirtualMemory,NtWriteVirtualMemory,NtOpenProcess,NtClose,NtFreeVirtualMemory -m jumper_randomized -o syscalls_common

$ i686-w64-mingw32-gcc -masm=intel -c syscalls_common.c
/tmp/ccfeQDkK.s: Assembler messages:
/tmp/ccfeQDkK.s:770: Error: symbol push_argument' is already defined /tmp/ccfeQDkK.s:780: Error: symbol ret_address_epilog' is already defined
/tmp/ccfeQDkK.s:784: Error: symbol do_sysenter_interrupt' is already defined /tmp/ccfeQDkK.s:811: Error: symbol push_argument' is already defined
/tmp/ccfeQDkK.s:821: Error: symbol ret_address_epilog' is already defined /tmp/ccfeQDkK.s:825: Error: symbol do_sysenter_interrupt' is already defined
/tmp/ccfeQDkK.s:852: Error: symbol push_argument' is already defined /tmp/ccfeQDkK.s:862: Error: symbol ret_address_epilog' is already defined
/tmp/ccfeQDkK.s:866: Error: symbol do_sysenter_interrupt' is already defined /tmp/ccfeQDkK.s:893: Error: symbol push_argument' is already defined
/tmp/ccfeQDkK.s:903: Error: symbol ret_address_epilog' is already defined /tmp/ccfeQDkK.s:907: Error: symbol do_sysenter_interrupt' is already defined
/tmp/ccfeQDkK.s:934: Error: symbol push_argument' is already defined /tmp/ccfeQDkK.s:944: Error: symbol ret_address_epilog' is already defined
/tmp/ccfeQDkK.s:948: Error: symbol do_sysenter_interrupt' is already defined /tmp/ccfeQDkK.s:975: Error: symbol push_argument' is already defined
/tmp/ccfeQDkK.s:985: Error: symbol ret_address_epilog' is already defined /tmp/ccfeQDkK.s:989: Error: symbol do_sysenter_interrupt' is already defined

Improvement suggestions

Hi,

Please consider the following improvements:

  1. If one does not use anything else but these definitions, NTSTATUS will be missing, you can just add:
#ifndef SW3_HEADER_H_
#define SW3_HEADER_H_

#include <windows.h>

#ifndef _NTDEF_
typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
typedef NTSTATUS* PNTSTATUS;
#endif
  1. If one needs to use another definition set like phnt, the definitions will clash at compile time. My suggestion is to rename the structures/definitions with a prefix like:
typedef struct _SW3_SYSTEM_HANDLE
{
	ULONG ProcessId;
	BYTE ObjectTypeNumber;
	BYTE Flags;
	USHORT Handle;
	PVOID Object;
	ACCESS_MASK GrantedAccess;
} SW3_SYSTEM_HANDLE, *PSW3SYSTEM_HANDLE;

Same for functions (there is no reason we cannot name them as we wish):

EXTERN_C NTSTATUS Sw3NtCreateProcess(
	OUT PHANDLE ProcessHandle,
	IN ACCESS_MASK DesiredAccess,
	IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
	IN HANDLE ParentProcess,
	IN BOOLEAN InheritObjectTable,
	IN HANDLE SectionHandle OPTIONAL,
	IN HANDLE DebugPort OPTIONAL,
	IN HANDLE ExceptionPort OPTIONAL);

This would prevent the "already defined" compiling issue. Perhaps add a flag like --custom-prefix which would prefix the functions/structured/definitions as the user specifies.

  1. Instead of the current initial see, you might want to consider using xoshiro prng.

Thanks!

Jumper and Jumper_randomized are not sending calls from NTDLL address space

My understanding is that Jumper and Jumper_randomized would jump into NTDLL space, and use the syscall instructions by jumping into those assembly instructions, however when I trace the program with Frida, I can see that none of calls are jumped or at least Frida script is catching them being called outside of the NTDLL space.

I also enabled the debug and prints for Found Syscalls and I see them getting kicked In. I tried debugging with the debugger and I saw jumps to NTDLL but I am not sure why the calls are still made from outside of the NTDLL address space.

Using the following functions and command to generate the syswhisper files in generic code injector
python3 syswhispers.py -a x64 -c msvc -m jumper_randomized -f NtAllocateVirtualMemory,NtWriteVirtualMemory,NtOpenProcess,NtCreateThreadEx,NtProtectVirtualMemory

I used the following Frida script to trace the syscalls origination:

var modules = Process.enumerateModules()
//send("[+] list of Modules " + modules.toString())
var ntdll = modules[1]
//send("[+] list of Modules " + ntdll.toString())


var ntdllBase = ntdll.base
send("[*] Ntdll base: " + ntdllBase)
var ntdllOffset = ntdllBase.add(ntdll.size)
send("[*] Ntdll end: " + ntdllOffset)

const mainThread = Process.enumerateThreads()[0];
Process.enumerateThreads().map(t => {
Stalker.follow(t.id, {
  events: {
    call: false, // CALL instructions: yes please
    // Other events:
    ret: false, // RET instructions
    exec: false, // all instructions: not recommended as it's
                 //                   a lot of data
    block: false, // block executed: coarse execution trace
    compile: false // block compiled: useful for coverage
  },
  onReceive(events) {    
  },
  transform(iterator){
      let instruction = iterator.next()
      do{
        
        //I think this reduces overhead
        if(instruction.mnemonic == "mov"){
            //Should provide a good filter for syscalls, might need further filtering
            if(instruction.toString() == "mov r10, rcx"){
                iterator.keep() //keep the instruction
                instruction = iterator.next() //next instruction should have the syscall number
								//This helps to clear up some false positives
                if(instruction.toString().split(',')[0] == "mov eax"){
                    var addrInt = instruction.address.toInt32()
                    //If the syscall is coming from somewhere outside the bounds of NTDLL
                    //then it may be malicious
                    if(addrInt < ntdllBase.toInt32() || addrInt > ntdllOffset.toInt32()){
                        send("[+] Found a potentially malicious syscall: " + instruction.toString())
                    }
                }
            }
        }
        
      iterator.keep()
      } while ((instruction = iterator.next()) !== null)
  }
  
 
})
})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.