小木曾雪菜 发表于 2020-8-1 23:46

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

本帖最后由 小木曾雪菜 于 2020-8-2 00:32 编辑

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

## 0x0 前言

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

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

同样,本教程和隔壁发的一样。
上期:(https://www.52pojie.cn/forum.php?mod=viewthread&tid=1212903)


![](./screenshot/rootfilm_done2.jpg)

## 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)

详见(https://github.com/jakcron/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

具体用法可以详见(https://yuzu-emu.org/help/quickstart/)和(https://github.com/Ryujinx/Ryujinx/blob/master/KEYS.md)。

### (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
>|Controlnca
>|LegalInformation nca
>|Meta nca
>|.cnmt // metadata file such as the information of each nca

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

```C
// 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//romfs`,修改的可执行文件放入`/atmosphere/content//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`后面的日文文本是注释,只是为了表明是那句话,并不是直接加载)

![](.\screenshot\rootfilm_lua.png)


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

![](.\screenshot\rootfilm_ko.png)

![](.\screenshot\rootfilm_chs.png)


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


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

![](.\screenshot\rootfilm_textasset.png)


## 0x3 il2cpp与ida结合分析

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

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

![](.\screenshot\rootfilm_gamelanguage.png)


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

![](.\screenshot\rootfilm_getlanguage.png)


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

![](.\screenshot\rootfilm_gameLanguage_0x14.png)


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

![](.\screenshot\rootfilm_ida_setlanguage.png)


## 0x4 修改main.elf

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

![](.\screenshot\rootfilms_patchedbytes.png)


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

![](.\screenshot\rootfilm_ida_diffaddr.png)


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

![](.\screenshot\rootfilm_done1.jpg)


## 0x5 总结

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

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

netspirit 发表于 2020-8-2 04:20

本帖最后由 netspirit 于 2020-8-2 04:21 编辑

楼主的id让我想起了这个图片....................{:301_1004:}


小木曾雪菜 发表于 2020-8-3 16:35

gunxsword 发表于 2020-8-3 15:24
像这种自带中文只是不让你选择的,多吗?这个方法通用性如何?

首先不是ns游戏都用unity开发,而是ns有unity开发的游戏。这种方法不是通用的,不同游戏的逻辑也各不相同,这个例子比较简单,特别适合作为上手教程来写。

那棵大树疯了 发表于 2020-8-2 07:49

支持 厉害厉害

youxiwater 发表于 2020-8-2 09:40

学习学习了。

芽衣 发表于 2020-8-2 09:50

冬马小三这教程不错{:17_1068:}

Gaho2002 发表于 2020-8-2 09:53

417788939 发表于 2020-8-2 09:50
冬马小三这教程不错

你是在搞颜色

只要再慢一点 发表于 2020-8-2 10:17

秋各位绅士推荐一下ios玩gal的模拟器{:1_918:}{:1_918:}

muririn 发表于 2020-8-2 10:38

支持一下

许仙呀 发表于 2020-8-2 12:24

学习一下

幼星羽 发表于 2020-8-2 13:22

谢谢大佬的分享
页: [1] 2 3 4
查看完整版本: Galgame汉化中的逆向(五):Switch平台下的Unity后端il2cpp分析