commit bf5031d0d0adf72c7ca65c6c57a7284dc038fb0d
parent 84a4f87fb1cc7498d92e9a468272fd80f5393f44
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Sun, 27 Aug 2023 21:09:48 +0200
feat(native): native grpc unaryCall hook
Diffstat:
5 files changed, 173 insertions(+), 17 deletions(-)
diff --git a/native/jni/src/grpc.h b/native/jni/src/grpc.h
@@ -0,0 +1,16 @@
+#pragma once
+
+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;
+}
diff --git a/native/jni/src/library.cpp b/native/jni/src/library.cpp
@@ -7,8 +7,10 @@
#include "logger.h"
#include "config.h"
#include "util.h"
+#include "grpc.h"
static native_config_t *native_config;
+static JavaVM *java_vm;
static auto fstat_original = (int (*)(int, struct stat *)) nullptr;
static int fstat_hook(int fd, struct stat *buf) {
@@ -20,13 +22,15 @@ static int fstat_hook(int fd, struct stat *buf) {
auto fileName = std::string(name);
//prevent blizzardv2 metrics
- if (native_config->disable_metrics && fileName.find("files/blizzardv2/queues") != std::string::npos) {
+ if (native_config->disable_metrics &&
+ fileName.find("files/blizzardv2/queues") != std::string::npos) {
unlink(name);
return -1;
}
//prevent bitmoji to load
- if (native_config->disable_bitmoji && fileName.find("com.snap.file_manager_4_SCContent") != std::string::npos) {
+ if (native_config->disable_bitmoji &&
+ fileName.find("com.snap.file_manager_4_SCContent") != std::string::npos) {
return -1;
}
@@ -34,12 +38,86 @@ static int fstat_hook(int fd, struct stat *buf) {
}
+static jobject native_lib_object;
+static jmethodID native_lib_on_unary_call_method;
+
+static auto unaryCall_original = (void *(*)(void *, const char *, grpc::grpc_byte_buffer **, void *,
+ void *, void *)) nullptr;
+
+static void *
+unaryCall_hook(void *unk1, const char *uri, grpc::grpc_byte_buffer **buffer_ptr, void *unk4,
+ void *unk5, void *unk6) {
+ auto slice_buffer = (*buffer_ptr)->slice_buffer;
+ // request without reference counter can be hooked using xposed ig
+ if (slice_buffer->ref_counter == nullptr) {
+ return unaryCall_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
+ }
+
+ auto env = (JNIEnv *) nullptr;
+ 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(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) {
+ return nullptr;
+ }
+
+ auto new_buffer = env->GetObjectField(native_request_data_object,
+ env->GetFieldID(native_request_data_class, "buffer",
+ "[B"));
+ auto new_buffer_length = env->GetArrayLength((jbyteArray) new_buffer);
+ auto new_buffer_data = env->GetByteArrayElements((jbyteArray) 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 auto ref_counter_struct_size =
+ (uintptr_t) slice_buffer->data - (uintptr_t) slice_buffer->ref_counter;
+
+ auto new_ref_counter = (void *) 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((void *) 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);
+
+ env->ReleaseByteArrayElements((jbyteArray) native_request_data_object, new_buffer_data, 0);
+ }
+
+ return unaryCall_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
+}
+
+
extern "C" JNIEXPORT void JNICALL
init(JNIEnv *env, jobject clazz, jobject classloader) {
LOGD("initializing native");
// config
native_config = new native_config_t;
+ // native lib object
+ native_lib_object = env->NewGlobalRef(clazz);
+ native_lib_on_unary_call_method = env->GetMethodID(env->GetObjectClass(clazz),
+ "onNativeUnaryCall",
+ "(Ljava/lang/String;[B)Lme/rhunk/snapenhance/nativelib/NativeRequestData;");
+
// load libclient.so
util::load_library(env, classloader, "client");
auto client_module = util::get_module("libclient.so");
@@ -53,15 +131,18 @@ init(JNIEnv *env, jobject clazz, jobject classloader) {
// hooks
DobbyHook((void *) DobbySymbolResolver("libc.so", "fstat"), (void *) fstat_hook,
(void **) &fstat_original);
+ //signature might change in the future (unstable for now)
+ DobbyHook((void *) util::find_signature(client_module.base, client_module.size,
+ "FD 7B BA A9 FC 6F 01 A9 FA 67 02 A9 F8 5F 03 A9 F6 57 04 A9 F4 4F 05 A9 FD 03 00 91 FF 43 13 D1"),
+ (void *) unaryCall_hook,
+ (void **) &unaryCall_original);
LOGD("native initialized");
}
-
-extern "C" JNIEXPORT void JNICALL
-loadConfig(JNIEnv *env, jobject clazz, jobject config_object) {
+void JNICALL load_config(JNIEnv *env, jobject clazz, 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"))
+#define GET_CONFIG_BOOL(name) env->GetBooleanField(config_object, env->GetFieldID(native_config_clazz, name, "Z"))
native_config->disable_bitmoji = GET_CONFIG_BOOL("disableBitmoji");
native_config->disable_metrics = GET_CONFIG_BOOL("disableMetrics");
@@ -72,13 +153,15 @@ loadConfig(JNIEnv *env, jobject clazz, jobject config_object) {
//jni onload
extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
+ java_vm = vm;
// register native methods
JNIEnv *env = nullptr;
vm->GetEnv((void **) &env, JNI_VERSION_1_6);
auto methods = std::vector<JNINativeMethod>();
methods.push_back({"init", "(Ljava/lang/ClassLoader;)V", (void *) init});
- methods.push_back({"loadConfig", "(Lme/rhunk/snapenhance/nativelib/NativeConfig;)V", (void *) loadConfig});
+ methods.push_back({"loadConfig", "(Lme/rhunk/snapenhance/nativelib/NativeConfig;)V",
+ (void *) load_config});
env->RegisterNatives(
env->FindClass("me/rhunk/snapenhance/nativelib/NativeLib"),
diff --git a/native/jni/src/util.h b/native/jni/src/util.h
@@ -1,4 +1,5 @@
#pragma once
+
#include <unistd.h>
#include <dlfcn.h>
#include <elf.h>
@@ -9,8 +10,8 @@ namespace util {
size_t size;
} ModuleInfo;
- static void hexDump(void* ptr, uint8_t line_length, uint32_t lines) {
- auto* p = (unsigned char*)ptr;
+ static void hexDump(void *ptr, uint8_t line_length, uint32_t lines) {
+ auto *p = (unsigned char *) ptr;
for (uint8_t i = 0; i < lines; i++) {
std::string line;
for (uint8_t j = 0; j < line_length; j++) {
@@ -23,12 +24,11 @@ namespace util {
}
}
- static ModuleInfo get_module(const char* libname)
- {
+ static ModuleInfo get_module(const char *libname) {
char path[256];
char buff[256];
int len_libname = strlen(libname);
- FILE* file;
+ FILE *file;
uintptr_t addr = 0;
size_t size = 0;
@@ -38,8 +38,8 @@ namespace util {
return {0, 0};
while (fgets(buff, sizeof buff, file) != NULL) {
- int len = strlen(buff);
- if (len > 0 && buff[len-1] == '\n') {
+ 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)) {
@@ -63,11 +63,42 @@ namespace util {
return {addr, size};
}
- void load_library(JNIEnv* env, jobject classLoader, const char* libName) {
+ void load_library(JNIEnv *env, jobject classLoader, const char *libName) {
auto runtimeClass = env->FindClass("java/lang/Runtime");
- auto getRuntimeMethod = env->GetStaticMethodID(runtimeClass, "getRuntime", "()Ljava/lang/Runtime;");
+ auto getRuntimeMethod = env->GetStaticMethodID(runtimeClass, "getRuntime",
+ "()Ljava/lang/Runtime;");
auto runtime = env->CallStaticObjectMethod(runtimeClass, getRuntimeMethod);
- auto loadLibraryMethod = env->GetMethodID(runtimeClass, "loadLibrary0", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V");
+ auto loadLibraryMethod = env->GetMethodID(runtimeClass, "loadLibrary0",
+ "(Ljava/lang/ClassLoader;Ljava/lang/String;)V");
env->CallVoidMethod(runtime, loadLibraryMethod, classLoader, env->NewStringUTF(libName));
}
+
+ uintptr_t find_signature(uintptr_t module_base, uintptr_t size, const std::string &pattern) {
+ 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;
+ }
+ }
+ return 0;
+ }
}
\ No newline at end of file
diff --git a/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt b/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt
@@ -1,11 +1,29 @@
package me.rhunk.snapenhance.nativelib
+import android.util.Log
+
class NativeLib {
+ var nativeUnaryCallCallback: (NativeRequestData) -> Unit = {}
+
fun initOnce(classloader: ClassLoader) {
System.loadLibrary("nativelib")
init(classloader)
}
+ @Suppress("unused")
+ private fun onNativeUnaryCall(uri: String, buffer: ByteArray): NativeRequestData? {
+ Log.d("SnapEnhance", "onNativeUnaryCall: uri=$uri, bufferSize=${buffer.size}, buffer=${buffer.contentToString()}")
+ val nativeRequestData = NativeRequestData(uri, buffer)
+ runCatching {
+ nativeUnaryCallCallback(nativeRequestData)
+ }.onFailure {
+ Log.e("SnapEnhance", "nativeUnaryCallCallback failed", it)
+ }
+ if (!nativeRequestData.buffer.contentEquals(buffer) || nativeRequestData.canceled) return nativeRequestData
+ return null
+ }
+
+
external fun init(classLoader: ClassLoader)
external fun loadConfig(config: NativeConfig)
}
\ No newline at end of file
diff --git a/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeRequestData.kt b/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeRequestData.kt
@@ -0,0 +1,7 @@
+package me.rhunk.snapenhance.nativelib
+
+data class NativeRequestData(
+ val uri: String,
+ var buffer: ByteArray,
+ var canceled: Boolean = false,
+)+
\ No newline at end of file