Android逆向

您所在的位置:网站首页 过签名校验的安装包是什么 Android逆向

Android逆向

2024-06-26 14:49| 来源: 网络整理| 查看: 265

1.什么是校验

是开发者在数据传送时采用的一种校正数据的一种方式 常见的校验有:签名校验(最常见)、dexcrc校验、apk完整性校验、路径文件校验等

2.什么是APK签名

通过对 Apk 进行签名,开发者可以证明对 Apk 的所有权和控制权,可用于安装和更新其应用。而在 Android 设备上的安装 Apk ,如果是一个没有被签名的 Apk,则会被拒绝安装。在安装 Apk 的时候,软件包管理器也会验证 Apk 是否已经被正确签名,并且通过签名证书和数据摘要验证是否合法没有被篡改。只有确认安全无篡改的情况下,才允许安装在设备上。

简单来说,APK 的签名主要作用有两个:

证明 APK 的所有者。允许 Android 市场和设备校验 APK 的正确性。

Android 目前支持以下四种应用签名方案: v1 方案:基于 JAR 签名。 v2 方案:APK 签名方案 v2(在 Android 7.0 中引入) v3 方案:APK 签名方案 v3(在 Android 9 中引入) v4 方案:APK 签名方案 v4(在 Android 11 中引入)

V1 签名的机制主要就在 META-INF 目录下的三个文件,MANIFEST.MF,ANDROID.SF,ANDROID.RSA,他们都是 V1 签名的产物。 (1)MANIFEST.MF:这是摘要文件。程序遍历Apk包中的所有文件(entry),对非文件夹非签名文件的文件,逐个用SHA1(安全哈希算法)生成摘要信息,再用Base64进行编码。如果你改变了apk包中的文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是程序就不能成功安装。

