本贴适用于有一定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