【Facebook】 实时消息监控难点解析

Facebook Messenger API 逆向技术难点详解

本文档详细分析 项目中遇到的 API 逆向技术难点及其解决方案


目录

  1. [E2EE 端到端加密协议逆向](#E2EE 端到端加密协议逆向)
  2. [GraphQL API 的 doc_id 逆向](#GraphQL API 的 doc_id 逆向)
  3. [动态 Token 提取](#动态 Token 提取)
  4. [MQTT WebSocket 消息同步协议](#MQTT WebSocket 消息同步协议)
  5. [ID 生成算法逆向](#ID 生成算法逆向)
  6. [移动 APP 登录协议逆向](#移动 APP 登录协议逆向)
  7. 响应反混淆处理
  8. 技术难点总结表

一、E2EE 端到端加密协议逆向 ⭐⭐⭐⭐⭐(最难)

技术挑战

Facebook Messenger 的 E2EE 使用 Signal Protocol + Meta 内部 Labyrinth/Lightspeed 协议,纯 Python 实现几乎不可能,原因如下:

  • 涉及 10 万+ 行 Go 代码,已有成熟实现
  • 需要处理 Curve25519Double RatchetSender 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 格式但非标准 UUID
  • session_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 服务可能违反其服务条款,请遵守相关法律法规和平台规定。


相关推荐
l1t1 小时前
JIT执行python脚本的工具codon安装和测试
开发语言·python
zithern_juejin2 小时前
Map/Set/WeakMap/WeakSet
javascript
2501_901006472 小时前
Golang怎么用gRPC Gateway_Golang gRPC Gateway教程【经典】
jvm·数据库·python
2501_901200532 小时前
golang如何实现错误预算Error Budget计算_golang错误预算Error Budget计算实现实战
jvm·数据库·python
2401_867623982 小时前
如何解决OUI图形界面无法调用_xhost与DISPLAY变量设置
jvm·数据库·python
Dxy12393102162 小时前
Python 去除 HTML 标签获取纯文本
开发语言·python·html
2401_824697662 小时前
CSS如何实现元素反转特效_使用transform-scaleX(-1)操作
jvm·数据库·python
CLX05052 小时前
如何在 WordPress AMP 网站中为特定模板禁用 AMP 渲染
jvm·数据库·python
砚底藏山河2 小时前
python、JavaScript 、JAVA,定制化数据服务,助力业务高效落地
java·javascript·python