AI Agent vs 40MB Binary: Can LLMs Replace Reverse Engineers?


최근 보안 업계에서 가장 소름 돋았던 사건을 꼽으라면 단연 xz 백도어 사태일 겁니다. 오픈소스 생태계의 신뢰를 무너뜨린 이 사건은, 결국 사람의 ‘직감’(로그인 지연 시간)으로 발견되었습니다. 그렇다면 과연 AI는 이런 은밀한 백도어를 바이너리 레벨에서 찾아낼 수 있을까요?

최근 Quesma 팀에서 흥미로운 실험을 진행했습니다. “40MB짜리 바이너리에 백도어를 숨기고, AI와 Ghidra에게 찾아보라고 시키면 어떻게 될까?” 라는 주제였죠. 오늘은 이 실험 결과와 Hacker News의 반응, 그리고 제 개인적인 기술적 견해를 섞어 깊게 파보려 합니다.

실험 설계: 소스 코드는 없다, 오직 바이너리뿐

우리가 평소에 쓰는 코딩 어시스턴트(Copilot 등)는 소스 코드를 봅니다. 변수명도 있고, 함수 구조도 명확하죠. 하지만 멀웨어 분석(Malware Analysis) 의 세계는 다릅니다. 컴파일러가 최적화를 위해 코드를 뭉개고, 인라인(inline) 시키고, 변수명은 다 날아갑니다. 남는 건 기계어와 어셈블리뿐입니다.

Quesma는 lighttpd, dnsmasq 같은 유명 오픈소스 프로젝트에 인위적인 백도어를 심었습니다. 그리고 Claude Opus, Gemini Pro 같은 모델들에게 Ghidra, Radare2 도구 접근 권한을 주고 분석을 시켰습니다.

결과는 어땠을까요? 가장 성능이 좋았던 Claude Opus 4.6조차 성공률은 49% 에 그쳤습니다. 동전 던지기 수준이죠.

성공 사례: AI가 ‘탐정’ 노릇을 할 때

놀랍게도 AI가 꽤 그럴싸한 추론을 해낸 케이스가 있습니다. lighttpd 서버에 “특정 HTTP 헤더가 있으면 쉘 명령어를 실행하는” 백도어를 심었을 때입니다.

Claude는 다음과 같은 과정을 거쳤습니다:

  1. 바이너리와 라이브러리 스캔.
  2. strings 명령어로 ‘bash’, ‘exec’ 같은 수상한 문자열 검색 (실패).
  3. nm -D로 임포트된 심볼 확인 -> popen 함수 발견.
  4. Radare2popen을 호출하는 함수(li_check_debug_header)를 역추적.
// AI가 찾아낸 백도어 로직 (Decompiled)
if (NULL != l) {
    // ...
    FILE *fp = popen(debugIn, "r"); // Execute attacker's command
    // ...
}

AI는 “디버그 헤더 처리 함수에서 popen을 호출하는 건 전형적인 백도어 패턴이다”라고 정확히 지적했습니다. 여기까지 보면 “와, 이제 리버스 엔지니어링도 AI가 다 하겠네?” 싶을 겁니다. 하지만 진짜 문제는 이제부터입니다.

실패 사례: AI의 치명적 약점, ‘합리화’

제가 이 리포트에서 가장 주목한 부분은 AI가 백도어를 찾고도 놓친 케이스입니다. dnsmasq에 아주 뻔한 백도어를 심었습니다. DHCP 옵션 224번이 오면 execl("/bin/sh")를 실행하는 코드였죠.

+ if (opt = option_find(mess, sz, 224, 1)) {
+    // ...
+    execl("/bin/sh", "sh", "-c", buf, NULL);
+ }

Claude Opus 4.6은 이 코드를 정확히 찾았습니다. 하지만 결론이 충격적입니다.

execl/bin/sh를 호출하는 건 dnsmasq의 DHCP 스크립트 실행 기능으로 보입니다. 이는 정상적인 동작 패턴입니다.”

이게 바로 LLM의 한계입니다. 문맥(Context)을 이해하는 척하지만, 실제로는 그럴싸한 이유를 갖다 붙여서 ‘합리화(Rationalization)‘를 합니다. 보안 감사에서 “이건 원래 이런 기능일 거야”라고 넘겨짚는 건 최악의 실수인데, AI는 너무나 자신 있게 실수를 저지릅니다.

False Positive: 양치기 소년 딜레마

더 큰 문제는 오탐(False Positive) 입니다. 모델들은 약 28%의 확률로 멀쩡한 코드를 백도어라고 우겼습니다. 예를 들어, Gemini 3 Pro는 단순히 설정값을 파싱하는 코드를 보고 “명령어 실행 취약점”이라고 보고했습니다.

현업 엔지니어로서 말하자면, False Positive가 높은 보안 도구는 쓰레기통으로 직행 입니다. 1,000개의 바이너리를 검사했는데 280개가 의심된다고 알람이 울리면, 보안팀은 결국 알람을 꺼버리게 됩니다. curl의 메인테이너가 “AI가 생성한 버그 리포트는 받지 않겠다”고 선언한 것도 같은 맥락입니다.

Hacker News의 반응과 인사이트

이 실험에 대해 Hacker News에서도 날카로운 지적들이 쏟아졌습니다.

  • 난독화(Obfuscation) 문제: “문자열이나 심볼 테이블만 지워도 성공률은 0%로 떨어질 것이다.” -> 전적으로 동의합니다. 이번 실험은 난독화가 없는 상태였음에도 50% 미만의 성공률을 보였습니다. 실제 공격자들은 바보가 아닙니다.
  • 도구로서의 가능성: “보안 감사용으론 못 써도, 파일 포맷 리버스 엔지니어링에는 훌륭하다.” -> 저도 이 부분은 긍정적입니다. 파서(Parser)를 처음부터 짜는 건 고역인데, AI가 Ghidra 결과물을 초벌 번역해 주는 건 생산성에 큰 도움이 됩니다.
  • 검증 부재: “왜 AI에게 백도어를 직접 실행(Exploit) 해보라고 안 시키지?” -> 현재 모델들의 Safety Filter(Alignment) 때문에 “해킹해봐”라고 하면 거부합니다. 하지만 로컬 모델을 쓴다면 이 제약을 풀고 검증까지 자동화할 수 있겠죠.

결론: AI는 아직 ‘주니어 분석가’ 수준도 아니다

이번 벤치마크 결과를 보며 느낀 점은 명확합니다.

  1. 자동화는 시기상조: AI에게 “이 바이너리 안전해?”라고 묻고 그 대답을 믿는 건 위험천만한 일입니다.
  2. 보조 도구로는 유용: 하지만 수천 개의 함수 중 ‘수상한 냄새’가 나는 곳을 좁혀주는 용도로는 쓸만합니다. nm, strings, grep질을 대신해 주는 똑똑한 인턴 정도죠.
  3. Tooling의 중요성: 실험에서 오픈소스인 Ghidra를 썼지만, 상용 툴인 IDA Pro를 썼다면 결과가 더 좋았을 겁니다. 특히 Go나 Rust 바이너리 분석에서 오픈소스 툴의 한계가 명확했습니다.

결국, 아직은 Human-in-the-loop 가 필수입니다. AI가 찾아낸 것을 사람이 검증해야지, AI에게 판단을 맡길 단계는 아닙니다. 하지만 1년 전만 해도 AI가 Ghidra를 조작조차 못 했던 걸 생각하면, 발전 속도는 무섭습니다. 내년 이맘때쯤엔 이 글을 수정해야 할지도 모르겠네요.