首页
论坛
课程
招聘
[原创]frida源代码分析--进程注入和server dbus通讯架构分析
2021-11-16 16:09 7007

[原创]frida源代码分析--进程注入和server dbus通讯架构分析

2021-11-16 16:09
7007

前言

frdia源码,分析的没什么太大的难度,分析过程中,主要是对vala语言的一些库,和语言不是很熟悉,和对一些编程语言的高级设计理念不是很熟悉,frida本身源码写的还是很不错的,但是可能是复用性太高了,各种代码都黏在在一起,实现的功能又比较多,开始分析起来比较麻烦。我分析的是15.1.3版本。

一、vala学习和meson编译系统

  • c 和vala 用valac编译到一起

    frdia使用c 和vala写的,配合meson编译系统。假如vala 想要引用c库,frida中的方法是*.vapi的方式。

    hellotest.vala

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    using MyHello;
    namespace Frida {
        public class LinuxHelperbackend {
            public static void main() {
                myplusone(1);
                int a = add123(5,6);
                int c = add(a,10);
                print("fefew = %d",c);
            }
     
            public static int add(int a,int b){
                return a+b;
            }
        }
    }

    helloworld.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <stdio.h>
     
    void myplusone(int a)
    {
            printf("%d",a+1);
    }
     
    int add(int a,int b){
        return a + b;
    }

    helloworld.vapi

    1
    2
    3
    4
    5
    6
    7
    [CCode (cheader_filename="helloworld.h",lower_case_cprefix="")]
    namespace MyHello {
        public void myplusone(int a);
     
        [CCode (cname = "add")]
        public int add123(int a,int b);  
    }
    1
    2
    3
    gcc -c helloworld.c
    ar -rc libhelloworld.a helloworld.o
    valac hellotest.vala --pkg helloworld --vapidir . -X -I. -X -L. -X -lhelloworld

    frida还专门写了个工具,用来自动生成文件 tools\resource-compiler

  • vala web,soup库
    直接看代码吧

    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
    public class NoodleSoupServer : Soup.Server {
        private int access_counter = 0;
     
        public NoodleSoupServer () {
            assert (this != null);
     
            // Links:
            //   http://localhost:8088/*
            this.add_handler (null, default_handler);     //添加一个连接以后的处理函数
        }
     
        private static void default_handler (Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
            unowned NoodleSoupServer self = server as NoodleSoupServer;
     
            uint id = self.access_counter++;
            print ("Default handler start (%u)\n", id);
     
            Timeout.add_seconds (0, () => {
                string html_head = "<head><title>Index</title></head>";
                string html_body = "<body><h1>Index:</h1></body>";
                msg.set_response ("text/html", Soup.MemoryUse.COPY, "<html>%s%s</html>".printf (html_head, html_body).data);
     
                // Resumes HTTP I/O on msg:
                self.unpause_message (msg);
                print ("Default handler end (%u)\n", id);
                return false;
            }, Priority.DEFAULT);
     
            // Pauses HTTP I/O on msg:
            self.pause_message (msg);
        }
     
        public static int main (string[] args) {
            try {
                int port = 8088;
     
                MainLoop loop = new MainLoop ();
     
                NoodleSoupServer server = new NoodleSoupServer ();
                server.listen_all (port, 0);      //监听web连接的端口
     
                loop.run ();
            } catch (Error e) {
                print ("Error: %s\n", e.message);
            }
            return 0;
        }
    }
  • vala dbus

    gdbus-demo-client.vala

    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
    [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();
    }

    gdbus-demo-server.vala

    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
    [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 ();
    }

    1、[DBus (name = "org.example.Demo")]

    2、获取一个DBusConnection对象

    3、register_object

    这两个进程就可以互相调用了

  • meson编译工具

    这里不讲太多,只讲custom_target函数,不懂的看文档吧

    https://mesonbuild.com/Getting-meson_zh.html

    meson.build

    1
    2
    3
    4
    5
    writec = find_program('writec.py')
     
    retval = custom_target('writec',
    output : 'retval.c',
    command : [writec, '@OUTPUT@'])

    writec.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/usr/bin/env python3
     
    import sys
     
    c = '''int
    retval(void) {
    return 0;
    }
    '''
     
    with open(sys.argv[1], 'w') as f:
        f.write(c)

    meson.build的custom_target会直接writec.py文件,command后面的数组,第一个是命令,后面的是参数,@OUTPUT@ 表示output ,最后会在build目录中生成一个retval.c文件

  • vala语法相关

    不懂vala语法的可以读一读 https://wiki.gnome.org/Projects/Vala

    yeid 看这个 http://blog.rastersoft.com/?p=1013

