security/화이트햇

WTF(What The Fuzz) 퍼저를 활용해 타겟 분석하기

민사민서 2024. 1. 6. 19:06

심기용 PL님과 정재영 선배릠께 무한한 감사를..

 

STEP1. 가상머신 환경 세팅

  • Hyper-v 사용해서 win10 가상머신 하나 만들어줬음
  • secure boot off, RAM 4GB, 프로세서 1개, 페이징 사용 x, 동적 메모리 사용 x

 

STEP2. (kdnet 이용한 네트워크) 커널 디버깅 세팅

https://lucidmaj7.tistory.com/236

 

Windbg 커널디버깅 설정하기 (Network 디버깅)

Windbg 커널디버깅 KDNET 설정하기 (Network 디버깅) 커널 드라이버를 개발하면서 언젠가?는 마주치게 되는 커널디버깅. 과거 물리적인 디버깅 대상 PC의 시리얼포트를 통해 개발 PC와 연결하여 커널디

lucidmaj7.tistory.com

  • guest VM에 windbg (old) 설치
  • kdnet 이용해 네트워크 디버깅 설정 (호스트 IP=가상머신 게이트웨이 주소/ipconfig로 확인 가능, 권장 포트번호: 50000-50039)
  • 명령어 실행 시 나오는 key 메모해둠

  • 로컬 pc로 와서 windbg x64 → File → Kernel Debugging → 포트번호 / key 입력 (Windbg Preview 말고 구버전)
  • shutdown -r -t 0 통해 guest VM 재부팅시켜주면 잘 연결되었음

 

 

STEP3. 커널 디버깅 중 USER_MODE 프로세스에 디버거 붙이는 방법

https://chp747.tistory.com/418

 

커널 디버깅 중 USER-MODE 프로세스에 디버거 붙이기

커널 디버깅 중 USER-MODE 프로세스에 디버거 붙이기 Windbg로 윈도우 커널 디버깅 중에 유저모드 프로세스를 디버깅하는 방법이 몇 가지 있는데, 일단 이 글에서는 다음과 같은 네 가지의 방법을 정

chp747.tistory.com

 

STEP3-1. 실행파일 로드하면서 디버거 붙인후, bp 세팅

  • pause 후 !gflag +ksl 통해 커널 디버깅 심볼 활성화
  • sxe ld wtf_test.exe 해서 bp 세팅
  • exe를 처음 로드할 때만 디버거 붙는데, 만약 디버거 붙었는데 브포 못걸고 g 눌렀다? 그러면 delete 후 ctrl+z 복구하면 됨
  • 디버깅 심볼 인식 안될 경우? .sympath+ C:\\Users\\Runner\\Desktop\\whitehat\\www\\wtf\\wtf_test\\x64\\Release!sym noisy.reload (/f /user) 해서 심볼파일 로드, 이것도 하고 open source file 해서 소스코드 띄워놓기도 해보고

  • bp wtf_test!main 등등 이렇게 브포 걸어두면 앞으로 매 실행마다 자동으로 브포 걸리겠죠?

  • 실제로 push rbp (ProcessImage 프롤로그)에 멈춘거 볼 수 있음

 

