/*
* 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 .
*
* Copyright (C) 2022 LSPosed Contributors
*/
//
// Created by Kotori2 on 2021/12/1.
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "slicer/reader.h"
#include "slicer/writer.h"
#include "obfuscation.h"
#include "logging.h"
using namespace lsplant;
namespace {
std::mutex init_lock{};
std::map signatures = {
{"Lde/robv/android/xposed/", ""},
{ "Landroid/app/AndroidApp", ""},
{ "Landroid/content/res/XRes", ""},
{ "Landroid/content/res/XModule", ""},
{ "Lorg/lsposed/lspd/core/", ""},
{ "Lorg/lsposed/lspd/nativebridge/", ""},
{ "Lorg/lsposed/lspd/service/", ""},
};
jclass class_file_descriptor;
jmethodID method_file_descriptor_ctor;
jclass class_shared_memory;
jmethodID method_shared_memory_ctor;
bool inited = false;
}
static std::string to_java(const std::string &signature) {
std::string java(signature, 1);
replace(java.begin(), java.end(), '/', '.');
return java;
}
void maybeInit(JNIEnv *env) {
if (inited) [[likely]] return;
std::lock_guard l(init_lock);
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, "", "(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, "", "(Ljava/io/FileDescriptor;)V");
auto regen = [](std::string_view original_signature) {
static auto& chrs = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
thread_local static std::mt19937 rg{std::random_device{}()};
thread_local static std::uniform_int_distribution pick(0, sizeof(chrs) - 2);
thread_local static std::uniform_int_distribution choose_slash(0, 10);
std::string out;
size_t length = original_signature.size();
out.reserve(length);
out += "L";
for (size_t i = 1; i < length - 1; 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 - 2) { // and the last character
out += "/";
} else {
out += chrs[pick(rg)];
}
}
out += "/";
return out;
};
for (auto &i: signatures) {
i.second = regen(i.first);
LOGD("%s => %s", i.first.c_str(), i.second.c_str());
}
LOGD("ObfuscationManager init successfully");
inited = true;
}
// https://stackoverflow.com/questions/4844022/jni-create-hashmap with modifications
jobject stringMapToJavaHashMap(JNIEnv *env, const decltype(signatures)& map) {
jclass mapClass = env->FindClass("java/util/HashMap");
if(mapClass == nullptr)
return nullptr;
jmethodID init = env->GetMethodID(mapClass, "", "()V");
jobject hashMap = env->NewObject(mapClass, init);
jmethodID put = env->GetMethodID(mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
auto citr = map.begin();
for( ; citr != map.end(); ++citr) {
jstring keyJava = env->NewStringUTF(citr->first.c_str());
jstring valueJava = env->NewStringUTF(citr->second.c_str());
env->CallObjectMethod(hashMap, put, keyJava, valueJava);
env->DeleteLocalRef(keyJava);
env->DeleteLocalRef(valueJava);
}
auto hashMapGobal = static_cast(env->NewGlobalRef(hashMap));
env->DeleteLocalRef(hashMap);
env->DeleteLocalRef(mapClass);
return hashMapGobal;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_org_lsposed_lspd_service_ObfuscationManager_getSignatures(JNIEnv *env, [[maybe_unused]] jclass obfuscation_manager) {
maybeInit(env);
static jobject signatures_jni = nullptr;
if (signatures_jni) return signatures_jni;
decltype(signatures) signatures_java;
for (const auto &i: signatures) {
signatures_java[to_java(i.first)] = to_java(i.second);
}
signatures_jni = stringMapToJavaHashMap(env, signatures_java);
return signatures_jni;
}
static int obfuscateDex(const void *dex, size_t size) {
// const char* new_sig = obfuscated_signature.c_str();
dex::Reader reader{reinterpret_cast(dex), size};
reader.CreateFullIr();
auto ir = reader.GetIr();
for (auto &i: ir->strings) {
const char *s = i->c_str();
for (const auto &signature: signatures) {
char* p = const_cast(strstr(s, signature.first.c_str()));
if (p) {
auto new_sig = signature.second.c_str();
// NOLINTNEXTLINE bugprone-not-null-terminated-result
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 jobject
Java_org_lsposed_lspd_service_ObfuscationManager_obfuscateDex(JNIEnv *env, [[maybe_unused]] jclass obfuscation_manager,
jobject memory) {
maybeInit(env);
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();
}