菜鸡啥都学点,啥都不精!!!

image-20250113134711544

模糊测试

简单介绍一下:
通过自动化生成并执行大量的随机测试用例来发现产品或协议的未知漏洞

image-20250203132333383

简单写一下

这个分为手动和自动两种情况

主要区别就在于data的生成是自动的还是手动的

有限变动就在于传参方式吧

image-20250203141659279

这里还讲了基于覆盖制导fuzz,它这里存在一些变动,对感知进程进行了一些反馈,并且基于反馈样本进行变异。

工具的使用

image-20250205183425641

这里详细学习AFI工具

一、AFL原理

AFL框架

AFL是一个基于覆盖制导的模糊工具测试工具。解释一下模糊测试,首先有一个进程,进程接收外界的输入然后给外界一个反馈,同时在基于覆盖制导的模糊测试运行的同时向外发送反馈,之后有相对于的输出。

这里我们就需要一个数据生成器,然后这里数据输入进去之后会触发进程中的哪些路径,之后对进程进行特殊处理,这里的专业术语叫插桩

然后提出疑问,如何让这个程序告诉你发触发了哪些路径?这里就需要用到插桩也就是插入一段代码,这里插入的代码我们称之为AFL_maybe_log。这里我们的进程就会传递路径信息,并且进行统计规整,之后把这个传给数据生成器,进行评估(反馈)。

如下图:

image-20250209224607626

插桩

如何插桩

两种方法:汇编层(ELF)和LLVM pass(官方api)

image-20250301140802307

函数执行什么功能

覆盖信息反馈

image-20250219165257351

主要讲的就是我们在插桩形成AFL_maybe_log时是有一段进程的,通过共享内存(shared_memory)进行访问、alloc、get和修改。还要注意的是AFL_maybe_log路径的生成的时候其实是有参数生成的(参数是随机生成的),路径的开始和结尾的路径唯一,根据这两个参数生成哈希 ,如下图

image-20250219170148089

而这个index代表条唯一路径,之后就会在共享内存的索引四的地方加一

image-20250219170729135

表示index=4的路径被触发了一次

如此我们的进程就修改了我们的共享内存,同样AFL-fuzz也可以去访问这块内存。这样我们进程内的路径信息就可以被AFL感知到了。

简单看下已经被插桩过的二进制文件

image-20250219171627079

首先获得共享内存,从进程环境变量里获得ID去定位共享内存

image-20250219171816329

生成哈希, index= afl_pre loc(这里是A) ^ random_num(这里是B)

但是如果我们从假设A-A的路径的话,我们会发现index会为0了,所以这里简单的右移1了,来避免这种情况的发生

image-20250219173357208

解释CFADD

预防整数溢出的

forkserver

image-20250301125530888

  • 当我们在进行模糊测试运行目标程序时,每一次目标的输入都对应着一次进程的创建,然后运行目标程序。但是这个的弊端在于时间的消耗成本太大,效率低下。

image-20250301125102221

  • 对此我们使用forkserver,首先我们的afl-fuzz创建forkserver,然后我们的forkserver再去创建我们的进程。

作用

首先forkserver有两个通信管道, 如上图。

上面的通信管道是命令信息,告诉fork server我要创建新进程了,然后根据这个去fork一个新进程出来。

下面的通信管道是子进程信息,简单来说就是把子进程的信息进行反馈

!!!!同时也会返回进程的pid

image-20250322181631195

目的

找到一个漏洞,也就是程序发生了崩溃,并且把这个崩溃的状态反馈给我们的AFL-fuzz,让我们知道我们的输入样本导致了我们的程序发生崩溃

image-20250301133428030

  • 当第一次运行时,他首先会进入初始化进程的逻辑里面

image-20250301133523532

  • 然后他会走一个while循环,通过管道会与afl-fuzz进行交互,直到触发异常,结束进程

image-20250301133902378

  • 除此之外其余都会执行上面的逻辑,也就是覆盖信息反馈(在插桩那块)。

完整AFL框架

