旅行青蛙破解分析從記憶體到存檔再到改包
最近朋友圈裡出現了一款日本的遊戲,十分火爆,於是忍不住想去破解看看。分析後發現這個遊戲的破解並不難,但是可以多種思路進行,是個很好的學習樣本,於是決定寫一篇文章分享給初學者們。
本文分三個方向進行破解分析,分別為 記憶體修改,存檔修改,apk修改。文章涉及的修改較為簡單,主要目的是給大家提供多元的分析思路,接下來我們一個一個來進行具體分析。
所使用樣本為 旅行青蛙 1.0.4版本(目前最新版本)。 連結:https://pan.baidu.com/s/1dSqHK6 密碼: qmvg
目錄
0x1.記憶體修改 → GG修改器修改數值,需root
0x2.存檔修改 → 存檔十六進位制修改,無需root;原創apk用於修改存檔,無需root
0x3.apk修改 → Unity3D遊戲指令碼修改,無需root
0x4.總結 → 文章整體思路和方向概況
正文
0x1.記憶體修改
思路:這個方式是用在已經root的手機上,也就是我們接觸比較多的修改器通過搜尋來確認關鍵數值的記憶體地址,然後將其修改,達到破解目的。
工具:GG修改器 / 需要ROOT許可權
因為比較簡單,這部分儘量簡要講。
開啟GG修改器和遊戲,進遊戲後檢視當前三葉草數量,GG修改器附加遊戲程式,並搜尋該數量。
附加後我們進行搜尋,搜尋37這個數值。
搜尋結果比較多,我們需要篩選,回到遊戲使用三葉草買東西,數值變化為27,然後我們搜尋27來確認三葉草數量的記憶體地址。
修改最終搜尋到的值為27000,回到遊戲就可以看到三葉草數量已經變化。
其他物品及抽獎券等數量均可用該方式修改,請大家自己嘗試。
這種方式非常方便,但是有個弊端就是需要我們有ROOT許可權,對於目前大部分安卓手機來講,ROOT許可權的獲取越來越難,接下來我們來分析不需要ROOT許可權的兩種修改方法。
0x2.存檔修改
思路:通過存檔檔案分析和修改完成關鍵數值修改
工具:十六進位制編輯器
單機遊戲都會有存檔,旅行青蛙當然也不例外,我們按照常規路徑去找一下,發現遊戲的存檔都在Tabikaeru.sav檔案中,路徑請看圖:
我們使用十六進位制編輯器將其開啟,編輯器可以用PC端的也可以用手機端的,自行選擇。
開啟後我們根據目前的三葉草數量27000進行搜尋,27000的十六進位制為0x6978,所以我們在十六進位制檔案中可以進行hex搜尋,搜尋 69 78 或 78 69。
(通常在十六進位制中的數值都是倒序記錄,比如0x6978會儲存為 78 69,在旅行青蛙1.0.1版本的存檔中就是這麼儲存的,不過在1.0.4版本的存檔中,已經變為了正序,即69 78)
經過搜尋我們找到了三葉草的數量,接下來我們將其修改驗證一下,將69 78 修改為 FF FF,儲存後放回手機中存檔的資料夾中,重新啟動,發現三葉草數量已經變更:
其他數值修改,比如抽獎券或者其他物品數量等,均可依照此方法進行,此處不再贅述,請大家自己嘗試。另外還可以在每次數值有較明顯變化後儲存存檔檔案,進行對比分析,來找到更多物品的數值。
為了更簡便的進行修改,我們做一個專用修改器apk用來在未root手機上專門完成此修改過程,原始碼如下 (完整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));
//抽獎券
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進位制字串轉換成位元組陣列
*/
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;
}
}
上述程式碼實現了存檔的直接修改,介面如下,不需要ROOT許可權:
輸入數值後,點選修改即可完成三葉草及抽獎券的修改 ,更多物品修改請自行嘗試 。
0x3.apk修改
思路:分析apk包,找到指令碼檔案,反編譯後找到關鍵method進行修改,然後重新打包
工具:Android Killer,DnSpy
Android Killer相關操作這裡不再贅述,反編譯後我們發現這是一個mono框架的Unity3D遊戲,Unity3D遊戲的指令碼檔案都存放在Assembly-CSharp.dll或Assembly-CSharp-firstpass.dll檔案中,很顯然,旅行青蛙的指令碼檔案位於Assembly-CSharp.dll,我們使用Dnspy進行分析看看。
我們搜尋三葉草的英文clover,發現getCloverPoint可能是我們需要找的關鍵method。
根據getCloverPoint的原始碼,我們發現這個method的功能是在三葉草數量發生變化時在三葉草數量進行增減運算,那麼我們可以對函式內部增加數量的這句程式碼進行修改,修改為發生變化增加固定數量的三葉草,比如10000。
(抽獎券相關修改也在SuperGameMaster中可以找到,method名為getTicket,此處不作演示,請大家自己嘗試修改)
修改後函式變更為:
儲存後打包apk執行,只要三葉草數量發生變化(如收割三葉草或者購買物品),三葉草的數量就會增加10000。
0x4.總結
本文通過多種思路對旅行青蛙的修改進行了分析,內容較為簡單,主要目的是分享遊戲破解分析的思路,有興趣的可以嘗試更多物品數量的修改。
本文由看雪論壇 黯夏子風 原創轉載請註明來自看雪社群相關文章
- 旅行青蛙(旅かえる)逆向筆記2018-05-05筆記
- 從密碼電池到記憶卡,承載記憶的載體,回憶那些年我們的遊戲存檔2020-01-15密碼遊戲
- 從【預編譯】到【宣告提升】到【作用域鏈】再到【閉包】2022-01-19編譯
- 利用MAT分析JVM記憶體問題,從入門到精通(二)2019-07-10JVM記憶體
- 從 HTTP 到 HTTPS 再到 HSTS2017-08-29HTTP
- 從HTTP到HTTPS再到HSTS2017-11-15HTTP
- Python3之從遞迴到閉包再到裝飾器2018-09-02Python遞迴
- 【React深入】從Mixin到HOC再到Hook2019-04-10ReactHook
- 從HBase offheap到Netty的記憶體管理2019-04-30Netty記憶體
- 虛擬記憶體到實體記憶體(32位)2015-01-07記憶體
- 記憶體分析與記憶體洩漏定位2017-11-03記憶體
- 從Oracle資料庫故障到AIX記憶體管理2021-02-03Oracle資料庫AI記憶體
- Ubuntu記憶體分析2019-01-22Ubuntu記憶體
- JVM記憶體分析2018-05-27JVM記憶體
- spark 原始碼分析之十六 -- Spark記憶體儲存剖析2019-07-18Spark原始碼記憶體
- 關於redis記憶體分析,記憶體優化2020-05-16Redis記憶體優化
- InnoDB儲存引擎——記憶體2017-03-10儲存引擎記憶體
- 11g從記憶體建立引數檔案2011-11-15記憶體
- 從Rails到Clojure再到Java,最後回到Rails2018-03-22AIJava
- Swoole 原始碼分析——記憶體模組之記憶體池2018-08-03原始碼記憶體
- 記憶體效能分析工具2019-02-20記憶體
- nginx共享記憶體分析2019-02-11Nginx記憶體
- Java 物件記憶體分析2020-12-03Java物件記憶體
- Go記憶體逃逸分析2022-02-28Go記憶體
- swoole記憶體管理分析2017-04-03記憶體
- Oracle記憶體全面分析2017-04-21Oracle記憶體
- 轉:Oracle 記憶體分析2012-12-24Oracle記憶體
- Java記憶體分析一2015-10-26Java記憶體
- 11-記憶體分析2024-06-23記憶體
- 使用dbms_shared_pool包將物件pin到記憶體中2011-03-02物件記憶體
- 快閃記憶體將改變資料庫儲存引擎的設計2014-09-15記憶體資料庫儲存引擎
- Unity效能分析(三)記憶體分析2024-04-30Unity記憶體
- 記記憶體條硬體損壞藍色畫面的 dump 檔案分析2024-07-10記憶體
- 從JAVA記憶體到垃圾回收,帶你深入理解JVM2021-01-26Java記憶體JVM
- ArkTS 的記憶體空間詳解:從 SemiSpace 到 HugeObjectSpace2024-10-29記憶體Object
- 黃金記憶體
V3.1 破解教程2004-12-13記憶體
- Redis 記憶體優化神技,小記憶體儲存大資料2022-07-13Redis記憶體優化大資料
- 從 MMU 看記憶體管理2022-02-17記憶體