포스트

퍼징(Fuzzing) 총정리: 원리부터 핵심 퍼저 비교와 한계점까지

퍼징(Fuzzing) 총정리: 원리부터 핵심 퍼저 비교와 한계점까지

1. 서론

보안 취약점을 찾아내고 시스템 해킹을 공부하다 보면, ‘퍼징(Fuzzing)’이라는 단어를 어느순간 듣게 된다. 많은 취약점 연구원들이 새로운 버그를 찾아낼 때 가장 먼저 활용하는 기법이기도 하다.

처음에는 퍼징이 단순히 컴퓨터에게 무작위로 아무 값이나 입력하게 시키는 단순한 작업인 줄 알았다. 하지만 오픈소스 저장소들을 찾아보고 기초적인 메커니즘을 공부할수록, 내부적으로 꽤 영리하고 체계적인 규칙에 의해 움직인다는 것을 알게 되었다.

이번 포스팅에서는 퍼징을 처음 공부하면서 정리한 가장 기초적인 개념과 작동 원리, 그리고 이 분야에서 가장 기본이 되는 핵심 오픈소스 퍼저(Fuzzer) 3개에 대해 정리해 보았다.


2. 퍼징과 퍼저의 정의 및 세대별 발전 과정

공부를 시작하면서 가장 먼저 부딪혔던 문제는 용어의 혼용이었다. 리서치를 해보니 헷갈리기 쉬운 두 개념에는 명확한 기준이 있었다.

A. 퍼징(Fuzzing)과 퍼저(Fuzzer)의 개념 차이

  • 퍼징 (Fuzzing): 프로그램의 입력 인터페이스에 예상치 못한 비정상적·무작위 데이터(Fuzz)를 대량으로 주입하여, 예외 처리 결함이나 비정상 종료(Crash)를 유도하는 보안 방법론이자 동적 테스트 행위를 뜻한다. 가장 직관적으로는 ‘행위’ 그 자체라고 이해하면 편했다.
  • 퍼저 (Fuzzer): 사람이 일일이 대입하기 불가능한 퍼징 프로세스를 컴퓨터가 스스로 지치지 않고 가속화된 형태로 수행하도록 설계된 자동화 소프트웨어 시스템이자 도구를 말한다. 즉, 퍼징을 수행하는 ‘툴’을 의미한다.

B. 기술적 세대 분류

퍼징 기술이 어떻게 발전해 왔는지 역사를 살펴보니, 컴퓨팅 자원의 성장과 컴파일러 기술의 진화에 맞춰 크게 4개 세대로 발전해 왔음을 알 수 있었다.

  1. 1세대: 무작위 퍼징 (Dumb Fuzzing): 1989년 Barton Miller 교수가 네트워크 라인의 노이즈 때문에 Unix 프로그램들이 종료되는 현상에서 착안했다고 한다. 입력 스트림에 완벽한 무작위 데이터만 주입하는 방식인데, 프로그램 초입의 파일 헤더 검증 로직 조차 통과하지 못해 깊은 코드 영역을 탐색하기엔 효율이 너무 떨어졌다.
  2. 2세대: 문법 기반 퍼징 (Generation/Grammar-based Fuzzing): SPIKE나 Peach Fuzzer 등이 여기에 해당한다. 분석가가 명세서를 분석해 문법 템플릿을 미리 정의해 주면, 퍼저가 이를 준수하면서 데이터 길이를 늘리거나 경계값을 주입하는 방식이다. 복잡한 포맷을 뚫을 수 있지만 타겟마다 템플릿을 수작업으로 개발해야 해서 확장성이 아쉬웠다.
  3. 3세대: 커버리지 기반 그레이박스 퍼징 (Coverage-guided Grey-box Fuzzing): 2013년 출시된 AFL(American Fuzzy Lop)이 이 세대를 이끌었다. 컴파일 타임이나 런타임에 가벼운 계측 코드를 심어서, 새로운 실행 경로를 발견한 유용한 입력값만 보존하고 진화시키는 유전 알고리즘을 도입했다. 퍼저가 스스로 타겟의 로직 구조를 학습하는 영리한 방식이다.
  4. 4세대: 하이브리드 퍼징 (Hybrid Fuzzing): 그레이박스 퍼징의 속도에 심볼릭/콘콜릭 실행(Symbolic/Concolic Execution) 기법을 결합한 최첨단 기술이다. 퍼저가 무작위 변이만으로 풀기 어려운 복잡한 암호화 로직이나 빽빽한 조건문을 만나면, 컴퓨터가 수학적 공식으로 조건의 정답을 계산해 낸 뒤 유효한 입력을 산출해 퍼저에게 역주입하는 방식이다. 현재는 AFL++가 이 제어 기술을 받아들여 메인스트림을 이끌고 있다.

3. 퍼저 내부 서브시스템 아키텍처 및 상호작용

하나의 완성된 퍼저 프로그램을 뜯어보면 내부적으로 Mutator(변이기), Executor(실행기), Monitor(감시관)라는 3가지 하위 구성 요소가 맞물려 돌아가고 있었다. 이들이 가상 공간에서 어떻게 상호작용하는지 정리해 보았다.

A. Mutator (변이기)

Mutator는 새로운 탐색 경로를 개척하기 위해 입력 데이터를 요리조리 비틀어보는 역할을 수행한다. 퍼저는 원래 정상적이던 샘플 파일(시드)을 메모리로 읽어온 뒤 다음과 같은 방식으로 변형을 가한다.

  • 비트 뒤집기: 데이터의 최소 단위인 비트를 반대로 뒤집어 데이터 구조를 의도적으로 무너뜨린다.
  • 산술 연산: 바이트 값에 임의의 숫자를 더하거나 빼서, 프로그램이 계산 오류를 일으키도록 유도한다.
  • 블록 조작: 데이터의 특정 덩어리를 삭제하거나, 복사하거나, 엉뚱한 위치에 끼워 넣는다.
  • 사전 주입: 미리 정의된 단어를 강제로 밀어 넣어 프로그램의 입구 검증 로직을 통과시킨다.

B. Executor (실행기)

Executor는 변이기가 가공한 이상한 데이터를 타겟 프로그램에 밀어 넣고 실제로 구동시키는 엔진이다. 퍼징은 초당 수천 번 이상 프로그램을 껐다 켜야 하므로 속도가 중요한데, 현대 퍼저들은 매번 프로그램을 처음부터 무겁게 실행하는 대신 포크서버(Forkserver)라는 영리한 메커니즘을 쓴다는 것을 알게 되었다.

프로그램을 실행하기 직전, 모든 라이브러리와 환경 세팅이 다 끝나서 가볍게 복사할 수 있는 준비 상태로 대기시켜 둔다. 그리고 퍼저가 신호를 보낼 때마다 메모리 상에서 현재 상태를 그대로 복사해서 자식 프로세스를 띄운다. 새로 프로그램을 처음부터 부팅하는 것보다 훨씬 가볍고 빠르게 타겟 코드를 가동할 수 있는 비결이다.

C. Monitor (감시관)

Monitor는 실행된 자식 프로그램의 생명 주기를 실시간으로 관찰하고 감시하는 모듈이다. 프로그램이 도중에 심각한 메모리 오류를 만나 비정상 종료를 일으키며 뻗을 때, 커널이 보내는 시그널들을 가로채 상황을 판단한다.

  • SIGSEGV (Segmentation Fault): 프로그램이 허용되지 않은 잘못된 메모리 주소를 참조해 터졌을 때 발생한다. (버퍼 오버플로우 등)
  • SIGBUS (Bus Error): 물리적인 주소 정렬이 잘못되었거나 존재하지 않는 메모리에 접근했을 때 터진다.
  • SIGABRT (Abort): 프로그램 스스로 혹은 검시 장치가 치명적인 메모리 오염을 감지하고 강제로 중단했을 때 발생한다.

만약 프로그램이 터지지 않고 무사히 끝났다면, Monitor는 프로그램이 실행되는 동안 새로 켜진 코드 경로가 있는지 체크한다. 새로운 경로가 발견되었다면 "이 데이터는 가치가 있다"고 판단하여 다음 변이의 기준이 될 새 시드로 저장한다.


4. 퍼징의 3대 분류 체계와 그레이박스 코드 커버리지 원리

A. 퍼징 아키텍처의 분류 매트릭스

퍼징을 공부할 때 어떤 기준으로 도구를 분류하는지 아는 것이 중요했다. 리서치 결과 크게 3가지 차원으로 나누어 볼 수 있었다.

  • 타겟 정보 파악 (지식 차원): 내부를 전혀 모른 채 결과만 보는 블랙박스, 소스코드를 수학적으로 완벽히 분석하는 화이트박스, 실행 중에 프로그램이 거쳐 간 경로 힌트만 스마트하게 받아오는 그레이박스로 분류된다.
  • 입력값 가공 (생성 차원): 정상 샘플 데이터를 조금씩 비틀어 쓰는 변이 기반과, 데이터 포맷의 규칙/문법을 배워서 바닥부터 새로 만드는 생성 기반이 존재한다.
  • 프로세스 통제 (실행 차원): 타겟이 터져도 퍼저가 안전하게 분리되어 있는 프로세스 외부 방식과, 타겟 함수를 퍼저 주소창 안으로 끌고 들어와 무한 반복 호출하여 속도를 극대화하는 프로세스 내부 방식으로 나뉜다.

