Group of Software Security In Progress

IMGDroid Detecting Image Loading Defects in Android Applications

作者:Wei Song, Mengqi Han,Jeff Huang

单位:Nanjing University,Texas A&M University

会议:ICSE 2021

链接:pdf

介绍

作者总结了5种图片加载缺陷的模式:通过intent传递图像,未经大小调整的图像解码,无权限的本地图像加载,未经缓存的重复解码以及UI线程中的图像解码。针对这些缺陷,作者设计了分析系统IMGDroid进行自动化检测。在21个开源Android应用程序上进行测试,成功检测出45个先前已知的图像加载缺陷,而且发现了15个新的此类缺陷。并且,对1,000个商用Android应用程序的检测表明,图像加载缺陷非常普遍。

背景

当收到显示图像的请求时,应用程序首先尝试在呈现之前从内存缓存中读取相应的bitmap对象。 如果bitmap不在内存缓存中,则应用程序将尝试从磁盘缓存中读取它。 如果成功,则应用将在渲染之前对图像(例如JPG格式)进行解码以获得bitmap(然后可以将其缓存在内存中)。 否则,它必须从网络或本地存储外部加载映像。 在进行一些处理和转换(例如,减小图像的大小)之后,可以将图像存储在磁盘cache中。 然后,对变换后的图像进行解码(可以将其缓存在内存中)并最终进行渲染。

img

如表1是较为常见的加载图片的框架。

img

IMGDROID

IMGDroid基于Soot和FlowDroid开发,实现上下文不敏感的调用分析和路径敏感的跨过程的控制流分析。

img

定义:

  1. Dominator:在cfg上,如果从入口点到节点N的所有路径都会经过节点N‘,那么N’为N的Dominator。
  2. Post-dominator:在cfg上,如果从节点N到出口点的所有路径都经过节点N’,那么N’为N的Post-dominator。
  3. Control Dependence:在cfg上,当且仅当存在一个从节点C到N路径p使得节点N是任意一个节点N’的Post-dominator,且N不是C的Post-dominator,则称C是N的控制依赖。

检测通过Intent传递图像

当使用intent传递数据较大(超过1M),会出现性能问题或者异常。

在跨过程的cfg上,从intent.putExtra(‘‘key’’, value) 或者 bundle.putParcelable(‘‘key’’, value)可达到startActivity(intent) 语句,并且value的类型是Bitmap, Drawable或者BitmapDrawable。

img

检测没有调整大小的图像解码

不调整过大的图片直接解码会影响app的运行效率甚至导致OOM异常。比如说一张手机相机拍摄的图片有4048 × 3036像素,系统直接加载需要48M(4048 × 3036 × 4 bytes)的内存,可能直接就消耗掉了所有可用内存。

调整大小的实现上一般分为两种:

1)解码和调整大小在一个method里,图片大小被method的一个参数控制。BitmapFactory类的Android native API和图片框架如Universal-Image-Loader和Fresco。

2)解码和调整大小在两个单独的method。Glide和Picasso。

一些Android native API只实现了解码而没有实现调整大小,Drawable.createFromPath(), Drawable.createFromStream(), 和ImageView.setImageURI()

img

检测方法:

  • 对于第一种,如果存在通过更改参数来减小图像大小的语句,并且该语句是解码语句的dominator,则没有缺陷。
  • 对于第二种,在跨过程cfg上,如果调整大小的语句解码语句的post-dominator则没有缺陷。

img

检测无权限加载本地图片

从本地存储读图片文件前需要先动态获取权限。

检测流程:

1)解码语句使用的变量是getExternalStorageDirectory()返回的值。使用FlowDroid完成。

2)AndroidManifest文件中是否声明了外部存储权限

3)应用是否能获取外部存储读取权限的语句(if(ContextCompat.checkSelfPermission(android.permission.READ_EXTERNAL_STORAGE)) 或者if(EasyPermissions.requestPermissions(android.permission.READ_EXTERNAL_STORAGE)))是解码语句的控制依赖。

img

检测没有缓存重复读取

当图片加载被实现为一个回调函数(例如,getView(), onDraw(), onBindViewHolder(), getGroupView(), getChildView()),如果没有进行缓存,那么每次回调执行的时候都会重复解码,这不仅会导致GUI滞后,而且还需要许多额外的CPU/GPU周期来进行图像解码和处理。

检测:

  • Android native API(除了ImageView.setImageURI()),Picasso和Glide在两个不同method中分别实现图片解码和缓存。在跨过程的cfg中,如果缓存语句不是解码语句的post-dominator,则存在缺陷。
  • Universal-Image-Loader方法displayImage(DisplayImageOptions)的参数同时控制大小和缓存,作者会检查是否已通过option.cacheInMemory()将参数设置为打开缓存开关。 如果option.cacheInMemory()不是displayImage(option)的dominator,则图像在解码后不会被缓存。
  • Fresco框架默认是开启缓存的,不存在问题。
  • ImageView.setImageURI()无法设置缓存,一定存在缺陷。 img

检测在UI线程里解码

如果图片在UI线程而不是后台线程里解码,会影响应用的效率,导致GUI之后,更长的响应时间,甚至没有响应。

Universal-Image-Loader, Glide和Fresco使用异步方法加载图片(即在后台线程),因此一定是没有却缺陷的。Android native API可以在UI线程和后台线程调用。Picasso提供了异步的方法和同步解码的方法。

通过后向回溯图片解码的调用链,如果可以找到一个UI事件的回调函数(事件处理),那么说明存在缺陷。

img

评估

RQ1: Performance of IMGDroid:effectiveness and efficiency。

RQ2: 图片的使用频率。

RQ3: 图片加载缺陷的普遍性

RQ4: 最普遍的缺陷

RQ5: 缺陷的分布

Benchmark

作者在有45个已知的图片加载缺陷的21个开源项目上进行了检测。找到了全部45个缺陷,并且额外发现了一些缺陷,并且进行了手工确认。

img

在1000个商业应用上的经验性研究

1000个应用中,897个使用了图片解码和展示操作,使用Android native API,Picasso, Glide, Universal-Image-Loader和Fresco的分别为 854 (85.4%), 78 (7.8%), 83 (8.3%), 45 (4.5%) 和 0 (0%)。

IMGDroid总共检测出了5,471个应用有图片加载缺陷,只有32个应用没有这种缺陷,平均每个应用有6.32个缺陷。5种缺陷的对应数量分别为 178 (3.25%), 2,678 (48.95%), 147 (2.69%), 310 (5.67%) 和 2,158 (39.44%)。没有调整大小缺陷是最多的,通过intent加载和没有读取权限缺陷是最少的。后两种是因为容易触发app crash所以容易被发现。

854个使用Android native API的应用共发现了5,209个缺陷,78个应用使用Picasso发现了255个缺陷,83个应用使用Glide只发现了5个缺陷,45个应用使用Universal-Image-Loader只发现了2个缺陷。

作者随机挑选了20个应用进行人工逆向,确认IMGDroid检测出来的内容都是正确的。

img