闲聊几句
看了很多C、C++、Golang、Rust的代码,粗浅地觉得一个Loader加载ShellCode的方式已经算是有一定的约定了。其中,往往都会涉及到Windows下的API调用,更通俗地讲:咱就是调用API的。
Windows 公开API的调用
一般来说,微软会公布一些众所周知的API,这些API直接去参考微软的文档去调用即可
例如很多经典的Loader写法中用到的:Virtualalloc() 函数
LPVOID VirtualAlloc(
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
转换为Go需要进行变量的转换,下面是一些变量类型的转换对应关系
如果你偷懒一点,那一切皆可为 uintptr
如果你问我为什么,点击下面的链接
https://cs.opensource.google/go/go/+/refs/tags/go1.22.5:src/syscall/dll_windows.go
当然更上层一点有Go封装好的调用系统API的库
https://pkg.go.dev/golang.org/x/sys/windows
再来看下Go官方实现的Virtualalloc() 函数
func VirtualAlloc(address uintptr, size uintptr, alloctype uint32, protect uint32) (value uintptr, err error) {
r0, _, e1 := syscall.Syscall6(procVirtualAlloc.Addr(), 4, uintptr(address), uintptr(size), uintptr(alloctype), uintptr(protect), 0, 0)
value = uintptr(r0)
if value == 0 {
err = errnoErr(e1)
}
return
}
当然这不可能是手敲的代码,为了偷懒当然是由对应的工具去生成这样的代码啦
使用下面这条命令 + 准备一个 syscall_windows.go 和 一个 zsyscall_windows.go,就可以愉快生成标准写法的Windows 原生 Go API
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go
如果你和我一样想偷懒,那就直接去借鉴一下Go官方的代码吧
https://cs.opensource.google/go/x/sys/+/refs/tags/v0.22.0:windows/syscall_windows.go
在 syscall_windows.go 文件中定义好要生成函数的形参,加上一点特别的注释,运行即可生成API函数
//sys VirtualAlloc(address uintptr, size uintptr, alloctype uint32, protect uint32) (value uintptr, err error) = kernel32.VirtualAlloc
解释一下:
- //sys:为工具识别的特征,一定一定要写!!!
- VirtualAlloc(address uintptr, size uintptr, alloctype uint32, protect uint32) (value uintptr, err error) 是函数名 + 形参 + 返回值
- = kernel32.VirtualAlloc:=号必不可少,是为了确认调用哪个DLL中的函数
Windows 未公开 API
未公开的API和结构体可以去各种大佬的博客看,也可以看这个网站
https://undocumented-ntinternals.github.io/
无论是什么API,最后都需要经过SyscallN()函数
那么仿照Go官方的写法,加上一点小心思
func VirtualAlloc(address uintptr, size uintptr, alloctype uint32, protect uint32) (value uintptr, err error) {
r0, _, e1 := syscall.SyscallN(
procVirtualAlloc.Addr(),
uintptr(address),
uintptr(size),
uintptr(alloctype),
uintptr(protect))
value = uintptr(r0)
if value == 0 {
err = errnoErr(e1)
}
return
}
有啥区别吗?自行对比哈
来尝试一下写一个未公开的API,以Go来实现
func NtDelayExecution(DelayInterval int64) (err error) {
delay := -(DelayInterval * 1000 * 10000)
r1, _, e1 := syscall.SyscallN(
procNtDelayExecution.Addr(),
uintptr(0),
uintptr(unsafe.Pointer(&delay)),
)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
如果使用官方的工具生成
//sys NtDelayExecution(DelayInterval int64) (err error) = ntdll.NtDelayExecution
搞定收工
总结
是不是很简单,没有万字吧
请登录后查看评论内容