二、frida源码分析

  • 源码结构

    • frida-clr 好像不太重要的样子,没怎么看

    • frida-core 大部分主要源码都在这里

      • portal

        https://frida.re/news/2021/07/18/frida-15-0-released/

      • lib
        这个主要是frida-agent-<arch>.so
      • inject

        frdia-inject ,可以单独执行,在相应的平台上,frida中下载的 frida-inject-15.1.10-android-arm.xz,可以不必在pc上执行,可以在端执行

      • src

        针对不同的平台实现了不同的进程注入,通讯等服务(不同平台实现不同的 help 和 inject)

      • server

        frida-server 入口函数,初始化环境,调用各种服务

      • frida.vala

        这个文件,好像是有些设备管理,但是编译core也需要他。

      • control-service.vala

        入口服务,对接外部各种功能的接口,记住这个类,挺重要的

    • frida-gum 这个是frida 和js交互的库,依赖v8或者quickjs,在js的库的基础上有封装了一套自动的东西,

    • frida-node 这个好像也不太重要的样子

    • frida-python 这个是python 绑定到frida上的库

    • frida-qml 这个应该很多人都不知道是干嘛的,这个其实qt的qml,qt有一套可以用js写界面的技术,叫做qml,frida实现了这个借口,可以用这个做界面,做界面推荐用这个,速度挺不错的

    • frida-swift 这个不太清楚

    • frida-tools 这个就是安装到电脑上的工具,用来连接frida-server

  • frida构成

    • frida-server

      在手机上启动的server,和pc端连接,启动frida-helper,与frida-helper保持dbus连接

      frida-server启动的时候,会在/data/local/tmp想生成一个目录,释放Frida-helper 和frida-agent

    • frida-helper

      frida-helper保持dbus连接,主要负责进程附加,so文件注入

      通过

    • frida-agent-<arch>.so

      注入到进程中的文件,内含v8,执行各种hook操作等等

    • frida-gadget

      估计和frida-agent差不多,不过,明显多了和pc端连接的功能,这个没具体分析

  • frida启动,和pc通讯分析

    server\server.vala

    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
    main函数开始执行
    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 函数
        public int run () {
            Idle.add (() => {
                start.begin ();
                return false;
            });
     
            exit_code = 0;
     
            loop.run ();
     
            return exit_code;
        }
     
        start.begin ();调用
          private async void start () {
            try {
                yield service.start (io_cancellable);    //这里调用的是ControlService的start函数
            } catch (GLib.Error e) {
                if (e is IOError.CANCELLED)
                    return;
                printerr ("Unable to start: %s\n", e.message);
                exit_code = 5;
                loop.quit ();
                return;
            }
     
            Idle.add (() => {
                ready ();
                return false;
            });
        }

    src\control-service.vala

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public async void start (Cancellable? cancellable = null) throws Error, IOError {
            if (state != STOPPED)
                throw new Error.INVALID_OPERATION ("Invalid operation");
            state = STARTING;
     
            try {
                yield service.start (cancellable);      //这里是WebService 调用start
            ....
        }

    frida-core\lib\base\socket.vala
    ```
    public async void start (Cancellable? cancellable) throws Error, IOError {

    1
    2
    3
    4
    5
    6
    7
    8
    9
        ............
        var start_request = new Promise<SocketAddress> ();
        schedule_on_dbus_thread (() => {                 //闭包,异步调用handle_start_request 函数
            handle_start_request.begin (start_request, cancellable);
            return false;
        });
     
        _listen_address = yield start_request.future.wait_async (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
39
40
41
42
43
44
45
46
47
48
49
50
private async void handle_start_request (Promise<SocketAddress> start_request, Cancellable? cancellable) {
        try {do_start
            SocketAddress effective_address = yield do_start (cancellable);    //调用 do_start等待这个函数返回
            .........
    }
 
 
 
private async SocketAddress do_start (Cancellable? cancellable) throws Error, IOError {
        server = (Soup.Server) Object.new (typeof (Soup.Server),
            "tls-certificate", endpoint_params.certificate);
 
        server.add_websocket_handler ("/ws", endpoint_params.origin, null, on_websocket_opened);  //这个应该是websocket打开时处理的
 
        if (endpoint_params.asset_root != null)
            server.add_handler ("/", on_asset_request);                    //看前面demo,web处理函数,这个也是
 
        SocketConnectable connectable = (flavor == CONTROL)
            ? parse_control_address (endpoint_params.address, endpoint_params.port)   //设置端口 27042
            : parse_cluster_address (endpoint_params.address, endpoint_params.port);
 
        Soup.ServerListenOptions listen_options = (endpoint_params.certificate != null)
            ? Soup.ServerListenOptions.HTTPS
            : 0;
 
        SocketAddress? first_effective_address = null;
        var enumerator = connectable.enumerate ();
        while (true) {
            SocketAddress? address;
            try {
                address = yield enumerator.next_async (io_cancellable);
            } catch (GLib.Error e) {
                throw new Error.NOT_SUPPORTED ("%s", e.message);
            }
            if (address == null)
                break;
 
            SocketAddress? effective_address = null;
            InetSocketAddress? inet_address = address as InetSocketAddress;
            if (inet_address != null) {
                uint16 start_port = inet_address.get_port ();
                uint16 candidate_port = start_port;
                do {
                    try {
                        server.listen (inet_address, listen_options);   //在这里监听
                        effective_address = inet_address;
            ........
            .........
    }
```

pc端会访问27042端口,去连接frida-server,我们接着分析一下 on_websocket_opened

1
2
3
4
5
6
7
8
9
10
private void on_websocket_opened (Soup.Server server, Soup.WebsocketConnection connection, string path,
          Soup.ClientContext client) {
      var peer = new WebConnection (connection);      //WebConnection 是个 IOStream
      SocketAddress remote_address = client.get_remote_address ();
 
      schedule_on_frida_thread (() => {
          incoming (peer, remote_address);    发送incoming 信号
          return false;
      });
  }

WebService的incoming信号在WebService初始化函数中注册了,调用了on_server_connection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
service.incoming.connect (on_server_connection);
 
 
  private void on_server_connection (IOStream connection, SocketAddress remote_address) {
 
      private async void handle_server_connection (IOStream raw_connection) throws GLib.Error {
          var connection = yield new DBusConnection (raw_connection, null, DELAY_MESSAGE_PROCESSING, null, io_cancellable); 
          //通过web传过来的 IOStream,生成了一个DBusConnection
 
          connection.on_closed.connect (on_connection_closed);
 
          Peer peer;
          AuthenticationService? auth_service = endpoint_params.auth_service;
          if (auth_service != null)
              peer = new AuthenticationChannel (this, connection, auth_service);  //这个类在gadget,应该是frida-gadget.so的时候会调用
          else
              peer = setup_control_channel (connection);            //这个在本地
          peers[connection] = peer;
 
          connection.start_message_processing ();
      }

这两个调用位置都会去注册dbus函数,我们分析setup_control_channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private ControlChannel setup_control_channel (DBusConnection connection) {
          return new ControlChannel (this, connection);
      }
 
ControlChannel初始化函数
 
            construct {
              try {
                  HostSession session = this;
                  registrations.add (connection.register_object (ObjectPath.HOST_SESSION, session));
 
                  AuthenticationService null_auth = new NullAuthenticationService ();
                  registrations.add (connection.register_object (Frida.ObjectPath.AUTHENTICATION_SERVICE, null_auth));
                  //注册了dbus对象,也就是说,pc端可以远程调用当前这个类的函数,这里完成了,
 
              ...........
          }

pc端先通过web连接到frida-server的端口,然后frida通过web的数据流,注册了一个dbus服务,供pc端调用,这样就实现了远程通讯

 

看一下ControlChannel,调用的函数

1
2
3
public async void input (uint pid, uint8[] data, Cancellable? cancellable) throws GLib.Error {
          yield parent.host_session.input (pid, data, cancellable);
      }

这个函数parent就是ControlService,最终还是调用的ControlService的host_session,这个host_session,我们后面说

  • control-service 跨进程attach,调用frida-agent-main

    前面我们分析了,pc端通过dbus可以远程调用ControlChannel,而ControlChannel,实际上调用的是ControlService。

    frida\frida-core\src\control-service.vala

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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);   调用了host_session的attach
            } catch (GLib.Error e) {
                throw_dbus_error (e);
            }
     
            ......
        }

    不同的平台实现了不同的host_session,通过BaseDBusHostSession来抽象实现相同功能,不同架构实现不同功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
        #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 QNX
                host_session = new QnxHostSession ();
    #endif

    我们分析linux平台,在LinuxHostSession初始化函数中,释放了frida-agent文件

    前面的host_session.attach,调用的是BaseDBusHostSession的attach

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public async AgentSessionId attach (uint pid, HashTable<string, Variant> options,
            Cancellable? cancellable) throws Error, IOError {
        var entry = yield establish (pid, options, cancellable);    调用了establish 函数
     
        var id = AgentSessionId.generate ();
        entry.sessions.add (id);
     
        try {
            yield entry.provider.open (id, options, cancellable);
        } catch (GLib.Error e) {
            entry.sessions.remove (id);
     
            throw new Error.PROTOCOL ("%s", e.message);
        }
     
        agent_sessions[id] = new AgentSessionEntry (entry.connection);
     
        return id;
    }

    BaseDBusHostSession类 establish 函数 401行,调用 perform_attach_to,调用的是各个平台的实现的perform_attach_to

    src\linux\linux-host-session.vala

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    protected override async Future<IOStream> perform_attach_to (uint pid, HashTable<string, Variant> 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;
            }

    调用的是linjector的inject_library_file和inject_library_resource函数

    linjector 也是分架构的,这里不细讲了,这里最终调用的是LinuxHelperProcess 的inject_library_file

