
简介本资源是一套基于Android QQ协议的Python轻量级实现方案面向网络协议研究者、逆向分析初学者及Python自动化开发爱好者旨在帮助理解QQ移动端通信机制并实现基础账号操作。资源共7个文件包含6个核心Python脚本如AndroidQQ.py负责协议封装、test.py提供调用示例和1个说明文本含研究群信息与协议背景提示总大小仅19KB结构紧凑、无冗余依赖便于快速导入调试与协议层拆解学习。已有247人下载学习适合希望从真实协议切入、掌握Android端IM登录/上线/注销等状态管理逻辑的实践者。读者可直接复用模块化代码结构结合txt中的研究线索延伸分析加密流程与包体特征是入门即时通讯协议逆向与Python网络编程的典型小而精样本。1. 用 Python 解析 Android QQ 协议不是模拟登录而是理解通信结构你手头有个叫AndroidQQ-Python.zip的压缩包解压后看到一堆.py文件、proto/目录和config.json但运行就报错ModuleNotFoundError: No module named qqproto或者你正尝试抓包 Android QQ 的 App 流量发现 TLS 握手后全是加密二进制Wireshark 里只显示Application Data—— 这不是“逆向失败”而是你还没切入 QQ 协议的真正入口协议层解析而非会话层模拟。本文讲的不是“用 Python 登录 QQ”而是把AndroidQQ-Python.zip当作一份可读、可调试、可验证的协议实现样本还原它如何将login_request封装成 protobuf 消息、如何计算sig字段、如何处理SSO加密通道握手。适合网络协议开发者、安全分析人员、以及想脱离黑盒 SDK 做深度集成的 IM 系统构建者。不依赖任何 APK 反编译或内存 dump所有逻辑都落在 Python 可执行、可断点、可单元测试的代码路径上。2. 从AndroidQQ-Python.zip结构反推 QQ 协议分层模型2.1 解压即见协议骨架四个核心目录的职责划分AndroidQQ-Python.zip解压后典型结构如下非完整列表仅关键路径├── core/ │ ├── sso.py # SSOSecure Sign-On通道管理密钥协商、token 生成、加密传输封装 │ ├── packet.py # 网络包构造与解析含 headercmdid, seq, uin、bodyprotobuf 序列化数据、crc32 校验 │ └── crypto.py # 密码学原语AES-CBC用于 body 加密、RSA用于 sig 签名、MD5/SHA1用于摘要 ├── proto/ │ ├── oicq/ # Google Protocol Buffers 定义文件.proto对应 QQ 服务端接口 │ │ ├── login.proto # LoginRequest/LoginResponse 消息定义 │ │ ├── message.proto # MsgBody/MsgHead/GroupMessage 定义 │ │ └── friend.proto # GetFriendListRequest/Response │ └── compile.sh # 使用 protoc 编译 .proto 到 Python 类需安装 protobuf-compiler ├── examples/ │ └── login_demo.py # 最小可运行示例构造登录请求、发送、解析响应 └── config.json # 配置项服务器地址如 msf2.qq.com:8080、APPID537114962、版本号8.9.5.1000这个结构暴露了 QQ 移动端协议的三层抽象应用层proto/中的.proto文件定义业务消息语义如LoginRequest.uin是 64 位整数LoginRequest.password_md5是 16 字节 hex string会话层core/sso.py实现 SSO 通道它不直接发 HTTP而是在 TCP 连接上建立带密钥协商的加密隧道传输层适配core/packet.py将 protobuf 消息序列化后按 QQ 自定义二进制格式打包固定 16 字节 header body 4 字节 crc而非 HTTP POST 或 WebSocket frame。提示AndroidQQ-Python.zip不是“Python 版 QQ 客户端”它缺少 UI 和持久化本质是协议栈的参考实现。它的价值在于把 Android QQ APK 中隐藏在 so 库里的协议逻辑用 Python 显式写出便于审计、修改、跨平台复用。2.2login_demo.py的最小可运行流程四步走通协议握手以examples/login_demo.py为起点我们拆解一次成功登录的完整链路已去除异常处理聚焦主干# examples/login_demo.py简化版 from core.sso import SSOClient from core.packet import build_packet from proto.oicq.login_pb2 import LoginRequest # 1. 初始化 SSO 客户端加载配置、生成设备标识 client SSOClient(config_pathconfig.json) # 2. 构造 LoginRequest protobuf 消息 req LoginRequest() req.uin 123456789 req.password_md5 be10adc3949ba59abbe56e057f20f883e # 123456 的 MD5 req.app_id client.config[app_id] # 537114962 req.version client.config[version] # 8.9.5.1000 # 3. 用 SSO 通道加密并打包 body req.SerializeToString() packet build_packet(cmdid0x0001, seq1, uinreq.uin, bodybody, sso_clientclient) # 4. 发送原始 TCP 包非 HTTP sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((client.config[host], client.config[port])) sock.send(packet) response sock.recv(8192) sock.close()这段代码揭示三个关键事实协议不是 HTTPbuild_packet()输出的是 raw bytessocket.send()直连msf2.qq.com:8080没有 Host 头、没有 Cookie、没有 TLS 握手SSO 自建加密cmdid 决定服务端路由cmdid0x0001对应登录接口0x0002是心跳0x000A是消息发送这些值硬编码在.proto注释或 Android 反编译 smali 中seq 和 uin 是状态标识seq是客户端自增序号防重放uin在 header 中明文传输服务端据此查用户状态但密码等敏感字段只在加密 body 中。2.2.1build_packet()的二进制格式详解必须掌握的字节布局core/packet.py中build_packet()函数生成的 16 字节 header 结构如下小端序OffsetLengthFieldExample Value说明0x002magic number0x0000QQ 协议魔数固定为 00x022cmdid0x0001命令 ID决定服务端处理逻辑0x044seq0x00000001请求序号客户端维护0x084uin0x075BCD15用户 UIN十进制 1234567890x0C4body_len0x000000A0后续 body 字节数未加密前header 后紧跟bodyprotobuf 序列化结果最后 4 字节是crc32(body)。整个 packet header body crc32。验证方法用xxd -g 1查看packet变量前 32 字节确认 offset 0x02 处是01 00cmdid0x0001 小端offset 0x08 处是15 cd 5b 07uin123456789 小端。注意body_len字段填的是原始 protobuf 字节数不是加密后长度。SSO 加密发生在build_packet()内部对body加密后再计算crc32但body_len字段值不变——这是服务端解包时的关键约定。2.3core/crypto.py中的三重密码学操作为什么不能跳过AndroidQQ-Python.zip的crypto.py并非简单调用cryptography库而是精确复现 Android QQ 的 Java 实现逻辑。以登录请求的sig字段为例非密码而是请求签名# core/crypto.py 中 sig 生成逻辑简化 def gen_sig(uin: int, app_id: int, password_md5: bytes, rand_key: bytes) - bytes: # Step 1: 构造原始签名字符串 raw f{uin}{app_id}android{password_md5.hex()}{rand_key.hex()}.encode() # Step 2: MD5 摘要注意不是 SHA256 md5_hash hashlib.md5(raw).digest() # Step 3: AES-CBC 加密keyrand_key, ivmd5_hash[:16] cipher AES.new(rand_key, AES.MODE_CBC, md5_hash[:16]) padded pad(md5_hash, AES.block_size) # PKCS#7 补位 return cipher.encrypt(padded)这个sig被塞入LoginRequest.sig字段服务端用相同逻辑验证。关键点rand_key 来自 SSO 握手不是固定值每次连接前通过SSOClient.handshake()获取保证签名一次性MD5 不是弱而是协议要求服务端校验时也用 MD5替换为 SHA256 会导致sig verify failedAES-CBC 的 IV 必须是md5_hash[:16]若误用全零 IV服务端解密失败返回0x0005错误码参数错误。表格crypto.py中各函数与协议环节的映射关系函数名输入输出协议环节备注gen_sig()uin, app_id, pwd_md5, rand_key16 字节加密 sigLoginRequest 构造必须与服务端完全一致aes_encrypt_body()plaintext, key, ivciphertextSSO 通道 body 加密keyrand_key, iv固定值rsa_sign()data, private_key_pembase64(sig)设备注册签名使用PKCS#1 v1.5非 PSS3. 用protoc编译.proto文件让 Python 知道 QQ 消息长什么样3.1 为什么不能直接import proto.oicq.login_pb2AndroidQQ-Python.zip中proto/目录下只有.proto文本文件没有login_pb2.py。Python 无法直接导入未编译的 protobuf 定义。常见错误$ python examples/login_demo.py ModuleNotFoundError: No module named proto.oicq.login_pb2这不是pip install protobuf能解决的——pip install protobuf只提供 runtime 库编译器protoc必须单独安装。3.2 在 Linux/macOS 上完成 protobuf 编译的完整命令链以下命令假设你已解压AndroidQQ-Python.zip到当前目录且protoc已在$PATH中若未安装请先brew install protobuf或apt install protobuf-compiler# 1. 进入 proto 目录 cd proto/ # 2. 创建输出目录避免污染源码 mkdir -p ../generated/ # 3. 编译所有 .proto 文件递归处理 oicq/ 子目录 protoc \ --python_out../generated \ --proto_path. \ oicq/*.proto # 4. 生成 __init__.py 让 Python 识别为包 touch ../generated/__init__.py touch ../generated/oicq/__init__.py # 5. 将生成的模块加入 Python path临时 export PYTHONPATH${PYTHONPATH}:$(pwd)/../generated执行后../generated/oicq/login_pb2.py就生成了。此时from proto.oicq.login_pb2 import LoginRequest才能成功。3.2.1protoc命令参数详解每个 flag 都影响生成结果参数值作用常见错误--python_out../generated输出目录路径指定.py文件生成位置若写成--python_out.文件会生成在proto/下导致 import 路径错乱--proto_path.proto 文件搜索根目录import common.proto时protoc从此路径找若省略oicq/login.proto中import oicq/common.proto会报File not foundoicq/*.proto输入文件列表支持 glob但需确保common.proto在oicq/下且被包含若只编译login.proto而它依赖common.proto则生成失败提示AndroidQQ-Python.zip中的.proto文件可能含import oicq/common.proto语句。务必确保common.proto与login.proto同在oicq/目录下否则protoc报错Import oicq/common.proto was not found or had errors。3.3 验证编译结果用 Python 交互式检查消息字段编译完成后立即验证LoginRequest是否可用$ python3 from generated.oicq.login_pb2 import LoginRequest req LoginRequest() req.uin 123456789 req.password_md5 be10adc3949ba59abbe56e057f20f883e req.ByteSize() # 返回序列化后字节数应 0 42 req.SerializeToString()[:16].hex() # 查看前 16 字节 hex 08000000000000000000000000000000如果req.ByteSize()返回0说明字段未正确赋值如uin赋了字符串123456789而非整数如果SerializeToString()报TypeError说明password_md5是 str 而非 bytes。这是新手最常踩的坑protobuf 字段类型严格Python 中 bytes 和 str 不能混用。4. 调试AndroidQQ-Python.zip的三大必查日志点4.1core/sso.py中的handshake()日志定位密钥协商失败SSO 握手是整个协议的前提。若client.handshake()卡住或返回空rand_key后续所有sig和加密都会失败。在core/sso.py的handshake()方法中插入日志# core/sso.py 行号约 120 def handshake(self): # ... 前置代码 print(f[DEBUG] SSO handshake start: {self.host}:{self.port}) sock.send(handshake_packet) resp sock.recv(1024) print(f[DEBUG] SSO handshake raw response: {resp[:32].hex()}) # 关键看前 32 字节 # ... 解析 resp if not self.rand_key: print([ERROR] SSO handshake failed: rand_key is None) return self.rand_key典型成功响应前 32 字节hex00000a0000000000000000000000000000000000000000000000000000000000其中0a00是 cmdid0x000A握手响应后续 4 字节是rand_key长度通常 16。若看到0000开头但第二字节不是0a说明服务端返回了错误码如0x0005参数错误。4.2core/packet.py中build_packet()的 CRC 校验日志排查包损坏服务端收到 packet 后第一件事是校验crc32(body)。若校验失败直接丢弃包客户端收不到任何响应。在build_packet()末尾加# core/packet.py 行号约 85 crc zlib.crc32(body) 0xffffffff packet header body crc.to_bytes(4, little) print(f[DEBUG] Packet CRC32: 0x{crc:08x}, body_len{len(body)}) # 必打 return packet对比服务端文档或抓包数据若服务端期望crc320x12345678而你打出0x87654321说明body在加密前已被篡改如 protobuf 字段类型错、补位方式错。4.3examples/login_demo.py的响应解析日志区分协议层与网络层错误不要只看sock.recv()是否超时。必须解析响应包# examples/login_demo.py response sock.recv(8192) print(f[DEBUG] Raw response length: {len(response)} bytes) if len(response) 16: print([ERROR] Response too short, likely connection reset) exit(1) # 解析 header cmdid int.from_bytes(response[2:4], little) seq int.from_bytes(response[4:8], little) print(f[DEBUG] Response cmdid0x{cmdid:04x}, seq{seq}) if cmdid 0x0001: # 登录响应 body response[16:-4] # 去 header 和 crc try: from generated.oicq.login_pb2 import LoginResponse resp LoginResponse() resp.ParseFromString(body) print(f[SUCCESS] Login success! UIN{resp.uin}, token{resp.token[:16]}...) except Exception as e: print(f[ERROR] Parse LoginResponse failed: {e}) else: print(f[ERROR] Unexpected cmdid 0x{cmdid:04x})这样能明确区分len(response) 0→ 网络断开防火墙拦截、IP 封禁cmdid ! 0x0001→ 协议错误如发了0x0001但服务端返回0x0005错误码ParseFromString()异常 →body解密失败或 protobuf 版本不匹配。5. 用 Wireshark 验证 Python 实现抓包比对字节级一致性5.1 设置 Android 模拟器抓包环境绕过 HTTPS 锁死Android QQ 8.9 默认强制 HTTPS但AndroidQQ-Python.zip直连msf2.qq.com:8080TCP非 TLS。要验证 Python 发包与真实 App 一致需让真实 App 也走明文 TCP。方法在 Android 模拟器中安装ShizukuPacket CaptureApp启动 Packet Capture选择msf2.qq.com域名打开 Android QQ触发登录此时流量走msf2.qq.com:8080非443导出 pcap 文件在 Wireshark 中过滤tcp.port 8080。5.2 Wireshark 中定位 QQ 协议包用 display filter 精准筛选真实 Android QQ 的登录包特征TCP Stream 中第一个包Stream index 0的cmdid0x0001body部分含password_md5的 hex 字符串如e10adc3949ba59abbe56e057f20f883eheader固定 16 字节magic0000。Wireshark display filtertcp.port 8080 tcp.len 16 (frame contains e10adc3949ba59abbe56e057f20f883e || tcp.payload[2:2] 01:00)此 filter 找到所有cmdid0x0001或含典型密码 MD5 的包。5.2.1 字节级比对用tshark提取 raw packet 并 hexdump导出 Wireshark 中的登录包为 raw 文件右键 → Export Packet Bytes再与 Python 生成的 packet 比对# 提取 Wireshark 导出的 packet.raw 前 64 字节 $ head -c 64 packet.raw | xxd -g 1 00000000: 00 00 01 00 01 00 00 00 15 cd 5b 07 a0 00 00 00 ..........[..... 00000010: 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ # 运行 Python 脚本生成 packet重定向到 file $ python examples/login_demo.py packet_py.bin 2/dev/null $ head -c 64 packet_py.bin | xxd -g 1两行xxd输出必须逐字节完全一致。若第 0x0C 字节body_len不同说明 Python 中body长度计算有误如 protobuf 序列化前多加了字段若第 0x10 字节后开始不同说明sig或加密逻辑偏差。提示AndroidQQ-Python.zip的core/crypto.py中pad()函数必须用 PKCS#7而非 PKCS#5且补位长度 block_size - (len(data) % block_size)。少补 1 字节或补错Wireshark 中body解密后就是乱码。5.3 服务端响应包解析确认 Python 能正确处理LoginResponse真实 App 的登录响应包中LoginResponse.token是一串 Base64 字符如AQAA...长度约 200 字节。在 Wireshark 中找到响应包提取bodyheader 后、crc 前用 Python base64 解码# 从 Wireshark 复制 body hex string如 0a01... body_hex 0a01... # 粘贴此处 body_bytes bytes.fromhex(body_hex) from generated.oicq.login_pb2 import LoginResponse resp LoginResponse() resp.ParseFromString(body_bytes) print(resp.token) # 应输出 Base64 字符串若ParseFromString()成功且resp.token非空证明你的 protobuf 编译和解析逻辑 100% 正确。此时AndroidQQ-Python.zip已具备生产级协议解析能力——下一步可扩展消息收发、群组操作、状态同步等。本文还有配套的精品资源点击获取