422 lines
16 KiB
Markdown
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.
|