freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

如何利用C2进行红队渗透攻击
2023-01-14 21:17:15
所属地 湖北省

0X00前言

在写这篇文章的时候,为了方便初学者理解主要内容,我想先针对性的讲讲Cobalt Strike这款红队渗透中常见的工具。众所周知Cobalt Strike是一个强大的内网域渗透工具,它采用图形化页面方便使用者能够直观的搞清楚大部分电脑在复杂域环境中的关系和权限,并且通过自动化生成的shellcod,方便使用者在 Active Directory等复杂环境中进行免杀作业。我们可以使用Cobalt Strike快速提升计算机用户权限,构建横向移动平台,以及搭建计算机后门,并对网站进行持久性的攻击。它是一个对于我们在域渗透中维权、提权、横向移动、留存后门等方面都有巨大的帮助,在这篇文章中我们会以这款工具为例子,解释一下C2在红队方面的应用。

1673699625_63c2a1299d557243aedb4.png!small?1673699626375

0X01常见的C2工具

C2, Command and Control, 命令与控制。主要是指攻击者通过与恶意软件的交互,对被害者进行控制,从而实施恶意活动的含义。从语义上来讲,C2即可用作为名词(基础设施)也可以作为动词(交互的行为),例如C2服务器(名词做定语)、攻击者进行C2攻击。另外,虽然在使用方法上C2适用于普遍的红队渗透攻击并在红队杀伤链中占据一定地位。但是在APT攻击中,APT攻击的目的往往不是为了破坏,而是为了窃密、监听。因此,在APT攻击中,C2通信常会有以下的用处:

用于攻击者的指令下发

用于资源下发和数据上传

因此C2架构的总体作用可以理解为:C2 架构也就可以理解为,恶意软件通过什么样的方式获取资源和命令,以及通过什么样的方式将数据回传给攻击者。以下是常见C2获取方式。

Cobalt Strike:GitHub上存在,但是可能有暗桩

Metasploit:kali自带BRC4:https://bruteratel.com/

Poshc2:https://github.com/nettitude/PoshC2

Empire:https://github.com/EmpireProject/Empire

BlackMamba:https://github.com/loseys/BlackMamba

0X02红队的CS细节

C-B-T 基础

Client(客户端):是操作员连接到团队服务器的方式。

  1. 客户端可以在与团队服务器相同的系统上运行或远程连接。

  2. 客户端可以在 Windows、macOS 或 Linux 系统上运行。

Beacon(信标):它是Cobalt Strike用于创建与团队服务器的连接的默认恶意软件负载的名称,也可以说Beacon是一个特别的Payload。而来自目标的活动回调会话也称为“信标”。Cobalt Strike具有两种类型的Beacon(如下所示):

  1. Stager:它是一个可选的Beacon负载。操作员可以通过发送一个初始的小型Beacon的shellcode 有效负载来“暂存”他们的恶意软件,该有效负载仅执行一些基本检查,然后查询配置的C2以获得功能齐全的后门。

  2. Full Backdoor(后门):它可以通过Beacon的stager;加载程序恶意软件系列或直接执行默认DLL导出“ReflectiveLoader”来执行。该后门运行在内存中,且可以通过多种方式与团队服务器建立连接。

Teamserver(服务端):是 Cobalt Strike 的 C2 服务器部分。它可以接受客户端连接、BEACON 回调和一般 Web 请求。

  1. 默认情况下,它接受 TCP 端口 50050 上的客户端连接。

  2. Team server 只支持在Linux 系统上运行。

拓展方向

