AI 사이버 챌린지와 티오리의 RoboDuck

DARPA AIxCC 도전을 위해 개발된 티오리의 CRS ‘RoboDuck’. 퍼징 대신 LLM으로 버그를 찾고 POV를 생성하는 새로운 방식의 시스템 설계를 살펴봅니다.
Xint's avatar
Aug 26, 2025
AI 사이버 챌린지와 티오리의 RoboDuck

💡

이 글은 영어로 작성된 원문 블로그 글을 한국어로 번역한 것입니다. 일부 표현은 한국어 독자에게 자연스럽게 전달되도록 다듬었습니다.

이번 글에서는 DARPA가 주최한 AI 사이버 챌린지(AI Cyber Challenge, 이하 AIxCC)를 위해 티오리가 개발한 사이버 추론 시스템(Cyber Reasoning System, 이하 CRS) RoboDuck의 설계와 접근 방식을 간략히 소개합니다. 이 시스템은 퍼징이나 기호 실행 같은 기존 기법을 사용하지 않고도 POV(Proof of Vulnerability)를 생성할 수 있다는 점에서 차별성을 갖습니다. LLM 에이전트에 대한 세부 내용은 다음 글에서 확인하실 수 있습니다.

AI 사이버 챌린지란?

AIxCC에서 CRS는 oss-fuzz에서 가져온 대규모 C 또는 자바 코드를 받아 패치와 POV를 생성해야 합니다. 도전 과제는 full mode이거나 delta mode일 수 있습니다. full mode에서 CRS는 저장소 안에서 일으킬 수 있는 모든 버그를 찾아야 하고, delta mode에서는 하나의 diff 안에서만 버그를 찾으면 됩니다. 또한 CRS는 SARIF 형식의 정적 분석 보고서를 받아 이를 검증하고 결과를 제출해야 합니다.

채점 방식은 조금 복잡하며 공식 규칙 문서에서 확인할 수 있습니다. 크게 보면, SARIF 평가는 1점, POV는 2점, 패치는 6점입니다. 시간이 지남에 따라 얻을 수 있는 점수는 기본 점수의 100%에서 50%까지 줄어듭니다. 또한 잘못됐거나 중복된 답을 제출할 때마다 줄어드는 정확도 점수가 점수에 곱해집니다.

모든 CRS는 애저 클라우드에서 실행되어야 하고 terraform이나 make 명령 하나로 배포할 수 있어야 하며 배포된 뒤에는 사람의 도움 없이 동작해야 합니다. 일반적인 인터넷에는 접근할 수 없지만, LLM API에는 접근할 수 있습니다. 또한 CRS는 주어진 예산 안에서 동작해야 하지만, 사용한 예산이 채점에 반영되지는 않습니다.

접근 방식

처음부터 티오리 팀은 “LLM 우선” 접근 방식을 선택했습니다. 티오리 팀은 퍼징이나 다른 표준적인 기법에 대한 많은 경험을 가지고 있지만 (DARPA가 주최한 Cyber Grand Challenge에서 우승한 Mayhem 팀에 참가한 멤버가 티오리 팀에 있습니다), 티오리의 설계는 챌린지의 모든 측면에서 LLM을 사용하는데 중점을 두었고, 퍼징과 정적 분석 기법은 백업으로 사용 가능하도록 했습니다. 이러한 선택에는 여러 이유가 있었습니다. 가장 신나고 재미있는 방법이었고, LLM이 개선됨에 따라 이러한 시스템은 공짜로 이득을 보게 되며, 2023년 AIxCC가 발표되었을 때도 LLM 에이전트는 이미 보안 작업에서 큰 가능성을 보여주고 있었습니다. 반면 어떤 팀들은 더 신중한 접근 방식을 취했습니다. LLM을 사용하여 퍼징과 같은 기존 기법을 돕는 입력이나 문법을 생성하는 것입니다.

패치에 대해서는 대부분의 팀들이 LLM 또는 LLM 에이전트를 써서 시스템이 찾은 버그를 고치는 소스 코드를 작성하는 크게 보아 비슷한 방법을 선택했습니다.

링크에 티오리 시스템 설계의 대략을 보여주는 도표가 있습니다.

버그 찾기

시스템이 해야 하는 첫 번째 단계는 버그 찾기입니다. 이를 위해 정적 분석과 퍼징을 합니다. 정적 분석은 전통적인 방법과 (fbinfer를 사용했습니다) 두 가지 LLM을 사용한 방법을 실행합니다.

Infer 정적 분석

