无Root修改应用内部存储数据

主要思路:

安卓有备份与恢复机制,大部分应用允许从备份数据恢复。
这是无root下覆写内部存储数据最轻松的方式。

不过应用自己也能禁止或者管理备份文件,具体查Android Developer Documentation

手机:MIUI 12,安卓版本11
开始折腾:

解包备份

首先备份,只备份想要的应用。
然后把备份文件拉到电脑上去
MIUI上是存储设备\MIUI\backup\AllBackup\
bak文件拉出来,拖到HxD里看二进制

Android的备份文件很简单,主要是安卓备份文件头+tar压缩文件,当然国产MIUI还有自己额外的文件头。

文件头:

选中的区域就是Android备份文件头,
ANDROID BACKUP标识,备份版本(5.0),加密模式(none)
选中区域上方是MIUI备份文件头
选中区域下方就是压缩文件开始部分

去掉MIUI文件头(选区上方数据),然后用abe.jar解包成tar文件↗github
java -jar abe.jar unpack xxxx.bak xxx.tar

abe.jar实现的是比较通用的安卓备份协议,然而MIUI里备份设置超级简单,就是MIUI文件头+Android备份文件头+tar文件内容,无压缩无加密,所以直接在HxD里把两个文件头去了直接当tar也行…

tar文件直接7zip解压就行,至此,应该能拿到备份的数据了:
apk和cache,Android/data/包名下的数据,应用私有的数据

Unity逆向

然后是逆向Unity,不过小游戏一般没什么壳和加密,所以直接上
apk解压,assets/bin/Data/,资源文件基本都在这儿,一般拿Unity Studio↗或者disUnity↗解压一下就行

