对 afl-as.h 的分析。
- 2024-10-16:更新部分
main_payload_64
内容
trampoline_fmt_32
main_payload_32
__afl_maybe_log
使用 lahf
(Load AH with Flags)将标志寄存器 eflags 的低八位存储到 ah 寄存器中。
接下来将 __afl_area_ptr
的值移入 edx 寄存器中,如果 edx 寄存器为 0,说明此时已经初始化了共享内存,当前程序位于子进程中,接下来会跳入 [[#__afl_setup|__afl_setup
]] 函数中,否则按顺序执行 [[#__afl_store|__afl_store
]] 函数。
__afl_store
上述代码中,ecx 指的是插桩核心代码中生成的随机数,也就是 R(MAP_SIZE)
的值。__afl_prev_loc
指的是上一个访问的基本块的哈希。
- 如果未定义
COVERAGE_ONLY
宏:
- 将
__afl_prev_loc
的值移入 edi;
- 将 edi 与 ecx 异或,相当于存储一条路径;
- ecx 逻辑右移;
- 将 ecx 存入
__afl_prev_loc
;
- 如果定义了
COVERAGE_ONLY
宏:
- 如果定义了
SKIP_COUNTS
宏:
- 将
[edx+edi*1]
的值与 1 进行或操作,将结果存回原处,这种说不定很适合服务器的持续运行的情况~;
- 如果未定义
SKIP_COUNTS
宏:
__afl_return
从插桩代码返回,首先还原 al,再还原 eflags 寄存器,最后返回。
__afl_setup
这一段代码主要用于分配共享内存
如果已经失败了,就直接返回,不再尝试启动;
保存 eax 和 ecx;
调用 getenv("AFL_SHM_ENV")
,失败的话跳转到 [[#__afl_setup_abort|__afl_setup_abort
]];
- 调用
atoi
函数,将获取到的 AFL_SHM_ENV
值转换为数字;
- 调用
shmat
函数访问这一段共享内存;
- 失败的话跳转到 [[#__afl_setup_abort|
__afl_setup_abort
]];
将获取到的内存地址放入 __afl_area_ptr
和 edx 中;
恢复 ecx 和 eax;
__afl_forkserver
该函数会向 (FORKSRV_FD + 1)
写入四字节数据,这个 (FORKSRV_FD + 1)
就是之前在 init_forkserver 中子进程设置的状态管道:
在写入数据后,检查返回值是否为 4,代表是否真的向管道内写入了 4 字节,如果不为 4 字节的话跳转到 [[#__afl_fork_resume|__afl_fork_resume
]],否则继续向下,进入 [[#__afl_fork_wait_loop|__afl_fork_wait_loop
]]。
__afl_fork_wait_loop
这一段是插桩代码的主逻辑,插桩代码将在此处循环。
首先从控制管道读取 4 字节数据。
接下来调用 fork,如果调用成功且在子进程,跳转到 [[#__afl_fork_resume|__afl_fork_resume
]],如果是父进程则继续执行;
父进程 forkserver32
在父进程中,首先将 fork 的返回值,也就是 fork 出的子进程 pid 存放在 __afl_fork_pid
中;
接下来向状态管道发送子进程 pid 信息;
接下来调用 waitpid(__afl_fork_pid, __afl_temp, 0)
等待子进程发来信号,__afl_temp
会保存子进程的程序状态;
在收到子进程状态后,会向 status 管道发送四字节数据,告知父进程现在处于等待状态,然后跳转到 [[#__afl_fork_wait_loop|__afl_fork_wait_loop
]] 函数。
__afl_fork_resume
子进程会进入这一段逻辑。这段逻辑会关闭两个管道并恢复现场,跳转到 [[#__afl_store|__afl_store
]]。
__afl_die
__afl_setup_abort
main_payload_64
__afl_maybe_log
64
在 64 位的 main_payload_64
中也会先触发 __afl_maybe_log
,先检查是否 map 了共享内存,如果没有的话,跳转到 [[#__afl_setup-64|__afl_setup
64]],有发话则继续执行 [[#__afl_store-64|__afl_store
64]]。
__afl_store
64
__afl_setup
64
如果 __afl_setup_failure
不为零,则之前哪里出现了问题,直接退出。
这里使用了一个在结尾定义的变量 __afl_global_area_ptr
,如果里面有值,则用该值替代 __afl_area_ptr
,之后执行 [[#__afl_store-64|__afl_store
64]]。如果没有值,则执行 [[#__afl_setup_first-64|__afl_setup_first
64]]。
__afl_setup_first
64
这里开始初始化共享内存区域。记录一下栈帧偏移。
rsp = -0x160
rsp=-0x168
这里在 r12 中保存了 rsp。最后也是在这里恢复的,后面的 rsp 可以不用计算了。
这里是初始化过程
在 __afl_setup_first
之后,继续执行 __afl_forkserver
。
__afl_forkserver
64
这里向 ST_PIPE
写四字节,如果没写进去,则跳转到 [[#__afl_fork_resume-64|__afl_fork_resume
64]],否则向下执行 __afl_fork_wait_loop
。
__afl_fork_wait_loop
64
从 CTL_PIPE
读四字节
fork 出子进程,判断自己在哪个位置,如果 fork 失败了则跳到 [[#__afl_die-64|__afl_die
64]],如果在子进程中则跳到 [[#__afl_fork_resume-64|__afl_fork_resume
64]],注意此时 rsp=-0x188
;如果在父进程中则继续执行。
在父进程中,向 ST_PIPE
写四字节
waitpid
等待子进程退出
退出之后再向 ST_PIPE
写四字节,然后跳回开头重复执行。
__afl_fork_resume
64
rsp=-0x188
,这里做恢复,在 popq 两次之后从 r12 中恢复 rsp,然后 pop 一次再做完整恢复。在恢复之后就执行 [[#__afl_store-64|__afl_store
64]]。
__afl_die
64
下文定义了一些变量。
__afl_global_area_ptr
64
在结尾定义了一个 8 字节的变量 __afl_global_area_ptr
参考资料