frida-gum代码阅读笔记

0x00 : 前言与预备知识

frida : frida是一个优秀的跨平台Dynamic instrumentation toolkit,具体可以看官网介绍

GObject对象系统

GObject这个比较重要,因为frida框架底层的hook框架Frida-gum是纯c写的,为了实现一些面向对象的编程,使用了Gobject。

注:本篇主要是看interceptor这种hook方式,针对函数头,之后会有一篇针对Stalker 模式的分析。

0x01 : 项目构架

直接拉下来的代码如下

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
╭─muhe@muheMacBookPro ~/Code/frida ‹master*›
╰─$ l
total 137608
drwxr-xr-x 29 muhe staff 928B Nov 13 17:22 .
drwxr-xr-x 90 muhe staff 2.8K Nov 5 16:44 ..
drwxr-xr-x 15 muhe staff 480B Nov 15 18:23 .git
-rw-r--r-- 1 muhe staff 383B Jan 21 2019 .gitignore
-rw-r--r-- 1 muhe staff 886B Jan 21 2019 .gitmodules
drwxr-xr-x 3 muhe staff 96B Nov 13 17:22 .vscode
-rw-r--r-- 1 muhe staff 2.4K Jan 21 2019 COPYING
-rw-r--r-- 1 muhe staff 1.2K Nov 7 18:11 Makefile
-rw-r--r-- 1 muhe staff 28K Nov 7 18:11 Makefile.linux.mk
-rw-r--r-- 1 muhe staff 28K Nov 7 18:11 Makefile.macos.mk
-rw-r--r-- 1 muhe staff 21K Nov 7 18:11 Makefile.sdk.mk
-rw-r--r-- 1 muhe staff 84K Apr 29 2019 Makefile.toolchain.mk
-rw-r--r-- 1 muhe staff 1.7K Nov 7 18:11 README.md
drwxr-xr-x 10 muhe staff 320B Nov 11 14:59 build
drwxr-xr-x 61 muhe staff 1.9K Jan 21 2019 capstone
-rw-r--r-- 1 muhe staff 1.0K Nov 7 18:11 config.mk
drwxr-xr-x 9 muhe staff 288B Jan 21 2019 frida-clr
drwxr-xr-x 21 muhe staff 672B Jan 21 2019 frida-core
drwxr-xr-x 20 muhe staff 640B Nov 11 17:37 frida-gum
drwxr-xr-x 15 muhe staff 480B Jan 21 2019 frida-node
drwxr-xr-x 20 muhe staff 640B Jan 21 2019 frida-python
drwxr-xr-x 27 muhe staff 864B Jan 21 2019 frida-qml
drwxr-xr-x 10 muhe staff 320B Jan 21 2019 frida-swift
drwxr-xr-x 12 muhe staff 384B Jan 21 2019 frida-tools
-rw-r--r-- 1 muhe staff 25K Nov 7 18:11 frida.sln
-rw-r--r-- 1 muhe staff 9.0K Nov 11 14:43 frida.srctrlbm
-rw-r--r-- 1 muhe staff 67M Nov 11 14:43 frida.srctrldb
-rw-r--r-- 1 muhe staff 6.1K Nov 11 14:35 frida.srctrlprj
drwxr-xr-x 47 muhe staff 1.5K Nov 7 18:11 releng

frida-gum是底层hook框架,跨平台;

frida-python , frida-node啥的是 bindings,暂时不管,不理解原理看也看不懂;

capstone 牛逼的反汇编框架,frida-gum中用到了,用于指令的读;

releng 编译相关的;

frida-core server/agent相关;

frida-tools 一些工具,比如frida-ps啥的。

重点是frida-gum ,这是理解这个框架的基础。

0x02 : 阅读frida-gum (x86为例)

frida-gum 注释并不多,甚至可以说几乎没,好在他代码写得好,构架合理代码规范好,所以阅读起来多读几遍,总会看懂的。

2.1. 构架

