新年快乐!
本篇文章是AFL源码阅读系列的最后一篇,在本篇之后就不会再专门大范围的聊AFL源码了,如果后续在实践过程中学习到了一些新的AFL使用/魔改技巧会出番外篇再分享~(茶)
本篇文章主要讲3个文件:
最好是看完上一篇再来看本篇。
本文件作为clang的wrapper
如果经历以上过程均没有找到,那么abort掉。
本函数编辑参数数组。
接下来AFL提到了如下两种方式来进行插桩:
1.传统模式:使用afl-llvm-pass.so
注入来插桩。
2.'trace-pc-guard' mode:使用原生的 LLVM instrumentation callbacks
第二种方式相关链接如下:
https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards
接下来扫描参数数组,设置对应的标志位
如果环境变量设置了"AFL_HARDEN"
如果是使用的方式2进行插桩。判断是否设置"AFL_INST_RATIO"
,若设置了则abort
接下来设置一些优化选项与对内置函数的检查。然后定义了两个宏,如下:
快速了解llvm可以看一下:
https://zhuanlan.zhihu.com/p/122522485
https://llvm.org/docs/WritingAnLLVMPass.html#introduction-what-is-a-pass
在AFL中只有一个Pass:
在AFLCoverage::runOnModule中进行如下操作。获取线程上下文。https://stackoverflow.com/questions/13184835/what-is-llvm-context
如果stderr
为终端。且未设置"AFL_QUIET"
模式。输出对应的信息。
否则设置be_quiet = 1。
设置插桩密度。
获取前一个桩的位置(随机数编号)
接下来进入插桩过程,扫描basic block:
生成当前block的随机编号
首先获取共享内存块的地址,然后找到对应当前桩的计数位置
设置对应的AFLPrevLoc
为cur_loc >> 1
,关于为什么要右移1,主要是为了做路径区分,可以看上一篇。
整体的过程还是非常清晰的。
afl的llvmmode中有三个功能在这里实现。
AFL尝试通过仅执行目标二进制文件一次来优化性能,
在main()之前停止它,然后克隆此“主”进程以获取
稳定提供fuzz目标。
尽管这种方法消除了许多OS,链接器和libc级别
执行程序的成本,它并不总是对二进制文件有帮助
执行其他耗时的初始化步骤-例如,解析大型配置
文件进入模糊数据。
在这种情况下,最好稍后再初始化forkserver
大多数初始化工作已经完成,但是在二进制尝试之前
读取模糊的输入并进行解析;在某些情况下,这可以提供10倍以上的收益
性能提升。
只需将:
这一段插入对应位置即可。
具体可见上文的-D__AFL_INIT()
宏
真正起作用的是如下:
接下来是__afl_start_forkserver()
一些库提供的API是无状态的,或者可以在其中重置状态的API
处理不同的输入文件之间。进行此类重置后,
一个长期存在的过程可以重复使用,以尝试多个测试用例,
消除了重复执行fork()调用的需求以及相关的OS开销。
基本结构如下:
关于循环的最大数量,循环内指定的数值控制AFL从头重新启动过程之前的最大迭代次数。这样可以最大程度地减少内存泄漏和类似故障的影响;1000是一个很好的起点,而更高的值会增加出现hiccups的可能性,而不会给带来任何实际的性能优势。
整体过程大致如下:
当第一次运行到AFL_LOOP 时,进行初始化然后return 1,此时满足```while (AFL_LOOP(1000))```,于是执行一次fuzz。
当我们再次进入fuzz loop时,计数减1,触发:raise(SIGSTOP)
暂停进程,而forkserver收到了此时的暂停信号,设置child_stopped = 1
,通知afl-fuzzer。
当afl-fuzzer再进行一次fuzz时,恢复之前的子进程继续执行,并设置child_stopped为0。
此时相当于重新执行了一遍程序,重新对afl_prev_loc设置,随后直接返回1,此时又进入```while (AFL_LOOP(1000))执行一次,接下来下一次触发
raise(SIGSTOP)```暂停进程。
https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards
如果想使用这个功能需要设置:
传入:With -fsanitize-coverage=trace-pc-guard
the compiler will insert the following code on every edge
然后重新编译。
此函数__sanitizer_cov_trace_pc_guard
将在每个basic block edge(边界)被调用,其实就是通过(*guard)索引到共享内存对应的计数位置,然后计数加一。
而guard的初始化如下:
首先获取了插桩密度。
然后从第一个guard遍历。llvm设置guard的收尾为start与stop。并设置guard指向的值。
注意有一定的概率不进行插桩(随机插桩)。
特别的,若此时的basic block因为概率选择的原因没有进行插桩,那么设置这里的guard指向的值为0,这里的0是作为一个特殊值,代表不进行插桩。
if
(!strcmp(cur,
"-m32"
)) bit_mode
=
32
;
if
(!strcmp(cur,
"armv7a-linux-androideabi"
)) bit_mode
=
32
;
if
(!strcmp(cur,
"-m64"
)) bit_mode
=
64
;
if
(!strcmp(cur,
"-x"
)) x_set
=
1
;
if
(!strcmp(cur,
"-fsanitize=address"
) ||
!strcmp(cur,
"-fsanitize=memory"
)) asan_set
=
1
;
if
(strstr(cur,
"FORTIFY_SOURCE"
)) fortify_set
=
1
;
if
(!strcmp(cur,
"-Wl,-z,defs"
) ||
!strcmp(cur,
"-Wl,--no-undefined"
))
continue
;
if
(!strcmp(cur,
"-m32"
)) bit_mode
=
32
;
if
(!strcmp(cur,
"armv7a-linux-androideabi"
)) bit_mode
=
32
;
if
(!strcmp(cur,
"-m64"
)) bit_mode
=
64
;
if
(!strcmp(cur,
"-x"
)) x_set
=
1
;
if
(!strcmp(cur,
"-fsanitize=address"
) ||
!strcmp(cur,
"-fsanitize=memory"
)) asan_set
=
1
;
if
(strstr(cur,
"FORTIFY_SOURCE"
)) fortify_set
=
1
;
if
(!strcmp(cur,
"-Wl,-z,defs"
) ||
!strcmp(cur,
"-Wl,--no-undefined"
))
continue
;
-
D__AFL_LOOP(_A)
=
({ static volatile char
*
_B __attribute__((used));
_B
=
(char
*
)
__attribute__((visibility(
"default"
)))
int
_L(unsigned
int
) __asm__(
"___afl_persistent_loop"
);
_L(_A); })
-
D__AFL_LOOP(_A)
=
({ static volatile char
*
_B __attribute__((used));
_B
=
(char
*
)
__attribute__((visibility(
"default"
)))
int
_L(unsigned
int
) __asm__(
"___afl_persistent_loop"
);
_L(_A); })
-
D__AFL_INIT()
=
do {
static volatile char
*
_A __attribute__((used)); \
_A
=
(char
*
)
__attribute__((visibility(
"default"
)))void _I(void) __asm__(
"___afl_manual_init"
); \
_I(); }
while
(
0
)
-
D__AFL_INIT()
=
do {
static volatile char
*
_A __attribute__((used)); \
_A
=
(char
*
)
__attribute__((visibility(
"default"
)))void _I(void) __asm__(
"___afl_manual_init"
); \
_I(); }
while
(
0
)
namespace {
class
AFLCoverage : public ModulePass {
public:
static char
ID
;
AFLCoverage() : ModulePass(
ID
) { }
bool
runOnModule(Module &M) override;
};
}
namespace {
class
AFLCoverage : public ModulePass {
public:
static char
ID
;
AFLCoverage() : ModulePass(
ID
) { }
bool
runOnModule(Module &M) override;
};
}
LLVMContext &C
=
M.getContext();
IntegerType
*
Int8Ty
=
IntegerType::getInt8Ty(C);
IntegerType
*
Int32Ty
=
IntegerType::getInt32Ty(C);
LLVMContext &C
=
M.getContext();
IntegerType
*
Int8Ty
=
IntegerType::getInt8Ty(C);
IntegerType
*
Int32Ty
=
IntegerType::getInt32Ty(C);
char be_quiet
=
0
;
if
(isatty(
2
) && !getenv(
"AFL_QUIET"
)) {
SAYF(cCYA
"afl-llvm-pass "
cBRI VERSION cRST
" by <lszekeres@google.com>\n"
);
}
else
be_quiet
=
1
;
char be_quiet
=
0
;
if
(isatty(
2
) && !getenv(
"AFL_QUIET"
)) {
SAYF(cCYA
"afl-llvm-pass "
cBRI VERSION cRST
" by <lszekeres@google.com>\n"
);
}
else
be_quiet
=
1
;
/
*
Decide instrumentation ratio
*
/
char
*
inst_ratio_str
=
getenv(
"AFL_INST_RATIO"
);
unsigned
int
inst_ratio
=
100
;
if
(inst_ratio_str) {
if
(sscanf(inst_ratio_str,
"%u"
, &inst_ratio) !
=
1
|| !inst_ratio ||
inst_ratio >
100
)
FATAL(
"Bad value of AFL_INST_RATIO (must be between 1 and 100)"
);
}
/
*
Decide instrumentation ratio
*
/
char
*
inst_ratio_str
=
getenv(
"AFL_INST_RATIO"
);
unsigned
int
inst_ratio
=
100
;
if
(inst_ratio_str) {
if
(sscanf(inst_ratio_str,
"%u"
, &inst_ratio) !
=
1
|| !inst_ratio ||
inst_ratio >
100
)
FATAL(
"Bad value of AFL_INST_RATIO (must be between 1 and 100)"
);
}
GlobalVariable
*
AFLMapPtr
=
new GlobalVariable(M, PointerType::get(Int8Ty,
0
), false,
GlobalValue::ExternalLinkage,
0
,
"__afl_area_ptr"
);
GlobalVariable
*
AFLMapPtr
=
new GlobalVariable(M, PointerType::get(Int8Ty,
0
), false,
GlobalValue::ExternalLinkage,
0
,
"__afl_area_ptr"
);
GlobalVariable
*
AFLPrevLoc
=
new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage,
0
,
"__afl_prev_loc"
,
0
, GlobalVariable::GeneralDynamicTLSModel,
0
, false);
GlobalVariable
*
AFLPrevLoc
=
new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage,
0
,
"__afl_prev_loc"
,
0
, GlobalVariable::GeneralDynamicTLSModel,
0
, false);
BasicBlock::iterator IP
=
BB.getFirstInsertionPt();
IRBuilder<> IRB(&(
*
IP));
BasicBlock::iterator IP
=
BB.getFirstInsertionPt();
IRBuilder<> IRB(&(
*
IP));
if
(AFL_R(
100
) >
=
inst_ratio)
continue
;
if
(AFL_R(
100
) >
=
inst_ratio)
continue
;
/
*
Make up cur_loc
*
/
unsigned
int
cur_loc
=
AFL_R(MAP_SIZE);
ConstantInt
*
CurLoc
=
ConstantInt::get(Int32Ty, cur_loc)
/
*
Make up cur_loc
*
/
unsigned
int
cur_loc
=
AFL_R(MAP_SIZE);
ConstantInt
*
CurLoc
=
ConstantInt::get(Int32Ty, cur_loc)
LoadInst
*
PrevLoc
=
IRB.CreateLoad(AFLPrevLoc);
PrevLoc
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
Value
*
PrevLocCasted
=
IRB.CreateZExt(PrevLoc, IRB.getInt32Ty());
LoadInst
*
PrevLoc
=
IRB.CreateLoad(AFLPrevLoc);
PrevLoc
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
Value
*
PrevLocCasted
=
IRB.CreateZExt(PrevLoc, IRB.getInt32Ty());
/
*
Load SHM pointer
*
/
LoadInst
*
MapPtr
=
IRB.CreateLoad(AFLMapPtr);
MapPtr
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
Value
*
MapPtrIdx
=
IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc))
/
*
Load SHM pointer
*
/
LoadInst
*
MapPtr
=
IRB.CreateLoad(AFLMapPtr);
MapPtr
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
Value
*
MapPtrIdx
=
IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc))
LoadInst
*
Counter
=
IRB.CreateLoad(MapPtrIdx);
Counter
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
Value
*
Incr
=
IRB.CreateAdd(Counter, ConstantInt::get(Int8Ty,
1
));
IRB.CreateStore(Incr, MapPtrIdx)
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
LoadInst
*
Counter
=
IRB.CreateLoad(MapPtrIdx);
Counter
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
Value
*
Incr
=
IRB.CreateAdd(Counter, ConstantInt::get(Int8Ty,
1
));
IRB.CreateStore(Incr, MapPtrIdx)
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
/
*
Set
prev_loc to cur_loc >>
1
*
/
StoreInst
*
Store
=
IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >>
1
), AFLPrevLoc);
Store
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
/
*
Set
prev_loc to cur_loc >>
1
*
/
StoreInst
*
Store
=
IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >>
1
), AFLPrevLoc);
Store
-
>setMetadata(M.getMDKindID(
"nosanitize"
), MDNode::get(C,
None
));
else
OKF(
"Instrumented %u locations (%s mode, ratio %u%%)."
,
inst_blocks, getenv(
"AFL_HARDEN"
) ?
"hardened"
:
((getenv(
"AFL_USE_ASAN"
) || getenv(
"AFL_USE_MSAN"
)) ?
"ASAN/MSAN"
:
"non-hardened"
), inst_ratio);
else
OKF(
"Instrumented %u locations (%s mode, ratio %u%%)."
,
inst_blocks, getenv(
"AFL_HARDEN"
) ?
"hardened"
:
((getenv(
"AFL_USE_ASAN"
) || getenv(
"AFL_USE_MSAN"
)) ?
"ASAN/MSAN"
:
"non-hardened"
), inst_ratio);
__AFL_INIT();
void __afl_manual_init(void) {
static u8 init_done;
if
(!init_done) {
__afl_map_shm();
__afl_start_forkserver();
init_done
=
1
;
}
}
void __afl_manual_init(void) {
static u8 init_done;
if
(!init_done) {
__afl_map_shm();
__afl_start_forkserver();
init_done
=
1
;
}
}
/
*
SHM setup.
*
/
static void __afl_map_shm(void) {
u8
*
id_str
=
getenv(SHM_ENV_VAR);
/
/
通过环境变量读取
id
/
*
If we're running under AFL, attach to the appropriate region, replacing the
early
-
stage __afl_area_initial region that
is
needed to allow some really
hacky .init code to work correctly
in
projects such as OpenSSL.
*
/
if
(id_str) {
/
/
如果读取成功
u32 shm_id
=
atoi(id_str);
__afl_area_ptr
=
shmat(shm_id, NULL,
0
);
/
/
获取shm的地址为__afl_area_ptr
/
*
Whooooops.
*
/
if
(__afl_area_ptr
=
=
(void
*
)
-
1
) _exit(
1
);
/
*
Write something into the bitmap so that even with low AFL_INST_RATIO,
our parent doesn't give up on us.
*
/
__afl_area_ptr[
0
]
=
1
;
/
/
设置__afl_area_ptr[
0
]为
1
}
}
/
*
SHM setup.
*
/
static void __afl_map_shm(void) {
u8
*
id_str
=
getenv(SHM_ENV_VAR);
/
/
通过环境变量读取
id
/
*
If we're running under AFL, attach to the appropriate region, replacing the
early
-
stage __afl_area_initial region that
is
needed to allow some really
hacky .init code to work correctly
in
projects such as OpenSSL.
*
/
if
(id_str) {
/
/
如果读取成功
u32 shm_id
=
atoi(id_str);
__afl_area_ptr
=
shmat(shm_id, NULL,
0
);
/
/
获取shm的地址为__afl_area_ptr
/
*
Whooooops.
*
/
if
(__afl_area_ptr
=
=
(void
*
)
-
1
) _exit(
1
);
/
*
Write something into the bitmap so that even with low AFL_INST_RATIO,
our parent doesn't give up on us.
*
/
__afl_area_ptr[
0
]
=
1
;
/
/
设置__afl_area_ptr[
0
]为
1
}
}
static void __afl_start_forkserver(void) {
static u8 tmp[
4
];
s32 child_pid;
u8 child_stopped
=
0
;
/
*
Phone home
and
tell the parent that we
're OK. If parent isn'
t there,
assume we're
not
running
in
forkserver mode
and
just execute program.
*
/
if
(write(FORKSRV_FD
+
1
, tmp,
4
) !
=
4
)
return
;
/
/
向状态管道写入
4
字节告知已启动
while
(
1
) {
u32 was_killed;
int
status;
/
*
Wait
for
parent by reading
from
the pipe. Abort
if
read fails.
*
/
/
*
当子进程超时,父进程会kill掉子进程
*
/
if
(read(FORKSRV_FD, &was_killed,
4
) !
=
4
) _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.
*
/
/
*
如果在persistent mode下,且子进程已经被killed
*
/
if
(child_stopped && was_killed) {
child_stopped
=
0
;
if
(waitpid(child_pid, &status,
0
) <
0
) _exit(
1
);
}
if
(!child_stopped) {
/
/
如果子进程真的彻底结束了
/
*
Once woken up, create a clone of our process.
*
/
/
/
重新fork一次
child_pid
=
fork();
if
(child_pid <
0
) _exit(
1
);
/
*
In child process: close fds, resume execution.
*
/
/
/
如果是fork出的子进程
if
(!child_pid) {
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.
*
/
/
*
如果子进程并非彻底结束而是暂停
*
/
/
*
重新启动这个暂停的子进程
*
/
kill(child_pid, SIGCONT);
child_stopped
=
0
;
}
/
*
In parent process: write PID to pipe, then wait
for
child.
*
/
/
/
在父进程(fork
-
server)中,向afl
-
fuzzer写
4
字节(子进程pid)到管道,告知fuzzer
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
;
/
/
child_stopped
=
1
则不确定究竟是否彻底结束
/
/
向状态管道写入
4
字节
if
(write(FORKSRV_FD
+
1
, &status,
4
) !
=
4
) _exit(
1
);
}
}
static void __afl_start_forkserver(void) {
static u8 tmp[
4
];
s32 child_pid;
u8 child_stopped
=
0
;
/
*
Phone home
and
tell the parent that we
're OK. If parent isn'
t there,
assume we're
not
running
in
forkserver mode
and
just execute program.
*
/
if
(write(FORKSRV_FD
+
1
, tmp,
4
) !
=
4
)
return
;
/
/
向状态管道写入
4
字节告知已启动
while
(
1
) {
u32 was_killed;
int
status;
/
*
Wait
for
parent by reading
from
the pipe. Abort
if
read fails.
*
/
/
*
当子进程超时,父进程会kill掉子进程
*
/
if
(read(FORKSRV_FD, &was_killed,
4
) !
=
4
) _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.
*
/
/
*
如果在persistent mode下,且子进程已经被killed
*
/
if
(child_stopped && was_killed) {
child_stopped
=
0
;
if
(waitpid(child_pid, &status,
0
) <
0
) _exit(
1
);
}
if
(!child_stopped) {
/
/
如果子进程真的彻底结束了
/
*
Once woken up, create a clone of our process.
*
/
/
/
重新fork一次
child_pid
=
fork();
if
(child_pid <
0
) _exit(
1
);
/
*
In child process: close fds, resume execution.
*
/
/
/
如果是fork出的子进程
if
(!child_pid) {
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.
*
/
/
*
如果子进程并非彻底结束而是暂停
*
/
/
*
重新启动这个暂停的子进程
*
/
kill(child_pid, SIGCONT);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-2-14 15:13
被Roland_编辑
,原因: