admin管理员组

文章数量:1622542

看了一些博客,都是在说fuzzer和fork server进行交互,由fork server fork出子进程来执行程序,但是不太明白这两者到底是如何在代码层面进行交互的。

run_target中有这么一段代码,大概意思是fuzzer给fork server传递prev_timed_out,然后再从fork server读取子进程的pid,child_pid:

    s32 res;

    /* In non-dumb mode, we have the fork server up and running, so simply
       tell it to have at it, and then read back PID. */

    if ((res = write(fsrv_ctl_fd, &prev_timed_out, 4)) != 4) {

      if (stop_soon) return 0;
      RPFATAL(res, "Unable to request new process from fork server (OOM?)");

    }

    if ((res = read(fsrv_st_fd, &child_pid, 4)) != 4) {

      if (stop_soon) return 0;
      RPFATAL(res, "Unable to request new process from fork server (OOM?)");

    }

    if (child_pid <= 0) FATAL("Fork server is misbehaving (OOM?)");

我现在的问题是,为什么fuzzer给fork server传了个参数,fork server就直接返回pid了呢?这中间两者是如何进行交互的?fork server做了什么,就传递了一个child_pid出来?

fork server进程是执行了下面这段代码(删去了一些不重要的代码):

  if (!forksrv_pid) {

    struct rlimit r;

    /* Isolate the process and configure standard descriptors. If out_file is
       specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */

    setsid();
    
    dup2(dev_null_fd, 1);
    dup2(dev_null_fd, 2);

    if (out_file) {

      dup2(dev_null_fd, 0);

    } else {

      dup2(out_fd, 0);
      close(out_fd);

    }

    /* Set up control and status pipes, close the unneeded original fds. */

    if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) PFATAL("dup2() failed");
    if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) PFATAL("dup2() failed");

    close(ctl_pipe[0]);
    close(ctl_pipe[1]);
    close(st_pipe[0]);
    close(st_pipe[1]);

    close(out_dir_fd);
    close(dev_null_fd);
    close(dev_urandom_fd);
    close(fileno(plot_file));

    execv(target_path, argv);

    /* Use a distinctive bitmap signature to tell the parent about execv()
       falling through. */

    *(u32*)trace_bits = EXEC_FAIL_SIG;
    exit(0);

  }

可能需要理解setsid();?
简单搜索了下,还得去理解进程相关只是,于是去问了bing,bing的回答告诉我:setsid()函数是一个系统调用,它的作用是创建一个新的会话(session),并使得当前进程成为会话的首进程(session leader),这个函数似乎和我想知道的东西没有联系。

问了下bing,并参考了这个博客:https://blog.csdn/Little_Bro/article/details/122694054,fork server的交互还和插桩有关系。

查看了AFL白皮书:https://github/mirrorer/afl/blob/master/docs/technical_details.txt,写的很粗略,还是得去看作者的博客:https://lcamtuf.blogspot/2014/10/fuzzing-binaries-without-execve.html

Unfortunately, there is also a problem: especially for simple libraries, you may end up spending most of the time waiting for execve(), the linker, and all the library initialization routines to do their job. I’ve been thinking of ways to minimize this overhead in american fuzzy lop, but most of the ideas I had were annoyingly complicated. For example, it is possible to write a custom ELF loader and execute the program in-process while using mprotect() to temporarily lock down the memory used by the fuzzer itself - but things such as signal handling would be a mess. Another option would be to execute in a single child process, make a snapshot of the child’s process memory and then “rewind” to that image later on via /proc/pid/mem - but likewise, dealing with signals or file descriptors would require a ton of fragile hacks.

为什么不直接多次调用execve()?因为每次调用 execve()都会有一些预处理的开销,作者想要加快这个过程。(不太了解预处理的过程,后续有需要再了解)

Luckily, Jann Horn figured a different, much simpler approach, and sent me a patch for afl out of the blue 😃 It boils down to injecting a small piece of code into the fuzzed binary - a feat that can be achieved via LD_PRELOAD, via PTRACE_POKETEXT, via compile-time instrumentation, or simply by rewriting the ELF binary ahead of the time. The purpose of the injected shim is to let execve() happen, get past the linker (ideally with LD_BIND_NOW=1, so that all the hard work is done beforehand), and then stop early on in the actual program, before it gets to processing any inputs generated by the fuzzer or doing anything else of interest. In fact, in the simplest variant, we can simply stop at main().

作者给出了一个很巧妙的解决方法,在被fuzzed的程序中插桩,让这个程序在完成预处理后暂停(比如再main函数的第一句话暂停),然后在这里调用fork(),被fork出来的子进程将会直接跳过预处理过程,开始执行实际处理。

Once the designated point in the program is reached, our shim simply waits for commands from the fuzzer; when it receives a “go” message, it calls fork() to create an identical clone of the already-loaded program; thanks to the powers of copy-on-write, the clone is created very quickly yet enjoys a robust level of isolation from its older twin. Within the child process(fork server创建的子进程), the injected code returns control to the original binary, letting it process the fuzzer-supplied input data (and suffer any consequences of doing so). Within the parent, the shim relays the PID of the newly-crated process to the fuzzer and goes back to the command-wait loop.

作者把插入的代码叫做slim(分隔片,还是很形象的),slim等待来自fuzzer的命令(对应run_target中的write(fsrv_ctl_fd, &prev_timed_out, 4)?),在收到fuzzer的命令后,fork server fork出来一个真正执行二进制程序的fuzzed进程,并给fuzzer返回一个pid。

这里有一个问题,函数参数是在哪里传递的呢?write(fsrv_ctl_fd, &prev_timed_out, 4)似乎没有传递参数。

接下俩作者还讨论了实际实现可能遇到的问题,以及插桩的汇编代码

https://blog.csdn/Little_Bro/article/details/12269405,这个博客对插桩代码进行了解释,但是我目前不需要对插桩代码理解的那么清楚,已经明白了fork server和fuzzer之间交互的逻辑

本文标签: ForkAFLfuzzerServer