4、代码审计基础:Smali语法入门、关键API搜索、敏感信息泄露检测。
好,咱们进入第四章节。说实话,很多搞Android安全的朋友,一听到“代码审计”就头大,觉得得把Java学透了才行。其实不然。你想想看,我们做逆向,手里拿到的往往是一个APK,反编译出来就是Smali。你要是看不懂Smali,那审计就无从谈起。
我个人习惯,是把Smali当成一种“中间语言”来看。它没那么可怕,说白了就是Dalvik虚拟机的一套指令集。今天我就带你把它啃下来,顺便聊聊怎么在代码里翻出那些不该出现的敏感信息。
4.1 Smali语法:从“天书”到“白话”
我第一次看Smali的时候,也是一脸懵。满屏的.method、const/4、iget-object,感觉像在看加密电报。但后来我发现,只要抓住几个核心规律,它比Java还直观。
4.1.1 基本结构与寄存器
Smali文件里,每个类对应一个.smali文件。结构大致是:
.class public Lcom/example/app/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"
# 字段
.field private tag:Ljava/lang/String;
# 方法
.method public onCreate(Landroid/os/Bundle;)V
.registers 3
.param p1, "savedInstanceState" # Landroid/os/Bundle;
.prologue
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
const-string v0, "Hello Smali"
return-void
.end method
这里有个关键概念——寄存器。Dalvik虚拟机不是基于栈的,它是基于寄存器的。你看.registers 3,意思就是这个方法用了3个寄存器。其中p0代表this,p1代表第一个参数,剩下的v0、v1才是局部变量。
4.1.2 常用指令速查
嗯,这里我整理了一张表,都是你审计时高频遇到的指令。记牢它们,Smali基本就能读通了。
| 指令 | 含义 | 示例 |
|---|---|---|
const/4 v0, 0x1 |
将整数1存入v0 | 相当于Java的 int a = 1; |
const-string v0, "key" |
将字符串"key"存入v0 | 相当于 String s = "key"; |
iget-object v0, p0, Lcom/example/App;->tag:Ljava/lang/String; |
获取实例字段 | 相当于 this.tag |
invoke-virtual {v0, v1}, Ljava/io/File;-> |
调用虚方法 | 相当于 new File(v1) |
if-eqz v0, :cond_0 |
如果v0等于0,跳转到cond_0 | 相当于 if (v0 == null) |
return-object v0 |
返回对象v0 | 相当于 return v0; |
你看,是不是没那么玄乎?invoke-xxx就是调用方法,iget/iput就是读写字段,const就是赋值。把这些基础指令记住,剩下的就是查表了。
4.2 关键API搜索:像侦探一样找线索
代码审计不是让你一行行读,那太傻了。我通常的做法是——先搜关键API。就像侦探到了案发现场,先找指纹和脚印一样。
4.2.1 敏感API清单
我在项目中遇到过很多次,一些看似人畜无害的代码,其实藏着大坑。下面这些API,你审计时必须重点关照:
- 文件操作:
FileOutputStream、FileInputStream、openFileOutput——看它把数据写哪了,是不是外部存储。 - 网络通信:
HttpURLConnection、OkHttp、WebView——检查是不是明文HTTP,WebView有没有启用JavaScript。 - 数据存储:
SharedPreferences、SQLiteDatabase、ContentResolver——看敏感数据是不是明文存储。 - 日志输出:
Log.d、Log.e、System.out.println——这是敏感信息泄露的重灾区。 - 加密相关:
Cipher、SecretKeySpec——看密钥是不是硬编码在代码里。
SharedPreferences存了用户的登录Token,而且文件权限还是MODE_WORLD_READABLE。这意味着任何App都能读到这个Token。嗯,这要是上线了,后果不堪设想。
4.2.2 使用grep进行快速搜索
在Smali代码里搜东西,我推荐用grep。比如:
# 搜索所有包含"password"的smali文件
grep -r "password" --include="*.smali" .
# 搜索所有调用Log.d的地方
grep -r "invoke-static.*Log;->d" --include="*.smali" .
# 搜索硬编码的密钥(常见模式:16字节或32字节的十六进制字符串)
grep -rE 'const-string.*[0-9a-fA-F]{32,}' --include="*.smali" .
你想想看,一个大型App可能有几千个Smali文件,手动翻得翻到猴年马月。用grep几秒钟就能定位到可疑位置,效率翻倍。
4.3 敏感信息泄露检测:揪出那些“不该出现”的东西
这部分是我觉得最有意思的。很多开发者安全意识不够,把各种敏感信息直接写死在代码里。我们的任务,就是把这些“裸奔”的数据揪出来。
4.3.1 硬编码密钥与Token
最常见的泄露就是硬编码。比如:
const-string v0, "sk_live_xxxxxxxxxxxxx" # 这是Stripe的Live密钥!
const-string v1, "AES256Key-1234567890abcdef" # 这是AES密钥
我在项目中遇到过最离谱的一次,是一个App直接把阿里云OSS的AccessKey和SecretKey写在了BuildConfig里。你想想,这相当于把保险柜的钥匙贴在了保险柜门上。
4.3.2 日志泄露
很多App在Debug模式下会打印大量日志,但Release版本忘了关。比如:
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
如果v1里存的是用户密码、身份证号、甚至信用卡CVV码,那这些信息就会通过logcat暴露出来。任何有ADB权限的人都能看到。
Log;->d、Log;->e、Log;->i。如果发现Release版本(没有BuildConfig.DEBUG判断)还在打日志,那就是一个高危漏洞。
4.3.3 WebView中的敏感信息
WebView也是个重灾区。比如:
invoke-virtual {v0, v1}, Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V
如果v1里拼接了用户Token,而且URL是HTTP的,那Token就会在网络上明文传输。更可怕的是,如果WebView还启用了setJavaScriptEnabled(true),那攻击者可以通过XSS轻松窃取这个Token。
4.4 知识体系总览
说了这么多,我画了一张图,帮你把这一章的知识点串起来。你看,整个代码审计的流程,就是从Smali语法入手,然后通过关键API搜索定位可疑点,最后聚焦到敏感信息泄露检测上。
你看,整个流程是层层递进的。先打好Smali基础,然后学会用工具快速定位,最后聚焦到具体的漏洞点上。这套方法论,我在实际项目中反复验证过,非常有效。
好了,这一章的内容就到这里。记住,代码审计不是死记硬背,而是建立一种“敏感度”。当你看到const-string后面跟着一串看起来像密钥的东西时,心里就要拉响警报。多练、多翻、多总结,你也能成为代码审计的高手。