AixLnyt/NTE-Network-Traffic-Analysis
GitHub: AixLnyt/NTE-Network-Traffic-Analysis
该项目通过Python脚本对游戏《异环》的PCAP网络流量进行离线逆向分析,还原其基于FlatBuffers的私有通讯协议结构与游戏状态数据。
Stars: 3 | Forks: 0
# NTE Network Traffic Analysis
[English](./README_EN.md) | 中文
逆向工程 **異環 (Neverness to Everness)** 的私有遊戲通訊協定。透過 PCAP 流量捕獲與 Python 靜態分析,還原協定結構、序列化格式、玩家狀態資料與任務系統。
## 免責聲明
- 本專案與 Perfect World、Neverness to Everness 開發團隊或任何關聯公司**無任何官方關係**,純屬第三方獨立研究。
- 所有分析均基於**個人帳號於本機自行錄製**的 PCAP 流量,純離線靜態分析,**不涉及**伺服器攻擊、封包注入、流量重放、認證憑證解密或任何形式的網路干擾。
- 本 repo **不包含任何 PCAP 原始檔、個人帳號資訊、UID 或其他可識別個人身分的資料**;文件中出現的數值均為示意用範例值。
- 本專案僅供協定結構研究與技術學習用途,**不提供、不協助開發任何外掛、自動化腳本、封包注入工具或破壞遊戲公平性的程式**。
- 使用本專案內容(包含程式碼與文件)即表示同意自行承擔風險與責任,並遵守該遊戲之服務條款(ToS)與當地法規。作者不對任何因使用本專案造成的帳號處分或法律後果負責。
- 程式碼以 "AS IS" 提供,不含任何明示或暗示的擔保。
## 目錄結構
C:\Users\User\Desktop\py\pcap\
│
├── nte_decoder_v3.py ← 主程式:完整 PCAP 解碼流程
├── dense_zone_differ.py ← 工具:Dense Zone 多份 PCAP 差異比對
├── frame_decoder.py ← 工具:單一 frame hex 逐欄位解析
├── export_frames.py ← 工具:從 pcap 匯出所有 frame hex
├── record_extractor.py ← 工具:quest record 分類提取與噪音過濾
│
├── frames.txt (export_frames.py 產生)
├── nte_player_info.txt (nte_decoder_v3.py 產生)
├── nte_quests_v3.csv (nte_decoder_v3.py 產生)
├── nte_decoded_v3.json (nte_decoder_v3.py 產生)
├── categorized_records.csv (record_extractor.py 產生)
└── dense_snapshots.json (dense_zone_differ.py 快照資料庫)
## 快速開始
pip install scapy
# 完整解碼一份 PCAP
python nte_decoder_v3.py test01.pcap 34.110.242.50
# 只看任務/物品記錄
python record_extractor.py test01.pcap 34.110.242.50
# Dense Zone 差異比對(找 HP/能量/CD)
python dense_zone_differ.py --add full_hp.pcap --tag full_hp
python dense_zone_differ.py --add low_hp.pcap --tag low_hp
python dense_zone_differ.py --diff full_hp low_hp --csv hp_diff.csv
# 匯出 frame hex 逐一解析
python export_frames.py test01.pcap 34.110.242.50 frames.txt
python frame_decoder.py --file frames.txt
## 協定逆向分析結果
### 傳輸層
| 項目 | 值 |
|---|---|
| 傳輸協定 | TCP |
| 目標伺服器 | `34.110.242.50:30031`(GCP 亞洲節點) |
| TLS | 有(OS 層解密,payload 本身無應用層加密) |
| Relay server | `34.146.x.x:302xx`(房間/地圖實例,per-session) |
### 封包格式(Framing)
┌────────────────────────────────────────────────────────┐
│ 4 bytes (LE uint32) │ Frame Payload │
│ length field │ (FlatBuffers 或自定義二進位) │
└────────────────────────────────────────────────────────┘
連線建立後的世界狀態幀(Frame 4)**沒有** length prefix,直接在 LE4 framing 中斷點後開始。
### Frame 結構(每次連線固定順序)
#### Frame 1 — 連線初始化(~200 bytes)
格式:FlatBuffers
| 欄位 | 範例值 | 說明 |
|---|---|---|
| server IP | `35.191.x.x` | GCP Load Balancer anycast IP |
| build date | `20200704` | 伺服器 build hash |
| session token | 4-5 字元亂碼 | per-session 隨機 token |
| field[0] u32 | `1117/1120` | message counter(每幀遞增)|
#### Frame 2 — 玩家狀態快照(~392–400 bytes)
格式:FlatBuffers,內含一個 nested table(14 個欄位)
| 欄位 | 範例值 | 含義 |
|---|---|---|
| field[0] | `1510030 / 1904370 / 786916` | Actor network handle(per-session)|
| **field[1]** | `1009876543 (10位數,範例值)` | **Perfect World SDK 內部帳號 ID**(跨 session/角色固定,非遊戲 UID)|
| field[2] | `284–304` | 不明 scalar |
| field[3] | `TagOthers` | Actor tag |
| field[4] | `34.x.x.x:302xx` | Relay server(per-session)|
| field[5] | 座標字串 | `X,Y,Z\|Pitch,Yaw,Roll\|ScaleX,Y,Z` |
| field[6] | `160–168` | 不明 scalar |
| **field[7]** | `123456789012 (12位數,範例值)` | **✅ 玩家遊戲 UID**(uint64 LE,已透過遊戲內截圖驗證)|
| field[8] | blueprint path | 角色藍圖路徑(含角色名)|
| field[11] | `1` | active flag |
| field[12] | `75777 / 77825` | Zone / Map Instance ID(隨地圖變化)|
| field[13] | `0xCDCDCDCD` | 未初始化欄位(MSVC debug heap fill pattern,伺服器未設定)|
#### Frame 3 — 心跳 / Tick 同步(72 bytes)
格式:FlatBuffers,vtable 結構與 Frame 1 相同,field[0] 值遞增(message counter),entropy 極低(~3.2),確認為心跳包。
#### Frame 4 — 遊戲世界狀態(~90–130 KB,視場景而定)
格式:自定義二進位,分兩個固定大小子區域:
Frame 4
├── Dense Zone [0 : 40960] 固定 40960 bytes
│ ├── Header [0 : 2560] 跨 session 完全靜態(連線 metadata,與玩家狀態無關)
│ └── Entity 陣列 [2560:40960] 動態長度記錄陣列(怪物/隊友/掉落物/裝飾品)
│ └── PrivateSpawnInfoRecord 以玩家 UID 標記的裝飾品擺放記錄
│
└── Sparse Zone [40960 : end] Quest/Item/社交字串記錄表(長度隨場景變化)
├── FlatBuffers header root_offset=16,3 個欄位
├── Quest/Item Record array 見下方格式
└── 副本場景額外資料
├── "Gameplay will be recorded" 系統訊息(反作弊提示)
├── 隊伍成員社交資料(暱稱/GUID/裝飾框)— 範圍外,未深入解析
└── 32 字元 hex GUID(角色/物品實例 ID)
### Quest Record 格式(Sparse Zone)
每筆 record 的完整 binary layout:
offset size 型別 說明
+0 1B uint8 padding
+1 4B uint32 type_a(任務狀態類型)
+5 4B uint32 type_b(任務子類型 / 分類代碼)
+9 4B uint32 unknown_C
+13 4B uint32 str_bytelen(UTF-16 bytes + 2-byte null)
+17 N UTF-16LE 任務/物品名稱字串
+17+N 2B null 字串結尾 \0\0
+17+N+2 7B 混合 val_a(4B) + val_b(2B) + pad(1B)
**type_b 分類代碼對照:**
| type_b | 分類 |
|---|---|
| 3 | 戰鬥獎勵任務 (Combat Award Quest) |
| 5 | 新手目標 (Newbie Goals) |
| 6 | 每日任務 |
| 7 | 釣魚等級獎勵 |
| 8 | 釣魚圖鑑 |
| 9 | 活動綁定任務 (Op Activity Bond) |
| 11 | 城市活動任務 |
| 12 | 社交/貝果任務 (Bagel System) |
| 13 | 覺醒系統 |
| 15 | 神秘島 |
| 22 | 道具庫存 |
## 各 PCAP 樣本對照
| PCAP | 角色 | 座標 | Zone ID | Sparse Zone 大小 | 備註 |
|---|---|---|---|---|---|
| test01 | Player_004_Lacrimosa | (-146236, 121043, 7105) | — | ~56KB | 第一份樣本,Frame4 截斷 |
| test02 | player_010_nanally | (17140~17672, 144226~144400, 8456) | 75777/77825 | ~56KB | 402 筆 quest record |
| test03 | Player_004_Lacrimosa | (38534, 134776, 3591) | 77825 | ~56KB | 登入流程,含 pwsdk.com API 列表 |
| test04 | Player_004_Lacrimosa | (38534, 134776, 3591) | 75777 | ~87KB | 監獄副本,新增 Prison quest 系列 |
跨 session 固定值:**UID `123456789012 (12位數,範例值)`**、**SDK帳號ID `1009876543 (10位數,範例值)`**、field[13] debug fill `0xCDCDCDCD`
## 腳本說明
### `nte_decoder_v3.py` — 主程式
完整端到端解碼流程,一個指令輸出三份報告。
用法:python nte_decoder_v3.py [target_ip]
輸出:
nte_player_info.txt Frame 0/1 的連線資訊(IP、角色、座標、UID)
nte_quests_v3.csv 所有 quest/item record(名稱、類型、進度值)
nte_decoded_v3.json 完整結構化 JSON
**CSV 欄位:**
| 欄位 | 說明 |
|---|---|
| quest_name | 任務/物品識別字串 |
| category | 自動分類(fishing / quest / item / ui_redpoint / world_object)|
| type_a | 狀態碼(1=進行中, 3=已完成)|
| type_b | 分類代碼(見上表)|
| val_a | 當前進度值 |
| val_b | 目標值(val_a == val_b = 完成)|
### `dense_zone_differ.py` — Dense Zone 差異分析
用法:
python dense_zone_differ.py --add --tag <標籤> 新增快照
python dense_zone_differ.py --diff --csv out.csv 比對
python dense_zone_differ.py --explore --offset N --length L hex dump
python dense_zone_differ.py --list 列出所有快照
**目前已知:**
- Dense Zone 固定 40960 bytes
- `[0:2560]` Header 區跨 session **完全靜態**,不含玩家狀態
- `[2560:40960]` 為動態 entity 陣列,長度隨場景內物件數量變化,直接 diff 容易被雜訊淹沒
**建議錄製方式(提高 diff 訊號比):**
1. 在**同一個場景/同一個 entity 陣列狀態**下錄兩次
2. 兩次之間只改變一個變數(例如「被打一拳」掉血)
3. 間隔時間越短越好,避免怪物/隊友進出造成陣列變動
### `record_extractor.py` — 任務記錄提取器
帶分類標籤的精簡版提取工具。
用法:python record_extractor.py [target_ip]
輸出:categorized_records.csv / categorized_records.json
### `export_frames.py` + `frame_decoder.py`
python export_frames.py [target_ip] [output_txt]
python frame_decoder.py --file frames.txt
python frame_decoder.py
## 如何錄製 PCAP
**必做設定:**
Wireshark → Edit → Preferences → Capture → Default capture snaplen = 0
**一般場景:**
1. 捕獲過濾器:`host 34.110.242.50`
2. 進入地圖,等載入完成後再多等 10 秒才停止錄製
**Dense Zone diff 專用:**
- 同場景錄兩次,中間只改變一個數值狀態(HP/能量/CD)
- 間隔越短越好
## 已知限制 / 待研究
- **Dense Zone entity 陣列**:`[2560:40960]` 結構未解,含怪物/隊友/掉落物/裝飾品的混合記錄,需要受控環境下的短間隔 diff 才能定位 HP/能量/CD 等個人數值
- **field[2] / field[6]**:Frame 2 中兩個不明 scalar(範圍 160-304),含義未知
- **field[12] Zone ID**:對應關係已確認隨地圖變化,但完整地圖 ID 對照表未建立
- **TLS 層**:`mapi.pwsdk.com` 等認證 API 全程加密,不在本專案分析範圍內
- **隊伍社交資料區**:副本場景的 Sparse Zone 內含其他玩家的暱稱/GUID/帳號層級 ID,本專案不解析此區段
## 環境需求
Python 3.10+
scapy
pip install scapy
标签:Python, Wireshark, 代码示例, 协议逆向, 句柄查看, 数据分析, 无后门, 游戏协议, 网络流量分析, 逆向工具