image-20250301134207568

路径反馈信息处理

数据规整

image-20250301141153357

注意假设我们a-b的路径触发了4-7次都会规整到8次,以此类推

这样的目的时让样本一和样本二差异性不是特别大

样本变异

image-20250301144734209

  • bitflip,按位翻转(确定变异)

简单来说就是0变1,1变0

image-20250301153854910

  • arithmetic,整数加/减算术运算(确定变异)
  • interest,特殊数值替换(确定变异)
  • dictionary,把自动生成或用户提供的token替换/插入到原文件(确定变异)
  • havoc,随机变异(不确定变异)
  • splice,文件拼接(不确定变异)

AFL的覆盖制导问题

AFL自身的缺陷

针对大型项目:项目简单、映射数组长度有限、路径过多

AFL对抗

image-20250301155809998

  • 进行检测afl-fuzz特征,如上图的环境变量,如此就可以进行afl-fuzz检测

image-20250301160021615

  • 检测198、199的管道是否存在,进行推测是否存在afl-fuzz
  • 清空共享内存

二、模糊测试实验

环境搭建:

bash
1
2
3
4
5
6
7
8
9
sudo docker aflplusolus/aflplusplus

sudo docker run --name afl -it -d aflplusplus/aflplusplus /bin/bash

sudo docker exec -it afl /bin/bash


#正常查看进程,是exit
sudo docker start -ai afl

这里笔者在拉dockers环境的时候,会遇到连接超时的问题

这里建议直接进行代理

image-20250319193947635

链接:

Docker 设置代理的三种方法(2025年02月08日亲测可用)_docker 代理-CSDN博客

—2025年3.19可以使用

基本的使用:

xpdf-3.02

bash
1
2
3
4
5
6
7
8
# 对 xpdf-3.02 进行编译
#Xpdf 3.02 是一个老版本 PDF 解析工具
wget https://dl.xpdfreader.com/old/xpdf-3.02.tar.gz
tar -xvzf xpdf-3.02.tar.gz
cd xpdf-3.02.tar.gz
CC=afl-clang-fast CXX=afl-clang-fast++ ./configure --prefix=/root/work/xpdf-3.02/install
AFL_USE_ASAN=1 make
make install
bash
1
2
3
4
5
# input
curl -LO https://github.com/mozilla/pdf.js-sample-files/raw/master/helloworld.pdf
curl -LO http://www.africau.edu/images/default/sample.pdf
wget https://www.melbpc.org.au/wp-content/uploads/2017/10/small-example-pdf-file.pdf
#刚开始可能没有一些命令,下载一下就可以了
bash
1
2
afl-fuzz -i input/ -o output/  /root/work/xpdf-3.02/install/bin/pdftotext @@ demo_test
#建议这里给绝对路径

image-20250320175520821

看到这个基本就是起起来了,按理说我没等一会,看看他有没有发生崩溃,查看一下漏洞

一般这个fuzz是不会停止的,我们手动断下,查看一下

image-20250320185413713

cmdline:记录AFL++启动时的完整命令行参数

crashes:存放AFL++发现的导致xpdf-3.02崩溃的样本

fastresume.bin:AFL++用于恢复fuzzing进度的二进制文件。

fuzz_bitmap:储存AFL++运行时的覆盖率信息,用于跟踪哪些代码路径已经被访问过,帮助AFL++生成AFL++生成新的变异输入

fuzzer_setup:记录AFL++运行时的配置信息

fuzzer_stats:当前AFL++运行的统计信息

