심기용 PL님과 정재영 선배릠께 무한한 감사를..
STEP1. 가상머신 환경 세팅
- Hyper-v 사용해서 win10 가상머신 하나 만들어줬음
- secure boot off, RAM 4GB, 프로세서 1개, 페이징 사용 x, 동적 메모리 사용 x
STEP2. (kdnet 이용한 네트워크) 커널 디버깅 세팅
https://lucidmaj7.tistory.com/236
- 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
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
- 디버깅하려는 프로세스의 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 설치하기
- wtf 깃헙(https://github.com/0vercl0k/wtf) 사이트 가서 받아옴
STEP6. WTF 하네스 작성하기
https://chp747.tistory.com/419
- 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 호출 등등)
퍼저 안 죽게 범위를 좁히자니 크래시 발견 확률이 떨어지고
취약점 찾으려고 범위를 넓히자니 계속 죽어 디버깅 통해 고쳐줘야 하고..
'security > 화이트햇' 카테고리의 다른 글
IDA에서 사용자 정의 구조체 생성하기 (1) | 2024.01.06 |
---|---|
Windbg 커맨드 정리 (2) | 2024.01.06 |
WinAFL 퍼징을 통해 타겟 분석하기 (1) | 2024.01.06 |
git 사용하기 - github pull/push/pull request (0) | 2023.09.28 |
PCAP Programming (0) | 2023.09.23 |