mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af2783cb03 |
No files matched your search
@@ -0,0 +1,153 @@
|
||||
# Bionic Steam client probe
|
||||
|
||||
This is a research harness for Valve's Android/Bionic ARM64 Steam client
|
||||
libraries. It does **not** vendor or redistribute Valve binaries. The build
|
||||
script fetches the current package from Valve's public Steam client-update
|
||||
endpoint into a temporary working directory.
|
||||
|
||||
The question under test is narrow:
|
||||
|
||||
> Can Valve's `androidarm64/libsteamclient.so` run directly under Android's
|
||||
> Bionic linker, and if so, what does it need in order to become a usable
|
||||
> Steam client?
|
||||
|
||||
## Result
|
||||
|
||||
Yes, the library itself works directly on stock Android. It is not a complete
|
||||
Steam desktop client, however. It is a Steamworks IPC client for a separate
|
||||
full Steam process.
|
||||
|
||||
On 2026-09-28, the public-beta ARM64 client manifest contained
|
||||
`bins_androidarm64_linuxarm64.zip.35688b00e791987ab70b1b6c6ddd006d4c864470`
|
||||
with SHA-256:
|
||||
|
||||
`811859a83c7c5ff220ac9731235e76fd0d5da603a5cdb56fe5f08e145a417cbd`
|
||||
|
||||
The archive contains Android ARM64 builds of `libsteamclient.so`,
|
||||
`libsteamnetworkingsockets.so`, `libtier0_s.so`, `libvstdlib_s.so`, and
|
||||
`steamservice.so`.
|
||||
## ABI evidence
|
||||
|
||||
`libsteamclient.so` is a genuine Android/NDK build, not a glibc ARM64 binary
|
||||
with an Android-flavored directory name:
|
||||
|
||||
- ELF64 AArch64 shared object.
|
||||
- Depends on `libandroid.so`, `liblog.so`, `libm.so`, `libdl.so`, and Bionic
|
||||
`libc.so`.
|
||||
- Uses the NDK C++ namespace (`std::__ndk1`).
|
||||
- Carries `.note.android.ident` and Android LLVM/Clang build strings.
|
||||
- Contains no `GLIBC_*` symbol-version requirements.
|
||||
|
||||
Android's stock linker on an AYN Thor loaded both Valve libraries with no
|
||||
compatibility shim. `CreateInterface("SteamClient023")` also succeeded.
|
||||
|
||||
## What `steamservice.so` proved
|
||||
|
||||
`steamservice.so` exports `SteamService_StartThread`,
|
||||
`SteamService_GetIPCServer`, `SteamService_Stop`, and
|
||||
`SteamService_Shutdown`. A non-null service name starts its worker thread
|
||||
successfully on Android and shuts down normally.
|
||||
|
||||
That service is **not** the Steamworks host expected by `libsteamclient.so`.
|
||||
Starting it does not create the TCP IPC listener used by `CreateSteamPipe`.
|
||||
The service probe is retained because it establishes that the Bionic service
|
||||
code itself is runnable and documents the calling convention.
|
||||
## The actual dependency: Steam IPC on TCP 57343
|
||||
|
||||
With no Steam host available, both the legacy exported C entry point and the
|
||||
`SteamClient023` virtual `CreateSteamPipe` method return `0`.
|
||||
|
||||
An `LD_PRELOAD` trace of `socket()` and `connect()` showed why:
|
||||
|
||||
```text
|
||||
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) -> fd
|
||||
connect(fd, 127.0.0.1:57343) -> ECONNREFUSED
|
||||
connect(fd, 127.0.0.1:57343) -> ECONNREFUSED
|
||||
```
|
||||
|
||||
A dummy listener captured the client's first 17 bytes:
|
||||
|
||||
```text
|
||||
0d 00 00 00 09 01 00 00 00 00 00 00 00 00 00 00 00
|
||||
```
|
||||
|
||||
Port 57343 is Steam's long-standing local IPC listener. Valve's Lepton
|
||||
Android compatibility environment uses the same Steam gateway for its
|
||||
Android `libsteamclient.so`.
|
||||
|
||||
## End-to-end proof
|
||||
|
||||
Desktop Steam on a Mac was listening on host `127.0.0.1:57343`. We mapped
|
||||
Android's loopback port to it temporarily with:
|
||||
|
||||
```sh
|
||||
adb reverse tcp:57343 tcp:57343
|
||||
```
|
||||
With no local `steamservice.so` involved, the unmodified Bionic client on the
|
||||
Thor then returned:
|
||||
|
||||
```text
|
||||
SteamClient023=<non-null> rc=0
|
||||
Steam_CreateSteamPipe=1
|
||||
Steam_ConnectToGlobalUser=1
|
||||
Steam_BConnected=1
|
||||
Steam_BLoggedOn=1
|
||||
Steam_BReleaseSteamPipe=1
|
||||
```
|
||||
|
||||
This proves the Bionic client is operational on normal Android and can speak
|
||||
the real cross-process Steam IPC protocol to an ordinary desktop Steam
|
||||
process.
|
||||
|
||||
## Implication for DroidDeck / PRoot
|
||||
|
||||
This discovery does **not** by itself remove PRoot. The Android archive does
|
||||
not contain the full Steam executable or `steamwebhelper`; the Bionic client
|
||||
expects that full Steam process to exist elsewhere and own port 57343.
|
||||
|
||||
The useful architectural split is now proven rather than hypothetical:
|
||||
|
||||
```text
|
||||
Android/Bionic workload
|
||||
-> Valve Bionic libsteamclient.so
|
||||
-> TCP Steam IPC :57343
|
||||
-> full Steam host process
|
||||
```
|
||||
|
||||
For a PRoot-free DroidDeck, the remaining problem is therefore the **host
|
||||
Steam process**, not Steamworks ABI compatibility. Plausible future backends
|
||||
are a real Linux environment (rooted chroot/namespaces), a VM/AVF guest with
|
||||
a host bridge, or a future Valve Bionic build of the full client. Reimplementing
|
||||
Steam's private IPC server should be treated as a last resort.
|
||||
## Reproducing
|
||||
|
||||
Build and fetch Valve's current Android package into `/tmp`:
|
||||
|
||||
```sh
|
||||
tools/research/bionic-steam-probe/fetch_build.sh
|
||||
```
|
||||
|
||||
Run without a Steam host. This should resolve the Bionic interfaces but
|
||||
return pipe/user handle `0`:
|
||||
|
||||
```sh
|
||||
tools/research/bionic-steam-probe/run_probe.sh
|
||||
```
|
||||
|
||||
If desktop Steam is running on the ADB host and listening on loopback, test
|
||||
the real bridge:
|
||||
|
||||
```sh
|
||||
tools/research/bionic-steam-probe/run_probe.sh --reverse-host-steam
|
||||
```
|
||||
|
||||
The runner removes the ADB reverse mapping on exit. No Valve binary is copied
|
||||
into this repository.
|
||||
|
||||
## Security note
|
||||
|
||||
Steam binds 57343 to loopback on desktop systems. Keep any DroidDeck bridge
|
||||
equally constrained; do not expose this private Steam IPC endpoint to the LAN.
|
||||
On Android all regular applications share the device network namespace, so a
|
||||
future local host design should also consider how to prevent unrelated apps
|
||||
from reaching a loopback-only Steam IPC listener.
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
MANIFEST_URL="${MANIFEST_URL:-https://client-update.steamstatic.com/steam_client_publicbeta_linuxarm64}"
|
||||
WORKDIR="${WORKDIR:-${TMPDIR:-/tmp}/droiddeck-bionic-steam-probe}"
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
mkdir -p "$WORKDIR/android" "$WORKDIR/bin"
|
||||
|
||||
find_ndk() {
|
||||
if [[ -n "${ANDROID_NDK_HOME:-}" ]]; then
|
||||
printf '%s\n' "$ANDROID_NDK_HOME"
|
||||
return
|
||||
fi
|
||||
if [[ -n "${ANDROID_NDK_ROOT:-}" ]]; then
|
||||
printf '%s\n' "$ANDROID_NDK_ROOT"
|
||||
return
|
||||
fi
|
||||
local sdk="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}}"
|
||||
local ndk
|
||||
ndk="$(find "$sdk/ndk" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)"
|
||||
[[ -n "$ndk" ]] || return 1
|
||||
printf '%s\n' "$ndk"
|
||||
}
|
||||
NDK="$(find_ndk)" || {
|
||||
echo "Android NDK not found; set ANDROID_NDK_HOME" >&2
|
||||
exit 1
|
||||
}
|
||||
HOST_TAG="linux-x86_64"
|
||||
[[ "$(uname -s)" == Darwin ]] && HOST_TAG="darwin-x86_64"
|
||||
CC="$NDK/toolchains/llvm/prebuilt/$HOST_TAG/bin/aarch64-linux-android26-clang"
|
||||
|
||||
curl -fsSL "$MANIFEST_URL" -o "$WORKDIR/manifest.vdf"
|
||||
PACKAGE="$(python3 - "$WORKDIR/manifest.vdf" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
text = open(sys.argv[1], encoding="utf-8").read()
|
||||
m = re.search(
|
||||
r'"bins_androidarm64_linuxarm64"\s*\{.*?"file"\s*"([^"]+)"',
|
||||
text,
|
||||
re.S,
|
||||
)
|
||||
if not m:
|
||||
raise SystemExit("bins_androidarm64_linuxarm64 not found")
|
||||
print(m.group(1))
|
||||
PY
|
||||
)"
|
||||
PACKAGE_SHA="$(python3 - "$WORKDIR/manifest.vdf" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
text = open(sys.argv[1], encoding="utf-8").read()
|
||||
block = re.search(r'"bins_androidarm64_linuxarm64"\s*\{(.*?)\n\s*\}', text, re.S)
|
||||
if not block:
|
||||
raise SystemExit("bins_androidarm64_linuxarm64 block not found")
|
||||
m = re.search(r'"sha2"\s*"([0-9a-fA-F]{64})"', block.group(1))
|
||||
if not m:
|
||||
raise SystemExit("sha2 missing from Android ARM64 package")
|
||||
print(m.group(1).lower())
|
||||
PY
|
||||
)"
|
||||
echo "Valve package: $PACKAGE"
|
||||
curl -fsSL "https://client-update.steamstatic.com/$PACKAGE" -o "$WORKDIR/android.zip"
|
||||
ACTUAL_SHA="$(python3 - "$WORKDIR/android.zip" <<'PY'
|
||||
import hashlib
|
||||
import sys
|
||||
print(hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest())
|
||||
PY
|
||||
)"
|
||||
[[ "$ACTUAL_SHA" == "$PACKAGE_SHA" ]] || {
|
||||
echo "SHA-256 mismatch: manifest=$PACKAGE_SHA download=$ACTUAL_SHA" >&2
|
||||
exit 1
|
||||
}
|
||||
python3 - "$WORKDIR/android.zip" "$WORKDIR/android" <<'PY'
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
archive, out = sys.argv[1:]
|
||||
wanted = {
|
||||
"libsteamclient.so",
|
||||
"steamservice.so",
|
||||
"libtier0_s.so",
|
||||
"libvstdlib_s.so",
|
||||
}
|
||||
with zipfile.ZipFile(archive) as zf:
|
||||
for name in zf.namelist():
|
||||
base = os.path.basename(name)
|
||||
if base in wanted:
|
||||
target = os.path.join(out, base)
|
||||
with open(target, "wb") as f:
|
||||
f.write(zf.read(name))
|
||||
print(f"extracted {name} -> {target}")
|
||||
PY
|
||||
|
||||
"$CC" -O0 -g -Wall -Wextra "$SCRIPT_DIR/steamclient_probe.c" -ldl -o "$WORKDIR/bin/steamclient-probe"
|
||||
"$CC" -O0 -g -Wall -Wextra "$SCRIPT_DIR/steamservice_probe.c" -ldl -o "$WORKDIR/bin/steamservice-probe"
|
||||
"$CC" -O0 -g -Wall -Wextra "$SCRIPT_DIR/steamclient_service_probe.c" -ldl -o "$WORKDIR/bin/steamclient-service-probe"
|
||||
"$CC" -shared -fPIC -O0 -g -Wall -Wextra "$SCRIPT_DIR/trace_hooks.c" -o "$WORKDIR/bin/trace-hooks.so"
|
||||
echo "Built probes in $WORKDIR/bin"
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
WORKDIR="${WORKDIR:-${TMPDIR:-/tmp}/droiddeck-bionic-steam-probe}"
|
||||
ADB="${ADB:-adb}"
|
||||
REMOTE="/data/local/tmp/droiddeck-bionic"
|
||||
REVERSE=0
|
||||
|
||||
if [[ "${1:-}" == "--reverse-host-steam" ]]; then
|
||||
REVERSE=1
|
||||
elif [[ -n "${1:-}" ]]; then
|
||||
echo "usage: $0 [--reverse-host-steam]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
[[ -f "$WORKDIR/bin/steamclient-probe" ]] || {
|
||||
echo "Run fetch_build.sh first" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ -f "$WORKDIR/android/libsteamclient.so" ]] || {
|
||||
echo "Valve libsteamclient.so is missing from $WORKDIR" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
"$ADB" shell "mkdir -p $REMOTE"
|
||||
"$ADB" push "$WORKDIR/bin/steamclient-probe" "$REMOTE/steamclient-probe" >/dev/null
|
||||
"$ADB" push "$WORKDIR/android/libsteamclient.so" "$REMOTE/libsteamclient.so" >/dev/null
|
||||
"$ADB" shell "chmod 755 $REMOTE/steamclient-probe"
|
||||
cleanup() {
|
||||
if (( REVERSE )); then
|
||||
"$ADB" reverse --remove tcp:57343 >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if (( REVERSE )); then
|
||||
"$ADB" reverse --remove tcp:57343 >/dev/null 2>&1 || true
|
||||
"$ADB" reverse tcp:57343 tcp:57343 >/dev/null
|
||||
echo "Mapped Android 127.0.0.1:57343 to host 127.0.0.1:57343."
|
||||
echo "A desktop Steam process must already be listening on the host."
|
||||
else
|
||||
echo "No bridge configured; CreateSteamPipe should be 0 unless Steam already"
|
||||
echo "exists in the Android network namespace."
|
||||
fi
|
||||
|
||||
set +e
|
||||
"$ADB" shell "$REMOTE/steamclient-probe $REMOTE/libsteamclient.so"
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
if (( REVERSE )); then
|
||||
if (( rc == 0 )); then
|
||||
echo "PASS: Bionic libsteamclient connected to host Steam."
|
||||
else
|
||||
echo "FAIL: Bionic libsteamclient did not obtain a Steam pipe." >&2
|
||||
fi
|
||||
fi
|
||||
exit "$rc"
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <dlfcn.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef void *(*create_interface_fn)(const char *, int *);
|
||||
typedef int32_t (*create_pipe_fn)(void);
|
||||
typedef int32_t (*connect_user_fn)(int32_t);
|
||||
typedef bool (*status_fn)(int32_t, int32_t);
|
||||
typedef bool (*release_pipe_fn)(int32_t);
|
||||
|
||||
static void *lookup(void *handle, const char *name) {
|
||||
dlerror();
|
||||
void *ptr = dlsym(handle, name);
|
||||
const char *error = dlerror();
|
||||
printf("dlsym %-28s = %p", name, ptr);
|
||||
if (error != NULL) printf(" error=%s", error);
|
||||
putchar('\n');
|
||||
return ptr;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "usage: %s LIBSTEAMCLIENT.SO\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
printf("pid=%d uid=%d\n", getpid(), getuid());
|
||||
void *client = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
|
||||
if (client == NULL) {
|
||||
fprintf(stderr, "dlopen failed: %s\n", dlerror());
|
||||
return 3;
|
||||
}
|
||||
|
||||
create_interface_fn create_interface =
|
||||
(create_interface_fn)lookup(client, "CreateInterface");
|
||||
create_pipe_fn create_pipe =
|
||||
(create_pipe_fn)lookup(client, "Steam_CreateSteamPipe");
|
||||
connect_user_fn connect_user =
|
||||
(connect_user_fn)lookup(client, "Steam_ConnectToGlobalUser");
|
||||
status_fn connected =
|
||||
(status_fn)lookup(client, "Steam_BConnected");
|
||||
status_fn logged_on =
|
||||
(status_fn)lookup(client, "Steam_BLoggedOn");
|
||||
release_pipe_fn release_pipe =
|
||||
(release_pipe_fn)lookup(client, "Steam_BReleaseSteamPipe");
|
||||
|
||||
int rc = -1;
|
||||
void *iface = create_interface != NULL
|
||||
? create_interface("SteamClient023", &rc) : NULL;
|
||||
printf("SteamClient023=%p rc=%d\n", iface, rc);
|
||||
|
||||
int32_t pipe = create_pipe != NULL ? create_pipe() : 0;
|
||||
printf("Steam_CreateSteamPipe=%d\n", pipe);
|
||||
int32_t user = (pipe != 0 && connect_user != NULL)
|
||||
? connect_user(pipe) : 0;
|
||||
printf("Steam_ConnectToGlobalUser=%d\n", user);
|
||||
|
||||
if (pipe != 0 && user != 0 && connected != NULL) {
|
||||
printf("Steam_BConnected=%d\n", connected(user, pipe));
|
||||
}
|
||||
if (pipe != 0 && user != 0 && logged_on != NULL) {
|
||||
printf("Steam_BLoggedOn=%d\n", logged_on(user, pipe));
|
||||
}
|
||||
|
||||
if (pipe != 0 && release_pipe != NULL) {
|
||||
printf("Steam_BReleaseSteamPipe=%d\n", release_pipe(pipe));
|
||||
}
|
||||
dlclose(client);
|
||||
|
||||
return pipe != 0 && user != 0 ? 0 : 4;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#include <dlfcn.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef void *(*create_interface_fn)(const char *, int *);
|
||||
typedef void *(*service_start_fn)(const char *);
|
||||
typedef void *(*service_get_ipc_fn)(void);
|
||||
typedef void (*service_void_fn)(void);
|
||||
typedef int32_t (*create_pipe_fn)(void);
|
||||
typedef int32_t (*connect_user_fn)(int32_t);
|
||||
typedef bool (*connected_fn)(int32_t, int32_t);
|
||||
typedef bool (*release_pipe_fn)(int32_t);
|
||||
|
||||
static void *must_sym(void *handle, const char *name) {
|
||||
dlerror();
|
||||
void *ptr = dlsym(handle, name);
|
||||
const char *error = dlerror();
|
||||
printf("dlsym %-28s = %p", name, ptr);
|
||||
if (error != NULL) printf(" error=%s", error);
|
||||
putchar('\n');
|
||||
fflush(stdout);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void dump_unix_sockets(const char *label) {
|
||||
FILE *file = fopen("/proc/self/net/unix", "r");
|
||||
if (file == NULL) return;
|
||||
printf("--- UNIX sockets: %s ---\n", label);
|
||||
char line[1024];
|
||||
while (fgets(line, sizeof(line), file) != NULL) {
|
||||
if (strstr(line, "steam") != NULL ||
|
||||
strstr(line, "Steam") != NULL ||
|
||||
strstr(line, "@") != NULL) {
|
||||
fputs(line, stdout);
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void *open_library(const char *path) {
|
||||
printf("dlopen(%s)\n", path);
|
||||
fflush(stdout);
|
||||
void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
||||
if (handle == NULL) {
|
||||
fprintf(stderr, "dlopen failed: %s\n", dlerror());
|
||||
} else {
|
||||
printf("dlopen ok handle=%p\n", handle);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "usage: %s STEAMSERVICE.SO LIBSTEAMCLIENT.SO [service_arg]\n",
|
||||
argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const char *service_arg = argc >= 4 ? argv[3] : ".";
|
||||
printf("pid=%d uid=%d service_arg=%s\n",
|
||||
getpid(), getuid(), service_arg);
|
||||
|
||||
void *service = open_library(argv[1]);
|
||||
if (service == NULL) return 3;
|
||||
service_start_fn service_start =
|
||||
(service_start_fn)must_sym(service, "SteamService_StartThread");
|
||||
service_get_ipc_fn get_ipc =
|
||||
(service_get_ipc_fn)must_sym(service, "SteamService_GetIPCServer");
|
||||
service_void_fn stop =
|
||||
(service_void_fn)must_sym(service, "SteamService_Stop");
|
||||
service_void_fn shutdown =
|
||||
(service_void_fn)must_sym(service, "SteamService_Shutdown");
|
||||
|
||||
printf("IPC before start=%p\n", get_ipc != NULL ? get_ipc() : NULL);
|
||||
void *started = service_start != NULL ? service_start(service_arg) : NULL;
|
||||
printf("StartThread=%p IPC after start=%p\n",
|
||||
started, get_ipc != NULL ? get_ipc() : NULL);
|
||||
fflush(stdout);
|
||||
usleep(200000);
|
||||
dump_unix_sockets("after service start");
|
||||
|
||||
void *client = open_library(argv[2]);
|
||||
if (client == NULL) return 4;
|
||||
|
||||
create_interface_fn create_interface =
|
||||
(create_interface_fn)must_sym(client, "CreateInterface");
|
||||
create_pipe_fn create_pipe =
|
||||
(create_pipe_fn)must_sym(client, "Steam_CreateSteamPipe");
|
||||
connect_user_fn connect_user =
|
||||
(connect_user_fn)must_sym(client, "Steam_ConnectToGlobalUser");
|
||||
connected_fn connected =
|
||||
(connected_fn)must_sym(client, "Steam_BConnected");
|
||||
connected_fn logged_on =
|
||||
(connected_fn)must_sym(client, "Steam_BLoggedOn");
|
||||
release_pipe_fn release_pipe =
|
||||
(release_pipe_fn)must_sym(client, "Steam_BReleaseSteamPipe");
|
||||
|
||||
int rc = -999;
|
||||
void *steam_client = create_interface != NULL
|
||||
? create_interface("SteamClient023", &rc) : NULL;
|
||||
printf("CreateInterface SteamClient023 -> %p rc=%d\n",
|
||||
steam_client, rc);
|
||||
|
||||
int32_t adapter_pipe = 0;
|
||||
if (steam_client != NULL) {
|
||||
void **vtable = *(void ***)steam_client;
|
||||
int32_t (*adapter_create_pipe)(void *) =
|
||||
(int32_t (*)(void *))vtable[0];
|
||||
printf("SteamClient023 vtable=%p CreateSteamPipe=%p\n",
|
||||
vtable, (void *)adapter_create_pipe);
|
||||
adapter_pipe = adapter_create_pipe(steam_client);
|
||||
printf("SteamClient023::CreateSteamPipe -> %d\n", adapter_pipe);
|
||||
}
|
||||
|
||||
int32_t pipe = create_pipe != NULL ? create_pipe() : 0;
|
||||
printf("Steam_CreateSteamPipe -> %d\n", pipe);
|
||||
dump_unix_sockets("after client CreateSteamPipe");
|
||||
|
||||
int32_t user = (connect_user != NULL && pipe != 0)
|
||||
? connect_user(pipe) : 0;
|
||||
printf("Steam_ConnectToGlobalUser(%d) -> %d\n", pipe, user);
|
||||
|
||||
if (connected != NULL && user != 0 && pipe != 0) {
|
||||
printf("Steam_BConnected -> %d\n", connected(user, pipe));
|
||||
}
|
||||
if (logged_on != NULL && user != 0 && pipe != 0) {
|
||||
printf("Steam_BLoggedOn -> %d\n", logged_on(user, pipe));
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
sleep(2);
|
||||
if (release_pipe != NULL && pipe != 0) {
|
||||
printf("Steam_BReleaseSteamPipe -> %d\n", release_pipe(pipe));
|
||||
}
|
||||
if (stop != NULL) stop();
|
||||
if (shutdown != NULL) shutdown();
|
||||
|
||||
dlclose(client);
|
||||
dlclose(service);
|
||||
puts("done");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef void *(*get_ipc_fn)(void);
|
||||
typedef void *(*start_thread_fn)(const char *);
|
||||
typedef void (*void_fn)(void);
|
||||
|
||||
static void *lookup(void *handle, const char *name) {
|
||||
dlerror();
|
||||
void *ptr = dlsym(handle, name);
|
||||
const char *error = dlerror();
|
||||
printf("dlsym %-28s = %p", name, ptr);
|
||||
if (error != NULL) {
|
||||
printf(" error=%s", error);
|
||||
}
|
||||
putchar('\n');
|
||||
fflush(stdout);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr,
|
||||
"usage: %s STEAMSERVICE.SO [symbols|getipc|start [arg]]\n",
|
||||
argv[0]);
|
||||
return 2;
|
||||
}
|
||||
printf("pid=%d uid=%d euid=%d\n", getpid(), getuid(), geteuid());
|
||||
printf("dlopen(%s)\n", argv[1]);
|
||||
fflush(stdout);
|
||||
|
||||
void *handle = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
|
||||
if (handle == NULL) {
|
||||
fprintf(stderr, "dlopen failed: %s\n", dlerror());
|
||||
return 3;
|
||||
}
|
||||
printf("dlopen ok handle=%p\n", handle);
|
||||
|
||||
get_ipc_fn get_ipc =
|
||||
(get_ipc_fn)lookup(handle, "SteamService_GetIPCServer");
|
||||
start_thread_fn start =
|
||||
(start_thread_fn)lookup(handle, "SteamService_StartThread");
|
||||
void_fn stop = (void_fn)lookup(handle, "SteamService_Stop");
|
||||
void_fn shutdown = (void_fn)lookup(handle, "SteamService_Shutdown");
|
||||
lookup(handle, "SteamService_RunMainLoop");
|
||||
lookup(handle, "CreateInterface");
|
||||
|
||||
const char *mode = argc >= 3 ? argv[2] : "symbols";
|
||||
if (!strcmp(mode, "getipc") || !strcmp(mode, "start")) {
|
||||
printf("GetIPCServer before start -> %p\n",
|
||||
get_ipc != NULL ? get_ipc() : NULL);
|
||||
fflush(stdout);
|
||||
}
|
||||
if (!strcmp(mode, "start")) {
|
||||
const char *arg = argc >= 4 ? argv[3] : NULL;
|
||||
printf("StartThread arg=%s\n", arg != NULL ? arg : "<NULL>");
|
||||
fflush(stdout);
|
||||
|
||||
void *result = start != NULL ? start(arg) : NULL;
|
||||
printf("StartThread -> %p; GetIPCServer -> %p\n",
|
||||
result, get_ipc != NULL ? get_ipc() : NULL);
|
||||
fflush(stdout);
|
||||
|
||||
sleep(2);
|
||||
if (stop != NULL) {
|
||||
puts("Stop()");
|
||||
fflush(stdout);
|
||||
stop();
|
||||
}
|
||||
if (shutdown != NULL) {
|
||||
puts("Shutdown()");
|
||||
fflush(stdout);
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
dlclose(handle);
|
||||
puts("done");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void trace_line(const char *kind, int fd,
|
||||
const struct sockaddr *addr, socklen_t len) {
|
||||
char buffer[768];
|
||||
const char *detail = "";
|
||||
char endpoint[512] = {0};
|
||||
|
||||
if (addr != NULL && addr->sa_family == AF_UNIX &&
|
||||
len > offsetof(struct sockaddr_un, sun_path)) {
|
||||
const struct sockaddr_un *un = (const struct sockaddr_un *)addr;
|
||||
size_t path_len = len - offsetof(struct sockaddr_un, sun_path);
|
||||
if (path_len > sizeof(un->sun_path)) path_len = sizeof(un->sun_path);
|
||||
if (path_len > 0 && un->sun_path[0] == '\0') {
|
||||
snprintf(endpoint, sizeof(endpoint), "@%.*s",
|
||||
(int)(path_len - 1), un->sun_path + 1);
|
||||
} else {
|
||||
snprintf(endpoint, sizeof(endpoint), "%.*s",
|
||||
(int)path_len, un->sun_path);
|
||||
}
|
||||
detail = endpoint;
|
||||
} else if (addr != NULL && addr->sa_family == AF_INET &&
|
||||
len >= sizeof(struct sockaddr_in)) {
|
||||
const struct sockaddr_in *in = (const struct sockaddr_in *)addr;
|
||||
unsigned long ip = ntohl(in->sin_addr.s_addr);
|
||||
snprintf(endpoint, sizeof(endpoint), "%lu.%lu.%lu.%lu:%u",
|
||||
(ip >> 24) & 0xff, (ip >> 16) & 0xff,
|
||||
(ip >> 8) & 0xff, ip & 0xff, ntohs(in->sin_port));
|
||||
detail = endpoint;
|
||||
}
|
||||
int n = snprintf(buffer, sizeof(buffer),
|
||||
"[probe-trace] %s fd=%d family=%d endpoint=%s\n",
|
||||
kind, fd, addr != NULL ? addr->sa_family : -1, detail);
|
||||
if (n > 0) write(STDERR_FILENO, buffer, (size_t)n);
|
||||
}
|
||||
|
||||
int socket(int domain, int type, int protocol) {
|
||||
int fd = (int)syscall(SYS_socket, domain, type, protocol);
|
||||
char buffer[160];
|
||||
int n = snprintf(buffer, sizeof(buffer),
|
||||
"[probe-trace] socket domain=%d type=%d protocol=%d -> %d errno=%d\n",
|
||||
domain, type, protocol, fd, fd < 0 ? errno : 0);
|
||||
if (n > 0) write(STDERR_FILENO, buffer, (size_t)n);
|
||||
return fd;
|
||||
}
|
||||
|
||||
int socketpair(int domain, int type, int protocol, int sv[2]) {
|
||||
int rc = (int)syscall(SYS_socketpair, domain, type, protocol, sv);
|
||||
char buffer[180];
|
||||
int n = snprintf(buffer, sizeof(buffer),
|
||||
"[probe-trace] socketpair domain=%d type=%d -> %d [%d,%d] errno=%d\n",
|
||||
domain, type, rc, rc == 0 ? sv[0] : -1,
|
||||
rc == 0 ? sv[1] : -1, rc < 0 ? errno : 0);
|
||||
if (n > 0) write(STDERR_FILENO, buffer, (size_t)n);
|
||||
return rc;
|
||||
}
|
||||
int connect(int fd, const struct sockaddr *addr, socklen_t len) {
|
||||
trace_line("connect", fd, addr, len);
|
||||
int rc = (int)syscall(SYS_connect, fd, addr, len);
|
||||
if (rc < 0) {
|
||||
char buffer[120];
|
||||
int n = snprintf(buffer, sizeof(buffer),
|
||||
"[probe-trace] connect rc=%d errno=%d\n", rc, errno);
|
||||
if (n > 0) write(STDERR_FILENO, buffer, (size_t)n);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int bind(int fd, const struct sockaddr *addr, socklen_t len) {
|
||||
trace_line("bind", fd, addr, len);
|
||||
int rc = (int)syscall(SYS_bind, fd, addr, len);
|
||||
if (rc < 0) {
|
||||
char buffer[120];
|
||||
int n = snprintf(buffer, sizeof(buffer),
|
||||
"[probe-trace] bind rc=%d errno=%d\n", rc, errno);
|
||||
if (n > 0) write(STDERR_FILENO, buffer, (size_t)n);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int listen(int fd, int backlog) {
|
||||
int rc = (int)syscall(SYS_listen, fd, backlog);
|
||||
char buffer[120];
|
||||
int n = snprintf(buffer, sizeof(buffer),
|
||||
"[probe-trace] listen fd=%d backlog=%d -> %d errno=%d\n",
|
||||
fd, backlog, rc, rc < 0 ? errno : 0);
|
||||
if (n > 0) write(STDERR_FILENO, buffer, (size_t)n);
|
||||
return rc;
|
||||
}
|
||||
Reference in new issue
Block a user