首页
社区
课程
招聘
对init_array段调用的方法进行Hook
发表于: 2024-7-27 16:27 3351

对init_array段调用的方法进行Hook

2024-7-27 16:27
3351

对init_array段调用的方法进行Hook

前言

在某个风和日丽、阳光灿烂、万里无云、空气清新、绿树成荫、蝉鸣如雷、炎热难耐、烈日当空、热浪滚滚、炎夏炙烤、炎炎夏日、烈日炎炎、艳阳高照、晴空万里、暑气逼人、炙热的阳光、烈日炎炎、汗如雨下、火辣辣的天气、烈日当空、酷热难耐的下午,Dev1l师傅发来了一个Frida检测的小Demo,反编译后发现主要逻辑都在init_array段,故写下本篇文章,记录下探索中学到的知识。

情景复现

由于Dev给的Demo用途还需要保密,因此这里我写一个init_array执行逻辑相同的Demo,来演示如何通过Fridahook .init_array段中的内容。

Demo Java层实现

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
package com.swdd.testapp;
 
import androidx.appcompat.app.AppCompatActivity;
 
import android.os.Bundle;
import android.widget.TextView;
 
import com.swdd.testapp.databinding.ActivityMainBinding;
 
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("testapp");
    }
    private ActivityMainBinding binding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }
    public native String stringFromJNI();
}

没错就是简单的Android studio生成的一个模板,加载了Native方法之后就会更改MainActivity渲染的一个TextView。

Native层实现

1
2
3
4
5
6
7
8
9
10
#include <jni.h>
#include <string>
 
extern "C" JNIEXPORT jstring JNICALL
Java_com_swdd_testapp_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

本身Native层是返回一个Hello from C++,那我们要如何在.init_array中运行它呢请看接下来的代码。

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
#include <jni.h>
#include <string>
#include <android/log.h>
 
#define LOG_TAG "GenFunction"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
 
char Text[512] = "Hello from C++";
 
static void genText(const char * newText){
    strcat(Text,newText);
}
 
static void Gen() __attribute__((constructor()));
static void Gen(){
    genText("\nHello .init_array");
}
 
extern "C" JNIEXPORT jstring JNICALL
Java_com_swdd_testapp_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    genText("\nhello stringFromJNI");
    return env->NewStringUTF(Text);
}

我们只需要在需要放到.init_array中运行的方法生命的时候加一段描述就好__attribute__((constructor()))此处其实也可以使用section去描述段名,我们这里使用constructor就是代表的.init_arry段。接下来看看app运行效果吧:

file

这里为了方便后续演示,我分别在.init_array中和strFromJni中各调用了一次。

尝试开始HOOK

首先我们对Native层进行分析。

file

指的注意的是我们这里的genText是非导出的情况,所以我们讨论的也是非导出情况下的Hook。

首先我们在segments窗口中看到各个段的偏移

file
双击.init_array即可进入。

file

这里我们可以记录下genText方法的偏移是0x808

file

这样我们第一次尝试Hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function main() {
    Java.perform(function () {
        var baseAddress = Module.getBaseAddress("libtestapp.so");
        console.log(baseAddress);
        Interceptor.attach(baseAddress.add(0x808), {
            onEnter: function (args) {
                var input = args[0].readUtf8String();
                console.log("Hook Success -> ", input);
            }, onLeave: function (retval) { }
        });
    });
}
//setImmediate(main);
setTimeout(main, 200);

file
此时发现问题,我们如果使用setTimeout(main,200)的话就只能Hook到fromJni了,.init_array由于时间问题导致无法HOOK,那么我们如果改成setImmediate会如何呢:

file
由于时间过早,还没有加载到libtestapp.so我们就开始hook了导致进程报错。

解决思路

