Frida源码学习

Frida源码学习

在Xposed流行的年代,我是不太喜欢frida的,主要原因就是不太喜欢用javascript写代码和持久化方案不太方便。

后边尝试用过几次后,就回不去了,不用重启手机、应用,就能随时hook,真香!

但现在很多App都对Frida进行检测,所以想通过学习源码,打造一款“免杀”版的frida。

frida-core

frida-core/lib/gadget/gadget.vala

1
2
3
4
5
6
7
8
9
10
public async AgentSessionId attach (uint pid, HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
log_info("public async AgentSessionId attach (uint pid, HashTable<string, Variant> options....");
validate_pid (pid);

if (resume_on_attach)
Frida.Gadget.resume ();

return yield parent.attach (options, this, cancellable);
}
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
private async AgentSessionId attach (HashTable<string, Variant> options, ControlChannel requester,
Cancellable? cancellable) throws Error, IOError {
var opts = SessionOptions._deserialize (options);
if (opts.realm != NATIVE)
throw new Error.NOT_SUPPORTED ("Only native realm is supported when embedded");

var id = AgentSessionId.generate ();

DBusConnection controller_connection = requester.connection;

AgentMessageSink sink;
try {
sink = yield controller_connection.get_proxy (null, ObjectPath.for_agent_message_sink (id),
DO_NOT_LOAD_PROPERTIES, cancellable);
} catch (IOError e) {
throw_dbus_error (e);
}

MainContext dbus_context = yield get_dbus_context ();

var session = new LiveAgentSession (this, id, opts.persist_timeout, sink, dbus_context);
sessions[id] = session;
session.closed.connect (on_session_closed);
session.script_eternalized.connect (on_script_eternalized);

try {
session.registration_id = controller_connection.register_object (ObjectPath.for_agent_session (id),
(AgentSession) session);
} catch (IOError io_error) {
assert_not_reached ();
}

session.controller = requester;

requester.sessions.add (id);

return id;
}

frida-core/src/control-service.vala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private async AgentSessionId attach (uint pid, HashTable<string, Variant> options, ControlChannel requester,
Cancellable? cancellable) throws Error, IOError {
AgentSessionId id;
try {
id = yield host_session.attach (pid, options, cancellable);
} catch (GLib.Error e) {
throw_dbus_error (e);
}

requester.sessions.add (id);

var opts = SessionOptions._deserialize (options);

var entry = new AgentSessionEntry (requester, id, opts.persist_timeout, io_cancellable);
sessions[id] = entry;
entry.expired.connect (on_agent_session_expired);

yield link_session (id, entry, requester, cancellable);

return id;
}
1
2
3
4
5
6
7
frida_base_agent_session_real_create_script(FridaAgentSession *base,const gchar *source,GHashTable *options, GCancellable *cancellable,GAsyncReadyCallback _callback_, gpointer _user_data_)

frida_base_agent_session_real_create_script_co(FridaBaseAgentSessionCreateScriptData *_data_)

frida_script_engine_create_script(FridaScriptEngine *self, const gchar *source, GBytes *bytes, FridaScriptOptions *options, GAsyncReadyCallback _callback_, gpointer _user_data_)

frida_script_engine_create_script_co(FridaScriptEngineCreateScriptData *_data_)
1
2
3
4
5
6
7
8
gum_script_backend_create(
GumScriptBackend *self,
const gchar *name,
const gchar *source,
GBytes *snapshot,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)

frida_base_agent_session_real_create_script

frida_script_engine_create_script

image-20230224152650389

https://github.com/MagicHavoc/Havoc-Study/blob/f6eadae1487bc1662565e11f0f6ec6ff85ca4163/fuzzers/AFL%2B%2B/afl%2B%2B-socket/frida_mode/src/js/js.c

https://mesonbuild.com/Build-targets.html

frida-gum

