Trinity部署、使用与原理分析

news2024/12/22 20:16:20

文章目录

  • 前言
  • 1、概述
    • 1.1、整体架构
    • 1.2、trinity-main
    • 1.3、childx
  • 2、安装与使用
    • 2.1、源码安装
      • 2.1.1 部署系统依赖组件
      • 2.1.2 使用源码安装系统
    • 2.2、使用方法
  • 3、测试用例
    • 3.1、Splice系统调用压力测试
    • 3.2、其它系统调用压力测试
    • 3.3、自定义系统调用压力测试
  • 4、总结
    • 4.1、部署架构
    • 4.2、漏洞检测对象
    • 4.3、漏洞检测方法
    • 4.4、种子生成/变异技术
  • 5、参考文献
  • 总结


前言

  本博客的主要内容为Trinity的部署、使用与原理分析。本博文内容较长,因为涵盖了Trinity的几乎全部内容,从部署的详细过程到如何使用Trinity对操作系统的系统调用进行Fuzz测试,以及对Trinity进行漏洞检测的原理分析,相信认真读完本博文,各位读者一定会对Trinity有更深的了解。以下就是本篇博客的全部内容了。


1、概述

  系统调用Fuzz测试并不是一个特别新的概念。早在1991年,人们就编写了一些应用程序,用垃圾数据轰炸系统调用输入,这些应用程序在使各种操作系统崩溃方面取得了各种程度的成功。

  然而,在修复了明显的愚蠢错误之后,大多数情况下,这些调用将在其函数入口点附近的内核非常早期被拒绝,因为其函数入口点附近的内核进行了基本的参数验证。

  Trinity是一个系统调用Fuzz测试工具,它采用一些技术向被调用的系统调用传递半智能参数。其智能特性包括:

  • 如果系统调用期望某种数据类型作为参数(例如文件描述符,即fd),则会传递一个。这也是初始启动速度缓慢的原因,因为它生成可以从/sys、/proc和/dev读取文件的fd列表,然后补充这些文件的fd以供各种网络协议套接字使用。(在第一次运行时缓存了有关成功/失败的协议的信息,大大提高了后续运行的速度)。
  • 如果系统调用只接受某些值作为参数(例如“flags”字段),则Trinity具有可以传递的所有有效标志的列表。为了使事情更有趣,偶尔它会对其中一个标志进行位翻转。
  • 如果系统调用只接受一定范围的值,则传递的随机值通常会偏向于适应该范围。
      Trinity将其输出记录到文件中(每个子进程一个文件),并在实际进行系统调用之前对文件进行fsync。这样,如果Trinity触发了导致内核恐慌的情况,我们应该能够通过检查日志准确了解到发生了什么情况。此外,Trinity工具基于C语言开发。

1.1、整体架构

  关于Trinity的整体架构如下图所示,此图出自LCA: The Trinity fuzz tester,不过由于此篇文章年份较为久远,其中trinity-watchdog的功能已经被整合到trinity-main中了,不过Trinity的核心还是trinity-main与其各个child,所以trinity-watchdog就显得不是那么重要了,不过下面我们还是会对其进行详细分析。

在这里插入图片描述

  对于以上架构,其中:

  • trinity-main:执行各种初始化操作,然后创建执行系统调用Fuzz的子程序。trinity-main创建的共享内存区域用于记录各种全局信息(打开文件描述符号、执行的系统调用总数以及成功和失败的系统调用数等等)和每个子进程的各种信息(pid和执行的系统调用信息等等)。
  • trinity-watchdog:确保系统正常工作。它会检查子进程是否正在运行(可能会在系统调用中被暂停),如果没有运行,则会将其杀死。当主进程检测到其中一个子进程已终止时(因为trinity-watchdog将其终止或出于其它原因)会启动一个新的子进程来替换它。trinity-watchdog还监视共享内存区域的完整性。
  • childx:对系统调用进行Fuzz的各个子程序

1.2、trinity-main

  trinity-main是Trinity工具运行的核心,而trinity-main运行是从trinity.c开始的,所以我们要着重分析trinity.c的主函数,看看它都做了什么事情。

