0%

Hook

  • Hook

Inline Hook

原理

修改原函数开头的汇编指令,跳转到自定义的函数进行hook,hook结束后,恢复原函数汇编指令,执行原流程

对抗

  1. 保护内存不可读写,因为inlinehook需要在内存中拿到函数地址
  2. 轮询crc校验内存是否被修改,被改了再改回来阻止hook

Plt/Got Hook

Plt & Got

原理

利用linux的延迟绑定机制,也就是找函数的时候 plt–>got –> 实际地址

Got –> 实际地址 的映射只会被初始化映射一次,后面就直接按映射地址去找了

因此可以通过修改 Got –> 实际地址 的映射【也就是修改GOT表的内容】来实现执行自定义的函数

  • 只要符号表没损坏,函数是动态注册还是非导出native方法都可以用got/plt hook

特点

  1. 可以hook 大量系统API,但难以精准hook某次函数调用,因此更适合oem厂商
  2. got/plt表中的函数不全
  3. 需要在自定义函数的结尾调用原函数恢复程序执行

Exception Hook

  • 对想要hook的地址设置非法指令,触发SIGILL异常,利用异常去实现hook

Objection Use

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
# hook list
jobs list
# 关闭Hook任务
jobs kill <job_id>

# 列出内存中加载的so
memory list modules
# 查看某个so库的导出函数
memory list exports <name.so> [--json name.json]
# dump
memory dump all <filename>
memory dump from_base <startaddress> <字节数> <文件>

# 列出内存中所有的类
android hooking list classes <class name>
# 列出当前应用程序使用的类方法s
android hooking list class_methods
# 列出当前应用程序使用的类加载器
android hooking list class_loaders
# 列出内存中所有的Activity
android hooking list activities
android hooking list receives
android hooking list services
# 内存堆上搜索类实例
android heap search instances <类名>
# search
android hooking search methods <keyword>
android hooking search classes <keyword>
# 跳转Activity
android intent launch_activity <活动名>
# 生成
android hooking generate simple <类名>
# hook
android hooking watch class <类名> [--dump-args --dump-backtrace --dump-return]
android hooking watch class_method <方法名> [--dump-args --dump-backtrace --dump-return]
android hooking watch class_method <方法名> "参数1, 参数2" --dump-args --dump-backtrace --dump-return

Frida

基本使用

加强版:

1
2
3
4
5
6
7
8
9
10
11
12
# 查看当前正在运行进程
frida-ps -Ua
# spawn hook
frida -U -f 包名 -l frida.js
# attach hook
frida -U 包名 -l frida.js
# 自定义端口启动
fs -l 0.0.0.0:1234
adb forward tcp:1234 tcp:1234
frida -H 127.0.0.1:1234 包名 -l hook.js
# 查看监听端口
netstat -an | grep LISTEN

问题

1. hook失败一次后打开APK一直提示wait for debugger

开发者选项–>等待调试程序–>关闭

2. 端口占用

1
2
3
ps -ef | grep frida
ps -ef | grep hluda
kill -9 PID

Java层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
function main(){
Java.perform(function(){
var hook = Java.use('com.example.androiddemo.Activity.FridaActivity1');
hook.a.implementation = function(str){
return false;
}
})
}
setImmediate(main)



// 重载:overload
function main(){
Java.perform(function(){
let Login = Java.use("com.example.androiddemo.Activity.LoginActivity");
Login[functionName].overload('java.lang.String','java.lang.String').implementation = function(str,str2){
return passwd;
}
})

}
setImmediate(main)



// 变量名和函数名相同
> 在重名的变量前加一个_下划线



// 内部类hook
> 指定包名加上内部类名:
> com.example.androiddemo.Activity.FridaActivity4$InnerClasses



//classloader问题
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
if(loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")){
console.log("Successfully found loader")
console.log(loader);
Java.classFactory.loader = loader;
}
}
catch(error){
console.log("find error:" + error)
}
},
onComplete: function () {
console.log("end1")
}
})