gum_script_backend_create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gum_quick_script_backend_iface_init: 初始化接口指针(如iface->create)
gum_script_backend_create
gum_quick_script_backend_create
gum_create_script_task_new
gum_script_task_new //传入gum_create_script_task_run函数指针, task->func = gum_create_script_task_run;
gum_script_task_set_task_data //设置js代码等数据
gum_script_task_run_in_js_thread
gum_script_scheduler_push_job_on_js_thread
gum_script_job_new
g_idle_source_new
g_source_set_priority
g_source_set_callback
g_source_attach



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
static void gum_quick_script_backend_iface_init (gpointer g_iface,
gpointer iface_data)
{
GumScriptBackendInterface * iface = g_iface;

iface->create = gum_quick_script_backend_create; //<--------------------
iface->create_finish = gum_quick_script_backend_create_finish;
iface->create_sync = gum_quick_script_backend_create_sync;
iface->create_from_bytes = gum_quick_script_backend_create_from_bytes;
iface->create_from_bytes_finish =
gum_quick_script_backend_create_from_bytes_finish;
iface->create_from_bytes_sync =
gum_quick_script_backend_create_from_bytes_sync;

iface->compile = gum_quick_script_backend_compile;
iface->compile_finish = gum_quick_script_backend_compile_finish;
iface->compile_sync = gum_quick_script_backend_compile_sync;
iface->snapshot = gum_quick_script_backend_snapshot;
iface->snapshot_finish = gum_quick_script_backend_snapshot_finish;
iface->snapshot_sync = gum_quick_script_backend_snapshot_sync;

iface->with_lock_held = gum_quick_script_backend_with_lock_held;
iface->is_locked = gum_quick_script_backend_is_locked;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void gum_quick_script_backend_create (GumScriptBackend * backend,
const gchar * name,
const gchar * source,
GBytes * snapshot,
GCancellable * cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GumQuickScriptBackend * self;
GumScriptTask * task;

self = GUM_QUICK_SCRIPT_BACKEND (backend);

task = gum_create_script_task_new (self, name, source, snapshot, cancellable,
callback, user_data);
gum_script_task_run_in_js_thread (task, self->scheduler);
g_object_unref (task);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static GumScriptTask * gum_create_script_task_new (GumQuickScriptBackend * backend,
const gchar * name,
const gchar * source,
GBytes * snapshot,
GCancellable * cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GumScriptTask * task;
GumCreateScriptData * d;

d = g_slice_new (GumCreateScriptData);
d->name = g_strdup (name);
d->source = g_strdup (source);
d->snapshot = (snapshot != NULL) ? g_bytes_ref (snapshot) : NULL;

task = gum_script_task_new ((GumScriptTaskFunc) gum_create_script_task_run,
backend, cancellable, callback, user_data);
gum_script_task_set_task_data (task, d,
(GDestroyNotify) gum_create_script_data_free);

return task;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GumScriptTask * gum_script_task_new (GumScriptTaskFunc func,
gpointer source_object,
GCancellable * cancellable,
GAsyncReadyCallback callback,
gpointer callback_data)
{
GumScriptTask * task;

task = g_object_new (GUM_TYPE_SCRIPT_TASK, NULL);

task->func = func;
task->source_object =
(source_object != NULL) ? g_object_ref (source_object) : NULL;
task->cancellable =
(cancellable != NULL) ? g_object_ref (cancellable) : NULL;
task->callback = callback;
task->callback_data = callback_data;

task->context = g_main_context_ref_thread_default ();

return task;
}

1
2
3
4
5
6
7
void gum_script_task_run_in_js_thread (GumScriptTask * self,
GumScriptScheduler * scheduler)
{
gum_script_scheduler_push_job_on_js_thread (scheduler, G_PRIORITY_DEFAULT,
(GumScriptJobFunc) gum_script_task_run, g_object_ref (self),
g_object_unref);
}
1
2
3
4
5
6
7
8
static void gum_script_task_run (GumScriptTask * self)
{
if (self->cancellable == NULL ||
!g_cancellable_is_cancelled (self->cancellable))
{
self->func (self, self->source_object, self->task_data, self->cancellable);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void
gum_script_scheduler_push_job_on_js_thread (GumScriptScheduler * self,
gint priority,
GumScriptJobFunc func,
gpointer data,
GDestroyNotify data_destroy)
{
GumScriptJob * job;
GSource * source;

job = gum_script_job_new (self, func, data, data_destroy);

source = g_idle_source_new ();
g_source_set_priority (source, priority);
g_source_set_callback (source,
(GSourceFunc) gum_script_scheduler_perform_js_job,
job,
(GDestroyNotify) gum_script_job_free);
g_source_attach (source, self->js_context);
g_source_unref (source);

gum_script_scheduler_start (self);
}

gum_create_script_task_run

1
2
3
4
gum_create_script_task_run
g_object_new
gum_quick_script_create_context
gum_script_task_return_pointer
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
static void
gum_create_script_task_run (GumScriptTask * task,
GumQuickScriptBackend * self,
GumCreateScriptData * d,
GCancellable * cancellable)
{
GumQuickScript * script;
GError * error = NULL;

if (d->snapshot != NULL)
{
gum_script_task_return_error (task,
g_error_new (GUM_ERROR, GUM_ERROR_NOT_SUPPORTED,
"snapshots are not supported by the QuickJS runtime"));
return;
}

script = g_object_new (GUM_QUICK_TYPE_SCRIPT,
"name", d->name,
"source", d->source,
"main-context", gum_script_task_get_context (task),
"backend", self,
NULL);

gum_quick_script_create_context (script, &error);

if (error == NULL)
{
gum_script_task_return_pointer (task, script, g_object_unref);
}
else
{
gum_script_task_return_error (task, error);
g_object_unref (script);
}
}
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
gboolean
gum_quick_script_create_context (GumQuickScript * self,
GError ** error)
{
GumQuickCore * core = &self->core;
JSRuntime * rt;
JSContext * ctx;
GumESProgram * program;
JSValue global_obj;
GumQuickScope scope = { core, NULL, };

g_assert (self->ctx == NULL);

rt = gum_quick_script_backend_make_runtime (self->backend);
JS_SetRuntimeOpaque (rt, core);

ctx = JS_NewContext (rt);
JS_SetContextOpaque (ctx, core);

if (self->bytecode != NULL)
{
program = gum_quick_script_backend_read_program (self->backend, ctx,
self->bytecode, error);
}
else
{
program = gum_quick_script_backend_compile_program (self->backend, ctx,
self->name, self->source, error);
}
if (program == NULL)
goto malformed_program;

self->rt = rt;
self->ctx = ctx;
self->program = program;

global_obj = JS_GetGlobalObject (ctx);

JS_DefinePropertyValueStr (ctx, global_obj, "global",
JS_DupValue (ctx, global_obj), JS_PROP_C_W_E);

_gum_quick_core_init (core, self, ctx, global_obj,
gum_quick_script_backend_get_scope_mutex (self->backend),
program, gumjs_frida_source_map, &self->interceptor, &self->stalker,
gum_quick_script_emit,
gum_quick_script_backend_get_scheduler (self->backend));

core->current_scope = &scope;

_gum_quick_kernel_init (&self->kernel, global_obj, core);
_gum_quick_memory_init (&self->memory, global_obj, core);
_gum_quick_module_init (&self->module, global_obj, core);
_gum_quick_process_init (&self->process, global_obj, &self->module, core);
_gum_quick_thread_init (&self->thread, global_obj, core);
_gum_quick_file_init (&self->file, global_obj, core);
_gum_quick_checksum_init (&self->checksum, global_obj, core);
_gum_quick_stream_init (&self->stream, global_obj, core);
_gum_quick_socket_init (&self->socket, global_obj, &self->stream, core);
#ifdef HAVE_SQLITE
_gum_quick_database_init (&self->database, global_obj, core);
#endif
_gum_quick_interceptor_init (&self->interceptor, global_obj, core);
_gum_quick_api_resolver_init (&self->api_resolver, global_obj, core);
_gum_quick_symbol_init (&self->symbol, global_obj, core);
_gum_quick_cmodule_init (&self->cmodule, global_obj, core);
_gum_quick_instruction_init (&self->instruction, global_obj, core);
_gum_quick_code_writer_init (&self->code_writer, global_obj, core);
_gum_quick_code_relocator_init (&self->code_relocator, global_obj,
&self->code_writer, &self->instruction, core);
_gum_quick_stalker_init (&self->stalker, global_obj, &self->code_writer,
&self->instruction, core);

JS_FreeValue (ctx, global_obj);

core->current_scope = NULL;

g_free (self->source);
self->source = NULL;

g_bytes_unref (self->bytecode);
self->bytecode = NULL;

return TRUE;

malformed_program:
{
JS_FreeContext (ctx);
JS_FreeRuntime (rt);

return FALSE;
}
}

编译 frida-gum

环境准备

1
2
3
4
5
6
7
8
9
10
11
apt-get update
apt-get install -y gobject-introspection libdwarf-dev libelf-dev libgirepository1.0-dev libglib2.0-dev libjson-glib-dev libsqlite3-dev libunwind-dev ninja-build
pip3 install meson==0.64.0


directory=$(pwd)
git clone -b 16.0.9 --recurse-submodules https://github.com/frida/frida.git
wget https://dl.google.com/android/repository/android-ndk-r25c-linux.zip && unzip android-ndk-r25c-linux.zip
export ANDROID_NDK_LATEST_HOME="${directory}/android-ndk-r25c"
cd ${directory}/frida/frida-gum

patch

https://gist.github.com/xjohjrdy/e652d910b945dc2ec61809bc8b1a0dcc

1
git diff bindings/gumjs/gumscriptscheduler.c tests/meson.build tests/gumtest.c gum/backend-linux/gumandroid.c > /tmp/frida_gum.patch
1
git apply /tmp/frida_gum.patch

编译

1
2
3
4
5
6
7
8
9
.github/env/bootstrap.sh linux-x86_64 android-arm64
# .github/env/bootstrap.sh linux-x86_64 android-arm

meson setup --native-file /tmp/native.txt --cross-file /tmp/cross.txt --default-library static -Doptimization=s -Dwerror=true -Dgumpp=disabled -Dgumjs=enabled -Dtests=enabled build
meson compile -C build

$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip build/tests/libgum-tests.so
readlink -f build/tests/libgum-tests.so
file build/tests/libgum-tests.so

运行测试用例

1
2
3
4
5
6
7
8
# on ubuntu
tar -C build/tests -czf /tmp/runner.tar.gz gum-tests data/
adb push /tmp/runner.tar.gz /data/local/tmp

# on pixel
cd /data/local/tmp
tar xf runner.tar.gz
LD_LIBRARY_PATH=/apex/com.android.art/lib64:/apex/com.android.runtime/lib64 ./gum-tests

编译frida-gadget

1
2
3
4
5
6
7
8
git clone -b 16.0.9 --recurse-submodules https://github.com/frida/frida.git
cd frida/frida-gum
git apply /tmp/frida_gadget.patch
cd ..
make core-android-arm64
#make core-android-arm

find . -type f -name "frida-gadget.so"

patch

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
diff --git a/bindings/gumjs/gumscriptscheduler.c b/bindings/gumjs/gumscriptscheduler.c
index cfd7befe..bfa3dab7 100644
--- a/bindings/gumjs/gumscriptscheduler.c
+++ b/bindings/gumjs/gumscriptscheduler.c
@@ -114,7 +114,7 @@ gum_script_scheduler_start (GumScriptScheduler * self)
{
self->js_loop = g_main_loop_new (self->js_context, TRUE);

- self->js_thread = g_thread_new ("gum-js-loop",
+ self->js_thread = g_thread_new ("yyy-xx-l00p",
(GThreadFunc) gum_script_scheduler_run_js_loop, self);
}
}
diff --git a/gum/backend-linux/gumandroid.c b/gum/backend-linux/gumandroid.c
index b479b3dc..a711baeb 100644
--- a/gum/backend-linux/gumandroid.c
+++ b/gum/backend-linux/gumandroid.c
@@ -15,6 +15,10 @@
#include <pthread.h>
#include <string.h>
#include <sys/system_properties.h>
+#include <android/log.h>
+#define LOGTAG "GUM_TEST"
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOGTAG, __VA_ARGS__)
+

#if (defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 4) || defined (HAVE_ARM)
# define GUM_ANDROID_LEGACY_SOINFO 1
@@ -982,10 +986,22 @@ gum_store_module_handle_if_name_matches (const GumSoinfoDetails * details,
flags |= RTLD_NOLOAD;
}

+
if (api->dlopen != NULL)
{
/* API level >= 26 (Android >= 8.0) */
- ctx->module = api->dlopen (details->path, flags, caller_addr);
+ LOGD("caller_addr11111");
+ LOGD("path: %s", details->path);
+ LOGD("caller_addr: %p", caller_addr);
+ if(strstr(details->path, "libc.so")){
+ ctx->module = api->dlopen ("libc.so", flags, caller_addr+0x2100);
+ }else if(strstr(details->path, "libart.so")){
+ ctx->module = api->dlopen ("libart.so", flags, caller_addr+0x2100);
+ }
+ else{
+ ctx->module = api->dlopen (details->path, flags, caller_addr+0x2100);
+ }
+
}
else if (api->do_dlopen != NULL)
{