查看原文
其他

最新GoLang免杀过国内主流杀软及卡巴斯基

qi4L 李白你好 2023-06-22

声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。

宝子们现在只对常读和星标的公众号才展示大图推送,建议大家把李白你好设为星标”,否则可能就看不到了啦!本文免杀由qi4L师傅制作投稿,有技术方面的问题可联系VX:WXID-Nu1r。

0x01 效果图

火绒

Windows Defender

360

卡巴斯基

免杀效果嘎嘎牛逼!

0x02 下载链接

EDR绕过,由GoLang编写GoLangUnhooker
https://github.com/qi4L/GoLangUnhookerhttps://github.com/qi4L/GoLangUnhooker/releases/tag/v0.2
golang实现的回调函数加载器库GolangCallbackLoader
https://github.com/qi4L/GolangCallbackLoader
欢迎师傅们下载使用,别忘了点点小星星  stars

0x03 实现原理

磁盘覆盖脱钩

假设Ntdll已经被挂钩, 取消挂钩 DLL 的过程如下:
  1. 将 ntdll.dll 的新副本从磁盘映射到进程内存
  2. 查找挂钩的 ntdll.dll 的 .text 部分的虚拟地址
  • 获取 ntdll.dll 基地址

  • 模块基址 + 模块的 .text 部分 VirtualAddress

  • 查找新映射的 ntdll.dll 的 .text 部分的虚拟地址

  • 获取挂钩模块的 .text 部分的原始内存保护

  • 将 .text 部分从新映射的 dll 复制到原始(挂钩的)ntdll.dll 的虚拟地址(在第 3 步中找到)——这是取消挂钩的主要部分,因为所有挂钩的字节都被磁盘中的新字节覆盖

  • 将原始内存保护应用到原始 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汇编示例:

    from GO

    // func Syscall(callid uint16, argh ...uintptr) (uint32, error, error)
    TEXT ·WinApiSyscall(SB),NOSPLIT,$168-64
    NO_LOCAL_POINTERS
    CALL runtime·entersyscall<ABIInternal>(SB)
    MOVQ trap+0(FP), BP // syscall entry
    // copy args down
    LEAQ a1+8(FP), SI
    LEAQ sysargs-160(SP), DI
    CLD
    MOVSQ
    MOVSQ
    MOVSQ
    SYSCALL
    MOVQ AX, r1+32(FP)
    MOVQ $0, r2+40(FP)
    CMPL AX, $-1
    JNE ok3

    LEAQ errbuf-128(SP), AX
    MOVQ AX, sysargs-160(SP)
    MOVQ $128, sysargs1-152(SP)
    MOVQ $SYS_ERRSTR, BP
    SYSCALL
    CALL runtime·exitsyscall(SB)
    MOVQ sysargs-160(SP), AX
    MOVQ AX, errbuf-168(SP)
    CALL runtime·gostring(SB)
    LEAQ str-160(SP), SI
    JMP copyresult3

    ok3: // 返回值
    CALL runtime·exitsyscall(SB)
    LEAQ ·emptystring(SB), SI

    copyresult3: // 错误
    LEAQ err+48(FP), DI

    CLD
    MOVSQ
    MOVSQ

    RET

    from ScareCrow

    TEXT ·Allocate(SB),$0-56
    XORQ AX,AX
    MOVW callid+0(FP), AX
    MOVQ PHandle+8(FP), CX
    MOVQ SP, DX
    ADDQ $0x48, DX
    MOVQ $0,(DX)
    MOVQ ZeroBits+35(FP), R8
    MOVQ SP, R9
    ADDQ $40, R9
    ADDQ $8,SP
    MOVQ CX,R10
    SYSCALL
    SUBQ $8,SP
    RET

    //Shout out to C-Sto for helping me solve the issue of ... alot of this also based on https://golang.org/src/runtime/sys_windows_amd64.s
    #define maxargs 8
    //func Syscall(callid uint16, argh ...uintptr) (uint32, error)
    TEXT ·NtProtectVirtualMemory(SB), $0-56
    XORQ AX,AX
    MOVW callid+0(FP), AX
    PUSHQ CX
    MOVQ argh_len+16(FP),CX
    MOVQ argh_base+8(FP),SI
    MOVQ 0x30(GS), DI
    MOVL $0, 0x68(DI)
    SUBQ $(maxargs*8), SP
    MOVQ SP, DI
    CLD
    REP; MOVSQ
    MOVQ SP, SI
    SUBQ $8, SP
    MOVQ 0(SI), CX
    MOVQ 8(SI), DX
    MOVQ 16(SI), R8
    MOVQ 24(SI), R9
    MOVQ CX, X0
    MOVQ DX, X1
    MOVQ R8, X2
    MOVQ R9, X3
    MOVQ CX, R10
    SYSCALL
    ADDQ $((maxargs+1)*8), SP
    POPQ CX
    MOVL AX, errcode+32(FP)
    MOVQ 0x30(GS), DI
    MOVL 0x68(DI), AX
    MOVQ AX, err_itable+40(FP)
    RET

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

    间接系统调用

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

    Patch Inline Hooking

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

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

    Export Address Table (EAT)

    间接调用通常是结合使用 GetModuleHandle 和 GetProcAddress来解析系统调用的地址。另一种方式是在进程环境块(PEB)中手动定位NTDLL.dll,通过解析导出地址表(EAT)找到系统调用。

    如果使用内存中已有的 NTDLL 基地址,这将不会绕过任何系统调用的 UM 挂钩。但是GO是不会自己把dll加载进去的,所以有效,但是要记得去卸载DLL。

    Dual-load 1 (Section)

    KnownDlls是对象命名空间中的一个目录,其中包含进程加载的最常见 DLL 的部分对象。

    存储在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs下。

    它通过减少可执行文件的加载时间来提高性能,并且可以通过打开节名\KnownDlls\ntdll.dll将 NTDLL 的新副本映射到进程中。

    重新加载之后,我们就相当于获取了一个未被hook的ntdll对象,就可以使用其中的syscall方法了。

    有些产品不会挂钩 NtMapViewOfSectionEx,但这仅在 Windows 10 1803 之后可用。上面的代码把NtMapViewOfSection换成NtMapViewOfSectionEx。

    NativePayload

    一个简单的思路,延时 + RWX 更改为 X 或 RX ,来源

    未公开的API

    逆向DLL中的未公开的API,只要序列号没有函数名的,然后直接系统调用传参即可。这样的API很可能不在EDR的名单上可以过。

    缺点:效果好成本也大,且实战环境多样,在win10 dll中找到的未公开API,win7 和 windows servse中很可能无。

    BlockOpenHandle

    阻止任何进程打开你的进程的句柄,只允许 SYTEM 打开你的进程的句柄,这样就可以避免远程内存扫描器

    🦚unhook库

    推一个unhook库,看函数名就知道集成了些unhook技巧。可以从磁盘或者内存中加载函数,但是系统调用自己去定义,他只定义了一部分系统调用,所有调用有些函数时候就会出现莫名其妙的错误。

    🦜TODO

    • 更新更多的EDR绕过技术

    0x03 参考链接


    • https://roberreigada.github.io/posts/playing_with_an_edr/;

    • https://s3cur3th1ssh1t.github.io/A-tale-of-EDR-bypass-methods/;

    • https://github.com/am0nsec/HellsGate;

    • https://ethicalchaos.dev/2020/05/27/lets-create-an-edr-and-bypass-it-part-1/;

    • https://ethicalchaos.dev/2020/06/14/lets-create-an-edr-and-bypass-it-part-2/;

    • https://thewover.github.io/Dynamic-Invoke/;

    • https://j00ru.vexillium.org/syscalls/nt/64/;

    • https://0xdarkvortex.dev/hiding-in-plainsight/

    • https://github.com/TheD1rkMtr/BlockOpenHandle

    0x04 往期精彩


    某次授权的大型内网渗透测试


    整理好的安全电子书合集


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存