前言

這是一款挺老的遊戲了,以前身邊很多人在玩所以我也玩了一會,在遇然得知網上有這遊戲的免費外掛後去嘗試了一下,感覺很爽,畢竟是一款半單機的遊戲,就算開掛也不太會影響別人。

在那之後有試過研究一下這外掛的原理,但當時可謂是0基礎,只能按別人影片裡的教學一步一操作地嘗試自己寫外掛,最後不出意外地失敗了。再之後又嘗試了幾次,同樣以失敗告終…

雖說一直失敗,但不得不說我對寫外掛一直都很感興趣,大學選了計算機專業很大部份原因也是因為對外掛感興趣吧。

今天總算是圓了這以前的遺憾了^^

前置操作

利用MT管理器的APK共存功能複製一個APK出來:安裝包提取→選擇APK→APK共存

Untitled

修改就只對新複製出來的那個APK進行修改,原APK不作任何修改,方便之後hook兩者來對比差異

閃退分析

以前在修改這遊戲後發現會閃退,一直以為它有簽名校驗,但其實並沒有。

它真的會校驗的是libmonsterstrike.so這個so,如何發現的?當不進行任何修改直接重打包APK後,它不會閃退;而只要修改了libmonsterstrike.so任一地方後,APP會閃退,由此可知該APP會校驗libmonsterstrike.so有沒有被修改。

然後就要找是哪裡在檢測libmonsterstrike.so的完整性,通過hook dlopen打印加載的so路徑

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
function hook_dlopen(soName, func) {
Interceptor.attach(Module.findExportByName(null, "dlopen"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log(path);
}
},
onLeave: function (retval) {
}
}
);

Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log(path);
}
},
onLeave: function (retval) {
}
}
);
}

會發現在加載libmonsterstrike.so後APP就會閃退,因此可知libmonsterstrike.so裡某個函數會檢測libmonsterstrike.so的完整性

Untitled

之後hook syscall,對比原版APP和修改了libmonsterstrike.so後的APP的syscall的區別,會發現修改版APP會比原版APP多調用了系統調用號為220的那個系統調用,打印其調用棧後會定位到applicationDidFinishLaunching這個函數

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
let sysNumMaps = {}
function hook_syscall(){
let syscall = Module.getExportByName(null, "syscall");

Interceptor.attach(syscall,
{
onEnter: function (args) {
if(args[0].toInt32() == 220){
this.print = true;
console.log('sys_num[220] called from:\n' +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
}
if(!sysNumMaps[args[0].toInt32()]){
sysNumMaps[args[0].toInt32()] = 1;
console.log("sysNumMaps: ", JSON.stringify(sysNumMaps));
}

},
onLeave: function (retval) {
if(this.print){
console.log("retval: ", retval);
}
}
}
);
}

從上述調用棧具體可以定位到applicationDidFinishLaunching裡的getTimeOffset,其實應該是getTimeOffset上面的checkAllKeys函數( checkAllKeys函數在下圖已被我patch掉,因此看不到 )

Untitled

嘗試將checkAllKeys用frida替換掉,看看還會不會閃退,0x21BEA6CcheckAllKeys的地址。結果是不會閃退,因此checkAllKeys就是那個萬惡的檢測函數,並且不包含任何業務邏輯,可以直接patch掉。

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
function hook_dlopen(soName, func) {
Interceptor.attach(Module.findExportByName(null, "dlopen"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log(path);
if (path.indexOf(soName) >= 0) {
this.is_can_hook = true;
}
}
},
onLeave: function (retval) {
if (this.is_can_hook) {
console.log("hook start...");
hook_func1(soName, func)
}
}
}
);

Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log(path);
if (path.indexOf(soName) >= 0) {
this.is_can_hook = true;
}
}
},
onLeave: function (retval) {
if (this.is_can_hook) {
console.log("hook start...");
hook_func1(soName, func)
}
}
}
);
}

function hook_func1(soName, func) {
var funcAddr;
var base;
if(typeof func == "string"){
funcAddr = Module.findExportByName(soName, func);
}else if(typeof func == "number"){
base = Module.findBaseAddress(soName);
funcAddr = base.add(func);

}
console.log("base: ", base)
console.log("func addr: ", funcAddr)

Interceptor.replace(funcAddr, new NativeCallback(function (a) {
console.log("替換 checkAllKey")
return 123;
}, 'void', ['pointer']));

// Interceptor.attach(funcAddr,{
// onEnter(args){
// console.log("有調用!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")

// },
// onLeave(retval){
// console.log("retval: ", retval)
// }
// })

console.log("hook done")
}

hook_dlopen("libmonsterstrike.so", 0x21BEA6C);

外掛功能實現

APP的主要功能在libmonsterstrike.so裡,並且它連符號都沒有去掉,因此可以通過搜尋關鍵字的方式來定位到相應的功能。先用frida hook來驗證,最後再修改匯編。

  1. TaskEnemy::addDamage:加攻擊力

實際上是TaskEnemy::addDamage裡的TaskActor::addDamage,它的第2個參數可以理解成增加的傷害。

這裡的修改方式是將某處對X21的賦值改成MOV X21, #0xFFFFFFFF,因為X21會在TaskActor::addDamage調用前賦給X1,而X1TaskActor::addDamage的第2個參數

Untitled

Untitled

  1. TaskCharBall::isStrikeAttackOk:判斷是否有大技

修改:W0( X0的低32位 )代表返回值,因此直接將其置1然後返回。

注:修改後需要按U再按P來重新生成函數

Untitled