With the development of mobile games, the reverse escape technology of mobile games is becoming more and more mature, especially in the aspect of Android. There are many kinds of breakthrough articles. Comparatively speaking, there are few reverse analysis articles about mobile games in iOS. Lu Xinyao, a mobile security expert of Netease shield, will analyze the general game and protection ideas by analyzing a unity game and a cocos Lua game .
Identify Unity games
The ipa package of iOS platform can be decompressed by compression software. Generally speaking, the game of Unity has the following file directory characteristics:
Escape thinking
Unity game will produce resource file global under \ Data\Managed\Metadata- metadata.dat . The strings used in the game are saved in a global-metadata.dat These strings are only read into memory when running dynamically. This makes static analysis of games more difficult with IDA. So in order to solve this problem, there are artificial wheels, i.e. Il2CppDumper. This can read global-metadata.dat The information in the file, combined with the executable.
github:
https://github.com/Perfare/Il2CppDumper Open il2cppdumper, and a window will pop up. The first one is to select macho to execute the program, and the second one is to select global-metadata.dat And then select the corresponding mode, generally select auto, and the following will be generated dump.cs It is the interface of c used in the game.
With the interface, we can search for the keywords of general game modification, such as battle, player, maxhp, fight, etc., and then we can locate that if the class shown in FightRoleData is the role data source when we fight, there is also a class called battle manager, which is a battle manager, including start fighting, pause fighting, and end fighting.
public class FightRoleData : ICloneable // TypeDefIndex: 2414 { // Fields public long Sid; // 0x10 public long OwnerId; // 0x18 public long Uid; // 0x20 public int Power; // 0x28 public int Level; // 0x2C public int Sex; // 0x30 public int FlagType; // 0x34 public int RoleUnit; // 0x38 public int Sit; // 0x3C public int AttackType; // 0x40 public int Race; // 0x44 public int Professional; // 0x48 public int Star; // 0x4C public int Quality; // 0x50 public int Impression; // 0x54 public int Awaken; // 0x58 public int IsNpc; // 0x5C public int Soul; // 0x60 public int Formation; // 0x64 public int SkinID; // 0x68 public int AwakenLv; // 0x6C public int[][] Skills; // 0x70 public int[] Runes; // 0x78 public double Hp; // 0x80 public double MaxHp; // 0x88 public double Rage; // 0x90 public double MaxRage; // 0x98 public double Aggro; // 0xA0 public double MoveSpeed; // 0xA8 public double Attack; // 0xB0 public double PhysisDefense; // 0xB8 public double MagicDefense; // 0xC0 ... ... ... // Properties public ERolePosType PostitionType { get; } public ERoleGender Gender { get; } public bool IsAwaken { get; } // Methods public virtual void Init(ErlArray erlData); // RVA: 0x100EDDB68 Offset: 0xEDDB68 private static double _getProperty(ErlArray attrData, int index, bool[] checker, ERoleProperty property); // RVA: 0x100EDE8A0 Offset: 0xEDE8A0 public ERolePosType get_PostitionType(); // RVA: 0x100EDE93C Offset: 0xEDE93C public ERoleGender get_Gender(); // RVA: 0x100EDE964 Offset: 0xEDE964 public bool get_IsAwaken(); // RVA: 0x100EDE97C Offset: 0xEDE97C public object Clone(); // RVA: 0x100EDE98C Offset: 0xEDE98C public void .ctor(); // RVA: 0x100EDE994 Offset: 0xEDE994 } // Namespace: public class BattleManager : MonoBehaviour // TypeDefIndex: 3127 { // Fields ... ... ... // Properties public Camera GameCamera { get; set; } public GameObject CameraBase { get; } public bool Loading { get; set; } public BattleView battleView { get; set; } public string BattleMusic { get; } public Dictionary`2<string, RoleModelConfig> RoleModelConfigDic { get; } public int TargetFrame { get; } public static BattleManager Instance { get; } public DragonBallBattle Battle { get; } public bool Pause { get; set; } public List`1<BattleRoleController> BattleRoleControllers { get; } public bool IsSkipSuperSkill { get; } private bool _startAnimPlaying { get; } // Methods ... ... ... public void StartBattle(); // RVA: 0x101BBB1EC Offset: 0x1BBB1EC public void SkipBattle(); // RVA: 0x101BE18B0 Offset: 0x1BE18B0 ... ... ... }
At this point, we can easily implement two functions to skip the battle and modify the attack power of our character. The first function can be achieved through hook The startbattle() method then gets this pointer, which is the BattleManager object. Then we call SkipBattle() method according to the BattleManager object. In the second way, we can modify the data of FightRoleData to implement. First, let's see where FightRoleData is used. We can find such a class by searching:
// Namespace: BattleSystem public static class BattleAPI // TypeDefIndex: 2490 { // Methods private static T _GetConfig(long id); // RVA: 0x1000E98B4 Offset: 0xE98B4 public static DragonBallBattle Create(BattleScene scene, string hexData); // RVA: 0x100B06CFC Offset: 0xB06CFC public static DragonBallBattle Create(BattleScene scene, byte[] dataBytes); // RVA: 0x100B0950C Offset: 0xB0950C public static DragonBallBattle Create(BattleScene scene, BattleData data, optional CallBack`1<DragonBallBattle> beforeInit); // RVA: 0x100B06E04 Offset: 0xB06E04 public static BattleRole CreateBattleRole(BattleRoleConfig roleConfig, FightRoleData roleData, BattleScene scene, DragonBallBattle battle, Dictionary`2<long, List`1<int[]>> seqCache, optional double initialCD, optional double autoCD); // RVA: 0x100B0B3A0 Offset: 0xB0B3A0 private static int[] _getUniqueAttackSequence(int[] seq, long sid, Dictionary`2<long, List`1<int[]>> cache, YKRandom random); // RVA: 0x100B0CC28 Offset: 0xB0CC28 private static BattleRole _createBattleRolePartner(BattlePartnerConfig partnerConfig, BattleScene scene, int[] level, DragonBallBattle battle); // RVA: 0x100B0A4B4 Offset: 0xB0A4B4 public static void ApplyProperty(BattleRoleData roleData, FightRoleData netData); // RVA: 0x100B0CDB0 Offset: 0xB0CDB0 private static BattleRole[] _getFormatBattleRoles(BattleScene scene, List`1<FightRoleData> data, BattleFormation formatiom, int battleIndex, DragonBallBattle battle, Dictionary`2<long, List`1<int[]>> seqCache, double[] initialCDModifier, double[] autoCD); // RVA: 0x100B09C1C Offset: 0xB09C1C public static int ServerIndexToConfigIndex(int index, ERolePosType posType); // RVA: 0x100B0E2A8 Offset: 0xB0E2A8 public static void ImportConfig(IConfigImporter importer); // RVA: 0x100B0E390 Offset: 0xB0E390 }
The function createbatterole uses the data of firmroledata. Then we can modify the attack power by hook createbatterole, and modify the value of roledata offset corresponding to the third parameter (the first parameter is this pointer), such as the value of attack at 0xB0 offset position.
- protect
Although Unity game turns IL into the form of CPP in iOS, it increases the reverse difficulty to a certain extent, because it is not easy to analyze the function from the code level when it turns to the assembly form. But because of the redundancy of il2cpp itself, too much string and symbol information is reserved. It is easy for analysts to find a breakthrough through this information, so here are some suggestions:
-
Encrypt global-metadata.dat
Mix up the function symbols at the c ා level (it is suggested to mix up several core classes due to the error prone confusion of function symbols)
String encryption, code obfuscation
The server should not trust the client and increase the verification of the data. For example, when I modify the attack power above, the server needs to sign the distributed roledata when it issues roledata. If my client modifies the data, the data signature is abnormal when the server verifies, so it is not trusted.
Let's talk about Unity game. Now let's talk about a cocos Lua game. -
Identify Lua games
Generally speaking, if it's a Lua script game from these two aspects, first extract ipa, and then enter the resource directory, which is src or res in general. It has the suffix of lua and luac. To be sure, we can drag binary into ida to see:
Search the lua luajit keyword to get the information as shown in the figure.
lua script game. We drag lua script into the game. Generally speaking, it must be encrypted, or compiled into luac/luajit form. Otherwise, it is too easy to be broken.
According to the above results, it is not clear that the plaintext storage has been encrypted, and it is likely that the first few bytes are encrypted in the way of X xtea (this way is officially provided by cocos and has obvious characteristics. After encryption, sign is added to the file header as a mark. The encrypted key is written directly in the code.)
Escape thinking
Generally speaking, there are two ways of thinking in Lua game:
Get lua script, replace lua script
Because of the dynamic characteristics of lua script, we only need to load our Lua script through Lua engine to hijack data
We use the dump method to get the script, and we can hook luaL_loadbuffer to get the decrypted script, but iOS is different from Android. Because Android lua is loaded through so, there must be an export function luaL_loadbuffer. But iOS lua has been integrated into the binary system, so the symbols are naturally dropped by the string. At this time, we can use the string to coordinate with the lua source code to locate. For example, the string I choose here is "error loading module '% s' from file", and then it is easy to find this function by tracing upward.
Compare f5 content with luaL_loadbuffer prototype
int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char *name);
Now we start to write the code to dump the script. Here I use frida to implement it. The reason is that frida is so easy to use for these one-time needs. It doesn't need to compile, restart the device, and use it out of the box.
script = session.create_script(""" var baseAddr = Module.findBaseAddress('QuickMud-mobile'); var luaL_loadbuffer = baseAddr.add(0x2DF644); Interceptor.attach(luaL_loadbuffer, { onEnter: function(args) { var name = Memory.readUtf8String(args[3]); var obj = {} obj.size = args[2].toInt32() obj.name = name; obj.content = Memory.readCString(args[1], obj.size); send(obj); } } ); """) def write(path, content): print('write:', path) folder = os.path.dirname(path) if not os.path.exists(folder): os.makedirs(folder) open(path, 'w').write(content) def on_message(message, data): if message['payload']['name']: name = message['payload']['name'] name = "/Add/Your/Dump/Path/"+ name content = message['payload']['content'].encode('utf-8') dirName = os.path.dirname(name) if not os.path.exists(dirName): os.makedirs(os.path.dirname(name)) if name.endswith('.lua'): write(name, content) script.on('message', on_message) script.load() sys.stdin.read()
With the decrypted script, we can cheat by modifying the script, because with the source code, we can even write an offline hook, which is very harmful to the game.
protect
It can be seen that the Lua script is very harmful if only encryption is used, so the Lua game needs to guarantee the security of lua script from the following points:
Compile Lua into luac or luajit, then modify opcode of lua engine, and then modify bytecode of luajit to increase the reverse difficulty
Although iOS strip has symbols, it is easy to locate lual because lua is open source_ Load buffer, so it is necessary to add string encryption and code logic confusion to protect the security of the game.
Note: the above games are for research purposes only. If there is any infringement, please contact to delete.
Appendix 1 recruitment of "pig factory"
iOS security development engineer of Netease Yidun
Job description
1. Research on the security technology of Netease mobile terminal (iOS)
2. Responsible for the research and development of the security protection scheme of Netease mobile terminal (iOS)
Job requirements
1. Bachelor degree or above, rich experience in iOS platform development
2. Solid Objective-C programming foundation, familiar with C/C + + Development
3. Familiar with assembly, master the common attack and defense technology of iOS
4. Familiar with IDA Pro, LLDB, CYCRIPT and other debugging analysis tools, with strong reverse analysis ability;
5. Rich experience in iOS jailbreak development
6. Experience in APP / game encryption protection preferred
7. Good learning and communication skills, strong analysis and problem solving skills