Suricata에 대한 설명은 아래 링크에 있습니다.
What is Suricata open source software?
OSS-Fuzz에서 Suricata 오픈 소스를 대상으로 Fuzzing을 진행하였는데, 해당 테스트 드라이버에 대한 소스코드를 분석해보도록 하겠습니다. 해당 환경은 oss-fuzz에서 제공한 환경입니다.
[Code Analysis]
아래 코드를 보면, argument를 통해 AFL-Fuzzer로 부터 생성된 입력이 들어오는 것을 알 수 있으며, 30번 라인을 보면 파일을 읽어오는 것을 볼 수 있다. 이는 AFL에서 Input file을 넘겨줘서 Testing이 진행되는 것을 알 수 있다. 이후, 35번 라인을 통해 해당 Input file에 대한 정보를 모두 읽어 오고, 40번 라인을 통해 파일 크기만큼 메모리에 매핑시켜주고 있다.
다음 코드에서 FPC_IsFuzzPacketCapture() 함수가 존재하기에, FPC_IsFuzzPacketCapture() 함수가 어떻게 구성되어 있는지 확인해보자. FPC_IsFuzzPacketCapture() 함수에서는 헤더 길이보다 사이즈가 작게 들어온 경우, 데이터가 "FPC"와 동일한지 비교, 데이터 링크에서 발생하는 에러가 있는지 확인하는 과정을 거친다.
다음으로, 올바르게 패킷이 캡쳐가 되었더라면, 아래 사진에 48번 라인으로 들어가게 된다. 이후 53번 라인에서 FPC_init() 함수를 통해 메모리 쓴 데이터를 FPC_buffer_t pkts에 연결시켜주고 Initialization을 진행하는 것을 알 수 있다. 이 때, FPC_buffer_t 타입은 아래와 같이 정의되어 있다.
// State for the three-handshaking
typedef enum FPC_tcp_state {
FPC_TCP_STATE_START = 0,
FPC_TCP_STATE_SYN,
FPC_TCP_STATE_SYNACK,
FPC_TCP_STATE_ESTABLISHED,
} FPC_tcp_state_t;
typedef struct _FPC_buffer {
const uint8_t *Data; // Content of input file
size_t Size; // Packet(file) Size
size_t offset; // Offset
uint32_t datalink; // Type of datalink
bool tcpSingleStream; // If it is a single stream,
FPC_tcp_state_t tcpState; // current tcp state
uint8_t pkt[FPC_SNAPLEN]; // packet
uint32_t seqCliAckSrv; // ACK (Client -> Server)
uint32_t seqSrvAckCli; // ACK (Server -> Client)
uint32_t nb; // number
} FPC_buffer_t;
이제 본론으로 Packet Initialization을 마치고 55번 라인으로 들어갔다고 가정해보면, capture stream 또는 savefile로 부터 패킷을 읽기 위해 pcap_open_dead() 함수를 통해 'Fake pcap_t' 를 생성한 후, pcap_dump_fopen()에 아까 생성한 pcap_t를 넘겨줘서 패킷을 덤프한다.
56번 라인에서 덤프한 이후, FPC_next() 함수를 통해 다음 패킷을 읽어오고, 덤프하는 작업을 반복한다.
pcap_open_dead() : 'Fake pcap_t' return
pcap_dump((u_char*) pdumper, &header, pkt) : 패킷을 pcap_dumper_t에 쓰기 위해, 호출 (Buffer, not savefile)
FPC_next(FPC_buffer_t *pkts, struct pcap_pkthdr *header, const uint8_t **pkt) : 다음 패킷 가져오기
패킷 캡쳐에 실패하여, 64번 라인로 들어왔다고 가정할 경우, pcap_open_offline() 함수를 통해 첫 번째 argument로 전달된 saved pcap file을 읽어와서, pcap_t * 타입으로 반환 후, pkts 변수에 저장된다. 그런 다음, FPC_datalink_from()을 통해 datalink 값을 받아온 후, Datalink Header Type이 LINKTYPE_NULL 혹은 LINKTYPE_ETHERNET 에 해당하는지 71번 줄을 통해 확인한다.
이후, 77번 라인부터 89번 라인까지는 한 패킷 단위로 읽은 후 패킷의 내용을 출력하는 작업을 반복하는 것을 알 수 있다.
위와 같은 Test Driver를 구현한 후, AFL++을 붙여 테스팅을 진행할 수 있다. 이번에 알아본 Suricata 오픈 소스 프로젝트는 Unit Test 방식으로 진행되었다. 아래 사진은 위에서 설명한 Test Driver로 테스팅한 사진이다. 대략 29시간 정도 테스팅을 진행해보았다. 단, 버그는 발견되지 않아 아쉬움이 남았다...
아래 코드는 Test Driver 원본 코드이다.
[main.c]
// Copyright (c) 2021 Catena cyber
// Author Philippe Antoine <p.antoine@catenacyber.fr>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <pcap/pcap.h>
#include "fuzz_pcap.h"
int main(int argc, char** argv)
{
struct stat filestat;
uint8_t * mapped;
char errbuf[PCAP_ERRBUF_SIZE];
const u_char *pkt;
int r;
if (argc != 2) {
fprintf(stderr, "Expect one argument\n");
return 1;
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Cannot open file\n");
return 1;
}
// 파일 정보 읽어오기
if (fstat (fd, &filestat) < 0) {
fprintf(stderr, "Cannot get size of file\n");
close(fd);
return 1;
}
// 파일 사이즈 만큼 메모리 매핑
// prot: (PROT_READ: pages may be read)
// flag: (MAP_PRIAVATE)
mapped = mmap(0, filestat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
fprintf(stderr, "Cannot mmap file\n");
close(fd);
return 1;
}
if (FPC_IsFuzzPacketCapture(mapped, filestat.st_size)) {
pcap_t *pd;
pcap_dumper_t *pdumper;
FPC_buffer_t pkts;
struct pcap_pkthdr header;
int r = FPC_init(&pkts, mapped, filestat.st_size);
if (r >= 0) {
pd = pcap_open_dead(pkts.datalink, FPC_SNAPLEN);
pdumper = pcap_dump_fopen(pd, stdout);
while (FPC_next(&pkts, &header, &pkt) > 0) {
pcap_dump((u_char *) pdumper, &header, pkt);
}
pcap_close(pd);
pcap_dump_close(pdumper);
}
} else {
struct pcap_pkthdr *header;
pcap_t * pkts = pcap_open_offline(argv[1], errbuf);
if (pkts == NULL) {
fprintf(stderr, "Cannot open pcap file\n");
} else {
uint8_t dl = FPC_datalink_from(pcap_datalink(pkts));
if (dl != FPC_DATALINK_ERROR) {
uint8_t bufts[FPC_TS_MAXSIZE];
//TODO check return value
fwrite(FPC0_MAGIC, FPC0_MAGIC_LEN-1, 1, stdout);
fwrite(&dl, 1, 1, stdout);
//loop over packets
while (pcap_next_ex(pkts, &header, &pkt) > 0) {
//TODO define a fixed endianess
memset(bufts, 0, FPC_TS_MAXSIZE);
memcpy(bufts, &header->ts.tv_sec, sizeof(header->ts.tv_sec));
memcpy(bufts+FPC_TS_MAXSIZE/2, &header->ts.tv_usec, sizeof(header->ts.tv_usec));
fwrite(bufts, FPC_TS_MAXSIZE, 1, stdout);
fwrite(pkt, 1, header->caplen, stdout);
if (header->caplen > FPC_SNAPLEN) {
fprintf(stderr, "Warning packet too bug for snaplen\n");
}
//TODO escape FPC0_MAGIC
fwrite(FPC0_MAGIC, FPC0_MAGIC_LEN, 1, stdout);
}
} else {
fprintf(stderr, "Cannot use pcap datalink\n");
}
pcap_close(pkts);
}
}
munmap(mapped, filestat.st_size);
close(fd);
return 0;
}
참고 문헌
[1] https://google.github.io/oss-fuzz/
[2] https://github.com/google/oss-fuzz
'SoftwareTesting' 카테고리의 다른 글
오픈소스 프로젝트에 AFL Fuzzer 붙이는 방법 (0) | 2021.12.01 |
---|---|
What is the Software Testing? (0) | 2021.11.25 |