手游逆向学习-学マス

2024-5-24|2024-11-20
AlphaBoom
AlphaBoom
type
status
date
slug
summary
tags
category
icon
password
经验:有简单使用过逆向工具但无实际逆向分析经验
目标:一次完整的流程

前期准备

运行环境

操作系统:Windows11
手机系统:Android,模拟器(可Root)
目标游戏架构:Unity

软件配置

  • Il2CppDumper
  • Ida
  • Frida

获取数据

资源数据

获取数据库、图片、模型等
Android的游戏数据在私有目录:/data/data/{package name}中,首先把相关数据先转移到电脑上,注意这里的数据需要Root权限才可以操作。
如果游戏未对资源进行加密就可以使用AssetsStudio之类的软件来查看文件了。

可执行文件

用于分析代码逻辑
对于Unity的手机游戏需要获取以下两个文件:
  • libil2cpp.so
  • global-metadata.dat
这两个文件可以直接从APK中获取,APK文件本身就是一个Zip包改后缀名解压就可以获得。不过游戏一般是通过GooglePlay安装接触不到APK文件。Android中安装后的APK文件会存放在/data/app目录下,但是由于里面不是包名比较难找(Android11之后的变更):
notion image
可以通过下面的方式打印文件树来进行查找,由于在第二级目录中会包含包名信息所以可以快速定位到目标文件在哪个位置。
但是现在都是Android App Bundle存在多个APK文件操作起来比较麻烦,不如直接在手机里找解压后的文件导出使用,首先libil2cpp.so会放在lib目录下(lib和apk文件同级),另外的global-metadata可以在/data/data/{package name}/files/sdcard/Android/data/{package name}/files里找找。

游戏分析

简单尝试后:
  • 资源文件经过加密
  • global-metadata.dat经过加密
所以需要先解密global-metadata.dat之后才能分析资源文件的加密算法,这个文件包含了很多元数据可以方便定位方法、字段信息对于后续的代码分析很重要。

解密Metadata

先查找加载global-metadata.dat的代码,导入ida之后打开Strings window查找字符串。
notion image
双击跳转到地址之后查找引用
notion image
notion image
查看这个sub_3963DD0函数
notion image
notion image
和源码对比下可以发现下面多了个奇怪的异或操作,找到这个byte_19F5A7D的数组导出来使用。
notion image
编写代码生成解密后的文件:

抓包解析

如何抓包

