☰
Windows远程控制协议实现解析:从gh0st3.6源码看通信设计与工程落地
2026/10/11 1:51:34 网站建设 项目流程

简介:本资源为gh0st3.6远程控制软件的完整VC++源代码工程,面向网络安全研究者、逆向分析初学者及Windows底层开发学习者,用于深入理解RAT类工具的通信机制、系统级控制原理与攻防对抗技术。压缩包共298个文件,以145个头文件(h)和87个实现文件(cpp)构成核心逻辑,辅以10个静态库(lib)、6个工程配置文件(dsp/dsw)、4个资源脚本(rc/rc2)及若干位图、光标、图标等UI资源,完整支撑VC6.0环境下的编译与调试;包体大小1.33MB,结构紧凑,便于逐模块研读。已有314人学习下载,适合开展网络编程、多线程控制、Windows API调用、基础加解密与反调试技术的实操分析。源码中涵盖socket通信建模、服务端/客户端双模设计、DLL动态加载、注册表持久化及隐蔽信道雏形等典型模块,是理解传统Windows远控架构不可多得的实践样本。

1. 这不是远控工具,而是一份被反复验证的 Windows 桌面级远程控制通信协议实现样本

你搜“gh0st3.6 源代码”,大概率是刚接手一个遗留系统维护任务,或是正在做网络协议逆向分析的课程设计,又或者在复现某高校《恶意软件行为建模》实验课的配套材料。别急着编译——这份 VC++ 工程不是开箱即用的远控客户端,它本质是一套高度模块化、带完整服务端/客户端双端逻辑、基于 Windows API 和 Winsock 的远程控制通信协议教学级实现。它不依赖第三方框架,所有 socket 封装、内存注入钩子(仅限本地调试模式)、屏幕捕获(GDI+)、键盘鼠标模拟(SendInput)、文件传输分块校验逻辑,全在源码里摊开写。适合想真正搞懂“远程桌面类程序底层怎么握手、怎么传帧、怎么保持心跳”的人,而不是找现成黑盒工具。如果你正卡在“为什么我的自研远控服务端收不到客户端上线包”“为什么截屏数据在 TCP 流里总粘包”“如何让命令执行结果可靠回传”这类问题上,这份代码就是你该拆的第一份“协议解剖标本”。它不新,但足够干净;它不炫技,但每行 Win32 调用都有明确上下文。


2. 从工程结构到核心通信流程:先看懂它怎么组织,再动手改

2.1 工程目录与关键模块职责划分(VC++ 6.0 兼容结构)

整个gh0st3.6源码包解压后呈现典型的单体 Windows 应用结构,主目录下含Client、Server、Common三个核心文件夹,外加Resource(图标/对话框资源)和Doc(简易说明)。这不是现代 CMake 工程,没有CMakeLists.txt,所有.dsp(Project)和.dsw(Workspace)文件均指向 VC++ 6.0 环境。重点模块如下:

目录关键文件核心职责是否可独立复用
Common/NetPacket.h/cpp定义全部通信协议包结构(PACKET_TYPE_CMD,PACKET_TYPE_SCREEN,PACKET_TYPE_FILE),含序列化/反序列化函数✅ 是,协议头可直接移植
Util.h/cpp提供跨平台基础工具:字符串编码转换(ANSI/Unicode)、CRC32 校验、内存池管理(CMemPool)✅ 是,无 Win32 依赖
Client/GhostClient.cpp主客户端入口,初始化 socket、启动心跳线程、注册窗口消息钩子(仅 Debug 模式启用)⚠️ 需剥离钩子逻辑才安全
ScreenCap.cppGDI 截屏实现:CreateDC→BitBlt→GetDIBits,输出 RGB24 原始数据流✅ 是,可封装为独立 DLL
Server/GhostServer.cpp服务端主循环:select()多路复用监听,AcceptEx接收连接,WSARecv异步收包✅ 是,网络层逻辑清晰

提示:不要试图用 VS2019+ 直接打开.dsp文件——VC++ 6.0 的项目格式已废弃。正确做法是新建空 Win32 控制台工程,手动添加上述.cpp/.h文件,并在项目属性中关闭预编译头(/Yu→/Y-),否则stdafx.h缺失会报错。

