最近朋友圈里出現(xiàn)了一款日本的游戲,十分火爆,于是忍不住想去破解看看。分析后發(fā)現(xiàn)這個(gè)游戲的破解并不難,但是可以多種思路進(jìn)行,是個(gè)很好的學(xué)習(xí)樣本,于是決定寫一篇文章分享給初學(xué)者們。
本文分三個(gè)方向進(jìn)行破解分析,分別為內(nèi)存修改,存檔修改,apk修改。文章涉及的修改較為簡單,主要目的是給大家提供多元的分析思路,接下來我們一個(gè)一個(gè)來進(jìn)行具體分析。
所使用樣本為 旅行青蛙 1.0.4版本(目前最新版本)。 鏈接: https://pan.baidu.com/s/1dSqHK6 密碼: qmvg
目錄
0x1.內(nèi)存修改→ GG修改器修改數(shù)值,需root
0x2.存檔修改→ 存檔十六進(jìn)制修改,無需root;原創(chuàng)apk用于修改存檔,無需root
0x3.apk修改 → Unity3D游戲腳本修改,無需root
0x4.總結(jié) → 文章整體思路和方向概況
正文
0x1.內(nèi)存修改
思路:這個(gè)方式是用在已經(jīng)root的手機(jī)上,也就是我們接觸比較多的修改器通過搜索來確認(rèn)關(guān)鍵數(shù)值的內(nèi)存地址,然后將其修改,達(dá)到破解目的。
工具:GG修改器 / 需要ROOT權(quán)限
因?yàn)楸容^簡單,這部分盡量簡要講。
打開GG修改器和游戲,進(jìn)游戲后查看當(dāng)前三葉草數(shù)量,GG修改器附加游戲進(jìn)程,并搜索該數(shù)量。
附加后我們進(jìn)行搜索,搜索37這個(gè)數(shù)值。
搜索結(jié)果比較多,我們需要篩選,回到游戲使用三葉草買東西,數(shù)值變化為27,然后我們搜索27來確認(rèn)三葉草數(shù)量的內(nèi)存地址。
修改最終搜索到的值為27000,回到游戲就可以看到三葉草數(shù)量已經(jīng)變化。
其他物品及抽獎(jiǎng)券等數(shù)量均可用該方式修改,請(qǐng)大家自己嘗試。
這種方式非常方便,但是有個(gè)弊端就是需要我們有ROOT權(quán)限,對(duì)于目前大部分安卓手機(jī)來講,ROOT權(quán)限的獲取越來越難,接下來我們來分析不需要ROOT權(quán)限的兩種修改方法。
0x2.存檔修改
思路:通過存檔文件分析和修改完成關(guān)鍵數(shù)值修改
工具:十六進(jìn)制編輯器
單機(jī)游戲都會(huì)有存檔,旅行青蛙當(dāng)然也不例外,我們按照常規(guī)路徑去找一下,發(fā)現(xiàn)游戲的存檔都在Tabikaeru.sav文件中,路徑請(qǐng)看圖:
我們使用十六進(jìn)制編輯器將其打開,編輯器可以用PC端的也可以用手機(jī)端的,自行選擇。
打開后我們根據(jù)目前的三葉草數(shù)量27000進(jìn)行搜索,27000的十六進(jìn)制為0x6978,所以我們?cè)谑M(jìn)制文件中可以進(jìn)行hex搜索,搜索 69 78 或 78 69。
(通常在十六進(jìn)制中的數(shù)值都是倒序記錄,比如0x6978會(huì)保存為 78 69,在旅行青蛙1.0.1版本的存檔中就是這么保存的,不過在1.0.4版本的存檔中,已經(jīng)變?yōu)榱苏?,?9 78)
經(jīng)過搜索我們找到了三葉草的數(shù)量,接下來我們將其修改驗(yàn)證一下,將69 78 修改為 FF FF,保存后放回手機(jī)中存檔的文件夾中,重新啟動(dòng),發(fā)現(xiàn)三葉草數(shù)量已經(jīng)變更:
其他數(shù)值修改,比如抽獎(jiǎng)券或者其他物品數(shù)量等,均可依照此方法進(jìn)行,此處不再贅述,請(qǐng)大家自己嘗試。另外還可以在每次數(shù)值有較明顯變化后保存存檔文件,進(jìn)行對(duì)比分析,來找到更多物品的數(shù)值。
為了更簡便的進(jìn)行修改,我們做一個(gè)專用修改器apk用來在未root手機(jī)上專門完成此修改過程,源碼如下(完整project參考附件):
package com.example.frog; import android.content.Context; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class MainActivity extends AppCompatActivity { private EditText editText; private EditText editText2; private Button button; private InputMethodManager inputMethodManager; private static final String FILE_PATH = Environment.getExternalStorageDirectory() + File.separator + "Android/data/jp.co.hit_point.tabikaeru/files/Tabikaeru.sav"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText = (EditText) findViewById(R.id.editText); editText2 = (EditText) findViewById(R.id.editText2); button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (editText.getText().toString().equals("") || editText2.getText().toString().equals("")) { return; } String cloverHex = String.format("%06X", Integer.valueOf(editText.getText().toString())); String couponHex = String.format("%06X", Integer.valueOf(editText2.getText().toString())); Log.d("123", " " + cloverHex); Log.d("123", " " + couponHex); writeToFile(cloverHex, couponHex); } }); } public void writeToFile(String cloverHex, String couponHex) { FileInputStream fileInputStream = null; FileOutputStream fileOutputStream = null; File file = new File(FILE_PATH); File newFile = new File(FILE_PATH); byte[] cloverByteArray = hexStringToByte(cloverHex); byte[] couponByteArray = hexStringToByte(couponHex); if (!file.exists()) { Log.d("123", "未找到文件Tabikaeru.sav"); return; } try { fileInputStream = new FileInputStream(file); byte[] arrayOfByte = new byte[fileInputStream.available()]; Log.d("123", "文件大小" + arrayOfByte.length); fileInputStream.read(arrayOfByte); if (arrayOfByte.length > 29) { file.delete(); Log.d("123", "刪除舊文件"); createFile(newFile); //三葉草 arrayOfByte[23] = cloverByteArray[0];//Byte.valueOf(cloverHex.substring(0, 2)); arrayOfByte[24] = cloverByteArray[1];//Byte.valueOf(cloverHex.substring(2, 4)); arrayOfByte[25] = cloverByteArray[2];//Byte.valueOf(cloverHex.substring(4, 6)); //抽獎(jiǎng)券 arrayOfByte[27] = couponByteArray[0];//Byte.valueOf(couponHex.substring(0, 2)); arrayOfByte[28] = couponByteArray[1];//Byte.valueOf(couponHex.substring(2, 4)); arrayOfByte[29] = couponByteArray[2];//Byte.valueOf(couponHex.substring(4, 6)); Log.d("123", " " + arrayOfByte.length); for (int i = 0; i <arrayOfByte.length; i++) { Log.d("123", " " + arrayOfByte[i]); } fileOutputStream = new FileOutputStream(newFile); fileOutputStream.write(arrayOfByte); } } catch (Exception e) { e.printStackTrace(); } finally { Toast.makeText(this, getString(R.string.saved), Toast.LENGTH_SHORT).show(); hideSoftInput(); try { if (fileInputStream != null) { fileInputStream.close(); } if (fileOutputStream != null) { fileOutputStream.close(); } } catch (Exception e) { e.printStackTrace(); } } } public void createFile(File file){ try{ file.getParentFile().mkdirs(); file.createNewFile(); }catch (IOException e){ e.printStackTrace(); } } public void hideSoftInput(){ if(inputMethodManager == null) { inputMethodManager = (InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE); } inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), 0); editText.clearFocus(); inputMethodManager.hideSoftInputFromWindow(editText2.getWindowToken(), 0); editText2.clearFocus(); } /** * 把16進(jìn)制字符串轉(zhuǎn)換成字節(jié)數(shù)組 */ public static byte[] hexStringToByte(String hex) { int len = (hex.length() / 2); byte[] result = new byte[len]; char[] achar = hex.toCharArray(); for (int i = 0; i < len; i++) { int pos = i * 2; result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1])); if (result[i] == 0) { result[i] = 00; } } return result; } private static int toByte(char c) { byte b = (byte) "0123456789ABCDEF".indexOf(c); return b; } }
上述代碼實(shí)現(xiàn)了存檔的直接修改,界面如下,不需要ROOT權(quán)限:
輸入數(shù)值后,點(diǎn)擊修改即可完成三葉草及抽獎(jiǎng)券的修改,更多物品修改請(qǐng)自行嘗試。
0x3.apk修改
思路:分析apk包,找到腳本文件,反編譯后找到關(guān)鍵method進(jìn)行修改,然后重新打包
工具:Android Killer,DnSpy
Android Killer相關(guān)操作這里不再贅述,反編譯后我們發(fā)現(xiàn)這是一個(gè)mono框架的Unity3D游戲,Unity3D游戲的腳本文件都存放在Assembly-CSharp.dll或Assembly-CSharp-firstpass.dll文件中,很顯然,旅行青蛙的腳本文件位于Assembly-CSharp.dll,我們使用Dnspy進(jìn)行分析看看。
我們搜索三葉草的英文clover,發(fā)現(xiàn)getCloverPoint可能是我們需要找的關(guān)鍵method。
根據(jù)getCloverPoint的源碼,我們發(fā)現(xiàn)這個(gè)method的功能是在三葉草數(shù)量發(fā)生變化時(shí)在三葉草數(shù)量進(jìn)行增減運(yùn)算,那么我們可以對(duì)函數(shù)內(nèi)部增加數(shù)量的這句代碼進(jìn)行修改,修改為發(fā)生變化增加固定數(shù)量的三葉草,比如10000。
(抽獎(jiǎng)券相關(guān)修改也在SuperGameMaster中可以找到,method名為getTicket,此處不作演示,請(qǐng)大家自己嘗試修改)
修改后函數(shù)變更為:
保存后打包apk運(yùn)行,只要三葉草數(shù)量發(fā)生變化(如收割三葉草或者購買物品),三葉草的數(shù)量就會(huì)增加10000。
0x4.總結(jié)
本文通過多種思路對(duì)旅行青蛙的修改進(jìn)行了分析,內(nèi)容較為簡單,主要目的是分享游戲破解分析的思路,有興趣的可以嘗試更多物品數(shù)量的修改。