【免杀】使用CobaltStrike的外置监听器绕过检测-番外

admin 2025年6月19日21:26:54评论4 views字数 4778阅读15分55秒阅读模式
在上一篇文章:【免杀】使用CobaltStrike的外置监听器绕过检测

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

我们实现了一个能通过external C2 来对杀软进行绕过的方法,那么为什么行呢?这里对通讯的流量进行分析。

首先是在spawnBeacon需要运行一段teamserver发送过来的shellcode

// Allocates a RWX page for the CS beacon, copies the payload, and starts a new threadvoidspawnBeacon(char *payload, DWORD len){    HANDLE threadHandle;    DWORD threadId = 0;    char *alloc = (char *)VirtualAlloc(NULL, len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);    memcpy(alloc, payload, len);    threadHandle = CreateThread(NULLNULL, (LPTHREAD_START_ROUTINE)alloc, NULL0, &threadId);}

在IDA中下断点看看,这里的客户端是自己编译的,因此选择Debug模式,这样IDA能通过pdb文件实现源码级F5(其实也不算反编译)

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

看看这段汇编长啥样

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

第一个call rbx翻译成C

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

这里从第三方客户端Dump下文件后查看

【免杀】使用CobaltStrike的外置监听器绕过检测-番外findPEFile

首先获得当前rsp的值,并且由于是小端序,应该从右向左读。从启示地址开始读取,直到读取到PE文件的DOS头的e_magic,接着再判断PE头,如果都成立,则返回DOS文件头的地址,所以我在上层函数中将返回的类型设置为了PIMAGE_DOS_HEAD

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

Part I. sub_180017948

在使用IDA打开Dump下来的bin文件时,IDA会将其默认解析为PE文件格式,说明这就是在反射式加载一个PE文件,所以要首先从LDR中加载出基本函数,例如LoadLibraryVirtualAlloc

首先是经典的InMemoryOrderModuleList循环遍历寻找

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

这个循环看不懂,我把i的类型设置为struct _LDR_DATA_TABLE_ENTRY *i再来看看

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

  • __ROR4__:将v8向右循环移动13位,使用这个作为单次循环读取到字符的哈希运算
  • break的条件是:v8 == 0x6A4ABC5B

既然是哈希,这个过程肯定是不可逆的,所以动态调试一下

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

发现值为:KERNEL32.DLL可以得到该哈希,注意这里字串是wchar_t*的宽字符类型,需要在 Options->String Literals 选择编码方式为unicode

也可以你想算法,尝试使用python暴力破解一下

import osdef ror4(value: int, bits: int) -> int:    value &= 0xFFFFFFFF  # 保证是32位    return ((value >> bits) | (value << (32 - bits))) & 0xFFFFFFFFdef hasher(filename: bytes)->int:    v8 = 0    for ch in filename:        v9 = ror4(v8, 13)        v8 = (v9 + ord(ch)) & 0xFFFFFFFF        v9 = ror4(v8, 13)        v8 = (v9 + 0) & 0xFFFFFFFF    return v8if __name__ == '__main__':    folder_path = "C:\Windows\System32"    files = os.listdir(folder_path)    for file in files:        if(file[-4:]!=".dll"):            continue        hash = hasher(file.upper())        if(hash==0x6A4ABC5B):            print("Found", file, "hash is right: "hex(hash).upper())            break

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

这里使用的是C:\Windows\System32目录下的DLL文件名

接着在静态我已经尽我最大努力了,可是还有很多不懂得

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

不过按照一般的流程就是找需要的导出函数了,动态调试看看

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

那么大概意思清楚了:读取dll的导出函数,进行自定义的哈希运算,找到符合的函数,从而加载函数,及GetProcAddress的实现

dov6=*v14+++__ROR4__(v6, 13);while ( *v14 );

知道了是kernel32.dll的函数,我们自己测试看看(用pefile偷个懒)

import osimport pefiledef ror4(value: int, bits: int) -> int:    value &= 0xFFFFFFFF  # 保证是32位    return ((value >> bits) | (value << (32 - bits))) & 0xFFFFFFFFdef hasher(filename: bytes, wide_char: bool = True)->int:    v8 = 0    for ch in filename:        v9 = ror4(v8, 13)        v8 = (v9 + ord(ch)) & 0xFFFFFFFF        if(wide_char):            v9 = ror4(v8, 13)            v8 = (v9 + 0) & 0xFFFFFFFF    return v8def list_exported_functions(pe_path:str)->list:    result = []    pe = pefile.PE(pe_path)    if not hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):        print("此文件没有导出表。")        return    for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:        name = exp.name.decode() if exp.name else "<no name>"        address = hex(pe.OPTIONAL_HEADER.ImageBase + exp.address)        ordinal = exp.ordinal        result.append(name)    return resultif __name__ == '__main__':    folder_path = "C:\Windows\System32"    files = os.listdir(folder_path)    filename = ""    for file in files:        if(file[-4:]!=".dll"):            continue        hash = hasher(file.upper())        if(hash==0x6A4ABC5B):            print("Found", file, "hash is right: "hex(hash).upper())            filename = file            break    if(filename != ""):        filepath = os.path.join(folder_path, filename)        func_names = list_exported_functions(filepath)        val = hasher(func_names[0], False)        print(func_names[0], hex(val))    

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

修改一下一小段脚本

    if(filename != ""):        filepath = os.path.join(folder_path, filename)        func_names = list_exported_functions(filepath)            for name in func_names:            v6 = hasher(name, False)            if v6 == 0xEC0E4E8E:                print("No.2",name,"t"f"0x{v6:02X}")            if v6 == 0x7C0DFCAA:                print("No.1",name,"t"f"0x{v6:02X}")            if v6 == 0x91AFCA54:                print("No.4",name,"t"f"0x{v6:02X}")            if v6 == 0x7946C61B:                print("No.5",name,"t"f"0x{v6:02X}")            if v6 == 0x753A4FC:                print("No.3",name,"t"f"0x{v6:02X}")            if v6 == 0xD3324904:                print("No.0",name,"t"f"0x{v6:02X}"

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

最后函数完成的v13

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

关于如何实现GetProcAddress,可以参考之前关注发的文章:PE文件格式解析 的 《如何找到导入的函数和DLL-导入表》部分

sub_180017D38

主函数ReflectiveLoader检查完成后进入sub_180017D38

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

根据之前的分析

  • *a函数就是GetModuleHandlerA,可以获得当前PE文件在内存中的位置
  • a[1]函数就是GetProcAddress

sub_180017F88

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

ntHead->FileHeader.Characteristics描述了IMAGE的特征。其中0x8000:反转单词的字节数。 此标志已过时。

根据参数修改下sub_180017F88的类型就很好看了

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

动态调试发现没有走上面的复杂逻辑,或许是为了兼容性?

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

等效运行

VirtualAlloc(0,     SizeofImage    MEM_COMMIT | MEM_RESERVE,     PAGE_EXECUTE_READWRITE)

后续函数

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

基本就是反射式载入那一套,你依旧可以参考:https://github.com/stephenfewer/ReflectiveDLLInjection

最后跳转到LoaderFlags或者AddressOfEntryPoint,这里是dllmain,开始运行恶意DLL

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

sub_180018158 CopyDOSHeader

复制PE文件DOS头

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

sub_180018218 ReflectSections

加载PE文件的Section到内存

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

sub_180018318 GetImportTable

从导入表开始导入相关函数和Dll

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

sub_1800185D8 DoRelocation

读取重定位表,进行重定位

【免杀】使用CobaltStrike的外置监听器绕过检测-番外

小结

继续深入下去可以挖掘出cobaltstrike具体功能实现的方法和技巧。

这种方式的有效shellcode仅有跳转到ReflectLoader之前的一小段汇编代码,所以看上“比较合法”,这样也大大提升了躲避检测的能力

同时这种方式可以使用之前提到过的反射式加载器加载,不过需要自己维持一个ipc(名称一定要正确)和socket来负责传递数据以及满足对应的堆栈条件

原文始发于微信公众号(不止Sec):【免杀】使用CobaltStrike的外置监听器绕过检测-番外

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月19日21:26:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【免杀】使用CobaltStrike的外置监听器绕过检测-番外https://cn-sec.com/archives/4180102.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息