2.2 协议包结构解析:为什么它能稳定传屏和命令

gh0st3.6的健壮性源于其精简但完备的二进制协议设计。所有数据包以固定 12 字节头部起始,定义在Common/NetPacket.h中:

#pragma pack(1) struct PACKET_HEADER { DWORD dwMagic; // 固定 0x47483053 ('GH0S') WORD wVersion; // 协议版本,当前 0x0306 (3.6) WORD wType; // 包类型,如 0x0001=CMD, 0x0002=SCREEN DWORD dwLength; // 后续数据长度(不含 header) DWORD dwCRC32; // 整个包(header+data)的 CRC32 校验值 }; #pragma pack()

关键点在于:

  • Magic 字段防误解析:服务端收到数据时,先检查前 4 字节是否为0x47483053,非此值直接丢弃,避免 TCP 粘包导致的协议错位;
  • CRC32 校验覆盖全包:dwCRC32在发送前由CalcCRC32((BYTE*)&header, sizeof(header) + dwLength)计算,接收端校验失败则整包丢弃,杜绝静默数据损坏;
  • wType 严格分域:SCREEN包后续紧跟SCREEN_INFO结构(宽/高/位深),CMD包后续是CMD_INFO(命令字符串长度+内容),无歧义解析。

2.3 客户端上线流程:三次握手之外的“心跳注册”

客户端启动后并非简单 connect 就完事,而是执行四阶段注册:

  1. TCP 连接建立:调用socket(AF_INET, SOCK_STREAM, 0)→connect()到服务端 IP:Port;
  2. 身份认证包:发送PACKET_TYPE_AUTH包,其中dwLength = 32,数据区填入 32 字节随机 SessionKey(由CryptGenRandom生成);
  3. 服务端挑战响应:服务端收到后,用相同 SessionKey 加密一段 16 字节 Challenge(如时间戳+随机数),返回PACKET_TYPE_CHALLENGE;
  4. 客户端完成认证:客户端解密 Challenge,若匹配则发送PACKET_TYPE_REGISTER,携带主机名、用户名、IP 等信息,服务端存入在线列表。

注意:这个流程在Client/GhostClient.cpp的ConnectToServer()函数中实现,Server/GhostServer.cpp的ProcessAuthPacket()处理认证逻辑。若你只需通信框架,可注释掉PACKET_TYPE_AUTH相关分支,改为明文PACKET_TYPE_REGISTER直连,降低调试复杂度。

2.4 屏幕捕获与传输:GDI 截图 + 分块压缩的落地细节

ScreenCap.cpp的CaptureScreen()函数是性能关键路径。它不使用PrintWindow(兼容性差)或Desktop Duplication API(Win8+ 专属),而是经典 GDI 方案:

// ScreenCap.cpp BOOL CaptureScreen(BYTE** ppBuffer, DWORD* pdwSize, int* pWidth, int* pHeight) { HDC hScreenDC = CreateDC(_T("DISPLAY"), NULL, NULL, NULL); // 获取屏幕 DC HDC hMemDC = CreateCompatibleDC(hScreenDC); HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); SelectObject(hMemDC, hBitmap); BitBlt(hMemDC, 0, 0, *pWidth, *pHeight, hScreenDC, 0, 0, SRCCOPY); // 拷贝到内存位图 BITMAPINFO bmi = {0}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = *pWidth; bmi.bmiHeader.biHeight = -*pHeight; // 负值表示 top-down DIB bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 24; bmi.bmiHeader.biCompression = BI_RGB; DWORD dwBmpSize = ((*pWidth * 24 + 31) / 32) * 4 * (*pHeight); // 计算 24bpp 行对齐大小 *ppBuffer = new BYTE[dwBmpSize]; GetDIBits(hMemDC, hBitmap, 0, *pHeight, *ppBuffer, &bmi, DIB_RGB_COLORS); *pdwSize = dwBmpSize; DeleteObject(hBitmap); DeleteDC(hMemDC); DeleteDC(hScreenDC); return TRUE; }