工具选择上一般来说Windows平台使用Fiddler,Mac平台使用Charles,当然也有其他选择例如mitmproxy等。实现上是由这类软件开启一个本地服务器,然后将要监控的设备的网络请求转发到这个代理服务器,这样就可以知道目标设备发送了什么请求。不过这个方式看不到加密的数据例如Https,所以通常使用下还是额外要配置Https的抓包,具体的工作就是把这类软件提供的证书让操作系统信任,这样应用层面也会认为这个证书有效使用这个证书的信息加密数据,进而代理软件也知道如何解密数据了。
具体设置流程可以跟着官方文档按照步骤进行:
不过由于现在的Android应用已经默认不信任用户证书了,这就导致大部分的应用依然抓不了Https包,所以需要额外通过Root权限将软件证书设置为系统证书。通用流程如下,以Fiddler证书举例(以下命令可以在Git Bash下执行):
  • 修改证书文件名:
    • 将证书放入系统目标目录,并设置文件权限
      • 重启设备、重启代理软件,为了确保修改已经应用可以考虑重启这些设备(根据实际操作经验是不需要的)。
      最后手段还可以考虑使用Wireshark去查看更细致的网络请求流程,不过由于更底层所以相对使用上不是特别容易。
      使用Fiddler抓包看了下发现只能看到获取游戏资源的API调用,之后再用WireShark看了下有对api.game-gakuen-idolmaster.jp 发送的请求并且其协议是TLSv1.3
      这里Fiddler看不到大概率是不走系统的代理的原因,安装通过TunProxy使用VPN转发请求,之后在Fiddler上可以看到请求数据了,不过请求失败。
      接下来看看有代理和无代理情况下的请求区别,先看看正常流程:
      notion image
      对照下失败情况:
      notion image
      再看下一般流程:
      notion image
      对照发现是由于没有走证书验证的流程,ServerHello之后直接changeCipherSpec确定了加密方式,所以Fiddler没办法做这个中间人。不过感觉怪怪的简单多去了解下TLS1.3,原来这个效果就是TLS1.3的流程,握手协议中的部分是会被加密的,TLS1.3对应的流程如下图:
      notion image
      额外的TLS1.3有中间盒兼容模式,例如抓包看到的Change Cipher Spec在TLS1.3中是不需要使用的,这里的目的只是用于伪装已达成让中间设备将其当做TLS1.2协议处理。
      另外查了下好像Fiddler classic一直没做TLS1.3的支持?随后换了几个工具测试,不过也是网络请求失败,这样推测下来大概还是本地搞了Certificate pinning的逻辑。

      资源加密

      接下来使用il2cppdumper提供之前获取的libil2cpp.so以及解密后的global-metadata.dat,生成相关文件来辅助逆向分析:
      • dump.cs # 查看c#代码定义内容的详细信息
      • Assembly-CSharp # 导入例如ILspy类的工具确认整体项目结构
      • ida_with_struct_py3.py # 导入ida

      偷懒声明

      学園アイドルマスターIdolyPride是同个开发公司制作,所以可以用IdolyPride的轮子来快速分析。找了下相关资料也已经能够确定学マス的解密算法以及秘钥信息,所以接下来就快速走遍流程。

      寻找Key

      第一个线索Octo,Octo是QualiArts的一个资源分发基础设施,见Unityにおける通信APIを色々試して罠を踏んだ話
      根据Octo的信息可以找到OctoFullSettings这个类,发现包含了一些名称大概就是Key的东西,所以可以考虑沿着这条道路继续探索。
      notion image
      查看构造方法的地址,然后在IDA中查找哪里调用了这个方法。经过上面的的步骤之后可以发现在Campus_Common_Octo_CampusOctoSetupper__CreateSetting调用了构造函数,根据名称可以判断这是游戏的包名所以这里面的代码就应该包含具体获取Key的流程。接下来具体分析代码:
      notion image
      上半部分大概就是构造函数的,下面V7+xx这块就应该是赋值操作了,多提一嘴像这种函数中Campus_Common_Octo_CampusOctoSetupper__GetB(env, v8)最后的参数V8可以忽略,大部分场景这个值都是NULL,好像扩展函数下是有值的,对Unity不了解看着容易晕,当然具体怎么实现都没关系啦。接下来对照offset信息决定看哪几个方法。
      notion image
      进入Campus_Common_Octo_CampusOctoSetupper__GetC:
      notion image
      最下面两个Stringliteral就是Key值了,对应不同的代码环境,env是个枚举在dump.cs里翻下就可以确定正式环境应该使用下面分支的值。

      数据库加密算法

      找到Octo.Data.SecureFile并找到其load函数地址进而找到Octo_Data_SecureFile__GetReader
      notion image
      会先读一个字节确认使用哪种模式,进入CreateMode1ReadStream:
      notion image
      最后做下文件校验,前16位是文件摘要,后面的是文件内容。最后再找下SecureFile Load调用的链可以追踪到SecureProtoBufDatabase这个类,根据名字可以发现解密的数据和Protocol Buffers有关,到这步可以先直接用Protocol Buffers工具查看解密后的数据,确认是该格式后再去找定义的Schema信息(也就是C#类)。

      资源加密算法

      在之前GetC的类中可以发现,获取AssetBundleCreateRequest的方法:
      进入这个方法:
      notion image
      最下面是Unity加载AssetBundle方法,那么处理解密就应该在MaskedHeaderStream,看下它的read方法:
      MaskedHeaderStream#read
      MaskedHeaderStream#read
      看下读ByteArray的方法:
      notion image
      这里就是具体解密算法,那么到此为止就可以大致分析出文件的加密思路。资源文件的HeaderLength长度是被加密的,加密算法则是与MaskBytes做异或运算,所以现在还需要获取maskBytes和HeaderLength的信息。回头看下MaskedHeaderStream的构造方法,可以确定HeaderLength是传入的数值256,maskBytes则没有代码处理,只有保存了一个maskString(maskString传入参数为abname),回到MaskedHeaderStream#read这张图片,可以发现当存在maskString时应该走CryptByString这个方法,方法里面和ByteArray是一样的只是多了一个StringToMaskBytes去生成maskBytes。

      解密算法实现

      开头已经提过可以使用IdolyPride的代码,具体算法可见:
      HoshimiToolkit
      MalitsPlusUpdated Aug 9, 2024

      网络请求

      根据Network做关键字搜索可以定位网络请求的实现应该在quaunity-api.Runtime中,根据名称可以猜测ApiBase应为处理通信请求的核心类,接下来的工作就从这里入手。

      证书验证

      前边抓包的时候有猜测终端上做了Certificate Pinning的逻辑,那么就从这个点开始吧。
      ApiBase中可以发现GetSSLRootCertificates的方法,查看实现可以发现是读取一个证书文件:
      notion image
      在去寻找调用这个方法的位置(直接xref找不到需要参考gRPC库的文档来找可能调用的位置):
      notion image
      可以发现的确是传给gRPC了,那这部分看到这里就可以了。

      通信加密

      Campus.Common.Network.Api中看到名为GetSecretKeyword的可疑方法,查找调用位置进而可定位到Qua.Network.DefaultMarshallerFactory这个类。观察结构后发现其中包含大量加解密和序列化的方法名,看起来核心算法大概就在这里了,下一步以这个类为基点来寻找具体加密和序列化方案。
      有空再看

      补充:内存Dump

      可以使用GameGuardianFrida,这次使用Frida,先在电脑上安装Frida之后确认PC上的版本之后去Github找对应版本的服务端文件。
      我这里使用MuMu12模拟器,所以选择frida-server-16.2.2-android-x86_64.xz,这里如果选错架构在手机里都能正常运行且无信息打印,所以不清楚的话可以尝试更换server的文件。下一步就是在手机上开启服务:
      之后准备dump:
      当然后续才是工作重点需要判断文件特征,可以参考Unity 游戏逆向:从内存中获取未保护的 IL2Cpp 可执行文件 ,另外这个帖子里提到的这个
      PADumper
      BryanGIGUpdated Nov 13, 2024
      工程大致看了眼参考价值也很高。
      ⚠️
      模拟器一般是x86架构兼容ARM,细心的话可能留意到之前在设备中放置的Server文件是x86架构。所以使用模拟器是可能存在预想外的问题,就我自己的体验在这种情况下Frida不会读取ARM架构的so文件,检索后也有类似Issue并且存在许多年也未处理,所以如果目标文件是这种情况还请注意。

      参考链接

      资源记录

      记录些查资料过程中发现的工具
      • iGameGod:一个iOS平台的逆向工具
      关于Google Play Points兑换奖励无法到账问题日语学习阶段性小结(JLPT N2合格)
      • Twikoo