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();

逆向时怎么做?三步走:

  1. 定位文件:用adb shell直接进目录,或者用Root Explorer查看。
  2. 读取内容cat xxx.xml 或者pull到电脑上。
  3. 分析字段:看key的命名规律,比如 tokensessionsecret 这些敏感词。
小技巧: 我习惯用 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下来是打不开的。怎么办?

警告: 如果数据库被加密,不要硬猜密码。正确的做法是:动态调试,hook住打开数据库的函数,拿到密钥。或者直接内存dump,从堆里找明文数据。

我记得有一次,一个金融类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);
    };
});

跑完这个脚本,密钥就打印出来了。然后你拿着密钥去解密文件,一切就明朗了。

避坑指南: 我曾经遇到一个App,加密密钥是动态生成的,每次启动都不一样。但密钥被写进了SharedPreferences里——这不等于白加密了吗?所以逆向时,别只盯着加密算法,也要看看密钥是怎么存储的。

12.4 实战案例:破解一个「加密笔记」App

咱们来个完整的案例。假设有个笔记App,用户数据存在本地,声称「军用级加密」。我们来验证一下。

第一步:定位存储文件

用adb shell进到 /data/data/com.example.notes/ 目录,发现 files/ 下有个 notes.dat 文件。用 cat 看,全是乱码——嗯,确实加密了。

第二步:分析加密逻辑

用Jadx反编译APK,搜索 CipherAESencrypt 等关键词。找到关键代码:

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的加密实现都有漏洞。要么密钥硬编码,要么密钥存本地,要么用弱算法。你只要耐心分析,总能找到突破口。

好了,这一章的内容就到这里。记住我上面说的这些思路,下次你拿到一个App,先翻翻它的本地存储,往往能有意想不到的收获。


公众号:蓝海资料掘金营,微信deep3321