
Windows x64 실행 파일용 정적 바이너리 계측 도구
peafl64는 Windows의 x64 PE를 위한 정적 계측 도구입니다.
정적 계측은 실행 파일을 편집하고 특정 위치에 코드를 추가하는 기법입니다.
이 계측은 바이너리의 모든 기본 블록 시작 부분에 코드를 추가하여 AFL 호환 방식으로 실행 흐름을 기록합니다.
이를 통해 소스 코드에 접근하지 않고도 Usermode(WinAFL 사용) 및 Kernelmode(kAFL 사용)에서 바이너리를 퍼징할 수 있습니다.
Windows 바이너리를 퍼징하는 다른 방법도 있지만, 이 프로젝트에서는 가장 빠른 방법인 정적 계측에 집중하기로 했습니다.
이 프로젝트는 wmliang의 pe-afl 도구를 기반으로 x64 지원을 추가했습니다.
계측 스크립트에는 IDA 분석 결과가 필요합니다.
이를 생성하려면 IDA에서 제공된 ida_dumper.py 스크립트를 실행하세요.
이 스크립트는 IDA 7+ 및 python3.8+가 필요합니다.
usage: pe_afl.py [-h] [-n] [-cb] [-tf] [-te THREAD_ENTRY] [-nt NTOSKRNL] [-e ENTRY] [-l ENLARGE] [-v] [-lf] pefile ida_dump
positional arguments:
pefile Target PE file for instrumentation
ida_dump dump.json from IDA (created by ida_dumper.py)
optional arguments:
-h, --help show this help message and exit
-n, --nop Instrument with NOPs for testing
-cb, --callback Instrument with a callback, which is in the helper driver that's written in C
-tf, --thread-filter Driver instrumentation that filters only on thread ID (must use "-te" with this option)
-te THREAD_ENTRY, --thread-entry THREAD_ENTRY
The address (RVA) of the thread's initialization function
-nt NTOSKRNL, --ntoskrnl NTOSKRNL
ntoskrnl.exe path for offset extraction (non-optional if instrumenting a driver)
-e ENTRY, --entry ENTRY
Inject code on entry point, ie. -e9090
-l ENLARGE, --enlarge ENLARGE
Enlarge factor for sections, default=4
-v, --verbose Print debug log
-lf, --logfile Print log to pe-afl64.log rather than stream
NOP로 유저모드 바이너리 계측하기
PS pe-afl-64> python .\pe_afl.py -n C:\Work\cmd.exe C:\Work\cmd.exe.dump.json
[*] User-mode binary is being instrumented
[*] Single-thread instrument is on
[*] Preparing new sections
[*] Added section .text^
[*] Added section .cov
[*] Expanding relative jumps
[*] Expanded 3874 of 14353 branches
[*] Building address map
[*] Updating relative instructions
[*] Updating relocations...
[*] Updating Export table
[*] Updating load config
[*] Updating exception records
[*] Updating the PE headers
[*] Finalizing...
[*] Creating instrumented code
[*] Writing address mapping to C:\Work\cmd.exe.mapping.txt
[*] Updating jump tables
[*] Updated .text
...
[*] Removing temporary PE files
[*] Fixing PE checksum
[*] Instrumented binary saved to: C:\Work\cmd.instrumented.exe
프로세스 ID 필터링으로 커널모드 바이너리 계측하기
python .\pe_afl.py -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\driver.sys" "C:\Work\driver.sys.dump.json"
스레드 ID 필터링 및 상세 출력으로 커널모드 바이너리 계측하기
python .\pe_afl.py -v -tf -te 0x40000 -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe.dump.json"
먼저 시스템이 BIOS 또는 UEFI를 사용하여 부팅되는지 확인해야 합니다.
Hyper-V 머신의 경우: Gen 1 머신은 BIOS 기반이고, Gen 2 머신은 UEFI 기반입니다.
시스템이 BIOS 기반이라면 winload.exe를 패치해야 합니다:
ImgpValidateImageHash 함수를 찾으세요.mov eax, edi를 xor eax, eax로 교체하세요.bcdedit /set path \Windows\system32\winload2.exe를 실행하세요.UEFI를 사용하여 부팅하는 경우 EfiGuard 유틸리티를 사용하여 winload.efi를 패치하세요.
Hyper-V Manager를 사용하는 경우 명령어 치트시트:
FS1:
cd EFI/Boot
Load EfiGuard.Dxe.efi
FS0:
\EFI\Boot\bootx64.efi
그런 다음 원하는 드라이버를 계측하세요. Windows 머신에 계측된 드라이버를 로드하려면 서명이 있어야 하며, OS 요구 사항을 통과하려면 자체 서명 인증서만으로 충분합니다:
# In elevated powershell terminal
$c = New-SelfSignedCertificate -Type CodeSigningCert -KeyUsage DigitalSignature -Subject 'CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US'
Set-AuthenticodeSignature .\driver.instrumented.sys -Certificate $c -Force
계측한 드라이버가 이미 시스템에서 사용 중인 경우 다음 명령을 (관리자 권한으로) 실행하여 드라이버 파일을 교체하세요:
set NAME=mydriver.sys
icacls %NAME% /save C:\windows\temp\%NAME%.icacls
takeown /F %NAME%
icacls %NAME% /grant Everyone:F
move %NAME% %NAME%.bak
move instrumented_driver.sys %NAME%
icacls . /restore C:\windows\temp\%NAME%.icacls
그런 다음 실행하세요:
bcdedit /set recoveryenabled no
bcdedit /set nointegritychecks on
shutdown -t 0 -r
WinAFL과의 통합은 제공된 헤더를 사용하여 하네스를 컴파일하는 방식으로 이루어집니다.
헤더와 함께 사용 방법을 보여주는 샘플 프로그램인 example.c가 있습니다.
제공된 헤더는 WinAFL에서 이미 제공하는 헤더를 약간 수정한 것으로, Syzygy라는 또 다른 정적 바이너리 계측 도구와 통합하기 위한 것입니다.
kAFL과 통합하는 방법은 매우 간단합니다.
일반적으로 kAFL 하네스는 가상 머신에서 실행되며 특수 "하이퍼콜"을 사용하여 퍼저의 프런트엔드와 통신합니다.
이러한 하이퍼콜은 퍼저에게 여러 작업을 지시하며, 그중에는 IntelPT에서 커버리지 데이터를 로드하여 AFL 비트맵으로 파싱하는 작업도 포함됩니다.
peafl64는 IntelPT 추적을 불필요하게 만들기 때문에 커버리지 데이터를 퍼저로 전송할 방법을 준비해야 합니다.
따라서 우리는 VM에서 실행되는 (유저모드) 하네스가 헬퍼 드라이버를 사용하여 수집한 커버리지 데이터를 보낼 수 있도록 qemu와 kvm을 "하이퍼콜"로 확장했습니다.
이 내용은 ESXi에서의 설정에 관한 것이지만 AWS와 같은 다른 가상화 플랫폼에도 적용될 수 있습니다.
설정 방법은 매우 간단합니다:
install.sh qemu 단계 대신 install.sh qemu_sbi를 실행하세요.kAFL과 peafl64를 사용하여 퍼징하려면 퍼징 머신을 설정해야 합니다:
그 외에는 우리의 kAFL 포크를 사용한 퍼징은 일반적인 kAFL 퍼징과 동일합니다.
단계 개요:
먼저 IDA를 사용하여 관심 있는 명령어와 위치를 찾고 분석합니다.
분석은 해당 정보를 JSON 형식으로 포함하는 dump.json 파일을 생성합니다.
instrument.py에는 계측 로직이 포함되어 있으며, 흐름 자체는 process_pe 함수에 설명되어 있습니다.
바이너리를 계측하기 위해 계측된 모든 코드가 위치하게 될 실행 가능 섹션을 복제합니다.
다음으로 모든 상대 명령어를 처리하고 이를 처리해야 하는지와 방법을 결정합니다.
주소 X에서 Y로 가는 short jmp가 있다고 가정해 보겠습니다. X와 Y 사이에 계측 코드를 삽입하면 이제 대상은 주소 Z에 있습니다 (Z = Y + len(instrumentation)).
즉, 점프를 수정해야 합니다.
주소 Z가 이제 short jmp 범위 밖에 있다면 점프를 short에서 far로 변환해야 합니다. (instrument.py:expand_relative_instructions)
예를 들어:
short jmp 0x57 (eb 55) -> near jmp 0x604 (e9 ff 05 00 00)call, loop 및 조건부 점프와 같은 명령어도 마찬가지입니다.
처리가 필요한 또 다른 일반적인 사례는 x64 어셈블리에서 도입된 rip-상대 명령어입니다:
call [rip+0x1000]계측된 코드를 가리키도록 다음 항목을 업데이트합니다: 재배치 테이블, 익스포트 테이블, 로드 구성, TLS 디렉터리 및 예외 레코드.
업데이트는 원래 섹션의 모든 주소를 계측된 대응 주소로 변경하여 수행됩니다. (instrument.py:update_addr)
PE 구조의 변경 사항(추가된 섹션, 변경된 진입점 및 PE 크기)을 반영하도록 헤더가 업데이트됩니다. (instrument.py:update_pe_headers)
추가로 peafl64는 Windows 커널을 계측할 수 있습니다. 이를 위해 DVRT(Dynamic Value Relocation Table)와 SSDT를 파싱하고 업데이트하며, PatchGuard 관련 세부 사항을 처리합니다.
DVRT는 PE를 로드할 때 변경해야 하는 주소의 위치를 설명하는 컴파일러 생성 테이블입니다.
KASLR을 개선하고 Spectre 취약점을 완화하는 데 사용됩니다 (1, 2, 3).
DVRT는 테이블 구조를 모방하는 클래스 집합을 사용하여 파싱됩니다 (drt.py; instrument.py:get_updated_dynamic_relocs)
SSDT를 처리하고 업데이트하는 것은 그렇게 간단하지 않았습니다.
해당 주소는 익스포트되지 않으므로 심볼이나 경험적 방법에 의존하여 위치를 찾아야 했습니다.
우리의 해결책은 모든 Windows 10 버전에서 NtWaitForSingleObject를 상수 마커로 사용하고 이를 기준으로 SSDT 주소를 찾는 경험적 접근 방식을 사용합니다. (instrument.py:ntoskrnl_update_KiServiceTable)
이 접근 방식은 모든 Windows 10 커널에서 작동하지만 다른 커널 버전에서는 조정이 필요할 것입니다.