框架:
dup2 pipe 与fork 一同使用,其框架如下:
思路:
父进程:
- 关闭
ctl_pipe[0]
读端, 获取st_pipe[0]
子进程中调用execv
的状态-> 即forkserver
是否准备好 - 写入
ctl_pipe[1]
通知子进程fuzz 进行一个fork
子进程:
-
关闭
st_pipe[0]
读端,获取ctl_pipe[1]
接收来自父进程的命令,开启一个fork -
写入
st_pipe[1]
, 通知父进程forkserver
准备好,并进入loop
循环
子进程详细描述如下:
调用execv
之前:
- 重定向
FORKSRV_FD
到ctl_pipe[0]
,重定向FORKSRV_FD + 1
到st_pipe[1]
1. 子进程只能读取命令
2. 子进程只能发送(“写出”)状态 - 关闭子进程里的一些文件描述符
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执行结束了。
-
父进程详细描述如下:
-
关闭不需要的endpoints
close(ctl_pipe[0]); close(st_pipe[1]); fsrv_ctl_fd = ctl_pipe[1];//父进程只能发送("写出")命令 fsrv_st_fd = st_pipe[0];//父进程只能读取状态
-
等待fork server启动,但是不能等太久
- 从管道里读取4个字节到status里,如果读取成功,则代表fork server成功启动,就结束这个函数并返回。
- 如果超时,就抛出异常。
-
子进程启动异常处理
源码:
开启管道:
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);
}
}