feat(sigbypass): SVC Bypass (ARM64) and OpenAt I/O Redirection
導入 ARM64 專用的 SVC/Seccomp 內核級簽名繞過 (Level 3),並全面強化檔案 I/O 重定向機制。 移除非 ARM64 架構的支持
This commit is contained in:
parent
01071283c6
commit
271eb7bd82
|
|
@ -60,7 +60,7 @@ jobs:
|
|||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'adopt'
|
||||
distribution: 'zulu'
|
||||
|
||||
- name: 設定 Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
|
|
@ -76,7 +76,7 @@ jobs:
|
|||
- name: 設定 CMake
|
||||
uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: '4.0.3'
|
||||
cmake-version: '4.1.1'
|
||||
|
||||
- name: 移除 Android 的 cmake
|
||||
shell: bash
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ fun Project.configureBaseExtension() {
|
|||
arguments += "-DEXTERNAL_ROOT=${File(rootDir.absolutePath, "core/external")}"
|
||||
arguments += "-DCORE_ROOT=${File(rootDir.absolutePath,
|
||||
"core/core/src/main/jni")}"
|
||||
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||
abiFilters("arm64-v8a")
|
||||
val flags = arrayOf(
|
||||
"-Wall",
|
||||
"-Qunused-arguments",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
-keep class org.lsposed.npatch.Patcher$Options { *; }
|
||||
-keep class org.lsposed.npatch.share.LSPConfig { *; }
|
||||
-keep class org.lsposed.npatch.share.PatchConfig { *; }
|
||||
-keep class org.lsposed.lspd.nativebridge.** { *; }
|
||||
-keep class org.lsposed.npatch.loader.SigBypass { *; }
|
||||
-keepclassmembers class org.lsposed.patch.NPatch {
|
||||
private <fields>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
-keep class org.lsposed.npatch.Patcher$Options { *; }
|
||||
-keep class org.lsposed.npatch.share.LSPConfig { *; }
|
||||
-keep class org.lsposed.npatch.share.PatchConfig { *; }
|
||||
-keep class org.lsposed.lspd.nativebridge.** { *; }
|
||||
-keep class org.lsposed.npatch.loader.SigBypass { *; }
|
||||
-dontwarn com.google.auto.value.AutoValue$Builder
|
||||
-dontwarn com.google.auto.value.AutoValue
|
||||
-dontwarn com.squareup.moshi.**
|
||||
|
|
|
|||
|
|
@ -281,6 +281,7 @@ private fun sigBypassLvStr(level: Int) = when (level) {
|
|||
0 -> stringResource(R.string.patch_sigbypasslv0)
|
||||
1 -> stringResource(R.string.patch_sigbypasslv1)
|
||||
2 -> stringResource(R.string.patch_sigbypasslv2)
|
||||
3 -> stringResource(R.string.patch_sigbypasslv3)
|
||||
else -> throw IllegalArgumentException("Invalid sigBypassLv: $level")
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +385,7 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
|
|||
)
|
||||
}
|
||||
) {
|
||||
repeat(3) {
|
||||
repeat(4) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@
|
|||
<string name="patch_sigbypasslv0">lv0: 关闭</string>
|
||||
<string name="patch_sigbypasslv1">lv1: 绕过 PM</string>
|
||||
<string name="patch_sigbypasslv2">lv2: 绕过 PM + openat (libc)</string>
|
||||
<string name="patch_sigbypasslv3">lv3: 绕过 PM + openat (libc) + SVC (仅 arm64) (测试版)</string>
|
||||
<string name="patch_override_version_code">覆写版本号</string>
|
||||
<string name="patch_override_version_code_desc">将修补的 App 版本号重写为 1\n这将允许后续降级安装,并且通常来说这不会影响应用实际感知到的版本号</string>
|
||||
<string name="patch_new_package">修补新包名</string>
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@
|
|||
<string name="patch_sigbypasslv0">lv0: 關閉</string>
|
||||
<string name="patch_sigbypasslv1">lv1: 繞過 PM</string>
|
||||
<string name="patch_sigbypasslv2">lv2: 繞過 PM + openat (libc)</string>
|
||||
<string name="patch_sigbypasslv3">lv3: 繞過 PM + openat (libc) + SVC (僅 arm64) (測試用)</string>
|
||||
<string name="patch_override_version_code">覆蓋版本編號</string>
|
||||
<string name="patch_override_version_code_desc">將打包應用程式的版本編號改成 1\n允許以後降級安裝,一般來說,這不會影響應用程式實際感知的版本編號。</string>
|
||||
<string name="patch_new_package">修補新套件名</string>
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@
|
|||
<string name="patch_sigbypasslv0">lv0: Off</string>
|
||||
<string name="patch_sigbypasslv1">lv1: Bypass PM</string>
|
||||
<string name="patch_sigbypasslv2">lv2: Bypass PM + openat (libc)</string>
|
||||
<string name="patch_sigbypasslv3">lv3: Bypass PM + openat(libc) + SVC (v8a only) (testing)</string>
|
||||
<string name="patch_new_package">Patch New PackageName</string>
|
||||
<string name="hint_patch_new_package">Input a new package for app</string>
|
||||
<string name="patch_override_version_code">Override version code</string>
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ public class LSPAppComponentFactoryStub extends AppComponentFactory {
|
|||
|
||||
private static void bootstrap() {
|
||||
try {
|
||||
archToLib.put("arm", "armeabi-v7a");
|
||||
archToLib.put("arm64", "arm64-v8a");
|
||||
archToLib.put("x86", "x86");
|
||||
archToLib.put("x86_64", "x86_64");
|
||||
|
||||
var cl = Objects.requireNonNull(LSPAppComponentFactoryStub.class.getClassLoader());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package org.lsposed.lspd.nativebridge;
|
||||
|
||||
public class SigBypass {
|
||||
public static native void enableOpenatHook(String origApkPath, String cacheApkPath);
|
||||
public static native void enableOpenatHook(String patchedApkPath, String originalApkPath, String packageName);
|
||||
public static native void disableOpenatHook();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package org.lsposed.lspd.nativebridge;
|
||||
|
||||
public class SvcBypass {
|
||||
// 核心功能方法
|
||||
public static native boolean initSvcHook();
|
||||
public static native void enableSvcRedirect(String path, String orig, String pkg);
|
||||
public static native void disableSvcRedirect();
|
||||
|
||||
// 狀態檢查與除錯方法
|
||||
public static native boolean isSvcHookActive();
|
||||
public static native void logSvcHookStats();
|
||||
public static native String getDebugInfo();
|
||||
|
||||
// 進程與檔案描述符 (FD) 相關方法
|
||||
public static native int getCurrentPid();
|
||||
public static native int getInitialPid();
|
||||
public static native boolean isChildProcess();
|
||||
|
||||
public static native String checkFd(int fd);
|
||||
public static native int dupFd(int fd);
|
||||
public static native long getFdInode(int fd);
|
||||
public static native boolean isSystemFile(int fd);
|
||||
|
||||
// 系統 APK 與證書相關方法
|
||||
public static native int findSystemApkFd(String path);
|
||||
public static native String[][] getSystemApkFds();
|
||||
public static native void refreshSystemFds();
|
||||
|
||||
public static native byte[] readCertificateFromFd(int fd);
|
||||
public static native byte[] readCertificateFromPath(String path);
|
||||
}
|
||||
|
|
@ -15,15 +15,21 @@ import android.util.Log;
|
|||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import org.lsposed.npatch.loader.util.XLog;
|
||||
import org.lsposed.npatch.share.Constants;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.lsposed.lspd.nativebridge.SvcBypass;
|
||||
import org.lsposed.npatch.loader.util.XLog;
|
||||
import org.lsposed.npatch.share.Constants;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
|
|
@ -34,6 +40,7 @@ public class SigBypass {
|
|||
|
||||
private static final String TAG = "NPatch-SigBypass";
|
||||
private static final Map<String, String> signatures = new HashMap<>();
|
||||
private static String cachedOriginalApkPath;
|
||||
|
||||
private static void replaceSignature(Context context, PackageInfo packageInfo) {
|
||||
boolean hasSignature = (packageInfo.signatures != null && packageInfo.signatures.length != 0) || packageInfo.signingInfo != null;
|
||||
|
|
@ -145,17 +152,96 @@ public class SigBypass {
|
|||
}
|
||||
}
|
||||
|
||||
private static String extractOriginalApk(Context context) {
|
||||
File cacheDir = new File(context.getCacheDir(), "npatch/origin");
|
||||
if (!cacheDir.exists()) cacheDir.mkdirs();
|
||||
|
||||
try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) {
|
||||
ZipEntry entry = sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH);
|
||||
if (entry == null) {
|
||||
Log.e(TAG, "Original APK not found in assets!");
|
||||
return null;
|
||||
}
|
||||
|
||||
File targetFile = new File(cacheDir, entry.getCrc() + ".apk");
|
||||
if (targetFile.exists() && targetFile.length() == entry.getSize()) {
|
||||
return targetFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
try (InputStream is = sourceFile.getInputStream(entry);
|
||||
FileOutputStream fos = new FileOutputStream(targetFile)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
while ((length = is.read(buffer)) > 0) {
|
||||
fos.write(buffer, 0, length);
|
||||
}
|
||||
}
|
||||
return targetFile.getAbsolutePath();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to extract original APK", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void hookJavaIO(String currentApkPath, String originalApkPath) {
|
||||
XC_MethodHook redirectHook = new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
if (param.args.length > 0) {
|
||||
if (param.args[0] instanceof String) {
|
||||
String path = (String) param.args[0];
|
||||
if (path.equals(currentApkPath)) {
|
||||
param.args[0] = originalApkPath;
|
||||
}
|
||||
} else if (param.args[0] instanceof File) {
|
||||
File file = (File) param.args[0];
|
||||
if (file.getPath().equals(currentApkPath)) {
|
||||
param.args[0] = new File(originalApkPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
XposedBridge.hookAllConstructors(ZipFile.class, redirectHook);
|
||||
try {
|
||||
XposedBridge.hookAllConstructors(FileInputStream.class, redirectHook);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
static void doSigBypass(Context context, int sigBypassLevel) throws IOException {
|
||||
// Level 1: Java PMS Hook
|
||||
if (sigBypassLevel >= Constants.SIGBYPASS_LV_PM) {
|
||||
hookPackageParser(context);
|
||||
proxyPackageInfoCreator(context);
|
||||
}
|
||||
if (sigBypassLevel >= Constants.SIGBYPASS_LV_PM_OPENAT) {
|
||||
String cacheApkPath;
|
||||
try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) {
|
||||
cacheApkPath = context.getCacheDir() + "/npatch/origin/" + sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc() + ".apk";
|
||||
String currentApkPath = context.getPackageResourcePath();
|
||||
cachedOriginalApkPath = extractOriginalApk(context);
|
||||
|
||||
if (cachedOriginalApkPath != null) {
|
||||
// 1. Java Core stability
|
||||
hookJavaIO(currentApkPath, cachedOriginalApkPath);
|
||||
// 2. Native OpenAt Hook
|
||||
org.lsposed.lspd.nativebridge.SigBypass.enableOpenatHook(
|
||||
currentApkPath,
|
||||
cachedOriginalApkPath,
|
||||
context.getPackageName()
|
||||
);
|
||||
|
||||
// Level 3: SVC (Seccomp) Hook
|
||||
if (sigBypassLevel >= Constants.SIGBYPASS_LV_SVC) {
|
||||
if (SvcBypass.initSvcHook()) {
|
||||
SvcBypass.enableSvcRedirect(
|
||||
currentApkPath,
|
||||
cachedOriginalApkPath,
|
||||
context.getPackageName()
|
||||
);
|
||||
XLog.i(TAG, "SVC Hook enabled");
|
||||
} else {
|
||||
XLog.w(TAG, "SVC Hook failed to init");
|
||||
}
|
||||
}
|
||||
}
|
||||
org.lsposed.lspd.nativebridge.SigBypass.enableOpenatHook(context.getPackageResourcePath(), cacheApkPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ add_subdirectory(${CORE_ROOT} core)
|
|||
aux_source_directory(src SRC_LIST)
|
||||
aux_source_directory(src/jni SRC_LIST)
|
||||
set(SRC_LIST ${SRC_LIST} api/patch_main.cpp)
|
||||
set(SRC_LIST ${SRC_LIST} src/jni/bypass_sig.cpp)
|
||||
set(SRC_LIST ${SRC_LIST} src/jni/bypass_svc.cpp)
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED ${SRC_LIST})
|
||||
|
||||
|
|
@ -25,4 +27,4 @@ if (DEFINED DEBUG_SYMBOLS_PATH)
|
|||
COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:${PROJECT_NAME}>
|
||||
COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug
|
||||
$<TARGET_FILE:${PROJECT_NAME}>)
|
||||
endif()
|
||||
endif()
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// Created by VIP on 2021/4/25.
|
||||
// Update by HSSkyBoy on 2025/9/11
|
||||
// Modified by HSSkyBoy on 2025/12/15
|
||||
//
|
||||
|
||||
#include "bypass_sig.h"
|
||||
|
|
@ -13,17 +13,22 @@
|
|||
#include "utils/hook_helper.hpp"
|
||||
#include "utils/jni_helper.hpp"
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
using lsplant::operator""_sym;
|
||||
|
||||
namespace lspd {
|
||||
|
||||
std::string apkPath;
|
||||
std::string redirectPath;
|
||||
static std::string targetApkPath;
|
||||
static std::string redirectApkPath;
|
||||
static std::string currentPackageName;
|
||||
static void *openat_backup = nullptr;
|
||||
|
||||
inline static constexpr const char* kLibCName = "libc.so";
|
||||
|
||||
// 修改回傳型別以匹配 kImg 的實際型別
|
||||
// 修改回傳型別以匹配 kImg 的實際型別
|
||||
std::unique_ptr<SandHook::ElfImg> &GetC(bool release = false) {
|
||||
static auto kImg = std::make_unique<SandHook::ElfImg>(kLibCName);
|
||||
if (release) {
|
||||
|
|
@ -33,38 +38,49 @@ namespace lspd {
|
|||
return kImg;
|
||||
}
|
||||
|
||||
// OpenAt Hook 邏輯
|
||||
inline static auto __openat_ =
|
||||
"__openat"_sym.hook->*[]<lsplant::Backup auto backup>(int fd, const char *pathname, int flag,
|
||||
int mode) static -> int {
|
||||
if (pathname && strcmp(pathname, apkPath.c_str()) == 0) {
|
||||
return backup(fd, redirectPath.c_str(), flag, mode);
|
||||
if (pathname && !targetApkPath.empty() && strcmp(pathname, targetApkPath.c_str()) == 0) {
|
||||
return backup(fd, redirectApkPath.c_str(), flag, mode);
|
||||
}
|
||||
return backup(fd, pathname, flag, mode);
|
||||
};
|
||||
|
||||
static bool HookOpenat(const lsplant::HookHandler &handler) { return handler(__openat_); }
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(void, SigBypass, enableOpenatHook, jstring origApkPath,
|
||||
jstring cacheApkPath) {
|
||||
if (origApkPath == nullptr || cacheApkPath == nullptr) {
|
||||
LOGE("Invalid arguments: original or cache path is null.");
|
||||
LSP_DEF_NATIVE_METHOD(void, SigBypass, enableOpenatHook,
|
||||
jstring jOrigApkPath,
|
||||
jstring jCacheApkPath,
|
||||
jstring jPkgName) {
|
||||
|
||||
if (jOrigApkPath == nullptr || jCacheApkPath == nullptr) {
|
||||
LOGE("Invalid arguments: paths cannot be null.");
|
||||
return;
|
||||
}
|
||||
|
||||
lsplant::JUTFString str1(env, origApkPath);
|
||||
lsplant::JUTFString str2(env, cacheApkPath);
|
||||
lsplant::JUTFString strOrig(env, jOrigApkPath);
|
||||
lsplant::JUTFString strRedirect(env, jCacheApkPath);
|
||||
|
||||
apkPath = str1.get();
|
||||
redirectPath = str2.get();
|
||||
targetApkPath = strOrig.get();
|
||||
redirectApkPath = strRedirect.get();
|
||||
|
||||
if (jPkgName != nullptr) {
|
||||
lsplant::JUTFString strPkg(env, jPkgName);
|
||||
currentPackageName = strPkg.get();
|
||||
}
|
||||
|
||||
LOGI("Enable OpenAt Hook: %s -> %s (Pkg: %s)",
|
||||
targetApkPath.c_str(), redirectApkPath.c_str(), currentPackageName.c_str());
|
||||
|
||||
LOGI("Attempting to hook __openat (libc). Original: %s, Redirect: %s",
|
||||
apkPath.c_str(), redirectPath.c_str());
|
||||
|
||||
auto r = HookOpenat(lsplant::InitInfo{
|
||||
.inline_hooker =
|
||||
[](auto t, auto r) {
|
||||
void *bk = nullptr;
|
||||
return HookInline(t, r, &bk) == 0 ? bk : nullptr;
|
||||
int ret = HookInline(t, r, &bk);
|
||||
if (ret == 0) openat_backup = bk;
|
||||
return ret == 0 ? bk : nullptr;
|
||||
},
|
||||
.art_symbol_resolver = [](auto symbol) {
|
||||
return GetC()->getSymbAddress(symbol);
|
||||
|
|
@ -77,8 +93,17 @@ namespace lspd {
|
|||
GetC(true);
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(void, SigBypass, disableOpenatHook) {
|
||||
LOGI("Disable OpenAt Hook requested");
|
||||
targetApkPath.clear();
|
||||
redirectApkPath.clear();
|
||||
}
|
||||
|
||||
// 註冊 JNI 方法
|
||||
static JNINativeMethod gMethods[] = {
|
||||
LSP_NATIVE_METHOD(SigBypass, enableOpenatHook, "(Ljava/lang/String;Ljava/lang/String;)V")};
|
||||
LSP_NATIVE_METHOD(SigBypass, enableOpenatHook, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"),
|
||||
LSP_NATIVE_METHOD(SigBypass, disableOpenatHook, "()V")
|
||||
};
|
||||
|
||||
void RegisterBypass(JNIEnv *env) { REGISTER_LSP_NATIVE_METHODS(SigBypass); }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
namespace lspd {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,255 @@
|
|||
#include "bypass_svc.h"
|
||||
#include "logging.h"
|
||||
#include "native_util.h"
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <pthread.h>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <linux/seccomp.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/audit.h>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace lspd {
|
||||
|
||||
// --- 共用結構與變數 ---
|
||||
struct SyscallRequest {
|
||||
long sys_no;
|
||||
long args[6];
|
||||
long result;
|
||||
bool completed;
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
static pthread_t g_trusted_thread;
|
||||
static bool g_is_hook_active = false;
|
||||
static std::queue<SyscallRequest*> g_request_queue;
|
||||
static std::mutex g_queue_mtx;
|
||||
static std::condition_variable g_queue_cv;
|
||||
|
||||
// --- 核心功能實作 (ARM64 Only) ---
|
||||
|
||||
// 1. 影子線程循環
|
||||
static void* trusted_thread_loop(void* arg) {
|
||||
LOGD("SvcBypass: Trusted thread started (TID: %d)", gettid());
|
||||
while (true) {
|
||||
SyscallRequest* req = nullptr;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_queue_mtx);
|
||||
g_queue_cv.wait(lock, [] { return !g_request_queue.empty(); });
|
||||
req = g_request_queue.front();
|
||||
g_request_queue.pop();
|
||||
}
|
||||
|
||||
if (req) {
|
||||
// 執行真正的 syscall
|
||||
req->result = syscall(req->sys_no,
|
||||
req->args[0], req->args[1], req->args[2],
|
||||
req->args[3], req->args[4], req->args[5]);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(req->mtx);
|
||||
req->completed = true;
|
||||
}
|
||||
req->cv.notify_one();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 2. SIGSYS 信號處理器
|
||||
static void sigsys_handler(int signo, siginfo_t* info, void* context) {
|
||||
if (signo != SIGSYS) return;
|
||||
|
||||
ucontext_t* ctx = (ucontext_t*)context;
|
||||
SyscallRequest req;
|
||||
|
||||
// ARM64: 從 regs 讀取 (x8=sys_no, x0-x5=args)
|
||||
// 直接存取 ARM64 特有的 regs 結構
|
||||
req.sys_no = ctx->uc_mcontext.regs[8];
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
req.args[i] = ctx->uc_mcontext.regs[i];
|
||||
}
|
||||
req.completed = false;
|
||||
|
||||
LOGD("SvcBypass: Trapped syscall %ld (PID: %d)", req.sys_no, getpid());
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_queue_mtx);
|
||||
g_request_queue.push(&req);
|
||||
}
|
||||
g_queue_cv.notify_one();
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(req.mtx);
|
||||
req.cv.wait(lock, [&req] { return req.completed; });
|
||||
}
|
||||
|
||||
// 將結果寫回 x0
|
||||
ctx->uc_mcontext.regs[0] = req.result;
|
||||
}
|
||||
|
||||
// --- JNI 接口層 ---
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jboolean, SvcBypass, initSvcHook) {
|
||||
// 如果已經激活,直接返回成功
|
||||
if (g_is_hook_active) return JNI_TRUE;
|
||||
|
||||
int ret = pthread_create(&g_trusted_thread, nullptr, trusted_thread_loop, nullptr);
|
||||
if (ret != 0) {
|
||||
LOGE("SvcBypass: Failed to create trusted thread");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = sigsys_handler;
|
||||
sa.sa_flags = SA_SIGINFO | SA_NODEFER;
|
||||
if (sigaction(SIGSYS, &sa, nullptr) < 0) {
|
||||
LOGE("SvcBypass: Failed to register SIGSYS handler");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
g_is_hook_active = true;
|
||||
LOGI("SvcBypass: Initialized successfully (ARM64)");
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(void, SvcBypass, enableSvcRedirect,
|
||||
jstring path, jstring orig, jstring pkg) {
|
||||
if (!g_is_hook_active) {
|
||||
LOGW("SvcBypass: Hook not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
// ARM64 BPF 規則
|
||||
struct sock_filter filter[] = {
|
||||
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),
|
||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_openat, 0, 1),
|
||||
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRAP),
|
||||
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
|
||||
};
|
||||
|
||||
struct sock_fprog prog = {
|
||||
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
|
||||
.filter = filter,
|
||||
};
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
|
||||
LOGE("SvcBypass: prctl(NO_NEW_PRIVS) failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
|
||||
LOGE("SvcBypass: prctl(SECCOMP) failed");
|
||||
} else {
|
||||
LOGI("SvcBypass: Seccomp filter applied (ARM64)");
|
||||
}
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(void, SvcBypass, disableSvcRedirect) {
|
||||
LOGW("SvcBypass: Cannot disable Seccomp filters once applied.");
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jboolean, SvcBypass, isSvcHookActive) {
|
||||
return g_is_hook_active ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jstring, SvcBypass, getDebugInfo) {
|
||||
return env->NewStringUTF("SvcBypass: Active (ARM64)");
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jint, SvcBypass, getCurrentPid) {
|
||||
return getpid();
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jint, SvcBypass, getInitialPid) {
|
||||
return getpid();
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(void, SvcBypass, logSvcHookStats) {
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jboolean, SvcBypass, isChildProcess) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jstring, SvcBypass, checkFd, jint fd) {
|
||||
char path[512];
|
||||
char link[64];
|
||||
snprintf(link, sizeof(link), "/proc/self/fd/%d", fd);
|
||||
ssize_t len = readlink(link, path, sizeof(path) - 1);
|
||||
if (len != -1) {
|
||||
path[len] = '\0';
|
||||
return env->NewStringUTF(path);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jint, SvcBypass, dupFd, jint fd) {
|
||||
return dup(fd);
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jlong, SvcBypass, getFdInode, jint fd) {
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == 0) return (jlong)st.st_ino;
|
||||
return -1;
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jboolean, SvcBypass, isSystemFile, jint fd) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jint, SvcBypass, findSystemApkFd, jstring path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jobjectArray, SvcBypass, getSystemApkFds) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(void, SvcBypass, refreshSystemFds) {
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jbyteArray, SvcBypass, readCertificateFromFd, jint fd) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LSP_DEF_NATIVE_METHOD(jbyteArray, SvcBypass, readCertificateFromPath, jstring path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
LSP_NATIVE_METHOD(SvcBypass, initSvcHook, "()Z"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, enableSvcRedirect, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, disableSvcRedirect, "()V"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, isSvcHookActive, "()Z"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, logSvcHookStats, "()V"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, getDebugInfo, "()Ljava/lang/String;"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, getCurrentPid, "()I"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, getInitialPid, "()I"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, isChildProcess, "()Z"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, checkFd, "(I)Ljava/lang/String;"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, dupFd, "(I)I"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, getFdInode, "(I)J"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, isSystemFile, "(I)Z"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, findSystemApkFd, "(Ljava/lang/String;)I"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, getSystemApkFds, "()[[Ljava/lang/String;"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, refreshSystemFds, "()V"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, readCertificateFromFd, "(I)[B"),
|
||||
LSP_NATIVE_METHOD(SvcBypass, readCertificateFromPath, "(Ljava/lang/String;)[B"),
|
||||
};
|
||||
|
||||
void RegisterSvcBypass(JNIEnv *env) {
|
||||
REGISTER_LSP_NATIVE_METHODS(SvcBypass);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
#include <jni.h>
|
||||
|
||||
namespace lspd {
|
||||
|
||||
void RegisterSvcBypass(JNIEnv* env);
|
||||
|
||||
} // namespace lspd
|
||||
|
|
@ -27,6 +27,7 @@
|
|||
#include "art/runtime/oat_file_manager.h"
|
||||
#include "elf_util.h"
|
||||
#include "jni/bypass_sig.h"
|
||||
#include "jni/bypass_svc.h"
|
||||
#include "native_util.h"
|
||||
#include "symbol_cache.h"
|
||||
#include "utils/jni_helper.hpp"
|
||||
|
|
@ -78,6 +79,7 @@ namespace lspd {
|
|||
void PatchLoader::InitHooks(JNIEnv* env) {
|
||||
Context::InitHooks(env);
|
||||
RegisterBypass(env);
|
||||
RegisterSvcBypass(env);
|
||||
}
|
||||
|
||||
void PatchLoader::SetupEntryClass(JNIEnv* env) {
|
||||
|
|
|
|||
|
|
@ -313,6 +313,14 @@ public class NPatch {
|
|||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when adding dex", e);
|
||||
}
|
||||
if (sigbypassLevel >= Constants.SIGBYPASS_LV_PM_OPENAT) {
|
||||
logger.i("Embedding original apk for SigBypass...");
|
||||
try (var is = new FileInputStream(srcApkFile)) {
|
||||
dstZFile.add(Constants.ORIGINAL_APK_ASSET_PATH, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when embedding original apk", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (isInjectProvider){
|
||||
try (var is = getClass().getClassLoader().getResourceAsStream("assets/mtprovider.dex")) {
|
||||
|
|
@ -360,7 +368,16 @@ public class NPatch {
|
|||
if (!injectDex && name.startsWith("classes") && name.endsWith(".dex")) continue;
|
||||
if (name.equals("AndroidManifest.xml")) continue;
|
||||
if (name.startsWith("META-INF") && (name.endsWith(".SF") || name.endsWith(".MF") || name.endsWith(".RSA"))) continue;
|
||||
srcZFile.addFileLink(name, name);
|
||||
|
||||
try (InputStream is = entry.open()) {
|
||||
if (name.endsWith(".so")) {
|
||||
dstZFile.add(name, is, false);
|
||||
} else {
|
||||
dstZFile.add(name, is);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PatchError("Failed to copy entry: " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
dstZFile.realign();
|
||||
|
|
|
|||
|
|
@ -17,5 +17,5 @@ public class Constants {
|
|||
final static public int SIGBYPASS_LV_DISABLE = 0;
|
||||
final static public int SIGBYPASS_LV_PM = 1;
|
||||
final static public int SIGBYPASS_LV_PM_OPENAT = 2;
|
||||
final static public int SIGBYPASS_LV_MAX = 3;
|
||||
final static public int SIGBYPASS_LV_SVC = 3;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public class LSPConfig {
|
|||
public String VERSION_NAME;
|
||||
public int CORE_VERSION_CODE;
|
||||
public String CORE_VERSION_NAME;
|
||||
public int sigBypassLevel;
|
||||
|
||||
private LSPConfig() {
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue