Files
pokemon_pinball_wiki/conagent-re.md
2026-07-06 20:06:06 -05:00

422 lines
16 KiB
Markdown

# Conagent Reverse Engineering Report
## Scope
Target binary:
- Target path: `/games/pokemon_pro/conagent`
- Workspace path: `games/pokemon_pro/conagent`
- SHA-256: `a76da91f0cfdaecceb579ef27d223ab8a86c313d0eefa5217b96823c43c97e69`
- Type: stripped AArch64 PIE ELF, dynamically linked, interpreter `/lib/ld-linux-aarch64.so.1`
- Size: 272,432 bytes
- Ghidra import path: `/pokemon_spike3/conagent`
- Ghidra language: `AARCH64:LE:64:v8A`
## Ghidra Status
The binary imported and auto-analyzed in Ghidra successfully. The MCP decompiler recovered the ELF entry stub, but Ghidra did not create ordinary functions for most of the stripped `.text` region during this pass.
Evidence:
- Ghidra import result: success, language `AARCH64:LE:64:v8A`, auto-analysis complete.
- Ghidra `entry` decompilation shows `__libc_start_main(&DAT_00103d90, ...)`.
- Ghidra `find_code_gaps` reported one large unanalyzed region from `00104411` to `0012f11f`, which covers most application logic.
- Local `objdump -p` shows the executable LOAD segment maps file offsets directly to VAs from `0x0` through `0x41007`, and Ghidra imported it with an image base of `0x100000`.
Because of that analysis limitation, this report combines:
- Ghidra import/import-symbol results.
- Local `objdump` disassembly.
- Local `strings -a -t x` evidence.
- Manual reconstruction from string clusters and call sites.
## High-level Role
`conagent` is the Stern Pinball Connectivity Agent. It bridges the local game process to Stern back-end services.
Confirmed evidence:
- Version/banner string at `0x36838`: `Stern Pinball Connectivity Agent %s`
- Version string at `0x36818`: `1.0.16 (Linux,ARM64,GNU)`
- Library string at `0x39448`: `libagent 1.0.16 (Linux,ARM64,GNU)`
- Local game-process socket path at `0x396d9`: `/usr/local/spike/agent.uds`
- Default logs:
- `0x36720`: `/dump/log/connectivity/agent.log`
- `0x36748`: `/dump/log/connectivity/agent.msglog`
- `0x36770`: `/dump/log/connectivity/agent.httplog`
Inferred architecture:
- The game process communicates with `conagent` over a local Unix-domain socket.
- `conagent` parses framed game messages, maintains a state machine, and translates selected game actions into HTTPS requests and file-transfer activity.
- It also accepts game-originated network configuration requests and can mutate host network interface configuration.
## Dynamic Dependencies and Imports
Dynamic libraries from `objdump -p`:
- `libcurl.so.4`
- `libssl.so.3`
- `libcrypto.so.3`
- `libstdc++.so.6`
- `libm.so.6`
- `libgcc_s.so.1`
- `libc.so.6`
- `ld-linux-aarch64.so.1`
Important imported APIs from Ghidra/import table:
- Network/socket IPC: `socket`, `bind`, `listen`, `accept`, `connect`, `send`, `recv`, `select`, `shutdown`, `setsockopt`, `getaddrinfo`, `freeaddrinfo`
- HTTP: `curl_global_init`, `curl_easy_init`, `curl_easy_setopt`, `curl_easy_perform`, `curl_easy_getinfo`, `curl_multi_*`, `curl_share_*`, `curl_slist_append`
- Crypto/signing: `SHA256_Init`, `SHA256_Update`, `SHA256_Final`, `EVP_sha256`, `HMAC`
- Filesystem/process: `fopen`, `fread`, `fwrite`, `fflush`, `fclose`, `rename`, `unlink`, `mkdir`, `stat`, `readlink`, `system`
- Shared memory/threading: `shm_open`, `shm_unlink`, `mmap`, `munmap`, `pthread_create`, `pthread_join`, `pthread_setname_np`, C++ `std::thread`, `std::condition_variable`
## Startup Flow
Recovered from entry at `0x4300` and main-like range at file VA `0x3d90`:
- ELF entry calls `__libc_start_main` with the main function pointer at `0x3d90`.
- The main function allocates a stack configuration block of about `0x140` bytes.
- It installs signal handling via `sigaction`.
- It calls internal setup around `0x23960`, then creates a logger/channel around `0x24310`.
- It logs banner/configuration strings from `agent.cpp`.
- It creates the game process interface around `0xc2c0`.
- It starts the game client/interface around `0xc940`.
- It loops calling an update function around `0xc990` with a `usleep(16000)` delay until a global shutdown flag is set.
- It shuts down the interface around `0xc8d0`, destroys logging around `0x23cb0`, and returns `0`.
Confirmed configuration strings:
- `0x36888`: `Configuration data:`
- `0x368a0`: `Log path : %s`
- `0x368c0`: `Message log path : %s`
- `0x368e0`: `HTTP log path : %s`
- `0x36900`: `Game process host : %s`
- `0x36920`: `Socket type : %s`
- `0x36940`: `Log level : %s`
- `0x36960`: `Transmit buffer size : %zu (%zuKB)`
- `0x36988`: `HTTP request count : %zu`
- `0x369a8`: `Log received messages: %s`
- `0x369c8`: `Log HTTP data : %s`
- `0x369e8`: `Log to console : %s`
- `0x36a08`: `Silent operation : %s`
## Local Game Protocol
Source-string evidence:
- `0x36ac0`: `src/agent_client.cpp`
- `0x36db0`: `Client receive thread started normally.`
- `0x36f08`: `client_rx`
- `0x37010`: `agent_client_rx`
- `0x37290`: `Message stream state reset; prev ID %d, next ID %d.`
- `0x373d0`: `Expected ID %d; got ID %d. Sending stream RESET.`
- `0x37408`: `Empty receive queue but ACTION_MESSAGE received.`
The local protocol appears to be a reliable framed message stream with:
- Message IDs.
- ACK/NACK handling.
- Resend handling.
- Stream reset handling.
- A receive thread named `agent_client_rx`.
Action names:
- `AgreeProtocolVersion`
- `AcceptConfig`
- `DenyConfig`
- `CommsEnable`
- `CommsDisable`
- `RegisterMachine`
- `AuthPlayer`
- `PingServer`
- `DisableNetworkInterface`
- `ConfigureNetworkInterface`
- `CheckForUpdates`
- `QueryServerTime`
- `QueueFileTransfer`
- `UpdateTransferState`
- `QueryTransferStatus`
- `QueryTransferEngineStatus`
- `QueryAchievementDescriptors`
- `GameToServerPOST`
- `GameToServerGET`
State names:
- `AwaitRegistration`
- `CommsDisabled`
- `NotConnected`
- `NegotiateProtocol`
- `AwaitConfig`
- `CommsEnabled`
Protocol/config message names:
- `HEARTBEAT`
- `CONFIG_SECURITY`
- `CONFIG_NETWORK`
- `CONFIG_TITLE`
- `CONFIG_DIAGNOSTICS`
- `CONFIG_ACCEPT`
- `CONFIG_DENY`
- `COMMS_ENABLE`
- `COMMS_DISABLE`
- `COMMS_REJECT`
- `SYSTEM_STATUS`
- `MACHINE_REGISTER`
- `SET_MACHINE_UUID`
- `PING_REQUEST`
- `PING_RESULT`
- `SERVER_RESPONSE`
- `PLAYER_AUTHENTICATE`
- `GAME_ACHIEVEMENT_QUERY`
- `DISABLE_INTERFACE_REQUEST`
- `CONFIGURE_INTERFACE_RESULT`
- `SOFTWARE_VERSION_INFO`
- `FILE_TRANSFER_REQUEST`
- `FILE_TRANSFER_STATUS`
- `FILE_TRANSFER_QUERY`
- `FILE_TRANSFER_UPDATE`
- `GAME_TO_SERVER_POST`
- `GAME_TO_SERVER_GET`
## Back-end API Behavior
The agent talks to HTTPS APIs with libcurl and an API token.
Confirmed endpoints and strings:
| Offset | String |
| --- | --- |
| `0x33a20` | `https://%s/api-token-auth/` |
| `0x33a40` | `{"username":"%s","password":"%s"}` |
| `0x301f8` | `/api/v1/ping` |
| `0x30388` | `/api/v1/game/version_upgrades_available` |
| `0x30818` | `/api/v3/game/player_auth` |
| `0x30c60` | `/api/v3/game/game_register` |
| `0x311a8` | `/api/v3/game/game_achievement_descriptors` |
| `0x31750` | `/api/v3/game/game_auth` |
| `0x33190` | `/api/v2/game/heartbeat` |
| `0x35618` | `http://%s:8345/b8Ag3XU6TH/wibbly_wobbly_timey_wimey/` |
| `0x35cb0` | `https://%s%s/?%.*s` |
| `0x35cc8` | `https://%s%s/` |
HTTP request headers/fields:
- `Authorization: Token %s`
- `Device-Token: %s`
- `Device-Refresh: %s`
- `Message-Number: `
- `App-Status-Code: `
- `Content-Type: application/json`
- User agent: `Spike-Connectivity-Agent/1.0`
The token sync flow:
- Requires server hostname/IP, API token username, and API token password.
- POSTs JSON credentials to `https://<server>/api-token-auth/`.
- Parses a JSON object and extracts string field `token`.
- Logs detailed cURL and HTTP failures.
The generic request flow:
- Builds HTTPS URLs from server hostname, endpoint, and optional query arguments.
- Adds `message_number` to API requests.
- Adds token/device headers.
- Supports at least `DELETE`, `POST`, `PUT`, and default GET behavior.
- Reads server-updated message numbers from response data/headers.
## Server Time Probe Endpoint
`/games/pokemon_pro/conagent` contains a separate plain-HTTP server-time probe:
```text
http://%s:8345/b8Ag3XU6TH/wibbly_wobbly_timey_wimey/
```
Evidence:
- `strings -a -t x /games/pokemon_pro/conagent` reports the URL format at file/string offset `0x35618`.
- Ghidra xref from string address `00135618` identifies `FUN_00115930`; the decompiler shows `src/client_http.cpp` constructing the URL with `snprintf(..., "http://%s:8345/b8Ag3XU6TH/wibbly_wobbly_timey_wimey/", server_host)`.
- `FUN_00115930` is the `http_client_query_server_time` path. It requires a destination buffer and backend server hostname/IP, optionally binds a network interface, sets libcurl options, uses user agent `Spike-Connectivity-Agent/1.0`, performs the request, stores the HTTP status, and copies up to `0x1000` bytes into the response buffer.
- The caller in `src/client_game.cpp` logs `Attempting to retrieve current date and time from server %s.`, calls the HTTP helper, and returns either the response body or an empty response to the game process.
- The game binary builds a `ServerTime` / `TIME_QUERY_REQUEST` message from `../source/net/net_connection.cpp` and logs `Requesting Agent to retrieve server time of day.`.
- The game-side response handler parses the returned body as JSON, reads `datetime`, applies it to TimeOfDay, and warns if `server_timezone` is present and not `UTC`. If no data is returned, it logs `TODO(Game): The server did not respond to the time query request.`
Interpretation: this is not one of the authenticated HTTPS `/api/v*` calls. It is a special connectivity-agent time endpoint on port `8345`, apparently used so the game can ask the configured server for current UTC time and update its internal TimeOfDay. The path token `b8Ag3XU6TH/wibbly_wobbly_timey_wimey` appears to be obscurity for a narrow time-service endpoint, not a secret copied into the wiki from a credential file.
## Game Registration and Authentication
Machine registration request fields:
- `serial_number`
- `machine_configuration_number`
- `model_number`
- `model_version`
- `user_uuid`
- `mac_address`
- `internal_ip_address`
Machine authentication request fields:
- `machine_uuid`
- `machine_configuration_number`
- `code_version`
Player authentication request fields:
- `user_uuid`
- `machine_uuid`
Game auth response fields:
- `stern_cdn_url`
- `stern_speed_test_url`
The binary contains detailed service status strings such as:
- `MACHINE_NOT_ENABLED`
- `MACHINE_NOT_REGISTERED`
- `MACHINE_HAS_BEEN_ARCHIVED`
- `MACHINE_PENDING_HARDWARE_REPLACEMENT`
- `GAME_MACHINE_TOKEN_INVALID`
- `GAME_MACHINE_TOKEN_EXPIRED`
- `GAME_MACHINE_TOKEN_REVOKED`
- `GAME_MACHINE_TOKEN_MISMATCH`
- `USER_NOT_ACTIVE`
- `USER_TOKEN_EXPIRED`
## File Transfer and Software Update Behavior
Evidence strings under `src/client_http.cpp`:
- `Launching download request %lu, priority %u, %s -> %s.`
- `Launching upload request %lu, priority %u, %s -> %s.`
- `File transfer request %lu has type %s, which is unsupported. The request will be dropped.`
- `File transfer %lu failed; partially downloaded file %s will be deleted.`
- `All software update file transfers will be paused and all other transfers will be canceled during the next http_client_update.`
- `Found transfer request %lu as the active software update transfer.`
- `Found transfer request %lu in the software update queue.`
Recovered behavior:
- File transfers are managed through libcurl multi handles.
- Transfers are prioritized into high-priority, normal-priority, software-update, and paused queues.
- Download destinations are opened/created on disk.
- Upload sources are opened and sized with file seeks.
- Partial downloads are deleted on failure.
- File transfer queue size and request size are configurable through `CONFIG_NETWORK`.
This likely feeds the `/connectivity/update/UPDATE_REQUESTED` flow documented in `update-and-recovery.md`, but that handoff was not proven in this pass.
## Network Configuration Mutation
The agent can apply game-requested network configuration to the host.
Evidence strings:
- `0x30448`: `/tmp/network_interface.%s`
- `0x2f888`: `iface %s inet dhcp`
- `0x2f8f0`: `iface %s inet static`
- `0x2f909`: `address %s`
- `0x2f919`: `netmask %s`
- `0x2f929`: `gateway %s`
- `0x2f939`: `dns-nameservers %s %s`
- `0x2f951`: `dns-nameservers %s`
- `0x2fa78`: `ifdown -i %s %s`
- `0x31d70`: `ifup -i %s %s`
Validation strings indicate it checks for:
- Interface name.
- IP address.
- Netmask.
- Gateway.
- Primary DNS for static config.
- Server hostname.
- Transfer queue capacity.
- Transfer request size.
- Flags `APPLY` or `STORE`.
- Required interface MAC address.
Security note:
- The binary imports `system`.
- The network code constructs `ifdown -i %s %s` and `ifup -i %s %s` command lines.
- The strings indicate validation exists, but this pass did not prove whether interface names and generated config paths are shell-escaped or restricted enough. This is a worthwhile follow-up audit target.
## CloudWatch Logging
The binary contains an AWS CloudWatch Logs client.
Evidence strings:
- `0x362b0`: `https://logs.us-west-1.amazonaws.com`
- `0x365e8`: `Logs_20140328.PutLogEvents`
- `0x36268`: `AWS4-HMAC-SHA256`
- `0x36280`: `/us-west-1/logs/aws4_request`
- `0x361b9`: `host:logs.us-west-1.amazonaws.com`
- `0x36478`: `src/cloudwatch_logger.cpp`
- `0x36498`: `nextSequenceToken`
- `0x364c8`: `UnrecognizedClientException`
- `0x364e8`: `CLOUD LOG: disabling due to credential errors`
The implementation imports OpenSSL SHA-256 and HMAC routines. It appears to build AWS Signature Version 4 requests for `PutLogEvents`.
Credential-related string labels:
- `keys`
- `tokens`
- `sternmachine`
- `sternapi`
This report does not include any secret values. No concrete AWS access keys were copied into this page.
## Security-relevant Observations
1. `conagent` is root-supervised by `/etc/init.d/conagent_monitor`, which restarts it forever.
2. It accepts commands from the local game process over `/usr/local/spike/agent.uds`.
3. It can write network interface config files and execute `ifup`/`ifdown` through shell command strings.
4. It receives game-originated direct HTTP GET/POST proxy requests, but strings show endpoint and payload validation/NACK paths.
5. It handles auth tokens, device tokens, refresh tokens, and CloudWatch credentials in-process.
6. It logs HTTP and message traffic to `/dump/log/connectivity/agent.*` depending on configuration.
7. File transfers can write to local paths and delete partial downloads on failure; path validation needs a dedicated pass.
## Generated C-like Export
See `conagent-pseudocode.c` in this wiki folder. It is a manually reconstructed, non-compilable pseudocode export based on the Ghidra import, local disassembly, and string evidence.
## Commands Used
```sh
file games/pokemon_pro/conagent
shasum -a 256 games/pokemon_pro/conagent
objdump -p games/pokemon_pro/conagent
nm -D games/pokemon_pro/conagent
strings -a -t x -n 4 games/pokemon_pro/conagent
objdump -d --start-address=0x3d90 --stop-address=0x42b0 games/pokemon_pro/conagent
objdump -d --start-address=0x23200 --stop-address=0x23d00 games/pokemon_pro/conagent
```
Ghidra MCP calls:
- `import_file` for `/Users/jordan/Downloads/pokemon_spike3_rootfs/games/pokemon_pro/conagent`
- `list_imports`
- `find_code_gaps`
- `decompile_function` at `00104300`
## Follow-up Targets
- Force Ghidra function creation for the large stripped `.text` region, then rerun decompilation on:
- Main at file VA `0x3d90` / Ghidra VA `0x103d90`.
- Logger creation/destruction around `0x23960` and `0x23cb0`.
- Agent client creation/update/shutdown around `0xc2c0`, `0xc940`, `0xc990`, and `0xc8d0`.
- HTTP client functions around the `src/client_http.cpp` string cluster.
- Audit shell command construction for `ifup`/`ifdown`.
- Audit direct Game-to-Server endpoint validation.
- Audit file-transfer local path validation and update handoff into `/connectivity/update`.
- Determine where API token username/password and device token material originate in runtime config messages.