fuzz libmodbus by AFL

fuzz libmodbus by AFL

下载编译libmodbus库

libmodbus库源码可从github仓库下载,命令如下:

1
git clone https://github.com/stephane/libmodbus/

下载好后进入源码文件夹,编译命令如下:

需要注意的是:编译使用afl-gcc,而不使用默认的gcc

1
2
3
4
cd libmodbus
./autogen.sh
CC=afl-gcc CXX=afl-g++ ./configure --enable-static
make -j4

--enable-static:用于生成静态库

注意:如果./autogen.sh命令运行出错,提示 autoreconf not found 如下图,则表示缺少autoconf。此外,还需安装libtool

image-20230703201442354

命令如下:

1
sudo apt install autoconf libtool

安装完毕后,重新运行autogen.sh后,会提示我们可以运行./configure,如下图。

image-20230703201704667

接着运行 ./configuremake ,执行成功则会看到如下界面,显示afl插桩提示。

image-20230703202039330

安装Preeny库

为什么需要安装Preeny库?

Modbus协议依托socket实现进程间的通信,而AFL本身并未提供对 socket 通信的支持。使用 AFL 对其进行fuzzing 时,需要将其输入输出重定向 stdio 中。纵然可以修改部分代码使其 socket 通信转移到 stdio,但这一过程可能会对 fuzz 的结果造成影响,同时工作量可能也较为繁杂。若是直接修改系统的 socket.h,可能会对其他的程序造成难以估量的影响。
这个库利用 LD_PRELOAD 机制,重写了 很多库函数, 其中 desock.c 这个文件负责重写 socket 相关的函数,其实现的功能就是当应用从 socket 获取输入时,其实是从 stdin 获取输入。

安装Preeny的依赖

Preeny需要使用 libini_config 和 libseccomp 来实现相关功能,在安装 Preeny之前要先安装这两个依赖,否则在编译时汇错,安装命令如下:

1
sudo apt install libini-config-dev libseccomp-dev

编译安装Preeny

安装完Preeny需要的依赖后,进入到preeny目录下,make即可。

安装成功后,可以在 preeny/x86_64-linux-gnu 目录下找到编译好的 desock.so,可以通过一个简单的demo来测试 desock.so 是否正常工作,demo如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* demo.c */
#include<sys/socket.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>

int main(){
int s = socket(AF_INFT, SOCK_STREAM, 0);
char buf[1024] = {0};
char send_msg[] = "hello, send by send() :\n";
send(s, send_msg, strlen(send_msg), 0);
recv(s, buf, 1024, 0);
printf("recv from revv() : %s\n", buf);
return 0;
}

编译运行

1
2
gcc -o sock_test demo.c
LD_PRELOAD="/home/guoxb/Downloads/preeny/x86_64-linux-gnu/desock.so" ./sock_test

image-20230703205209408

可以看到,send 函数成功将消息发送给了 stdout ,而 recv 函数也成功从 stdin 中接受了消息。

编译测试程序

在 libmodbus 目录下有一个 test 文件,包含几个官方提供的测试样例,可以通过afl-gcc对其进行插桩编译,作为被测程序。此处值得注意的是,在编译时要注意手动指定modbus库和头文件,命令如下:

1
afl-gcc bandwidth-server-many-up.c -o server -I ../src/ ../src/.libs/libmodbus.a

-I 后面的两个参数分别指定了 modbus.hlibmodbus.a 的路径,两者缺一不可

生成测试用例

可以直接生成一串随机字符串作为 modbus 测试程序的输入,但这种随机生成的数据并不够 interesting。

在 libmodbus 提供的测试用例中,random-test-client 和 random-test-server 这对程序能为我们提供不错的测试数据,在 test/README.md 中对这两个程序的描述如下:

1
2
3
- `random-test-server` is necessary to launch a server before running random-test-client. By default, it receives and replies to Modbus query on the localhost and port 1502.

- `random-test-client` sends many different queries to a large range of addresses and values to test the communication between the client and the server.

我们在本地运行这一堆程序,然后通过 tcpdump 工具将数据包保存到本地,从中挑选若干数据包作为输入测试样例,步骤如下:

  1. 首先使用 random-test-server 在 127.0.0.1:1502 起一个 modbus tcp 服务

    server端:

    1
    tests$ ./random-test-server 
  2. 然后开启 tcpdump 保存数据包到目录 ~/modbus.pcap

    1
    $ sudo tcpdump -i lo -w ~/modbus.pcap
  3. 最后使用 random-test-client 随机发送各种 modbus 请求到 127.0.0.1:1502

    client端

    1
    tests$ ./random-test-client

注:上述三个步骤命令分别在三个不同的终端下运行。

然后写一个脚本(源自参考博客)把 ~/modbus.pcap 中由客户端发送的数据包(也就是目的地址是 127.0.0.1:1520 的数据包)的内容提取出来,每个数据包内容保存为一个单独的文件。

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
# python2
from scapy.all import *
save_path = "/tmp/seeds/"
uuid = 0

if not os.path.exists(save_path):
os.system("mkdir %s" %(save_path))

def save_to_file(data):
global uuid
with open("{}{}".format(save_path, uuid), "w") as fp:
fp.write(str(data))
uuid += 1
print "write test file: {}".format(uuid)

modbus_session = ''
pg = rdpcap("modbus.pcap")
session = pg.sessions()
for k in session.keys():
if k.endswith("127.0.0.1:1502"):
modbus_session = session[k]

for s in modbus_session:
payload = s[TCP].payload
if len(payload) > 4:
save_to_file(payload)

print "Total: %d tests" %(uuid)

注意:如果向偷懒直接运行该脚本,则将该脚本放置在 modbus.pcap 同目录下。

运行脚本后,可以得到提取后的测试用例。

开始测试

现在,我们可以使用生成的数据集作为初始样本集进行 fuzz 了。这里最好使用 afl-cmin 来精简下数据集,直接使用的话被AFL判 uselee 的 testcase 挺多的。

fuzz命令如下:

1
LD_PRELOAD="/home/guoxb/Downloads/preeny/x86_64-linux-gnu/desock.so" afl-fuzz -i /tmp/seeds/  -o out ./server

不出意外的话,就进入熟悉的AFL界面了,如下:

image-20230703212153136

参考博客

https://blog.csdn.net/qq_42768012/article/details/118567601

https://www.cnblogs.com/hac425/p/9416917.html

参考博客1中还提到了AFL的另一种模式 Persistent Mode,该模式在程序的某个代码位置不断喂生成的变异数据 进行 fuzz , 而不用每次喂数据都得重新 fork 一个程序。要使用这个特性则需要编译 llvm_mode。

挖坑,留着以后再看。