오늘 리뷰해볼 논문은 Large Language Model guided Protocol Fuzzing (ChatAFL)입니다.
해당 논문은 아래의 링크에서 확인 가능합니다.
https://www.ndss-symposium.org/ndss-paper/large-language-model-guided-protocol-fuzzing/
Large Language Model guided Protocol Fuzzing - NDSS Symposium
Ruijie Meng (National University of Singapore, Singapore), Martin Mirchev (National University of Singapore), Marcel Böhme (MPI-SP, Germany and Monash University, Australia), Abhik Roychoudhury (National University of Singapore) How to find security flaws
www.ndss-symposium.org
자세한 출처 표기는 다음과 같습니다.
Meng, R., Mirchev, M., Böhme, M., & Roychoudhury, A. (2024).
CHATAFL: Large Language Model guided Chain-of-Thought Fuzzing for Protocol Implementations.
Network and Distributed System Security Symposium (NDSS).
이전까지의 포스팅에서 저희가 다양한 Fuzzing 도구들에 대해 살펴보았습니다.
해당 논문도 약 15p정도의 논문입니다

해당 논문은 Fuzzing과 Ai를 결합한 논문이고, 최초의 LLM활용 모델은 아니지만, 가장 효율적인 퍼징에 성공한 연구입니다.
CHATAFL은 현재 오픈소스로 깃허브에서 다운로드 받을 수 있습니다.
배경지식
먼저 ChatAFL이 나오게 된 배경입니다. 네트워크 프로토콜(RTSP, FTP)는 보안 취약점이 많고, 취약점이 매우 큰 risk로 이어지기 때문에 점검이 필수적입니다. 하지만 네트워크 프로토콜들은 순서와 형식이 중요하고, 그에 따라 실행하면서 상태를 추측하는게 매우 큰 어려움으로 다가왔습니다.
예를들어 RTSP의 PLAY 요청에 대해 보면서 기존 퍼징의 한계에 대해 확인해보겠습니다.
제일 먼저 초기 입력 의존성입니다.
기존의 Mutation-based Fuzzing 도구들은 Seed라는 초기 입력 데이터를 기반으로 작동합니다.
RTSP프로토콜 점검을 위해 개발자가 SETUP -> PLAY -> TEARDOWN 순서로 이어지는 기본적인 시드 파일이 넣었다고 가정해보면, 이 세부 PLAY안에서 PLAY를 PL@Y,PRAY등으로 바꾸는 시도는 기존 퍼저거 가능하지만, 시드에 포함되지 않은 PUASE나 RECORD같은 요청을 생성할 수 없습니다.

두번째로 구조화된 입력입니다.
위의 그림의 (a)는 RTSP의 구조를 나타내고, (b)는 실제 예시를 보여줍니다.
만약에 주황색으로 써진 PLAY ~~ RTSP/1.0\r\a중 한글자라도 달라지면 Invalid input이기 때문에 새로운 상태로의 변이가 불가능합니다.
하지만 일반적인 퍼저는 비트를 무작위로 뒤집으면서 mutation을 만들어내기 때문에 이러한 요청이 거절당할 확률이 높고, 새로운 상태로의 전이를 유발하기가 힘듭니다.