B. 코드 커버리지 측정의 기술적 메커니즘 (AFL 아키텍처 분석)

현대 퍼징의 대세인 그레이박스 퍼저가 똑똑한 이유는 단순 코드 한 줄이 실행되었는지가 아니라, 블록과 블록 사이의 연결 통로인 ‘에지 커버리지(Edge Coverage)’를 추적하기 때문이다. A 블록에서 B 블록으로 가는 것과, B 블록에서 A 블록으로 거꾸로 오는 흐름의 방향성까지 완벽하게 구별해야 정교한 버그를 찾을 수 있어서다.

AFL 아키텍처의 커버리지 추적 원리는 소스코드 빌드 단계에서 프로그램 내부의 모든 분기점마다 고유한 무작위 ID를 부여하는 것부터 시작된다. 그리고 퍼저와 타겟 프로그램이 실시간으로 공유하는 64KB 크기의 거대한 비트맵 지도를 개설하여 경로를 기록한다. 프로그램이 특정 분기점을 통과할 때마다, 현재 위치의 고유 ID와 바로 직전 분기점의 ID를 조합하여 지도의 인덱스를 계산하고 해당 칸의 숫자를 1씩 증가시키는 이론적 아키텍처를 가지고 있다.

🔍 기술 분석: 왜 이전 위치 값을 한 칸 밀어서 섞어줄까?

여기서 핵심은 인덱스를 계산할 때 이전 위치의 ID 값을 오른쪽으로 1비트 시프트한 뒤 현재 위치의 ID와 XOR 연산을 수행한다는 점이다. 만약 단순히 두 ID 값을 곧바로 XOR 연산하게 되면, A to B로 이동할 때의 결과와 B to A로 거꾸로 올 때의 결과가 수학적으로 완전히 동일해지는 문제가 발생한다. 경로의 역방향 흐름까지 서로 다른 에지로 완벽하게 식별하고 분기점의 방향성을 보존하기 위해 비트를 한 칸 밀어서 섞어주는 논리적 설계가 적용되어 있음을 알게 되었다.

🔍 기술 분석: 히트 카운팅 버킷 메커니즘

또한, 특정 조건문이나 루프 분기가 얼마나 많이 실행되었는지 기록하는 히트 카운팅 메커니즘도 매우 정교했다. 루프가 1번 실행될 때와 1000번 실행될 때는 메모리 오염 상태가 완전히 다르기 때문에 횟수 추적이 필수적이다. 그러나 100번 돌 때와 101번 돌 때처럼 미세한 차이를 모두 새로운 경로로 인정해 버리면 퍼저가 관리해야 할 시드 데이터가 기하급수적으로 폭발하게 된다.

이를 제어하기 위해 실행 횟수를 1, 2, 3, 4~7, 8~15, 16~31, 32~127, 128~255라는 8개의 거대한 버킷 영역으로 나누어 지도에 기록하는 이론을 취하고 있다. 루프의 실행 횟수가 늘어나 이 버킷의 경계를 넘어설 때만 “의미 있는 상태 변화가 일어났다”고 감시관이 판단하여 해당 입력값을 새 시드로 큐에 등록하게 된다.


5. 퍼저의 눈이 되어주는 핵심 기술: 인스트루멘테이션

처음에는 ‘계측’이라는 번역어가 잘 와닿지 않았는데, 공부해 보니 인스트루멘테이션은 눈이 멀어 있던 퍼저에게 ‘눈’을 달아주는 기술이었다.

그레이박스 퍼징이 똑똑하게 피드백 루프를 돌릴 수 있는 근본적인 이유가 바로 이 기술 덕분이다. 인스트루멘테이션이란 쉽게 말해 “프로그램이 실행되는 동안 내부에서 어느 코드 분기점이 실행되는지 추적하기 위해, 원래 코드 사이에 ‘CCTV 역할의 추적용 센서 코드’를 몰래 심는 행위”를 의미한다.

이 센서 코드를 언제, 어떻게 심느냐에 따라 퍼징 도구들의 기술적 아키텍처와 성격이 완전히 갈리게 된다.