1、rdi xxx.dll CNA脚本调用 (https://github.com/pe3zx/my-infosec-awesome/blob/master/Offensive.md

CAN是Aggressor Scrip脚本的后缀,它允许攻击者修改和扩展CS客户端。

2、Opsec BOF :它是随着 Cobalt Strike 4.1 的发布,添加了一项新功能,允许代码以更加 OPSEC 友好的方式运行。这是通过所谓的信标对象文件 (BOF) 实现的。

BOF 允许用户在不遵循 Cobalt Strike 明确定义的模式的情况下执行代码。通常,Cobalt Strike 将始终执行跨进程注入或启动cmd.exe / powershell.exe 以完成其更有用的目标。

当使用 BOF 对技术进行编码时,您可以获得在信标本身内部运行代码而无需启动子进程的好处。您也没有与 execute-assembly 关联的妥协指标 (IoC),您可以在其中启动 spawn-to 进程并将代码注入其中。

但是,绕过我们将所有C#工具重新编码到 BOF 中,会发现出现错误。原因是BOF的结构不允许长时间运行的任务。它不是真正的插件格式,也不被如此对待。但值得注意的是。BOF 最适用于将快速返回结果的一次性命令。如果您想在信标中执行长时间运行的任务,请利用execute-assembly及其将这些项目视为长时间运行的作业的性质 (进程的内存当中执行 容易崩 beacon掉线 编译时运行)。

例如:我们通过powershell.exe去执行CS生成的payload!

1673699869_63c2a21d0459d6bafccc5.png!small?1673699869455

3、spwn: 这个功能,中文意思是产卵,它的功能就是可以派生出更多的Beacon让一个团队进行分布式渗入攻击。通常我们在团队主服务器上给队友来派生Beacon这样只要主服务器权限不掉,还能继续操作。尽量派生出多个Beacon,让我们的操作都在子Beacon。

这里简单叙述下 如何操作从主服务器 派生到 其他队友服务器的过程:

队友服务器Listeners生成===> 团队服务器Listeners生成===>使用队友的IP。Spawn其实很好理解它的目的就通过让队友的服务器生成监听后,再用团队服务器生成Server IP并指向队友。我们通过灵活的运用Spawn不仅可以使团队效率提高,还能较好的维持权限。

插件化管理

我们知道CS还有另外一个更强大的功能那就是插件化管理,也就是说我们需要用CS执行某个功能的时候可以通过实现该功能的插件进行执行(相较于其他C2方便很多)。Kits可供下载,具有有效许可证,并且只能与许可(或破解)安装一起使用。并且军械库工具包有时会随Cobalt Strike的破解版一起分发。

Mimikatz kit:允许操作员更新他们的 Mimikatz 版本,而无需等待 Cobalt Strike 软件更新(https://blog.cobaltstrike.com/2021/07/29/introducing-mimikatz-kit/)。

Burp2Malleable kit:通过编写的快速Python实用程序将来自Burp套件的HTTP请求转换为CS Malleable C2 配置文件。它允许您从 BurpSuite(或 ZAP、POSTMAN 等)获取您希望流量看起来像/与之融合的内容的捕获/精心制作的请求,并将它们转换为 CobaltStrike Malleable C2 配置文件。它允许运营商选择数据的存储位置和格式,从而使流量看起来尽可能干净(https://github.com/CodeXTF2/Burp2Malleable)。

Artifact Kit:允许操作员修改所有CS可执行文件、DLL 和 shellcode 的模板,至今仍在使用(https://www.cobaltstrike.com/help-artifact-kit)。

Elevate Kit:允许操作员将他们的权限升级漏洞集成为CS命令。而且它是公开的,它本质上只是一个攻击者脚本,用于简化将您自己的 PowerShell脚本、二进制文件等加载到Beacon会话中(https://www.cobaltstrike.com/help-elevate))。

Applet/PowerApplet Kit:允许操作员修改CS的内置 Java Applet 有效负载,但现在不再广泛使用(https://blog.cobaltstrike.com/2017/10/03/kits-profiles-and-scripts-oh-my/)。

Resource Kit:允许操作员修改CS使用的脚本模板(主要作为加载程序),至今仍有不少人在使用(https://www.cobaltstrike.com/help-resource-kit)。

域前置技术

有时“重定向器”就像带有 nginx 代理的云实例一样简单,但另一种高效的重定向器方法是“域前端”。域前端是一种技术,运营商可以通过该技术通过内容交付网络 (CDN) 的基础设施重定向来隐藏网络连接的真实目的地。该技术最初被记录为绕过互联网审查的一种手段,也被威胁行为者(例如APT 29 )用来伪装 C2 流量。

当 HTTPS 请求前置时,会直接与 CDN 托管的信誉良好的域建立连接。这是“前端”域。加密请求将包含一个唯一标识符,通常包含在 HTTP“主机”标头中,CDN 使用该标识符将请求路由到运营商控制的服务器。由于域前端流量最初发送到 CDN,因此当防御者观察时,它将使用 CDN 的合法 SSL/TLS 证书。

1673700158_63c2a33e92cfd2f714de9.png!small?1673700159062

在这种情况下,要阻止流向运营商域的流量,防御者需要解密流量以发现 HTTPS 连接中的真正目的地,但这并不总是可行的。除了资源密集型之外,可能无法解密某些 CDN 的流量。例如,某些 CDN 在其 SSL/TLS 证书上强制执行证书固定 ,以防止使用组织提供的可信根证书拦截和解密流量。 伪装流量旨在看起来像合法服务,但实际上是与运营商服务器的直接连接,而前端流量则被发送到合法服务(CDN),并从那里转发给运营商。如果主机标头值和目标域相同,则这只是一个普通的直接 HTTP 连接。

如果主机标头和目标域不同并且主机标头是

合法域(例如在 Alexa Top 1M 中),则很可能是域伪装

用于 CDN 端点的域(例如 .azureedge.net),那么它可能是域前端。

有多个公共指南可用于测试域是否可前置。此repo还包括CDN提供商预编译的前端域列表。请注意,公开列表可能已过时,可能仍需要手动验证。

小提示:如果不做域前置技术并且使用Stager,就会导致样本非常容易被溯源发现(如下图所示)!

1673699961_63c2a27955caf492e148d.png!small?1673699961843

0X03主流的C2免杀

Stager(分阶段)

也可以说Stager分阶段的shellcode;分阶段有效载荷分解了攻击的不同阶段,通常使用单个有效载荷本来可以执行的多个有效载荷阶段。这些有效载荷通常被分解为Stager(即"初始有效载荷或信标")可执行文件和主要有效载荷文件。Stager是一个小的可执行文件,它是一个初始有效负载。它是一段相对较小的代码,用于为更大、更强大的有效载荷(称为阶段有效载荷)做准备。这意味着“Stager 设置了舞台”。当初始条目利用漏洞时,stager 通常是某些利用代码的一部分。在这里,利用代码将成功利用目标漏洞,然后运行Stager代码(payload)开始行动。

Stager的主要任务是在不被发现的情况下成功执行,返回到攻击者基础设施以下载所需的主要有效载荷,然后设置系统以执行该有效载荷。下载阶段或更大的主要有效载荷可以是一个或多个有效载荷,具体取决于攻击者所需的能力。下载阶段后,Stager便会传递执行控制以继续恶意活动。

stageless(无阶段)

CS的stageless载荷可执行文件和DLL与生成的.exe文件没有太大区别。CS的Artifact Kit从相同的源代码为Stager载荷和有效载荷stager构建工件。不同之处在于无阶段有效载荷的可执行文件和DLL模板有更多空间来容纳整个Beacon有效载荷。而且CS的载荷的共同点是原始输出,我们可以将其视为包含并运行Beacon的一大块Shellcode。当使用者导出一个无阶段负载工件时,CS会将这一大块Shellcode 修补到所需的PowerShell、EXE、DLL .....中。

DLL

我们可以通过使用Msfvenom等工具对包含有效载荷的可执行文件进行编码,可以绕过某些AV。或者使用Shellter或Veil等工具创建能够绕过常见防病毒解决方案的自定义可移植可执行文件 (PE)。这些工具还允许您将有效负载注入合法软件,以更好地从AV中屏蔽您的恶意代码。这些工具可以成功执行其任务,但是如果多次使用相同的二进制文件,则很有可能将其添加到现有的AV/EDR签名数据库中。使用VirusTotal等网站来测试可执行文件的检测率也可能会加快将恶意软件添加到 AV 签名数据库的过程。一般来说,将二进制文件上传到目标目前有点不必要的风险,因此我想研究使用不需要传输到目标磁盘的恶意软件执行横向移动的方法。

Cobalt Strike 的伟大之处在于可以选择在目标内存execute-assembly中执行 .NET二进制文件,而无需传输它。按照同样的想法,我希望能够将恶意软件传输到目标,该目标将在内存中执行并避免由于它存在于磁盘上而不必要地触发AV。我遇到了一种称为反射DLL注入的技术,并认为它是天才。反射DLL注入涉及将 .NET动态链接库.DLL加载到目标内存中。Powershell 等常用工具可用于加载DLL,并允许执行您选择的DLL 中可用的方法。这会导致无盘恶意软件执行。然而,我喜欢这个概念,为这样的任务做准备有点冗长,因此我的程序员本能开始发挥作用,我想为什么不创建一些自动化。

补充:

HTML Application:生成基于HTML的攻击载荷(可执行文件、Powershell、VBA)

MS Office Macro:生成基于Office的攻击载荷

Payload Generator:载荷生成器,可用于生成C、C#、Java、Python等载荷

USB/CD AutoPlay:生成自动播放功能的后门

Windows Dropper:捆绑正常文件

Windows Executable:生成EXE、DLL后门

Windows Executables:增强版的Windows可执行文件


0X04免杀的DLL加载

白加黑

DLL注入

反射型DLL注入

Dropper:注射器的实现

原理:简单来说,就是在目标进程中开辟一块堆空间,用于存储DLL的路径,之后使用CreateRemoteThread在目标进程中开启远程线程。

步骤:

  1. 获取目标进程PID

  2. 提升Dropper进程权限

  3. 打开目标进程

  4. 在目标进程内开辟缓冲区,用来存储DLL的路径

  5. 找到目标进程中加载的kernel32.dll的句柄,通过该句柄来获取目标进程中kernel32.dll的导出函数LoadLibrary函数的地址

  6. 通过CreateRemoteThread函数来调用LoadLibrary,使目标进程加载Payload DLL

获取目标进程PID(本来想注入计算器,但是获取计算器的进程ID的时候总是获取一个辅助进程ID,所以就注入记事本了)

HANDLEGetThePidOfTargetProcess()
{
//Get the pid of the process which to be injected.
HWNDinjectionProcessHwnd=FindWindowA(0, "Untitled - Notepad");
DWORDdwInjectionProcessID;
GetWindowThreadProcessId(injectionProcessHwnd, &dwInjectionProcessID);
cout<<"Notepad's pid -> "<<dwInjectionProcessID<<endl;
HANDLEinjectionProcessHandle=::OpenProcess(PROCESS_ALL_ACCESS|PROCESS_CREATE_THREAD, 0, dwInjectionProcessID);//dwInjectionProcessID);
returninjectionProcessHandle;
}

进行注入

BOOLDoInjection(char*InjectionDllPath,HANDLEinjectionProcessHandle)
{
DWORDinjBufSize=lstrlen((LPCWSTR)InjectionDllPath) +1;
LPVOIDAllocAddr=VirtualAllocEx(injectionProcessHandle, NULL, injBufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(injectionProcessHandle, AllocAddr, (void*)InjectionDllPath, injBufSize, NULL);
PTHREAD_START_ROUTINEpfnStartAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
HANDLEhRemoteThread;
if((hRemoteThread=CreateRemoteThread(injectionProcessHandle, NULL, 0, pfnStartAddr, AllocAddr, 0, NULL)) ==NULL)
{
ER=GetLastError();
cout<<"Create Remote Thread Failed!"<<endl;
returnFALSE;
}
else
{
cout<<"Create Remote Thread Success!"<<endl;
returnTRUE;
}
}

Payload:要注入的DLL

在网上搜索了一些关于DLL注入的资料,发现都没有被注入的DLL的实现,这里首先占用少量篇幅来说明DLL的实现。这个DLL和一般的DLL实现方式一样,只不过是需要在DLL加载起来的时候,就要执行一些函数。在DllMain中的switch中的DLL_PRPCESS_ATTACH分支下,使用CreateThread函数,对要执行的函数创建进程。

caseDLL_PROCESS_ATTACH:
std::cout<<"DLL_PROCESS_ATTACH"<<std::endl;
hThread=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)inj, NULL, 0, &dwThreadId);

要执行的函数如下,为了方便使用如PCHunter类工具进行监控,所以该函数实现了一个发送tcp连接包的功能。

voidTryConnect()
{
WSADATAwsa;
if(WSAStartup(MAKEWORD(1, 1), &wsa) !=0)
{
return;
}
SOCKETm_socket=socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_INSocketSendIn;
SocketSendIn.sin_family=AF_INET;
SocketSendIn.sin_addr.S_un.S_addr=inet_addr("114.114.114.114");
SocketSendIn.sin_port=htons(53);
connect(m_socket, (SOCKADDR*)&SocketSendIn, sizeof(SOCKADDR));
closesocket(m_socket);
WSACleanup();
}

当DLL第一次被映射到进程的地址空间时,会调用DllMain函数,函数的ul_reason_for_call参数的值为DLL_PROCESS_ATTACH。由于加载后并未有线程创建,所以我们需要手动创建一个线程,线程函数即为我们想要执行的函数。创建线程后,会再次调用DllMain函数,此时 ul_reason_for_call参数的值为DLL_THREAD_ATTACH,只有当DLL处理完这一通知后,系统才允许执行线程函数。

参考链接:https://bbs.pediy.com/thread-224078.htm

DLL注入在32位的注入程序要注入32位的进程中,不要试图注入64位进程,这种注入确实是可以实现,但是很麻烦,所以就放到最后去学习。而反射式DLL我理解就是让DLL自身不使用LoadLibraryA函数,将自身映射到目标进程内存中

LoadLibraryA函数

含义:将指定的模块加载到调用进程的地址空间中。指定的模块可能会导致其他模块被加载。对于其他加载选项,请使用 LoadLibraryEx函数(文章:参考链接

使用语法

HMODULELoadLibraryA(
LPCTSTRlpLibFileName//模块的的的名字

FARPROCGetProcAddress(
HMODULEhModule, // DLL模块句柄
LPCSTRlpProcName// 函数名
);

函数参数

lpFileName [in](模块的名称,这可以是库模块(.dll文件)或可执行模块(.exe文件)):指定的名称是模块的文件名,与模块定义(.def)文件中的LIBRARY关键字所指定的与库模块本身中存储的名称无关。

如果字符串指定完整路径,则该函数仅搜索该模块的路径

如果字符串指定一个没有路径的模块名称或者相对路径,则该函数使用标准搜索策略来查找模块

如果该功能找不到该模块,则该功能失败。指定路径时,一定要使用反斜杠(\),而不是正斜杠(/)

如果字符串指定了没有路径的模块名称,并且省略了文件扩展名,则函数会将缺省库扩展名.dll附加到模块名称。获取函数地址步骤如下:

  1. 得到PE头的地址

  2. 得到导出函数表结构体指针的地址

  3. 获取导出表结构体的内存地址(RVA)

  4. 找到导出表名称数组在内存中的地址(RVA)

  5. 获取导出函数地址表在内存中的地址(RVA)

  6. 获取导出函数序号表在内存中的地址(RVA)

  7. 通过导出函数名来获取导出函数地址(RVA)

反射式DLL注入

回想我们注射器实现的过程中所调用的函数,与正常的注入似乎没有太大的区别,而且像CreateRemoteProcess这种危险函数杀软抓的很严,是可以被替换掉的,而且没有发现LoadLibraryA函数。但这个样本有明显的特征:解析PE结构,所以当我们遇到这种样本的时候,可以考虑为反射式DLL注入。如果怀疑存在反射式DLL注射,进入到这个函数的时候,可以去思考这是不是在解析PE文件。当然,如果动态跟踪的话,及时查看内存,也可以分辨的出来。(GitHub地址:https://github.com/SudoZhange/ProcessInjection

原理:将已经注入到目标进程的DLL加载到内存,实现LoadLibrary的功能。

步骤:这里描述下大体的流程

  1. 获取目标进程PEB,从而获取一些需要用到的函数地址,如:VirtualAlloc

  2. 复制PE头,由于PE头的形态并没有像节一样需要展开,所以为复制

  3. 解析PE头,并加载节,与2不一样的是,这里用的是加载,到了节这里,已经在PE头中的信息指定了RVA,所以这里要进行“加载”

  4. 处理导入表,获取导入函数的地址

  5. 处理重定位表,由于基址和默认的加载地址不同,所以需要修改重定位表,否则,程序内的直接寻址会出问题

  6. 调用镜像入口点,到这里,镜像已经加载完毕

小提示:由于直接编写DLL,直接进行反射加载,无法用VS进行调试,所以我之前新建了一个可执行的项目,在该项目中,实现了加载的功能,后续只需将函数导出,和变换下加载的DLL即可。

详细步骤

首先,获取DLL起始位置。

//caller功能:获取当前指令的下一条指令的地址。
uiLibraryAddress=caller();
while(TRUE)
{
if(((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic==IMAGE_DOS_SIGNATURE)
{
//pe头偏移RVA
uiHeaderValue=((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
//判断PE头的正确性
if(uiHeaderValue>=sizeof(IMAGE_DOS_HEADER) &&uiHeaderValue<1024)
{
//pe头在内存中的位置
uiHeaderValue+=uiLibraryAddress;
//如果找到文件头就退出循环
if(((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature==IMAGE_NT_SIGNATURE)
break;
}
}
uiLibraryAddress--;
}

我在调试的可执行的Demo中,更改的。

//callAddress:在缓冲区开辟空间的起始地址,在原注入中uiLibraryAddress = caller();0x10偏移是为了模拟寻找起始地址的过程
uiLibraryAddress=callAddress+0x10;

获取目标进程PEB,获取需要的函数地址,需要的函数有:VirtualAlloc(用来为镜像要加载的地址分配空间)、LoadLibraryA(处理导入表)、GetProcAddress(同上)、NtFlushInstructionCache(刷新数据,让CPU执行新指令)。

0X05结尾

今天的分享就到这里了,如有不对,欢迎指正。参考链接如下所示:

https://thedfirreport.com/2021/08/29/cobalt-strike-a-defenders-guide

https://blog.csdn.net/weixin_40412037/article/details/126354860

https://thedfirreport.com/2021/08/29/cobalt-strike-a-defenders-guide/

https://cloud.tencent.com/developer/article/1927027

https://unit42.paloaltonetworks.com/brute-ratel-c4-tool/

https://www.secrss.com/articles/27850

https://github.com/stephenfewer/ReflectiveDLLInjection

https://github.com/sud01oo/ProcessInjection

https://www.trustedsec.com/blog/a-developers-introduction-to-beacon-object-files/

# dll注入 # Cobalt strike # cobalt strike快速上手 # C2 # C2通信
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录