真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網站制作重慶分公司

如何淺析iOS手游逆向和保護

本篇文章為大家展示了如何淺析iOS手游逆向和保護,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

創(chuàng)新互聯(lián)建站長期為超過千家客戶提供的網站建設服務,團隊從業(yè)經驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網生態(tài)環(huán)境。為定襄企業(yè)提供專業(yè)的成都網站建設、網站設計,定襄網站改版等技術服務。擁有10年豐富建站經驗和眾多成功案例,為您定制開發(fā)。

背景介紹

隨著手游的發(fā)展,隨之而來的手游逆向破解技術也越來越成熟,尤其是Andorid方面,各種破解文章比比皆是,相對而言,iOS方面關于手游的逆向分析文章比較少,網易易盾移動安全專家呂鑫垚將通過分析一款unity游戲和一款cocos-lua游戲來剖析一般向的游戲破解及保護思路。

識別Unity游戲

iOS平臺的ipa包可以通過壓縮軟件解壓,一般來說Unity的游戲有如下文件目錄特征:

如何淺析iOS手游逆向和保護

破解思路

Unity游戲會在   \Data\Managed\Metadata下生產資源文件global-metadata.dat。游戲中使用的字符串都被保存在了一個global-metadata.dat的資源文件里,只有在動態(tài)運行時才會將這些字符串讀入內存。這使得用IDA對游戲進行靜態(tài)分析變得更加困難。那么為了解決這個困難,有人造了輪子,即Il2CppDumper。此可讀取global-metadata.dat文件中的信息,并與可執(zhí)行文件結合起來。

github:https://github.com/Perfare/Il2CppDumper   打開Il2CppDumper,會彈出一個窗口,第一個選擇macho執(zhí)行程序,第二個選擇global-metadata.dat,然后選擇對應的模式一般選auto,然后會生成如下的dump.cs  里面就是這個游戲用到的c#的接口。

如何淺析iOS手游逆向和保護有了接口以后,我們就可以搜索一般游戲修改的關鍵字battle,player,maxhp,fight等,然后我們定位到如果所示的類FightRoleData是我們戰(zhàn)斗的時候角色數(shù)據(jù)來源,還有一個叫battlemanager的類,這個類是一個戰(zhàn)斗管理者,包括開始戰(zhàn)斗,暫停戰(zhàn)斗,結束戰(zhàn)斗。

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 RoleModelConfigDic { get; }
    public int TargetFrame { get; }
    public static BattleManager Instance { get; }
    public DragonBallBattle Battle { get; }
    public bool Pause { get; set; }
    public List`1 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
    ...
    ...
    ...
}

至此,我們可以很容易實現(xiàn)兩個功能跳過戰(zhàn)斗,修改我們角色的攻擊力,第一個功能可以通過hook StartBattle()方法然后獲得this指針也就是BattleManager對象,然后我們根據(jù)BattleManager對象來調用SkipBattle()方法就可以了,第二個方式的話我們可以修改FightRoleData的數(shù)據(jù)來實現(xiàn),那我們我們首先來看下FightRoleData在哪些地方被用到了,通過搜索可以發(fā)現(xiàn)這么個類:

// 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 beforeInit); // RVA: 0x100B06E04 Offset: 0xB06E04
    public static BattleRole CreateBattleRole(BattleRoleConfig roleConfig, FightRoleData roleData, BattleScene scene, DragonBallBattle battle, Dictionary`2> seqCache, optional double initialCD, optional double autoCD); // RVA: 0x100B0B3A0 Offset: 0xB0B3A0
    private static int[] _getUniqueAttackSequence(int[] seq, long sid, Dictionary`2> 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 data, BattleFormation formatiom, int battleIndex, DragonBallBattle battle, Dictionary`2> 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
}

其中CreateBattleRole這個函數(shù)用到了FightRoleData的數(shù)據(jù),那么我們可以通過hook CreateBattleRole這個函數(shù),同時修改第三個參數(shù)(第一個參數(shù)是this指針)對應的roledata的偏移里面的數(shù)值比如0xB0偏移位置的attack的值達到修改攻擊力的目的。

