commit 28d1ea2a431d3b776218621d93f3d686b24f9134
parent 17ddfa5b9c95804616fabd14062e40ae7885971a
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Fri, 26 Jul 2024 01:54:43 +0200
feat: rust native
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
Diffstat:
42 files changed, 1813 insertions(+), 774 deletions(-)
diff --git a/.gitmodules b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "dobby"]
- path = native/jni/external/dobby
- url = https://github.com/jmpews/Dobby
diff --git a/build.gradle.kts b/build.gradle.kts
@@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.kotlinAndroid) apply false
alias(libs.plugins.compose.compiler) apply false
+ alias(libs.plugins.rust.android) apply false
}
var versionName = "2.1.0"
@@ -12,7 +13,7 @@ var versionCode = 210
rootProject.ext.set("appVersionName", versionName)
rootProject.ext.set("appVersionCode", versionCode)
rootProject.ext.set("applicationId", "me.rhunk.snapenhance")
-rootProject.ext.set("buildHash", properties["debug_build_hash"] ?: java.security.SecureRandom().nextLong(1000000000, 99999999999).toString(16))
+rootProject.ext.set("buildHash", properties["debug_build_hash"] ?: java.security.SecureRandom().nextLong(Long.MAX_VALUE / 1000L, Long.MAX_VALUE).toString(16))
tasks.register("getVersion") {
doLast {
diff --git a/common/build.gradle.kts b/common/build.gradle.kts
@@ -23,7 +23,7 @@ android {
buildConfigField("int", "VERSION_CODE", "${rootProject.ext["appVersionCode"]}")
buildConfigField("String", "APPLICATION_ID", "\"${rootProject.ext["applicationId"]}\"")
buildConfigField("long", "BUILD_TIMESTAMP", "${System.currentTimeMillis()}L")
- buildConfigField("String", "BUILD_HASH", "\"${rootProject.ext["buildHash"]}\"")
+ buildConfigField("String", "BUILD_HASH", "\"${rootProject.ext["buildHash"]}\".toString()")
val gitHash = ByteArrayOutputStream()
exec {
commandLine("git", "rev-parse", "HEAD")
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/BridgeFiles.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/BridgeFiles.kt
@@ -33,6 +33,7 @@ enum class InternalFileHandleType(
MAPPINGS("mappings", "mappings.json"),
MESSAGE_LOGGER("message_logger", "message_logger.db", isDatabase = true),
PINNED_BEST_FRIEND("pinned_best_friend", "pinned_best_friend.txt"),
+ NATIVE_SIG_CACHE("native_sig_cache", "native_sig_cache.txt"),
SIF("sif", "libsif.so");
fun resolve(context: Context): File = if (isDatabase) {
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MappingsWrapper.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MappingsWrapper.kt
@@ -6,6 +6,7 @@ import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
import me.rhunk.snapenhance.common.BuildConfig
import me.rhunk.snapenhance.common.Constants
+import me.rhunk.snapenhance.common.bridge.FileHandleScope
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.bridge.InternalFileWrapper
import me.rhunk.snapenhance.common.logger.AbstractLogger
@@ -15,7 +16,7 @@ import me.rhunk.snapenhance.mapper.ClassMapper
import kotlin.reflect.KClass
class MappingsWrapper(
- fileHandleManager: LazyBridgeValue<FileHandleManager>
+ private val fileHandleManager: LazyBridgeValue<FileHandleManager>
): InternalFileWrapper(fileHandleManager, InternalFileHandleType.MAPPINGS, defaultValue = "{}") {
private lateinit var context: Context
private var mappingUniqueHash: Long = 0
@@ -68,6 +69,10 @@ class MappingsWrapper(
fun refresh() {
mappingUniqueHash = getUniqueBuildId()
+
+ // reset native signature cache
+ fileHandleManager.value.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.NATIVE_SIG_CACHE.key).delete()
+
val classMapper = ClassMapper(*mappers.values.toTypedArray())
runCatching {
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt
@@ -193,6 +193,16 @@ class SnapEnhance {
}
private fun initNative() {
+ val nativeSigCacheFileHandle = appContext.fileHandlerManager.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.NATIVE_SIG_CACHE.key).toWrapper()
+
+ val oldSignatureCache = nativeSigCacheFileHandle.readBytes()
+ .takeIf {
+ it.isNotEmpty()
+ }?.toString(Charsets.UTF_8)?.also {
+ appContext.native.signatureCache = it
+ appContext.log.verbose("old signature cache $it")
+ }
+
val lateInit = appContext.native.initOnce {
nativeUnaryCallCallback = { request ->
appContext.event.post(NativeUnaryCallEvent(request.uri, request.buffer)) {
@@ -201,6 +211,14 @@ class SnapEnhance {
}
}
appContext.reloadNativeConfig()
+ }.let { init ->
+ {
+ init()
+ appContext.native.signatureCache.takeIf { it != oldSignatureCache }?.let {
+ appContext.log.verbose("new signature cache $it")
+ nativeSigCacheFileHandle.writeBytes(it.toByteArray(Charsets.UTF_8))
+ }
+ }
}
if (appContext.bridgeClient.getDebugProp("disable_sif", "false") != "true") {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
@@ -23,6 +23,7 @@ material3 = "1.2.1"
okhttp = "5.0.0-alpha.14"
rhino = "1.7.15"
rhino-android = "1.6.0"
+rust-android = "0.9.4"
[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
@@ -59,5 +60,6 @@ androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "compose-compiler" }
+rust-android = { id = "org.mozilla.rust-android-gradle.rust-android", version.ref = "rust-android" }
[bundles]
\ No newline at end of file
diff --git a/native/.gitignore b/native/.gitignore
@@ -1,2 +1,3 @@
/build
-/.cxx-
\ No newline at end of file
+/.cxx
+/rust/target+
\ No newline at end of file
diff --git a/native/build.gradle.kts b/native/build.gradle.kts
@@ -1,4 +1,5 @@
plugins {
+ alias(libs.plugins.rust.android)
alias(libs.plugins.androidLibrary)
alias(libs.plugins.kotlinAndroid)
}
@@ -8,43 +9,16 @@ val nativeName = rootProject.ext.get("buildHash")
android {
namespace = rootProject.ext["applicationId"].toString() + ".nativelib"
compileSdk = 34
- buildToolsVersion = "34.0.0"
- ndkVersion = "26.3.11579264"
buildFeatures {
buildConfig = true
}
defaultConfig {
- buildConfigField("String", "NATIVE_NAME", "\"$nativeName\"")
- packaging {
- jniLibs {
- excludes += "**/libdobby.so"
- }
- }
- externalNativeBuild {
- cmake {
- arguments += listOf(
- "-DOBFUSCATED_NAME=$nativeName",
- "-DBUILD_PACKAGE=${rootProject.ext["applicationId"]}",
- "-DBUILD_NAMESPACE=${namespace!!.replace(".", "/")}"
- )
- }
- ndk {
- //noinspection ChromeOsAbiSupport
- abiFilters += properties["debug_abi_filters"]?.toString()?.split(",")
- ?: listOf("arm64-v8a", "armeabi-v7a")
- }
- }
+ buildConfigField("String", "NATIVE_NAME", "\"$nativeName\".toString()")
minSdk = 28
}
- externalNativeBuild {
- cmake {
- path("jni/CMakeLists.txt")
- version = "3.22.1"
- }
- }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
@@ -53,4 +27,36 @@ android {
kotlinOptions {
jvmTarget = "17"
}
-}-
\ No newline at end of file
+}
+
+cargo {
+ module = "rust"
+ libname = nativeName.toString()
+ targetIncludes = arrayOf("libsnapenhance.so")
+ targets = listOf("arm64", "arm")
+}
+
+fun getNativeFiles() = File(projectDir, "build/rustJniLibs/android").listFiles()?.flatMap { abiFolder ->
+ abiFolder.takeIf { it.isDirectory }?.listFiles()?.toList() ?: emptyList()
+}
+
+tasks.register("cleanNatives") {
+ doLast {
+ println("Cleaning native files")
+ getNativeFiles()?.forEach { file ->
+ file.deleteRecursively()
+ }
+ }
+}
+
+tasks.named("preBuild").configure {
+ dependsOn("cleanNatives", "cargoBuild")
+ doLast {
+ getNativeFiles()?.forEach { file ->
+ if (file.name.endsWith(".so")) {
+ println("Renaming ${file.absolutePath}")
+ file.renameTo(File(file.parent, "lib$nativeName.so"))
+ }
+ }
+ }
+}
diff --git a/native/jni/CMakeLists.txt b/native/jni/CMakeLists.txt
@@ -1,26 +0,0 @@
-cmake_minimum_required(VERSION 3.22.1)
-
-find_program(CCACHE_FOUND ccache)
-if(CCACHE_FOUND)
- set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
- set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
-endif(CCACHE_FOUND)
-
-project("nativelib")
-
-set(DOBBY_GENERATE_SHARED OFF)
-add_subdirectory(external/dobby)
-
-add_library(${CMAKE_PROJECT_NAME} SHARED
- src/library.cpp
- )
-
-add_compile_definitions(BUILD_NAMESPACE="${BUILD_NAMESPACE}")
-add_compile_definitions(BUILD_PACKAGE="${BUILD_PACKAGE}")
-target_link_libraries(${CMAKE_PROJECT_NAME}
- android
- log
- dobby_static
- )
-
-set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${OBFUSCATED_NAME})-
\ No newline at end of file
diff --git a/native/jni/external/dobby b/native/jni/external/dobby
@@ -1 +0,0 @@
-Subproject commit b0176de574104726bb68dff3b77ee666300fc338
diff --git a/native/jni/src/common.h b/native/jni/src/common.h
@@ -1,25 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-#include "util.h"
-
-#ifdef __aarch64__
-#define ARM64 true
-#else
-#define ARM64 false
-#endif
-
-typedef struct {
- bool disable_bitmoji;
- bool disable_metrics;
- bool composer_hooks;
- char custom_emoji_font_path[256];
-} native_config_t;
-
-namespace common {
- static JavaVM *java_vm;
- static jobject native_lib_object;
-
- static util::module_info_t client_module;
- static native_config_t *native_config = new native_config_t;
-}
diff --git a/native/jni/src/dobby_helper.h b/native/jni/src/dobby_helper.h
@@ -1,13 +0,0 @@
-#pragma once
-
-#include <pthread.h>
-#include <dobby.h>
-
-
-static pthread_mutex_t hook_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-static void inline SafeHook(void *addr, void *hook, void **original) {
- pthread_mutex_lock(&hook_mutex);
- DobbyHook(addr, hook, original);
- pthread_mutex_unlock(&hook_mutex);
-}-
\ No newline at end of file
diff --git a/native/jni/src/hooks/composer_hook.h b/native/jni/src/hooks/composer_hook.h
@@ -1,189 +0,0 @@
-#pragma once
-#include <stdio.h>
-
-namespace ComposerHook {
- enum {
- JS_TAG_FIRST = -11,
- JS_TAG_BIG_DECIMAL = -11,
- JS_TAG_BIG_INT = -10,
- JS_TAG_BIG_FLOAT = -9,
- JS_TAG_SYMBOL = -8,
- JS_TAG_STRING = -7,
- JS_TAG_MODULE = -3,
- JS_TAG_FUNCTION_BYTECODE = -2,
- JS_TAG_OBJECT = -1,
-
- JS_TAG_INT = 0,
- JS_TAG_BOOL = 1,
- JS_TAG_NULL = 2,
- JS_TAG_UNDEFINED = 3,
- JS_TAG_UNINITIALIZED = 4,
- JS_TAG_CATCH_OFFSET = 5,
- JS_TAG_EXCEPTION = 6,
- JS_TAG_FLOAT64 = 7,
- };
-
- typedef struct JSRefCountHeader {
- int ref_count;
- } JSRefCountHeader;
-
- struct JSString {
- JSRefCountHeader header;
- uint32_t len : 31;
- uint8_t is_wide_char : 1;
- uint32_t hash : 30;
- uint8_t atom_type : 2;
- uint32_t hash_next;
-
- union {
- uint8_t str8[0];
- uint16_t str16[0];
- } u;
- };
-
- typedef union JSValueUnion {
- int32_t int32;
- double float64;
- void *ptr;
- } JSValueUnion;
-
- typedef struct JSValue {
- JSValueUnion u;
- int64_t tag;
- } JSValue;
-
- typedef struct list_head {
- struct list_head *next, *prev;
- } list_head;
-
- struct JSGCObjectHeader {
- int ref_count;
- uint8_t gc_obj_type : 4;
- uint8_t mark : 4;
- uint8_t dummy1;
- uint16_t dummy2;
- struct list_head link;
- };
-
- struct JSContext {
- JSGCObjectHeader header;
- void *rt;
- struct list_head link;
-
- uint16_t binary_object_count;
- int binary_object_size;
-
- JSValue *array_shape;
- JSValue *class_proto;
- JSValue function_proto;
- JSValue function_ctor;
- JSValue array_ctor;
- JSValue regexp_ctor;
- JSValue promise_ctor;
- JSValue native_error_proto[8];
- JSValue iterator_proto;
- JSValue async_iterator_proto;
- JSValue array_proto_values;
- JSValue throw_type_error;
- JSValue eval_obj;
-
- JSValue global_obj;
- JSValue global_var_obj;
- };
-
- static void* global_instance;
- static JSContext *global_ctx;
- static std::string* composer_loader;
-
- HOOK_DEF(JSValue, js_eval, void* instance, JSContext *ctx, void* this_obj, char *input, uintptr_t input_len, const char *filename, unsigned int flags, unsigned int scope_idx) {
- if (global_instance == nullptr || global_ctx == nullptr) {
- global_instance = instance;
- global_ctx = ctx;
-
- if (composer_loader != nullptr) {
- LOGD("Injecting composer loader");
- composer_loader->resize(composer_loader->size() + input_len);
- memcpy((void*) (composer_loader->c_str() + composer_loader->size() - input_len), input, input_len);
-
- input = (char*) composer_loader->c_str();
- input_len = composer_loader->size();
- }
- } else {
- if (composer_loader != nullptr) {
- delete composer_loader;
- composer_loader = nullptr;
- }
- }
- return js_eval_original(instance, ctx, this_obj, input, input_len, filename, flags, scope_idx);
- }
-
- void setComposerLoader(JNIEnv *env, jobject, jstring code) {
- global_instance = nullptr;
- global_ctx = nullptr;
- auto code_str = env->GetStringUTFChars(code, nullptr);
- if (composer_loader != nullptr) delete composer_loader;
- composer_loader = new std::string(code_str, env->GetStringUTFLength(code));
- env->ReleaseStringUTFChars(code, code_str);
- }
-
- jstring composerEval(JNIEnv *env, jobject, jstring script) {
- if (!ARM64) return env->NewStringUTF("Architecture not supported");
- if (global_instance == 0 || global_ctx == nullptr) {
- return env->NewStringUTF("Composer not ready");
- }
-
- auto script_str = env->GetStringUTFChars(script, nullptr);
- auto length = env->GetStringUTFLength(script);
- auto jsvalue = js_eval_original(global_instance, global_ctx, (void*) &global_ctx->global_obj, (char *) script_str, length, "<eval>", 0, 0);
- env->ReleaseStringUTFChars(script, script_str);
-
- if (jsvalue.tag == JS_TAG_STRING) {
- auto str = (JSString *) jsvalue.u.ptr;
- return env->NewStringUTF((const char *) str->u.str8);
- }
-
- std::string result;
- switch (jsvalue.tag) {
- case JS_TAG_INT:
- result = std::to_string(jsvalue.u.int32);
- break;
- case JS_TAG_BOOL:
- result = jsvalue.u.int32 ? "true" : "false";
- break;
- case JS_TAG_NULL:
- result = "null";
- break;
- case JS_TAG_UNDEFINED:
- result = "undefined";
- break;
- case JS_TAG_OBJECT:
- result = "[object Object]";
- break;
- case JS_TAG_EXCEPTION:
- result = "Failed to evaluate script";
- break;
- case JS_TAG_FLOAT64:
- result = std::to_string(jsvalue.u.float64);
- break;
- default:
- result = "[unknown tag " + std::to_string(jsvalue.tag) + "]";
- break;
- }
-
- return env->NewStringUTF(result.c_str());
- }
-
- void init() {
- auto js_eval_ptr = util::find_signature(
- common::client_module.base,
- common::client_module.size,
- ARM64 ? "00 E4 00 6F 29 00 80 52 76 00 04 8B" : "A1 B0 07 92 81 46",
- ARM64 ? -0x28 : -0x7
- );
- if (js_eval_ptr == 0) {
- LOGE("js_eval_ptr signature not found");
- return;
- }
- SafeHook((void*) js_eval_ptr, (void *) js_eval, (void **) &js_eval_original);
- }
-}-
\ No newline at end of file
diff --git a/native/jni/src/hooks/custom_emoji_font.h b/native/jni/src/hooks/custom_emoji_font.h
@@ -1,20 +0,0 @@
-#pragma once
-
-#include <sys/stat.h>
-
-namespace CustomEmojiFont {
- HOOK_DEF(int, open_hook, const char *pathname, int flags, mode_t mode) {
- auto custom_path = common::native_config->custom_emoji_font_path;
- if (strstr(pathname, "/system/fonts/NotoColorEmoji.ttf") != 0 && custom_path[0] != 0) {
- struct stat buffer;
- if (stat(custom_path, &buffer) == 0) {
- pathname = custom_path;
- }
- }
- return open_hook_original(pathname, flags, mode);
- }
-
- void init() {
- SafeHook((void *) DobbySymbolResolver("libc.so", "open"), (void *)open_hook, (void **)&open_hook_original);
- }
-}-
\ No newline at end of file
diff --git a/native/jni/src/hooks/duplex_hook.h b/native/jni/src/hooks/duplex_hook.h
@@ -1,26 +0,0 @@
-#pragma once
-
-
-namespace DuplexHook {
- HOOK_DEF(jboolean, IsSameObject, JNIEnv * env, jobject obj1, jobject obj2) {
- if (obj1 == nullptr || obj2 == nullptr) return IsSameObject_original(env, obj1, obj2);
-
- auto clazz = env->FindClass("java/lang/Class");
- if (!env->IsInstanceOf(obj1, clazz)) return IsSameObject_original(env, obj1, obj2);
-
- jstring obj1ClassName = (jstring) env->CallObjectMethod(obj1, env->GetMethodID(clazz, "getName", "()Ljava/lang/String;"));
- const char* obj1ClassNameStr = env->GetStringUTFChars(obj1ClassName, nullptr);
-
- if (strstr(obj1ClassNameStr, "com.snapchat.client.duplex.MessageHandler") != 0) {
- env->ReleaseStringUTFChars(obj1ClassName, obj1ClassNameStr);
- return JNI_FALSE;
- }
-
- env->ReleaseStringUTFChars(obj1ClassName, obj1ClassNameStr);
- return IsSameObject_original(env, obj1, obj2);
- }
-
- void init(JNIEnv* env) {
- SafeHook((void *)env->functions->IsSameObject, (void *)IsSameObject, (void **)&IsSameObject_original);
- }
-}-
\ No newline at end of file
diff --git a/native/jni/src/hooks/fstat_hook.h b/native/jni/src/hooks/fstat_hook.h
@@ -1,27 +0,0 @@
-#pragma once
-
-namespace FstatHook {
- HOOK_DEF(int, fstat_hook, int fd, struct stat *buf) {
- char name[256];
- memset(name, 0, sizeof(name));
- snprintf(name, sizeof(name), "/proc/self/fd/%d", fd);
- readlink(name, name, sizeof(name));
-
- std::string fileName(name);
-
- if (common::native_config->disable_metrics && fileName.find("files/blizzardv2/queues") != std::string::npos) {
- unlink(name);
- return -1;
- }
-
- if (common::native_config->disable_bitmoji && fileName.find("com.snap.file_manager_4_SCContent") != std::string::npos) {
- return -1;
- }
-
- return fstat_hook_original(fd, buf);
- }
-
- void init() {
- SafeHook((void *)DobbySymbolResolver("libc.so", "fstat"), (void *)fstat_hook, (void **)&fstat_hook_original);
- }
-}-
\ No newline at end of file
diff --git a/native/jni/src/hooks/linker_hook.h b/native/jni/src/hooks/linker_hook.h
@@ -1,54 +0,0 @@
-#pragma once
-
-#include <map>
-
-namespace LinkerHook {
- static auto linker_openat_hooks = std::map<std::string, std::pair<uintptr_t, size_t>>();
-
- void JNICALL addLinkerSharedLibrary(JNIEnv *env, jobject, jstring path, jbyteArray content) {
- const char *path_str = env->GetStringUTFChars(path, nullptr);
- jsize content_len = env->GetArrayLength(content);
- jbyte *content_ptr = env->GetByteArrayElements(content, nullptr);
-
- auto allocated_content = (jbyte *) malloc(content_len);
- memcpy(allocated_content, content_ptr, content_len);
- linker_openat_hooks[path_str] = std::make_pair((uintptr_t) allocated_content, content_len);
-
- LOGD("added linker hook for %s, size=%d", path_str, content_len);
-
- env->ReleaseStringUTFChars(path, path_str);
- env->ReleaseByteArrayElements(content, content_ptr, JNI_ABORT);
- }
-
- HOOK_DEF(int, linker_openat, int dirfd, const char *pathname, int flags, mode_t mode) {
- for (const auto &item: linker_openat_hooks) {
- if (strstr(pathname, item.first.c_str())) {
- LOGD("found openat hook for %s", pathname);
- static auto memfd_create = (int (*)(const char *, unsigned int)) DobbySymbolResolver("libc.so", "memfd_create");
- auto fd = memfd_create("me.rhunk.snapenhance", 0);
- LOGD("memfd created: %d", fd);
-
- if (fd == -1) {
- LOGE("memfd_create failed: %d", errno);
- return -1;
- }
- if (write(fd, (void *) item.second.first, item.second.second) == -1) {
- LOGE("write failed: %d", errno);
- return -1;
- }
- lseek(fd, 0, SEEK_SET);
-
- free((void *) item.second.first);
- linker_openat_hooks.erase(item.first);
-
- LOGD("memfd written");
- return fd;
- }
- }
- return linker_openat_original(dirfd, pathname, flags, mode);
- }
-
- void init() {
- DobbyHook((void *) DobbySymbolResolver(ARM64 ? "linker64" : "linker", "__dl___openat"), (void *) linker_openat, (void **) &linker_openat_original);
- }
-}-
\ No newline at end of file
diff --git a/native/jni/src/hooks/sqlite_mutex.h b/native/jni/src/hooks/sqlite_mutex.h
@@ -1,46 +0,0 @@
-#pragma once
-
-#include <map>
-
-namespace SqliteMutexHook {
- typedef struct {
- pthread_mutex_t mutex;
- } sqlite3_mutex;
-
- typedef struct {
- void* pVfs;
- void* pVdbe;
- void* pDfltColl;
- sqlite3_mutex* mutex;
- } sqlite3;
-
- static std::map<std::string, sqlite3_mutex *> mutex_map = {};
-
- HOOK_DEF(int, sqlite3_open_hook, const char *filename, sqlite3 **ppDb, unsigned int flags, const char *zVfs) {
- auto result = sqlite3_open_hook_original(filename, ppDb, flags, zVfs);
- if (result == 0) {
- auto mutex = (*ppDb)->mutex;
- if (mutex == nullptr) return result;
-
- auto last_slash = strrchr(filename, '/') + 1;
- if (last_slash > filename) {
- LOGD("sqlite3_open_hook: %s", last_slash);
- mutex_map[last_slash] = mutex;
- }
- }
- return result;
- }
-
- void init() {
- auto open_database_sig = util::find_signature(
- common::client_module.base, common::client_module.size,
- ARM64 ? "FF FF 00 A9 3F 00 00 F9" : "9A 46 90 46 78 44 89 46 05 68",
- ARM64 ? -0x3C : -0xd
- );
- if (open_database_sig == 0) {
- LOGE("sqlite3 openDatabase sig not found");
- return;
- }
- SafeHook((void *) open_database_sig, (void *) sqlite3_open_hook, (void **) &sqlite3_open_hook_original);
- }
-}-
\ No newline at end of file
diff --git a/native/jni/src/hooks/unary_call.h b/native/jni/src/hooks/unary_call.h
@@ -1,87 +0,0 @@
-#pragma once
-
-#include "../common.h"
-#include "../util.h"
-
-namespace UnaryCallHook {
- namespace grpc {
- typedef struct {
- void* ref_counter;
- size_t length;
- uint8_t* data;
- } ref_counted_slice_byte_buffer;
-
- typedef struct {
- void* reserved;
- void* type;
- void* compression;
- ref_counted_slice_byte_buffer *slice_buffer;
- } grpc_byte_buffer;
- }
-
- static jmethodID native_lib_on_unary_call_method;
-
- HOOK_DEF(void *, unaryCall_hook, void *unk1, const char *uri, grpc::grpc_byte_buffer **buffer_ptr, void *unk4, void *unk5, void *unk6) {
- // request without reference counter can be hooked using xposed ig
- auto slice_buffer = (*buffer_ptr)->slice_buffer;
- if (slice_buffer->ref_counter == 0) {
- return unaryCall_hook_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
- }
-
- JNIEnv *env = nullptr;
- common::java_vm->GetEnv((void **)&env, JNI_VERSION_1_6);
-
- auto jni_buffer_array = env->NewByteArray(slice_buffer->length);
- env->SetByteArrayRegion(jni_buffer_array, 0, slice_buffer->length, (jbyte *)slice_buffer->data);
-
- auto native_request_data_object = env->CallObjectMethod(common::native_lib_object, native_lib_on_unary_call_method, env->NewStringUTF(uri), jni_buffer_array);
-
- if (native_request_data_object != nullptr) {
- auto native_request_data_class = env->GetObjectClass(native_request_data_object);
- auto is_canceled = env->GetBooleanField(native_request_data_object, env->GetFieldID(native_request_data_class, "canceled", "Z"));
-
- if (is_canceled) {
- LOGD("canceled request for %s", uri);
- return nullptr;
- }
-
- auto new_buffer = (jbyteArray)env->GetObjectField(native_request_data_object, env->GetFieldID(native_request_data_class, "buffer", "[B"));
- auto new_buffer_length = env->GetArrayLength(new_buffer);
- auto new_buffer_data = env->GetByteArrayElements(new_buffer, nullptr);
-
- //we need to allocate a new ref_counter struct and copy the old ref_counter and the new_buffer to it
- const static auto ref_counter_struct_size = (uintptr_t)slice_buffer->data - (uintptr_t)slice_buffer->ref_counter;
-
- auto new_ref_counter = malloc(ref_counter_struct_size + new_buffer_length);
- //copy the old ref_counter and the native_request_data_object
- memcpy(new_ref_counter, slice_buffer->ref_counter, ref_counter_struct_size);
- memcpy((void *)((uintptr_t)new_ref_counter + ref_counter_struct_size), new_buffer_data, new_buffer_length);
-
- //free the old ref_counter
- free(slice_buffer->ref_counter);
-
- //update the slice_buffer
- slice_buffer->ref_counter = new_ref_counter;
- slice_buffer->length = new_buffer_length;
- slice_buffer->data = (uint8_t *)((uintptr_t)new_ref_counter + ref_counter_struct_size);
- }
-
- return unaryCall_hook_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
- }
-
- void init(JNIEnv *env) {
- auto unaryCall_func = util::find_signature(
- common::client_module.base, common::client_module.size,
- ARM64 ? "A8 03 1F F8 C2 00 00 94" : "0A 90 00 F0 3F F9",
- ARM64 ? -0x48 : -0x37
- );
-
- native_lib_on_unary_call_method = env->GetMethodID(env->GetObjectClass(common::native_lib_object), "onNativeUnaryCall", "(Ljava/lang/String;[B)L" BUILD_NAMESPACE "/NativeRequestData;");
-
- if (unaryCall_func != 0) {
- SafeHook((void *)unaryCall_func, (void *)unaryCall_hook, (void **)&unaryCall_hook_original);
- } else {
- LOGE("Can't find unaryCall signature");
- }
- }
-}-
\ No newline at end of file
diff --git a/native/jni/src/library.cpp b/native/jni/src/library.cpp
@@ -1,115 +0,0 @@
-#include <jni.h>
-#include <string>
-#include <dobby.h>
-#include <vector>
-#include <thread>
-
-#include "logger.h"
-#include "common.h"
-#include "dobby_helper.h"
-#include "hooks/linker_hook.h"
-#include "hooks/unary_call.h"
-#include "hooks/fstat_hook.h"
-#include "hooks/sqlite_mutex.h"
-#include "hooks/duplex_hook.h"
-#include "hooks/composer_hook.h"
-#include "hooks/custom_emoji_font.h"
-
-bool JNICALL init(JNIEnv *env, jobject clazz) {
- LOGD("Initializing native");
- using namespace common;
-
- native_lib_object = env->NewGlobalRef(clazz);
- client_module = util::get_module("libclient.so");
-
- if (client_module.base == 0) {
- LOGD("libclient.so not found, trying split_config");
- client_module = util::get_module(("split_config." + std::string(ARM64 ? "arm64_v8a" : "armeabi-v7a") + ".apk").c_str());
- if (client_module.base == 0) {
- LOGE("can't find split_config!");
- return false;
- }
- }
-
- LOGD("client_module offset=0x%lx, size=0x%zx", client_module.base, client_module.size);
-
- auto threads = std::vector<std::thread>();
-
- #define RUN(body) threads.push_back(std::thread([&] { body; }))
-
- RUN(UnaryCallHook::init(env));
- RUN(FstatHook::init());
- RUN(SqliteMutexHook::init());
- RUN(DuplexHook::init(env));
- if (common::native_config->custom_emoji_font_path[0] != 0) {
- RUN(CustomEmojiFont::init());
- }
- if (common::native_config->composer_hooks) {
- RUN(ComposerHook::init());
- }
-
- for (auto &thread : threads) {
- thread.join();
- }
-
- LOGD("Native initialized");
- return true;
-}
-
-void JNICALL load_config(JNIEnv *env, jobject, jobject config_object) {
- auto native_config_clazz = env->GetObjectClass(config_object);
-#define GET_CONFIG_BOOL(name) env->GetBooleanField(config_object, env->GetFieldID(native_config_clazz, name, "Z"))
- auto native_config = common::native_config;
-
- native_config->disable_bitmoji = GET_CONFIG_BOOL("disableBitmoji");
- native_config->disable_metrics = GET_CONFIG_BOOL("disableMetrics");
- native_config->composer_hooks = GET_CONFIG_BOOL("composerHooks");
-
- memset(native_config->custom_emoji_font_path, 0, sizeof(native_config->custom_emoji_font_path));
- auto custom_emoji_font_path = env->GetObjectField(config_object, env->GetFieldID(native_config_clazz, "customEmojiFontPath", "Ljava/lang/String;"));
- if (custom_emoji_font_path != nullptr) {
- auto custom_emoji_font_path_str = env->GetStringUTFChars((jstring) custom_emoji_font_path, nullptr);
- strncpy(native_config->custom_emoji_font_path, custom_emoji_font_path_str, sizeof(native_config->custom_emoji_font_path));
- env->ReleaseStringUTFChars((jstring) custom_emoji_font_path, custom_emoji_font_path_str);
- }
-}
-
-void JNICALL lock_database(JNIEnv *env, jobject, jstring database_name, jobject runnable) {
- auto database_name_str = env->GetStringUTFChars(database_name, nullptr);
- auto mutex = SqliteMutexHook::mutex_map[database_name_str];
- env->ReleaseStringUTFChars(database_name, database_name_str);
-
- if (mutex != nullptr) {
- auto lock_result = pthread_mutex_lock(&mutex->mutex);
- if (lock_result != 0) {
- LOGE("pthread_mutex_lock failed: %d", lock_result);
- return;
- }
- }
-
- env->CallVoidMethod(runnable, env->GetMethodID(env->GetObjectClass(runnable), "run", "()V"));
-
- if (mutex != nullptr) {
- pthread_mutex_unlock(&mutex->mutex);
- }
-}
-
-
-extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *_) {
- common::java_vm = vm;
- JNIEnv *env = nullptr;
- vm->GetEnv((void **)&env, JNI_VERSION_1_6);
-
- auto methods = std::vector<JNINativeMethod>();
- methods.push_back({"init", "()Z", (void *)init});
- methods.push_back({"loadConfig", "(L" BUILD_NAMESPACE "/NativeConfig;)V", (void *)load_config});
- methods.push_back({"lockDatabase", "(Ljava/lang/String;Ljava/lang/Runnable;)V", (void *)lock_database});
- methods.push_back({"setComposerLoader", "(Ljava/lang/String;)V", (void *) ComposerHook::setComposerLoader});
- methods.push_back({"composerEval", "(Ljava/lang/String;)Ljava/lang/String;",(void *) ComposerHook::composerEval});
- methods.push_back({"addLinkerSharedLibrary", "(Ljava/lang/String;[B)V", (void *) LinkerHook::addLinkerSharedLibrary});
-
- LinkerHook::init();
-
- env->RegisterNatives(env->FindClass(std::string(BUILD_NAMESPACE "/NativeLib").c_str()), methods.data(), methods.size());
- return JNI_VERSION_1_6;
-}
diff --git a/native/jni/src/logger.h b/native/jni/src/logger.h
@@ -1,9 +0,0 @@
-#pragma once
-
-#include <android/log.h>
-
-#define LOG_TAG "SnapEnhanceNative"
-
-#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
-
diff --git a/native/jni/src/util.h b/native/jni/src/util.h
@@ -1,83 +0,0 @@
-#pragma once
-
-#include <unistd.h>
-#include <sys/mman.h>
-
-#define HOOK_DEF(ret, func, ...) ret (*func##_original)(__VA_ARGS__); ret func(__VA_ARGS__)
-
-namespace util {
- typedef struct {
- uintptr_t base;
- size_t size;
- } module_info_t;
-
- static module_info_t get_module(const char *libname) {
- char buff[256];
- int len_libname = strlen(libname);
- uintptr_t start_offset = 0;
- uintptr_t end_offset = 0;
-
- auto file = fopen("/proc/self/smaps", "rt");
- if (file == NULL)
- return {0, 0};
-
- while (fgets(buff, sizeof buff, file) != NULL) {
- int len = strlen(buff);
- if (len > 0 && buff[len - 1] == '\n') {
- buff[--len] = '\0';
- }
- if (len <= len_libname || memcmp(buff + len - len_libname, libname, len_libname)) {
- continue;
- }
- size_t start, end, offset;
- char flags[4];
- if (sscanf(buff, "%zx-%zx %c%c%c%c %zx", &start, &end,
- &flags[0], &flags[1], &flags[2], &flags[3], &offset) != 7) {
- continue;
- }
-
- if (flags[0] != 'r' || flags[2] != 'x') {
- continue;
- }
-
- if (start_offset == 0) {
- start_offset = start;
- }
- end_offset = end;
- }
- fclose(file);
- if (start_offset == 0) {
- return {0, 0};
- }
- return { start_offset, end_offset - start_offset };
- }
-
- static uintptr_t find_signature(uintptr_t module_base, uintptr_t size, const std::string &pattern, int offset = 0) {
- std::vector<char> bytes;
- std::vector<char> mask;
- for (size_t i = 0; i < pattern.size(); i += 3) {
- if (pattern[i] == '?') {
- bytes.push_back(0);
- mask.push_back('?');
- } else {
- bytes.push_back(std::stoi(pattern.substr(i, 2), nullptr, 16));
- mask.push_back('x');
- }
- }
-
- for (size_t i = 0; i < size; i++) {
- bool found = true;
- for (size_t j = 0; j < bytes.size(); j++) {
- if (mask[j] == '?' || bytes[j] == *(char *) (module_base + i + j)) {
- continue;
- }
- found = false;
- break;
- }
- if (found) {
- return module_base + i + offset;
- }
- }
- return 0;
- }
-}-
\ No newline at end of file
diff --git a/native/rust/Cargo.lock b/native/rust/Cargo.lock
@@ -0,0 +1,724 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_log-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
+
+[[package]]
+name = "android_logger"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826"
+dependencies = [
+ "android_log-sys",
+ "env_filter",
+ "log",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "bytes"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
+
+[[package]]
+name = "cc"
+version = "1.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "crc32fast"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "dobby-rs"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0b8fbf3688f3584f3f87ec7024032d81da6e8b868a1d23d8a6c0a399e7c9935"
+dependencies = [
+ "dobby-sys",
+ "thiserror",
+]
+
+[[package]]
+name = "dobby-sys"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cdffdeaa52be950db80677f22f549050675f226b77e97fdbe10bb2dd846ac7b"
+
+[[package]]
+name = "env_filter"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6dc8c8ff84895b051f07a0e65f975cf225131742531338752abfb324e4449ff"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "procfs"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4"
+dependencies = [
+ "bitflags",
+ "chrono",
+ "flate2",
+ "hex",
+ "lazy_static",
+ "procfs-core",
+ "rustix",
+]
+
+[[package]]
+name = "procfs-core"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29"
+dependencies = [
+ "bitflags",
+ "chrono",
+ "hex",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.204"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.204"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.120"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "snapenhance"
+version = "0.1.0"
+dependencies = [
+ "android_logger",
+ "dobby-rs",
+ "jni",
+ "log",
+ "nix",
+ "once_cell",
+ "paste",
+ "procfs",
+ "serde_json",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/native/rust/Cargo.toml b/native/rust/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "snapenhance"
+version = "0.1.0"
+authors = ["rhunk"]
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+android_logger = "0.14.1"
+dobby-rs = "0.1.0"
+jni = "0.21.1"
+log = "0.4.22"
+nix = { version = "0.29.0", features = ["fs"] }
+once_cell = "1.19.0"
+paste = "1.0.15"
+procfs = "0.16.0"
+serde_json = "1.0.120"
diff --git a/native/rust/build.rs b/native/rust/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("cargo:rustc-link-lib=static=c++");
+}+
\ No newline at end of file
diff --git a/native/rust/src/common.rs b/native/rust/src/common.rs
@@ -0,0 +1,49 @@
+use jni::{objects::GlobalRef, JavaVM};
+use once_cell::sync::{Lazy, OnceCell};
+
+use crate::mapped_lib::MappedLib;
+
+static NATIVE_LIB_INSTANCE: OnceCell<GlobalRef> = OnceCell::new();
+static JAVA_VM: OnceCell<usize> = OnceCell::new();
+
+pub static CLIENT_MODULE: Lazy<MappedLib> = Lazy::new(|| {
+ let mut client_module = MappedLib::new("libclient.so".into());
+
+ if let Err(error) = client_module.search() {
+ warn!("Unable to find libclient.so: {}", error);
+
+ client_module = MappedLib::new("split_config.arm".into());
+
+ if let Err(error) = client_module.search() {
+ panic!("Unable to find split_config.arm: {}", error);
+ }
+ }
+
+ client_module
+});
+
+
+pub fn set_native_lib_instance(instance: GlobalRef) {
+ NATIVE_LIB_INSTANCE.set(instance).expect("NativeLib instance already set");
+}
+
+pub fn native_lib_instance() -> GlobalRef {
+ NATIVE_LIB_INSTANCE.get().expect("NativeLib instance not set").clone()
+}
+
+pub fn set_java_vm(vm: *mut jni::sys::JavaVM) {
+ JAVA_VM.set(vm as usize).expect("JavaVM already set");
+}
+
+pub fn java_vm() -> JavaVM {
+ unsafe {
+ JavaVM::from_raw(*JAVA_VM.get().expect("JavaVM not set") as *mut jni::sys::JavaVM).expect("Failed to get JavaVM")
+ }
+}
+
+pub fn attach_jni_env(block: impl FnOnce(&mut jni::JNIEnv)) {
+ let jvm = java_vm();
+ let mut env: jni::AttachGuard = jvm.attach_current_thread().expect("Failed to attach to current thread");
+
+ block(&mut env);
+}+
\ No newline at end of file
diff --git a/native/rust/src/config.rs b/native/rust/src/config.rs
@@ -0,0 +1,54 @@
+use std::{error::Error, sync::Mutex};
+use jni::{objects::JObject, JNIEnv};
+use crate::util::get_jni_string;
+
+static NATIVE_CONFIG: Mutex<Option<NativeConfig>> = Mutex::new(None);
+
+pub fn native_config() -> NativeConfig {
+ NATIVE_CONFIG.lock().unwrap().as_ref().expect("NativeConfig not loaded").clone()
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct NativeConfig {
+ pub disable_bitmoji: bool,
+ pub disable_metrics: bool,
+ pub composer_hooks: bool,
+ pub custom_emoji_font_path: Option<String>,
+}
+
+impl NativeConfig {
+ fn new(env: &mut JNIEnv, obj: JObject) -> Result<Self, Box<dyn Error>> {
+ macro_rules! get_boolean {
+ ($field:expr) => {
+ env.get_field(&obj, $field, "Z")?.z()?
+ };
+ }
+
+ macro_rules! get_string {
+ ($field:expr) => {
+ match env.get_field(&obj, $field, "Ljava/lang/String;")?.l()? {
+ jstring => if !jstring.is_null() {
+ Some(get_jni_string(env, jstring.into())?)
+ } else {
+ None
+ },
+ }
+ };
+ }
+
+ Ok(Self {
+ disable_bitmoji: get_boolean!("disableBitmoji"),
+ disable_metrics: get_boolean!("disableMetrics"),
+ composer_hooks: get_boolean!("composerHooks"),
+ custom_emoji_font_path: get_string!("customEmojiFontPath"),
+ })
+ }
+}
+
+pub fn load_config(mut env: JNIEnv, _class: JObject, obj: JObject) {
+ NATIVE_CONFIG.lock().unwrap().replace(
+ NativeConfig::new(&mut env, obj).expect("Failed to load NativeConfig")
+ );
+
+ info!("Config loaded {:?}", native_config());
+}+
\ No newline at end of file
diff --git a/native/rust/src/hook.rs b/native/rust/src/hook.rs
@@ -0,0 +1,50 @@
+use std::sync::Mutex;
+
+pub static MUTEX: Mutex<()> = Mutex::new(());
+
+#[macro_export]
+macro_rules! def_hook {
+ ($func:ident, $ret:ty, | $($arg:ident : $arg_type:ty),* | $body:block) => {
+ paste::item! {
+ #[allow(non_upper_case_globals)]
+ static mut [<$func _original>]: std::option::Option<extern "C" fn($($arg_type),*) -> $ret> = None;
+
+ fn $func($($arg: $arg_type),*) -> $ret {
+ {
+ #[allow(unused_unsafe)]
+ unsafe {
+ $body
+ }
+ }
+ }
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! dobby_hook {
+ ($sym:expr, $hook:expr) => {
+ paste::item! {
+ unsafe {
+ if let Ok(_) = crate::hook::MUTEX.lock() {
+ if let Some(ptr) = dobby_rs::hook($sym, $hook as *mut std::ffi::c_void).ok().map(|x| x as *mut std::ffi::c_void) {
+ [<$hook _original>] = std::mem::transmute(ptr);
+ }
+ }
+ }
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! dobby_hook_sym {
+ ($lib:expr, $sym:expr, $hook:expr) => {
+ if let Some(hook_symbol) = dobby_rs::resolve_symbol($lib, $sym) {
+ crate::dobby_hook!(hook_symbol, $hook);
+ debug!("hooked symbol: {}", $sym);
+ } else {
+ panic!("Failed to resolve symbol: {}", $sym);
+ }
+ };
+}
+
diff --git a/native/rust/src/lib.rs b/native/rust/src/lib.rs
@@ -0,0 +1,146 @@
+#[macro_use]
+extern crate log;
+
+mod common;
+
+mod hook;
+mod util;
+mod mapped_lib;
+mod config;
+mod sig;
+
+mod modules;
+
+use android_logger::Config;
+use log::LevelFilter;
+use modules::{composer_hook, duplex_hook, fstat_hook, linker_hook, sqlite_hook, unary_call_hook};
+
+use jni::objects::{JObject, JString};
+use jni::sys::{jint, jstring, JNI_VERSION_1_6};
+use jni::{JNIEnv, JavaVM, NativeMethod};
+use util::get_jni_string;
+
+use std::ffi::c_void;
+use std::thread::JoinHandle;
+
+fn pre_init() {
+ linker_hook::init();
+}
+
+fn init(mut env: JNIEnv, _class: JObject, signature_cache: JString) -> jstring {
+ debug!("Initializing native lib");
+
+ let start_time = std::time::Instant::now();
+
+ // load signature cache
+
+ if !signature_cache.is_null() {
+ let sig_cache_str = get_jni_string(&mut env, signature_cache).expect("Failed to convert mappings to string");
+
+ if let Ok(signature_cache) = serde_json::from_str(sig_cache_str.as_str()) {
+ sig::add_signatures(signature_cache);
+ } else {
+ error!("Failed to load signature cache");
+ }
+ }
+
+ common::set_native_lib_instance(env.new_global_ref(_class).ok().expect("Failed to create global ref"));
+
+ let _ = common::CLIENT_MODULE;
+
+ // initialize modules asynchronously
+
+ let mut threads: Vec<JoinHandle<()>> = Vec::new();
+
+ macro_rules! async_init {
+ ($($f:expr),*) => {
+ $(
+ threads.push(std::thread::spawn(move || {
+ $f;
+ }));
+ )*
+ };
+ }
+
+ async_init!(
+ duplex_hook::init(),
+ unary_call_hook::init(),
+ composer_hook::init(),
+ fstat_hook::init(),
+ sqlite_hook::init()
+ );
+
+ threads.into_iter().for_each(|t| t.join().unwrap());
+
+ info!("native init took {:?}", start_time.elapsed());
+
+ // send back the signature cache
+ if let Ok(signature_cache) = serde_json::to_string(&sig::get_signatures()) {
+ env.new_string(signature_cache).ok().expect("Failed to create new string").into_raw()
+ } else {
+ std::ptr::null_mut()
+ }
+}
+
+
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "system" fn JNI_OnLoad(_vm: JavaVM, _: *mut c_void) -> jint {
+ android_logger::init_once(
+ Config::default()
+ .with_max_level(LevelFilter::Debug)
+ .with_tag("SnapEnhanceNative")
+ );
+
+ info!("JNI_OnLoad called");
+
+ std::panic::set_hook(Box::new(|panic_info| {
+ error!("{:?}", panic_info);
+ }));
+
+ common::set_java_vm(_vm.get_java_vm_pointer());
+
+ let mut env = _vm.get_env().expect("Failed to get JNIEnv");
+
+ let native_lib_class = env.find_class("me/rhunk/snapenhance/nativelib/NativeLib").expect("NativeLib class not found");
+
+ env.register_native_methods(
+ native_lib_class,
+ &[
+ NativeMethod {
+ name: "init".into(),
+ sig: "(Ljava/lang/String;)Ljava/lang/String;".into(),
+ fn_ptr: init as *mut c_void,
+ },
+ NativeMethod {
+ name: "loadConfig".into(),
+ sig: "(Lme/rhunk/snapenhance/nativelib/NativeConfig;)V".into(),
+ fn_ptr: config::load_config as *mut c_void,
+ },
+ NativeMethod {
+ name: "addLinkerSharedLibrary".into(),
+ sig: "(Ljava/lang/String;[B)V".into(),
+ fn_ptr: linker_hook::add_linker_shared_library as *mut c_void,
+ },
+ NativeMethod {
+ name: "lockDatabase".into(),
+ sig: "(Ljava/lang/String;Ljava/lang/Runnable;)V".into(),
+ fn_ptr: sqlite_hook::lock_database as *mut c_void,
+ },
+ NativeMethod {
+ name: "setComposerLoader".into(),
+ sig: "(Ljava/lang/String;)V".into(),
+ fn_ptr: composer_hook::set_composer_loader as *mut c_void,
+ },
+ NativeMethod {
+ name: "composerEval".into(),
+ sig: "(Ljava/lang/String;)Ljava/lang/String;".into(),
+ fn_ptr: composer_hook::composer_eval as *mut c_void,
+ }
+ ]
+ ).expect("Failed to register native methods");
+
+ pre_init();
+
+ JNI_VERSION_1_6
+}
diff --git a/native/rust/src/mapped_lib.rs b/native/rust/src/mapped_lib.rs
@@ -0,0 +1,49 @@
+use std::error::Error;
+
+use procfs::process::{MMPermissions, MMapPath};
+
+#[derive(Debug)]
+pub(crate) struct MappedRegion {
+ pub start: u64,
+ pub end: u64,
+ pub perms: MMPermissions,
+}
+
+#[derive(Debug)]
+pub(crate) struct MappedLib {
+ name: String,
+ pub regions: Vec<MappedRegion>,
+}
+
+impl MappedLib {
+ pub fn new(name: String) -> Self {
+ Self {
+ name,
+ regions: Vec::new(),
+ }
+ }
+
+ pub fn search(&mut self) -> Result<&Self, Box<dyn Error>> {
+ procfs::process::Process::myself()?.maps()?.iter().for_each(|map| {
+ let pathname = &map.pathname;
+
+ if let MMapPath::Path(path_buffer) = pathname {
+ let path = path_buffer.to_string_lossy();
+
+ if path.contains(&self.name) {
+ self.regions.push(MappedRegion {
+ start: map.address.0,
+ end: map.address.1,
+ perms: map.perms,
+ });
+ }
+ }
+ });
+
+ if self.regions.is_empty() {
+ return Err(format!("No regions found for {}", self.name).into());
+ }
+
+ Ok(self)
+ }
+}
diff --git a/native/rust/src/modules/composer_hook.rs b/native/rust/src/modules/composer_hook.rs
@@ -0,0 +1,184 @@
+#![allow(dead_code, unused_mut)]
+
+use std::{cell::Cell, ffi::{c_void, CStr}, sync::Mutex};
+use jni::{objects::JString, sys::jobject, JNIEnv};
+use crate::{common, config, def_hook, dobby_hook, sig, util::get_jni_string};
+
+const JS_TAG_BIG_DECIMAL: i64 = -11;
+const JS_TAG_BIG_INT: i64 = -10;
+const JS_TAG_BIG_FLOAT: i64 = -9;
+const JS_TAG_SYMBOL: i64 = -8;
+const JS_TAG_STRING: i64 = -7;
+const JS_TAG_MODULE: i64 = -3;
+const JS_TAG_FUNCTION_BYTECODE: i64 = -2;
+const JS_TAG_OBJECT: i64 = -1;
+const JS_TAG_INT: i64 = 0;
+const JS_TAG_BOOL: i64 = 1;
+const JS_TAG_NULL: i64 = 2;
+const JS_TAG_UNDEFINED: i64 = 3;
+const JS_TAG_UNINITIALIZED: i64 = 4;
+const JS_TAG_CATCH_OFFSET: i64 = 5;
+const JS_TAG_EXCEPTION: i64 = 6;
+const JS_TAG_FLOAT64: i64 = 7;
+
+#[repr(C)]
+struct JsString {
+ /*
+ original structure :
+ struct JSString {
+ struct JSRefCountHeader {
+ int ref_count;
+ };
+ uint32_t len : 31;
+ uint8_t is_wide_char : 1;
+ uint32_t hash : 30;
+ uint8_t atom_type : 2;
+ uint32_t hash_next;
+
+ union {
+ uint8_t str8[0];
+ uint16_t str16[0];
+ } u;
+ };
+ */
+ pad: [u32; 4],
+ str8: [u8; 0],
+ str16: [u16; 0],
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+union JsValueUnion {
+ int32: i32,
+ float64: f64,
+ ptr: *mut c_void,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct JsValue {
+ u: JsValueUnion,
+ tag: i64,
+}
+
+static mut GLOBAL_INSTANCE: Option<*mut c_void> = None;
+static mut GLOBAL_CTX: Option<*mut c_void> = None;
+static COMPOSER_LOADER_DATA: Mutex<Cell<Option<Box<String>>>> = Mutex::new(Cell::new(None));
+
+static mut JS_EVAL_ORIGINAL2: Option<unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *mut u8, usize, *const u8, u32) -> JsValue> = None;
+
+def_hook!(
+ js_eval,
+ *mut c_void,
+ |arg0: *mut c_void, arg1: *mut c_void, arg2: *mut c_void, arg3: *const u8, arg4: *const u8, arg5: *const u8, arg6: *mut c_void, arg7: u32| {
+ let mut arg3 = arg3;
+ let mut arg4 = arg4;
+ let mut arg5 = arg5;
+
+ if GLOBAL_INSTANCE.is_none() || GLOBAL_CTX.is_none() {
+ GLOBAL_INSTANCE = Some(arg0);
+ GLOBAL_CTX = Some(arg1);
+
+ let mut loader_data = COMPOSER_LOADER_DATA.lock().unwrap();
+ let mut loader_data = loader_data.get_mut();
+
+ let loader_data = loader_data.as_mut().unwrap();
+ loader_data.push_str("\n");
+ #[cfg(target_arch = "aarch64")]
+ {
+ loader_data.push_str(CStr::from_ptr(arg3).to_str().unwrap());
+ arg3 = loader_data.as_mut_ptr();
+ arg4 = loader_data.len() as *const u8;
+ }
+
+ // On arm the original JS_Eval function is inlined so the arguments are shifted
+ #[cfg(target_arch = "arm")]
+ {
+ loader_data.push_str(CStr::from_ptr(arg4).to_str().unwrap());
+ arg4 = loader_data.as_mut_ptr();
+ arg5 = loader_data.len() as *const u8;
+ }
+
+ debug!("injected composer loader!");
+ } else {
+ COMPOSER_LOADER_DATA.lock().unwrap().take();
+ }
+
+ js_eval_original.unwrap()(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
+ }
+);
+
+pub fn set_composer_loader(mut env: JNIEnv, _: *mut c_void, code: JString) {
+ let new_code = get_jni_string(&mut env, code).expect("Failed to get code");
+
+ COMPOSER_LOADER_DATA.lock().unwrap().replace(Some(Box::new(new_code)));
+}
+
+#[allow(unreachable_code, unused_variables)]
+pub unsafe fn composer_eval(env: JNIEnv, _: *mut c_void, script: JString) -> jobject {
+ #[cfg(not(target_arch = "aarch64"))]
+ {
+ return env.new_string("Architecture not supported").unwrap().into_raw();
+ }
+
+ let mut env = env;
+
+ let script_str = get_jni_string(&mut env, script).expect("Failed to get script");
+ let script_length = script_str.len();
+
+ let js_value = JS_EVAL_ORIGINAL2.expect("No js eval found")(
+ GLOBAL_INSTANCE.expect("No global instance found"),
+ GLOBAL_CTX.expect("No global context found"),
+ std::ptr::null_mut(),
+ (script_str + "\0").as_ptr() as *mut u8,
+ script_length,
+ "<eval>\0".as_ptr(),
+ 0
+ );
+
+ let result: String = if js_value.tag == JS_TAG_STRING {
+ let string = js_value.u.ptr as *mut JsString;
+ CStr::from_ptr((*string).str8.as_ptr() as *const u8).to_str().unwrap().into()
+ } else if js_value.tag == JS_TAG_INT {
+ js_value.u.int32.to_string()
+ } else if js_value.tag == JS_TAG_BOOL {
+ if js_value.u.int32 == 1 { "true" } else { "false" }.into()
+ } else if js_value.tag == JS_TAG_NULL {
+ "null".into()
+ } else if js_value.tag == JS_TAG_UNDEFINED {
+ "undefined".into()
+ } else if js_value.tag == JS_TAG_OBJECT {
+ "[object]".into()
+ } else if js_value.tag == JS_TAG_FLOAT64 {
+ js_value.u.float64.to_string()
+ } else if js_value.tag == JS_TAG_EXCEPTION {
+ "Failed to evaluate script".into()
+ } else {
+ "[unknown tag ".to_owned() + &js_value.tag.to_string() + "]".into()
+ };
+
+ env.new_string(result).unwrap().into_raw()
+}
+
+pub fn init() {
+ if !config::native_config().composer_hooks {
+ return
+ }
+
+ if let Some(signature) = sig::find_signature(
+ &common::CLIENT_MODULE,
+ "00 E4 00 6F 29 00 80 52 76 00 04 8B", -0x28,
+ "A1 B0 07 92 81 46", -0x7
+ ) {
+ dobby_hook!(signature as *mut c_void, js_eval);
+
+ unsafe {
+ JS_EVAL_ORIGINAL2 = Some(std::mem::transmute(js_eval_original.unwrap()));
+ }
+
+ debug!("js_eval {:#x}", signature);
+ } else {
+ warn!("Unable to find js_eval signature");
+ }
+}
+
diff --git a/native/rust/src/modules/custom_font_hook.rs b/native/rust/src/modules/custom_font_hook.rs
diff --git a/native/rust/src/modules/duplex_hook.rs b/native/rust/src/modules/duplex_hook.rs
@@ -0,0 +1,41 @@
+use std::ffi::c_void;
+
+use jni::{objects::JObject, sys::jboolean, JNIEnv};
+
+use crate::{common, def_hook, dobby_hook, util::get_jni_string};
+
+
+def_hook!(
+ is_same_object,
+ jboolean,
+ |env: JNIEnv, obj1: JObject, obj2: JObject| {
+ let mut env = env;
+
+ if obj1.is_null() || obj2.is_null() {
+ return is_same_object_original.unwrap()(env, obj1, obj2);
+ }
+
+ let class = env.find_class("java/lang/Class").unwrap();
+
+ if !env.is_instance_of(&obj1, class).unwrap() {
+ return is_same_object_original.unwrap()(env, obj1, obj2);
+ }
+
+ let obj1_class_name = env.call_method(&obj1, "getName", "()Ljava/lang/String;", &[]).unwrap().l().unwrap().into();
+ let class_name = get_jni_string(&mut env, obj1_class_name).expect("Failed to get class name");
+
+ if class_name.contains("com.snapchat.client.duplex.MessageHandler") {
+ debug!("is_same_object hook: MessageHandler");
+ return 0;
+ }
+
+ is_same_object_original.unwrap()(env, obj1, obj2)
+ }
+);
+
+
+pub fn init() {
+ common::attach_jni_env(|env| {
+ dobby_hook!((**env.get_native_interface()).IsSameObject.unwrap() as *mut c_void, is_same_object);
+ });
+}+
\ No newline at end of file
diff --git a/native/rust/src/modules/fstat_hook.rs b/native/rust/src/modules/fstat_hook.rs
@@ -0,0 +1,37 @@
+
+use std::fs;
+
+use nix::libc;
+
+use crate::{config::{self, native_config}, def_hook, dobby_hook_sym};
+
+def_hook!(
+ fstat_hook,
+ i32,
+ |fd: i32, statbuf: *mut libc::stat| {
+ if let Ok(link) = fs::read_link("/proc/self/fd/".to_owned() + &fd.to_string()) {
+ if let Some(filename) = link.file_name().map(|t| t.to_string_lossy()) {
+ let config = native_config();
+ if config.disable_metrics && filename.contains("files/blizzardv2/queues") {
+ if libc::unlink((filename.to_owned() + "\0").as_ptr()) == -1 {
+ warn!("Failed to unlink {}", filename);
+ }
+ return -1;
+ }
+
+ if config.disable_bitmoji && filename.contains("com.snap.file_manager_4_SCContent") {
+ return -1;
+ }
+ }
+ }
+
+ fstat_hook_original.unwrap()(fd, statbuf)
+ }
+);
+
+pub fn init() {
+ let config = config::native_config();
+ if config.disable_metrics || config.disable_bitmoji {
+ dobby_hook_sym!("libc.so", "fstat", fstat_hook);
+ }
+}+
\ No newline at end of file
diff --git a/native/rust/src/modules/linker_hook.rs b/native/rust/src/modules/linker_hook.rs
@@ -0,0 +1,60 @@
+use std::{collections::HashMap, ffi::{c_void, CStr}, sync::Mutex};
+
+use jni::{objects::{JByteArray, JString}, JNIEnv};
+use nix::libc;
+use once_cell::sync::Lazy;
+
+use crate::{def_hook, dobby_hook_sym};
+
+static SHARED_LIBRARIES: Lazy<Mutex<HashMap<String, Box<Vec<i8>>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
+
+def_hook!(
+ linker_openat,
+ i32,
+ |dir_fd: i32, pathname: *mut u8, flags: i32, mode: i32| {
+ let pathname_str = CStr::from_ptr(pathname).to_str().unwrap().to_string();
+
+ if let Some(content) = SHARED_LIBRARIES.lock().unwrap().remove(&pathname_str) {
+ let memfd = libc::syscall(libc::SYS_memfd_create, "me.rhunk.snapenhance\0".as_ptr(), 0) as i32;
+ let content = content.into_boxed_slice();
+
+ if libc::write(memfd, content.as_ptr() as *const c_void, content.len() as libc::size_t) == -1 {
+ panic!("failed to write to memfd");
+ }
+
+ if libc::lseek(memfd, 0, libc::SEEK_SET) == -1 {
+ panic!("failed to seek memfd");
+ }
+
+ std::mem::forget(content);
+
+ info!("opened shared library: {}", pathname_str);
+ return memfd;
+ }
+
+ linker_openat_original.unwrap()(dir_fd, pathname, flags, mode)
+ }
+);
+
+pub fn add_linker_shared_library(mut env: JNIEnv, _: *mut c_void, path: JString, content: JByteArray) {
+ let path = env.get_string(&path).unwrap().to_str().unwrap().to_string();
+ let content_length = env.get_array_length(&content).expect("Failed to get array length");
+ let mut content_buffer = Box::new(vec![0i8; content_length as usize]);
+
+ env.get_byte_array_region(content, 0, content_buffer.as_mut_slice()).expect("Failed to get byte array region");
+
+ debug!("added shared library: {}", path);
+
+ SHARED_LIBRARIES.lock().unwrap().insert(path, content_buffer);
+}
+
+pub fn init() {
+ #[cfg(target_arch = "aarch64")]
+ {
+ dobby_hook_sym!("linker64", "__dl___openat", linker_openat);
+ }
+ #[cfg(target_arch = "arm")]
+ {
+ dobby_hook_sym!("linker", "__dl___openat", linker_openat);
+ }
+}
diff --git a/native/rust/src/modules/mod.rs b/native/rust/src/modules/mod.rs
@@ -0,0 +1,6 @@
+pub mod linker_hook;
+pub mod duplex_hook;
+pub mod sqlite_hook;
+pub mod fstat_hook;
+pub mod unary_call_hook;
+pub mod composer_hook;+
\ No newline at end of file
diff --git a/native/rust/src/modules/sqlite_hook.rs b/native/rust/src/modules/sqlite_hook.rs
@@ -0,0 +1,81 @@
+use std::{collections::HashMap, ffi::{c_void, CStr}, mem::size_of, ptr::addr_of_mut, sync::Mutex};
+
+use jni::{objects::{JObject, JString}, JNIEnv};
+use nix::libc::{self, pthread_mutex_t};
+use once_cell::sync::Lazy;
+
+use crate::{common, def_hook, dobby_hook, sig, util::get_jni_string};
+
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+struct Sqlite3Mutex {
+ mutex: pthread_mutex_t
+}
+
+#[repr(C)]
+struct Sqlite3 {
+ pad: [u8; 3 * size_of::<usize>()],
+ mutex: *mut Sqlite3Mutex
+}
+
+static SQLITE3_MUTEX_MAP: Lazy<Mutex<HashMap<String, pthread_mutex_t>>> = Lazy::new(|| Mutex::new(HashMap::new()));
+
+def_hook!(
+ sqlite3_open,
+ i32,
+ |filename: *const u8, pp_db: *mut *mut Sqlite3, flags: u32, z_vfs: *const i8| {
+ let result = sqlite3_open_original.unwrap()(filename, pp_db, flags, z_vfs);
+
+ if result == 0 {
+ let sqlite3_mutex = (**pp_db).mutex;
+
+ if sqlite3_mutex != std::ptr::null_mut() {
+ let filename = CStr::from_ptr(filename).to_string_lossy().to_string().split("/").last().expect("Failed to get filename").to_string();
+ debug!("sqlite3_open hook {:?}", filename);
+
+ SQLITE3_MUTEX_MAP.lock().unwrap().insert(
+ filename,
+ (*sqlite3_mutex).mutex
+ );
+ }
+ }
+
+ result
+ }
+);
+
+
+pub fn lock_database(mut env: JNIEnv, _: *mut c_void, filename: JString, runnable: JObject) {
+ let database_filename = get_jni_string(&mut env, filename).expect("Failed to get database filename");
+ let mutex = SQLITE3_MUTEX_MAP.lock().unwrap().get(&database_filename).map(|mutex| *mutex);
+
+ if let Some(mut mutex) = mutex {
+ if unsafe { libc::pthread_mutex_lock(addr_of_mut!(mutex)) } != 0 {
+ error!("pthread_mutex_lock failed");
+ return;
+ }
+
+ env.call_method(runnable, "run", "()V", &[]).expect("Failed to call run method");
+
+ if unsafe { libc::pthread_mutex_unlock(addr_of_mut!(mutex)) } != 0 {
+ error!("pthread_mutex_unlock failed");
+ }
+ } else {
+ warn!("No mutex found for database: {}", database_filename);
+ }
+}
+
+
+pub fn init() {
+ if let Some(signature) = sig::find_signature(
+ &common::CLIENT_MODULE,
+ "FF FF 00 A9 3F 00 00 F9", -0x3C,
+ "9A 46 90 46 78 44 89 46 05 68",-0xd
+ ) {
+ debug!("Found sqlite3_open signature: {:#x}", signature);
+ dobby_hook!(signature as *mut c_void, sqlite3_open);
+ } else {
+ warn!("Failed to find sqlite3_open signature");
+ }
+}+
\ No newline at end of file
diff --git a/native/rust/src/modules/unary_call_hook.rs b/native/rust/src/modules/unary_call_hook.rs
@@ -0,0 +1,123 @@
+use std::ffi::{c_void, CStr};
+
+use jni::{objects::{JByteArray, JMethodID, JValue}, signature::ReturnType};
+use nix::libc;
+use once_cell::sync::OnceCell;
+
+use crate::{common::{self}, def_hook, dobby_hook, sig};
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct RefCountedSliceByteBuffer {
+ ref_counter: *mut c_void,
+ length: usize,
+ data: *mut u8
+}
+
+#[repr(C)]
+struct GrpcByteBuffer {
+ reserved: *mut c_void,
+ type_: *mut c_void,
+ compression: *mut c_void,
+ slice_buffer: *mut RefCountedSliceByteBuffer
+}
+
+static NATIVE_LIB_ON_UNARY_CALL_METHOD: OnceCell<JMethodID> = OnceCell::new();
+
+def_hook!(
+ unary_call,
+ *mut c_void,
+ |unk1: *mut c_void, uri: *const u8, grpc_byte_buffer: *mut *mut GrpcByteBuffer, unk4: *mut c_void, unk5: *mut c_void, unk6: *mut c_void| {
+ macro_rules! call_original {
+ () => {
+ unary_call_original.unwrap()(unk1, uri, grpc_byte_buffer, unk4, unk5, unk6)
+ };
+ }
+
+ // make a local copy of the slice buffer
+ let mut slice_buffer = *(**grpc_byte_buffer).slice_buffer;
+
+ if slice_buffer.ref_counter.is_null() {
+ return call_original!();
+ }
+
+ let java_vm = common::java_vm();
+ let mut env = java_vm.get_env().expect("Failed to get JNIEnv");
+
+ let slice_buffer_length = slice_buffer.length as usize;
+ let jni_buffer = env.new_byte_array(slice_buffer_length as i32).expect("Failed to create new byte array");
+ env.set_byte_array_region(&jni_buffer, 0, std::slice::from_raw_parts(slice_buffer.data as *const i8, slice_buffer_length)).expect("Failed to set byte array region");
+
+ let uri_str = CStr::from_ptr(uri).to_str().unwrap();
+
+ let native_request_data_object = env.call_method_unchecked(
+ common::native_lib_instance(),
+ NATIVE_LIB_ON_UNARY_CALL_METHOD.get().unwrap(),
+ ReturnType::Object,
+ &[
+ JValue::from(&env.new_string(uri_str).unwrap()).as_jni(),
+ JValue::from(&jni_buffer).as_jni()
+ ]
+ ).expect("Failed to call onNativeUnaryCall method").l().unwrap();
+
+ if native_request_data_object.is_null() {
+ return call_original!();
+ }
+
+ let is_canceled = env.get_field(&native_request_data_object, "canceled", "Z").expect("Failed to get canceled field").z().unwrap();
+
+ if is_canceled {
+ info!("canceled request for {}", uri_str);
+ return std::ptr::null_mut();
+ }
+
+ let new_buffer: JByteArray = env.get_field(&native_request_data_object, "buffer", "[B").expect("Failed to get buffer field").l().unwrap().into();
+ let new_buffer_length = env.get_array_length(&new_buffer).expect("Failed to get array length") as usize;
+
+ let mut new_buffer_data = Box::new(vec![0i8; new_buffer_length]);
+ env.get_byte_array_region(&new_buffer, 0, new_buffer_data.as_mut_slice()).expect("Failed to get byte array region");
+
+ let ref_counter_struct_size = (slice_buffer.data as usize) - (slice_buffer.ref_counter as usize);
+
+ //we need to allocate a new ref_counter struct and copy the old ref_counter and the new_buffer to it
+ let new_ref = {
+ let new_ref = libc::malloc(ref_counter_struct_size + new_buffer_length) as *mut c_void;
+ libc::memcpy(new_ref, slice_buffer.ref_counter, ref_counter_struct_size);
+ libc::memcpy(new_ref.offset(ref_counter_struct_size as isize), new_buffer_data.as_ptr() as *const c_void, new_buffer_length);
+ libc::free(slice_buffer.ref_counter);
+ new_ref
+ };
+
+ slice_buffer.ref_counter = new_ref;
+ slice_buffer.length = new_buffer_length;
+ slice_buffer.data = new_ref.offset(ref_counter_struct_size as isize) as *mut u8;
+
+ // update the grpc byte buffer
+ *(**grpc_byte_buffer).slice_buffer = slice_buffer;
+
+ debug!("unary_call {}", uri_str);
+
+ call_original!()
+ }
+);
+
+pub fn init() {
+ if let Some(signature) = sig::find_signature(
+ &common::CLIENT_MODULE,
+ "A8 03 1F F8 C2 00 00 94", -0x48,
+ "0A 90 00 F0 3F F9", -0x37
+ ) {
+ dobby_hook!(signature as *mut c_void, unary_call);
+ common::attach_jni_env(|env| {
+ NATIVE_LIB_ON_UNARY_CALL_METHOD.set(
+ env.get_method_id(
+ env.get_object_class(common::native_lib_instance()).unwrap(),
+ "onNativeUnaryCall",
+ "(Ljava/lang/String;[B)Lme/rhunk/snapenhance/nativelib/NativeRequestData;"
+ ).expect("Failed to get onNativeUnaryCall method id")
+ ).expect("unary call method already set");
+ });
+ } else {
+ error!("Can't find unaryCall signature");
+ }
+}+
\ No newline at end of file
diff --git a/native/rust/src/sig.rs b/native/rust/src/sig.rs
@@ -0,0 +1,99 @@
+use std::sync::Mutex;
+
+use procfs::process::MMPermissions;
+
+use crate::mapped_lib::MappedLib;
+
+
+static SIGNATURE_CACHE: Mutex<Vec<(String, Vec<usize>)>> = Mutex::new(Vec::new());
+
+pub fn add_signatures(signatures: Vec<(String, Vec<usize>)>) {
+ SIGNATURE_CACHE.lock().unwrap().extend(signatures);
+}
+
+pub fn get_signatures() -> Vec<(String, Vec<usize>)> {
+ SIGNATURE_CACHE.lock().unwrap().clone()
+}
+
+pub fn find_signatures(module_base: usize, size: usize, pattern: &str, once: bool) -> Vec<usize> {
+ let mut results = Vec::new();
+ let mut bytes = Vec::new();
+ let mut mask = Vec::new();
+ let mut i = 0;
+
+ if let Some(cache) = SIGNATURE_CACHE.lock().unwrap().iter().find(|(sig, _)| sig == pattern) {
+ return cache.1.clone().into_iter().map(|offset| module_base + offset).collect();
+ }
+
+ while i < pattern.len() {
+ if pattern.chars().nth(i).unwrap() == '?' {
+ bytes.push(0);
+ mask.push('?');
+ } else {
+ bytes.push(u8::from_str_radix(&pattern[i..i+2], 16).unwrap());
+ mask.push('x');
+ }
+ i += 3;
+ }
+
+ let mut i = 0;
+ while i < size {
+ let mut found = true;
+ let mut j = 0;
+
+ while j < bytes.len() {
+ if mask[j] == '?' || bytes[j] == unsafe { *(module_base as *const u8).offset(i as isize + j as isize) } {
+ j += 1;
+ continue;
+ }
+ found = false;
+ break;
+ }
+ if found {
+ if once {
+ SIGNATURE_CACHE.lock().unwrap().push((pattern.to_string(), vec![i]));
+ return vec![module_base + i];
+ }
+ results.push(module_base + i);
+ }
+ i += 1;
+ }
+
+ SIGNATURE_CACHE.lock().unwrap().push((pattern.to_string(), results.clone()));
+ results
+}
+
+pub fn find_signature_executable(mapped_lib: &MappedLib, pattern: &str) -> Option<usize> {
+ let executable_regions = mapped_lib.regions.iter().filter(|region| {
+ region.perms.contains(MMPermissions::EXECUTE) && region.perms.contains(MMPermissions::READ)
+ }).collect::<Vec<_>>();
+
+ for region in executable_regions {
+ let size = (region.end - region.start) as usize;
+ let module_base = region.start as usize;
+
+ if size > 0 {
+ let results = find_signatures(module_base, size, pattern, true);
+
+ if results.is_empty() {
+ warn!("Signature not found in region: {:#x} - {:#x}", region.start, region.end);
+ } else {
+ debug!("Found {} results in region: {:#x} - {:#x}", results.len(), region.start, region.end);
+ return Some(results[0]);
+ }
+ }
+ }
+
+ None
+}
+
+pub fn find_signature(mapped_lib: &MappedLib, _arm64_pattern: &str, _arm64_offset: i64, _arm32_pattern: &str, _arm32_offset: i64) -> Option<usize> {
+ #[cfg(target_arch = "aarch64")]
+ {
+ return find_signature_executable(mapped_lib, _arm64_pattern).map(|address| (address as i64 + _arm64_offset) as usize);
+ }
+ #[cfg(target_arch = "arm")]
+ {
+ return find_signature_executable(mapped_lib, _arm32_pattern).map(|address| (address as i64 + _arm32_offset) as usize);
+ }
+}+
\ No newline at end of file
diff --git a/native/rust/src/util.rs b/native/rust/src/util.rs
@@ -0,0 +1,8 @@
+use std::error::Error;
+
+use jni::{objects::JString, JNIEnv};
+
+pub fn get_jni_string(env: &mut JNIEnv, obj: JString) -> Result<String, Box<dyn Error>> {
+ let string = env.get_string(&obj)?;
+ Ok(string.to_str()?.to_string())
+}
diff --git a/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt b/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt
@@ -5,9 +5,9 @@ import android.util.Log
import kotlin.math.absoluteValue
import kotlin.random.Random
-@Suppress("KotlinJniMissingFunction")
class NativeLib {
var nativeUnaryCallCallback: (NativeRequestData) -> Unit = {}
+ var signatureCache: String? = null
companion object {
var initialized = false
@@ -21,9 +21,7 @@ class NativeLib {
initialized = true
callback(this)
return@runCatching {
- if (!init()) {
- throw IllegalStateException("NativeLib init failed. Check logcat for more info")
- }
+ signatureCache = init(signatureCache) ?: throw IllegalStateException("NativeLib init failed. Check logcat for more info")
}
}.onFailure {
initialized = false
@@ -67,7 +65,7 @@ class NativeLib {
System.load(generatedPath)
}
- private external fun init(): Boolean
+ private external fun init(signatureCache: String?): String?
private external fun loadConfig(config: NativeConfig)
private external fun lockDatabase(name: String, callback: Runnable)
external fun setComposerLoader(code: String)