前言

某成人水果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,间接调用解密函数进行解密

  1. 编写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,}
  1. 设置brida

  2. 点击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中

综合以上剖析,破解的步骤如下

  1. 屏蔽掉/api/mv/watching 接口调用
  2. user.getIs_vip 永远返回true

破解

  1. 应用frida进行hook, 验证想法是否正确。一切正常,胜利运行
  2. 关上AndroidKiller, 反编译出smail代码
  3. 批改smail代码,批改如下。这里为了UI界面失常显示,顺便批改了getVip_level , 让它永远返回4


    间接goto掉接口调用相干的逻辑

  4. 回编译,重签名。最终成果如下

结语

破解APP能够先推测开发的开发思路,而后通过抓包,反编译,动态剖析,hook,逐渐了解APP运行逻辑。最终制订破解计划,批改代码,实现破解。这次又白嫖了一个vip,真香