A. 컴파일 타임 정적 계측

  • 메커니즘: 분석하려는 대상 프로그램의 소스코드가 확보된 상태에서 사용한다. 소스코드를 컴퓨터가 실행할 수 있는 파일로 빌드하는 과정에서, 컴파일러 툴이 개입하여 모든 분기점마다 추적 코드를 자동으로 쏙쏙 박아넣는다.
  • 특징: 파일이 만들어질 때부터 센서가 깔끔하게 내장되어 나오기 때문에, 실행할 때 버벅거림이 없고 속도가 압도적으로 빠르다. 대표적으로 소스가 있을 때의 AFL++LibFuzzer가 이 방식을 쓴다. 단, 소스코드가 없으면 이 방식을 쓸 수 없다.

B. 동적 바이너리 계측 (DBI)

  • 메커니즘: 소스코드가 전혀 없는 블랙박스 상태의 파일(.exe, .dll 등)을 대상으로 한다. 컴파일 단계에 개입할 수 없으므로, 프로그램이 실행되어 가상 메모리 위에서 돌아가고 있는 런타임 시점에 디버거 툴이나 에뮬레이터가 기계어 코드를 실시간으로 가로채서 추적 코드를 사이에 억지로 끼워 넣는다.
  • 특징: 소스코드가 없어도 윈도우나 맥의 폐쇄형 프로그램을 통째로 퍼징할 수 있어 범용성이 극도로 높다. 대표적으로 Jackalope가 이 방식을 사용한다. 하지만 실행 중에 실시간으로 코드를 뜯어고치며 감시해야 하므로 정적 계측에 비해 속도가 상대적으로 느리다는 트레이드오프가 존재한다.

6. 글로벌 핵심 퍼저 3대장 구조적 비교 분석

인스트루멘테이션 기술과 실행 모델의 차이를 이해하고 나니, 리서치의 출발점이었던 세 가지 저장소의 아키텍처적 포지션이 명확하게 연결되었다.

A. AFLplusplus (AFL++)

  • 저장소: AFLplusplus/AFLplusplus
  • 특징: 현재 취약점 연구원들이 가장 널리 애용하는 표준 그레이박스 퍼저다. 기존의 전설적인 퍼저인 AFL을 오픈소스 커뮤니티가 이어받아 대대적으로 업그레이드한 버전이다.
  • 계측 및 실행: 소스코드가 있을 경우 정적 계측 모드를 사용하지만, 소스코드가 없는 환경에서도 에뮬레이터 엔진(QEMU, Frida 등)을 연동하여 실시간으로 실행 경로를 추적해 내는 강력한 범용성을 보여준다. 기본적으로 프로세스 외부 포크서버 방식을 채택하여 안정성이 뛰어나다.

B. Jackalope

  • 저장소: googleprojectzero/Jackalope
  • 특징: 구글 프로젝트 제로(Google Project Zero) 팀에서 만든 블랙박스 전용 퍼저다. 리눅스 생태계와 달리 소스코드를 절대 구할 수 없는 Windows나 macOS 환경의 독점 상용 프로그램들을 분석할 때 매우 강력하다.
  • 계측 및 실행: 가벼운 디버거 기반 추적 도구인 TinyInst를 연동해서 쓴다. 소스코드가 없는 상태에서도 프로그램 내부의 가상 주소 실행 경로를 정밀하게 추적해 낸다. 디버거 형태로 타겟을 제어하므로 속도는 다소 제한적이지만, 소스 없는 Windows 상용 바이너리를 뚫어내는 데 특화되어 있다.

C. Google Fuzzing (LibFuzzer)

  • 저장소: google/fuzzing
  • 특징: 구글이 대규모 자동화 테스트 인프라를 운영하며 정립한 가이드이자 베이스라인이다. 컴파일러에 내장되어 작동하는 LibFuzzer의 활용에 초점을 맞추고 있다.
  • 계측 및 실행: 소스코드를 빌드할 때 플래그를 주입하여 컴파일러가 완전히 통제하는 정적 계측을 수행한다. 특히 프로세스 내부(In-process) 실행 구조를 채택하여, 타겟 파싱 코드가 퍼저 본체 메모리 내부에서 직접 돌기 때문에 프로세스를 새로 껐다 켜는 비용이 전혀 없어 초당 수만 번 이상의 경이적인 속도를 보장한다.

7. 영역별 특화 퍼저 라이브러리/라인업 기술 분석

리서치를 더 진행해 보니 이 셋 외에도 특화된 강력한 퍼저들이 많다는 것을 알게 되었다.