我们看下程序运行的统计信息

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[AFL++ 6d9aaf4da41f] /AFLplusplus/xpdf-3.02/output/default # cat fuzzer_stats
start_time : 1742465728
last_update : 1742467846
run_time : 2118
fuzzer_pid : 94249
cycles_done : 0
cycles_wo_finds : 0
time_wo_finds : 59
fuzz_time : 1797
calibration_time : 71
cmplog_time : 0
sync_time : 0
trim_time : 249
execs_done : 359587
execs_per_sec : 169.75
execs_ps_last_min : 181.71
corpus_count : 1639
corpus_favored : 206
corpus_found : 1636
corpus_imported : 0
corpus_variable : 0
max_depth : 5
cur_item : 1602
pending_favs : 170
pending_total : 1578
stability : 100.00%
bitmap_cvg : 12.94%
saved_crashes : 2
saved_hangs : 5
total_tmout : 129
last_find : 1742467846
last_crash : 1742467692
last_hang : 1742467384
execs_since_crash : 27787
exec_timeout : 40
slowest_exec_ms : 0
peak_rss_mb : 622
cpu_affinity : 0
edges_found : 2438
total_edges : 18838
var_byte_count : 0
havoc_expansion : 0
auto_dict_entries : 0
testcache_size : 3791475
testcache_count : 1639
testcache_evict : 0
afl_banner : ...ot/work/xpdf-3.02/install/bin/pdftotext
afl_version : ++4.32a
target_mode : shmem_testcase default
command_line : afl-fuzz -i input/ -o output/ /root/work/xpdf-3.02/install/bin/pdftotext @@ demo_test
[AFL++ 6d9aaf4da41f] /AFLplusplus/xpdf-3.02/output/default #

总结一下:发现了 2 个崩溃样本,发现了 5 个卡死样本,测试了 35.9 万个样本,覆盖率 12.94%,但仍有提升空间,执行速度 169 execs/sec。

libxml2-2.9.4

安装

bash
1
2
3
4
5
6
wget http://xmlsoft.org/download/libxml2-2.9.4.tar.gz
tar -xvzf libxml2-2.9.4.tar.gz
cd libxml2-2.9.4.tar.gz
CC=afl-clang-lto CXX=afl-clang-lto++ CFLAGCXXFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address" ./configure --prefix="/root/work/libxml/install/" --disable-shared --without-debug --without-ftp --without-http --without-legacy --without-python LIBS='-ldl'
make
make install
bash
1
2
-x xml.dict
# 字典

三、ASAN原理

四、模糊测试挖掘命令注入

逻辑块漏洞和内存块漏洞

命令注入漏洞

image-20250322161224590

前提

  • 程序存在system函数,或者类似的函数
  • system函数可以被我们命令执行触发(可达)

这里可以通过fuzz来证明函数是否可达

  • 对system函数的参数可控(可控)

暂时无法解决

image-20250322180058110

证明可达

简单来说就是,当一个样本能流经system函数时让函数崩溃

image-20250322180238035

这里就要介绍怎么样定制化执行system函数让函数崩溃,也就是我们要自定义一个函数,只要这个函数被调用,就证明函数执行了system函数,这里就用到了hook

弊端:

image-20250322180210803

当程序运行到system(id)时函数崩溃,但是后面的函数不会执行,无法判断后面函数是否存在内存块漏洞,保证不了afl-fuzz的完整性。

改善:

  • 准备两个样本,当样本一执行到自定义函数时崩溃,但是当样本二执行是则继续向下进行。

但是我们如果情况是system(id)函数执行时,下面如果存在system(命令注入)则无法crash

image-20250322180920602

改善:

当某地址函数自定义函数第一次被调用时crash(也就是多了一个地址)

image-20250322182638884

这里讲一下afl-fuzz进行forkserver时,不仅反馈子进程信息还反馈子进程的pid。

现在就是要把命令注入的crash和内存块的crash分开,这里也就引用到sytem hook,只要执行则增加标记 single user 1。

然后就forkserver就把这个子进程信息返还给afl-fuzz,证明可达

代码实现

这里是使用hook,关于hook我们使用LD_PRELOAD(它是linuxld.so动态连接器提供的一个环境变量,它的作用是在加载程序时强制优先加载指定的共享库)。

libc.so时标准动态连接库

image-20250323175217109

