首页
论坛
专栏
课程

[原创]旅行青蛙破解分析从内存到存档再到改包

2018-1-31 19:39 15151

[原创]旅行青蛙破解分析从内存到存档再到改包

2018-1-31 19:39
15151

最近朋友圈里出现了一款日本的游戏,十分火爆,于是忍不住想去破解看看。分析后发现这个游戏的破解并不难,但是可以多种思路进行,是个很好的学习样本,于是决定写一篇文章分享给初学者们。

本文分三个方向进行破解分析,分别为 内存修改,存档修改,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.总结

本文通过多种思路对旅行青蛙的修改进行了分析,内容较为简单,主要目的是分享游戏破解分析的思路,有兴趣的可以尝试更多物品数量的修改。





[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

最后于 2019-1-11 19:33 被kanxue编辑 ,原因:
上传的附件:
上一主题 下一主题
最新回复 (29)
八岛 1 2018-1-31 19:50
2
0
aihacker 2018-1-31 20:25
3
0
zylyy 2018-1-31 21:46
4
0
,这个也需要修改?
黯夏子风 3 2018-1-31 21:55
5
0
zylyy [em_2],这个也需要修改?
只是作为一个学习样本,不针对游戏本身
holing 12 2018-1-31 23:16
6
0
不错
ying爵 2018-2-1 08:49
7
0
能分析照片是怎么生成的吗
黯夏子风 3 2018-2-1 09:44
8
0
ying爵 能分析照片是怎么生成的吗
没具体分析,可以从GetTmpPicture、LoadPicture、ChangePicture这几个method入手看一下。
知乎上关于游戏逻辑的分析 https://www.zhihu.com/question/68733553/answer/305463907
嫉猜 2018-2-1 10:44
9
0
ying爵 能分析照片是怎么生成的吗
知乎上有把程序逆向的,说的很清楚,制作者在游戏内有路线设计,明信片也是这样
川幽 2018-2-1 19:30
10
0
下了app-debug.apk,没改成功
Deeppanda 2018-2-1 21:06
11
0
关于0x2.存档修改的一点疑问
不root怎么修改其他APP里的数据?物理机中一般都不允许吧?
W/System.err: java.io.FileNotFoundException: /storage/emulated/0/Android/data/jp.co.hit_point.tabikaeru/files/Tabikaeru.sav: open failed: EACCES (Permission denied)
堂前燕 2018-2-1 21:13
12
0
思路清晰,分析到位
wx_Safe3 2018-2-1 21:57
13
0
试试
黯夏子风 3 2018-2-1 22:18
14
0
川幽 下了app-debug.apk,没改成功
游戏版本升级到1.0.4再修改,如果版本没有错,退出游戏后修改试下
黯夏子风 3 2018-2-1 22:21
15
0
Deeppanda 关于0x2.存档修改的一点疑问不root怎么修改其他APP里的数据?物理机中一般都不允许吧?W/System.err: java.io.FileNotFoundException: /storage/ ...
只是对文件管理器中的存档文件进行编辑修改,并不需要root,一种可能是没有给应用sd卡文件读写权限,另一种可能是游戏版本并非1.0.4,因为旧版本的存档文件名跟新版本不同
聖blue 2018-2-1 22:44
16
0
robbercn 2018-2-2 11:10
17
0
学习了,感谢感谢
super面包 2018-2-2 12:06
18
0
确实没成功。。而且全盘搜索都没搜到那个文件。。
黯夏子风 3 2018-2-2 13:00
19
0
super面包 确实没成功。。而且全盘搜索都没搜到那个文件。。
1.0.1版本存档名字不是这个,1.0.4存档名字才是这个,目测你安装版本并非1.0.4
super面包 2018-2-2 13:48
20
0
是1.04,才从应用宝上下载的
Wiiiisp 2018-2-2 14:39
21
0
最后改apk那个,测试了下改getCloverPoint没有效果,要改Assembly-CSharp.dll->  {}-  ->  CloverFarm  ->  cloverDataFormat.point  =  10000.    不过感谢提供这个教学
仙果 19 2018-2-2 15:03
22
0
厉害
黯夏子风 3 2018-2-2 15:15
23
0
wx_Fly_569111 最后改apk那个,测试了下改getCloverPoint没有效果,要改Assembly-CSharp.dll-> {}- -> CloverFarm -> cloverDataFor ...
getCloverPoint的效果是数量产生变化是增加三叶草,不是直接改数量
黯夏子风 3 2018-2-2 15:44
24
0
super面包 是1.04,才从应用宝上下载的
按路径查看下有存档了再用吧,帖子里的apk就是改的存档文件
鑫达 2018-2-5 15:26
25
0
厉害  如果游戏是联网的  也能用这个思路修改吗  服务器会不会通过呢
猎头Sky 2018-2-6 17:13
26
0
此楼层已删除
东北的大雪 2018-3-7 00:17
27
0
给个
蒼星 2018-3-31 16:29
28
0
請問  不小心將時間往前調得太多了  也能用這個方法修改嗎?  我不小心把時間調到明年去了  又捨不得砍掉重練  ...
黯夏子风 3 2018-4-3 09:20
29
0
蒼星 請問 不小心將時間往前調得太多了 也能用這個方法修改嗎? 我不小心把時間調到明年去了 又捨不得砍掉重練 ...
没有查看最新的版本目前是什么情况,如果是1.0.4,可以的,如果是最新版,内存方式可以,存档及方式不确定,如果有需要我闲下来看看最新版,不过现在玩的人少多了
junkboy 2019-1-11 20:16
30
0
#寻宝大战#祝看雪19岁快乐!
游客
登录 | 注册 方可回帖
返回