对 afl-as.h 的分析。
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
参考资料