首页
社区
课程
招聘
[原创]飞车手游Java层分析到Xposed模块实战
发表于: 2025-10-6 20:38 1446

[原创]飞车手游Java层分析到Xposed模块实战

2025-10-6 20:38
1446

上一篇帖子:飞车手游分析 & 加速,秒通关实现

大家对我上次的使用修改器修改游戏的帖子似乎比较感兴趣,本帖将带大家深入分析这个飞车小游戏并且编写快捷易用的Xposed模块

上一篇帖子我们简单使用修改器修改了这个游戏,但是碰到了切屏就失效,搜索数据浪费时间等问题,这篇帖子我们使用Xposed模块来解决这些问题,成品放在附件里面了

最终效果:速度锁400,积分赛66666分

cf5K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1x3U0y4Q4x3X3g2@1N6W2)9J5c8X3W2K9N6f1y4Y4z5r3p5`.

上一期我们找到了这个游戏的速度地址,但是每一局都要重新搜索,修改,有时候一局可能还需要搜索修改多次,会显得很麻烦。

本期的受害者依旧是 ↓↓↓

AmazingGame!!!

我们提到过一个想法就是Hook撞墙的代码来获取对象数据,但是这里的主要撞墙逻辑并不是传入实例后调方法的形式,所以想Hook方法参数,就只能hook玩家之间的碰撞了

在Java层我们是无法像Native层一样下访问断点追踪的,因为Java为了安全性避免了直接的内存操作,而且是运行环境也是ART虚拟机,即使下断点,访问点也都是在Code System段,所以很难通过地址定位类信息,而且搜了几天几乎没找到任何相关的追踪手段,根据我的思考和探索,有以下初步的思路

各位师傅有更好的办法浇浇我

没有进行Java层的混淆,比较适合萌新体质()

我们首先尝试搜索wallspeedplayer等关键信息,这里我选择从KeyEvent入手

在Android开发中android.view.KeyEvent为玩家操作游戏提供了支持,也会为我们提供一些关键信息

我们在这里的回调函数中很轻松的找到了对我们对象状态的判断,很明显我们的游戏对局对象是inGame,其中存储着我们在这局游戏中的状态等信息

图片描述

还发现了可爱的flag(?)

图片描述

另一个方法也印证了这一点

图片描述

我们可以进入到InGame这个类看看,发现了我们上次对内存中状态码的初步判断是正确的

发现了public static Vaisseau player = new Vaisseau();,结合后面的代码,说明玩家是一个Vaisseau类的实例

图片描述

我们的目标是获取速度信息,我们在类里面搜索speed找到了对应的信息,这似乎是一个列表,这里设置了敌人的defaultParams(默认参数)我们修改之后发现敌人不能动了,不过这不是我们分析的重点

图片描述

结合游戏玩法,很明显这是根据敌人选择不同的基础速率,而且float类型也符合我们对速度的判断

图片描述

但是这并不是我们想要的数据,我们继续分析,来到了这里,发现这里是我们游戏角色运动的逻辑,都在doPhysics方法中

玩家加速逻辑(这里可以看出来player.param[3]是角色速度上限)

图片描述

玩家之间碰撞的逻辑检测,其中是在扣除vzb这个变量数据,所以我们合理认为vzb是速度信息

图片描述

所以接下来我们来到角色类(Vaisseau类)实例中,通过查找速度的交叉引用寻找到了碰撞逻辑

图片描述

第二关和第五关也找到判断玩家是否在赛道上的逻辑了

图片描述

这里是玩家的速度变化逻辑

图片描述

这里分析了一下游戏的vxb,vyb,vzb,三者是xyz三个方向的速度

图片描述

到这里其实我们关于游戏的基本信息都分析出来了,这是最直接最暴力的方法,虽然在大型,有混淆的游戏中花费的时间成本会大幅提高,但能应对各种乱七八糟的情况

其实到这里我们发现我们使用monitor并不可行,因为这里的撞墙并不是通过调用一个方法实现的,而是直接进行if判断后加减,我们如果尝试这种方法,很有可能会被误导到各种角度坐标计算方法中去,从而无法自拔,所以这只能作为一种技巧使用

实现不了的关键点在于无法获取类实例的地址,也没有找到相关的api

来来回回研究了半天,不会获取类实例的地址,寄了,半成品脚本放在这里,师傅们有建议的话可以提一下,谢谢喵

注入就崩了,可能是解析方法错了?也中道崩殂了

Java虚拟机如果创建了这些类,肯定是需要一个标识用于管理他们的,但是单纯的使用Java Heap Dump的话我们很难确定实例的头部究竟在哪里,但是GGlua脚本就为我们很好的解决了这个问题

我们使用Android Studio的Profiler这个工具来dump堆信息

官方文档

首先根据上一篇帖子,我们知道了这个值在Java Heap中的地址

图片描述

首先我们要思考一个Java Heap上的对象,系统是如何判断它属于哪个类呢?其实就是根据对象头部的指针

就像身份证上的“籍贯”,告诉JVM这个对象是哪个类的实例

Java的对象头:原理与源码详解

所以我们在这里可以看到类实例头部,会有一个指针,这个指针是指向J内存的

J (JIT Memory)

所以我们可以借助这一点来使用GGlua来为我们自动确定类的头部信息

图片描述

我们使用GG修改器执行如下GGlua脚本

我们在/storage/emulated/0/JSnow_FindClass.txt中可以得到类实例头部的地址

然后我们使用Android Studio自带的Profiler功能将此时的Java Heap整个Dump下来

图片描述

根据官方文档,我们需要使用SDK工具将 .hprof 文件从 Android 格式转换为 Java SE .hprof 文件格式

接下来使用MAT(MemoryAnalyzer)工具分析我们输出的堆转储文件

我们根据GGlua脚本输出得到的地址搜索,很快就在找到了这个类,然后我们就可以直接快速定位关键类进行Java层分析了,相比我们在Java层分析下来这种方法的效率明显更高

图片描述

所以我们可以简单使用frida写出一个作弊脚本,这里只是简单试一下效果,所以没有优化

如果-UF指定顶层会导致hook的是修改器

hook发现的确有效,说明我们寻找的是正确的,但是我们要寻找合适的Hook点,因为这种方法会导致所有赛车都加速,达不到获胜的目的

但是我们不可能每次打开游戏都启动frida,连接usb,会显得比较麻烦,而且连着个电脑打游戏总会不得劲(但是听说有一个叫算法助手的app可以便捷启动frida脚本)

我们新建项目之后,需要新建lib目录**(不是libs)**下加入我们的Xposed的jar包,为我们提供代码补全,方便我们调用api,然后在main目录下新建assets目录,新建xposed_init文件,这里用于声明Xposed的入口点,我这里是com.JSnow.cheatmodule.XposedCheatModule,所以就向其中添加com.JSnow.cheatmodule.XposedCheatModule这一串内容就可以了

图片描述

为了减小app体积,我们需要在build.gradle文件中添加compileOnly,让jar包不被打包到我们的app中

图片描述

然后我们就可以在我们指定的入口点开始写Hook了

图片描述

分析了一圈,尝试了不少Hook点,感觉还是绘制的方法Hook起来体验最好,而且player对象是没有看到作为参数传递到某个方法中的,所以就只能利用反射获取这个实例了

现在大家都喜欢用Lsposed模块,Lsposed模块是兼容了Xposed模块的,加载后记得勾选游戏和系统框架
因为xposed的原理是替换系统的app_process文件,所以不重启是无法生效的

核心代码:

最终效果:181K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1x3U0y4Q4x3X3g2@1N6W2)9J5c8X3W2K9N6f1y4Y4z5r3p5`.
(其它地方都不能点击即看,最后还是b站收留了我的视频)

