feat: rust native

Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
rhunk 2024-07-26 01:54:43 +02:00
parent 17ddfa5b9c
commit 28d1ea2a43
42 changed files with 1803 additions and 762 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "dobby"]
path = native/jni/external/dobby
url = https://github.com/jmpews/Dobby

View File

@ -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 {

View File

@ -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")

View File

@ -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) {

View File

@ -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 {

View File

@ -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") {

View File

@ -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]

1
native/.gitignore vendored
View File

@ -1,2 +1,3 @@
/build
/.cxx
/rust/target

View File

@ -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
@ -54,3 +28,35 @@ android {
jvmTarget = "17"
}
}
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"))
}
}
}
}

View File

@ -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})

@ -1 +0,0 @@
Subproject commit b0176de574104726bb68dff3b77ee666300fc338

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}

View File

@ -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__)

View File

@ -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;
}
}

724
native/rust/Cargo.lock generated Normal file
View File

@ -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"

19
native/rust/Cargo.toml Normal file
View File

@ -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"

3
native/rust/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-link-lib=static=c++");
}

49
native/rust/src/common.rs Normal file
View File

@ -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);
}

54
native/rust/src/config.rs Normal file
View File

@ -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());
}

50
native/rust/src/hook.rs Normal file
View File

@ -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);
}
};
}

146
native/rust/src/lib.rs Normal file
View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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");
}
}

View File

@ -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);
});
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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");
}
}

View File

@ -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");
}
}

99
native/rust/src/sig.rs Normal file
View File

@ -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);
}
}

8
native/rust/src/util.rs Normal file
View File

@ -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())
}

View File

@ -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)