frida是一款便携的,自由的,支持全平台的,hook框架,可以通过编写JavaScript,Python代码来和frida_server端进行交互,还记得当年用xposed时那种写了一大堆代码每次修改都要重新打包安装重启手机,那种调试调到头皮发麻的痛苦百分之30的时间都是在那里安装重启安装重启直到有一天遇到了小甜甜。
Frida的核心是c编写的有多种语言绑定例如 Node.js、 Python、 Swift、 .NET、 Qml
一般我们都使用js去编写frida脚本因为js的异常处理机制非常棒相比于其他语言更高效好用。
frida-core的功能有进程注入、进程间通信、会话管理、脚本生命周期管理等功能,屏蔽部分底层的实现细节并给最终用户提供开箱即用的操作接口。而这一切的实现都在 frida-core 之中,正如名字所言,这其中包含了 frida 相关的大部分关键模块和组件,比如 frida-server、frida-gadget、frida-agent、frida-helper、frida-inject 以及之间的互相通信底座。
frida-gum是基于inline-hook实现的他还有很多丰富的功能比如用于代码跟踪 Stalker、用于内存访问监控的MemoryAccessMonitor,以及符号查找、栈回溯实现、内存扫描、动态代码生成和重定位等。
Interceptor 是 inline-hook 的封装
潜行者又称为尾行痴汉,可以实现指定线程中所有函数、所有基本块、甚至所有指令的跟踪但是有很大的缺点比如在32位或者thumb下问题很大,一般想使用指令跟踪都是使用内存断点或者unidbg模拟执行so但是有很多问题,内存断点的反调试倒是很容易解决但是性能是一个很大的缺陷代码触发断点后会先中断到内核态,然后再返回到用户态(调试器)执行跟踪回调,处理完后再返回内核态,然后再回到用户态继续执行,这来来回回的黄花菜都凉了。但Unidbg的使用门槛动不动就补环境,龙哥说样本和Unidbg之间摩擦出的火花才是最迷人的。或者说人话——“他妈的Unidbg怎么又报错了,我该怎么办?”
Stalker的简单使用
Stalker也可以用来还原ollvm混淆 记录函数的真实执行地址结合ida反汇编没执行的代码都nop掉可以很大程度上帮助辅助混淆算法分析当然可能不太准确但也是一种非常棒的思路。
Stalker的功能实现,在线程即将执行下一条指令前,先将目标指令拷贝一份到新建的内存中,然后在新的内存中对代码进行插桩,如下图所示:
这其中使用到了代码动态重编译的方法,好处是原本的代码没有被修改,因此即便代码有完整性校验也不影响,另外由于执行过程都在用户态,省去了多次中断内核切换,性能损耗也达到了可以接受的水平。由于代码的位置发生了改变,如前文 Interceptor 一样,同样要对代码进行重定位的修复
MemoryAccessMonitor可以实现对指定内存区间的访问监控,在目标内存区间发生读写行为时可以触发用户指定的回调函数。
通过阅读源码发现这个功能的实现方法非常简洁,本质上是将目标内存页设置为不可读写,这样在发生读写行为时会触发事先注册好的中断处理函数,其中会调用到用户使用 gum_memory_access_monitor_new 注册的回调方法中。
frida注入的主要思路就是找到目标进程,使用ptrace跟踪目标进程获取mmap,dlpoen,dlsym等函数库的便宜获取mmap在目标进程申请一段内存空间将在目标进程中找到存放[frida-agent-32/64.so]的空间启动执行各种操作由agent去实现。
补充:frida注入之后会在远端进程分配一段内存将agent拷贝过去并在目标进程中执行代码,执行完成后会 detach 目标进程,这也是为什么在 frida 先连接上目标进程后还可以用gdb/ida等调试器连接,而先gdb连接进程后 frida 就无法再次连上的原因(frida在注入时只会ptrace一下下注入完毕后就会结束ptrace所以ptrace占坑这种反调试使用spawn方式启动即可)。
frida-agent 注入到目标进程并启动后会启动一个新进程与 host 进行通信,从而 host 可以给目标进行发送命令,比如执行代码,激活/关闭 hook,同时也能接收到目标进程的执行返回以及异步事件信息等。
frida 的 hook 区分了 art 模式和 dalvik 模式。
把 java 函数变成 native 函数,然后修改入口信息为自定义函数信息。
art模式也是需要将java 函数变成 native 函数但是不同于dalvik,art下有两种解释器一种汇编解释器一种smali解释器
//2022.7.17 寒冰大佬指出问题 安卓7.0 有三种解释器switch结构,goto跳转表,汇编
1.如果函数已经存在quick code, 则指向这个函数对应的 quick code的起始地址,而当quick code不存在时,它的值则会代表其他的意义。
2.当一个 java 函数不存在 quick code时,它的值是函数artQuickToInterpreterBridge 的地址,用以从 quick 模式切换到 Interpreter 模式来解释执行 java 函数代码。
3.当一个 java native(JNI)函数不存在 quick code时,它的值是函数 art_quick_generic_jni_trampoline 的地址,用以执行没有quick code的 jni 函数。
所以 frida 要将 java method 转为 native method,需要将ARTMethod 结构进行如下修改:
frida
-
core: Frida 核心库
frida
-
gum: inline
-
hook 框架
bindings:
frida
-
python: python
frida
-
node: Node.js
frida
-
qml: Qml
frida
-
swift: Swift
frida
-
tools: CLI tools
capstone: instruction disammbler
frida
-
core: Frida 核心库
frida
-
gum: inline
-
hook 框架
bindings:
frida
-
python: python
frida
-
node: Node.js
frida
-
qml: Qml
frida
-
swift: Swift
frida
-
tools: CLI tools
capstone: instruction disammbler
GumInterceptor
*
interceptor;
GumInvocationListener
*
listener;
gum_init ();
interceptor
=
gum_interceptor_obtain ();
/
/
GumInvocationListener
*
的接口
listener
=
g_object_new (EXAMPLE_TYPE_LISTENER, NULL);
/
/
开始 hook `
open
` 函数
gum_interceptor_begin_transaction (interceptor);
gum_interceptor_attach_listener (interceptor,
GSIZE_TO_POINTER (gum_module_find_export_by_name (NULL,
"open"
)),
listener,
GSIZE_TO_POINTER (EXAMPLE_HOOK_OPEN));
gum_interceptor_end_transaction (interceptor);
/
/
测试 hook 效果
close (
open
(
"/etc/hosts"
, O_RDONLY));
/
/
结束 hook
gum_interceptor_detach_listener (interceptor, listener);
g_object_unref (listener);
g_object_unref (interceptor);
GumInterceptor
*
interceptor;
GumInvocationListener
*
listener;
gum_init ();
interceptor
=
gum_interceptor_obtain ();
/
/
GumInvocationListener
*
的接口
listener
=
g_object_new (EXAMPLE_TYPE_LISTENER, NULL);
/
/
开始 hook `
open
` 函数
gum_interceptor_begin_transaction (interceptor);
gum_interceptor_attach_listener (interceptor,
GSIZE_TO_POINTER (gum_module_find_export_by_name (NULL,
"open"
)),
listener,
GSIZE_TO_POINTER (EXAMPLE_HOOK_OPEN));
gum_interceptor_end_transaction (interceptor);
/
/
测试 hook 效果
close (
open
(
"/etc/hosts"
, O_RDONLY));
/
/
结束 hook
gum_interceptor_detach_listener (interceptor, listener);
g_object_unref (listener);
g_object_unref (interceptor);
Interceptor.attach(addr, {
onEnter: function (args) {
this.args0
=
args[
0
];
this.tid
=
Process.getCurrentThreadId();
/
/
跟随
Stalker.follow(this.tid, {
events: {
/
/
事件
call: true,
/
/
呼叫
ret: false,
/
/
返回
exec
: true,
/
/
执行
block: false,
/
/
块
compile
: false
/
/
编译
},
/
/
接收
onReceive(events){
for
(const [index,value] of Stalker.parse(events)) {
console.log(index,value);
/
/
findModuleByAddress {
"name"
:
"libc.so"
,
"base"
:
"0x7d1f0af000"
,
"size"
:
3178496
,
"path"
:
"/apex/com.android.runtime/lib64/bionic/libc.so"
}
/
/
console.log(
"tuzi"
,Process.findModuleByAddress(
0x7d1f13adb8
));
}
}
/
/
onCallSummary(summay){
/
/
console.log(
"onCallSummary"
+
JSON.stringify(summay));
/
/
},
});
}, onLeave: function (retval) {
Stalker.unfollow(this.tid);
}
});
Interceptor.attach(addr, {
onEnter: function (args) {
this.args0
=
args[
0
];
this.tid
=
Process.getCurrentThreadId();
/
/
跟随
Stalker.follow(this.tid, {
events: {
/
/
事件
call: true,
/
/
呼叫
ret: false,
/
/
返回
exec
: true,
/
/
执行
block: false,
/
/
块
compile
: false
/
/
编译
},
/
/
接收
onReceive(events){
for
(const [index,value] of Stalker.parse(events)) {
console.log(index,value);
/
/
findModuleByAddress {
"name"
:
"libc.so"
,
"base"
:
"0x7d1f0af000"
,
"size"
:
3178496
,
"path"
:
"/apex/com.android.runtime/lib64/bionic/libc.so"
}
/
/
console.log(
"tuzi"
,Process.findModuleByAddress(
0x7d1f13adb8
));
}
}
/
/
onCallSummary(summay){
/
/
console.log(
"onCallSummary"
+
JSON.stringify(summay));
/
/
},
});
}, onLeave: function (retval) {
Stalker.unfollow(this.tid);
}
});
/
/
C 代码
gboolean
gum_memory_access_monitor_enable (GumMemoryAccessMonitor
*
self
,
GError
*
*
error)
{
if
(
self
-
>enabled)
return
TRUE;
/
/
...
self
-
>exceptor
=
gum_exceptor_obtain ();
gum_exceptor_add (
self
-
>exceptor, gum_memory_access_monitor_on_exception,
self
);
/
/
...
}
/
/
C 代码
gboolean
gum_memory_access_monitor_enable (GumMemoryAccessMonitor
*
self
,
GError
*
*
error)
{
if
(
self
-
>enabled)
return
TRUE;
/
/
...
self
-
>exceptor
=
gum_exceptor_obtain ();
gum_exceptor_add (
self
-
>exceptor, gum_memory_access_monitor_on_exception,
self
);
/
/
...
}
/
/
js代码
function read_write_break(){
function hook_dlopen(addr, soName, callback) {
Interceptor.attach(addr, {
onEnter: function (args) {
var soPath
=
args[
0
].readCString();
if
(soPath.indexOf(soName) !
=
-
1
) hook_call_constructors();
}, onLeave: function (retval) {
}
});
}
var dlopen
=
Module.findExportByName(
"libdl.so"
,
"dlopen"
);
var android_dlopen_ext
=
Module.findExportByName(
"libdl.so"
,
"android_dlopen_ext"
);
hook_dlopen(dlopen,
"libaes.so"
, set_read_write_break);
hook_dlopen(android_dlopen_ext,
"libaes.so"
, set_read_write_break);
function set_read_write_break(){
/
/
实现一个异常回调 处理好这个异常就可以正常返回
Process.setExceptionHandler(function(details) {
console.log(JSON.stringify(details, null,
2
));
console.log(
"lr"
, DebugSymbol.fromAddress(details.context.lr));
console.log(
"pc"
, DebugSymbol.fromAddress(details.context.pc));
Memory.protect(details.memory.address, Process.pointerSize,
'rwx'
);
console.log(Thread.backtrace(details.context, Backtracer.ACCURATE).
map
(DebugSymbol.fromAddress).join(
'\n'
)
+
'\n'
);
return
true;
});
var addr
=
Module.findBaseAddress(
"libaes.so"
).add(
0x6666
);
Memory.protect(addr,
8
,
'---'
);
/
/
修改内存页的权限
/
*
*
*
比如有一个地址是
0x12345678
我想看一下是那个代码去访问了这个地址
*
我只需要把这个内存地址置空 有函数去访问这个地址时 就会触发非法访问异常
*
比较鸡肋这种方法 这种方法会一次修改一个内存页 并且触发一次就无效了
*
/
}
}
/
/
js代码
function read_write_break(){
function hook_dlopen(addr, soName, callback) {
Interceptor.attach(addr, {
onEnter: function (args) {
var soPath
=
args[
0
].readCString();
if
(soPath.indexOf(soName) !
=
-
1
) hook_call_constructors();
}, onLeave: function (retval) {
}
});
}
var dlopen
=
Module.findExportByName(
"libdl.so"
,
"dlopen"
);
var android_dlopen_ext
=
Module.findExportByName(
"libdl.so"
,
"android_dlopen_ext"
);
hook_dlopen(dlopen,
"libaes.so"
, set_read_write_break);
hook_dlopen(android_dlopen_ext,
"libaes.so"
, set_read_write_break);
function set_read_write_break(){
/
/
实现一个异常回调 处理好这个异常就可以正常返回
Process.setExceptionHandler(function(details) {
console.log(JSON.stringify(details, null,
2
));
console.log(
"lr"
, DebugSymbol.fromAddress(details.context.lr));
console.log(
"pc"
, DebugSymbol.fromAddress(details.context.pc));
Memory.protect(details.memory.address, Process.pointerSize,
'rwx'
);
console.log(Thread.backtrace(details.context, Backtracer.ACCURATE).
map
(DebugSymbol.fromAddress).join(
'\n'
)
+
'\n'
);
return
true;
});
var addr
=
Module.findBaseAddress(
"libaes.so"
).add(
0x6666
);
Memory.protect(addr,
8
,
'---'
);
/
/
修改内存页的权限
/
*
*
*
比如有一个地址是
0x12345678
我想看一下是那个代码去访问了这个地址
*
我只需要把这个内存地址置空 有函数去访问这个地址时 就会触发非法访问异常
*
比较鸡肋这种方法 这种方法会一次修改一个内存页 并且触发一次就无效了
*
/
}
}
1.
注入进程
ptrace
dlopen
2.hook
目标函数
2.1
Java Hook
Static Field Hook:静态成员hook
Method Hook:函数hook
2.2
Native So Hook
GOT Hook:全局偏移表hook
SYM Hook:符号表hook
Inline Hook:函数内联hook
执行自身代码
获取敏感信息
修改返回值
etc.
1.
注入进程
ptrace
dlopen
2.hook
目标函数
2.1
Java Hook
Static Field Hook:静态成员hook
Method Hook:函数hook
2.2
Native So Hook
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-7-17 16:54
被王麻子本人编辑
,原因: 有点细节补充一下