Skip to content

Instantly share code, notes, and snippets.

@chvancooten
Last active December 9, 2021 10:03
Show Gist options
  • Save chvancooten/083dbdfd4a10261ee8dfecb4caf07e6c to your computer and use it in GitHub Desktop.
Save chvancooten/083dbdfd4a10261ee8dfecb4caf07e6c to your computer and use it in GitHub Desktop.
Failed attempt to get Syswhispers2 to work in Nim
#pragma once
// Code below is adapted from @modexpblog. Read linked article for more details.
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
#ifndef SW2_HEADER_H_
#define SW2_HEADER_H_
#include <windows.h>
#include <winternl.h>
#define SW2_SEED 0x42A16EF3
#define SW2_ROL8(v) (v << 8 | v >> 24)
#define SW2_ROR8(v) (v >> 8 | v << 24)
#define SW2_ROX8(v) ((SW2_SEED % 2) ? SW2_ROL8(v) : SW2_ROR8(v))
#define SW2_MAX_ENTRIES 500
#define SW2_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
// Typedefs are prefixed to avoid pollution.
typedef struct _SW2_SYSCALL_ENTRY
{
DWORD Hash;
DWORD Address;
} SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY;
typedef struct _SW2_SYSCALL_LIST
{
DWORD Count;
SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES];
} SW2_SYSCALL_LIST, *PSW2_SYSCALL_LIST;
typedef struct _SW2_PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} SW2_PEB_LDR_DATA, *PSW2_PEB_LDR_DATA;
typedef struct _SW2_LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase;
} SW2_LDR_DATA_TABLE_ENTRY, *PSW2_LDR_DATA_TABLE_ENTRY;
typedef struct _SW2_PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PSW2_PEB_LDR_DATA Ldr;
} SW2_PEB, *PSW2_PEB;
DWORD SW2_HashSyscall(PCSTR FunctionName);
BOOL SW2_PopulateSyscallList();
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash);
// typedef struct _UNICODE_STRING
// {
// USHORT Length;
// USHORT MaximumLength;
// PWSTR Buffer;
// } UNICODE_STRING, *PUNICODE_STRING;
#ifndef InitializeObjectAttributes
#define InitializeObjectAttributes( p, n, a, r, s ) { \
(p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
#endif
typedef struct _PS_ATTRIBUTE
{
ULONG Attribute;
SIZE_T Size;
union
{
ULONG Value;
PVOID ValuePtr;
} u1;
PSIZE_T ReturnLength;
} PS_ATTRIBUTE, *PPS_ATTRIBUTE;
// typedef struct _OBJECT_ATTRIBUTES
// {
// ULONG Length;
// HANDLE RootDirectory;
// PUNICODE_STRING ObjectName;
// ULONG Attributes;
// PVOID SecurityDescriptor;
// PVOID SecurityQualityOfService;
// } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
// typedef struct _CLIENT_ID
// {
// HANDLE UniqueProcess;
// HANDLE UniqueThread;
// } CLIENT_ID, *PCLIENT_ID;
typedef struct _PS_ATTRIBUTE_LIST
{
SIZE_T TotalLength;
PS_ATTRIBUTE Attributes[1];
} PS_ATTRIBUTE_LIST, *PPS_ATTRIBUTE_LIST;
EXTERN_C NTSTATUS NtAllocateVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID * BaseAddress,
IN ULONG ZeroBits,
IN OUT PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect);
EXTERN_C NTSTATUS NtWriteVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN PVOID Buffer,
IN SIZE_T NumberOfBytesToWrite,
OUT PSIZE_T NumberOfBytesWritten OPTIONAL);
EXTERN_C NTSTATUS NtOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL);
EXTERN_C NTSTATUS NtCreateThreadEx(
OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ProcessHandle,
IN PVOID StartRoutine,
IN PVOID Argument OPTIONAL,
IN ULONG CreateFlags,
IN SIZE_T ZeroBits,
IN SIZE_T StackSize,
IN SIZE_T MaximumStackSize,
IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL);
EXTERN_C NTSTATUS NtClose(
IN HANDLE Handle);
EXTERN_C NTSTATUS NtProtectVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID * BaseAddress,
IN OUT PSIZE_T RegionSize,
IN ULONG NewProtect,
OUT PULONG OldProtect);
#endif
{.passC:"-masm=intel".}
# INCLUDE SYSCALLS.C FROM SYSWHISPERS2 TO DYNAMICALLY RESOLVE SYSCALL IDS
{.emit: """
#include "../templates/syscalls.h"
SW2_SYSCALL_LIST SW2_SyscallList;
DWORD SW2_HashSyscall(PCSTR FunctionName)
{
DWORD i = 0;
DWORD Hash = SW2_SEED;
while (FunctionName[i])
{
WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++);
Hash ^= PartialName + SW2_ROR8(Hash);
}
return Hash;
}
BOOL SW2_PopulateSyscallList()
{
// Return early if the list is already populated.
if (SW2_SyscallList.Count) return TRUE;
PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60);
PSW2_PEB_LDR_DATA Ldr = Peb->Ldr;
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
PVOID DllBase = NULL;
// Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
// in the list, so its safer to loop through the full list and find it.
PSW2_LDR_DATA_TABLE_ENTRY LdrEntry;
for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
{
DllBase = LdrEntry->DllBase;
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
PIMAGE_NT_HEADERS NtHeaders = SW2_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (VirtualAddress == 0) continue;
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
// If this is NTDLL.dll, exit loop.
PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
if ((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue;
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break;
}
if (!ExportDirectory) return FALSE;
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
// Populate SW2_SyscallList with unsorted Zw* entries.
DWORD i = 0;
PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries;
do
{
PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
// Is this a system call?
if (*(USHORT*)FunctionName == 'wZ')
{
Entries[i].Hash = SW2_HashSyscall(FunctionName);
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
i++;
if (i == SW2_MAX_ENTRIES) break;
}
} while (--NumberOfNames);
// Save total number of system calls found.
SW2_SyscallList.Count = i;
// Sort the list by address in ascending order.
for (DWORD i = 0; i < SW2_SyscallList.Count - 1; i++)
{
for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++)
{
if (Entries[j].Address > Entries[j + 1].Address)
{
// Swap entries.
SW2_SYSCALL_ENTRY TempEntry;
TempEntry.Hash = Entries[j].Hash;
TempEntry.Address = Entries[j].Address;
Entries[j].Hash = Entries[j + 1].Hash;
Entries[j].Address = Entries[j + 1].Address;
Entries[j + 1].Hash = TempEntry.Hash;
Entries[j + 1].Address = TempEntry.Address;
}
}
}
return TRUE;
}
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash)
{
// Ensure SW2_SyscallList is populated.
if (!SW2_PopulateSyscallList()) return -1;
for (DWORD i = 0; i < SW2_SyscallList.Count; i++)
{
if (FunctionHash == SW2_SyscallList.Entries[i].Hash)
{
return i;
}
}
return -1;
}
""".}
type
PS_ATTR_UNION* {.pure, union.} = object
Value*: ULONG
ValuePtr*: PVOID
PS_ATTRIBUTE* {.pure.} = object
Attribute*: ULONG
Size*: SIZE_T
u1*: PS_ATTR_UNION
ReturnLength*: PSIZE_T
PPS_ATTRIBUTE* = ptr PS_ATTRIBUTE
PS_ATTRIBUTE_LIST* {.pure.} = object
TotalLength*: SIZE_T
Attributes*: array[2, PS_ATTRIBUTE]
PPS_ATTRIBUTE_LIST* = ptr PS_ATTRIBUTE_LIST
proc SW2_GetSyscallNumber(FunctionHash: DWORD): DWORD {.importc: "SW2_GetSyscallNumber".}
proc NtCreateThreadEx*(ThreadHandle: PHANDLE, DesiredAccess: ACCESS_MASK, ObjectAttributes: POBJECT_ATTRIBUTES, ProcessHandle: HANDLE, StartRoutine: PVOID, Argument: PVOID, CreateFlags: ULONG, ZeroBits: SIZE_T, StackSize: SIZE_T, MaximumStackSize: SIZE_T, AttributeList: PPS_ATTRIBUTE_LIST): NTSTATUS {.asmNoStackFrame.} =
asm """
mov [rsp +8], rcx
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 40
mov ecx, 1688680562
call SW2_GetSyscallNumber
add rsp, 40
mov rcx, [rsp +8]
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
syscall
ret
"""
proc NtOpenProcess*(ProcessHandle: PHANDLE, DesiredAccess: ACCESS_MASK, ObjectAttributes: POBJECT_ATTRIBUTES, ClientId: PCLIENT_ID): NTSTATUS {.asmNoStackFrame.} =
asm """
mov [rsp +8], rcx
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 40
mov ecx, 2637503528
call SW2_GetSyscallNumber
add rsp, 40
mov rcx, [rsp +8]
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
syscall
ret
"""
proc NtClose*(Handle: HANDLE): NTSTATUS {.asmNoStackFrame.} =
asm """
mov [rsp +8], rcx
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 40
mov ecx, 3595428325
call SW2_GetSyscallNumber
add rsp, 40
mov rcx, [rsp +8]
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
syscall
ret
"""
proc NtWriteVirtualMemory*(ProcessHandle: HANDLE, BaseAddress: PVOID, Buffer: PVOID, NumberOfBytesToWrite: SIZE_T, NumberOfBytesWritten: PSIZE_T): NTSTATUS {.asmNoStackFrame.} =
asm """
mov [rsp +8], rcx
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 40
mov ecx, 589551407
call SW2_GetSyscallNumber
add rsp, 40
mov rcx, [rsp +8]
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
syscall
ret
"""
proc NtAllocateVirtualMemory*(ProcessHandle: HANDLE, BaseAddress: PVOID, ZeroBits: ULONG, RegionSize: PSIZE_T, AllocationType: ULONG, Protect: ULONG): NTSTATUS {.asmNoStackFrame.} =
asm """
mov [rsp +8], rcx
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 40
mov ecx, 2199165319
call SW2_GetSyscallNumber
add rsp, 40
mov rcx, [rsp +8]
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
syscall
ret
"""
proc NtProtectVirtualMemory*(ProcessHandle: HANDLE, BaseAddress: PVOID, RegionSize: PSIZE_T, NewProtect: ULONG, OldProtect: PULONG): NTSTATUS {.asmNoStackFrame.} =
asm """
mov [rsp +8], rcx
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 40
mov ecx, 598550847
call SW2_GetSyscallNumber
add rsp, 40
mov rcx, [rsp +8]
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
syscall
ret
"""
@yamakadi
Copy link

yamakadi commented Dec 9, 2021

  {.emit: """
  #pragma once

  #ifndef SW2_HEADER_H_
  #define SW2_HEADER_H_
  
  #include <windows.h>
  #include <stdio.h>
  
  #define SW2_SEED 0x37813952
  #define SW2_ROL8(v) (v << 8 | v >> 24)
  #define SW2_ROR8(v) (v >> 8 | v << 24)
  #define SW2_ROX8(v) ((SW2_SEED % 2) ? SW2_ROL8(v) : SW2_ROR8(v))
  #define SW2_MAX_ENTRIES 500
  #define SW2_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
  
  typedef struct _SW2_SYSCALL_ENTRY
  {
      DWORD Hash;
      DWORD Address;
  } SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY;
  
  typedef struct _SW2_SYSCALL_LIST
  {
      DWORD Count;
      SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES];
  } SW2_SYSCALL_LIST, *PSW2_SYSCALL_LIST;
  
  typedef struct _SW2_PEB_LDR_DATA {
    BYTE Reserved1[8];
    PVOID Reserved2[3];
    LIST_ENTRY InMemoryOrderModuleList;
  } SW2_PEB_LDR_DATA, *PSW2_PEB_LDR_DATA;
  
  typedef struct _SW2_LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;
  } SW2_LDR_DATA_TABLE_ENTRY, *PSW2_LDR_DATA_TABLE_ENTRY;
  
  typedef struct _SW2_PEB {
    BYTE Reserved1[2];
    BYTE BeingDebugged;
    BYTE Reserved2[1];
    PVOID Reserved3[2];
    PSW2_PEB_LDR_DATA Ldr;
  } SW2_PEB, *PSW2_PEB;
  
  DWORD SW2_HashSyscall(PCSTR FunctionName);
  BOOL SW2_PopulateSyscallList();
  EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash);
  
  #ifndef InitializeObjectAttributes
  #define InitializeObjectAttributes( p, n, a, r, s ) { \
    (p)->Length = sizeof( OBJECT_ATTRIBUTES );        \
    (p)->RootDirectory = r;                           \
    (p)->Attributes = a;                              \
    (p)->ObjectName = n;                              \
    (p)->SecurityDescriptor = s;                      \
    (p)->SecurityQualityOfService = NULL;             \
  }
  #endif
  #endif
  
  SW2_SYSCALL_LIST SW2_SyscallList = {0,1};
  
  DWORD SW2_HashSyscall(PCSTR FunctionName)
  {
      DWORD i = 0;
      DWORD Hash = SW2_SEED;
  
      while (FunctionName[i])
      {
          WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++);
          Hash ^= PartialName + SW2_ROR8(Hash);
      }
  
      return Hash;
  }
  
  BOOL SW2_PopulateSyscallList()
  {
      // Return early if the list is already populated.
      if (SW2_SyscallList.Count) return TRUE;
  
      PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60);
      PSW2_PEB_LDR_DATA Ldr = Peb->Ldr;
      PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
      PVOID DllBase = NULL;
  
      // Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
      // in the list, so it's safer to loop through the full list and find it.
      PSW2_LDR_DATA_TABLE_ENTRY LdrEntry;
      for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
      {
          DllBase = LdrEntry->DllBase;
          PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
          PIMAGE_NT_HEADERS NtHeaders = SW2_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
          PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
          DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
          if (VirtualAddress == 0) continue;
  
          ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
  
          // If this is NTDLL.dll, exit loop.
          PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
  
          if ((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue;
          if ((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break;
      }
  
      if (!ExportDirectory) return FALSE;
  
      DWORD NumberOfNames = ExportDirectory->NumberOfNames;
      PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
      PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
      PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
  
      // Populate SW2_SyscallList with unsorted Zw* entries.
      DWORD i = 0;
      PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries;
      do
      {
          PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
  
          // Is this a system call?
          if (*(USHORT*)FunctionName == 'wZ')
          {
              Entries[i].Hash = SW2_HashSyscall(FunctionName);
              Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
  
              i++;
              if (i == SW2_MAX_ENTRIES) break;
          }
      } while (--NumberOfNames);
  
      // Save total number of system calls found.
      SW2_SyscallList.Count = i;
  
      // Sort the list by address in ascending order.
      for (DWORD i = 0; i < SW2_SyscallList.Count - 1; i++)
      {
          for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++)
          {
              if (Entries[j].Address > Entries[j + 1].Address)
              {
                  // Swap entries.
                  SW2_SYSCALL_ENTRY TempEntry;
  
                  TempEntry.Hash = Entries[j].Hash;
                  TempEntry.Address = Entries[j].Address;
  
                  Entries[j].Hash = Entries[j + 1].Hash;
                  Entries[j].Address = Entries[j + 1].Address;
  
                  Entries[j + 1].Hash = TempEntry.Hash;
                  Entries[j + 1].Address = TempEntry.Address;
              }
          }
      }
  
      return TRUE;
  }
  
  EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash)
  {
      // Ensure SW2_SyscallList is populated.
      if (!SW2_PopulateSyscallList()) return -1;
  
      for (DWORD i = 0; i < SW2_SyscallList.Count; i++)
      {
          if (FunctionHash == SW2_SyscallList.Entries[i].Hash)
          {
              return i;
          }
      }
  
      return -1;
  }
  """.}

Adding this and rewriting the asm definitions NimlineWhispers creates worked for me 😃
Since nim compiles down to C, you can pull in most header only libraries like this.

PS: I found this by chance while stumbling my way through SysWhispers2 and nim 🤣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment