找回密码
 立即注册

抖音登录

使用抖音一键登录

QQ登录

只需一步,快速开始

微信登录

只需一步,快速开始

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

[复制链接]
发表于 2026-2-11 13:43:47 来自手机 | 显示全部楼层 |阅读模式
本贴适用于有一定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[0] = 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[0] = 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[512];
    FILE                    *fp;
    uintptr_t                base_addr;
    char                     perm[5];
    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, "%"RIxPTR"-%*lx %4s %lx %*x:%*x %*d%n",
                  &base_addr, perm, &offset, &pathname_pos) != 3)
            continue;

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

        // 获取路径名
        while(isspace(line[pathname_pos]) && 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_len - 1] == '
') {
            pathname[pathname_len - 1] = '';
            pathname_len -= 1;
        }
        if(0 == pathname_len) continue;
        if('[' == pathname[0]) continue;

        // 检查是否为可执行映射且需要Hook
        if (perm[2] == '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);
            }
        }
    }
}

写完了如果你前面的都没看懂,我只想告诉你,mt的去签在Java层是“欺骗”了API,在native层是通过Hook open、openat 等系统调用,拦截并重定向了所有访问原始APK文件的请求,使其指向一个携带伪造签名的副本。此外,根据楼主先前的测试(可能不适用于最新的去签功能),mt的去签对PC端安卓模拟器无效。
免责声明: 本站部分文章来自互联网收集,仅供用于学习和交流/测试,请遵循相关法律法规,本站一切资源不代表第柒论坛立场,如有侵权/违规/后门/不妥请联系本站管理员删除。敬请谅解!侵删请致信E-mail:ailm@hlye.cn
回复

使用道具 举报

懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
高级模式
B Color Image Link Quote Code Smilies |上传

本版积分规则

QQ|Archiver|手机版|小黑屋|网页地图|第柒网 ( 赣ICP备2026000926号 )

GMT+8, 2026-2-25 16:48

Powered by Discuz! X3.5

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表