src\linux\frida-helper-process.vala

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
```
 
    public async void inject_library_file (uint pid, PathTemplate path_template, string entrypoint, string data,
            string temp_path, uint id, Cancellable? cancellable) throws Error, IOError {
        var helper = yield obtain_for_cpu_type (cpu_type_from_pid (pid), cancellable);
        try {
            yield helper.inject_library_file (pid, path_template, entrypoint, data, temp_path, id, cancellable);
            injectee_ids[id] = helper;
        } catch (GLib.Error e) {
            throw_dbus_error (e);
        }
    }
```
helper是从obtain_for_cpu_type返回的,obtain_for_cpu_type会根据不同的架构调用 obtain_for_32bit 和 obtain_for_64bit
 
```
    private async LinuxHelper obtain_for_32bit (Cancellable? cancellable) throws Error, IOError {
        if (factory32 == null) {
            var store = get_resource_store ();
            if (sizeof (void *) != 4 && store.helper32 == null)
                throw new Error.NOT_SUPPORTED ("Unable to handle 32-bit processes due to build configuration");
            factory32 = new HelperFactory (store.helper32, store, main_context);
            factory32.lost.connect (on_factory_lost);
            factory32.output.connect (on_factory_output);
            factory32.uninjected.connect (on_factory_uninjected);
        }
 
        return yield factory32.obtain (cancellable);
    }
 
    private async LinuxHelper obtain_for_64bit (Cancellable? cancellable) throws Error, IOError {
        if (factory64 == null) {
            var store = get_resource_store ();
            if (sizeof (void *) != 8 && store.helper64 == null)
                throw new Error.NOT_SUPPORTED ("Unable to handle 64-bit processes due to build configuration");
            factory64 = new HelperFactory (store.helper64, store, main_context);
            factory64.lost.connect (on_factory_lost);
            factory64.output.connect (on_factory_output);
            factory64.uninjected.connect (on_factory_uninjected);
        }
 
        return yield factory64.obtain (cancellable);
    }
```
get_resource_store 这里释放文件frida-helper
 
