Android静态扫描分析

背景

国内目前,工信部对应用的隐私问题以及安全问题有了规范的要求,今年来经常可以看到有App下架整改的消息。

在海外版本,Google Play经常性的也有相关的政策更新,如近期要求新应用需要以aab格式上架、支付库需要使用版本3、targetSDK需要使用30等等。

以上的检查项包含了:隐私相关的敏感API、权限,应用安全规范,sdk版本等等,如果到手只有一个Apk文件,那么最简单的做法就是使用jadx进行反编译,直接查看Manifest文件、代码。

但是通过jadx进行人工的反编译进行静态扫描的话,成本有点高且效率低。因此,需要一个工具,将以上的扫描操作在工具中实现,让相关的检测人员只需要关注检测项即可。

这里的工具仅针对产物APK进行静态分析,以下介绍相关思路。

工具与环境

  • java
  • apktool
  • apksigner
  • ....

这边先简单的介绍下主要工具,省略号处可扩展其他检测项所需工具,如谷歌的非SDK接口黑灰名单的appcompat。

apktool

apktool,是一个反编译Android Apk的第三方工具。可以将apk反编译为smali,图示为相关指令,其中d(decode),标识反编译;与之对应的是b(building),标识编译。app-release.apk 是要反编译的目标apk,,-o 表示反编译结果输出路径,如果没有 -o 参数,默认直接在当前文件夹。

反编译产物有

  • AndroidManifest.xml 是 Android 项目中的清单文件。
  • res 是资源文件,layout 和 value 都在这里。
  • smali 文件夹 是相关的Java 代码转换为 Smali 语言后的文件。

我们主要需要扫描的地方,基本都在smali里。权限、四大组件、包名、VersionCode等可以通过解析AndroidManifest.xml去获取。

apksigner

apksigner,是Android SDK 构建工具里带有的一个签名工具,但在此处静态分析中,我们是将其用于验证APK签名。

apksigner verify [options] app-name.apk
复制代码

其他工具

当然,静态分析不止这两个工具。你可以按需,继续加其他工具,如提到的谷歌的非SDK接口黑灰名单的appcompat、arm-linux-androideabi-readelf等

思路

扫描思路也十分简单,使用apktool将apk反编译,获取到smali代码,逐个文件去扫这些文件里面是否含有不合规代码段。

网上相关的smali语法知识也不少,这边就不做介绍了,这里推荐一个AndroidStudio里的Plugins——java2smali

它可以将你写好的Java & Kotlin 转成smali

相当的便利。

smali文件的开头将类名、父类、包名路径写出来了,这个smali文件的全限定名为com/example/myapplication/Java2Smali,父类是android/app/Activity,源文件名为Java2Smali.java。

在我们的唯一方法java2smali中,我们也可以清晰的看到方法开始以及结束的标志。

line 19.method java2smali()V开始,最后到line 39.end method结束。

在上面的方法中,我们只做了一件事,新建了一个PendingIntent对象,传入了一个Intent对象。

之前GooglePlay上架的时候,PendingIntent报了个错 Implicit PendingIntent

这里对如何静态扫描出这个问题做一个简单分析。

先得知道为什么会出现这个错。当使用了PendingIntent,且Intent是使用了隐式Intent

然后我们可以开始分析可能出现的情况了。

PendingIntent.getActivity(this,0,base,PendingIntent.FLAG_UPDATE_CURRENT);

PendingIntent.getActivity(this,0,new Intent(this,MainActivity.class),PendingIntent.FLAG_UPDATE_CURRENT);

PendingIntent.getActivity(this,0,new Intent("test").setComponent(new ComponentName(this,MainActivity.class)).setPackage("test"),PendingIntent.FLAG_UPDATE_CURRENT);
复制代码

第一种,Intent在别处创建。其他两种都是在PendingIntent创建时再创建Intent。

先将这些代码写好,可以逐个的转成Smali去查看。

这边直接将三个同时转成Smali了。

当我们获取到一个Smali文件的时候,首先以.method/.end method将整个文件进行分割,获取到每个方法,然后再使用.line再次分割,获取到每行的smali代码。

.line 15的代表着那个在别处创建Intent的。

.line 17 和 .line 19的都是与PendingIntent一起创建的Intent。

我们先来看.line 17 和 .line 19。

.line 17是一个显式的Intent的示例,可以比较清楚的看出,invoke-direct调用了Intent的构造方法,然后传入p0(this),和const-class v2, Lcom/example/myapplication/MainActivity;

.line 19,也是一个显式Intent的示例,创建了一个Intent对象,然后参数v2 为const-string v2, "test"。然后调用了setComponent,创建ComponentName对象,传入p0,和const-class v3, Lcom/example/myapplication/MainActivity;。最后调用了move-result-object v1,将上条计算结果的对象指针移入v1寄存器,也就是Intent。同理最后的setPackage也是类似的。

此处,我们需要的是判断这个Intent是否是显式Intent,那么我们首先可以判断它是否有调用Landroid/content/Intent;->setPackage(Ljava/lang/String;)Landroid/content/Intent;或Landroid/content/Intent;->setComponent(Landroid/content/ComponentName;)Landroid/content/Intent;。还可以查看Intent的构造方法参数,是否传入的是一个const-class。

但这种检测也有缺陷所在,比如Intent是通过构造方法传入,这样子就比较难的去定位了。

即使在test()方法中,已经创建好了Intent对象,而且也是显式调用,但在java2smali方法中,仅能看到一个.param p1, "base" # Landroid/content/Intent;被传入PendingIntent的构造方法里,并不能确定是否是显式调用。

总结

以上便是一个简单的静态扫描示例,还可以继续进行扩展,如:V2签名校验、所接入的SDK汇总、敏感API调用、权限分析等等。

但静态扫描还是有所缺陷的。比如 :

  • 混淆之后,无法判断代码的具体来源
  • 仅能判断是否存在该代码,具体是否有使用无法确定

这块可能需要编译期的检测和动态检测一起参与检测才能继续完善。

おすすめ

転載: juejin.im/post/7039512844768903198