cf) lockmem 사용하여 pagefault 방지할 수 있음(https://github.com/0vercl0k/lockmem). 이건 일단 보류

 

Step3-2. 실행중인 exe에 디버거 attach 시키기

https://thepassion.tistory.com/168

 

[WinDbg] Kernel 모드에서 특정 프로세스 디버깅하기

Kernel 모드에서 특정 프로세스 디버깅하기 이번 포스트에서는 WinDbg를 Debugee OS에 연결 한 후(Kernel-Mode), TestApp.exe프로세스를 디버깅하는 절차에 대해 설명합니다. n 디버깅 하려는 TestApp.exe의 EPROCES

thepassion.tistory.com

  • 디버깅하려는 프로세스의 EPROCESS 주소를 확인한다: !process 0 0 xxxxxx.exe

  • 해당 프로세스의 context로 이동한다: .process /i ffffba8fbff09080

  • context switching 후 브레이크 포인트를 건다
  • 디버깅 심볼 존재한다면 .reload /user 통해 로드시키면 된다
  • 만약 이랬는데도 bp 안걸리면 context(process) switching으로 충분하지 않은 것, 특정 쓰레드로 스위칭해야함, 멀티쓰레딩 환경에서 다른 쓰레드에서 실행되는 코드일 경우 이런 상황 발생 (https://github.com/0vercl0k/wtf/issues/85)
  • 이러고 이제 bp 트리거하는 액션 ㄱㄱ

 

STEP4. 메모리 덤프 뜨기 (using bdump.js)

⇒ 여기서 rsi & rax가 가리키는 버퍼에는 파일 내용이 담겨있음, 이 데이터를 조작해가면서 스냅샷 퍼징을 돌려봅시다

⇒ f_gets() 직후에 메모리 스냅샷을 뜨면 되겠다

 

bdump(https://github.com/yrp604/bdump) 사용

  • 전제조건: Hyper-V, VM with 1 vCPU
  • .scriptload "C:\\\\Users\\\\Runner\\\\Desktop\\\\whitehat\\\\www\\\\wtf\\\\bdump\\\\bdump.js”

  • (32bit 바이너리인 경우 추가로 이 명령어 입력) ! wow64exts.sw
  • !bdump_full "C:\\\\Users\\\\Runner\\\\Desktop\\\\whitehat\\\\www\\\\wtf\\\\memdump” 이러면 4GB 메모리 전체 덤프 떠진다

 

 

STEP5. WTF 설치하기

 

STEP6. WTF 하네스 작성하기

https://chp747.tistory.com/419

 

WTF(WHAT THE FUZZ) TUTORIAL

WTF(WHAT THE FUZZ) TUTORIAL wtf(What The Fuzz) fuzzer가 실제로 취약점을 잘 찾을 수 있는지 궁금해서 테스트 프로그램으로 퍼징을 돌려보려고 한다. Target Program 퍼징할 대상은 대충 다음과 같이 만들었다. fg

chp747.tistory.com

  • backend engine 시작 시점은 스냅샷 찍은곳, 종료 지점은 bp 세팅한 곳 ⇒ 그 사이를 계속 돌면서 fuzzing 진행
#include "backend.h"
#include "targets.h"
#include <fmt/format.h>
#include "crash_detection_umode.h"

namespace fs = std::filesystem;

namespace Test_wtf {

constexpr bool LoggingOn = false;

template <typename... Args_t>
void DebugPrint(const char *Format, const Args_t &...args) {
  if constexpr (LoggingOn) {
    fmt::print("Test WTF : ");
    fmt::print(fmt::runtime(Format), args...);
  }
}

bool InsertTestcase(const uint8_t *Buffer, const size_t BufferSize) {
  
  const Gva_t buf = Gva_t(g_Backend->Rsi());
  if (!g_Backend->VirtWriteDirty(buf, Buffer, BufferSize)){
        DebugPrint("VirtWriteDirty failed\n");
        return false;
  }
  
  return true;
}
  • 이미 파일 데이터가 쓰인 buf 메모리에 mutation을 덮어쓸 계획임
  • 파일데이터는 rsi에 담겨있었음 (아까 테케 커널 디버깅으로 분석해보았을 때)
  • rsi 값을 가져와서 buf 주소를 얻고 해당 주소에 VirtWriteDirty() 이용해 mutated data 덮어씀
bool Init(const Options_t &Opts, const CpuState_t &) {
  
  if (!g_Backend->SetBreakpoint(Gva_t(0x007ff7640812b6), [](Backend_t *Backend) {
        DebugPrint("ProcessImage finish\n");
        Backend->Stop(Ok_t());
      })) {
    DebugPrint("Failed to SetBreakpoint 0x007ff7640812b6\n");
    return false;
  }

  SetupUsermodeCrashDetectionHooks();

  return true;
}
  • bp는 타겟 함수의 에필로그 (ret) 주소에 건다 // 매번 주소가 달라질 것
  • backend engine의 종료 및 crash detection에 사용

 

//
// Register the target.
//

Target_t Test_wtf("test_wtf", Init, InsertTestcase);

}
  • 작성한 fuzzer를 wtf에 등록함

 

STEP7. WTF 빌드

  • wtf\src\build\build-release-msvc.bat 실행한다
  • wtf\src\build\ 아래 생기는 sln 솔루션을 visual studio로 열어 솔루션 빌드도 해준다

  • wtf/src/build/RelWithDebInfo 아래에 wtf.exe 와 wtf.pdb 생긴다 ⇒ 얘네를 퍼징에 사용

** 하네스 새로 작성할때마다 다시 빌드해줘야함

 

STEP8. WTF 퍼징 수행

  • wtf/targets 아래 폴더 하나(test_wtf)를 새로 판다
    • inputs 폴더, state 폴더, outputs 폴더, crashes 폴더 그리고 bat 파일 두 개를 생성한다

→ inputs 안에는 test file들

→ state 안에는 mem.dmp , regs.json (메모리 덤프뜬거) 를 넣는다

// server.bat
..\..\src\build\RelWithDebInfo\wtf.exe master --max_len=8096 --runs=1000000 --target . --name test_wtf

// fuzz-bochscpu.bat
..\..\src\build\RelWithDebInfo\wtf.exe fuzz --backend=bochscpu --name test_wtf --limit 1000000

 

 

STEP9. 크래시 검증 + WTF (aka 개복치) 가 왜 죽었는지 분석

  • 크래시 나왔고, 재현도 잘 되는군여

  • 그리고 퍼저가 터짐 (translation of GVA 0x15bdcbee000 failed)
    • GVA(Guest VA)의 변환 과정에서 터짐, guest va → guest pa로 변환하는 과정에서 뭔가 문제가 생겼나보다
    • 다양한 원인이 있겠지만 이번 경우는 buffer write할 때 너무 큰 값 입력해서 할당된 메모리 영역 넘어가 터진거같음 (invalid VA)

  • res.json 확인해서 덤프 뜰 당시의 rsi에 담긴 버퍼 주소를 파악함
  • windbg로 덤프파일 열어서 버퍼 주소랑 GVA translation 실패한 주소를 봐보면 메모리 할당 x

  • max_len 옵션으로 8096은 너무 큰가보군요 3000 정도로 줘야겠다
  • 오 잘 돌아가네요!! (div by zero 크래시도 나옴, mutation 수(runs)를 더 늘려도 될 듯?)

 

 

 

단순히 어셈블리를 에뮬레이팅해주는 기능이라 퍼저가 중간에 죽는 경우가 많음 (시스템 콜, API 호출 등등)

퍼저 안 죽게 범위를 좁히자니 크래시 발견 확률이 떨어지고

취약점 찾으려고 범위를 넓히자니 계속 죽어 디버깅 통해 고쳐줘야 하고..