【萌新的逆向入门2】论如何抓包提取 Arcaea 的网络接口并伪造成绩

注意!免责声明:本博文相关内容已涉及 lowiro 的实际利益且直接违反 Arcaea 的 TOS。本博文内容仅用于网络安全的个人研究与学习。请勿将博文内容用于商业或者非法用途,如果阁下愿意继续阅读,请您承诺将为自己的全部行为负责。

(复制粘贴

0x00 前言 (不想听我啰嗦的可跳过)

啊哈,没想到这个萌新的逆向入门系列居然还能更到第二篇。

其实本来计划上的第二篇是写把自制谱导入 IOS 设备,因为我的音游社里大部分人还是平板党,但是没有人有一块安卓平板emm。

但是我发现我的所有 IOS 设备没一台能完美越狱的。唯一一台能 9.3.5 不完美越狱的 Mini 1 也在我反复尝试 frida 砸壳失败后被卡出了玄学 bug,越狱么得了。所以这个计划就暂时搁置了emm。

那么最近,我正好在研究网络接口和爬虫相关的东西;加上最近 Arcaea 的排行榜上多了好多技术人(甚至还发生了霸榜事件);加上我也一直很好奇 ester 大佬的查分器 究竟是个什么原理;再加上 ester 大佬因为某些原因退坑后, 中文维基 上的角色梯子数据没人更新了。所以我自己也是依葫芦画瓢地试着抓了包。

虽然流程很简单,但还是写篇博文记录一下好了。

安卓设备需要能安装上 xposed 框架。
IOS 设备似乎没法解决 SSL pinning 的问题,所以用不了。

0x01 如何抓包

1.选择一款网络抓包软件

可选 Charles 或者 Fiddler

这里我用了前者。因为之前抓flash的amf的时候,fiddler显示不了内容,但是花瓶可以!
批注 2020-02-11 091212.jpg
安装和破解过程略过,请自行百度。

接下来以 Win10 上的 Charles 抓一台安卓 5.0.1 设备为例。

2.安装电脑端证书

由于 Arcaea 用了 https 也就是 SSL加密,所以需要装 Charles 的代理证书。

MAC 的教程点我

点击菜单栏 - 帮助 - SSL代理 - 安装Charles Root证书
批注 2020-02-11 085157.jpg
然后在弹出的证书对话框里点安装证书。再在弹出的导入对话框里选本地计算机,点下一步后确认 UAC。然后记得把证书装到受信任的根证书颁发机构,不然它不信任的。一路确认看到导入成功即可。
批注 2020-02-11 090051.jpg

2.安装手机端证书

点击菜单栏 - 帮助 - SSL代理 - 在移动设备或远程浏览器上安装Charles Root证书
批注 2020-02-11 094444.jpg
在弹出的框里找到 192.168.xxx.xxx:8888 的地址记住(或者也可以 ipconfig 看,就是你电脑的 IP 地址)
然后掏出手机,在 WiFi 设置界面里,保证手机和电脑连了同一个 WiFi,点开这个 WiFi 设置代理配置。
这里 Charles 应该会弹出一个 Connection from xxx 的提示框,Allow 即可。
设置完代理,访问 http://chls.pro/ssl ,会下载一个 pem 证书,下载完跟着点击打开并安装即可。
result-2020-02-11-10-05-32.jpg

3.配置抓取规则

这里我们还需要配置 Charles 电脑端的抓取规则,不然会发现注释里提示没有为此主机启用 SSL 代理。
批注 2020-02-11 100253.jpg

点击菜单栏 - 代理 - SSL代理设置,在弹出的框里添加一个 *:443 即可。
批注 2020-02-11 101644.jpg

4.安装 JustTrustMe 模块

这个时候手机上点开 Arcaea,就应该可以看到 Charles 里有来自 https://arcapi.lowiro.com 的记录了。可是它的旁边还是有一个红色的图标,内容也看不到。(截图忘了)这是因为 Arcaea 客户端做了 SSL pinning验证,它不信任我们刚装的证书。所以我们要再请出一大杀手,基于 Xposed框架 的 JustTrustMe

什么是 Xposed框架以及安装的方法请自行百度,这个涉及到刷机什么的方法太多。

装完 Xposed框架后点击下面的链接下载安装 JustTrustMe ,激活模块重启手机。
https://github.com/Fuzion24/JustTrustMe/releases/download/v.2/JustTrustMe.apk

5.大功告成

接下来就可以在手机上进行不同操作,然后研究抓到的数据和接口了!
批注 2020-02-11 103400.jpg

0x02 如何伪造成绩

下述仅仅是为了技术上的验证与学习。请不要真的使用它刷分。音游王行为没有意义,还会给其他认真推分的玩家造成困扰,也会有被封号的风险。

注:这里可以直接参考 sch 大佬的视频(av68697624),原理都是一样的。
以及前几天 sch 大佬直接把他视频里的 ArcaeaParty 开源了,可以学习一下。https://github.com/cnSchwarzer/ArcaeaParty

注2:为了简洁化,本博文内所有 APIURL 均省略了 https://arcapi.lowiro.com/8 的断点,自行在前面加上即可。
同时,这也意味着本博文是以 v8 的 API 进行说明。截至我写博文时,此本版本 API 已因为之前的霸榜事件而被关闭。
现在最新的版本是 2.5.1 使用的 v9,不保证本博文所述方法均可使用,也不排除 616 在之后的版本完全更换 API 的可能。

注3:本博文采用 Pythonrequests 库来伪造请求,当然您也可选用自己最顺手的工具。(人生苦短,py它不香么

1.如何登录

批注 2020-02-12 005222.jpg

首先找到这个登录请求仔细观察,发现它是个发往
/auth/loginpost 请求,有一个 grant_type=client_credentials 的数据。
而用户名和密码没有用表单传输,而是直接用了常见的 Basic Auth,也就是 base64('{userid}:{password}') 写在请求头里。

而请求成功后返回来的数据是 Json 格式的。可以看出来这是传回来了个 Bearer Token。记下这个 Token,它代表了你的登录身份。如果不去重新登录更新它的话,据说其有效期有一个月。

批注 2020-02-12 011716.jpg

写一个模拟登录的函数。这里用了 Session().headers.update()Session 添加通用的请求头。其中 AppVersion 可能需要更改。
由于只有登录时要求 DeviceId 而其他请求不需要,所以它和 Authorization 一样直接写进 POST 方法的参数里。Session会把上述请求头加起来一起发送。
DeviceId 有生成规范,但是没有校验,是可以随便改的。

POST 方法还有一个 verify = True 的参数,这是因为这个请求需要 SSL 验证。关于 PythonSSL 验证的相关内容请自行百度。一样也是仅有登录时需要,后续请求就可以改成 False 了。
(我尝试时,发现如果开着 Charles 会导致玄学 SSL 错误而登录失败,关掉再登录即可。

这个时候,应该已经可以拿到 Bearer Token了,也就是登录成功了。之后把请求头的 Authorization 参数改成它即可自由请求了。

2.请求个人信息的数据

这个跟伪造成绩没关系的就咕咕咕了...反正依葫芦画瓢,很简单的,有兴趣的话可以研究研究那些数据分别代表了什么

3.伪造成绩并上传

好终于到了第二部分的重头戏,一样的找个抓到的请求分析。

批注 2020-02-12 013802.jpg

可以看到打完歌结算时,程序先向 /score/token 发送 GET 请求,拿到了一个 token。也是一样的记下这个 token

批注 2020-02-12 013835.jpg

之后程序会向 /score/song 发送 POST 请求。表单里即是这次打歌成绩的数据。(瞎打的成绩就不要在意了

1.song_token 即是刚刚记下来的 GETtoken
2.song_hash 是游玩谱面 .aff 文件的哈希校验值。这个校验值算法理论上应该通过逆向游戏程序得到,但是 我太菜了(划重点),对着 IDA 看了一整天都没搞清楚这个 getChartHash() 究竟是个啥逻辑。
批注 2020-02-11 222335.jpg
所以这里我之前就直接看 sch 大佬的视频了。其实就是对整个文档跑了个 MD5没准碰碰运气就试到了批注 2020-02-12 015846.jpg
3.song_id, difficulty, score shiny_perfect_count, perfect_count, near_count, miss_count, health 这几个就不用说了,很明显了,玩过游戏的都应该知道。关于分数的算法详见附录一(shiny还挺形象的,可是为啥pure叫perfect,far叫near呢
4.modifier 暂时还不知道有啥用,填 0 就行。
5.clear_type 这是 APIv8 新加出来的,sch 用的 v7 就没有。显然它是代表打歌完成类型,从 0 - 5 分别是[ "TL", "NC", "FR", "PM", "EC", "HC" ]。注意 health 应该和这个相关。如果不仔细写,就会再现之前的病女理论值 TL
6.submission_hash 是这整个请求前面所有数据的哈希值。和song_hash 一样,算法理论上应该通过逆向游戏程序得到,但是 我真的太菜了,还是借用 sch 大佬的成果。它的生成方法是把上述所有键值对的值直接连起来,再接上一个 moeuguusalt 后跑 MD5。以图上成绩举例,就是 MD5('3Tf0...Mks=6056...20b5inkarusi277432332533286174300moeuguu')
关于这个 salt ,我是在 sch 大佬的视频里看到的。但是如果逆向游戏程序,也是可以明显看到这个不无规律的字符串从而猜到它是 salt(好像这个算法有点难猜吧
批注 2020-02-11 151731.jpg

一样写个函数伪造数据并上传。
批注 2020-02-12 022217.jpg
返回的 JSON 长这样就说明提交成功了。显然 user_rating 就是更新的 ptt 值 ×100。
批注 2020-02-12 023431.jpg
然后就可以打开游戏,看看全球榜上你伪造的理论值了!

注意:这种方法只能上传免费包和账户所购买的曲包曲(不一定要解锁)。如果上传没有购买的曲目或者是上传超过理论值的分数,应该会很快就被封号。

之前的免费包刷榜事件,应该就是通过这种方法,再加上几个简单的循环函数就实现的。由于这次事件,616 暂时关闭了免费包的世界排行榜,所以我也无从测试新的 API 还能不能用这个方法。

总之,上述的一切仅仅是为了技术上的验证与学习。现实中请不要真的用它刷分。音游王行为没有意义,还会给其他认真推分的玩家造成困扰,也会有被封号的风险。

4.其他接口

包括有加好友删好友获取好友信息的接口(也就是查分器的原理),创建账户的接口(可无限刷小号),获取地图信息的接口,碎片购买体力的接口等等。由于与伪造成绩无关,也略去了。可自行研究。咕咕咕,懒得写了

0x03 附录一 分数算法

基本分:类似SDVX,满分为10000000,单个Note的分数为(10000000/谱面Note总量),游玩过程中每个Pure获得完整的分数,Far获得一半分数,Lost不得分(基本分达到10000000后任何判定均不增加基本分)
判定附加分;每个大Pure会额外加1分,其他判定均不加分,附加分无上限。拿到所有附加分时,即成绩为(10000000+谱面Note总量)时的成绩成为理论值
举例:Tiferet Future难度谱面共计1086键。某神仙在一次练习的时候打出了984大Pure,97小Pure,3Far,2Lost的成绩。Pure每个音符得分为9208.103,Far每个音符得分为4604.052,Lost每个音符得分为0,总分即为(984+97)x9208.103+3x4604.552+2x0+984=9968755.639,显示得分为9968755 (摘自 Arcaea 中文维基)

其实维基上这个算法就可以了,如果你用科学计算器算的话。但是如果你是分开来算的,除完总note数得到的小数位数取得不够,很可能会崩精度。

所以我还是闲得去读了读逆向得到的分数算法。计算机去算实数是很容易崩精度的,所以这里采用了一种先乘后除的算法。
QQ图片20200212032718.jpg
这里 a2 是Far,a3 是总p,a4 是大p,a5是总note。
所以程序会先判断总p数是否等于总note数,如果相等就直接返回 10,000,000 + 大p数。
如果不等的话,分数是:
1,000,000,000 x ( 5 x Far + 10 x 总P ) / 总note / 1000 + 大p
这里 / 的除直接就是向下取整了。

2020.02.12 某魏

Last modification:February 12th, 2020 at 04:08 am

Leave a Comment

One comment

  1. 羽尘

    哈哈哈哈咕咕咕了OωO