Cobaltstrike4.0 —— shellcode分析(一)

简介: Cobaltstrike4.0 —— shellcode分析

本文主要内容

本文主要对cobaltstrike4.0中shellcode的运作原理的分析。

Cobaltstirke 4.0 shellcode分析

一、shellcode生成

Cobaltstrike启动服务端,然后打开aggressor 端,如下图生成payload:

打开生成的payload:

长度是1600个byte

创建一个加载这个段机器码的加载器,在cobaltstrike中这段机器码我们一般叫stager,所以我们简单写一个stagerloader:

二、shellcodeloader

这里实现的方法很多,可以直接通过c++内联汇编,获取shellcode的存储地址,然后直接跳转过去;也可以分配内存空间,将对应payload当成一个返回类型为void的函数来执行。如下图,也是比较常见的c实现的loader的形式:

#include   
int main(void) {  
    unsigned char buf\[\] = 上面那串payload;;  
    //创建一个堆(这里看个人习惯,不建堆也可以直接分配)  
    HANDLE myHeap = HeapCreate(HEAP\_CREATE\_ENABLE\_EXECUTE, 0, 0);  
    //从堆上分配一块内存  
    void\* exec = HeapAlloc(myHeap, HEAP\_ZERO\_MEMORY, sizeof(buf));  
    //payload复制过去  
    memcpy(exec, buf, sizeof(buf));  
    //将exec强制转换成返回类型为void的函数指针,并调用这个函数  
    ((void(\*)())exec)();  
    return 0;  
}

编译链接生成exe,(使用vs编译链接的时候建议把什么优化,安全检查,随机基址,以及相关清单信息啥的都关掉,方便后面我们对exe进行调试)

通过x86 的release生成的exe如下:

完成之后我们先简单测试下,丢到虚拟机运行下,看下上线啥的是否正常:

如下图,没啥问题,成功上线:

接下来我们使用ollydbg来调试下这个exe,来看看这个payload在执行什么内容:

od运行之后,一个call和一个jmp:使用这个编译器生成好像都是这样,不重要,我们要找的代码在jmp里面,跟过去就行了

过来之后又是一堆操作,调用啥的,代码还挺长:

一个个过肯定是能找到执行我们payload的调用的,过的方法呢,就看过完对应call之后,aggressor端是否新增上线设备就行。但是这里有简单方法,比如给heapAlloc上调试断点,f9,就直接跳过去了。

最后是在这个call里面调用了对应的payoad:

f7进去看看,如下图,就是我们自己写的代码了:其中的四步内容如下:

三、shellcode分析

1、第一阶段(stager)

直接跟进去调用的payload:如下图,就是我们生成的payload:(从这里开始往下,因为笔者调试的是多是多次进行运行,所以每次分配的堆的内存空间不一样,比如下图是9805c8开始,下下下下张图(从下开始第四张)是9c05c8开始,所以这里我们主要看代码逻辑即可,地址可能对不上,笔者尽量在完整的一段逻辑里面分析的时候一次过)

第一个call:

和上图call配合,这里pop ebp,获取到eip,如下图然后压入特征码和参数返回eip:

上图中,其中参数有,“wininet”,和一个特征码 726774c,然后通过call ebp 返回。

如下图,返回之后通过fs寄存器,找打TEB——>PEB——>PEB_LDR_DATA——>内存模块加载List(InMemoryOrderLinks)——_LDR_DATA_TABLE_ENTRY

——>获取模块的名称和基址,为后续遍历模块和函数名做准备。

然后对获取的模块名,对其进行特定的一个hash算法,如下图:第一个获取到的模块名是,exe本身这个模块,所以模块名是:“shellcodeloader.exe”

对其进行特定的求特征算法,(算法逻辑就是:对每个字符判断,如果大于0x60,就减0x20(其实就是小写转大写),然后累加,在累加前将上一次的累加结果循环右移14位)

最后得出来的值是在edi中,将其压入栈中,后面会使用到。

然后找到edx+10 对应位置:如下图

