4、代码审计基础:Smali语法入门、关键API搜索、敏感信息泄露检测。

好,咱们进入第四章节。说实话,很多搞Android安全的朋友,一听到“代码审计”就头大,觉得得把Java学透了才行。其实不然。你想想看,我们做逆向,手里拿到的往往是一个APK,反编译出来就是Smali。你要是看不懂Smali,那审计就无从谈起。

我个人习惯,是把Smali当成一种“中间语言”来看。它没那么可怕,说白了就是Dalvik虚拟机的一套指令集。今天我就带你把它啃下来,顺便聊聊怎么在代码里翻出那些不该出现的敏感信息。

4.1 Smali语法:从“天书”到“白话”

我第一次看Smali的时候,也是一脸懵。满屏的.methodconst/4iget-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代表thisp1代表第一个参数,剩下的v0v1才是局部变量。

我的小技巧: 遇到复杂方法,我习惯先在纸上把寄存器列表画出来。v0到vN是局部变量,p0到pN是参数。这样逻辑就清晰多了。

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;->(Ljava/lang/String;)V 调用虚方法 相当于 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,你审计时必须重点关照:

  • 文件操作: FileOutputStreamFileInputStreamopenFileOutput——看它把数据写哪了,是不是外部存储。
  • 网络通信: HttpURLConnectionOkHttpWebView——检查是不是明文HTTP,WebView有没有启用JavaScript。
  • 数据存储: SharedPreferencesSQLiteDatabaseContentResolver——看敏感数据是不是明文存储。
  • 日志输出: Log.dLog.eSystem.out.println——这是敏感信息泄露的重灾区。
  • 加密相关: CipherSecretKeySpec——看密钥是不是硬编码在代码里。
我曾经踩过的坑: 有一次审计一个金融类App,发现它用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;->dLog;->eLog;->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搜索定位可疑点,最后聚焦到敏感信息泄露检测上。

Android代码审计知识体系 Smali语法入门 寄存器与指令 类与方法结构 字段与注解 关键API搜索 敏感信息泄露检测 硬编码密钥 日志泄露 WebView风险 文件存储 网络传输

你看,整个流程是层层递进的。先打好Smali基础,然后学会用工具快速定位,最后聚焦到具体的漏洞点上。这套方法论,我在实际项目中反复验证过,非常有效。

好了,这一章的内容就到这里。记住,代码审计不是死记硬背,而是建立一种“敏感度”。当你看到const-string后面跟着一串看起来像密钥的东西时,心里就要拉响警报。多练、多翻、多总结,你也能成为代码审计的高手。

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