首先根据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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
+---------------------+         +------------------------+
|                     |         |                        |
|    Java Runtime     |         |    Native Runtime      |
|  (Dalvik/ART VM)    |         |   (Native Executable)  |
|                     |         |                        |
+---------------------+         +------------------------+
           |                               |
           |                               |
           |       System.loadLibrary()    |
           |------------------------------>|
           |                               |
           |                               |
           |          Runtime Linker       |
           |       (e.g., ld.so on Linux)  |
           |                               |
           |  +-------------------------+  |
           |  |                         |  |
           |  |     Load .so File       |  |
           |  |     (dlopen)            |  |
           |  |                         |  |
           |  +-------------------------+  |
           |               |               |
           |               |               |
           |  +-------------------------+  |
           |  |                         |  |
           |  |  Resolve Symbols        |  |
           |  |  (dlsym)                |  |
           |  |                         |  |
           |  +-------------------------+  |
           |               |               |
           |               |               |
           |  +-------------------------+  |
           |  |                         |  |
           |  |  Initialize .so         |  |
           |  |                         |  |
           |  +-------------------------+  |
           |               |               |
           |               |               |
           |  +-------------------------+  |
           |  |                         |  |
           |  |  Execution              |  |
           |  |  (Invoke Native Code)   |  |
           |  |                         |  |
           |  +-------------------------+  |
           |                               |
           |                               |

那么最开始要启动的肯定是dlopen了,(在 android 7.0 之后使用的是 android_dlopen_ext),该方法储存在libdl.so 中,因此我们首先可以通过加载libdl.so,对android_dlopen_ext进行Hook,来获取更早的时机。

Hook android_dlopen_ext:

1
2
3
4
5
6
7
8
9
10
11
function main() {
    var dlopenAdd = Module.findExportByName("libdl.so", "android_dlopen_ext");
    Interceptor.attach(dlopenAdd, {
        onEnter: function (args) {
            console.log("Loaded dlopen with -> " + args[0].readCString());
        }, onLeave(retVal) {
 
        }
    })
}
setImmediate(main);

file

可以发现加载libtestapp.so的时机还挺早的,接下来我们就要对libtestapp.so这个字段做过滤了,找到libtestapp.so才做处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function main() {
    var dlopenAdd = Module.findExportByName("libdl.so", "android_dlopen_ext");
    Interceptor.attach(dlopenAdd, {
        onEnter: function (args) {
            if (args[0].readCString().indexOf("libtestapp.so") != -1) {
                console.log("Loaded dlopen with -> " + args[0].readCString());
 
            }
        }, onLeave(retVal) {
 
        }
    })
}
setImmediate(main);

如果此时直接Hook依旧会产生报错:

file

说明时机依旧还是太早了,那么如何操作呢,我们看到如下流程:

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
+------------------------+             +-------------------------+
|                        |             |                         |
|   Android Framework    |             |     Native Executable   |
|                        |             |                         |
+------------------------+             +-------------------------+
            |                                   |
            |         loadLibrary("mylib")      |
            |---------------------------------->|
            |                                   |
            |         Runtime Linker (ld.so)    |
            |---------------------------------->|
            |                                   |
            |       Find and Load mylib.so      |
            |---------------------------------->|
            |                                   |
            |          Call Constructors        |
            |        (callConstructorAdd)       |
            |---------------------------------->|
            |                                   |
            |                                   |
            |            Initialize             |
            |              (init_array)         |
            |---------------------------------->|
            |                                   |
            |              Execute              |
            |            (main function)        |
            |---------------------------------->|
            |                                   |

可以发现Init_array之前还有一个 Call Constructors , 这个时候已经加载了libtestapp.so了,我们即可进行操作。

Hook callConstructorAdd

callConstructorAdd函数在编译后导出名字为:__dl__ZN6soinfo17call_constructorsEv
Symbols = Process.getModuleByName("linker64").enumerateSymbols(); 去获取他的相对偏移就好了。

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
function startHook() {
    Java.perform(function () {
 
        var baseAddress = Module.getBaseAddress("libtestapp.so");
        console.log(baseAddress);
        Interceptor.attach(baseAddress.add(0x808), {
            onEnter: function (args) {
                var input = args[0].readUtf8String();
                console.log("Hook Success -> ", input);
            }, onLeave: function (retval) { }
        });
 
    });
}
 
function main() {
    var dlopenAdd = Module.findExportByName("libdl.so", "android_dlopen_ext");
    Interceptor.attach(dlopenAdd, {
        onEnter: function (args) {
            if (args[0].readCString().indexOf("libtestapp.so") != -1) {
                console.log("Loaded dlopen with -> " + args[0].readCString());
                var Symbols = Process.getModuleByName("linker64").enumerateSymbols();
                for (var index = 0; index < Symbols.length; index++) {
                    if (Symbols[index].name.indexOf("__dl__ZN6soinfo17call_constructorsEv") != -1) {
                        console.log("callConstructorAdd -> " , Symbols[index].address);
                    }
                }
            }
        }, onLeave(retVal) {
 
        }
    })
}
setImmediate(main);
//setTimeout(main, 200);

