Creating a Nix Workflow to Fuzz netconsd

Michael Lynch

Nix로 netconsd 퍼징 워크플로 만들기

요즘은 잠이 잘 오지 않을 때면 퍼즈 테스트할 소프트웨어를 찾곤 합니다.

이번 주 초에는 Fady Othman이 쓴 글 “Meta Bug Bounty — Fuzzing ’netconsd’ for fun and profit.”가 떠올랐습니다. 코드를 철저하게 퍼징하는 방법에 대한 훌륭한 튜토리얼입니다.

대부분의 퍼징 관련 블로그 글과 마찬가지로, 이 작업도 재현하기가 조금 어려웠습니다. 독자가 작성자의 환경과 툴체인을 어떻게 재현해야 할지 직접 알아내야 하기 때문입니다.

xpdf 때처럼 Nix를 이용해 Othman의 퍼저를 쉽게 재현할 수 있는 버전으로 만들어 보기로 했습니다. xpdf에 대해 쓴 글은 훨씬 더 꼼꼼하고 완성도가 높습니다. 이 글은 궁금하신 분들을 위한 간단한 메모 정도로 봐 주시면 됩니다.

fuzz-netconsd

몇 시간 만에 netconsd용으로 만든 퍼징 워크플로는 다음과 같습니다:

git과 Nix가 설치되어 있다면 다음 명령어로 제 워크플로를 실행할 수 있습니다:

nix run gitlab:mtlynch/fuzz-netconsd

제가 작성한 퍼저 중 퍼징 도구가 컴파일된 바이너리를 셸을 통해 실행하는 대신 함수 호출을 통해 테스트 대상 코드에 직접 진입하는 방식은 이번이 처음입니다. honggfuzz와 AFL++에서는 이를 “persistent mode”라고 부르는데, 매 퍼즈 실행마다 새 프로세스를 생성하는 대신 하나의 프로세스를 계속 유지할 수 있기 때문인 것 같습니다.

함수 호출 방식의 퍼징이 흥미로운 점은 대상 코드가 공유 라이브러리를 제공하지 않더라도 할 수 있다는 것입니다. 저는 호출하려는 코드가 들어 있는 .o 파일과 해당 .h 헤더를 해당 단계의 Nix 출력 디렉터리로 복사하는 방식으로 워크플로의 installPhase에서 이 작업을 수행했습니다.

필요한 헤더와 오브젝트 파일을 확보한 뒤, 퍼징하려는 함수인 ncrx_process를 호출하는 데 필요한 최소한의 코드만 담은 간단한 테스트 하네스를 작성했습니다:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "ncrx.h"

int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
  // Convert the random bytes of the payload to a null-terminated string.
  char *payload = (char *)malloc(len + 1);
  if (!payload) return 0;
  memcpy(payload, buf, len);
  payload[len] = '\0';

  struct timespec ts;
  if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
    perror("clock_gettime");
    return 1;
  }

  uint64_t now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;

  struct ncrx *ncrx = ncrx_create(NULL);

  // Call the function that we're fuzzing.
  ncrx_process(payload, now, 0, ncrx);

  // Clean up the resources we allocated.
  ncrx_destroy(ncrx);
  free(payload);

  return 0;
}

퍼시스턴트 모드 퍼징은 생각보다 쉬웠습니다. netconsd에 대해 많이 알 필요도 없었습니다. 지금도 netconsd가 정확히 무슨 일을 하는지는 잘 모르지만, 퍼저에서 그 파싱 함수 중 하나를 호출할 정도는 이해했습니다.

아마 아무것도 찾지 못할 겁니다

Othman은 자신의 퍼징 작업으로 100% 커버리지를 달성했다고 보고했는데, 이는 매우 철저한 수준입니다.

netconsd의 커밋 히스토리를 확인해 보니 Othman의 작업 이후로는 거의 활동이 없었습니다. 글에서 명시적으로 밝히지는 않았지만, Othman이 발견한 취약점을 수정한 변경은 아마 이것이 아닐까 생각합니다:

이 시점에서 퍼징으로 뭔가를 많이 찾을 수 있을 것 같지는 않습니다. 12시간 동안 크래시가 발생하지 않아 퍼저를 중단했습니다.

남은 작업

  • Othman은 퍼즈 코퍼스를 위한 엣지 케이스 입력을 생성하기 위해 klee 도구를 사용했습니다. 저는 이 부분을 구현하지 않았지만, klee가 이미 Nix용으로 패키징되어 있어 워크플로에 잘 어울릴 것입니다.
  • 저는 Othman이 사용한 AFL++ 대신 honggfuzz를 사용했습니다. 저는 honggfuzz의 단순함을 선호하지만, Othman은 AFL++의 다양한 옵션을 잘 다뤄 더 높은 커버리지를 달성했습니다.
    • 첫 번째 초안 구현은 AFL++을 사용했으니, 해당 버전을 사용해 보고 싶다면 참고하시기 바랍니다.

원문은 Michael Lynch님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.