主动调用

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
//Java Code
private static boolean static_bool_var = false;
private boolean bool_var = false;
private static void setStatic_bool_var() {
static_bool_var = true;
}

private void setBool_var() {
this.bool_var = true;
}

//hook Code
function main(){
Java.perform(function(){
var MainActivity = Java.use('com.example.retest.MainActivity')
//静态函数,直接调用
MainActivity.staticsecret();
//静态变量,直接调用
MainActivity.static_bool_var.value = true;

//动态函数主动调用
Java.choose('com.example.retest.MainActicity',{
//实例化对象
onMatch:function(ins){
//调用动态函数和变量
ins.secret()
ins.bool_var.value = true;
},
onComplete: function(){
console.log('search Complete');
}
})
})
}
setImmediate(main)

Log类Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Java.perform(function () {
function hook_log() {
console.log("hook log ----------------:");
var Log = Java.use('android.util.Log');
Log.v.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) {
console.log(tag + " v", content);
};
Log.d.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) {
console.log(tag + " d", content);
};
Log.w.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) {
console.log(tag + " w", content);
};
Log.i.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) {
console.log(tag + " i", content);
};
Log.e.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) {
console.log(tag + " e", content);
};
}
hook_log()
})

StackHook

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
function printStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, "\\n");
console.log("=============================" + name + " Stack strat=======================");
console.log(replaceStr);
console.log("=============================" + name + " Stack end=======================\r\n");
Exception.$dispose();
}
});
}

function showStacks() {
Java.perform(function () {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
})

}

OnClickListener

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
var jclazz = null;
var jobj = null;

function getObjClassName(obj) {
if (!jclazz) {
var jclazz = Java.use("java.lang.Class");
}
if (!jobj) {
var jobj = Java.use("java.lang.Object");
}
return jclazz.getName.call(jobj.getClass.call(obj));
}

function watch(obj, mtdName) {
var listener_name = getObjClassName(obj);
var target = Java.use(listener_name);
if (!target || !mtdName in target) {
return;
}
// send("[WatchEvent] hooking " + mtdName + ": " + listener_name);
target[mtdName].overloads.forEach(function (overload) {
overload.implementation = function () {
//send("[WatchEvent] " + mtdName + ": " + getObjClassName(this));
console.log("[WatchEvent] " + mtdName + ": " + getObjClassName(this))
return this[mtdName].apply(this, arguments);
};
})
}

function OnClickListener() {
Java.perform(function () {

//以spawn启动进程的模式来attach的话
Java.use("android.view.View").setOnClickListener.implementation = function (listener) {
if (listener != null) {
watch(listener, 'onClick');
}
return this.setOnClickListener(listener);
};

//如果frida以attach的模式进行attch的话
Java.choose("android.view.View$ListenerInfo", {
onMatch: function (instance) {
instance = instance.mOnClickListener.value;
if (instance) {
console.log("mOnClickListener name is :" + getObjClassName(instance));
watch(instance, 'onClick');
}
},
onComplete: function () {
}
})
})
}
setImmediate(OnClickListener);

VpnHook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function main(){
Java.perform(function (){
Java.use("android.net.NetworkInfo").isConnected.implementation = function(){
console.log("first called!")
return false
}

Java.use("java.net.NetworkInterface").getName.implementation = function(){
console.log("second called!")
return ""
// return null
}

Java.use("android.net.NetworkCapabilities").hasTransport.implementation=function(){
console.log("third called!")
return false
}
//android.net.ConnectivityManager.getNetworkCapabilities
})
}

setImmediate(main)

SSLHook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function hook_ssl() {
Java.perform(function () {
var ClassName = "com.android.org.conscrypt.Platform";
var Platform = Java.use(ClassName);
var targetMethod = "checkServerTrusted";
var len = Platform[targetMethod].overloads.length;
console.log(len);
for (var i = 0; i < len; ++i) {
Platform[targetMethod].overloads[i].implementation = function () {
console.log("class:", ClassName, "target:", targetMethod, " i:", i, arguments);
};
}
});
}

