首页
社区
课程
招聘
[原创]无限续杯——从app破解角度学习安卓保护手段
2022-12-5 17:48 104956

[原创]无限续杯——从app破解角度学习安卓保护手段

2022-12-5 17:48
104956

0.前言

最近tz不太好用,之前用的破解版的某洞已经彻底挂了。于是在网上找了一个某豹加速器。但是下载后发现只有两小时的会员时间,用完后还要花重金续费。作为社会主义爱国青年,面对这种违法app还在肆无忌惮的收费的现象,当然是不能忍了,于是决心破解之,以达到维护中华人民共和国宪法与法律尊严的目的!

违法app截图:

1.root检测

将app装在手机上打开,结果有提示:


应该是检测到root了,因为我在用一台root过的手机进行实验。

使用jadx加载apk,全局搜索关键字:检测到当前设备

确实有这个字符串,他的id名叫 toast_app_fail,再次搜索这个id名看看哪里引用了这个:

发现了很多处调用,主要在MainActivity与StartActivity里。出现在这两个文件里也正常,因为这通常是android程序最早启动的两个类,很多检测都会放在启动阶段做。注意到每次调用有不同的后缀,逗号,叹号等等。我们的是逗号,所以选择后缀为逗号的进入。

看到调用了DeviceUtils.a()函数,如果返回true就会弹出 检测设备...字符串,然后退出。看看.a()函数是什么

好家伙,果然是在检查root。检查设备是否root的常规方式就是尝试打开这些/system目录下的su文件,如果能打开,则说明设备有root权限。

使用MT管理器打开包,找到a函数,通过修改smali代码,让a函数不检测而直接返回false即可:

直接让他返回v0即可。

重新打包,安装。又弹出了那串文字,不过后缀变成了感叹号:

查看这个感叹号出现的位置:

好家伙,看到那个DeviceUtils.AntiRoot()了。如此欲盖弥彰的安全保护,真令破解者狂喜。本着学习的态度,我们还是看一下这个函数怎么antiroot的:

点进去发现是一个native函数,而加载的so叫myapplication:

使用ida加载libmyapplication.so这个文件,全局搜索AntiRoot:

发现还是在检查 su 文件,如果发现有就返回“yes”,没有就返回“no”,这和java代码中检测对上了

而check_su_files()还是在本地尝试打开各种su文件:

既然如此,可以直接用MT管理器修改java代码,将比对的“yes”改成“f*ck”,这样就算检测到root,java层的比对也会失败。

至此,root检测就全部绕过了。可以打开app了。但是打开后一片空白:

这种情况通常是因为没有拿到正确的返回数据导致的。app打开后肯定会向后端请求各种数据,如果数据异常,则无法正常显示。这时候就要抓包看看了。

2.代理检测

打开手机代理,

启动fiddler,结果app又无法正常启动,这次弹出了这个:

app生怕你不知道他在检测代理,给出了非常温馨的提示。那我们就按图索骥。

看到字符串id是 toast_api_proxy_fail,查找引用:

和root检测的流程基本相同。不过是调用了一个b函数来检测是否有代理的,看看b函数:

可以看到,代理检测的逻辑是要确保 http.proxyHost(代理地址) 为空字符串,或者http.proxyPort(代理端口)为-1。

我们直接修改java代码让他始终返回false就好。

这样之后,就可以成功打开app,并且抓到了数据包:

但是,app内部依然是一片白,查看返回的数据,发现是加密的:

看到后面的== 以为是base64,但是base64解码失败,于是猜测是aes或者des加密。

3.数据解密

去哪里找解密的地方呢?这需要研究andorid系统网络请求的数据流了。

通常,客户端会使用okhttp作为http客户端进行收发请求。(目前还有使用cronet做客户端的,但仅限于字节这样的大厂,而且大多是在okhttp上嫁接的。)

okhttp工作方式是责任链,或者说pipline,每一环节处理一些事情,比如在发送阶段,第一个pipline是添加基本信息,如设备id,时间戳,等等。第二个pipline是计算签名值,第三个pipline是把发送数据加密,然后发出去。

收包时候也有pipline,解密操作一般就放在某个pipline中。所以我们的目的是找到okhttp客户端创建的地方,然后找到他添加pipline的地方。

查看okhttp,好家伙,被混淆了:


通常,okhttp的创建是在okhttpclient这个类里做的。混淆通常只能混淆函数名,类名,但是无法混淆函数的实现,包括一些特殊的字符串,我们可以从字符串入手,找到okhttpclient。

下载一份okhttp的源码,查看okhttpclient这个类有何特征:

发现在内部的builder里,有三个连续的字符串“timeout”,而这是无法被混淆的。我们在jadx里全局搜索“timeout”:

果然有。

点进去看看,发现结构和okhttpclient的结构如出一辙,我们断定这就是okhttpclient类。

查看引用,我们也因此找到了app创建okhttpclient的位置:

okhttp客户端另一个特征是对读写超时的设置,从TIMEUnit也可以看出这是在设置http请求的读写超时。

同时,看到后面一连串的.a函数,这是在添加一个一个pipline。(okhttp中叫拦截器,不过我个人感觉本质就是pipline)

分别查看这些pipline。

一个是添加请求头的:

还有一个是和数据处理有关的:

看到里面的response,bodystring字符串,好有d_key_three,不由的让人浮想联翩,这是在做什么,为什么出现了秘钥和返回体?

点进去看f5865a.a这个函数:

实锤!!看到了password,iv,SecretKeySpec,Cipher,doFinal这些特征。显然是在做aes解密。

为了更准确,看看SecretKetSpec 中的f5866b与Cipher.getInstance(f)中的f分别是什么?

f5766b与f都反编译崩了,转到smali看看:

确信了,在用AES解密,模式是 CBC,填充方式是PKCS5padding

我们hook这个a函数,看看解密后的值是什么?

写一个简单的frida脚本:

hook后发现,打印出了奇怪的东西:

传入的参数确实是加密的返回数据,但是解密后没有东西。而且秘钥很怪,以error结尾??

查看系统日志:

发现解密失败,报错Unsupported key。

仔细看了一下,所使用的的秘钥和iv 确实是14字节。而aes通常有128,192,256三种秘钥,所以秘钥只能是16,24,32字节,那么显然秘钥错了。

找找秘钥生成的地方,发现秘钥是由三部分相加得来:

第一部分是从资源文件中获取的:

第二部分是通过app内置数据库获取的,不过采用的是默认值:

第三部分是从native获取的:

查看这个native函数:

原来,在native获取秘钥的时候又做了一起签名检查!如果签名不对则返回error,只有签名正确才会返回正确的后缀。

修改frida脚本:

直接将正确的秘钥强行塞给解密函数,这次终于解密了,不过:

返回的数据显示“签名验证失败”

4.签名校验

因为签名验证失败,所以没有拿到正确的返回数据。我们看看签名是怎么算的。

在http请求头里看到了sign字段:

这个就是签名。

因为通常,签名的命名方式为sign,sig,authcode,sec之类。特殊的例如某音,使用了希腊神话中的神的名字来命名。

jadx全局搜 "sign"。(注意用带双引号的sign来搜索,这样会提高搜索效率。因为通常生成sign时会把sign作为key,put进一个map里。而key是一个String,所以代码中生成sign的地方一定有字符串"sign")

果然有:

看看a7的生成:

阅读a函数,可以知道:

先加入了ts参数作为时间戳。然后把所有参数首尾相连。然后添加了类似于aes秘钥的后缀。然后通过另一个a函数计算a2,再将a2全部小写。这就是签名的生成。

看看另一个a函数:

是在算MD5摘要。

那么问题就出在了那个类似于秘钥的后缀上。

确实,因为签名错误了所以也返回了错误的后缀,不过使用的是aaxx函数,不同于aes时候使用的ddmm函数。

同样的套路,签名不正确就返回error,正确才返回真正的后缀。

看看本地是怎么做签名校验的?


通过反射获取signature,然后计算MD5,与硬编码的正确SIGN_MD5进行比对。

既然如此,我们hook aaxx函数让他返回正确的值:

再次运行后,成功得到了正确的数据:

同时,app界面也正常了,不过显示已到期:)

5.无限续杯

我们成功绕过了root检测,代理检测,签名校验。但是每一台设备只有2小时的免费机会,我们想要白嫖,怎么办?

通常app会通过唯一设备标识来跟踪设备,即deviceID

deviceId可以有很多种计算方式。可以直接获取安卓设备本身的唯一标志(高版本android禁用了),可以获取设备的MAC地址,也可以生成一个UUID,或者随机字符串,藏在设备的某个角落。当apk重新安装时,先去看看之前这里有没有藏过相关的文件,如果有,就直接读出来当做设备ID,如果没有,则创建一个。这样做可以保证apk删除前后依然能跟踪设备。当然,如果你能发现这个文件,并且把他删掉,那么下次安装的时候app就会认为是一个新的设备了。

我们先找找deviceid在哪,全局搜索deviceid:

发现了跟多地方,有from app,有from sdcard

跟入一个sdcard相关的函数:

看到了是从f文件读取的设备id。我们看看f文件的路径:

好家伙,在alarms文件夹呢。打开看看:

果然,sd卡下的alarm文件下有一个文件,打开后的确是发送数据时使用的设备id。

删掉这个文件,重新启动,发现设备id还是没有变化,难道还有其他地方存着?

注意到上面的from app:

看到deviceid是从b函数获取的,看看b函数:

懂了,原来是从sharedpreferences里拿的。

SharedPreferences 是app内部存储数据的一种方式,而sd卡是一种外部存储方式。具体知识可以参考Android数据存储相关文章。

我们打开sharedPreferences

果然看到了customdeviceid:

看看他的设备id是怎么获取到的?

首先通过a(10)获取了一个10位的随机字符串a2:

然后计算了“33dfdfer21”+a2+"sddddsfe"的md5值 a3。

最后用“33dfdfer21”+a3+a3[0:10]为最终的设备id。

所以设备id是随机生成的。

每台新设备有2小时的免费时间,也就是7200秒。我们考虑每7000秒重新生成一次设备id,不就可以无限续杯了吗?

看看app在那里获取到这个deviceid并发送给服务器的,注意到之前的okhttp 的pipline中有一个就是添加http头的:

看到deviceid是通过a()方法获得的。查看a()方法:

最终是返回了一个字符串。其实不用继续跟下去了,无非是从app获得或者从sd卡获得。我们在这里hook就行。

理论上只要写一个字符串随机生成算法,保证2小时内保持一直就行了。我这里使用时间戳除以7000的方式获取:


字节写一个函数,然后使用android studio编译出smali代码。将对应的smalidaima插入到a()函数返回之前即可:

随机设备id的smali代码:

插入到a()函数返回之前:

这样,我们每隔两小时会使用全新的deviceid,对服务端来说好像是新的设备安装了他的app,然后就可以又白嫖两小时的免费时间了。


6后记

这些不符合社会主义核心价值观的软件,都要统统破解了才好。为了维护社会正气,我辈当仁不让。

同时仅就安卓安全保护而言,这款app该做的都做了,但路数都比较常规。

唯一的亮点是将秘钥分段存储,在签名错误时返回错误的秘钥以对包体篡改提供更强的保护。

本次教程以学习研究为目的,请勿以此做违法犯罪的事情。遵纪守法的社会主义好公民,从我做起。


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2023-11-21 13:14 被乐子人编辑 ,原因:
收藏
点赞33
打赏
分享
打赏 + 5.00雪花
打赏次数 1 雪花 + 5.00
 
