
Indirekte Syscalls + DInvoke einfach gemacht.
Indirekte Syscalls + DInvoke einfach gemacht.
Konzipiert für den direkten Import in deine eigenen Nim-Projekte, verwendet nimvoke Makros, um die Details indirekter Systemaufrufe und Delegatdeklarationen im DInvoke-Stil zu abstrahieren. Diese Bibliothek soll einfach zu bedienen und out-of-the-box relativ op-sec-freundlich sein. Funktions- und Bibliotheksnamen, die in den Makros verwendet werden, werden zur Kompilierzeit gehasht, und SSNs sowie Adressen der syscall-Anweisung werden unabhängig von etwaigen Hooks abgerufen. Alle Syscalls laufen über die korrekte syscall-Anweisung in ntdll.dll.
Mit nimble installieren (nimble install https://github.com/nbaertsch/nimvoke), dann einfach die entsprechende Bibliothek importieren und das zugehörige Makro aufrufen. Weitere Details finden sich in examples.
DInvoke:
import winim/lean
import nimvoke/dinvoke
dinvokeDefine(
ZwAllocateVirtualMemory,
"ntdll.dll",
proc (ProcessHandle: Handle, BaseAddress: PVOID, ZeroBits: ULONG_PTR, RegionSize: PSIZE_T, AllocationType: ULONG, Protect: ULONG): NTSTATUS {.stdcall.}
)
var
hProcess: HANDLE = 0xFFFFFFFFFFFFFFFF
shellcodeSize: SIZE_T = 1000
baseAddr: PVOID
status: NTSTATUS
status = ZwAllocateVirtualMemory(
hProcess,
&baseAddr,
0,
&shellcodeSize,
MEM_RESERVE or MEM_COMMIT,
PAGE_READWRITE)
Syscalls:
import winim/lean
import nimvoke/syscalls
var
hProcess: HANDLE = 0xFFFFFFFFFFFFFFFF
shellcodeSize: SIZE_T = 1000
baseAddr: PVOID
status: NTSTATUS
status = syscall(NtAllocateVirtualMemory,
hProcess,
&baseAddr,
0,
&shellcodeSize,
MEM_RESERVE or MEM_COMMIT,
PAGE_READWRITE
)
Alle Nim-Binaries importieren einen Satz von Kern-Win32-Funktionen. Alle anderen Win32-Funktionen werden über dynlib aufgelöst, das GetProcAddress und LoadLibraryA verwendet, um Funktionen zur Laufzeit aufzulösen. Diese DInvoke-Implementierung kann verhindern, dass neue Funktionen offengelegt werden, die nicht von anderem Code verwendet werden (sei es die Nim-Laufzeit/GC oder stdlib-Code, der über dynlib importiert), kann jedoch die Kernimporte von Nim nicht entfernen oder das Verhalten der Standard- oder Drittanbieter-Bibliotheken verändern.
Beim Import parst nimvoke/syscalls den EAT von ntdll.dll, um alle Syscalls zu finden und die benötigten Daten zu extrahieren. Informationen zu allen Syscalls werden im Speicher abgelegt.
type
Syscall* = object
pName*: PCHAR
ord*: WORD
pFunc*: PVOID
pSyscall*: PVOID = NULL
ssn*: WORD
hooked*: bool
...
syscallSeq: seq[Syscall] # stores all syscall data
syscallTable* = initTable[string, ptr Syscall]() # maps syscall `Zw` hashed-name to `Syscall` object's in the `syscallSeq`
Die SSNs werden ermittelt, indem alle Syscalls nach ihrer Adresse sortiert und gezählt werden. Diese Methode sollte unabhängig von etwaigem Hooking funktionieren. Es werden keine Funktionsnamen-Strings im Speicher abgelegt, aber ein Zeiger auf die Funktionsnamen-Strings im EAT von ntdll.dll wird für die Hash-Berechnung gehalten.
Syscalls verwenden ein einzelnes Trampolin (von FreshyCalls), um die SSN nach eax zu verschieben, die Argumente vorzubereiten und zur korrekten syscall-Anweisung in ntdll.dll zu springen.
Dieses Trampolin wird auf einem neuen privaten Heap allokiert.
Wenn du Code vor diesem Syscall-Initialisierungscode ausführen möchtest (z. B. für Sandbox-Evasion oder Environmental Keying), musst du ihn vor der import nimvoke/syscalls-Anweisung in deinem Code aufrufen.
Hast du Vorschläge? Eröffne ein Issue! PRs sind ebenfalls willkommen.