Menu

某TAP上商业街游戏修改

最近在看论坛教程,学习一些移动安全上面的知识,索性也找了一个游戏学学修改。

我在某TAP上,下载了一款游戏 -> Shopping Mall Tycoon_v1.1.2

通过查看APK文件,发现他是基于Unity开发,然后看到游戏已经打包成libil2cpp了。

依葫芦画瓢,使用 Il2CppDumper https://github.com/Perfare/Il2CppDumper 进行dump,so。

至于DUMP教程,其实论坛很多,比如https://www.52pojie.cn/thread-733757-1-1.html

然后剩下就是分析DUMP文件了。

首先,我们必须要进入游戏看看游戏的场景,这个步骤很重要,发现一些游戏的特性。

如图:

发现了游戏中货币至少3种,然后体验下基础玩法,最后看了一下钻石的获得方式。

得到如下结论:

建筑物升级: 4个 分支

1.可容纳人数
2.人均消费
3.消费速度
4.改造

然后钻石是通过观看广告

然后还有一些细节,就是离线后,可以挂机,也就是说,有离线收益。

总和上面的信息,我在DUMP.CS中搜索如下关键字

gold , money , diamond , face , house , watch

然后看看有没有开发者的接口

搜索 debugcontrol , gm , gamemaster , gmui , testui

我一共从dump.cs中找到了如下关键函数


// Namespace:
public class GameData : MonoBehaviour // TypeDefIndex: 3339
{
// Fields
省略

// Methods
public void .ctor(); // RVA: 0x1D6FE0 Offset: 0x1D6FE0
private void Awake(); // RVA: 0x1D71EC Offset: 0x1D71EC
private void Update(); // RVA: 0x1D794C Offset: 0x1D794C
public void Init(); // RVA: 0x1D7284 Offset: 0x1D7284
public bool Lock(int id); // RVA: 0x1D7F44 Offset: 0x1D7F44
public string LockString(int id); // RVA: 0x1D85F0 Offset: 0x1D85F0
public void GetExp(int getExp); // RVA: 0x1D6840 Offset: 0x1D6840
public void UpdateGround(); // RVA: 0x1D90F4 Offset: 0x1D90F4
public float GetExpProgress(); // RVA: 0x1D98EC Offset: 0x1D98EC
public string GetExpPorgressString(); // RVA: 0x1D99E8 Offset: 0x1D99E8
public void MoneyNotSave(MoneyType type, long number); // RVA: 0x1D9B64 Offset: 0x1D9B64
public long GetMoney(MoneyType type); // RVA: 0x1D445C Offset: 0x1D445C
public void SaveMoney(MoneyType type, long number); // RVA: 0x1CA70C Offset: 0x1CA70C
public bool GetMoneyEnough(MoneyType type, long need); // RVA: 0x1D9FA0 Offset: 0x1D9FA0
public long GetMoneyReally(MoneyType type); // RVA: 0x1D9FD8 Offset: 0x1D9FD8
public void SaveThiefNum(); // RVA: 0x1DA020 Offset: 0x1DA020
public void Saveuuid(); // RVA: 0x1DA094 Offset: 0x1DA094
public void SaveGround(GroundItem item); // RVA: 0x1D9634 Offset: 0x1D9634
public void GetGround(GroundItem ground, int groundIndex); // RVA: 0x1DA108 Offset: 0x1DA108
public void SaveRole(int id); // RVA: 0x1D7AB0 Offset: 0x1D7AB0
public int GetRole(int id); // RVA: 0x1D7BF0 Offset: 0x1D7BF0
public long GetRoleBasePower(int id); // RVA: 0x1DA628 Offset: 0x1DA628
public float GetHouseCapcity(int buildingId, int pro); // RVA: 0x1DA834 Offset: 0x1DA834
public long GetHouseCapcityGold(int buildingId, int pro); // RVA: 0x1DA9D8 Offset: 0x1DA9D8 容纳人数 升级需要资金
public long GetHouseCost(int buildingId, int pro); // RVA: 0x1DABC8 Offset: 0x1DABC8
public long GetHouseCostGold(int buildingId, int pro); // RVA: 0x1DADB8 Offset: 0x1DADB8 消费数 升级需要资金
public float GetHouseTime(int buildingId, int pro); // RVA: 0x1DAFA8 Offset: 0x1DAFA8
public long GetHouseTimeGold(int buildingId, int pro); // RVA: 0x1DB150 Offset: 0x1DB150 消费速度 升级需要资金
public long GetHouseReformGold(int buildingId, int buildingLevel); // RVA: 0x1DB340 Offset: 0x1DB340 店铺升级 需要资金
public int GetPeopleNum(); // RVA: 0x1DB530 Offset: 0x1DB530
public int GetMaxCap(int buildingId); // RVA: 0x1DB640 Offset: 0x1DB640
public void SaveAchieve(int id, int condition); // RVA: 0x1DB660 Offset: 0x1DB660
public int GetAchieve(int id); // RVA: 0x1D7CC4 Offset: 0x1D7CC4
public void SaveBuildingsBuild(int id); // RVA: 0x1DB770 Offset: 0x1DB770
public int GetBuildingsBuild(int id); // RVA: 0x1D7D98 Offset: 0x1D7D98
public void SaveGroundNum(); // RVA: 0x1D9878 Offset: 0x1D9878
public void SaveSaleDay(); // RVA: 0x1C8808 Offset: 0x1C8808
public void SaveOpenLevel(int value); // RVA: 0x1DB8B0 Offset: 0x1DB8B0
public void SaveOpenHP(int value); // RVA: 0x1DB924 Offset: 0x1DB924
public void SaveWatchTimes(); // RVA: 0x1D7E6C Offset: 0x1D7E6C
public void SaveWatchGetCount(); // RVA: 0x1D7ED8 Offset: 0x1D7ED8
public void SaveSellExp(int needExp); // RVA: 0x1DB998 Offset: 0x1DB998
public int GetThisNeedExp(); // RVA: 0x1D8E18 Offset: 0x1D8E18
public int GetNextNeedExp(); // RVA: 0x1DB99C Offset: 0x1DB99C
public long GetWatchDiamond(); // RVA: 0x1DBA84 Offset: 0x1DBA84 看广告获得钻石数量
public void ChangeScene(int scene); // RVA: 0x1DBAA0 Offset: 0x1DBAA0
public void GetPlaying(); // RVA: 0x1DBB14 Offset: 0x1DBB14
public void SavePlaying(); // RVA: 0x1DBF18 Offset: 0x1DBF18
public static void SetVector3(string key, Vector3 vector); // RVA: 0x1DC440 Offset: 0x1DC440
public static Vector3 GetVector3(string key); // RVA: 0x1DBDD4 Offset: 0x1DBDD4
public void UpdateGiveRole(); // RVA: 0x1D8F00 Offset: 0x1D8F00
public void UpdateVersion(); // RVA: 0x1DC560 Offset: 0x1DC560
private static void .cctor(); // RVA: 0x1DC6C8 Offset: 0x1DC6C8
}