然后将文件传入HelperFactory中,调用他的obtain返回,返回一个helper
 
src\linux\frida-helper-process.vala
```
        public async LinuxHelper obtain (Cancellable? cancellable) throws Error, IOError {
         .......      
        obtain_request = new Promise<LinuxHelper> ();
 
        if (helper_file == null) {
            assign_helper (new LinuxHelperBackend ());
 
            obtain_request.resolve (helper);
            return helper;
        }
 
        .........
 
        try {
            string socket_path = Path.build_filename (resource_store.tempdir.path, Uuid.string_random ());
            string socket_address = "unix:abstract=" + socket_path;  //dbus类型
 
 
            .........
            string[] envp = Environ.unset_variable (Environ.get (), "LD_LIBRARY_PATH");
 
            try {
                string cwd = "/";
                string[] argv = new string[] { "su", "-c", helper_file.path, socket_address };  //超级权限启动
                bool capture_output = false;
                pending_superprocess = yield SuperSU.spawn (cwd, argv, envp, capture_output, cancellable);   //启动进程
            } catch (Error e) {
                string[] argv = { helper_file.path, socket_address };
 
                GLib.SpawnFlags flags = GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN | /* GLib.SpawnFlags.CLOEXEC_PIPES */ 256;
                GLib.Process.spawn_async (null, argv, envp, flags, null, out pending_pid);
            }
            ........
 
            var incoming_handler = service.incoming.connect ((c) => {   //管道连接的时候会返回一个流
                pending_stream = c;
                obtain.callback ();
                return true;
            });
            ........
 
 
            yield;
 
            service.disconnect (incoming_handler);
            service.stop ();
            service = null;
            timeout_source.destroy ();
            timeout_source = null;
 
            if (pending_error == null) {
                pending_connection = yield new DBusConnection (pending_stream, null, DBusConnectionFlags.NONE, null,
                    cancellable);           //通过这个生成一个DBusConnection
                pending_proxy = yield pending_connection.get_proxy (null, ObjectPath.HELPER, DO_NOT_LOAD_PROPERTIES,
                    cancellable);                          //这里,获取对应进程的ObjectPath.HELPER服务,helper进程在启动后注册这个服务。
                if (pending_connection.is_closed ())
                    throw new Error.NOT_SUPPORTED ("Helper terminated prematurely");
            }
        } catch (GLib.Error e) {
            if (timeout_source != null)
                timeout_source.destroy ();
 
            if (service != null)
                service.stop ();
 
            if (e is Error || e is IOError.CANCELLED)
                pending_error = e;
            else
                pending_error = new Error.PERMISSION_DENIED ("%s", e.message);
        }
 
        if (pending_error == null) {
            superprocess = pending_superprocess;
            process_pid = pending_pid;
 
            connection = pending_connection;
            connection.on_closed.connect (on_connection_closed);
 
            assign_helper (new HelperSession (pending_proxy));    // pending_proxy 就是可以远程调用helper进程的对象
 
            obtain_request.resolve (helper);
            return helper;                           //返回的这helper 是HelperSession类型,见assign_helper
        } else {
            if (pending_pid != 0)
                Posix.kill ((Posix.pid_t) pending_pid, Posix.Signal.KILL);
 
            obtain_request.reject (pending_error);
            obtain_request = null;
 
            throw_api_error (pending_error);
        }
    }
```
所以obtain_for_cpu_type返回的是 HelperSession这个对象,然后调用inject_library_file
 
