一个用于 Golang 二进制文件的命令行 Windows API 跟踪工具。
注意: 此工具是一个概念验证和工作进行中的原型,请相应对待。欢迎反馈!
尽管 Golang 程序在构建方式和运行时行为方面包含许多细微差别,但它们仍然需要与操作系统层交互,这意味着在某些时候它们确实需要调用 Windows API 的函数。
Go 运行时包包含一个名为 asmstdcall 的函数,该函数是用于与 Windows API 交互的一种“网关”。由于这个函数预计会调用 Windows API 函数,我们可以假设它需要访问诸如函数地址及其参数等信息,这就是事情开始变得更有趣的地方。
Asmstdcall 接收一个参数,该参数是指向类似以下结构体的指针:
struct LIBCALL {
DWORD_PTR Addr;
DWORD Argc;
DWORD_PTR Argv;
DWORD_PTR ReturnValue;
[...]
}
其中一些字段是在调用 API 函数后填充的,例如返回值;其他字段则由 asmstdcall 接收,例如函数地址、参数数量和参数列表。无论如何,当这些字段被设置时,很明显 asmstdcall 函数处理了大量关于 Golang 编译程序执行的有趣信息。
gftrace 利用 asmstdcall 及其工作方式来监视上述结构体的特定字段并将其记录给用户。该工具能够记录 Golang 应用程序调用的每个 Windows 函数的函数名、参数以及返回值。所有这些都不需要挂钩单个 API 函数或为其提供签名。
该工具还尝试忽略 Go 运行时初始化期间的所有噪音,仅记录初始化之后调用的函数(即主包的函数)。
如果您想了解有关此项目和研究的更多信息,请查看博客文章。
下载最新的发布版本。
gftrace.exe、gftrace.dll 和 gftrace.cfg 位于同一目录中。gftrace.cfg 文件中指定要跟踪的 API 函数(不应用 API 过滤器,工具无法工作)。gftrace.exe,并将目标 Golang 程序路径作为参数传递。gftrace.exe <文件路径> <参数>
您只需在 gftrace.cfg 文件中指定要跟踪的函数,用逗号分隔且无空格:
CreateFileW,ReadFile,CreateProcessW
Golang 方法 X(属于包 Y)在特定场景下会调用的确切 Windows API 函数只能通过分析该方法本身或尝试猜测来确定。有一些有趣的特性可以用来确定,例如,Golang 应用程序似乎总是倾向于调用 "Wide" 和 "Ex" 集合中的函数(例如 CreateFileW、CreateProcessW、GetComputerNameExW 等),因此在分析时可以考虑这一点。
默认配置文件包含多个我测试过的函数(至少大部分),我可以肯定 Golang 应用程序在某些时候会调用它们。我会尽量定期更新它。
跟踪一个简单的 Golang 文件(调用 os.ReadFile 两次)中的 CreateFileW() 和 ReadFile():
- CreateFileW("C:\Users\user\Desktop\doc.txt", 0x80000000, 0x3, 0x0, 0x3, 0x1, 0x0) = 0x168 (360)
- ReadFile(0x168, 0xc000108000, 0x200, 0xc000075d64, 0x0) = 0x1 (1)
- CreateFileW("C:\Users\user\Desktop\doc2.txt", 0x80000000, 0x3, 0x0, 0x3, 0x1, 0x0) = 0x168 (360)
- ReadFile(0x168, 0xc000108200, 0x200, 0xc000075d64, 0x0) = 0x1 (1)
跟踪 TunnelFish 恶意软件中的 CreateProcessW():
- CreateProcessW("C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe", "powershell /c "Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn; Get-Recipient | Select Name -ExpandProperty EmailAddresses -first 1 | Select SmtpAddress | ft -hidetableheaders"", 0x0, 0x0, 0x1, 0x80400, "=C:=C:\Users\user\Desktop", 0x0, 0xc0000ace98, 0xc0000acd68) = 0x1 (1)
- CreateProcessW("C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe", "powershell /c "Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn; Get-Recipient | Select Name -ExpandProperty EmailAddresses -first 1 | Select SmtpAddress | ft -hidetableheaders"", 0x0, 0x0, 0x1, 0x80400, "=C:=C:\Users\user\Desktop", 0x0, 0xc0000c4ec8, 0xc0000c4d98) = 0x1 (1)
- CreateProcessW("C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe", "powershell /c "Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn; Get-Recipient | Select Name -ExpandProperty EmailAddresses -first 1 | Select SmtpAddress | ft -hidetableheaders"", 0x0, 0x0, 0x1, 0x80400, "=C:=C:\Users\user\Desktop", 0x0, 0xc00005eec8, 0xc00005ed98) = 0x1 (1)
- CreateProcessW("C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe", "powershell /c "Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn; Get-Recipient | Select Name -ExpandProperty EmailAddresses -first 1 | Select SmtpAddress | ft -hidetableheaders"", 0x0, 0x0, 0x1, 0x80400, "=C:=C:\Users\user\Desktop", 0x0, 0xc0000bce98, 0xc0000bcd68) = 0x1 (1)
- CreateProcessW("C:\WINDOWS\system32\cmd.exe", "cmd /c "wmic computersystem get domain"", 0x0, 0x0, 0x1, 0x80400, "=C:=C:\Users\user\Desktop", 0x0, 0xc0000c4ef0, 0xc0000c4dc0) = 0x1 (1)
- CreateProcessW("C:\WINDOWS\system32\cmd.exe", "cmd /c "wmic computersystem get domain"", 0x0, 0x0, 0x1, 0x80400, "=C:=C:\Users\user\Desktop", 0x0, 0xc0000acec0, 0xc0000acd90) = 0x1 (1)
- CreateProcessW("C:\WINDOWS\system32\cmd.exe", "cmd /c "wmic computersystem get domain"", 0x0, 0x0, 0x1, 0x80400, "=C:=C:\Users\user\Desktop", 0x0, 0xc0000bcec0, 0xc0000bcd90) = 0x1 (1)
[...]
跟踪 Sunshuttle 恶意软件中的多个函数:
- CreateFileW("config.dat.tmp", 0x80000000, 0x3, 0x0, 0x3, 0x1, 0x0) = 0xffffffffffffffff (-1)
- CreateFileW("config.dat.tmp", 0xc0000000, 0x3, 0x0, 0x2, 0x80, 0x0) = 0x198 (408)
- CreateFileW("config.dat.tmp", 0xc0000000, 0x3, 0x0, 0x3, 0x80, 0x0) = 0x1a4 (420)
- WriteFile(0x1a4, 0xc000112780, 0xeb, 0xc0000c79d4, 0x0) = 0x1 (1)
- GetAddrInfoW("reyweb.com", 0x0, 0xc000031f18, 0xc000031e88) = 0x0 (0)
- WSASocketW(0x2, 0x1, 0x0, 0x0, 0x0, 0x81) = 0x1f0 (496)
- WSASend(0x1f0, 0xc00004f038, 0x1, 0xc00004f020, 0x0, 0xc00004eff0, 0x0) = 0x0 (0)
- WSARecv(0x1f0, 0xc00004ef60, 0x1, 0xc00004ef48, 0xc00004efd0, 0xc00004ef18, 0x0) = 0xffffffff (-1)
- GetAddrInfoW("reyweb.com", 0x0, 0xc000031f18, 0xc000031e88) = 0x0 (0)
- WSASocketW(0x2, 0x1, 0x0, 0x0, 0x0, 0x81) = 0x200 (512)
- WSASend(0x200, 0xc00004f2b8, 0x1, 0xc00004f2a0, 0x0, 0xc00004f270, 0x0) = 0x0 (0)
- WSARecv(0x200, 0xc00004f1e0, 0x1, 0xc00004f1c8, 0xc00004f250, 0xc00004f198, 0x0) = 0xffffffff (-1)
[...]
跟踪 DeimosC2 框架代理中的多个函数:
- WSASocketW(0x2, 0x1, 0x0, 0x0, 0x0, 0x81) = 0x130 (304)
- setsockopt(0x130, 0xffff, 0x20, 0xc0000b7838, 0x4) = 0xffffffff (-1)
- socket(0x2, 0x1, 0x6) = 0x138 (312)
- WSAIoctl(0x138, 0xc8000006, 0xaf0870, 0x10, 0xb38730, 0x8, 0xc0000b746c, 0x0, 0x0) = 0x0 (0)
- GetModuleFileNameW(0x0, "C:\Users\user\Desktop\samples\deimos.exe", 0x400) = 0x2f (47)
- GetUserProfileDirectoryW(0x140, "C:\Users\user", 0xc0000b7a08) = 0x1 (1)
- LookupAccountSidw(0x0, 0xc00000e250, "user", 0xc0000b796c, "DESKTOP-TEST", 0xc0000b7970, 0xc0000b79f0) = 0x1 (1)
- NetUserGetInfo("DESKTOP-TEST", "user", 0xa, 0xc0000b7930) = 0x0 (0)
- GetComputerNameExW(0x5, "DESKTOP-TEST", 0xc0000b7b78) = 0x1 (1)
- GetAdaptersAddresses(0x0, 0x10, 0x0, 0xc000120000, 0xc0000b79d0) = 0x0 (0)
- CreateToolhelp32Snapshot(0x2, 0x0) = 0x1b8 (440)
- GetCurrentProcessId() = 0x2584 (9604)
- GetCurrentDirectoryW(0x12c, "C:\Users\user\AppData\Local\Programs\retoolkit\bin") = 0x39 (57)
[...]
asmstdcall 中直接调用 API。VirtualAlloc 总是被运行时包多次调用;在调用 CreateProcessW 之前 CreateFileW 会被多次调用等)。该工具会忽略 Golang 运行时初始化噪音,但之后由用户决定每个场景中哪些函数更适合过滤。gftrace 根据 GPL v3 许可证发布。有关更多信息,请参阅名为 LICENSE 的文件。