feat(native): native grpc unaryCall hook

This commit is contained in:
rhunk 2023-08-27 21:09:48 +02:00
parent 84a4f87fb1
commit bf5031d0d0
5 changed files with 172 additions and 17 deletions

16
native/jni/src/grpc.h Normal file
View 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;
}

View File

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

View File

@ -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;
@ -39,7 +39,7 @@ namespace util {
while (fgets(buff, sizeof buff, file) != NULL) {
int len = strlen(buff);
if (len > 0 && buff[len-1] == '\n') {
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;
}
}

View File

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

View File

@ -0,0 +1,7 @@
package me.rhunk.snapenhance.nativelib
data class NativeRequestData(
val uri: String,
var buffer: ByteArray,
var canceled: Boolean = false,
)