防護

Unity游戲在iOS中雖然將il轉成了cpp的形式,這在一定程度上增大了逆向難度,因為轉成了匯編形式不容易從代碼層面去分析功能。但是因為il2cpp本身的冗余性,太多的字符串、符號信息被保留了。分析者很容易通過這些信息找到突破口,所以這里給出幾點意見:

  1. 加密global-metadata.dat

  2. 在c#層面進行函數(shù)符號混淆(由于函數(shù)符號混淆容易出錯所以建議對核心的幾個類進行混淆)

  3. 字符串加密,代碼混淆

  4. 服務端不要信任客戶端,增加對數(shù)據(jù)的校驗,比如我上面修改了攻擊力,服務器在下發(fā)roledata的時候就需要對下發(fā)的roledata進行簽名,如果我客戶端修改了數(shù)據(jù),服務器校驗的時候就數(shù)據(jù)簽名異常,不予以信任。

談了點Unity游戲,現(xiàn)在我們來談談一款cocos-lua游戲。

識別Lua游戲

一般來說通過這兩方面來看是不是lua腳本游戲,首先解壓ipa,然后進入資源目錄一般來說是src或者res,里面有類似lua,luac后綴,保險一點我們把二進制拖進ida看下:

如何淺析iOS手游逆向和保護搜索lua luajit關鍵字得到如圖信息。

如何淺析iOS手游逆向和保護判定是lua腳本游戲。我們把lua腳本拖進游戲看下一般來說肯定是加密了,或者編譯為luac/luajit形式,不然就太容易被破解了。

如何淺析iOS手游逆向和保護

根據(jù)以上結果來看,不是明文存儲做了加密,而且看頭幾個字節(jié)很有可能是采用了xxtea這種加密方式(這種方式是cocos官方提供的而且特征很明顯,加密后將sign追加在文件頭部作為標識。加密的key則是直接寫在代碼里面的)

破解思路

Lua游戲的話一般來說這么2種思路:

  1. 獲取lua腳本,替換lua腳本

  2. 因為lua腳本的動態(tài)特性,我們只需要通過lua引擎去加載我們的lua腳本就能達到劫持數(shù)據(jù)的作用

我們這邊通過dump的方式來獲取腳本,可以通過hook   luaL_loadbuffer來獲取解密后的腳本,但是iOS跟安卓還是有些不同,因為安卓lua是通過so來加載的,所以必定有導出函數(shù)luaL_loadbuffer。但是iOS   lua已經集成到二進制中了,所以符號自然就被strip掉了,這個時候我們可以通過字符串配合lua源碼來定位,比如我這邊選擇的字符串是”error  loading module '%s' from file",然后向上追溯就很容易找到這個函數(shù)。

如何淺析iOS手游逆向和保護對比下f5內容與luaL_loadbuffer原型

int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char *name);

現(xiàn)在我們就開始編寫代碼來dump腳本,這邊我用frida來實現(xiàn),原因是frida對于這些一次性的需求實在是太好用了,不需要編譯,不需要重啟設備,開箱即用。

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()

如何淺析iOS手游逆向和保護如何淺析iOS手游逆向和保護如何淺析iOS手游逆向和保護

有了解密后的腳本我們就可以通過修改腳本達到作弊的效果,因為有了源碼我們甚至可以寫一個脫機掛出來,這對游戲的危害極大。

防護

可以看到lua腳本如果只加密危害是很大的,所以lua游戲需要保障lua腳本的安全可以從以下幾點入手:

  1. 對lua編譯為luac 或者 luajit 然后在此基礎上對lua引擎修改opcode,然后修改luajit的bytecode增大逆向的難度

  2. iOS雖然strip了符號,但是由于lua是開源的很容易定位到luaL_loadbuff,所以有必要加上字符串加密和代碼邏輯混淆來保護游戲的安全。

上述內容就是如何淺析iOS手游逆向和保護,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


文章題目:如何淺析iOS手游逆向和保護
文章出自:http://weahome.cn/article/jhjdhj.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部