这个的框架的构架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
total 200
drwxr-xr-x 20 muhe staff 640B Nov 11 17:37 .
drwxr-xr-x 29 muhe staff 928B Nov 13 17:22 ..
-rw-r--r-- 1 muhe staff 34B Jan 21 2019 .git
-rw-r--r-- 1 muhe staff 70B Jan 21 2019 .gitignore
drwxr-xr-x 3 muhe staff 96B Nov 11 17:37 .vscode
-rw-r--r-- 1 muhe staff 5.6K Jan 21 2019 COPYING
drwxr-xr-x 5 muhe staff 160B Jan 21 2019 bindings
-rw-r--r-- 1 muhe staff 2.1K Jan 21 2019 config.h.in
drwxr-xr-x 3 muhe staff 96B Jan 21 2019 ext
drwxr-xr-x 85 muhe staff 2.7K Jan 21 2019 gum
-rw-r--r-- 1 muhe staff 5.1K Jan 21 2019 gum-32.vcxproj
-rw-r--r-- 1 muhe staff 16K Jan 21 2019 gum-32.vcxproj.filters
-rw-r--r-- 1 muhe staff 5.1K Jan 21 2019 gum-64.vcxproj
-rw-r--r-- 1 muhe staff 16K Jan 21 2019 gum-64.vcxproj.filters
-rw-r--r-- 1 muhe staff 8.5K Jan 21 2019 gum-common.props
drwxr-xr-x 4 muhe staff 128B Jan 21 2019 libs
-rw-r--r-- 1 muhe staff 6.8K Jan 21 2019 meson.build
-rw-r--r-- 1 muhe staff 190B Jan 21 2019 meson_options.txt
drwxr-xr-x 28 muhe staff 896B Jan 21 2019 tests
drwxr-xr-x 7 muhe staff 224B Jan 21 2019 vapi

核心是在gum目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
gum
├── arch-arm
├── arch-arm64
├── arch-mips
├── arch-x86
├── backend-arm
├── backend-arm64
├── backend-darwin
├── backend-dbghelp
├── backend-elf
├── backend-libdwarf
├── backend-libunwind
├── backend-linux
├── backend-mips
├── backend-posix
├── backend-qnx
├── backend-windows
└── backend-x86
....// gum下其他文件

这里有必要说一下,frida-gum 为了实现跨平台,抽象出来 构架无关/平台无关/系统无关的api,比如一些内存操作,在frida-gum里可能就是gum_xxxxx,但是根据不同平台,调用到对应平台的api里去,正是做了很好的封装,上层代码才会看起来“平台无关”。

还有几个核心的对象,后面的代码里频繁提及:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct _GumInterceptor
{
GObject parent;

GRecMutex mutex;

GHashTable * function_by_address;

GumInterceptorBackend * backend;
GumCodeAllocator allocator;

volatile guint selected_thread_id;

GumInterceptorTransaction current_transaction;
};

从这个拦截器类索引出去的对象都需要好好注意,比如 GumInterceptorBackend , 最好可以生成一个uml图,阅读代码的时候对比着看。

2.2. 代码阅读

2.2.1 准备工作

面对比较大的代码,重要的是找到一个入口,从这个点开始读,我这里大概看了下单元测试的代码,发现基本是: 初始化,测试各种功能,清理,退出。

那么我的阅读思路就是 :

  1. 初始化部分
  2. 各种功能,比如 内存模块,指令读写模块,代码修复模块
  3. 清理 这部分大概过一下就行

这里我参考了 jmpews师傅的关于设计hook框架的文章,了解一个hook框架如何设计,分哪些模块,在阅读代码的时候能够有针对性一些。

  • 内存分配 模块
  • 指令写 模块
  • 指令读 模块
  • 指令修复 模块 relocator
  • 跳板 模块
  • 调度器 模块 enter_thunk部分实现
  • 栈 模块

具体可以参考他的文章: 如何构建一款像 frida 一样的框架

2.2.2 hook从0到1

阅读顺序根据单元测试gum-test.c确定的,具体的可以看代码

gum_interceptor_obtain()

这部分是 拦截器初始化

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
// 初始化 interceptor 对象初始化
GumInterceptor *
gum_interceptor_obtain (void)
{
GumInterceptor * interceptor;

g_mutex_lock (&_gum_interceptor_lock);

if (_the_interceptor != NULL)
{
interceptor = GUM_INTERCEPTOR (g_object_ref (_the_interceptor));
}
else
{
_the_interceptor = g_object_new (GUM_TYPE_INTERCEPTOR, NULL);
g_object_weak_ref (G_OBJECT (_the_interceptor),
the_interceptor_weak_notify, NULL);

interceptor = _the_interceptor;
}

g_mutex_unlock (&_gum_interceptor_lock);

return interceptor;
}


