Skip to content

convenient use of syscalls with a single line and a comfort wrapper, unfriendly for reverse engineer

License

Notifications You must be signed in to change notification settings

annihilatorq/shadow_syscall

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 

Repository files navigation

shadow syscalls

Easy to use syscall wrapper based on shellcode. Each call is hashed using intrins and is not reverse-engineer friendly.

Target platform (as of today) - MSVC (no others have been tested), x64 Release & Debug, CPP 14 - 23

Quick example

shadowsyscall(NTSTATUS, NtTerminateProcess).call((HANDLE)0xDEADC0DE, -1);

Shellcode uses VirtualAlloc and VirtualFree function wrappers from kernelbase.dll, memory allocation itself is based on NtAllocateVirtualMemory | NtFreeVirtualMemory, which are executed in runtime by addresses of these routines.

SHADOWSYSCALL_FORCEINLINE void* nt_virtual_alloc(pointer_t nt_allocate_virtual_memory, void* lpAddress, unsigned long long dwSize, unsigned long flAllocationType, unsigned long flProtect)
{
using call_type = ntstatus(__stdcall*)(void*, void*, unsigned long long, unsigned long long*, unsigned long, unsigned long);
void* base_address = lpAddress;
unsigned long long region_size = dwSize;
ntstatus result = reinterpret_cast<call_type>(nt_allocate_virtual_memory)((void*)-1, &base_address, 0, &region_size, flAllocationType & 0xFFFFFFC0, flProtect);
if (NT_SUCCESS(result)) {
return base_address;
}
return nullptr;
}

Detailed example

int main(void)
{
    NTSTATUS result = 0;

    // Execute "NtTerminateProcess" syscall
    shadowsyscall(NTSTATUS, NtTerminateProcess).call((HANDLE)0xDEADC0DE, -1);

    // NtTerminateProcess cached call
    for (int i = 0; i < 5; ++i)
        result = shadowsyscall(NTSTATUS, NtTerminateProcess).cached_call((HANDLE)0xDEADC0DE, -1);

    // Check for return value
    std::cout << "Last NtTerminateProcess return value: 0x" << std::hex << result << '\n';

    // As expected, console output is - 0xc0000008, which refers to STATUS_INVALID_HANDLE
    // More about NTSTATUS error handling below:
    // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55

    return EXIT_SUCCESS;
}

🚀 Features

  • Caching each call.
  • Ability to disable exceptions within the code.
  • Doesn't leave any strings in executable memory.
  • Compile-time hashing export.
  • Hash seed is pseudo-randomized, based on compilation time.
  • Runtime hash based on intrins.
  • Doesn't leave any imports in the executable.
  • Ability to switch between SSE and AVX intrins.
  • Header includes only <intrin.h> so that the compilation time is minimized.

📜 What's a syscall?

syscall_architecture

📄 Documentation

  • SHADOWSYSCALL_COMPILETIME_HASH(str) -> compile-time hash
  • SHADOWSYSCALL_RUNTIME_HASH(str) -> runtime hash
  • shadowsyscall(type, syscall_name) -> class wrapper allows you to call some methods, also includes .call() and .cached_call() method

🛠️ Configuration

#define EFFECT
SHADOWSYSCALL_NO_FORCEINLINE disables force inlining
SHADOWSYSCALL_DISABLE_EXCEPTIONS disables all exceptions and returns 0 if the function fails.
SHADOWSYSCALL_DISABLE_INTRIN_HASH disables runtime intrin-based hashing, and leaves normal arithmetic operations in place
SHADOWSYSCALL_NO_CACHING completely disables caching of all syscalls.
SHADOWSYSCALL_USE_AVX_INTRINS use only AVX in the hashing algorithm instead of SSE intrins
SHADOWSYSCALL_CASE_INSENSITIVE disables case sensitivity in the hashing algorithm

Thanks to

invers1on ❤️