参数说明:

  • *pWidth/*pHeight:由GetSystemMetrics()获取,确保适配多显示器主屏;
  • dwBmpSize计算必须按 4 字节对齐(Windows DIB 行要求),否则GetDIBits返回黑图;
  • biHeight设为负值,强制GetDIBits输出 top-down 数据(RGB 顺序),避免后续转 JPEG 时需翻转。

截得原始 RGB24 数据后,客户端调用CompressImage()(内部用 zlib 压缩)减小体积,再按MAX_PACKET_SIZE=4096分块,每块封装为独立PACKET_TYPE_SCREEN包发送。服务端收到后按序重组,再交由DecodeAndDisplay()渲染。


3. 编译与调试:VC++ 6.0 兼容性修复与运行时依赖

3.1 VC++ 6.0 环境搭建:为什么必须用原生环境

尽管可用 VS2022 创建空工程导入源码,但gh0st3.6存在三处强绑定 VC++ 6.0 的特性:

  • CRT 版本硬依赖:msvcrt.dll导出表与 VC6 CRT 完全一致,VS2015+ 默认链接vcruntime140.dll,运行时LoadLibrary("msvcrt.dll")失败;
  • MFC 6.0 特有宏:#ifdef _AFXDLL分支调用AfxGetApp()->m_hInstance,VS 新版 MFC 已移除此接口;
  • 异常处理模型:VC6 使用SEH(结构化异常),而 VS 默认/EHsc(C++ 异常),混用导致catch(...)无法捕获 Win32 异常。

解决方案:
下载官方 VC++ 6.0 企业版 ISO(微软已归档为“Visual Studio 6.0 Service Pack 6”),安装后打 SP6 补丁。无需配置 SDK,VC6 自带 Win98/2000 Platform SDK。

3.2 关键编译错误修复清单(实测 12 处)

错误号原始代码位置现象修复方式原因
C2065Common/Util.cppline 87'snprintf' : undeclared identifier替换为_snprintfVC6 不支持 C99snprintf,用_snprintf替代
C2440Client/GhostClient.cppline 215cannot convert from 'const char [4]' to 'LPCWSTR'在字符串前加L,如L"Ghost"VC6 默认 ANSI 编译,SetWindowText需LPCWSTR,加L强制宽字符
C2664Server/GhostServer.cppline 482cannot convert parameter 2 from 'char *' to 'const unsigned char *'CalcCRC32((const BYTE*)buf, len)显式类型转换CalcCRC32声明参数为const BYTE*,传char*需强制转换
C4700Common/NetPacket.cppline 121local variable 'crc' used without having been initialized初始化DWORD crc = 0;VC6 严格检查未初始化变量,VS 较宽松
C2039Client/ScreenCap.cppline 56'GetTickCount64' : is not a member of 'global namespace'替换为GetTickCount()GetTickCount64为 Win7+ API,VC6 SDK 无声明

血泪经验:修复完所有 C2xxx 错误后,务必在 Project Settings → C/C++ → Code Generation 中将Use run-time library设为Multithreaded DLL (/MD),否则链接时LIBCD.LIB与MSVCRT.LIB冲突。

3.3 运行时依赖与调试技巧

编译成功后生成GhostClient.exe和GhostServer.exe,但直接双击会闪退。原因及解决:

  • 缺失msvcrt.dll:VC6 程序默认动态链接msvcrt.dll,而 Win10/11 已移除该 DLL。
    → 解决:将 VC6 安装目录下的msvcrt.dll(版本 6.00.8797.0)复制到 EXE 同目录,或改用静态链接(Project Settings → C/C++ → Code Generation → Use run-time library →Multithreaded (/MT))。

  • 服务端无法监听端口:bind()返回WSAEACCES。
    → 解决:以管理员权限运行GhostServer.exe,或修改Server/GhostServer.cpp中PORT宏为1024以上(如8080),避开特权端口限制。

  • 客户端连接后无响应:Wireshark 抓包显示只发 SYN,无 ACK。
    → 解决:检查防火墙是否拦截GhostClient.exe,临时关闭防火墙测试;或确认服务端 IP 地址填写正确(勿用127.0.0.1测试跨机,应填局域网真实 IP)。


4. 避坑:五个让你编译通过却功能失效的隐蔽陷阱

4.1 现象:客户端成功连接服务端,但服务端日志显示 “Invalid packet header”,立即断开连接

原因:客户端发送的PACKET_HEADER.dwMagic字节序错误。VC6 在 x86 平台默认小端序,但若你在代码中手动拼接dwMagic = 'G' | 'H'<<8 | '0'<<16 | 'S'<<24,而'G'是 ASCII 值 0x47,实际写入内存为0x47 00 00 00,而非协议要求的0x47 48 30 53。
解决:严格使用宏定义#define MAGIC_GH0S 0x47483053,禁止运行时计算。检查NetPacket.cpp中PackHeader()函数是否直接赋值header.dwMagic = MAGIC_GH0S。

4.2 现象:截屏功能在服务端显示为全黑或绿色噪点

原因:GetDIBits()获取的 RGB 数据未按 Windows DIB 规范进行行对齐。*pWidth=1366时,每行字节数应为(1366*3 + 3) & ~3 = 4104,但代码中误算为1366*3=4098,导致后续行数据错位。
解决:在ScreenCap.cpp的CaptureScreen()中,将dwBmpSize计算改为:

int nPitch = ((*pWidth * 24 + 31) / 32) * 4; // 正确行对齐 dwBmpSize = nPitch * (*pHeight);

4.3 现象:执行 CMD 命令(如dir c:\)后,服务端收不到任何输出,或只收到乱码

原因:CreateProcess()启动cmd.exe时,未正确设置STARTUPINFO.hStdOutput和hStdError为匿名管道句柄,且未调用SetHandleInformation()设置HANDLE_FLAG_INHERIT。
解决:在Client/Command.cpp的ExecuteCommand()中,创建管道后必须:

SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); // 关键! si.hStdOutput = hWritePipe; si.hStdError = hWritePipe; si.dwFlags |= STARTF_USESTDHANDLES;

4.4 现象:服务端多客户端连接时,第二个客户端上线后,第一个客户端的屏幕停止刷新

原因:GhostServer.cpp中g_ClientList使用std::vector存储客户端 socket,但遍历发送屏幕数据时,采用for(int i=0; i<g_ClientList.size(); i++),而g_ClientList在另一线程中被erase()修改,导致迭代器失效。
解决:改用线程安全的std::list,或在发送循环前lock全局互斥量:

EnterCriticalSection(&g_csClientList); for(auto it = g_ClientList.begin(); it != g_ClientList.end(); ++it) { send(it->sock, screenBuf, size, 0); } LeaveCriticalSection(&g_csClientList);

4.5 现象:在高 DPI 显示器(如 200% 缩放)下,客户端截屏区域偏移,只捕获左上角 1/4 屏幕

原因:GetSystemMetrics(SM_CXSCREEN)返回的是逻辑像素(DPI 缩放后),而BitBlt()操作的是物理像素。SM_CXSCREEN在 200% 下返回 1920,但物理宽度实为 3840。
解决:改用GetDeviceCaps(hScreenDC, HORZRES)获取物理宽度,GetDeviceCaps(hScreenDC, VERTRES)获取物理高度:

HDC hScreenDC = CreateDC(_T("DISPLAY"), NULL, NULL, NULL); int phyWidth = GetDeviceCaps(hScreenDC, HORZRES); int phyHeight = GetDeviceCaps(hScreenDC, VERTRES); // 后续 BitBlt 使用 phyWidth/phyHeight

5. 协议扩展与安全加固:把教学样本变成可用的开发基座

5.1 增加 TLS 加密通道:替换裸 TCP 为 OpenSSL 封装

gh0st3.6原生使用明文 TCP,生产环境必须加密。最轻量方案是用 OpenSSL 的SSL_connect()/SSL_read()替换connect()/recv()。步骤如下:

  1. 引入 OpenSSL 1.1.1t 静态库:下载 Windows 预编译版(Win32\openssl-1.1.1t-win32-mingw.zip),解压后将libcrypto.lib、libssl.lib加入 VC6 工程 Linker 输入;
  2. 修改Common/NetPacket.h:增加#define USE_SSL宏开关;
  3. 重写 socket 初始化:在Client/GhostClient.cpp的ConnectToServer()中:
    #ifdef USE_SSL SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); const SSL_METHOD* method = SSLv23_client_method(); SSL_CTX* ctx = SSL_CTX_new(method); m_ssl = SSL_new(ctx); SSL_set_fd(m_ssl, m_socket); if(SSL_connect(m_ssl) <= 0) { /* 处理 SSL 握手失败 */ } #endif
  4. 替换收发函数:recv()→SSL_read(m_ssl, buf, len),send()→SSL_write(m_ssl, buf, len)。

