LSPosed/daemon/src/main/cpp/obfuscation.cpp

213 lines
7.7 KiB
C++

/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2022 LSPosed Contributors
*/
//
// Created by Kotori2 on 2021/12/1.
//
#include <jni.h>
#include <unistd.h>
#include <algorithm>
#include <random>
#include <unordered_map>
#include <sys/mman.h>
#include <android/sharedmem.h>
#include <android/sharedmem_jni.h>
#include <slicer/dex_utf8.h>
#include <fcntl.h>
#include "slicer/reader.h"
#include "slicer/writer.h"
#include "obfuscation.h"
bool obfuscate_enabled(JNIEnv* env, jclass obfuscation_manager) {
auto method_enabled = JNI_GetStaticMethodID(env, obfuscation_manager, "enabled", "()Z");
auto result = JNI_CallStaticBooleanMethod(env, obfuscation_manager, method_enabled);
return result;
}
extern "C"
JNIEXPORT void JNICALL
Java_org_lsposed_lspd_service_ObfuscationManager_init(JNIEnv *env, jclass obfuscation_manager) {
if (!obfuscate_enabled(env, obfuscation_manager)) return;
LOGD("ObfuscationManager.init");
if (auto file_descriptor = JNI_FindClass(env, "java/io/FileDescriptor")) {
class_file_descriptor = JNI_NewGlobalRef(env, file_descriptor);
} else return;
method_file_descriptor_ctor = JNI_GetMethodID(env, class_file_descriptor, "<init>", "(I)V");
if (auto shared_memory = JNI_FindClass(env, "android/os/SharedMemory")) {
class_shared_memory = JNI_NewGlobalRef(env, shared_memory);
} else return;
method_shared_memory_ctor = JNI_GetMethodID(env, class_shared_memory, "<init>", "(Ljava/io/FileDescriptor;)V");
LOGD("ObfuscationManager init successfully");
}
extern "C"
JNIEXPORT jstring JNICALL
Java_org_lsposed_lspd_service_ObfuscationManager_getObfuscatedSignature(JNIEnv *env, jclass obfuscation_manager) {
if (!obfuscate_enabled(env, obfuscation_manager)) return env->NewStringUTF(old_signature.c_str());
if (!obfuscated_signature.empty()) return env->NewStringUTF(obfuscated_signature.c_str());
auto regen = []() {
static auto& chrs = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
thread_local static std::mt19937 rg{std::random_device{}()};
thread_local static std::uniform_int_distribution<std::string::size_type> pick(0, sizeof(chrs) - 2);
thread_local static std::uniform_int_distribution<std::string::size_type> choose_slash(0, 10);
std::string out;
size_t length = old_signature.size();
out.reserve(length);
out += "L";
for (size_t i = 1; i < length; i++) {
if (choose_slash(rg) > 8 && // 80% alphabet + 20% slashes
out[i - 1] != '/' && // slashes could not stick together
i != 1 && // the first character should not be slash
i != length - 1) { // and the last character
out += "/";
} else {
out += chrs[pick(rg)];
}
}
return out;
};
auto contains_keyword = [](std::string_view s) -> bool {
for (const auto &i: {
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class",
"continue", "const", "default", "do", "double", "else", "enum", "exports", "extends",
"final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof",
"int", "interface", "long", "module", "native", "new", "package", "private", "protected",
"public", "requires", "return", "short", "static", "strictfp", "super", "switch",
"synchronized", "this", "throw", "throws", "transient", "try", "var", "void", "volatile",
"while"}) {
if (s.find(i) != std::string::npos) return true;
}
return false;
};
do {
obfuscated_signature = regen();
} while (contains_keyword(obfuscated_signature));
LOGD("ObfuscationManager.getObfuscatedSignature: %s", obfuscated_signature.c_str());
return env->NewStringUTF(obfuscated_signature.c_str());
}
int obfuscateDex(const void *dex, size_t size) {
const char* new_sig = obfuscated_signature.c_str();
dex::Reader reader{reinterpret_cast<const dex::u1*>(dex), size};
reader.CreateFullIr();
auto ir = reader.GetIr();
for (auto &i: ir->strings) {
const char *s = i->c_str();
char* p = const_cast<char *>(strstr(s, old_signature.c_str()));
if (p) {
memcpy(p, new_sig, strlen(new_sig));
}
}
dex::Writer writer(ir);
size_t new_size;
WA allocator;
auto *p_dex = writer.CreateImage(&allocator, &new_size); // allocates memory only once
return allocator.GetFd(p_dex);
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_lsposed_lspd_service_ObfuscationManager_preloadDex(JNIEnv *env, jclass obfuscation_manager) {
using namespace std::string_literals;
std::lock_guard lg(dex_lock);
if (lspdDex != -1) return lspdDex;
std::string dex_path = "framework/lspd.dex";
std::unique_ptr<FILE, decltype(&fclose)> f{fopen(dex_path.data(), "rb"), &fclose};
if (!f) {
LOGE("Fail to open dex from %s", dex_path.data());
return -1;
}
fseek(f.get(), 0, SEEK_END);
size_t size = ftell(f.get());
rewind(f.get());
LOGD("Loaded %s with size %zu", dex_path.data(), size);
auto *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(f.get()), 0);
if (addr == MAP_FAILED) {
PLOGE("Map dex");
return -1;
}
int new_dex;
if (!obfuscate_enabled(env, obfuscation_manager)) {
new_dex = ASharedMemory_create("", size);
auto new_addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, new_dex, 0);
memcpy(new_addr, addr, size);
munmap(new_addr, size);
} else {
new_dex = obfuscateDex(addr, size);
}
munmap(addr, size);
LOGD("LSPApplicationService::preloadDex: %d, size=%zu", new_dex, ASharedMemory_getSize(new_dex));
lspdDex = new_dex;
return new_dex;
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_lsposed_lspd_service_ObfuscationManager_getPreloadedDexSize(JNIEnv *, jclass ) {
if (lspdDex != -1) {
return ASharedMemory_getSize(lspdDex);
}
return 0;
}
extern "C"
JNIEXPORT jobject
Java_org_lsposed_lspd_service_ObfuscationManager_obfuscateDex(JNIEnv *env, jclass obfuscation_manager,
jobject memory) {
if (!obfuscate_enabled(env, obfuscation_manager)) { return memory; }
int fd = ASharedMemory_dupFromJava(env, memory);
auto size = ASharedMemory_getSize(fd);
LOGD("fd=%d, size=%zu", fd, size);
const void* mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED) {
LOGE("old dex map failed?");
return nullptr;
}
auto new_fd = obfuscateDex(mem, size);
// construct new shared mem with fd
auto java_fd = JNI_NewObject(env, class_file_descriptor, method_file_descriptor_ctor, new_fd);
auto java_sm = JNI_NewObject(env, class_shared_memory, method_shared_memory_ctor, java_fd);
return java_sm.release();
}