对混淆的Android应用进行渗透测试

发表于:2018-5-09 13:53

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:秋真平    来源:安全客

  前言
  我刚为一个金融机构完成了一个移动应用的渗透测试。我写下这些主要是为将来的手工反编译工作做个笔记。我看了很多文章,测试了一些用于Android应用反编译的工具,但是他们大多数是用于分析恶意软件的。有时候我需要做渗透测试而进行反编译和测试应用。
  很多时候,是分析恶意软件还是分析应用都无所谓,但它们是有区别的。例如,当测试一个银行或者金融应用(跟一个团队一起):
  我们可以确定这个应用不是恶意的,所以我们可以安全地使用真实设备
  混淆通常只是到DEX层面,而且不会修复本地代码(Dalvik VM),因为它们想保证便携性
  我们需要能够运行和测试应用,而不仅仅是提取字符串来猜测应用的功能(对于一些恶意软件分析,只需要提取字符串)
  有时候我们需要修改和重新打包应用从而越过root验证、SSL pinning等等,然后重新分发APK给团队成员(通常测试不需要重新打包一个恶意软件)
  大家可能会问:如果是为了渗透测试,为什么不直接要应用的debug版本呢?在很多情况下可以这么做,这使得我们的工作变得很容易。而有些情况下,由于银行和应用提供者之间的合同(或者其他法律或技术原因),他们只提供一个Play Store或者iTunes链接。
  我不能告诉大家我测试的应用,但我可以说下所使用的加壳方法。  
  试试自动工具
  在手工开始工作之前,有几个反编译工具和网站可以在很多混淆场景提供帮助。APK Deguard是其中之一。它最大只支持16Mb的APK文件,所以如果有很多资源文件就要删了确保不会超过限制。这个工具可以识别库文件,所以有时候可以完美地得到重构方法和类名。不幸的是,它也有很多bug:有些变量是从类里消失的方法、有时候它生成4个字节大小的类(就是null)。
  我试过几个其他的看起不错的工具,例如simplify(确实不错,但我测试它时,很慢)。我还试了Dex-Oracle(没用)。JADX也有一些简单的编译重命名工具,但这种情况下不够用。
  每当我发现一个工具不起作用,我通常会花一些时间看看能不能让它工作。最后发现手工有时是最好的。   
  使用XPosed框架
  有些情况下,使用XPosed框架是很好的,我们可以记录下任何方法,或者替换存在的方法。有一点我很不喜欢,就是每次更新模块都需要重启(或者软重启)。
  还有几个模块,例如JustTrustMe,可以和很多应用一起使用,用来绕过SSL pinning。但它不是对所有应用起作用。例如,上次我发现对Instagram不起作用(但当然,可能有人打了补丁可以用了)。还有RootCloak,也可以在很多应用隐藏root信息,但这个模块已经有些时间没更新了。
  难过的是我测试过的应用,这些工具都不能用,应用还是可以检测到设备的root信息,而且也不能绕过SSL pinning。  
  使用Frida
  Frida也是一个有趣的工具,很多时候有用。已经有一些基于Frida的有趣的脚本,例如:appmon。
  Frida和XPosed都有一个缺点:函数内部执行跟踪,例如我们无法在一个方法中打印一个确定的值。   
  解包和重打包
  这种情况很常见:检查应用是否检查它自己的签名。首先,我使用一个锁定bootloader、没有root的真实设备(不是模拟器)。我们可以用apktool解包应用:
  apktool d app.apk
  cd app
  apktool b
  对dist/app.apk重签名然后在设备上安装。我遇到的情况是:应用无法运行,只显示一个提示“App is not official”。  
  查找原始字符串
  我们可以用:
  grep -r const-string smali/
  来提取所有代码里的所有字符串。我遇到的情况是:没能找到很多字符串。我找到的字符串,是用于加载类的。这意味着当我们重命名一个类时要小心,因为它可能作为一个字符串在某些地方被引用。  
  插入日志代码
  通过一些努力,我们可以调试一个小项目,但我更喜欢为两件事做调试日志:反编译字符串和跟踪执行。
  为了插入调试信息,我创建了一个Java文件然后转换成smali代码。这个方法可以打印任何Java对象。首先,在smali文件夹下增加用于调试的smali文件。
  手工插入日志代码,我们只需要:
  invoke-static {v1}, LLogger;->printObject(Ljava/lang/Object;)V
  用我们想要打印的寄存器替换v1。
  大多数时候,反编译函数在所有地方都有相同的参数和返回值,在这个情况下,签名是:
  .method private X(III)Ljava/lang/String;
  我们可以写一个脚本:
  1.查找反编译函数
  2.插入一个调用来记录字符串
  打印反编译函数中的结果字符串是容易的,但有一个问题:这字符串是从哪来的(哪一行,哪个文件)?
  我们可以像这样插入更详细的日志代码:
  const-string v1, "Line 1 file http.java"
  invoke-static {v1}, LMyLogger;->logString(Ljava/lang/String;)V
  但这需要有未使用的寄存器来存字符串(需要追踪现在哪个寄存器是未使用的),或者我们可以增加本地寄存器数量然后使用最后一个寄存器(在函数已经使用了所有寄存器时不起作用)。
  我用了另一个方法:我们可以用一个堆栈跟踪(StackTrace)来跟踪这个方法在哪被调用。要识别行号,我们只需要在smali文件中,在调用反编译函数之前增加新的“.line”指令。为了让编译的类名便于记忆,在smali最前面增加“.source”。刚开始我们还不知道这个类是做什么用的,所以只需要用uuid给它一个唯一标识符。  
  跟踪启动
  在Java里,我们可以创建静态初始化器(static initializer),然后当类第一次被使用时它将会被执行。我们可以在 <clinit> 开始处增加日志代码:
  class Test {
      static {
             System.out.println("test");
      }
  }
  这里我用了UUID(随机生成UUID然后将它当做字符串放在每个类里),它将帮助我处理编译命名。
  class Test {
      static {
             System.out.println("c5922d09-6520-4b25-a0eb-4f556594a692");
      }
  }
  如果这个信息出现在logcat里,我们就可以知道类被调用/使用了。我可以像这样编辑命名:
  vi $(grep -r UUID smali|cut -f 1 -d ':' )
  或者我们也可以设置一个文件夹,放置带有到原始文件链接的UUID。
   
  编写新的smali代码
  我们可以手工编写简单的smali代码,但更复杂的代码我们应该用Java来写,然后再转换成smali。确保它在设备上有效也是一个不错的主意。
  javac *.java
  dx --dex --output=classes.dex *.class
  zip Test.zip classes.dex
  apktool d Test.zip
  现在我们得到一个可以插入的smali(复制到smali文件夹)
  这个方法也可以用来测试应用本身的部分代码。我们可以提取smali代码,加上main,然后运行。
  adb push Test.zip /sdcard/
  adb shell ANDROID_DATA=/sdcard dalvikvm -cp /sdcard/Test.zip NameOfMainClass   
  从Java层面思考
  应用里有几个类从字节数组中提取一个dex文件为临时命名,然后移除该文件。这个数组时加密的,文件名时随机的。我们想知道的第一件事是:这个文件是否重要?我们需要修复它吗?
  为了保存文件,我们可以修复反编译字符串:如果它返回“delete”,我们就返回“canRead”。函数的签名是兼容的,即“()Z”(一个不接受参数并且返回布尔值的函数)
  事实证明替换文件(修复)有点困难。在smali代码中看起来有点复杂,但总体来说有这些方面:
  1.使用SecureRandom随机生成几个unicode字符
  2.将内建的数组解密成内存中的一个zip文件
  3.以固定偏移量(offset)读取zip文件
  4.手动压缩(deflate)zip文件
  5.将解压结果写入一个步骤1生成的随机dex文件名
  6.加载dex文件
  7.删除临时的dex文件
  我尝试修复字节数组,但我还需要调整内部很多数字(大小和偏移量)。在从Java层面思考后,答案是只需要创建一个可以完成我们想要做的的Java代码。所以这才是我所做的:
  我创建一个叫“FakeOutputStream”的类,然后修改代码让它不是查找java.io.FileOutputStrem,而是加载FakeOutputStream。
  FakeOutputStream将把源代码写入/sdcard/orig-x-y,x和y是偏移量和大小。相反地,它会加载/sdcard/fake-x-y的内容然后写入到临时文件。
  注意:当我第一次运行这个应用时,它会生成/sdcard/orig-x-y,并且我可以逆向生成的DEX。我也可以修改这个dex文件并且把它当做/sdcard/fake-x-y push,然后这个文件会被加载。   
  是时候修复了
  所有文件内容解密后,我们就可以开始修复工作了,例如移除root检测,包签名检测,调试检测,SSL pinning检测等等。
  在主APK外动态获取dex文件有一个优势:我们可以轻易地通过在应用外替换dex文件来测试增加替换函数。



上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

精彩评论

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号