Document VPX emulator status and recovery plan

This commit is contained in:
MrARM
2026-07-14 23:10:10 -05:00
parent 717cc7d94f
commit 32751448c6
9 changed files with 1073 additions and 13 deletions

384
qr-code-analysis.md Normal file
View File

@@ -0,0 +1,384 @@
# QR Code Analysis
## Scope
The QR code system on Pokemon Pro involves **two separate code paths**:
1. **QR Code Generation** — The game uses Nayuki's `qrcodegen` library to generate QR codes (WiFi config, Stern Insider Connected login, support URLs) and display them on-screen.
2. **QR Code Scanning** — A standalone hardware scanner module connected to the game via a `node4` bridge board (LPC812/LPC1124 MCU on the SPIKE-3 nodebus) and a serial UART.
**Important finding**: The QR code scanning functionality is in the **main game binary** (`/games/pokemon_pro/game`), **not** in `conagent`. The `conagent` binary has no QR-related strings or code. See `conagent-re.md` for the connectivity agent's separate role.
## Source Binary
- Target: `/games/pokemon_pro/game`
- Workspace path: `games/pokemon_pro/game`
- SHA-256: (see `artifact-index.md`)
- Type: stripped AArch64 PIE ELF, 52,829,400 bytes
- Embedded library: `qrcodegen` (Nayuki's QR Code Generator, C library)
## QR Code Generation
### Library: qrcodegen
The game embeds **Nayuki's QR Code Generator** library (`qrcodegen.c` at file offset `0x25e6c88`). This is a standalone C library that generates QR code symbol data from text input. Confirmed API functions:
| Function | Offset | Purpose |
|---|---|---|
| `qrcodegen_encodeSegmentsAdvanced` | `0x26028f8` | Core encoding entry point |
| `qrcodegen_getSize` | `0x2602760` | Get QR code module size |
| `qrcodegen_getModule` | `0x2602998` | Get individual module bit |
| `qrcodegen_isNumeric` | `0x2602728` | Check if text is numeric mode |
| `qrcodegen_isAlphanumeric` | `0x26029b0` | Check if text is alphanumeric mode |
| `qrcodegen_makeBytes` | `0x2602a00` | Create byte segment |
| `qrcodegen_makeNumeric` | `0x2602a18` | Create numeric segment |
| `qrcodegen_makeAlphanumeric` | `0x2602a38` | Create alphanumeric segment |
| `qrcodegen_makeEci` | `0x2602a30` | Create ECI segment |
Internal assertion strings confirm library version constraints:
- `qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX` (0x25e6c98)
- `21 <= qrsize && qrsize <= 177` (0x25e6d90)
- `0 <= (int)ecl && (int)ecl < 4` (0x25e7070)
### QR Code Output: BMP Files
Generated QR codes are rendered as **BMP image files**:
| Path | Offset | Purpose |
|---|---|---|
| `/tmp/qrx_.bmp` | `0x25db068` | Temporary QR code image |
| `/tmp/login.bmp` | `0x25e1760` | Login QR code output |
| `/connectivity/qr_code_storage/%d.bmp` | `0x25e26e8` + `0x25e2718` | Persistent QR code storage |
### QR Code Configuration Properties
QR code display properties are configured via a JSON-like object with these fields (all at offsets `0x25db270``0x25db2e0`):
| Field | Type | Purpose |
|---|---|---|
| `qrCodeText` | string | The text/URL to encode in the QR code |
| `qrCodeAppendMachineUuid` | bool | Whether to append machine UUID to text |
| `qrCodeStartTime` | timestamp | QR code validity window start |
| `qrCodeEndTime` | timestamp | QR code validity window end |
| `qrCodeXPosition` | int | Screen X position |
| `qrCodeYPosition` | int | Screen Y position |
| `qrCodeSize` | int | QR code size in pixels |
### Generated QR Code Types
| Type | String Context | Offset |
|---|---|---|
| Stern Insider Connected login | `Stern Insider Connected™ QR code.` | `0x25a7dba` |
| Connected Login QR | `Connected™ Login QR code.` | `0x25a7e99` |
| WiFi QR Code | `Scan WiFi QR Code` | `0x25a8360` |
| Quick Registration | `Connectivity/QrCodes/quick_registration_qr_code` | `0x25e1cf8` |
| Support | `Connectivity/QrCodes/support` | `0x25dc7e8` |
| Tech Support | `SCAN THIS QR CODE TO REACH OUR TECH SUPPORT TEAM` | `0x25dc231` |
## QR Code Scanning — Hardware Architecture
### Physical Components
The QR scanner system consists of:
1. **Scanner module** — A standalone 2D barcode scan engine (OEM part `part:520-9865-00` at `0x25b8c20`). The specific module model (Zebra, Honeywell, Newland, etc.) is not identifiable from strings; the firmware is a thin bridge without scanner-brand identifiers.
2. **Node4 bridge board** — A `node4` SPIKE-3 nodebus peripheral with dual firmware options:
- `node4-LPC812-1_33_0.hex` (LPC812, Cortex-M0+, 12 KB decoded)
- `node4-LPC1124_303-1_33_0.hex` (LPC1124, Cortex-M0+, 14 KB decoded)
3. **Connections**:
- Ribbon cable from scanner module to node4 board
- RJ45 nodebus cable from node4 to the game system
4. **GPIO antenna switch**: Two RF antennas on `gpiochip1` lines 3 (`ant1`) and 7 (`ant2`), controlled via `libgpiod.so.2`
### Serial Interface
The game communicates with the scanner module via **serial UART**:
| Path | Selection Logic | Offset |
|---|---|---|
| `/dev/ttyAMA2` | Default (kernel release byte != '5') | `0x25e9468` |
| `/dev/ttyAMA5` | Kernel release byte == '5' | `0x25e9478` |
Selection function at VA `0x94b8b0`: Uses `uname()` to check a kernel release byte, compares to `0x35` (`'5'`), selects ttyAMA5 on match, ttyAMA2 otherwise.
### Serial Configuration (VA `0x92d350`)
- **Open**: `open(path, O_RDWR | O_NOCTTY)`
- **Termios2**: Uses `TCGETS2` (ioctl `0x541e`) / `TCSETS2` (ioctl `0x541f`) — `struct termios2` for raw baud rates
- **Data bits**: 8 data bits, no parity
- **Stop bits**: 1 stop bit (configurable to 2 via `config[5] == 2`)
- **Local mode**: `CLOCAL | CREAD` (`0x880`)
- **Baud rate**: Configurable via 4-entry table indexed by `config[4] - 5`; actual rates not extractable from static analysis (uses non-standard termios2 CBAUD encodings)
- **Flow control**: `tcflow(fd, TCOON)`, `tcflush(fd, TCIOFLUSH)`
- **Timeout**: 1000 ms via `tcdrain`
### Node4 Bridge Firmware
The `node4` firmware files are thin, simple firmware images (6-11 ARM Thumb function entries) that bridge between:
- **Scanner side**: UART connection to the scanner module (baud rate divisors suggest 57600 for LPC812, 9600/230400/38400 for LPC1124)
- **Nodebus side**: SPIKE-3 nodebus protocol with node address `0x04`
The firmware does **not** contain:
- Scanner decode libraries (zbar, quirc, etc.)
- Scanner brand identifiers or protocol tables
- Any human-readable strings at all
This confirms the scanner module is a pre-integrated 2D barcode engine that handles decode internally, and the node4 board is a thin UART-to-nodebus bridge.
## QR Scanner Protocol
### Bridge Protocol (VA `0x94b9d0``0x94bbfc`)
The game communicates with the bridge (and through it to the scanner) using a custom framed protocol:
- **Buffer size**: 256 bytes (`mov w2, #0x100`)
- **Frame format**: `STX` + escaped body (cmd, seq, payload, checksum) + `ETX`
- **STX**: `0x02` — start of frame
- **ETX**: `0x03` — end of frame, triggers message processing
- **ESC**: `0x1B` — escape byte; next byte has bit `0x80` set
- **Checksum**: `(-sum(body)) & 0xFF`, verified before dispatch
- **Valid commands**: `0x10` through `0x41` (checked via `sub w0, w2, #0x10; cmp w0, #0x31`)
- **Command 0x30**: Firmware CRC handler at VA `0x953fc0`
- **Error handling**: "bridge rx checksum error", "message overrun", "too many message", "bridge %s unknown command: 0x%02x"
### Scanner Status Register (16 states)
The game tracks scanner status via an enum/string table at `0x25b8a40``0x25b8c00`:
| Offset | String | Meaning |
|---|---|---|
| `0x25b8a40` | `QR SCANNER BACKLIGHT` | Backlight control command |
| `0x25b8a58` | `QR SCANNER STATUS READY` | Scanner is ready for a scan |
| `0x25b8a70` | `QR SCANNER STATUS STRING` | Text string decoded from QR |
| `0x25b8a90` | `QR SCANNER STATUS ENCODED DATA` | Raw binary encoded data received |
| `0x25b8ab0` | `QR SCANNER STATUS MISSED STRING` | A scan was started but no decode |
| `0x25b8ad0` | `QR SCANNER STATUS OVERRUN` | Buffer overrun condition |
| `0x25b8af0` | `QR SCANNER STATUS UNSUPPORTED_AP` | Unsupported access point (WiFi) |
| `0x25b8b18` | `QR SCANNER STATUS FIFO OVERRUN` | Hardware FIFO overrun |
| `0x25b8b38` | `QR SCANNER STATUS CONFIG` | Configuration change event |
| `0x25b8b58` | `QR SCANNER STATUS ERROR` | Generic error |
| `0x25b8b70` | `QR SCANNER STATUS ERR1` | Error code 1 |
| `0x25b8b88` | `QR SCANNER STATUS ERR2` | Error code 2 |
| `0x25b8ba0` | `QR SCANNER STATUS ERR3` | Error code 3 |
| `0x25b8bb8` | `QR SCANNER STATUS ERR4` | Error code 4 |
| `0x25b8bd0` | `QR SCANNER STATUS ERR5` | Error code 5 |
| `0x25b8be8` | `QR SCANNER STATUS ERR6` | Error code 6 |
| `0x25b8c00` | `QR SCANNER STATUS NO READER` | Scanner disconnected |
### GPIO Antenna Switch (VA `0x950260`)
The QR scanner has a **dual-antenna RF switch** controlled via `gpiochip1`:
| GPIO | Label | Function |
|---|---|---|
| Line 3 | `ant1` | Antenna 1 |
| Line 7 | `ant2` | Antenna 2 |
Control logic:
- `arg=0`: Both antennas off (low)
- `arg=1`: `ant1` high, `ant2` low
- `arg=2`: `ant2` high, `ant1` low
## QR Parsing Classes
### C++ Class Hierarchy
The game uses C++ classes for QR code parsing, identified from RTTI/vtable references at `0x25fe1c0``0x25fe2d0`:
| Class | Offset | Role |
|---|---|---|
| `QrOfflineParser` | `0x25fe1c0` | Parses offline/local QR codes |
| `NetworkQrParser` | `0x25fe218` | Parses network/connectivity QR codes |
| `QrOfflineListener` | `0x25fe2d0` | Listens for offline QR scan events |
| `NetworkDataInterface` | `0x25fe1d8` | Base interface for network data |
| `CommandInterface` | `0x25fe1f0` | Base interface for commands |
| `NetworkData` | `0x25fe208` | Network data container |
### QR Code Format: `REG:T:`
The game uses a **proprietary QR code data format** identified by the prefix `REG:T:` at `0x25dbe30`:
- `REG:T:WPA` (offset `0x25dbe30`) — WiFi configuration QR code
- `REG:T:ETH` (offset `0x25dbe40`) — Ethernet/machine registration QR code
The format extends `REG:T:{TYPE}:{key}:{value}:{key}:{value}:...` with fields parsed programmatically.
### WiFi QR Data Flow
When a `REG:T:WPA` QR code is scanned:
1. `NetworkQrParser` extracts SSID, PSK, and encryption type
2. Validation: `"Not a valid WiFi QR code"` (`0x25a7200`)
3. Game builds `wpa_supplicant` config block:
```
ssid="%s"
scan_ssid=1
key_mgmt=%s (WPA-PSK)
pairwise=%s (TKIP or "CCMP TKIP")
group=%s
psk=%s
```
4. Writes to `/etc/wpa_supplicant.conf` (`0x25dfd68`)
5. Controls WiFi via `wpa_supplicant` control interface:
- `ADD_NETWORK` (`0x25d5688`)
- `SET_NETWORK` (`0x25d5808`)
- `ENABLE_NETWORK %s` (`0x25d5780`)
- `CTRL-EVENT-CONNECTED` (`0x25d57e8`)
- `CTRL-EVENT-DISCONNECTED` (`0x25d57e8`)
6. Control socket: `/var/run/wpa_supplicant/wlan0` (`0x25d5708`)
7. Also uses `/usr/sbin/wpa_cli status` (`0x25e26b8`)
### Stern Insider Connected QR Data Flow
When a Stern Insider Connected login QR code is scanned:
1. `QrOfflineParser` extracts user UUID from QR code
2. Flow: `QERT_SCANNED_USER_ACCOUNT` (`0x25dedd0`) → `ScannedUserAccountUUID` (`0x25dd568`) → `AuthenticatePlayer` (`0x25dd590`)
3. Player UUID is sent to backend via `PLAYER_AUTHENTICATE` (`0x25ecf18`)
4. Audit counters track results:
- `AUD_CONNECTIVITY_USERS_SCANNED` (`0x2594d60`)
- `AUD_CONNECTIVITY_USERS_JOINED` (`0x2594d80`)
- `AUD_CONNECTIVITY_BAD_QR_SCANS` (`0x2594ea8`)
- `AUD_CONNECTIVITY_BAD_USER_SCANS` (`0x2594e88`)
## Exploit Surface Analysis
### High Priority Targets
#### 1. `REG:T:WPA` → wpa_supplicant Command Injection
**Risk**: HIGH. The QR code content is parsed and passed to `wpa_supplicant` control interface commands (`ADD_NETWORK`, `SET_NETWORK`, `ENABLE_NETWORK`). If the SSID or PSK fields are not properly sanitized, an attacker could:
- Inject arbitrary `wpa_cli` or `wpa_supplicant` control interface commands
- Write to `/etc/wpa_supplicant.conf` with crafted content
- Potentially execute shell commands via the control interface
**Evidence**: The game uses `wpa_cli` at `/usr/sbin/wpa_cli status` (`0x25e26b8`). The control socket at `/var/run/wpa_supplicant/wlan0` (`0x25d5708`) accepts textual commands. SSID and PSK are inserted into format strings (`ssid="%s"`, `psk="%s"`) — if these use `sprintf`/`snprintf` without length limits, buffer overflow is possible.
#### 2. Serial Bridge Buffer Handling
**Risk**: MEDIUM-HIGH. The bridge protocol uses a **256-byte buffer** with a **1000 ms timeout**. The frame format uses STX/ETX/ESC framing with checksum. Potential issues:
- Buffer overflow if a message larger than 256 bytes is sent (the `read()` call reads up to 256 bytes, but the parser accumulates across multiple reads)
- Escape sequence mishandling (ESC `0x1B` sets bit 0x80 on next byte — crafted sequences could cause state confusion)
- Checksum bypass (the checksum is a simple sum complement)
#### 3. RapidJSON Parsing of QR Content
**Risk**: MEDIUM. The game uses **RapidJSON** library to parse QR-related data. Failure messages:
- `Failed to parse JSON.` (`0x259e9e0`)
- `INVALID JSON: expected "achievement_descriptors"` (`0x25dff48`)
- `INVALID JSON: expected "machine"` (`0x25e0088`)
- `INVALID JSON: failed to parse "machine"` (`0x25e00b0`)
RapidJSON assertion failures at `0x25d6348``0x25d6f20` (internal type checks). If assertions are compiled in (debug build), malformed JSON can crash the process. If assertions are compiled out, type confusion bugs may be exploitable.
#### 4. `qrCodeText` Buffer Handling
**Risk**: MEDIUM. The `qrCodeText` field is a raw string field that gets encoded into QR code data. If the text is used in `sprintf`/`snprintf` for display or file path construction without proper bounds checking, it could be a buffer overflow vector. QR codes are saved to `/connectivity/qr_code_storage/%d.bmp` using a numeric index — if the index is attacker-controlled, format string injection via `%d` is possible.
#### 5. Nodebus Secure Protocol
**Risk**: MEDIUM. The netbridge emulator (`netbridge_emu.py`) documents that secure nodebus requests (command `0x11`) use XOR encryption with session key rotation when `wire_address & 0xC0 == 0xC0`. If the encryption is weak or the session key derivation is predictable, a physical attacker with nodebus access could forge QR scan results.
### Medium Priority Targets
#### 6. GPIO Antenna Bypass
The dual-antenna system uses GPIO lines 3 and 7 on `gpiochip1`. If the antenna selection logic can be influenced by QR scan data, an attacker could potentially read from or interfere with the RF path.
#### 7. Audit Counter Manipulation
Audit counters (`AUD_CONNECTIVITY_BAD_QR_SCANS`, `AUD_CONNECTIVITY_USERS_SCANNED`, etc.) are tracked in shared memory or audit logs. If the QR parser crashes before incrementing error counters, brute-force scanning of QR codes could go undetected.
#### 8. Node4 Firmware Update Path
The `node4` bridge board has an LPC812/LPC1124 MCU that can be firmware-updated over the nodebus. If the update path is triggered by a crafted nodebus message, an attacker could flash malicious firmware that intercepts or modifies QR scan data.
### Low Priority / Design Notes
#### 9. `/tmp/qrx_.bmp` Race Condition
The QR code BMP is written to `/tmp/qrx_.bmp` — a predictable path in a world-writable directory. A local attacker could replace the QR code image between generation and display, but this requires local code execution on the Raspberry Pi.
#### 10. Pre-Generated QR Code Assets
The game includes pre-generated QR code images:
- `assets/nuk/images/Connectivity/QrCodes/quick_registration_qr_code.png`
- `assets/nuk/images/Connectivity/Icons/ScanQR/ScanQR.png`
These are imported into Godot but have **no script or scene references** — they are orphaned assets. The `quick_registration_qr_code.png` is a static QR code that may encode a fixed URL or machine identifier.
## Audit Counter Strings
| Offset | String | Purpose |
|---|---|---|
| `0x2594d60` | `AUD_CONNECTIVITY_USERS_SCANNED` | Users scanned via QR |
| `0x2594d80` | `AUD_CONNECTIVITY_USERS_JOINED` | Users joined via QR |
| `0x2594e88` | `AUD_CONNECTIVITY_BAD_USER_SCANS` | Failed user QR scans |
| `0x2594ea8` | `AUD_CONNECTIVITY_BAD_QR_SCANS` | Failed QR scans overall |
| `0x2594ec8` | `AUD_CONNECTIVITY_NET_CONNECTION_BAD_ACTION` | Bad network action |
| `0x25964b0` | `AUD_RULE_AREA_DISCOVER_POKEMON_SCAN_STARTED` | In-game scan started |
| `0x25964e0` | `AUD_RULE_AREA_DISCOVER_POKEMON_SCANNED` | In-game scan completed |
| `0x2596508` | `AUD_RULE_AREA_DISCOVER_POKEMON_SCAN_FAILED` | In-game scan failed |
## Godot UI Classes
| Class Name | Offset | Role |
|---|---|---|
| `NetPlayQrCode` | `0x25db058` | QR code display in network play |
| `NetWrongQrOverlay` | `0x25d9d20` | Error overlay for invalid QR |
| `MenuPageQrAdjustments` | `0x25ff0e8` | Menu page for QR adjustments |
| `QrRC` | `0x280770f` | QR resource class (abbreviated) |
## Diagnostic Messages
| Offset | String |
|---|---|
| `0x25dd3e8` | `Check QR scanner RJ45 nodebus cable` |
| `0x25dd410` | `Check QR scanner ribbon cable` |
| `0x25a85e0` | `Pull Interlock Switch To Power QR Scanner` |
| `0x25a5528` | `This login QR code is no longer valid` |
| `0x25a5578` | `This machine is not registered with Insider Connected` |
## Key Differences from conagent
The QR code system is **entirely separate** from `conagent`:
| Aspect | conagent | QR Scanner (game binary) |
|---|---|---|
| Binary | `/games/pokemon_pro/conagent` | `/games/pokemon_pro/game` |
| Role | Backend connectivity agent | Main game process |
| QR code gen | None | Uses `qrcodegen` library |
| QR code scan | None | Via hardware scanner + node4 bridge |
| WiFi config | No | Yes, via `wpa_supplicant` |
| User auth | API token/device auth | QR-based player UUID scan |
## Follow-up Targets
1. **Binary-level analysis**: Use Ghidra to decompile:
- `QrOfflineParser::parse` (find via vtable xref from `0x25fe1c0`)
- `NetworkQrParser::parse` (find via vtable xref from `0x25fe218`)
- The `REG:T:WPA` string splitting and SSID/PSK extraction code
- The `wpa_cli` control interface invocation site
- The serial bridge read/parse loop at VA `0x94b9d0`
2. **Dynamic testing**: If hardware or emulation is available:
- Fuzz the serial bridge protocol with crafted STX/ETX/ESC frames
- Test `REG:T:WPA` with SSID/PSK containing shell metacharacters
- Test `REG:T:WPA` with oversized SSID (>256 bytes) for buffer overflow
- Test RapidJSON parsing with malformed JSON in QR code content
3. **Node4 firmware analysis**: Disassemble `node4-LPC812-1_33_0.hex` and `node4-LPC1124_303-1_33_0.hex` as ARM Thumb to:
- Recover the UART baud rate configuration
- Understand the scanner module command passthrough protocol
- Verify the firmware update mechanism
4. **Physical inspection**: If hardware is available:
- Identify the scanner module model from the PCB
- Check if the scanner module has its own debug/firmware update port
- Verify the baud rate and protocol between node4 MCU and scanner module
5. **Nodebus security audit**: Verify the XOR encryption scheme for secure nodebus commands and assess whether it can be bypassed to inject forged QR scan results.