星 - 逆向
type: channel
link: https://blog.hoshi.tech/category/reverse_engineering/
items: 记录Hackergame 2023参与经历
items: Nikke拆包解密记录
items: 记录下初次尝试逆向PyInstaller程序时参考的学习资料
items: 记一次简单的Electron应用修改 - WeMod
items: 记一次JavaScript游戏修改 - Vampire Survivors - 存档修改等
items: 更新：实现PC端QQ当前子频道消息防撤回
items: 记录下初次尝试解PHP混淆时的学习资料
items: 解密明日方舟的数据信息 - 远古版本0.7.8
items: 提取宝石研物语的Live2D - 远古1.0版本
items: 尝试修复iOS ArcaeaCountdownScore - 版本3.6.0

记录Hackergame 2023参与经历
type: item
date: 2023-11-04T05:26:00+00:00
description: 0x0 前言这次HG刚好赶上公共假期，有时间研究，比去年要好多了，但也发现原来HG这么卷。凌晨3,4,5点都有人提交flag，麻了。友情链接wlt233 【CTF】writeup——hackergame 2023BenPigChu USTC Hackergame 2023 Writeup0x1 题目Hackergame 启动大声喊出 Hackergame 启动，开始今年的冒险！好活签到，Hackergame，启动！跟历年解题思路一致，提交后修改URL中的?similarity=100即可。flag{wE1CoMe-TO-hACkergAme-@nD-3nJOY-hAcKIN9-zoZ3}猫咪小测注意看，这只可爱的橙猫猫叫小喵。只要有阳光的地方就能见到它的身影，只要有鱼干的地方就能听到它的喵声。但今天的小喵有些不同，它的眼神充满了紧张与期待，因为一年一度的风云际会，大师喵主办的全校猫咪小测马上就要开始了。「在科大，即使身为猫咪，也要兼备充分的科大常识、扎实的数理基础、以及独立的科研能力」大师喵蹲坐在主席台上，目光炯炯。小喵乖巧地坐在课桌前，爪子紧按着用来答题的鼠标，似乎一松开鼠标就会自己溜走一样。不得不承认，这场小测并不容易。「生而为猫，我很抱歉」后排传来了一声沮丧而近乎绝望的呜咽，应该是今年刚入校的少年喵。本就有着比众喵更小的年龄，其当年足可引以为傲的天赋在科大里却也算不上什么，于是每次测验都只能位居末流。小喵还没来得及回头，大师喵「噌」地一下从讲台上腾跃而下，扑到后排那名猫咪的身前，用舌头细细梳理这名猫咪的后颈上的绒毛。「不可以这样说哦，静心努力你也可以做出来的喵~」啊，大师喵那一如既往的温柔。少年喵非常感动。「嗯嗯我明白了。それなら別の言い方をしよう：猫として生まれて、すみません」「给我好好做题（敲），不是要你换一种语言再说一遍啊（kusa）」「不愧是大师喵」，小喵钦佩地望着大师喵的身影。「这种说话还能带括号的技巧我也想学！」提示：解出谜题不需要是科大在校猫咪。解题遇到困难？你可以参考以下题解：2018 年猫咪问答题解2020 年猫咪问答++ 题解2021 年猫咪问答 Pro Max 题解2022 年猫咪问答喵题解参考历年题目，考察信息搜索能力。感觉今年题目比去年要简单些。想要借阅世界图书出版公司出版的《A Classical Introduction To Modern Number Theory 2nd ed.》，应当前往中国科学技术大学西区图书馆的哪一层？Google搜索中国科学技术大学 "西区图书馆"得到一篇西区图书馆简介，可以看到外文书库位于12楼。今年 arXiv 网站的天体物理版块上有人发表了一篇关于「可观测宇宙中的鸡的密度上限」的论文，请问论文中作者计算出的鸡密度函数的上限为 10 的多少次方每立方秒差距？Google arXiv 可观测宇宙中的鸡的密度上限 一篇知乎问题你见过哪些极品论文？的回答，其中可看到答案为23。为了支持 TCP BBR 拥塞控制算法，在编译 Linux 内核时应该配置好哪一条内核选项？Google linux 编译 bbr config查看各路大神的编译教程，可得答案为CONFIG_TCP_CONG_BBR。🥒🥒🥒：「我……从没觉得写类型标注有意思过」。在一篇论文中，作者给出了能够让 Python 的类型检查器 MyPY mypy 陷入死循环的代码，并证明 Python 的类型检查和停机问题一样困难。请问这篇论文发表在今年的哪个学术会议上？Google搜索Python mypy infinite loop pdf可得一篇论文名为Python Type Hints Are Turing Complete，其中角标Logo为ECOOP 2023，ECOOP即为答案。flag1：flag{W3lc0m3-to-4tTENd-7hE-neko-3Xam-Zoz3}flag2：flag{r3Al-m4s73r-oF-the-NeK0-exaM-1n-uStc}更深更暗小 E 正在收看电视新闻。「诶，你知道吗，『泰坦』号潜水艇失事了！」小 E 对旁边的小 C 说。小 C 凑近电视机，看了一眼新闻里的画面。「是我眼花了吗？我刚刚有一瞬间好像在残骸上看到了一个 flag？」小 C 惊讶地说。「玩 CTF 玩的。」小 E 对此不以为然，「一定是你看错了。」小 C 却十分相信自己没有看错。打开F12，查看main.js，可以看到Flag算法明晃晃的写在其中。let token = params.get('token') || localStorage.getItem('token'); async function getFlag(token) { // Generate the flag based…

Nikke拆包解密记录
type: item
date: 2022-11-09T06:26:00+00:00
description: 0x0 背景最近一个新二游Nikke正式开服了，由于某种原因在各群各站传的沸沸扬扬。玩这个游戏的冲什么来懂得都懂，实际玩了一下后发现也就只有模型和剧情值得一览。这不得解解包，四舍五入通下关。主要文章是分析完之后补写，实际分析过程是动调静态一起上的。0x1 寻找解密方法查看AssetBundle可以发现Header被修改。正常应该为UnityFS + 1 byte版本号 + 5.x.x.2021.3.9f1 + 2 bytes AB大小等。而Nikke修改后的Header为NKAB + 不明 + GoddessofVictory等。且观察发现并非简单的将信息移动，那就需要分析看看了。起手il2cppdumper后直接在dump.cs中搜索关键词NKAB就能够定位到相关方法，这个字符串在类名中包含，并且很贴心的只出现了一次：// Namespace: UnityEngine.ResourceManagement.ResourceProviders internal enum NKBundleEncryptionMode // TypeDefIndex: 25907 { // Fields public ushort value__; // 0x0 public const NKBundleEncryptionMode Default = 0; } // Namespace: UnityEngine.ResourceManagement.ResourceProviders public class NKABStreamProcessorWithSeek : IDataConverter // TypeDefIndex: 25908 { public Stream CreateReadStream(Stream input, string id) { } public Stream CreateWriteStream(Stream input, string id) { } public void .ctor() { } } // Namespace: UnityEngine.ResourceManagement.ResourceProviders internal class NKAssetBundleStream : Stream // TypeDefIndex: 25909 { // Fields [CompilerGenerated] private readonly Stream <baseStream>k__BackingField; // 0x28 [CompilerGenerated] private readonly NKBundleHeader k__BackingField; // 0x30 [CompilerGenerated] private readonly Aes <AES>k__BackingField; // 0x38 [CompilerGenerated] private ICryptoTransform <transform>k__BackingField; // 0x40 [CompilerGenerated] private byte[] <encryptedBuffer>k__BackingField; // 0x48 [CompilerGenerated] private byte[] <plainBuffer>k__BackingField; // 0x50 [CompilerGenerated] private readonly string <Name>k__BackingField; // 0x58 [CompilerGenerated] private bool <Disposed>k__BackingField; // 0x60 // Properties protected Stream baseStream { get; } public NKBundleHeader Header { get; } protected Aes AES { get; } protected ICryptoTransform transform { get; set; } protected byte[] encryptedBuffer { get; set; } protected byte[] plainBuffer { get; set; } protected string Name { get; } protected bool Disposed { get; set; } public override bool CanRead { get; } public override b…

记录下初次尝试逆向PyInstaller程序时参考的学习资料
type: item
date: 2022-05-21T13:16:00+00:00
description: 0x0 背景6盘倒了之后出了个PikPak，虽说比不上6盘，但也够用。问题在于官方没有PC客户端，想要文件批量下载很不方便。在官方群中倒是有许多官方发布的第三方客户端链接，其中一款里调用IDM下载居然还是付费功能...想逆向看看机器码生成的逻辑，简单记录下过程中参考的资料。0x1 过程丢到IDA打开后看到有个WinMain(x, x, x, x)，查看调用发现其中一个方案里有这条字符串：Cannot open PyInstaller archive from executable .....。看来是PyInstaller打包后的Python程序。印象中GC佬有提到过PyInstaller的逆向，链接在这：记一次简单的Python逆向也谷歌了几份资料发现都是使用pyinstxtractor拆包，uncompyle6反编译。 python逆向实战：反编译pyinstaller打包生成的exe中提到PYZ-00.pyz_extracted应该是主要关注的目录。但第一次尝试使用pyinstxtractor解包后发现是空目录。这才注意到log中提到需要Python3.8来解包：[!] Warning: This script is running in a different Python version than the one used to build the executable. [!] Please run this script in Python308 to prevent extraction errors during unmarshalling [!] Skipping pyz extraction不想再额外装一份Python3.8，还好谷歌找到有便携版Python：Portable Python 3.8。下载解压后运行Console-Launcher.exe即可在终端中使用Python3.8成功解包：[+] Processing PikPak_xxxxx.exe [+] Pyinstaller version: 2.1+ [+] Python version: 308 [+] Length of package: 44069253 bytes [+] Found 137 files in CArchive [+] Beginning extraction...please standby [+] Possible entry point: pyiboot01_bootstrap.pyc [+] Possible entry point: pyi_rth_subprocess.pyc [+] Possible entry point: pyi_rth_pkgutil.pyc [+] Possible entry point: pyi_rth_multiprocessing.pyc [+] Possible entry point: pyi_rth_inspect.pyc [+] Possible entry point: pyi_rth_win32api.pyc [+] Possible entry point: pyi_rth_win32comgenpy.pyc [+] Possible entry point: pyi_rth_pyqt5.pyc [+] Possible entry point: pikpak.pyc [+] Found 563 files in PYZ archive [+] Successfully extracted pyinstaller archive: PikPak_xxxxx.exe You can now use a python decompiler on the pyc files within the extracted directory根据提示程序主入口可能为pikpak.pyc，可以此文件入手。使用uncompyle6反编译时报错\# Deparsing stopped due to parse error翻看原库发现对于Python3.7以上版本推荐使用decompyle3:For Python 3.7 and 3.8, the code in decompyle3 is generally better.不过使用decompyle3后也会报同样的错，但其实结合字节码和反编译成功的源码是可以看出逻辑的。

记一次简单的Electron应用修改 - WeMod
type: item
date: 2022-05-09T16:51:29+00:00
description: 0x0 背景风灵月影宗弟子翻找宗主秘籍时再次看到了WeMod，之前有稍微了解过是尝试做统一整合的修改器平台。不过风大自己也有修改器管理程序就没太在意WeMod。这次看到WeMod有游戏内Overlay，倒是很吸引人。但可惜是付费功能，本想着不贵的话就入手了，但发现WeMod是订阅制，且年费高达70美金。偶尔才有时间玩游戏，为了这个功能未免有些太贵，尝试白嫖一下。0x1 修改安装完成后尝试抓包看下是否能够从MitM入手，发现抓不到有用的连接。打开安装目录发现是熟悉的Electron应用。app-VER\resources下可以看到app.asar文件，其为压缩后的Electron资源文件。使用Node包asar即可解压。安装asar:npm install -g asar解压到app文件夹：asar extract app.asar ./app开启抓包及应用调试之前在实现PC端QQ当前子频道消息防撤回有提到过如何调试Electron应用，这里同理。解压asar后打开App入口index.js搜索关键词commandLine可以看到bootstrap()方法：function bootstrap() { B.commandLine.appendSwitch("no-proxy-server"); // 设置应用不走代理 if (C.devMode) { B.commandLine.appendSwitch("ignore-certificate-errors", "true"); if (B.commandLine.hasSwitch("electron-reload")) { n(320)(`${__dirname}\\output`, { electron: process.execPath }); } if (B.commandLine.hasSwitch("wemod-temp")) { C.windowOptions.webPreferences.partition = "temp"; } }可以看到此方法里有设置应用不走代理，可以顺手注释掉。在其后添加一行开关方便我们进行调试：B.commandLine.appendSwitch('remote-debugging-port', '8315');重新打包资源asar pack .\app\ app.asar定位打包替换资源后启动应用，可以发现能够正常抓包了，且数据并未加密。找到/v3/account调用，可以看到返回值如下：{ "uuid": "xxx", "username": "xxx", "email": null, "profileImage": "/avatars/default/xxx/{size}.png", "checkoutState": "mixed", "country": "xx", "flags": 0, "audience": 1, "subscription": null, "joinedAt": "2022-xx-xxTxx:xx:xxZ", "boosts": 0, "boostedGames": {}, "followedGames": {}, "answeredPolls": [ "25" ], "releaseChannels": [] }这其中有个subscription字段很明显值得调查下。（但我眼瞎了当时没发现这个字段，进而在代码中搜索pro来逐渐定位到此字段）在output/app-bundle.js中搜索此字段可以发现确实为我们需要着手的目标。代码中有许多处对subscription进行判断，那么只需要在赋值时修改此字段，那么就可以一劳永逸啦。搜寻中可以找到getUserAccount()方法，只需修改这里即可。商业软件，详细过程略尝试可以使用Overlay等不经过服务器的付费功能。

记一次JavaScript游戏修改 - Vampire Survivors - 存档修改等
type: item
date: 2022-04-24T18:38:00+00:00
description: 0x0 背景首先从B站王老菊那发现了Steam小游戏Vampire Survivors，买来等有时间玩了发现想解锁属性还挺肝，随即想搞搞事。看下游戏目录很容易可以看出是基于Electron做的。文件搜索Vampire Survivors可以找到存档文件在C:\Users\USER_NAME\AppData\Roaming\Vampire_Survivors\saves目录下，打开发现就是以Json格式保存的，但有个checksum字段防止玩家篡改。那么可以通过此字段作为入口进行分析。我自己是适当修改了金币数量省肝，未修改属性及无敌等避免无趣。另：在游戏主界面按下金手指上上下下左右左右 Esc Enter可获得3000金币，仅可获取一次。这个金手指仅可获取一次对应存档中CheatCodeUsed字段0x1 存档修改与解混淆在steamapps\common\Vampire Survivors\resources\app下执行文本搜索关键词SaveData.sav或checksum即可定位到文件.webpack\renderer\main.bundle.js。备份一下原文件后用Prettier格式化一下:npx prettier --write .\main.bundle.js打开稍微看下能够看出为常见混淆思路，特征为大数组+偏移函数+解密函数。之前尝试PHP反混淆时遇到过类似的混淆，搜索资料知晓在JavScript中为类ob混淆。解决思路同样为通过AST还原。随意谷歌下找到挺多大佬已经写过JavaScript解混淆的代码，前排找到篇教程中有实例代码，直接拿来针对修改使用。Gist备份 ob-decrypt.js先按照ob混淆的特征重新排序下代码块，AST的前三段应当为大数组+偏移函数+解密函数。即为：function a0_0x3cab() { const _0x3ec6ef = [ "every", "random", ... "graphics2", ]; a0_0x3cab = function () { return _0x3ec6ef; }; return a0_0x3cab(); } (function (_0x422224, _0x2db768) { const _0x369aef = a0_0x150b, _0x19e7b7 = _0x422224(); while (!![]) { try { const _0x31194f = parseInt(_0x369aef(0xb30)) / 0x1 + parseInt(_0x369aef(0x522)) / 0x2 + parseInt(_0x369aef(0x8c8)) / 0x3 + (parseInt(_0x369aef(0x405)) / 0x4) * (parseInt(_0x369aef(0x367)) / 0x5) + (parseInt(_0x369aef(0x1d9)) / 0x6) * (-parseInt(_0x369aef(0x5a5)) / 0x7) + (parseInt(_0x369aef(0x67f)) / 0x8) * (parseInt(_0x369aef(0x6ce)) / 0x9) + (-parseInt(_0x369aef(0xa7f)) / 0xa) * (parseInt(_0x369aef(0x4eb)) / 0xb); if (_0x31194f === _0x2db768) break; else _0x19e7b7["push"](_0x19e7b7["shift"]()); } catch (_0x19db15) { _0x19e7b7["push"](_0x19e7b7["shift"]()); } } })(a0_0x3cab, 0x51997) function a0_0x150b(_0x8a0c12, _0x437a92) { const _0x3cabcb = a0_0x3cab(); return ( (a0_0x150b = function (_0x150b54, _0x5ea125) { _0x150b54 = _0x150b54 - 0xdf; let _0x512b08 = _0x3cabcb[_0x150b54]; return _0x512b08; }), a0_0x150b(_0x8a0c12, _0x437a92) ); }掏出好帮手AST可视化工具丢进代码，定位到解密函数以及存档函数，参照这对上面拿到的解混淆工具进行修改。（存档函数，其实即便不进行混淆还原也能够基本看出存档验证逻辑了，源代码如下）[_0x5e9c75(0x1b9)]() { const _0x78dcb4 =…

更新：实现PC端QQ当前子频道消息防撤回
type: item
date: 2021-11-06T06:52:00+00:00
description: [scode type="yellow"]更新：拿到创建频道的资格了，进行了详细测试，以下方法证实用处不大。此方法仅能实现目前浏览的子频道 中的消息防撤回，因为其余子频道的消息没有进行下载，仅仅更新了消息预览。且消息并没有保存下来，切换子频道后会触发重新加载子频道消息，获取消息的逻辑还是在QQ中（ipcRenderer.invoke('[gpro-invoke][0]','getChannelMsg')），导致防撤回的消息丢失。如有兴趣防撤回当前子频道的消息，请继续阅读。效果：[/scode]0x0 背景QQ近期公（内？）测了QQ频道，即TX版的Discord。有幸受群友邀请进入频道玩耍。本来以为PCQQ需要挺久才会支持，没想到目前已经可以使用了，只是功能相比手机端没有那么完善，且BUG不少，但至少聊天是够用了。经群友提醒PC版的QQ频道是Electron应用，缝合进QQ提供一个入口。既然是Electron，那么就可以简单搞点事情了，试试防撤回？0x1 开启DevToolsWindows任务管理器可以看到QQ频道的程序名为QQGuild，没有打开文件位置的选项。用Everything搜一下QQGuild即可发现其位于$HOME/AppData/Local/Tencent/QQGuild/1.x/。打卡即可看到熟悉的Electron应用结构，从resources/app/package.json可得知程序主入口为background.js格式化后在最下方可以看到加载方法： class y { constructor() { this.initGpro(), this.initInvoker(); } initGpro() { const e = p.a, t = new m.NodeIGProSdkListener(e); this.wrapper = new m.NodeIGProSdkWrapper(t); const n = new v(); (n.dbPath = "gpro.db"), (n.clientVer = a.app.getVersion() + "-330c67c4"), (n.pSqlite3 = 0), (n.platform = f.a), (n.platVer = f.b), (n.a2 = f.c ? _.a.a2 : ""), (n.d2 = f.c ? _.a.d2 : ""), (n.d2Key = f.c ? _.a.d2Key : ""), (n.userDataDir = d.a), (n.machineId = c), (n.argv = process.argv), (n.isProduction = !f.c), (p.a.uin = String(f.c ? _.a.uin : "")), (p.a.wrapper = this.wrapper), this.wrapper.start(_.a.uin, n), h.a.info("initGpro call wrapper.start"); } initInvoker() { Object(g.a)(this.wrapper); } } class v {} var M = n(14); a.protocol.registerSchemesAsPrivileged([ { scheme: "app", privileges: { secure: !0, standard: !0 } }, ]), h.a.info("main process start"), f.d && (a.app.commandLine.appendSwitch("--ignore-certificate-errors", "true"), f.c || a.app.dock.hide()), a.app.on("window-all-closed", () => { h.a.info("[app window-all-closed]"), f.c && a.app.quit(); }), a.app.on("activate", () => { var e; f.d && (null === (e = M.a.win) || void 0 === e || e.show()); }), ...... ...... process.on("unhandledRejection", (e) => { h.a.error(null == e ? void 0 : e.toString()); }), process.on("uncaughtException", (e) => { h.a.error(null == e ? void 0 : e.toStri…

记录下初次尝试解PHP混淆时的学习资料
type: item
date: 2021-10-27T14:27:00+00:00
description: [scode type="red"]免责声明：本文仅为博主安全研究学习记录，不提供明确步骤，强烈反对参考本文内容进行不正当用途，请后果自负。[/scode]本文算是水文吧，不暴露破解细节就没什么好讲的内容，此前也没有接触过PHP相关的逆向，权当记录下哪些前辈的文章对PHP解混淆有帮助。simon816/PHPDeobfuscator: Advanced PHP deobfuscator)初探PHP-Parser和PHP代码混淆开发简单的PHP混淆器与解混淆器 - zsx's BlogPHP解密：EnPHP mzphp2加密 无法还原变量名的混淆加密 - 吾爱破解以下无营养水文，可略过，有价值的内容上面的文章里都提到了前段时间物色自建网盘程序的时候发现不少都是基于PHP开发的，测试到其中一款的时候发现其实商业版的功能已经存在，只是没有授权不开放 。随之手痒尝试突破限制。实际上并未选择使用此程序从官网下载到程序后解压，在入口index.php中发现实例化了Application。转到定义发现是一个充斥着大量乱码的PHP文件，看看其他文件均未进行混淆，确定本文件即为关键点。格式化代码后入口函数如下：结合其他函数可以发现代码本身未被加密，但是变量被混淆成乱码、控制流被打乱以及到处都用到某个数组。Google一阵PHP反混淆后发现那些所谓的在线PHP解混淆网站都没啥用处。而此工具 simon816/PHPDeobfuscator可解流程控制、HEX字符串并格式化代码。使用方法：php index.php -f foo.php > bar.php还原流程控制后发现其开头定义了一个名称为乱码的全局数组：$_SERVER[foo] = explode("bar",gzinflate("foobar"));该数组在后续代码中大量被引用，猜测所有的字符串都存在此数组中了。继续搜索发现这与常用混淆方案EnPHP十分类似，且已经有相当多的文章讲述如何解此类混淆并提供示例代码，照猫画虎后成功解决混淆。其实这里才是重点，但此程序并未更新加密方式，本文不能提供具体步骤，请参考初探PHP-Parser和PHP代码混淆进行学习研究。解掉混淆后踩坑发现本文件有好几处自身文件大小检验，如果大小不符则会无限循环在DATA_PATH下创建无限文件。而在本文件之外也有全局变量中存在文件大小与否的校验，如果不通过则直接退出程序。

解密明日方舟的数据信息 - 远古版本0.7.8
type: item
date: 2021-07-19T15:02:00+00:00
description: [scode type="red"]免责声明：本文仅为博主安全研究学习记录，强烈反对参考本文内容进行不正当用途，包括但不限于作弊行为等。请后果自负。[/scode][scode type="yellow"]此文为补写，意为记录，重新分析也是用回远古版本安装包，应该已不适用于目前版本游戏[/scode]0x0 背景2019年5月跟着群友入坑了明日方舟（于2019年11月左右弃坑），顺手拆了CG和立绘（Perfare佬yyds）。后来突发奇想想做个多游戏抽卡模拟器（剧透：咕了）。素材已经到位，接着就是卡池数据了，得想个办法拆出游戏数据，总不能期期手动跟进卡池更新吧？于是说干就干，打开资源文件夹，发现在\AB\Android\gamedata\excel目录下有gacha_table.ab文件，这就是卡池数据了。丢进AssetStudio，点开gacha_table就发现全是看不懂的数据，导出后为gacha_table.bytes。没办法，只能分析下是怎么解密数据的了。注：本文为补写，按照回忆重新开始分析，与文中不同的时当时是直接搜索加密相关关键词直接找到几个解密类Hook的。0x1 寻找解密方法游戏基于UnityEngine引擎且使用了il2cpp。按部就班掏出Il2CppDumper加载libil2cpp.so及assets\bin\Data\Managed\Metadata\global-metadata.dat，然后丢IDA分析并加载dump出的IDA python脚本。（值得一提（？）的是，ARM和ARMv7架构对应lib中libil2cpp.so是完全相同的。仅有少量第三方库存在差异。）与此同时使用dnSpy加载dump出的dummy dll寻找到可疑目标类Torappu.GachaDB，其继承自Torappu.DB.ConstTable -> SingletonAbstractTable -> AbstractTable。跳转到Torappu.DB，好家伙，疑似3个加解密方法：第一个应是Json密文的序列化及反序列化、第二个是很明显的AES加解密，第一次接触RijndaelManaged类还是在崩坏学园2里、而第三个看着像是基于随机种子的加解密。查看DBLoader.LoadTable()方法，伪代码：int __fastcall DBLoader__LoadTable(int a1, _DWORD *abstractTable) { int v3; // r1 int result; // r0 int isProdMode; // r0 int *tableConfig; // r1 int converter; // r5 int v8; // r0 int v9; // r0 if ( !byte_3023ECE ) { sub_26B1D18(15387); byte_3023ECE = 1; } if ( !abstractTable ) goto LABEL_13; v3 = (*(int (__fastcall **)(_DWORD *, _DWORD))(*abstractTable + 212))( abstractTable, *(_DWORD *)(*abstractTable + 216)); result = 1; if ( v3 ) return result; isProdMode = AbstractTable__get_isProdMode(1); tableConfig = abstractTable + 3; if ( isProdMode ) tableConfig = abstractTable + 4; if ( !*tableConfig ) { LABEL_13: v9 = ((int (*)(void))loc_26E73D0)(); return CryptUtils__CalculateMD5FromFileOrEmpty(v9); } converter = ConverterFactory__Create(*tableConfig, *(_DWORD *)(*tableConfig + 20)); v8 = Class_Torappu_DB_DBLoader; if ( (*(_BYTE *)(Class_Torappu_DB_DBLoader + 178) & 1) != 0 && !*(_DWORD *)(Class_Torappu_DB_DBLoader + 96) ) v8 = il2cpp_runtime_class_init_0(); return DBLoader___DoLoadTable(v8, abstractTable, converte…

提取宝石研物语的Live2D - 远古1.0版本
type: item
date: 2021-07-18T05:44:00+00:00
description: [scode type="yellow"]此文为补写，意为记录，已不适用于目前版本游戏[/scode]0x0 故事那是2017年... 突然两张GIF刷爆了我加的多个群组。 大伙们都是LSP了，看到这哪还顶得住，纷纷开始寻找原游戏出处。然后发现是TapTap中还未上架的游戏《宝石研物语》，这还不得感觉关注一波？于是游戏关注度飞涨。然后...不太妙的感觉...不过不碍事，游戏关注持续增加。一直到上线后一度挤爆服务器，后续多天也经常维护。至于玩家们最关注的Live2D，则不出意外的惨遭和谐，掀裙子的动画直接消失了。这哪能忍，那么就尝试看看资源文件，是否真的删掉了动画还是仅不播放此动画了。0x1 资源解密解压安装包，assets\res\下有live2d文件夹。好耶，点开模型配置文件model.json！...坏耶，被加密了，再看看动作文件，贴图文件等，发现全部被加密，且文件头为都为gemtrade。结合这游戏是Cocos2d-Lua开发的，盲猜一波是xxtea加密（?）。掏出IDA，加载libcocos2dlua.so，定位到cocos2d::FileUtils::getDataFromFile（），伪代码：cocos2d::Data *__fastcall cocos2d::FileUtils::getDataFromFile(cocos2d::Data *a1, cocos2d::FileUtils *a2, int a3, _BYTE *a4) { int v8; // r2 int v10[8]; // [sp+0h] [bp-20h] BYREF cocos2d::Data::Data(a1); v8 = *(_DWORD *)a2; v10[1] = (int)a1; v10[0] = (int)off_CD734C60; (*(void (__fastcall **)(cocos2d::FileUtils *, int, int *))(v8 + 28))(a2, a3, v10); if ( !cocos2d::Data::isNull(a1) ) *a4 = cocos2d::FileUtils::decodeData(a2, a1); return a1; }发现多了一个FileUtils::decodeData()方法，那么它应该就是目标了，此方法中调用FileUtils::decodeBuffer()紧接着FileUtils::decodeXOR()，伪代码：char *__fastcall cocos2d::FileUtils::decodeXOR(cocos2d::FileUtils *this, unsigned __int8 *a2, int a3, int *a4) { unsigned int v8; // r4 int v9; // r5 int v10; // r8 int v11; // r0 unsigned int v12; // r6 unsigned int v13; // r8 unsigned int v14; // r10 unsigned int v15; // r5 char *v16; // r11 int v17; // r0 __int16 v18; // r12^2 char *v19; // r3 unsigned int v20; // r0 unsigned int v21; // r2 if ( a3 <= 19 ) return 0; v8 = memcmp(&unk_CD6C15E8, a2, 8u); if ( v8 ) return 0; v9 = cocos2d::FileUtils::char2uint(this, a2, 8); v10 = cocos2d::FileUtils::char2uint(this, a2, 12); v11 = cocos2d::FileUtils::char2uint(this, a2, 16); v12 = (unsigned int)(v11 + v10) >> 1; v13 = (unsigned int)(v10 - v11) >> 1; v14 = a3 - 20; *a4 = v14; v15 = v9 - v12 * v13; v16 = (char *)malloc(v14); memcpy(v16, a2 + 20, v14); while ( 1 ) { v17 = cocos2d::FileUtils::char2uint(this, (unsigned __int8 *)v16, v8); v18 = HIWORD(v17) ^ HIWORD(v15); v16[v8] = v17 ^ …

尝试修复iOS ArcaeaCountdownScore - 版本3.6.0
type: item
date: 2021-07-17T10:11:00+00:00
description: [scode type="yellow"]本文于完成2个月后凭回忆补写并重新配图，且博主并未学过C系语言，难免会有错漏，请见谅[/scode][scode type="lblue"]博主使用设备为iPad Pro 10.5（2017）架构为ARM64，文中所有修改都针对ARM64[/scode][scode type="red"]免责声明：本文禁止转载，仅为博主安全研究学习记录，强烈反对参考本文内容进行不正当用途，包括但不限于作弊行为等。请后果自负。[/scode]0x0 前言由大佬 esterTion 编写的iOS插件 ArcaeaCountdownScore 用于lowiro制作的音游Arcaea。此插件的作用是将游戏分数计算改为倒计分制，即开局满分，产生每个far或者lost时分数递减。这种计分制度对我这种菜鸡即为有用，可以更容易的提前许多预估出本局打歌是否可以达到理想的分数并选择继续还是重试。而616截止至目前（写文时3.6.4）都没有推出具有此功能的搭档。不幸的是，插件在很久前就失效了，表现为什么反应都没有，而鸽王一直都没有更新，我也一直在等待。此时的我并未加入任何音游相关的群组。直至616推出了3.6.0版本更新，此版本中在请求头中新增了X-Random-Challenge字段来验证请求，与此相关的代码逆出来看着简直像一坨shit。于是我想尝试加入一些音游群组向大佬们取经。进入一些群组后才发现原来 esterTion已然弃坑许久（具体原因可参见其B站动态），那么大佬们不再维护ArcaeaCountdownScore，就只能自己撸起袖子上了。。。小知识：616在iOS 3.6.0版本请求头中同时也加入了X-Client-Certificate-Subject-CN以及EnableObfuseCheck，而安卓则在后续版本中加入趣事：616在iOS 3.6.0版本中意外（？）打包进了Algo.zip0x1 方法注入拿到 ArcaeaCountdownScore 源码后首先看看hook的方法：ArcUtil_calculateScore = (unsigned long)MSFindSymbol(NULL, "__ZN7ArcUtil14calculateScoreEiiiii");::aru:blood2:: 怪不得没有任何反应了，iOS二进制文件中已经没有了符号，插件无从找起，方法都没有被替换，当然什么反应都没有了。那么当务之急就是找到新的方法位置进行hook。iOS中没有符号了，那么掏出安卓版3.6.0的二进制想办法对比定位。找到ArcUtil::calculateScore()，然后按x寻找其xref引用，可见一堆调用了calculateScore()的方法。接下来就是在这些方法中找出可以用于在iOS二进制中搜索的信息，由于我之前就有看过OnlineManager::submitScoreInternal()方法，并且此方法中也调用了calculateScore()，所以直接选择从此方法入手。伪代码： v133 = std::string::append((int)&v550, "&score=", 7u); v134 = *(_OWORD *)v133; v553 = *(void **)(v133 + 16); v552 = v134; *(_QWORD *)(v133 + 8) = 0LL; *(_QWORD *)(v133 + 16) = 0LL; *(_QWORD *)v133 = 0LL; v135 = (std::__ndk1 *)ArcUtil::calculateScore( (ArcUtil *)*(unsigned int *)(a4 + 32), *(_DWORD *)(a4 + 28), *(_DWORD *)(a4 + 24), *(_DWORD *)(a4 + 20), *(_DWORD *)(a4 + 28) + *(_DWORD *)(a4 + 32) + *(_DWORD *)(a4 + 24), *(_BYTE *)(a4 + 132)); std::to_string(v135, v136);可以看到调用calculateScore()方法前有个字符串拼接，其中部分字符串为&score=，那么就可以在iOS的二进制中搜索此字符串：找到后查找其引用进入的即为OnlineManager::submitScoreInternal()方法（如果使用其他引用进行查找发现多个调用，则需要对比找到其在iOS中对应方法），伪代码： v72 = std::string::append(&v328, "&score="); v73 = *(_OWORD *)v72; v331 = *(_QWO…