网上几乎都是只有对该游戏勋章等逻辑的修改,没找到过对游戏局内资源的相关分析,所以就来分析一下吧
布豪,我的CE怎么失灵了!
我们首先使用CE桥接我们的Android设备搜索数据,根据对游戏玩法的了解,这里的数据不可能出现浮点数的情况,所以我们直接4字节搜索即可,多次筛选搜索到我们的金币数量,发现直接修改只能修改显示效果,并不能修改实际读取的数据

这里尝试使用CE修改器修改依旧无济于事,就算显示99999但是依旧买不了东西,也尝试过使用GG修改器等工具,但是修改之后只有显示效果,刷新之后我们的资源数据依旧会便会修改之前的值

我们可以选择使用数据的变化来筛选真实数据,但是这样的话这篇文章就太水了,而且并不是所有的游戏的加密数据都可以通过是否变化来确认的
内存断点——谁动了我的蛋糕
这里尝试过一些提供了断点功能的修改器,但是效果都不尽人意,还得是CE更好用
这里直接修改数据只是起显示作用,如果尝试造其他东西点击是无效的,而且下一轮更新之后金币会变回去,我们选择对这里的数据下一个写入断点,因为写入的次数一般会远小于访问次数,而且更接近代码的关键位置,所以相比访问断点,写入断点会让我们的分析更加简单一些
根据访问断点找到是由"libworld-conqueror-3.so"+189927(hex地址)这个地址写入的

我们将唯一一个32位arm的so拖入IDA分析,这里很明显是根据前面的判断来决定是否改写内存地址

