1、前言
相信大家都有看小说的经历,小说大部分是免费的,但是看着看着容易出广告且摇晃一下就跳到广告详情页面,这十分恶心,无意间也听到某个同事抱怨,怀着学习的想法,试着逆向某猫小说,并去广告,最后成功修改,以下用于记录逆向过程
2、前置知识和准备
- 一部root的安卓机,模拟器不行(后续会解释到)
- 安卓基础、MT管理器
- lsposed+frida
- IDA(交互式反汇编器专业版)、010 Edit、jadx-gui等
- 一些汇编的基础知识和部分ARM指令集(我也是模棱两可)
- 了解二进制文件的大小端模式
3、开始逆向
3.1、导入jadx查看源码
第一步毫无疑问是下载apk然后导入jadx-gui,导入解析结果如下
打开发现没有进行加壳,这可太好了,省去了脱壳的步骤
3.2、尝试搜索关键字
一般带有会员机制的都会有vip,vip_level,is_vip等字眼
果不其然 搜出很多,可以做一下尝试,看到方式是判断和1是不是相等,那直接改为true就好了
3.3、MT文件管理器修改smali代码
通过MT管理器找到对应的dex文件,然后在返回值之前添加一个true的代码
保存之后转为java代码查看效果
已经生效了
参考以下链接(七猫小说破解思路)
同理需要改一下所有方法
isVipExpired
isVipState
isShowYearVip
在baseinfo类名底下赋值1
在做了一系列操作之后,重新打包安装打开一看
好家伙,一看就是做了签名校验,并且提示了两个弹窗,接下来就是做去签名校验的逻辑了,
3.4、frida分析签名校验位置
根据弹窗结果,搜索关键字参数错误/验签失败,都无果,那么接下来上frida神器,找一下弹窗的位置,看看是在哪做了签名校验,也能通过MT、NP一些工具的去签名校验做尝试
function printStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
function main() {
Java.perform(function () {
var toast = Java.use("android.widget.Toast");
toast.show.implementation = function () {
console.log("toast.show: ");
printStacks();
return this.show();
}
})
}
setImmediate(main);
好家伙,当我执行frida的时候,给我直接Process terminated了,这应该是做了frida的检测
4、过frida进程检测
4.1、定位过检测的so
过frida检测有很多方案
- 检测frida-server文件名
- 监听frida端口
- 检测/proc/pid/maps映射文件等
方案1和方案2都可以通过简单的操作进行测试,但是经过测试任然退出了,那么只能霸王硬上弓了,硬分析,根据so的加载流程,会走dlopen函数,那么先hook一下dlopen函数看看是哪个so文件检测到了 然后退出了
function hook_dlopen() {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log("load " + path);
}
}
});
}
function main() {
Java.perform(function () {
hook_dlopen()
})
}
setImmediate(main);
如图清晰明了看出来是加载了libsaoaidesc.so文件,看起来像是某家加固厂商的so文件,网上有很多分析该文件的检测流程,这里不做分析,直接暴力不让他加载这个so文件就好了,直接就能跳过检测
这里有个坑就是必须用真机才能找到加载的so文件,因为模拟器的架构一般是x86的而app中没有x86的so文件,只有armv8a和v7a架构的,用frida是扫描不到的,可以参考该链接
function hook_dlopen() {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if (path.indexOf('libmsaoaidsec') >= 0) {
//加载一个空的字符串
ptr(pathptr).writeUtf8String("");
console.log("rewrite libmsaoaidsec.so ptr ");
}
}
}
});
}
可以看到已经跳过了检测
5、分析签名校验
5.1、调用链路hook
过了frida之后进行toast的hook,查看校验路径,发现是起了的线程去弹窗了
继续hook调用位置,看看是怎么触发的
这里有两个思路,因为文本是传入的,可以hook构造方法,看看这个“验签失败”是传入的,还是getString出来的,分别做尝试,做了构造方法尝试,果然是传进来的
hook一下这个异常类,看看在哪创建的
其实到这里就已经不用看了,BaseResponse意味着这是一个响应体,说明思路应该从HTTP请求入手了,“验签失败”是由某个响应体返回的,接下来就是爬虫抓取接口分析了
5.2 爬虫寻找验证接口
手机端的爬虫这里我用了Reqable,小黄鸟,有两种方式 1、手机和电脑是一个局域网,手机代理了电脑的IP和端口,请求打到电脑上查看请求 2、手机直接抓包,本机开启代理直接进行抓包
这里选择第二种,安装好证书之后开启中间人代理,直接全局搜索关键字
通过和不修改任何操作的安装包抓包最后发现是sign的问题,sign传入了一个FAIL的值,问题转为SIGN的值为什么是FAIL的问题了
5.3、Sign接口的分析
上一节提到因为sign的值为FAIL导致的请求失败,那么肯定在某个位置传入了sign的值,只要逆向出这个sign的值就能过签名校验了,思路还是老样子全局搜索sign关键字寻找,这里找出很多,有个技巧就是寻找跟APP包名的,其他的可能是第三方依赖的sign
最后定位到这里
继续跟踪,哦豁,发现是一个native方法了,接下来就要分析so文件了,但是发现没有System.loadLaibary这个方法,不确定是在哪个so文件中了
5.4、分析so层的sign获取
上节讲到native方法不知道是在哪个so文件中,要确定的是肯定是在libs中的所有so文件,所以有以下思路 1、native方法肯定会会被导出,所以通过Frida hook所有导出函数确定在哪个so中即可 2、如果是静态导出的java方法,可以通过JAVA_COM..(导出函数的命名规则)的关键字搜索所有so文件
最后再libcommon-encryption.so中找到,具体的方式可以参照这篇文章
进来之后发现这里有很明显的FAIL关键字,也就是sign
这里逐步分析,首先是 (v6 & 1) != 0,说明v6必须是0,不然就为fail了,然后
v6 = AndroidUtils::isCheckFailed(v5),也就是说v5作为参数传进去返回的值必须是0
,发现v5是通过init创建一个线程返回,然后调用回调返回回来的,
点击进去thread_callback,大致看了一下发现有个未混淆的方法名:checkSignUseApplicationPackageManager,其实这里已经可以看出来大致的意思了。
通过PackageManager去获取签名,所以核心签名校验就在这里,点进去发现运用了大量的Java反射和加密算法,要看懂需要花点时间
5.5、修改so层源码跳过签名校验
上节分析到是通过这个方法的返回值来判断的,那么直接对这个方法,交叉引用,然后取反就能跳过签名校验了
简单分析一下这里,如果签名校验不通过那么v4就等于2和自然就报错了,那么只要把v4无论怎么样都改为1就行了
这里看一下地址,需要把#2改为#1,怎么改呢,要打开arm64的指令集手册
由于elf是小端模式所以要反过来 52 80 00 48,转为2进制为 01010010 10000000 00000000 01001000,查看一下指令集,这里刚好能对的上,也就说5-20位是立即数,我们取出原来的就是 0100000000000000 转为十进制就是2 那么我们要改为1 就是 1000000000000000 最后装回去就是 01010010 10000000 00000000 00101000 再改为十六进制就是 52 80 00 28,其实和上面的地址是一样的,这里只是解析一下怎么来的
那么接下来就简单了,打开IDA然后编辑HEX 改为28然后应用即可,查看结果
可以看到无论怎么样v1的值都会1即可,同理改掉其他引用
5.6、替换so文件然后重新打包
这里用010Editor查找48008052,都改为28008052,然后保存为新的so文件
然后再重新安装即可食用,再次打开就发现没有弹出验签失败的问题了
6、总结
写这篇文章的意义是在于记录如何从0开始完整的逆向到成功的一个流程,用于学习,仅供参考
有了思路才有技术,有了技术才有出路