# Paralogue MITM Toolkit Man-in-the-middle proxy and protocol analysis toolkit for **Operation Lovecraft: Fallen Doll** (Paralogue). Intercepts, decodes, modifies, and injects game network traffic in real time via a desktop GUI. ## Features - **Real-time packet inspector** — decoded protobuf messages with JSON syntax highlighting and hex dump - **Bidirectional wire decryption** — ChaCha20 key extraction via Frida, proxy-side decrypt/re-encrypt - **Packet modification** — frame-aware S->C rewriter with pluggable modifier callbacks - **Packet injection** — send custom or replayed packets in both C->S and S->C directions - **Full type mapping** — all 191 dispatch-table msg_ids resolved to protobuf type names via Frida GetTypeName() - **ImGui GUI** with 6 tabs: Packets, Config, Session, Wire, Dispatch, Composer ## Requirements - Python 3.12+ (tested on 3.13) - Windows 10/11 - [Frida](https://frida.re/) (`pip install frida-tools`) - [imgui-bundle](https://github.com/pthom/imgui_bundle) (`pip install imgui-bundle`) - [cryptography](https://pypi.org/project/cryptography/) (`pip install cryptography`) - [protobuf](https://pypi.org/project/protobuf/) (`pip install protobuf`) ``` pip install frida-tools imgui-bundle cryptography protobuf ``` ## Quick Start ### GUI mode (recommended) ```bat cd frida_hooks run_gui.bat ``` 1. GUI opens with proxy listening on ports 11001 (game) and 18801 (HTTP dispatch) 2. Click **"Launch Game (Steam)"** or launch via Steam manually 3. Frida auto-attaches, hooks installed, redirect active 4. Log in — packets stream into the **Packets** tab 5. Toggle modifiers in the **Config** tab 6. Click any packet → **Send** tab → **"Replay Original"** or **"Open in Composer"** ### Headless mode (CLI) ```bat cd frida_hooks run_mitm.bat :: normal passthrough run_mitm.bat --inject-items :: with all modifiers active run_mitm.bat --no-decoder :: proxy only, no Frida ``` ### Re-extract type names (after game update) ```bat :: Game must be running cd frida_hooks run_extract_types.bat ``` Updates `dispatch_type_names.json`, `known_msg_ids.json`, and `factory_to_type.json`. ## Architecture ``` ┌────────────┐ ┌─────────────────────┐ ┌──────────────┐ │ Game │◄──►│ Proxy (port 11001) │◄──►│ Real Server │ │ Client │ │ ┌───────────────┐ │ │ 3.33.185.193│ │ │ │ │ WireRewriter │ │ │ │ │ Frida ────┼────┤ │ S->C + C->S │ │ │ │ │ hooks │ │ └───────────────┘ │ │ │ └────────────┘ │ ┌───────────────┐ │ └──────────────┘ │ │ Modifier Chain│ │ │ └───────────────┘ │ └──────────┬──────────┘ │ SharedState ┌──────────▼──────────┐ │ ImGui GUI │ │ (main thread) │ └─────────────────────┘ ``` ### Threading model - **Main thread**: ImGui render loop at ~60fps - **Background daemon thread**: asyncio event loop running the TCP proxy, Frida bridge, and injection consumer - **SharedState**: thread-safe bridge (deque for packets, Lock for config, inject_queue for packet injection) ### Encryption The game uses **ChaCha20** stream cipher with a shared key+nonce for both directions but independent byte counters: | Offset | Field | Usage | |--------|-------|-------| | +0x68 | key ptr | 32B ChaCha20 key (shared) | | +0x78 | nonce ptr | 12B nonce (shared) | | +0x88 | EVP ctx | encrypt (C->S) | | +0x90 | EVP ctx | decrypt (S->C) | | +0x98 | counter | encrypt byte offset (C->S) | | +0xA0 | counter | decrypt byte offset (S->C) | The proxy uses `WireRewriter` per direction to decrypt→modify→re-encrypt. ChaCha20 XOR involution means identity transforms produce byte-identical output. ### Wire format ``` [2B big-endian prefix N][N-byte PacketHead protobuf][body_len bytes body] ``` PacketHead fields: `msg_id`, `body_len`, `packet_id` (request-response correlation), `seq_id`, `flag`, `total_pack_count`. ## GUI Tabs ### Packets Real-time scrolling table of decoded messages. Filter by direction, source (Frida/wire), msg_id, or type name. Click a row to expand: - **Tree** — recursive protobuf field viewer - **JSON** — syntax-highlighted JSON via TextEditor - **Hex** — formatted hex dump with ASCII sidebar - **Send** — replay original or open in Composer ### Config Toggle modifiers individually: - **Card Bag Expand** — `CardBagOpenRsp.rets` → 0..2000 (unlock experiments) - **Inject Blueprints** — add 11000..11999 to bag (lootbox items) - **Role Attrs Saturate** — max affection, dilation, unlock all roles - **Inject Bag Items** — dense fill across cloth/card/normal ranges - **Unlock Functions** — populate `PlayerMainDataRsp.unlockFunctions` Parameter sliders for card range, attr values. Auto-saves to `gui_config.json`. ### Session Session key/nonce, game PID, Frida status (color-coded), proxy ports, dispatch summary, packet counts. Action buttons: - **Restart Frida** — reattach after game relaunch - **Launch Game (Steam)** — opens via `steam://rungameid/2701680` ### Wire Raw encrypted traffic hex viewer with timestamps and direction arrows. ### Dispatch All 191 msg_id → protobuf type mappings. Green = pinned (resolved), red = unknown. Searchable by type name. ### Composer Compose and send custom protobuf packets: 1. Select type from dropdown (all 191 dispatch types) 2. Edit JSON in syntax-highlighted editor 3. Choose direction (C->S / S->C) 4. Click Send ## Files | File | Purpose | |------|---------| | `frida_hooks/paralogue_gui.py` | ImGui GUI application | | `frida_hooks/paralogue_mitm.py` | Core proxy + Frida bridge + WireRewriter | | `frida_hooks/paralogue_decoder.py` | Protobuf framing, identification, parsing | | `frida_hooks/protocol_decoder.js` | Frida hooks for decrypt/encrypt wrappers | | `frida_hooks/redirect_connect.js` | Frida ws2_32!connect redirect hook | | `frida_hooks/dispatch_dump.js` | Frida dispatch table walker | | `frida_hooks/extract_type_names.js` | Frida GetTypeName() extraction | | `frida_hooks/item_table_dump.js` | Frida bag item subsystem walker | | `frida_hooks/paralogue_net_full.proto` | 239-message protobuf schema | | `frida_hooks/paralogue_net_full_pb2.py` | Compiled protobuf module | | `frida_hooks/dispatch_type_names.json` | msg_id → type name (ground truth, 191 entries) | | `frida_hooks/factory_to_type.json` | vtable → type name (stable across runs) | | `frida_hooks/known_msg_ids.json` | Merged msg_id → type cache | | `frida_hooks/gui_config.json` | Persisted GUI modifier toggles | | `frida_hooks/run_gui.bat` | GUI launcher | | `frida_hooks/run_mitm.bat` | CLI launcher | | `frida_hooks/run_extract_types.bat` | Type extraction launcher | | `RESEARCH.md` | Detailed protocol research notes | ## Game Data Model ### Bag items (BagItemFullS2C, msg_id 1036) | ID range | Category | Description | |----------|----------|-------------| | 0-999 | Experiments/Cards | Unlocked via CardBagOpen | | 11000-11999 | Pack/Blueprints | Lootbox items (open → grant card) | | 20000-28999 | Card/Normal | Collectibles | | 31000-31999 | Cloth | Outfit pieces | | 40000-40999 | Normal | Misc items | | 99000-99999 | Money | Currencies (with counts) | ### Experiments - Stored as client-side `learn_cards` collection (persisted on disk) - Updated incrementally via `CardBagOpenRsp.rets[].card_id` - UI filters by client DataTable (invalid IDs silently dropped) - Unlock all: enable "Card Bag Expand" modifier, open one blueprint ### Roles (RoleListFullS2C, msg_id 1202) Per-role data: `name`, `is_unlock`, `favor_lv`, `worn_clothes[]`, `preset_clothes[]`, `attrs{}`, `appearance`, `clothes_colors`. ## License Research and educational purposes only. Not affiliated with the game developers.