# 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:///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.