unity遊戲生成與修改so文件教程

原文鏈接:https://www.52pojie.cn/thread-618515-1-1.html
本文主要介紹如何對unity3d引擎製作的遊戲進行修改。包含了apk文件安裝後在手機中的位置分析、修改遊戲時遇見內聯函數之坑時的解決辦法,以及so文件的原理介紹與解析修改。並將實例教學如何修改unity3d遊戲(想學崩壞3修改的同學請注意啦)。教程是給入門新手看的,請大神繞道勿噴。文章的核心內容在最後利用Il2CppDumper的部分,前面清楚的話可直接繞到最後看。(因爲手機截圖下來的圖片分辨率太大。看起來排版會不太舒服,可以直接到文章最後下載文檔查看,排版會舒服很多)
基礎知識
0x1.apk安裝後在手機中的目錄
apk安裝後會在兩個包下生成相關包:data/data/、data/app/。

這裏拿網易雲音樂的安裝目錄舉例。Data/App目錄下通常會有三個文件:
1.lib文件夾(包含so庫文件)、
2.oat文件夾(OAT文件是一種android私有ELF文件格式,它不僅包含有從DEX文件翻譯而來的本     地機器指令,還包含有原來的DEX文件內容。這使得我們無需重新編譯原有的APK就可以讓它正常地在ART 裏面運行
3.base.apk啓動包。【其中apk啓動包是不允許重命名或刪除的,因爲app運行時其實就是鏈接到這個啓動包,然後才能繼續啓動操作。這個啓動包用beyond對比後可以發現,與原安裝包沒有任何不同,所以就相當與apk的原版安裝包】。
                                       


Data/data目錄下一般是存儲lib文件夾(保護so庫文件)以及其他數據文件、緩存等。只需要知道這裏的lib實際上與data/app目錄下的lib目錄中內容是一樣的。  
遊戲在運行的時候,一般都會載入dada/data目錄中的lib與data/app中的lib,通常來說只需要修改data/data中的lib文件夾中的so文件即可達到成功修改的效果。當然也有一小部分遊戲根本不讀取data/data目錄下的lib文件夾,待會會講到。
        
0x2.Unity3D中的資源路徑

Application.dataPath

此屬性用於返回程序的數據文件所在文件夾的路徑。例如在Editor中就是Assets了。

Application.streamingAssetsPath

此屬性用於返回流數據的緩存目錄,返回路徑爲相對路徑,適合設置一些外部數據文件的路徑。

Application.persistentDataPath

此屬性用於返回一個持久化數據存儲目錄的路徑,可以在此路徑下存儲一些持久化的數據文件。

Application.temporaryCachePath

此屬性用於返回一個臨時數據的緩存目錄。
android平臺

Application.dataPath

/data/app/xxx.xxx.xxx.apk

Application.streamingAssetsPath

jar:file:///data/app/xxx.xxx.xxx.apk/!/assets

Application.persistentDataPath

/data/data/xxx.xxx.xxx/files

Application.temporaryCachePath

/data/data/xxx.xxx.xxx/cache
IOS平臺

Application.dataPath

Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data

Application.streamingAssetsPath

Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx.app/Data/Raw

Application.persistentDataPath

Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Documents

Application.temporaryCachePath

Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Library/Caches

0x3.C#的inline內聯函數優化
雖然C#不支持inline,但是JIT支持自動inline,即將IL轉成真正機器碼時,會自動將某些函數進行inline展開,只是條件非常苛刻,網上提到JIT自動進行inline展開的一些選擇依據:
1)函數內部有循環語句、catch語句等複雜結構,都不做inline優化。
2)函數體比較長的不做inline優化,只有比較簡單的纔可能inline優化。(有人說IL不足32字節才做inline),
2)編譯成機器碼時,inline展開的代碼比函數調用更短的,一定做inline。(注:如果參數多而代碼少,就符合此情況)
這裏爲什麼要講內聯函數呢,加入如果遊戲中有一個讀取人物攻擊力的函數,其內部代碼十分簡單,結果被編譯爲機器碼的時候變爲了內聯函數。那麼這個時候要來修改就十分麻煩了,因爲你找到那個讀取人物攻擊力的函數是沒有用的,修改了也是白修改,只能到每一處調用這個函數的地方逐行修改。
好了,說了這麼多,下面從開始unity3d遊戲開發的的角度逐漸逆向分析。

