Galgame汉化中的逆向(五):Switch平台下的Unity后端il2cpp分析

by devseed,

0x0 前言

前面我们主要介绍了一下pc平台下galgame的一些分析方法, 比如说如何去解密文本,如何调用系统字库,以及涉及到动态生成的shellcode相关的内容,寻找密钥等内容。 可是,主机平台往往无法动态调试,分析算法通常只能静态,ida loader也远没有pc平台的好用,相关的资料也比较少。 并且主机平台往往都会用各种自定义字库,这个往往会增加不小的难度。

因此主机平台测试和分析起来就会异常的麻烦,非常需要耐心。 本节将以il2cpp为例,谈谈switch平台下如何分析。这个游戏日版官方屏蔽了其他语言,我们将分析怎么去找相关代码,并修改switch中的可执行文件来调出中文。

0x1 Switch分析的准备工作

(1) 文件格式

Meta (.npdm)
GameCard Image (.xci)
Nintendo Content Archive (.nca)
Content Metadata (.cnmt)
Nintendo Software Object (.nso)
Nintendo Relocatable Software Object (.nro)
Kernel Initial Process List (.ini)
Kernel Initial Process (.kip)
Nintendo Application Control Property (.nacp)
ES Ticket (v2 only) (.tik)
PKI Certificate (.cert)

详见nstool,对于分析游戏我们常用的就是exefs下的main文件了,通常为
nso格式。可以用nx2elf转为elf进行编辑(ARN64汇编),然后elf2nso再转回nso

(2) keys

解密游戏内容或者系统固件等需要各种key,常用key介绍如下:

BIS_key.txt // Built-In Storage, unique for each console to decrypt nand, get from TegraRcmGUI biskeydump
prod.keys //decrypt system files and encrypted > > game files, get from Lockpick_RCM
title.keys //These are only used for games that are not dumped from cartridges, get from Lockpick_RCM

具体用法可以详见YUZURyujinx

(3) 游戏档案文件系统(pfs)

PartitionFS (pfs), HashedPartitionFS(hfs) // nsp or xci
| Program nca (the biggest nca in nsp) // nca in xci at /secure path
    |RomFS // this is file system of the rom in nca or xci, can be in romfs.bin or directories
    |ExeFs // the exeuctable code, the version must be consonance to patch
|Control nca
|LegalInformation nca
|Meta nca
|.cnmt // metadata file such as the information of each nca

通常提取游戏文件需要从 nsp -> nca -> exefs/romfsxci -> exefs/romfs,常用的命令行如下:

1
2
3
4
5
6
7
8
// NAX0 Reader -> NCA Reader(Program part, the biggest nca) -> RomFS Reader -> Individual Files
hactoolnet -k prod.keys -t pfs0 --outdir %outpath% %nsppath% // nsp -> nca
hactoolnet -k prod.keys -t nca --romfsdir %romfspath% %ncapath% // nca -> romfs dir
//if missing nca title key, convert to xci and extract xci, the
// hactool -k prod.keys -t nca --basefake --romfsdir %outpath% %ncapath%
hactoolnet -k prod.keys -t xci --romfsdir %outpath% %xcipath% //xci -> romfs dir

hactoolnet -k prod.keys -t nca --exefsdir %exefspath% %ncapath% //extract exe code

进行汉化或mod将修改的档案文件放入/atmosphere/content/[title id]/romfs,修改的可执行文件放入/atmosphere/content/[title id]/exefs注意可执行文件版本必须和游戏版本相同,否则报错。

(4) 相关工具

  • nx2elf, elf2nso
  • hactoolnet
  • SwitchIDAProLoader, reswitched loaders
  • AssetStudio, UABE
  • Il2CdumpppDumper
  • dnSpy
  • ida pro 7.0

0x2 观察

解包后此游戏是unity引擎的特征很明显,用AssetStudio或者UABE来提取资源。一般unity引擎的文本大多在TextAsset或者MonoBehaviour(存储unity的序列化对象)里。