我们发现是否赋值与R0 + 0xC和R0 + 0x10有关
我们使用Frida进行inlineHook,这样更精准一点,方便我们查看数据的具体情况,这里需要注意这里是Thumb指令集,我们Hook的地址需要 + 1,否则会因为错误指令替换直接让游戏崩溃
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function Hook_Native1() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); Interceptor.attach(base.add(0x18991C + 1), { onEnter: function (args) { console.log("======== Register ========"); console.log("R0: " + this.context.r0); console.log("R4: " + this.context.r4); console.log("======== var ========"); var p_r0_c = ptr(this.context.r0).add(0xc); console.log("word__r0 + 0xc: " + p_r0_c.readU16()); var p_r0_10 = ptr(this.context.r0).add(0x10); console.log("word__r0 + 0x10: " + p_r0_10.readU32()); console.log("======== Memory ========") console.log(hexdump(ptr(this.context.r0), { length: 0x20, header: true, ansi: false }));
}, onLeave: function (retval) {
} }); } setTimeout(Hook_Native1, 1000);
|
我们当前金币是318,钢铁是245,我们制造花费120金币,50钢铁的步兵炮,我们可以在其中根据r0 + 0x10的数据找到对金币进行读写的点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| ======== Register ======== R0: 0xcc39da98 R4: 0x956 ======== var ======== word__r0 + 0xc: 0 word__r0 + 0x10: 2389 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF cc39da98 bc da 39 cc 88 9d 58 22 00 00 00 00 00 00 04 00 ..9...X"........ cc39daa8 55 09 00 00 00 00 00 00 08 00 00 00 08 00 00 00 U...............
# 涉及金币的调用 ======== Register ======== R0: 0xcc39dcd8 R4: 0xc6 ======== var ======== word__r0 + 0xc: 0 word__r0 + 0x10: 318 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF cc39dcd8 fc dc 39 cc 88 50 38 0a 00 00 00 00 00 00 04 00 ..9..P8......... cc39dce8 3e 01 00 00 00 00 00 00 0b 00 00 00 0b 00 00 00 >...............
# 涉及钢铁的调用 ======== Register ======== R0: 0xcc39dd38 R4: 0xc3 ======== var ======== word__r0 + 0xc: 0 word__r0 + 0x10: 245 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF cc39dd38 5c dd 39 cc 17 bf 33 74 00 00 00 00 00 00 04 00 \.9...3t........ cc39dd48 f5 00 00 00 00 00 00 00 0b 00 00 00 0b 00 00 00 ................
======== Register ======== R0: 0xcc39da98 R4: 0x957 ======== var ======== word__r0 + 0xc: 0 word__r0 + 0x10: 2390 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF cc39da98 bc da 39 cc 88 9d 58 22 00 00 00 00 00 00 04 00 ..9...X"........ cc39daa8 56 09 00 00 00 00 00 00 08 00 00 00 08 00 00 00 V...............
|
根据数据我们首先对内存数据做一定的猜测
1 2 3 4 5 6
| ======== Register ======== R0: object address R4: state code ======== var ======== word__r0 + 0xc: ??? word__r0 + 0x10: option data
|
在非购买状态下该函数的数据在持续递增,在购买的时候又会和我们的资源数量有关,而且不同状态下使用的R0又不相同。所以可能不是对同一个玩家对象的操作,所以这很有可能是一个公用的数据处理函数,我们需要寻找上层的调用
我们尝试打印调用栈(这里如果在函数头部和改写数据处下两个inline Hook会导致0x18991C 的原始指令被第一个 hook 破坏,导致崩溃,所以直接Hook这个函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function Hook_Native1() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); Interceptor.attach(base.add(0x189914 + 1), { onEnter: function (args) { console.log('========called from========\n' + Thread.backtrace(this.context, Backtracer.FUZZY) .map(DebugSymbol.fromAddress).join('\n') + '\n'); }, onLeave: function (retval) { console.log("======== Register ========"); console.log("R0: " + this.context.r0); console.log("R2: " + this.context.r2); console.log("======== var ========"); var p_r0_c = ptr(this.context.r0).add(0xc); console.log("word__r0 + 0xc: " + p_r0_c.readU16()); var p_r0_10 = ptr(this.context.r0).add(0x10); console.log("word__r0 + 0x10: " + p_r0_10.readU32()); console.log("======== Memory ========") console.log(hexdump(ptr(this.context.r0), { length: 0x20, header: true, ansi: false })); } });
} setTimeout(Hook_Native1, 1000);
|
我们再次尝试购买物品打印堆栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| # 这里应该是时间或者显示的调用,不涉及核心资源,我们无需关心 ========called from======== 0xce2e41c5 libworld-conqueror-3.so!0xcc1c5 0xce2d9a34 libworld-conqueror-3.so!0xc1a34 0xce2e871c libworld-conqueror-3.so!0xd071c 0xce2e2918 libworld-conqueror-3.so!0xca918 0xce2e2cec libworld-conqueror-3.so!0xcacec 0xce2d2bb8 libworld-conqueror-3.so!Java_com_easytech_wc3_ecRender_nativeRender+0x43 0xce71e148 base.odex!com.android.billingclient.api.zzaj.nativeOnBillingServiceDisconnected [DEDUPED]+0x5f 0xce73da6c base.odex!com.easytech.wc3.ecRender.onDrawFrame+0x43 0x75328184 boot-framework.oat!android.opengl.GLSurfaceView$GLThread.guardedRun+0xe13 0x753296e0 boot-framework.oat!android.opengl.GLSurfaceView$GLThread.run+0xb7 0xecebb292 libc.so!je_arena_tdata_get_hard+0xe9 0xec5d4976 libart.so!art_quick_invoke_stub_internal+0x45 0xec5adad4 libart.so!art_quick_invoke_stub+0xe3 0xeceaa690 libc.so!je_arena_tcache_fill_small+0x257 0xec262c68 libart.so!_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+0x8b 0xec50ab80 libart.so!_ZN3art12_GLOBAL__N_118InvokeWithArgArrayERKNS_33ScopedObjectAccessAlreadyRunnableEPNS_9ArtMethodEPNS0_8ArgArrayEPNS_6JValueEPKc+0x37
======== Register ======== R0: 0xe6691ab8 R2: 0x3f ======== var ======== word__r0 + 0xc: 0 word__r0 + 0x10: 4550 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF e6691ab8 dc 1a 69 e6 88 9d 58 22 00 00 00 00 00 00 04 00 ..i...X"........ e6691ac8 c6 11 00 00 00 00 00 00 08 00 00 00 08 00 00 00 ................
# 下方是金币和钢铁逻辑的调用信息 ========called from======== 0xce2e41c5 libworld-conqueror-3.so!0xcc1c5 0xce2e3cfa libworld-conqueror-3.so!0xcbcfa 0xce3551b4 libworld-conqueror-3.so!0x13d1b4 0xce355db2 libworld-conqueror-3.so!0x13ddb2 0xce2ef470 libworld-conqueror-3.so!0xd7470 0xce354cda libworld-conqueror-3.so!0x13ccda 0xce3562f4 libworld-conqueror-3.so!0x13e2f4 0xce326460 libworld-conqueror-3.so!0x10e460 0xce33a2e0 libworld-conqueror-3.so!0x1222e0 0xce2e6e3e libworld-conqueror-3.so!0xcee3e 0xce30865a libworld-conqueror-3.so!0xf065a 0xce2e9842 libworld-conqueror-3.so!0xd1842 0xec46ca78 libart.so!_ZN3artL12FindMethodIDERNS_18ScopedObjectAccessEP7_jclassPKcS5_b+0xb3 0xec44cba0 libart.so!_ZN3art3JNI20CallStaticIntMethodVEP7_JNIEnvP7_jclassP10_jmethodIDSt9__va_list+0x1bf 0xec46c808 libart.so!_ZN3art9JNIEnvExt17AddLocalReferenceIP11_jthrowableEET_NS_6ObjPtrINS_6mirror6ObjectEEE+0x23 0xec446c7c libart.so!_ZN3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jclassPKcS6_+0x1cf
======== Register ======== R0: 0xe6691cf8 R2: 0x3f ======== var ======== word__r0 + 0xc: 0 word__r0 + 0x10: 58 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF e6691cf8 1c 1d 69 e6 88 50 38 0a 00 00 00 00 00 00 04 00 ..i..P8......... e6691d08 3a 00 00 00 00 00 00 00 0b 00 00 00 0b 00 00 00 :............... ========called from======== 0xce2e41c5 libworld-conqueror-3.so!0xcc1c5 0xce2e3cfa libworld-conqueror-3.so!0xcbcfa 0xce3551be libworld-conqueror-3.so!0x13d1be 0xce355db2 libworld-conqueror-3.so!0x13ddb2 0xce2ef470 libworld-conqueror-3.so!0xd7470 0xce354cda libworld-conqueror-3.so!0x13ccda 0xce3562f4 libworld-conqueror-3.so!0x13e2f4 0xce326460 libworld-conqueror-3.so!0x10e460 0xce33a2e0 libworld-conqueror-3.so!0x1222e0 0xce2e6e3e libworld-conqueror-3.so!0xcee3e 0xce30865a libworld-conqueror-3.so!0xf065a 0xce2e9842 libworld-conqueror-3.so!0xd1842 0xec46ca78 libart.so!_ZN3artL12FindMethodIDERNS_18ScopedObjectAccessEP7_jclassPKcS5_b+0xb3 0xec44cba0 libart.so!_ZN3art3JNI20CallStaticIntMethodVEP7_JNIEnvP7_jclassP10_jmethodIDSt9__va_list+0x1bf 0xec46c808 libart.so!_ZN3art9JNIEnvExt17AddLocalReferenceIP11_jthrowableEET_NS_6ObjPtrINS_6mirror6ObjectEEE+0x23 0xec446c7c libart.so!_ZN3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jclassPKcS6_+0x1cf
======== Register ======== R0: 0xe6691d58 R2: 0x3f ======== var ======== word__r0 + 0xc: 0 word__r0 + 0x10: 160 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF e6691d58 7c 1d 69 e6 17 bf 33 74 00 00 00 00 00 00 04 00 |.i...3t........ e6691d68 a0 00 00 00 00 00 00 00 0b 00 00 00 0b 00 00 00 ................
|
so层分析——抽丝剥茧
分析堆栈数据我们发现资源调用和显示调用上层都在0xcc1c5这个地址,再上一层开始出现了分歧
- 可能是计时调用:
0xc1a34
- 资源变化调用:
0xcbcfa
但是我们跳转到0xcbcfa什么都没有发现,这几个函数也没找到我们感兴趣的信息

我们尝试去到调用栈的更上一层0x13d1b4

我们尝试进入到这里的3个函数发现了很有意思的东西(其实这里已经可以带着修改器直接搜索修改加密数据了)



后面分析的时候回来F5刷新了一下变成这样了……但是没什么大的影响,后面Hook的时候就明白这里数据的意义了

因为三个函数结构几乎一模一样,使用我们以money的函数为例进行简单的猜测
1 2 3 4 5 6 7 8 9 10 11 12
| int __fastcall sub_13C9B8(int result, int a2) { int v3;
*(_DWORD *)(result + 36) = a2 ^ 0x7EAD3; if ( !*(_BYTE *)(result + 104) ) { v3 = sub_CCD74(); return sub_CC194(v3, "BattleMoney", a2); } return result; }
|
然后我们进入sub_CC194函数,在这里我们找到了我们之前下断点分析到的0x18991C函数

这时我们知道了我们最初分析的函数的参数意义,它的作用就是将数据回填到object中
1
| sub_189914(object, (int)resource_string, (int)data);
|
我们就可以借助frida快速分析我们前面Hook的数据的实际意义了,我们这时就已经不需要再打印调用栈了,只需要输出我们的cstring即可解析每一次操作的实际意义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function Hook_Native1() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); Interceptor.attach(base.add(0x189914 + 1), { onEnter: function (args) { console.log("======== string data ========"); console.log("r1 cstring: " + ptr(this.context.r1).readCString()); }, onLeave: function (retval) { console.log("======== Register ========"); console.log("R0: " + this.context.r0); console.log("R2: " + this.context.r2); console.log("======== var ========"); var p_r0_c = ptr(this.context.r0).add(0xc); console.log("word__r0 + 0xc: " + p_r0_c.readU16()); var p_r0_10 = ptr(this.context.r0).add(0x10); console.log("dword__r0 + 0x10: " + p_r0_10.readU32()); console.log("======== Memory ========") console.log(hexdump(ptr(this.context.r0), { length: 0x20, header: true, ansi: false })); } });
} setTimeout(Hook_Native1, 1000);
|
根据输出的数据,很明显我们就可以看出来我们之前Hook时递增的函数就是日期了(因为这个游戏存在随着游玩时间解锁更多场景的玩法,所以需要计时)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| ======== string data ======== r1 cstring: GameDate ======== Register ======== R0: 0xcc1838e8 R2: 0x3f ======== var ======== word__r0 + 0xc: 0 dword__r0 + 0x10: 202 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF cc1838e8 0c 39 18 cc 88 9d 58 22 00 00 00 00 00 00 04 00 .9....X"........ cc1838f8 ca 00 00 00 00 00 00 00 08 00 00 00 08 00 00 00 ................
======== string data ======== r1 cstring: BattleMoney ======== Register ======== R0: 0xcc183b28 R2: 0x3f ======== var ======== word__r0 + 0xc: 0 dword__r0 + 0x10: 10 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF cc183b28 4c 3b 18 cc 88 50 38 0a 00 00 00 00 00 00 04 00 L;...P8......... cc183b38 0a 00 00 00 00 00 00 00 0b 00 00 00 0b 00 00 00 ................
======== string data ======== r1 cstring: BattleSteel ======== Register ======== R0: 0xcc183b88 R2: 0x3f ======== var ======== word__r0 + 0xc: 0 dword__r0 + 0x10: 80 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF cc183b88 ac 3b 18 cc 17 bf 33 74 00 00 00 00 00 00 04 00 .;....3t........ cc183b98 50 00 00 00 00 00 00 00 0b 00 00 00 0b 00 00 00 P...............
======== string data ======== r1 cstring: GameDate ======== Register ======== R0: 0xcc1838e8 R2: 0x3f ======== var ======== word__r0 + 0xc: 0 dword__r0 + 0x10: 203 ======== Memory ======== 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF cc1838e8 0c 39 18 cc 88 9d 58 22 00 00 00 00 00 00 04 00 .9....X"........ cc1838f8 cb 00 00 00 00 00 00 00 08 00 00 00 08 00 00 00 ................
|
所以这里基本就清晰了,这里类似解析object获取field的操作来根据操作的资源类型获取其字段,而字段偏移0x10就是存储的数据

