Fuzzing

WinAFL Fuzzing

zz! 2025. 5. 10. 00:04
728x90

WinAFL

AFL(American Fuzzy Lop)은 coverage-guided 퍼징 툴이다. Windows에서 사용할 수 있도록 변경을 한 것이 WinAFL 입니다.

 

준비물

Visual Studio 2022

DynamoRIO github

https://github.com/DynamoRIO/dynamorio/releases

 

Releases · DynamoRIO/dynamorio

Dynamic Instrumentation Tool Platform. Contribute to DynamoRIO/dynamorio development by creating an account on GitHub.

github.com

 

 

DynamoRIO는 프로그램이 실행되는 동안 프로그램의 모든 부분에서 실시간으로 가로채서 분석, 수정할 수 있게 해주는 도구입니다.

 

간단하게 WinAFL을 이용해서 Fuzzing 테스트를 진행할 것 이기 때문에, 아래의 소스코드를 사용을 하여 DLL을 만들었습니다.

#include <Windows.h>
#include <iostream>

extern "C" __declspec(dllexport) int ohmygod(const char* path)
{
    std::cout << path << std::endl;
    char data[40] = { 0, };
    char buf[30] = { 0, };

    HANDLE hFile = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (hFile)
    {
        DWORD dwRead;
        ReadFile(hFile, data, sizeof(data), &dwRead, NULL);
        std::cout << "read : " << dwRead << std::endl;
        if (dwRead)
        {
            memcpy(buf, data, dwRead + 40);  
            std::cout << buf;
        }
        CloseHandle(hFile);
    }
    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved)
{
    return TRUE;
}

memcpy 부분에서 오버플로우가 발생을 하는 DLL입니다.

 

64bit 환경

자 저같은 경우는 64bit를 대상으로 퍼징을 하겠다고 가정하고 환경을 구성을 해보겠습니다.

x64 Native Tools Command Prompt for VS 2022 를 관리자 권한으로 실행을 합니다.

cd C:\
git clone https://github.com/googleprojectzero/winafl
cd winafl
git submodule update --init --recursive
mkdir build64
cd build64
cmake -G"Visual Studio 17 2022" -A x64 .. -DDynamoRIO_DIR=C:\Users\user\Downloads\DynamoRIO\cmake -DUSE_DRSYMS=1 -DINTELPT=1 -DUSE_COLOR=1
cmake -build . --config Release

여기까지 명령어를 입력하고 가장 중요한 것이 있습니다.

"C:\winafl\build64\bin\Release" 경로에 afl-fuzz.exe 와 winafl.dll 이 존재하는지 확인을 합니다.

 

하네스(Harness)

대상 함수(또는 라이브러리)를 퍼저(Fuzzer)가 호출할 수 있도록 인자를 만들어주고, 함수만 반복 실행할 수 있게 감싸주는 테스트용 코드입니다.

→ 말를 수레에 연결할 때 쓰는 장비를 하네스(harness)라고 합니다. Fuzzer는 대상 함수에 연결되기 위해 하네스를 필요로 합니다.

사용을 하는 이유?

전체프로그램을 실행하는 대신, 성능을 위해서 특정 함수만 반복 실행을 합니다. 그렇다면, 전체 프로그램을 대상으로 퍼징 테스트를 진행하는 것 보다 특정 함수만 반복 실행해서 하므로, 속도 측면으로 차이가 많이 날 수 밖에 없습니다.

아까 DLL을 대상으로 하네스 코드는 다음과 같습니다.

#include <iostream>
#include <windows.h>

typedef int(__stdcall* _OHMYGOD)(const char* data);
_OHMYGOD func;

extern "C" __declspec(dllexport) __declspec(noinline) int fuzzme(const char* path)
{
    int result = func(path);
    return result;
}

int main(int argc, char* argv[])
{
    HMODULE hMod = GetModuleHandle(0);

    hMod = LoadLibrary(L"C:\\winafl\\build64\\bin\\Release\\Project4.dll");
    if (NULL == hMod)
    {
        printf("dll load error\n");
        return 0;
    }

    func = (_OHMYGOD)GetProcAddress(hMod, "ohmygod");
    fuzzme(argv[1]);
}

WinAFL 옵션

WinAFL을 실행하기전에 명령어 옵션에 대해 알아보겠습니다.

WinAFL을 사용할 때 옵션을 다음과 같이 줍니다.

