mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
218 lines
11 KiB
C
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;
|
|
}
|