好友
阅读权限 20
听众
最后登录 1970-1-1
本帖最后由 Fourseasons 于 2026-5-8 12:59 编辑
这个熊大快跑apk文件是一个使用il2cpp打包的unity游戏。
如何判断一个安卓unity游戏是mono打包还是il2cpp打包呢?
将apk改成zip解压缩
如果看到下面这些,那就是IL2CPP
lib/arm64-v8a/libil2cpp.so
lib/armeabi-v7a/libil2cpp.so
assets/bin/Data/Managed/Metadata/global-metadata.dat
如果看到下面这些,那就是Mono
assets/bin/Data/Managed/Assembly-CSharp.dll
assets/bin/Data/Managed/UnityEngine*.dll
并且 没有 libil2cpp.so 、没有 global-metadata.dat ,那基本就是 Mono。
Mono 包下,游戏逻辑通常直接在:assets/bin/Data/Managed/Assembly-CSharp.dll里。
对于IL2CPP打包的apk文件,该如何逆向呢?
先大概了解unity IL2CPP打包的流程
C# 代码
-> IL
-> IL2CPP 转成 C++
-> 编译成 libil2cpp.so
方法名,类名,字段名 主要靠 global-metadata.dat
真实函数逻辑 在 libil2cpp.so
使用工具 Il2CppDumper
其实一般都是有防护的,就是比如对于gm.dat会进行加密等。
这个就是对gm.dat文件进行加密了,第一次使用工具dump失败。
现在ai很强大了,可以使用ai去辅助我们分析和定位它的加密逻辑。
加载函数调用链:
libil2cpp.so
-> il2cpp_init / 相关初始化入口
-> vm::Runtime::Init
-> vm::MetadataCache::Initialize
-> vm::MetadataLoader::LoadMetadataFile
使用ida去载入libil2cpp.so文件。
等待加载好后直接搜索字符串 global-metadata.dat,发现这个可以搜索到,此刻还是挺开心的,开发者没有混淆这个字符串。
通过这个字符串可以定位到MetadataCache::Initialize()函数,也就是sub_D186D0
这个函数的作用是调用sub_D42514("global-metadata.dat")去加载metadata,成功后读取 metadata 头里的各个 offset/count,分配若干运行时表,然后把一批“索引型指针”修正成真正的内存地址。
所以要继续跟进sub_D42514函数,这个函数就是LoadMetadataFile函数。[C++] 纯文本查看 复制代码
__int64 __fastcall sub_D42514(const char *s)
{
void *n8_9; // x8
char *v3; // x9
size_t n8_1; // x0
char *v5; // x8
unsigned __int64 n8_7; // x9
char *v7; // x8
unsigned __int64 n8_4; // x9
char *v9; // x8
void *n8_5; // x9
unsigned __int64 n8_2; // x8
char *v12; // x9
__int64 v13; // x0
__int64 v14; // x1
__int64 v15; // x19
__int64 v16; // x20
__int64 v17; // x0
__int64 v18; // x21
__int64 v19; // x19
_QWORD v21[2]; // [xsp+0h] [xbp-C0h] BYREF
void *v22[2]; // [xsp+10h] [xbp-B0h] BYREF
void *v23; // [xsp+20h] [xbp-A0h]
const char *Metadata; // [xsp+28h] [xbp-98h] BYREF
__int64 n8; // [xsp+30h] [xbp-90h]
void *v26; // [xsp+38h] [xbp-88h]
char *v27; // [xsp+40h] [xbp-80h] BYREF
unsigned __int64 n8_8; // [xsp+48h] [xbp-78h]
void *v29; // [xsp+50h] [xbp-70h]
int v30; // [xsp+5Ch] [xbp-64h] BYREF
void *v31[2]; // [xsp+60h] [xbp-60h] BYREF
void *v32; // [xsp+70h] [xbp-50h]
__int64 v33; // [xsp+78h] [xbp-48h] BYREF
unsigned __int64 n8_6; // [xsp+80h] [xbp-40h]
void *v35; // [xsp+88h] [xbp-38h]
char *v36; // [xsp+90h] [xbp-30h] BYREF
unsigned __int64 n8_3; // [xsp+98h] [xbp-28h]
sub_D570BC(v31);
Metadata = "Metadata";
n8 = 8;
n8_9 = (void *)((unsigned __int64)LOBYTE(v31[0]) >> 1);
if ( ((__int64)v31[0] & 1) != 0 )
v3 = (char *)v32;
else
v3 = (char *)v31 + 1;
if ( ((__int64)v31[0] & 1) != 0 )
n8_9 = v31[1];
v27 = v3;
n8_8 = (unsigned __int64)n8_9;
sub_CE95F0(&v33, &v27, &Metadata);
if ( ((__int64)v31[0] & 1) != 0 )
operator delete(v32);
n8_1 = strlen(s);
Metadata = s;
n8 = n8_1;
if ( (v33 & 1) != 0 )
v5 = (char *)v35;
else
v5 = (char *)&v33 + 1;
if ( (v33 & 1) != 0 )
n8_7 = n8_6;
else
n8_7 = (unsigned __int64)(unsigned __int8)v33 >> 1;
v27 = v5;
n8_8 = n8_7;
sub_CE95F0(v31, &v27, &Metadata);
v30 = 0;
if ( (v33 & 1) != 0 )
v7 = (char *)v35;
else
v7 = (char *)&v33 + 1;
if ( (v33 & 1) != 0 )
n8_4 = n8_6;
else
n8_4 = (unsigned __int64)(unsigned __int8)v33 >> 1;
v36 = v7;
n8_3 = n8_4;
sub_CE67B4(v22, &v36);
if ( ((__int64)v22[0] & 1) != 0 )
v9 = (char *)v23;
else
v9 = (char *)v22 + 1;
if ( ((__int64)v22[0] & 1) != 0 )
n8_5 = v22[1];
else
n8_5 = (void *)((unsigned __int64)LOBYTE(v22[0]) >> 1);
v36 = v9;
n8_3 = (unsigned __int64)n8_5;
sub_CE67B4(&Metadata, &v36);
v21[0] = "global-metadata2.dat";
v21[1] = 20;
n8_2 = (unsigned __int64)(unsigned __int8)Metadata >> 1;
if ( ((unsigned __int8)Metadata & 1) != 0 )
v12 = (char *)v26;
else
v12 = (char *)&Metadata + 1;
if ( ((unsigned __int8)Metadata & 1) != 0 )
n8_2 = n8;
v36 = v12;
n8_3 = n8_2;
sub_CE95F0(&v27, &v36, v21);
if ( ((unsigned __int8)Metadata & 1) != 0 )
operator delete(v26);
if ( ((__int64)v22[0] & 1) != 0 )
operator delete(v23);
v13 = sub_CF87CC(&v27, 3, 1, 1, 0, &v30);
if ( v30 )
{
v13 = sub_CF87CC(v31, 3, 1, 1, 0, &v30);
if ( v30 )
{
sub_CF9FF8("ERROR: Could2 not open %s");
LABEL_41:
v19 = 0;
goto LABEL_42;
}
}
v15 = v13;
v16 = sub_CFA184(v13, v14);
v17 = sub_CF8AE0(v15, &v30);
if ( v30 )
goto LABEL_41;
v18 = v17;
sub_CF8A0C(v15, &v30);
if ( v30 )
{
sub_CFA194(v16);
goto LABEL_41;
}
v19 = sub_D424A4(v16, v18);
sub_CFA194(v16);
LABEL_42:
if ( ((unsigned __int8)v27 & 1) != 0 )
operator delete(v29);
if ( ((__int64)v31[0] & 1) != 0 )
operator delete(v32);
if ( (v33 & 1) != 0 )
operator delete(v35);
return v19;
}
这个函数先取程序数据目录.../Data,拼接"Metadata",再拼接传入的文件名s,然后又额外构造了一个路径,目标文件名是 global-metadata2.dat,尝试打开文件,如果打开失败,就回退到global-metadata.dat,成功后拿文件句柄去做读取,调用sub_D424A4(v16, v18)生成最终的返回值。
说明sub_D424A4这个函数就是对global-metadata.dat解密的,跟进看一下
按照这个逻辑,读取的位置是4,7,10,13,16……
也就是从第4字节开始,每隔3字节取出1个,然后对每个取出的字节做取反。
解密脚本
[Python] 纯文本查看 复制代码
def dec(data:bytes)->bytes:
out = bytearray()
if len(data) >= 9:
count = (len(data) - 7) // 3
​
for i in range(count):
i = 4 + i*3
x = (~(data[i])) & 0xff
out.append(x)
return bytes(out)
​
def main():
input_file = r"E:\re\actual combat\熊大快跑\assets\bin\Data\Managed\Metadata\global-metadata.dat"
output_file = r"E:\re\actual combat\熊大快跑\assets\bin\Data\Managed\Metadata\global-metadata1.dat"
​
with open(input_file,"rb") as f:
data = f.read()
result = dec(data)
with open(output_file,"wb") as f:
f.write(result)
print("完成")
​
if __name__ == "__main__":
main()
解出来的global-metadata1.dat放到010里面看看,前四字节是AF 1B B1 FA,那就没问题了
然后使用工具去dump。
然后就是ida加载libil2cpp.so文件,去依次导入script.json, il2cpp.h文件,就能进行静态分析了。
看到文件夹里面有dump.cs文件,dump.cs是工具根据global-metadata.dat和libil2cpp.so恢复出来的一份类,字段,方法等清单,方便查找定位。包含的内容
不包含的内容
类名:比如GameManager 函数逻辑:大括号里面的具体执行代码,显示为{ } 字段:变量名和在内存里的偏移 局部变量:函数内部临时定义的变量名 方法名:比如Start() 完整控制流:比如for如何循环等 RVA,VA,Offset 具体的算法实现
RVA是相对于模块基址的偏移,VA是真正运行时的虚拟地址(函数真正的内存地址),Offset是文件内的偏移。
VA = ImageBase + RVA
真实地址 = libli2cpp.so在内存中的基址+RVA
安卓系统每次加载so文件的起始地址是不一样的,但是RVA(也就是偏移)是不变的。
这个文件的作用:可以查找到关键函数,去ida里面静态分析。
还可以根据dump.cs里面的类名和方法名,还有地址,去hook。
在这个文件里面,分析的思路就是先搜索关键词,比如要确定想要修改金币和钻石,那就搜索coins,diamond,count等关键词。
会出现多个结果,这就要需要区分它是显示层还是真实数据层,比如:textDiamondNum,coinNum 这种Text字段大概率是显示层。
真正要看的是有get/set属性的,有Save/Init的类,有 UseItem/AddItem 这种业务函数的类。
定位到Item类,这个是管理道具的类
[C#] 纯文本查看 复制代码
// Namespace:
[Serializable]
public class Item // TypeDefIndex: 6927
{
// Fields
public int itemId; // 0x10
public string name; // 0x18
public string spriteName; // 0x20
public int price; // 0x28
public string description; // 0x30
public string commond; // 0x38
public string tag; // 0x40
private string CountStr; // 0x48
public int quality; // 0x50
private string LevelStr; // 0x58
private const string KEY_COUNT = "itemNum";
private const string KEY_LEVEL = "itemLevel";
​
// Properties
public int Count { get; set; }
public int Level { get; set; }
​
// Methods
​
// RVA: 0xEC033C Offset: 0xEBC33C VA: 0xEC033C
public void .ctor() { }
​
// RVA: 0xEC0410 Offset: 0xEBC410 VA: 0xEC0410
public void .ctor(int itemId) { }
​
// RVA: 0xEC04F0 Offset: 0xEBC4F0 VA: 0xEC04F0 Slot: 4
public virtual void Init() { }
​
[Obsolete("该方法仅用于在木块化代码时候修改数据使用", False)]
// RVA: 0xEC0640 Offset: 0xEBC640 VA: 0xEC0640 Slot: 5
public virtual void InitOld() { }
​
// RVA: 0xEBC754 Offset: 0xEB8754 VA: 0xEBC754
public int get_Count() { }
​
// RVA: 0xEBC78C Offset: 0xEB878C VA: 0xEBC78C
public void set_Count(int value) { }
​
// RVA: 0xEC076C Offset: 0xEBC76C VA: 0xEC076C
public int get_Level() { }
​
// RVA: 0xEC07A4 Offset: 0xEBC7A4 VA: 0xEC07A4
public void set_Level(int value) { }
​
// RVA: 0xEC07E0 Offset: 0xEBC7E0 VA: 0xEC07E0
public void Save() { }
}
private string CountStr 和 public int Count { get; set; }
对外公开使用的属性是 int 类型的 Count ,但是在底层内存里面,实际存储的变量是一个私有的字符串 CountStr。
get_Count() :读方法,比如应用商店这个界面想显示你有多少的钱,就会去读取 Item.Count ,但是实际上底层调用的是 get_Count()它就是把实际存储的CountStr经过解密算法转成数字交出去。
set_Count():写方法, 当在游戏中吃到了金币,或者花了钱,游戏需要更新余额时,底层调用的是 set_Count。传入的 value 就是新的余额,这个方法会把新余额加密并存入 CountStr。
在 Unity il2cpp 的底层 C++ 结构中,所有类的实例(对象)都继承自 Il2CppObject。这个基类自带一个 16 字节(0x10)的对象头。子类的第一个自定义字段 itemId 刚好存放在内存偏移 0x10 的位置。
itemId 搜索一下,会发现,这就是用于存放这个游戏里面物品的 id。
[C#] 纯文本查看 复制代码
// Namespace:
public enum ItemIDCustom // TypeDefIndex: 6712
{
// Fields
public int value__; // 0x0
public const ItemIDCustom None = 0;
public const ItemIDCustom Diamond = 1;
public const ItemIDCustom Coin = 2;
public const ItemIDCustom Shield = 3;
public const ItemIDCustom DoubleCoin = 4;
public const ItemIDCustom Pet_Skill = 11;
public const ItemIDCustom Magnet = 20;
public const ItemIDCustom RideShard = 21;
public const ItemIDCustom PetXiaobai = 30;
public const ItemIDCustom Pet1 = 31;
public const ItemIDCustom Pet2 = 32;
public const ItemIDCustom Pet3 = 33;
public const ItemIDCustom PetMax = 49;
public const ItemIDCustom Suipian1 = 51;
public const ItemIDCustom Suipian2 = 52;
public const ItemIDCustom Suipian3 = 53;
public const ItemIDCustom Suipian4 = 54;
public const ItemIDCustom Suipian5 = 55;
public const ItemIDCustom Suipian6 = 56;
public const ItemIDCustom Suipian7 = 57;
public const ItemIDCustom Suipian8 = 58;
public const ItemIDCustom Suipian9 = 59;
public const ItemIDCustom Suipian10 = 60;
public const ItemIDCustom PetNet = 70;
public const ItemIDCustom Fish1 = 71;
public const ItemIDCustom Fish2 = 72;
public const ItemIDCustom Fish3 = 73;
public const ItemIDCustom Fish4 = 74;
public const ItemIDCustom Fish5 = 75;
public const ItemIDCustom Fish6 = 76;
public const ItemIDCustom Role1 = 100;
public const ItemIDCustom Role2 = 101;
public const ItemIDCustom Role3 = 102;
public const ItemIDCustom Role4 = 103;
public const ItemIDCustom Role5 = 104;
public const ItemIDCustom Role6 = 105;
public const ItemIDCustom RolePackage = 122;
public const ItemIDCustom PetPackage = 123;
public const ItemIDCustom RiderPackage = 130;
public const ItemIDCustom WeaponPackage = 131;
public const ItemIDCustom TimeLimitedPackage = 132;
public const ItemIDCustom Skill = 150;
public const ItemIDCustom ToyCard1 = 201;
public const ItemIDCustom ToyCard2 = 202;
public const ItemIDCustom ToyCard3 = 203;
public const ItemIDCustom ToyCard4 = 204;
public const ItemIDCustom Physical = 240;
public const ItemIDCustom Resurrect = 800;
public const ItemIDCustom Lottery = 801;
public const ItemIDCustom Free_Game = 802;
public const ItemIDCustom VIP = 803;
public const ItemIDCustom Level_Star = 804;
public const ItemIDCustom Car = 805;
public const ItemIDCustom Boomb = 806;
public const ItemIDCustom Grass = 807;
public const ItemIDCustom Time = 808;
public const ItemIDCustom Oil = 820;
public const ItemIDCustom Equip_Start = 850;
public const ItemIDCustom Skin_Start = 900;
public const ItemIDCustom Rider_Start = 930;
public const ItemIDCustom Snow_Board = 960;
public const ItemIDCustom DoubleAttack = 1000;
public const ItemIDCustom HpRecover = 1001;
public const ItemIDCustom YiJiBiSha = 1002;
public const ItemIDCustom PAOZHU = 1003;
public const ItemIDCustom ReelStart = 1020;
public const ItemIDCustom FragmentStart = 1040;
public const ItemIDCustom SnowMan = 1041;
public const ItemIDCustom Water = 1042;
public const ItemIDCustom Fertilize = 1043;
public const ItemIDCustom WaterGift = 325;
public const ItemIDCustom FertilizeGift = 326;
public const ItemIDCustom WeaponStart = 1100;
public const ItemIDCustom GameLevel = 1150;
public const ItemIDCustom Egg = 1160;
public const ItemIDCustom Melon = 5;
public const ItemIDCustom Peach = 6;
public const ItemIDCustom StartFly = 8;
public const ItemIDCustom Apple = 9;
public const ItemIDCustom Honey = 10;
public const ItemIDCustom AppleGift = 301;
public const ItemIDCustom HoneyGift = 302;
public const ItemIDCustom ZhuanshuGift = 303;
public const ItemIDCustom FightGift = 304;
public const ItemIDCustom LimitGift = 305;
public const ItemIDCustom FeedPetGift = 306;
public const ItemIDCustom LuxuryFeedGift = 307;
public const ItemIDCustom ShoesGift = 308;
public const ItemIDCustom PropGift = 309;
public const ItemIDCustom RoleGift = 310;
public const ItemIDCustom CUPayMonthly = 311;
public const ItemIDCustom SuperFavorableGift = 315;
public const ItemIDCustom CMPayMonthly = 317;
public const ItemIDCustom CollectAllCard = 1200;
public const ItemIDCustom Coupon_5 = 1300;
public const ItemIDCustom Coupon_15 = 1301;
public const ItemIDCustom Coupon_30 = 1302;
public const ItemIDCustom ShoesStart = 1500;
public const ItemIDCustom Hammer = 1044;
public const ItemIDCustom whiteCard = 1045;
public const ItemIDCustom RedCard = 1046;
public const ItemIDCustom LotteryTicket = 1047;
public const ItemIDCustom WorldCupTicket = 1048;
public const ItemIDCustom ResurrectionTicket = 1049;
}
我们能看到钻石id是1,金币id是2,苹果id是9,蜂蜜id是10。
[C#] 纯文本查看 复制代码
public const ItemIDCustom Diamond = 1;
public const ItemIDCustom Coin = 2;
public const ItemIDCustom Apple = 9;
public const ItemIDCustom Honey = 10;
所以对于金币和钻石的修改思路: 当拦截到get_Count或set_Count时,传入的第一个参数永远是 args[0],在面向对象编程底层的C++中,args[0] 就是 this 指针,也就是当前的 Item 对象的内存首地址,往后移动 0x10(16 字节),到达 itemId 变量的内存地址,然后用 readS32() 读出一个32位的整数 ,这个数就是itemId 变量存的值。然后根据读出的数来判断是金币(1),还是钻石(2),还是苹果(9),还是蜂蜜(10)。然后把返回值改成大数。
对于宠物的界面,还有孵化时间,孵化的时间也可以修改。
在cs文件里面能够找到下面这个关键类
[C#] 纯文本查看 复制代码
// Namespace:
public class PetManager : MonoBehaviour // TypeDefIndex: 3545
{
// Fields
public static PetManager instance; // 0x0
private const string PETCONFIGNAME = "PetAttrConfig";
private Dictionary<int, PetAttr> m_DicPetAttr; // 0x20
private Item m_PetItem; // 0x28
private int m_PetCount; // 0x30
private OnPetEquipChangeEvent mEquipChangeEvent; // 0x38
private static int _curPetId; // 0x8
public static int m_PetEquipLevel; // 0xC
​
// Properties
public int cur_PetId { get; set; }
public int PetCount { get; }
​
// Methods
​
// RVA: 0x19F19C0 Offset: 0x19ED9C0 VA: 0x19F19C0
public int get_cur_PetId() { }
​
// RVA: 0x19F1AAC Offset: 0x19EDAAC VA: 0x19F1AAC
public void set_cur_PetId(int value) { }
​
// RVA: 0x19F1BCC Offset: 0x19EDBCC VA: 0x19F1BCC
public int get_PetCount() { }
​
// RVA: 0x19F1BD4 Offset: 0x19EDBD4 VA: 0x19F1BD4
private void Awake() { }
​
// RVA: 0x19F1C3C Offset: 0x19EDC3C VA: 0x19F1C3C
public void Initialize() { }
​
// RVA: 0x19F1C40 Offset: 0x19EDC40 VA: 0x19F1C40
private void LoadConfig() { }
​
// RVA: 0x19F1E4C Offset: 0x19EDE4C VA: 0x19F1E4C
private void OnLoadConfig(string _name) { }
​
// RVA: 0x19F1EBC Offset: 0x19EDEBC VA: 0x19F1EBC
public PetAttr GetPetAttribute(int _petId) { }
​
// RVA: 0x19F1FA0 Offset: 0x19EDFA0 VA: 0x19F1FA0
public int GetPetHatchHoneyNum(int _petId) { }
​
// RVA: 0x19F2050 Offset: 0x19EE050 VA: 0x19F2050
public void SavePetHatchHoneyNum(int _petId, int _count) { }
​
// RVA: 0x19F210C Offset: 0x19EE10C VA: 0x19F210C
public int GetPetUpgradeAppleNum(int _petId) { }
​
// RVA: 0x19F2224 Offset: 0x19EE224 VA: 0x19F2224
public void SavePetUpgradeAppleNum(int _petId, int _count) { }
​
// RVA: 0x19F2348 Offset: 0x19EE348 VA: 0x19F2348
public HatchState GetPetHatchStateForOldVersion(int _petId) { }
​
// RVA: 0x19F08E8 Offset: 0x19EC8E8 VA: 0x19F08E8
public void SavePetHatchState(int _petId, HatchState _state) { }
​
// RVA: 0x19F2B1C Offset: 0x19EEB1C VA: 0x19F2B1C
public HatchState GetPetHatchState(int _petId) { }
​
// RVA: 0x19F2C1C Offset: 0x19EEC1C VA: 0x19F2C1C
public void SavePetStartHatchTime(int _petId) { }
​
// RVA: 0x19F2D0C Offset: 0x19EED0C VA: 0x19F2D0C
public DateTime GetPetStartHatchTime(int _petId) { }
​
// RVA: 0x19F2E44 Offset: 0x19EEE44 VA: 0x19F2E44
public GameObject CreatePet() { }
​
// RVA: 0x19F2E4C Offset: 0x19EEE4C VA: 0x19F2E4C
public int GetOwnPetCount() { }
​
// RVA: 0x19F2EF8 Offset: 0x19EEEF8 VA: 0x19F2EF8
public void .ctor() { }
​
// RVA: 0x19F2FC0 Offset: 0x19EEFC0 VA: 0x19F2FC0
private static void .cctor() { }
}
能够知道代码中保存孵化宠物开始孵化时间的函数是 public void SavePetHatchState(int _petId, HatchState _state)
读取开始孵化时间的函数是 public DateTime GetPetStartHatchTime(int _petId) (RVA: 0x19F2D0C)
返回值是 DateTime(时间类型),这个对象本质上是一个64位整数,记录从“公元1年”到现在的滴答数。
根据这个函数名去 ida 里面定位这两个函数
对应的 PetManager$$GetPetStartHatchTime,会从同一个key去读回字符串。
这里能够确定的就是:开始时间就是读档里的开始时间。
然后对GetPetStartHatchTime进行交叉引用,看它那里被调用了,定位到PetUIItem__RefreshItem函数
这是一个很长的UI刷新函数,读不懂,借助ai进行分析
这个函数会进行下面的操作 GetPetStartHatchTime(petId) 取 cur_PetAttr->fields.hatchTime 构造 TimeSpan 调 System_DateTime__op_Addition(start, span)
在C#里面 TimeSpan 代表的是一段“时间跨度”或“时间间隔”
那就能确定这个计算公式了
结束时间 = 开始的时间 + 孵化的时间
修改思路:游戏判断孵化是否完成,是要设定条件的,在这个游戏里面就是 已经过去的时间 >= 孵化的时间 。
当前时间 >= 开始的时间 + 孵化的时间 (即当前时间要大于等于结束时间)
问题是我们不知道这个孵化的时间是多久,这里能简单修改的就是GetPetStartHatchTime 这个开始的时间。
那么就能通过 当前时间 - 开始时间 > 孵化时间 就可以了。
那就用frida拦截这个函数,并且把开始时间改成0(公元1年),用当前时间来减去0,肯定远远大于系统设定的孵化时间,就能完成秒孵化了。
frida脚本我这里是让ai写的。
[JavaScript] 纯文本查看 复制代码
"use strict";
​
let installed = false;
​
const CONFIG = {
TargetCount: 999999, // 所有目标物品锁定的数量
Items: {
Diamond: 1, // 钻石
Coin: 2, // 金币
Apple: 9, // 苹果
Honey: 10 // 蜂蜜
}
};
​
const RVAs = {
// Item类偏移
Item_get_Count: 0xEBC754,
Item_set_Count: 0xEBC78C,
Item_field_itemId: 0x10,
// 宠物偏移
PetManager_GetPetStartHatchTime: 0x19F2D0C
};
​
​
function ptrFromRva(base, rva) {
return base.add(rva); //绝对地址 = 模块基址 + 相对偏移
}
​
// 使用最强力的模块枚举方式寻找基址,防止被加固或系统隐藏
function getIl2CppBase() {
try {
const modules = Process.enumerateModules();
for (let i = 0; i < modules.length; i++) {
if (modules[i].name.indexOf("libil2cpp.so") !== -1) {
return modules[i].base;
}
}
} catch (e) {}
return null;
}
​
// 判断当前处理的道具 ID 是否在我们的“无限清单”中
function isTargetItem(itemId) {
return itemId === CONFIG.Items.Coin ||
itemId === CONFIG.Items.Diamond ||
itemId === CONFIG.Items.Apple ||
itemId === CONFIG.Items.Honey;
}
​
​
function install(base) {
if (installed) return;
installed = true;
​
console.log("\n ======================================");
console.log("libil2cpp.so 挂载成功 基址: " + base);
console.log(" ======================================\n");
​
//无限的金币,钻石,苹果和蜂蜜
try {
const itemGetCount = ptrFromRva(base, RVAs.Item_get_Count);
const itemSetCount = ptrFromRva(base, RVAs.Item_set_Count);
​
// 拦截读取 (get_Count)
Interceptor.attach(itemGetCount, {
onEnter(args) {
this.self = args[0];
},
onLeave(retval) {
if (!this.self || this.self.isNull()) return;
try {
const itemId = this.self.add(RVAs.Item_field_itemId).readS32();//读出id
if (isTargetItem(itemId)) { //判断id是什么
retval.replace(CONFIG.TargetCount);//把结果改为 999999
}
} catch (e) {}
}
});
​
// 拦截写入 (set_Count) 不让扣钱
Interceptor.attach(itemSetCount, {
onEnter(args) {
const self = args[0];
if (!self || self.isNull()) return;
try {
const itemId = self.add(RVAs.Item_field_itemId).readS32();
if (isTargetItem(itemId)) {
args[1] = ptr(CONFIG.TargetCount); //把写入的值改为 999999
}
} catch (e) {}
}
});
​
console.log("无线物资已启用");
} catch (e) {
console.error("[-] 无限物资 Hook 失败: " + e);
}
​
//宠物秒孵化
try {
const getStartTimeAddr = ptrFromRva(base, RVAs.PetManager_GetPetStartHatchTime);
Interceptor.attach(getStartTimeAddr, {
onLeave(retval) {
// 强制返回 0 (公元1年),制造极大时间差
retval.replace(ptr(0));
}
});
console.log("宠物秒孵化已启用 (RVA: 0x19F2D0C)");
} catch (e) {
console.error("秒孵化 Hook 失败: " + e);
}
​
console.log("所有魔法加载完毕");
console.log("\n ======================================");
}
​
​
function waitForIl2Cpp() {
console.log(" 正在扫描内存等待 libil2cpp.so 加载...");
const timer = setInterval(() => {
const base = getIl2CppBase();
if (base !== null) {
clearInterval(timer);
install(base);
}
}, 500); // 500ms轮询,防止设备卡顿
}
​
setImmediate(waitForIl2Cpp);
hook后的结果,也是直接满级了。
看一下宠物界面,也是无敌了 :lol
后记:我是四月份左右看的这个,当时是最新的,但是现在五月份出了4.6.0版本了。换了全新的布局和UI界面,还挺好看的,跟这个很有年代感的版本完全不一样了。(os:可能是顶富火起来了吧)但是前段时间我有些忙,也就没精力再去看了,这个4.5.0的版本就到这吧。欢迎各位大佬提点 {:1_893:}{:1_893:} {:1_893:}
免费评分
查看全部评分