A. 사용자 애플리케이션 진단 영역: AFL++ vs Honggfuzz

  • AFL++: 포크서버 아키텍처와 다양한 플러그인 확장성에 무게 중심이 있다. 연구원이 원하는 대로 탐색 알고리즘을 개별 교체하며 실험적 취약점을 도출하기에 용이하다.
  • Honggfuzz: 구글에서 개발한 퍼저로, 멀티스레딩(컴퓨터의 모든 CPU 코어를 100% 활용) 자원 최적화에 특화되어 있다. 하나의 프로세스 하위에서 다수의 스레드가 동시에 타겟 메모리를 공략하므로 자원 효율성이 압도적이며, 소스 없는 바이너리 진단 시 하드웨어 트레이싱 기능을 복잡한 세팅 없이 즉시 연동해 낸다.

B. 운영체제 커널 진단 영역: Syzkaller vs Nyx

  • Syzkaller: 운영체제의 심장인 커널 계층의 논리적 규칙 및 문법 공격에 치중한다. 리눅스 및 안드로이드 커널의 인터페이스인 시스템 콜 구조를 이해하고, 유효성 있는 커널 명령 시퀀스를 지능적으로 변이시켜 커널 가상 메모리의 패닉을 유도한다.
  • Nyx: 최첨단 가상화 하이퍼바이저 기반 스냅숏 퍼저이다. 커널 퍼징의 최대 약점은 커널이 한 번 터지면 OS를 재부팅하느라 수십 초의 시간이 소모된다는 것인데, Nyx는 커널이 터지는 순간 터지기 딱 1밀리초 전의 깨끗한 메모리 상태로 시스템 가상머신을 통째로 되돌려 버리는 메커니즘을 수행하여 속도를 극한으로 가속한다.

C. 원격 및 네트워크 진단 영역: Boofuzz vs ffuf

  • Boofuzz: 네트워크 통신 프로토콜의 데이터 구조 파싱 결함을 추적한다. 세션과 패킷 문법 구조를 파이썬 코드로 구조화한 뒤, 통신 연결 상태를 유지하면서 패킷 내부 필드 데이터만 정교하게 변이시켜 IoT 장비나 네트워크 프로그램의 메모리 에러를 유도한다.
  • ffuf: 웹 인프라의 경로 및 파라미터 탐색에 특화되어 있다. Go 언어 기반의 고속 HTTP 통신 메커니즘을 사용하여 웹 서버에 사전 파일을 무차별 대입함으로써, 외부에 노출되지 않은 디렉터리, 환경설정 파일(.env), 관리자 주소 등의 진입점을 식별한다.

8. 퍼징 중 크래시가 발생하는 5단계 연쇄 과정

리서치 중 크래시에 대해 추가로 더 조사해보았다. "숫자 공간에 문자를 넣거나, 정해진 입력 크기를 넘겼을 때 프로그램이 터진다"고 하는 크래시의 구체적인 탐지 메커니즘이었다. 단순히 프로그램이 꺼지는 현상인 줄 알았으나, 실제로는 CPU 하드웨어와 운영체제(OS)가 유기적으로 엮인 5단계 연쇄 체포 과정이라는 것을 알게 되었다.

1단계: 변이 데이터 주입 (Mutator & Executor)

퍼저의 Mutator(변이기)가 시드 파일을 뒤틀어 위험한 데이터를 생성하면, Executor(실행기)가 이 입력값을 들고 대기 중이던 타겟 프로그램 복사본 프로세스(fork)에 강제로 밀어 넣으며 가동시킨다.

2단계: 가상 메모리 오염 발생 (Target Process)

타겟 프로그램이 데이터를 검증 없이 파싱하다가 메모리 결함을 범한다. 정해진 버퍼 크기를 넘겨 뒤쪽 메모리 영역과 리턴 주소(Return Address)를 쓰레기로 덮어씌우거나, 문자가 들어가면서 주소록 계산 오류를 일으켜 존재하지 않거나 권한이 없는 가상 주소를 가리키게 된다.

3단계: CPU 하드웨어의 예외(Exception) 감지

오염된 프로그램이 권한이 없는 잘못된 주소 영역을 읽거나 쓰려고 시도하는 즉시, CPU 내부의 MMU(메모리 관리 장치)가 칩 레벨에서 이를 실시간으로 적발한다. MMU는 시스템 전체의 붕괴를 막기 위해 해당 프로세스의 연산을 즉각 동결시키고, 커널에 하드웨어 예외 경보를 던진다.