这样我们就拿到了callConstructorAdd内存中的地址:
file
接下来就可以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 main() {
    var dlopenAdd = Module.findExportByName("libdl.so", "android_dlopen_ext");
    Interceptor.attach(dlopenAdd, {
        onEnter: function (args) {
            if (args[0].readCString().indexOf("libtestapp.so") != -1) {
                console.log("Loaded dlopen with -> " + args[0].readCString());
                var Symbols = Process.getModuleByName("linker64").enumerateSymbols();
                for (var index = 0; index < Symbols.length; index++) {
                    if (Symbols[index].name.indexOf("__dl__ZN6soinfo17call_constructorsEv") != -1) {
                        console.log("callConstructorAdd -> ", Symbols[index].address);
                        Interceptor.attach(Symbols[index].address, {
                            onEnter: function (args) {
                                console.log("callConstructorAdd Called!");
                            }, onLeave: function (retval) { }
                        });
                    }
                }
            }
        }, onLeave(retVal) {
 
        }
    })
}
setImmediate(main);

file

这个时候我们发现调用了非常多次,所以我们需要做一个Hook过的标记,Hook过之后就不再hook了,接下来我们再调用hook .init_array中调用的方法的代码:
file

EXP:

注意StartHook在onLeave和onEnter中效果是一样的。

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
function startHook() {
    Java.perform(function () {
 
        var baseAddress = Module.getBaseAddress("libtestapp.so");
        console.log(baseAddress);
        Interceptor.attach(baseAddress.add(0x808), {
            onEnter: function (args) {
                var input = args[0].readUtf8String();
                console.log("Hook Success -> ", input);
            }, onLeave: function (retval) { }
        });
 
    });
}
 
function main() {
    var dlopenAdd = Module.findExportByName("libdl.so", "android_dlopen_ext");
    var isHooked = false;
    Interceptor.attach(dlopenAdd, {
        onEnter: function (args) {
            if (args[0].readCString().indexOf("libtestapp.so") != -1) {
                console.log("Loaded dlopen with -> " + args[0].readCString());
                var Symbols = Process.getModuleByName("linker64").enumerateSymbols();
                for (var index = 0; index < Symbols.length; index++) {
                    if (Symbols[index].name.indexOf("__dl__ZN6soinfo17call_constructorsEv") != -1) {
                        console.log("callConstructorAdd -> ", Symbols[index].address);
                        Interceptor.attach(Symbols[index].address, {
                            onEnter: function (args) {
                                if (!isHooked) {
                                    console.log("callConstructorAdd Called!");
                                    isHooked = true;
                                    startHook();
                                }
                            }, onLeave: function (retval) { }
                        });
                    }
                }
            }
        }, onLeave(retVal) {
 
        }
    })
}
setImmediate(main);
//setTimeout(main, 200);

结尾:

本文参考:https://www.jianshu.com/p/59d1d3054abe


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-7-27 16:44 被Shangwendada编辑 ,原因:
上传的附件:
收藏
免费 6
支持
分享
最新回复 (5)
雪    币: 1388
活跃值: (2887)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-7-30 16:53
0
雪    币: 400
活跃值: (785)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Da_
3
感谢分享,大佬有个问题请教一下,如果是init_proc里的方法呢,方式和这个一样嘛。init_proc和init_array有什么区别,哪个对抗的难度更高?
2024-10-11 10:04
0
雪    币: 1985
活跃值: (1825)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享.
2024-10-11 10:43
0
雪    币: 2270
活跃值: (5537)
能力值: ( LV8,RANK:146 )
在线值:
发帖
回帖
粉丝
5
https://github.com/Simp1er/MobileSec/blob/master/hook_init_array.js 可以直接用我的这个脚本,init_array和init_proc 通杀
2024-10-11 11:16
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
直接在mmap时hook
2024-10-11 12:38
0
游客
登录 | 注册 方可回帖
返回
//