Windows 内网远程控制工具,专用于单向隔离网络(被控端可出站访问控制端,控制端无法反向访问被控端)。所有通信隧道由客户端主动发起,服务端仅被动监听,全程无反向连接。
本工具仅用于企业内网运维、安全审计等合法用途。未经授权远程控制他人计算机属违法行为。
- ✅ 严格单向网络:服务端只
accept,绝不主动连接客户端(已代码审查佐证) - ✅ 客户端主动注册上线、设备唯一硬件标识(MAC+主机名)
- ✅ 实时远程桌面:DXGI 桌面复制采集 → H.264 编码 → 传输 → 解码渲染
- ✅ 键鼠反向控制:绝对坐标映射,支持移动/按下/抬起(可拖拽)、滚轮、键盘
- ✅ AES-128-GCM 全链路加密(PSK 经 HKDF 派生),密钥校验拒绝非法设备
- ✅ 断线自动重连(指数退避),心跳 1s、秒级上下线检测
- ✅ 控制端多标签会话(最多 4 路同时)、设备搜索与备注、批量断开、深色运维控制台配色
- ✅ 键盘只在点过画面之后送到对端,连按两次 Esc 或按释放热键交还;Alt+F4 与 Win 永远留在本机
- ✅ 画质三档(流畅 / 均衡 / 清晰)、全屏、二次控制密码
- ✅ 事件日志抽屉(同一行文本既进日志文件也进窗口)、设置页、记住窗口布局与默认画质
- ✅ 客户端绿色单文件(OpenSSL/OpenH264/CRT 静态链接),支持开机自启
| 模块 | 选型 |
|---|---|
| 语言 | C++17 + Win32 |
| 服务端 GUI | Qt 6 (MSVC) |
| 采集 | Desktop Duplication API (DXGI),BitBlt 降级 |
| 编解码 | OpenH264(自包含,不依赖系统 Media Foundation 编码器) |
| 加密 | OpenSSL AES-128-GCM + HKDF + HMAC-SHA256 |
| 传输 | Winsock2 + 自定义二进制分帧协议 |
帧格式:[4B 总长(大端)][1B 类型][载荷];除心跳外载荷均为 AES-128-GCM 信封
[12B nonce][密文][16B tag],内层再带 [1B 真实类型][业务载荷]。
消息类型:Register / RegisterAck / Heartbeat / StartStream / StopStream / VideoFrame / InputEvent / RequestKeyframe / AuthChallenge / AuthResponse / DisplayChanged / StateReport(能力 flags + 桌面尺寸)/ LockWorkstation / Ping-Pong(延迟读数=一次网络往返,与画面是否在动无关)/ QueryDisplayModes → DisplayModes(当前分辨率 + 可选模式清单,兼作切换后的回执)→ SetDisplayMode (真的改对端桌面分辨率,只作用于本次会话,不写注册表)。
前置:VS2022(Build Tools 亦可)、Qt6 MSVC、vcpkg:
vcpkg install openssl:x64-windows-static openh264:x64-windows-static
然后运行 build.bat(需按本机路径设置 VCPKG / QTDIR),产物:
build/bin/Release/agent.exe # 客户端,单文件
build/bin/Release/control_server.exe # 服务端,随附 Qt 运行时(windeployqt)
手动 cmake:
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 ^
-DCMAKE_PREFIX_PATH=C:/Qt/6.7.3/msvc2019_64 ^
-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake ^
-DVCPKG_TARGET_TRIPLET=x64-windows-static
cmake --build build --config Release
正式版发四个包(SHA256SUMS.txt 是它们的 SHA-256)。当前构建 = v1.0.7,尚未发布到 GitHub——
Releases · latest 现在仍指向 v1.0.5;
1.0.7 的真身在本地 build\package\dist\(packaging\stage.ps1 重新产出),所以下表写的是本批次:
| 给谁 | 安装版 | 绿色版 |
|---|---|---|
| 控制端(服务端,运维坐的那台) | MyRemote-Server-v1.0.7-setup.exe |
MyRemote-Server-v1.0.7-portable.zip |
| 被控端(客户端,要被远控的机器) | MyRemote-Agent-v1.0.7-setup.exe |
MyRemote-Agent-v1.0.7-portable.zip |
- 控制端装在
C:\MyRemote\Server,不要装进Program Files。server_config.json和日志都写在 exe 旁边,设置页每次保存都要重写它;非提权进程写不进受保护目录,表现就是状态栏那句"设置没能写进文件"。 - 控制端安装版加一条按程序放行的入站防火墙规则(不按端口,所以以后改监听端口不用回来补规则),卸载时删掉。被控端只出站,不需要任何放行;用绿色版则要自己放行 TCP 7500。
- 被控端安装版默认勾选"安装并启动系统服务"——这是唯一能在无人登录时工作的形态。不想装服务就取消勾选,或用绿色版里的
install-service.bat。 - 被控端默认还会在桌面放一个快捷方式(向导里可取消勾选)。它刻意不带任何参数:服务在跑时双击它只打开配置窗,服务停了时双击它以前台方式把客户端真的跑起来——所以托盘里点了"退出"之后,回到机器前双击的就是它;隐藏了图标,也是从它的"显示托盘图标"复选框勾回来。
- 覆盖安装不需要先卸载:被控端包在复制文件之前就会停服务、收掉残留进程并等文件真的可删(1.0.0 那版会在
[Files]阶段撞DeleteFile 失败;错误代码 5)。控制端包相反——发现控制中心正在运行只会告诉你并中止,不替你关,因为那一瞬间可能正有人开着远程会话。 - 卸载删掉自己装进去的文件与服务,配置和日志都留在原地:
%ProgramData%\MyRemote\整目录不动,运行期写出来的config.json/agent.log/host.status因为不是安装器放进去的,也照样留在安装目录里。误删不丢现场,重装即恢复。 - 包没有代码签名,首次运行会吃 SmartScreen:点"更多信息 → 仍要运行"。
远程批量推送(不依赖 WinRM,走管理共享即可):
MyRemote-Agent-v1.0.7-setup.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART ^
/TASKS=service /SERVERIP=10.0.0.5 /SERVERPORT=7500 /SECRETKEY=与控制端一致的串三个参数都是选填:一个都不填就什么都不写(实测退出码 0、目录里没有 config.json、已有服务没被碰),之后再填有两个入口——开始菜单的"配置界面"快捷方式,或直接双击 agent.exe(前台启动先弹配置窗,保存后才开始连;静默安装不会弹这个窗)。填了任意一项就交给 agent.exe --set-server --ip .. --port .. --key .. 落盘:安装器不再自己猜路径,而是由 agent 按运行期同一套解析顺序找到它真正会读的那一份(%ProgramData%\MyRemote\config.json 优先,其次 exe 旁边那份),先读回来、只覆盖你显式给出的那几项、再整体写回——设备名、控制密码、编码宽度与"显示托盘图标"因此在覆盖安装时原样保住,换控制端地址既可以重推包也可以直接改那份文件。那份配置如果存在却一个已知键都读不出来,--set-server 会拒绝写入并以退出码告知安装器(安装日志留下一行):宁可不写,也不覆盖。卸载:unins000.exe /VERYSILENT。
现场先跑这两条:agent.exe --version / control_server.exe --version 报版本号,agent.exe --service-state 打印服务状态、会话解析结果,以及宿主写下的那份实时状态——host: 一行里的 registered= / paused= / proxies= 只有在宿主还活着并且还在刷新时才是"现在",否则整行前面会挂一个 STALE(原因)(pid 已经不在了,或文件超过 15 秒没动过)。服务停着的时候看到的多半就是上一代宿主的记录。
装了服务的机器过去没有任何可见痕迹,而 M18 把图标画在物理控制台会话里、RDP 里永远看不见。M19 改成:宿主用 SYSTEM 权限在每个有人登录的会话(控制台、RDP 都算)里拉起一个以该会话用户身份运行的托盘代理,图标画在操作者自己的会话里;菜单命令经命名管道回到宿主执行。代理随会话登录而出现、随会话断开而退出,--service-state 的 proxies= 列出当前哪几个会话有图标。停在登录界面或按了 Win+L 时会话里没有交互桌面,图标自然看不见,解锁就回来。右键菜单四项的语义是刻意分开的:
| 菜单项 | 做什么 | 怎么回来 |
|---|---|---|
| 打开配置 | 就地改服务器地址/连接密钥/控制密码,保存即热生效 | — |
| 停止远程控制 | 只关这条隧道:进程与服务都活着,控制端那侧设备行转成"重连中" | 同一个菜单的"启动远程控制";重启机器也自动恢复可控(这个状态刻意不落盘) |
| 隐藏托盘图标 | 收掉图标,并把 tray_icon: false 写进配置。这是全机语义:这台机器上每个会话的图标一起退休(图标归各自进程画,所以删也由各自删),不是一次性地点掉你右键的那一枚 |
双击桌面/开始菜单的快捷方式,勾回"显示托盘图标"——运行中的 agent 会立刻把图标建回来 |
| 退出(同时停止服务,下次开机自动回来) | 先删图标、再停服务、进程随后结束;启动类型仍是自动 | 双击客户端:服务没在跑,这次就以前台方式真的跑起来并重新注册,菜单里给出「启用后台服务…」那一项回到受管形态(它的完整文案随令牌而变,见下文);或者等下次开机 |
"停止远程控制"期间 sc query MyRemoteAgent 照旧报 RUNNING——暂停的是隧道,不是服务,这就是它和开机自启的两码事。想知道"这台现在到底能不能被连",判据是托盘提示、--service-state 里的 paused=1 和控制端设备行的"重连中",不是 services.msc。
M22(v1.0.5)在代码审查与现场判账基础上修了四条边界缺陷,逐条的真实状态是:监管线程可被死账客户端挂死已修并有本机判据(cancel-first);host.status 的几秒宽盲区由 SCM 状态定案已修并在 2026-09-02 本机见到 STALE(service stopped);服务错误框按 GetLastError() 分岔只修了一半(--service-state 的首行与托盘说明框的尾巴仍会把权限之外的原因说成"没装服务"/"需要管理员权限");退出时 "bye" 未排空那条修得弱于约定(等的是出队而不是送达,之后再加一段写死的 Sleep(100))。当时列在 M22 名下的第五项 F5(那一项自带可供性且真办得成)没有做,所以 1.0.5/1.0.6 装机后托盘那一项看起来一模一样。判据脚本与产物未随仓库留存,"全部四条均已通过"这句话在 2026-09-02 被撤回。
M24(v1.0.7)把欠下的补齐:「启用后台服务」这一项现在自己说清代价(受限令牌下带「需管理员批准」后缀)并且真的去要一次批准,被拒与起不来分成两条读得出的话,只有 ACCESS_DENIED 才会提"管理员权限";svc::query() 的首行四岔分流(拿不到 SCM / 真没装 / 令牌读不到 / 查询失败),且读不到服务时也照样打印 console session:、stations:、host:;bye 的等待换成真门——每个代理各自记账"说了几行/写出去几行",一个共享 deadline 覆盖全部代理,未落定的点名告警;以及服务能否从 STOP_PENDING 停下来的一次实测(见 TASKS.md 的 M24 账)。
M20 之后这套图标不再有"点不动"的状态:菜单动作一律投递给工作线程执行,托盘消息线程从不做可能卡住的事(写管道有 500ms 上限,超时就断开这条管道);宿主每 5 秒对每个代理发一次心跳,代理 15 秒收不到任何话就自己删图标退出——宁可暂时没图标,也不留一枚点不动的幽灵;宿主侧发现代理不回 pong 就拔掉它的管道并补拉一个新的。"隐藏/退休"都不再靠 TerminateProcess:先说 bye/die,等它自己退,删图标的责任始终在画图标的进程手里。M21-3 补上的是这条链路的话术:宿主写完 tray_icon: false 之后内存里那一份也跟着变,下一轮广播带上 tray_icon=0,各会话代理读到就自己删图标退出,监管不再补生——只改盘上副本等于永远没说"这枚图标不该存在"。
前台(便携)模式的菜单按服务的形状收敛:装过服务就不再给「安装开机自启」(它和服务抢同一个设备号)与「以管理员身份重启」(宿主本来就是 SYSTEM,这条没有意义);服务装了但停着——正是托盘点了"退出"之后——才给「启用后台服务」那一项。M21-4 把这个形状改成弹菜单那一刻现问:主循环以 ≤1/s 采样一个原子值,菜单只读它(查 SCM 一旦放上托盘消息线程,M20 那种"菜单再也不出来"就回来了)。所以别人中途用 services.msc 停/起、或者你刚点过"启用后台服务",下一枚菜单就是对的。跟随延迟是一轮主循环:隧道正常时 ≤1.2s;控制端联系不上的机器,一轮循环里含 10s 连接超时,实测 13s。
M24 给这一项补上了两样东西。文案自带可供性:受限令牌下读作「启用后台服务(当前:前台运行,开机不自启;需管理员批准)」,提权令牌下仍是原来那句——判的是进程启动时就定下的 g_elevated,弹菜单那一刻不碰 SCM。点它真办事:提权就直接 StartService,受限就去要一次批准(runas 拉起 --start-service --no-elevate),之后由 SCM 裁决起没起来,而不是先宣布成功。「已取消」和"框根本没弹起来"是两条字面读得出的话,"需要管理员权限"这句只在错误码真是 ACCESS_DENIED 时出现。点击成功后自己刷新一次形状缓存——13s 那一轮不能当点击的判决,否则刚启用完的下一枚菜单还会继续给这一项。
图标注册成功不等于看得见:Windows 11 会把第一次见到的托盘图标收进右下角的 ^ 里,直到用户自己把它拖出来,而这个偏好应用无权代设(只有用户账户下的 NotifyIconSettings 能改)。所以"托盘没出现"先看 ^ 里面;--service-state 的 proxies= 与 tray-<会话>.log 能区分"没画"和"画了但收起来了"。
- 服务端:将
deploy/server_config.json放到control_server.exe同目录(可省略,使用默认端口 7500 / 默认密钥),运行。控制端窗口右下角的“设置”能改同一份文件里的监听端口、绑定地址、最大接入数与日志文件——日志文件立刻生效,其余三项要重启控制端;连接密钥只在这份文件里改(改它会让所有已注册设备一起失效,不该做成一个按钮)。 - 客户端:把
agent.exe拷到目标电脑,直接双击运行即打开图形配置界面,填写服务器地址/端口/连接密钥,可点“测试连接并注册”验证;点“保存并后台运行”后 agent 自动转入后台并注册到服务端。仅改配置不启动:agent.exe --config-ui。调试窗口:agent.exe --console --background。 - 推荐安装为服务(唯一能在无人登录时工作的形态):
agent.exe --install-service。服务以 LocalSystem 开机即自启,在控制台会话里托管一个采集/注入进程,因此机器停在登录界面时也能远程查看并远程输入密码登录 Windows。配置读写位置改为%ProgramData%\MyRemote\config.json(普通用户可写,便于现场改配置);到底读哪一份的次序是--config指定 >%ProgramData%\MyRemote\config.json(存在即用)>{app}\config.json(存在即用)> 都没有就往 ProgramData 新建——安装向导的连接页也走这同一条路(--set-server),所以不会再出现"向导里填了、agent 读的是另一份"。服务正在跑时再双击agent.exe只会打开这份配置后退出,不会建立第二条隧道(排障需要强行接管用--force),服务被托盘"退出"停掉之后再双击则是把客户端重新跑起来,见坐在机器前的人:托盘图标。配套命令:--uninstall-service、--start-service、--stop-service、--service-state(服务状态、会话解析结果,以及宿主写下的那份记录,见上文STALE)、--set-server(只改连接信息,其余字段原样保住)。 - 不能装服务的机器可退回登录计划任务:
agent.exe --install-autostart(卸载--uninstall-autostart)注册最高权限计划任务,登录后以管理员权限静默运行;它做不到服务能做的“无人登录时可用”。--install-service会自动删掉这个遗留任务,避免两个 agent 抢同一个设备号。 - 控制端双击在线设备 → 输入该设备的控制密码(客户端未设置则留空)→ 查看/操作远程桌面。
任务管理器、UAC 弹窗、管理员控制台等提权窗口只接受来自提权进程的注入输入(UIPI)。以普通权限运行的 agent 会在设备行上标一枚
受限小牌:可远程操作普通窗口,但点不动提权窗口。在目标机托盘图标右键选“以管理员身份重启”即可接管;装成服务后不存在这个问题(宿主以 SYSTEM 运行,高于任何提权窗口)。其余几枚小牌:
服务由 MyRemoteAgent 服务托管;登录界面对端此刻正停在登录/凭据界面,可以直接远程输密码;非控制台该会话此刻没接在物理显示器上(典型原因是有人正用 RDP 操作这台机器)——隧道不会因此断开,RDP 一放手画面就自己回来,无需人工干预。设备行里它们是色块小牌,标签页标题和会话页眉里放不下色块,就写成同一批词:TEST-WIN · 服务。 想主动回到登录界面输密码,点“返回登录界面”(锁定工作站)。注意 Ctrl+Alt+Del 无法被注入:若该机策略要求先按 Ctrl+Alt+Del 才出现登录框(DisableCAD=0),这一页远程点不动,请将该值设为 1。
客户端与服务端的 secret_key 必须一致,否则注册被拒。
- 两个进程,一个二进制:
agent.exe --service(LocalSystem,Session 0)只做监管——不联网、不碰 GDI、不弹界面;它把唯一一个agent.exe --session-host用复制的 SYSTEM 令牌投放到当前持有物理控制台的那个会话里(Winsta0\Default)。 - 宿主按名字跟随真实输入桌面(
Default⇄Winlogon⇄SAC-Desktop),所以登录界面与 UAC 同意框都能看、能打;它从不调用SwitchDesktop——那会把显示器从现场用户手里抢走。 - 隧道与设备身份归宿主:服务从不建立连接,手工启动的 agent 检测到服务正在运行就让位,因此一台机器永远只有一行设备。托盘图标归每个会话自己的代理(M19)——图标只可能画在调用者所在会话的通知区域,所以宿主自己不画图标,而是把菜单命令的执行留在自己这边:"停止远程控制"期间隧道关掉、宿主与代理都活着,图标才能继续显示"已停止"并给出恢复入口。
- 仍然只出站:客户端不监听、不 accept,控制端地址由被控机主动连出。
- Session 0 本身确实无法捕获交互桌面,早期因此放弃了服务形态;正确解法不是不用服务,而是让服务只当_launcher_,采集放在控制台会话里。
服务装在哪个路径就跑哪个 exe(binPath 取自 GetModuleFileNameW),所以可以直接从 build 树安装、改完就重装:
build\bin\Release\agent.exe --stop-service REM 先释放 agent.exe,否则 LNK1168
cmake --build build --config Release --target agent
build\bin\Release\agent.exe --install-service REM ChangeServiceConfigW 重指 binPath 并启动
build\bin\Release\agent.exe --service-state只改宿主逻辑时可以完全绕开服务:agent.exe --background --console。
- 串流为 CPU 软件编码,帧率上限取决于被控机自身开销:同一 3400×1812 桌面,1920 档实测本机 30fps(采集 20ms+编码 10ms)、弱机仅 2fps(采集 170ms+编码 250ms),画质档位里的 1280 上限即为此准备;后续可换 GPU 缩放/编码
- 服务端解码已移出 GUI 线程(独立解码线程 + 单槽最新帧),帧率显示区分 NET(收包)与 DEC(解码)
- DXGI 复制的纹理不含硬件指针,远程画面里看不到对端的箭头;曾试过在控制端本地回显,但每次鼠标移动都触发整帧重绘、操作明显变迟钝,已撤销
- 登录界面相关(M14)的能力边界:
- 预启动/BitLocker PIN/BSOD/固件层不可达——服务还没开始运行,需要 iDRAC/iLO/虚拟机控制台
- Ctrl+Alt+Del 无法被注入(系统硬性限制)。开机后/锁屏后的欢迎界面本身就是安全桌面且已聚焦密码框,远程输密码登录不需要它;但若策略
DisableCAD=0要求先按 Ctrl+Alt+Del,那一页远程点不动,请设为 1 - 开机后设备要等系统启动完成、网络就绪才上线(服务是自启的,不再叠加延迟自启的 1~2 分钟;宿主连不上服务器时按 1s→30s 退避重试)
- 登录/注销/快速用户切换/宿主崩溃时设备行会闪断,服务在 ≤2 秒内拉起新宿主;控制窗口会自动重新鉴权并恢复画面(60 秒内最多尝试 5 次,超过后需在状态栏提示时重新双击)
- 他人正用 RDP 操作这台机器时,控制台会话处于“已断开”状态:宿主仍然守在控制台会话上,因此画面会静止或发黑(输入仍落进该会话),这是 Windows 的限制而不是掉线;RDP 断开后画面自动回来。要真把桌面搬回物理显示器,只能在目标机上注销或断开那个 RDP 会话——“Attach to console” 按钮已随服务形态一起删除
- 设置了
WDA_EXCLUDEFROMCAPTURE的窗口与硬件保护视频流仍捕获为黑色;指纹/人脸/安全密钥类 Windows Hello 需要本地硬件 - 多显示器只采集主屏;登录界面固定在主屏,因此不是新增的退化
%ProgramData%\MyRemote\config.json允许本机普通用户写(保住“双击改配置”的流程),代价是任何本地用户都能改服务器地址与控制密码——这与 M14 之前“任何人都能跑 agent.exe --config”的暴露面相同。若要收紧,改成仅管理员可写并让配置框自提权- 托盘代理以该会话登录用户的身份运行,"暂停/退出/改配置"的权力属于坐在该会话里的人——这是"只有用户才能更好地控制客户端"的定案。管道 DACL 是
SYSTEM、Administrators与IU(交互式登录组):凡是在这台机器上交互登录过的用户都能连上它,不是"该会话的那个用户"。所以这条管道只走"图标层面的命令"(state/pause/resume/hide/quit/save_config),secret_key与control_password刻意不推上管道,代理的配置窗也从不自己落盘——写配置的一律是持有真配置的那个宿主 - 同一会话里的任何进程都能画出一枚一模一样的托盘图标和菜单,因此图标只能当作现场信息与操作入口,不能当作权威凭据。要看现状,看
--service-state的paused=/registered=/proxies=与控制端设备行
- DXGI 桌面复制在部分虚拟机/远程会话下不可用时自动降级 BitBlt