大家会好奇这篇文章感觉有种似曾相识的感觉,标题不一样了?的确之前那篇文章因为某种原因被删了,为什么要删大家懂得!的确是我的过错,标题弄得太明显给某公司带来烦扰,所以就整理再发一次!看过的同学就当回顾,没看过的就认真看一下吧!不过本文会增加一个知识点,就是今天有个同学在看我的博客(博客这篇文章没有删)的时候发现,某音的应用已经做了安全提示了:
看到最新版的应用已经开始检查设备是否安装Xposed了,如果安装就给这个提示,检查的原因就是为了保证应用的安全不会hook操作!但是比较奇怪的是,他只是检查不做任何其他处理!也不知道是不是我之前发的文章导致他们加了这个检测机制还是什么原因?先不管这个了,我们来看看应用对Xposed检测有哪些方法,其实这个在看雪上有大神已经总结了,主要是支付宝先做的策略,而且在很早有人在知乎上提问了:
为了应用安全这个操作是必要的,看雪上的那个大神通过分析支付宝代码得到了检测Xposed策略大致如下:
1、支付宝的Xposed检测原理是: Xposed框架将Hook信息存储在字段fieldCache,methodCache,constructorCache 中, 利用java 反射机制获取这些信息,检测Hook信息中是否含有支付宝App中敏感的方法,字段,构造方法
2、支付宝的SO检测原理是: 检测进程中使用so名中包含关键”hack|inject|hook|call” 的信息
3、支付宝的Root检测原理是:是否含有su程序和ro.secure是否为1
具体方案大家可以网上搜索即可。但是这种方式支付宝做的不好是直接明文的在代码中检查这几个字段,那么解决就太简单了,直接用Jadx打开应用全局搜索代码中包含这几个字段,然后找到关键代码把检测代码给注释即可!所以为了安全可以把这些字段加密放到so中检查安全性会更高。当然检测到应用被hook了就可以给个提示或者某些功能用不了都可以的!
还有一种防止被Hook的方式就是可以查看XposedBridge这个类,有一个全局的hook开关,所有有的应用在启动的时候就用反射把这个值设置成true,这样Xposed的hook功能就是失效了!
有的同学会好奇怎么反射操作呀?因为Xposed的hook原理的就是在程序启动都注入jar功能,所以安装hook模块之后,每个应用内部都包含了这个Xposed功能jar,就相当于你的应用中有了Xposed的所有功能类,所以在应用中反射Xposed的类是可以成功的,当然前提是设备装了Xposed并且有相应的hook模块!而这些方案已经都是公开的,一些应用为了防止被hook做了很强的安全防护策略,大家感兴趣可以去看看”一号店”应用,会发现hook他也是失败的,并且很难找到相应的检测机制!这个我也是没时间一直没研究,后面会研究出文章的,敬请期待!
不多说了,赶紧来分析某短视频应用,不过本文不在利用粗暴的静态方式去破解了,应广大同学要求,就介绍IDA动态调试so来进行破解,这样也能给大家带来更多IDA的使用技巧。毕竟我写文章技术都是为了你们。这种分析请求数据的突破口一般都是抓包,这不用多说了。不过都是用了https请求,所以需要手动在设备中安装Fiddler证书,才能抓到正确的数据信息:
打开某音之后,看到数据刷的很快,发现一个feed的接口是返回的首页的数据,在分析它的请求参数中有三个字段是minCursor,maxCursor,count其实这三个字段就是后面他进行数据分页请求的关键,到后面再详细说。不过这里看到还没有什么问题,不过问题往下看他的更多请求参数,会发现两个字段:
这时候会发现其他参数都和本地设备有关或者直接写死的,唯独这两个参数信息是始终变化的。所以猜想这两个字段是用于请求协议数据加密和服务端进行校验的。那么如果我们想单独构造信息去请求,这两个字段的信息一定要正确,不然请求不到正确的数据的。好了,这里简单了,使用Jadx打开app即可,直接全局搜索字段的信息”as=”:
看到这里是构造了as和cp两个字段的值,直接点击进入即可:
这里大致的逻辑也比较简单,利用UserInfo.getUserInfo函数获取字符串,然后对半开给as和cp两个字段,右移操作就是除以2的意思。这里不分析代码来看看参数怎么来的,直接用Xposed进行hook这个函数打印参数看结果,粗暴快捷,以后其实这都是快速解决问题的一种方式,hook大法是最无敌的
先来看一下这个加密方法的参数信息,看到是native的,也就是说加密操作是在so中做的,后面需要调试的也是这个so了。也不管了,hook这个方法然后打印信息看参数构造:
直接运行模块,打印日志看信息:
看到三个参数打印的值,发现大致三个参数的意义是:当前时间戳,请求url,请求参数的数组信息。好了,我们现在可以新建一个Android工程,然后构造这三个参数信息,然后调用它的so函数,为了能够快速找到这个so名称可以用万能大法:全局搜索字符串信息”System.loadLibrary” 即可:
这样就找到了这个so名称了,到libs目录下把这个so拷贝出来到我们自己构建的demo工程中,这里先不着急调试去分析native的加密函数功能,还是老规矩拿来主义,直接上层构造一个和他一样的包名的native函数,调用它的so获取结果即可:
然后就开始构造参数信息了,这里为了简单,先把那些公共的参数信息写死比如设备的sid,aid,版本号等信息:
这里我们利用公众参数信息,构造请求字段数组信息,然后还有单独的几个字段值不能参与操作,可以通过之前的打印日志分析出来。当然时间戳也是服务器格式,和本地是少三位的,直接除以1000即可。下面为了简单直接把公众参数写死即可,当然后续优化就是把这些参数值进行动态获取填充即可:
构造好参数之后,然后就开始调用native函数,然后获取返回结果即可:
到这里,我们构造的工作就完成了,下面就开始直接运行吧,不过可惜的是,没有这么顺利,直接运行闪退了:
这里应该进行加载so出现问题了,那么我们在回过头看看我们是不是有些环境没初始化,看看UserInfo类中是不是还有一些初始化方法没调用:
这里看到的确有两个方法有点可疑,全局查看这两个方法的调用地方:
看到全局中就一个地方调用了setAppId方法,而且值就是写死的为2,另一个方法initUser就有点麻烦了,不过还是万能的hook大法,直接hook这个方法打印他的参数信息即可
运行模块,查看打印的日志信息:
看到了,参数信息一致都是这个字符串信息,直接拷贝出来赋值调用即可:
在次运行,发现可惜了,还是报错,而且诡异的是日志没打印,也就说setAppId和initUser函数没有调用就退出了,那么就会想到?是不是so中的JNI_OnLoad函数中做了一些判断逻辑呢?直接用IDA打开这个so文件即可:
打开之后找到JNI_OnLoad函数,F5查看他的C代码,发现果然内部有很多判断,然后直接调用exit函数退出了。所以如果想成功的调用它的so方法,得先过了他的这些判断了,这里就要开始进行调试操作了,其实这里可以直接静态分析有一个快捷的方法,但是为了给大家介绍动态调试so技巧,就多走点弯路吧,后面再说一下粗暴简单的技巧。
下面就来开始进行调试so文件了,关于IDA调试so文件,在我的新书Android应用安全防护和逆向分析中的第22章详细介绍了,感兴趣的同学,可以点击底部”阅读原文”进行购买,这里不会在详细介绍具体步骤了,直接上手干:
第一步:拷贝IDA安装目录下的android_server文件到设备目录下
第二步:运行android_server命令
第三步:转发端口和用debug模式打开应用
这里有同学好奇为什么不需要修改xml中的debug属性呢?因为我调试的是我自己的demo工程,而Eclipse默认签名打包出来的apk这个属性值就是true的,所以不需要进行修改了。
第四步:启动IDA附件进程
设置本地地址和一些选项:
因为现在已经知道需要调试JNI_OnLoad函数,所以需要设置挂起load函数处,然后选择调试的应用进程:
第五步:查看调试端口连接调试器
在Eclipse中的DDMS中查看有红色小蜘蛛的调试应用端口号是8647,然后连接调试器:
这里一定注意端口正确,不然链接操作的。
第六步:点击IDA中的运行按钮,或者F9快捷键
这时候发现红色蜘蛛变成绿色了,而且调试对话框也没有了。这时候就进入调试页面了:
为了安全起见,再一次查看debug选项有没有挂起load函数:
如果发现没有,还需要进行手动勾选上:
因为我们给JNI_OnLoad函数挂起了,而一个应用会加载很多系统的so文件,所以这里一直点击运行或者F9:
过了系统so加载步骤:
这些都是系统的so文件加载,所以一路往下都直接运行越过调试即可:
可以看到这里会加载很多系统的so文件,当我们点击应用的按钮,加载自己的libuserinfo.so文件的时候才开始进行调试工作:
点击OK加载进来,然后就停留在了挂起状态了,这时候,我们在右侧栏查找这个so文件:
然后点击,继续查找他的JNI_OnLoad函数:
点击进入JNI_OnLoad函数处,下个断点:
因为我们在之前静态分析了这个函数内部有很多个exit函数,为了好定位是哪个地方exit了,所以在每个exit函数之前下个断点,来判定退出逻辑,这里下断点有技巧,因为是if语句,所以在arm指令中肯定就是CMP指令之后的BEQ跳转,所以在每个CMP指令下个断点即可,这里发现了9个地方,所以下了断点也很多,慢慢分析:
第一处的CMP指令下个断点,然后运行到此处,查看R3寄存器值是否为0:
是0,那么第一处exit就没问题,接着往下走,直接按F9到下一个CMP判断指令断点处:
发现第二处的CMP中的R3寄存器值也是0,所以也没问题,直接F9到下一个断点:
到了第三处判断发现R3寄出去你的值不是0了,而是1,所以为了继续能够往下走,就修改R3寄存器值即可,在右侧栏的寄存器中右击R3寄存器,然后点击修改值:
把1改成0,保存即可:
修改成功了,运行发现就过了判断:
就继续往下走,到下一个断点:
这里有问题了,我们如果直接F9到第四处CMP的话,发现直接退出调试了,说明在3和4处判断中间有问题了,最终发现是这个跳转指令出现的问题,这里需要多次单步调试F7键,定位出现问题的地方了,我们进入这个跳转地址:
然后发现内部还有BL指令,出现问题了,继续深入查看:
看到了,这里发现问题的原因了,有一个GlobalContext类,而这个类应该是Java层的,native层应该用反射机制获取全局的Context变量,我们直接去Jadx中搜索这个类:
那么问题就清楚了,因为我们的demo工程中压根没这个类,所以native中获取全局context变量就失败了,所以就exit失败退出了,解决办法也简单,直接在demo工程中构造这个类,然后在Application中初始化context即可:
构造的时候一定要注意包名一致,然后在demo中的Application类进行设置context即可:
然后再次运行项目,可惜还是不行,所以还得进行调试JNI_OnLoad函数了,方法步骤和上面类似,不多说了,不过这里应该是过了上面的Context获取失败的问题了,继续往下走,发现了重要信息了:
这里有一个类似于MD5的值,继续往下走BL处:
看到R0寄存器中的值,发现是demo应用的签名信息,继续往下走BLX看看:
这里看到大致清楚了,是获取应用签名信息,也就是签名校验了:
好吧,在这一处判断exit函数中,应该是通过反射获取Java层的context值,然后在获取应用的签名信息和已经保存的原始某音签名信息做对比,如果不对就退出了。那么过签名校验也很方便,直接利用我之前的kstools原理,把hook代码代码拷贝到工程中,拦截获取签名信息,然后返回正确的签名信息即可:
具体的hook代码去看我的kstools实现原理即可:Android中自动爆破签名信息工具kstools;然后hook代码一定要在Application的第一行代码调用,不然没有效果的。这样操作完成之后,其实已经出数据了,不过为了能够进入加密函数进行调试分析具体的加密算法,我们继续调试JNI_OnLoad函数:
继续往下走,会发现在第五个exit判断之后,有一个函数出问题了,就是这个BL,进入内部查看:
哈哈,发现了这个字符串信息,弄过调试的同学大致都猜到了,这个是反调试操作,继续往下走:
这里可以百分百确定是读取status文件中的TracerPid字段值来进行反调试检测了,给下面的两个CMP指令下个断点:
发现R3寄存器中的确不是0,所以为了过了反调试,直接修改R3寄存器值为0即可:
这样就过了反调试检测了,单步往下走到下一个exit的判断断点,最终还有一个地方需要处理就是第七个CMP指令:
处理方法直接修改R3寄存器中的值为0即可,就这样我们成功的过了JNI_OnLoad中的所有判断exit的地方了:
然后给加密函数getUserInfo下个断点:
直接点击进入即可:
也成功到达了加密函数断点处,这里已经没有任何判断逻辑了,就是一个单纯的加密函数了,不过这里不在进行调试分析了,感兴趣的同学可以自己操作了,因为我们的目的达到了,就是成功的获取到了加密之后的数据了:
看到了,我们成功的获取到了as和cp值,然后构造到请求url中,也成功的拿到了返回数据。我们这里多了一步解析json数据而已,原始的json数据是这样的:
之所以要解析,也是为了后面的项目准备的,到时候我会公开项目的开发进程。不管怎么样,到这里我们就成功的获取某音的加密信息了。主要通过动态调试JNI_OnLoad函数来解决so调用闪退问题,在文章开头的时候说到了,其实本文可以直接用简单粗暴的方式解决,就是万能大法:全局搜索字符串信息,包括Jadx中查找和IDA中查看,因为字符串信息给我们带来的信息非常多,有时候靠猜一下就可以定位到破解口了,比如这里,我们在IDA中使用快捷键Shift+F12打开字符串窗口:
凭着这些关键字符串信息就能断定so中做了哪些操作。记住这些敏感的字符串信息,对日后的逆向非常关键。
上面就解决了应用的请求数据加密信息问题了,下面来总结一下本次逆向学习到的技术:
- 第一、看到在native层用反射去调用Java层的方法获取信息也是一种防护so被恶意调用的方式。比如本文的context变量获取。
- 第二、签名校验永远都不过时,其实本文当时没想到他有签名校验,因为看到native函数中都没有传递context变量,谁知道他是用反射调用Java层方法获取的,长知识和经验了。
- 第三、反调试也是永远不过时的,不过他这里的反调试检测有点简单了,就一处而且就一个进程,如果高级点应该启多个进程,循环检查tracepid值进行校验,会增大难度。
- 第四、在逆向中有时候在Java层没必要去花时间分析一个方法的参数和返回值构造情况,直接利用Xposed进行hook大法打印方法的参数信息靠猜也就出来了。
- 第五、静态方式分析永远都不会过时,全局搜索字符串也是最基本法则,靠猜就可以快速获取结果。
上面解决了某音的加密问题,下面再来看一下某山小视频的加密信息,突破口依然是使用Fiddler进行抓包查看数据:
通过抓包会很神奇的发现,和某音的数据结构字段几乎异曲同工,果然是自家兄弟,继续查看他的加密字段:
这里不在多说了,既然加密的字段都是一样的。那么我们直接不多说用Jadx打开某山小视频,看看他有没有那个native类UserInfo信息:
看来不用多一次分析了,完全一样,那么直接用同一个so,同一个加密即可,在demo工程中运行查看日志:
初始化都是一样的,直接运行:
看到了,数据也回来了,看看他的原始json数据:
到这里,我们就成功的破解了某音和某山小视频的数据请求协议了,有了这两个短视频数据,后面我们开发app就简单了,当然在文章开始的时候也说了,现阶段短视频四小龙:某音,某山,某拍,某手,那么已经干掉了前面两个,下一个会是谁呢?争取在年底把这四个app全部爆破成功,能够请求到他的数据。为我们后面的app开发作为基础。
严重说明
通过本文可以发现,我们其实没有真正意义上的破解它的算法,但是结果却是我们想要的,这就够了。而对于现在很多app把加密算法放到so中,在对so做一些防护,这样就很难利用本文的技术去调用app的so了。不过so再怎么防护就是那么几种方法,我们依然可以用动态调试来解决。有人在文中很好奇那些判断校验不能直接修改so指令做到吗?比如签名校验,没必要在Java层进hook呀,直接修改CMP和BEQ指令呗?的确可以这么做,但是这是下一篇介绍的内容,因为我们下一篇要破解下一个短视频app,就用到这种方式过校验。敬请期待!每次讲解调试就需要大量截图非常累,所以大家看完觉得好就多点一下广告和分享更多喜欢逆向的同学吧!