4단계: OS 커널의 사형 선고 및 시그널 발송

CPU의 경보를 접수한 운영체제 커널은 선을 넘은 가상 프로세스를 처벌하기 위해 즉시 강제 종료 절차를 밟는다. 이때 커널은 프로세스의 숨통을 끊으며 "무슨 죄를 지어 처형당했는지" 공식 신호를 마크해 둔다. 가상 메모리 무단 침범죄에 해당하는 가장 대표적인 신호가 바로 번호 11번의 SIGSEGV (Segmentation Fault) 시그널이다.

5단계: 퍼저 감시관(Monitor)의 최종 증거 수집

타겟 프로그램을 자식 프로세스로 묶어두고 대기하던 퍼저의 Monitor(감시관)는 OS 커널 채널 무전기 (waitpid 시스템 콜)를 통해 자식이 SIGSEGV 시그널을 맞고 비명횡사하는 순간을 가로챈다. 감시관은 이 공식 종료 성적표를 보고 "크래시 성공"을 확정 지은 뒤, 방금 주입했던 변이 데이터를 crashes/ 폴더에 증거물로 안전하게 격리 저장한다.


9. 퍼징의 사각지대를 없애는 다양한 세니타이저(Sanitizer)

현대 소프트웨어 테스트와 보안 연구 진영에서는 퍼징의 탐지 누수를 막기 위해 다양한 세니타이저 기술을 적극적으로 활용한다는 것을 알게 되었다. 메모리 오염뿐만 아니라 논리적 예외 상황까지 잡아내는 이 보조 도구들은 각자 독특한 이론적 메커니즘을 가지고 작동하며, 타겟 프로그램의 성격과 사냥하려는 버그의 종류에 따라 다르게 선택된다.

A. AddressSanitizer (ASAN)

  • 주요 탐지 대상: Use-After-Free(UAF), 힙/스택 버퍼 오버플로우, Out-of-Bounds 접근.
  • 이론적 메커니즘: 메모리를 해제하는 즉시 할당기가 재사용하지 못하도록 격리소 큐(Quarantine Queue)에 강제로 가두어 버린다. 동시에 실제 가상 메모리 8바이트의 안전 상태를 나타내는 지도를 섀도우 메모리(Shadow Memory) 1바이트 공간에 1:1로 매핑하여 감시한다. 모든 메모리 참조 명령어 직전에 이 섀도우 지도를 확인하는 검사 로직을 자동으로 주입하여, 프로그램이 격리소에 있는 해제된 주소를 건드리는 순간 OS가 눈치채기 전에 프로그램을 강제로 자폭(SIGABRT 시그널)시킨다. 성능 오버헤드는 약 2배 수준으로, 탐지 능력 대비 가벼워 퍼징 실무에서 가장 기본으로 켜놓는 센서다.

B. MemorySanitizer (MSAN)

  • 주요 탐지 대상: 초기화되지 않은 메모리 읽기 (Uninitialized Memory Reads).
  • 이론적 메커니즘: 프로그램이 메모리를 새로 할당받으면 그 안에는 기존에 쓰이던 쓰레기 값(Garbage 값)이 채워져 있다. 개발자가 이 공간에 새로운 값을 채워 넣지 않은 채로 조건문이나 연산에 바로 사용하면 치명적인 논리 오류나 정보 유출이 발생한다. MSAN은 가상 메모리의 모든 비트가 올바르게 초기화 되었는지를 추적하는 그림자 비트를 만들어 감시하며, 초기화되지 않은 데이터를 읽으려고 시도하는 순간을 정확히 잡아내 크래시를 유도한다. 메모리 오버헤드가 매우 크지만, 하트블리드 같은 정보 유출형 버그를 잡아낼 때 강력하다.

C. ThreadSanitizer (TSAN)

  • 주요 탐지 대상: 데이터 레이스 (Data Races), 데드락 (Deadlocks).
  • 이론적 메커니즘: 멀티스레드 프로그램에서 두 개 이상의 스레드가 적절한 동기화(Mutex 등) 없이 동일한 메모리 주소에 동시에 접근하고, 그중 하나라도 쓰기 연산을 수행할 때 발생하는 데이터 레이스는 타이밍에 따라 결과가 달라져 일반 퍼징으로 잡기 불가능에 가깝다. TSAN은 프로그램 내부의 모든 스레드가 수행하는 메모리 접근 이력과 동기화 이벤트의 논리적 인과관계(Happens-before 관계)를 수학적으로 추적하여, 꼬여버린 타이밍의 동시성 버그들을 적발한다. 연산량이 많아 속도가 크게 저하되므로 멀티스레드 기반 서버 프로그램을 타겟팅할 때 제한적으로 활용된다.