所以我们就可以根据分析的结果写出Hook脚本,并且尝试向其中写入数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function Hook_Native1() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); var resource = null; Interceptor.attach(base.add(0x189914 + 1), { onEnter: function (args) { console.log("======== data string ========"); console.log("r1 cstring: " + ptr(this.context.r1).readCString()); if (ptr(this.context.r1).readCString() == "BattleOil") { resource = "BattleOil"; } else if (ptr(this.context.r1).readCString() == "BattleSteel") { resource = "BattleSteel"; } else if (ptr(this.context.r1).readCString() == "BattleMoney") { resource = "BattleMoney"; } }, onLeave: function (retval) { console.log("======== Register ========"); console.log("R0: " + this.context.r0); console.log("R2: " + this.context.r2); console.log("======== var ========"); var p_r0_c = ptr(this.context.r0).add(0xc); console.log("word__r0 + 0xc: " + p_r0_c.readU16()); var p_r0_10 = ptr(this.context.r0).add(0x10); console.log("dword__r0 + 0x10: " + p_r0_10.readU32()); console.log("======== Memory ========") console.log(hexdump(ptr(this.context.r0), { length: 0x20, header: true, ansi: false })); if (resource != null) { p_r0_10.writeU32(0x1000); console.log("Modified " + resource + " to 0x1000"); resource = null; } } });
} setTimeout(Hook_Native1, 1000);
|
Hook修改之后虽然显示确实为4096,可是依旧只有显示效果,无法实际修改真实数据,下一轮游戏或者实际购买的时候资源数量又会变回修改之前的数据