(2)ANDROID.SF:这是对摘要的签名文件。对前一步生成的MANIFEST.MF,使用SHA1-RSA算法,用开发者的私钥进行签名。在安装时只能使用公钥才能解密它。解密之后,将它与未加密的摘要信息(即,MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被异常修改。

(3)ANDROID.RSA文件中保存了公钥、所采用的加密算法等信息。

在某些情况下,直接对apk进行v1签名可以绕过apk的签名校验

v2方案会将 APK 文件视为 blob,并对整个文件进行签名检查。对 APK 进行的任何修改(包括对 ZIP 元数据进行的修改)都会使 APK 签名作废。这种形式的 APK 验证不仅速度要快得多,而且能够发现更多种未经授权的修改。

V3 签名(APK 签名方案 v3+): Android 9.0(API 级别 28)及更高版本引入的新签名方案。它是对 V2 签名的进一步增强。V3 签名采用了更强大的签名算法(基于 RSA 或 ECDSA)和更长的密钥长度,以提供更高的安全性。V3 签名与 V2 签名类似,仍然采用增量签名的方式,只对 APK 内容的部分进行签名。V3 签名在验证和完整性检查方面与 V2 签名相似,但它还提供了一些额外的安全特性,例如签名块的完整性保护和签名的附加时间戳。需要注意的是,V3 签名是可选的,应用可以同时包含 V1、V2 和 V3 三种签名方案。V3 签名的引入进一步提升了应用的安全性和验证机制,同时确保了对旧版 Android 设备的兼容性。开发者可以选择在构建应用时使用 V3 签名来提高应用的安全性水平。

V4 签名(APK 签名方案 v4+): Android 11.0 引入,用来支持 ADB 增量 APK 安装。v4 签名基于根据 APK 的所有字节计算得出的 Merkle 哈希树。它完全遵循 fs-verity 哈希树的结构(例如,对salt进行零填充,以及对最后一个分块进行零填充。)Android 11 将签名存储在单独的 [apk name].apk.idsig 文件中。v4 签名需要 v2 或 v3 签名作为补充。运行 adb install --incremental 命令时,adb 会要求 .apk.idsig 文件存在于 .apk 旁边。默认情况下,它还会使用 .idsig 文件尝试进行增量安装;如果此文件缺失或无效,该命令会回退到常规安装。

3.什么是签名校验

如何判断是否有签名校验? 不做任何修改,直接签名安装,应用闪退则说明大概率有签名校验

一般来说,普通的签名校验会导致软件的闪退,黑屏,卡启动页等

kill/killProcess-----kill/KillProcess()可以杀死当前应用活动的进程,这一操作将会把所有该进程内的资源(包括线程全部清理掉).当然,由于ActivityManager时刻监听着进程,一旦发现进程被非正常Kill,它将会试图去重启这个进程。这就是为什么,有时候当我们试图这样去结束掉应用时,发现它又自动重新启动的原因. system.exit-----杀死了整个进程,这时候活动所占的资源也会被释放。 finish----------仅仅针对Activity,当调用finish()时,只是将活动推向后台,并没有立即释放内存,活动的资源并没有被清理

还有一种是三角校验,就是so检测dex,动态加载的dex(在软件运行时会解压释放一段dex文件,检测完后就删除)检测so,dex检测动态加载的dex

普通获取签名校验代码:

private boolean SignCheck() { String trueSignMD5 = "d0add9987c7c84aeb7198c3ff26ca152"; String nowSignMD5 = ""; try { // 得到签名的MD5 PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(),PackageManager.GET_SIGNATURES); Signature[] signs = packageInfo.signatures; String signBase64 = Base64Util.encodeToString(signs[0].toByteArray()); nowSignMD5 = MD5Utils.MD5(signBase64); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return trueSignMD5.equals(nowSignMD5); }

系统将应用的签名信息封装在 PackageInfo 中,调用 PackageManager 的 getPackageInfo(String packageName, int flags) 即可获取指定包名的签名信息

4.签名校验对抗 所有网上公开的对抗方式最终都会被开发厂商应对,但对抗就是这样,有盾就有矛,一只矛攻破不了就换一只,或者同时用几只 方法一:核心破解插件,不签名安装应用 方法二:一键过签名工具,例如MT、NP、ARMPro、CNFIX、Modex的去除签名校验功能 方法三:具体分析签名校验逻辑(手撕签名校验) 方法四:io重定向--VA&SVC:ptrace+seccompSVC的TraceHook沙箱的实现&无痕Hook实现思路

方法一和方法二都是使用工具,这里就不多提了,网上很多教程

主要针对方法三和方法四展开说下

5.对抗方式

5.1 PMS代理

PackageManagerService(简称PMS),是Android系统核心服务之一,处理包管理相关的工作,常见的比如安装、卸载应用等。

HOOK PMS代码:

package com.zj.hookpms; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import android.content.Context; import android.content.pm.PackageManager; import android.util.Log; public class ServiceManagerWraper { public final static String ZJ = "ZJ595"; public static void hookPMS(Context context, String signed, String appPkgName, int hashCode) { try { // 获取全局的ActivityThread对象 Class activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 获取ActivityThread里面原始的sPackageManager Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager"); sPackageManagerField.setAccessible(true); Object sPackageManager = sPackageManagerField.get(currentActivityThread); // 准备好代{过}{滤}理对象, 用来替换原始的对象 Class iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager"); Object proxy = Proxy.newProxyInstance( iPackageManagerInterface.getClassLoader(), new Class[]{iPackageManagerInterface}, new PmsHookBinderInvocationHandler(sPackageManager, signed, appPkgName, 0)); // 1. 替换掉ActivityThread里面的 sPackageManager 字段 sPackageManagerField.set(currentActivityThread, proxy); // 2. 替换 ApplicationPackageManager里面的 mPM对象 PackageManager pm = context.getPackageManager(); Field mPmField = pm.getClass().getDeclaredField("mPM"); mPmField.setAccessible(true); mPmField.set(pm, proxy); } catch (Exception e) { Log.d(ZJ, "hook pms error:" + Log.getStackTraceString(e)); } } public static void hookPMS(Context context) { String Sign = "原包的签名信息"; hookPMS(context, Sign, "com.zj.hookpms", 0); } }

ActivityThread的静态变量sPackageManager ApplicationPackageManager对象里面的mPM变量

5.2 IO重定向

什么是IO重定向?

例:在读A文件的时候指向B文件

平头哥的核心代码Virtual Engine for Android(Support 12.0 in business version)

IO重定向可以干嘛?

1,可以让文件只读,不可写

2,禁止访问文件

3,路径替换

具体实现: 过签名检测(读取原包) 风控对抗(例:一个文件记录App启动的次数) 过Root检测,Xposed检测(文件不可取)

using namespace std; string packname; string origpath; string fakepath; int (*orig_open)(const char *pathname, int flags, ...); int (*orig_openat)(int,const char *pathname, int flags, ...); FILE *(*orig_fopen)(const char *filename, const char *mode); static long (*orig_syscall)(long number, ...); int (*orig__NR_openat)(int,const char *pathname, int flags, ...); void* (*orig_dlopen_CI)(const char *filename, int flag); void* (*orig_dlopen_CIV)(const char *filename, int flag, const void *extinfo); void* (*orig_dlopen_CIVV)(const char *name, int flags, const void *extinfo, void *caller_addr); static inline bool needs_mode(int flags) { return ((flags & O_CREAT) == O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE); } bool startsWith(string str, string sub){ return str.find(sub)==0; } bool endsWith(string s,string sub){ return s.rfind(sub)==(s.length()-sub.length()); } bool isOrigAPK(string path){ if(path==origpath){ return true; } return false; } //该函数的功能是在打开一个文件时进行拦截,并在满足特定条件时将文件路径替换为另一个路径 //fake_open 函数有三个参数: //pathname:一个字符串,表示要打开的文件的路径。 //flags:一个整数,表示打开文件的方式,例如只读、只写、读写等。 //mode(可选参数):一个整数,表示打开文件时应用的权限模式。 int fake_open(const char *pathname, int flags, ...) { mode_t mode = 0; if (needs_mode(flags)) { va_list args; va_start(args, flags); mode = static_cast(va_arg(args, int)); va_end(args); } //LOGI("open, path: %s, flags: %d, mode: %d",pathname, flags ,mode); string cpp_path= pathname; if(isOrigAPK(cpp_path)){ LOGI("libc_open, redirect: %s, --->: %s",pathname, fakepath.data()); return orig_open("/data/user/0/com.zj.wuaipojie/files/base.apk", flags, mode); } return orig_open(pathname, flags, mode); } //该函数的功能是在打开一个文件时进行拦截,并在满足特定条件时将文件路径替换为另一个路径 //fake_openat 函数有四个参数: //fd:一个整数,表示要打开的文件的文件描述符。 //pathname:一个字符串,表示要打开的文件的路径。 //flags:一个整数,表示打开文件的方式,例如只读、只写、读写等。 //mode(可选参数):一个整数,表示打开文件时应用的权限模式。 //openat 函数的作用类似于 open 函数,但是它使用文件描述符来指定文件路径,而不是使用文件路径本身。这样,就可以在打开文件时使用相对路径,而不必提供完整的文件路径。 //例如,如果要打开相对于当前目录的文件,可以使用 openat 函数,而不是 open 函数,因为 open 函数只能使用绝对路径。 // int fake_openat(int fd, const char *pathname, int flags, ...) { mode_t mode = 0; if (needs_mode(flags)) { va_list args; va_start(args, flags); mode = static_cast(va_arg(args, int)); va_end(args); } LOGI("openat, fd: %d, path: %s, flags: %d, mode: %d",fd ,pathname, flags ,mode); string cpp_path= pathname; if(isOrigAPK(cpp_path)){ LOGI("libc_openat, redirect: %s, --->: %s",pathname, fakepath.data()); return orig_openat(fd,fakepath.data(), flags, mode); } return orig_openat(fd,pathname, flags, mode); } FILE *fake_fopen(const char *filename, const char *mode) { string cpp_path= filename; if(isOrigAPK(cpp_path)){ return orig_fopen(fakepath.data(), mode); } return orig_fopen(filename, mode); } //该函数的功能是在执行系统调用时进行拦截,并在满足特定条件时修改系统调用的参数。 //syscall 函数是一个系统调用,是程序访问内核功能的方法之一。使用 syscall 函数可以调用大量的系统调用,它们用于实现操作系统的各种功能,例如打开文件、创建进程、分配内存等。 // static long fake_syscall(long number, ...) { void *arg[7]; va_list list; va_start(list, number); for (int i = 0; i < 7; ++i) { arg[i] = va_arg(list, void *); } va_end(list); if (number == __NR_openat){ const char *cpp_path = static_cast(arg[1]); LOGI("syscall __NR_openat, fd: %d, path: %s, flags: %d, mode: %d",arg[0] ,arg[1], arg[2], arg[3]); if (isOrigAPK(cpp_path)){ LOGI("syscall __NR_openat, redirect: %s, --->: %s",arg[1], fakepath.data()); return orig_syscall(number,arg[0], fakepath.data() ,arg[2],arg[3]); } } return orig_syscall(number, arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6]); } //函数的功能是获取当前应用的包名、APK 文件路径以及库文件路径,并将这些信息保存在全局变量中 //函数调用 GetObjectClass 和 GetMethodID 函数来获取 context 对象的类型以及 getPackageName 方法的 ID。然后,函数调用 CallObjectMethod 函数来调用 getPackageName 方法,获取当前应用的包名。最后,函数使用 GetStringUTFChars 函数将包名转换为 C 字符串,并将包名保存在 packname 全局变量中 //接着,函数使用 fakepath 全局变量保存了 /data/user/0//files/base.apk 这样的路径,其中 是当前应用的包名。 //然后,函数再次调用 GetObjectClass 和 GetMethodID 函数来获取 context 对象的类型以及 getApplicationInfo 方法的 ID。然后,函数调用 CallObjectMethod 函数来调用 getApplicationInfo 方法,获取当前应用的 ApplicationInfo 对象。 //它先调用 GetObjectClass 函数获取 ApplicationInfo 对象的类型,然后调用 GetFieldID 函数获取 sourceDir 字段的 ID。接着,函数使用 GetObjectField 函数获取 sourceDir 字段的值,并使用 GetStringUTFChars 函数将其转换为 C 字符串。最后,函数将 C 字符串保存在 origpath 全局变量中,表示当前应用的 APK 文件路径。 //最后,函数使用 GetFieldID 和 GetObjectField 函数获取 nativeLibraryDir 字段的值,并使用 GetStringUTFChars 函数将其转换为 C 字符串。函数最后调用 LOGI 函数打印库文件路径,但是并没有将其保存在全局变量中。 extern "C" JNIEXPORT void JNICALL Java_com_zj_wuaipojie_util_SecurityUtil_hook(JNIEnv *env, jclass clazz, jobject context) { jclass conext_class = env->GetObjectClass(context); jmethodID methodId_pack = env->GetMethodID(conext_class, "getPackageName", "()Ljava/lang/String;"); auto packname_js = reinterpret_cast(env->CallObjectMethod(context, methodId_pack)); const char *pn = env->GetStringUTFChars(packname_js, 0); packname = string(pn); env->ReleaseStringUTFChars(packname_js, pn); //LOGI("packname: %s", packname.data()); fakepath= "/data/user/0/"+ packname +"/files/base.apk"; jclass conext_class2 = env->GetObjectClass(context); jmethodID methodId_pack2 = env->GetMethodID(conext_class2,"getApplicationInfo","()Landroid/content/pm/ApplicationInfo;"); jobject application_info = env->CallObjectMethod(context,methodId_pack2); jclass pm_clazz = env->GetObjectClass(application_info); jfieldID package_info_id = env->GetFieldID(pm_clazz,"sourceDir","Ljava/lang/String;"); auto sourceDir_js = reinterpret_cast(env->GetObjectField(application_info,package_info_id)); const char *sourceDir = env->GetStringUTFChars(sourceDir_js, 0); origpath = string(sourceDir); LOGI("sourceDir: %s", sourceDir); jfieldID package_info_id2 = env->GetFieldID(pm_clazz,"nativeLibraryDir","Ljava/lang/String;"); auto nativeLibraryDir_js = reinterpret_cast(env->GetObjectField(application_info,package_info_id2)); const char *nativeLibraryDir = env->GetStringUTFChars(nativeLibraryDir_js, 0); LOGI("nativeLibraryDir: %s", nativeLibraryDir); //LOGI("%s", "Start Hook"); //启动hook void *handle = dlopen("libc.so",RTLD_NOW); auto pagesize = sysconf(_SC_PAGE_SIZE); auto addr = ((uintptr_t)dlsym(handle,"open") & (-pagesize)); auto addr2 = ((uintptr_t)dlsym(handle,"openat") & (-pagesize)); auto addr3 = ((uintptr_t)fopen) & (-pagesize); auto addr4 = ((uintptr_t)syscall) & (-pagesize); //解除部分机型open被保护 mprotect((void*)addr, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC); mprotect((void*)addr2, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC); mprotect((void*)addr3, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC); mprotect((void*)addr4, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC); DobbyHook((void *)dlsym(handle,"open"), (void *)fake_open, (void **)&orig_open); DobbyHook((void *)dlsym(handle,"openat"), (void *)fake_openat, (void **)&orig_openat); DobbyHook((void *)fopen, (void *)fake_fopen, (void**)&orig_fopen); DobbyHook((void *)syscall, (void *)fake_syscall, (void **)&orig_syscall); }

smali中校验代码前面插入下面的代码

sget-object p10, Lcom/zj/wuaipojie/util/ContextUtils;->INSTANCE:Lcom/zj/wuaipojie/util/ContextUtils; invoke-virtual {p10}, Lcom/zj/wuaipojie/util/ContextUtils;->getContext()Landroid/content/Context; move-result-object p10 invoke-static {p10}, Lcom/zj/wuaipojie/util/SecurityUtil;->hook(Landroid/content/Context;)V



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3