一、通過unity3d打包生成libil2cpp.so:
1.如何識別u3d遊戲?打開解壓包,如果lib文件夾下有libunity.so就證明這是一個unity3d遊戲。
2.要修改Unity3d遊戲,首先就要對其遊戲代碼存放位置有一個基本的瞭解。Unity3d生成遊戲的遊戲主邏輯一般放在三個地方:libil2cpp.so、Assembly-CSharp.dll、lua腳本。
【對於libil2cpp.so來說:我們知道,unity3d最大的一個特點是一次製作,多平臺部署,而這一核心功能是靠Mono實現的。但是在2014年年中的時候,Unity3D引出了IL2CPP的概念,IL2CPP,英文意思即Intermediate Language to cpp,就是把IL中間語言轉換成CPP文件。】
上面所說的這三個地方通常來說是唯一的,即只會出現一種情況。這是由unity3d引擎的生成方式決定的。下面通過開發者的角度對unity3d生成遊戲進行實例講解:

新建unity3d工程,工程命名爲HelloCPP!:
 
 
利用ugui創建兩個text,一個爲“CoinUI”顯示“金幣”,一個爲“Coin”顯示金幣值,並創建腳本GameManager,綁定在MainCamera中。

腳本GameManager代碼如下:
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine.UI;
  4. using UnityEngine;
  5.  
  6.  
  7. public class GameManager : MonoBehaviour {
  8.  
  9.  
  10.     private GameObject coin;
  11. void Start () {
  12.         coin = GameObject.Find("Coin");
  13.  
  14. }
  15.     private int GetCoin()
  16.     {
  17.         return 50;
  18.             
  19.     }
  20.     public void ChangeCoin()
  21.     {
  22.         float v = GetCoin();
  23.         coin.GetComponent<Text>().text = v.ToString();
  24.     }
  25. }
  26. 方法與按鈕事件綁定。那麼當點擊按鈕的時候就會更新一次</font>ui。</font></font></font>
複製代碼
代碼中的GetCoin方法放回一個50的數值,當遊戲運行起來的時候,腳本會將ui界面中Coin的值改爲50,如下:
 

好了,遊戲邏輯已經寫完了,保存場景,直接打包,點擊主菜單file>BuildSetting進入打包界面,選擇轉化爲android平臺,並點擊playersetting進入配置界面:
這裏我把PackageName設置爲com.hellocpp。
然後頁面下拉,找到scriptingbackend:
 
 


這裏的scriptingBackend就是設置生成遊戲的遊戲邏輯存放方式,如果選擇默認的Mono2x的話,會在反編譯後的apk的assets\bin\Data\Managed目錄下找到Assembly-CSharp.dll文件,也就是大多數unity遊戲邏輯存放的位置,這種情況下,lib文件夾下是沒有libil2cpp.so文件的。如果是選擇IL2CPP的話,會在lib文件夾下生成libil2cpp.so文件,並在assets\bin\Data\Managed\Metadata目錄下生成global-metadata.dat配置文件。
 

對於生成Assembly-CSharp.dll文件的情況來說,用reflector很容易修改,這裏略過,直接講解生成libil2cpp.so文件的情況。把生成的apk直接拖入ide中反編譯,進入根目錄後,進入lib文件夾中觀察。


二、對生成的apk進行反編譯分析

直接把apk拖入ide,然後進入lib文件夾查看
 

 
 
生成了兩個文件夾,一個是armeabi-v7a,即arm架構,一個爲x86,是因特爾架構。我們這裏進入arm文件夾中分析。
【有時會有人問,爲什麼so修改後模擬器運行閃退,無法正常運行?這種情況多半是因爲你只修改了arm文件夾下的so,所以只能在大部分真機中運行,因爲真機多半是arm架構的,而模擬器是因特爾架構的,所以在模擬器上運行會奔潰。】
 
可以看到,裏面一共三個文件,其中libunity與libmain是unity的內部文件,我們不需要去管它,現在只需要知道這裏確實生成了libil2cpp.so即可。

