mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
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:
Binary file not shown.
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user