fuzz實戰之honggfuzz


Honggfuzz實戰

前言

本文介紹 libfuzzerafl 聯合增強版 honggfuzz .同時介紹利用 honggfuzzfuzz 網絡應用服務。

介紹

honggfuzz 也是 google 開發的一款 fuzz . 其設計思路 和 libfuzzerafl 類似 ,感覺就是 libfuzzer + afl 的 增強版。

編譯

git clone https://github.com/google/honggfuzz.git
cd honggfuzz
make

honggfuzz 的使用文檔在

https://github.com/google/honggfuzz/tree/master/docs

對幾條命令做個解釋

honggfuzz -f input_dir -z -s -- /usr/bin/djpeg

-f : 指定初始樣本集目錄

-z : 使用編譯時的指令插樁信息來 為 樣本變異做回饋, 默認選項

-s : 表示目標程序從 stdin 獲取輸入,即樣本數據通過 stdin 喂給程序

honggfuzz -f input_dir -- /usr/bin/djpeg ___FILE___

___FILE___ : 類似於 afl@@ , 表示程序通過文件獲取輸入,fuzz 過程會被替換為相應的樣本文件名

honggfuzz -f input_dir -P -- /usr/bin/djpeg_persistent_mode

-P : 表示使用 persistent 模式

簡單示例(libFuzzer模式)

這里用 libfuzzer-workshop 里面的 libxml2 來進行 fuzz 測試

https://github.com/Dor1s/libfuzzer-workshop/tree/master/lessons/08

下載那個 libxml2.tgz 然后解壓,用 hfuzz-clang 編譯

tar xvf libxml2.tgz 
cd  libxml2/
CC=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang CXX=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang++ 
export CC=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang
export CXX=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang++ 
./autogen.sh 
CC=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang CXX=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang++  ./configure 
make -j4

/home/haclh/vmdk_kernel/honggfuzz/honggfuzz 所在的目錄

然后會生成 libxml2/.libs/libxml2.a

看看 fuzzer 代碼

#ifdef __cplusplus
extern "C" {
#endif

#include <inttypes.h>
#include "libxml/parser.h"
#include <stdlib.h>

#include <libhfuzz/libhfuzz.h>

FILE* null_file = NULL;

int LLVMFuzzerInitialize(int* argc, char*** argv)
{
    null_file = fopen("/dev/null", "w");
    return 0;
}
int LLVMFuzzerTestOneInput(const uint8_t* buf, size_t len)
{
    xmlDocPtr p = xmlReadMemory((const char*)buf, len, "http://www.google.com", "UTF-8", XML_PARSE_RECOVER | XML_PARSE_NONET);
    if (!p) {
        return 0;
    }
    xmlDocFormatDump(null_file, p, 1);
    xmlFreeDoc(p);
    return 0;
}

#ifdef __cplusplus
}
#endif

libfuzzer 的代碼基本一致,用 hfuzz-clang 編譯(類似於 libfuzzer , 需要和 libhfuzz.a 鏈接)。

/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang persistent-xml2.c -Ilibxml2/include  libxml2/.libs/libxml2.a  /usr/lib/x86_64-linux-gnu/liblzma.a /home/haclh/vmdk_kernel/honggfuzz/libhfuzz/libhfuzz.a -lz -o persistent-xml2

然后運行 fuzz

/home/haclh/vmdk_kernel/honggfuzz/honggfuzz -W out -f ~/vmdk_kernel/libfuzzer-workshop-master/lessons/08/corpus2/ -- ./persistent-xml2

-W : 指定輸出目錄

-f : 指定 初始樣本集目錄

Persistent Fuzzing

honggfuzz 還支持 persistent 模式 , 而且他這種模式實現的比 afl 要 更加容易使用。直接上 demo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <inttypes.h>
extern HF_ITER(uint8_t** buf, size_t* len);
void test(char* buf){
    if (buf[0] == 'f') {
      printf("one\n");
      if (buf[1] == 'o') {
        printf("two\n");
        if (buf[2] == 'o') {
          printf("three\n");
          if (buf[3] == '!') {
            printf("four\n");
            abort();
          }
        }
      }
    }
}
int main(void) {
  for (;;) {
    size_t len;
    uint8_t *buf;

    HF_ITER(&buf, &len);
    test(buf);

  }
  return 0;
}


代碼和普通的應用差不多, 需要注意的就是 HF_ITER程序可以通過這個宏 來獲取 honggfuzz 生成的樣本數據,這就非常方便了,我們可以把它插入對數據處理的邏輯, 然后循環這段邏輯,就可以不斷的進行 fuzz 而不用重新起進程。

對應到上面的代碼就是, 一個死循環,循環里面使用 HF_ITER 獲取數據,然后把數據喂給我們要測試的邏輯 (這里是 test 函數), 在 test 函數內部 如果滿足條件,會觸發 abort 模擬一個crash, 讓 honggfuzz 捕獲到。

編譯

