Codeforces 板子合集
Header Setup: Visual Studio 2022, clang-cl, cmake bits/stdc++.h // C++ includes used for precompiling -*- C++ -*- // Copyright (C) 2003-2013 Free Software Foundation, Inc. // // This file is part of the GNU ISO C++ Library. This library is free // software; you can redistribute it and/or modify it under the // terms of the GNU General Public License as published by the // Free Software Foundation; either version 3, or (at your option) // any later version....
Project SEKAI 逆向(7):3D 模型提取
Project SEKAI 逆向(7):3D 模型 1. 文件结构 目前发现的 3D 模型基本都在 [ab cache]/live_pv/model/ 下 初步观察: (1) Body 即目标模型;当然,作为skinned mesh,而且带有blend shapes,处理细节会很多;后面继续讲 (2) 处的 MonoBehavior 就其名字猜测是碰撞盒 (3) 处的几个 Texture2D 则作为texture map 2. 模型收集 利用sssekai取得数据的流程在(5)中已有描述,这里不再多说 首先整理下根据mesh发现的数据需求 (1) Static Mesh pjsk发现的所有mesh在相应assetbundle中会有1个或更多GameObject的ref;对于这些ref,static mesh会出现在m_MeshRenderer之中 其他细节暂且不说;因为做 Skinned Mesh 导入时都是我们要处理的东西 (2) Skinned Mesh 不同于static mesh,这些ref会出现在m_SkinnedMeshRenderer之中 同时,我们也会需要骨骼结构的信息;bone weight以外,也需要bone path(后面会用来反向hash)和transform (3) Blend Shapes 这些可以出现在static/skinned mesh之中;如果存在,我们也会需要blend shape名字的hash,理由和bone path一致 加之,Unity存在aseetbundle中动画path也都是crc,blendshape不是例外 总结: (1) 所以对于static mesh,搜集对应GameObject即可 (2) 对于skinned mesh,同时也需要构造bone hierarchy(就是个单根有向无环图啦),并且整理vertex权重; 则需要收集的,反而只是bone的transform而已;transform有子/父节点信息,也有拥有transform的GameObject的ref (3) 的数据,在(1)(2)中都会有 3. 模型导入 当然,这里就不考虑将模型转化为中间格式了(i.e. FBX,GLTF)...
Project SEKAI 逆向(6):Live2D 资源
Project SEKAI 逆向(6):Live2D 资源 分析variant:世界計劃 2.6.1 (Google Play 台服) 1. Live2D 模型 所有live2d资源都可以在 [abcache]/live2d/下找到;包括模型及动画 首先,.moc3,.model3,.physics3资源都可以直接利用Live2D Cubism Editor直接打开 而模型材质需要额外更名;这些信息都在BuildModelData中 补全后即可导入,效果如图 2. 动画 Key 预处理 可惜动画并不是.motion3格式 封包中有的是Unity自己的Animation Clip 在提取资源时,所有的动画key只能读到对应key string的CRC32 hash;导出/操作必须知道string-hash关系 这些string在moc3以外的文件中未知:当然,碰撞出string也不现实;猜想string和Live2D参数有关 尝试搜索无果 幸运的是Live2D Unity SDK可以免费取得,而且附带样例 还记得前文处理BlendShape时,可以知道AnimationClip的源.anim会有path的源string,而不是crc 尝试加入前缀 可以定位;下面介绍如何构建CRC表,完成crc-string map 3. moc3 反序列化 + CRC打表 每次读取都从moc3文件构造应该可行;不过考虑到有导入纯动画的需求,显然一个常量map是需要的 故需要能读取moc3中所有参数名;参照https://raw.githubusercontent.com/OpenL2D/moc3ingbird/master/src/moc3.hexpat 在 ImHex 中可见: 提取参数名脚本如下: from typing import BinaryIO from struct import unpack # https://github.com/OpenL2D/moc3ingbird/blob/master/src/moc3.hexpat class moc3: Parameters : list Parts: list def __init__(self, file : BinaryIO) -> None: # Header: 64 bytes file....
Project SEKAI 逆向(5): AssetBundle 脱机 + USM 提取
Project SEKAI 逆向(5): AssetBundle 脱机 + USM 提取 分析variant:世界計劃 2.6.1 (Google Play 台服) 1. AssetBundleInfo 前文提及的AssetBundleInfo是从设备提取出来的;假设是已经加载过的所有资源的缓存的话: 在刚刚完成下载的设备上提取该文件时,该文件 4MB 但是在初始化后重现抓包时发现的该文件为 13MB curl -X GET 'https://184.26.43.87/obj/sf-game-alisg/gdl_app_5245/AssetBundle/2.6.0/Release/online/android21/AssetBundleInfo.json' -H 'Host: lf16-mkovscdn-sg.bytedgame.com' -H 'User-Agent: UnityPlayer/2020.3.32f1 (UnityWebRequest/1.0, libcurl/7.80.0-DEV)' -H 'Accept-Encoding: deflate, gzip' -H 'X-Unity-Version: 2020.3.32f1' 推测设备上文件为已缓存资源库,而这里的即为全量资源集合;尝试dump sssekai apidecrypt .\assetbundleinfo .\assetbundleinfo.json 查身体模型数看看吧 此外,这里的数据还会多几个field 新数据库: "live_pv/model/character/body/21/0001/ladies_s": { "bundleName": "live_pv/model/character/body/21/0001/ladies_s", "cacheFileName": "db0ad5ee5cc11c50613e7a9a1abc4c55", "cacheDirectoryName": "33a2", "hash": "28b258e96108e44578028d36ec1a1565", "category": "Live_pv", "crc": 2544770552, "fileSize": 588586, "dependencies": [ "android1/shader/live" ], "paths": null, "isBuiltin": false, "md5Hash": "f9ac19a16b2493fb3f6f0438ada7e269", "downloadPath": "android1/live_pv/model/character/body/21/0001/ladies_s" }, 设备数据库:...
Project SEKAI 逆向(4): pjsk AssetBundle 反混淆 + PV 动画导入
Project SEKAI 逆向(4): pjsk AssetBundle 反混淆 + PV 动画导入 分析variant:世界計劃 2.6.1 (Google Play 台服) 1. 数据提取 pjsk资源采用热更新模式;本体运行时之外,还会有3~4G左右的资源 (**注:**不定量,见下一篇) 尝试从本机提取资源 没有magic UnityFS,考虑ab文件有混淆 2. 加载流程分析 进dnSpy直接搜assetbundle找相关Class 进ida看impl,可以很轻松的找到加载ab的嫌疑流程 最后直接调用了unity的LoadFromStream,Sekai.AssetBundleStream实现了这样的Stream: 可以注意到 加载时根据 _isInverted flag 决定是否进行反混淆操作 如果有,则先跳过4bytes,之后5bytes按位取反 最后移交InvertedBytesAB继续处理 注意到n00应为128,v20为读取offset 这里考虑offset=0情况,那么仅前128字节需要处理 跟进InvertedBytesAB 可见,这里即跳过4bytes后,每 8bytes,取反前5bytes 综上,解密流程分析完毕;附脚本: import sys import os def decrypt(infile, outfile): with open(infile, 'rb') as fin: magic = fin.read(4) if magic == b'\x10\x00\x00\x00': with open(outfile,'wb') as fout: for _ in range(0,128,8): block = bytearray(fin.read(8)) for i in range(5): block[i] = ~block[i] & 0xff fout....
Project SEKAI 逆向(3): pjsk API中间人攻击 POC
Project SEKAI 逆向(3): pjsk API中间人攻击 POC 分析variant:世界計劃 2.6.1 (Google Play 台服) 这里和il2cpp关系貌似不大;主要记录一下api包劫持可以用的手段 1. 工具 host使用https://github.com/mitmproxy/mitmproxy, victim上安装https://github.com/NVISOsecurity/MagiskTrustUserCerts后,导入mitmproxy的CA,重启就能把它变成根证书 最后,mitmproxy所用脚本:https://github.com/mos9527/sssekai/blob/main/sssekai/scripts/mitmproxy_sekai_api.py 2. 分析 上一篇猜测api的封包用了protobuf;并不然,这里是MessagePack 如此,数据schema和报文在一起;不用额外挖了 用mitmproxy做个poc实时解密转json看看: 3. MITM 没错,搞这个只是想看看MASTER谱面长什么样… 锁MASTER的字段貌似在这里;尝试直接修改 def response(self, flow : http.HTTPFlow): if self.filter(flow): body = self.log_flow(flow.response.content, flow) if body: if 'userMusics' in body: print('! Intercepted userMusics') for music in body['userMusics']: for stat in music['userMusicDifficultyStatuses']: stat['musicDifficultyStatus'] = 'available' flow.response.content = sekai_api_encrypt(packb(body)) 选项可以点亮;但无果:貌似live会在服务端鉴权 不过live鉴权id貌似不会在开始live中使用;抓包中没有对id的二次引用 考虑可能谱面难度选择只在客户端进行,那么修改上报服务器的难度系数也许能够绕过 def request(self,flow: http.HTTPFlow): print(flow.request.host_header, flow.request.url) if self.filter(flow): body = self....
Project SEKAI 逆向(2): Frida动态调试及取API AES KEY/IV
Project SEKAI 逆向(2): Frida动态调试及取API AES KEY/IV 分析variant:世界計劃 2.6.1 (Google Play 台服) 0. 版本更新 毕竟美服的数据库滞后好几个版本号… 换到台服之后版本号(2.4.1->2.6.1)有变化,同时上一篇所说的metadata加密手段也换了 同样,拉取一份修复过的dump分析,可见: metadata加载部分不再进行全局加解密,观察二进制也可以发现: 2.4.0解密后metadata 2.6.1源metadata多出来8字节? 加载部分出入不大,尝试删掉8字节直接分析 卡在读string这一块;到Il2CppGlobalMetadataHeader.stringOffset检查一下 果然有混淆;这部分应该是明文,而unity的例子中开始应该是mscrolib 而so中il2cpp对metadata的使用却没加多余步骤;推测这部分在加载时进行了原地解密 1. metadata 动态提取 和上一篇记录的il2cpp.so提取步骤如出一辙,这里不再多说 比对apk的和dump出来的二进制: 果然string部分明朗了;继续dump 成功!带信息进ida;而非常戏剧化的事情是: 混淆没了。 果然是非常pro-customer的版本更新啊(确信) 2. frida-gadget 注入 WSA上注入各种碰壁,不过实体机上即使无Root也可以通过frida-gadget修改apk注入 之前用过改dex的方式尝试过,并不成功;挑一个运行时会加载的.so下手: 注: 后面目标lib换成了libFastAES.so,截图尚未更新 拿apktool打包后签名即可安装到真机 注: 我的frida跑在WSL上,拿 Windows 机器的adb做后端在 Win/Linux 机器配置分别如下 adb.exe -a -P 5555 nodaemon server export ADB_SERVER_SOCKET=tcp:192.168.0.2:5555 用 https://github.com/vfsfitvnm/frida-il2cpp-bridge 测试,未发现问题 3. IL2CPP 动态调用 runtime 接下来就可以直接调用runtime的东西了 回顾上文,貌似API业务逻辑只加了一套对称加密(AES128 CBC);这里同样如此 总结: APIManager为单例实例 APIManager.get_Crypt()取得Crypt,其aesAlgo即为.NET标准的AesManaged 综上,简单写一个脚本: import "frida-il2cpp-bridge"; Il2Cpp....
Project SEKAI 逆向(1): 文件解密及API初步静态分析
Project SEKAI 逆向(1): 文件解密及API初步静态分析 分析variant:ColorfulStage 2.4.1 (Google Play 美服) 1. 解密 metadata 利用 Il2CppDumper 对apk中提取出来的global-metadata.data和il2cpp.so直接分析,可见至少metadata有混淆 IDA静态分析libunity.so, 定位到export的il2cpp_init;没有发现有关混淆的处理 考虑直接分析il2cpp.so,定位到global-metadata.dat有关流程 从这里的xref可以很轻松的摸到Il2Cpp的metadata加载流程 (注:部分变量已更名) _BYTE *__fastcall MetadataLoader::LoadMetadataFile(char *a1) { unsigned __int64 v2; // x8 char *v3; // x9 __int64 v4; // x0 _BYTE *v5; // x8 unsigned __int64 v6; // x9 const char *v7; // x0 int v8; // w21 int v9; // w20 _BYTE *mapped_metadata; // x19 __int64 v11; // x8 __int64 v13; // [xsp+0h] [xbp-E0h] BYREF unsigned __int64 v14; // [xsp+8h] [xbp-D8h] char *v15; // [xsp+10h] [xbp-D0h] size_t len[2]; // [xsp+30h] [xbp-B0h] __int64 v17[2]; // [xsp+80h] [xbp-60h] BYREF char *v18; // [xsp+90h] [xbp-50h] char *v19; // [xsp+98h] [xbp-48h] BYREF __int64 v20; // [xsp+A0h] [xbp-40h] unsigned __int8 v21; // [xsp+A8h] [xbp-38h] _BYTE v22[15]; // [xsp+A9h] [xbp-37h] BYREF _BYTE *v23; // [xsp+B8h] [xbp-28h] sub_17A953C(); v19 = "Metadata"; v20 = 8LL; v2 = (unsigned __int64)(unsigned __int8)v13 >> 1; if ( (v13 & 1) !...