Come on! Learn the hand game protection of reversion

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

Tags: iOS Mobile Unity Android

Posted on Tue, 09 Jun 2020 04:04:28 -0400 by plutarck