首页
社区
课程
招聘
[原创]frida源代码分析--进程注入和server dbus通讯架构分析
发表于: 2021-11-16 16:09 33945

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

2021-11-16 16:09
33945

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

c 和vala 用valac编译到一起

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

hellotest.vala

helloworld.c

helloworld.vapi

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

vala web,soup库
直接看代码吧

vala dbus

gdbus-demo-client.vala

gdbus-demo-server.vala

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

2、获取一个DBusConnection对象

3、register_object

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

meson编译工具

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

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

meson.build

writec.py

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-clr 好像不太重要的样子,没怎么看

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

portal

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

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

src\control-service.vala

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

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

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

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

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

看一下ControlChannel,调用的函数

这个函数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

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

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

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

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

src\linux\linux-host-session.vala

调用的是linjector的inject_library_file和inject_library_resource函数

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

src\linux\frida-helper-process.vala

frida-server 释放文件,启动frida-helper

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

所以inject_library_file调用的是 LinuxHelperService 的 inject_library_file

这个backend是LinuxHelperBackend

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

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

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

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 相关的特征还有很多,可以自行寻找。

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;
        }
    }
}
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;
        }
    }
}
#include <stdio.h>
 
void myplusone(int a)
{
        printf("%d",a+1);
}
 
int add(int a,int b){
    return a + b;
}
#include <stdio.h>
 
void myplusone(int a)
{
        printf("%d",a+1);
}
 
int add(int a,int b){
    return a + b;
}
[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);  
}
[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);  
}
gcc -c helloworld.c
ar -rc libhelloworld.a helloworld.o
valac hellotest.vala --pkg helloworld --vapidir . -X -I. -X -L. -X -lhelloworld
gcc -c helloworld.c
ar -rc libhelloworld.a helloworld.o
valac hellotest.vala --pkg helloworld --vapidir . -X -I. -X -L. -X -lhelloworld
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;
    }
}
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;
    }
}
[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();
}
[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();
}
[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 ();
}
[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 ();
}
writec = find_program('writec.py')
 
retval = custom_target('writec',
output : 'retval.c',
command : [writec, '@OUTPUT@'])
writec = find_program('writec.py')
 
retval = custom_target('writec',
output : 'retval.c',
command : [writec, '@OUTPUT@'])
#!/usr/bin/env python3
 
import sys
 
c = '''int
retval(void) {
return 0;
}
'''
 
with open(sys.argv[1], 'w') as f:
    f.write(c)
#!/usr/bin/env python3
 
import sys
 
c = '''int
retval(void) {
return 0;
}
'''
 
with open(sys.argv[1], 'w') as f:
    f.write(c)
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;
        });
    }
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;
        });
    }
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
        ....
    }
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
        ....
    }
    ............
    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);
}
    ............
    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);
}
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;
            ........
            .........
    }
```
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;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2021-11-16 16:22 被Thehepta编辑 ,原因:
收藏
免费 14
支持
分享
最新回复 (6)
雪    币: 6052
活跃值: (6267)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
干货多多啊
2021-11-17 16:46
0
雪    币: 7309
活跃值: (3788)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
3
分析得很详细
2021-11-20 16:38
0
雪    币: 220
活跃值: (89)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
最近正学习frida,好文章呀!
2022-1-11 14:58
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6

大神,请教一下,看看该如何解决,多谢。
用vsCode查看vala源码
这个设置有问题,导致vala源码无法看到函数的定义实现的地方:
Vala › Code: Svr File Path
/usr/bin/vlangsrvc.exe

在win10和ubuntu上缺省都是这个设置,具体该怎么填写?

最后于 2022-2-7 20:47 被david1008编辑 ,原因:
2022-2-7 20:46
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
太秀啦
2022-2-8 08:24
0
游客
登录 | 注册 方可回帖
返回
//