세번째 문제는 상태 의존성입니다.
단순히 구조에 맞춰서 보낸다고 프로토콜상에서 올바른 입력으로 처리하지 않습니다. 프로그램의 현재 상태에 따른 정확한 입력을 보내야합니다.
현재 INIT일 경우 규격에 맞은 PLAY 요청을 보낸다고 해도, 프로토콜의 상태에 맞지 않은 입력이기 때문에 Invalid input이 됩니다.
기존의 퍼저들은 현재의 상태에 기반한 fuzzing을 하지 않기 때문에 우연히 퍼징해서 새로운 state를 찾아냈다고 하더라도, 다시 그 state까지 도달하기 위한 입력값들을 정확하게 찾아낼 수 없습니다.
위의 3가지 문제를 각각 C1,C2,C3로 정의하고, CHATAFL이 해당 문제들을 어떻게 해결하는지 확인하겠습니다
- C1) 시드 의존성 (Dependence on initial seeds)
- C2) 알려지지 않은 메세지 구조 (Unknown message structure)
- C3) 알려지지 않은 상태 공간 (Unknown state space)
LLM guided fuzzing
위의 문제들을 해결하기 위한 전략이 LLM입니다.
LLM은 Large Language Model로 매우 많은 량의 텍스트 데이터를 학습한 인공지능 모델입니다.
LLM을 이용하여 어떻게 3가지 문제를 풀어나가는지 확인해보겠습니다.
C1) 시드 의존성
아까 포스팅에 작성하였듯이 , 기존 퍼징 방식은 초기 시드에서의 변형만 가능합니다. 시드를 조금 더 풍부하게 만들기 위해 LLM에게 초기 시드에 메세지를 추가로 전송하게 하였습니다.
예를 들어, 기존의 시드가 SETUP -> PLAY -> TEARDOWN이었다면, LLM을 통해 SETUP과 PLAY사이에 PAUSE를 추가하는 방식으로 시드를 다양하게 만들었습니다. 이건 단순히 랜덤으로 추가하는건 아닌 LLM에 현재 시퀀스와 서버의 응답, 그리고 앞서 추출한 문법을 보냅니다. 이를테면 다음과 같은 프롬프트를 보냅니다.
[RTSP 통신 로그]
위의 내용은 RTSP 통신 로그야.
문법에 맞는 메세지 중 아직 이 시퀀스에 없는 새로운 타입을 골라서 위치에 끼워넣어줘
그럼 LLM에 문맥을 분석하고 SETUP -> PLAY -> PAUSE(새로추가) -> TEARDOWN
식으로 적절한 위치에 문법을 끼워넣습니다.
해당 작업을 100번 반복한 뒤, 통계를 내었습니다.
| 상태 | Accepted | Unsupported | Session Mismatch |
| 값 | 55.1 | 20.4 | 24.5 |
여기서 성공률이 생각보다 낮게 나왔는데, 여기서 Unsupported에 해당하는 내용엔 LLM이 생성한 문법이 정확하지만, test를 진행한 프로세스에서 지원하지 않는 문법이었기 때문에, 실제로 유효하지 않은 입력을 생성한 것은 아니었고, session Mismatch는 RTSP가 현재 사용자의 Session ID와 같은 동적 값을 알지 못하기 때문에 발생하였습니다.
해당 Session mismatch는 LLM에게 질문할 프롬프트에 이전 단계의 응답과 사용자의 정보를 포함하게 함으로써 해결 가능합니다.
결과적으로, LLM을 통해 Seed에 더 풍부한 값들을 넣을 수 있고, C1을 해결 가능합니다.
c2) 알려지지 않은 메세지 구조
네트워크 프로토콜의 규격은 IETF라는 표준화 기구에 정의되어 있는데 순수 자연어로만 작성된 문서입니다.
아까 예를 들었던 RTSP를 보면 RFC 2326에 정의되어 있고 약 90~100페이지이고, HTTP의 경우 RFC2616에 약 180페이지 정도의 분량으로 정의되어 있습니다.
또한 해당 프로토콜들은 상호 의존성이 있기 때문에 해당 개념들을 새로운 ai 에이전트에 대해 학습을 시키려면 수천 페이지 이상을
학습 시켜야 하기 때문에 시간이 매우 많이 들고 비효율적입니다.
하지만 LLM의 경우 이미 공개된 RFC 문서에 대해 전부 학습을 했기 때문에 위의 제약들에 전혀 영향을 받지 않습니다.
여기서 사전 case study의 결과가 있는데, RTSP 프로토콜을 대상으로 프롬프트를 50번씩 던져 문법을 추출하게 했씁니다.
RTSP의 명령어 요청은 총 10가지가 있는데,

결과를 보면 50번중 약 90%이상의 문법 적중률을 보여주었습니다. 결과에 보면 SET_DESCRIPTION과 같은 이상한 타입을 대답할 때도 있었지만 2%의 극소수이기 때문에 LLM이 문법 변이 가이드로 쓰기에 충분하다는 case study가 있었습니다.

