Android应用攻与防
安卓系统是由谷歌推出的一款移动终端,由于开源,所以国内出现了许多使用相关系统的厂商,比如小米,oppo,vivo,魅族等。
在国内,这些系统的用户群体甚多。我们日常使用的社交、游戏、工作等应用,很多都装在安卓系统上。
由于安卓系统的开源性,很多安全问题也随之而来。那么,在安卓系统上运行的应用又是如何保证自身安全的呢?
本文通过实战挖洞,展现一下Android应用的防守与攻击方式。
1
简介
为了避免应用被攻击,各种应用在投产前会用一些技术手段进行加固。但是在持续对抗下,总会出现各种反加固的手段。
部分开发者可能忽视应用的安全性,各大应用市场上也有很多未采取加固手段或进行简单加固的应用。
首先,我们来看一看一个未进行任何加固手段的应用,运行起来有多危险。
下图是开发工程师加密身份证号的AES加密算法。AES算法能保证身份证号以密文形式在网络上传输,不被他人窃取,将代码打包安装在我们手机中运行。
由于安卓应用包很容易获取,源码包被攻击者拿去反编译,反编译结果如下图:
对比开发者写的代码和攻击者反编译的代码,几乎完全一样,攻击者很容易分析出我们的代码逻辑,从而造成破坏。
如上面程序,拿到我们加密算法逻辑,对传输中的加密内容进行解密,来获取身份证号。
由此可见,没有做任何安全防护的安卓源码犹如“裸奔”。
2
防守原理
上面实例可见,没有加固的安卓应用,运行起来是相当危险的。下面介绍几种常见的应用加固方式。包括源码混淆、应用加壳、应用运行环境检测。
源代码混淆保护
混淆是将代码中的类、方法、变量等信息进行重命名,把它们改成一些如“a,b,c,d”这样毫无意义的名字,这样就增加了攻击者逆向难度。混淆效果图如下:
应用加壳保护
加壳是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作。
应用加固的一种手法对原始二进制原文进行隐藏。简单描述就是代码隐藏了,只有程序运行时才会还原。
我们将上面加密身份证号的应用加壳,然后使用工具反编译如下,已经看不到任何源码信息。
攻击者如果想要攻击,第一步就需要脱壳。我这里是只进行简单的加固,对安卓的关键dex文件整体加固,只有在运行的时候才会还原原有dex文件。当然还有其他不同类型的加壳
安卓应用加壳大致经历了四代:
第一代——壳Dex整体加密:
原理是对dex这个文件进行整体加密加壳,存放在APK的资源中,运行时将加密后的dex文件在内存中解密,并让Dalvik虚拟机动态加载执行。
第二代——壳Dex抽取:
通过将代码中的方法名和方法体进行分离,对分离的方法体进行加密处理,同时被加密的方法体中注入解密接口。运行加固包,执行被加密函数时,通过调用其中的解密接口,对当前方法进行解密。
第三代——壳混合加密:
结合第一、二代的加固优点,根据应用的实际情况采用不同的混合加固方式,从而达到加固效果和强度的最优化。
第四代——壳vmp保护:
VMP技术是将原来的可执行代码转换为自定义的字节码,这些自定义的字节码只能由自定义的虚拟机解释器执行。
应用运行环境检测保护
安卓应用如果不进行运行环境检测,就像你在一个装着针孔摄像头的房间睡觉,却不进行安全排查。显然,这样很容易引发很多安全问题。
安卓应用亦是如此。我们需要检测手机是不是有root权限、装了危险应用、设置了代理、开启了调试模式等等。
比如检测root。检测在常用目录下是否存在su命令,如存在,我们此时可以提示用户有风险或者强行退出。
publicstatic boolean checkRootPathSU()
{
Filef=null;
FinalStringkSuSearchPaths[]={"/system/bin/","/system/xbin/","/system/sbin/","/sbin/","/vendor/bin/"};
try{
for(inti=0;i<kSuSearchPaths.length;i++)
{
f=newFile(kSuSearchPaths[i]+"su");
if(f!=null&&f.exists())
{
Log.i(LOG_TAG,"findsu in : "+kSuSearchPaths[i]);
returntrue;
}
}
}catch(Exceptione)
{
e.printStackTrace();
}
returnfalse;
}
检测代理,发现我们在抓包,就断开连接。
publicstatic boolean isWifiProxy(Context context) {
Finalboolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >=Build.VERSION_CODES.ICE_CREAM_SANDWICH;
StringproxyAddress;
intproxyPort;
if(IS_ICS_OR_LATER) {
proxyAddress= System.getProperty("http.proxyHost");
StringportStr = System.getProperty("http.proxyPort");
proxyPort= Integer.parseInt((portStr != null ? portStr : "-1"));
}else {
proxyAddress= android.net.Proxy.getHost(context);
proxyPort= android.net.Proxy.getPort(context);
}
Log.i("代理信息","proxyAddress:"+proxyAddress + "prot : " proxyPort")
return(!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
}
3
实战攻击
国庆某SRC有三倍金币活动,在家正好挖到某个App逻辑漏洞,发现该App不用充钱,也能成为永久VIP会员。
特意写个过程分享一下。由于涉及到隐私问题,关键信息进行打码处理。仅提供了详细的攻击过程,给大家做个参考。
挖洞思路如下:
反编译—脱壳—抓包—绕过代理检测—静态分析—写脚本hook—动态修改—攻击成功。
拿到攻击目标应用安装包.apk文件。首先进行反编译,查看源码情况:
发现应用包已进行加壳处理。直接上工具进行脱壳。我这里使用frida_dexdump进行脱壳。
打印目标应用进程PID= 17460:
执行脱壳命令:
发现脱出来很多dex文件,为了方便分析,使用apktool进行解包,替换dex后重打包。
重打包apktoolb 原始版-oC:\Users\11.apk。
脱壳前后进行对比,发现脱出来很多源代码:
手机运行该应用,发现有个会员功能,会员需要付费购买。
此时,心里想该应用会员校验机制复杂不复杂?能不能绕过支付成为会员?
直接开搞。
手机设置代理,开启BurpSuite进行抓包。一打开目标软件就“网络异常”,说明这个应用检测了运行环境,检测到手机开了代理就断开连接。
查看脱壳后的源码,发现进行了代理检测:
既然有代理检测我们就不设置代理,采用一种透明代理模式。
在BurpSuite中监听80和443这两个端口,并且将其设置为透明代理模式:
手机连接电脑,以下命令:
再查看Burpsuite,此时已经能抓到包,成功绕过代理检测:
点击关于我的信息,查看返回包,发现有关于会员信息的关键字VIP,VipstartTime,VipendTime,isVIP等等。记住这些关键字,然后去源代码里面直接搜索,发现有相关信息:
静态分析跟踪调用逻辑关系,发现判断是不是会员用了isVip进行判定,该字段值是true就是会员,false是非会员:
编写Fridahook脚本,让判断会员的方法一直返回ture。把会员到期时间也进行更换,指定一个返回时间戳,后面还需要更换两个关键参数,这里就不放出来了。
手机端开启frida-server:
PC端执行我们的脚本:
进行了动态修改,此时查看手机页面,已经成为了会员,绕过成功,使用一下会员才有的功能,发现亦能使用。
到这里攻击已经完成,回顾一下破解思路:
首先拿到应用包,查看是否进行加壳——答案是已加壳。抓包发现进行了环境检测,使用透明代理模式进行绕过。
随后我们进行逆向分析代码,成功找到绕过支付成为VIP的逻辑漏洞。
使用frida进行hook动态修改,修改运行中的代码,成功成为会员,攻击成功。