/GoLangUnhooker

EDR绕过,由GoLang编写

Primary LanguageGo

✅磁盘覆盖脱钩

假设Ntdll已经被挂钩, 取消挂钩 DLL 的过程如下:

  1. 将 ntdll.dll 的新副本从磁盘映射到进程内存
  2. 查找挂钩的 ntdll.dll 的 .text 部分的虚拟地址
    • 获取 ntdll.dll 基地址
    • 模块基址 + 模块的 .text 部分 VirtualAddress
  3. 查找新映射的 ntdll.dll 的 .text 部分的虚拟地址
  4. 获取挂钩模块的 .text 部分的原始内存保护
  5. 将 .text 部分从新映射的 dll 复制到原始(挂钩的)ntdll.dll 的虚拟地址(在第 3 步中找到)——这是取消挂钩的主要部分,因为所有挂钩的字节都被磁盘中的新字节覆盖
  6. 将原始内存保护应用到原始 ntdll.dll 的刚脱钩的 .text 部分

这个方法理论上可以应用于其他dll。

✅Threadless Process Injection

来自 BsidesCymru 2023 演讲 Needles Without the Thread

✅动态API解析

如果使用IDA或者x64debug之类的工具看自己编写的马,很容易发现,不论是API函数还是DLL,很轻松就可以找到。

那么就可以使用一些算法来让API函数与DLL,不是明文的出现在代码中(这里推荐 djb2)。

其次通过GetProcAddress的方式,来获取函数地址,然后指向函数指针,以这种方法可以规避一定的EDR产品。

我并没有归纳所有调用方式,但会给出一个示例:

dll1 = djb2md5.API(dll1)
funcAdd = djb2md5.API(funcAdd)
hNtdll, err1 := syscall.LoadLibrary(dll1)
if err1 != nil {
  log.Fatal(err1, " LoadLibrary")
}
ret, err2 := syscall.GetProcAddress(hNtdll, funcAdd)
if err2 != nil {
  log.Fatal(err2, " GetProcAddress")
}
addr, _, _ = syscall.SyscallN(GetProcAddressHash("6967162730562302977", "5569890453920123629"), uintptr(0), uintptr(len(pp1)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)

✅直接系统调用

在最新的CS4.8也集成了这种调用方式,但那是开箱即用的,这很不好,对于杀软方很容易就可以打标。

且大多数 EDR 产品将在用户态下挂钩 win32 api 调用。

这里给出GO汇编示例:

TEXT ·proc(SB), NOSPLIT, $0-16
    MOVQ $message_string(SB), RDI
    MOVQ $len(message_string)-1, RDX
    MOVQ $55h,
    SYSCALL // 执行系统调用
    RET

    len EQU $-message_string

缺点:汇编代码在 Windows 操作系统版本之间的某些点上是不同的,有时甚至在服务包/内置编号之间也是不同的。

✅间接系统调用

通过函数地址 + syscall.SyscallN的方式来调用API,就是间接系统调用。

✅Patch Inline Hooking

通过应用正确的函数调用,重新钩住被钩住的函数。

这个方法理论上可以应用于其他函数。

TODO

  • 更新更多的EDR绕过技术;

参考