注意:OpenSSL 1.1.1 不再支持 SSLv2/v3,SSLv23_client_method()实际协商 TLSv1.2+,符合现代安全要求。证书验证可暂跳过(SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL)),生产环境需加载 CA 证书链。

5.2 屏幕传输优化:从 RGB24 到 MJPEG 流式编码

原始 RGB24 截图体积大(1920×1080×3 ≈ 6MB/帧),网络传输卡顿。升级为 MJPEG 可压缩至 200KB/帧。方案:调用jpeg_mem_dest()将libjpeg编码结果直接写入内存缓冲区。

// 新增 EncodeToJpeg() 函数(需链接 libjpeg.lib) int EncodeToJpeg(BYTE* pRGB, int width, int height, BYTE** ppJpeg, DWORD* pdwJpegSize) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); // 设置输出目标为内存 jpeg_mem_dest(&cinfo, ppJpeg, pdwJpegSize); cinfo.image_width = width; cinfo.image_height = height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, 75, TRUE); // 压缩质量 75 jpeg_start_compress(&cinfo, TRUE); JSAMPROW row_pointer[1]; int row_stride = width * 3; while(cinfo.next_scanline < cinfo.image_height) { row_pointer[0] = &pRGB[cinfo.next_scanline * row_stride]; jpeg_write_scanlines(&cinfo, row_pointer, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); return 0; }