저희는 Infer가 여러 언어를 지원하고 버그를 찾기 위해 프로젝트마다 별도의 규칙을 작성할 필요가 없기 때문에 Infer를 선택했습니다. 함수간 값 분석을 수행함으로써 Infer는 null 역참조, 범위 초과, 오버플로우와 같은 버그를 발견할 수 있습니다. 실제로는 많은 문제가 있습니다. 우선, Infer가 소스 코드를 잘 이해하려면 컴파일 과정을 가로채야 합니다. C의 경우 충분히 간단하지만, Java의 경우 결국 저희 CRS 제출물에는 포함되지 않았습니다.

다음 문제는 Infer가 작업이 안전하다는 것을 증명할 수 없을 때 보고서를 생성한다는 점입니다. 이는 훌륭한 기능이지만 Infer가 특정 작업이 안전하다는 것을 증명하지 못하면 많은 양의 오탐이 발생합니다. 또한 대부분의 실제 코드처럼 Infer 자체에도 버그가 있기 때문에 버그를 놓칠 수 있습니다. 특히 많이 쓰이지 않는 것으로 보이는 오버플로우 검사에 버그가 있어 티오리 CRS에서는 버그를 수정했습니다.

Infer는 가치 있는 도구였지만 저희 시스템에서 사용된 대로는 약 99.9%의 오탐률을 보였습니다.

LLM 정적 분석

저희 CRS에서는 전체의 일부분에 불과하지만, LLM 정적 분석은 깊고 흥미로운 분야입니다. 전체적인 개념은 간단합니다. LLM에게 코드를 주고 버그를 찾으라고 하는 것입니다. 코드의 크기 때문에 LLM 에이전트와 같은 더 복잡한 방법으로는 할 수 없고, 한 번의 LLM 완성으로 해야 합니다.

그래도 여러가지 문제가 남습니다. 프롬프트, 컨텍스트, 출력 양식 등등. CRS에서는 두 가지 방법을 사용합니다. “단일 함수” 방법에서는 LLM에게 한 번에 추가적인 컨텍스트 없이 한 함수만 주고 분석을 요청합니다. 큰 코드 모드에서는 유용한 함수간 분석이 가능하도록 연관된 코드를 조심스럽게 묶어서 파일 여러개 분량의 코드를 줍니다.

분량 상의 문제로 세부 사항을 이 블로그 글에서 다 다룰 수는 없고 더 자세한 내용은 소스를 읽기를 권장합니다. 흥미롭게도 LLM 기반 정적 분석은 전통적인 정적 분석이 놓친 버그를 여럿 찾아냈습니다. 물론 대부분의 정적 분석 도구처럼 LLM 기반 정적 분석도 사용된 모델과 작동 방법에 따라 여전히 높은 오탐율을 보입니다.

diff가 주어지면 훨씬 간단합니다. LLM 에이전트에게 diff를 주고 변경으로 인해 발생한 버그를 찾으라고 합니다. 컴파일 과정 분석에 따라 사용되지 않는 코드를 제거한 diff와 원본 diff를 준 두 에이전트를 병렬로 실행합니다. 이 분석은 범위가 대폭 줄어들어 훨씬 적은 오탐을 보입니다.

퍼징

퍼징은 단순하게 했습니다. 프로젝트가 제공하는 libfuzzer 하니스를 써서 기본적인 퍼징을 구현합니다. 많은 프로젝트들이 공통된 데이터 포맷을 사용하므로, 대회 전에 미리 받아둔 퍼징 코퍼스와 하니스를 맞추려고 시도합니다.

더 이상 앞으로 나가지 못하고 있는 퍼저를 돕기 위해 LLM 기반 에이전트가 커버리지 데이터를 사용하여 아직 도달하지 못한 큰 코드 영역을 찾아서 입력을 만들려고 시도합니다. 커버리지를 포화시킨 시드로 시작한 코퍼스를 사용해도, 이 에이전트는 보통 새로운 커버리지를 보여주는 시드를 만들어 낼 수 있습니다. 추가로, POV 생성기가 만들어내는 출력이 퍼저에 시드로 추가됩니다. 이 시드로부터 퍼저가 POV 생성기보다 더 빨리 크래시에 도달하는 경우도 있었습니다!

정적 분석 결과와 달리 퍼징 결과는 버그 보고서 양식으로 되어 있지 않기 때문에, 퍼저가 찾은 크래시는 분류를 해서 자연어 설명을 작성해야 합니다. 먼저 스택 해시에 따라 크래시를 분류하고, 패치에 따라 분류하고, 마지막으로 LLM 에이전트가 크래시에 대한 버그 보고서를 작성해서 기존 취약점과 같은지를 맞춰 보게 됩니다. 

버그 거르기

