socket的探究与实现
字数 1330 2025-08-07 08:22:33

Socket编程深入解析与实现

一、Socket基础概念

Socket(套接字)是应用程序通过网络进行通信的端点,是对TCP/IP协议的封装。在病毒木马开发中,Socket常用于数据传输和回传。

Socket缓冲区机制

  • 每个Socket创建时都会分配两个缓冲区:输入缓冲区和输出缓冲区
  • write()/send()不直接发送数据,而是先写入输出缓冲区
  • read()/recv()从输入缓冲区读取数据,而非直接从网络读取
  • 缓冲区特性:
    • 每个TCP套接字有独立缓冲区
    • 创建套接字时自动生成
    • 关闭套接字会继续发送输出缓冲区数据
    • 关闭套接字会丢失输入缓冲区数据

二、阻塞模式详解

发送数据时的阻塞

  1. 检查缓冲区空间,不足则阻塞直到空间足够
  2. 缓冲区被锁定时阻塞直到解锁
  3. 大数据分批次写入
  4. 所有数据写入缓冲区后函数返回

接收数据时的阻塞

  1. 检查缓冲区,无数据则阻塞直到数据到达
  2. 读取长度小于缓冲区数据时不能全部读出
  3. 读取到数据后函数才返回

三、TCP粘包问题

产生原因

  • 接收和发送无关,read()/recv()会尽可能多接收数据
  • 多次发送可能被合并接收,缺乏边界标识

示例问题

客户端发送学号1和3,服务器可能错误接收为13

解决方案

  • 需要应用层协议设计边界标识
  • 固定长度数据包
  • 特殊分隔符
  • 长度前缀

四、TCP连接建立过程(三次握手)

  1. 客户端发送SYN包

    • 设置SYN标志位
    • 生成随机序号Seq=1000
    • 进入SYN-SEND状态
  2. 服务器响应SYN-ACK包

    • 设置SYN和ACK标志位
    • 生成随机序号Seq=2000
    • Ack=客户端Seq+1=1001
    • 进入SYN-RECV状态
  3. 客户端发送ACK包

    • 设置ACK标志位
    • Ack=服务器Seq+1=2001
    • 进入ESTABLISHED状态
    • 服务器收到后也进入ESTABLISHED状态

五、Windows下的Socket编程(Winsock)

版本区别

  • Winsock1: WINSOCK.H + WSOCK32.LIB
  • Winsock2: WINSOCK2.H + WS2_32.LIB
  • 扩展功能: MSWSOCK.H + MSWSOCK.LIB

基本API

  1. socket() - 创建套接字

    SOCKET WSAAPI socket(
      [in] int af,       // 地址族,如AF_INET
      [in] int type,     // 类型,如SOCK_STREAM
      [in] int protocol  // 协议,通常为0
    );
    
  2. bind() - 绑定地址

    int bind(
      [in] SOCKET s,
      const sockaddr *addr,
      [in] int namelen
    );
    
  3. listen() - 监听连接

    int WSAAPI listen(
      [in] SOCKET s,
      [in] int backlog  // 最大挂起连接数
    );
    
  4. accept() - 接受连接

  5. connect() - 连接服务器

  6. send()/recv() - 数据收发

六、服务端实现代码

初始化Winsock

WSADATA wsadata = {0};
WORD w_version_req = MAKEWORD(2, 2);
WSAStartup(w_version_req, &wsadata);

创建Socket

SOCKET g_ServerSocket = socket(AF_INET, SOCK_STREAM, NULL);

绑定地址

sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(port);
ServerAddr.sin_addr.S_un.S_addr = inet_addr(ipaddr);
bind(g_ServerSocket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr));

监听连接

listen(g_ServerSocket, 5);  // 最多5个挂起连接

接受连接

sockaddr_in addr = {0};
int dwLength = sizeof(addr);
g_clientsocket = accept(g_ServerSocket, (sockaddr*)(&addr), &dwLength);

数据接收

char szBuffer[MAX_PATH] = {0};
int Ret = recv(g_clientsocket, szBuffer, MAX_PATH, 0);

数据发送

send(g_clientsocket, cmd, (strlen(cmd)+1), 0);

七、客户端实现代码

客户端与服务端的主要区别:

  • 不需要bind()listen()
  • 直接使用connect()连接服务器

