feat: security features
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
@@ -13,7 +13,6 @@ typedef struct {
|
||||
bool disable_bitmoji;
|
||||
bool disable_metrics;
|
||||
bool composer_hooks;
|
||||
bool remap_executable;
|
||||
char custom_emoji_font_path[256];
|
||||
} native_config_t;
|
||||
|
||||
|
||||
@@ -9,8 +9,5 @@ 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);
|
||||
if (common::native_config->remap_executable) {
|
||||
mprotect((void *)((uintptr_t) *original & PAGE_MASK), PAGE_SIZE, PROT_EXEC);
|
||||
}
|
||||
pthread_mutex_unlock(&hook_mutex);
|
||||
}
|
||||
54
native/jni/src/hooks/linker_hook.h
Normal file
54
native/jni/src/hooks/linker_hook.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#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"
|
||||
@@ -17,9 +18,6 @@
|
||||
bool JNICALL init(JNIEnv *env, jobject clazz) {
|
||||
LOGD("Initializing native");
|
||||
using namespace common;
|
||||
util::remap_sections([](const std::string &line, size_t size) {
|
||||
return line.find(BUILD_PACKAGE) != std::string::npos;
|
||||
}, native_config->remap_executable);
|
||||
|
||||
native_lib_object = env->NewGlobalRef(clazz);
|
||||
client_module = util::get_module("libclient.so");
|
||||
@@ -66,7 +64,6 @@ void JNICALL load_config(JNIEnv *env, jobject, jobject config_object) {
|
||||
native_config->disable_bitmoji = GET_CONFIG_BOOL("disableBitmoji");
|
||||
native_config->disable_metrics = GET_CONFIG_BOOL("disableMetrics");
|
||||
native_config->composer_hooks = GET_CONFIG_BOOL("composerHooks");
|
||||
native_config->remap_executable = GET_CONFIG_BOOL("remapExecutable");
|
||||
|
||||
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;"));
|
||||
@@ -97,15 +94,6 @@ void JNICALL lock_database(JNIEnv *env, jobject, jstring database_name, jobject
|
||||
}
|
||||
}
|
||||
|
||||
void JNICALL hide_anonymous_dex_files(JNIEnv *, jobject) {
|
||||
util::remap_sections([](const std::string &line, size_t size) {
|
||||
return (
|
||||
(common::native_config->remap_executable && size == PAGE_SIZE && line.find("r-xp 00000000 00") != std::string::npos && line.find("[v") == std::string::npos) ||
|
||||
line.find("dalvik-DEX") != std::string::npos ||
|
||||
line.find("dalvik-classes") != std::string::npos
|
||||
);
|
||||
}, common::native_config->remap_executable);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *_) {
|
||||
common::java_vm = vm;
|
||||
@@ -118,7 +106,9 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *_) {
|
||||
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({"hideAnonymousDexFiles", "()V", (void *)hide_anonymous_dex_files});
|
||||
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;
|
||||
|
||||
@@ -52,46 +52,6 @@ namespace util {
|
||||
return { start_offset, end_offset - start_offset };
|
||||
}
|
||||
|
||||
static void remap_sections(std::function<bool(const std::string &, size_t)> filter, bool remove_read_permission) {
|
||||
char buff[256];
|
||||
auto maps = fopen("/proc/self/maps", "rt");
|
||||
|
||||
while (fgets(buff, sizeof buff, maps) != NULL) {
|
||||
int len = strlen(buff);
|
||||
if (len > 0 && buff[len - 1] == '\n') buff[--len] = '\0';
|
||||
|
||||
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 (!filter(buff, end - start)) continue;
|
||||
|
||||
auto section_size = end - start;
|
||||
auto section_ptr = mmap(0, section_size, PROT_READ | PROT_EXEC | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
|
||||
if (section_ptr == MAP_FAILED) {
|
||||
LOGE("mmap failed: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(section_ptr, (void *)start, section_size);
|
||||
|
||||
if (mremap(section_ptr, section_size, section_size, MREMAP_MAYMOVE | MREMAP_FIXED, start) == MAP_FAILED) {
|
||||
LOGE("mremap failed: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
auto new_prot = (flags[0] == 'r' ? PROT_READ : 0) | (flags[1] == 'w' ? PROT_WRITE : 0) | (flags[2] == 'x' ? PROT_EXEC : 0);
|
||||
if (remove_read_permission && flags[0] == 'r' && flags[2] == 'x') {
|
||||
new_prot &= ~PROT_READ;
|
||||
}
|
||||
mprotect((void *)start, section_size, new_prot);
|
||||
}
|
||||
fclose(maps);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -8,7 +8,5 @@ data class NativeConfig(
|
||||
@JvmField
|
||||
val composerHooks: Boolean = false,
|
||||
@JvmField
|
||||
val remapExecutable: Boolean = false,
|
||||
@JvmField
|
||||
val customEmojiFontPath: String? = null,
|
||||
)
|
||||
@@ -1,6 +1,9 @@
|
||||
package me.rhunk.snapenhance.nativelib
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.random.Random
|
||||
|
||||
@Suppress("KotlinJniMissingFunction")
|
||||
class NativeLib {
|
||||
@@ -11,19 +14,21 @@ class NativeLib {
|
||||
private set
|
||||
}
|
||||
|
||||
fun initOnce(callback: NativeLib.() -> Unit) {
|
||||
fun initOnce(callback: NativeLib.() -> Unit): () -> Unit {
|
||||
if (initialized) throw IllegalStateException("NativeLib already initialized")
|
||||
runCatching {
|
||||
return runCatching {
|
||||
System.loadLibrary(BuildConfig.NATIVE_NAME)
|
||||
initialized = true
|
||||
callback(this)
|
||||
if (!init()) {
|
||||
throw IllegalStateException("NativeLib init failed. Check logcat for more info")
|
||||
return@runCatching {
|
||||
if (!init()) {
|
||||
throw IllegalStateException("NativeLib init failed. Check logcat for more info")
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
initialized = false
|
||||
Log.e("SnapEnhance", "NativeLib init failed", it)
|
||||
}
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@@ -54,10 +59,18 @@ class NativeLib {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
fun loadSharedLibrary(content: ByteArray) {
|
||||
if (!initialized) throw IllegalStateException("NativeLib not initialized")
|
||||
val generatedPath = "/data/app/${Random.nextLong().absoluteValue.toString(16)}.so"
|
||||
addLinkerSharedLibrary(generatedPath, content)
|
||||
System.load(generatedPath)
|
||||
}
|
||||
|
||||
private external fun init(): Boolean
|
||||
private external fun loadConfig(config: NativeConfig)
|
||||
private external fun lockDatabase(name: String, callback: Runnable)
|
||||
external fun setComposerLoader(code: String)
|
||||
external fun composerEval(code: String): String?
|
||||
external fun hideAnonymousDexFiles()
|
||||
private external fun addLinkerSharedLibrary(path: String, content: ByteArray)
|
||||
}
|
||||
Reference in New Issue
Block a user