我们在编码美丽微信公众号已经弄过了很多app了,不管是协议还是外挂,我们都是那么一路走过来了,在操作的过程中也发现了很多问题就是应用不在乎安全问题带来的后果,因为安全始终都是不可忽视的问题,辛辛苦苦写的代码被人看的体无完肤对不起自己也对不起公司,所以如果你做了这几件事至少可以防止一些人把你的app给强奸了。本文就来总结一下不用加固方式也可以让你的应用变得更加安全可靠。
一、混淆永远都不可或缺
这里说的混淆不是说的传统大家都知道的简单混淆策略,而是高级一点的混淆策略,首先是代码混淆,大家可以参考小黄车app的代码:
[图片上传失败...(image-543f3d-1531723331377)]
看到了吧人家把代码混淆成中国人可以看懂的信息,可惜这样的信息对于我们破解来说就很麻烦了,关于怎么做到的,之前的文章已经介绍了,大家可以查阅这里:Android中把代码混淆成中文,当然可以简单一点就是用 -classobfuscationdictionary实现也可以。代码混淆成这样不算什么,还有更厉害的就是把资源混淆成一坨屎的应用:
[图片上传失败...(image-787108-1531723331377)]
看到了吗资源文件反编译之后全是这鸟样,感觉世界都快崩溃了,这个方案暂时没研究不过大家可以进入编码美丽小密圈咨询bin神找MT管理器作者咨询方案。当把代码混淆成一坨屎,把资源混淆成一坨翔的时候,让破解者顶着一坨翔破解吧。
二、防止应用被抓包
在混淆之后我们还是觉得不够安全,因为有的人很贱,总是抓包偷窥我们的数据,不过我一般不会这么贱因为我一般正大光明的抓包看数据,应用的网络访问接口和返回数据有的时候很重要,所以为了防止应用不被干,能用https的一定要用,虽然用了也不管用,因为我们有Xposed。用了https之后也要重写SSLSocketFactory类,不懂这个技术可以看看这篇文章:Android中破解防止被抓包应用方案 有了这个还是不行,因为我们会反编译干掉这个类。还需要加上系统的api来判断当前设备有没有挂代理不管是Fiddler还是VPN只要有就认为是不安全的,这两个api方法很简单:
**System.getProperty(“http.proxyHost”); **
**System.getProperty(“http.proxyPort”); **
正常情况下这两个返回值是null,所以你们懂得怎么做了,如果发现不为空那么就表示当前设备被挂代理了,那么就有可能被抓包的风险了,这里额外说一下破解者在除了用Fiddler抓包,还可以直接在手机上用Packet Capture和Debug Proxy这两个工具进行抓包:
[图片上传失败...(image-789be9-1531723331377)]
不过可惜这两个工具都用了系统的VPNService功能做的,所以这时候上面接口返回的值就不为空了,那么这两个工具就等于废了。那么加了这些就一定管用吗?肯定不管用,因为要是管用那我下面还说什么呢?因为这些都是Java层代码,所以安全点就把这些判断放到native层做。只要找到了这个地方直接nop掉就好了。所以我们还得想办法防止,抓包的时候我们要是隐藏我们的接口域名就好了,因为破解者通过抓包找到域名,然后再去Jadx中搜索就定位到了参数加密地方了,那要是我们把域名做成动态下发的,不用域名访问而是ip地址访问,这些ip地址通过配置获取,这样被抓到了也搜不到增加破解难度。不过这些还是没啥用,因为你动态下发只要找到这个地方就会找到ip地址了,到时候还是一样被干了。所以我们还可以这么做,网络请求用底层的socket进行访问操作,这样就不会被抓包了,可惜的是这世界上还有一个叫做tcpdump+WireShark的工具,也是于事无补。所以我已经疯了,因为想不到什么方案了。最后请求数据中一定要记得带上签名加密信息,不要一味的放在请求参数中,我偷偷的告诉你很多破解者容易忽视请求头信息。
[图片上传失败...(image-99d5a2-1531723331377)]
三、防止应用被Hook操作
有时候最烦人的就是自己的应用被别人hook了,感觉自己的腚被人捅了一样,所以为了保护好自己的腚,我们需要做一些事防止被hook,这个就要学习人家支付宝了,同样是腰间盘为什么别人总是这么突出:
[图片上传失败...(image-f94d0c-1531723331377)]
虽然支付宝的防护被一个帅气英俊的美男子程序猿破了,但是还是要说他的方案也是备选之一,如何防止应用被hook住:
第一、Xposed框架将Hook信息存储在字段fieldCache,methodCache,constructorCache 中, 利用java 反射机制获取这些信息,检测Hook信息中是否含有支付宝App中敏感的方法,字段,构造方法
[图片上传失败...(image-4e321a-1531723331377)]
第二、检测进程中使用so名中包含关键”hack|inject|hook|call” 的信息,这个主要是防止有的人不用这个Xposed框架,而是直接自己写一个注入功能
[图片上传失败...(image-1ed175-1531723331377)]
第三、root检测原理是:是否含有su程序和ro.secure是否为1
[图片上传失败...(image-55193d-1531723331377)]
第四、防止被Hook的方式就是可以查看XposedBridge这个类,有一个全局的hook开关,所有有的应用在启动的时候就用反射把这个值设置成true,这样Xposed的hook功能就是失效了:
[图片上传失败...(image-23e2d5-1531723331377)]
第五、如果应用被Xposed进行hook操作之后,抛出的异常堆栈信息中就会包含Xposed字样,所以可以通过应用自身内部抛出异常来检测是否包含Xposed字段来进行防护:
[图片上传失败...(image-172c5b-1531723331377)]
可惜虽然他很突出但是忘了一点就是把操作放到native层会好点,毕竟我不怎么熟悉arm指令的,但是我会Frida。
四、防止应用被调试
在你都加了上面的几个操作之后,腚的确没有被人捅了,但是感觉心中被人植入了苍老师,让你感觉浑身发热。所以做了以上这么多之后还有一点很重要就是不要让你的应用被人家扒光了一样看你的身体。因为现在很多破解者在静态方式不成之后就采用了动态调试办法进行操作。所以我们为了让应用更安全就做好防止反调试操作
第一种:先占坑,自己附加
代码非常简单,在so中加上这行代码即可:ptrace(PTRACE_TRACEME, 0, 0, 0);其中PTRACE_TRACEME代表:本进程被其父进程所跟踪。其父进程应该希望跟踪子进程,一般一个进程只能被附加一次,我们在破解调试的时候都会附加需要调试应用的进程,如果我们先占坑,父进程附加自己,那么后面在附加调试就会失败。加上这段代码我们运行之后看一下效果:
[图片上传失败...(image-cb893-1531723331377)]
我们在进行破解动态调试的时候都知道附加进程的status文件中的TracerPid字段就是被调试的进程pid,这里我们运行程序之后,查看进程对应的status文件,发现TracerPid值就是进程的父进程pid值。那么后面如果有进程在想附加调试就是会失败的。这种方式启动一定的调试作用,但是不是绝对安全的。关于解决这种反调试方案后面再说。
第二种:调试状态检查
这种方式是纯属借助Android中的api进行检验,有两种方法:
第一:检查应用是否属于debug模式
直接调用Android中的flag属性:ApplicationInfo.FLAG_DEBUGGABLE,判断是否属于debug模式:
[图片上传失败...(image-2d2c-1531723331377)]
这个其实就是为了防止现在破解者为了调试应用将应用反编译在AndroidManifest.xml中添加:android:debuggable属性值,将其设置true。然后就可以进行调试。
[图片上传失败...(image-7fc428-1531723331377)]
添加这个属性之后,我们可以用 dumpsys package [packagename] 命令查看debug状态:
[图片上传失败...(image-ca622d-1531723331377)]
所以我们可以检查应用的AppliationInfo的flag字段是否为debuggable即可。不过这种方式也不是万能的,后面会介绍如何解决这种反调试问题。
第二:检查应用是否处于调试状态
这个也是借助系统的一个api来进行判断:android.os.Debug.isDebuggerConnected();这个就是判断当前应用有没有被调试,我们加上这段代码之后,之前IDA动态调试的时候其中有一个步骤进行jdb连接操作:
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700,当连接成功之后,这个方法就会返回true,那么我们可以利用这个api来进行判断当前应用是否处于调试状态来进行反调试操作。但是这种方式不是万能的,后面会介绍如何解决这种反调试问题。
第三种:循环检查端口
我们在之前破解逆向的时候,需要借助一个强大利器,那就是IDA,在使用IDA的时候,我们知道需要在设备中启动android_server作为通信,那么这个启动就会默认占用端口23946:
[图片上传失败...(image-6d8859-1531723331377)]
我们可以查看设备的tcp端口使用情况 cat /proc/net/tcp:
[图片上传失败...(image-dd3a2d-1531723331377)]
其中5D8A转化成十进制就是23946,而看到uid是0,因为我们运行android_server是root身份的,uid肯定是0了。所以我们可以利用端口检查方式来进行反调试策略,当然这种方式不是万能的,后面会详细介绍如何解决这样的反调试方法。同时还要检查有没有android_server这个进程,有的话也是表示有可能被调试了。
[图片上传失败...(image-ab1937-1531723331377)]
当然还有最近很常用的Frida框架,他的端口号是27042和27043,以及进程名是frida-server。另可错杀一千不能放过一个。
第四种:循环检查TracerPid值
在第一种方式中,我们简单的介绍了如果应用被调试了,那么他的TracerPid值就是调试进程的pid值,而在使用IDA进行调试的时候,需要在设备端启动android_server进行通信,那么被调试的进程就会被附加,这就是android_server进程的pid值了:
[图片上传失败...(image-584c8c-1531723331377)]
查看一下android_server的pid值:
[图片上传失败...(image-394e9e-1531723331377)]
所以我们可以在自己的应用中的native层加上一个循环检查自己status中的TracerPid字段值,如果非0或者是非自己进程pid(如果采用了第一种方案的话,这里也是需要做一次过滤的);那么就认为被附加调试了。当然这里还有一种方案,就是可以检查进程列表中有没有android_server进程,不过这种方式都不是万能的,后面会详细介绍如何解决这种反调试方案。
五、签名校验防护策略
在做了上面的一些工作之后发现腚也不疼了,心中也坦然了,可是谁知道有些人太坏了偷偷的把应用的衣服给换了,本来是一个待入闺中的大家闺秀,结果被换了一套比基尼摇身一变成了外围女搁谁谁受得了,所以为了不让自己的应用衣服被换我们得买点胶水把衣服粘住,不要被人恶意拆解我们的应用然后二次打包谋取暴利,签名校验一般方案有两种:
第一:直接在本地做防护,如果发现签名不一致直接退出应用
第二:将签名信息携带请求参数中参与加密,服务端进行签名校验,失败就返回错误数据即可
而这两种方式也都不是最安全的防护,因为只要有签名校验的逻辑,在本地都可以进行过滤掉。特别用了我的kstools工具之后完全就处于游离状态了。不过签名校验不要按照传统方式去做了因为那个已经众人皆知不安全了,我们需要校验各个文件的信息,比如微信的dex文件校验,阿里聚安全的签名文件校验等高强度操作。对于文件校验也是最实际一点的,不要在用系统方法获取签名信息校验了,那个已经过时了。
六、加固方案
说了这么多了感觉还是不安全,的确肯定不安全,因为上面的工作你要是还是放在java层去做那么等于没说,永远记住NDK不仅仅是为了性能效率考虑的,也是为了安全考虑使用,很多人看得懂Java代码但是看不懂arm指令,如果在Native层做了这么多防护肯定效果会更好,当然如果你的应用为了更安全那就要考虑加固了,加固是最终方案了至于什么加固厂商那就要自己选择了,但是现在很多大公司不会考虑加固原因有两个:
第一、现在没有一个加固厂商跑出来说我们家的加固方案最牛逼,保证0崩溃率,只要有崩溃率那么对于千万级的应用也是很难忍受的,因为每天都有几千甚至几万用户手机的应用在崩溃会疯的。
第二、现在加固厂商也就那么几家,用过加固的人或者没用过用腚想想也知道,肯定需要上传签名文件的,因为他加固最后还得重新签名,最主要的是你把没加固前的应用给他,你能保证他和我一样很正直不会偷窥你的应用?不会在你的应用中加点什么他们想要的?所以现在很多公司为了更安全点算了还是不去找加固厂商了。本来还安全点结果弄得不安全了。
现在也有很多公司已经在投入人力物力财力自己做加固,就是自己公司开发一套加固方案然后用于自己公司的所有产品,这样肯定是最安全的了,但是这个前提是人力物力财力的投入,如果小公司那肯定是不会考虑的。所以加固本身来说技术肯定是最高端的也是最终的方案的。但是也不是说百分百安全,因为这个世界上还有一个完美的动作叫做脱壳。
更多安全内容讨论,可以进入编码美丽小密圈,各路大神等着你一起交流技术
[图片上传失败...(image-1054ae-1531723331376)]
七、总结
所以本文说了很多方案,不可否认如果用了这些方案和没用区别还是有的,本来想着是把这些方案写成一个SDK开源的,但是最后想到没时间没精力弄就放弃了,但是我还是希望有热爱动手的同学能够按照这几个方案写一个完整的SDK出来也是造福人类光宗耀祖了。安全不息,逆向不止,生死看淡,不服就干!