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:
D.gitmodules | 3---
Mbuild.gradle.kts | 3++-
Mcommon/build.gradle.kts | 2+-
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/bridge/BridgeFiles.kt | 1+
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MappingsWrapper.kt | 7++++++-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt | 18++++++++++++++++++
Mgradle/libs.versions.toml | 2++
Mnative/.gitignore | 5+++--
Mnative/build.gradle.kts | 65+++++++++++++++++++++++++++++++++++------------------------------
Dnative/jni/CMakeLists.txt | 27---------------------------
Dnative/jni/external/dobby | 1-
Dnative/jni/src/common.h | 25-------------------------
Dnative/jni/src/dobby_helper.h | 14--------------
Dnative/jni/src/hooks/composer_hook.h | 190-------------------------------------------------------------------------------
Dnative/jni/src/hooks/custom_emoji_font.h | 21---------------------
Dnative/jni/src/hooks/duplex_hook.h | 27---------------------------
Dnative/jni/src/hooks/fstat_hook.h | 28----------------------------
Dnative/jni/src/hooks/linker_hook.h | 55-------------------------------------------------------
Dnative/jni/src/hooks/sqlite_mutex.h | 47-----------------------------------------------
Dnative/jni/src/hooks/unary_call.h | 88-------------------------------------------------------------------------------
Dnative/jni/src/library.cpp | 115-------------------------------------------------------------------------------
Dnative/jni/src/logger.h | 9---------
Dnative/jni/src/util.h | 84-------------------------------------------------------------------------------
Anative/rust/Cargo.lock | 724+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/Cargo.toml | 19+++++++++++++++++++
Anative/rust/build.rs | 4++++
Anative/rust/src/common.rs | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/config.rs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/hook.rs | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/lib.rs | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/mapped_lib.rs | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/modules/composer_hook.rs | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/modules/custom_font_hook.rs | 0
Anative/rust/src/modules/duplex_hook.rs | 42++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/modules/fstat_hook.rs | 38++++++++++++++++++++++++++++++++++++++
Anative/rust/src/modules/linker_hook.rs | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/modules/mod.rs | 7+++++++
Anative/rust/src/modules/sqlite_hook.rs | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/modules/unary_call_hook.rs | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/sig.rs | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/rust/src/util.rs | 8++++++++
Mnative/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt | 8+++-----
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)