这个位置其实就是,_LDR_DATA_TABLE_ENTRY中的0x18偏移的内容(edx是头指针,在里面的0x08偏移的位置),下表是该结构体的内容,所以这里其实就是拿到对应的模块的基址

struct \_LDR\_DATA\_TABLE\_ENTRY  
{  
    struct \_LIST\_ENTRY InLoadOrderLinks;           //0x0               
    struct \_LIST\_ENTRY InMemoryOrderLinks;            //0x8  
    struct \_LIST\_ENTRY InInitializationOrderLinks;   //0x10                
    VOID\* DllBase;                           //0x18                                 
    VOID\* EntryPoint;                      //0x1c                                   
    ULONG SizeOfImage;                  //0x20                                      
    struct \_UNICODE\_STRING FullDllName;     //0x24                                
    struct \_UNICODE\_STRING BaseDllName;      //0x2c                                 
    ....  
};

拿到基址之后,如下,寻找3c偏移,这里是在DOS头3c偏移找PE起始位置,然后找pe头中的78偏移。

PE头中的0x78偏移:如下,可以看到这个位置其实就是可选头里面的导出表

找到导出表之后,接下来的test命令,判断是否为0,也就是判断这里是否有导出表:

等于0的话,来到如下,这里我们第一次运行的时候也的确等于0,因为我们的exe里面没有导出表,下面的对导出表为空的处理其实就是接着遍历下一个模块,然后跳回到获取模块名的位置:

接着我们去看看当某个模块的导入表不为空的时候,怎么处理的,其实第二个模块就是ntdll.dll (一般程序第三个是kernel32.dll),此时就满足导出表不为空,

然后其操作是:找到导出表里面的0x18偏移和0x20偏移的地方,这个表的结构如下:

然后如下图,遍历名称,计算特征值(这里的计算算法和之前对模块的特征算法有点不一样,区别就是没有判断是否是大于0x60,然后小写转大写),和之前栈中的特征码比较:

如下图:拿计算出来模块的特征值加上函数的特征值,最后和我们在开头压入栈中的特征值做比较:

如果不相等:就遍历下一个函数,一直到这个模块的全部以名称形式导出的函数被遍历完,然后重复遍历下一个模块:

这里我们来看下,当找到对应的函数,其通过这种hash算法计算出来的值和压入栈中的相等时的情况,

1、这个函数是什么函数

2、对这个函数干什么

如下图,可以看到,当我们遍历到,kernel32.dll模块里面的LoadLibraryExA函数的时候,特征码就相等了:

如下图是干了什么,这里面一顿操作,通过循环次数获取在其导出序号表里面获取到导出序号,然后利用导出序号在导出地址表里面获取到其导出地址,之后就可以随便调用LoadLibraryExA:

如下是,获取到之后的动作,利用这个函数,来加载wininet这个dll:

试想为什么要加载这个dll,其实大概能猜出来,肯定是后面用的函数载这个wininet这个dll模块里面,而本身的程序里面没有加载这个模块。

我们继续往下看:之后就是使用相同的方式,传入对应的特征码,然后循环遍历模块去找函数,这里其实大概率下面的这些函数都载wininet这个dll里面:

大概看了下是如下的9个函数,根据对应特征码,使用同样的方法获取到对应函数的地址,并通过压入堆栈的数据作为参数并调用对应函数:

简单看下这些函数是什么:

第一个:A779563A对应的是wininet里面的InternetOpenA函数

第二个:C69F8957对应的wininet里面的InternetConnectA

第三个:3B2E55EB对应的wininet里面的HttpOpenrequestA

第四个:7B18062D对应的是wininet里面的HttpSendRequestA

第五个:315E2145对应的是user32里面的GetDesktopWindow

第六个:0BE057B7对应的是wininet里面的InternetErrorDlg

第七个:E553A458对应的是kernel32里面的VirtualAlloc

第八个:E2899612对应的是wininet里面的InternetReadFile

这里面的逻辑,就是建立和cobaltstrike server的链接,然后发送get请求,开辟内容空降将返回的内容存起来,最后运行对应返回的内容,(上述这个步骤就是我们说的通过stager拉取beacon的操作)



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