紫色为我们正常调用system函数,红色则是我们使用hook,当我们使用LD_PRELOAD去指定动态库,比如时hook.so ,这样我们的hook.so就会先于libc.so加载,如果我们的hook.so里面实现了一个system函数的实现,那么下一次调用system函数,则不会执行libc.so的system函数,而是执行我们的hook.so。这样以来我们就实现了劫持system函数。

代码:

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <signal.h>

/* getenv => 文件输入 */
/* 全局变量部分 */
#define DICT_NUM 0x100
#define VALUE_LEN 0x1000
#define KEY_LEN 0x100

char *KEY[DICT_NUM] = {0};
char *VALUE[DICT_NUM] = {0};
int NUM = 0;
int TMP_NUM = 0;

/* 函数声明 */
typedef int (*__LIBC_START_MAIN)(int (*main)(int, char **, char **), int argc, char **argv, int (*init)(int, char **, char **), void (*fini)(void), void (*rtld_fini)(void), void *stack_end);
typedef int (*__UCLIBC_MAIN)(int (*main)(int, char **, char **), int argc, char **argv, int (*init)(int, char **, char **), void (*fini)(void), void (*rtld_fini)(void), void *stack_end);

// 解析http报文
void parse_file();

// hook main
int main_hook(int, char **, char **);
static int (*main_orig)(int, char **, char **);

// hook start
int __libc_start_main(int (*)(int, char **, char **), int, char **, int (*)(int, char **, char **), void (*)(void), void (*)(void), void *);
int __uClibc_main(int (*)(int, char **, char **), int, char **, int (*)(int, char **, char **), void (*)(void), void (*)(void), void *);

// hook getenv
char* getenv(const char *);

/* ==================================================================== */

/* system => 命令注入检测 */
/* 函数声明 */
typedef int(*SYSTEM)(const char *command);

// 检测system参数是否敏感(当前为空)
void check_cmd(const char *);

// 登记当前system函数路径 + 当前路径是否是第一次被触发(仅在当前路径第一次被触发时做命令注入检测
int log_addr(size_t);
void check_repeat(size_t);

// hook system
int system(const char *);




int main_hook(int argc, char **argv, char **envp)
{
int ret = 0;
puts("[+] hooking main");

parse_file(argv);
ret = main_orig(argc, argv, envp);
puts("\n[+] hooking main fin");
return ret;
}

int __libc_start_main(int (*main)(int, char **, char **), int argc, char **argv, int (*init)(int, char **, char **), void (*fini)(void), void (*rtld_fini)(void), void *stack_end)
{

static __LIBC_START_MAIN true__libc_start_main = NULL;

main_orig = main;

true__libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");

return true__libc_start_main(main_hook, argc, argv, init, fini, rtld_fini, stack_end);
}

int __uClibc_main(int (*main)(int, char **, char **), int argc, char **argv, int (*init)(int, char **, char **), void (*fini)(void), void (*rtld_fini)(void), void *stack_end)
{

static __UCLIBC_MAIN true__uClibc_main = NULL;

main_orig = main;

true__uClibc_main = dlsym(RTLD_NEXT, "__uClibc_main");

return true__uClibc_main(main_hook, argc, argv, init, fini, rtld_fini, stack_end);
}


void parse_file(char **argv)
{
FILE *fp = NULL;
char *value = NULL;
int i = 0;

printf("[*] net msg: %s\n", argv[1]);

fp = fopen(argv[1], "r");
if(NULL == fp)
{
printf("[-] open %s failed\n", argv[1]);
exit(-1);
}
value = calloc(VALUE_LEN, sizeof(char));

while((fgets(value, VALUE_LEN, fp)) != NULL)
{
i = strlen(value);
if(value[i-1] == '\n')
{
value[i-1]=0;
}

if(NUM < DICT_NUM)
{
VALUE[NUM] = value;

NUM ++;

value = calloc(VALUE_LEN, sizeof(char));
}
else
{
break;
}
}
// for(i=0; i < NUM; i++)
// {
// printf("[*] %d, %s\n", i, VALUE[i]);
// }

fclose(fp);
}

