mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-23 18:16:15 +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 "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"),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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