feat: DorkNet.Quest, a sideload patcher for the 2023 Quest client

Patches the Quest build of Rec Room 2023.03.21 (20230317 / 2128210, the
same appVersion as the PC client) to connect to a DorkNet server, using
one native hook library loaded via DT_NEEDED on libmain.so.

- host rewrite onto <svc>.<ServerHost> or a {service}-dev.example.com template
- Photon app ids and region injection (PhotonRegion must match PC)
- TLS / pinning / image-signature bypass, integrity callbacks neutralised
- no-Meta-account build; the server treats platform id 0/1/blank as a
  placeholder and keys those accounts by device id
- build.ps1 -Obb packages a DorkNet-<ver>.xapk with one-command
  installers for Windows and macOS/Linux; app is named via AppName
- README, docs/quest.md and NOTICE updated; credits RecFlare and
  And64InlineHook (MIT)

Tested on a Quest 2: all hooks arm, requests reach the dev server.
This commit is contained in:
Alexa
2026-10-15 20:29:32 +03:00
parent cebe7ae3ee
commit 975753deac
28 changed files with 3237 additions and 20 deletions
Binary file not shown.
+88
View File
@@ -0,0 +1,88 @@
// Compile-time configuration for libdorknet.so.
//
// scripts/gen_config_header.py turns dorknet-quest.cfg into config_generated.h
// (put on the include path by the build), baking the operator's values into the
// APK. Any key left out of the cfg falls back to the built-in default below.
// The same cfg can also be pushed to
// /sdcard/Android/data/<package>/files/dorknet-quest.cfg
// to override at runtime without a rebuild (dn_load_runtime_config).
#ifndef DORKNET_QUEST_CONFIG_H
#define DORKNET_QUEST_CONFIG_H
#include <stdbool.h>
#include <stddef.h>
#if defined(__has_include)
# if __has_include("config_generated.h")
# include "config_generated.h"
# endif
#endif
// The apex the client's *.rec.net hosts are rewritten onto. "api.rec.net"
// becomes "api.<this>", "auth.rec.net" -> "auth.<this>", etc. No default: a
// build with no server configured would do nothing useful.
#ifndef DN_SERVER_HOST
# define DN_SERVER_HOST ""
#endif
// Empty = keep the client's own (Rec Room's) Meta app id, which selects the
// no-Meta-account build (platform entitlement/login are steered past).
#ifndef DN_OCULUS_APPID
# define DN_OCULUS_APPID ""
#endif
// Photon app ids: per-deployment, no default. Empty leaves the client's own
// (dead) value in place, so multiplayer will not connect until set.
#ifndef DN_PHOTON_REALTIME
# define DN_PHOTON_REALTIME ""
#endif
#ifndef DN_PHOTON_VOICE
# define DN_PHOTON_VOICE ""
#endif
#ifndef DN_PHOTON_CHAT
# define DN_PHOTON_CHAT ""
#endif
#ifndef DN_PHOTON_SERVER
# define DN_PHOTON_SERVER "" // "" = Photon Cloud
#endif
#ifndef DN_PHOTON_PORT
# define DN_PHOTON_PORT 0
#endif
// Photon Cloud fixed region. Must match the PC clients' region (ClientMod
// PhotonCloudRegion), or Quest and PC players land on different masters.
#ifndef DN_PHOTON_REGION
# define DN_PHOTON_REGION "us"
#endif
#ifndef DN_TLS_TRUST_BYPASS
# define DN_TLS_TRUST_BYPASS true // accept a self-hosted TLS chain + skip pinning
#endif
#ifndef DN_ACCEPT_UNSIGNED_IMAGES
# define DN_ACCEPT_UNSIGNED_IMAGES true
#endif
#ifndef DN_LOG_HTTP
# define DN_LOG_HTTP true
#endif
#ifndef DN_DEBUG_HTTP
# define DN_DEBUG_HTTP false // also log request/response bodies (incl. tokens!)
#endif
#ifndef DN_LOG_BODY_MAX
# define DN_LOG_BODY_MAX 10000
#endif
#ifndef DN_RUNTIME_CFG_NAME
# define DN_RUNTIME_CFG_NAME "dorknet-quest.cfg"
#endif
// Package the runtime cfg is read from; the build's gen step overrides this.
#ifndef DN_PACKAGE
# define DN_PACKAGE "net.dorknet.RecRoom"
#endif
// Apply /sdcard/Android/data/<DN_PACKAGE>/files/dorknet-quest.cfg over the
// baked defaults. Each out pointer already holds the compiled-in value.
void dn_load_runtime_config(char *server_host, size_t server_host_cap,
char *oculus_appid, size_t oculus_appid_cap,
char *photon_realtime, char *photon_voice, char *photon_chat,
char *photon_server, size_t photon_server_cap, int *photon_port,
char *photon_region, size_t photon_region_cap,
bool *tls_bypass, bool *accept_images,
bool *log_http, bool *debug_http, int *body_max);
#endif // DORKNET_QUEST_CONFIG_H
+613
View File
@@ -0,0 +1,613 @@
// DorkNet.Quest native hook library (libdorknet.so).
//
// Loaded via a DT_NEEDED entry added to libmain.so, so it runs before Unity.
// It waits for libil2cpp.so, then installs arm64 inline hooks (And64InlineHook)
// at RVAs resolved from the build's dump.cs by scripts/resolve_targets.py and
// baked into targets_generated.h at compile time. It is the Quest counterpart
// of DorkNet.ClientMod (the PC MelonLoader mod) and does the same jobs:
//
// * rewrite <service>.rec.net hosts to <service>.<ServerHost> (the ns
// response then supplies every other service URL) -- like ClientMod's
// UriPatches,
// * inject the operator's Photon app ids -- like the PC byte patch
// tools/patch-client.ps1,
// * optionally set the operator's Meta (Oculus) app id,
// * accept the self-hosted TLS chain and the image signature the operator's
// server produces -- like ClientMod's TlsPatches / the PC ImageSigning,
// * neutralise the integrity callbacks that would reject a re-signed,
// hook-loaded client -- like ClientMod's AntiTamper/ToxMod/FileHash patches,
// * when no Meta app id is set, steer the Oculus platform path past its
// entitlement/login checks to the client's own credential screen.
//
// Every RVA is checked against a 20-byte FNV-1a fingerprint from the target
// table before it is hooked, so the wrong build is refused rather than
// miswritten. Obfuscated Assembly-CSharp names are re-randomised per release
// but shared with the PC build of the same release; this build (20230317 /
// versionCode 2128210) shares its names with the PC 2023-03-21 decompile.
//
// No Rec Room code or assets are included. Third-party inline hooker in
// src/third_party/And64InlineHook (MIT, (c) Rprop).
#include <dlfcn.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "third_party/And64InlineHook/And64InlineHook.hpp"
#include "config.h"
#include "targets_generated.h"
extern "C" int __android_log_print(int prio, const char *tag, const char *fmt, ...);
#define LOGI(...) __android_log_print(4, "DORKNET", __VA_ARGS__)
#define LOGE(...) __android_log_print(6, "DORKNET", __VA_ARGS__)
// --------------------------------------------------------------- config -----
static const char *kOfficialSuffix = ".rec.net";
static char g_server_host[160] = DN_SERVER_HOST; // <service>.rec.net -> <service>.<this>
static char g_oculus_appid[64] = DN_OCULUS_APPID; // "" = keep the client's own
static char g_photon_realtime[64] = DN_PHOTON_REALTIME;
static char g_photon_voice[64] = DN_PHOTON_VOICE;
static char g_photon_chat[64] = DN_PHOTON_CHAT;
static char g_photon_server[128] = DN_PHOTON_SERVER; // "" = Photon Cloud
static int g_photon_port = DN_PHOTON_PORT;
static char g_photon_region[16] = DN_PHOTON_REGION;
static bool g_tls_bypass = DN_TLS_TRUST_BYPASS;
static bool g_accept_images = DN_ACCEPT_UNSIGNED_IMAGES;
static bool g_log_http = DN_LOG_HTTP;
static bool g_debug_http = DN_DEBUG_HTTP;
static int g_body_max = DN_LOG_BODY_MAX;
// AppSettings field offsets (dump.cs; unchanged across the 2023 builds).
static const size_t kAppIdRealtimeOff = 0x10;
static const size_t kAppIdChatOff = 0x18;
static const size_t kAppIdVoiceOff = 0x20;
static const size_t kUseNameServerOff = 0x30; // bool
static const size_t kFixedRegionOff = 0x38;
static const size_t kServerOff = 0x48;
static const size_t kPortOff = 0x50; // int
static const size_t kProtocolOff = 0x60; // int (Udp = 0)
// HTTPRequest / HTTPResponse field offsets (dump.cs).
static const size_t kUriFieldOffset = 0x10; // HTTPRequest.<Uri>
static const size_t kReqMethodOffset = 0x18; // HTTPRequest.<MethodType> (byte)
static const size_t kReqResponseOffset = 0x70; // HTTPRequest.<Response>
static const size_t kReqExceptionOffset = 0x80; // HTTPRequest.<Exception>
static const size_t kReqStateOffset = 0xB4; // HTTPRequest.<State> (int)
static const size_t kRespStatusOffset = 0x18; // HTTPResponse.<StatusCode> (int)
static const size_t kRespDataOffset = 0x48; // HTTPResponse.<Data> (byte[])
// Il2CppArray data layout on 64-bit.
static const size_t kArrayLengthOff = 0x18;
static const size_t kArrayDataOff = 0x20;
// Oculus platform message types (dump.cs ONGCNIAMKCG.DJCEHCFKHME).
static const unsigned kMsgEntitlementGetIsViewerEntitled = 409688241u;
static const unsigned kMsgUserGetUserProof = 578880643u;
static const char *kPlaceholderNonce = "dorknet-no-platform-account";
static const unsigned long long kNoPlatformUserId = 1ULL;
static const size_t kLogChunk = 1500;
static const uint32_t kUnityTlsSuccess = 0; // UNITYTLS_X509VERIFY_SUCCESS / UNITYTLS_SUCCESS
static const size_t kErrorStateCodeOff = 0x4;
// --------------------------------------------------------------- il2cpp ------
typedef void *(*il2cpp_object_get_class_t)(void *obj);
typedef void *(*il2cpp_class_get_method_from_name_t)(void *klass, const char *name, int argc);
typedef void *(*il2cpp_object_new_t)(void *klass);
typedef void *(*il2cpp_string_new_t)(const char *str);
typedef void *(*il2cpp_runtime_invoke_t)(void *method, void *obj, void **params, void **exc);
static il2cpp_object_get_class_t il2cpp_object_get_class;
static il2cpp_class_get_method_from_name_t il2cpp_class_get_method_from_name;
static il2cpp_object_new_t il2cpp_object_new;
static il2cpp_string_new_t il2cpp_string_new;
static il2cpp_runtime_invoke_t il2cpp_runtime_invoke;
static uintptr_t g_il2cpp_base;
// Il2CppString: [klass][monitor][int32 length][char16 chars...]
static void str_to_utf8(void *s, char *out, size_t cap) {
out[0] = '\0';
if (!s || cap == 0) return;
int32_t len = *(int32_t *)((char *)s + 0x10);
if (len < 0) return;
const uint16_t *chars = (const uint16_t *)((char *)s + 0x14);
size_t i = 0;
for (; i < (size_t)len && i + 1 < cap; i++)
out[i] = chars[i] < 0x80 ? (char)chars[i] : '?';
out[i] = '\0';
}
static const unsigned char *invoke_bytes(void *obj, const char *method, size_t *len) {
*len = 0;
if (!obj) return nullptr;
void *cls = il2cpp_object_get_class(obj);
if (!cls) return nullptr;
void *m = il2cpp_class_get_method_from_name(cls, method, 0);
if (!m) return nullptr;
void *exc = nullptr;
void *arr = il2cpp_runtime_invoke(m, obj, nullptr, &exc);
if (!arr || exc) return nullptr;
*len = (size_t) * (uint64_t *)((char *)arr + kArrayLengthOff);
return (const unsigned char *)((char *)arr + kArrayDataOff);
}
static void invoke_str(void *obj, const char *method, char *out, size_t cap) {
out[0] = '\0';
if (!obj) return;
void *cls = il2cpp_object_get_class(obj);
if (!cls) return;
void *m = il2cpp_class_get_method_from_name(cls, method, 0);
if (!m) return;
void *exc = nullptr;
void *s = il2cpp_runtime_invoke(m, obj, nullptr, &exc);
if (s && !exc) str_to_utf8(s, out, cap);
}
// --------------------------------------------------------------- logging ----
static void log_chunked(const char *prefix, const char *text) {
size_t n = strlen(text);
if (n <= kLogChunk) { LOGI("%s%s", prefix, text); return; }
char buf[kLogChunk + 1];
for (size_t off = 0, part = 1; off < n; off += kLogChunk, part++) {
size_t take = n - off < kLogChunk ? n - off : kLogChunk;
memcpy(buf, text + off, take);
buf[take] = '\0';
LOGI("%s[%zu] %s", prefix, part, buf);
}
}
static bool looks_binary(const unsigned char *d, size_t n) {
if (n == 0) return false;
size_t sample = n < 4096 ? n : 4096, nontext = 0;
for (size_t i = 0; i < sample; i++) {
unsigned char b = d[i];
if (b == 0) return true;
if (b < 0x20 && b != 0x09 && b != 0x0A && b != 0x0D) nontext++;
}
return nontext * 100 / sample > 10;
}
static void format_body(const unsigned char *d, size_t n, char *out, size_t cap) {
if (!d || n == 0) { snprintf(out, cap, "<empty>"); return; }
size_t limit = (size_t)g_body_max;
if (limit > cap - 64) limit = cap - 64;
if (!looks_binary(d, n)) {
size_t take = n < limit ? n : limit;
memcpy(out, d, take);
out[take] = '\0';
if (take < n) snprintf(out + take, cap - take, "... <truncated %zu bytes>", n - take);
return;
}
int w = snprintf(out, cap, "<binary %zu bytes>", n);
out[w < 0 ? 0 : (size_t)w] = '\0';
}
// --------------------------------------------------------------- hooks -------
static void *(*orig_SendRequest)(void *request);
static void *hk_SendRequest(void *request) {
if (!request) return orig_SendRequest(request);
void *uri = *(void **)((char *)request + kUriFieldOffset);
if (!uri) return orig_SendRequest(request);
void *uri_class = il2cpp_object_get_class(uri);
if (!uri_class) return orig_SendRequest(request);
void *m_abs = il2cpp_class_get_method_from_name(uri_class, "get_AbsoluteUri", 0);
if (!m_abs) return orig_SendRequest(request);
void *exc = nullptr;
void *s = il2cpp_runtime_invoke(m_abs, uri, nullptr, &exc);
if (!s || exc) return orig_SendRequest(request);
char url[2048];
str_to_utf8(s, url, sizeof(url));
if (g_log_http) {
int method = *(unsigned char *)((char *)request + kReqMethodOffset);
static const char *methods[] = {"GET", "HEAD", "POST", "PUT", "DELETE",
"PATCH", "MERGE", "OPTIONS", "CONNECT"};
const char *mname = (method >= 0 && method < 9) ? methods[method] : "?";
LOGI("[http] %s %s", mname, url);
if (g_debug_http) {
size_t blen = 0;
const unsigned char *body = invoke_bytes(request, "GetEntityBody", &blen);
if (body && blen) {
size_t cap = (size_t)g_body_max + 128;
char *text = (char *)malloc(cap);
if (text) { format_body(body, blen, text, cap); log_chunked("[http] body=", text); free(text); }
}
}
}
// Rewrite any "<service>.rec.net" host to "<service>.<ServerHost>".
const char *scheme_end = strstr(url, "://");
if (!scheme_end) return orig_SendRequest(request);
const char *host = scheme_end + 3;
const char *host_end = host;
while (*host_end && *host_end != '/' && *host_end != ':' && *host_end != '?') host_end++;
size_t host_len = (size_t)(host_end - host);
size_t suf = strlen(kOfficialSuffix);
if (host_len <= suf || strncasecmp(host_end - suf, kOfficialSuffix, suf) != 0)
return orig_SendRequest(request);
char newurl[1024];
size_t head = (size_t)(host - url); // "https://"
size_t service_len = host_len - suf; // "api", "auth", ...
// ServerHost is either a plain apex ("example.com" -> "api.example.com") or a
// template with "{service}" for servers on DorkNet's hyphen host style
// ("{service}-dev.example.com" -> "ns-dev.example.com").
static const char kServiceToken[] = "{service}";
const char *token = strstr(g_server_host, kServiceToken);
size_t new_host_len = token ? strlen(g_server_host) - (sizeof(kServiceToken) - 1) + service_len
: service_len + 1 + strlen(g_server_host);
if (head + new_host_len + strlen(host_end) + 1 >= sizeof(newurl))
return orig_SendRequest(request);
memcpy(newurl, url, head);
size_t p = head;
if (token) {
size_t before = (size_t)(token - g_server_host);
memcpy(newurl + p, g_server_host, before);
p += before;
memcpy(newurl + p, host, service_len);
p += service_len;
strcpy(newurl + p, token + sizeof(kServiceToken) - 1);
} else {
memcpy(newurl + p, host, service_len);
p += service_len;
newurl[p++] = '.';
strcpy(newurl + p, g_server_host);
}
strcat(newurl + head, host_end);
void *new_str = il2cpp_string_new(newurl);
void *new_uri = il2cpp_object_new(uri_class);
void *ctor = il2cpp_class_get_method_from_name(uri_class, ".ctor", 1);
if (!new_str || !new_uri || !ctor) return orig_SendRequest(request);
void *args[1] = {new_str};
exc = nullptr;
il2cpp_runtime_invoke(ctor, new_uri, args, &exc);
if (exc) return orig_SendRequest(request);
*(void **)((char *)request + kUriFieldOffset) = new_uri;
LOGI("intercepted %s -> %s", url, newurl);
return orig_SendRequest(request);
}
static void (*orig_CallCallback)(void *thiz, void *method);
static void hk_CallCallback(void *thiz, void *method) {
if (!thiz || !g_log_http) { orig_CallCallback(thiz, method); return; }
char url[2048];
invoke_str(*(void **)((char *)thiz + kUriFieldOffset), "get_AbsoluteUri", url, sizeof(url));
int state = *(int *)((char *)thiz + kReqStateOffset);
void *resp = *(void **)((char *)thiz + kReqResponseOffset);
if (!resp) {
void *ex = *(void **)((char *)thiz + kReqExceptionOffset);
if (ex) {
char msg[900];
invoke_str(ex, "ToString", msg, sizeof(msg));
LOGI("[http<-] NO-RESPONSE state=%d %s | %s", state, url, msg);
} else {
LOGI("[http<-] NO-RESPONSE state=%d %s | (no exception)", state, url);
}
} else {
int status = *(int *)((char *)resp + kRespStatusOffset);
LOGI("[http<-] %d %s", status, url);
if (g_debug_http || status < 200 || status >= 300) {
size_t blen = 0;
const unsigned char *data = invoke_bytes(resp, "get_Data", &blen);
size_t cap = (size_t)g_body_max + 128;
char *text = (char *)malloc(cap);
if (text) { format_body(data, blen, text, cap); log_chunked("[http<-] body=", text); free(text); }
}
}
orig_CallCallback(thiz, method);
}
static void *hk_get_AppID(void *method) {
(void)method;
return il2cpp_string_new(g_oculus_appid);
}
static void *(*orig_Photon)(void *thiz, void *method);
static void *hk_Photon(void *thiz, void *method) {
void *s = orig_Photon(thiz, method);
if (s) {
if (g_photon_realtime[0]) *(void **)((char *)s + kAppIdRealtimeOff) = il2cpp_string_new(g_photon_realtime);
if (g_photon_chat[0]) *(void **)((char *)s + kAppIdChatOff) = il2cpp_string_new(g_photon_chat);
if (g_photon_voice[0]) *(void **)((char *)s + kAppIdVoiceOff) = il2cpp_string_new(g_photon_voice);
*(unsigned char *)((char *)s + kUseNameServerOff) = 1;
*(void **)((char *)s + kFixedRegionOff) = il2cpp_string_new(g_photon_region);
*(int *)((char *)s + kProtocolOff) = 0;
if (g_photon_server[0]) {
*(void **)((char *)s + kServerOff) = il2cpp_string_new(g_photon_server);
*(int *)((char *)s + kPortOff) = g_photon_port ? g_photon_port : 4533;
}
static int logged = 0;
if (logged < 2) {
logged++;
LOGI("photon appsettings: RT=%s Voice=%s Chat=%s region=%s server=%s port=%d",
g_photon_realtime[0] ? g_photon_realtime : "(unset)",
g_photon_voice[0] ? g_photon_voice : "(unset)",
g_photon_chat[0] ? g_photon_chat : "(unset)", g_photon_region,
g_photon_server[0] ? g_photon_server : "(cloud)", g_photon_port);
if (!g_photon_realtime[0])
LOGE("photon app ids are not configured; multiplayer will try Rec Room's dead apps");
}
}
return s;
}
static uint32_t (*orig_UnityTls)(void *thiz, uint64_t chain, void *es, void *method);
static uint32_t hk_UnityTls(void *thiz, uint64_t chain, void *es, void *method) {
uint32_t r = orig_UnityTls(thiz, chain, es, method);
if (r != kUnityTlsSuccess) {
static int logged = 0;
if (logged < 3) { logged++; LOGI("unitytls verify: forcing 0x%x -> SUCCESS", r); }
if (es) *(uint32_t *)((char *)es + kErrorStateCodeOff) = kUnityTlsSuccess;
}
return kUnityTlsSuccess;
}
// void NotifyServerCertificate(Certificate): a no-op accepts the chain.
static void hk_NotifyServerCertificate(void *thiz, void *cert, void *method) {
(void)thiz; (void)cert; (void)method;
}
// bool pin verifiers: accept unconditionally (replace-only, so no mismatch
// report is ever built and the client never quits).
static bool hk_pin_true4(void *a, void *b, void *c, void *d) { (void)a;(void)b;(void)c;(void)d; return true; }
static bool hk_pin_true4b(void *a, void *b, void *c, void *d) { (void)a;(void)b;(void)c;(void)d; return true; }
static bool hk_image_true(void *data, void *sig, void *method) {
(void)data; (void)sig; (void)method;
return true;
}
// void callbacks the client runs on an integrity rejection -- no-op them so a
// re-signed, hook-loaded sideload is not warned/quit (as DorkNet.ClientMod does
// on PC). The quit funnels and the report are left to run but are logged.
static void hk_void0(void *thiz, void *method) { (void)thiz; (void)method; }
static void hk_void1(void *thiz, void *arg, void *method) { (void)thiz; (void)arg; (void)method; }
static bool hk_toxmod_false(void *method) { (void)method; return false; }
static const char *cheat_reason(int r) {
switch (r) {
case 0: return "Obscured"; case 1: return "Time"; case 2: return "Inject";
case 3: return "GiftCount"; case 4: return "Engine"; case 5: return "UnknownDll";
case 6: return "ImageSignature"; case 7: return "AvatarHack";
case 100: return "NetworkCertificatePublicKey"; case 101: return "NetworkCertificateIssuer";
case 102: return "NetworkCertificateMissing"; case 103: return "NetworkCertificateMismatch";
case 150: return "AutosaveChecksumMismatch"; case 151: return "AutosaveSubRoomIdMismatch";
case 152: return "AutosaveChecksumException"; case 200: return "Photon_MissingHash";
case 201: return "Photon_CorruptHash"; case 203: return "Photon_DifferentHash";
case 300: return "AppData_Runtime_LengthMismatch"; case 301: return "AppData_Runtime_LastWriteTimeMismatch";
case 302: return "AppData_Runtime_FileModified"; case 310: return "AppData_Boot_InvalidSignature";
case 311: return "AppData_Boot_UnableToVerifySignatures"; case 320: return "Config_MissingHash";
case 321: return "Config_DifferentHash"; case 400: return "Photon_InstantiateTool";
case 500: return "Memory_Hash_Mismatch"; case 600: return "Driver_Invalid_Signature";
default: return "?";
}
}
static void log_caller(const char *what, void *ret) {
uintptr_t a = (uintptr_t)ret;
if (g_il2cpp_base && a > g_il2cpp_base)
LOGI("[caller] %s <- libil2cpp rva 0x%lx", what, (unsigned long)(a - g_il2cpp_base));
else
LOGI("[caller] %s <- %p", what, ret);
}
static void *(*orig_CheatReport)(int reason, void *msg, uint64_t code, void *method);
static void *hk_CheatReport(int reason, void *msg, uint64_t code, void *method) {
char text[512];
str_to_utf8(msg, text, sizeof(text));
LOGI("[integrity] report reason=%d (%s) msg=\"%s\"", reason, cheat_reason(reason), text);
log_caller("report", __builtin_return_address(0));
return orig_CheatReport(reason, msg, code, method);
}
static void (*orig_FatalQuit)(int code, void *reason, void *method);
static void hk_FatalQuit(int code, void *reason, void *method) {
char msg[512];
str_to_utf8(reason, msg, sizeof(msg));
LOGI("[exit] FatalApplicationQuit(%d, \"%s\")", code, msg);
log_caller("fatal quit", __builtin_return_address(0));
orig_FatalQuit(code, reason, method);
}
static void (*orig_TryQuit)(int code, void *method);
static void hk_TryQuit(int code, void *method) {
LOGI("[exit] TryApplicationQuit(%d)", code);
log_caller("try quit", __builtin_return_address(0));
orig_TryQuit(code, method);
}
// ------------------------------------------------------- oculus platform -----
typedef bool (*ovr_IsError_t)(void *m);
typedef unsigned (*ovr_GetType_t)(void *m);
static ovr_IsError_t orig_ovr_IsError;
static ovr_GetType_t ovr_GetType;
static bool hk_ovr_IsError(void *m) {
bool real = orig_ovr_IsError(m);
if (!real || !ovr_GetType) return real;
unsigned t = ovr_GetType(m);
if (t == kMsgEntitlementGetIsViewerEntitled || t == kMsgUserGetUserProof) return false;
return real;
}
static void *(*orig_ovr_GetUserProof)(void *m);
static void *hk_ovr_GetUserProof(void *m) {
void *p = orig_ovr_GetUserProof ? orig_ovr_GetUserProof(m) : nullptr;
return p ? p : (void *)0x1;
}
static const char *(*orig_ovr_GetNonce)(void *proof);
static const char *hk_ovr_GetNonce(void *proof) {
if (proof && proof != (void *)0x1 && orig_ovr_GetNonce) {
const char *n = orig_ovr_GetNonce(proof);
if (n && n[0]) return n;
}
return kPlaceholderNonce;
}
static unsigned long long (*orig_ovr_GetLoggedInUserID)();
static unsigned long long hk_ovr_GetLoggedInUserID() {
unsigned long long real = orig_ovr_GetLoggedInUserID ? orig_ovr_GetLoggedInUserID() : 0;
return real ? real : kNoPlatformUserId;
}
static unsigned long long (*orig_ovr_User_GetID)(void *u);
static unsigned long long hk_ovr_User_GetID(void *u) {
unsigned long long real = orig_ovr_User_GetID ? orig_ovr_User_GetID(u) : 0;
return real ? real : kNoPlatformUserId;
}
// ----------------------------------------------------------------- init ------
static uintptr_t module_base(const char *name) {
FILE *f = fopen("/proc/self/maps", "r");
if (!f) return 0;
char line[512];
uintptr_t base = 0;
while (fgets(line, sizeof(line), f))
if (strstr(line, name)) { base = (uintptr_t)strtoull(line, nullptr, 16); break; }
fclose(f);
return base;
}
static uint64_t fnv1a(const unsigned char *d, size_t n) {
uint64_t h = 0xCBF29CE484222325ULL;
for (size_t i = 0; i < n; i++) { h ^= d[i]; h *= 0x100000001B3ULL; }
return h;
}
// Arm a target by id: verify the 20-byte fingerprint, then install the hook.
static bool arm(const DnTarget *t, void *hook, void **orig) {
void *addr = (void *)(g_il2cpp_base + t->rva);
uint64_t fp = fnv1a((const unsigned char *)addr, DN_HOOK_BYTES);
if (fp != t->fingerprint) {
LOGE("REFUSING %s: rva 0x%lx fingerprint 0x%llx != expected 0x%llx (wrong build?)",
t->id, (unsigned long)t->rva, (unsigned long long)fp,
(unsigned long long)t->fingerprint);
return false;
}
void *dummy = nullptr;
A64HookFunction(addr, hook, orig ? orig : &dummy);
LOGI("armed %s @ rva 0x%lx", t->id, (unsigned long)t->rva);
return true;
}
static bool resolve_il2cpp(void *h) {
#define SYM(n) n = (n##_t)dlsym(h, #n); if (!n) { LOGE("dlsym " #n " failed"); return false; }
SYM(il2cpp_object_get_class)
SYM(il2cpp_class_get_method_from_name)
SYM(il2cpp_object_new)
SYM(il2cpp_string_new)
SYM(il2cpp_runtime_invoke)
#undef SYM
return true;
}
static void arm_oculus_platform_bypass() {
void *ovr = nullptr;
for (int i = 0; i < 600; i++) {
ovr = dlopen("libovrplatformloader.so", RTLD_NOLOAD | RTLD_NOW);
if (ovr) break;
usleep(100 * 1000);
}
if (!ovr) { LOGE("libovrplatformloader.so never appeared; platform bypass not armed"); return; }
ovr_GetType = (ovr_GetType_t)dlsym(ovr, "ovr_Message_GetType");
void *isErr = dlsym(ovr, "ovr_Message_IsError");
if (ovr_GetType && isErr) {
A64HookFunction(isErr, (void *)hk_ovr_IsError, (void **)&orig_ovr_IsError);
LOGI("platform entitlement/proof bypass armed (no Meta app id configured)");
} else {
LOGE("ovr symbols missing; platform bypass not armed");
return;
}
void *a;
if ((a = dlsym(ovr, "ovr_GetLoggedInUserID")))
A64HookFunction(a, (void *)hk_ovr_GetLoggedInUserID, (void **)&orig_ovr_GetLoggedInUserID);
if ((a = dlsym(ovr, "ovr_User_GetID")))
A64HookFunction(a, (void *)hk_ovr_User_GetID, (void **)&orig_ovr_User_GetID);
if ((a = dlsym(ovr, "ovr_Message_GetUserProof")))
A64HookFunction(a, (void *)hk_ovr_GetUserProof, (void **)&orig_ovr_GetUserProof);
if ((a = dlsym(ovr, "ovr_UserProof_GetNonce")))
A64HookFunction(a, (void *)hk_ovr_GetNonce, (void **)&orig_ovr_GetNonce);
}
static void *init_thread(void *) {
void *h = nullptr;
for (int i = 0; i < 600; i++) {
h = dlopen("libil2cpp.so", RTLD_NOLOAD | RTLD_NOW);
if (h) break;
usleep(100 * 1000);
}
if (!h) { LOGE("libil2cpp.so never appeared; aborting"); return nullptr; }
g_il2cpp_base = module_base("libil2cpp.so");
if (!g_il2cpp_base) { LOGE("could not find libil2cpp.so base"); return nullptr; }
if (!resolve_il2cpp(h)) return nullptr;
dn_load_runtime_config(g_server_host, sizeof(g_server_host), g_oculus_appid, sizeof(g_oculus_appid),
g_photon_realtime, g_photon_voice, g_photon_chat,
g_photon_server, sizeof(g_photon_server), &g_photon_port,
g_photon_region, sizeof(g_photon_region),
&g_tls_bypass, &g_accept_images, &g_log_http, &g_debug_http, &g_body_max);
LOGI("DorkNet.Quest init: build %s/%s il2cpp @ 0x%lx; host-rewrite -> *.%s",
DN_TARGET_VERSION_NAME, DN_TARGET_VERSION_CODE, (unsigned long)g_il2cpp_base, g_server_host);
arm(&dn_targets.http_send_request, (void *)hk_SendRequest, (void **)&orig_SendRequest);
if (g_log_http)
arm(&dn_targets.http_call_callback, (void *)hk_CallCallback, (void **)&orig_CallCallback);
arm(&dn_targets.photon_app_settings, (void *)hk_Photon, (void **)&orig_Photon);
if (g_oculus_appid[0]) {
arm(&dn_targets.oculus_get_app_id, (void *)hk_get_AppID, nullptr);
arm(&dn_targets.oculus_get_mobile_app_id, (void *)hk_get_AppID, nullptr);
LOGI("oculus app id override: %s", g_oculus_appid);
}
if (g_tls_bypass) {
arm(&dn_targets.tls_unity_verify, (void *)hk_UnityTls, (void **)&orig_UnityTls);
arm(&dn_targets.tls_bc_notify_server_certificate, (void *)hk_NotifyServerCertificate, nullptr);
arm(&dn_targets.pin_nameserver, (void *)hk_pin_true4, nullptr);
arm(&dn_targets.pin_https, (void *)hk_pin_true4b, nullptr);
arm(&dn_targets.pin_bc_verifier, (void *)hk_pin_true4, nullptr);
}
if (g_accept_images)
arm(&dn_targets.image_signature_verify, (void *)hk_image_true, nullptr);
// Integrity callbacks that would reject a re-signed, hook-loaded client.
arm(&dn_targets.hile_warning_callback, (void *)hk_void1, nullptr);
arm(&dn_targets.unknown_dll_callback, (void *)hk_void0, nullptr);
arm(&dn_targets.file_hash_callback, (void *)hk_void1, nullptr);
arm(&dn_targets.toxmod_can_initialize, (void *)hk_toxmod_false, nullptr);
// Log-only diagnostics.
arm(&dn_targets.cheat_report, (void *)hk_CheatReport, (void **)&orig_CheatReport);
arm(&dn_targets.fatal_application_quit, (void *)hk_FatalQuit, (void **)&orig_FatalQuit);
arm(&dn_targets.try_application_quit, (void *)hk_TryQuit, (void **)&orig_TryQuit);
if (!g_oculus_appid[0]) arm_oculus_platform_bypass();
LOGI("DorkNet.Quest init complete");
return nullptr;
}
__attribute__((constructor)) static void on_load() {
LOGI("DorkNet.Quest hooks loaded (build " __DATE__ " " __TIME__ ")");
pthread_t t;
pthread_create(&t, nullptr, init_thread, nullptr);
pthread_detach(t);
}
@@ -0,0 +1,596 @@
/*
* @date : 2018/04/18
* @author : Rprop (r_prop@outlook.com)
* https://github.com/Rprop/And64InlineHook
*/
/*
MIT License
Copyright (c) 2018 Rprop (r_prop@outlook.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <android/log.h>
#if defined(__aarch64__)
#include "And64InlineHook.hpp"
#define A64_MAX_INSTRUCTIONS 5
#define A64_MAX_REFERENCES (A64_MAX_INSTRUCTIONS * 2)
#define A64_NOP 0xd503201fu
#define A64_JNIEXPORT __attribute__((visibility("default")))
#define A64_LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "A64_HOOK", __VA_ARGS__))
#ifndef NDEBUG
# define A64_LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "A64_HOOK", __VA_ARGS__))
#else
# define A64_LOGI(...) ((void)0)
#endif // NDEBUG
typedef uint32_t *__restrict *__restrict instruction;
struct context
{
struct fix_info
{
uint32_t *bp;
uint32_t ls; // left-shift counts
uint32_t ad; // & operand
};
struct insns_info
{
union
{
uint64_t insu;
int64_t ins;
void *insp;
};
fix_info fmap[A64_MAX_REFERENCES];
};
int64_t basep;
int64_t endp;
insns_info dat[A64_MAX_INSTRUCTIONS];
public:
inline bool is_in_fixing_range(const int64_t absolute_addr) {
return absolute_addr >= this->basep && absolute_addr < this->endp;
}
inline intptr_t get_ref_ins_index(const int64_t absolute_addr) {
return static_cast<intptr_t>((absolute_addr - this->basep) / sizeof(uint32_t));
}
inline intptr_t get_and_set_current_index(uint32_t *__restrict inp, uint32_t *__restrict outp) {
intptr_t current_idx = this->get_ref_ins_index(reinterpret_cast<int64_t>(inp));
this->dat[current_idx].insp = outp;
return current_idx;
}
inline void reset_current_ins(const intptr_t idx, uint32_t *__restrict outp) {
this->dat[idx].insp = outp;
}
void insert_fix_map(const intptr_t idx, uint32_t *bp, uint32_t ls = 0u, uint32_t ad = 0xffffffffu) {
for (auto &f : this->dat[idx].fmap) {
if (f.bp == NULL) {
f.bp = bp;
f.ls = ls;
f.ad = ad;
return;
} //if
}
// What? GGing..
}
void process_fix_map(const intptr_t idx) {
for (auto &f : this->dat[idx].fmap) {
if (f.bp == NULL) break;
*(f.bp) = *(f.bp) | (((int32_t(this->dat[idx].ins - reinterpret_cast<int64_t>(f.bp)) >> 2) << f.ls) & f.ad);
f.bp = NULL;
}
}
};
//-------------------------------------------------------------------------
#define __intval(p) reinterpret_cast<intptr_t>(p)
#define __uintval(p) reinterpret_cast<uintptr_t>(p)
#define __ptr(p) reinterpret_cast<void *>(p)
#define __page_size 4096
#define __page_align(n) __align_up(static_cast<uintptr_t>(n), __page_size)
#define __ptr_align(x) __ptr(__align_down(reinterpret_cast<uintptr_t>(x), __page_size))
#define __align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1))
#define __align_down(x, n) ((x) & -(n))
#define __countof(x) static_cast<intptr_t>(sizeof(x) / sizeof((x)[0])) // must be signed
#define __atomic_increase(p) __sync_add_and_fetch(p, 1)
#define __sync_cmpswap(p, v, n) __sync_bool_compare_and_swap(p, v, n)
#define __predict_true(exp) __builtin_expect((exp) != 0, 1)
#define __flush_cache(c, n) __builtin___clear_cache(reinterpret_cast<char *>(c), reinterpret_cast<char *>(c) + n)
#define __make_rwx(p, n) ::mprotect(__ptr_align(p), \
__page_align(__uintval(p) + n) != __page_align(__uintval(p)) ? __page_align(n) + __page_size : __page_align(n), \
PROT_READ | PROT_WRITE | PROT_EXEC)
//-------------------------------------------------------------------------
static bool __fix_branch_imm(instruction inpp, instruction outpp, context *ctxp)
{
static constexpr uint32_t mbits = 6u;
static constexpr uint32_t mask = 0xfc000000u; // 0b11111100000000000000000000000000
static constexpr uint32_t rmask = 0x03ffffffu; // 0b00000011111111111111111111111111
static constexpr uint32_t op_b = 0x14000000u; // "b" ADDR_PCREL26
static constexpr uint32_t op_bl = 0x94000000u; // "bl" ADDR_PCREL26
const uint32_t ins = *(*inpp);
const uint32_t opc = ins & mask;
switch (opc) {
case op_b:
case op_bl:
{
intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp);
int64_t absolute_addr = reinterpret_cast<int64_t>(*inpp) + (static_cast<int32_t>(ins << mbits) >> (mbits - 2u)); // sign-extended
int64_t new_pc_offset = static_cast<int64_t>(absolute_addr - reinterpret_cast<int64_t>(*outpp)) >> 2; // shifted
bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr);
// whether the branch should be converted to absolute jump
if (!special_fix_type && llabs(new_pc_offset) >= (rmask >> 1)) {
bool b_aligned = (reinterpret_cast<uint64_t>(*outpp + 2) & 7u) == 0u;
if (opc == op_b) {
if (b_aligned != true) {
(*outpp)[0] = A64_NOP;
ctxp->reset_current_ins(current_idx, ++(*outpp));
} //if
(*outpp)[0] = 0x58000051u; // LDR X17, #0x8
(*outpp)[1] = 0xd61f0220u; // BR X17
memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr));
*outpp += 4;
} else {
if (b_aligned == true) {
(*outpp)[0] = A64_NOP;
ctxp->reset_current_ins(current_idx, ++(*outpp));
} //if
(*outpp)[0] = 0x58000071u; // LDR X17, #12
(*outpp)[1] = 0x1000009eu; // ADR X30, #16
(*outpp)[2] = 0xd61f0220u; // BR X17
memcpy(*outpp + 3, &absolute_addr, sizeof(absolute_addr));
*outpp += 5;
} //if
} else {
if (special_fix_type) {
intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr);
if (ref_idx <= current_idx) {
new_pc_offset = static_cast<int64_t>(ctxp->dat[ref_idx].ins - reinterpret_cast<int64_t>(*outpp)) >> 2;
} else {
ctxp->insert_fix_map(ref_idx, *outpp, 0u, rmask);
new_pc_offset = 0;
} //if
} //if
(*outpp)[0] = opc | (new_pc_offset & ~mask);
++(*outpp);
} //if
++(*inpp);
return ctxp->process_fix_map(current_idx), true;
}
}
return false;
}
//-------------------------------------------------------------------------
static bool __fix_cond_comp_test_branch(instruction inpp, instruction outpp, context *ctxp)
{
static constexpr uint32_t lsb = 5u;
static constexpr uint32_t lmask01 = 0xff00001fu; // 0b11111111000000000000000000011111
static constexpr uint32_t mask0 = 0xff000010u; // 0b11111111000000000000000000010000
static constexpr uint32_t op_bc = 0x54000000u; // "b.c" ADDR_PCREL19
static constexpr uint32_t mask1 = 0x7f000000u; // 0b01111111000000000000000000000000
static constexpr uint32_t op_cbz = 0x34000000u; // "cbz" Rt, ADDR_PCREL19
static constexpr uint32_t op_cbnz = 0x35000000u; // "cbnz" Rt, ADDR_PCREL19
static constexpr uint32_t lmask2 = 0xfff8001fu; // 0b11111111111110000000000000011111
static constexpr uint32_t mask2 = 0x7f000000u; // 0b01111111000000000000000000000000
static constexpr uint32_t op_tbz = 0x36000000u; // 0b00110110000000000000000000000000 "tbz" Rt, BIT_NUM, ADDR_PCREL14
static constexpr uint32_t op_tbnz = 0x37000000u; // 0b00110111000000000000000000000000 "tbnz" Rt, BIT_NUM, ADDR_PCREL14
const uint32_t ins = *(*inpp);
uint32_t lmask = lmask01;
if ((ins & mask0) != op_bc) {
uint32_t opc = ins & mask1;
if (opc != op_cbz && opc != op_cbnz) {
opc = ins & mask2;
if (opc != op_tbz && opc != op_tbnz) {
return false;
} //if
lmask = lmask2;
} //if
} //if
const uint32_t msb = __builtin_clz(~lmask);
intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp);
int64_t absolute_addr = reinterpret_cast<int64_t>(*inpp) + (static_cast<int32_t>((ins & ~lmask) << msb) >> (lsb - 2u + msb));
int64_t new_pc_offset = static_cast<int64_t>(absolute_addr - reinterpret_cast<int64_t>(*outpp)) >> 2; // shifted
bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr);
if (!special_fix_type && llabs(new_pc_offset) >= (~lmask >> (lsb + 1))) {
if ((reinterpret_cast<uint64_t>(*outpp + 4) & 7u) != 0u) {
(*outpp)[0] = A64_NOP;
ctxp->reset_current_ins(current_idx, ++(*outpp));
} //if
(*outpp)[0] = (((8u >> 2u) << lsb) & ~lmask) | (ins & lmask); // B.C #0x8
(*outpp)[1] = 0x14000005u; // B #0x14
(*outpp)[2] = 0x58000051u; // LDR X17, #0x8
(*outpp)[3] = 0xd61f0220u; // BR X17
memcpy(*outpp + 4, &absolute_addr, sizeof(absolute_addr));
*outpp += 6;
} else {
if (special_fix_type) {
intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr);
if (ref_idx <= current_idx) {
new_pc_offset = static_cast<int64_t>(ctxp->dat[ref_idx].ins - reinterpret_cast<int64_t>(*outpp)) >> 2;
} else {
ctxp->insert_fix_map(ref_idx, *outpp, lsb, ~lmask);
new_pc_offset = 0;
} //if
} //if
(*outpp)[0] = (static_cast<uint32_t>(new_pc_offset << lsb) & ~lmask) | (ins & lmask);
++(*outpp);
} //if
++(*inpp);
return ctxp->process_fix_map(current_idx), true;
}
//-------------------------------------------------------------------------
static bool __fix_loadlit(instruction inpp, instruction outpp, context *ctxp)
{
const uint32_t ins = *(*inpp);
// memory prefetch("prfm"), just skip it
// http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897420050.html
if ((ins & 0xff000000u) == 0xd8000000u) {
ctxp->process_fix_map(ctxp->get_and_set_current_index(*inpp, *outpp));
++(*inpp);
return true;
} //if
static constexpr uint32_t msb = 8u;
static constexpr uint32_t lsb = 5u;
static constexpr uint32_t mask_30 = 0x40000000u; // 0b01000000000000000000000000000000
static constexpr uint32_t mask_31 = 0x80000000u; // 0b10000000000000000000000000000000
static constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111
static constexpr uint32_t mask_ldr = 0xbf000000u; // 0b10111111000000000000000000000000
static constexpr uint32_t op_ldr = 0x18000000u; // 0b00011000000000000000000000000000 "LDR Wt/Xt, label" | ADDR_PCREL19
static constexpr uint32_t mask_ldrv = 0x3f000000u; // 0b00111111000000000000000000000000
static constexpr uint32_t op_ldrv = 0x1c000000u; // 0b00011100000000000000000000000000 "LDR St/Dt/Qt, label" | ADDR_PCREL19
static constexpr uint32_t mask_ldrsw = 0xff000000u; // 0b11111111000000000000000000000000
static constexpr uint32_t op_ldrsw = 0x98000000u; // "LDRSW Xt, label" | ADDR_PCREL19 | load register signed word
// LDR S0, #0 | 0b00011100000000000000000000000000 | 32-bit
// LDR D0, #0 | 0b01011100000000000000000000000000 | 64-bit
// LDR Q0, #0 | 0b10011100000000000000000000000000 | 128-bit
// INVALID | 0b11011100000000000000000000000000 | may be 256-bit
uint32_t mask = mask_ldr;
uintptr_t faligned = (ins & mask_30) ? 7u : 3u;
if ((ins & mask_ldr) != op_ldr) {
mask = mask_ldrv;
if (faligned != 7u)
faligned = (ins & mask_31) ? 15u : 3u;
if ((ins & mask_ldrv) != op_ldrv) {
if ((ins & mask_ldrsw) != op_ldrsw) {
return false;
} //if
mask = mask_ldrsw;
faligned = 7u;
} //if
} //if
intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp);
int64_t absolute_addr = reinterpret_cast<int64_t>(*inpp) + ((static_cast<int32_t>(ins << msb) >> (msb + lsb - 2u)) & ~3u);
int64_t new_pc_offset = static_cast<int64_t>(absolute_addr - reinterpret_cast<int64_t>(*outpp)) >> 2; // shifted
bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr);
// special_fix_type may encounter issue when there are mixed data and code
if (special_fix_type || (llabs(new_pc_offset) + (faligned + 1u - 4u) / 4u) >= (~lmask >> (lsb + 1))) { // inaccurate, but it works
while ((reinterpret_cast<uint64_t>(*outpp + 2) & faligned) != 0u) {
*(*outpp)++ = A64_NOP;
}
ctxp->reset_current_ins(current_idx, *outpp);
// Note that if memory at absolute_addr is writeable (non-const), we will fail to fetch it.
// And what's worse, we may unexpectedly overwrite something if special_fix_type is true...
uint32_t ns = static_cast<uint32_t>((faligned + 1) / sizeof(uint32_t));
(*outpp)[0] = (((8u >> 2u) << lsb) & ~mask) | (ins & lmask); // LDR #0x8
(*outpp)[1] = 0x14000001u + ns; // B #0xc
memcpy(*outpp + 2, reinterpret_cast<void *>(absolute_addr), faligned + 1);
*outpp += 2 + ns;
} else {
faligned >>= 2; // new_pc_offset is shifted and 4-byte aligned
while ((new_pc_offset & faligned) != 0) {
*(*outpp)++ = A64_NOP;
new_pc_offset = static_cast<int64_t>(absolute_addr - reinterpret_cast<int64_t>(*outpp)) >> 2;
}
ctxp->reset_current_ins(current_idx, *outpp);
(*outpp)[0] = (static_cast<uint32_t>(new_pc_offset << lsb) & ~mask) | (ins & lmask);
++(*outpp);
} //if
++(*inpp);
return ctxp->process_fix_map(current_idx), true;
}
//-------------------------------------------------------------------------
static bool __fix_pcreladdr(instruction inpp, instruction outpp, context *ctxp)
{
// Load a PC-relative address into a register
// http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897645644.html
static constexpr uint32_t msb = 8u;
static constexpr uint32_t lsb = 5u;
static constexpr uint32_t mask = 0x9f000000u; // 0b10011111000000000000000000000000
static constexpr uint32_t rmask = 0x0000001fu; // 0b00000000000000000000000000011111
static constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111
static constexpr uint32_t fmask = 0x00ffffffu; // 0b00000000111111111111111111111111
static constexpr uint32_t max_val = 0x001fffffu; // 0b00000000000111111111111111111111
static constexpr uint32_t op_adr = 0x10000000u; // "adr" Rd, ADDR_PCREL21
static constexpr uint32_t op_adrp = 0x90000000u; // "adrp" Rd, ADDR_ADRP
const uint32_t ins = *(*inpp);
intptr_t current_idx;
switch (ins & mask) {
case op_adr:
{
current_idx = ctxp->get_and_set_current_index(*inpp, *outpp);
int64_t lsb_bytes = static_cast<uint32_t>(ins << 1u) >> 30u;
int64_t absolute_addr = reinterpret_cast<int64_t>(*inpp) + (((static_cast<int32_t>(ins << msb) >> (msb + lsb - 2u)) & ~3u) | lsb_bytes);
int64_t new_pc_offset = static_cast<int64_t>(absolute_addr - reinterpret_cast<int64_t>(*outpp));
bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr);
if (!special_fix_type && llabs(new_pc_offset) >= (max_val >> 1)) {
if ((reinterpret_cast<uint64_t>(*outpp + 2) & 7u) != 0u) {
(*outpp)[0] = A64_NOP;
ctxp->reset_current_ins(current_idx, ++(*outpp));
} //if
(*outpp)[0] = 0x58000000u | (((8u >> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8
(*outpp)[1] = 0x14000003u; // B #0xc
memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr));
*outpp += 4;
} else {
if (special_fix_type) {
intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr & ~3ull);
if (ref_idx <= current_idx) {
new_pc_offset = static_cast<int64_t>(ctxp->dat[ref_idx].ins - reinterpret_cast<int64_t>(*outpp));
} else {
ctxp->insert_fix_map(ref_idx, *outpp, lsb, fmask);
new_pc_offset = 0;
} //if
} //if
// the lsb_bytes will never be changed, so we can use lmask to keep it
(*outpp)[0] = (static_cast<uint32_t>(new_pc_offset << (lsb - 2u)) & fmask) | (ins & lmask);
++(*outpp);
} //if
}
break;
case op_adrp:
{
current_idx = ctxp->get_and_set_current_index(*inpp, *outpp);
int32_t lsb_bytes = static_cast<uint32_t>(ins << 1u) >> 30u;
int64_t absolute_addr = (reinterpret_cast<int64_t>(*inpp) & ~0xfffll) + ((((static_cast<int32_t>(ins << msb) >> (msb + lsb - 2u)) & ~3u) | lsb_bytes) << 12);
A64_LOGI("ins = 0x%.8X, pc = %p, abs_addr = %p",
ins, *inpp, reinterpret_cast<int64_t *>(absolute_addr));
if (ctxp->is_in_fixing_range(absolute_addr)) {
intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr/* & ~3ull*/);
if (ref_idx > current_idx) {
// the bottom 12 bits of absolute_addr are masked out,
// so ref_idx must be less than or equal to current_idx!
A64_LOGE("ref_idx must be less than or equal to current_idx!");
} //if
// *absolute_addr may be changed due to relocation fixing
A64_LOGI("What is the correct way to fix this?");
*(*outpp)++ = ins; // 0x90000000u;
} else {
if ((reinterpret_cast<uint64_t>(*outpp + 2) & 7u) != 0u) {
(*outpp)[0] = A64_NOP;
ctxp->reset_current_ins(current_idx, ++(*outpp));
} //if
(*outpp)[0] = 0x58000000u | (((8u >> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8
(*outpp)[1] = 0x14000003u; // B #0xc
memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); // potential overflow?
*outpp += 4;
} //if
}
break;
default:
return false;
}
ctxp->process_fix_map(current_idx);
++(*inpp);
return true;
}
//-------------------------------------------------------------------------
static void __fix_instructions(uint32_t *__restrict inp, int32_t count, uint32_t *__restrict outp)
{
context ctx;
ctx.basep = reinterpret_cast<int64_t>(inp);
ctx.endp = reinterpret_cast<int64_t>(inp + count);
memset(ctx.dat, 0, sizeof(ctx.dat));
static_assert(sizeof(ctx.dat) / sizeof(ctx.dat[0]) == A64_MAX_INSTRUCTIONS,
"please use A64_MAX_INSTRUCTIONS!");
#ifndef NDEBUG
if (count > A64_MAX_INSTRUCTIONS) {
A64_LOGE("too many fixing instructions!");
} //if
#endif // NDEBUG
uint32_t *const outp_base = outp;
while (--count >= 0) {
if (__fix_branch_imm(&inp, &outp, &ctx)) continue;
if (__fix_cond_comp_test_branch(&inp, &outp, &ctx)) continue;
if (__fix_loadlit(&inp, &outp, &ctx)) continue;
if (__fix_pcreladdr(&inp, &outp, &ctx)) continue;
// without PC-relative offset
ctx.process_fix_map(ctx.get_and_set_current_index(inp, outp));
*(outp++) = *(inp++);
}
static constexpr uint_fast64_t mask = 0x03ffffffu; // 0b00000011111111111111111111111111
auto callback = reinterpret_cast<int64_t>(inp);
auto pc_offset = static_cast<int64_t>(callback - reinterpret_cast<int64_t>(outp)) >> 2;
if (llabs(pc_offset) >= (mask >> 1)) {
if ((reinterpret_cast<uint64_t>(outp + 2) & 7u) != 0u) {
outp[0] = A64_NOP;
++outp;
} //if
outp[0] = 0x58000051u; // LDR X17, #0x8
outp[1] = 0xd61f0220u; // BR X17
*reinterpret_cast<int64_t *>(outp + 2) = callback;
outp += 4;
} else {
outp[0] = 0x14000000u | (pc_offset & mask); // "B" ADDR_PCREL26
++outp;
} //if
const uintptr_t total = (outp - outp_base) * sizeof(uint32_t);
__flush_cache(outp_base, total); // necessary
}
//-------------------------------------------------------------------------
extern "C" {
static __attribute__((__aligned__(__page_size))) uint32_t __insns_pool[A64_MAX_BACKUPS][A64_MAX_INSTRUCTIONS * 10];
//-------------------------------------------------------------------------
class A64HookInit
{
public:
A64HookInit()
{
__make_rwx(__insns_pool, sizeof(__insns_pool));
A64_LOGI("insns pool initialized.");
}
};
static A64HookInit __init;
//-------------------------------------------------------------------------
static uint32_t *FastAllocateTrampoline()
{
static_assert((A64_MAX_INSTRUCTIONS * 10 * sizeof(uint32_t)) % 8 == 0, "8-byte align");
static volatile int32_t __index = -1;
int32_t i = __atomic_increase(&__index);
if (__predict_true(i >= 0 && i < __countof(__insns_pool))) {
return __insns_pool[i];
} //if
A64_LOGE("failed to allocate trampoline!");
return NULL;
}
//-------------------------------------------------------------------------
A64_JNIEXPORT void *A64HookFunctionV(void *const symbol, void *const replace,
void *const rwx, const uintptr_t rwx_size)
{
static constexpr uint_fast64_t mask = 0x03ffffffu; // 0b00000011111111111111111111111111
uint32_t *trampoline = static_cast<uint32_t *>(rwx), *original = static_cast<uint32_t *>(symbol);
static_assert(A64_MAX_INSTRUCTIONS >= 5, "please fix A64_MAX_INSTRUCTIONS!");
auto pc_offset = static_cast<int64_t>(__intval(replace) - __intval(symbol)) >> 2;
if (llabs(pc_offset) >= (mask >>1)) {
int32_t count = (reinterpret_cast<uint64_t>(original + 2) & 7u) != 0u ? 5 : 4;
if (trampoline) {
if (rwx_size < count * 10u) {
A64_LOGE("rwx size is too small to hold %u bytes backup instructions!", count * 10u);
return NULL;
} //if
__fix_instructions(original, count, trampoline);
} //if
if (__make_rwx(original, 5 * sizeof(uint32_t)) == 0) {
if (count == 5) {
original[0] = A64_NOP;
++original;
} //if
original[0] = 0x58000051u; // LDR X17, #0x8
original[1] = 0xd61f0220u; // BR X17
*reinterpret_cast<int64_t *>(original + 2) = __intval(replace);
__flush_cache(symbol, 5 * sizeof(uint32_t));
A64_LOGI("inline hook %p->%p successfully! %zu bytes overwritten",
symbol, replace, 5 * sizeof(uint32_t));
} else {
A64_LOGE("mprotect failed with errno = %d, p = %p, size = %zu",
errno, original, 5 * sizeof(uint32_t));
trampoline = NULL;
} //if
} else {
if (trampoline) {
if (rwx_size < 1u * 10u) {
A64_LOGE("rwx size is too small to hold %u bytes backup instructions!", 1u * 10u);
return NULL;
} //if
__fix_instructions(original, 1, trampoline);
} //if
if (__make_rwx(original, 1 * sizeof(uint32_t)) == 0) {
__sync_cmpswap(original, *original, 0x14000000u | (pc_offset & mask)); // "B" ADDR_PCREL26
__flush_cache(symbol, 1 * sizeof(uint32_t));
A64_LOGI("inline hook %p->%p successfully! %zu bytes overwritten",
symbol, replace, 1 * sizeof(uint32_t));
} else {
A64_LOGE("mprotect failed with errno = %d, p = %p, size = %zu",
errno, original, 1 * sizeof(uint32_t));
trampoline = NULL;
} //if
} //if
return trampoline;
}
//-------------------------------------------------------------------------
A64_JNIEXPORT void A64HookFunction(void *const symbol, void *const replace, void **result)
{
void *trampoline = NULL;
if (result != NULL) {
trampoline = FastAllocateTrampoline();
*result = trampoline;
if (trampoline == NULL) return;
} //if
// fix Android 10 .text segment is read-only by default
__make_rwx(symbol, 5 * sizeof(size_t));
trampoline = A64HookFunctionV(symbol, replace, trampoline, A64_MAX_INSTRUCTIONS * 10u);
if (trampoline == NULL && result != NULL) {
*result = NULL;
} //if
}
}
#endif // defined(__aarch64__)
@@ -0,0 +1,42 @@
/*
* @date : 2018/04/18
* @author : Rprop (r_prop@outlook.com)
* https://github.com/Rprop/And64InlineHook
*/
/*
MIT License
Copyright (c) 2018 Rprop (r_prop@outlook.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#define A64_MAX_BACKUPS 256
#ifdef __cplusplus
extern "C" {
#endif
void A64HookFunction(void *const symbol, void *const replace, void **result);
void *A64HookFunctionV(void *const symbol, void *const replace,
void *const rwx, const uintptr_t rwx_size);
#ifdef __cplusplus
}
#endif
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 RLib
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.