char* getenv(const char *name)
{
int i = 0;
char *buf = NULL;

// printf("hooking getenv about %s\n", name);
if(0 == NUM)
{
puts("[-] parse file failed?");
}

// printf("[+] TMP_NUM, NUM: %d %d\n", TMP_NUM, NUM);
for(i; i < TMP_NUM; i++)
{
if(0 == strcmp(name, KEY[i]))
{
printf("[*] get %s: %s\n", KEY[i], VALUE[i]);
return VALUE[i];
}
}

if(TMP_NUM < NUM)
{
buf = calloc(KEY_LEN, sizeof(char));
strncpy(buf, name, KEY_LEN);
KEY[TMP_NUM] = buf;
printf("[*] find %s: %s\n", KEY[TMP_NUM], VALUE[TMP_NUM]);
TMP_NUM ++;
return VALUE[TMP_NUM-1];
}
else
{
printf("[*] none %s: %s\n", name, NULL);
return "";
}

}

void check_cmd(const char *cmd)
{
if(0)
{
raise(SIGUSR2);
}

}

int log_addr(size_t caller_addr)
{
FILE *fp = NULL;
if(fp = fopen("log_addr.txt", "a+"))
{
fprintf(fp, "0x%lx\n", caller_addr);
fflush(fp);
fclose(fp);
return 0;
}
else
{
fprintf(stderr, "[-] file open failed\n");
return -1;
}
}

void check_repeat(size_t caller_addr)
{
FILE *fp = NULL;
size_t buf;
if(fp = fopen("log_addr.txt", "a+"))
{
while(fscanf(fp, "0x%lx\n", &buf) != EOF)
{
if(buf == caller_addr)
{
return ;
}
}
log_addr(caller_addr);
raise(SIGUSR2);
return ;
}
else
{
fprintf(stderr, "[-] file read failed\n");
return ;
}

}

int system(const char *command)
{
static void *handle = NULL;
static SYSTEM true_system = NULL;
size_t caller_addr = 0;
size_t input = 0;

// x86_64 get caller_addr
// __asm__ volatile(
// "mov %0, [rbp+8];"
// :"=r"(caller_addr)
// :"r"(input)
// );

// x86 get caller_addr
// __asm__ volatile(
// "mov %0, [ebp+4];"
// :"=r"(caller_addr)
// :"r"(input)
// );

// mips get caller_addr
__asm__ volatile(
"move %0, $31;"
:"=r"(caller_addr)
:"r"(input)
);

// 检测caller_addr是否重复, 如果不重复则raise(SIGSEGV), 保留crash样本
check_repeat(caller_addr);

// 检测输入字符串是否可用于命令注入攻击
check_cmd(command);

true_system = (SYSTEM)dlsym(RTLD_NEXT, "system");
if(!handle)
{
return -1;
}

return true_system(command);
}
//gcc编译

五、实现模糊测试工具

附件这里就不贴了

NetFuzz-2.0

image-20250324183913473

这里跟前面的分析的二进制文件对应

image-20250324184150664

image-20250324184458186

这里是创建forksever的类,跟进看一下初始化

image-20250324184820383

之后就是创建两个管道一个,就是命令管道另一个就是接收子进程的pid和反馈信息的

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os
from .global_var import FORKSRV_FD

class class_pipe:
def __init__(self):
self.st_pipe_read = -1
self.ctl_pipe_write = -1

def set_pipe(self):
st_pipe = os.pipe()
ctl_pipe = os.pipe()

os.dup2(st_pipe[1], FORKSRV_FD + 1)
os.dup2(ctl_pipe[0], FORKSRV_FD)

os.close(st_pipe[1])
os.close(ctl_pipe[0])

self.st_pipe_read = st_pipe[0]
self.ctl_pipe_write = ctl_pipe[1]
print("[+] set_pipe: st_pipe_read: {}, ctl_pipe_write: {}".format(self.st_pipe_read, self.ctl_pipe_write))
#创建了两个管道一个是读,一个是写