static void
gum_interceptor_init (GumInterceptor * self)
{
g_rec_mutex_init (&self->mutex);

self->function_by_address = g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) gum_function_context_destroy);

// 分配器初始化
gum_code_allocator_init (&self->allocator, GUM_INTERCEPTOR_CODE_SLICE_SIZE);

// 创建拦截器后端
self->backend = _gum_interceptor_backend_create (&self->allocator);

gum_interceptor_transaction_init (&self->current_transaction, self);
}

因为GObject的使用,gum_interceptor_init 这个构造函数,在 interceptor对象创建出来的时候触发。

重点看拦截器后端的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GumInterceptorBackend *
_gum_interceptor_backend_create (GumCodeAllocator * allocator)
{
GumInterceptorBackend * backend;

backend = g_slice_new (GumInterceptorBackend);
backend->allocator = allocator;

//初始化 codewriter和relocator
gum_x86_writer_init (&backend->writer, NULL);
gum_x86_relocator_init (&backend->relocator, NULL, &backend->writer);

// 创建 thunk
gum_interceptor_backend_create_thunks (backend);

return backend;
}

这里初始化的writerrelocator分别用于指令写和指令恢复。

thunks的初始化,这两个是用于调度执行,分别对应 进入hook和离开hook。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void
gum_interceptor_backend_create_thunks (GumInterceptorBackend * self)
{
GumX86Writer * cw = &self->writer;

self->enter_thunk = gum_code_allocator_alloc_slice (self->allocator);
gum_x86_writer_reset (cw, self->enter_thunk->data);
gum_emit_enter_thunk (cw);
gum_x86_writer_flush (cw);
g_assert_cmpuint (gum_x86_writer_offset (cw), <=, self->enter_thunk->size);

self->leave_thunk = gum_code_allocator_alloc_slice (self->allocator);
gum_x86_writer_reset (cw, self->leave_thunk->data);
gum_emit_leave_thunk (cw);
gum_x86_writer_flush (cw);
g_assert_cmpuint (gum_x86_writer_offset (cw), <=, self->leave_thunk->size);
}

因为原理类似,只举例enter_thunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void
gum_emit_enter_thunk (GumX86Writer * cw)
{
const gssize return_address_stack_displacement = 0;
// save ret addr
gum_emit_prolog (cw, return_address_stack_displacement);

// 构造自己的函数栈
gum_x86_writer_put_lea_reg_reg_offset (cw, GUM_REG_XSI,
GUM_REG_XBP, GUM_FRAME_OFFSET_CPU_CONTEXT);
gum_x86_writer_put_lea_reg_reg_offset (cw, GUM_REG_XDX,
GUM_REG_XBP, GUM_FRAME_OFFSET_TOP);
gum_x86_writer_put_lea_reg_reg_offset (cw, GUM_REG_XCX,
GUM_REG_XBP, GUM_FRAME_OFFSET_NEXT_HOP);

gum_x86_writer_put_call_address_with_aligned_arguments (cw, GUM_CALL_CAPI,
GUM_ADDRESS (_gum_function_context_begin_invocation), 4,
GUM_ARG_REGISTER, GUM_REG_XBX,
GUM_ARG_REGISTER, GUM_REG_XSI,
GUM_ARG_REGISTER, GUM_REG_XDX,
GUM_ARG_REGISTER, GUM_REG_XCX);

gum_emit_epilog (cw);
}
gum_interceptor_attach_listener
1
2
3
4
5
6
7
8
9
10
11
GumAttachReturn
gum_interceptor_attach_listener (GumInterceptor * self,
gpointer function_address,
GumInvocationListener * listener,
gpointer listener_function_data)
{

...


}
gum_interceptor_transaction_begin
gum_interceptor_instrument ✨

这里要说的是 function_address 就是要hook的目标函数,frida-gum把要hook的目标封装成了 GumFunctionContext对象,方便操作