int main(int argc, char* argv[])  
{  
    int ret = EXIT_SUCCESS;  
    const char taskname[13]="trinity-main";  
  
    outputstd("Trinity " VERSION "  Dave Jones <davej@codemonkey.org.uk>\n");  
  
    progname = argv[0];  
  
    mainpid = getpid();  
  
    getrlimit(RLIMIT_NOFILE, &max_files_rlimit);  
  
    page_size = getpagesize();  
    num_online_cpus = sysconf(_SC_NPROCESSORS_ONLN);  
    max_children = num_online_cpus * 4; /* possibly overridden in params. */  
  
    select_syscall_tables();  
  
    create_shm();  
  
    parse_args(argc, argv);  
  
    init_uids();  
  
    change_tmp_dir();  
  
    init_shm();  
  
    init_taint_checking();  
  
    if (munge_tables() == FALSE) {  
        ret = EXIT_FAILURE;  
        goto out;  
    }  
  
    if (show_syscall_list == TRUE) {  
        dump_syscall_tables();  
        goto out;  
    }  
  
    if (show_ioctl_list == TRUE) {  
        dump_ioctls();  
        goto out;  
    }  
  
    if (show_unannotated == TRUE) {  
        show_unannotated_args();  
        goto out;  
    }  
  
    init_syscalls();  
  
    do_uid0_check();  
  
    if (do_specific_domain == TRUE)  
        find_specific_domain(specific_domain_optarg);  
  
    pids_init();  
  
    init_logging();  
  
    init_object_lists(OBJ_GLOBAL);  
  
    setup_initial_mappings();  
  
    parse_devices();  
  
    /* FIXME: Some better object construction method needed. */  
    create_futexes();  
    create_sysv_shms();  
  
  
    setup_main_signals();  
  
    no_bind_to_cpu = RAND_BOOL();  
  
    prctl(PR_SET_NAME, (unsigned long) &taskname);  
  
    if (open_fds() == FALSE) {  
        if (shm->exit_reason != STILL_RUNNING)  
            panic(EXIT_FD_INIT_FAILURE);    // FIXME: Later, push this down to multiple EXIT's.  
  
        _exit(EXIT_FAILURE);  
    }  
  
    setup_ftrace();  
  
    main_loop();  
  
    destroy_global_objects();  
  
    if (is_tainted() == TRUE)  
        stop_ftrace();  
  
    output(0, "Ran %ld syscalls. Successes: %ld  Failures: %ld\n",  
        shm->stats.op_count, shm->stats.successes, shm->stats.failures);  
    if (show_stats == TRUE)  
        dump_stats();  
	  
	    shutdown_logging();  
	  
	    ret = set_exit_code(shm->exit_reason);  
	out:  
	  
	    exit(ret);  
	}  

  这段代码由C语言实现。该程序的主要功能是执行Trinity模糊测试工具,用于模拟系统调用以测试系统的稳定性和安全性。下面是主函数的主要步骤和功能:

  1. 输出Trinity的版本信息和作者信息。
  2. 初始化一些参数和数据结构,包括获取系统的文件描述符限制(RLIMIT_NOFILE)、获取页大小、获取在线CPU数量等。
  3. 选择要使用的系统调用表(select_syscall_tables)。
  4. 创建共享内存(create_shm)。
  5. 解析命令行参数(parse_args)。
  6. 初始化用户标识(init_uids)。
  7. 更改临时目录(change_tmp_dir)。
  8. 初始化共享内存(init_shm)。
  9. 初始化污点检查(init_taint_checking)。
  10. 如果需要,修改系统调用表(munge_tables)。
  11. 根据命令行参数,显示系统调用列表、IO控制列表、未注释参数列表等。
  12. 初始化系统调用(init_syscalls)。
  13. 检查是否以root用户身份运行程序(do_uid0_check)。
  14. 如果需要,查找特定的域(find_specific_domain)。
  15. 初始化进程标识符(pids_init)。
  16. 初始化日志记录(init_logging)。
  17. 初始化对象列表(init_object_lists)。
  18. 设置初始映射(setup_initial_mappings)。
  19. 解析设备(parse_devices)。
  20. 创建互斥锁(create_futexes)和System V共享内存(create_sysv_shms)。
  21. 设置主信号处理函数(setup_main_signals)。
  22. 设置线程名称(prctl)。
  23. 打开文件描述符(open_fds)。
  24. 设置Ftrace跟踪(setup_ftrace)。
  25. 进入主循环(main_loop)。
  26. 销毁全局对象(destroy_global_objects)。
  27. 如果系统被污染,停止Ftrace跟踪(stop_ftrace)。
  28. 输出模拟的系统调用统计信息(output)。
  29. 如果需要,显示更详细的统计信息(dump_stats)。
  30. 关闭日志记录(shutdown_logging)。
  31. 设置退出码并退出程序(exit)。

  在这段代码中,我们主要关注上方我标红色的三部分,即:

  • 选择要使用的系统调用表(select_syscall_tables)
    select_syscall_tables函数(此函数位于“/trinity/tables.c”源代码文件中)的具体实现如下图所示:
    在这里插入图片描述
    可以发现,此函数的主要目的是根据程序的运行的环境选择合适的系统调用表,并将选定的系统调用表复制到相应的数据结构中,以便后续程序可以根据需要使用这些系统调用。
    不过有一个点需要注意,这里选择的系统调用表并不是系统自带的,而是经过Trinity修改后的系统调用结构体数组,以SYSCALLS64(即syscalls_x86_64)为例,其结构体数组(此结构体数组位于“/trinity/include/syscalls-x86_64.h”源代码文件中)具体实现如下图所示:
    在这里插入图片描述
    在这里定义了Trinity可以进行Fuzz的系统调用,这些系统调用分别在“/trinity/syscalls”目录中被实现。
    在这里插入图片描述
    后续就可以通过这些已经被Trinity实现的系统调用来生成对应的参数,从而对其进行Fuzz。我们可以随便打开一个Trinity自定义的系统调用,比如“alarm.c”。
    在这里插入图片描述
    可以发现,Trinity实现的自定义的系统调用都是由结构体表示的,而这些结构体中的成员才是Trinity生成测试数据的基础,所以我们就要关注这个syscallentry结构体(此结构体位于“/trinity/include/syscall.h”源代码文件中)中究竟都定义了什么。
struct syscallentry {  
    void (*sanitise)(struct syscallrecord *rec);  
    void (*post)(struct syscallrecord *rec);  
    int (*init)(void);  
    char * (*decode)(struct syscallrecord *rec, unsigned int argnum);  
  
    unsigned int number;  
    unsigned int active_number;  
    const char name[80];  
    const unsigned int num_args;  
    unsigned int flags;  
  
    const enum argtype arg1type;  
    const enum argtype arg2type;  
    const enum argtype arg3type;  
    const enum argtype arg4type;  
    const enum argtype arg5type;  
    const enum argtype arg6type;  
  
    const char *arg1name;  
    const char *arg2name;  
    const char *arg3name;  
    const char *arg4name;  
    const char *arg5name;  
    const char *arg6name;  
  
    struct results results1;  
    struct results results2;  
    struct results results3;  
    struct results results4;  
    struct results results5;  
    struct results results6;  
  
    unsigned int successes, failures, attempted;  
    unsigned int errnos[NR_ERRNOS];  
  
    /* FIXME: At some point, if we grow more type specific parts here, 
     * it may be worth union-ising this 
     */  
  
    /* ARG_RANGE */  
    const unsigned int low1range, hi1range;  
    const unsigned int low2range, hi2range;  
    const unsigned int low3range, hi3range;  
    const unsigned int low4range, hi4range;  
    const unsigned int low5range, hi5range;  
    const unsigned int low6range, hi6range;  
  
    /* ARG_OP / ARG_LIST */  
    const struct arglist arg1list;  
    const struct arglist arg2list;  
    const struct arglist arg3list;  
    const struct arglist arg4list;  
    const struct arglist arg5list;  
    const struct arglist arg6list;  
  
    const unsigned int group;  
    const int rettype;  
};  

  这段代码定义了一个结构体syscallentry,用于表示一个系统调用的条目。这个结构体包含了系统调用的各种信息,包括函数指针、系统调用号、名称、参数信息、标志等。下面是syscallentry结构体中各个成员的含义:

  • sanitise:指向一个函数,用于对系统调用进行清理或修正。
  • post:指向一个函数,用于在系统调用执行后进行处理。
  • init:指向一个函数,用于初始化系统调用相关的数据结构。
  • decode:指向一个函数,用于解码系统调用的参数。
  • number:系统调用号。
  • active_number:激活的系统调用号。
  • name:系统调用的名称。
  • num_args:系统调用的参数个数。
  • flags:系统调用的标志。
  • arg1type~arg6type:系统调用参数的类型。
  • arg1name~arg6name:系统调用参数的名称。
  • results1~results6:系统调用的结果。
  • successesfailuresattempted:系统调用的成功、失败和尝试次数。
  • errnos:系统调用可能返回的错误码。
  • low1range~hi6range:系统调用参数的范围。
  • arg1list~arg6list:系统调用参数的列表。
  • group:系统调用的分组。
  • rettype:系统调用的返回类型。

  可以发现,这个结构体用于描述生成系统调用测试用例的各种属性和信息,也就是说,Trinity的所有秘密都隐藏在这里,即Trinity就是根据此结构体生成的各个系统调用的测试数据。

  • 初始化系统调用(init_syscalls)
    init_syscalls函数(此函数位于/trinity/tables.c)的具体实现如下图所示。
    在这里插入图片描述
    此函数中根据是否是双系统架构的系统调用表进行不同函数的调用,不过这两个函数都差不多,我们以init_syscalls_biarch函数(此函数实现在“tables-biarch.c”源代码文件中)为例。
    在这里插入图片描述
    此函数主要进行的操作为:
    • 如果该条目存在且标记为激活状态(ACTIVE
    • 并且该条目有初始化函数(init
    • 则调用该初始化函数

       通过以上初始化过程,系统调用表中的每个系统调用条目将会被正确地初始化,以便后续在Fuzz测试或其它操作中使用。

  • 进入主循环(main_loop)
    main_loop函数定义在“/trinity/main.c”源代码文件中,其具体内容如下所示。
void main_loop(void)  
{  
    fork_children();  
  
    while (shm->exit_reason == STILL_RUNNING) {  
  
        handle_children();  
  
        taint_check();  
  
        if (shm_is_corrupt() == TRUE)  
            goto corrupt;  
  
        while (check_all_locks() == TRUE) {  
            reap_dead_kids();  
            if (shm->exit_reason == EXIT_REACHED_COUNT)  
                kill_all_kids();  
        }  
  
        if (syscalls_todo && (shm->stats.op_count >= syscalls_todo)) {  
            output(0, "Reached limit %lu. Telling children to exit.\n", syscalls_todo);  
            panic(EXIT_REACHED_COUNT);  
        }  
  
        check_children_progressing();  
  
        print_stats();  
  
        /* This should never happen, but just to catch corner cases, like if 
         * fork() failed when we tried to replace a child. 
         */  
        if (shm->running_childs < max_children)  
            fork_children();  
    }  
  
    /* if the pid map is corrupt, we can't trust that we'll 
     * ever successfully finish pidmap_empty, so skip it */  
    if ((shm->exit_reason == EXIT_LOST_CHILD) ||  
        (shm->exit_reason == EXIT_SHM_CORRUPTION))  
        goto dont_wait;  
  
    handle_children();  
  
    /* Are there still children running ? */  
    while (pidmap_empty() == FALSE) {  
        static unsigned int last = 0;  
  
        if (last != shm->running_childs) {  
            last = shm->running_childs;  
  
            output(0, "exit_reason=%d, but %d children still running.\n",  
                shm->exit_reason, shm->running_childs);  
        }  
  
        /* Wait for all the children to exit. */  
        while (shm->running_childs > 0) {  
            taint_check();  
  
            handle_children();  
            kill_all_kids();  
            /* Give children a chance to exit before retrying. */  
            sleep(1);  
        }  
        reap_dead_kids();  
    }  
  
corrupt:  
    kill_all_kids();  
  
dont_wait:  
    output(0, "Bailing main loop because %s.\n", decode_exit(shm->exit_reason));  
}  

   此函数的作用是执行主要的程序循环。以下是该函数的主要逻辑:

  1. fork_children()函数用于创建子进程。
  2. 在共享内存(shm)的exit_reasonSTILL_RUNNING时,执行循环体内的操作:
    • handle_children()处理子进程的相关操作。
    • taint_check()进行污点检查。
    • 如果共享内存被损坏(shm_is_corrupt() == TRUE),则跳转到corrupt标签处处理。
    • 在所有锁都被持有时,等待并回收已结束的子进程,如果exit_reasonEXIT_REACHED_COUNT,则杀死所有子进程。
    • 如果有待执行的系统调用并且已经达到执行系统调用的限制(syscalls_todo),则输出相关信息并panic
    • 检查子进程是否正在进行。
    • 打印统计信息。
    • 如果当前运行的子进程数量小于最大子进程数量,则再次创建子进程。
  3. 如果exit_reasonEXIT_LOST_CHILDEXIT_SHM_CORRUPTION,则跳转到dont_wait标签处。
  4. 处理子进程的相关操作。
  5. 当还有子进程在运行时,进行如下操作:
    • 等待所有子进程退出。
    • 检查并处理污点。
    • 处理子进程的相关操作。
    • 杀死所有子进程。
    • 休眠1秒。
  6. 回收已结束的子进程。
  7. 如果共享内存被损坏,直接杀死所有子进程。
  8. 输出程序退出信息。

   以上程序代码看似很多,不过我们只需要关注标红色的部分,即“fork_children()函数用于创建子进程。”,因为此部分内容才是真正创建子程序(即childx)来对各种系统调用进行Fuzz的核心部分,也就是说之前的内容都是铺垫和初始化,后面才到了Trinity工具的核心部分。关于子程序(即childx)这部分内容,将会在下一章节展开讲解。

1.3、childx

  根据前面的分析,现在我们已经知道了子进程(即childx)是由fork_children函数创建的。而在fork_children函数(此函数实现在“/trinity/main.c”源代码文件)中,我们要关注spawn_child函数,因为正是在此函数中,创建了真正的子进程。
在这里插入图片描述

  在spawn_child函数(此函数实现在“/trinity/main.c”源代码文件)中,我们要关注child_process函数,因为当子进程创建完毕后,就需要child_process函数来执行子进程的各种操作。
在这里插入图片描述

  在child_process函数(此函数实现在“/trinity/child.c”源代码文件)中,看起来有很多复杂的操作和函数,不过我们要抽丝剥茧,找到核心函数,即我们要关注random_syscall函数,因为此函数才是在子进程中对系统调用进行Fuzz的核心函数。
在这里插入图片描述

  random_syscall函数(此函数实现在“/trinity/random-syscall.c”源代码文件)看起来就比较清晰了,要做的工作也一目了然。
在这里插入图片描述

  此函数用于执行随机的系统调用,下面是此函数的主要执行步骤:

  1. 获取指向当前子进程的系统调用记录的指针rec
  2. 调用set_syscall_nr函数设置系统调用号。如果设置失败,则返回失败。
  3. 使用memset函数将系统调用后缀缓冲区postbuffer的内容清零。
  4. 调用generate_syscall_args函数生成系统调用的参数,并打印参数信息。
  5. 调用output_syscall_prefix函数输出系统调用前缀信息。
  6. 调用do_syscall函数执行系统调用,并记录系统调用的返回值和错误码。
  7. 调用output_syscall_postfix函数输出系统调用后缀信息。
  8. 调用handle_syscall_ret函数处理系统调用的返回值和错误码。
  9. 最后将函数的返回值设置为TRUE,表示成功执行了系统调用。

  看起来调用了很多函数来处理这个过程,不过核心函数只有两个,即:

  • generate_syscall_args
    此函数实现在“/trinity/generate-args.c”源代码文件中,其主要作用是生成用于系统调用Fuzz的各种参数。
    在这里插入图片描述
    这个函数其实也只是一个封装,因为其核心是调用的generic_sanitise函数(此函数实现在“/trinity/generate-args.c”源代码文件中),这个函数是生成用于系统调用Fuzz的各种参数的函数封装。
    在这里插入图片描述
    可以发现,在此函数中,主要是通过调用fill_arg函数(此函数实现在“/trinity/generate-args.c”源代码文件中)来对参数进行生成的。也就是说,Trinity工具最终就是通过fill_arg函数来生成用于系统调用Fuzz的各种参数。
static unsigned long fill_arg(struct syscallrecord *rec, unsigned int argnum)  
{  
    struct syscallentry *entry;  
    unsigned int call;  
    enum argtype argtype;  
  
    call = rec->nr;  
    entry = syscalls[call].entry;  
  
    if (argnum > entry->num_args)  
        return 0;  
  
    argtype = get_argtype(entry, argnum);  
  
    switch (argtype) {  
    case ARG_UNDEFINED:  
        if (RAND_BOOL())  
            return (unsigned long) rand64();  
        return (unsigned long) get_writable_address(page_size);  
  
    case ARG_FD:  
        if (RAND_BOOL()) {  
            unsigned int i;  
            /* If this is the 2nd or more ARG_FD, make it unique */  
            for (i = 0; i < argnum; i++) {  
                enum argtype arg;  
                arg = get_argtype(entry, i);  
                if (arg == ARG_FD)  
                    return get_new_random_fd();  
            }  
        }  
        return get_random_fd();  
  
    case ARG_LEN:  
        return (unsigned long) get_len();  
  
    case ARG_ADDRESS:  
        return handle_arg_address(rec, argnum);  
  
    case ARG_NON_NULL_ADDRESS:  
        return (unsigned long) get_non_null_address();  
  
    case ARG_MMAP:  
        return (unsigned long) get_map();  
  
    case ARG_PID:  
        return (unsigned long) get_pid();  
  
    case ARG_RANGE:  
        return handle_arg_range(entry, argnum);  
  
    case ARG_OP:    /* Like ARG_LIST, but just a single value. */  
        return handle_arg_op(entry, argnum);  
  
    case ARG_LIST:  
        return handle_arg_list(entry, argnum);  
  
    case ARG_CPU:  
        return (unsigned long) get_cpu();  
  
    case ARG_PATHNAME:  
        return (unsigned long) generate_pathname();  
  
    case ARG_IOVEC:  
        return handle_arg_iovec(entry, rec, argnum);  
  
    case ARG_IOVECLEN:  
    case ARG_SOCKADDRLEN:  
        /* We already set the len in the ARG_IOVEC/ARG_SOCKADDR case 
         * So here we just return what we had set there. */  
        return get_argval(rec, argnum);  
  
    case ARG_SOCKADDR:  
        return handle_arg_sockaddr(entry, rec, argnum);  
  
    case ARG_MODE_T:  
        return handle_arg_mode_t();  
  
    case ARG_SOCKETINFO:  
        return (unsigned long) get_rand_socketinfo();  
    }  
  
    BUG("unreachable!\n");  
}  

   总之,该函数用于根据给定的系统调用记录和参数编号来填充参数值。下面是函数的主要步骤:

  1. 获取当前系统调用记录rec对应的系统调用入口entry
  2. 检查参数编号是否超出系统调用的参数数量,如果是,则直接返回零。
  3. 获取参数类型argtype,该类型是由get_argtype函数根据系统调用入口和参数编号确定的。
  4. 根据参数类型执行相应的操作:
    • 如果参数类型是ARG_UNDEFINED,则根据随机布尔值决定返回随机的64位值或者可写地址的值。
    • 如果参数类型是ARG_FD,则根据随机布尔值决定返回随机的文件描述符或者唯一的新文件描述符。
    • 如果参数类型是ARG_LEN,则返回随机的长度值。
    • 如果参数类型是ARG_ADDRESS,则调用handle_arg_address函数处理地址参数。
    • 如果参数类型是ARG_NON_NULL_ADDRESS,则返回随机的非空地址值。
    • 如果参数类型是ARG_MMAP,则返回随机的内存映射地址。
    • 如果参数类型是ARG_PID,则返回随机的进程ID。
    • 如果参数类型是ARG_RANGE,则调用handle_arg_range函数处理范围参数。
    • 如果参数类型是ARG_OP,则调用handle_arg_op函数处理操作参数。
    • 如果参数类型是ARG_LIST,则调用handle_arg_list函数处理列表参数。
    • 如果参数类型是ARG_CPU,则返回随机的CPU编号。
    • 如果参数类型是ARG_PATHNAME,则调用generate_pathname函数生成路径名。
    • 如果参数类型是ARG_IOVEC,则调用handle_arg_iovec函数处理IO向量参数。
    • 如果参数类型是ARG_IOVECLENARG_SOCKADDRLEN,则返回之前设置的参数长度。
    • 如果参数类型是ARG_SOCKADDR,则调用handle_arg_sockaddr函数处理套接字地址参数。
    • 如果参数类型是ARG_MODE_T,则调用handle_arg_mode_t函数处理权限参数。
    • 如果参数类型是ARG_SOCKETINFO,则返回随机的套接字信息。
  5. 如果上述所有情况都不匹配,则产生一个BUG(错误)并输出错误消息。

   对于此函数,我们就不继续向下分析了,因为我大致看了一眼,这里不管是否调用了其它函数,最终也都基本是通过随机数来生成的测试参数,并没有什么更有价值的东西,所以对于此函数分析到此即可。

  • do_syscall
    此函数实现在“/trinity/syscall.c”源代码文件中,其主要目的是用于根据Trinity工具生成的系统调用参数来执行系统调用。
    在这里插入图片描述
    此函数的核心是调用__do_syscall函数(此函数实现在“/trinity/syscall.c”源代码文件中)来完成真正的Fuzz测试。
static void __do_syscall(struct syscallrecord *rec, enum syscallstate state)  
{  
    unsigned long ret = 0;  
  
    errno = 0;  
  
    shm->stats.op_count++;  
  
    if (dry_run == FALSE) {  
        int nr, call;  
        bool needalarm;  
  
        nr = rec->nr;  
        /* Some architectures (IA64/MIPS) start their Linux syscalls 
         * At non-zero, and have other ABIs below. 
         */  
        call = nr + SYSCALL_OFFSET;  
        needalarm = syscalls[nr].entry->flags & NEED_ALARM;  
        if (needalarm)  
            (void)alarm(1);  
  
        lock(&rec->lock);  
        rec->state = state;  
        unlock(&rec->lock);  
  
        if (rec->do32bit == FALSE) {  
            ret = syscall(call, rec->a1, rec->a2, rec->a3, rec->a4, rec->a5, rec->a6);  
        } else {  
            ret = syscall32(call, rec->a1, rec->a2, rec->a3, rec->a4, rec->a5, rec->a6);  
        }  
  
        /* If we became tainted, get out as fast as we can. */  
        if (is_tainted() == TRUE) {  
            stop_ftrace();  
            panic(EXIT_KERNEL_TAINTED);  
            _exit(EXIT_FAILURE);  
        }  
  
        if (needalarm)  
            (void)alarm(0);  
    }  
  
    lock(&rec->lock);  
    rec->errno_post = errno;  
    rec->retval = ret;  
    rec->state = AFTER;  
    unlock(&rec->lock);  
}  

   该函数负责实际执行系统调用,并将执行结果和可能的错误信息记录在系统调用记录中,下面是此函数的主要步骤:

  1. 将全局变量errno置为0,以便记录系统调用执行过程中的错误信息。
  2. 增加共享内存中统计信息stats中的操作计数。
  3. 如果不是在干扰测试(dry_run)模式下执行系统调用,则执行以下步骤:
    • 获取系统调用号和调用方式(32位或64位)。
    • 根据系统调用号调用相应的系统调用函数syscallsyscall32,并传入相应的参数。
    • 如果系统调用需要定时器警告(alarm),则设置定时器。
    • 在执行系统调用之前,将系统调用记录的状态设置为state
    • 执行系统调用,并将返回值存储在ret中。
    • 如果系统调用执行过程中产生了污染(tainted),则立即停止函数跟踪(ftrace)、触发内核污染退出(panic)并退出程序。
    • 如果系统调用需要定时器警告,执行完系统调用后取消定时器。
  4. errno存储在系统调用记录中的errno_post字段中。
  5. 将系统调用的返回值存储在系统调用记录中的retval字段中。
  6. 将系统调用记录的状态设置为AFTER
  7. 解锁系统调用记录的锁。

   每个子程序(即childx)都是按照上面分析的过程执行的,当我们想要对某个系统调用进行Fuzz时,就会创建对应的子程序,并通过generate_syscall_args函数生成对应的测试数据,最终通过do_syscall函数进行测试。

2、安装与使用

软件环境硬件环境约束条件
Ubuntu-22.04.2-desktop-amd64(内核版本5.19.0-43-generic)内存16GB具体的约束条件可见“2.1、源码安装”章节所示的软件版本约束
其余的软件环境可见“2.1、源码安装”章节所示的软件环境硬盘30GBTrinity-v1.9
暂无使用4个处理器,每个处理器4个内核,共分配16个内核暂无
暂无Trinity部署在VMware Pro 17上的Ubuntu22.04.2系统上(主机系统为Windows 11),硬件环境和软件环境也是对应的VMware Pro 17的硬件环境和软件环境暂无

2.1、源码安装

2.1.1 部署系统依赖组件

  1. 首先使用如下命令更新软件源:
$ sudo apt-get update
  1. 首先使用如下命令下载并安装Trinity所需要的组件:
$ sudo apt-get install git
$ sudo apt-get install vim
$ sudo apt-get install build-essential libelf-dev libnuma-dev

2.1.2 使用源码安装系统

  1. 首先进入系统根目录,并执行如下命令下载Trinity源代码文件。注意:下载的Trinity源代码的版本为v1.9:
$ cd /
$ sudo git clone https://github.com/kernelslacker/trinity.git
  1. 然后进入Trinity源代码目录,进行配置文件的生成以及编译和安装:
$ cd trinity/
$ sudo ./configure
$ sudo make
$ sudo make install
  1. 完成以上操作后,Trinity就已经安装完毕,就可以继续后面的测试了

2.2、使用方法

  Trinity的测试命令格式如下所示:

./trinity -C<num> -c<num> -l <path_to_log_file> -o <path_to_output_directory> --no-pause <path_to_executable>

  对于以上各个参数的解释如下:

  • -C<num>:指定测试的子进程数量为<num>
  • -c<num>:指定重复执行系统调用的次数为<num>次,并为每个系统调用使用随机输入数据
  • -l <path_to_log_file>:指定日志输出到<path_to_log_file>文件
  • -o <path_to_output_directory>:指定生成的测试用例输出到<path_to_output_directory>目录
  • --no-pause:避免在测试过程中需要手动确认生成的测试用例
  • <path_to_executable>:指定要进行测试的可执行文件的路径

  下面是一个测试命令示例:

./trinity -C10 -c1 -l log.txt -o testcases --no-pause ./example

  此测试命令的含义为:对名为example的可执行文件启10个子进程测试,在运行时,Trinity将重复执行系统调用1次,并为每个系统调用使用随机输入数据,将日志输出到log.txt文件,将生成的测试用例输出到testcases目录。
  其实Trinity还提供了将近二十种参数供读者使用,这些参数对后续的测试有相当大的作用,比如可以自定义随机种子、选择32/64位的系统调用、使用调试模式进行测试等。所以,为了方便后续测试使用,特将所有参数及其含义整理如下,在后续测试时,可时常查阅此表:

参数含义
--quiet/-q减少冗长输出。使用一次表示不输出寄存器的值,使用两次还会抑制系统调用计数。
--verbose增加输出信息的详细程度
-D调试模式。如果Trinity发生段错误,这个模式可以捕获核心转储,因为默认情况下子进程会忽略这些信号。
-sN使用N作为随机种子(如果省略则使用当前时间作为种子)。注意,目前存在一些错误,因此使用相同的种子运行Trinity不一定能得到完全相同的结果。
--kernel_taint/-T控制应该考虑哪些内核污点标志。支持以下标志名称:PROPRIETARY_MODULEFORCED_MODULEUNSAFE_SMPFORCED_RMMODMACHINE_CHECKBAD_PAGEUSERDIEOVERRIDDEN_ACPI_TABLEWARNCRAPFIRMWARE_WORKAROUND和OOT_MODULE。例如,要设置trinity仅监视BADWARNMACHINE_CHECK标志,应该指定“-T BAD,WARN,MACHINE_CHECK”参数。
--list/-L列出已知系统调用及其偏移量。
--proto/-P对于网络套接字,仅使用特定的数据包族。
--victims/-V受害者文件/目录。默认情况下,Trinity会遍历“/dev”、“/sys”和“/proc”。使用此选项可以指定其他路径(目前仅限一个路径)。
-p在进行系统调用后暂停。
--children/-C子进程数。
-x排除一个系统调用不被调用。当你不断遇到已知的内核bug时,这个选项非常有用。
-cN使用随机输入执行系统调用N。如果只是添加了一个系统调用,这个选项非常有用。
--group/-g用于指定启用一组系统调用。目前定义了“vm”“vfs”两个组。
--logging/-l <arg>①:off:禁用写日志到文件。如果你有一个串行控制台,这非常有用,但你可能会丢失关于调用了哪个系统调用、设置了哪些映射等信息。但这会让事情变得更快,因为它不再在每个系统调用后执行fsync()。②:<hostname>:将数据包通过UDP发送到运行在另一台主机上的trinity服务器。注意:此功能仍在开发中。启用此功能将禁用日志写入文件。③:<dir>:指定trinity将转储其日志文件的目录。
--ioctls/-I将显示所有可用的ioctl。
--arch/-a显式选择32位或64位变体的系统调用。

3、测试用例

3.1、Splice系统调用压力测试

  Splice系统调用压力测试指集中进行Splice系统调用的测试。splice()系统调用是用于在两个文件描述符之间移动数据的系统调用,可以用于高效地传输数据。通过指定-c splice,Trinity工具就可以重复使用splice()系统调用来进行测试和Fuzz。下面为进行Splice系统调用压力测试的详细步骤。

  1. 首先进入Trinity源代码目录,并执行如下命令进行Splice系统调用压力测试:
$ cd /
$ cd trinity/
$ ./trinity -c splice
  1. 命令成功执行后,Trinity就开始对Splice系统调用进行Fuzz,不过由于执行时间太长,只展示了Fuzz过程,并没有展示Fuzz结果:
    在这里插入图片描述

   注:实际执行中遇到的问题及解决方法

  • 问题1:
    1. 在进行Splice系统调用压力测试的步骤1的第三步启动测试的时候,出现如下问题:
      在这里插入图片描述

    2. 产生以上问题是因为无法打开日志文件,我们只需要执行$ sudo ./trinity -c splice命令,以root用户权限启动测试,虽然执行这条命令后会提示此命令执行失败,即提示我们不能用root用户权限启动,但是这时已经打开了日志文件,就可以继续使用非root用户权限进行后续的测试了

    3. 此时我们就解决了该问题,然后回到步骤1的第三步重新继续向下操作,其余的测试如果遇到这个问题也按照同样的方法解决

3.2、其它系统调用压力测试

  1. 首先进入Trinity源代码目录,并执行如下命令进行除Splice系统调用之外的每个系统调用的压力测试:
$ cd /
$ cd trinity/
$ ./trinity -x splice
  1. 命令成功执行后,Trinity就开始对除Splice系统调用之外的每个系统调用进行Fuzz,不过由于执行时间太长,只展示了Fuzz过程,并没有展示Fuzz结果:
    在这里插入图片描述

3.3、自定义系统调用压力测试

  1. 首先进入Trinity源代码目录,并执行如下命令进行关闭日志,并抑制大部分输出以尽可能快地运行,且使用16个子进程的系统调用的压力测试:
$ cd /
$ cd trinity/
$ ./trinity -qq -l off -C16
  1. 命令成功执行后,Trinity就开始对系统调用进行自定义的Fuzz,不过由于执行时间太长,只展示了Fuzz过程,并没有展示Fuzz结果:
    在这里插入图片描述

4、总结

4.1、部署架构

  关于Trinity部署的架构图,如下所示。
在这里插入图片描述

  对于以上架构图,我们具体来看Trinity是否对其中的组件进行了修改。详情可参见下方的表格。

是否有修改具体修改内容
主机内核
主机操作系统

4.2、漏洞检测对象

  1. 检测的对象为主机内核
  2. 针对的内核版本为5.19.0-43-generic
  3. 针对的漏洞类型为崩溃性错误

4.3、漏洞检测方法

  1. 根据Trinity的系统调用结构体生成系统调用测试用例
  2. 使用syscall()函数(其函数原型为long syscall(long number, ...);)执行系统调用,从而对内核进行Fuzz测试
  3. 将测试结果保存到主机中
  4. 目前可以进行测试的系统调用共325个

4.4、种子生成/变异技术

  1. 初始种子由Trinity生成
  2. 没有对种子进行变异
  3. 种子生成的策略基于随机,即随机生成特定类型的参数的具体值(比如bool类型、int类型和char类型等)

5、参考文献

[1] LCA: The Trinity fuzz tester
[2] [原创]内核漏洞挖掘技术系列(1)——trinity
[3] Linux system call fuzzer


总结

  以上就是本篇博文的全部内容,可以发现,Trinity的部署与使用的过程并不复杂,并且Trinity的Fuzz测试过程的脉络也比较清楚,是一个典型的Fuzz测试的过程。总而言之,Trinity是一个不错的Fuzz测试的工具,值得大家学习。相信读完本篇博客,各位读者一定对Trinity有了更深的了解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1680611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为正式放弃高通芯片 | 百能云芯

5月15日&#xff0c;据外媒最新报道&#xff0c;高通公司正式确认&#xff0c;华为已无需依赖其处理器供应。 在出口许可被正式吊销前&#xff0c;高通的首席财务官已公开表示&#xff0c;预计明年与华为之间的芯片销售将为零&#xff0c;因为华为决定不再从高通购买4G芯片。 报…

centos7下使用docker安装fastdfs服务

先查看容器是否已经存在 docker ps -a 删除掉之前的tracker及storage服务 docker rm tracker docker rm storage 1、没有镜像先下载镜像 docker pull morunchang/fastdfs 2、运行服务 a、不指定物理服务器路径 docker run -d --name tracker --nethost morunchang/fastdfs sh…

Android Studio kotlin 转 Java

一. 随笔记录 java代码可以转化成kotlin代码&#xff0c;当然 Kotlin 反过来也可以转java 在Android Studio中 可以很方便的操作 AS 环境&#xff1a;Android Studio Iguana | 2023.2.1 二. 操作步骤 1.步骤 顶部Tools ----->Kotlin ------>Show Kotlin Bytecode 步…

Zotero 使用入门(笔记)

参考文献&#xff1a;Zotero入门完整教程-共27节-免费&#xff0c;李长太&#xff0c; 仅供参考学习

vue2基础语法03——过滤器filter

vue2基础语法03——过滤器filter 1. 前言1.1 需求1.2 不用过滤器实现1.2.1 插值语法、计算属性、方法实现1.2.2 更多关于计算属性 和 方法 2. 使用过滤器实现2.1 说明2.2 例子12.3 例子2——优化2.3.1 默认字母不分割2.3.2 默认字母以分割 2.4 过滤器使用地方 3. 全局过滤器4. …

实验室无法培养的菌,原来可以这么研究!

厌氧氨氧化&#xff08;anammox&#xff09;细菌在全球氮循环和废水氮去除中发挥着至关重要的作用&#xff0c;由于anammox细菌生长缓慢、难以培养等特点&#xff0c;对其生态学和生物学特性知之甚少。近日&#xff0c;凌恩生物合作客户重庆大学陈猷鹏教授团队在《Science of t…

Gemini 5.14日更新 - 推出Gemini Advance服务

收到Gemini Advance试用邀请 今天和往常一样&#xff0c;打开Gemini&#xff0c;惊喜的发现右小角一行小字&#xff1a;试用Gemini Advance。好家伙&#xff0c;OpenAI 刚推出ChatGPT 4o&#xff0c;Google立马推出Gemini Advance&#xff0c;说明国外高科技企业也是很拼的。 …

哈夫曼编码的应用

数据结构与算法课的一个简单实验&#xff0c;记录一下&#xff0c;以供参考。 文章目录 要求测试样例统计字母出现次数建立哈夫曼树对字符编码对原文进行编码译码 要求 输入一段100—200字的英文短文&#xff0c;存入一文件a中。统计短文出现的字母个数n及每个字母的出现次数…

公域流量如何引流到私域流量?

公域流量和私域流量是数字营销中常用的两种流量类型。公域流量指的是通过搜索引擎、社交媒体等公共平台获取的流量&#xff0c;而私域流量则是指企业自身拥有的用户群体和数据。那么&#xff0c;如何将公域流量引流到私域流量呢&#xff1f;下面我将为您详细解答。 1、提供有价…

开源社区社群兴趣搭子圈子,系统开发新论坛交流兴趣爱好圈子论坛可搭建本地同城社群圈子社区圈子同城找搭子圈子,包含小程序+公众号H5+安卓苹果app,源码交付!

优势 1、长时间的陪玩APP源码开发经验&#xff0c;始终坚持从客户的实际需求出发 2、提供安全的陪玩系统源码开发解决方案 3、需求定制不走弯路&#xff0c;源码交付&#xff0c;可二开 4、追求精细化服务&#xff0c;力求做好每一个陪玩系统源码开发环节搭建流程支持 PC 端…

多步预测系列 | LSTM、CNN、Transformer、TCN、串行、并行模型集合

● 环境框架&#xff1a;python 3.9 pytorch 1.8 及其以上版本均可运行 ● 使用对象&#xff1a;论文需求、毕业设计需求者 ● 代码保证&#xff1a;代码注释详细、即拿即可跑通。 往期精彩内容&#xff1a; 时序预测&#xff1a;LSTM、ARIMA、Holt-Winters、SARIMA模型的分…

MySQL 进阶使用【函数、索引、视图、存储过程、存储函数、触发器】

前言 做数仓开发离不开 SQL &#xff0c;写了很多 HQL 回头再看 MySQL 才发现&#xff0c;很多东西并不是 HQL 所独创的&#xff0c;而是几乎都来自于关系型数据库通用的 SQL&#xff1b;想到以后需要每天和数仓打交道&#xff0c;那么不管是 MySQL 还是 Oracle &#xff0c;都…

部署YUM仓库及 NFS共享服务

YUM仓库服务 部署YUM软件仓库 使用YUM工具管理软件包 一、YUM概述 1.YUM (Yellow dog Updater Modified) 基于RPM包构建的软件更新机制可以自动解决依赖关系所有软件包由集中的YUM软件仓库提供 2. 准备安装源3-1 2.1 软件仓库的提供方式 FTP服务:ftp://..HTTP服务:htt…

线性回归学习笔记

学习了王天一博士的机器学习40讲&#xff0c;做个小总结&#xff1a; 1、机器学习中&#xff0c;回归问题隐含了输入变量和输出变量均可连续取值的前提。 2、单变量线性回归&#xff0c;所有样本到直线的欧氏距离之和最小&#xff0c;即均方误差最小化。 3、最小二乘法的几何意…

OceanBase集群如何进行OCP的替换

有OceanBase社区版的用户提出替换 OCP 管控平台的需求。举例来说&#xff0c;之前的OCP平台采用单节点&#xff0c;然而随着OceanBase集群的陆续上线和数量的不断增多&#xff0c;担心单节点的OCP可能面临故障风险&#xff0c;而丧失对OceanBase集群的管控能力。另此外&#xf…

Leetcode - 周赛397

目录 一&#xff0c;3146. 两个字符串的排列差 二&#xff0c;3147. 从魔法师身上吸取的最大能量 三&#xff0c;3148. 矩阵中的最大得分 四&#xff0c;3149. 找出分数最低的排列 一&#xff0c;3146. 两个字符串的排列差 本题就是求同一个字符在两个字符串中的下标之差的…

网页版Figma汉化

最近学习Figma&#xff0c;简单介绍一下网页版Figma的汉化方法 1.打开网址&#xff1a;Figma软件汉化-Figma中文版下载-Figma中文社区 2.下载汉化插件离线包 解压汉化包 3.点开谷歌的管理扩展程序 4.点击加载已解压的扩展程序&#xff0c;选择刚刚解压的包 这样就安装好了汉化…

stm32ADC注入通道使用笔记(以STM32F407 为例)

ADC_JDR1 存放的是第一次转换的数据 ADC_JDR2 存放的是第二次转换的数据 ADC_JDR3 存放的是第三次转换的数据 ADC_JDR4 存放的是第四次转换的数据 1.当 JL 0&#xff08;定序器中有 1 次注入转换&#xff09;时&#xff0c;ADC 将仅转换 JSQ4[4:0] 通道。值存入ADC_JDR1中…

dfs记忆化搜索,动态规划

动态规划概念&#xff1a; 给定一个问题&#xff0c;将其拆成一个个子问题&#xff0c;直到子问题可以直接解决。然后把子问题的答案保存起来&#xff0c;以减少重复计算。再根据子问题的答案反推&#xff0c;得出原问题解。 821 运行时间长的原因&#xff1a; 重复大量计算…

IT革新狂潮:引领未来的技术趋势

方向一&#xff1a;技术革新与行业应用 当前现状&#xff1a; 量子计算&#xff1a;量子计算的研究正在加速&#xff0c;尽管目前仍处于初级阶段&#xff0c;但其在药物研发、加密技术和材料科学等领域的应用潜力已被广泛认可。 虚拟现实&#xff08;VR&#xff09;与增强现实…