<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Frida Internal - Part 2: 核心組件 frida-core

          共 23495字,需瀏覽 47分鐘

           ·

          2022-04-09 20:33

          前文已經(jīng)介紹了 frida 中的核心組件?frida-gum?以及對應(yīng)的 js 接口?gum-js,但僅有這些基礎(chǔ)功能并不能讓 frida 成為如此受歡迎的 Instrumentation (hook) 框架。為了實現(xiàn)一個完善框架或者說工具,需要實現(xiàn)許多系統(tǒng)層的功能。比如進程注入、進程間通信、會話管理、腳本生命周期管理等功能,屏蔽部分底層的實現(xiàn)細節(jié)并給最終用戶提供開箱即用的操作接口。而這一切的實現(xiàn)都在?frida-core?之中,正如名字所言,這其中包含了 frida 相關(guān)的大部分關(guān)鍵模塊和組件,比如 frida-server、frida-gadget、frida-agent、frida-helper、frida-inject 以及之間的互相通信底座。本文主要節(jié)選其中關(guān)鍵的部分進行分析和介紹。

          前情提要:

          Vala

          如果曾經(jīng)瀏覽過 frida-core 的代碼,會發(fā)現(xiàn)其大部分源文件都是?.vala?后綴的。這其實是 GNOME 中使用的一個高級語言,和傳統(tǒng)高級語言不同的是 vala 代碼會被編譯器先編譯成 C 代碼,然后再編譯成二進制文件,因此也可以認為 vala 語言是 C 的一個語法糖拓展。

          Vala[1]?使用 glib 的?GObject?類型系統(tǒng)來構(gòu)造類和接口以實現(xiàn)面向?qū)ο螅湔Z法有點類似于?C#,支持許多現(xiàn)代語言的高級特性,包括但不限于接口、屬性、內(nèi)存管理、異常、lambda、信號等等。Vala 既可以通過 API 文件訪問已有的 C 庫文件,也可以從 C 中很容易調(diào)用 Vala 的方法。

          一個簡單的 Vala 代碼示例如下:

          //?test.vala
          int?main?(string[]?args)?{
          ????stdout.printf?("nargs=%d\n",?args.length);
          ????foreach?(string?arg?in?args)?{
          ????????stdout.printf?("%s\n",?arg);
          ????}
          ????return?0;
          }

          編譯運行也非常簡單:

          $?valac?test.vala
          $?./test?hello?world
          nargs=3
          ./test
          hello
          world

          $?rabin2?-l?test
          [Linked?libraries]
          libglib-2.0.so.0
          libc.so.6

          2?libraries

          Vala 程序也可以直接編譯為 C 代碼,使用?valac -C test.vala?即可生成對應(yīng)的?test.c?文件。更多的示例程序可以查看?Vala 的官方文檔[2]

          接口

          在上文中我們介紹了 frida-gum 和 gum-js,這二者都有對應(yīng)的 release devkit 可以下載,frida-core 也一樣。我們在 frida-core-devkit 中可以獲取到編譯好的靜態(tài)庫、頭文件以及簡單的示例程序,下面就以接口為著手點進行分析。

          以 Android 平臺為例,最常用的方式是先將?frida-server?推送到設(shè)備端啟動,然后在本地使用?frida-tools?去加載 JS 腳本并執(zhí)行 hook 操作。frida-tools?是基于 Python 的 binding 編寫的,本質(zhì)上還是調(diào)用了?frida-core,連接設(shè)備并加載腳本的過程如下所示:

          //?獲取設(shè)備句柄
          FridaManager?*manager?=?frida_device_manager_new?();
          FridaDeviceList?*devices?=?frida_device_manager_enumerate_devices_sync?(manager,?NULL,?&error);
          FridaDevice?*?local_device?=?frida_device_list_get?(devices,?i);

          //?連接設(shè)備上的指定進程
          FridaSession?*session?=?frida_device_attach_sync?(local_device,?target_pid,?FRIDA_REALM_NATIVE,?NULL,?&error);

          //?創(chuàng)建腳本
          options?=?frida_script_options_new?();
          frida_script_options_set_name?(options,?"example");
          frida_script_options_set_runtime?(options,?FRIDA_SCRIPT_RUNTIME_QJS);
          script?=?frida_session_create_script_sync?(session,
          ?????????"console.log('hello');",
          ?????????options,?NULL,?&error);

          //?加載腳本
          g_signal_connect?(script,?"message",?G_CALLBACK?(on_message),?NULL);
          frida_script_load_sync?(script,?NULL,?&error);

          雖然這里用的是 C 的接口,但實際上代碼是在?vala?中以類方法的方式定義的,以?frida_device_attach_sync?這個方法為例,其定義在?src/frida.vala?中:

          namespace?{
          ????//?...
          ????public?class?Device?:?Object?{
          ????????//?...
          ????????public?Session?attach_sync?(uint?pid,?SessionOptions??options?=?null,
          ????????????????Cancellable??cancellable?=?null)?throws?Error,?IOError?{
          ????????????var?task?=?create<AttachTask>?();
          ????????????task.pid?=?pid;
          ????????????task.options?=?options;
          ????????????return?task.execute?(cancellable);
          ????????}
          ????}
          }

          因此 vala 編譯后的 C 代碼符號也是可以比較容易對應(yīng)到實際代碼的,這對于開發(fā)和調(diào)試有很大的幫助。

          frida-server

          雖然連接、加載腳本的邏輯是在 PC 端編寫和執(zhí)行的,但實際操作還是在設(shè)備端,即?frida-server?所運行的系統(tǒng)中。

          該進程的主函數(shù)定義在?server/server.vala?中:

          namespace?Frida.Server?{
          private?static?int?main?(string[]?args)?{
          ????Environment.init?();
          ????//?...
          ????return?run_application?(endpoint_params,?options,?on_ready);
          }
          private?static?int?run_application?(EndpointParameters?endpoint_params,?ControlServiceOptions?options,?ReadyHandler?on_ready)?{
          ????//?...
          ????TemporaryDirectory.always_use?((directory?!=?null)???directory?:?DEFAULT_DIRECTORY);

          ????application?=?new?Application?(new?ControlService?(endpoint_params,?options));
          ????//?...
          ????return?application.run?();
          }
          //?...
          }

          Application?實際上是一層簡單的封裝,其?run?方法會間接調(diào)用到?servcie.start,即構(gòu)造函數(shù)中傳入的?ControlService。該類定義在?src/control-service.vala?中,包含 3 個主要的屬性:

          • ??HostSession host_session

          • ??EndpointParameters endpoint_params

          • ??ControlServiceOptions options

          其中最重要的是?HostSession,該屬性是一個接口類型,基于面向?qū)ο蟮亩鄳B(tài)方式實現(xiàn)了不同平臺下的實現(xiàn),如下所示:

          public?ControlService?(EndpointParameters?endpoint_params,?ControlServiceOptions??options?=?null)?{
          ????HostSession?host_session;
          #if?WINDOWS
          ????var?tempdir?=?new?TemporaryDirectory?();
          ????host_session?=?new?WindowsHostSession?(new?WindowsHelperProcess?(tempdir),?tempdir);
          #endif
          #if?DARWIN
          ????host_session?=?new?DarwinHostSession?(new?DarwinHelperBackend?(),?new?TemporaryDirectory?(),
          ????????opts.report_crashes);
          #endif
          #if?LINUX
          ????var?tempdir?=?new?TemporaryDirectory?();
          ????host_session?=?new?LinuxHostSession?(new?LinuxHelperProcess?(tempdir),?tempdir,?opts.report_crashes);
          #endif
          #if?FREEBSD
          ????host_session?=?new?FreebsdHostSession?();
          #endif
          #if?QNX
          ????host_session?=?new?QnxHostSession?();
          #endif

          HostSession?的接口定義在?lib/base/session.vala?中,接口如下所示:

          public?interface?HostSession?:?Object?{

          public?abstract?async?void?ping?(uint?interval_seconds,?Cancellable??cancellable)?throws?GLib.Error;

          public?abstract?async?HashTableVariant>?query_system_parameters?(Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?HostApplicationInfo?get_frontmost_application?(HashTableVariant>?options,
          ????Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?HostApplicationInfo[]?enumerate_applications?(HashTableVariant>?options,
          ????Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?HostProcessInfo[]?enumerate_processes?(HashTableVariant>?options,
          ????Cancellable??cancellable)?throws?GLib.Error;

          public?abstract?async?void?enable_spawn_gating?(Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?void?disable_spawn_gating?(Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?HostSpawnInfo[]?enumerate_pending_spawn?(Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?HostChildInfo[]?enumerate_pending_children?(Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?uint?spawn?(string?program,?HostSpawnOptions?options,?Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?void?input?(uint?pid,?uint8[]?data,?Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?void?resume?(uint?pid,?Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?void?kill?(uint?pid,?Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?AgentSessionId?attach?(uint?pid,?HashTableVariant>?options,
          ????Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?void?reattach?(AgentSessionId?id,?Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?InjectorPayloadId?inject_library_file?(uint?pid,?string?path,?string?entrypoint,?string?data,
          ????Cancellable??cancellable)?throws?GLib.Error;
          public?abstract?async?InjectorPayloadId?inject_library_blob?(uint?pid,?uint8[]?blob,?string?entrypoint,?string?data,
          ????Cancellable??cancellable)?throws?GLib.Error;

          public?signal?void?spawn_added?(HostSpawnInfo?info);
          public?signal?void?spawn_removed?(HostSpawnInfo?info);
          public?signal?void?child_added?(HostChildInfo?info);
          public?signal?void?child_removed?(HostChildInfo?info);
          public?signal?void?process_crashed?(CrashInfo?crash);
          public?signal?void?output?(uint?pid,?int?fd,?uint8[]?data);
          public?signal?void?agent_session_detached?(AgentSessionId?id,?SessionDetachReason?reason,?CrashInfo?crash);
          public?signal?void?uninjected?(InjectorPayloadId?id);
          }

          其中包括了應(yīng)用枚舉、進程查找、進程注入、進程啟動以及各類信號回調(diào)的接口原型,基于這些接口實現(xiàn)對目標進程的劫持和動態(tài)修改。由于我們重點是分析 Android 系統(tǒng)上的實現(xiàn),因此后文主要分析?LinuxHostSession,對于其他平臺的分析也是類似的。

          LinuxHostSession?的代碼在?src/linux/linux-host-session.vala?中,其中定義了幾個重要的屬性,分別是:

          • ??injector: 類型為 Linjector

          • ??agent: 類型為 AgentDescriptor

          • ??system_server_agent: 類型為 SystemServerAgent

          • ??robo_launcher: 類型為 RoboLauncher

          它們都在構(gòu)造函數(shù)中進行初始化,分別實現(xiàn)了進程注入、server 與注入代碼之間的進程間通信以及遠程調(diào)用行為,下文會分別進行簡要介紹。

          進程注入

          從接口名稱來看,進程注入的實現(xiàn)大概率是?inject_library_file,表示注入一個動態(tài)庫到目標進程中。所注入的動態(tài)庫稱為 agent。

          回到注入的實現(xiàn)上,frida-server 是怎么實現(xiàn)進程注入的呢?根據(jù)已有經(jīng)驗,由于我們以 root 權(quán)限啟動,在 Linux 中一般有多種實現(xiàn)進程注入的方式,比如 ptrace、/proc/pid/mem,DLL/SO 劫持、LD_PERLOAD 等,因此實際實現(xiàn)還要回到代碼。

          Android 中實現(xiàn)進程注入的方法如下:

          protected?override?async?Future<IOStream>?perform_attach_to?(uint?pid,?HashTableVariant>?options,
          ????????Cancellable??cancellable,?out?Object??transport)?throws?Error,?IOError?{
          ????PipeTransport.set_temp_directory?(tempdir.path);

          ????var?t?=?new?PipeTransport?();

          ????var?stream_request?=?Pipe.open?(t.local_address,?cancellable);

          ????uint?id;
          ????string?entrypoint?=?"frida_agent_main";
          ????string?agent_parameters?=?make_agent_parameters?(t.remote_address,?options);
          ????var?linjector?=?injector?as?Linjector;
          #if?HAVE_EMBEDDED_ASSETS
          ????id?=?yield?linjector.inject_library_resource?(pid,?agent,?entrypoint,?agent_parameters,?cancellable);
          #else
          ????id?=?yield?linjector.inject_library_file?(pid,?Config.FRIDA_AGENT_PATH,?entrypoint,?agent_parameters,?cancellable);
          #endif
          ????injectee_by_pid[pid]?=?id;

          ????transport?=?t;

          ????return?stream_request;
          }

          確實是使用了?inject_library_xxx?的方式,HAVE_EMBEDDED_ASSETS?為真時使用的是從自身代碼中釋放出來的 agent 庫,位置在:

          agent?=?new?AgentDescriptor?(PathTemplate?("frida-agent-.so"),
          ????new?Bytes.static?(blob32.data),
          ????new?Bytes.static?(blob64.data),
          ????new?AgentResource[]?{
          ????????new?AgentResource?("frida-agent-arm.so",?new?Bytes.static?(emulated_arm.data),?tempdir),
          ????????new?AgentResource?("frida-agent-arm64.so",?new?Bytes.static?(emulated_arm64.data),?tempdir),
          ????},
          ????AgentMode.INSTANCED,
          ????tempdir);

          這也是 Android 平臺中 frida-server 的默認實現(xiàn)。

          Linjector?定義在?src/linux/linjector.vala?,即 Linux 平臺上通用的進程注入實現(xiàn),調(diào)用鏈路為:

          • ??inject_library_resource

          • ??inject_library_file_with_template

          • ??helper.inject_library_file

          • ??helper._do_inject?(src/linux/frida-helper-backend.vala)

          _do_inject?不使用 vala 而是直接使用 C 實現(xiàn),代碼在?src/linux/frida-helper-backend-glue.c?:

          void
          _frida_linux_helper_backend_do_inject?(FridaLinuxHelperBackend?*?self,?guint?pid,?const?gchar?*?path,?const?gchar?*?entrypoint,?const?gchar?*?data,?const?gchar?*?temp_path,?guint?id,?GError?**?error)
          {
          ??params.open_impl?=?frida_resolve_libc_function?(pid,?"open");
          ??params.close_impl?=?frida_resolve_libc_function?(pid,?"close");
          ??params.write_impl?=?frida_resolve_libc_function?(pid,?"write");
          ??params.syscall_impl?=?frida_resolve_libc_function?(pid,?"syscall");

          #if?defined?(HAVE_GLIBC)
          //?...
          #elif?defined?(HAVE_UCLIBC)
          //?...
          #elif?defined?(HAVE_ANDROID)
          ??params.dlopen_impl?=?frida_resolve_android_dlopen?(pid);
          ??params.dlopen_flags?=?RTLD_LAZY;
          ??params.dlclose_impl?=?frida_resolve_linker_address?(pid,?dlclose);
          ??params.dlsym_impl?=?frida_resolve_linker_address?(pid,?dlsym);
          #endif

          ??instance?=?frida_inject_instance_new?(self,?id,?pid,?temp_path);
          ??frida_inject_instance_attach?(instance,?&saved_regs,?error);
          ??//?...
          ??frida_inject_instance_start_remote_thread?(instance,?&exited,?error);
          ??frida_inject_instance_detach?(instance,?&saved_regs,?NULL);
          }

          frida_inject_instance_attach?中定義了真正的 attach 實現(xiàn):

          static?gboolean
          frida_inject_instance_attach?(FridaInjectInstance?*?self,?FridaRegs?*?saved_regs,?GError?**?error)
          {
          ??const?pid_t?pid?=?self->pid;

          ??if?(can_seize)
          ????ret?=?ptrace?(PTRACE_SEIZE,?pid,?NULL,?PTRACE_O_TRACEEXEC);
          ??else
          ????ret?=?ptrace?(PTRACE_ATTACH,?pid,?NULL,?NULL);
          ??//?...
          }

          因此我們可以得出結(jié)論,frida 的進程注入是通過 ptrace 實現(xiàn)的,注入之后會在遠端進程分配一段內(nèi)存將 agent 拷貝過去并在目標進程中執(zhí)行代碼,執(zhí)行完成后會 detach 目標進程

          這也是為什么在 frida 先連接上目標進程后還可以用 gdb 等調(diào)試器連接,而先 gdb 連接進程后 frida 就無法再次連上的原因。

          frida-agent?注入到目標進程并啟動后會啟動一個新進程與 host 進行通信,從而 host 可以給目標進行發(fā)送命令,比如執(zhí)行代碼,激活/關(guān)閉 hook,同時也能接收到目標進程的執(zhí)行返回以及異步事件信息等。

          frida-agent

          在上節(jié)中調(diào)用?inject_library?指定了注入動態(tài)庫后執(zhí)行的的函數(shù)符號為?frida_agent_main,該函數(shù)也是由 vala 生成而來,源文件定義在?lib/agent/agent.vala?中:

          namespace?Frida.Agent?{
          ????public?void?main?(string?agent_parameters,?ref?Frida.UnloadPolicy?unload_policy,?void?*?injector_state)?{
          ????????if?(Runner.shared_instance?==?null)
          ????????????Runner.create_and_run?(agent_parameters,?ref?unload_policy,?injector_state);
          ????????else
          ????????????Runner.resume_after_transition?(ref?unload_policy,?injector_state);
          ????}
          ????//?...

          關(guān)鍵的調(diào)用路徑如下:

          • ??Runner.create_and_run

          • ??new Runner (...)

          • ??shared_instance.run

          • ??Runner.run

          • ??Runner.start

          其中?Runner.run?函數(shù)在調(diào)用完?start?后會進入?main_loop?循環(huán),直至進程退出或者收到 server 的解除命令。

          Runner.start?的作用是準備 Interceptor 以及 GumJS 的 ScriptBackend,并連接到啟動時指定的?transport_uri?建立通信隧道。

          system_server

          另外一個我覺得比較有意思的是 frida-server 中除了注入到目標進程的 agent,還有一個 agent,即?system_server_agent

          用過?frida-tools?的朋友應(yīng)該都知道其中有個?frida-ps?命令,可以查看設(shè)備中的應(yīng)用名稱、包名甚至是 Icon,也可以查看當前運行在窗口頂部的應(yīng)用。之前一直以為是通過?pm list packages?和?dumpsys?等命令實現(xiàn)的,看過代碼之后才發(fā)現(xiàn)原來 frida-server 還對?system_server?進程進行了注入,并且所使用的 agent 就是?system_server_agent

          如果不了解?system_server,可以先回顧一下筆者之前的文章 "Android 用戶態(tài)啟動流程分析",其中提到在 Android 系統(tǒng)啟動時,zygote 啟動的第一個進程就是?system_server,這也是系統(tǒng)中第一個啟動的 Java 進程,其中包含了?ActivityManagerService、?PackageManagerService?等系統(tǒng)服務(wù)。frida-server 對其進行注入,一方面是為了獲取系統(tǒng)中的應(yīng)用信息,另一方面也是為了可以實現(xiàn)?spawn?方式啟動應(yīng)用的功能。

          以獲取當前窗口中展示在最上層的應(yīng)用功能為例,接口為?get_frontmost_application,最終的實現(xiàn)在?SystemServerAgent:

          public?async?HostApplicationInfo?get_frontmost_application?(FrontmostQueryOptions?options,
          ????????Cancellable??cancellable)?throws?Error,?IOError?{
          ????var?scope?=?options.scope;
          ????var?scope_node?=?new?Json.Node.alloc?().init_string?(scope.to_nick?());

          ????Json.Node?result?=?yield?call?("getFrontmostApplication",?new?Json.Node[]?{?scope_node?},?cancellable);
          ????//?...
          }

          這實際上是調(diào)用了?src/linux/agent/system-server.js?中導(dǎo)出的方法:

          rpc.exports?=?{
          ??getFrontmostApplication(scope)?{
          ????return?performOnJavaVM(()?=>?{
          ??????const?pkgName?=?getFrontmostPackageName();
          ??????if?(pkgName?===?null)
          ????????return?null;

          ??????const?appInfo?=?packageManager.getApplicationInfo(pkgName,?0);

          ??????const?appLabel?=?loadAppLabel.call(appInfo,?packageManager).toString();
          ??????const?pid?=?computeAppPids(getAppProcesses()).get(pkgName)????0;
          ??????const?parameters?=?(scope?!==?'minimal')???fetchAppParameters(pkgName,?appInfo,?scope)?:?null;

          ??????return?[pkgName,?appLabel,?pid,?parameters];
          ????});
          ??}
          ??//?...
          }

          這部分 JS 代碼也是內(nèi)置到 frida-server 中的,基于 gum-js 實現(xiàn)了對?system_server?的 Java 代碼調(diào)用和劫持功能。

          frida-gadget

          除了?frida-server,另外一個比較常用的模塊就是?frida-gadget[3]?了。攻擊者可以通過重打包修改動態(tài)庫的依賴或者修改 smali 代碼去實現(xiàn)向三方應(yīng)用注入 gadget,當然也可以通過任意其他加載動態(tài)庫的方式。

          gadget 本身是一個動態(tài)庫,在加載到目標進程中后會馬上觸發(fā) ctor 執(zhí)行指定代碼,默認情況下是掛起當前進程并監(jiān)聽在 27042 端口等待 Host 的連接并恢復(fù)運行。其文件路徑為?lib/gadget/gadget.vala?,啟動入口為?Frida.Gadget.load:

          public?void?load?(Gum.MemoryRange??mapped_range,?string??config_data,?int?*?result)?{
          ????location?=?detect_location?(mapped_range);
          ????config?=?(config_data?!=?null)
          ??????????????????parse_config?(config_data)
          ????????????????:?load_config?(location);

          ????Gum.Cloak.add_range?(location.range);

          ????interceptor?=?Gum.Interceptor.obtain?();
          ????interceptor.begin_transaction?();
          ????exceptor?=?Gum.Exceptor.obtain?();

          ????//?try-catch
          ????var?interaction?=?config.interaction;
          ????if?(interaction?is?ScriptInteraction)?{
          ????????controller?=?new?ScriptRunner?(config,?location);
          ????}?else?if?(interaction?is?ScriptDirectoryInteraction)?{
          ????????controller?=?new?ScriptDirectoryRunner?(config,?location);
          ????}?else?if?(interaction?is?ListenInteraction)?{
          ????????controller?=?new?ControlServer?(config,?location);
          ????}?else?if?(interaction?is?ConnectInteraction)?{
          ????????controller?=?new?ClusterClient?(config,?location);
          ????}?else?{
          ????????//?throw?"Invalid?interaction?specified"
          ????}
          ????interceptor.end_transaction?();

          ????if?(!wait_for_resume_needed)
          ????????resume?();
          ????//?..
          ????start?(request);
          }

          正如文檔中所說,Gadget 啟動時會去指定路徑搜索配置文件,并根據(jù)其設(shè)置進入不同的運行模式,默認的配置文件如下所示:

          {
          ??"interaction":?{
          ????"type":?"listen",
          ????"address":?"127.0.0.1",
          ????"port":?27042,
          ????"on_port_conflict":?"fail",
          ????"on_load":?"wait"
          ??}
          }

          即使用?listen?模式,監(jiān)聽在 27042 端口并等待連接。除了 listen 以外,還支持以下幾種模式:

          • ??connect: Gadget 啟動后主動連接到指定地址;

          • ??script: 啟動后直接加載指定的 JavaScript 文件;

          • ??script-directory: 啟動后加載指定目錄下的所有 JavaScript 文件;

          值得一提的是,script-directory?模式可以用來實現(xiàn)系統(tǒng)級別的插件功能,比如在指定目錄中編寫一個?twitter.js,用于實現(xiàn)針對 Twitter 的 Hook 功能,比如自定義過濾、自動點贊等等;另外寫一個?twitter.config?來過濾應(yīng)用:

          {
          ??"filter":?{
          ????"executables":?["Twitter"],
          ????"bundles":?["com.twitter.twitter-mac"],
          ????"objc_classes":?["Twitter"]
          ??}
          }

          這樣就只有滿足指定條件的進程才會加載?twitter.js?腳本,從而實現(xiàn)特定應(yīng)用的持久化 Hook 功能。熟悉 Xposed 或者 Tweak 開發(fā)的應(yīng)該都知道這意味著什么。

          IPC 通信

          在 frida-core 中有許多需要進程間通信的行為,比如 frida-server 需要與注入到目標進程中的 agent 進行通信,通知目標進程開啟或者關(guān)閉 Interceptor;agent 同樣也需要與 host 進行通信,在 gum-js 中將?console.log?或者?send?的消息發(fā)送給 host,或者接收一些異步的應(yīng)用退出和異常事件等。而其中這些進程間的交互都是通過?D-Bus[4]?去實現(xiàn)的。

          D-Bus?是一種基于消息的進程間通信機制,全稱為 Desktop Bus,最初從 FreeDesktop 中的模塊獨立出來。其主要作用是提供在 Linux 操作系統(tǒng)桌面環(huán)境中的組件通信,比如 GNOME 或 KDE。D-Bus 使用 C 語言開發(fā),提供了 GLib、Qt、Python 等編程接口,在 frida-core 中主要使用其 Vala 接口進行集成。

          下面從實際代碼看一個 D-Bus Vala 接口實現(xiàn)的 C/S 通信示例。首先是服務(wù)端,定義一個?DemoServer, 內(nèi)部定義了四個方法:

          [DBus?(name?=?"org.example.Demo")]
          public?class?DemoServer?:?Object?{

          ????private?int?counter;

          ????public?int?ping?(string?msg)?{
          ????????stdout.printf?("%s\n",?msg);
          ????????return?counter++;
          ????}

          ????public?int?ping_with_signal?(string?msg)?{
          ????????stdout.printf?("%s\n",?msg);
          ????????pong(counter,?msg);
          ????????return?counter++;
          ????}

          ????/*?Including?any?parameter?of?type?GLib.BusName?won't?be?added?to?the
          ???????interface?and?will?return?the?dbus?sender?name?(who?is?calling?the?method)?*/

          ????public?int?ping_with_sender?(string?msg,?GLib.BusName?sender)?{
          ????????stdout.printf?("%s,?from:?%s\n",?msg,?sender);
          ????????return?counter++;
          ????}

          ????public?void?ping_error?()?throws?Error?{
          ????????throw?new?DemoError.SOME_ERROR?("There?was?an?error!");
          ????}

          ????public?signal?void?pong?(int?count,?string?msg);
          }

          [DBus?(name?=?"org.example.DemoError")]
          public?errordomain?DemoError
          {
          ????SOME_ERROR
          }

          void?on_bus_aquired?(DBusConnection?conn)?{
          ????try?{
          ????????conn.register_object?("/org/example/demo",?new?DemoServer?());
          ????}?catch?(IOError?e)?{
          ????????stderr.printf?("Could?not?register?service\n");
          ????}
          }

          void?main?()?{
          ????Bus.own_name?(BusType.SESSION,?"org.example.Demo",?BusNameOwnerFlags.NONE,
          ??????????????????on_bus_aquired,
          ??????????????????()?=>?{},
          ??????????????????()?=>?stderr.printf?("Could?not?aquire?name\n"));

          ????new?MainLoop?().run?();
          }

          其中使用?Bus.own_name?去連接總線?org.example.Demo,連接成功后注冊了名為?/org/example/demo?的服務(wù)。客戶端可以通過連接同樣的總線和服務(wù)去獲取到對應(yīng)的遠程對象接口,從而在本地實現(xiàn)遠程調(diào)用。客戶端示例如下:

          [DBus?(name?=?"org.example.Demo")]
          interface?Demo?:?Object?{
          ????public?abstract?int?ping?(string?msg)?throws?IOError;
          ????public?abstract?int?ping_with_sender?(string?msg)?throws?IOError;
          ????public?abstract?int?ping_with_signal?(string?msg)?throws?IOError;
          ????public?signal?void?pong?(int?count,?string?msg);
          }

          void?main?()?{
          ????/*?Needed?only?if?your?client?is?listening?to?signals;?you?can?omit?it?otherwise?*/
          ????var?loop?=?new?MainLoop();

          ????/*?Important:?keep?demo?variable?out?of?try/catch?scope?not?lose?signals!?*/
          ????Demo?demo?=?null;

          ????try?{
          ????????demo?=?Bus.get_proxy_sync?(BusType.SESSION,?"org.example.Demo",
          ????????????????????????????????????????????????????"/org/example/demo");

          ????????/*?Connecting?to?signal?pong!?*/
          ????????demo.pong.connect((c,?m)?=>?{
          ????????????stdout.printf?("Got?pong?%d?for?msg?'%s'\n",?c,?m);
          ????????????loop.quit?();
          ????????});

          ????????int?reply?=?demo.ping?("Hello?from?Vala");
          ????????stdout.printf?("%d\n",?reply);

          ????????reply?=?demo.ping_with_sender?("Hello?from?Vala?with?sender");
          ????????stdout.printf?("%d\n",?reply);

          ????????reply?=?demo.ping_with_signal?("Hello?from?Vala?with?signal");
          ????????stdout.printf?("%d\n",?reply);

          ????}?catch?(IOError?e)?{
          ????????stderr.printf?("%s\n",?e.message);
          ????}
          ????loop.run();
          }

          客戶端中需要定義與遠程對象一致的?interface,并通過?get_proxy?請求獲得遠程對象的本地代理,從而實現(xiàn)透明的遠程調(diào)用。注意的是編譯需要添加對應(yīng)的庫:

          $?valac?--pkg?gio-2.0?demo-server.vala
          $?valac?--pkg?gio-2.0?demo-client.vala

          從效果上看有點類似于 Android 的 binder 通信機制。但是 D-Bus 是一種更加上層的封裝,在不同操作系統(tǒng)上可以使用不同的底層實現(xiàn)。在 Linxu 操作系統(tǒng)中通常是基于 UNIX socket 實現(xiàn)的多進程通信,當然也可以修改配置去通過 TCP socket 實現(xiàn)。

          frida 執(zhí)行進程注入時同時也實現(xiàn)了 IPC 相關(guān)的初始化,在?src/host-session-service.vala?中可以看到關(guān)鍵代碼如下:

          private?async?AgentEntry?establish?(uint?pid,?HashTableVariant>?options,
          ????????Cancellable??cancellable)?throws?Error,?IOError?{
          ????//?...
          ????DBusConnection?connection;
          ????var?stream_request?=?yield?perform_attach_to?(pid,?options,?io_cancellable,?out?transport);
          ????IOStream?stream?=?yield?stream_request.wait_async?(io_cancellable);

          ????connection?=?yield?new?DBusConnection?(
          ????????stream,
          ????????ServerGuid.HOST_SESSION_SERVICE,
          ????????AUTHENTICATION_SERVER?|?AUTHENTICATION_ALLOW_ANONYMOUS?|?DELAY_MESSAGE_PROCESSING,
          ????????null,?io_cancellable);
          ????controller_registration_id?=?connection.register_object?(
          ????????ObjectPath.AGENT_CONTROLLER,
          ????????(AgentController)?this);
          ????connection.start_message_processing?();
          ????provider?=?yield?connection.get_proxy?(null,?ObjectPath.AGENT_SESSION_PROVIDER,
          ????????DO_NOT_LOAD_PROPERTIES,?io_cancellable);
          }

          這里使用了?DBusConnection?來連接總線,效果和?own_name?是類似的。其中總線的名稱為?ServerGuid.HOST_SESSION_SERVICE,所注冊的 RPC 服務(wù)名稱為?ObjectPath.AGENT_CONTROLLER。另外這里還通過總線去獲取了一個遠程對象?ObjectPath.AGENT_SESSION_PROVIDER,用以實現(xiàn)透明的遠程調(diào)用。這些服務(wù)的名稱都定義在?lib/base/session.vala?文件中:

          namespace?Frida?{
          ????//?...
          ????namespace?ServerGuid?{
          ????????public?const?string?HOST_SESSION_SERVICE?=?"6769746875622e636f6d2f6672696461";
          ????}

          ????namespace?ObjectPath?{
          ????????public?const?string?HOST_SESSION?=?"/re/frida/HostSession";
          ????????public?const?string?AGENT_SESSION_PROVIDER?=?"/re/frida/AgentSessionProvider";
          ????????public?const?string?AGENT_SESSION?=?"/re/frida/AgentSession";
          ????????public?const?string?AGENT_CONTROLLER?=?"/re/frida/AgentController";
          ????????public?const?string?AGENT_MESSAGE_SINK?=?"/re/frida/AgentMessageSink";
          ????????public?const?string?CHILD_SESSION?=?"/re/frida/ChildSession";
          ????????public?const?string?TRANSPORT_BROKER?=?"/re/frida/TransportBroker";
          ????????public?const?string?PORTAL_SESSION?=?"/re/frida/PortalSession";
          ????????public?const?string?BUS_SESSION?=?"/re/frida/BusSession";
          ????????public?const?string?AUTHENTICATION_SERVICE?=?"/re/frida/AuthenticationService";
          ????}
          }

          通過搜索?[DBus (name = "xxx")]?可以看到 frida-core 中定義的所有 IPC 服務(wù),感興趣的可以有針對性地去進行深入分析。

          拓展閱讀:

          • ??https://dbus.freedesktop.org/doc/dbus-tutorial.html

          • ??https://wiki.gnome.org/Projects/Vala/DBusServerSample

          • ??https://www.reddit.com/r/commandline/comments/13o581/dbus_vs_unix_sockets/

          SELinux

          在?Android/Linux Root 的那些事兒?中曾經(jīng)介紹過 Android 系統(tǒng)的安全訪問控制體系,不是簡單的?uid=0?就可以為所欲為的。frida 這種又注入進程又各種進程間通信的,顯然違反了 SELinux 的默認規(guī)則,那么它是如何實現(xiàn)的呢?其實說起來也很簡單,就是對當前系統(tǒng)的 SELinux 規(guī)則進行了 patch,本節(jié)就來分析下其具體是如何做的。

          patch 的代碼主要集中在?lib/selinux/patch.c?文件中,代碼就不貼了,主要流程可以簡單描述如下:

          1. 1.?從?/sys/fs/selinux/policy?文件中加載當前系統(tǒng)的 SELinux policy;

          2. 2.?在當前 sepolicy 中增加新的 SELinux 文件類型?frida_file

          3. 3. 依次插入新的 SELinux 規(guī)則(見后文);

          4. 4.?通過寫入?/sys/fs/selinux/load?文件保存修改后的 sepolicy;

          5. 5. (可選) 如果上述保存操作失敗,就先臨時將 SELinux 設(shè)置為 permissive 然后再保存一次,保存成功后重新恢復(fù)為 enforcing;

          第 3 步中插入的 SELinux 規(guī)則如下:

          typedef?struct?_FridaSELinuxRule?FridaSELinuxRule;
          struct?_FridaSELinuxRule
          {
          ??const?gchar?*?sources[4];
          ??const?gchar?*?target;
          ??const?gchar?*?klass;
          ??const?gchar?*?permissions[16];
          };
          static?const?FridaSELinuxRule?frida_selinux_rules[]?=
          {
          ??{?{?"domain",?NULL?},?"domain",?"process",?{?"execmem",?NULL?}?},
          ??{?{?"domain",?NULL?},?"frida_file",?"dir",?{?"search",?NULL?}?},
          ??{?{?"domain",?NULL?},?"frida_file",?"fifo_file",?{?"open",?"write",?NULL?}?},
          ??{?{?"domain",?NULL?},?"frida_file",?"file",?{?"open",?"read",?"getattr",?"execute",?"?map",?NULL?}?},
          ??{?{?"domain",?NULL?},?"frida_file",?"sock_file",?{?"write",?NULL?}?},
          ??{?{?"domain",?NULL?},?"shell_data_file",?"dir",?{?"search",?NULL?}?},
          ??{?{?"domain",?NULL?},?"zygote_exec",?"file",?{?"execute",?NULL?}?},
          ??{?{?"domain",?NULL?},?"$self",?"process",?{?"sigchld",?NULL?}?},
          ??{?{?"domain",?NULL?},?"$self",?"fd",?{?"use",?NULL?}?},
          ??{?{?"domain",?NULL?},?"$self",?"unix_stream_socket",?{?"connectto",?"read",?"write",?"getattr",?"getopt",?NULL?}?},
          ??{?{?"domain",?NULL?},?"$self",?"tcp_socket",?{?"read",?"write",?"getattr",?"getopt",?NULL?}?},
          ??{?{?"zygote",?NULL?},?"zygote",?"capability",?{?"sys_ptrace",?NULL?}?},
          ??{?{?"?app_zygote",?NULL?},?"zygote_exec",?"file",?{?"read",?NULL?}?},
          };

          其中?$self?表示當前進程的 context,即 getcon 獲取的值。在完成對 sepolicy 的動態(tài)修改后,frida 就可以正常的在 enforcing 的環(huán)境下運行了。

          后記

          frida-core?在?gum-js?的基礎(chǔ)上實現(xiàn)了操作系統(tǒng)層面的主要功能,比如進程注入、進程間通信、系統(tǒng)信息采集、系統(tǒng)權(quán)限管理等功能。正是因為這些核心組件的加入,使得 frida 成為了一個上手簡單卻又功能強大的動態(tài)分析框架。另外由于這些核心組件大部分使用 Vala 語言編寫,在保障內(nèi)存安全性的同時也提供了豐富的跨平臺能力,支持 Android、iOS、Linux、MacOS、Windows 這些主流操作系統(tǒng)的不同架構(gòu)。雖然本文僅針對 Android/Linux 操作系統(tǒng)的主要組件進行了介紹,但相信從中也能看出 frida-core 項目本身的代碼風(fēng)格架構(gòu)設(shè)計,對于其他組件或者其他操作系統(tǒng)上的實現(xiàn)也是大同小異的,后續(xù)遇到具體問題的時候再深入分析對應(yīng)組件即可。

          引用鏈接

          [1]?Vala:?https://wiki.gnome.org/Projects/Vala/
          [2]?Vala Sample:?https://wiki.gnome.org/Projects/Vala/Documentation#Sample_Code
          [3]?frida-gadget:?https://frida.re/docs/gadget/
          [4]?D-Bus:?https://en.wikipedia.org/wiki/D-Bus


          瀏覽 169
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  97办公室三级电影中文字幕 | 国产小黄片在线播放 | 操操网网址| 青青草伊人在线 | 爱干av麻豆 |