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日志可以看到添加用户时的命令行参数:
如果使用参数欺骗:
可以看到记录的参数已经改变了:
这项技术被用于各种恶意软件,后面集成到了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程序获得参数通常有两种方式:
main(int argc, char* argv[])
的形式- 使用WINAPI
GetCommandLine
获得指向参数的指针
前者一般是控制台程序所用,后者则是GUI应用程序使用,GUI应用程序的主函数通常为WinMain
:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);
参数pCmdLine
就是用来获取命令行参数的。
那么WINAPIGetCommandLine
的从哪里得到参数的?通过调试可以得知,GetCommandLine
从PEB
(进程环境块)中获得参数,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
设为小于缓冲区真正长度时,进程检测程序就只会根据长度读取内容,而实际执行时则用的是缓冲区全部内容。
通过以下几步来实现这一技术:
- 使用假命令行参数,以暂停状态启动一个进程
- 修改PEB以填充真正的参数
- 恢复进程
@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
);
它指向STARTUPINFO
或STARTUPINFOEX
结构,后者是这样的:
typedef struct _STARTUPINFOEXA {
STARTUPINFOA StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;
lpAttributeList
由 InitializeProcThreadAttributeList 函数创建,UpdateProcThreadAttribute
函数可以对其添加属性,有一个PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
属性,属性值为一个指向进程句柄的指针,用来作为父进程,通过设置这个值就可以达到父进程PPID欺骗的目的。
原作者给出了C++的代码示例,还有人写出了PowerShell版本。