12、本地数据存储逆向:SharedPreferences、SQLite数据库、文件加密与解密实战
各位好,今天我们来聊聊本地数据存储的逆向。说实话,这块内容在移动安全里属于「基础但极其重要」的环节。为什么?因为很多开发者觉得数据存在本地就安全了,结果呢?我见过太多App把用户密码、Token直接明文扔进SharedPreferences里,简直像把家门钥匙挂在门外。
咱们这一章,我会带着大家从逆向视角,把SharedPreferences、SQLite数据库、文件加密解密这三个存储方式挨个扒一遍。你想想看,如果你能读懂App在本地存了什么、怎么存的,那后续的脱壳、协议分析都会轻松很多。
12.1 SharedPreferences:最容易被忽视的「金矿」
SharedPreferences,说白了就是个轻量级的键值对存储。Android系统把它存成XML文件,路径在 /data/data/包名/shared_prefs/ 下。我刚开始做逆向时,第一件事就是去翻这个目录,经常能翻到惊喜。
举个例子,某社交App的登录逻辑:
// 伪代码,实际逆向时你会看到类似逻辑
SharedPreferences sp = getSharedPreferences("user_info", MODE_PRIVATE);
sp.edit()
.putString("token", "eyJhbGciOiJIUzI1NiIs...")
.putString("user_id", "123456")
.putBoolean("is_vip", true)
.apply();
逆向时怎么做?三步走:
- 定位文件:用adb shell直接进目录,或者用Root Explorer查看。
- 读取内容:
cat xxx.xml或者pull到电脑上。 - 分析字段:看key的命名规律,比如
token、session、secret这些敏感词。
grep -r "token\|password\|secret" /data/data/包名/ 一把梭,效率极高。
但要注意,有些App会对value做简单编码,比如Base64。你看到一串乱码别慌,先试试Base64解码。我曾经遇到一个App,把密码用Base64编码后存进去,解码出来就是明文——这加密跟没加密一样。
12.2 SQLite数据库:结构化数据的「心脏」
当数据量大了,SharedPreferences就不够用了。这时候SQLite数据库登场。路径在 /data/data/包名/databases/ 下,后缀通常是 .db 或 .sqlite。
逆向SQLite数据库,我推荐两个工具:
- SQLite Browser:图形化,适合快速浏览。
- sqlite3命令行:适合脚本化操作,比如批量导出。
实战中,我经常这么干:
# 1. 把数据库pull到电脑
adb pull /data/data/com.example.app/databases/app.db .
# 2. 用sqlite3打开
sqlite3 app.db
# 3. 查看所有表
.tables
# 4. 查看表结构
.schema users
# 5. 直接查询敏感数据
SELECT * FROM users WHERE username = 'admin';
嗯,这里要注意:很多App会对数据库文件做加密,比如用SQLCipher。这时候你直接pull下来是打不开的。怎么办?
我记得有一次,一个金融类App的数据库被SQLCipher加密了。我hook了 net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase 方法,直接打印出密钥参数——结果密钥是 "123456"。你说开发者图啥呢?
12.3 文件加密与解密:从「黑盒」到「白盒」
文件存储是最后一道防线。很多App会把敏感数据写到 /data/data/包名/files/ 或 /sdcard/Android/data/包名/ 下,然后做一层加密。
常见的加密方式有:
| 加密方式 | 特征 | 逆向方法 |
|---|---|---|
| AES/CBC/PKCS5Padding | 文件头有16字节IV,数据块对齐 | Hook javax.crypto.Cipher,拿到密钥和IV |
| RSA/ECB/PKCS1Padding | 文件较小,通常用于加密对称密钥 | Hook Cipher.init(),获取公钥/私钥 |
| 自定义XOR | 文件看起来像乱码,但重复模式明显 | 分析so层逻辑,找到XOR密钥 |
| 魔改Base64 | 字符集被替换,比如把A-Z换成其他 | 对比标准Base64表,找出映射关系 |
实战中,我推荐用Frida来Hook加密函数。举个例子,假设App用AES加密文件:
// Frida脚本:Hook AES加密
Java.perform(function() {
var Cipher = Java.use('javax.crypto.Cipher');
Cipher.init.overload('int', 'java.security.Key').implementation = function(mode, key) {
console.log('[+] Cipher.init called');
console.log(' Mode: ' + mode);
console.log(' Key: ' + key.getEncoded());
return this.init(mode, key);
};
});
跑完这个脚本,密钥就打印出来了。然后你拿着密钥去解密文件,一切就明朗了。
12.4 实战案例:破解一个「加密笔记」App
咱们来个完整的案例。假设有个笔记App,用户数据存在本地,声称「军用级加密」。我们来验证一下。
第一步:定位存储文件
用adb shell进到 /data/data/com.example.notes/ 目录,发现 files/ 下有个 notes.dat 文件。用 cat 看,全是乱码——嗯,确实加密了。
第二步:分析加密逻辑
用Jadx反编译APK,搜索 Cipher、AES、encrypt 等关键词。找到关键代码:
public static byte[] encrypt(byte[] data) {
SecretKeySpec key = new SecretKeySpec("ThisIsASecretKey".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
看到没?密钥硬编码在代码里了。而且用的是ECB模式,连IV都不需要。这加密水平,说实话,跟没加密区别不大。
第三步:解密数据
写个Python脚本:
from Crypto.Cipher import AES
import base64
key = b'ThisIsASecretKey'
cipher = AES.new(key, AES.MODE_ECB)
with open('notes.dat', 'rb') as f:
encrypted_data = f.read()
decrypted_data = cipher.decrypt(encrypted_data)
print(decrypted_data.decode('utf-8'))
跑完脚本,所有笔记内容都出来了。所谓的「军用级加密」,就是个笑话。
好了,这一章的内容就到这里。记住我上面说的这些思路,下次你拿到一个App,先翻翻它的本地存储,往往能有意想不到的收获。