一、静态完整性校验

1、对Dex文件进行完整性校验

由于 Android 稍高版本在安装 APK 的过程中会把 classes.dex 抠出来转化为odex或者vdex等其他格式的优化文件,原 APK 中无 classes.dex,但是会存在除 classes.dex 外的其他多dex文件(比如classes2.dex、classes3.dex等),故无法校验 classes.dex,也无法校验整个 APK 文件哈希值。

通过读取 APK 包中的除 classes.dex 外的其他 dex 文件,并计算其 CRC 的值,将得到的 CRC 与原始 CRC 进行比较,判断是否被修改过,原始 CRC 值保存在资源文件或其他自定义文件中相关代码:

String apkPath=context.getPackageCodePath();//获取Apk的路径

ZipFile zipFile=new ZipFile(apkPath);

ZipEntry dexEntry=ZipFile.getEntry("classes.dex");//读取zip包中的classes.dex文件

String dexCRC=dexEntry.getCrc().toString();//计算dex的CRC文件值

对 Dex 文件的局部(可以是头部、代码段等等)求MD5值,并将MD5写入dex文件的末尾或其他不影响dex文件加载运行的位置(本人暂时还没碰到)

相关代码:

MessageDigest digest=MessageDigest.getInstance("MD5");

byte[] bytes=new byte[1024];

FileInputStream fileinputStream=new FileInputStream(new File(dexPath));

int byteCount;

while(fileinputStream.read(bytes)!=-1){

digest.update(bytes,0,byteCount);

}

BigInteger bigInteger=new BigInteger(1,digest.digest());//计算dex的哈希

String MD5=bigInteger.toString();//dex的哈希值

2、对Apk文件进行完整性校验(Android 5.0以下)

通过 APK 包的 MD5 摘要进行判断文件是否被修改过,这里需要计算 APK 的 MD5 值,然后将 MD5 上传到服务器进行判断,或者等待服务器下发原始文件的 MD5 值,进行比较判断。

相关代码:

String apkPath=context.getPackageCodePath();//获取Apk的路径

MessageDigest digest=MessageDigest.getInstance("MD5");

byte[] bytes=new byte[1024];

FileInputStream fileinputStream=new FileInputStream(new File(apkPath));//读取apk文件

int byteCount;

while(fileinputStream.read(bytes)!=-1){

digest.update(bytes,0,byteCount);

}

BigInteger bigInteger=new BigInteger(1,digest.digest());//计算apk的哈希

String MD5=bigInteger.toString();//apk的哈希值

3、签名信息校验

(1)通过 Android.content.pm.PackageInfo.getPacketInfo() 函数获取包信息,然后拿到签名信息。

(2)该签名信息为一个 byte 数组,可以转换成 X.509 格式的证书信息,或者直接对该签名信息求哈希。

(3)现阶段为了增加分析的难度,通常都会将部分代码放到.so文件中。

相关代码:

PackageInfo packageInfo=content.getPackageManager()

.getPackageInfo(content.getPackageName()

PackageManager.GET_SIGNATURES);//获取包信息

Signature[] signature=packageInfo.signatures;

Signature sign=signature[0];

MessageDigest digest=MessageDigest.getInstance("MD5");

digest.update(sign);

二、静态完整性校验apk的一些逆向思路

1、获取apk签名需要获取APK的路径

我们可以在获取APK路径的地方下断点。这样可以定位到相关校验代码。通过修改参数,传入一个官方APK路径,也可以绕过校验。

Context.getPackageCodePath() 用来获得当前应用程序对应的 apk 文件的路径:/data/app/包名/xxx.apk

Context.getPackageResourcePath() 获取该程序的安装包路径 : /data/app/包名/xxx.apk

packageInfo.applicationInfo.sourceDir 这里面也可以获取apk路径

2、在相关API下断

大部分应用在获取签名信息时都会调用系统API,我们可以在相关的API下断:

V1签名(Android 7.0以下):

android.content.pm.PackageInfo.getPacketInfo(ClassName,flags).signatures

这里需要注意 当flags为64的时候,该函数会获取签名信息。所以需要在getpackageinfo下断点,当flags为64的时候,就是获取签名信息。

V2签名(Android 7-9):

// 1.反射实例化PackageParser对象

Object packageParser = getPackageParser(path);

// 2.反射获取parsePackage方法

Object packageObject = getPackageInfo(path,packageParser);

// 3.调用collectCertificates方法

Method collectCertificatesMethod = packageParser.getClass(). getDeclaredMethod("collectCertificates",packageObject.getClass(),int.class);

