Cobalt Strike的几个功能原理分析

本文首发于T00LS社区,未经许可禁止转载。

Cobalt Strike可以说是目前最流行的红队工具,其设计和开发者也是拥有多年经验的红队人员,所以了解其设计思想和技术是很有必要的。本文将对Cobalt Strike的一些功能进行简单分析,其中某些技术可能已经过时或不适用。

进程参数欺骗

argue [command] [fake arguments]

eg:
argue net1 /test /test /test /test /test
run net1 user admin admin12345 /add

Cobalt Strike有一个beacon命令可以进行进程参数欺骗,从而绕过防护软件进行敏感操作,如添加用户等。
通过Sysmon日志可以看到添加用户时的命令行参数:
20210822153830QQ截图20210822153744
如果使用参数欺骗:
20210822154049QQ截图20210822154029
可以看到记录的参数已经改变了:
20210822154221QQ截图20210822154159

这项技术被用于各种恶意软件,后面集成到了Cobalt Strike中。
许多语言都可以在主函数处接收参数,以C语言为例:

int main(int argc, char* argv[])
{
	printf("%s \n",argv[0]);
	return 0;
}

主函数一般是一个程序的入口,操作系统初始化后就转到入口点执行程序,入口点由连接器设置,如在VC++下,控制台程序入口点函数为mainCRTStartup,此函数再调用我们写的main函数。
Visual Studio新建一个C++控制台项目,搜索mainCRTStartup,追溯调用,可以来到__scrt_common_main_seh,其中调用了invoke_main,也就是调用了main函数。不同编译器在上述流程中会有差异。

Windows程序获得参数通常有两种方式:

  1. main(int argc, char* argv[])的形式
  2. 使用WINAPIGetCommandLine获得指向参数的指针

前者一般是控制台程序所用,后者则是GUI应用程序使用,GUI应用程序的主函数通常为WinMain

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);

参数pCmdLine就是用来获取命令行参数的。

那么WINAPIGetCommandLine的从哪里得到参数的?通过调试可以得知,GetCommandLinePEB(进程环境块)中获得参数,PEB中存放了很多全局信息。如果修改PEB中的相关信息,就可以达到参数欺骗的目的。

PEB将命令行参数存放在RTL_USER_PROCESS_PARMETERS结构的CommandLine中,它是一个UNICODE_STRING类型的值:

typedef struct _UNICODE_STRING {
	USHORT Length;
	USHORT MaximumLength;
	PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

可以看到Buffer参数是存放内容的缓冲区,进程检测程序会根据Length来获取缓冲区内容,但如果把Length设为小于缓冲区真正长度时,进程检测程序就只会根据长度读取内容,而实际执行时则用的是缓冲区全部内容。

通过以下几步来实现这一技术:

  1. 使用假命令行参数,以暂停状态启动一个进程
  2. 修改PEB以填充真正的参数
  3. 恢复进程

@xpn给了一个代码示例,比较短:argument_spoofing.cpp

阻止非微软签名的DLL

blockdlls命令可以阻止beacon子进程加载任何非微软签名的DLL,这可以避免被某些安全软件通过加载DLL的方式检测到。
此方法主要利用UpdateProcThreadAttribute函数实现:

BOOL UpdateProcThreadAttribute(
  LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
  DWORD                        dwFlags,
  DWORD_PTR                    Attribute,
  PVOID                        lpValue,
  SIZE_T                       cbSize,
  PVOID                        lpPreviousValue,
  PSIZE_T                      lpReturnSize
);

其中Attribute参数可以设为PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,再将lpValue置为PROCESS_CREATION_MITIGATION_POLICY_FONT_DISABLE_ALWAYS_ON,则代表子进程缓解措施阻止未正确签名的EXE或DLL,这在Windows 8和Windows Server 2012之前不受支持。
通过beacon派生出来的进程可以受到此策略的保护,但是beacon的父进程并不受保护,这点需要注意。

PPID父进程欺骗

ppid [pid] 将指定进程作为父进程

PPID即父进程号,进程链也是杀软判断的一个依据。例如使用powershell脚本上线,默认情况下beacon进程是在powershell.exe进程下,很容易被发现异常。
CreateProcess函数中有一个参数lpStartupInfo

BOOL CreateProcessA(
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

它指向STARTUPINFOSTARTUPINFOEX结构,后者是这样的:

typedef struct _STARTUPINFOEXA {
  STARTUPINFOA                 StartupInfo;
  LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;

lpAttributeListInitializeProcThreadAttributeList 函数创建,UpdateProcThreadAttribute函数可以对其添加属性,有一个PROC_THREAD_ATTRIBUTE_PARENT_PROCESS属性,属性值为一个指向进程句柄的指针,用来作为父进程,通过设置这个值就可以达到父进程PPID欺骗的目的。

原作者给出了C++的代码示例,还有人写出了PowerShell版本