再探so层——什么叫我改的数据是假的
这时候我们仔细思考前面我们遇到了一个加密数据的解密操作,而且我们下的断点本来就是显示数据的写入断点,而非实际数据的写入断点
但是我们不必沮丧,因为它如果想要显示这个数据,那这个数据一定有真实来源,这时候我们想到数据解密的上一层就是分别对我们的资源进行操作

我们向上可以找到这里对v10的初始化
我们更换一下脚本进行Hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function Hook_Native2() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); var resource = null; Interceptor.attach(base.add(0x0D743A + 1), { onEnter: function (args) { }, onLeave: function (retval) { var p_r0_c = ptr(this.context.r0).add(0x40); console.log("dword__r0 + 0x40: " + p_r0_c.readU32());
} });
} function Hook() { Hook_Native2(); }
setTimeout(Hook, 1000);
|
1 2 3 4 5 6 7 8 9
| base:0xcddd8000 dword__r0 + 0x40: 80 dword__r0 + 0x40: 80 dword__r0 + 0x40: 80 dword__r0 + 0x40: 80 dword__r0 + 0x40: 80 dword__r0 + 0x40: 80 dword__r0 + 0x40: 80 dword__r0 + 0x40: 80
|
我们发现前面的猜想的确存在问题,这里的参数2是我们花费的价格
这里我们如果查看交叉引用会有很多地方对这三个函数进行的调用,但是这里我们在修改数据的时候根据堆栈追上来的,所以只有这里才进行了实际的数据写操作
所以我们只需要顺着这一条调用链一直向上追踪,大概率是可以找到对实际数据的操作的
因为我们是下写入断点追上来的,所以其它处的调用我们不需要关心,这样只会让我们的分析难度倍增
原来的函数调用:
1 2 3
| sub_13C9F0(object, -price[16]); sub_13CA5C(object, -price[17]); sub_13CAC8(object, -price[18]);
|
那我们只要将花费的价格修改为相反数,我们将程序patch为这样,将取反操作修改为NOP指令即可验证这里的是否能实际修改我们内存中的数据:
实际尝试后发现这里的确有效果,然后就是……不要好的不要对的,只要贵的!