collectCertificatesMethod.invoke(packageParser,packageObject,0);

// 4.获取mSignatures属性

Field signaturesField = packageObject.getClass().getDeclaredField("mSignatures");

signaturesField.setAccessible(true);

Signature[] mSignatures = (Signature[]) signaturesField.get(packageObject);

V3签名(Android 9及以上):

这几个是v3签名的系统验证函数

PackageManagerService.InstallPackageLI()

PackageParser.collectCertificates()

ApkSignatureVerifier.verify()

ApkSignatureSchemeV3Verifier.verify()

ApkSigningBlockUtils.findSignature()

ApkSigningBlockUtils.findApkSigningBlock()

ApkSigningBlockUtils.findApkSignatureSchemeBlock()

SignatureInfo.SignatureInfo()

ApkSignatureSchemeV3Verifier.verify()

ApkSignatureSchemeV3Verifier.verifySigner()

ApkSignatureSchemeV3Verifier.verifyAdditionalAttributes()

ApkSignatureSchemeV3Verifier.verifyProofOfRotationStruct()

ApkSignatureSchemeV3Verifier.VerifiedProofOfRotation()

3、可以在获取哈希相关的API处下断。

4、可以通过弹出窗口或者toast提示进行代码回溯,定位到签名校验的附近。

三、动态完整性

1、Xpose Hook 检测

(1)检测关键字

de.robv.android.xposed.XposedHelpers类的

静态fieldCache字段 保存被hook的字段信息

静态methodCache字段 保存被hook的方法信息

静态constructorCache字段 保存被hook的类信息

(2)检测内存

检测内存映射列表中是否包含如下文件:

XposedBridge.so

XposedBridge.jar

(3)检测方法的调用栈

handleHookMethod

invokeOriginalMethodNative

注:

(1)在dalvik.system.NativeStart.main 方法后出现 de.robv.android.xposed.XposedBridge.main的方法调用

(2)如果Xposed hook了调用栈里的一个方法,还会有de.robv.android.xposed.XposedBridge.handleHookedMethod和de.robv.android.xposed.XposedBridge.invokeOriginalMethodNative调用

(3)检测标记位de.robv.android.xposed.XposedBridge类,静态 disableHooks 成员变量,这个字段表示是否对当前应用进行hook操作。

2、Frida Hook 检测

(1)检测进程

FridaServer通过TCP与PC上的Frida进行通信,所以可以检测进程中是否存在 FridaServer进程

(2)检测端口

默认端口:27047

非默认端口:

A. 用nmap -sV来找到开放端口

B. 对每个开放端口发送D-Bus认证协议

C. 哪个端口回复了哪个就是FridaServer进程的端口

(3)检测内存

搜索内存是否存在以下两个文件

frida-gadget.so

frida-agent.so`

(4)暴力扫描

在映射的so文件中扫描Frida的库特征,例如:“gadgets”,“LIBFRIDA”等等,这 两个在Frida的所有版本中都有存在

3、Cydia Substrate Hook检测

(1)检测包名

检测设备安装目录是否存在com.saurik.substrate

(2)检测调用栈

com.android.internal.os.ZygoteInit

com.saurik.substrate.MS$2

注:

(1)在dalvik.system.NativeStart.main调用后会出现2次

com.android.internal.os.ZygoteInit.main,而不是一次。

(2)如果Substrate hook了调用栈里的一个方法,还会出现com.saurik.substrate.MS$2.invoked、

com.saurik.substrate.MS$MethodPointer.invoke和跟Substrate扩展相关的方法。

(3)检测内存

检测内存映射中是否存在com.saurik.substrate文件

4、动态完整性校验的一些逆向思路

(1)搜索字符串

例如:搜索 de.robv.android.xposed.XposedBridge 或者搜索上述字符串的base64编码后的值。

(2)获取堆栈的函数下断

大部分应用都是主动触发异常,然后获取堆栈信息,判断里面是否有相关hook的关键字,可以对 getStackTrace() 下断,这个是获取堆栈的API。

(3)对文件读取函数下断

如果获取内存,需要打开当前进程的 /proc文件,可以对文件读取相关的API下断,例如: new file 、new FildeReader。

(4)对相关的获取包名下断

函数 getPackageName() 获取相关的包名信息,判断参数里是否包含hook工具的包名。

本文由看雪论坛 陌殇 原创

声明:本文来自看雪学院,版权归作者所有。文章内容仅代表作者独立观点,不代表安全内参立场,转载目的在于传递更多信息。如有侵权,请联系 anquanneican@163.com。