```
        public async void inject_library_file (uint pid, PathTemplate path_template, string entrypoint, string data,
            string temp_path, uint id, Cancellable? cancellable) throws Error, IOError {
        try {
            yield proxy.inject_library_file (pid, path_template, entrypoint, data, temp_path, id, cancellable);
        } catch (GLib.Error e) {
            throw_dbus_error (e);
        }
    }
```
 
调用的实际就是dbus的对象,调用的就是pending_proxy
  • frida-server 释放文件,启动frida-helper

  • frida-help 调用inject_library_file分析
    start函数,这里注册了Frida.ObjectPath.HELPER,这个服务,供frida-server调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
        private async void start () {
        try {
            connection = yield new DBusConnection.for_address (parent_address, DELAY_MESSAGE_PROCESSING);
            connection.on_closed.connect (on_connection_closed);
     
            LinuxRemoteHelper helper = this;
            helper_registration_id = connection.register_object (Frida.ObjectPath.HELPER, helper);
     
            connection.start_message_processing ();
        } catch (GLib.Error e) {
            printerr ("Unable to start: %s\n", e.message);
            run_result = 1;
            shutdown.begin ();
        }
    }

    所以inject_library_file调用的是 LinuxHelperService 的 inject_library_file

    1
    2
    3
    4
    public async void inject_library_file (uint pid, PathTemplate path_template, string entrypoint, string data,
            string temp_path, uint id, Cancellable? cancellable) throws Error, IOError {
        yield backend.inject_library_file (pid, path_template, entrypoint, data, temp_path, id, cancellable);
    }

    这个backend是LinuxHelperBackend

    1
    2
    3
    4
    5
    6
    7
    8
        public async void inject_library_file (uint pid, PathTemplate path_template, string entrypoint, string data,
            string temp_path, uint id, Cancellable? cancellable) throws Error, IOError {
        string path = path_template.expand (arch_name_from_pid (pid));
     
        _do_inject (pid, path, entrypoint, data, temp_path, id);
     
        yield establish_session (id, pid);
    }

    最后调用的是_do_inject,这个_do_inject是c函数,从外部中导入到vala中的

三、frida检测

看到这里也不容易,可能你对源码分析不关心,所以,我针对上面的源码分析的结果,讲一下如何防御sdk,检测frida。

 

frida是使用vala编写的,并且内置了v8(或quickjs),也就是说,也就是说我们检测frida,也可以检测vala特征,和v8特征

  • 1、进程内字符串扫描,“frida”和“v8”,进程内注入的文件是frida-agent.so这个文件,这个库的入口函数是frida_agent_main ,用ida搜一下frida,一大堆相关的字符

v8搜索显示

 

还可以搜索quickjs 这个引擎的符号或者字符串,如果想要完全改掉这些字符,这个真的有点麻烦了。

  • 2、检测frida的线程

    vala有个Gmain,也会开启这个线程的,包括js引擎,也是有单独线程的,另外进程通讯用的是dbus,也是有线程的,而且由于他们都是现场的第三方库,创建线程的名字也好找。

    遍历 /porc/self/task/*/status

    gum js 引擎的线程名字: Name: gum-js-loop

    vala 引擎的线程名字: Name: gmain

    dbus 线程名字: Name: gdbus

关于frida检测这个,我只是提供了一个思路,关于v8 vala 相关的特征还有很多,可以自行寻找。


【公告】【iPhone 13、ipad、iWatch】11月15日中午12:00,看雪·众安 2021 KCTF秋季赛 正式开赛【攻击篇】!!!文末有惊喜~

最后于 2021-11-16 16:22 被ChicWalk编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (2)
雪    币: 2671
活跃值: 活跃值 (2018)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
mb_aoooaosd 活跃值 2021-11-17 16:46
2
0
干货多多啊
雪    币: 3410
活跃值: 活跃值 (1430)
能力值: (RANK:820 )
在线值:
发帖
回帖
粉丝
海风月影 活跃值 19 2021-11-20 16:38
3
0
分析得很详细
游客
登录 | 注册 方可回帖
返回