前言
某成人水果APP非VIP用户存在播放次数限制,每天只能播放3次。超过3次需要购买VIP。 由于过于贫穷,于是抽空,对其安卓APP进行了逆向分析,最终成功破解了其播放次数限制。
破解思路
思路一
通过使用发现,当我点击列表,跳转至视频详情页时,播放次数就会减一。如果APP开发人员,更改播放次数和获取视频详情时使用了两个单独的接口,我们就可以通过屏蔽更改播放次数的接口,让可播放次数永远不减少,从而实现无限播放
思路二
APP存在匿名注册,安装并打开APP后,无需手机号和邮箱,会自动生成一个匿名账号。这种匿名注册一般都是会根据手机硬件相关的信息生成uid。这样的话,可以在每次播放次数用完后,hook手机硬件相关的API, 返回新的硬件信息,注册新的匿名账号,从而实现无限播放
抓包
有了大致思路,老规矩,先抓包看看APP的接口信息。打开BurpSuite, 配置代理。发现请求和响应都存在加密。
所以需要先解密。
查找解密函数
因为App最终还是需要使用明文数据的,所以APP中肯定有解密函数。
打开Jadx-gui, 将apk打开,发现没有加壳。搜索请求参数关键字_ver, timestamp,sign等,定位到加密逻辑
EncrytUitl.encrypt
这个方法进行了加密,EncryUtil.decrypt
这个方法进行解密。
解密
由于加解密都是通过so库完成,逆向出完整解密逻辑比较困难并且也没有必要。所以直接通过frida + brida + burpsuite,直接调用解密函数进行解密
编写frida hook代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | function calldecrypt( str ) {
if (! str ) {
return ;
}
return new Promise((resolve) = > {
Java.perform(function x() {
const encryptUtil = Java.use( 'com.qq.lib.EncryptUtil' )
const ret = encryptUtil.decrypt( str , 'PT0dPVxYXglbDARZXwlfXwwMDApYDgReWV4JXl4ICAtfWAxcJD4WCD8SFRYCFQ==' )
resolve(ret)
});
});
}
function callencrypt( str ) {
if (! str ) {
return ;
}
return new Promise(resolve = > {
const encryptUtil = Java.use( 'com.qq.lib.EncryptUtil' )
const ret = encryptUtil.encrypt( str , 'PT0dPVxYXglbDARZXwlfXwwMDApYDgReWV4JXl4ICAtfWAxcJD4WCD8SFRYCFQ==' )
resolve(ret)
});
}
rpc.exports = {
calldecrypt,
callencrypt,
}
|
设置brida
点击decrypt标签即可解密
watching接口
解密后,发现/api/mv/watching
接口非常可疑,很像是用来标记观看次数。报文如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | / / request
{
"bundle_id" : "gov.bkzuj.pikpnf" ,
"id_log" : "83952" ,
"log" : "{\"83952\":1}" ,
"new_player" : "fx" ,
"oauth_id" : "98fd1cc24524a16ee0f8ef0e76040411" ,
"oauth_type" : "android" ,
"timestamp" : "1674835200" ,
"token" : "867cee9b486e89748097588652a94b0a" ,
"version" : "3.3.0"
}
/ / response
{
"crypt" :true,
"data" :{
"canWatchCount" : 3 , / / 可以观看的次数
"left" : 1 , / / 剩余次数
"todayTimestamp" : 1674835200 ,
"watched" : 2 / / 已观看次数
},
"isLogin" :false,
"isVV" :false,
"msg" :"",
"needLogin" :false,
"status" : 1
}
|
使用burpsuite intercept并且drop相关请求,可观看次数的确没有减少。
然而经过多次尝试,发现次数虽然没有减少,但是当连续观看3次之后,需要重启APP或者去个人中心页面,刷新个人信息,才能继续观看。
猜测可能是因为APP会在内存中维持一个状态,进行计数。重启或者去个人中心页面刷新,会重置这个状态。
继续抓包,发现重启或者刷新个人中心页面,都会调用/api/users/getBaseInfo
,其响应报文中有一个can_watch字段
1 2 3 4 5 6 7 8 9 10 11 12 13 | {
"crypt" :true,
"data" :{
/ / ...
"can_watch" : 1 ,
/ / ...
},
"isLogin" :false,
"isVV" :false,
"msg" :"",
"needLogin" :false,
"status" : 1
}
|
搜索can_watch相关代码
代码逻辑为,接口请求成功后,如果can_watch >= 0
, 则将其存放到sp中,key为key_left_watch_count
搜索key_left_watch_count
相关代码
相关代码逻辑大致为,进入详情页播放时,判断是否为vip,如果是,则跳过can_watch
逻辑,直接可以播放。如果非vip,则从sp中读取can_watch
的值,如果大于1,则可以播放,并且将can_watch - 1
后重新写入sp中
综合以上分析,破解的步骤如下
- 屏蔽掉
/api/mv/watching
接口调用
- 让
user.getIs_vip
永远返回true
破解
- 使用frida进行hook, 验证想法是否正确。一切正常,成功运行
- 打开AndroidKiller, 反编译出smail代码
修改smail代码,修改如下。这里为了UI界面正常显示,顺便修改了getVip_level
, 让它永远返回4
直接goto掉接口调用相关的逻辑
回编译,重签名。最终效果如下
结语
破解APP可以先揣测开发的开发思路,然后通过抓包,反编译,静态分析,hook,逐步理解APP运行逻辑。最终制定破解方案,修改代码,完成破解。这次又白嫖了一个vip,真香
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!