
简介这是一份基于 Visual Studio 2019 环境的 Gh0st 远程控制软件完整源码工程包面向具有一定 C/C 基础的开发者、安全方向学习者以及需要研究远程管理工具实现原理的技术人员。源码工程共198个文件以 h/cpp 头文件与源文件为核心辅以 sln/vcxproj 工程配置、rc/ico/bmp/png 等界面资源以及少量依赖库压缩包约9.6MB便于直接打开工程阅读与编译。已有64人学习下载。内容涵盖远程控制常见功能模块包括文件传输、屏幕监控、音频捕捉、远程协助等完整实现并附带界面素材与工程结构说明适合用于分析远控软件的功能架构、通信协议设计以及代码组织方式也可作为二次开发或安全研究的基础参考。1. Gh0st 远控 VS2019为什么 2025 年还在折腾这段老代码一份标注着「Gh0st 远控 VS2019」的源码包2025 年还在网盘和技术社群里被反复转发并不是因为它能绕过现代安全产品而是因为这段老代码把远程控制的骨架讲得足够直白服务端负责下发指令客户端负责回传屏幕和文件中间靠一个自造的 TCP 协议串联。对做恶意代码分析、红队工具研究或者单纯想搞懂 Windows Socket 编程的人来说用 VS2019 亲手把它编译起来再在隔离环境里跑通一次最小联调比看十篇原理文章都管用。这篇笔记想做的就是「解压 → 编译 → 联调 → 抓包」这一整条路的实战记录顺便把新手最容易翻车的几个坎提前指出来。至于「2025 首发」这个说法听听就好——代码本身是十几年前的东西新的只是打包方式。2. 先看代码骨架Gh0st 的 C/S 架构、命令分发与协议指纹2.1 服务端与客户端的目录划分从 MainFrm 到 ProcessCmd 的调用链解压这类源码包里面几乎都是两个工程Server 和 Client。命名容易把人绕进去——这里的 Server 是控制端就是你手里操作的那台机器Client 是被控端上线后被操作的那台机器。我第一次看 Gh0st 时在这上面反了好半天所以先把这个对应关系钉死后面看代码才不会晕。Server 一般是 MFC 对话框程序主类名常见的是 MainFrm 或者 Gh0stDlg。它的核心逻辑就两件事维护上线主机列表以及把用户的操作转成协议指令发给对应主机。右键菜单点「屏幕查看」「文件管理」「远程 Shell」背后都是同一个套路创建一个新的功能对话框或线程把 socket 句柄传过去然后走各自的收发循环。Client 端就简单得多它没有一个花哨的界面常见做法是一个 WinMain 加一个后台主循环。关键函数一般叫 ProcessCmd有的变种叫 ProcessCommand本质上是一个巨大的 switch-case 命令分发器。命令号在头文件里定义成枚举看一眼就能列出整个恶意软件的功能清单enum CMD_NUMBER { CMD_SHELL 0x0001, // 远程 Shell CMD_SCREEN 0x0002, // 屏幕查看 CMD_FILE 0x0003, // 文件管理 CMD_KEYBOARD 0x0004, // 键盘记录 CMD_UPDATE 0x0005, // 更新自身 };这段枚举的作用是给后面所有的指令分发提供一个「编号即功能」的映射。分析恶意样本时我一般会先把这类枚举抄出来因为它是整个样本行为的目录页。逻辑说明ProcessCmd 按收到的命令号进入不同分支每个分支再调用对应的功能类比如 CScreen 负责截屏和传输CFileManager 负责列目录和传文件。参数说明命令号通常占用 2 字节网络字节序和主机字节序的问题在跨平台联调时才会暴露纯 Windows 环境下问题不大。调用链大致是这样服务端的监听线程 accept 到连接后为每个 socket 建一个接收线程接收线程先收固定长度的包头再按包头里的长度字段收载荷完整数据包交给 ProcessCmd由它创建具体的功能对象。这条链在多数 Gh0st 变种里高度一致所以读它的价值在于你只需要跟一条链走完一遍以后再看什么 AsyncRAT、Orcus 的源码会发现只是外壳变了内里的消息循环和分发逻辑如出一辙。2.2 自研协议从第一个字节的心跳包识别 Gh0stGh0st 的通信协议不复杂但很有辨识度。最明显的就是心跳包客户端上线之后会周期性发送一个固定标记的短包常见值是0x00000000或者0x00000001服务端收到后回一个对应应答。抓包时只要看到「固定长度短包 恒定间隔 固定头部字节」基本就能往 Gh0st 家族上靠。这类自制协议一般长这样字段长度作用包头标记4 字节0x00000000 或包长度命令号2 字节对应 ProcessCmd 的分支载荷长度4 字节后续数据的字节数载荷变长压缩/加密后的业务数据我分析未知变种时第一步不是看反汇编而是先抓 10 分钟流量把固定字节和可变字节分开。Gh0st 的协议头在同一个变种内几乎不变只有载荷在变。这个「先抓包再读代码」的顺序能帮你省掉大量逆向时间。另外要注意字节序有的变种把包长字段写成大端有的写成小端Wireshark 里直接看 Info 列最容易发现规律不要一上来就钻进制毒细节里。2.3 异或加密与简单压缩老代码里的两个「省事」选择Gh0st 的数据保护方式放到今天看相当原始一层异或加密加一层简单压缩。加密用的 Key 通常就硬编码在客户端某个 cpp 里算法是对称的流式异或代码写出来也就几行void XorCrypt(char* buf, int len, const char* key) { int keyLen strlen(key); for (int i 0; i len; i) { buf[i] ^ key[i % keyLen]; } }这段代码的意思是对每个字节轮流和 Key 的对应字节做异或Key 用完之后从头再循环。逻辑说明这种加密是 ECB 模式同样的明文会产生同样的密文而且 Key 固定所以流量分析时相当于透明。实战中我抓到一段密文只要在代码里搜连续使用的字符串常量很容易就能定位 Key。参数说明Key 的长度和内容不同变种差别很大分析前务必确认你手里这个包用的哪一份 Key不要拿网上公开的通用 Key 去解大概率解出来是乱码。屏幕图像数据则是先压缩再加密。老代码里常见的是 Aplib 或 LZ 变种压缩后的数据体积小很多截屏传输时才不至于卡死。这部分的要点是处理的顺序先压缩、后加密。还原的时候顺序反过来——先解异或再做解压。分析这类样本时如果只关心检测不关心内容还原那压缩算法可以完全跳过如果你想写一个协议还原工具压缩解压这一步才是真正耗时的地方。3. 用 VS2019 打开 Gh0st环境配置、三处必改点与常见编译报错3.1 环境准备装好 ATL 和 MFC切回多字节字符集如果你是从 VS2019 下载安装教程一路点过来的默认安装直接打开 Gh0st 工程大概率第一秒就报错。原因很典型默认安装里没有老代码依赖的 ATL 和 MFC 组件。Gh0st 的服务端是 MFC 程序而且不少变种用到了 ATL 的字符串转换宏比如A2T、T2A。装这些组件不需要重装整个 IDE在 Visual Studio Installer 里选择「修改」切到「单个组件」搜索 ATL 和 MFC勾上对应版本即可。装完之后可以先跑一条命令确认环境到位。用 PowerShell 检查 ATL 头文件是否真实存在Get-ChildItem C:\Program Files (x86)\Microsoft Visual Studio\2019 -Recurse -Filter atlbase.h -ErrorAction SilentlyContinue | Select-Object FullName这条命令的作用是在 VS2019 安装目录里递归查找 atlbase.h如果没有任何输出说明 ATL 组件还没安装到位。参数说明路径里的2019要根据你实际安装的版本目录改社区版、专业版、企业版的目录名前缀都一样但如果有多个版本并存注意别查错路径。另外还有一个隐藏问题老工程默认是 32 位VS2019 里新建或转换后的工程默认活动平台可能是 x64。Gh0st 老代码里有大量把指针强制转成int的写法x64 下编译会冒出一堆 C4312、C4311 警告有的甚至直接编译不过。我一般会在工具栏把解决方案平台切到 x86 再编省很多事。字符集是最容易批量报错的地方。老代码几乎全程用char和char*保存 IP、路径和命令缓冲区而 VS2019 新建工程默认字符集是 Unicode所有 Win32 API 自动映射到宽字符版本参数一比对就全错了。解决办法右键工程 → 配置属性 → 高级 → 字符集 → 选「使用多字节字符集」。这个选项在属性页里的位置比较深直接搜索「字符集」也能跳到。3.2 三处必改点随机种子、Socket 收尾与 C4996 安全函数老代码在 VS2019 下编译最常见的不是逻辑错误而是编译器版本带来的「语法洁癖」。第一处必改点是随机种子。老代码喜欢写srand((unsigned)time(NULL))这在 VS2019 下能编译过但会有一个隐患如果两个客户端在同一秒钟启动它们的随机序列完全一样这在生成会话 ID 或临时 key 时会造成碰撞。我习惯顺手改成 C11 的随机数#include random std::random_device rd; // 真随机种子每次运行都不同 std::mt19937 gen(rd()); // 梅森旋转算法替代老式 rand逻辑说明std::random_device从操作系统取熵比time(NULL)可靠得多std::mt19937是生成随机数的核心引擎。参数说明如果只是为了让代码跑通srand((unsigned)time(NULL))加个头文件也能凑合但既然已经开箱改码顺手替换掉更干净。第二处是 Socket 收尾的顺序。Gh0st 客户端退出时经常直接closesocket然后WSACleanup在 Debug 下没事Release 下偶发崩溃。稳妥的收尾顺序是shutdown(sock, SD_SEND); // 先通知对端不再发送 closesocket(sock); // 再关闭套接字 WSACleanup(); // 最后清理 Winsock 环境逻辑说明shutdown会触发 TCP 的 FIN 握手让对方能感知到连接结束直接closesocket可能把还没发完的数据直接丢掉。参数说明SD_SEND表示禁止后续发送操作但还允许接收这对协议收尾是合适的。第三处是整个工程级的修改老代码大量使用strcpy、sprintf、fopen这些函数在 VS2019 里默认报 C4996。两种处理方式最省事的是在工程预处理器里加宏#define _CRT_SECURE_NO_WARNINGS逻辑说明这个宏告诉编译器不把 CRT 的旧版函数当错误处理C4996 直接消失。另一个更「正统」的方式是把strcpy改成strcpy_s但老代码里这类调用有成百上千处而且_s版本参数语义不同全局替换很容易改出新 bug。我的习惯是先加宏跑通确认行为正常后再决定要不要逐步替换。毕竟目标是研究这个恶意样本的行为不是给它做代码现代化改造。3.3 常见报错清单C2440、C4996、LNK2019 的处理编译 Gh0st 的报错来来去去就那么几类我把最常遇到的情况整理一下错误码报错场景处理方式C4996strcpy、sprintf、fopen等 CRT 函数预处理定义_CRT_SECURE_NO_WARNINGSC2440const char*转LPWSTR失败字符集切到多字节或给字符串加_T()LNK2019无法解析的外部符号WSAStartup链接器附加依赖项加ws2_32.libC1083找不到atlbase.hVS Installer 补装 ATL 组件C1190找不到 MFC 头文件VS Installer 补装 MFC 组件C2440 是最容易把人吓到的一行报错跟着一片其实九成都是字符集问题。切回多字节字符集后这类错误会自动消失大半。LNK2019 是链接阶段的错误报错信息里会出现WSAStartup、socket、send等 Winsock 函数原因就是工程没有链接ws2_32.lib。在工程属性 → 链接器 → 输入 → 附加依赖项里加上即可。还有一个不太起眼但老工程常踩的坑打开 sln 时 VS2019 会提示做一次性工程升级对话框里有两个选项选「保留现有属性」而不是「重新创建属性表」否则原有的字符集、运行库配置会被重置成默认值刚调好的编译选项白费。4. 跑通最小联调服务端监听、客户端地址、Wireshark 抓包验证4.1 服务端启动端口、提示符与「上线」行为的确认编译通过只是第一步真正让代码跑起来才是验证你理解是否正确的时刻。先在隔离虚拟机里启动 Server.exe它会弹出一个 MFC 窗口窗口底部一般有日志输出区会显示监听端口。Gh0st 变种默认端口从 80、81、8000 到 2014 的都有我建议你不要用默认值改成一个不常用的端口比如 7373免得和测试环境里的其他流量互相干扰抓包时也更干净。启动后用系统命令确认监听状态netstat -ano | findstr 7373这条命令的作用是在本机网络连接表中查找监听 7373 端口的进程。输出里看到LISTENING状态说明服务端的监听线程已经起来了。参数说明-a显示所有连接和监听端口-n用数字形式显示地址和端口而不是做反向域名解析-o显示进程 ID实测时用-ano组合最省事。如果你看到的是空结果优先检查防火墙是否拦截了 Server.exe 的入站连接或者 Server 是否以管理员权限运行。联调环境我习惯用 VM 的 host-only 网络两端都配静态内网 IP。这样既不受外网干扰流量也全部留在虚拟网络里抓包时不会混入杂讯。提醒一句不要在这个阶段就把 Client 放到真实机器上跑后面 5.4 节会专门聊这个问题。4.2 设置客户端地址从源码里改 IP 和端口再重新编译Gh0st 的客户端不像商业远控那样有图形化配置器回连地址通常直接写死在某个 cpp 文件里。常见的位置是Config.cpp或者Global.cpp里面会有类似这样的定义char g_ServerIP[32] 192.168.56.101; // 服务端 IP int g_ServerPort 7373; // 服务端端口需要手动改成你 Server 所在机器的 IP。用 VS 的「在文件中替换」就能全局改掉。如果你在 Linux 环境里做批量修改也可以用 sed 脚本sed -i s/192\.168\.56\.101/192.168.56.102/g Client/Config.cpp这条命令把 Config.cpp 里所有匹配的旧 IP 替换成新 IP。参数说明sed 的替换分隔符是/IP 里的.在正则里是「匹配任意字符」的通配符所以必须写成\.才表示字面意义上的点g表示一行里所有匹配都替换而不只是每行第一个。改完之后必须重新编译 Client因为地址是编译进二进制的字符串常量不重编就不会生效。这里有个新手经常绕弯的点花大量时间去找一个不存在的「配置文件」或者「上线地址设置工具」。实际上这类老源码包里Client 的唯一配置就是源码里的这两行常量。把源码改好、重新编译、然后在同一网段里运行 Client是最快能跑通的路。如果你改完 IP 重新编译还是上不了线不要怀疑是配置工具的问题回到源码里搜所有出现 IP 字符串的地方有的变种会把地址写到资源脚本里藏在.rc文件中。4.3 用 Wireshark 验证抓包看心跳与指令流量联调跑通后一定要抓包确认一遍而不是看着窗口显示「已连接」就收工。Wireshark 的显示过滤器很简单tcp.port 7373这行过滤器的意思是在所有抓到的包里只显示源或目的端口为 7373 的 TCP 段。参数说明如果你用的端口不是 7373换成你实际设置的端口即可如果 Server 端同时有多个连接也可以叠加ip.addr 192.168.56.101只保留某个主机的流量。观察两个特征就能确认 Gh0st 在工作。第一个是心跳Client 上线后每隔固定时间常见 30 秒到 60 秒就会发一个长度相同的短包这是保活机制也是后面写检测规则的核心特征。第二个是触发式大包你在 Server 端点一次「屏幕查看」抓包里立刻会出现一个客户端请求包紧接着是一连串大长度的 TCP 段那就是屏幕图像数据在飞。如果抓了十分钟只有心跳、没有业务流量多半是你没有在服务端下发指令而不是协议有问题。命令行场景下可以用 tshark 直接抓取并保存为 pcaptshark -i eth0 -f tcp port 7373 -w gh0st_traffic.pcap逻辑说明-i eth0指定抓包网卡-f是捕获过滤器在数据进入 Wireshark 之前就先筛掉无关流量-w把原始包写入文件。参数说明-f和显示过滤器语法不完全一样比如这里不能写成tcp.port 7373这种带空格和等号的写法捕获过滤器对应的是tcp port 7373。抓完的文件拿到 Wireshark 里再慢慢分析比现场盯着终端看舒服得多。5. 避坑指南编译、联调与样本分析时的 5 个常见问题5.1 报错 C1083无法打开包括文件 atlbase.h现象编译一开始就失败错误列表里明确写着无法打开包括文件 atlbase.h。原因VS2019 默认不安装 ATL 组件而这个老代码正好用了 ATL 的字符串转换宏。解决打开 Visual Studio Installer点「修改」切到「单个组件」勾选 .NET 和 C 相关的 ATL 组件等装完再重开 VS 即可。这个坑也侧面解释了一个现象网上不少截图里 Gh0st 是在老版 VS2008/VS2010 下编译成功的因为那时候 ATL 是默认组件不是人家技术比你好是环境不同。5.2 C4996 成片出现整个项目都是红色波浪线现象strcpy、sprintf、fopen全都被标记为不安全函数编译输出里是几百条 C4996 警告或错误。原因微软从 VS2005 开始把这些 CRT 函数标记为 deprecated用新函数替代它们。解决在工程属性 → C/C → 预处理器 → 预处理器定义里添加_CRT_SECURE_NO_WARNINGS重新编译即可。不要顺手做全局「查找替换」把strcpy改成strcpy_s老代码里这些函数经常和自定义内存管理混用逐行替换反而会引入新的内存越界问题。5.3 编译通过exe 双击后秒退现象Debug 下运行正常Release 下进程启动后窗口一闪就消失。原因老代码里存在未初始化的指针或数组越界Debug 模式的内存布局和 Release 不同问题在 Release 下被放大。这类问题没有统一的答案排查顺序是先把工程切到 Debug 确认能跑再把优化选项改成/Od禁用优化跑一次如果禁用优化后正常就可以逐步把优化级别往上加观察在哪个优化等级触发问题。解决后还有个玄学点这类老程序对「当前工作目录」有隐式依赖VS 里按 F5 跑和在资源管理器里双击跑工作目录不一样表现可能完全不同。建议在工程属性 → 调试 → 工作目录里显式设置为$(TargetDir)排除这个变量。5.4 编译出来的 Client.exe 被 Defender 秒删现象编译完成后 exe 生成在输出目录里几秒钟后文件消失系统通知里显示已被 Windows Defender 隔离。原因Gh0st 的行为特征太明显静态扫描引擎几乎一认一个准。解决研究环境下把工作目录加入 Defender 的排除项或者直接在断网的隔离虚拟机里做分析和编译。这里要明确一点加白名单是为了让样本分析流程不被中断不是为了做出一个能过杀软的「成果」。分析完毕这个文件仍然要按恶意样本的处置流程封存或销毁绝不能拿到真实环境里跑。5.5 虚拟机里能连换物理机就卡住不上线现象Client 和 Server 都在虚拟机里时一切正常把 Client 挪到物理机上运行就死活不上线。原因网络环境变了。虚拟机的 NAT 模式有独立的虚拟网段物理机访问虚拟机里的服务端需要额外的端口映射而 Gh0st 的 Client 是主动回连模式回连包如果没人转发连接请求直接超时。解决最省事的是把两端都放在同一个 host-only 网络里Server 和 Client 用同网段的内网 IP 互通。如果一定要跨网段联调是在防火墙上放行对应端口而不是在系统里反复重装。换用真实局域网也一样先保证两台机器在同一二层网络里不要一上来就跨公网做测试。绝大多数「连不上」问题最先排查的永远是网络路径不是代码逻辑。6. 把 Gh0st 当样本读流量特征、行为链和后续能做的三件事6.1 从抓到的包反推检测规则一张 YARA 就能起手读 Gh0st 这类老样本最有价值的产出是把它转化成检测能力。一个简单可用的起点是 YARA 规则从编译产物里提取字符串特征rule Gh0st_RemoteControl { meta: author analyst strings: $s1 Gh0st ascii wide nocase $s2 ScreenManager ascii wide $s3 FileManager ascii wide condition: uint16(0) 0x5A4D and 2 of them }逻辑说明uint16(0) 0x5A4D是检查文件是否为 PE 格式MZ 头2 of them表示至少命中两条字符串才告警。参数说明ascii wide同时匹配窄字节和宽字节字符串nocase忽略大小写。这只是第一层静态检测真正部署到网络侧还得结合前面抓包看到的心跳周期、固定包长这类行为特征写成 Suricata 规则效果才稳。把这套流程走通之后我建议的下一步方向有三个一是拿这份流量样本去对比新远控的协议设计看它们加了哪些层、省了哪些层工程取舍一目了然二是把 ProcessCmd 那套分发逻辑背下来以后再遇到陌生样本先找它的「命令分发函数」就能快速定位行为面三是试着给它写一个最小化的协议模拟器纯属练手但对理解 TCP 粘包、半包处理非常有帮助。我自己分析 Agent Tesla、AsyncRAT 时用的很多习惯都是当年啃 Gh0st 留下的——先抓包、再找分发函数、最后才看功能模块。这套流程在 2025 年依然能用这也是这段老代码值得花一个周末去读的原因。希望帮到你。本文还有配套的精品资源点击获取