setTimeout(hook_ssl, 100);

SockerHook

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
function pass_ssl(){
var ClassName = "com.android.org.conscrypt.Platform";
var Platform = Java.use(ClassName);
var targetMethod = "checkServerTrusted";
var len = Platform[targetMethod].overloads.length;
console.log(len);
for (var i = 0; i < len; ++i) {
Platform[targetMethod].overloads[i].implementation = function () {
console.log("class:", ClassName, "target:", targetMethod, " i:", i, arguments);
};
}
}

// hook强制更新
function hook_update(){
var appUpdateActivity = Java.use("com.huawei.updatesdk.service.otaupdate.AppUpdateActivity");
appUpdateActivity.onCreate.implementation = function (aaa){
console.log("--------hook update--------")
showStacks()
this.finish();
};
}

function main(){
Java.perform(function(){
var SwitchConfig = Java.use('mtopsdk.mtop.global.SwitchConfig')
SwitchConfig.isGlobalSpdySwitchOpen.implementation = function(){
console.log('SwitchConfig')
return false
}
pass_ssl();
hook_update();
})
}
//frida -U com.taobao.taobao -l hook_socker.js


setTimeout(main, 100);

绕过FLAG_SECURE或防止截屏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// https://github.com/iddoeldor/frida-snippets
// Bypass FLAG_SECURE or screenshot prevention

Java.perform(function () {
Java.use('android.view.SurfaceView').setSecure.overload('boolean').implementation = function (flag) {
send('[1] flag:' + flag);
this.call(false);
};
var LayoutParams = Java.use('android.view.WindowManager$LayoutParams');
Java.use('android.view.Window').setFlags.overload('int', 'int').implementation = function (flags, mask) {
send('flag secure: ' + LayoutParams.FLAG_SECURE.value);
send('before:' + flags);
flags = (flags.value & ~LayoutParams.FLAG_SECURE.value);
send('after:' + flags);
this.call(this, flags, mask);
};
});

spdy

1
2
3
4
5
6
7
8
Java.perform(function () {
var SwitchConfig = Java.use('mtopsdk.mtop.global.SwitchConfig');
SwitchConfig.isGlobalSpdySwitchOpen.overload().implementation = function(){
var ret = this.isGlobalSpdySwitchOpen.apply(this, arguments);
console.log("isGlobalSpdySwitchOpenl "+ret)
return false
}
})

Native层Hook

hook jni_onload

  • 如果想追踪jnionload在apk启动时的调用,需要先hook全局的dlopen函数,然后再通过dlopen函数去hook目标so中JniOnload的调用
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 hook_dlopen(soName = '') {
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();
if (path.indexOf(soName) >= 0) {
this.is_can_hook = true;
}
}
},
onLeave: function (retval) {
if (this.is_can_hook) {
hook_JNI_OnLoad()
}
}
}
);
}

function hook_JNI_OnLoad(){
let exportAddress = Module.findExportByName("soName", "JNI_OnLoad");

var baseAddress = Module.findBaseAddress("soName");
//console.log('Base Address: ' + baseAddress);

// 设定指令偏移
var offset = 0x17D19C;
var instructionAddress = baseAddress.add(offset);
console.log('Instruction Address: ' + instructionAddress);
Interceptor.attach(instructionAddress, {
onEnter: function (args) {
console.log("init called : " + instructionAddress);
}
});

Interceptor.attach(exportAddress, {
onEnter: function (args) {
console.log("JNI_OnLoad called : " + exportAddress);
}
});
}

setImmediate(hook_dlopen, "soName")

枚举内存中加载的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
var modules = Process.enumerateModules();
for (var i in modules){
var module = modules[i];
console.log(module.name);
}
hook函数
//获取函数地址
var memcmp_base = Module.findExportByName("libtiny.so",'memcmp');
console.log(memcmp_base);
//设置拦截器
Interceptor.attach(memcmp_base, {
onEnter: function (args) {
var s1 = args[0]; //参数1:地址
var s2 = args[1]; //参数2:地址
var n = args[2].toInt32(); //参数3:长度
console.log("address1:",s1);
console.log("address2:",s2);
//读取对应内存位置内容
var data1 = Memory.readByteArray(s1,n);
var data2 = Memory.readByteArray(s2,n);
console.log("data1:",data1);
console.log("data2:",data2);
}
});
基地址+相对偏移定位
var baseAddress = Module.getBaseAddress('libtiny.so');
var offset = 0x24B4D8; // 替换为目标函数的偏移量