好了,現在我們要分析修改這個apk,手機中運行起來我們發現其顯示金幣爲50,我們現在來修改其數值。



按照國際慣例,先在ide中搜索字符串“金幣”,發現沒有結果,於是判斷遊戲邏輯在so中,我們再搜索loadlibrary,然後發現了裏面唯一用到的原生方法是在libmain中,然後估計就有人去分析libmain.so文件了,但libmain.so裏面其實是沒有遊戲核心邏輯的,這只是unity內部的一些庫,真正的遊戲核心邏輯是在libil2cpp.so中,這個庫文件實在載入libmain後才被調用的。
所以,碰到unity遊戲,一定要先看看lib文件夾下是否有libil2cpp.so,如果有的話,直接分析這個so就行了,從smali分析存粹是浪費體力。
打開IDA,載入so,搜索coin,會發現依然找不到相關函數,推測在jni中動態加載,然而搜索jni也是找不到任何函數。在view-A面板中尋找,發現大多數函數只有一個函數尾,而函數頭似乎被可以“掐”掉了。
 



三、對Il2CppDumper.exe工具的介紹
出現上述情況的原因與unity引擎中的MetadataCache.cpp相關,打開u3d目錄,可找到MetadataCache.cpp:
 

意思就是在生成libil2cpp.so時,u3d同時會在目錄assets\bin\Data\Managed\Metadata下生產資源文件global-metadata.dat。遊戲中使用的字符串都被保存在了一個叫global-metadata.dat的資源文件裏,只有在動態運行時纔會將這些字符串讀入內存。這使得用IDA對遊戲進行靜態分析變得更加困難。那麼爲了解決這個困難,有人造了輪子,即Il2CppDumper.exe。此可讀取global-metadata.dat文件中的信息,並與libil2cpp.so結合起來。
 


相關源碼可看國外大神的分析:還原使用IL2CPP編譯的unity遊戲的symbol(一)
【https://www.nevermoe.com/?p=572】以及(github:https://github.com/nevermoe/unity_metadata_loader)與github:https://github.com/Perfare/Il2CppDumper)

好了,如果你覺得這個看起來過於麻煩的話,可以直接略過,只要學會使用其工具化下來的exe就行了。

這裏爲了方便下載直接使用,我已經把exe文件生成出來了,會直接打包到百度雲。
這個exe文件主要是通過對global-metadata.dat與so文件的結合自動生成相關函數與其對應在ida中的偏移地址。(相關原理其實就是分析global-metadata.dat,這裏是自動幫我們省去了這個步驟)。

使用方法:
打開Il2CppDumper.exe,會彈出一個窗口,第一個選擇lib2cpp.so,第二個選擇global-metadata.dat,然後按下鍵盤鍵2,就會自動完成後續的操作了。
 


 

生成的文件就是這個dump.cs,我們點進去後直接搜索coin,定位到這裏:
 

下面的數字就是偏移量,複製511f50後進入ida,按g鍵進入到相關地址
發現代碼沒有展開的話,按一下c鍵就可以了。
 
可以看到,他這裏是返回了50。那麼,這個時候我們就興奮了,這裏就是我們要修改的地方!講道理把這裏的0x32修改爲0xFF00後,我們在遊戲中點擊按鈕,顯現的值就應該變爲65280了:
 
用hex二進制修改器修改後,命名爲libil2cpp改.so。
接下來可以直接把so替換掉原so然後打包回編譯,但這種辦法遇到apk有簽名驗證或其他亂七八糟的檢驗時不好操作。這裏我們使用另一種部分,即先安裝apk到手機,然後進去根目錄下去手動把so給替換掉(手機需root)。

把apk與修改後的so一起扔進手機:

 


 
安裝apk後,先打開來看看,點擊按鈕後,金幣爲50
 

好了,接下來就是替換so了。在前面的基礎知識中我們講到,apk安裝後,會在data/data與data/app下分別生成自己的包文件。並且兩個文件夾下都有lib,裏面封裝了一樣的so庫文件。那麼我們是去替換哪一個呢?答案:兩個都試試。
因爲有些app只讀取data/app/com.hellocpp目錄下的lib文件夾信息,不讀取data下的文件夾信息,比如這個apk。你會發現你直接把data/data下的com.hellocpp包給刪掉也是完全可以運行的,但是如果你刪了app目錄下的com.hellocpp/lib,立刻無法運行。
我們把原so重命名爲libil2cpp.so原,然後把改後的so命名爲libil2cpp.so
 