1
2
3
function_address = gum_interceptor_resolve (self, function_address); // ?
// 创建跳板
function_ctx = gum_interceptor_instrument (self, function_address);
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
static GumFunctionContext *
gum_interceptor_instrument (GumInterceptor * self,
gpointer function_address)
{
GumFunctionContext * ctx;

// 要hook的函数,封装成了 GumFunctionContext,此时
// 根据 地址,得到与之对应的 GunFunctionContext对象
ctx = (GumFunctionContext *) g_hash_table_lookup (self->function_by_address,
function_address);
if (ctx != NULL)
return ctx;
// 如果获取到的是空的对象,必须初始化了才能使用
// 只写几个字断,分配内存/hook的函数地址/interceptor成员
ctx = gum_function_context_new (self, function_address);
if (ctx == NULL)
return NULL;
// 创建跳板
if (!_gum_interceptor_backend_create_trampoline (self->backend, ctx))
{
gum_function_context_finalize (ctx);
return NULL;
}

// 设置完成后, 添加到哈希表
// hash_table, key, value
// hook函数地址,GumFunctionContext对象对应, 方便查找
g_hash_table_insert (self->function_by_address, function_address, ctx);

// 当前 transaction 添加到 任务中, 设置回调 函数 gum_interceptor_activate 拦截器激活函数
gum_interceptor_transaction_schedule_prologue_write (
&self->current_transaction, ctx, gum_interceptor_activate);

return ctx;
}

这里贴一下跳板代码方便理解:

