Facebook Messenger API 逆向技术难点详解
本文档详细分析 项目中遇到的 API 逆向技术难点及其解决方案
目录
- [E2EE 端到端加密协议逆向](#E2EE 端到端加密协议逆向)
- [GraphQL API 的 doc_id 逆向](#GraphQL API 的 doc_id 逆向)
- [动态 Token 提取](#动态 Token 提取)
- [MQTT WebSocket 消息同步协议](#MQTT WebSocket 消息同步协议)
- [ID 生成算法逆向](#ID 生成算法逆向)
- [移动 APP 登录协议逆向](#移动 APP 登录协议逆向)
- 响应反混淆处理
- 技术难点总结表
一、E2EE 端到端加密协议逆向 ⭐⭐⭐⭐⭐(最难)
技术挑战
Facebook Messenger 的 E2EE 使用 Signal Protocol + Meta 内部 Labyrinth/Lightspeed 协议,纯 Python 实现几乎不可能,原因如下:
- 涉及 10 万+ 行 Go 代码,已有成熟实现
- 需要处理 Curve25519 、Double Ratchet 、Sender Keys 等复杂密码学算法
- 协议频繁更新,维护成本极高
- 安全审计要求严格,自行实现风险大
解决方案:Go 桥接混合架构
核心思路 :Python 不自己实现加密,而是调用成熟的 Go 库 mautrix/meta
进程间通信设计
python
# _listening_e2ee.py - 启动 Go 桥接进程
class _BridgeProcess:
def __init__(self, binary: Path, *, log_stderr: bool = True) -> None:
# 启动 Go 子进程
self._proc = subprocess.Popen(
[str(binary)],
stdin=subprocess.PIPE, # Python → Go
stdout=subprocess.PIPE, # Go → Python
stderr=subprocess.PIPE,
bufsize=0,
)
# 启动线程异步读取事件
self._reader = threading.Thread(target=self._read_loop, daemon=True)
self._reader.start()
JSON-RPC 协议封装
python
def call(self, method: str, params: Optional[dict] = None, timeout: float = 60.0):
# 构建 JSON-RPC 请求
rid = next(self._next_id)
payload = {"id": rid, "method": method, "params": params or {}}
line = (json.dumps(payload, separators=(",", ":")) + "\n").encode("utf-8")
# 写入 Go 进程 stdin
self._proc.stdin.write(line)
self._proc.stdin.flush()
# 等待响应
resp = q.get(timeout=timeout)
return resp.get("data") or {}
发送 E2EE 消息
python
def send_e2ee_message(self, chat_jid: str, text: str, ...):
return self._bridge.call("sendE2EEMessage", {
"chatJid": chat_jid,
"text": text,
"replyToId": reply_to_id,
"replyToSenderJid": reply_to_sender_jid,
})
架构优势
| 优势 | 说明 |
|---|---|
| ✅ 安全性高 | 使用经过审计的成熟代码库 |
| ✅ 维护简单 | 无需自己维护加密算法 |
| ✅ 性能好 | Go 更适合加密计算 |
| ✅ 稳定性强 | Go 崩溃不会影响 Python |
二、GraphQL API 的 doc_id 逆向 ⭐⭐⭐⭐
技术挑战
Facebook 使用 Relay Modern GraphQL 框架,每个查询都有固定的 doc_id:
doc_id是 GraphQL 查询的哈希标识- 从前端 JavaScript 代码中提取
- 随着 Facebook 版本更新而变化
- 没有公开文档,需要逆向查找
代码实现
GraphQL 查询构建
python
# _search.py - 搜索功能实现
def func(dataFB, keywordSearch):
# doc_id=6866854183333610 是通过逆向分析得到的
dataForm = formAll(dataFB, "SearchCometResultsInitialResultsQuery", 6866854183333610)
# GraphQL variables 需要严格匹配前端格式
dataForm["variables"] = json.dumps({
"count": 5,
"args": {
"callsite": "COMET_GLOBAL_SEARCH",
"context": {
"bsid": str(randStr(8) + "-" + randStr(4) + "-" + randStr(4) + "-" + randStr(4) + "-" + randStr(12)),
"tsid": str(random.random())
},
"text": str(keywordSearch)
},
"cursor": None,
"feedbackSource": 23,
"fetch_filters": True,
# ... 更多参数,少一个都可能失败
})
工具函数封装
python
# _utils.py - GraphQL 表单构建
def formAll(dataFB, FBApiReqFriendlyName=None, docID=None, requireGraphql=True):
dataForm = {
"fb_dtsg": dataFB["fb_dtsg"],
"jazoest": dataFB["jazoest"],
"__a": 1,
"__user": str(dataFB["FacebookID"]),
"__req": str_base(__reg, 36),
"__rev": dataFB["clientRevision"],
"av": dataFB["FacebookID"],
}
if requireGraphql:
dataForm["fb_api_caller_class"] = "RelayModern"
dataForm["fb_api_req_friendly_name"] = FBApiReqFriendlyName
dataForm["doc_id"] = str(docID) # 关键:逆向得到的查询 ID
return dataForm
三、动态 Token 提取 ⭐⭐⭐⭐
技术挑战
动态 Token(fb_dtsg, jazoest, hash 等):
- 嵌入在 HTML 中的 JavaScript 变量里
- 格式经常变化(Facebook 不定期修改)
- 需要从混淆的 JS 代码中提取
- 没有固定的 DOM 结构,不能用 XPath/CSS 选择器
解决方案:字符串分割技术
Token 提取实现
python
# _session.py - 从首页提取 Token
def dataGetHome(setCookies):
# 请求首页 HTML
sendRequests = requests.get(**mainRequests).text
# 使用字符串分割技术(比 HTML 解析更稳定)
splitDataList = [
# 格式:[变量名, 前缀标记, 后缀标记]
["fb_dtsg", 'DTSGInitialData",[],{"token":"', '"'],
["fb_dtsg_ag", 'async_get_token":"', '"'],
["jazoest", "jazoest=", '"'],
["hash", 'hash":"', '"'],
["sessionID", 'sessionId":"', '"'],
["FacebookID", '"actorID":"', '"'],
["clientRevision", "client_revision":", ","],
]
dictValueSaved = {}
for i in splitDataList:
nameValue = i[0]
try:
exportValue = dataSplit(i[1], i[2], HTML=sendRequests, defaultValue=True)
except (IndexError, AttributeError, TypeError):
exportValue = f"Unable to retrieve data for {nameValue}"
dictValueSaved[nameValue] = exportValue
return dictValueSaved
分割工具函数
python
# _utils.py - 字符串分割工具
def dataSplit(string1, string2, numberSplit1=None, numberSplit2=None, HTML=None, ...):
if defaultValue:
numberSplit1, numberSplit2 = 1, 0
# 先按前缀分割取后半部分,再按后缀分割取前半部分
return HTML.split(string1)[numberSplit1].split(string2)[numberSplit2]
四、MQTT WebSocket 消息同步协议 ⭐⭐⭐⭐
技术挑战
实现实时消息监听需要解决:
- MQTT 主题和消息格式的逆向
sequence_id+sync_token的增量同步机制- 断线重连和队列溢出处理
- 消息去重和状态维护
代码实现
MQTT 连接配置
python
# _listening.py - MQTT 连接实现
def connect_mqtt(self):
# 构建连接参数(完全模拟浏览器)
user = {
"u": self.dataFB["FacebookID"],
"s": session_id,
"chat_on": json_minimal(True),
"fg": False,
"d": generate_client_id(),
"ct": "websocket",
"aid": 219994525426954, # Facebook 应用 ID
"st": "/t_ms",
"cp": 3,
"ecp": 10,
}
host = f"wss://edge-chat.facebook.com/chat?region=eag&sid={session_id}"
# MQTT 客户端配置
self.mqtt = mqtt.Client(
client_id="mqttwsclient",
clean_session=True,
protocol=mqtt.MQTTv31,
transport="websockets",
)
增量同步机制
python
def _messenger_queue_publish(client: mqtt.Client, userdata, flags, rc):
# 关键:先获取 last_seq_id
self.get_last_seq_id()
queue = {
"sync_api_version": 10,
"max_deltas_able_to_process": 1000,
"delta_batch_size": 500,
"encoding": "JSON",
"entity_fbid": self.dataFB['FacebookID'],
"orca_version": "1.2.0"
}
# 首次连接 vs 后续增量同步
if self.syncToken is None:
topics = "/messenger_sync_create_queue"
queue["initial_titan_sequence_id"] = self.lastSeqID
else:
topics = "/messenger_sync_get_diffs"
queue["last_seq_id"] = self.lastSeqID
queue["sync_token"] = self.syncToken
# 发布同步请求
client.publish(topics, json_minimal(queue), qos=1)
消息处理与错误恢复
python
def on_message(client, userdata, msg):
j = json.loads(msg.payload.decode())
# 解析消息 delta
if j.get('deltas') is not None:
delta = j["deltas"][0]
if delta.get('messageMetadata') is not None:
self.bodyResults["body"] = delta.get("body")
self.bodyResults["userID"] = delta["messageMetadata"]["actorFbId"]
self.bodyResults["messageID"] = delta["messageMetadata"]["messageId"]
# 更新同步标记(避免重复消息)
if "syncToken" in j and "firstDeltaSeqId" in j:
self.syncToken = j["syncToken"]
self.lastSeqID = j["firstDeltaSeqId"]
# 队列溢出处理(errorCode=100)
if "errorCode" in j and j["errorCode"] == 100:
print("Queue overflow - resetting and retrying...")
self.syncToken = None
self.get_last_seq_id()
五、ID 生成算法逆向 ⭐⭐⭐
技术挑战
Facebook 使用多种自定义 ID 算法:
threading_id:时间戳 + 随机数的二进制组合client_id:UUID 格式但非标准 UUIDsession_id:随机 64 位整数
这些算法没有公开文档,必须通过抓包分析逆向得到。
代码实现
Threading ID(最复杂)
python
# _utils.py - threading_id 生成
def gen_threading_id():
return str(int(
# 步骤1:时间戳(毫秒)转二进制
format(int(time.time() * 1000), "b") +
# 步骤2:32位随机数转二进制(取后22位)
("0000000000000000000000" +
format(int(random.random() * 4294967295), "b"))[-22:] +
# 步骤3:合并后转十进制
, 2
))
Client ID
python
def generate_client_id():
def gen(length):
return "".join(random.choices(string.ascii_lowercase + string.digits, k=length))
# 格式:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
return gen(8) + '-' + gen(4) + '-' + gen(4) + '-' + gen(4) + '-' + gen(12)
Session ID
python
def generate_session_id():
# 1 到 2^53-1 范围的随机整数
return random.randint(1, 2 ** 53)
六、移动 APP 登录协议逆向 ⭐⭐⭐⭐
技术挑战
模拟 Facebook Android 客户端 登录需要:
- 生成完整的设备指纹(device_id, machine_id, adid)
- 提取固定的 API Key 和 Access Token
- 处理 2FA 双因素认证流程
- 构建正确的请求签名
代码实现
设备指纹生成
python
# _facebookLogin.py - 登录类初始化
class loginFacebook:
def __init__(self, username, password, AuthenticationGoogleCode=None):
# 生成设备指纹(UUID 格式)
self.deviceID = self.adID = self.secureFamilyDeviceID = \
f"{randStr(8)}-{randStr(4)}-{randStr(4)}-{randStr(4)}-{randStr(12)}"
self.manchineID = randStr(24)
self.usernameFacebook = username
self.passwordFacebook = password
self.twoTokenAccess = AuthenticationGoogleCode
请求头构建
python
def _headers(self):
return {
"Host": "b-graph.facebook.com",
# 模拟 Android 客户端 User-Agent
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G988N Build/NRD90M) [FBAN/FB4A;FBAV/340.0.0.27.113;FBPN/com.facebook.katana;FBLC/vi_VN;]",
"X-Fb-Friendly-Name": "authenticate",
"Authorization": "OAuth null",
"X-Fb-Connection-Type": "unknown",
"X-Fb-Connection-Quality": "EXCELLENT",
}
登录表单构建
python
def _base_form(self, password, credentials_type, try_num):
return {
"adid": self.adID,
"device_id": self.deviceID,
"machine_id": self.manchineID,
"email": self.usernameFacebook,
"password": password,
"credentials_type": credentials_type,
# 逆向得到的固定值
"api_key": "882a8490361da98702bf97a021ddc14d",
"access_token": "350685531728|62f8ce9f74b12f84c123cc23437a4a32",
"jazoest": "22421" if credentials_type == "password" else "22327",
# ... 更多参数
}
2FA 处理流程
python
def main(self):
# 第一步:密码登录
data_form = self._base_form(self.passwordFacebook, "password", 1)
dataJson = self._login(data_form)
# 如果需要 2FA(error_subcode == 1348162)
error = dataJson.get("error")
if error and error.get("error_subcode") == 1348162:
token_2fa = GetToken2FA(self.twoTokenAccess)
data_form_2fa = self._base_form(token_2fa, "two_factor", 2)
data_form_2fa["twofactor_code"] = token_2fa
data_form_2fa["userid"] = error.get("error_data", {}).get("uid", "")
data_form_2fa["first_factor"] = error.get("error_data", {}).get("login_first_factor", "")
return self._login(data_form_2fa)
return dataJson
七、响应反混淆处理 ⭐⭐
技术挑战
Facebook API 响应会添加 for (;;); 前缀:
- 防止 JSON 劫持攻击
- 必须先移除前缀才能解析 JSON
- 所有 POST API 都有这个特性
代码实现
python
# _send.py - 消息发送响应处理
sendRequests = requests.post(**_main).text
# 移除 for (;;); 前缀
sendRequests = json.loads(sendRequests.split("for (;;);")[1])
# _attachments.py - 附件上传响应处理
resultRequests = requests.post(...).text
resultRequests = json.loads(resultRequests.replace("for (;;);", ""))["payload"]
八、技术难点总结表
| 难点 | 难度 | 核心技术 | 解决方案 | 文件位置 |
|---|---|---|---|---|
| E2EE 加密 | ⭐⭐⭐⭐⭐ | Signal Protocol | Go 桥接进程 + JSON-RPC | _listening_e2ee.py |
| GraphQL doc_id | ⭐⭐⭐⭐ | Relay Modern | 逆向前端 JS 获取 ID | _search.py, _utils.py |
| Token 提取 | ⭐⭐⭐⭐ | 字符串分割 | HTML 字符串分割技术 | _session.py, _utils.py |
| MQTT 协议 | ⭐⭐⭐⭐ | WebSocket | 增量同步 + 队列管理 | _listening.py |
| ID 生成算法 | ⭐⭐⭐ | 位运算 | 时间戳+随机数组合 | _utils.py |
| APP 登录 | ⭐⭐⭐⭐ | HTTP 请求模拟 | 设备指纹 + 2FA | _facebookLogin.py |
| 响应反混淆 | ⭐⭐ | 字符串处理 | 移除 for (;;); 前缀 |
_send.py, _attachments.py |
附录:逆向工程工具推荐
| 工具 | 用途 |
|---|---|
| Charles Proxy | HTTP/HTTPS 抓包分析 |
| Wireshark | 网络协议分析(MQTT/WebSocket) |
| Chrome DevTools | 前端 JS 调试、网络请求分析 |
| Frida | 移动端 App 逆向 |
| Ghidra | 二进制逆向分析 |
⚠️ 重要声明:本文档仅供技术研究和学习使用。使用逆向技术访问 Facebook 服务可能违反其服务条款,请遵守相关法律法规和平台规定。