//计算目标函数的地址
var targetAddress = baseAddress.add(offset);
console.log(targetAddress);

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
function two(){
var baseAddress = Module.getBaseAddress('');
var offset = 0x24B4D8; // 替换为目标函数的偏移量
var targetAddress = baseAddress.add(offset);

// 设定指令偏移
var offset = 0x24BAA4;
var instructionAddress = baseAddress.add(offset);
console.log('Instruction Address: ' + instructionAddress);

// 读取指令内容(例如 4 字节)
var instructionBytes = Memory.readByteArray(instructionAddress, 4);

// 打印指令字节码
var byteArray = new Uint8Array(instructionBytes);
var hexString = Array.from(byteArray).map(b => ('0' + b.toString(16)).slice(-2)).join(' ').toUpperCase();
console.log('Instruction Bytes: ' + hexString);

// hook指令
Interceptor.attach(instructionAddress, {
//执行前
onEnter: function(args) {
console.log('X1 before MOV: ' + this.context.x1);
console.log('X27: ' + this.context.x27);
var data1 = Memory.readByteArray(this.context.x27,0x10);
console.log("data1:",data1);
console.log('-------------------------------------------------')
},
//执行结束
onLeave: function(retval) {
}
});

}

调用追踪

1
2
3
4
5
6
7
8
9
10
var backtrace = Thread.backtrace(this.context, Backtracer.FUZZY)
backtrace.forEach(function(addr){
console.log('0x'+addr.toString(16));
var symbol = DebugSymbol.fromAddress(addr);
if (symbol) {
console.log(address + ' - ' + symbol.name);
} else {
console.log(address + ' - [Unknown]');
}
});

hook_RegisterNatives

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
function find_RegisterNatives(params) {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];

//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
hook_RegisterNatives(addrRegisterNatives)
}
}

}

function hook_RegisterNatives(addrRegisterNatives) {

if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);

var methods_ptr = ptr(args[2]);

var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", ptr(fnPtr_ptr).sub(find_module.base), " callee:", DebugSymbol.fromAddress(this.returnAddress));

}
}
});
}
}

setImmediate(find_RegisterNatives);

hook jni NewStringUTF

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
function test(){
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrNewStringUTF = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("NewStringUTF") >= 0 && symbol.name.indexOf("CheckJNI") < 0) {
addrNewStringUTF = symbol.address;
console.log("NewStringUTF is at ", symbol.address, symbol.name);
break
}
}
if (addrNewStringUTF !== null) {
Interceptor.attach(addrNewStringUTF, {
onEnter: function(args) {
var utfString = Memory.readUtf8String(args[0]);
//console.log("JNI_NewStringUTF called with string: " + utfString);

if (utfString.indexOf("XYAAAAAQAAAAEAAAB") !== -1) {
console.log(utfString);
// 4.4 读取当前在执行那个so文件,及so文件中的地址
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
// 4.5 打印调用栈
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
}
},
onLeave: function(retval) {
}
});
} else {
console.log("NewStringUTF not found.");
}
}

setImmediate(test)

dump so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var libxx = Process.getModuleByName("xxx.so");
console.log("*****************************************************");
console.log("name: " +libxx.name);
console.log("base: " +libxx.base);
console.log("size: " +ptr(libxx.size));


var file_path = "/data/data/" + libxx.name + "_" + libxx.base + "_" + ptr(libxx.size) + ".so";
console.log(file_path);

var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(libxx.base), libxx.size, 'rwx');
var libso_buffer = ptr(libxx.base).readByteArray(libxx.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump]:", file_path);
}