Frida解析——兄弟,看看你的对象
我们发现这里的确是对我们的数据进行了实际的修改的函数调用,那么我们的Hook为什么还是只能改变显示数据,而不能修改实际数据呢?
我们向内部寻找,发现这里它将我们的数据赋值给了object
我们可以尝试一下在它赋值给object之后,传参给sub_CC194的时候修改我们需要支付的金额,看看会不会生效,看看这里的决定性因素是否为object[9]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function Hook_Native3() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); var resource = null; Interceptor.attach(base.add(0xCC194+1), { onEnter: function (args) { this.context.r2 = ptr(10000); }, onLeave: function (retval) { } });
}
|
Hook之后发现这里只能修改显示数值,使用数据之后输入仍然会恢复修改之前的值,所以关键性的数据其实是这里的object[9],它的数据直接导致了我们资源数量的变化,所以这里的object很有可能就是我们真实的object地址,我们可以对它进行修改
但是在我们不知道实际意义的情况下乱改大概率游戏会直接崩溃,我们直接读这个object里面的指针尝试自己解析这个object的实际意义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| function Hook_Native5() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); var resource = null; Interceptor.attach(base.add(0x13C9B8+1), { onEnter: function (args) { console.log("======== r0 struct onEnter ========"); var p_r0 = ptr(this.context.r0).add(0x04); console.log("dword__r0 + 0x4: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x08); console.log("dword__r0 + 0x8: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x0c); console.log("dword__r0 + 0xc: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x10); console.log("dword__r0 + 0x10: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x14); console.log("dword__r0 + 0x14: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x18); console.log("dword__r0 + 0x18: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x1c); console.log("dword__r0 + 0x1c: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x20); console.log("dword__r0 + 0x20: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x24); console.log("dword__r0 + 0x24: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x28); console.log("dword__r0 + 0x28: " + (p_r0.readU32() ^ 0x7EAD3)); }, onLeave: function (retval) {
console.log("======== r0 struct onLeave ========"); var p_r0 = ptr(this.context.r0).add(0x04); console.log("dword__r0 + 0x4: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x08); console.log("dword__r0 + 0x8: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x0c); console.log("dword__r0 + 0xc: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x10); console.log("dword__r0 + 0x10: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x14); console.log("dword__r0 + 0x14: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x18); console.log("dword__r0 + 0x18: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x1c); console.log("dword__r0 + 0x1c: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x20); console.log("dword__r0 + 0x20: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x24); console.log("dword__r0 + 0x24: " + (p_r0.readU32() ^ 0x7EAD3)); var p_r0 = ptr(this.context.r0).add(0x28); console.log("dword__r0 + 0x28: " + (p_r0.readU32() ^ 0x7EAD3)); } });
}
function Hook() { Hook_Native5(); }
setTimeout(Hook, 1000);
|
解析出的数据如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| [MI 8::世界征服者3 ]-> base:0xce410000 ======== r0 struct onEnter ======== dword__r0 + 0x4: -865798469 dword__r0 + 0x8: 518865 dword__r0 + 0xc: 518866 dword__r0 + 0x10: 518871 dword__r0 + 0x14: 518867 dword__r0 + 0x18: 518867 dword__r0 + 0x1c: -906161933 dword__r0 + 0x20: -910901549 dword__r0 + 0x24: 2346 dword__r0 + 0x28: 970 ======== r0 struct onLeave ======== dword__r0 + 0x4: 518907 dword__r0 + 0x8: 518867 dword__r0 + 0xc: 95059 dword__r0 + 0x10: 57875 dword__r0 + 0x14: -865846573 dword__r0 + 0x18: -860606353 dword__r0 + 0x1c: 518867 dword__r0 + 0x20: -1066104750 dword__r0 + 0x24: -1066104750 dword__r0 + 0x28: -914469197
|
根据我们的观察,dword__r0 + 0x24及其之后的数据和我们的实际资源数量一致,所以我们在最上层这3个函数任意一个设置Hook即可,这里传入的就是真实的object

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function Hook_Native5() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); var resource = null; Interceptor.attach(base.add(0x13C9F0+1), { onEnter: function (args) {
var p_money = ptr(this.context.r0).add(0x24); p_money.writeU32(0x7EAD3 ^ 6666); var p_iron = ptr(this.context.r0).add(0x28); p_iron.writeU32(0x7EAD3 ^ 6666); var p_oil = ptr(this.context.r0).add(0x2C); p_oil.writeU32(0x7EAD3 ^ 6666); }, onLeave: function (retval) { } });
}
|
然后建造任意物品即可,修改真实有效,完美收工!

