某小说-会员分析过程
前置工具:
雷电模拟器9.0.77.0(64) + Redmi Note 8真机 frida15.22 jadx
脱壳网站https://nop.gs/
分析流程
脱壳
首先MT提取安装包发现该APP是使用了腾讯御进行加固的
脱壳的话使用上面那个网站就好了,上传APP后等待一会就会提示成功,随便选着一个下载方式下载下来。
下载下来的是一个压缩包,解压密码是:dump
解压完成之后进入文件夹会看到类似这样的文件结构,其中7z的文件名就是该APP脱壳后的md5值,最好记下来。如果不小心丢失了该压缩包,可以通过这个md5值去上面那个网站进行查询。
application.txt文件里面存储了该App脱壳后的Activity主入口,需要我们手动修改一下。
把这个7z压缩包push到模拟器
adb push d:\Android\APP\1419bf6dec8.zip\1419bf6dec8.7z /data/local/tmp
来到模拟器该路径下发现push过来的7z包没有后缀,手动添加一下解压该包就好。
这就是脱壳后的dex文件了,我们进入原包查看一下区别
显然,脱壳包里也包含了原包中的dex文件,我们需要把脱壳包里的这两个文件删除掉。
只需要这三个就好了,全选这三个包后选着DEX修复
这样选着就好。接着我们把修复好的dex文件复制到原包中。记得先把原包中所有的dex删除掉
修改一下dex的名称,把classes4改为classes.dex就好了。
接着我们进入AndroidManifest.xml中修改一下app的入口点
在application标签中修改android:name的属性为脱壳包中txt文件提供的字符串。修改完成之后保存就好了
可以看到已经是伪加固了,签名的话使用MT给一个签名就好了。
我们安装App测试一下
程序无闪退,代表脱壳已经成功了。
会员分析
使用jadx反编译app后通过关键字快速定位到代码
例如:isVIP
显然这个是比较好的入口点,根据类名不难得到这个是用户信息类
尝试使用frida去hook这个函数使得这个函数直接返回true观察app的变化
let UserVip = Java.use("com....model.entity.User");
UserVip["isVip"].implementation = function () {
// console.log(`User.isVip is called`);
let result = this["isVip"]();
result = true;
console.log(`User.isVip result=${result}`);
return result;
};
发现显示了会员到期的时间,但是这个时间不对啊,这是取1970的时间VIP。测试会员功能也发现无法使用。
我们继续分析isVIP方法,在方法名点击按X查看交叉引用。
定位到这个方法,跟踪进入分析
代码调用了getVipEndTime函数,根据函数名不难看出是会员到期时间,不得不夸夸这app开发,真滴好。
跟进这个函数分析
直接返回了一个long类型的vipEndTime属性,查看这个属性的交叉引用
这太完美了,直接使用frida去hook这个函数,传递一个long类的参数。
let UserVipTime = Java.use("com.....entity.User");
UserVipTime["getVipEndTime"].implementation = function () {
// console.log(`User.getVipEndTime is called`);
let result = this["getVipEndTime"]();
result = 4735689600000;
console.log(`User.getVipEndTime result=${result}`);
return result;
};
观察app的变化
发现已经显示我们自定义的时间到期了,如何自定义时间呢?
4735689600000减去自1970年的时间戳就好了。
可以发现VIP功能可以正常使用了。
用户名称自定义
在用户类User中发现了一个setNick的方法,显然这个是设置用户名的,这样的话我们就能够自定义用户名了。
使用frida去hook这个函数,传入自定义的名称后观察app的变化。
let UserNick = Java.use("com.....entity.User");
UserNick["getNick"].implementation = function () {
// console.log(`User.getNick is called`);
let result = this["getNick"]();
result = "GaGa";
console.log(`User.getNick result= ${result}`);
return result;
};
发现app并没有显示我们的用户名,但是getNick也确实是获取到了我们设置的用户名,那么为啥没有显示呢?
此时换个思路分析,既然app这里显示了点击登陆那么肯定要先去判断我们有没有用户信息,如果用户信息为空的话就显示点击登陆,否则就显示用户的名称。我们在jadx中直接搜索点击登陆这四个字
竟然没有搜到,问题是:在jadx中,中文字符串是以Unicode的形式存放的,所以我们需要搜索
\u70b9\u51fb\u767b\u5f55
如果B为false,那么就直接走else分析,显示点击登陆。显然不能让B返回false。
我们找到这个B函数分析
如果user为空或者getMobile函数返回空,那么这个函数就直接返回false,问题就在这里了,我们让getMobile函数返回一个值,这样B函数就会返回true了。至于g为什么不为空?当我们调用setNick函数的时候这里的user就已经不为空了。
分析getMobile函数
![[imgae-番薯 27.png]]
直接返回mobile的值,既然这个函数要反会一个String类的数据,那么我们直接让他返回我们的名称不就好了
使用frida去hook他
let User = Java.use("com....model.entity.User");
User["getMobile"].implementation = function () {
// console.log(`User.getMobile is called`);
let result = this["getMobile"]();
console.log(`User.getMobile result=${result}`);
return "GaGa";
};
再次观察app变化。
实习/校招/社招
安全服务工程师、安全攻防工程师、安全研发工程师、前后端开发工程师、测试工程师等等,所有岗位均可内推 |
推荐阅读
渗透实战|记一次简单的Docker逃逸+反编译jar接管云主机