最近萌发了一个做app的念头,大致什么样的app先暂时不说,后面会详细介绍这个app的开发流程和架构,不过先要解决一些技术前提问题,技术问题就是需要分析解密当前短视频四小龙:某音,某山,某拍,某手这四家短视频的数据请求加密协议,只有破解了加密协议,才能自定义数据请求拉回数据。不多说了,本文先来第一站搞定某音和某山这两个数据请求的加密协议,为什么说是这两个呢?因为我们在后面分析会发现这两个app其实用的是一套加密协议,毕竟这两个app都是某头条内部孵化的项目。所以只要搞定一个即可。我们决定搞定某音吧,毕竟我比较看好和喜爱看某音。
不多说了,赶紧来分析吧,不过本文不在利用粗暴的静态方式去破解了,应广大同学要求,就介绍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中IDA动态调试so文件详解;这里不会在详细介绍具体步骤了,直接上手干:
第一步:拷贝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中做了哪些操作。记住这些敏感的字符串信息,对日后的逆向非常关键。
上面就解决了某音的请求数据加密信息问题了,下面来总结一下本次逆向学习到的技术:
上面解决了某音的加密问题,下面再来看一下某山小视频的加密信息,突破口依然是使用Fiddler进行抓包查看数据:
通过抓包会很神奇的发现,和某音的数据结构字段几乎异曲同工,果然是自家兄弟,继续查看他的加密字段:
不想多说了,既然加密的字段都是一样的。那么我们直接不多说用Jadx打开某山小视频,看看他有没有那个native类UserInfo信息:
哈哈,看来不用多一次分析了,完全一样,那么直接用同一个so,同一个加密即可,在demo工程中运行查看日志:
初始化都是一样的,直接运行:
看到了,数据也回来了,看看他的原始json数据:
到这里,我们就成功的破解了某音和某山小视频的数据请求协议了,有了这两个短视频数据,后面我们开发app就简单了,当然在文章开始的时候也说了,现阶段短视频四小龙:某音,某山,某拍,某手,那么已经干掉了前面两个,下一个是谁呢?猜对有奖。争取在年底把这四个app全部爆破成功,能够请求到他的数据。为我们明年的app作为基础
严重声明
本文的意图只有一个就是通过分析app学习更多的逆向技术,如果有人利用本文知识和技术进行非法操作进行牟利,带来的任何法律责任都将由操作者本人承担,和本文作者无任何关系,最终还是希望大家能够秉着学习的心态阅读此文。鉴于安全问题,样本和源码都去编码美丽小密圈自取!
通过本文可以发现,我们其实没有真正意义上的破解它的算法,但是结果却是我们想要的,这就够了。而对于现在很多app把加密算法放到so中,在对so做一些防护,这样就很难利用本文的技术去调用app的so了。不过so再怎么防护就是那么几种方法,我们依然可以用动态调试来解决。有人在文中很好奇,那些判断校验不能直接修改so指令做到吗?比如签名校验,没必要在Java层进hook呀,直接修改CMP和BEQ指令呗?的确可以这么做,但是这是下一篇介绍的内容,因为我们下一篇要破解下一个短视频app,就用到这种方式过校验。敬请期待!