D. UndefinedBehaviorSanitizer (UBSAN)

  • 주요 탐지 대상: 부호 있는 정수 오버플로우, 잘못된 비트 시프트 연산, 잘못된 포인터 정렬.
  • 이론적 메커니즘: C/C++ 언어 표준 명세서에서 명확하게 “어떻게 작동할지 정의되지 않았다”고 규정한 행동들을 잡아낸다. 예를 들어 부호 있는 정수의 최대 범위를 넘겨버리는 연산 등이 발생하면 컴파일러가 이를 마음대로 왜곡하여 최적화하기 때문에 기괴한 보안 취약점으로 이어진다. UBSAN은 빌드 타임에 표준을 위반하는 수학적 연산 흐름이 발생하는지 감시하는 정교한 체크 장치들을 코드 사이에 삽입하여 탐지해 낸다. 다른 세니타이저들과 달리 성능 하락이 거의 없어 상시 가동하기 좋다.

E. LeakSanitizer (LSAN)

  • 주요 탐지 대상: 메모리 누수 (Memory Leaks).
  • 이론적 메커니즘: 프로그램이 메모리를 할당받아 놓고 프로세스가 종료되거나 함수가 끝날 때까지 해제하지 않아 메모리가 질질 새는 현상을 포착한다. 프로그램이 끝나는 시점에 가상 메모리 힙 공간 전체를 스캔하여, 현재 프로그램 내부의 그 어떤 포인터로도 가리키고 있지 않아 미아가 되어버린 메모리 블록을 찾아내 보고서를 남긴다. 통상 ASAN 내부에 기본 탑재되어 함께 작동하는 경우가 많다.

💡 리서치를 통해 알게 된 세니타이저 운용 팁 세니타이저들을 무조건 다 켜고 퍼징을 돌리면 좋을 것 같지만, 내부 메커니즘이 서로 충돌하는 경우가 많다는 사실을 알게 되었다. 따라서 버그 타겟에 따라 ASAN + UBSAN 조합을 먼저 돌려보고, 스레드나 동시성 문제를 볼 때는 TSAN 단독 빌드로 나누어 퍼징 캠페인을 설계해야 효율적이라는 것을 알게 되었다.


10. 퍼징의 보이지 않는 벽: 찾을 수 없는 취약점 유형

퍼징과 크래시의 인과관계를 깊이 리서치하면서 깨달은 가장 큰 한계점은, 퍼저가 철저하게 “프로그램을 물리적으로 터뜨릴 수 있는 가상 메모리 오염 위협”에만 스펙트럼이 맞춰져 있다는 사실이었다. 세니타이저를 동원하더라도 프로그램의 가상 메모리 규칙이나 연산 명세가 깨지지 않아 시스템이 아무런 에러 없이 평온하고 정상적으로 연산을 완료(200 OK)해버리는 다음과 같은 ‘사각지대 취약점’들은 일반 퍼징으로 탐지하기가 원천적으로 불가능하다.

A. 비즈니스 로직 취약점

설계 자체의 논리적 오류다. 쇼핑몰 결제창에서 상품 수량을 음수 값(-1)으로 조작해 넣었더니 결제 총액이 마이너스가 되며 계좌로 돈이 입금되는 버그가 있다면, 프로그램 입장에서는 주어진 음수 값을 가지고 정상적으로 사칙연산을 수행한 뒤 에러 없이 안전하게 프로세스를 종료한다. 크래시가 나지 않으므로 퍼저는 이 치명적인 보안 결함을 인지하지 못한다.

B. 인증 및 권한 우회

계정 보안의 허점이다. 로그인하지 않은 세션 상태에서 관리자 전용 URI 주소(/admin/user_delete)를 직접 주소창에 입력했더니 검증 검사 로직이 누락되어 회원 삭제 명령이 정상 수행되는 경우(IDOR 등)를 말한다. 시스템은 요청에 따라 지극히 정상적으로 데이터베이스를 조회하고 삭제 완료 화면을 띄우므로 퍼저는 이를 완벽한 정상 실행으로 간주한다.

C. 암호학적 결함

암호화 키를 소스코드에 그대로 하드코딩해 두었거나, 이미 수학적으로 깨진 취약한 알고리즘(MD5, SHA-1)을 사용하여 비밀번호를 해싱하는 경우다. 컴퓨터 입장에서는 주어진 암호화 함수를 돌려 아주 정상적인 연산 결과를 도출해 낸다. 수학적으로 암호가 복호화되기 쉬운 상태라는 것을 퍼저가 스스로 눈치챌 방법은 없다.