连接服务器

connect(g_ServerSocket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr));

多线程接收

CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, NULL, NULL);

八、进程间通信实现命令结果返回

共享内存方法(不适用)

  • system()函数不返回执行结果到变量
  • 无法通过共享内存获取命令输出

匿名管道方法(推荐)

HANDLE hRead, hWrite;
SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);

CreatePipe(&hRead, &hWrite, &sa, 0);

STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = hRead;
si.hStdOutput = hWrite;
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);

CreateProcessA(NULL, lpscmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

CloseHandle(hWrite);
WaitForSingleObject(pi.hThread, -1);
WaitForSingleObject(pi.hProcess, -1);

char lpsRetBuffer[4096] = {0};
DWORD RetBufferSize = 0;
ReadFile(hRead, lpsRetBuffer, 4096, &RetBufferSize, NULL);

CloseHandle(hRead);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

九、完整实现效果

  1. 服务端启动监听
  2. 客户端连接服务端
  3. 服务端发送命令
  4. 客户端执行命令并通过管道返回结果
  5. 服务端显示客户端返回的命令执行结果

通过匿名管道实现了命令执行结果的回传,解决了原始方案中无法获取命令输出结果的问题。

Socket编程深入解析与实现 一、Socket基础概念 Socket(套接字)是应用程序通过网络进行通信的端点,是对TCP/IP协议的封装。在病毒木马开发中,Socket常用于数据传输和回传。 Socket缓冲区机制 每个Socket创建时都会分配两个缓冲区:输入缓冲区和输出缓冲区 write()/send() 不直接发送数据,而是先写入输出缓冲区 read()/recv() 从输入缓冲区读取数据,而非直接从网络读取 缓冲区特性: 每个TCP套接字有独立缓冲区 创建套接字时自动生成 关闭套接字会继续发送输出缓冲区数据 关闭套接字会丢失输入缓冲区数据 二、阻塞模式详解 发送数据时的阻塞 检查缓冲区空间,不足则阻塞直到空间足够 缓冲区被锁定时阻塞直到解锁 大数据分批次写入 所有数据写入缓冲区后函数返回 接收数据时的阻塞 检查缓冲区,无数据则阻塞直到数据到达 读取长度小于缓冲区数据时不能全部读出 读取到数据后函数才返回 三、TCP粘包问题 产生原因 接收和发送无关, read()/recv() 会尽可能多接收数据 多次发送可能被合并接收,缺乏边界标识 示例问题 客户端发送学号1和3,服务器可能错误接收为13 解决方案 需要应用层协议设计边界标识 固定长度数据包 特殊分隔符 长度前缀 四、TCP连接建立过程(三次握手) 客户端发送SYN包 设置SYN标志位 生成随机序号Seq=1000 进入SYN-SEND状态 服务器响应SYN-ACK包 设置SYN和ACK标志位 生成随机序号Seq=2000 Ack=客户端Seq+1=1001 进入SYN-RECV状态 客户端发送ACK包 设置ACK标志位 Ack=服务器Seq+1=2001 进入ESTABLISHED状态 服务器收到后也进入ESTABLISHED状态 五、Windows下的Socket编程(Winsock) 版本区别 Winsock1: WINSOCK.H + WSOCK32.LIB Winsock2: WINSOCK2.H + WS2_ 32.LIB 扩展功能: MSWSOCK.H + MSWSOCK.LIB 基本API socket() - 创建套接字 bind() - 绑定地址 listen() - 监听连接 accept() - 接受连接 connect() - 连接服务器 send()/recv() - 数据收发 六、服务端实现代码 初始化Winsock 创建Socket 绑定地址 监听连接 接受连接 数据接收 数据发送 七、客户端实现代码 客户端与服务端的主要区别: 不需要 bind() 和 listen() 直接使用 connect() 连接服务器 连接服务器 多线程接收 八、进程间通信实现命令结果返回 共享内存方法(不适用) system() 函数不返回执行结果到变量 无法通过共享内存获取命令输出 匿名管道方法(推荐) 九、完整实现效果 服务端启动监听 客户端连接服务端 服务端发送命令 客户端执行命令并通过管道返回结果 服务端显示客户端返回的命令执行结果 通过匿名管道实现了命令执行结果的回传,解决了原始方案中无法获取命令输出结果的问题。