抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

框架:

dup2 pipe 与fork 一同使用,其框架如下:

image-20220602100927633

思路:

父进程:

  1. 关闭ctl_pipe[0] 读端, 获取st_pipe[0] 子进程中调用execv的状态-> 即 forkserver是否准备好
  2. 写入ctl_pipe[1] 通知子进程fuzz 进行一个fork

子进程:

  1. 关闭st_pipe[0] 读端,获取ctl_pipe[1] 接收来自父进程的命令,开启一个fork

  2. 写入st_pipe[1] , 通知父进程forkserver准备好,并进入loop循环

子进程详细描述如下:

调用execv之前:

  1. 重定向FORKSRV_FDctl_pipe[0],重定向FORKSRV_FD + 1st_pipe[1]
    1. 子进程只能读取命令
    2. 子进程只能发送(“写出”)状态
  2. 关闭子进程里的一些文件描述符
  3. execv(target_path, argv)带参数执行target

第一个target会进入_fn_start_forkserver_loop,并充当fork server,在整个Fuzz的过程中,它都不会结束,每次要Fuzz一次target,都会从这个fork server fork出来一个子进程去fuzz。

execv: target目标中_fn_start_forkserver:

  • 首先设置child_stopped为0,然后通过FORKSRV_FD + 1向状态管道写入4个字节,告知fuzz父进程已经准备好了。

  • 然后进入fuzz loop循环

    • 通过read从控制管道FORKSRV_FD读取4个字节,如果当前管道中没有内容,就会堵塞在这里,如果读到了,就代表fuzz父进程命令我们fork server去执行一次fuzz

    • 如果child_stopped为0 :

      则直接fork出一个子进程去进行fuzz然后此时对于子进程就会关闭和控制管道和状态管道相关的fd,然后return跳出fuzz loop,恢复正常执行。

    • 如果child_stopped为1,这是对于persistent mode的特殊处理,此时子进程还活着,只是被暂停了,所以可以通过kill(child_pid, SIGCONT)来简单的重启,然后设置child_stopped为0。

    • 然后fork server向状态管道FORKSRV_FD + 1写入子进程的pid,然后等待子进程结束,注意这里对于persistent mode,我们会设置waitpid的第三个参数为WUNTRACED,代表若子进程进入暂停状态,则马上返回。

    • WIFSTOPPED(status)宏确定返回值是否对应于一个暂停子进程,因为在persistent mode里子进程会通过SIGSTOP信号来暂停自己,并以此指示运行成功,所以在这种情况下,我们需要再进行一次fuzz,就只需要和上面一样,通过SIGCONT信号来唤醒子进程继续执行即可,不需要再进行一次fuzz。

      设置child_stopped为1。

    • 当子进程结束以后,向状态管道FORKSRV_FD + 1写入4个字节,通知fuzz父进程这次target执行结束了。

父进程详细描述如下:

  1. 关闭不需要的endpoints

    close(ctl_pipe[0]);
    close(st_pipe[1]);
    
    fsrv_ctl_fd = ctl_pipe[1];//父进程只能发送("写出")命令
    fsrv_st_fd = st_pipe[0];//父进程只能读取状态
  2. 等待fork server启动,但是不能等太久

    1. 从管道里读取4个字节到status里,如果读取成功,则代表fork server成功启动,就结束这个函数并返回。
    2. 如果超时,就抛出异常。
  3. 子进程启动异常处理

源码:

开启管道:

int st_pipe[2], ctl_pipe[2];

/*  = 0 success */
if( pipe(st_pipe) || pipe(ctl_pipe)) PFATAL("pipe() failed");

子进程:

if (!forksrv_pid )
{
        ACTF("[%d:/child] child ",__LINE__);
        /* cid code */

        setsid();
        close(ctl_pipe[1]); /* 关闭ctl 写端 */
        close(st_pipe[0]);  /* 关闭st 读端 */


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

        ACTF("[%d:/child] start to execv %s",__LINE__,target_path.c_str());
        
        execv(target_path.c_str(), argv);        
}

父进程:

/* fpid code */
ACTF("[%d:/parent] start to parent ",__LINE__);

close(ctl_pipe[0]); /* close read */
close(st_pipe[1]); /* close write */ 

fsrv_ctl_fd = ctl_pipe[1]; /* write */ 
fsrv_st_fd = st_pipe[0];  /* read */

/* Wait for the fork server to come up, but don't wait too long. */
it.it_value.tv_sec = ((exec_tmout * 100*FORK_WAIT_MULT)/1000);
it.it_value.tv_usec = ((exec_tmout * 100*FORK_WAIT_MULT) % 1000) * 1000;

setitimer(ITIMER_REAL,&it,NULL);

rlen = read(fsrv_st_fd,&status,4);

ACTF("[%d:/parent] rlen = : %d \n",__LINE__,rlen);

it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;

setitimer(ITIMER_REAL, &it, NULL);

if (rlen == 4) {
    OKF("[%d:/parent] All right - fork server is up.",__LINE__);
    return;
}

FATAL("[%d:/parent] Fork server handshake failed",__LINE__);

**execv:**中

static void _fn_start_forkserver(void){
    static u8 tmp[4];

    i32 child_pid;
    u8 child_stopped = 0;   

    if (write(FORKSRV_FD + 1, tmp, 4) != 4)
    {
        PFATAL("write FORKSRV_FD+1 !=4  %d",__LINE__);
        return;
    } 
    OKF("[%d:/execv] Fuzz  I am ready\n",__LINE__);
    while(1){
        OKF("[%d:/execv] _fn_forkserver_loop ....",__LINE__);
        u32 was_killed;
        i32 status;

        /* Wait for parent by reading from the pipe. Abort if read fails. */
        if (read(FORKSRV_FD, &was_killed, 4) != 4){
            // PFATAL("abort read fails %d",__LINE__);
            _exit(1);
        } 

        /* If we stopped the child in persistent mode, but there was a race
        condition and afl-fuzz already issued SIGKILL, write off the old
        process. */
        if(child_stopped && was_killed ){
            child_stopped = 0;
            if (waitpid(child_pid, &status, 0) < 0){
                PFATAL("write off the old process. %d",__LINE__);
                _exit(1);
            } 
        }

        OKF("[%d:/execv]  ....",__LINE__);

        if(!child_stopped){
            OKF("[%d:/execv] start a child pid ",__LINE__);
            /* Once woken up, create a clone of our process. */
            child_pid = fork();
            if (child_pid < 0) _exit(1);

            /* In child process: close fds, resume execution. */
            if(!child_pid){
                OKF("[%d:/execv] close FORKSRV_FD & FORKSRV_FD+1",__LINE__);
                close(FORKSRV_FD);
                close(FORKSRV_FD + 1);
                return;
            }
        }else{
            /* Special handling for persistent mode: if the child is alive but
            currently stopped, simply restart it with SIGCONT. */
            OKF("[%d:/execv] kill child_pid",__LINE__);
            kill(child_pid, SIGCONT);
            child_stopped = 0;
                
        }
        OKF("[%d:/execv]  ....",__LINE__);
        /* In parent process: write PID to pipe, then wait for child. */
        if (write(FORKSRV_FD + 1, &child_pid, 4) != 4){
            _exit(1);
        } 
        if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0)
        {
            _exit(1);
        }
        /* In persistent mode, the child stops itself with SIGSTOP to indicate
        a successful run. In this case, we want to wake it up without forking
        again. */

        if (WIFSTOPPED(status)) child_stopped = 1;

        /* Relay wait status to pipe, then loop back. */

        if (write(FORKSRV_FD + 1, &status, 4) != 4) _exit(1);

    }
}

评论吧



本站总访问量为 访客数为

鲁 ICP 备 20018157 号-1
Copyright 2021 - 2022 sizaif. All Rights Reserved