签名校验
签名校验
1.什么是校验
在安卓逆向中,校验通常指开发者通过代码逻辑对应用的关键信息(如签名、文件完整性、运行环境等)进行验证,确保应用未被篡改或破解。
- 常见的校验有
签名校验(Signature Check)
目的:验证APK的签名是否与开发者预期一致,防止应用被重新打包或篡改。
实现方式:
- 代码中通过
PackageManager
获取签名信息,与预设的签名哈希值对比。 - 若签名不匹配,触发退出或限制功能(如付费验证失效)。
- 代码中通过
示例代码:
1
2
3
4
5
6
7// 获取当前APK的签名信息
Signature[] signatures = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES).signatures;
String currentSignature = signatures[0].toCharsString();
// 对比预设的合法签名
if (!currentSignature.equals("预设的签名哈希值")) {
exit();
}
文件完整性校验
- 目的:验证APK或资源文件(如DEX、SO库)是否被修改。
- 实现方式:
- 计算文件的哈希值(如MD5、SHA-1)并与预设值对比。
- 检测关键文件的大小或修改时间是否异常。
环境校验
- 目的:检测是否运行在模拟器、Root环境或调试模式。
- 实现方式:
- 检查系统属性(如
ro.build.tags
是否包含test-keys
)。 - 检测
su
文件是否存在(Root环境)。 - 使用
android.os.Debug.isDebuggerConnected()
检测调试状态。
- 检查系统属性(如
逻辑校验
- 目的:验证关键业务逻辑是否被篡改(如付费验证、加密算法)。
- 实现方式:
- 校验关键函数的返回值是否符合预期。
- 使用代码混淆或Native代码(C/C++)隐藏校验逻辑。
JNI/Native 层校验
- 许多应用会在 C/C++ 层进行完整性检查,检测
lib.so
是否被修改,或者检查 Java 方法的行为是否被 Hook。 - 例如,某些反调试技术会在
lib.so
中实现ptrace
检测或frida
检测。
签名校验(最常见)、dexcrc校验、apk完整性校验、路径文件校验等
2.什么是APK签名
APK签名是Android系统的安全机制,用于验证应用来源和完整性。
通过对 Apk 进行签名,开发者可以证明对 Apk 的所有权和控制权,可用于安装和更新其应用。而在 Android 设备上的安装 Apk ,如果是一个没有被签名的 Apk,则会被拒绝安装。在安装 Apk 的时候,软件包管理器也会验证 Apk 是否已经被正确签名,并且通过签名证书和数据摘要验证是否合法没有被篡改。只有确认安全无篡改的情况下,才允许安装在设备上。
简单来说,APK 的签名主要作用有两个:
- 证明 APK 的所有者。
- 允许 Android 市场和设备校验 APK 的正确性。
(1)签名机制
每个安卓应用在打包时都会使用开发者的私钥进行签名,签名信息存储在 META-INF
目录下,主要包括:
CERT.RSA
(公钥证书)CERT.SF
(摘要信息)MANIFEST.MF
(文件哈希值)
当用户安装 APK 时,Android 系统会:
- 使用
CERT.RSA
内的公钥验证CERT.SF
的真实性。 - 通过
CERT.SF
的哈希值检查MANIFEST.MF
,确认文件未被修改。 - 通过
MANIFEST.MF
中记录的哈希值,校验 APK 内所有文件是否被篡改。
如果签名校验失败,APK 将无法安装。
**(2)**Android 目前支持以下四种应用签名方案:
V1 签名(JAR 签名,Android 1.6+)
只校验
META-INF
目录下的签名文件,对APK
本身的完整性保护较弱,可通过 Zip 方式修改APK
而不影响签名。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文件中保存了公钥、所采用的加密算法等信息。
V2 签名(APK Signature Scheme v2,Android 7.0+)
- 直接对整个 APK 文件进行签名,提高了安全性,防止对 APK 进行 Zip 修改后仍能通过签名校验。
- 在某些情况下,直接对apk进行v1签名可以绕过apk的签名校验
v2方案会将 APK 文件视为 blob,并对整个文件进行签名检查。对 APK 进行的任何修改(包括对 ZIP 元数据进行的修改)都会使 APK 签名作废。这种形式的 APK 验证不仅速度要快得多,而且能够发现更多种未经授权的修改。
V3 签名(APK Signature Scheme v3,Android 9.0+)
- 增加了对 Key Rotation(密钥轮换)的支持,允许开发者更换私钥后仍能保持应用更新。
V4 签名(APK Signature Scheme v4,Android 11+)
- 用于加速 APK 安装,但不会影响完整性校验。
3.什么是签名校验
基本概念
1. 什么是签名校验?
签名校验是 Android 应用常见的安全机制,用于检测应用是否被篡改或重新签名。它的主要目的是防止二次打包和恶意篡改。
当开发者编译和打包 APK 时,APK 会使用开发者的私钥进行签名。Android 系统在安装应用时会验证签名,如果 APK 被篡改,签名就会失效,导致无法安装。
但在 逆向分析 中,攻击者可以修改 APK 并重新签名,因此应用通常会在运行时 自检签名,如果签名不匹配,应用可能会终止运行或执行反调试逻辑。
2. 签名校验的工作原理
签名校验通常分为 系统级校验 和 应用自校验:
(1)系统级签名校验
- 作用:当用户安装 APK 时,Android 系统会自动检查 APK 的签名。
- 流程:
- 提取 APK 的
CERT.RSA
证书。 - 使用该证书的公钥验证
CERT.SF
和MANIFEST.MF
文件,确保 APK 未被篡改。 - 如果 APK 签名无效(如篡改了
classes.dex
或AndroidManifest.xml
),则安装失败。
- 提取 APK 的
- 特点:
- 由 Android 系统执行,不可绕过。
- 只有相同签名的应用才能相互更新(应用升级必须使用相同的开发者签名)。
(2)应用自校验(运行时签名校验)
- 作用:在应用运行时检查自身签名,防止被二次打包或篡改后运行。
- 实现方式:
- 获取应用的签名信息,并与预存的合法签名进行比对。
- 如果签名不匹配,则终止应用运行或执行反调试逻辑。
3. 签名校验的实现方式
在 Android 代码中,签名校验通常通过以下方式实现:
(1)获取签名(兼容 Android 7.0+)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33java
复制编辑
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import java.security.MessageDigest;
public static String getSignature(Context context) {
try {
PackageManager pm = context.getPackageManager();
PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
Signature[] signatures = packageInfo.signingInfo.getApkContentsSigners();
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(signatures[0].toByteArray());
return bytesToHex(md.digest()); // 计算签名哈希值
} catch (Exception e) {
return null;
}
}
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}- 该代码计算应用的 SHA-256 签名哈希值,并与预存的值比对,防止篡改。
(2)比对签名
1
2
3
4
5
6
7
8java
复制编辑
String expectedSignature = "ABC123DEF456..."; // 预存的签名哈希
String currentSignature = getSignature(context);
if (!expectedSignature.equals(currentSignature)) {
System.exit(0); // 签名不匹配,退出应用
}
4. 签名校验的常见绕过方法
在逆向工程中,攻击者通常会修改 APK 并重新签名,使其能够安装并运行。但如果应用进行了运行时签名校验,则需要使用以下方法绕过:
(1)Hook 绕过
使用 Frida/Xposed Hook
getPackageInfo()
方法,伪造签名返回值:1
2
3
4
5
6
7
8
9
10
11
12javascript
复制编辑
Java.perform(function() {
var pm = Java.use("android.app.ApplicationPackageManager");
pm.getPackageInfo.overload("java.lang.String", "int").implementation = function(pkg, flags) {
var info = this.getPackageInfo(pkg, flags);
var fakeSignature = Java.use("android.content.pm.Signature").$new("ABC123DEF456..."); // 伪造签名
info.signatures.value = [fakeSignature];
return info;
};
});- 该代码 Hook 了
getPackageInfo()
方法,使其始终返回合法的签名。
(2)修改 Smali 代码
反编译 APK:
1
2
3bash
复制编辑
apktool d app.apk -o app_smali找到
getSignature()
方法,将签名比对逻辑改为return true
:1
2
3
4
5
6smali
复制编辑
const-string v0, "ABC123DEF456..."
return v0
//使校验函数直接返回true或跳过校验步骤重新打包并签名:
1
2
3
4
5bash
复制编辑
apktool b app_smali -o new_app.apk
zipalign -p 4 new_app.apk new_app_aligned.apk
apksigner sign --ks mykeystore.jks --out new_app_signed.apk new_app_aligned.apk
(3)直接修改 APK 签名
使用
zipalign
和apksigner
重新签名 APK:1
2
3
4bash
复制编辑
zipalign -p 4 modded.apk modded_aligned.apk
apksigner sign --ks mykeystore.jks --out modded_signed.apk modded_aligned.apk但是如果应用内置了签名校验代码,仍然需要 Hook 相关方法绕过。
5. 总结
如何增强签名校验?(开发者视角)
- 避免硬编码签名值
- 动态从服务器获取合法签名,或通过代码混淆分散存储。
- Native层校验
- 将校验逻辑写入C/C++库(.so文件),并添加反调试、代码混淆等保护。
- 多维度校验
- 同时校验签名哈希、证书序列号、公钥等信息,而非单一哈希值。
- 环境检测
- 结合Root检测、模拟器检测,防止校验逻辑在调试环境中运行。
- 服务端协同
- 关键业务请求时,服务端二次校验客户端的签名信息。
签名校验类型 作用 绕过方式 系统级签名校验 检查 APK 安装时的合法性 无法绕过,必须重新签名 应用自校验 运行时检测签名是否匹配 Hook getPackageInfo()
、修改 Smali- 签名校验主要用于防止二次打包和破解,但可以通过 Hook 或修改 Smali 绕过。
- 更高级的保护手段可能会将签名校验与代码完整性校验、JNI 保护等结合,增加破解难度。
如何判断是否有签名校验?
不做任何修改,直接签名安装,应用闪退则说明大概率有签名校验
一般来说,普通的签名校验会导致软件的闪退,黑屏,卡启动页等
当然,以上都算是比较好的,有一些比较狠的作者,则会直接rm -rf /,把基带都格掉的一键变砖。
1 |
|
在我个人见过最恶心的签名校验中,当属三角校验(低调大佬教的)最烦人。
所谓三角校验,就是so检测dex,动态加载的dex(在软件运行时会解压释放一段dex文件,检测完后就删除)检测so,dex检测动态加载的dex
示例
- 打开算法助手的拦截和防止退出
- 我们可以在日志中查看到拦截信息
- 查找到闪退的方法
- 查询到闪退的代码将其注释掉,就可以解决闪退等问题。
- 我们也可以通过算法助手的签名监听来进行定位
- 通过日志中的签名信息找到其相应的堆栈进行查询
- 对堆栈进行查询找到关键方法
checkSign
进行分析。
- 通过获取安装包签名信息的值与
SIGNATURE
的值进行比对
- 我们可以将判断的逻辑if-nez改为if-eqz,直接把判断取反
- 直接对sign值进行查询
- 将包内的sign替换掉,就可以通过普通签名校验
普通获取签名校验代码:
1 |
|
系统将应用的签名信息封装在 PackageInfo 中,调用 PackageManager 的 getPackageInfo(String packageName, int flags) 即可获取指定包名的签名信息。
4.签名校验对抗
方法一:核心破解插件,不签名安装应用
方法二:一键过签名工具,例如MT、NP、ARMPro、CNFIX、Modex的去除签名校验功能
方法三:具体分析签名校验逻辑(手撕签名校验)
方法四:io重定向–VA&SVC:ptrace+seccomp
方法五:去作者家严刑拷打拿到.jks文件和密码
jks文件了
.jks
文件是 Java KeyStore 文件,主要用于存储加密密钥、证书和私钥,通常用于 SSL/TLS 证书管理。Java 应用程序使用.jks
文件来确保安全的通信。常见用途
- SSL/TLS 证书管理(HTTPS 服务器、客户端身份验证)
- Java 应用安全(如 Spring Boot、Tomcat)
- Android 签名(用于 APK 签名)
常见命令(keytool 工具)
keytool
是 Java 自带的密钥管理工具,可用于创建和管理.jks
文件。1. 生成新的 JKS 文件
1
2keytool -genkey -alias myalias -keyalg RSA -keystore mykeystore.jks -validity 3650 -storepass mypassword
alias myalias
:指定别名keyalg RSA
:密钥算法keystore mykeystore.jks
:JKS 文件名validity 3650
:证书有效期(天)storepass mypassword
:密钥库密码
2. 查看 JKS 文件内容
1
2keytool -list -keystore mykeystore.jks -storepass mypassword
3. 导入证书到 JKS
1
2keytool -import -trustcacerts -alias mycert -file mycert.crt -keystore mykeystore.jks
4. 导出证书
1
2keytool -export -alias myalias -file mycert.crt -keystore mykeystore.jks
5. 转换 JKS 到 PKCS12
1
2keytool -importkeystore -srckeystore mykeystore.jks -destkeystore mykeystore.p12 -deststoretype PKCS12
JKS 在 Android 中的应用
Android 应用使用 JKS 文件(通常是
.keystore
格式)来签名 APK。例如:1
2arsigner -verbose -keystore my-release-key.jks my-app.apk myalias
5.手动实现PM代{过}{滤}理
1.什么是PMS
思路源自:Android中Hook 应用签名方法
PackageManagerService(简称PMS),是Android系统核心服务之一,处理包管理相关的工作,常见的比如安装、卸载应用等。
2.实现方法以及原理解析
HOOK PMS代码:
1 |
|
ActivityThread的静态变量sPackageManager
ApplicationPackageManager对象里面的mPM变量
获取原包签名信息
- 使用MT管理器,点击签名信息并查看原始数据
- 这一串内容均为其那名信息,将内容全选复制为base64并与原包签名信息进行替换
3.替换信息后,调用代码
该方法较为过时,简单了解一下即可。
6.IO重定向
什么是IO重定向
IO 重定向(Input/Output Redirection)是指在计算机系统中,将标准输入(stdin)、标准输出(stdout)或标准错误输出(stderr)重定向到不同的文件或设备,而不是默认的终端(屏幕和键盘)。这是 Unix/Linux 和 Windows 命令行中常见的操作,主要用于控制程序的输入和输出。 在 **Android 逆向** 中,**IO 重定向**(Input/Output Redirection)主要用于劫持、修改或捕获应用程序的标准输入、标准输出(stdout)和标准错误(stderr)。它在分析、调试和 Hook 过程中非常重要。
1. IO 重定向在 Android 逆向中的作用
在 Android 逆向过程中,我们通常希望获取应用程序的 调试日志、标准输出、标准错误,或者将输入重定向到目标进程。由于 Android 应用通常运行在 Linux 环境(基于 Android 内核) 下,它也遵循 Linux 的 IO 机制,因此可以使用 重定向技术 进行信息捕获或干预。
2. IO 重定向的常见用途
(1)捕获应用标准输出
有些 Android 应用会在
stdout
或stderr
中打印调试信息,例如:1
2
3
4c
复制编辑
printf("Secret Key: %s\n", secretKey);但这些信息不会直接显示在 Logcat 中,而是需要重定向输出才能捕获。
方法1:使用 strace 或 logcat
使用
strace
监视 IO 调用1
2
3
4bash
复制编辑
strace -p <PID> -e write这可以监视
write
系统调用,捕获应用写入stdout
和stderr
的数据。使用
logcat
捕获日志1
2
3
4bash
复制编辑
logcat -s "AppTag"
方法2:使用
run-as
重定向如果目标应用是
debuggable
,可以使用run-as
切换到应用的 UID 并重定向 IO:1
2
3
4bash
复制编辑
run-as com.example.app sh -c 'exec > /data/data/com.example.app/output.log 2>&1'这会将应用的
stdout
和stderr
重定向到output.log
,然后可以用adb pull
拉取日志:1
2
3
4bash
复制编辑
adb pull /data/data/com.example.app/output.log .
(2)Hook IO 函数
重定向
open
、read
、write
等系统调用,可以在动态调试中捕获关键数据,例如应用读写的文件、网络数据等。方法1:使用 Frida Hook IO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17python
复制编辑
import frida
script_code = """
Interceptor.attach(Module.findExportByName(null, 'write'), {
onEnter: function(args) {
send("Writing data: " + Memory.readUtf8String(args[1]));
}
});
"""
device = frida.get_usb_device()
session = device.attach("com.target.app")
script = session.create_script(script_code)
script.on("message", lambda msg, data: print(msg))
script.load()作用:
- Hook
write()
系统调用,劫持目标进程的输出内容(如日志、密钥等)。 - 适用于需要 监视应用输出数据 的场景。
(3)修改应用输入
在 Android 逆向中,某些应用会通过
stdin
读取数据,比如命令行工具或者 Shell 脚本。可以通过 输入重定向 伪造数据输入,例如:1
2
3
4bash
复制编辑
echo "fake_password" | ./target_binary或者 Hook
read()
函数,修改stdin
输入:1
2
3
4
5
6
7
8python
复制编辑
Interceptor.attach(Module.findExportByName(null, 'read'), {
onEnter: function(args) {
args[1].writeUtf8String("hacked_input");
}
});
(4)劫持 Java 层 IO
在 Java 层,
System.out.print()
默认会输出到stdout
,可以通过 HookSystem.out
来修改或捕获:1
2
3
4
5
6
7
8
9java
复制编辑
System.setOut(new PrintStream(new OutputStream() {
@Override
public void write(int b) {
// 劫持 IO,改写输出
}
}));这在 Hook Java 方法时可以用于修改应用日志或隐藏关键输出。
3. 总结
用途 技术 捕获 stdout
、stderr
strace
、logcat
、run-as
Hook IO 函数 Frida
、Xposed
、LD_PRELOAD
修改输入 stdin
重定向、Hookread()
Java 层 IO 劫持 System.setOut()
IO 重定向在 Android 逆向中的核心作用:
- 获取应用未公开的调试信息
- 监视或篡改应用输入
- 分析目标应用的日志、密钥或敏感数据
- 结合 Frida/Xposed 进行 Hook,提高调试能力
例:在读A文件的时候指向B文件
Virtual Engine for Android(Support 12.0 in business version)
IO重定向可以干嘛?
1,可以让文件只读,不可写
2,禁止访问文件
3,路径替换
具体实现:
过签名检测(读取原包)
风控对抗(例:一个文件记录App启动的次数)
过Root检测,Xposed检测(文件不可取)
新的API签名校验与普通签名校验Ture方法一致
- 分析API的签名校验算法,发现它newsign定位好签名之后,将新的API与原来的进行比对,仍可以采用普通校验的方式将新的API替换,ture处理掉。
CRC校验
- 观察校验部分的代码发现,解压后获取dex文件,获取它的crc的一个值。而我们每做一次修改,dex文件中的值都会发生对应的改变,而它的值需要与j进行一个对比,我们顺着思路查看j在哪里.
- Ctrl+F顺着j进行查找,发现是通过一个方法传入一个值
- 我们顺着观察发现string类里面有crc的值
- 跳转查看发现每一次修改都会使数值发生转变,不可能每一次都对数值进行修改,写死会导致校验失败。需要运用io重定向
- 代码已经写在so文件中,现在缺少一个调用。使用smail代码。它是获取上下文的文本,然后传入到hook方法中,hook方法是一个native方法。
- 首先打开编辑器搜索到checkSign方法后,打开导航找到crc,查看他的调用。
- 因为要进行重定向,要在校验之前完成这部分工作才可以读到原包。在方法开头将我们的调用代码放入进去。
- 在软件数据目录下(数据目录1)新建一个files文件,将没有经过修改的原包复制在该文件中,重命名为base.apk,这样才可以实现一个io的重定向读取原包的信息。(具体原因在代码部分解释)
- 以上就是一个简单的io重定向的实现(crc读取dex的一个crc值,而hash读取整个APK一个hash的一个值)
io重定向原码,相当于hook的open,openat等几个函数(用于读取底层文件函数)
1 |
|
1 |
|
7.其他常见校验
root检测:
反制手段
1.算法助手、对话框取消等插件一键hook
2.分析具体的检测代码
3.利用IO重定向使文件不可读
4.修改Andoird源码,去除常见指纹
1 |
|
定义了一个 isDeviceRooted()
函数,该函数调用了三个检测 root 的方法:checkRootMethod1()
、checkRootMethod2()
和 checkRootMethod3()
。
checkRootMethod1()
方法检查设备的 build tags
是否包含 test-keys
。这通常是用于测试的设备,因此如果检测到这个标记,则可以认为设备已被 root。
checkRootMethod2()
方法检查设备是否存在一些特定的文件,这些文件通常被用于执行 root 操作。如果检测到这些文件,则可以认为设备已被 root。
checkRootMethod3()
方法使用 Runtime.exec()
方法来执行 which su
命令,然后检查命令的输出是否不为空。如果输出不为空,则可以认为设备已被 root。
模拟器检测
1 |
|
通过检测系统的 Build
对象来判断当前设备是否为模拟器。具体方法是检测 Build.FINGERPRINT
属性是否包含字符串 "generic"
。
反调试检测
安卓系统自带调试检测函数
1 |
|
debuggable属性
1 |
|
ptrace检测
1 |
|
每个进程同时刻只能被1个调试进程ptrace ,主动ptrace本进程可以使得其他调试器无法调试
调试进程名检测
1 |
|
frida检测
8.smali语法小课堂之赋值
1.Int型赋值
1 |
|
const/4和const/16的区别?
const/4 最大只允许存放4个二进制位(4bit),
const/16 最大值允许存放16个二进制位(16bit), 第一位(即最高位)默认为符号位。单位换算 1byte=8bit
举例说明下寄存器的取值范围: # 以下数据定义高位默认为符号位
const/4 v0,0x2 # 最大只允许存放半字节数据 取值范围为 -8 and 7
const/16 v0 , 0xABCD # 定义一个寄存器变量,最大只允许存放16位数据 比如short类型数据 取值范围为-32768~32767
const v0 , 0xA# 定义一个寄存器, 最大只允许存放32位数据,比如int类型数据 将数字10赋值给v0 取值范围-2147483647~2147483647
const/high16 #定义一个寄存器, 最大只允许存放高16位数值 比如0xFFFF0000末四位补0 存入高四位0XFFFF
2.Long型赋值
const-wide vx, lit32 表示将一个 32 位的常量存储到 vx 与 vx+1 两个寄存器中 —— 即一个 long 类型的数据
1 |
|
会员到期时间就是2022年12月24日。那么1854460ef29L 怎么来的呢?也就是(2022年12月24日-1970年1月1日)×365天×24小时×60分钟×60秒×1000毫秒,转换成16进制就大概是那个数了
3.变量赋值(正则)
1 |
|
1 |
|