Files
Radicade/Tools/quest-http-inspect.c
T
2026-10-16 23:33:13 -07:00

218 lines
11 KiB
C

// Exact-build diagnostic. Logs response paths/status and chunked-read completion only.
#include <android/log.h>
#include <dlfcn.h>
#include <jni.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include "quest-image-placeholder.h"
#define LOG(...) __android_log_print(ANDROID_LOG_INFO, "RadicadeHTTP", __VA_ARGS__)
static uintptr_t base;
static void (*original)(void *, void *, void *);
static void *(*current_uri)(void *, void *);
static void *(*absolute_path)(void *, void *);
static void (*original_quit)(int, void *, void *);
static void *(*original_send)(void *, void *);
static int (*original_image_verify)(void *, void *);
static int verify_image(void *data, void *signature) {
// This exact-build entry point is image-only, after key-id/header parsing.
// Recognize the private server's legacy placeholder, never arbitrary signatures.
if (data && signature && *(uintptr_t *)((char *)signature + 0x18) == sizeof(image_placeholder)
&& !memcmp((char *)signature + 0x20, image_placeholder, sizeof(image_placeholder))) {
LOG("Radicade legacy image placeholder accepted in image-only verifier");
return 1;
}
return original_image_verify(data, signature);
}
static void *(*object_class)(void *);
static void *(*object_new)(void *);
static void *(*string_new)(const char *);
static void *(*method_named)(void *, const char *, int);
static void *(*invoke)(void *, void *, void **, void **);
static void (*write_barrier)(void *, void **, void *);
static void *inspect_send(void *request, void *method) {
if (!request) return original_send(request, method);
void *uri = *(void **)((char *)request + 0x10);
if (!uri) return original_send(request, method);
void *klass = object_class(uri), *exc = 0;
void *getter = method_named(klass, "get_AbsoluteUri", 0);
if (!getter) return original_send(request, method);
void *str = invoke(getter, uri, 0, &exc);
if (!str || exc) return original_send(request, method);
int len = *(int *)((char *)str + 0x10);
if (len < 0 || len > 1700) return original_send(request, method);
const uint16_t *chars = (const uint16_t *)((char *)str + 0x14);
char url[2048];
for (int i = 0; i < len; ++i) {
if (chars[i] > 127) return original_send(request, method);
url[i] = (char)chars[i];
}
url[len] = 0;
const char *origin = "https://ns.radicade.app/";
if (!strncmp(url, origin, strlen(origin))) {
void *setter = method_named(object_class(request), "set_IsKeepAlive", 1);
if (setter) {
unsigned char enabled = 0;
void *ka_args[] = {&enabled};
invoke(setter, request, ka_args, &exc);
if (exc) return original_send(request, method);
}
}
const char *prefix = "https://ns.radicade.app/images/";
if (strncmp(url, prefix, strlen(prefix)) || strstr(url, "rcsig=") || strchr(url, '#'))
return original_send(request, method);
strcat(url, strchr(url, '?') ? "&" : "?");
strcat(url, "rcsig=KEY%3ARSA%3Ap1.radicade.internal");
void *ctor = method_named(klass, ".ctor", 1);
void *newuri = object_new(klass), *newstr = string_new(url);
if (!ctor || !newuri || !newstr) return original_send(request, method);
void *args[] = {newstr};
invoke(ctor, newuri, args, &exc);
if (!exc) {
write_barrier(request, (void **)((char *)request + 0x10), newuri);
LOG("Image request key-id aligned for Radicade HTTPS origin");
}
return original_send(request, method);
}
static void inspect_quit(int code, void *reason, void *method) {
LOG("fatal quit code=%d caller_rva=%lx reason_chars=%d", code,
(unsigned long)((uintptr_t)__builtin_return_address(0) - base),
reason ? *(int *)((char *)reason + 0x10) : 0);
original_quit(code, reason, method);
}
static void path_for(void *response, char *out, size_t size) {
strcpy(out, "<unknown>");
void *request = *(void **)((char *)response + 0x78);
if (!request) return;
void *uri = current_uri(request, 0);
if (!uri) return;
void *str = absolute_path(uri, 0);
if (!str) return;
int len = *(int *)((char *)str + 0x10);
const uint16_t *chars = (const uint16_t *)((char *)str + 0x14);
if (len < 0) return;
size_t i = 0;
for (; i < (size_t)len && i + 1 < size; ++i) {
// Never include query/fragment data even if a malformed URI reaches us.
if (chars[i] == '?' || chars[i] == '#') break;
out[i] = chars[i] >= 32 && chars[i] < 127 ? (char)chars[i] : '_';
}
out[i] = 0;
}
static void inspect_chunk(void *response, void *stream, void *method) {
char path[192];
path_for(response, path, sizeof(path));
int status = *(int *)((char *)response + 0x18);
LOG("chunk begin response=%p status=%d path=%s", response, status, path);
original(response, stream, method);
LOG("chunk end response=%p", response);
}
static void jump(unsigned char *dst, uintptr_t target) {
uint32_t code[] = {0x58000050, 0xd61f0200}; // ldr x16,+8; br x16
memcpy(dst, code, 8);
memcpy(dst + 8, &target, 8);
}
static void *install(void *unused) {
(void)unused;
sleep(30);
void *lib = dlopen("libil2cpp.so", RTLD_NOW | RTLD_NOLOAD);
if (!lib) { LOG("IL2CPP unavailable; no hook installed"); return 0; }
Dl_info info;
if (!dladdr(dlsym(lib, "il2cpp_domain_get"), &info)) return 0;
base = (uintptr_t)info.dli_fbase;
// ReadChunkLength's unused receiver is optimized away at call sites.
// Hook ReadChunked instead, which actually consumes its response receiver.
unsigned char *target = (unsigned char *)(base + 0x6d941e0);
// Position-independent original prologue; refuse any other code.
uint32_t expected[] = {0xd10203ff, 0xa9026ffc, 0xa90367fa, 0xa9045ff8};
if (memcmp(target, expected, sizeof(expected))) { LOG("Prologue mismatch; no hook installed"); return 0; }
long page = sysconf(_SC_PAGESIZE);
if (page <= 0) return 0;
unsigned char *trampoline = mmap(0, (size_t)page, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (trampoline == MAP_FAILED) return 0;
memcpy(trampoline, target, 16);
jump(trampoline + 16, (uintptr_t)target + 16);
if (mprotect(trampoline, (size_t)page, PROT_READ | PROT_EXEC)) return 0;
__builtin___clear_cache((char *)trampoline, (char *)trampoline + 32);
original = (void (*)(void *, void *, void *))trampoline;
current_uri = (void *(*)(void *, void *))(base + 0x6d8d328);
absolute_path = (void *(*)(void *, void *))(base + 0xa0a956c);
void *start = (void *)((uintptr_t)target & ~((uintptr_t)page - 1));
if (mprotect(start, (size_t)page, PROT_READ | PROT_WRITE | PROT_EXEC)) return 0;
jump(target, (uintptr_t)inspect_chunk);
__builtin___clear_cache((char *)target, (char *)target + 16);
mprotect(start, (size_t)page, PROT_READ | PROT_EXEC);
LOG("Chunk diagnostic active; path/status only");
unsigned char *quit = (unsigned char *)(base + 0x3a1c0b4);
uint32_t quit_expected[] = {0xf81d0ff5, 0xa9014ff4, 0xa9027bfd, 0x910083fd};
if (memcmp(quit, quit_expected, 16)) { LOG("Quit prologue mismatch"); return 0; }
unsigned char *qt = mmap(0, (size_t)page, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (qt == MAP_FAILED) return 0;
memcpy(qt, quit, 16);
jump(qt + 16, (uintptr_t)quit + 16);
if (mprotect(qt, (size_t)page, PROT_READ | PROT_EXEC)) return 0;
__builtin___clear_cache((char *)qt, (char *)qt + 32);
original_quit = (void (*)(int, void *, void *))qt;
void *qp = (void *)((uintptr_t)quit & ~((uintptr_t)page - 1));
if (mprotect(qp, (size_t)page, PROT_READ | PROT_WRITE | PROT_EXEC)) return 0;
jump(quit, (uintptr_t)inspect_quit);
__builtin___clear_cache((char *)quit, (char *)quit + 16);
mprotect(qp, (size_t)page, PROT_READ | PROT_EXEC);
LOG("Quit diagnostic active; code/caller only, original behavior preserved");
object_class = dlsym(lib, "il2cpp_object_get_class");
object_new = dlsym(lib, "il2cpp_object_new");
string_new = dlsym(lib, "il2cpp_string_new");
method_named = dlsym(lib, "il2cpp_class_get_method_from_name");
invoke = dlsym(lib, "il2cpp_runtime_invoke");
write_barrier = dlsym(lib, "il2cpp_gc_wbarrier_set_field");
if (!object_class || !object_new || !string_new || !method_named || !invoke || !write_barrier)
return 0;
unsigned char *send = (unsigned char *)(base + 0x688e200);
uint32_t send_expected[] = {0xd10103ff, 0xa90157f6, 0xa9024ff4, 0xa9037bfd};
if (memcmp(send, send_expected, 16)) { LOG("Send prologue mismatch"); return 0; }
unsigned char *st = mmap(0, (size_t)page, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (st == MAP_FAILED) return 0;
memcpy(st, send, 16);
jump(st + 16, (uintptr_t)send + 16);
if (mprotect(st, (size_t)page, PROT_READ | PROT_EXEC)) return 0;
__builtin___clear_cache((char *)st, (char *)st + 32);
original_send = (void *(*)(void *, void *))st;
void *spage = (void *)((uintptr_t)send & ~((uintptr_t)page - 1));
if (mprotect(spage, (size_t)page, PROT_READ | PROT_WRITE | PROT_EXEC)) return 0;
jump(send, (uintptr_t)inspect_send);
__builtin___clear_cache((char *)send, (char *)send + 16);
mprotect(spage, (size_t)page, PROT_READ | PROT_EXEC);
LOG("Radicade image key-id compatibility active; verification unchanged");
LOG("Radicade fresh-connection experiment active");
unsigned char *iv = (unsigned char *)(base + 0x6deb934);
uint32_t iv_expected[] = {0xd10343ff, 0xf9004bf7, 0xa90a57f6, 0xa90b4ff4};
if (memcmp(iv, iv_expected, 16)) { LOG("Image verifier prologue mismatch"); return 0; }
unsigned char *it = mmap(0, (size_t)page, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (it == MAP_FAILED) return 0;
memcpy(it, iv, 16);
jump(it + 16, (uintptr_t)iv + 16);
if (mprotect(it, (size_t)page, PROT_READ | PROT_EXEC)) return 0;
__builtin___clear_cache((char *)it, (char *)it + 32);
original_image_verify = (int (*)(void *, void *))it;
void *ip = (void *)((uintptr_t)iv & ~((uintptr_t)page - 1));
if (mprotect(ip, (size_t)page, PROT_READ | PROT_WRITE | PROT_EXEC)) return 0;
jump(iv, (uintptr_t)verify_image);
__builtin___clear_cache((char *)iv, (char *)iv + 16);
mprotect(ip, (size_t)page, PROT_READ | PROT_EXEC);
LOG("Legacy image placeholder compatibility active; TLS unchanged");
return 0;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
(void)vm; (void)reserved;
pthread_t thread;
if (!pthread_create(&thread, 0, install, 0)) pthread_detach(thread);
return JNI_VERSION_1_6;
}