public sealed class HOUSE : IMessage`1<HOUSE>, IMessage, IEquatable`1<HOUSE>, IDeepCloneable`1<HOUSE> // TypeDefIndex: 3382
{
// Fields
省略

// Properties
省略

// Methods
[DebuggerNonUserCodeAttribute] // RVA: 0x16BE2C Offset: 0x16BE2C
public void .ctor(); // RVA: 0x3F8888 Offset: 0x3F8888
[DebuggerNonUserCodeAttribute] // RVA: 0x16BE3C Offset: 0x16BE3C
public void .ctor(HOUSE other); // RVA: 0x3F8964 Offset: 0x3F8964
public static MessageParser`1<HOUSE> get_Parser(); // RVA: 0x3F8C50 Offset: 0x3F8C50
public static MessageDescriptor get_Descriptor(); // RVA: 0x3F8CDC Offset: 0x3F8CDC
private MessageDescriptor Google.Protobuf.IMessage.get_Descriptor(); // RVA: 0x3F8E84 Offset: 0x3F8E84
[DebuggerNonUserCodeAttribute] // RVA: 0x16BE4C Offset: 0x16BE4C
public HOUSE Clone(); // RVA: 0x3F8EFC Offset: 0x3F8EFC
public int get_ID(); // RVA: 0x3F8F6C Offset: 0x3F8F6C
public void set_ID(int value); // RVA: 0x3F8F74 Offset: 0x3F8F74
public int get_Name(); // RVA: 0x3F8F7C Offset: 0x3F8F7C
public void set_Name(int value); // RVA: 0x3F8F84 Offset: 0x3F8F84
public int get_CapacityStart(); // RVA: 0x3F8F8C Offset: 0x3F8F8C
public void set_CapacityStart(int value); // RVA: 0x3F8F94 Offset: 0x3F8F94
public float get_Capcity(); // RVA: 0x3F8F9C Offset: 0x3F8F9C
public void set_Capcity(float value); // RVA: 0x3F8FA4 Offset: 0x3F8FA4
public long get_CapcityGold(); // RVA: 0x3F8FAC Offset: 0x3F8FAC
public void set_CapcityGold(long value); // RVA: 0x3F8FB4 Offset: 0x3F8FB4
public float get_CapcityMulti(); // RVA: 0x3F8FC4 Offset: 0x3F8FC4
public void set_CapcityMulti(float value); // RVA: 0x3F8FCC Offset: 0x3F8FCC
public long get_EarningStart(); // RVA: 0x3F8FD4 Offset: 0x3F8FD4
public void set_EarningStart(long value); // RVA: 0x3F8FDC Offset: 0x3F8FDC
public float get_ShopPower(); // RVA: 0x3F8FEC Offset: 0x3F8FEC
public void set_ShopPower(float value); // RVA: 0x3F8FF4 Offset: 0x3F8FF4
public long get_ShopPowerGold(); // RVA: 0x3F8FFC Offset: 0x3F8FFC
public void set_ShopPowerGold(long value); // RVA: 0x3F9004 Offset: 0x3F9004
public float get_ShopPowerMulti(); // RVA: 0x3F9014 Offset: 0x3F9014
public void set_ShopPowerMulti(float value); // RVA: 0x3F901C Offset: 0x3F901C
public int get_TimeStart(); // RVA: 0x3F9024 Offset: 0x3F9024
public void set_TimeStart(int value); // RVA: 0x3F902C Offset: 0x3F902C
public float get_WaitTime(); // RVA: 0x3F9034 Offset: 0x3F9034
public void set_WaitTime(float value); // RVA: 0x3F903C Offset: 0x3F903C
public long get_WaitTimeGold(); // RVA: 0x3F9044 Offset: 0x3F9044
public void set_WaitTimeGold(long value); // RVA: 0x3F904C Offset: 0x3F904C
public float get_WaitTimeMulti(); // RVA: 0x3F905C Offset: 0x3F905C
public void set_WaitTimeMulti(float value); // RVA: 0x3F9064 Offset: 0x3F9064
public string get_Reform(); // RVA: 0x3F906C Offset: 0x3F906C
public void set_Reform(string value); // RVA: 0x3F9074 Offset: 0x3F9074
public long get_ReformGold(); // RVA: 0x3F90F4 Offset: 0x3F90F4
public void set_ReformGold(long value); // RVA: 0x3F90FC Offset: 0x3F90FC
public float get_ReformMulti(); // RVA: 0x3F910C Offset: 0x3F910C
public void set_ReformMulti(float value); // RVA: 0x3F9114 Offset: 0x3F9114
public string get_Desc(); // RVA: 0x3F911C Offset: 0x3F911C
public void set_Desc(string value); // RVA: 0x3F9124 Offset: 0x3F9124
public int get_OpenLevel(); // RVA: 0x3F91A4 Offset: 0x3F91A4
public void set_OpenLevel(int value); // RVA: 0x3F91AC Offset: 0x3F91AC
public string get_BuildValue(); // RVA: 0x3F91B4 Offset: 0x3F91B4
public void set_BuildValue(string value); // RVA: 0x3F91BC Offset: 0x3F91BC
public string get_BuildImage(); // RVA: 0x3F923C Offset: 0x3F923C
public void set_BuildImage(string value); // RVA: 0x3F9244 Offset: 0x3F9244
public int get_Score(); // RVA: 0x3F92C4 Offset: 0x3F92C4
public void set_Score(int value); // RVA: 0x3F92CC Offset: 0x3F92CC
public long get_NeedGold(); // RVA: 0x3F92D4 Offset: 0x3F92D4
public void set_NeedGold(long value); // RVA: 0x3F92DC Offset: 0x3F92DC
[DebuggerNonUserCodeAttribute] // RVA: 0x16BE5C Offset: 0x16BE5C
public override bool Equals(object other); // RVA: 0x3F92EC Offset: 0x3F92EC
[DebuggerNonUserCodeAttribute] // RVA: 0x16BE6C Offset: 0x16BE6C
public bool Equals(HOUSE other); // RVA: 0x3F9370 Offset: 0x3F9370
[DebuggerNonUserCodeAttribute] // RVA: 0x16BE7C Offset: 0x16BE7C
public override int GetHashCode(); // RVA: 0x3F96B4 Offset: 0x3F96B4
[DebuggerNonUserCodeAttribute] // RVA: 0x16BE8C Offset: 0x16BE8C
public override string ToString(); // RVA: 0x3F9B1C Offset: 0x3F9B1C
[DebuggerNonUserCodeAttribute] // RVA: 0x16BE9C Offset: 0x16BE9C
public void WriteTo(CodedOutputStream output); // RVA: 0x3F9BA4 Offset: 0x3F9BA4
[DebuggerNonUserCodeAttribute] // RVA: 0x16BEAC Offset: 0x16BEAC
public int CalculateSize(); // RVA: 0x3FA598 Offset: 0x3FA598
[DebuggerNonUserCodeAttribute] // RVA: 0x16BEBC Offset: 0x16BEBC
public void MergeFrom(HOUSE other); // RVA: 0x3FAC10 Offset: 0x3FAC10
[DebuggerNonUserCodeAttribute] // RVA: 0x16BECC Offset: 0x16BECC
public void MergeFrom(CodedInputStream input); // RVA: 0x3FADEC Offset: 0x3FADEC
private static void .cctor(); // RVA: 0x3FB3CC Offset: 0x3FB3CC
[CompilerGeneratedAttribute] // RVA: 0x16BEDC Offset: 0x16BEDC
private static HOUSE <_parser>m__0(); // RVA: 0x3FB4A8 Offset: 0x3FB4A8
}
关键函数我已经标红,然后标红的都是我自己修改的函数。