D. 순서가 중요한 ‘상태 기반 취약점’

AFL++ 같은 일반적인 퍼저들은 데이터를 던지고 프로그램이 켜졌다가 꺼지면 상황이 리셋되는 단발성 입력 구조다. 하지만 실제 시스템은 [회원가입] ➡️ [이메일 인증] ➡️ [프로필 변경] ➡️ [관리자 권한 획득]처럼 정해진 시퀀스 단계를 밟아야 취약한 상태 공간에 도달한다. 퍼저는 뜬금없는 단발성 데이터를 던지기 때문에 앞 단계 조건문을 만족하지 못해 깊은 방까지 진입조차 하지 못한다.

E. 터지지 않는 ‘주입형 취약점’

SQL Injection, Command Injection 등 웹에서 흔한 주입형 공격도 사각지대다. 퍼저의 변이로 인해 입력창에 악의적인 시스템 명령어(; rm -rf /) 문자열이 채워졌다고 하더라도, 시스템 입장에서는 이 명령어를 에러 없이 ‘아주 올바르게(?) 실행 완료’해버린다. 가상 메모리 상에서 세그폴트가 발생하는 것이 아니기 때문에 퍼저는 정상 실행으로 판단하고 지나친다.

F. 자원을 고갈시키는 ‘알고리즘 복잡도 공격’

특정 정규표현식 파서에 꼬인 문자열을 넣으면 내부 연산량이 기하급수적으로 폭증하며 CPU 점유율이 100%로 치솟아 무한 루프에 빠지는 현상이 있다. 프로그램이 메모리 규칙을 위반한 게 아니라 그저 열심히 연산을 수행하고 있는 상태이므로 시그널이 발생하지 않고, 퍼저는 이를 단순 타임아웃으로 처리해 버려 심각한 시스템 마비 취약점임을 인지하지 못한다.

G. 하드웨어 결함 및 부채널 공격

CPU의 하드웨어적 구조를 노리는 멜트다운, 스펙터 취약점이나 미세한 연산 시간의 차이를 분석해 키를 알아내는 타이밍 공격 등이 여기에 해당한다. 퍼저는 어디까지나 소프트웨어 코드의 메모리 경계선 및 실행 경로만 감시하므로 CPU 내부의 캐시 메모리 상태 변화 같은 물리적인 영역은 원천적으로 탐지할 수 없다.

정리: 퍼징의 공격 스펙트럼 한계

  • 퍼징이 최강인 영역: 버퍼 오버플로우, UAF(Use-After-Free), 정수 오버플로우, DoS(시스템 마비) 유발 버그 등 “프로그램을 물리적으로 터뜨릴 수 있는 가상 메모리 오염 위협”
  • 퍼징이 전멸하는 영역: 권한 상승, 로직 우회, 암호 키 노출, 데이터 위변조 등 “프로그램은 정상 구동되지만 논리적 뼈대가 잘못된 구조적 위협”

11. 결론 및 요약

이번 리서치와 개념 정리를 통해 복잡해 보이던 퍼징 생태계의 큰 틀을 제대로 잡을 수 있었다. 퍼징은 소스코드의 논리 오류나 웹 권한 검증 단계를 잡기엔 부적합하므로, “파일 헤더나 복잡한 바이트 데이터를 잘게 쪼개어 파싱하는 Parser(파서) 프로그램”을 공략할 때 최고의 효율을 발휘한다는 본질을 확실히 깨달았다.

최종적으로 타겟 시스템의 제약 환경에 맞춰 도구를 선정하는 매칭 공식을 요약하며 포스팅을 마친다.

  • 소스코드 가용 환경 (C/C++): 컴파일러 기반의 고속 내장형 퍼저인 LibFuzzer 적용.
  • 리눅스 기반 범용 프로그램: 유연한 플러그인 생태계를 갖춘 그레이박스 표준인 AFL++ 가동.
  • 소스 없는 Windows/macOS 독점 프로그램: TinyInst DBI 기술을 바인딩한 Jackalope 투입.
  • OS 커널 계층 침투: 시스템 콜 문법 분석 기반의 Syzkaller와 가상화 스냅숏 엔진인 Nyx 결합 운영.
  • 네트워크 프로토콜 및 웹 타겟: Boofuzzffuf를 통한 원격 진입점 차단 및 필드 변이 주입.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.