共计 2549 个字符,预计需要花费 7 分钟才能阅读完成。
前言
某成人水果 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 代码
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
接口十分可疑,很像是用来标记观看次数。报文如下
// 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 字段
{
"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,真香😀