afl-fuzz [afl 옵션] — [DynamoRIO 옵션] — [타겟 프로그램]

Afl 옵션은 다음과 같습니다.

-i : testcase 디렉토리

-o : output 디렉토리

-D : DynamoRIO bin 디렉토리

-t : 타임아웃

이 네 가지 옵션은 필수로 사용해야 하는 옵션입니다.

DynamoRIO 옵션

-nargs : 프로그램을 실행 시킬 때 들어가는 인자의 갯수

-covtype : 커버리지를 등록하는 타입

-coverage_module : 커버리지를 등록하는 대상을 쓰는 옵션. 프로그램이 사용하는 dll 경로를 등록하고 싶으면 dll을 추가합니다.

-target_module : 대상이 되는 코드가 들어있는 타겟을 씁니다.

-target_offset : 대상이 되는 코드의 주소

더 다양한 옵션은 github를 참고하시면 될 것 같습니다.

WinAFL 실행

입력 디렉토리 설정

afl은 사용자가 지정한 입력 디렉토리에 있는 파일들을 퍼징 데이터로 사용을 합니다. crash를 유발하지 않는 정상 입력 데이터이어야 첫 실행 시 에러가 발생하지 않습니다.

mkdir C:\winafl_in
echo "abcd" > C:\winafl_in\1.txt

winafl 실행

afl-fuzz.exe -D C:\Users\user\Downloads\DynamoRIO\bin64 -i "C:\\winafl_in" -o "C:\\winafl_out" -t 1000 -- -coverage_module Project4.dll -target_module Project5.exe -target_method fuzzme -fuzz_iterations 10 -nargs 1 -- C:\winafl\build64\bin\Release\\Project5.exe @@

저 같은 경우에는 타겟 dll이 Project4.dll 이고, 하네스 프로그램은 Project5.exe 입니다.

위에 명령어를 토대로 옵션을 다시 복습을 해보겠습니다.

-D : DynamoRIO bin 디렉토리인데, 저 같은 경우에는 64bit 대상이므로 bin64로 지정을 하였습니다.

-i : 입력 디렉토리 입니다.

-o : output 디렉토리입니다. 저 디렉토리 같은 경우는 디렉토리를 안 만들어줘도 자동으로 생성해줍니다.

-t : 타임아웃 인데, 1000 일 경우에는 test case당 timeout이 1초입니다.

-fuzz_iterations : 한 번 실행된 프로세스 안에서 반복 호출하는 방식입니다. 10번 호출하고 프로세스를 재시작합니다.

-fuzz_iterations 값이 크면 성능은 빠르지만, 메모리 누수에 대한 단점이 있습니다. 하지만 값이 너무 작으면 퍼징 효율이 떨어진다는 단점이 있습니다.

-nargs : 프로그램 실행 시 들어가는 인자 갯수입니다.

마지막 부분은 hanerss 경로 입니다.

 

Winafl 실행 결과

crash 가 발생할 경우에는 C:\winafl_out\crashes 경로에 저장이 됩니다.

프로세스를 종료하려면 Ctrl + C를 누르면 된다.

Winafl 출력

출력 디렉토리가 생성이 되고 하위 디렉토리들이 생성이 됩니다.

queue/ 입력 파일들이 저장이 된다.

crashes/ 프로그램이 크래시한 원인이 된 입력 파일들이 저장된다.

hangs/ 일정 시간 이상 응답하지 않은 입력 파일들이 저장된다.

hxd 이용해서 어떤식으로 바꼈는지 분석이 가능합니다.

 

크래시 재현

스택 버퍼 오버플로우가 발생을 한 것을 알 수 있습니다. 똑같은 크래시를 재현 가능합니다.

지금은 이미 dll 파일 어디 코드가 문제인지 아는 상황 이긴합니다. 하지만 WinDbg 사용법 연습도 할 겸 어디서 발생하였는지 확인을 해보겠습니다.

 

참고자료

https://1nzag.tistory.com/12

https://minseosavestheworld.tistory.com/199

https://hackyboiz.github.io/2021/05/23/fabu1ous/winafl-1/

https://github.com/googleprojectzero/winafl

https://codetronik.tistory.com/204

https://hackyboiz.github.io/2021/07/04/fabu1ous/50cve/

https://github.com/googleprojectzero/winafl/blob/master/afl_docs/README

728x90