저번에 로컬로 설치한 ollama3에 RTSP PLAY 요청의 문법을 물어봤는데 잘 알려주고 있습니다. 아마 다른 요청들에 대해서도 정확하게 수행을 할 것으로 예상을 할 수 있습니다.
LLM의 응답에 기반한 문법으로 fuzzing문법을 생성한다면, 아까 C2의 알려지지 않은 메세지 구조에 대한 문제를 해결할 수 있습니다.
C3)알려지지 않은 상태 공간
LLM이 현재 상태를 이해하고, 다음 상태로의 전이를 발생시킬 수 있는지 테스트해보았습니다.
INIT, READY, PLAY, RECORD 등 각 상태에 도달한 대화 로그를 LLM에게 보여주고, 상태를 바꾸려면 다음에 뭘 보내야 할지 프롬프트를 작성해 100번씩 물어봤습니다.

결과적으로 69~89%의 매우 높은 전이 성공률을 보여줬고, 10~30%의 상태를 전이하진 못했지만 문법적으로 유효한 메세지를 보냈습니다.
해당 결과는 LLM이 현재 상태를 정확하게 파악 가능하고, 매우 높은 전이 성공률을 보여준다는걸 입증합니다.
이제 상세한 LLM guided fuzzing에 대한 구현을 확인해보겠습니다.
LLM-Guided Protocol Fuzzing
CHATAFL은 기존의 상태기반퍼징 도구인 AFLNET에 LLM(논문에서는 chatgpt 3.5-turbo 모델)을 결합한 도구입니다.
여기서 핵심적인 부분은 모든 응답과 상태를 LLM에게 물어보는 것이 아닌 PlateauLen이라는 상태변이시도횟수를 저장한 변수를 만들고, 특정 횟수이상 시도를 해도 상태 전이가 안되는 상태에서만 LLM을 사용하는 것입니다.

아주 상세한 알고리즘 설명은 접어두고, 간단하게만 살펴보겠습다.
먼저 진행할 state를 가져오고 메세지 시퀀스를 가져옵니다.(M)
현재 가져온 메세지 시퀀스(M)을 3부분으로 나눕니다(M1,M2,M3) 여기서 M1은 해당 상태까지 도달하기 위한 앞부분이고, M2가 이번에 변조할 타겟 메시지, M3는 뒷부분입니다
대부분의 경우 M2의 값에 문법 기반 변이를 진행하고, 가끔씩 무작위 변이도 수행합니다.
이때 Plateau >= MaxPlateau 일 경우엔, 새로운 coverage가 안나오는 경우이기 때문에 LLM을 호출하여 현재 상황에서 전이를 위한 문법을 요청합니다.
이때 Crash/Interesting/Nothing에 따라 적절한 입력을 취합니다.
여기서 여러가지 잡기술(?)이 들어있는데, 먼저 LLM의 입력을 파싱하기가 더 편하도록, 프롬프트 안에 "JSON의 결과만 보내줘"와 같이 결과만 담을 수 있도록 프롬프트를 입력하는 것입니다.
쉽게 이해하기 위해 LLM에 직접 두가지를 질문해보겠습니다.
- RTSP의 Play Request를 보내기 위한 문법 알려줘
- RTSP의 Play Request를 보내기 위한 JSON 결과만 보내줘
의 두가지 질문을 보냈습니다.


제가 사용한 llama3.1은 성능이 굉장히 구린 로컬 LLM이라 좀 정확하진 않은데, , gpt같은 최신 모델에다 물어보면 정확한 문법 구조를 입력해줄 것입니다.
왼쪽 사진은 LLM의 답장에 자연어가 많이 포함되어 있어서 그대로 파싱했을때, 프로그램의 input으로 넣을수가 없는데, 두번째처럼 결과만 보내줘 같은 프롬프트튜닝을 하면, 파로 입력으로 사용이 가능합니다.
두번째로는 문법 기반 변이입니다.
처음에 LLM을 이용하여 추출해서 저장한 문법 파일(G)을 퍼저가 가지고 있다가, 변이할 때 꺼내서 사용합니다.

