对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 ) {
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 ) {
genText( "\nhello stringFromJNI" );
return env->NewStringUTF(Text);
}
|
我们只需要在需要放到.init_array中运行的方法生命的时候加一段描述就好__attribute__((constructor()))
此处其实也可以使用section去描述段名,我们这里使用constructor就是代表的.init_arry段。接下来看看app运行效果吧:
这里为了方便后续演示,我分别在.init_array中和strFromJni中各调用了一次。
尝试开始HOOK
首先我们对Native层进行分析。
指的注意的是我们这里的genText是非导出的情况,所以我们讨论的也是非导出情况下的Hook。
首先我们在segments窗口中看到各个段的偏移
双击.init_array即可进入。
这里我们可以记录下genText方法的偏移是0x808
这样我们第一次尝试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) { }
});
});
}
setTimeout(main, 200);
|
此时发现问题,我们如果使用setTimeout(main,200)
的话就只能Hook到fromJni了,.init_array由于时间问题导致无法HOOK,那么我们如果改成setImmediate会如何呢:
由于时间过早,还没有加载到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);
|
可以发现加载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依旧会产生报错:
说明时机依旧还是太早了,那么如何操作呢,我们看到如下流程:
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);
|
这样我们就拿到了callConstructorAdd内存中的地址:
接下来就可以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);
|
这个时候我们发现调用了非常多次,所以我们需要做一个Hook过的标记,Hook过之后就不再hook了,接下来我们再调用hook .init_array中调用的方法的代码:
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);
|
结尾:
本文参考:https://www.jianshu.com/p/59d1d3054abe
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2024-7-27 16:44
被Shangwendada编辑
,原因: