Search

Fuzzing: From Zero 0-day #2 | Windows Application Fuzzing

태그
RedSpider
Windows
Property
thumbnail_2.png
목차
RS-OS/App팀 dgkim(dong) 연구원

1. 개요

From Zero to 0-day 시리즈 1부에서는 Fuzzing 개요, Linux Application Fuzzing에 대해 설명했습니다. 2부에서는 Windows Application Fuzzing 과정을 단계별로 실습하며 실제로 KVE(Korea Vulnerabilities and Exposures)를 획득하기까지의 과정을 공유합니다.

2. Windows Fuzzing 도구

2.1. WinAFL

WinAFL은 대표적인 Grey-box Fuzzing의 도구인 AFL(American Fuzzy Lop)을 기반으로 한 Fuzzing 도구로, Windows 환경에서 Fuzzing을 지원합니다.
WinAFL (Windows)
DynamoRIO를 이용한 동적 계측
Persistent Mode를 이용한 빠른 Fuzzing

2.2. Application Verifier

Verifier는 Windows User-Mode Application 런타임 검증 도구입니다. 이는 API 오용, 메모리 누수, 그리고 스레드 동기화 문제 등을 감지하는 역할을 수행합니다. Verifier는 소스 코드의 유무와 상관없이 Windows Application에서 사용 가능합니다.
더 자세한 내용은 Application Verifier의 공식 문서에서 확인할 수 있습니다.

2.3. Application Verifier 예제

코드-1stack_bufheap_bufinput_size만큼 복사하는 예제입니다.
#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #define SIZE 10 #define STACK_SIZE SIZE*2 int main() { uint32_t input_size; uint8_t stack_buf[STACK_SIZE] = { 0 }; printf("stack_buf = %p\nsize = %d\n", stack_buf, STACK_SIZE); memset(stack_buf, 0x41, STACK_SIZE); uint8_t* heap_buf = (uint8_t*)malloc(SIZE); printf("heap_buf = %p\nsize = %d\n", heap_buf, SIZE); scanf("%d", &input_size); printf("memcpy(heap_buf, stack_buf, %d)", input_size); memcpy(heap_buf, stack_buf, input_size); free(heap_buf); return 0; }
C++
복사
Address Sanitizer 사용 샘플 : 코드-1
코드-1을 Windows 환경에서 컴파일한 후, 1부의 Sanitizer예제와 마찬가지로 Application Verifier를 활성화 하기 전에는 heap_buf 크기(10)보다 더 큰 값(11)을 넣어도 프로그램이 비정상 종료되지 않습니다.
>cl subtle_bug.c /Fe:subtle_bug.exe Microsoft (R) C/C++ 최적화 컴파일러 버전 19.42.34433(x64) Copyright (c) Microsoft Corporation. All rights reserved. subtle_bug.c Microsoft (R) Incremental Linker Version 14.42.34433.0 Copyright (C) Microsoft Corporation. All rights reserved. /out:subtle_bug.exe subtle_bug.obj >.\subtle_bug.exe stack_buf = 0000001E005DFCC0 size = 20 heap_buf = 0000015DFC99FB40 size = 10 11 memcpy(heap_buf, stack_buf, 11) >echo %ERRORLEVEL% 0
PowerShell
복사
Application Verifier없이 비정상적인 입력 : 비정상 종료 되지 않음
그러나 Application Verifier를 활성화 후, cdb.exe를 통해 프로그램을 디버깅하고, heap_buf 크기(10)보다 더 큰 값(11)을 넣으면 Exception이 발생합니다.
1. Application Verifier 실행
2. Application Verifier, Application 선택
3. Basics 선택 및 활성화
>"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe" .\subtle_bug.exe // ... CommandLine: .\subtle_bug.exe // ... 0:000> g stack_buf = 00000084C278FCC0 size = 20 heap_buf = 000001C73E483FF0 size = 10 11 memcpy(heap_buf, stack_buf, 11) ======================================= VERIFIER STOP 000000000000000F: pid 0x1774: Corrupted suffix pattern for heap block. 000001C7391E1000 : Heap handle used in the call. 000001C73E483FF0 : Heap block involved in the operation. 000000000000000A : Size of the heap block. 000001C73E483FFA : Corruption address. ======================================= This verifier stop is not continuable. Process will be terminated when you use the `go' debugger command. ======================================= (1774.22c8): Break instruction exception - code 80000003 (first chance) vrfcore!VerifierStopMessageEx+0x858: 00007ffc`247f4048 cc int 3 0:000> !analyze -v ******************************************************************************* * * * Exception Analysis * * * ******************************************************************************* APPLICATION_VERIFIER_HEAPS_CORRUPTED_HEAP_BLOCK_SUFFIX (f) // ...
PowerShell
복사
Application Verifier 비정상적인 입력 : 비정상 종료됨.
Application Verifier는 11바이트 데이터를 쓰는 과정에서 발생한 Heap Buffer Overflow(APPLICATION_VERIFIER_HEAPS_CORRUPTED_HEAP_BLOCK_SUFFIX)를 탐지했습니다. 이처럼 Application Verifier를 활성화하면, 당장 Application의 Crash를 유발하지 않더라도 메모리를 손상시키는 잠재적인 버그까지 정확히 찾아낼 수 있습니다.
Application Verifier가 이러한 버그를 탐지 할 수 있는 이유

3. Forkserver, Persistent mode Fuzzing

3.1. Forkserver Fuzzing

3.1.1. AFL Forkserver Fuzzing

프로그램을 실행할 때는 Process 생성, Shared Library 로드 등 많은 초기화 과정이 필요합니다. 매 Fuzzing 마다 이 과정을 반복하는 것은 Fuzzing 속도를 크게 저하하는 원인이 됩니다. 이를 해결하기 위해 AFL은 아래 그림과 같은 forkserver 구조를 도입했습니다. 최초에 Target Program을 한 번만 실행하여 Shared Library 로드등의 초기화 과정을 끝낸 뒤, 해당 Process를 fork()하는 방식으로 로드 부하를 최소화 합니다.
AFL의 forkserver 구조
AFL forkserver 구조의 동작 과정은 다음과 같습니다.
1.
AFL Fuzzerexecv함수를 통해 target_binary를 실행합니다.
target_binary 실행
static void afl_fauxsrv_execv(afl_forkserver_t *fsrv, char **argv) { pid_t child_pid; // ... while (1) { // finally: exec... execv(fsrv->target_path, argv); } // ... }
C
복사
afl-forkserver.c : target_binary를 실행하는 코드
2.
AFL컴파일러로 컴파일된 target_binaryinit_array를 통해 _afl_manual_init() 함수를 호출합니다.
target_binaryinit_array를 통한 afl_manual_init() 함수 호출
3.
_afl_manual_init() 함수에서 fork()이후 Parent Process는 forkserver가 되어 fork(), waitpid()를 반복해서 호출합니다. 이는 Process 초기화 오버헤드를 제거하고 fork()를 통해 target_binary Process를 빠르게 생성하여 Fuzzing 속도를 극대화합니다.
_afl_manual_init() : fork()이후 Parent Process의 실행 흐름
4.
_afl_manual_init() 함수에서 fork()이후 Child Process는 원래의 코드를 실행합니다.
_afl_manual_init() : fork() 이후 Child Process의 실행 흐름

3.1.2. AFL Forkserver Fuzzing 한계점

Forkserver는 Process의 메모리 상태를 복제하는 POSIX 표준 시스템 콜인 fork()를 기반으로 합니다. 따라서 fork()를 지원하지 않는 Windows에서는 이 구조를 직접 사용할 수 없습니다.

3.2. Persistent mode Fuzzing

3.2.1. WinAFL Persistent mode Fuzzing

Windows는 fork() 시스템 콜을 지원하지 않으므로, Fuzzing 성능 향상을 위해 아래 그림과 같은 Persistent Mode를 사용합니다. 이 방식은 Process를 fork()하는 대신, Fuzzing Target Process가 종료되지 않고 내부에서 특정 함수를 반복적으로 실행하는 구조입니다. WinAFL은 DynamoRIO와 같은 도구를 이용해 이를 구현합니다.
WinAFL의 Persistent mode Fuzzing 구조
1.
drwrap_wrap_ex함수를 통해 Fuzzing 대상 함수 실행 직전과 직후를 각각 pre_fuzz_handler, post_fuzz_handlerHooking합니다.
fuzz_func() 함수 전, 후를 Hooking
static void event_module_load(void *drcontext, const module_data_t *info, bool loaded) { // ... app_pc to_wrap = 0; // ... fuzz_func = info->start/* starting address of module */ + options.fuzz_offset /* Fuzzing Target Function Offset */; // ... drwrap_wrap_ex(fuzz_func, pre_fuzz_handler, post_fuzz_handler, NULL, options.callconv); }
C
복사
winafl.c : fuzzing target 함수를 Hooking하는 코드
2.
fuzz_func() 실행 전, pre_fuzz_handler가 호출되어 인자, Stack Pointer, Funzzing Target Function PC등을 저장합니다.
static void pre_fuzz_handler(void *wrapcxt, INOUT void **user_data) { int i; void *drcontext; app_pc target_to_fuzz = drwrap_get_func(wrapcxt); dr_mcontext_t *mc = drwrap_get_mcontext_ex(wrapcxt, DR_MC_ALL); drcontext = drwrap_get_drcontext(wrapcxt); fuzz_target.xsp = mc->xsp; // Save Stack Pointer fuzz_target.func_pc = target_to_fuzz; // Save Fuzzing Target Function PC // ... if (!options.no_loop) { if (fuzz_target.iteration == 0) { // save arguments for (i = 0; i < options.num_fuz_args; i++) options.func_args[i] = drwrap_get_arg(wrapcxt, i); } else { // restore arguments for (i = 0; i < options.num_fuz_args; i++) drwrap_set_arg(wrapcxt, i, options.func_args[i]); } } // ... }
C
복사
3.
fuzz_func() 함수 실행 후, post_fuzz_handler가 호출되어 pre_fuzz_handler에서 저장했던 정보를 복구하고, 프로그램 실행 흐름(PC)을 다시 fuzz_func()로 변경합니다.
static void post_fuzz_handler(void *wrapcxt, void *user_data) { dr_mcontext_t *mc; void *drcontext; mc = drwrap_get_mcontext(wrapcxt); fuzz_target.iteration++; if(fuzz_target.iteration == options.fuzz_iterations) { dr_exit_process(0); } mc->xsp = fuzz_target.xsp; // Restore Stack Pointer mc->pc = fuzz_target.func_pc; // Restore Fuzzing Target Function PC drwrap_redirect_execution(wrapcxt); // Fuzzing Target Function 함수로 실행 흐름 변경 }
C
복사
이 과정을 통해 Windows 환경에서도 fork() 없이 빠른속도로 Fuzzing을 수행할 수 있습니다.

3.2.2. WinAFL persistent mode Fuzzing 한계점

Persistent Mode Fuzzing은 단일 Process를 재사용해 Fuzzing의 속도를 극대화하는 기법입니다. 하지만 Fuzzing이 반복되면서 Global Variable, Heap Memory, File Handle 같은 자원이 초기화 또는 해제되지 않아 State Corruption이 발생할 수 있습니다.
이러한 State Corruption은 실제 취약점이 아님에도 Crash를 유발하는 False Positive, 실제 취약점을 놓치는 False Negative로 이어져 Fuzzing 결과의 신뢰도를 크게 저하시킵니다. 따라서 Persistent Mode Fuzzing을 제대로 활용하려면, 매 실행마다 상태를 완벽히 되돌리는 정교한 초기화 로직 구현이 필수적입니다.
이 문제를 근본적으로 해결하기 위해 Windows에서 fork()를 구현하여 Fuzzing하는 연구도 존재합니다.

4. WinAFL 환경 구성

환경 정보
Windows 버전 : Windows 11 23H2(OS Build 22631.5624), 64비트
Windows 24H2 환경에서 WinAFL 버그가 존재하므로 23H2 버전 선택
DynamoRIO 버전: 11.90.20287, 64비트
WinAFL 버전 : 1.17
Visual Studio Community : 17.14.9
1.
Install DynamoRIO
공식 DynamoRIO 다운로드 링크를 통해 다운로드 및 압축 해제합니다. (DynamoRIO-Windows-11.90.20287)
2.
WinAFL clone 및 빌드 (on x64 Developer Tools)
> git clone --recursive https://github.com/googleprojectzero/winafl > cd winafl > mkdir build64 > cd build64 > cmake -A x64 .. -DDynamoRIO_DIR=c:\Users\78RL\DynamoRIO-Windows-11.90.20287\cmake -DINTELPT=1 > cmake --build . --config Release
Bash
복사
3.
WinAFL 빌드 완료
> cd bin\Release\ > dir C:\Users\78RL\winafl\build64\bin\Release 디렉터리 <DIR> . <DIR> .. 31,232 afl-analyze.exe 165,888 afl-fuzz.exe 30,720 afl-showmap.exe 38,912 afl-tmin.exe 14,848 custom_net_fuzzer.dll 13,824 custom_winafl_server.dll 102,400 libipt.dll 11,776 test.exe 13,312 test_gdiplus.exe 12,288 test_netmode.exe 12,288 test_servermode.exe 125,440 winafl.dll 51,712 winaflpt-debug.exe 13개 파일 624,640 바이트 2개 디렉터리 45,234,941,952 바이트 남음 > .\afl-fuzz.exe WinAFL 1.17 by <ifratric@google.com> Based on AFL 2.43b by <lcamtuf@google.com> .\afl-fuzz.exe [ afl options ] -- [instrumentation options] -- \path\to\fuzzed_app [ ... ] Required parameters: -i dir - input directory with test cases -o dir - output directory for fuzzer findings -t msec - timeout for each run Instrumentation type: -D dir - directory with DynamoRIO binaries (drrun, drconfig) -w winafl - Path to winafl.dll -P - use Intel PT tracing mode -y - use TinyInst tracing mode -Y - enable the static instrumentation mode Execution control settings: -f file - location read by the fuzzed program (stdin) -m limit - memory limit for the target process -p - persist DynamoRIO cache across target process restarts -c cpu - the CPU to run the fuzzed program Fuzzing behavior settings: -d - quick & dirty mode (skips deterministic steps) -n - fuzz without instrumentation (dumb mode) -x dir - optional fuzzer dictionary (see README) Other stuff: -I msec - timeout for process initialization and first run -T text - text banner to show on the screen -M \ -S id - distributed mode (see parallel_fuzzing.txt) -C - crash exploration mode (the peruvian rabbit thing) -e - expert mode to run WinAFL as a DynamoRIO tool -l path - a path to user-defined DLL for custom test cases processing -V - show version number and exit Attach: -A module - attach to the process that loaded the provided module For additional tips, please consult afl_docs\README.
Bash
복사
WinAFL 빌드 완료

5. Fuzzing Target 선정

5.1. Fuzzing Target 선정 기준

Fuzzing Target 선정 기준은 1부에서 언급한 기준을 참고합니다.

5.2. Windows : ArkLibrary(BandiZip)

1.
Fuzzing Harness 작성 난이도 : 낮음
ArkLibrary는 SDK를 통해 간단한 API를 제공합니다.
2.
Fuzzing Target의 구현, 알고리즘의 복잡성 : 복잡함
ArkLibrary또한 다양한 포맷을 지원하므로 알고리즘, 구현이 복잡할 가능성이 높습니다.
3.
파급력 :
Windows 환경에서 BanidiZip은 무료로 제공되며, ArkLibrary는 여러 회사들에서 사용하고 있어 취약점이 발생 시 파급력이 큽니다.
4.
SDK 제공
ArkLibrary는 자체적으로 SDK를 제공하여 Harness 작성에 큰 도움이 됩니다.

6. Fuzzing 진행

6.1. Windows : ArkLibrary(BandiZip) Harness 작성

ArkLibrary는 SDK를 제공하며 아래와 같이 아주 간단한 사용 예제를 제공합니다.
#include "ArkLib.h" ... CArkLib ark; ark.Create(ARK_DLL_RELEASE_FILE_NAME, NULL, NULL); ark.Open("sample.zip", NULL); ark.ExtractAllTo("c:/output/");
C++
복사
ArkLibrary : ArkLibrary SDK 사용 예제
1.
SDK 분석 및 최적화
SDK에서 제공하는 기본 사용 예제는 ExtractAllTo() 함수를 통해 압축 해제 결과를 디스크에 직접 쓰는 방식입니다. 그러나 이러한 파일 입출력은 Fuzzing 속도를 저하므로 비효율적입니다.
이 문제를 해결하기 위해 SDK의 Ark.h 헤더 파일을 분석합니다. Open() 함수로 파일을 읽어 들인 후 TestArchive() 함수를 호출하면, 디스크에 파일을 쓰지 않고도 라이브러리 내부의 압축 해제 및 데이터 검증 로직을 모두 실행할 수 있습니다.
//////////////////////////////////////////////////////////////////////////////////////////////////// // // 압축파일의 압축 해제 인터페이스 // struct IArk { ARKMETHOD(void) Release() PURE; // ... ARKMETHOD(BOOL32) Open(LPCSTR filePath, LPCSTR password) PURE; // ... ARKMETHOD(BOOL32) TestArchive() PURE; // ... }
C++
복사
ArkLibrary : Ark.h의 압축 해제 인터페이스
2.
최종 Harness 코드
위 분석 내용을 바탕으로, 파일 생성 없이 ArkLibrary의 핵심 기능을 테스트하는 최종 Harness를 아래와 같이 작성합니다. 이 Harness는 Open()으로 파일을 열고 TestArchive()를 호출하여 Fuzzing을 수행한 뒤, Release()로 자원을 해제합니다.
// ... typedef IArk* (*CreateArkPtr)(uint32_t); __declspec(noinline) bool __stdcall fuzzme(char* src_path); IArk* ark; CreateArkPtr CreateArk_func; extern "C" __declspec(dllexport) __declspec(noinline) bool __stdcall fuzzme(const char* src_path) { ark = CreateArk_func(ARK_VERSION); if (ark == NULL) { return false; } if (!ark->Open(src_path, NULL)) { ark->Release(); return false; } if (!ark->TestArchive()) { ark->Release(); return false; } ark->Release(); return true; } // ...
C++
복사
ArkLibrary Harness

6.2. Seed Corpus 수집

Coverage-guided Fuzzing에서 가장 중요한 요소는 양질의 초기 입력 데이터, 즉 Seed corpus를 확보하는 것입니다. Seed corpus가 높은 코드 coverage를 달성할수록, Fuzzer가 더 깊은 로직에 도달하여 취약점을 발견할 확률이 높아집니다.
따라서 본 글에서는 높은 커버리지 달성을 위해 BandiZip에서 보고된 버그 샘플들을 수집하여 Seed corpus로 사용합니다.

6.3. Windows ArkLibrary(BandiZip) Fuzzing

1.
ArkLibrary Harness를 컴파일합니다.
> cl ark_harness.c /Fe:ark_harness.exe
PowerShell
복사
2.
drrun.exe를 이용한 테스트
아래의 명령어를 실행하여 fuzzme 함수가 10회 반복 호출되는지 검사합니다.
"C:\Users\78RL\DynamoRIO-Windows-11.90.20287\bin64\drrun.exe" -c "C:\Users\78RL\winafl\build64\bin\Release\winafl.dll" -debug -target_module ark_harness.exe -target_method fuzzme -fuzz_iterations 10 -nargs 2 -- ark_harness.exe "C:\Users\78RL\seed\test.zip"
Bash
복사
테스트 결과, 아래와 같이 pre_fuzz_handlerpost_fuzz_handler가 반복적으로 호출되며 모든 것이 정상적으로 실행되는 것을 확인 했습니다.
drrun.exe 실행 결과
3.
Application Verifier 설정
더욱 정확한 메모리 오류 탐지를 위해 Application Verifier를 활성화합니다. 단, WinAFL의 Persistent mode를 사용할 경우, 핸들(Handles) 누수 관련하여 오탐(false positive)이 발생할 수 있습니다. 따라서 다음과 같이 Handles 옵션을 비활성화한 후 설정을 저장합니다.
Application Verifier : Handles 설정 비활성화
4.
Fuzzing 시작
주요 옵션 설명
-i in: 초기 시드(seed) 파일이 있는 입력 디렉터리
-o out: Crash, hang 등 Fuzzing 결과를 저장할 출력 디렉터리
-D C:\...: DynamoRIO 바이너리 경로
-coverage_module ark.x64.dll: 코드 커버리지를 측정할 핵심 Target 모듈
-fuzz_iterations 5000: Persistent Mode에서 한 번의 실행으로 반복할 횟수
-target_module ark_harness.exe -target_method fuzzme: Fuzzing을 수행할 대상 실행 파일과 함수
@@: Fuzzer가 생성한 입력 파일 경로가 위치할 자리
"C:\Users\78RL\winafl\build64\bin\Release\afl-fuzz.exe" -i in -o out -w "C:\Users\78RL\winafl\build64\bin\Release\winafl.dll" -D C:\Users\78RL\DynamoRIO-Windows-11.90.20287\bin64 -t 20000 -- -coverage_module ark.x64.dll -fuzz_iterations 5000 -target_module ark_harness.exe -target_method fuzzme -nargs 1 -- ark_harness.exe @@
Bash
복사
1.
진행 상황 (Process & Cycle)
run time: Fuzzer가 총 실행된 시간. (현재 30초)
last new path: 새로운 코드 경로를 마지막으로 발견한 시점. 이 시간이 짧을수록 Fuzzer가 활발하게 새 경로를 찾고 있다는 의미. (현재 1초 전)
cycles done: Corpus을 한 번씩 모두 테스트한 횟수. 0은 아직 첫 번째 사이클을 진행 중이라는 뜻.
2.
성능 및 커버리지 (Performance & Coverage)
exec speed: 초당 실행 횟수. Fuzzing에서 가장 중요한 성능 지표 중 하나로, 높을수록 좋음. (현재 초당 131회)
total paths: 현재까지 발견하여 큐에 저장된 고유한 경로(실행 흐름)의 총개수. (현재 64개)
map density: 전체 코드 중 얼마나 많은 부분을 탐색했는지 나타내는 코드 커버리지 지표. (현재 약 3.51%)
3.
결과 (Overall Results)
uniq crashes: 발견된 고유한 crash의 수. Fuzzing의 주된 목표로, 이 숫자를 높이는 것이 중요함. (현재 0개)
uniq hangs: 프로그램을 멈추게 하는 고유한 입력(hang)의 수. (현재 0개)

7. ArkLibrary Heap Overflow 취약점 분석

7.1. 취약점 동적 분석

Fuzzer에서 탐지된 crash 파일을 이용하여 실제 취약점을 재현하고, 디버거를 통해 동작을 분석합니다.
분석 환경 설정
1.
Application Verifier 활성화: Bandizip.exe에 대해 Application Verifier의 기본 옵션을 활성화하여 메모리 오류 탐지를 강화합니다.
2.
AeDebug 설정: WinDbg를 시스템의 기본 디버거로 설정하여, 프로그램이 비정상적으로 종료될 때 자동으로 디버거가 실행되도록 합니다.
3.
crash 재현 및 확인
위 환경에서 조작된 .rar 파일을 반디집으로 열면, Heap Overflow로 인해 프로그램이 강제 종료되며 디버거가 실행됩니다.
디버깅 결과, ark_x64.dll 모듈 내부에서 crash가 발생했음을 확인했습니다. rdx 레지스터는 악성 .rar 파일에 포함된 공격자의 데이터(e.g., 41414141...)를 가리키고 있었고, 이 데이터가 메모리에 쓰기(write) 작업을 수행하는 mov 명령어에서 사용되면서 Heap Buffer Overflow를 유발합니다. 이는 공격자가 조작된 데이터를 통해 프로그램의 메모리를 덮어쓸 수 있음을 의미합니다.
Breakpoint 0 hit ark_x64!CreateArkCompressor+offset: addr e84eb81500 call memcpy 0:009> u @rip ark_x64!CreateArkCompressor+0x2e93d: addr e84eb81500 call memcpy addr 488b0e mov rcx,qword ptr [rsi] addr 664489240f mov word ptr [rdi+rcx],r12w addr 4c89742448 mov qword ptr [rsp+48h],r14 addr 4c897c2450 mov qword ptr [rsp+50h],r15 addr 488b4c2460 mov rcx,qword ptr [rsp+60h] addr 4885c9 test rcx,rcx addr 7405 je ark_x64!CreateArkCompressor+offset2 (addr) 0:009> r rcx, rdx, r8 rcx=000002745542eff0(dst) rdx=000002745faa0ff0(src) r8=fffffffffffffffe(size) 0:009> dqs rdx ; 공격자가 제어가능한 입력 데이터 00000274`5faa0ff0 41414141`41414141 00000274`5faa0ff8 42424242`42424241 00000274`5faa1000 43434343`43434342 00000274`5faa1008 44444444`44444443 00000274`5faa1010 00000000`00004544 00000274`5faa1018 00000000`00000000 00000274`5faa1020 00000000`00000000 00000274`5faa1028 00000000`00000000 00000274`5faa1030 00000000`00000000 00000274`5faa1038 00000000`00000000 00000274`5faa1040 00000000`00000000 00000274`5faa1048 00000000`00000000 00000274`5faa1050 00000000`00000000 00000274`5faa1058 00000000`00000000 00000274`5faa1060 00000000`00000000 00000274`5faa1068 00000000`00000000 0:009> dqs rcx ; 목적지 Buffer (size = 0) 00000274`5542eff0 d0d0d0d0`d0d0d0c0 00000274`5542eff8 d0d0d0d0`d0d0d0d0
PowerShell
복사
Attacker-Controlled Heap Overflow

7.2. 취약점 정적 분석

IDA를 이용해 ark.x64.dll 모듈을 정적으로 분석하여 취약점의 근본 원인을 파악합니다.
ark.x64.dll 의 취약한 함수의 디컴파일 코드
1.
UnpDataSize 값 읽기
취약점은 .rar 파일 헤더로부터 압축 해제될 데이터의 크기, 즉 UnpDataSize 값을 읽는 것에서 시작됩니다. 공격자는 이 값을 비정상적으로 큰 값(e.g., 0xFFFFFFFF)으로 조작할 수 있습니다.
2.
Integer Overflow
UnpDataSize 값을 이용하여 (UnpDataSize >> 1) + 1 계산을 통해 메모리를 할당할 크기를 결정합니다.
UnpDataSize가 매우 큰 값이면 이 계산 과정에서 Integer Overflow가 발생하여, 예상보다 훨씬 작은 크기(0)가 malloc 함수에 전달됩니다.
3.
Heap Overflow
Integer Overflow 인해 매우 작은(0) Buffer(dst)가 할당된 직후, memcpy 함수가 호출됩니다.
이때 memcpy는 복사할 데이터의 크기로 원본 UnpDataSize을 그대로 사용합니다.
결과적으로, 아주 작은 dst Buffer에 매우 큰 크기의 데이터를 복사하려는 시도가 일어나면서 할당된 Heap 영역을 훨씬 넘어서는 대량의 쓰기 작업이 발생하고, 이는 Heap Buffer Overflow로 이어집니다.

8. 취약점 제보

8.1. KVE (KISA)

취약점 신고 접수 페이지로 이동 및 아래 내용 채운 후 취약점을 제보합니다.

8.2. 결과

ArkLibrary : KVE-2024-0290

9. ArkLibrary 패치 분석

memcpy 수행 전 UnpDataSize0x3ffffffc 이하인지 검사하도록 코드가 수정되었습니다.
이 값(0x3ffffffc)은 malloc의 크기를 계산하는 과정((UnpDataSize >> 1) + 1)에서 Integer Overflow가 발생하지 않는 최대 값입니다. 이 검증을 통해 malloc에 항상 유효한 크기가 전달되도록 보장합니다.
KVE-2024-0290 패치

10. 결론

2부에서는 Windows 환경에서 상용 소프트웨어(ArkLibrary)를 대상으로 Fuzzing을 수행하여 실제 Heap Overflow 취약점을 발견했으며, 이 취약점은 KVE를 획득했습니다. 이 과정을 통해 Fuzzing이 단순한 이론을 넘어, 실제 제품의 보안 취약점을 찾아내는 강력하고 실효성 높은 기법임을 입증했습니다.
78ResearchLab RedSpider-OS/APP 연구팀은 이처럼 알려지지 않은 0-Day 위협을 탐지하는 사이버 보안 신기술을 연구·개발하여 고객의 제품과 서비스에 선제적인 보안 솔루션을 제공하고 있습니다.