海零梦 发表于 2026-2-11 13:43:47

Mt管理器去除签名校检分析

本贴适用于有一定Java,C基础的同学观看

基于https://github.com/L-JINBIN/ApkSignatureKillerEx/

一,总述
从整体架构上来说,ApkSignatureKillerEx(以下称mt去签)分为两层
第一层∶Java层,动态代理PackageManager,篡改签名返回
第二层∶native层,通过Hook文件系统调用重定向apk路径(谁说没有重定向的)

由于代码片段较长,以下详细分析采用楼层更新的模式进行更新

二,第一层∶Java层
2.1
初始化
public class KillerApplication extends Application {
    public static final String URL = "https://github.com/L-JINBIN/ApkSignatureKillerEx";

    static {
      String packageName = "bin.mt.signature";
      String signatureData = "MIICwzCCAaugAwIBAgIERUjRgzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdBbmRyb2lkMB4X
" +
                "DTIyMTIyNDE0NDkzMloXDTQ3MTIxODE0NDkzMlowEjEQMA4GA1UEAxMHQW5kcm9pZDCCASIwDQYJ
" +
                "KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKjVjd0eL4NPJW4uBR40hDkHtwdTQ7INP3hqgIs7U/kM
" +
                "gck2MtNIFSPYJVDZKwuLWgZLAKSDu9607indxUWftfTwJ9ynUfzoVq39+RAkRqe/XnL5WdLM0v5H
" +
                "CRtJW/nPBevunhJdoelCJJY1MsAl+WJCpSdfkkkeC0uXYeVYzCVwietIMfHEJOdEvjdXna0mdfuR
" +
                "1NqA85K8RGj9FLEdOKy0ZnMQbHzCp1/FwJSXpOqAuoKsttrmAji7FfsqXVRhk+dTBBGybCzVtaDH
" +
                "sIGyKzdsF2mKUPL3f0Q8XLKbkHRLmHGdVQlysIrrH7kn6Bx82cZTuYdPBUkrBO6w2NdMa+UCAwEA
" +
                "AaMhMB8wHQYDVR0OBBYEFNL0ebiSTntg/5Hcar3/MEUdlYRHMA0GCSqGSIb3DQEBCwUAA4IBAQBg
" +
                "v60JCBcJT+unHuVJge2wqEWjoUXV4JJG0Vn6kURbfiiC2rAtFOq6CFk+50HXyg2ZahosQ4ZPf8oT
" +
                "yG1/+JQaw9QUvB4TtwwdCr9i9IvAjjAFT6ariY0bOJNJvTjsmHJMptjNFQt4DPdveuknQv3Ztemb
" +
                "5BaxlpTegSZzL1ReOpKIygWf7qTqDnTtZsipt/OMttkn/dnhA9iiGJ5Jy+HLXQOc7+QgTYGPyAX5
" +
                "2IcWd9l5OrWShpflwsNHsAAU5MMAO/sWR/F/7zxKa50Ve67ta/7rUOkkcD3D0taUBsUeAo6n6rSs
" +
                "9Rk4tPEQRm59UJoof9cho7PxsMcTGb9UiNuJ
";
      killPM(packageName, signatureData);
      killOpen(packageName);
    }
全局初始化,在应用启动的最早阶段执行

2.2 killPM,动态代理PackageManager
依赖于Android的Parcel序列化机制,替换PackageInfo.CREATOR实现在反序列化过程中篡改
private static void killPM(String packageName, String signatureData) {
    // 将Base64伪造证书解码为Signature对象
    Signature fakeSignature = new Signature(Base64.decode(signatureData, Base64.DEFAULT));
   
    // 获取系统原始的CREATOR
    Parcelable.Creator originalCreator = PackageInfo.CREATOR;
   
    // 创建篡改后的CREATOR
    Parcelable.Creator creator = new Parcelable.Creator() {
      @Override
      public PackageInfo createFromParcel(Parcel source) {
            PackageInfo packageInfo = originalCreator.createFromParcel(source);
            
            // 仅篡改目标应用的PackageInfo
            if (packageInfo.packageName.equals(packageName)) {
                // Android 8.1及以下版本的签名存储位置
                if (packageInfo.signatures != null && packageInfo.signatures.length > 0) {
                  packageInfo.signatures = fakeSignature;
                }
               
                // Android 9.0及以上版本的签名存储位置
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                  if (packageInfo.signingInfo != null) {
                        Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners();
                        if (signaturesArray != null && signaturesArray.length > 0) {
                            signaturesArray = fakeSignature;
                        }
                  }
                }
            }
            return packageInfo;
      }

      @Override
      public PackageInfo[] newArray(int size) {
            return originalCreator.newArray(size);
      }
    };
   
    // 通过反射替换PackageInfo.CREATOR
    try {
      findField(PackageInfo.class, "CREATOR").set(null, creator);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
   
    片段过长见下楼层


// Android 9+的Hidden API绕过
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
      HiddenApiBypass.addHiddenApiExemptions("Landroid/os/Parcel;", "Landroid/content/pm", "Landroid/app");
    }
   
    // 清理PackageManager和Parcel的缓存确保生效
    try {
      Object cache = findField(PackageManager.class, "sPackageInfoCache").get(null);
      cache.getClass().getMethod("clear").invoke(cache);
    } catch (Throwable ignored) { }
   
    try {
      Map mCreators = (Map) findField(Parcel.class, "mCreators").get(null);
      mCreators.clear();
    } catch (Throwable ignored) { }
}
PackageInfo是通过Parcel传递的,应用调用PackageInfo的时候,系统通过Binder获取数据,序列化为Parcel,应用端再通过CREATOR反序列化,在反序列化的过程中篡改,使得读取的是fakeSignature

此外,还引入了getApkPath和isApkPath来定位apk路径,具体代码含违规字符,可查阅KillerApplication.java

三,第二层∶Native层路径重定向
主要是用jni连接Java层和Native层,这点很重要,因为楼主的手撕签名之旅遇到了无数大佬一眼就去查jni
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "xhook.h"
#include "xh_log.h"

// 全局变量存储路径
const char *apkPath__;
const char *repPath__;

// 原始函数指针声明
int (*old_open)(const char *, int, mode_t);
int (*old_open64)(const char *, int, mode_t);
int (*old_openat)(int, const char*, int, mode_t);
int (*old_openat64)(int, const char*, int, mode_t);

JNIEXPORT void JNICALL
Java_bin_mt_signature_KillerApplication_hookApkPath(
    JNIEnv *env,
    __attribute__((unused)) jclass clazz,
    jstring apkPath,
    jstring repPath) {
   
    // 转换Java字符串为C字符串
    apkPath__ = (*env)->GetStringUTFChars(env, apkPath, 0);
    repPath__ = (*env)->GetStringUTFChars(env, repPath, 0);

    // 注册对四个关键文件操作函数的Hook
    xhook_register(".*\.so$", "openat64", openat64Impl, (void **) &old_openat64);
    xhook_register(".*\.so$", "openat", openatImpl, (void **) &old_openat);
    xhook_register(".*\.so$", "open64", open64Impl, (void **) &old_open64);
    xhook_register(".*\.so$", "open", openImpl, (void **) &old_open);

    // 立即刷新使Hook生效
    xhook_refresh(0);
}

此外,用了四个hook(open,openat,open64,openat64),确保native层的去签,此处以open和openat为例
// Hook标准open函数
int (*old_open)(const char *, int, mode_t);
static int openImpl(const char *pathname, int flags, mode_t mode) {
    if (strcmp(pathname, apkPath__) == 0){
      return old_open(repPath__, flags, mode); // 路径重定向
    }
    return old_open(pathname, flags, mode);
}

// Hook openat函数
int (*old_openat)(int, const char*, int, mode_t);
static int openatImpl(int fd, const char *pathname, int flags, mode_t mode) {
    if (strcmp(pathname, apkPath__) == 0){
      return old_openat(fd, repPath__, flags, mode); // 路径重定向
    }
    return old_openat(fd, pathname, flags, mode);
}

xHook把以上的四个函数替换成了openImpl,openatImpl
4.1
static void xh_core_refresh_impl() {
    char                     line;
    FILE                  *fp;
    uintptr_t                base_addr;
    char                     perm;
    unsigned long            offset;
    char                  *pathname;
   
    // 打开/proc/self/maps
    if(NULL == (fp = fopen("/proc/self/maps", "r"))) {
      XH_LOG_ERROR("fopen /proc/self/maps failed");
      return;
    }

    // 逐行解析maps文件
    while(fgets(line, sizeof(line), fp)) {
      // 解析行内容
      if(sscanf(line, "%"PRIxPTR"-%*lx %4s %lx %*x:%*x %*d%n",
                  &base_addr, perm, &offset, &pathname_pos) != 3)
            continue;

      // 跳过非私有映射和不可访问的映射
      if (perm != 'p') continue;
      if (perm == '-' && perm == '-' && perm == '-')
            continue;

      // 获取路径名
      while(isspace(line) && pathname_pos = (int)(sizeof(line) - 1)) continue;
      
      pathname = line + pathname_pos;
      size_t pathname_len = strlen(pathname);
      if(0 == pathname_len) continue;
      
      // 处理换行符和特殊路径
      if(pathname == '
') {
            pathname = '';
            pathname_len -= 1;
      }
      if(0 == pathname_len) continue;
      if('[' == pathname) continue;

      // 检查是否为可执行映射且需要Hook
      if (perm == 'x') {
            // 检查ELF头部
            if(0 != xh_core_check_elf_header(base_addr, pathname))
                continue;
            
            // 创建或更新映射信息
            xh_core_map_info_t mi_key;
            mi_key.pathname = pathname;
            xh_core_map_info_t *mi = RB_FIND(xh_core_map_info_tree,
                                             &xh_core_map_info, &mi_key);
            
            if(NULL != mi) {
                // 已存在,检查基地址是否变化
                if(mi->base_addr != base_addr)

{
                  mi->base_addr = base_addr;
                  xh_core_hook(mi); // 重新Hook
                }
            } else {
                // 新映射,创建并Hook
                mi = malloc(sizeof(xh_core_map_info_t));
                mi->pathname = strdup(pathname);
                mi->base_addr = base_addr;
                xh_core_hook(mi); // 应用Hook
            }
      }
    }
    fclose(fp);
}

4.2 实际hook
static void xh_core_hook(xh_core_map_info_t *mi) {
    if(!xh_core_sigsegv_enable) {
      xh_core_hook_impl(mi);
    } else {   
      // 信号保护模式
      xh_core_sigsegv_flag = 1;
      if(0 == sigsetjmp(xh_core_sigsegv_env, 1)) {
            xh_core_hook_impl(mi);
      } else {
            XH_LOG_WARN("catch SIGSEGV when init or hook: %s", mi->pathname);
      }
      xh_core_sigsegv_flag = 0;
    }
}

static void xh_core_hook_impl(xh_core_map_info_t *mi) {
    // 初始化ELF信息
    if(0 != xh_elf_init(&(mi->elf), mi->base_addr, mi->pathname))
      return;
   
    // 遍历所有注册的Hook
    xh_core_hook_info_t   *hi;
    xh_core_ignore_info_t *ii;
    int ignore;
   
    TAILQ_FOREACH(hi, &xh_core_hook_info, link) {
      // 检查路径正则匹配
      if(0 == regexec(&(hi->pathname_regex), mi->pathname, 0, NULL, 0)) {
            ignore = 0;
            
            // 检查忽略列表
            TAILQ_FOREACH(ii, &xh_core_ignore_info, link) {
                if(0 == regexec(&(ii->pathname_regex), mi->pathname, 0, NULL, 0)) {
                  if(NULL == ii->symbol) { // 忽略所有符号
                        ignore = 1;
                        break;
                  }
                  
                  if(0 == strcmp(ii->symbol, hi->symbol)) { // 忽略特定符号
                        ignore = 1;
                        break;
                  }
                }
            }
            
            // 应用Hook
            if(0 == ignore) {
                xh_elf_hook(&(mi->elf), hi->symbol, hi->new_func, hi->old_func);
            }
      }
    }
}

写完了{:4_112:}如果你前面的都没看懂,我只想告诉你,mt的去签在Java层是“欺骗”了API,在native层是通过Hook open、openat 等系统调用,拦截并重定向了所有访问原始APK文件的请求,使其指向一个携带伪造签名的副本。此外,根据楼主先前的测试(可能不适用于最新的去签功能),mt的去签对PC端安卓模拟器无效。
页: [1]
查看完整版本: Mt管理器去除签名校检分析