后面的 self.status = class_status()就显示进程的状态的,也是有统计

python
1
2
3
4
5
6
7
8
9
10
class class_status():
def __init__(self):
self.total_execs = 0 #总尝试次数
self.total_crashes = 0#崩溃次数
self.unique_crashes = 0#唯一崩溃次数,通常是去除重复崩溃后的统计值
self.pending_favored = 0#待处理的“优先”项数
self.queued_paths = 0#已经排队的路径数
self.unique_paths = 0#独特路径的数量
self.queue_cycle = 0#当前排队周期
self.working = ""#表示当前正在进行的任务或操作状态

这里主要讲一下fuzz_one这个函数

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def fuzz_one(forksrv, queue_cur):

fp = open(queue_cur.fname, "rb")
# print(queue_cur.fname)
line = fp.readline() #将文件拿出
# print(line)

if(queue_cur.was_fuzzed == 0):#这里进行一次判断,如果是第一次拿出,则was_fuzzed=0,继续进行确定性变异
# 确定性变异
outbuf = list(line)
outbuf_len = len(outbuf)
# print(outbuf)
forksrv.status.working = "det"
det(forksrv, outbuf, outbuf_len)

outbuf = list(line)
outbuf_len = len(outbuf)
_dicts(forksrv, outbuf, outbuf_len)
else:
pass

queue_cur.was_fuzzed = 1

# 随机变异
outbuf = list(line)
outbuf_len = len(outbuf)
# print(outbuf)
forksrv.status.working = "havoc"
havoc(forksrv, outbuf, outbuf_len)

如果是确定性变异,则继续看一下他是怎么变异的

image-20250324191111104

下面的还有很多种情况,也就是按bit进行反转

python
1
2
3
4
5
6
stage_max = outbuf_len << 3

for stage_cur in range(stage_max):
FLIP_BIT(outbuf, stage_cur)# 反转outbuf中stage_cur上面的位
gen2fuzz(forksrv, bytes(outbuf))#将变异样本进行拼接,进行投喂
FLIP_BIT(outbuf, stage_cur) #恢复初始状态

这里注意数据规整
image-20250324192536647

一般超过500的,大部分都是web服务异常,也就发生了crash,并且保存样本。

image-20250324193204571

WEBserver

这里以nginx为例

特征:

  • 多进程(可自定义变成单进程)
  • socket(支持标准的输入,标准文件输入)

六、网络协议漏洞挖掘实战

webserver的常见架构。

image-20250330142454536

程序运行的是开始就是首先会监听80端口,然后在做一个sp协议,进行端口解析,根据协议要求在去做一些要求,当webserver遭受crash之后,这种架构会很快让它恢复,让webserver对外界的请求达到一种不间断的情况。

看图片,首先监听80端口,然后创建三个子进程,现在的问题是谁来处理80端口的数据呢?这就要提到一个惊群现象。

例子:如果有一个数据包发过来,然后我们的三个子进程都会被触发去处理,这就叫做惊群现象。以nginx为例,它使用加锁,当数据包发过来时,他们三个会去抢占这个资源锁,谁先抢到谁先对外界进行服务。以lighttpd为例,它的产生是一个比较轻量的环境,比如说在路由器上,它比较消耗资源,所以它的处理方式是不处理,谁抢的谁来。

其实它还有一个守护进程,然后前面有一个初始状态,然后创建守护进程,这就是完整的架构。

解释一下保护进程

image-20250330151539403

简单来说就是三个进程如果有进程崩溃后,守护进程会重新创建一个新进程,来维护着三个进程,以用来维持对外界的服务。

ligttpd-基础fuzz

主要用于嵌入式设备。

下载就不说了

拿到项目之后直接vscode打开,然后正常编译./autogen.sh然后运行./configure。(报错拷打GPT)

然后运行make -j8(默认路径是下在/usr/sbin/lighttpd目录下了)

plaintext
1
afl-fuzz -i input/ -o output/ -- ./src/lighttpd -D -f lighttpd.conf