mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-24 02:22:11 +02:00
feat(native): native grpc unaryCall hook
This commit is contained in:
parent
84a4f87fb1
commit
bf5031d0d0
16
native/jni/src/grpc.h
Normal file
16
native/jni/src/grpc.h
Normal file
@ -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;
|
||||||
|
}
|
@ -7,8 +7,10 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "grpc.h"
|
||||||
|
|
||||||
static native_config_t *native_config;
|
static native_config_t *native_config;
|
||||||
|
static JavaVM *java_vm;
|
||||||
|
|
||||||
static auto fstat_original = (int (*)(int, struct stat *)) nullptr;
|
static auto fstat_original = (int (*)(int, struct stat *)) nullptr;
|
||||||
static int fstat_hook(int fd, struct stat *buf) {
|
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);
|
auto fileName = std::string(name);
|
||||||
|
|
||||||
//prevent blizzardv2 metrics
|
//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);
|
unlink(name);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//prevent bitmoji to load
|
//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;
|
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
|
extern "C" JNIEXPORT void JNICALL
|
||||||
init(JNIEnv *env, jobject clazz, jobject classloader) {
|
init(JNIEnv *env, jobject clazz, jobject classloader) {
|
||||||
LOGD("initializing native");
|
LOGD("initializing native");
|
||||||
// config
|
// config
|
||||||
native_config = new native_config_t;
|
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
|
// load libclient.so
|
||||||
util::load_library(env, classloader, "client");
|
util::load_library(env, classloader, "client");
|
||||||
auto client_module = util::get_module("libclient.so");
|
auto client_module = util::get_module("libclient.so");
|
||||||
@ -53,13 +131,16 @@ init(JNIEnv *env, jobject clazz, jobject classloader) {
|
|||||||
// hooks
|
// hooks
|
||||||
DobbyHook((void *) DobbySymbolResolver("libc.so", "fstat"), (void *) fstat_hook,
|
DobbyHook((void *) DobbySymbolResolver("libc.so", "fstat"), (void *) fstat_hook,
|
||||||
(void **) &fstat_original);
|
(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");
|
LOGD("native initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void JNICALL load_config(JNIEnv *env, jobject clazz, jobject config_object) {
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
loadConfig(JNIEnv *env, jobject clazz, jobject config_object) {
|
|
||||||
auto native_config_clazz = env->GetObjectClass(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"))
|
||||||
|
|
||||||
@ -72,13 +153,15 @@ loadConfig(JNIEnv *env, jobject clazz, jobject config_object) {
|
|||||||
//jni onload
|
//jni onload
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
JNI_OnLoad(JavaVM *vm, void *reserved) {
|
JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||||
|
java_vm = vm;
|
||||||
// register native methods
|
// register native methods
|
||||||
JNIEnv *env = nullptr;
|
JNIEnv *env = nullptr;
|
||||||
vm->GetEnv((void **) &env, JNI_VERSION_1_6);
|
vm->GetEnv((void **) &env, JNI_VERSION_1_6);
|
||||||
|
|
||||||
auto methods = std::vector<JNINativeMethod>();
|
auto methods = std::vector<JNINativeMethod>();
|
||||||
methods.push_back({"init", "(Ljava/lang/ClassLoader;)V", (void *) init});
|
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->RegisterNatives(
|
||||||
env->FindClass("me/rhunk/snapenhance/nativelib/NativeLib"),
|
env->FindClass("me/rhunk/snapenhance/nativelib/NativeLib"),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <elf.h>
|
#include <elf.h>
|
||||||
@ -23,8 +24,7 @@ namespace util {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ModuleInfo get_module(const char* libname)
|
static ModuleInfo get_module(const char *libname) {
|
||||||
{
|
|
||||||
char path[256];
|
char path[256];
|
||||||
char buff[256];
|
char buff[256];
|
||||||
int len_libname = strlen(libname);
|
int len_libname = strlen(libname);
|
||||||
@ -65,9 +65,40 @@ namespace util {
|
|||||||
|
|
||||||
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 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 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));
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,11 +1,29 @@
|
|||||||
package me.rhunk.snapenhance.nativelib
|
package me.rhunk.snapenhance.nativelib
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
class NativeLib {
|
class NativeLib {
|
||||||
|
var nativeUnaryCallCallback: (NativeRequestData) -> Unit = {}
|
||||||
|
|
||||||
fun initOnce(classloader: ClassLoader) {
|
fun initOnce(classloader: ClassLoader) {
|
||||||
System.loadLibrary("nativelib")
|
System.loadLibrary("nativelib")
|
||||||
init(classloader)
|
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 init(classLoader: ClassLoader)
|
||||||
external fun loadConfig(config: NativeConfig)
|
external fun loadConfig(config: NativeConfig)
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package me.rhunk.snapenhance.nativelib
|
||||||
|
|
||||||
|
data class NativeRequestData(
|
||||||
|
val uri: String,
|
||||||
|
var buffer: ByteArray,
|
||||||
|
var canceled: Boolean = false,
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user