/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang test.c /home/haclh/vmdk_kernel/honggfuzz/libhfuzz/libhfuzz.a -o test

然后運行

mkdir in
echo 111 > in/1
/home/haclh/vmdk_kernel/honggfuzz/honggfuzz -P -f in/ -W out -- ./test

-P : 用於開啟 persistent 模式

馬上就能看的 crash 了。

然后會在 out 目錄生成觸發 crash 的文件

22:28 haclh@ubuntu:persistent $ xxd out/SIGABRT.PC.7ffff6efa428.STACK.17326c6e5e.CODE.-6.ADDR.\(nil\).INSTR.cmp____\$0xfffffffffffff000\,%rax.fuzz 
00000000: 666f 6f21                                foo!

內容滿足觸發 abort 的條件

Honggfuzz NetDriver

介紹

這個是 honggfuzz 自帶的一個庫 用於 fuzz socket 類程序

https://github.com/google/honggfuzz/tree/master/libhfnetdriver

用它 fuzz socket 程序很方便, 只要把 main 函數 改成 HFND_FUZZING_ENTRY_FUNCTION (: 不確定是不是所有的都這樣,具體還是得看代碼 ,以后再研究研究

int main(int argc, char *argv[]){
    ......................
    ......................
    ......................
}

改成

HFND_FUZZING_ENTRY_FUNCTION(int argc, char *argv[]){
    ......................
    ......................
    ......................
}

然后用 hfuzz-clang 編譯, 同時用 libhfnetdriver.a 鏈接即可。

demo

不知道為啥 libmodbustcp server 用這種方式跑不起來,這里用一個 有漏洞的 socket 程序作為例子,試試這個功能。

vuln.c

#include <crypt.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

/* Do nothing with first message */
void handleData0(char *data, int len) {
    printf("Auth success\n");
}

/* Second message is stack based buffer overflow */
void handleData1(char *data, int len) {
    char buff[8];
    bzero(buff, 8);
    memcpy(buff, data, len);
    printf("Handledata1: %s\n", buff);
}

/* Third message is heap overflow */
void handleData2(char *data, int len) {
    char *buff = malloc(8);
    bzero(buff, 8);
    memcpy(buff, data, len);
    printf("Handledata2: %s\n", buff);
    free(buff);
}

void handleData3(char *data, int len) {
    printf("Meh: %i\n", len);
}

void handleData4(char *data, int len) {
    printf("Blah: %i\n", len);
}

void doprocessing(int sock) {
    char data[1024];
    int n = 0;
    int len = 0;

    while (1) {
        bzero(data, sizeof(data));
        len = read(sock, data, 1024);

        if (len == 0 || len <= 1) {
            return;
        }

        printf("Received data with len: %i on state: %i\n", len, n);
        switch (data[0]) {
            case 'A':
                handleData0(data, len);
                write(sock, "ok", 2);
                break;
            case 'B':
                handleData1(data, len);
                write(sock, "ok", 2);
                break;
            case 'C':
                handleData2(data, len);
                write(sock, "ok", 2);
                break;
            case 'D':
                handleData3(data, len);
                write(sock, "ok", 2);
                break;
            case 'E':
                handleData4(data, len);
                write(sock, "ok", 2);
                break;
            default:
                return;
        }

        n++;
    }
}

HFND_FUZZING_ENTRY_FUNCTION(int argc, char *argv[]) {
    int sockfd, newsockfd, portno, clilen;
    char buffer[256];
    struct sockaddr_in serv_addr, cli_addr;
    int n, pid;

    if (argc == 2) {
        portno = atoi(argv[1]);
    } else {
        portno = 5001;
    }

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("ERROR opening socket");
        exit(1);
    }

    int reuse = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char *)&reuse, sizeof(reuse)) < 0)
        perror("setsockopt(SO_REUSEPORT) failed");

    bzero((char *)&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    printf("Listening on port: %i\n", portno);

    /* Now bind the host address using bind() call.*/
    if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("ERROR on binding");
        exit(1);
    }

    listen(sockfd, 5);
    clilen = sizeof(cli_addr);

    while (1) {
        newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
        if (newsockfd < 0) {
            perror("ERROR on accept");
            exit(1);
        }
        printf("New client connected\n");
        doprocessing(newsockfd);
        printf("Closing...\n");
        shutdown(newsockfd, 2);
        close(newsockfd);
    }
}

就是一個簡單的 socket 程序,其中有兩個漏洞 一個棧溢出 一個堆溢出, 同時把 int main 改成了 HFND_FUZZING_ENTRY_FUNCTION

代碼修改自: https://github.com/google/honggfuzz/blob/master/socketfuzzer/vulnserver_cov.c

編譯

/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang vuln.c /home/haclh/vmdk_kernel/honggfuzz/libhfnetdriver/libhfnetdriver.a  -o vuln

關鍵是要和 libhfnetdriver.a 鏈接

運行

mkdir in
echo A > in/1
echo B > in/2
_HF_TCP_PORT=5001 /home/haclh/vmdk_kernel/honggfuzz/honggfuzz  -f in -- ./vuln