정적 분석이 생성하는 많은 수의 오탐을 고려할 때, 버그를 패치하거나 버그를 일으키려고 하기 전에 이를 걸러내는 것이 꼭 필요합니다. 이는 매우 “실제적인” 문제입니다. 많은 사람들이 숨겨진 적은 수의 가치 있는 결과를 찾기 위해 수천 또는 수만개의 정적 분석 보고서를 골라내는 고통을 겪고 있습니다.

이 부분은 취약점 점수 매기기와 에이전트 기반 분석으로 구성되어 있습니다.

취약점 점수 매기기

이 부분도 겉보기와 달리 복잡하고 중요한 부분입니다. 핵심적으로는 LLM에게 버그가 진짜일 가능성이 높은지 낮은지 단일 토큰을 출력하도록 하는 분류기입니다. 0이나 1을 출력하게 하는 대신, 토큰의 로그 확률을 사용하고, 비결정성을 극복하기 위해 여러번 샘플링을 시도합니다. 이 단계의 출력은 0에서 1까지의 점수입니다. 이 점수는 다음 단계의 분석에서 어떤 버그부터 처리할지 우선순위를 정하는데 사용할 수 있습니다.

에이전트 기반 분석

취약점을 더 분석하기 위해 LLM 에이전트를 사용합니다. 이 에이전트에게는 소스 코드를 탐색할 수 있는 도구가 주어지며 에이전트가 해야 할 일은 보고서가 유효한지 판단하고 유효하다면 추가 정보를 제공하는 것입니다. 취약점 점수 매기기는 보고서당 약 0.001 달러의 비용이 드는 반면, 에이전트 기반 분석은 0.5 달러 정도가 듭니다. 수만개의 취약점 보고서가 있을 수 있기 때문에, 예산 한도 내에서 상위 20% 의 보고서만 분석합니다. 이러한 2단계 방식을 통해 비용을 제어하고 조정하기 쉬운 방식으로 오탐과 미탐의 균형을 잡을 수 있습니다.

POV 만들기

찾은 버그들 중 두 단계의 거르기를 통과한 버그들은 POV 생성기로 보내집니다. POV 생성기는 또다른 LLM 에이전트로, 이 에이전트가 해야 할 일은 주어진 버그를 일어나게 하는 입력을 만들어 내는 것입니다.

입력 인코더

모든 POV는 저희가 “입력 인코더”라고 부르는 것을 사용합니다. 이는 매개변수를 받아 하니스에서 사용할 수 있는 바이너리를 생성하는 파이썬 함수입니다. 함수가 받는 매개변수는 스크립트를 생성하는 LLM 에이전트가 결정합니다. 이상적으로는 매개변수가 ID와 비밀번호처럼 쉽게 인코딩할 수 있는 유의미한 조각이어야 하지만, 경우에 따라서는 그냥 바이너리이거나 텍스트일 수도 있습니다.

이 인코더는 독립적으로 테스트됩니다. 예를 들어 username 매개변수가 있다면, 테스트 문자열을 주입하고 에이전트가 디버그 중단점을 설정하여 올바른 값이 올바른 변수에 들어갔는지 검사할 수 있습니다.

이 인코더는 복잡하고 반드시 정확하게 구현해야 하며, 동일한 하니스를 대상으로 하는 모든 POV 생성기가 공유합니다. 노력을 한 곳에 집중하고 추가 테스트를 수행함으로써 인코더를 다음 시도에 재사용할 수 있습니다. 입력 인코더를 POV 생성기에 주면 POV 생성기가 입력 포맷을 디버깅하는데 노력을 분산하는 대신 버그를 발생시키는 작업에 집중할 수 있습니다.

POV 생성기

POV 생성기 자체는 비교적 간단합니다. 소스 코드에 직접 접근할 수 없는 대신 “소스 코드 질문”에 대답하는 서브에이전트가 주어집니다. 직접 디버깅하는 대신 디버그 서브에이전트가 주어지며, POV를 테스트할 수 있고 입력 인코더를 가져올 수 있습니다. 더 자세한 정보는 다른 글을 확인하시기 바랍니다.

POV 생성기는 세 가지 다른 LLM 모델을 써서 병렬로 실행되는데, 사용되는 모델은 앤트로픽의 클로드 소넷 3.5와 4, 그리고 오픈AI의 o3입니다. 병렬로 실행하면 비용이 더 들지만 지연 시간을 줄일 수 있어서 채점 방식의 시간 점수를 얻는데 도움이 됩니다. 생성기가 성공하면 (POV를 실행하여 크래시가 발생하는지를 보고 확인할 수 있습니다) 병렬로 실행되던 다른 생성기는 종료됩니다.

패치 만들기

버그가 POV 생성기로 보내지는 동시에 패치 생성기로도 보내집니다. 이 또한 비용을 더 쓰는 대신 지연 시간을 줄이기 위한 최적화입니다. 패치 생성기는 2024년 중반에 대부분 작성되었지만 현대적인 코딩 에이전트와 비슷합니다. 흥미로운 특징 하나는 생성된 패치를 서열 정렬 알고리즘을 써서 적용 위치를 식별하고 작은 실수를 교정한다는 점입니다.

POV나 퍼징 크래시가 취약점과 맞추어지면 관련 패치들을 실행해서 패치가 실제로 문제점을 해결하는지 확인합니다. 그렇지 않다면 패치 생성기를 다시 시작하고 실패한 케이스를 테스트로 삼아 패치가 문제점을 해결하는지 자동으로 확인하게 됩니다.

지원 코드

유감스럽게도 CRS를 개발하는데 들어가는 노력의 많은 부분은 시스템이 일을 할 수 있도록 돕는 지원 코드에 들어갑니다. 테스트해본 결과 CRS는 잘 작동하지만 실패하는 경우는 보통 전체적인 접근 방식의 근본적인 문제라기보다는 이들 지원 코드의 문제였습니다.

오케스트레이션과 스케줄링

가장 먼저 필요한 것은 이 모든 에이전트와 작업의 오케스트레이션입니다. 저희 CRS는 단일 비동기 파이썬 프로세스로 수천개의 작업을 띄우고 모든 작업을 관리했습니다. 작업 스케줄링은 LLM API와 Docker처럼 제한된 자원에 접근하는 것을 관리합니다.

소스 탐색

임의의 유닉스 명령을 실행할 수 있는 범용 도구 대신 에이전트는 소스 탐색을 위해 전용 도구를 사용합니다. 조금 더 자세하게는 에이전트 설계에 대한 다른 글에서 찾을 수 있습니다. 이를 위해 Tree-sitter나 gtags와 같은 간단한 도구나 Joern이나 clang AST와 같은 더 복잡한 도구를 사용합니다.

빌드

거의 모든 작업을 위해 CRS는 대상 프로젝트를 빌드해야 합니다. (LLM 정적 분석은 그렇지 않다는 점을 주목할만 합니다.) 일반적으로 이는 어려운 문제이지만 oss-fuzz 패키징이 제공되기 때문에 가능합니다. 여러 다른 빌드가 필요한데 디버그 정보를 포함한 빌드, 커버리지를 잴 수 있는 빌드, 정적 분석을 위해 가로챈 빌드, sanitizer 각각에 대한 빌드, 그리고 패치를 적용한 빌드가 있습니다. 지연 시간을 줄이기 위해 일부 빌드는 바로 실행되고, 자원 소모를 줄이기 위해 다른 빌드는 필요할 때 실행되며, 반복 작업을 줄이기 위해 캐싱을 사용합니다.

Docker 실행

빌드나 퍼징과 같이 자원을 많이 소모하는 작업을 CRS와 같은 기계에서 실행하면 자원이 모자랄 수 있습니다. Kubernetes와 같은 동적 스케일링 대신, 정적으로 할당된 Docker 호스트를 택했습니다. 무거운 작업은 무엇이나 원격 호스트로 보내서 실행합니다. 이렇게 하면 스케일링이 단순하고, 비용이 예측 가능합니다. 또한 시스템을 살펴볼 운영자를 둘 수 없는 시스템의 사고 가능성을 최소화하고, 작업을 띄우는데 기다리는 시간을 최소화할 수 있습니다.

Docker가 원격에서 실행되기 때문에 컨테이너 안팎으로 많은 데이터를 네트워크를 통해 전송해야 합니다. 파이널에 제출한 티오리의 CRS 버전에서는 tarfile이나 다른 단순한 파일을 네트워크를 통해 전송하는 간단한 방법을 사용했습니다.

결론

미리 정해지지 않은 대규모 코드에서 버그를 찾아내고, 실제로 발생시키며, 이를 수정하는 시스템을 만드는 일은 쉽지 않은 도전이었습니다. 그러나 저희는 다양한 코드 환경에서도 안정적으로 작동하는 새로운 접근 방식을 설계할 수 있었습니다. 일부 선택과 설계 결정은 단순했지만, LLM을 활용해 직접 POV를 생성하는 방식은 다른 팀들이 시도하지 않은 차별화된 방법이었습니다. 이 글에서 CRS의 모든 세부 내용을 다루지는 못했지만, 공개된 소스 코드와 관련 글들을 이해하는 데 도움이 될 수 있는 개요를 제공했으리라 생각합니다.

Share article
티오리한국 뉴스레터를 구독하고
최신 보안 인사이트를 바로 받아보세요.

Theori © 2025 All rights reserved.