赞赏  orz1ruo   +5.00 2023/02/15 精品文章~
最新回复 (37)
雪    币: 1720
活跃值: (8676)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
你瞒我瞒 2022-12-6 09:35
2
0
雪    币: 839
活跃值: (5004)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
suuuuu 2022-12-6 10:30
3
2
破解就破解,还谈价值观,搞笑呢
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_arjunooq 2022-12-6 22:56
4
0
某豹有bug,每天签到拿五分钟免费时间,连接后一直放后台不要打开,就能一直用下去
雪    币: 1128
活跃值: (897)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
幼稚园小朋友 2022-12-7 16:17
5
0
赖东东不错嘛
雪    币: 1892
活跃值: (1750)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 9 2022-12-7 16:37
6
0
666
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
茄子不茄 2022-12-7 17:02
7
0
修改完smali之后,重打包安装,有显示签名不一致,应该怎么解决
雪    币: 29
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
玉皇 2022-12-7 17:03
8
0
茄子不茄 修改完smali之后,重打包安装,有显示签名不一致,应该怎么解决
把旧的apk删了就好
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
茄子不茄 2022-12-7 17:06
9
0
玉皇 把旧的apk删了就好

在MT中进行的,可以直接重新安装,如果删除apk,所对应的data/app路径下所对应的目录会丢失

最后于 2022-12-7 17:07 被茄子不茄编辑 ,原因:
雪    币: 3266
活跃值: (4325)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
乐子人 3 2022-12-7 17:20
10
0
茄子不茄 修改完smali之后,重打包安装,有显示签名不一致,应该怎么解决
修改完后把APK先复制一份出来,然后再删掉原始的apk。后面在复制出来的里进行操作就行了,因为都是MT的签名
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
茄子不茄 2022-12-7 17:24
11
0
乐子人 修改完后把APK先复制一份出来,然后再删掉原始的apk。后面在复制出来的里进行操作就行了,因为都是MT的签名
好的,谢佬
雪    币: 13545
活跃值: (1195)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
严启真 2022-12-7 22:08
12
0
清单中带sharedUserId的这行删除应该就行
雪    币: 13545
活跃值: (1195)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
严启真 2022-12-7 22:08
13
0
茄子不茄 修改完smali之后,重打包安装,有显示签名不一致,应该怎么解决
清单中带sharedUserId的这行删除应该就行
雪    币: 278
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
无相孤君 2022-12-8 11:45
14
0
不错学习了
雪    币: 893
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
智取棋 2022-12-8 15:50
15
0
可以,很有爱国精神
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
heyimyh 2022-12-10 10:07
16
0
内容不错!学习了 就是说话怪怪的
雪    币: 248
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
@=llfly 2022-12-20 16:08
17
0
挺好 挺详细! 感谢分享 
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Atomic_MM 2023-1-29 16:23
18
0
挺好 挺详细! 感谢分享 
雪    币: 0
活跃值: (1186)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Circ1e 2023-1-31 15:54
19
0
精彩
雪    币: 115
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_ykunklqz 2023-2-1 15:03
20
0
正能量
雪    币: 257
活跃值: (213327)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
shinratensei 1 2023-2-1 16:55
21
0
tql
雪    币: 55
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_tawpgynq 2023-2-13 11:50
22
0
看不懂,大佬有个app害得家破人亡,希望大佬们帮一下
雪    币: 3266
活跃值: (4325)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
乐子人 3 2023-2-13 13:08
23
0
mb_tawpgynq 看不懂,大佬有个app害得家破人亡,希望大佬们帮一下
啥app
雪    币: 294
活跃值: (987)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
hackbs 2023-2-13 18:22
24
0
这什么apk呀 有样本吗?学习学习
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Octave 2023-2-13 22:43
25
0
这什么apk呀 有样本吗?学习学习
游客
登录 | 注册 方可回帖
返回