KeepALifeUS/sfs2x-py
GitHub: KeepALifeUS/sfs2x-py
纯 Python 实现的 SmartFoxServer 2X 二进制协议库,支持完整类型系统的编解码、XOR 混淆、压缩和 AES 加密,专为协议分析、MITM 代理和游戏机器人开发而构建。
Stars: 0 | Forks: 0
# sfs2x-py
[](https://github.com/KeepALifeUS/sfs2x-py/actions/workflows/ci.yml)
[](https://pypi.org/project/sfs2x-py/)
[](https://pypi.org/project/sfs2x-py/)
[](https://pypi.org/project/sfs2x-py/)
[](LICENSE)
**SmartFoxServer 2X 二进制协议的纯 Python 实现。**
以完整的类型保真度编码、解码和操作 SFS2X 数据包 —— 专为协议分析、MITM 代理和游戏机器人开发而构建。
## 什么是 SmartFoxServer 2X?
[SmartFoxServer 2X](https://www.smartfoxserver.com/) (SFS2X) 是一个被数百款移动端和网页游戏使用的实时多人游戏服务器。它通过 TCP 使用专有的二进制协议进行通信,该协议具有以下特点:
- 包含 19 种数据类型的丰富类型系统 (SFSObject / SFSArray)
- 对客户端到服务器的数据包进行 XOR 混淆
- 可选的 zlib/zstd 压缩
- AES-128-CBC 会话加密
**sfs2x-py** 是首个完全实现此传输协议的开源 Python 库。目前不存在公开的官方 SDK 或二进制格式文档 —— 本项目完全通过逆向工程构建。
## 功能特性
| 功能 | 描述 |
|---------|-------------|
| **完整类型系统** | 所有 19 种 SFS2X 数据类型:NULL, BOOL, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, UTF_STRING, 类型化数组, SFS_ARRAY, SFS_OBJECT |
| **数据包分帧** | 编码和解码 C2S (客户端到服务器) 和 S2C (服务器到客户端) 数据包 |
| **XOR 混淆** | 源自数据包大小的旋转 4 字节密钥 —— 在编码/解码时自动应用 |
| **压缩** | 针对 S2C 的 zlib 和 zstd 解压;针对 C2S 的 zlib 压缩 |
| **AES 加密** | 通过 `/BlueBox/CryptoManager` 端点进行会话密钥交换 |
| **类型保留解码** | `decode_typed()` 保留传输类型 (BYTE vs INT vs LONG) —— 对于需要重新编码数据包的 MITM 代理至关重要 |
| **数据包构建器** | 高级辅助函数:`make_extension_request()`, `make_keepalive()`, `parse_s2c_command()` |
| **零配置** | 除 `pycryptodome`, `aiohttp`, `zstandard` 外无外部依赖 |
## 安装
```
pip install sfs2x-py
```
从源码安装:
```
git clone https://github.com/KeepALifeUS/sfs2x-py.git
cd sfs2x-py
pip install -e ".[dev]"
```
需要 Python 3.10+。已在 3.10 到 3.14 版本上测试。
## 快速开始
### 编码和解码 SFSObject
```
from sfs2x import SFSCodec, TypedValue, INT, LONG
# 将 dict 编码为二进制 SFSObject
obj = {
"username": "player1",
"level": TypedValue(INT, 42),
"score": TypedValue(LONG, 1_000_000),
}
data = SFSCodec.encode(obj)
# 将其解码还原
decoded, bytes_consumed = SFSCodec.decode(data)
print(decoded)
# {'username': 'player1', 'level': 42, 'score': 1000000}
```
### 构建并发送扩展请求
```
from sfs2x import make_extension_request, TypedValue, INT
# 构建即发即用的 C2S 数据包(应用 XOR 混淆)
packet = make_extension_request(
cmd="chat.send",
params={"msg": "Hello!", "channel": TypedValue(INT, 1)},
server_id=1234,
)
# 通过 TCP 发送
sock.sendall(packet)
```
### 解码捕获的 S2C 流量
```
from sfs2x import decode_s2c_packet, parse_s2c_command, iter_s2c_packets
# 单数据包
raw = bytes.fromhex("80001f...")
obj, consumed = decode_s2c_packet(raw)
cmd, params = parse_s2c_command(obj)
print(f"{cmd}: {params}")
# 数据包流(跳过数据包间的噪声字节)
for obj, offset in iter_s2c_packets(tcp_stream):
cmd, params = parse_s2c_command(obj)
print(f"[{offset:#x}] {cmd}: {params}")
```
### 用于 MITM 代理的类型保留解码
在代理流量时,您需要重新编码数据包而不改变传输类型。服务器可能会将 `BYTE(5)` 和 `INT(5)` 区别对待,尽管它们都代表数字 5。
```
from sfs2x import decode_c2s_packet_typed, encode_c2s_packet
# 解码时保留精确的 wire types
obj, consumed = decode_c2s_packet_typed(raw_packet)
# obj["p"]["level"] 是 TypedValue(INT, 5),而不仅仅是 5
# 修改字段
obj["p"]["p"]["gold"] = TypedValue(INT, 9999)
# 重新编码 —— 所有其他字段保留原始 wire types
modified_packet = encode_c2s_packet(
obj["c"].value, obj["a"].value, obj["p"],
server_id=1234,
)
```
### AES 会话加密
```
from sfs2x import KeyExchange
kx = KeyExchange()
# 选项 1:从 CryptoManager 端点获取密钥
aes = await kx.fetch_crypto_key("game.example.com", 8443, session_token)
# 选项 2:从原始字节设置密钥(如果您捕获了密钥交换)
aes = kx.set_from_bytes(raw_32_bytes) # first 16 = key, last 16 = IV
# 加密/解密数据包载荷
encrypted = aes.encrypt(payload)
decrypted = aes.decrypt(encrypted)
```
## 传输协议参考
### 数据包分帧
**S2C (服务器到客户端):**
```
[0x80] [size: 2B BE] [SFSObject payload]
[0x88] [size: 4B BE] [SFSObject payload] (big-sized, >64KB)
[0xA0] [size: 2B BE] [zlib/zstd compressed] (compressed)
[0xB0] [dec_size: 4B] [zstd frame] (zstd with size prefix)
```
**C2S (客户端到服务器):**
```
[0xC4] [server_id: 2B BE] [size: 2B BE] [XOR-obfuscated SFSObject]
[0xE4] [server_id: 2B BE] [size: 2B BE] [XOR(zlib(SFSObject))]
```
**XOR 密钥推导:**
```
key = [size & 0xFF, (size >> 8) & 0xFF, 0x00, 0x00]
```
作为旋转的 4 字节掩码应用。位置 2 和 3 是空操作 (密钥字节为 0)。
### SFSObject 封装
每个数据包的载荷都是一个根 SFSObject:
```
"c" -> controller (BYTE: 0 = system, 1 = extension)
"a" -> action (SHORT: 0 = handshake, 1 = login, 13 = extension, 29 = keepalive)
"p" -> parameters (SFS_OBJECT)
```
扩展命令 (c=1, a=13) 具有嵌套参数:
```
"p"."c" -> command name (UTF_STRING, e.g. "chat.send")
"p"."r" -> room ID (INT, usually -1)
"p"."p" -> command params (SFS_OBJECT)
```
### SFS2X 类型系统
| ID | 类型 | Python | 传输大小 |
|----|------|--------|-----------|
| 0 | NULL | `None` | 0 |
| 1 | BOOL | `bool` | 1 byte |
| 2 | BYTE | `int` | 1 byte (无符号) |
| 3 | SHORT | `int` | 2 bytes (有符号) |
| 4 | INT | `int` | 4 bytes (有符号) |
| 5 | LONG | `int` | 8 bytes (有符号) |
| 6 | FLOAT | `float` | 4 bytes |
| 7 | DOUBLE | `float` | 8 bytes |
| 8 | UTF_STRING | `str` | 2B 长度 + UTF-8 |
| 9 | BOOL_ARRAY | `list[bool]` | 2B 计数 + 数据 |
| 10 | BYTE_ARRAY | `bytes` | 4B 计数 + 数据 |
| 11-16 | 类型化数组 | `list` | 2B 计数 + 数据 |
| 17 | SFS_ARRAY | `list` | 2B 计数 + 类型化元素 |
| 18 | SFS_OBJECT | `dict` | 2B 计数 + 键值对 |
自动调整大小:普通的 Python `int` 值会自动编码为能容纳该值的最小类型 (BYTE -> SHORT -> INT -> LONG)。使用 `TypedValue` 强制指定特定的传输类型。
## API 参考
### `sfs2x.objects`
| 函数 | 返回值 | 描述 |
|----------|---------|-------------|
| `SFSCodec.encode(obj)` | `bytes` | 将字典编码为 SFSObject 二进制 |
| `SFSCodec.decode(data)` | `(dict, int)` | 解码 SFSObject,返回 (dict, bytes_consumed) |
| `SFSCodec.decode_typed(data)` | `(dict, int)` | 解码并将传输类型保留为 `TypedValue` |
| `TypedValue(type_id, value)` | — | 在编码时强制指定特定的传输类型 |
### `sfs2x.protocol`
| 函数 | 返回值 | 描述 |
|----------|---------|-------------|
| `encode_c2s_packet(ctrl, action, params, server_id, compress)` | `bytes` | 构建带有 XOR 混淆的 C2S 数据包 |
| `decode_c2s_packet(data)` | `(dict, int)` | 解码 C2S 数据包 |
| `decode_c2s_packet_typed(data)` | `(dict, int)` | 解码 C2S 并保留传输类型 |
| `encode_s2c_packet(obj, compress)` | `bytes` | 构建 S2C 数据包 |
| `decode_s2c_packet(data)` | `(dict, int)` | 解码 S2C 数据包 (处理压缩) |
| `make_extension_request(cmd, params, room_id, server_id)` | `bytes` | 构建扩展请求数据包 |
| `make_keepalive(client_time, server_id)` | `bytes` | 构建保活 (keepalive) 数据包 |
| `parse_s2c_command(obj)` | `(str\|None, dict)` | 从 S2C 中提取命令名称和参数 |
| `iter_s2c_packets(data)` | `Iterator` | 迭代字节流中的数据包 |
### `sfs2x.crypto`
| 函数 | 返回值 | 描述 |
|----------|---------|-------------|
| `AESCipher(key, iv)` | — | 每个会话具有固定 IV 的 AES-128-CBC |
| `KeyExchange()` | — | 通过 CryptoManager 管理密钥交换 |
| `await kx.fetch_crypto_key(host, port, token)` | `AESCipher` | 从服务器获取密钥 |
| `kx.set_from_bytes(data)` | `AESCipher` | 从原始 32 字节设置密钥 |
| `make_password_hash(token, password)` | `str` | MD5 登录哈希 |
## 使用案例
- **协议分析** —— 解码从 tcpdump/Wireshark 捕获的流量以理解游戏通信
- **MITM 代理** —— 实时拦截、检查和修改数据包 (参见 `examples/mitm_proxy.py`)
- **游戏机器人** —— 构建直接与 SFS2X 服务器通信的无头客户端
- **安全研究** —— 审计游戏服务器实现中的漏洞
- **模组工具** —— 构建与基于 SFS2X 的游戏交互的自定义工具
## 示例
`examples/` 目录包含:
- **`decode_packet.py`** —— 演示编码和解码示例数据包
- **`mitm_proxy.py`** —— 透明 TCP 代理,双向记录所有 SFS2X 命令
## 贡献
有关设置说明和指南,请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
## 许可证
[MIT](LICENSE) —— 可用于任何用途。
标签:IP 地址批量处理, Python, SFS2X, SmartFoxServer, TCP/IP, 中间人攻击, 二进制协议, 云资产清单, 协议分析, 协议实现, 开源库, 搜索引擎爬虫, 数据加密, 数据包编解码, 数据压缩, 数据解析, 无后门, 权限提升, 游戏开发, 游戏服务器, 游戏机器人, 网络安全, 网络库, 网络通信, 逆向工具, 逆向工程, 防御绕过, 隐私保护