前面三條命令用於生成兩個簡單樣本文件

最后一條命令用於啟動 fuzzer .

_HF_TCP_PORT 指定 server 監聽的端口, fuzzer 會和這個端口建立 tcp 連接,然后發送數據。

秒出 crash

參考

FUZZING TCP SERVERS

實戰

本節以 mongoose 為例 實戰一波

https://github.com/cesanta/mongoose

這是一個用於 嵌入式網絡服務的庫, 實現了很多 IOT 中用到的協議。

本節以 http 為例進行 fuzz

程序的源代碼內有很多的示例 , 其中 examples/simplest_web_server 目錄里面是一個很簡單的 http server

把代碼中的 int main 改成 HFND_FUZZING_ENTRY_FUNCTION

// Copyright (c) 2015 Cesanta Software Limited
// All rights reserved

#include "mongoose.h"

static const char *s_http_port = "8000";
static struct mg_serve_http_opts s_http_server_opts;

static void ev_handler(struct mg_connection *nc, int ev, void *p) {
  if (ev == MG_EV_HTTP_REQUEST) {
    mg_serve_http(nc, (struct http_message *) p, s_http_server_opts);
  }
}

HFND_FUZZING_ENTRY_FUNCTION(void) {
  struct mg_mgr mgr;
  struct mg_connection *nc;

  mg_mgr_init(&mgr, NULL);
  printf("Starting web server on port %s\n", s_http_port);
  nc = mg_bind(&mgr, s_http_port, ev_handler);
  if (nc == NULL) {
    printf("Failed to create listener\n");
    return 1;
  }

  // Set up HTTP server parameters
  mg_set_protocol_http_websocket(nc);
  s_http_server_opts.document_root = ".";  // Serve current directory
  s_http_server_opts.enable_directory_listing = "yes";

  for (;;) {
    mg_mgr_poll(&mgr, 1000);
  }
  mg_mgr_free(&mgr);

  return 0;
}

然后用 hfuzz-clang 編譯, 注意要和 libhfnetdriver.a 鏈接

export HONGGFUZZ_HOME=/home/haclh/vmdk_kernel/honggfuzz
$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang simplest_web_server.c ../../mongoose.c -o simplest_web_server  -I../.. -DMG_DISABLE_DAV_AUTH -DMG_ENABLE_FAKE_DAVLOCK $HONGGFUZZ_HOME/libhfnetdriver/libhfnetdriver.a -pthread

然后開始 fuzz

_HF_TCP_PORT=8000 /home/haclh/vmdk_kernel/honggfuzz/honggfuzz -W oout -f corpus_http1/ -w httpd.wordlist -- ./simplest_web_server

其中用到的樣本集 和 字典文件來自

https://github.com/google/honggfuzz/tree/master/examples/apache-httpd

發現了 crash ,下面定位觸發漏洞代碼, 使用 -fsanitize=address 編譯,可以在 觸發漏洞時停下來。

clang -fsanitize=address simplest_web_server.c ../../mongoose.c -o simplest_web_server -g -W -Wall -Werror -I../.. -Wno-unused-function  -DMG_DISABLE_DAV_AUTH -DMG_ENABLE_FAKE_DAVLOCK -pthread

然后觸發漏洞的樣本發送給服務器,就會 crash

01:19 haclh@ubuntu:simplest_web_server $ ./simplest_web_server 
Starting web server on port 8000
=================================================================
==16867==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x619000000980 at pc 0x0000004e653d bp 0x7fffb8bab790 sp 0x7fffb8baaf40
READ of size 876 at 0x619000000980 thread T0
    #0 0x4e653c in __asan_memcpy /home/haclh/vmdk_kernel/libfuzzer-workshop-master/src/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:23
    #1 0x53b9d6 in mbuf_insert /tmp/t/mongoose-6.11/examples/simplest_web_server/../../mongoose.c:1477:24
    #2 0x53bafc in mbuf_append /tmp/t/mongoose-6.11/examples/simplest_web_server/../../mongoose.c:1490:10

然后可以定位到  mongoose.c8925

 8923   if (mg_start_process(opts->cgi_interpreter, prog, blk.buf, blk.vars, dir,
 8924                        fds[1]) != 0) {
 8925     size_t n = nc->recv_mbuf.len - (hm->message.len - hm->body.len);
 8926     struct mg_connection *cgi_nc =
 8927         mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler MG_UD_ARG(nc));
 8928     struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(nc);
 8929     cgi_pd->cgi.cgi_nc = cgi_nc;
 8930 #if !MG_ENABLE_CALLBACK_USERDATA
 8931     cgi_pd->cgi.cgi_nc->user_data = nc;
 8932 #endif
 8933     nc->flags |= MG_F_HTTP_CGI_PARSE_HEADERS;
 8934     /* Push POST data to the CGI */
 8935     if (n > 0 && n < nc->recv_mbuf.len) {
 8936       mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, n);
 8937     }                                       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM