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://forum.butian.net/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进行研究分析还是比较有价值的,能为后续我们对免杀技术手段的研究打一个夯实的基础。

相关文章
|
存储 API 数据安全/隐私保护
1.13 导出表劫持ShellCode加载
在`Windows`操作系统中,动态链接库`DLL`是一种可重用的代码库,它允许多个程序共享同一份代码,从而节省系统资源。在程序运行时,如果需要使用某个库中的函数或变量,就会通过链接库来实现。而在`Windows`系统中,两个最基础的链接库就是`Ntdll.dll`和`Kernel32.dll`。Ntdll.dll是Windows系统内核提供的一个非常重要的动态链接库,包含了大量的系统核心函数,如文件操作、进程和线程管理、内存操作等等。在进程启动时,操作系统会先加载`Ntdll.dll`,并将其映射到该进程的地址空间中。由于`Ntdll.dll`是如此重要,所以任何对其的劫持都是无效的。这
108 0
|
存储 Python Windows
1.7 完善自定位ShellCode后门
在之前的文章中,我们实现了一个正向的匿名管道`ShellCode`后门,为了保证文章的简洁易懂并没有增加针对调用函数的动态定位功能,此类方法在更换系统后则由于地址变化导致我们的后门无法正常使用,接下来将实现通过PEB获取`GetProcAddrees`函数地址,并根据该函数实现所需其他函数的地址自定位功能,通过枚举内存导出表的方式自动实现定位所需函数的动态地址,从而实现后门的通用性。
74 1
|
存储 Serverless 数据安全/隐私保护
1.9 动态解密ShellCode反弹
动态解密执行技术可以对抗杀软的磁盘特征查杀。其原理是将程序代码段中的代码进行加密,然后将加密后的代码回写到原始位置。当程序运行时,将动态解密加密代码,并将解密后的代码回写到原始位置,从而实现内存加载。这种技术可以有效地规避杀软的特征码查杀,因为加密后的代码通常不会被标记为恶意代码。
141 0
|
存储 安全 网络协议
1.12 进程注入ShellCode套接字
在笔者前几篇文章中我们一直在探讨如何利用`Metasploit`这个渗透工具生成`ShellCode`以及如何将ShellCode注入到特定进程内,本章我们将自己实现一个正向`ShellCode`Shell,当进程被注入后,则我们可以通过利用NC等工具连接到被注入进程内,并以对方的权限及身份执行命令,该功能有利于于Shell的隐藏。本章的内容其原理与`《运用C语言编写ShellCode代码》`中所使用的原理保持一致,通过动态定位到我们所需的网络通信函数并以此来构建一个正向Shell,本章节内容对`Metasploit`工具生成的Shell原理的理解能够起到促进作用。
100 0
|
存储 安全 Shell
1.6 编写双管道ShellCode后门
本文将介绍如何将`CMD`绑定到双向管道上,这是一种常用的黑客反弹技巧,可以让用户在命令行界面下与其他程序进行交互,我们将从创建管道、启动进程、传输数据等方面对这个功能进行详细讲解。此外,本文还将通过使用汇编语言一步步来实现这个可被注入的`ShellCode`后门,并以此提高代码通用性。最终,我们将通过一个实际的漏洞攻击场景来展示如何利用这个后门实现内存注入攻击。
80 0
|
安全 Windows
【工具分享】免杀360&火绒的shellcode加载器
【工具分享】免杀360&火绒的shellcode加载器
380 0
|
存储 算法 安全
Cobaltstrike4.0 —— shellcode分析(一)
Cobaltstrike4.0 —— shellcode分析
381 0
|
存储 算法 安全
Cobaltstrike4.0 —— shellcode分析(二)
Cobaltstrike4.0 —— shellcode分析
291 0