间接系统调用(Indirect syscalls)+ DInvoke,简单易用。
nimvoke 的设计目的是直接导入到您自己的 Nim 项目中,它使用宏来抽象化间接系统调用和 DInvoke 风格委托声明的细节。这个库力求开箱即用且相对符合操作安全(op-sec)要求。宏中使用的函数名和库名在编译时进行哈希处理,并且无论是否存在钩子,都会检索 SSN 和 syscall 指令地址。所有系统调用都经过 ntdll.dll 中正确的 syscall 指令。
通过 nimble 安装(nimble install https://github.com/nbaertsch/nimvoke),然后只需导入相关库并调用相应的宏。更多详情请参阅 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
)
所有 Nim 二进制文件都会导入一组核心 Win32 函数。所有其他 Win32 函数都通过 dynlib 解析,dynlib 使用 GetProcAddress 和 LoadLibraryA 在运行时解析函数。这个 DInvoke 实现可以防止暴露未被其他代码使用的 新 函数(无论是 Nim 运行时/GC 还是通过 dynlib 导入的 stdlib 代码),但无法移除 Nim 的核心导入,也无法改变标准库或第三方库的行为。
导入时,nimvoke/syscalls 会解析 ntdll.dll 的 EAT(导出地址表),以查找所有系统调用并提取所需数据。所有系统调用的信息都存储在内存中。
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`
SSN 的获取方式是将所有系统调用按地址排序并计数。这种方法无论是否存在钩子都应该有效。内存中不会存储函数名字符串,但会保存 ntdll.dll 的 EAT 中函数名字符串的指针,用于计算哈希。
系统调用使用一个单一的跳板(来自 FreshyCalls),将 SSN 移动到 eax,准备参数,然后跳转到 ntdll.dll 中正确的 syscall 指令。这个跳板分配在一个新的私有堆上。
如果你想在这个系统调用初始化代码之前运行代码(例如用于沙箱规避或环境键控),你需要在代码中的 import nimvoke/syscalls 语句之前调用它。
有建议吗?请打开 issue!也欢迎提交 PR。