// Exact-build diagnostic. Logs response paths/status and chunked-read completion only. #include #include #include #include #include #include #include #include #include #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, ""); 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; }