接下来就是修改了,修改的教程论坛也可以找到很多

比如:https://www.52pojie.cn/forum.php?mod=viewthread&tid=818304

我就不多做介绍了。

上面 4个升级函数差不多,在IDA中,我修改如下位置:
这4个升级函数代码段还是比较长,他要计算等级 周期 然后计算最后的花费。
但我们只需要关注最后计算的结果:

关键就在这里了:
.text:001DAF6C 3F 40 FD EB BL j___floatdisf
.text:001DAF70 10 0A 00 EE VMOV S0, R0
.text:001DAF74 10 4A 01 EE VMOV S2, R4
.text:001DAF78 01 0A 20 EE VMUL.F32 S0, S0, S2
.text:001DAF7C 10 0A 10 EE VMOV R0, S0
.text:001DAF80 9B 3F FD EB BL j___fixsfdi
.text:001DAF84 F0 8D BD E8 LDMFD SP!, {R4-R8,R10,R11,PC}

其实,我也不怎么看的懂ARM汇编,猜就是把一个浮点付给R0,然后,我只需要把一个固定值付给R0就好了。
所以我改成了MOV R0,0x0 ,对应的机器码:00 00 A0 E3 ,然后用UE等BINHEX编辑工具,找到OFFSET去修改就好了。然后我把4个升级函数都改成0。

接下来就是Diamond观看函数,差不多的原理,我就不截图了,直接贴代码段
这个是watch diamon 我修改后的代码段: 我把修改了的地方也标红了
.text:001DBA84 3C 00 90 E5 LDR R0, [R0,#0x3C]
.text:001DBA88 00 01 80 E0 ADD R0, R0, R0,LSL#2
.text:001DBA8C 05 00 80 E2 ADD R0, R0, #5
.text:001DBA90 FF 00 50 E3 CMP R0, #0xFF
.text:001DBA94 FF 0F 0F E3 MOVLT R0, #0xFFFF
.text:001DBA98 40 1E A0 E1 MOV R1, R0,ASR#28
.text:001DBA9C 1E FF 2F E1 BX LR

这里我说一下,我第一次修改后,发现不对,因为对ARM汇编不熟悉,所以忽略了ASR这个指令。。。。然后修改了操作位数后,结果正确了。但是这个并不完美,因为还是要观看一次广告。。。

剩下就是收益了,具体方法和上面差不多,我就不继续废话了。

最后,给一个几个结果截图吧:

然后分享一下修改后的APK

修改后的APK包:

下载:https://www.lanzous.com/i69kc9g 密码:heb3

 

Categories:   Garfield's Diary

Comments