关于Android中的签名校验是一种很普遍的安全防护策略了,很多应用也都做了这部分的工作,在之前我也介绍了几篇关于如何爆破应用的签名校验问题的文章,不了解的同学可以去查看:Android中爆破应用签名校验功能,当时介绍完这篇文章之后,其实总结了现在爆破签名校验的几种方式,其中最方便快捷的就是:全局搜索字符串内容:“signature”,因为只要有签名校验功能,一定会调用系统的一个方法,而这个方法中就是包含了这个字符串内容。
之前的这篇文章中介绍的签名校验处理方式也是如此,找到具体签名校验功能之后,直接替换正确的签名信息就可,或者把if判断手动改成true也可以。但是当时说到一个问题,就是现在应用为了加强防护,几乎在重要的内容部分中都加了签名校验功能,所以如果要手动的改,就需要把每个签名校验的地方都得改一下,这样会显得很费劲,有的人说了,有一个好的办法,就是用Xposed来hook这个应用的获取系统签名的方法,然后替换方法的返回值即可。这种方式是可以的,但是不是最好的,因为如果你解决了签名校验,二次打包给别人用,不可能叫人家还root手机,然后在装Xposed框架功,是不合理的。所以我们需要从根本上解决这个问题,也是本文介绍的一个重点。后面会介绍一种万能的高效方法。
之前的文章中我们可以看到,大部分签名校验都是在Java层,本地做的校验。所以爆破难度不是很大,而今天我们要介绍的一个应用样本他的签名校验是放在so中,而且结合了服务端进行验证,难度加大。不过万变不离其宗,签名校验永远都是需要获取应用本身的签名信息,进行比对操作的。
下面就开始进入本文的主题,看一下应用样本的签名校验问题,拿到样本之后,直接反编译,二次打包,安装出现如下错误信息提示:
这个直接弹出对话框信息,点击确定就退出程序了,这个比较简单,反编译之后,通过提示内容,找到签名校验入口,在values/string.xml中找到这个字符串信息即可:
然后用Jadx打开这个应用,全局搜索这个name值:
点击进入代码位置即可:
看到i方法中有一个show方法,应该就是对话框展示的逻辑。看看这个i方法调用的地方:
看到了,在之前有一个判断,如果为false,就是走到i方法,展示对话框,所以这个checkHashKey方法肯定是签名校验的方法,进入查看:
是个native方法,全局搜SocketJNI类调用的地方:
在这里看到会加载一个so文件,也就是libmzd.so文件,我们用IDA打开这个文件(文件在反编译之后的libs目录下):
找到对应的native函数,查看具体逻辑代码:
为了更好的阅读代码,使用F5,查看对应的C语言代码,这里的逻辑非常简单,就是把Java层传递的签名字符串内容,在做一次MD5值,然后和指定的字符串"8f2a24...."作比对即可。那么上面传入到的checkHashKey方法的值是通过com.xiaoenai.app.utils.aj.m方法:
这里看到就是标准的获取应用签名信息的方法。
到这里就看到了一个Java层的签名校验方法,这里解决这个问题有很多种方法,可以修改对应的smali方法,把if判断强制改成true即可。还可以直接修改m方法的返回值为正确的签名字符串内容。这里选择第二种,因为我们需要引用的签名字符串内容,后面会继续用到。这个获取方法也很简单,直接写一个Demo案例,通过应用包名构建出对应的Context变量,然后就开始获取签名信息即可:
运行这个demo程序,前提是你的设备中要安装一个官方版本的应用,这样才能获取到正确的签名字符串内容:
看到了,其中签名字符串:"aN+VCd8ns0yqsotX2WuKyScq/ZA=" 就是正确的官方版本的值。下面在顺便写一个直接返回这个字符串的方法,然后变编译得到对应的smali代码:
然后把这个方法的的代码拷贝替换样本应用的aj.m方法代码即可:
保存,二次打包即可,记住:这里没必要手动的编写smali代码返回指定的签名字符串,除非你对smali语法非常熟悉了,不过我是不会这么做的。因为我不熟悉smali,最笨的最有效的方法就是自己手动写一个Java方法,然后反编译得到对应的smali代码即可。
二次打包成功之后,安装应用,运行发现竟然还有错误,登录失败:
这时候,就想到了,样本应用可能做了服务端数据验证,下面就来看看如何解决这个问题,这个问题的突破口比较简单,不要在用提示字符串信息去找了,因为这错误信息可能是服务端返回的,这时候就需要借助抓包了,每次请求一次,就抓一次服务包,这里用了Fiddler工具:
这里看到,请求的参数非常简单,一个加密之后的数据data和版本号ver,返回的数据提示加密签名错误,所以我们可以在Jadx中全局搜"login2"字符串信息,找到突破口:
找到之后,双击进入代码:
看到最终调用了,a方法,点击进入a方法:
这里会通过一个过程构造出一个json参数格式,继续往下看:
携带了这些信息参数值,为了更好的看到这个json数据格式,我们可以利用Xposed下一个hook功能:
然后运行这个Xposed模块,在点击样本的登录功能,查看日志信息:
看到了,result就是最终拼接好的json参数格式内容。其中最后一个字符串sig是将签名整个参数做一次加密操作,为了在服务端校验参数的完整性。得到这个json格式之后,会调用com.xiaoenai.app.utils.b.a.a方法:
继续查看这个方法实现:
继续跟踪代码:
又是到了native方法了,到这里其实上次Java将拼接好的参数信息以json格式传递到native层进行加密操作,继续看native层代码实现,依然在之前的那个libmzd.so中:
找到对应的核心函数功能,跟踪查看实现:
继续跟踪:
这里看到了,有data字符串内容了,也就是这里开始对上面传递的json数据进行加密,然后拼接到data中,在native层在拼接一套json:{"data":"加密之后的值", "ver":"1.1"},而在这里有一个寻找加密key的函数,可惜这里我不在分析了,因为我看的头疼,他的加密算法还是比较复杂的。所以就放弃分析了。
那么到这里,我们有什么方式可以得到加密算法呢?有的同学可能想到了动态调试,这个方法是最好的,但是这个app做了很多反调试策略,动态调试也是不好弄的,而且这个算法有点复杂,及时动态调试,也要详细阅读arm指令,才能猜到这个加密算法功能。所以这条路不好走。
那么还有其他方法了?当然有,就是文章开头说到的一句解决签名校验的基本法则:全局搜索字符串"signature";在IDA中也是如此,使用Shirt+F12打开so中的字符串窗口,然后搜索signature:
果然找到了,双击进入:
看到这里可能是在native层调用了系统获取签名的方法,选中红色框中的内容,然后按X键,展示被调用的地方索引:
双击进入,这个就是我们之前分析的那个SocketJNI的init方法:
到这里,为了更好的阅读代码,需要把这个函数地址改成可读,也就是JNIEnv*即可,选中红色框,按下Y键即可:
修改成JNIEnv*,确定即可:
这样就看的比较清楚了,而这部分代码也非常简单,就是调用系统获取签名的方法,然后赋值到一个变量中,不过这里获取的不是字符串内容,而是int类型的hashCode值:
在返回到arm代码处,看到赋值,就是将R0寄存器值搞到R9中去。
到这里,我们一定要思路清楚,就是不管native层怎么加密,最终会利用应用的签名值来做加密的key信息。所以我们只要解决掉获取签名信息值即可,也就是这里的R9寄存器值,我们还需要再一次去获取正版的签名信息的hashCode值即可:
然后运行,查看值:-2081383250
然后我们可以修改上面的arm指令:MOV R9,R0;不过这里还不好弄,因为这个hashCode值int类型四个字节,而这里看到一个命令才2个字节,肯定不够用。需要借助LDR伪指令来进行操作了。但是这里不做这么费劲的修改了。因为假如其他地方也有这样的功代码,那么还得去修改,就是回到了我们文章开始说到的那个问题,要解决所有的签名校验功能,才是本文的目的。
不过到这里,我们可以先这么尝试检验我们的爆破结果,就是借助Xposed框架,hook这个应用获取签名的hashCode方法,将返回值替换成正确的-2081383250值:
修改之后,运行:
果然成功了,而且对于之前的去除对话框那个逻辑也可以不用那么麻烦了,这么一来整个app所有的签名校验地方都是爆破了。不过这种方式利用的是Xposed,所以不是最终方案。下面就来介绍一种高新技术来解决这个问题。
不知道大家是否还记得我之前介绍过一个系统篇系列文章,介绍如何Hook系统的各个服务,比如AMS,PMS等功能,然后在应用内进行拦截activity启动的功能。不了解的同学可以看这篇文章:Android中Hook系统服务功能;原理非常简单就是利用反射机制+动态代理技术,替换系统服务的Binder对象即可。这个技术好处是免root操作,缺点是只能在应用内部进行操作使用。那有的人好奇了,既然只能在应用本身内部有效,那有什么用呀?其实不然,现在有些开发场景就需要这个技术,及时现在没用到,将来也不一定,这不在这里就用到了。下面我们就用这个技术来Hook系统的PMS服务,拦截应用获取签名信息的方法。因为只要在本应用中操作,所以正好符合要求。
操作步骤很简单,我们在样本应用启动的入口处,加上我们的拦截代码即可,那么下面我们先把拦截PMS服务代码写好,这个代码下载地址文章末尾给出。
就是利用反射去hook系统的类,然后用动态代理构造一个自己的PMS Binder进行替换即可:
在这里我们可以通过方法名来拦截签名信息,我们用正版的签名信息构造一个新的Signature对象,然后将其设置已有的对象中即可。因为我们最终还是需要把代码放到样本案例的入口处,所以就在这里将这两个类放到样本入口类的包下面,具体包名可以手动构造:
然后将这个demo反编译,得到对应的smali代码,拷贝到样本案例对应的包下面,然后在样本案例的入口处加上方法调用:ServiceManagerWraper.hookPMS(this); 对应的smali代码如下:
invoke-static {p0}, Lcom/xiaoenai/app/ServiceManagerWraper;->hookPMS(Landroid/content/Context;)V
而样本入口可以从AndroidManifest.xml中的application找到:
在Application的onCreate方法入口添加即可,添加完成之后,二次打包,再次使用Jadx打开修改之后的apk包:
入口处代码已经添加完毕了。然后安装运行,就可以完成。而这么操作之后应用所有的签名信息就是正确的。包括native层的签名校验逻辑。所以这种方式是最靠谱的。不要改多处签名校验的逻辑了。
到这里我们就成功的爆破了一款服务端+Native双签名校验的样本案例了,而这次操作签名爆破之后,后面将不会在介绍更多的签名校验了,原因很简单,因为这次操作之后,我们发明了一个通用的爆破方式,可以解决签名校验问题了,下面就来总结一下现阶段Android中签名校验逻辑处理方式:
第一、基本法则不能忘:全局搜索字符串"signature",Java层可以用Jadx进行搜索,so中可以用IDA进行搜索。
第二、在逆向领域中,字符串信息永远是第一选择的爆破的突破口,在之前的文章我已经多次讲到了,不管是IDA,还是Jadx工具,只要全局搜索关键字符串信息,就可以找到我们想要的入口。
第三、本文的重点:发明了一种全新的高效的爆破应用签名校验逻辑,就是可以手动Hook样本应用的PMS功能,然后在应用的入口处加上hook代码。最终在hook的invoke方法中拦截想要的方法即可。
有了本文的思路,后面会开发一个工具,一键式解决签名问题,原理就是利用我之前介绍的 icodetools工具 和本文介绍的hook系统PMS服务,篡改应用签名信息。关于具体细节和工具开发敬请期待。如果此工具开发完成,那么对于签名校验的应用绝对是一个新的挑战。安全不息,逆向不止!
严重声明:本文的目的只有一个,通过一个案例来分析现在应用逆向分析技巧,如果有人利用本文内容进行任何商业目的和非法牟利,带来的任何法律责任将由操作者本人承担,和本文作者没有任何关系,所以还是由衷的希望大家秉着技术学习的目的阅读此文,非常感谢!
Hook代码下载地址:https://github.com/fourbrother/HookPmsSignature
本文介绍的内容稍微有点多,所以大家看的可能有点累,其实还有一部分内容没介绍,就是如何访问已有的so文件中的函数,变量值,这个是我在这个样本案例中用到的一个方法,限于篇幅原因就不多介绍了。但是一定要记住本文的最后一种爆破签名校验方式的方法。此等绝对高级正能量。希望可以言传。最后看完文章,如果觉得有收获,就多多点赞扩散分享,如果有打赏那就最好啦啦!!
更多内容:点击这里
关注微信公众号,最新技术干货实时推送