Files
pokemon_pinball_wiki/qr-code-analysis.md

385 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.