提取后发现此游戏文本与脚本在TextAsset里面,且游戏脚本为lua,根据id来定位数据库的文本,如_talk(16),不同语言调用不同的数据库来实现多国语言。(lua脚本__talk后面的日文文本是注释,只是为了表明是那句话,并不是直接加载)

我们还发现游戏虽然只有日文,但是资源里却包含中文和韩文的文本,猜测是通过某个变量来确定语言。


经过搜查,发现各lua脚本中没有明显的和语言相关的内容,因此可以确定是在unity里面设置了,我们需要去分析unity代码了。看到资源里没有c# 的dll,但是有\Managed\Metadata\global-metadata.dat,可以肯定用了il2cpp后端,也就是说所有的逻辑代码都在main文件里了。分析il2cpp的原生代码,这比mono后端的直接逆向成c#代码要难,但是由于可以得到函数名称和成员变量偏移,比分析纯native code 要容易多了。

当然还有另一种方法,直接把日文文本文件替换成中文文本文件,但是由于此游戏有1w+个小文件,并且不同语言文件都是哈希值后缀,不容易区分,这种方法非常麻烦,因此不考虑。

0x3 il2cpp与ida结合分析

和一般的unity il2cpp分析方法类似,用Il2CdumpppDumper根据mainglobal-metadata.dat来得到Assembly-CSharp.dll,虽然没有逻辑代码但是可以在里面看到类的声明。再根据RVAOffset可以在ida里面定位了。 (可以用ida跑ida.py脚本,然后输入生成的script.json自动添加函数名)

dnspy打开Assembly-CSharp.dll,搜索language可以看到相关的结构,在GameCore类中有gameLanguage的枚举结构。

顺藤摸瓜,找到了gameLanguage get函数,看到总是返回日语,这句是无法得到其他语言的罪魁祸首。修改也很简单,我们将返回值设定为1即为中文(由上面enum的值得知jp=0, ch=1, ko=2)。由于arm64是4字节定长指令,返回值是X0,所以可以把 LDR W0, [X8, #0X14] 改为 LDR W0, 1

这么改完理论上是可以挑出来汉语了,但是实际测试发现只有图标是汉语,文本还是日语。我们看到这句LDR W0, [X8, #0X14],发现游戏不一定全是通过get language来获得语言版本的,可能通过某个偏移为0x14的变量。我们继续在c#代码里看,然后发现确实有个gameLanguage的成员函数,offset是0x14

然后继续在ida里面找发现了 set_language函数,结合f5伪代码可以看到修改位置如红框,需要把0x14偏移的变量赋值为1。这里有个麻烦事,STR WZR, [X8, #0x14],其中WZR为零寄存器,相当于赋值为0。直接改mov [x8, #0x14], 1是错误的,arm64没这种用法,但是程序有很紧凑,没地code cave。好在上面有 mov w20, #1w20也没再变过,直接就改成了STR W20, [X8, #0x14]

0x4 修改main.elf

上面分析完了现在该落实到文件上了,用ida keypatch插件可以看到要改的地方。

然而这时候却发现apply patch失败,貌似ida对switch的可执行文件兼容不好,初步分析可能是地址不一致导致的。

那只能手动去改了,搜索若干个字节作为标志字节串,直到结果唯一。然后再手动修改需要patch的字节。注意不能直接修改main因为nso是压缩格式,未必能找到相关字节,需要转换成main.elf再patch。之后再转回nso格式的main,送入机器测试即可。看着没什么问题,中文出来了,搞定!

0x5 总结

本节教程以一个简单的例子谈谈如何上手非pc游戏的分析,并且介绍了一下Switch平台的特点以及如何修改可执行文件。相对于pc平台galgame的汉化,主机平台门槛更高,难度更大,也更加冷门。psp时代之后相关的教程更是少之又少,于是想着来聊聊主机平台的游戏,和大家交流一下。

另外由于这个游戏是这两天发售的,刚搞完印象很深刻,例子不难又很典型,特别适合作为主机汉化的开篇教程,于是就先写这篇了。 Galgame汉化中的逆向(四)关于动态生成shellcode与寻找密钥 将下次再更新了。