
1. 項目概述從一次“發送失敗”開始的逆向之旅如果你也嘗試過在TikTok上通過私信發送一個商品鏈接卻發現對方收到的只是一個無法點擊的純文本或者你好奇那些帶貨主播是如何在聊天中瞬間彈出精美的商品卡片那么你大概能理解我最初的好奇心。這背后是TikTok IM即時通訊協議在起作用更具體地說是WebSocket Secure連接上流動的、經過層層加密的消息。作為一個對網絡協議和客戶端逆向有濃厚興趣的開發者我決定親手揭開這層神秘的面紗目標很明確逆向分析TikTok IM的WSS通信理解其消息加密機制并最終實現模擬發送一個完整的、可交互的商品卡片消息。這個過程不僅是對一個流行應用協議的探索更是一次完整的網絡協議逆向、密碼學應用和客戶端行為模擬的實戰演練。無論你是安全研究員、爬蟲工程師還是單純對移動應用底層通信機制著迷的極客這篇手把手的記錄都能為你提供一條清晰的路徑和一堆實實在在的“坑位”提示。2. 核心思路與技術棧選型逆向一個像TikTok這樣擁有龐大工程師團隊和成熟風控體系的應用協議不能靠蠻力。我的核心思路是“由外而內動靜結合”。首先我們需要一個能夠攔截和查看所有網絡請求的工具這是我們的“眼睛”。其次由于核心邏輯往往封裝在客戶端內部我們需要能夠動態調試或靜態分析客戶端代碼的能力這是我們的“手術刀”。基于這個思路我選擇了以下技術棧組合并解釋為什么這么選。2.1 抓包與協議分析工具Fiddler Everywhere 自簽名證書為什么是Fiddler Everywhere而不是Charles或Wireshark對于HTTPS/WSS流量解密三者原理類似都需要在設備上安裝根證書。Fiddler Everywhere的界面更現代對WebSocket消息的展示和過濾非常直觀而且跨平臺支持好。Wireshark更底層能抓到所有網卡流量但對于需要解密HTTPS的移動端場景配置略繁瑣。Charles是經典選擇但Fiddler Everywhere的個人免費版功能已經足夠強大。注意在Android高版本特別是Android 7上系統不再信任用戶安裝的證書除非將證書安裝到系統證書目錄這通常需要Root權限。對于非Root設備一個可行的方案是使用VirtualXposed、太極等虛擬環境或者直接使用已經Root的測試設備/模擬器。iOS設備同樣需要手動信任已安裝的描述文件。這是逆向分析移動端App的第一道也是勸退很多人的一道坎。2.2 動態調試與代碼分析Jadx-GUI FridaJadx-GUI用于將TikTok的APK文件反編譯成可讀的Java/Kotlin代碼。它的優勢在于圖形化界面搜索、跳轉、查看調用關系非常方便是靜態分析的起點。我們可以通過搜索關鍵詞如“WebSocket”、“wss”、“encrypt”、“商品”、“commerce”、“card”等來定位相關代碼模塊。Frida這是本次逆向的“神器”。它是一個動態插樁框架可以在應用程序運行時注入JavaScript代碼來Hook鉤子任何函數監控參數、返回值甚至修改邏輯。當靜態分析遇到混淆TikTok肯定有重度混淆導致代碼難以閱讀時Frida可以讓我們在運行時觀察真實的數據流驗證我們的猜測。例如我們可以Hook消息發送前的加密函數直接打印出加密前的明文和加密后的密文。2.3 協議模擬實現Python websockets庫一旦我們分析清楚了消息格式和加密算法就需要用代碼來模擬客戶端行為進行驗證和發送。Python因其豐富的庫和簡潔的語法成為首選。websockets庫提供了異步的WebSocket客戶端實現非常適合用于和服務器進行長連接通信。此外我們可能還需要protobuf如果TikTok使用Protocol Buffers序列化、cryptography用于實現加密算法等庫。2.4 目標設備與環境測試設備一臺已經Root的Android手機或性能足夠的Android模擬器如雷電模擬器、夜神模擬器。Root是為了方便安裝系統級證書和進行更深度的Hook。TikTok版本選擇一個相對較舊但功能穩定的版本例如v28.x左右。新版本可能增加了更強的防護或修改了協議增加逆向難度。可以從第三方APK下載站獲取歷史版本。網絡環境確保測試設備可以正常訪問TikTok服務。這需要自行解決網絡連通性問題但請注意我們的所有操作僅限于協議研究和學習必須在法律和TikTok用戶協議允許的范圍內進行。3. 逆向分析實戰定位、抓包與解密理論準備就緒現在讓我們戴上手套拿起手術刀開始實操。3.1 第一步建立抓包環境配置Fiddler Everywhere啟動Fiddler Everywhere在設置中開啟HTTPS解密功能。它會生成一個根證書。安裝證書到測試設備確保手機和電腦在同一局域網。在手機Wi-Fi設置中配置代理為手動主機填電腦的IP地址端口填Fiddler的監聽端口默認8888。用手機瀏覽器訪問http://電腦IP:8888下載并安裝Fiddler的根證書。對于Root設備使用adb shell和mount命令將系統分區掛載為可寫然后將證書文件通常需要從PEM格式轉換為DER格式并重命名推送到/system/etc/security/cacerts/目錄并修改權限為644。重啟后證書即被系統信任。驗證抓包在手機上打開任意一個使用HTTPS的App如瀏覽器在Fiddler中應該能看到解密的HTTPS流量。如果看到的是Tunnel to ... 443說明證書未成功被系統信任。3.2 第二步捕獲TikTok IM的WSS連接在配置好代理并信任證書的手機上打開TikTok。進行會觸發IM通信的操作打開私信對話框發送一條普通文本消息。回到Fiddler Everywhere你應該能看到大量的tiktokv.com或tiktokcdn.com等域名的請求。我們需要從中找到WebSocket連接。在Fiddler的會話列表里查找Protocol列為HTTP/1.1 101 Switching Protocols的請求。這通常就是WebSocket的升級請求。查看其完整URL可能會是類似于wss://webcast3-ws-web-*.tiktokv.com/...這樣的格式。這就是IM的WSS連接。選中這個WebSocket會話在右側的Inspectors選項卡中選擇WebSocket視圖。在這里你可以實時看到客戶端和服務器之間來回發送的消息幀。不過此時你看到的Payload很可能是亂碼或二進制數據——因為它們被加密了。3.3 第三步靜態分析定位加密邏輯現在我們知道WSS連接在哪但消息是加密的。下一步是找出加密發生在代碼的哪個位置。使用Jadx-GUI打開TikTok APK。這個過程可能需要幾分鐘因為APK很大且混淆嚴重。關鍵詞搜索搜索連接相關WebSocket,wss://,okhttp3.WebSocketListenerTikTok很可能使用OkHttp作為網絡庫。搜索消息發送sendMessage,encode,encrypt。可以嘗試搜索“消息”、“發送”的中文拼音或常見翻譯如xiaoxi,fasong,message,send。搜索商品相關commerce,product,goods,card,商品,卡片。這有助于我們定位到生成商品卡片消息體的具體代碼。分析調用鏈路通過搜索你可能會找到幾個候選的類和方法。例如一個名為com.bytedance.ies.im.core.service.a類名是混淆后的的類其中有一個sendMessage方法。點進去查看它的代碼邏輯。通常發送流程會是構造消息體 - 序列化可能是JSON或Protobuf- 加密 - 通過WebSocket發送。尋找加密函數在sendMessage方法內部或它調用的方法里尋找諸如Cipher.getInstance,AES/ECB/PKCS5Padding,encrypt,encode等調用。注意查看方法的參數和返回值。你可能會發現類似byte[] encryptData(byte[] plainText, byte[] key)這樣的方法簽名。實操心得面對重度混淆類名和方法名可能毫無意義如a.a.b.c()。此時不要糾結于名字而要關注代碼“結構”和“常量”。例如查找硬編碼的字符串常量Jadx可以搜索字符串如算法名稱AES、模式ECB、填充PKCS5Padding或者查找特定的數字常量如密鑰長度16、24、32對應AES-128/192/256。這些是破解混淆的錨點。3.4 第四步動態Hook驗證與密鑰提取靜態分析給出了可疑的加密函數位置但密鑰從哪里來加密模式是否正確需要用Frida在運行時驗證。編寫Frida腳本假設我們通過靜態分析懷疑com.bytedance.ies.im.core.utils.Encryptor.encryptAES是加密函數。// hook_im.js Java.perform(function() { var Encryptor Java.use(com.bytedance.ies.im.core.utils.Encryptor); // Hook encryptAES方法假設它接收兩個參數明文byte數組和密鑰byte數組 Encryptor.encryptAES.implementation function(plainData, key) { console.log(\n[] EncryptAES Called!); // 打印明文可能是Hex或Base64 console.log(Plaintext (hex): bytesToHex(plainData)); console.log(Plaintext (str): Java.use(java.lang.String).$new(plainData)); // 打印密鑰 console.log(Key (hex): bytesToHex(key)); // 調用原方法獲取加密結果 var result this.encryptAES(plainData, key); console.log(Ciphertext (hex): bytesToHex(result)); // 同時我們可以嘗試在Fiddler中抓取同一時刻發送的WSS幀對比這個Ciphertext是否一致 console.log(---); return result; }; // 輔助函數將byte數組轉為Hex字符串 function bytesToHex(bytes) { if (!bytes) return null; var hex []; for (var i 0; i bytes.length; i) { hex.push((bytes[i] 0xFF).toString(16).padStart(2, 0)); } return hex.join(); } });注入并觸發在電腦上啟動Frida Server需推送到手機并運行。使用命令frida -U -l hook_im.js -f com.zhiliaoapp.musically包名附加到TikTok進程。在手機上再次發送一條消息。此時你的終端應該會打印出Hook到的加密信息。關聯抓包數據在Frida腳本運行的同時確保Fiddler正在抓包。當腳本打印出一次加密調用時立刻去Fiddler的WebSocket視圖里找到最新的一條客戶端發送的消息幀。對比Frida打印出的Ciphertext (hex)和Fiddler中消息幀的Payload可能需要將Payload從Base64或原始字節轉換為Hex進行比較。如果兩者匹配恭喜你你已經精準定位了加密函數和密鑰注意事項密鑰可能不是硬編碼的而是從服務器下發的或者在登錄時協商的。你的Hook腳本可能第一次捕獲到的密鑰就是正確的。但也可能需要Hook密鑰的生成或獲取函數。此外加密可能不止一層可能有先壓縮再加密或者對消息頭、消息體分別加密的情況。需要耐心地層層剝離。4. 消息結構拆解與商品卡片模擬找到了加密方法我們就可以嘗試解密一條正常的消息看看它的結構。4.1 解密與解析消息格式錄制一條樣本消息在Fiddler中找到一條你發送普通文本時對應的客戶端WebSocket發送幀復制其Payload可能是Base64編碼的。編寫解密腳本根據Hook到的算法例如AES-ECB-PKCS5Padding和密鑰用Python的cryptography庫編寫解密函數。from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend import base64 def decrypt_aes_ecb(ciphertext_b64, key_hex): key bytes.fromhex(key_hex) cipher Cipher(algorithms.AES(key), modes.ECB(), backenddefault_backend()) decryptor cipher.decryptor() ciphertext base64.b64decode(ciphertext_b64) # 注意ECB模式不需要IV plaintext_padded decryptor.update(ciphertext) decryptor.finalize() # 去除PKCS5/PKCS7填充 padding_len plaintext_padded[-1] plaintext plaintext_padded[:-padding_len] return plaintext # 使用Hook捕獲的密鑰和抓包的Payload key 你的16/24/32字節Hex密鑰 payload_b64 從Fiddler復制的Base64 decrypted decrypt_aes_ecb(payload_b64, key) print(decrypted.decode(utf-8, errorsignore))分析明文結構解密后的數據很可能是一種序列化格式。常見的有JSON最易讀直接json.loads()即可。Protocol Buffers (Protobuf)二進制格式需要.proto定義文件才能正確解析。如果沒有定義文件解析起來非常困難但可以通過分析代碼中com.google.protobuf相關的使用來尋找線索或者使用protobuf-inspector這類工具進行盲解析。Thrift或自定義二進制格式可能性較小但存在。 如果是JSON你就能清晰地看到消息結構例如包含message_type、content、sender、timestamp等字段。content字段里可能就包含了文本或卡片信息。4.2 構造商品卡片消息這是本次逆向的終極目標。我們需要模仿客戶端構造一個能生成商品卡片的消息體。定位卡片生成代碼回到Jadx利用之前找到的與“商品”、“卡片”相關的類。搜索product_card,commerce_card,generateCard等方法。找到負責構建卡片消息體的類和方法。注意觀察它需要哪些參數商品ID (product_id)、商品標題、圖片URL、價格、跳轉鏈接等等。Hook卡片構建方法使用Frida Hook你找到的卡片構建方法。在真實的TikTok App中分享一個商品到私信觸發這個Hook。打印出該方法構建出的完整消息對象或其中的關鍵字段。這是獲取正確消息結構的最直接方法。組裝完整消息根據Hook得到的數據結構用Python字典如果最終是JSON或Protobuf對象如果使用Protobuf來組裝一個一模一樣的消息體。消息體中message_type字段很可能是一個特定的枚舉值比如2代表富媒體消息content字段內再嵌套具體的卡片類型和數據。序列化與加密將組裝好的消息體按照分析出的格式JSON字符串或Protobuf序列化轉換成字節流。然后調用我們逆向出來的加密函數用Python復現對字節流進行加密。建立WSS連接并發送import asyncio import websockets import json async def send_product_card(): # 1. 建立WSS連接需要從抓包中獲取準確的WSS URL和必要的Headers如鑒權Token uri wss://webcast3-ws-web-*.tiktokv.com/... headers { User-Agent: ..., Authorization: Bearer ..., # 關鍵需要有效的登錄態Token } async with websockets.connect(uri, extra_headersheaders) as websocket: # 2. 構造商品卡片消息明文 card_message { type: 2, # 假設2是卡片消息類型 seq_id: 123456, content: { card_type: product, product_id: 123456789, title: 測試商品, image_url: https://..., price: 99.99, jump_url: https://www.tiktok.com/product/... } } plaintext json.dumps(card_message).encode(utf-8) # 3. 加密 ciphertext encrypt_aes_ecb(plaintext, key) # 使用之前復現的加密函數 # 4. 發送可能需要Base64編碼 await websocket.send(base64.b64encode(ciphertext).decode()) print(商品卡片消息已發送) asyncio.run(send_product_card())核心難點與避坑鑒權TokenWSS連接建立時往往需要攜帶用戶登錄憑證Token。這個Token通常存在于App的本地存儲或內存中。可以通過Frida Hook登錄后的Token存儲函數來獲取。沒有有效的Token服務器會拒絕連接或發送消息。消息序列號seq_idIM協議通常要求消息有嚴格遞增的序列號用于保證消息順序和去重。你需要從客戶端代碼或服務器響應中維護一個正確的序列號。心跳保活WSS連接需要定期發送心跳包Ping/Pong或特定指令來保持連接。你需要從抓包中分析出心跳包的格式和間隔并在你的Python客戶端中模擬。風控策略TikTok有完善的風控。短時間內發送大量消息、消息內容異常、從未知IP建立連接等行為都可能導致連接被斷開或賬號受限。模擬行為應盡量貼近真實用戶間隔時間隨機化。5. 常見問題排查與經驗總結在整個逆向和模擬過程中我遇到了無數問題以下是幾個最具代表性的排查思路問題1Fiddler抓不到TikTok的HTTPS/WSS流量。排查首先確認手機代理設置正確且電腦防火墻允許Fiddler端口連接。然后檢查TikTok是否使用了證書綁定SSL Pinning。證書綁定會驗證服務器證書是否與App內硬編碼的證書匹配繞過系統證書導致Fiddler解密失敗。解決使用Frida腳本來禁用SSL Pinning。網上有通用的禁用腳本也可以針對TikTok使用的網絡庫如OkHttp3進行Hook。例如HookOkHttpClient.Builder的sslSocketFactory和hostnameVerifier方法將其替換為信任所有證書的實現。問題2Hook加密函數時打印出的明文是亂碼或看起來不像消息內容。排查可能Hook錯了函數或者消息在加密前經過了壓縮如GZIP或另一種編碼如Protobuf。解決向上追溯調用棧。在Frida腳本中可以使用Java.use(android.util.Log).e(TAG, 調用棧: Java.use(android.util.Log).getStackTraceString(Java.use(java.lang.Throwable).$new()))來打印堆棧看看是誰調用了這個加密函數。從而找到更早的、處理原始消息體的函數進行Hook。問題3模擬發送的消息服務器不響應或返回錯誤。排查這是一個綜合問題。需要逐一檢查WSS連接URL和Headers是否完全正確特別是Host、Origin、User-Agent和鑒權Header。消息格式加密前的明文結構是否完全正確字段名、類型、嵌套關系是否與抓包解密后的一致可以使用json.dumps(..., indent2)美化打印后仔細對比。加密算法和密鑰是否100%還原包括算法、模式、填充、初始向量如果有。可以用Hook到的明文和密鑰用你的Python加密函數加密看結果是否與Hook到的密文一致。序列號和時序seq_id是否連續且大于上一次的值消息發送的節奏是否過快解決采用“最小化驗證”策略。先不發送復雜的商品卡片而是嘗試發送最簡單的文本消息hello。如果文本消息能成功發送并被對方接收說明連接、基礎消息格式、加密都沒問題問題就出在商品卡片消息體的具體構造上。問題4賬號因模擬行為被限制功能。經驗這是進行此類逆向模擬的最大風險。務必使用測試專用的小號切勿使用主力賬號。模擬的行為應盡可能“人性化”連接后等待一會兒再發消息消息間隔加入隨機延遲如3-10秒消息內容不要完全一致。即便如此也不能保證100%安全要有賬號被暫時限制的心理準備和備用方案。這次對TikTok IM協議的逆向之旅更像是一次系統的工程訓練。它不僅僅關乎一個加密算法或一個API端點而是涵蓋了環境搭建、流量分析、靜態逆向、動態調試、協議模擬和風控對抗等多個環節。每一個環節都可能遇到意想不到的困難需要耐心、細致的觀察和邏輯推理。最終成功發送出商品卡片的那一刻所有的折騰都變得值得。更重要的是這套方法論可以遷移到對其他移動應用協議的分析中。記住逆向工程的樂趣在于探索和理解請務必在法律和道德框架內進行尊重知識產權和用戶隱私。