最后附上Hook脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
| var resource = null;
function Hook_Native1() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); Interceptor.attach(base.add(0x189914 + 1), { onEnter: function (args) { console.log("======== data string ========"); console.log("r1 cstring: " + ptr(this.context.r1).readCString()); if (ptr(this.context.r1).readCString() == "BattleOil") { resource = "BattleOil"; } else if (ptr(this.context.r1).readCString() == "BattleSteel") { resource = "BattleSteel"; } else if (ptr(this.context.r1).readCString() == "BattleMoney") { resource = "BattleMoney"; } }, onLeave: function (retval) { console.log("======== Register ========"); console.log("R0: " + this.context.r0); console.log("R2: " + this.context.r2); console.log("======== var ========"); var p_r0_c = ptr(this.context.r0).add(0xc); console.log("word__r0 + 0xc: " + p_r0_c.readU16()); var p_r0_10 = ptr(this.context.r0).add(0x10); console.log("dword__r0 + 0x10: " + p_r0_10.readU32()); console.log("======== Memory ========") console.log(hexdump(ptr(this.context.r0), { length: 0x20, header: true, ansi: false })); } });
}
function Hook_Native2() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); var resource = null; Interceptor.attach(base.add(0x0D743A + 1), { onEnter: function (args) { }, onLeave: function (retval) { var p_r0_c = ptr(this.context.r0).add(0x40); console.log("dword__r0 + 0x40: " + p_r0_c.readU32());
} });
} function Hook_Native3() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); var resource = null; Interceptor.attach(base.add(0xCC194+1), { onEnter: function (args) { this.context.r2 = ptr(10000); }, onLeave: function (retval) { } });
} function Hook_Native4() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); var resource = null; Interceptor.attach(base.add(0x13C9B8+1), { onEnter: function (args) { this.context.r1 = ptr(10000); }, onLeave: function (retval) { } });
} function Hook_Native5() { var base = Module.findBaseAddress("libworld-conqueror-3.so"); console.log("base:" + base); var resource = null; Interceptor.attach(base.add(0x13C9F0+1), { onEnter: function (args) {
var p_money = ptr(this.context.r0).add(0x24); p_money.writeU32(0x7EAD3 ^ 6666); var p_iron = ptr(this.context.r0).add(0x28); p_iron.writeU32(0x7EAD3 ^ 6666); var p_oil = ptr(this.context.r0).add(0x2C); p_oil.writeU32(0x7EAD3 ^ 6666); }, onLeave: function (retval) { } });
}
function Hook() { Hook_Native5(); }
setTimeout(Hook, 1000);
|
神秘so
The End