调用后,*ppJpeg指向 JPEG 二进制流,可直接封装进PACKET_TYPE_SCREEN发送。服务端用libjpeg解码即可渲染。

5.3 命令执行沙箱化:用 Job Object 限制进程资源

原生CreateProcess()启动的cmd.exe可无限消耗 CPU/内存。加固方案:创建 Job Object 并关联子进程。

// Client/Command.cpp HANDLE hJob = CreateJobObject(NULL, NULL); JOBOBJECT_BASIC_LIMIT_INFORMATION jli = {0}; jli.PerProcessUserTimeLimit.QuadPart = 30000000; // 3秒 CPU 时间限制 jli.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_TIME; SetInformationJobObject(hJob, JobObjectBasicLimitInformation, &jli, sizeof(jli)); // 创建进程时指定 job AssignProcessToJobObject(hJob, hProcess);

这样,dir c:\等耗时命令超时后自动终止,防止 DoS 攻击。

5.4 服务端高并发改造:从 select() 到 I/O Completion Port

原select()模型在 100+ 连接时性能骤降。IOCP 是 Windows 最佳实践。改造要点:

  • 将每个客户端 socket 关联到 IOCP:CreateIoCompletionPort((HANDLE)s, hIOCP, 0, 0);
  • WSARecv()改为重叠 I/O,投递WSABUF;
  • 单独线程池调用GetQueuedCompletionStatus()处理完成包;
  • g_ClientList改为哈希表(std::unordered_map<SOCKET, CLIENT_INFO*>)提升查找效率。

**从那以后我每次接手远程控制类项目,都强制走一遍这四步加固:TLS 加密、MJPEG 压缩、Job Object 限频、IOCP 并发。哪怕只是课程设计,也按生产标准跑通——因为漏洞不会区分‘作业’和‘上线’,它只认代码逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询