大功告成,我們重新打開遊戲,然後會發現。
沒有任何變化(心涼)
 

正常情況這樣修改後就應該會成功了的,但是這裏爲什麼依然沒有任何變化呢。
這裏又涉及到前面說的基礎知識,當這種情況發生的時候,很可能就是函數內聯了。
你修改函數本體是沒有任何效果的,因爲這個函數被調用它的函數內置了。你必須找到所有調用這個函數的地方,去找到相關點修改。這個就需要去看彙編代碼了。
我們也可以動態調試的時候在getcoin()方法處下一個斷點,然後ida動態調試,會發現按鈕按下時確實沒有斷下來(限於篇幅請讀者自行嘗試)。或者我們直接把那個函數本體給nop掉,會發現程序依舊正常運行,這都說明了函數確實內聯了。

內聯了的函數很難分析,我遇到了就只能跑路,這裏只是點出其位置,再深入的分析就要去好好讀代碼了,不多分析(如果有大神會的話麻煩評論區指點指點)

這裏我直接找到這個地方,改爲mov r0,0
 
再次替換後運行結果確實變爲0了:
 
 

實例二
好了,分析完上面這個核心處存在內聯函數的apk,我們下面來一個最常見的apk修改實例。
仍然是上面這個apk的功能,但不同的是爲了防止其編譯的時候又被當成內聯函數編譯了,我在方法GetCoin()內增加了一個循環和幾個debug,確保其不被當作內聯。其他功能不變。依舊是在GetCoin()中返回50,然後在ChangeCoin()修改ui界面的數值。
代碼如下:

 
然後同樣步驟打包生成apk,但把包名改爲了com.HellobanInline
生成apk後直接扔進ide中反編譯,然後把global-metadata.dat與libil2cpp.so拿出來,用Il2CppDumper.exe把函數名生成出來:
 
打開dump.cs,搜索GetCoin()
 

函數位置在偏移511d48上。
 
因爲方法是返回一個int值的數值,我們直接讓其返回0xff00,也就是65280.
 

用hex二進制文件修改後把文件命名【libil2cpp改.so】。與apk一起扔到手機中。
Apk安裝完成後,進入data/app中的包com.HellobanInline-1的lib/arm中,把【libil2cpp改.so】複製進來,重命名如下:
【這裏對爲什麼包名後面有一個-1做一下解釋:這是因爲複製版本覆蓋。一般來說第一次安裝的話包名後綴-1,第二次覆蓋安裝就會多一個相同包名後綴爲-2,再次覆蓋安裝又會變爲-1……】
                    

好了,大功告成,這個時候充滿期待的打開apk吧。點擊按鈕後數值已經由50變爲了65280!
效果圖:






  •  
  •  


     
  • 市面上絕大部分遊戲都是直接生成c#方法名後到ida中直接修改就生效了,像實例一的比較少見,但有助與深入理解。
    一般生成方法名後,就看修改經驗或遊戲開發經驗了,比較火的u3d遊戲有很多,比如崩壞3的修改,你可以搜索方法名“GetBaseAttack”,修改爲一個超大值,那麼你人物就一擊十幾億,防禦十幾億,生命十幾億了。對於崩二的話,其加了愛加密的殼,並且似乎有檢驗so是否被篡改,若有高人能跳過檢測希望能告訴我一下,十分感謝!然後再其他的遊戲修改也都是這套路,多多熟悉就會了。

    大概就這些內容吧,自己羅裏吧嗦的,感覺篇幅有點累贅了。

    [size=10.5000pt] 




    百度雲鏈接:鏈接:https://pan.baidu.com/s/1htaB64k 密碼:xrf3    解壓密碼www.52pojie.cn
    world文檔:  http://pan.baidu.com/s/1i5EC5pj 密碼:ja84

圖片1.png (78.43 KB, 下載次數: 10)

圖片1.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章