然而更多情况是遇到新版本和加密,解析不出来(doge
disunity支持的unity版本确实过老了,个人觉得Unity Studio更好用,而且还有GUI(短期记忆每天被迫查文档…
虽然我最后还是没能提取资源,有空再研究下(瘫

反编译代码

所以美术资源先放一边,先来代码
Unity现在应该都是il2cpp backend了,mono就不提了,所以直接定位路径:
运行库so:lib\arm64-v8a\libil2cpp.so
元数据文件:设备\Android\data\应用包名\il2cpp\Metadata\global-metadata.dat
然后把两个文件丢给Il2CppDumper↗,dump一下class和方法声明
Il2CppDumper.exe ibil2cpp.so global-metadata.dat
生成一堆文件,对于我来说里面最有用的是dump.cs,所有C#类和方法声明,外加每个方法的虚拟地址
然后开始递归搜索,搜Diamond查找相关方法,然后在ida中查看相应的Assembly-CSharp.dll文件,分析具体代码…

实际上根本分析不动(╯‵□′)╯︵┻━┻,但至少现在有汇编可以看了

存档文件

回头看存档文件,apps\com.ssicosm.dungeon_princess_2_free\sp\com.ssicosm.dungeon_princess_2_free.v2.playerprefs.xml
800KB+,一看就是存档(用Preference Key-Value存档是认真的吗…)
点进去大概是这种画风:

 复制代码 隐藏代码
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="LFZGUwhpJ11cQitXDFRvBTl5FUxZWQhgBFRFU1A%3D">0%2FevWhQCAEK4%2FBE%3D</string>
    <string name="MFZZQjkFOm9VVxZZC2cBaSlGEVFfWFM%3D">DwIABV3MAg%3D%3D</string>
    <string name="MFZZQjkAOm9VVxZZC2cCaSpg">VW5pdAUCAGnZ1gg%3D</string>
    <string name="LFZGUwhpJ0pRVQNEFnRVVxJeAEpvBzl5FUxZWQhgBFRFU1E%3D">SW52ZRQCAGnZ1gg%3D</string>
    <string name="LFZGUwhpLV1cWwNCKFlZWjkCOndAQg9ZC25RWhNTVg%3D%3D">SW52ZRQCAGnZ1gg%3D</string>

%3D,很明显,URL编码,先过一遍URL解码
LFZGUwhpJ11cQitXDFRvBTl5FUxZWQhgBFRFU1A%3D=>LFZGUwhpJ11cQitXDFRvBTl5FUxZWQhgBFRFU1A=

末尾等号,多半是base64之类的,然后直接base64解码…

…获得了一堆乱码

然后只能回头分析代码,看来是简单加密过了(doge

存档解密

众所周知,AES, DES 是超级常用的加密算法,
然后我顺着C#和Unity相关的密码学库(Encrypt)找引用…找了半天没找到,淦(时间成本+⑨)
只能从存档相关变量入手,比如Diamond这个变量,
直接在dump.cs里搜,找到相关类Data_Manager_Cntr,是个数据存储工具人,
查关键字save,找到SaveStageDataAll()方法,对应虚拟地址0xA7FDD4,离文件存储指日可待
IDA启动,载入DummyDll下的Assembly-CSharp.dll分析

实际上完全没必要看懂代码,直接找B 0xABCDEF之类的地址跳转指令和调用函数指令,
一路跳到真正的加密解密的地方:ObscuredIntObscuredByteEncryptIntValue
看汇编我怀疑是异或,因为有EOR这个指令,异或计算是最高效简便的办法,至少能直接拦住改存档的萌新数个小时(比如我)

实际上看到一半啃不动了,打开iLSpy↗,看了一下包名,毕竟小型游戏或多或少会用些SDK和社区插件,除了我这种动不动就想要造一个轮子的人(专门生产不能用的方轮子)
然后查到了有个叫anti-cheat toolkit的插件,C#源代码(旧?新版本)直接到手,完美(:з」∠),剩下的就很简单了

看了下源码,和ida中比对了一下,虽然有的地方不同,但变化不大。
加密算法非常简单,就是异或!
作者指定一个key,(这个key可能和设备唯一标示符有关),然后数据转成字节流,按位与key进行异或就行了,key长度不够的话就循环用key
对于一个键值对,键名会被初始key异或混淆一下,然后键名+初始key组成一个新的key来异或键值对的值,然后再加上3bytes的类型信息和4bytes的原始数据的xxhash
异或用来混淆的话,加密解密计算就很简单了,异或奇数次,加密,异或偶数次,解密(不用遇到数论题真是太好了)

现在问题就是怎么获取key
如果能挂断点分析的话,应该能很轻松地拿到了
打开安卓模拟器,游戏启动失败…不对,可能设备相关,我模拟器拿到的key可能不管用

Android_ID高版本下,每个应用获得的ID与 硬件信息,软件信息,APP本身签名信息关联,所以很有可能每个应用拿到不一样的ANDROID_ID。Unity中用deviceUniqueIdentifier获得唯一标示符,参考

手机没root,告辞(当然可以考虑强制让系统dump内存,可是…C#的GC会把内存搬来搬去)
还是静态分析吧(误

进入游戏变动游戏数值,比如Diamond 16->15,然后备份,拉取存档,和原文比较,
找到变动的值,S2lhbQUCAI5W1RE= => VGlhbQUCAEi9msI=
对应的键:IVFRWwlYAQ==

好吧,看不出来什么,转成二进制
S2lhbQUCAI5W1RE= =>0x5469616d 05 02 00 48bd9ac2
VGlhbQUCAEi9msI==>0x4b69616d 05 02 00 8e56d511
按照加密算法,`0x5469616d0x4b69616d是加密数据,其他是类型信息0x05 02 00和哈希信息0x48bd9ac20x8e56d511
所以
0x5469616d xor key = 16
0x4b69616d xor key = 15
解得key=0x5b 69 61 6d

但是这个key只对这个变量起作用,并且键值对的键也作为key的一部分,所以,我们拿这个key去还原对应的键IVFRWwlYAQ==
对应二进制为0x2151515b095801,获得Diam四个字母,注意到这和Diamond相似,且正好匹配7个字节,那就可以认为这个键的真实值是Diamand字符串了
得到真正的原始key=”0x45383036663665″,7个字节的key,先扔进xml解密一下

注意到解密出artifac这个单词(artifact),遗物当然得拼全了,于是更新下我们的key到8位0x4538303666366538

再次解密,这次拿装备名字试试,比如inven_Br怎么看都应该是inven_Bracer,得到key=0x453830366636653830366636
注意到key非常长,但对一些长字符串数据仍解密不成功,考虑到循环,尝试缩短key,得到key=0x653830366636
这个就是真正的key了,解密后数据就是

 复制代码 隐藏代码
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="Inven_BeltMail_3_OptionValue6">1.7000000476837158 @20@2@0</string>
    <string name="Unit_3_Weapon_1_Option5"> @15@2@0</string>
    <string name="Unit_6_Weapon_2_LV">0 @5@2@0</string>
    <string name="Inven_BracersLeather_1_OptionValue7">0.0 @20@2@0</string>

总之看起来非常舒服就是了233

另外anti-cheat toolkit也对内存数据部分进行了异或混淆,所以就算内存查找也多半搜不到真实数据,需要一些特别的技巧

打包备份文件

这部分本来应该很简单的,我们不是有abe.jar吗,直接java -jar abe.jar pack xxx.tar xxx.ab不就行了….

然后我就凉了(MIUI直接恢复失败)

首先是tar打包的问题,abe.jar对应的github readme提示过,先把原备份文件的文件单给拉下来(文件在tar包中存储的顺序很重要)
tar tf 原备份文件.tar | grep -F "com.包名.name" > package.list
然后再根据此列表打包,
tar cf 打包的压缩名.tar -T package.list
package.list存储了你要压缩的文件,这样能保证严格按照安卓备份的文件顺序打包

注意linux和windows下打包产生的tar可能还是不满足Android/MIUI备份文件要求,
虽然MIIUI告诉我恢复成功了,但是app数据从原先8MB变成了100KB,很明显没有完全成功啊)

抓取debug日志分析,adb.exe找不到的话用这个来抓取日志↗

 复制代码 隐藏代码

05-16 00:57:08.924 W/BpBinder(30448): Slow Binder: BpBinder transact took 3851ms, interface=miui.app.backup.IBackupManager, code=3 oneway=false
05-16 00:57:08.924 I/ACC_EVENT_TRIGGERED( 7597): TYPE: 2048     PACKAGE: com.miui.backup
05-16 00:57:08.924 I/AUTOSTART_DEBUG( 7597): EVENT IS VALID     TYPE: 2048     PACKAGE: com.miui.backup
05-16 00:57:08.925 E/Backup:BackupManager(30448): IOException
05-16 00:57:08.925 E/Backup:BackupManager(30448): java.io.IOException: write failed: EPIPE (Broken pipe)
05-16 00:57:08.925 E/Backup:BackupManager(30448): 
免责声明:
本工作室所有程序与文章均为游戏爱好者自发进行共同探讨研究为目的。
所有买家自愿赞助的费用全部用于服务器、网络租赁与维护、更新等费用,本工作室完全没有任何赢利性商业行为。
本工作室提到的所谓“购买”、“充值”、“续费”等实质为赞助方式,并非商业用语所指的行为模式。
如果对阁下(任何个人、单位、团体、机关、企事业单位等)产生冒犯或者侵害,请您立即告知我们,我们将改正模式。
海神科技 » 无Root修改应用内部存储数据

独家打造,全球唯一,每个细节我们都用心处理!

24小时自动发卡 联系QQ在线客服