이때 PLAY, CSeq와 같은 헤더 이름이나 키워드는 바뀌지 않도록 설정한 뒤 , 오직 <Value>에 해당하는 부분만 변경하도록 설정하여, 문법 기반으로만 변이가 일어나도록 합니다.
세번째로, 프롬프트에 문법 예시를 2개정도 같이 포함해서 보냄으로써 LLM이 답변을 할 때 더 정확한 답변을 유도하는데, 이걸 In-context Few shot learning이라고 합니다.
네번째로, 환각 증상과 가끔씩 나오는 부적절한 답변을 막기 위해, 여러번 물어 본 뒤 다수결의 답장을 채택합니다.
위의 과정들을 통해 더 정확하고 효율적인 LLM guided fuzzing을 구현할 수 있습니다.
논문에는 각각의 C1, C2, C3를 해결하기 위해 더 자세한 각각의 구현에 대해 설명되어 있지만 이정도로만 리뷰하고 결론으로 넘어가겠습니다.
결론
위의 CHATAFL이 얼마나 효율적인지의 실험 결과입니다. 아까의 C1,C2,C3에 더해 실제 리얼월드에서 얼마나 유효한지 탐색하기 위해 제로데이 취약점을 탐색했다고 합니다.
Live555, ProFTPD,PureFTPD,Kamailio,Exim,Froked-daapd라는 총 6개의 대상(네트워크 프로토콜의 오픈소스 구현체)으로
- State coverage
- Code coverage
- New bugs
의 측면에서 실험했고, 그 결과
- State Transition : 평균적으로 48% 더 많은 경로를 탐색했고, Live555(RTSP)에서는 91% 더 많은 상태 탐색, 같은 양의 길을 찾는데 48배 빠르게 찾음
- State coverage : 프로그램이 갈 수 있는 거의 모든 상태를 정복, 모든 상태 중 거의 90%상태를 찾았고 AFLNET과 NSFUZZ에 비해 각각 29%, 25% 더 많은 상태를 찾음
- Code coverage : 다른 도구들보다 약 6~7%의 코드 분기를 실행하였고, 같은 양의 코드 분기를 실행하는데 6배(vs AFLNET), 10배(vs NSFUZZ) 빠름
- New bugs : 약 9개의 제로데이 취약점을 찾았고, 그 중 7개는 RCE가 가능한 매우 높은 등급의 취약점이고 CVE를 지급 받음
기존 도구들과 비교했을 때 굉장히 유의미한 결과를 획득했습니다.
나의 생각 ..
여기서 제 생각을 조금 더 적어보자면, 저도 예전에 취약점을 탐색하면서 LLM을 이용한 퍼징을 어떻게 구현할지 생각을 해봤는데, 제가 생각했던 방식은 매번 LLM에게 요청을 하면서 서버에 상태전이를 계속 발생시키는 방식이었습니다.
근데 해당 방식은 너무 느리고, gpt와 같은 최신 LLM에 api로 요청을 할 경우 너무 많은 비용이 들어가고, 로컬 LLM을 사용하게 되면 성능이 너무 많이 떨어져서 효율적이지 않습니다.
이러한 문제를 seed enrich와 PlateauLen을 통해 효율성을 매우 높이는 접근이 굉장히 좋은 아이디어인듯 싶습니다.
또한 이러한 문법 규칙들에 대한 학습을 시키는게 좀 까다로울 것 같은데, 이걸 LLM에 api로 이용하는것도 효율을 굉장히 높이는 방법인 것 같습니다.
외에도 few shot learning이나 LLM의 답변을 다듬는 등 굉장히 디테일적으로 훌륭한 방법들이 많이 들어간 것 같고, 논문 자체의 가시성도 좋아서 읽기도 편하고 이해가 잘 됐던 것 같습니다..!
이 논문의 실험들이 23년도에 했던 내용들인데 로컬 LLM을 써보니까 너무 성능이 안좋고 답답한데, 이 논문이 나왔을 당시에 사용했던 gpt 3.5-turbo도 현재 사용되는 LLM의 성능에 비하면 많이 떨어지다보니, 현재 최신의 LLM모델을 사용하면 실험결과가 얼마나 좋아질지도 궁금해지는 것 같습니다..
일단 오늘 논문 리뷰는 이것으로 마치겠습니다.
읽어주셔서 감사합니다~
