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

    发布:51Code 时间: 2018-05-11 13:15

  • 前言 我刚为一个金融机构完成了一个移动应用的渗透测试。我写下这些主要是为将来的手工反编译工作做个笔记。我看了很多文章,测试了一些用于Android应用反编译的工具,但是他们...

  • 前言

    我刚为一个金融机构完成了一个移动应用的渗透测试。我写下这些主要是为将来的手工反编译工作做个笔记。我看了很多文章,测试了一些用于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文件来测试增加替换函数。

    来自:秋真平
    文章来源:安全客
    博为峰对此不表示赞同或者反对,也不为其提供证明,仅供阅读者交流参考。
    上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编caoxiaoyan@51testing.com,我们将立即处理
  • 上一篇:测试开发-iOS测试之移动端测试用例设计总结

    下一篇:Android测试需要get的知识——JNI

相关资讯
网站导航
Copyright(C)51Code软件开发网 2003-2018 , 沪ICP备05003035号