( 原文地址:https://0xffff.one/d/482 )

更新:0xffff上的原文由于论坛用户变多后为了规避版权问题被删了,不过反正都是完全搬运的(而且反正也是我写的)所以问题不大(逃

上次在Android破解初探之:Synthesia介绍(shui)了下Android应用的破解方法,这次继续来搞事情。

环境搭建·

由于元气骑士是一个Unity做的游戏,所以需要一些解Unity的工具(大部分工具的用法都参照了kjkjkAIStudio的教程):

  • Il2CppDumper:Unity游戏代码的一些信息会放在一个metadata文件里,如用到的字符串的值、用到的函数名字等,il2cppdumper是用来帮助把这些信息在逆向时添加进库文件里(指的是ida文件)。在github就可以下载。
  • IDA:上次说过了,但这次需要用到python支持,所以要先配好。
  • dnSpy:上面说的matadata恢复出的信息用这个东西看会更方便一点,不得不说它的搜索功能真好用。。。同样github上就有。
  • AssetStudio:可以提取Unity的asset数据,因为需要用到asset中的一个文件。github下载
  • C#环境:il2cpp.so里面的东西是C#写的,由于我太菜了所以解它的加密时直接照着它的函数写了个- -

解包·

首先说一下我搞的版本时GooglePlay下的v2.5.1版本,在国内应用商店下的强制我登录,还要实名- -

用AndroidKiller可以解包,但在重打包时发现凉屋好像对签名做了检查,如果发现包被改过的话进入游戏时会直接跳转到官网,绕签名的方法到现在我还没搞出来(咕咕咕)。所以如果没有AK或者不想搞AK的话可以直接把后缀改成.zip解包,或用7-zip等解。

解包后关注两个文件:一个是lib里的libil2cpp.so,在 lib/对应arm版本/libil2cpp.so,至于arm版本,32位的就是armeabi-v7a文件夹,64位的就是arm64-v8a文件夹,实在不知道版本的可以看一下安装完后手机(或虚拟机吧)里的 /data/app/com.ChillyRoom…的那个目录里的lib目录里是arm还是arm64。

另一个是matadata文件,在解包后的目录里的 assets/bin/Data/Managed/Metadata/global-metadata.dat。

拿到两个文件后要用il2cppdumper提取数据,但这个要先知道Unity版本,Unity版本可以打开 assets/bin/Data/level0 文件看,文件开头一堆二进制中会有一串数字,那个就是Unity版本(v2.5.1用的是2018.4):

il2cppdumper的用法在github上有介绍了,输入命令+so文件路径+metadata文件路径后输入Unity版本,再输3(Auto Plus)就好:

使用后会在使用目录里多了四个文件/文件夹(看日期就可以看出来是哪些了),把这些文件放好,一会会用到。

还有asset文件可以先解出来,AssetStudio打开解包出来的asset文件夹,然后Export All提取。

看源码·

首先是把il2cpp.so用IDA打开(文件比较大,会解比较久- -,记得32/64位对应好),解完后通过IDA中的 file->Script file ,然后选择刚才metadata提取的scripy.py,等一会就会把符号/名字等贴上去了(注意IDA要支持python,不会的可以看kjkjkAIStudio的视频)。

另外由于IDA的搜索功能(跟dnSpy比)实在一般,所以可以通过dnSpy把metadata提取出来的 DummyDll/Assembly-CSharp.dll 用dnSpy打开,然后 编辑->搜索程序集 调出搜索窗口。搜到的函数对应的RVA值就是函数在il2cpp.so的地址,在IDA里跳到这地址就是对应函数(快捷键g)。

破解过程·

1. 内购破解·

说起内购破解,在很久(大概一年多或更久前吧)以前,元气骑士的角色是否解锁、钱的多少都是由一个xml文件决定的,只要改掉这个文件里对应的数据就是破解完了。但是,在最近(大概几个月前吧)改的时候发现游戏会把我的xml删掉然后建个新的,所以这次才和元气骑士搞上了(知道昨天才发现原来是我把数据改错了,游戏解析不到数据才会删档,让我还patch了这么久- -)。

xml文件的路径在 /data/data/com.ChillyRoom.DungeonShooter/shared_prefs/com.ChillyRoom.DungeonShooter.v2.playerprefs.xml ,里面的东西都有名字的,猜出大概意思然后改掉就好了,比如"c1_unlock"就是character1的解锁情况,”gems“就是钱。(下面会贴一下改好的)

但改的时候要注意数值不能太大(不然删档),“unity.player_sessionid”、"unity.cloud_userid"等最好保留原来的(不然会发生什么我也不知道)

2. 合成材料破解·

内购因为是明文存储的所以好处理,合成材料则放在另一个文件 (/data/data/com.ChillyRoom.DungeonShooter/files/item_data.data ,几乎元气出花园后更新的东西都放这里了),是加密数据。

材料的存取跟一个叫 ItemData 的类有关,重点关注它的Save和Load方法。

IDA中可以大约看到,Save函数从游戏中获取到data后是通过函数JsonUtil__SaveJsonWithCrypt存储数据

SaveJsonWithCrypt会先把数据转换成JSON格式,然后调用EncryptHandler__Encrypt加密存储

EncryptHandler__Encrypt中可以看到是用了C#的DESCryptoServiceProvider进行DES加密,key和data是外部传进去的,iv是由CryptUtil__DecryptXor函数计算出来,key和iv都要通过il2cpp_array_new_specific_0函数进行padding

CryptUtil__DecryptXor逆向一下大概就是(把那些范围检测的都去掉了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
string XorDecrypt(string s1, string s2){
int i = 0;
int sl1 = s1.Length;
int sl2 = s2.Length;
//Console.WriteLine(slen1);
//Console.WriteLine(slen2);
string result = "";
while(true){
if(sl1 <= i) break;
char c1 = s1[i];
char c2 = s2[i-i/sl2*sl2];
char cr = (char)(i%0xf*(i%5)%0x5c^c1^c2);
++i;
result += cr;
}
return result;
}

而il2cpp_array_new_specific_0逆了一下大概就是如果传进去的string少于8bytes(其实是a2,只是在这里是8)的话就在后面补0补到8bytes(由于key和iv都小于8bytes,所以剩下的就没追进去了)。

然后key的值从函数调用来看是StringLiteral_11064,iv就是StringLiteral_8948和StringLiteral_8949进行CryptUtil__DecryptXor的结果。关于StringLiteral其实差不多是个常量,它的值可以在metadata提出出来的stringliteral.json文件里找,但要注意下下标,IDA的StringLiteral是从1开始的。得到的key是"iambo",iv xor前的两个值是”\x11(55(#“和"PASSWORD",xor后得到iv是”Ahbool“。

由于密码学学艺不精,花了一个下午入门了C#然后才照着原程序把加解密程序撸了出来- -,最后把JSON文件解密了出来,因为太长了就不图了。关注一下里面的东西:

  • plants:现在花园里种的植物,想改什么把plantName改了就好,state就是生长状况。
  • commodities:应该就是在卖的商品。
  • itemUnlock:一些解锁了的东西,现在我只知道填"plant_pot3"~"plant_pot7"的话可以把所有花圃都解锁了。
  • forgeWeapons:现在在研究台的武器。
  • blueprints:蓝图及拥有状态,状态:None(就是空着)是没有,Got是拥有,Researched是已经研究出来了。要改的话建议先改成Got,游戏中再进行研究。
  • seeds:种子及其数量。
  • materials:材料及其数量。
  • 其他:我也没搞清楚是什么。。。

可以看出要改的话大部分东西都是需要先知道名字的,至于物品名字,是放在asset解出来的 TextAsset/ItemInfo.txt 文件里,是个JSON文件,有了修改item所需的全部名字。写个python把名字全部提取出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import json
import binascii


with open('path/to/ItemInfo.txt', encoding='utf-8') as f:
items = json.loads(f.read())
blueprints = items['blueprints']
materials = items['materials']
seeds = items['seeds']

for b in blueprints:
print("\""+b['name']+"\": \"Got\",")
print('--------------------------')
for m in materials:
print("\""+m['name']+"\": 999999,")
print('--------------------------')
for s in seeds:
print("\""+s['name']+"\": 999999,")

把JSON文件填好后重新加密放回原来位置就好。

注意一点是xml文件和data文件放的时候都需要先把游戏关闭,不然游戏会把内存中的数据放到文件中(就是改了等于没改)。

秀下结果·

最后的最后·

破解只是为了玩一下/学一下,请勿用于非法用途(逃)(源码的话发出来也不太好,可以在下面留言或群里私我,逃)