成品和源码都打包了一份,感兴趣的师傅可以拿来玩玩,游戏本体下载:

794K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6o6g2p5k6Q4x3X3c8m8M7X3y4Z5K9i4k6W2M7#2)9J5c8U0t1H3x3U0c8Q4x3X3c8F1k6i4N6K6N6r3q4J5i4K6u0r3M7X3g2D9k6h3q4K6k6i4y4Q4x3V1k6V1L8%4N6F1L8r3!0S2k6q4)9J5c8Y4j5I4i4K6u0W2x3q4)9J5k6e0m8Q4x3V1k6F1M7K6t1@1i4K6g2X3L8h3W2K6j5#2)9J5k6i4A6A6M7l9`.`.

function find_obj(){
    Java.perform(function() {
        // 我们使用修改器获得到的Java实例中速度字段的地址
        var targetAddress = ptr("0x1390013c");
        console.log("目标地址: " + targetAddress);
         
        // 获取当前加载的所有类
        console.log("开始搜索类实例...");
         
        var processedClasses = new Set();
         
        // 使用Java.enumerateLoadedClasses获取所有已加载类
        Java.enumerateLoadedClasses({
            onMatch: function(className) {
                // 只搜索App内的类,避免系统类
                if (!className.startsWith("net.osaris") ||
                    !className.startsWith("com.pangbai.") ||
                    !className.startsWith("adrt")
                 ) {
                    return;
                }
                 
                try {
                    if (processedClasses.has(className)) return;
                    processedClasses.add(className);
                     
                    var javaClass = Java.use(className);
                     
                    // 搜索该类的所有实例
                    Java.choose(className, {
                        onMatch: function(instance) {
                            try {
                                console.log("检查实例: " + instance);
                                console.log("addr: " + instance.$handle);
                                // 卡在这里了
                                // 获取实例的地址(这里读不出来,抛出异常)
                                var instanceAddr = ptr(instance.$handle);
                                console.log("实例地址: " + instanceAddr);
                                 
                                var maxObjectSize = 1024;
                                var minAddr = instanceAddr;
                                var maxAddr = instanceAddr.add(maxObjectSize);
                                 
                                // 检查目标地址是否在该范围内
                                if (targetAddress.compare(minAddr) >= 0 &&
                                    targetAddress.compare(maxAddr) <= 0) {
                                    console.log("找到可能匹配的对象!");
                                    console.log("类名: " + className);
                                    console.log("实例地址: " + instanceAddr);
                                    console.log("字段可能偏移量: " + targetAddress.sub(instanceAddr));
                                     
                                    // 尝试打印对象详情
                                    console.log("对象信息: " + instance.toString());
                                     
                                    // 尝试列出字段
                                    var clazz = instance.getClass();
                                    var fields = clazz.getDeclaredFields();
                                    console.log("类字段数量: " + fields.length);
                                    for (var i = 0; i < fields.length; i++) {
                                        fields[i].setAccessible(true);
                                        console.log("字段: " + fields[i].getName() +
                                                 ", 类型: " + fields[i].getType());
                                    }
                                }
                            } catch (e) {
                                console.error("实例检查错误: " + e);
                            }
                            return "continue";
                        },
                        onComplete: function() {
                             
                        }
                    });
                } catch (e) {
                    console.error("类处理错误: " + e);
                }
            },
            onComplete: function() {
                console.log("所有类搜索完成");
            }
        });
         
        console.log("搜索已启动,等待结果...");
    });
}
 
setTimeout(find_obj, 1000);
function find_obj(){
    Java.perform(function() {
        // 我们使用修改器获得到的Java实例中速度字段的地址
        var targetAddress = ptr("0x1390013c");
        console.log("目标地址: " + targetAddress);
         
        // 获取当前加载的所有类
        console.log("开始搜索类实例...");
         
        var processedClasses = new Set();
         
        // 使用Java.enumerateLoadedClasses获取所有已加载类
        Java.enumerateLoadedClasses({
            onMatch: function(className) {
                // 只搜索App内的类,避免系统类
                if (!className.startsWith("net.osaris") ||
                    !className.startsWith("com.pangbai.") ||
                    !className.startsWith("adrt")
                 ) {
                    return;
                }
                 
                try {
                    if (processedClasses.has(className)) return;
                    processedClasses.add(className);
                     
                    var javaClass = Java.use(className);
                     
                    // 搜索该类的所有实例
                    Java.choose(className, {
                        onMatch: function(instance) {
                            try {
                                console.log("检查实例: " + instance);
                                console.log("addr: " + instance.$handle);
                                // 卡在这里了
                                // 获取实例的地址(这里读不出来,抛出异常)
                                var instanceAddr = ptr(instance.$handle);
                                console.log("实例地址: " + instanceAddr);
                                 
                                var maxObjectSize = 1024;
                                var minAddr = instanceAddr;
                                var maxAddr = instanceAddr.add(maxObjectSize);
                                 
                                // 检查目标地址是否在该范围内
                                if (targetAddress.compare(minAddr) >= 0 &&
                                    targetAddress.compare(maxAddr) <= 0) {
                                    console.log("找到可能匹配的对象!");
                                    console.log("类名: " + className);
                                    console.log("实例地址: " + instanceAddr);
                                    console.log("字段可能偏移量: " + targetAddress.sub(instanceAddr));
                                     
                                    // 尝试打印对象详情
                                    console.log("对象信息: " + instance.toString());
                                     
                                    // 尝试列出字段
                                    var clazz = instance.getClass();
                                    var fields = clazz.getDeclaredFields();
                                    console.log("类字段数量: " + fields.length);
                                    for (var i = 0; i < fields.length; i++) {
                                        fields[i].setAccessible(true);
                                        console.log("字段: " + fields[i].getName() +
                                                 ", 类型: " + fields[i].getType());
                                    }
                                }
                            } catch (e) {
                                console.error("实例检查错误: " + e);
                            }
                            return "continue";
                        },
                        onComplete: function() {
                             
                        }
                    });
                } catch (e) {
                    console.error("类处理错误: " + e);
                }
            },
            onComplete: function() {
                console.log("所有类搜索完成");
            }
        });
         
        console.log("搜索已启动,等待结果...");
    });
}
 
setTimeout(find_obj, 1000);
function find_obj()
{
    Java.perform(function() {
     
    var fieldAddress = ptr("0x134EB318");
     
    console.log("已知字段地址: " + fieldAddress);
     
    var searchRange = 256; // 搜索范围
     
    // 从字段地址向前搜索
    for (var offset = 0; offset < searchRange; offset += 8) {
        var potentialHeaderAddr = ptr(fieldAddress).sub(offset);
         
        try {
            var value = Memory.readPointer(potentialHeaderAddr);
            console.log(potentialHeaderAddr + " -> " + value);
             
            // 尝试将读取的值作为类引用
            try {
                // 尝试将该值作为对象或类信息的引用
                var possibleClass = Java.cast(value, Java.use("java.lang.Class"));
                console.log("可能的类引用: " + potentialHeaderAddr + " -> " + possibleClass.getName());
                console.log("找到可能的对象头部,距离字段偏移: " + offset + " 字节");
            } catch(e) {
                // 尝试作为对象引用
                try {
                    var possibleObj = Java.cast(value, Java.use("java.lang.Object"));
                    console.log("可能的对象引用: " + potentialHeaderAddr + " -> " + possibleObj.getClass().getName());
                } catch(e2) {
                    console.log(potentialHeaderAddr + " -> [无法转换为对象或类]");
                }
            }
        } catch(e) {
            console.log(potentialHeaderAddr + " -> [无法读取]");
        }
    }
     
    
});
}
 
setTimeout(find_obj, 1000);

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

最后于 2025-10-9 12:35 被JSnow编辑 ,原因:
上传的附件:
收藏
免费 29
支持
分享
最新回复 (21)
雪    币: 3043
活跃值: (3899)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
过来瞧瞧
2025-10-6 21:25
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
666
2025-10-6 23:32
0
雪    币: 104
活跃值: (7189)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
tql
2025-10-7 08:56
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
66
2025-10-7 09:02
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
666
2025-10-7 15:28
0
雪    币: 1776
活跃值: (2585)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
路过学习
2025-10-7 22:10
0
雪    币: 152
活跃值: (582)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
666
2025-10-7 22:31
0
雪    币: 3789
活跃值: (4090)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
6
2025-10-9 10:12
0
雪    币: 160
活跃值: (395)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10

666

最后于 2025-10-10 08:35 被Qc101编辑 ,原因:
2025-10-10 08:35
0
雪    币: 129
活跃值: (2331)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
11
6
2025-10-10 09:09
0
雪    币: 143
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
6666
2025-10-10 10:23
0
雪    币: 942
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
666
2025-10-10 11:48
0
雪    币: 204
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
666
2025-10-10 12:48
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
666
2025-10-10 13:58
0
雪    币: 10
活跃值: (862)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
3+3
2025-10-10 15:23
0
雪    币: 5611
活跃值: (3924)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
666
2025-10-13 09:54
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
666
2025-10-13 17:33
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
谢谢分享
2025-10-13 18:22
0
雪    币: 86
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
谢谢分享
2025-11-17 23:17
0
雪    币: 228
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
666学习了
2025-11-18 09:09
0
雪    币: 210
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22
666
2025-11-18 09:59
0
游客
登录 | 注册 方可回帖
返回