1
2
3
4
5
6
7
8
9
10
00C30200  mov         al,byte ptr ds:[FF00C121h]  
00C30205 xor eax,0C30200h
00C3020A jmp 00C30000 // 跳到上面的 enter_thunk
00C3020F push dword ptr ds:[0C30200h]
00C30215 jmp 00C30100 // 跳到 leave_thunk
// 原函数修复的指令,7个字节
00C3021A push ebp
00C3021B mov ebp,esp
00C3021D cmp dword ptr [ebp+8],0
00C30221 jmp gum_test_target_function+7h (0D6FB97h) // 跳回原函数,因为写跳转用了7字节,所以+7
gum_interceptor_transaction_end
1
2
3
// 当前 transaction 添加到 任务中, 设置回调 函数 gum_interceptor_activate 拦截器激活函数
gum_interceptor_transaction_schedule_prologue_write (
&self->current_transaction, ctx, gum_interceptor_activate);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 拦截器激活
static void
gum_interceptor_activate (GumInterceptor * self,
GumFunctionContext * ctx,
gpointer prologue)
{
if (ctx->destroyed)
return;

g_assert (!ctx->activated);
ctx->activated = TRUE;

// 激活
_gum_interceptor_backend_activate_trampoline (self->backend, ctx,
prologue);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
_gum_interceptor_backend_activate_trampoline (GumInterceptorBackend * self,
GumFunctionContext * ctx,
gpointer prologue)
{
GumX86Writer * cw = &self->writer;
guint padding;

// 设置base
gum_x86_writer_reset (cw, prologue);
// 设置pc
cw->pc = GPOINTER_TO_SIZE (ctx->function_address);
// 写jmp, 跳转到 跳板中, 进入跳板这已经到hook里了
gum_x86_writer_put_jmp_address (cw, GUM_ADDRESS (ctx->on_enter_trampoline));
gum_x86_writer_flush (cw);
g_assert_cmpint (gum_x86_writer_offset (cw),
<=, GUM_INTERCEPTOR_REDIRECT_CODE_SIZE);

// 原本代码(hook点),剩余的地方nop补齐
padding = ctx->overwritten_prologue_len - gum_x86_writer_offset (cw);
for (; padding != 0; padding--)
gum_x86_writer_put_nop (cw);
gum_x86_writer_flush (cw);
}

2.2.3 执行流程

通过设置函数返回地址(__gum_function_context_begin/end_invocation),控制流程,这就是ROP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
原函数
----------------------------------------------------
跳板 02C80204
----------------------------------------------------
`enter_chunk` // 首先要保存现场, 构造栈帧,随后进入下一个函数 ⬇️
`__gum_function_context_begin_invocation` // 通过设置栈(ret addr)控制执行流程
----------------------------------------------------
replacement_function
----------------------------------------------------
跳板 02C8020F
----------------------------------------------------
`leave_chunk`
`__gum_function_context_end_invocation`
----------------------------------------------------
继续执行

0x03 : 调试分析帮助理解

这里调试了单元测试中写hook和函数替换的逻辑,过程如下:

_gum_interceptor_backend_create()

后端初始化,初始化两个thunk

enter_thunk

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
00C30000  pushfd  
00C30001 cld
00C30002 pushad
00C30003 lea esp,[esp-4]
00C3000A lea eax,[esp+2Ch]
00C30011 mov dword ptr [esp+10h],eax
00C30015 mov ebx,dword ptr [esp+28h]
00C30019 mov ebp,esp
00C3001B and esp,0FFFFFFF0h
00C30021 sub esp,200h
00C30027 fxsave [esp]
00C3002B lea esi,[ebp]
00C30031 lea edx,[ebp+2Ch]
00C30037 lea ecx,[ebp+28h]
00C3003D push ecx
00C3003E push edx
00C3003F push esi
00C30040 push ebx
00C30041 call __gum_function_context_begin_invocation (0CE8E1Fh)
00C30046 add esp,10h
00C30049 fxrstor [esp]
00C3004D mov esp,ebp
00C3004F lea esp,[esp+4]
00C30056 popad
00C30057 popfd
00C30058 ret

leave_thunk

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
00C30100  pushfd  
00C30101 cld
00C30102 pushad
00C30103 lea esp,[esp-4]
00C3010A lea eax,[esp+28h]
00C30111 mov dword ptr [esp+10h],eax
00C30115 mov ebx,dword ptr [esp+28h]
00C30119 mov ebp,esp
00C3011B and esp,0FFFFFFF0h
00C30121 sub esp,200h
00C30127 fxsave [esp]
00C3012B lea esi,[ebp]
00C30131 lea edx,[ebp+28h]
00C30137 sub esp,4
00C3013A push edx
00C3013B push esi
00C3013C push ebx
00C3013D call __gum_function_context_end_invocation (0CEAB1Bh)
00C30142 add esp,0Ch
00C30145 add esp,4
00C30148 fxrstor [esp]
00C3014C mov esp,ebp
00C3014E lea esp,[esp+4]
00C30155 popad
00C30156 popfd
00C30157 ret
1
2
3
4
5
6
7
8
// hook 构造
GumAttachReturn
gum_interceptor_attach (GumInterceptor * self,
gpointer function_address,
GumInvocationListener * listener,
gpointer listener_function_data)


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
GumAttachReturn
gum_interceptor_attach (GumInterceptor * self,
gpointer function_address,
GumInvocationListener * listener,
gpointer listener_function_data)
{
GumAttachReturn result = GUM_ATTACH_OK;
GumFunctionContext * function_ctx;

if (gum_process_get_code_signing_policy () == GUM_CODE_SIGNING_REQUIRED)
goto policy_violation;

gum_interceptor_ignore_current_thread (self);
GUM_INTERCEPTOR_LOCK (self);
gum_interceptor_transaction_begin (&self->current_transaction);
self->current_transaction.is_dirty = TRUE;

// 获取hook目标函数的地址
function_address = gum_interceptor_resolve (self, function_address);

// 获取这个函数的 GumFunctionContext 对象
// 没有就新建一个
// 这里已经 准备好了跳板,写好了hook
// 添加任务,设置相对应的回调函数
function_ctx = gum_interceptor_instrument (self, function_address);
if (function_ctx == NULL)
goto wrong_signature;

if (gum_function_context_has_listener (function_ctx, listener))
goto already_attached;
// 添加监听器
gum_function_context_add_listener (function_ctx, listener,
listener_function_data);

goto beach;

policy_violation:
{
return GUM_ATTACH_POLICY_VIOLATION;
}
wrong_signature:
{
result = GUM_ATTACH_WRONG_SIGNATURE;
goto beach;
}
already_attached:
{
result = GUM_ATTACH_ALREADY_ATTACHED;
goto beach;
}
beach:
{
// 到这里,基本没什么问题,hook什么都打好了
// 拦截器激活 跳板激活
// 这里把原函数开头改写
gum_interceptor_transaction_end (&self->current_transaction);
GUM_INTERCEPTOR_UNLOCK (self);
gum_interceptor_unignore_current_thread (self);

return result;
}
}

on_invoke_trampoline 跳板

1
2
3
4
5
6
7
8
9
10
00C30200  mov         al,byte ptr ds:[FF00C121h]  
00C30205 xor eax,0C30200h
00C3020A jmp 00C30000 // 跳到上面的 enter_thunk
00C3020F push dword ptr ds:[0C30200h]
00C30215 jmp 00C30100 // 跳到 leave_thunk
// 原函数修复的指令,7个字节
00C3021A push ebp
00C3021B mov ebp,esp
00C3021D cmp dword ptr [ebp+8],0
00C30221 jmp gum_test_target_function+7h (0D6FB97h) // 跳回原函数,因为写跳转用了7字节,所以+7

gum_interceptor_transaction_end (&self->current_transaction); 调用 gum_interceptor_activate() 然后_gum_interceptor_backend_activate_trampolie() 随后,目标函数开头被修改:

1
2
3
4
5
6
7
8
gpointer GUM_NOINLINE
gum_test_target_function (GString * str)
{
00D6FB90 jmp 00C30204
if (str != NULL)
00D6FB95 nop
00D6FB96 nop
00D6FB97 je gum_test_target_function+19h (0D6FBA9h)

直接跳转到 00C30204, 其实就是 跳板,因为反汇编的地址差了点,所以开始的指令不太一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
00C30204  push        dword ptr ds:[0C30200h]  
00C3020A jmp 00C30000
00C3020F push dword ptr ds:[0C30200h]
00C30215 jmp 00C30100
00C3021A push ebp
00C3021B mov ebp,esp
00C3021D cmp dword ptr [ebp+8],0
00C30221 jmp gum_test_target_function+7h (0D6FB97h)
00C30226 add byte ptr [eax],al
00C30228 add byte ptr [eax],al
00C3022A add byte ptr [eax],al
00C3022C add byte ptr [eax],al
00C3022E add byte ptr [eax],al

调用流程调试分析,这里分两个情况,是否存在``replacement_function`

首先是不存在,只是打个hook(根据 TESTCASE(attach_one);)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
call 原函数
----------------------------------------------------
原函数
----------------------------------------------------
跳板
----------------------------------------------------
`enter_chunk` // 首先要保存现场, 构造栈帧,随后进入下一个函数 ⬇️
`__gum_function_context_begin_invocation` // 通过设置栈(ret addr)控制执行流程
----------------------------------------------------
跳板+n (00C3021A) // 执行原函数的 修复的若干字节
----------------------------------------------------
原函数
----------------------------------------------------
`leave_chunk`
`__gum_function_context_end_invocation`
----------------------------------------------------
继续执行....

存在替换的函数(TESTCASE(replace_one);)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
原函数
----------------------------------------------------
跳板 02C80204
----------------------------------------------------
`enter_chunk` // 首先要保存现场, 构造栈帧,随后进入下一个函数 ⬇️
`__gum_function_context_begin_invocation` // 通过设置栈(ret addr)控制执行流程
----------------------------------------------------
replacement_function
----------------------------------------------------
跳板 02C8020F
----------------------------------------------------
`leave_chunk`
`__gum_function_context_end_invocation`
----------------------------------------------------
继续执行

replace_one 的跳板

1
2
3
4
5
6
7
8
9
02C80204  push        dword ptr ds:[2C80200h]  
02C8020A jmp 02C80000
02C8020F push dword ptr ds:[2C80200h]
02C80215 jmp 02C80100
02C8021A mov edi,edi
02C8021C push ebp
02C8021D mov ebp,esp
02C8021F jmp malloc+5h (01E5A7B5h)

0x04 : 结语

这个过程大概花了我一周 5天多的样子,挺难的个人感觉,需要捋清楚的话,配合调试会好很多,最开始我直接看的代码,看+做笔记,脑内debug,最后编译了工程,vs调试,清晰多了,还是建议边调试边看。

如果文中有任何问题,欢迎批评指正 : )

后面可能会在他基础上做点事情吧…这框架真牛逼 !

0x05 : 参考与引用

rida-gum源码解读

gobject c语言

如何构建一款像 frida 一样的框架