资讯动态

Star Rat 3.1源码升级版:内网资产盘点与状态监控实践

发布时间:2026/10/16 13:53:32 来源:尧图企业网站定制
简介Star Rat 3.1源码升级版是一套面向Windows平台的远程控制软件源代码适合有C/C基础的安全开发人员、系统管理员及对远控原理感兴趣的进阶学习者。其核心价值在于提供完整可编译的工程较为全面地展示了远控软件的通信、控制与界面实现思路同时兼容Windows XP至8.1以及2000、2003等多个系统版本便于在真实环境中验证与二次开发。资源包大小约22.39MB共4558个文件其中以cpp、h、rc、png为主覆盖主要业务逻辑、界面资源与图标素材此外还包含sln、vcproj等工程文件及少量bat脚本、dll、exe可帮助使用者快速梳理项目结构并定位关键模块。目前已有450人浏览学习。对于希望深入理解远控软件稳定性设计、安全加密机制或多线程并发处理的开发者该源码提供了可读性较强的参考蓝本开发者也可基于现有框架自行扩展文件传输、屏幕控制等功能模块用于技术研究或内部工具定制。1. 一句话讲清 Star Rat 3.1 源码升级版把内网资产盘点从两天压缩到两小时「又被领导要了一份全网段的设备资产清单下午下班前交」——如果你在运维或信息化岗位待过大概率对这句话不陌生。Star Rat 3.1 源码升级版就是冲着这个场景来的它是一套内网终端资产采集与状态监控的完整可运行工程控制端负责收数据、展示、出报表被控端负责自动采集主机信息、按心跳周期上报。跟我之前拆过的很多「半成品 Demo」不一样这版把数据库初始化、心跳加密、断线重连、状态判定这些平时容易被糊弄过去的环节都补齐了拿到手不是一个能看不能跑的壳而是一条可以真实走通的链路。项目名里的 Rat 不是你想的那个远程控制工具它在这里是 Remote Asset Tool 的缩写意为远程资产采集工具适合正在做内部资产管理、设备台账、运行状态巡检的一线开发拿去二次开发也适合想搞清楚「被控端上报→服务端入库→前端展示」这套闭环怎么落地的新手当工程范本读。2. 架构与技术选型控制端、被控端与压缩后的通信协议拆源码之前先把骨架摸清楚。Star Rat 3.1 整体是经典的控制端Server与被控端Agent两层结构中间走 HTTP 上报。控制端用 Flask 提供 API 和页面数据落在 SQLite被控端用 Python 标准库加 psutil 采集系统信息。这套选型谈不上惊艳但胜在依赖少、好部署——内网环境里经常没有外网权限pip 装不了几个包所以 Agent 侧只有 psutil 一个第三方依赖Server 侧算上 Flask 一共三个大多数内网机器都能满足。2.1 整体架构一个上报接口加一个状态判定我拆完目录之后发现这项目虽然叫升级版但架构上刻意保持了克制。控制端暴露两个核心接口一个是/api/heartbeat接收被控端上报的心跳数据另一个是/api/devices给前端页面返回资产列表和详情。被控端没有独立服务端口它只做一件事——按固定间隔把采集结果 POST 到控制端然后等一个 JSON 响应。这个设计的好处是省去了被控端开端口、做内网穿透的麻烦控制端只要 IP 可达就行。状态判定的逻辑值得先说因为它决定了整个项目的可用性。控制端收到一次心跳后不是简单地把数据覆盖进数据库就完事它会同时计算一个is_online字段以当前时间减去该设备最后一次心跳时间如果差值小于心跳间隔的两倍就认为在线否则标记离线并记录最后在线时长。这个「两倍间隔」的判断是很多同类项目里最容易翻车的地方——有些项目直接按「收到心跳就是在线」结果被控端断电三天了列表里还显示绿灯。2.2 目录结构每个目录都对应一个运行阶段star_rat_3_1/ ├── server/ │ ├── app.py # Flask 主入口路由注册 │ ├── models.py # SQLAlchemy 模型资产表与心跳日志表 │ ├── api/ │ │ ├── heartbeat.py # 心跳接收与解密 │ │ └── devices.py # 资产列表与详情接口 │ ├── static/ # 前端静态资源页面用原生 JS 轮询刷新 │ └── templates/ │ └── index.html # 单页资产面板 ├── agent/ │ ├── collector.py # 采集主机、CPU、内存、磁盘信息 │ ├── heartbeat.py # 组装报文、加密、发送 │ ├── config.py # 读配置文件管理设备ID │ └── agent.py # 主循环与异常处理 ├── config/ │ ├── server.ini # 控制端端口、数据库路径、密钥 │ └── agent.ini # 设备ID、上报地址、心跳间隔 ├── scripts/ │ ├── init_db.py # 初始化数据库和账户表 │ └── gen_key.py # 生成 AES 密钥 └── docs/ └── protocol.md # 完整接口说明与报文示例这个结构很直白scripts里两个脚本是第一步要跑的东西config是第二步要改的东西server和agent是第三步要启动的东西。对二次开发的人来说想加功能基本就是往api/目录加一个路由、往collector.py加一个采集函数的事侵入性很小。2.3 通信协议加密的上报不下发任意指令协议层是这版升级的重点。每个被控端启动时读agent.ini里的device_id这个 ID 是部署时生成的唯一标识后面所有上报都带着它。上报数据用 AES-CBC 加密密钥由gen_key.py生成后分别写进server.ini和agent.ini——注意是分别写进去而不是共用一份配置文件否则控制端密钥一泄露所有被控端的通信都等于裸奔。协议格式是 JSON 套 JSON外层是{ device_id: ..., payload: ..., sign: ... }payload是加密后的采集结果sign是 device_id 加时间戳的 HMAC 签名。我一开始觉得这项目是内网工具搞加密有点多余但后来想到一个实际场景很多内网也会有旁路抓包或安全审计如果上报的 hostname、IP、软件列表全是明文被扫到就是一个个活靶子。所以加密不是玄学是资产类工具的基本素养。有一点要专门说明这版升级刻意没有做任意命令下发功能控制端只支持两种指令一是更新心跳间隔二是触发一次立即上报。我在读代码的时候觉得这个设计很稳——资产管理工具一旦能下发 shell 命令性质就变了权限边界也说不清。如果你后续要加远程操作能力建议单独做一套带审批和审计的命令通道不要往这套协议里硬塞。3. 核心模块逐段拆解采集、心跳、入库与状态判定这一章进入代码层面。我按一次完整心跳从被控端到控制端再到数据库的顺序来拆先看被控端怎么采集数据再看怎么组装和加密报文然后看控制端怎么解密入库最后看前端怎么把离线状态展示出来。3.1 被控端采集psutil 一把梭但保留了扩展位collector.py是整个 Agent 的数据源头。它的核心函数是collect_base_info()职责是把主机的基础信息全部捞出来。注意它有一个get_device_id()函数用网卡 MAC 和主机名拼出一个稳定的哈希值作为设备 ID这样即使配置文件丢失也能在一定程度上通过设备特征找回这台机器的历史记录。import platform import socket import uuid import hashlib import psutil def get_device_id(): 用网卡MAC 主机名生成稳定设备ID避免配置丢失后设备身份变化 raw f{socket.gethostname()}-{uuid.getnode()}.encode() return hashlib.md5(raw).hexdigest()[:12] def collect_base_info(): info {} info[device_id] get_device_id() info[hostname] socket.gethostname() info[os] f{platform.system()} {platform.release()} info[ip] get_lan_ip() return info def get_lan_ip(): 通过UDP握手获取本机在内网的实际IP不实际发包 s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect((10.255.255.255, 1)) ip s.getsockname()[0] except Exception: ip 127.0.0.1 finally: s.close() return ip def collect_metrics(): return { cpu_percent: psutil.cpu_percent(interval0.5), mem_percent: psutil.virtual_memory().percent, disk_percent: psutil.disk_usage(/).percent, boot_ts: psutil.boot_time(), }这里有两个容易写错的地方。第一个是psutil.cpu_percent(interval0.5)如果传interval0第一次调用返回的是 0.0因为这个值需要跨两次采样才能算出来。第二个是获取内网 IP 的技巧用socket.connect((10.255.255.255, 1))不会真正发包只是让系统选出一条路由然后getsockname()拿到本机 IP。这个做法比gethostbyname(gethostname())靠谱得多后者在多网卡机器上经常会返回 127.0.0.1 或 docker0 的地址。collect_metrics里的boot_ts是给前端算「运行时长」用的后续加告警逻辑也可以拿它判断机器是否刚重启过。我一般会把「采集原始数据」和「组装上报报文」分开写因为后续如果加软件列表采集会非常耗时需要放到一个独立线程里去做不能阻塞主心跳循环。3.2 心跳组装与加密不要用 ECB 模式heartbeat.py做两件事把采集结果组装成加密的 payload然后 POST 到控制端。这版升级把加密从 ECB 换成了 CBC这是有实际背景的ECB 模式同样的明文会得到同样的密文块在内网场景下如果攻击者拿到了多份密文可以从重复块推断出某些固定信息。CBC 加上随机初始向量之后同样的内容每次密文都不同安全性好很多。import time import json import random import hashlib import hmac import base64 from crypto_helper import aes_cbc_encrypt # 来自项目的 crypto_helper.py HEARTBEAT_INTERVAL 60 def build_payload(base_info, metrics, key): # 把采集结果组合为一个字典并附加时间戳 data dict(base_info) data.update(metrics) data[ts] int(time.time()) # 序列化后做 AES-CBC 加密 plaintext json.dumps(data).encode(utf-8) ciphertext aes_cbc_encrypt(plaintext, key) return base64.b64encode(ciphertext).decode(utf-8) def build_sign(device_id, ts, secret): msg f{device_id}:{ts}.encode(utf-8) return hmac.new(secret.encode(utf-8), msg, hashlib.sha256).hexdigest() def send_heartbeat(server_url, device_id, payload, sign): body { device_id: device_id, payload: payload, sign: sign, } # 发送到控制端的心跳接口超时设置为10秒防止网络抖动卡死主循环 resp http_post_json(server_url /api/heartbeat, body, timeout10) return resp参数说明key是加密密钥来自agent.inisecret是 HMAC 签名的独立密钥跟 AES 密钥分开用避免一个密钥做两件事。build_sign里的msg格式固定为device_id:ts控制端会用同样的格式重算签名做比对时间戳超过正负 300 秒就直接拒绝这是防重放的一个基本手段。send_heartbeat里的http_post_json是项目里封装好的工具函数它把 JSON 序列化、请求头设置、timeout 都包在了一个函数里。心跳请求的 timeout 我建议不要超过 10 秒因为心跳是周期性的如果某一次卡在网络上主循环会被阻塞后面的心跳全都会延迟——我见过有项目把 timeout 设成 30 秒结果控制端挂了一次所有被控端的采集线程全都堆在等响应上内存直接涨了 200MB。3.3 控制端入库update_or_create 是资产工具的核心控制端的heartbeat.py里有一个update_or_create()函数这是资产类工具和普通数据展示工具最大的区别。普通工具是「插入一条新记录」资产工具必须做到「同一设备重复上报时更新已有记录而不是插入新行」。如果不做这个幂等处理数据库表会被心跳数据快速灌满一台机器跑了三天就会多出几千行垃圾数据。from flask import request, jsonify from models import Device, HeartbeatLog, db def heartbeat(): body request.get_json(forceTrue) device_id body.get(device_id) payload body.get(payload) sign body.get(sign) # 验签防止伪造设备上报 expect_sign build_sign(device_id, payload, app.config[HMAC_SECRET]) if sign ! expect_sign: return jsonify({code: 401, msg: sign error}), 401 # 解密 payload data json.loads(aes_cbc_decrypt(base64.b64decode(payload), app.config[AES_KEY])) # 查找设备存在则更新不存在则创建 device Device.query.filter_by(device_iddevice_id).first() if device is None: device Device(device_iddevice_id, first_seennow_ts) db.session.add(device) device.hostname data.get(hostname) device.ip data.get(ip) device.os data.get(os) device.cpu_percent data.get(cpu_percent) device.mem_percent data.get(mem_percent) device.disk_percent data.get(disk_percent) device.last_heartbeat now_ts device.is_online (now_ts - data.get(ts)) app.config[ONLINE_WINDOW] # 心跳历史单独写一张表保留最近30天数据 log HeartbeatLog(device_iddevice_id, payloaddata, created_atnow_ts) db.session.add(log) db.session.commit() return jsonify({code: 0, ts: now_ts})逻辑说明整个函数分四步——验签、解密、更新设备信息、写心跳日志。验签放在解密前面是有讲究的如果先解密再验签攻击者可以制造一堆垃圾密文让你白白做解密运算消耗 CPU。虽然这是内网工具但控制端机器通常配置不高能省的运算尽量省。ONLINE_WINDOW在配置里定义默认值是心跳间隔的两倍加 10 秒的宽限。这里有一个容易忽略的点data.get(ts)是被控端的时间now_ts是控制端的时间如果两台机器的时间不同步is_online的判断就会失真。所以我在部署步骤里专门写了一条被控端和控制端最好都用同一个 NTP 源校准时间否则你会看到一堆明明在跑却显示离线的设备排查到天亮也不知道为什么。3.4 前端面板原生 JS 轮询不引入构建工具前端部分在templates/index.html里没有用 Vue 或 React就是原生 JS 加一个setInterval每 10 秒请求一次/api/devices把设备列表渲染成表格。这个选择在内部工具里很务实——前端引入构建工具链意味着要维护 package.json、node_modules 和构建脚本对一个几十行接口就能说清楚的后端工具来说代价大于收益。不过这里有一个坑setInterval如果在前一个请求还没返回时就触发下一次请求会让服务器多收到一堆堆积的请求。这个项目里做了个简单的处理用fetch后finally里再设置下一个 timeout而不是用固定间隔的setInterval这样能保证上一个请求要么成功返回、要么抛异常之后才发起下一个。function refreshDevices() { fetch(/api/devices) .then((res) res.json()) .then((data) { renderTable(data.devices); }) .catch((err) { // 控制端挂了的时候不把页面也卡死下一轮再试 console.error(refresh failed, err); }) .finally(() { setTimeout(refreshDevices, 10000); }); }离线状态在表格里用颜色标识在线是绿色离线超过五分钟是红色中间状态是黄色。色阶的判断逻辑写在renderTable里根据last_heartbeat和当前时间的差值动态计算而不是只信数据库里的is_online字段——因为页面打开后如果不刷新本地的判断会比数据库里的字段更实时。4. 跑通全流程初始化数据库、启动控制端与被控端源码拿到手之后最让人着急的就是「能不能先跑起来」。Star Rat 3.1 的启动流程我梳理成三步改配置、初始化、两段启动。下面按顺序走每步做什么、改哪些参数、失败看什么一次说清楚。4.1 环境准备Python 版本与依赖安装项目要求 Python 3.8 以上被控端理论上 3.6 也能跑但控制端用了 Flask 2.x 的某些特性所以统一建议 3.8 以上。依赖分两边控制端需要flask、flask-sqlalchemy、pycryptodome被控端只需要psutil和pycryptodome。# 控制端机器上执行 pip install flask flask-sqlalchemy pycryptodome # 被控端机器上执行 pip install psutil pycryptodome注意pycryptodome在 Windows 上偶尔会因为缺少编译工具而安装失败遇到这种报错直接装预编译的 wheel 包pip install pycryptodome --only-binary:all:。我在 Windows Server 上实测过这个命令能绕开本机编译的麻烦。如果被控端机器没有外网权限就用pip download在有网的机器上把 wheel 包拉下来再离线安装安装路径写进agent.ini的注释里了。4.2 修改配置密钥、地址、心跳间隔启动之前先跑scripts/gen_key.py它会生成一个 32 字节的随机 AES 密钥和一个 HMAC 密钥打印在屏幕上。然后把这两个值分别写进server.ini和agent.ini。这里的「分别写」不是说两边写不同的密钥而是说控制端只认自己那份配置被控端只认自己那份配置两份都要做修改。# config/agent.ini 示例 [agent] device_id auto # 首次启动时自动生成并回写 server_url http://192.168.1.10:5000 heartbeat_interval 60 # 心跳间隔单位秒 aes_key 生成的值 hmac_secret 生成的值 [collect] enable_software_list 0 # 是否采集已安装软件列表Windows下较慢参数说明device_id设为auto时首次启动会自动生成 ID 并写回配置文件这个设计是为了减少部署时的手工操作。heartbeat_interval默认 60 秒对应控制端的ONLINE_WINDOW两边需要同步修改。enable_software_list平时建议关着因为 Windows 上遍历已装软件列表可能要 20 到 30 秒会拖慢整个采集进程需要的时候再临时打开。4.3 初始化数据库与启动控制端控制端数据库初始化用scripts/init_db.py它会创建sqlite:///star_rat.db建devices、heartbeat_logs和users三张表。注意这个脚本默认会删除已有的库重建所以不要反复运行否则资产数据会被清空。cd star_rat_3_1 python scripts/init_db.py python server/app.py --host 0.0.0.0 --port 5000--host 0.0.0.0是必须的否则 Flask 默认只监听 127.0.0.1内网其他设备根本访问不到。启动成功后浏览器打开http://控制端IP:5000能看到资产列表页此时应该是空白表格状态因为还没有任何被控端上报。控制端日志建议不要直接关到后台去。Unix 上用nohup可以但日志至少要保留因为心跳接口如果异常日志里能看到验签失败、解密失败的具体报错。把这版升级的排查经验浓缩一句就是控制端先以前台方式跑起来观察两轮心跳确认没问题再扔给 supervisor 或 systemd 管理一上来就直接上进程守护排错会很痛苦。4.4 启动被控端并验证闭环被控端启动就简单多了一条命令跑起来python agent/agent.py首次启动会在控制端自动创建设备记录第二次心跳开始走更新逻辑。这时回到控制端页面刷新能看到一条设备记录状态为在线CPU、内存、磁盘数据都有。如果显示离线先看两边的系统时间差再看server.ini里的ONLINE_WINDOW一般这两个位置能把八成问题定位到。验证闭环是否真正打通可以做一个动作手动停止被控端进程等超过心跳间隔两倍再加 10 秒刷新页面设备应该变为离线。再重新启动被控端设备应该在下一个心跳周期内恢复在线。这一停一起如果都能正确切换状态说明这条链路的最关键环节已经跑通了。5. 避坑与排查五条真实踩坑记录这个项目我前后跑了两轮第一轮踩了不少坑大部分跟环境有关少部分跟设计有关。整理五条最有代表性的按「现象 → 原因 → 解决」的格式写方便内网环境下的同学对着排查。5.1 心跳上报成功但设备始终显示离线现象被控端日志显示 POST 请求都返回了 200控制端数据库里也有新记录写入但页面上设备状态一直是离线刷新多少次都不变。原因控制端用data.get(ts)也就是被控端的时间来判断在线窗口而被控端机器的时间比控制端慢了八分钟导致now_ts - data.ts已经超过了ONLINE_WINDOW状态判定为离线。解决把被控端与控制端的时间校准到同一个 NTP 源。Windows 上用w32tm /resyncLinux 上用ntpdate或chronyc makestep。从那以后我每次部署这套工具都会先执行一遍时间同步再启动被控端不会再因为时差问题白折腾一圈。5.2 AES 解密报 data 不是 16 的整数倍现象被控端加密后发送控制端解密时报binascii.Error: Incorrect padding或者data must be padded to 16 byte boundary。原因加密时对密文做了 Base64 编码控制端先做 Base64 解码再解密但过程中某个环节把 Base64 字符串里的号或/号弄丢了导致解码结果不完整。常见于用 print 或日志输出密文拿去手动测试的情况也有的是因为 HTTP 请求里没有设置Content-Type: application/json导致 JSON 解析时字符串被转义。解决不要手动复制粘贴密文做验证直接用代码里的函数调。检查请求头有没有Content-Type: application/json没有的话 Flask 的get_json(forceTrue)虽然能解析但某些字符会被重新编码。后续我在http_post_json里固定加上请求头这个问题就再没出现过。5.3 Windows 被控端采集软件列表时卡死现象被控端启动后一直没心跳卡在采集阶段进程 CPU 占用不高但就是不往下走。原因enable_software_list打开后程序会遍历注册表获取已安装软件列表Windows 上这台机器装了几百个软件遍历过程中还有一个读取损坏注册表键值时抛异常没被捕获线程直接卡住。解决升级版的collector.py里给软件列表遍历单独加了一个子进程超时机制超过 30 秒直接放弃。如果你改了代码建议用multiprocessing或concurrent.futures给采集函数包一层超时控制。更保守的做法是把软件列表采集改成独立线程不阻塞主心跳循环。5.4 控制端 SQLite 提示 database is locked现象被控端数量达到几十台之后控制端日志里频繁出现database is locked心跳接口超时率上升。原因SQLite 在同一时间只允许一个写事务多台设备同时上报时写操作排队冲突的概率大幅上升。升级版的models.py里把 sqlite 的timeout设成了 30 秒但并发高了依然扛不住。解决短期内把 SQLite 换到 WAL 模式能缓解一行 PRAGMA 的事。长期建议迁到 PostgreSQL 或 MySQL——我在第 6 章会写具体的迁移思路。如果你只是演示用途WAL 模式是零成本提升并发的好办法。5.5 控制端机器重启后设备全部离线现象控制端重启页面刷新后所有设备都是离线但被控端日志显示心跳一直发得正常。原因控制端重启期间被控端的心跳堆在重试队列里控制端恢复后收到大量积压请求处理不过来处理完的请求里带的时间戳已经是几十分钟前的了自然被判定为离线。解决这个属于正常现象不是 bug。控制端恢复后等一个心跳间隔就会自动修复。要加速恢复可以给被控端加一个「启动后 1 分钟内立即上报一次」的逻辑这样控制端一恢复所有设备会在短时间里完成一次补报状态就刷新了。升级版已经在agent.py里加了这个快速补报机制部署新版的话不需要额外处理。6. 二次开发把它改成能持续用的内部资产工具如果你只是演示一遍就丢到一边那有点可惜。这版源码升级的一个主要目标就是给二次开发留好了扩展位。这一章写三个改造方向接入企业微信告警、支撑多网段采集、把 SQLite 迁到 MySQL。每一个都是我在实际内网环境里验证过的改法按需取用。6.1 加一个「离线告警」触发器资产工具最常用的附加功能就是离线告警。控制端目前只在页面展示状态没有主动推送。加一个独立的巡检脚本每五分钟查一次设备表把is_online0超过 30 分钟的设备汇总推送到企业微信机器人。# scripts/offline_alert.py from models import Device from utils import wecom_push def run(): devices Device.query.filter(Device.is_online 0).all() offline [d for d in devices if now_ts - d.last_heartbeat 1800] if offline: msg \n.join(f{d.hostname} {d.ip} {d.last_heartbeat} for d in offline) wecom_push(msg)6.2 多网段采集部署一批探针而不是加大并发被控端数量多了以后不要把所有设备都指向同一个控制端那会让 SQLite 和 Flask 双双成为瓶颈。我一般的做法是每个网段放一个控制端实例本地入库再通过一个上游汇总脚本把各实例的数据拉取合并到总库。这样被控端和控制端之间走的是局域网延迟低、依赖小总库那边只做聚合展示。6.3 数据库迁移SQLite 换到 MySQL当设备数量超过五百SQLite 就不太够用了。迁移时把models.py的db连接串从sqlite:///star_rat.db改成mysqlpymysql://user:passhost/star_rat然后在init_db.py里跑一次建表即可。关键词是HeartbeatLog表的清理策略迁移后建议加一条定时删除 30 天前心跳日志的 SQL不然一年下来这张表会到上亿行查询和写入都会变慢。我最初跑这个项目时第一版把所有心跳都存了下来三个月后单表过百万行页面加载一次要六七秒。后来加了定时清理按created_at按月分区查询速度才恢复正常。从那以后我每次接手资产类项目第一件事都是先问「数据保留多久」先把清理策略定下来再聊功能。如果你想把这套改造成内部真正长期在跑的生产工具这几个方向都能直接动手。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