11、Android数据存储安全:SharedPreferences与数据库逆向、文件系统访问(root权限下)、KeyStore与加密数据提取、实战:从本地存储中提取用户凭证与敏感信息
各位好,我是老周。今天咱们聊一个非常实在的话题——Android本地存储安全。说白了,就是App把数据存到手机里,我们怎么把它翻出来。
你可能觉得,数据都加密了,应该安全吧?嗯,我在实际项目中见过太多「加密了个寂寞」的案例。有些App号称用了加密,结果密钥就硬编码在代码里,或者用了一个固定的IV向量。你想想看,这跟没加密有什么区别?
核心观点:本地存储安全,从来不是「存了没」的问题,而是「别人拿到手机后,能不能读出来」的问题。
11.1 SharedPreferences:看似简单,实则漏洞百出
SharedPreferences是Android最基础的键值对存储方式。很多新手开发者喜欢用它存token、用户ID、甚至密码。我个人习惯是,拿到一个App先看它的SharedPreferences文件。
文件位置在:/data/data/包名/shared_prefs/。如果你有root权限,直接adb shell进去就能看。
# 进入应用数据目录
adb shell
su
cd /data/data/com.example.app/shared_prefs/
ls -la
cat user_prefs.xml
我曾经在一个金融类App里,看到他们把用户的登录token直接明文存在了login_prefs.xml里。文件名就叫这个,你说是不是生怕逆向工程师找不到?
避坑指南:我曾经在测试某社交App时,发现它的SharedPreferences文件权限是MODE_WORLD_READABLE。这意味着任何App都能读取这个文件。虽然Android 4.2以后已经废弃了这个模式,但有些老项目还在用。如果你遇到这种情况,恭喜你,数据等于公开的。
11.2 SQLite数据库:数据仓库的「后门」
数据库比SharedPreferences复杂一些,但本质上也是文件。位置在/data/data/包名/databases/。我一般直接pull下来用SQLite浏览器打开。
adb pull /data/data/com.example.app/databases/app.db ./
sqlite3 app.db
.tables
SELECT * FROM user_info;
这里有个坑——很多App用Room或GreenDao这类ORM框架,但底层还是SQLite。我见过一个App,数据库里存了用户的聊天记录,而且没有加密。更离谱的是,它把数据库文件命名为chat.db。嗯,生怕别人不知道这是聊天数据。
注意:有些App会对数据库进行加密,比如使用SQLCipher。这时候你直接pull下来是打不开的。但别急,密钥通常藏在Native层或者硬编码在Java代码里。我后面会讲怎么提取。
11.3 文件系统访问:root权限下的「自由」
有了root权限,整个文件系统都是你的。但要注意,Android从4.4开始引入了SELinux,即使有root权限,某些目录也可能被限制访问。
我常用的命令组合:
# 查看SELinux状态
getenforce
# 临时关闭SELinux(需要root)
setenforce 0
# 然后就可以自由访问了
ls -la /data/data/com.example.app/
个人经验:我建议不要轻易关闭SELinux,除非你很清楚自己在做什么。有些App会检测SELinux状态,一旦发现被关闭就拒绝运行。更好的做法是用chcon命令修改文件的安全上下文。
文件系统里值得关注的地方:
/data/data/包名/files/— 应用私有文件/data/data/包名/cache/— 缓存文件,有时会有意外收获/data/data/包名/app_webview/— WebView缓存,可能包含Cookie/sdcard/Android/data/包名/— 外部存储,权限更低
11.4 KeyStore:Android的「保险柜」
Android KeyStore是官方推荐的密钥存储方案。密钥一旦存入KeyStore,理论上无法被导出。但「理论上」和「实际上」之间,往往隔着一条鸿沟。
KeyStore的数据存储在/data/misc/keystore/目录下。但直接读这个目录是没用的,因为密钥被TEE(可信执行环境)保护着。
那怎么提取?我常用的思路:
- Hook方案:用Frida Hook KeyStore的API调用,在密钥被使用时截获明文
- 备份方案:有些App会把KeyStore的密钥备份到其他地方,比如SharedPreferences
- 弱算法方案:如果App用了
AndroidKeyStore但算法是AES/ECB/PKCS5Padding,那加密等于没加
// Frida Hook KeyStore示例
Java.perform(function() {
var KeyStore = Java.use('java.security.KeyStore');
KeyStore.getEntry.overload('java.lang.String', 'java.security.KeyStore$ProtectionParameter').implementation = function(alias, protParam) {
console.log('[+] KeyStore.getEntry called for alias: ' + alias);
var entry = this.getEntry(alias, protParam);
if (entry) {
console.log('[+] Entry class: ' + entry.getClass().getName());
}
return entry;
};
});
重要提醒:KeyStore在Android 6.0以下版本存在一个严重漏洞——密钥可以被导出。如果你在逆向老设备上的App,可以试试直接读/data/misc/keystore/user_0/目录下的文件。我曾经用这个方法从一个Android 5.0的设备上提取了某银行App的RSA私钥。
11.5 实战:提取用户凭证与敏感信息
好了,理论说完了,咱们来点实际的。假设你拿到了一台root过的手机,目标App是com.example.bank。我们一步步来。
第一步:全局搜索敏感关键词
grep -r "token\|password\|secret\|key" /data/data/com.example.bank/
第二步:检查SharedPreferences
cat /data/data/com.example.bank/shared_prefs/*.xml
第三步:导出数据库
adb pull /data/data/com.example.bank/databases/
第四步:Hook运行时数据
用Frida Hook住App的登录和网络请求方法,截获明文数据。
// Frida Hook OkHttp
Java.perform(function() {
var OkHttpClient = Java.use('okhttp3.OkHttpClient');
OkHttpClient.newCall.implementation = function(request) {
console.log('[+] Request URL: ' + request.url());
console.log('[+] Request Headers: ' + JSON.stringify(request.headers()));
return this.newCall(request);
};
});
实战结果:我在一次测试中,用上述方法从某银行App里提取到了用户的身份证号、银行卡号、以及登录密码(明文)。密码存在SharedPreferences里,key叫user_pwd。你说这安全做得...嗯,我只能说还有很大提升空间。
知识体系总览
下面这张图总结了本章的核心知识点和它们之间的关系:
总结
Android本地存储安全,说白了就是一场「开发者 vs 逆向工程师」的猫鼠游戏。开发者以为存到KeyStore就安全了,结果密钥在代码里写死;开发者以为数据库加密了就没事,结果密码是「123456」。
我个人觉得,安全没有银弹。但至少要做到:
- 敏感数据不要明文存,这是底线
- 密钥不要硬编码,用KeyStore + 用户生物识别
- 数据库加密用SQLCipher,密钥从服务端获取
- 定期做安全测试,别等上线了才发现问题
好了,这一章就到这里。记住我说的——「本地存储没有绝对安全,只有相对难搞」。