베어메탈 바이너리의 프로세서 식별 (전략 2): LLM 테스트
요약
문서화되지 않은 프로세서를 식별하기 위해 LLM을 활용하는 두 가지 전략을 비교 분석합니다. Gemini를 이용한 트랜스코디피케이션 테이블 생성과 Ghidra 역어셈블 결과물을 LLM으로 분석하는 방식의 성능을 테스트합니다.
핵심 포인트
- Gemini를 활용한 로우 바이트 시퀀스 기반 테이블 생성 성공
- Ghidra 역어셈블 출력값을 LLM으로 분석하는 전략 검토
- dolphinMistral, gemma4, qwen2.5-coder 등 다양한 로컬 LLM 벤치마크 수행
- 리버스 엔지니어링 워크플로우 자동화를 위한 LLM의 가능성 확인
로우 바이너리 (raw binary)로부터 문서화되지 않은 프로세서를 역어셈블 (disassembly)하는 과정을 산업화하는 것은 다음과 같은 4가지 주요 단계로 나눌 수 있는 복잡한 과제입니다:
- 바이너리가 이미 알려진 프로세서에 속하지 않는지 확인.
- 바이너리가 알려진 프로세서의 난독화(obfuscated), 압축(compressed) 또는 암호화(encrypted)된 코드에 해당하지 않는지 확인.
- 문서화되지 않은 프로세서 생성기 (undocumented processor generator) 구축.
- 분석 워크플로우 및 역어셈블 생성 파이프라인 개발.
이 프로젝트의 첫 번째 단계의 목표는 다양한 전략을 평가하고 가장 효과적인 접근 방식을 식별하는 것입니다.
2. 평가된 전략들
첫 번째 전략은 주어진 프로세서에 대해 바이너리를 정적(statically) 및 동적(dynamically)으로 역어셈블하기 위해 트랜스코디피케이션 테이블 (transcodification table, 바이트 시퀀스를 어셈블리 명령어로 매핑)을 생성하는 것을 목표로 했습니다. 이 프로세스는 하나 이상의 바이트가 해당 프로세서의 알려진 명령어와 일치하지 않거나, 역어셈블이 성공적으로 완료될 때까지 계속됩니다 (참고: 역어셈블에 성공했다고 해서 해당 바이너리가 반드시 특정 프로세서를 위해 컴파일되었다는 것을 의미하지는 않습니다).
이 전략을 구현하고 트랜스코디피케이션 테이블을 구축하기 위해 여러 접근 방식이 테스트되었습니다:
Ghidra 트랜스코디피케이션 테이블 생성: 실패.
네이티브 역어셈블러 (Native disassembler) 트랜스코디피케이션 테이블 생성: 실패.
Gemini를 사용하여 로우 바이트 시퀀스로부터 테이블 생성: 성공.
(전략 1 하에서 수행된 테스트의 전체 요약은 "Identifying the Processor of a Bare-Metal Binary — Strategy 1"을 참조하십시오.)
두 번째 전략은 완전히 다른 접근 방식을 취합니다. 이는 Ghidra를 사용하여 수많은 대상 아키텍처 (177개의 프로세서)에 대해 바이너리를 역어셈블한 다음, 대규모 언어 모델 (LLM)을 활용하여 결과물인 역어셈블 출력값을 분석함으로써 어떤 프로세서가 실제로 바이너리와 일치하는지 결정하는 방식으로 구성됩니다.
3. 테스트 프로토콜
이 벤치마크를 위해 선택된 로컬 LLM은 다음과 같습니다:
- dolphinMistral24b
- dolphin3-cyber
- gemma4:26b
- qwen3-coder:30b
- qwen2.5-coder
단일 C 소스 코드 파일과 자동화된 툴체인 (toolchain)을 사용하여, 약 30개의 서로 다른 프로세서에 대해 로우 베어메탈 (raw bare-metal) 및 표준 ELF 형식 모두로 테스트용 바이너리를 생성했습니다.
평가를 위해 세 가지 특정 바이너리가 선택되었습니다:
- Firmware 1: 대상 프로세서가 Ghidra에 의해 네이티브로 지원됨.
- Firmware 2: 대상 프로세서가 Ghidra에 의해 지원되지 않음.
- Firmware 3: 로우 베어메탈 바이너리로 명시적으로 역어셈블된 (disassembled) ELF 파일 (로우 바이트 파싱을 강제함).
역어셈블 (Disassembly) 출력 파일은 PyGhidra와 커스텀 Java 헤드리스 (headless) 스크립트를 사용하여 일괄 생성되었습니다. 그런 다음 각 모델은 정확히 동일한 시스템 프롬프트 (system prompt)를 사용하여 생성된 모든 역어셈블 파일을 분석하도록 요청되었습니다:
Python
SYSTEM_PROMPT = """당신은 리버스 엔지니어링 (reverse engineering) 및 프로세서 아키텍처 (processor architectures) 전문가입니다.
당신의 작업은 로우 펌웨어 역어셈블의 일관성을 검증하는 것입니다.
제공된 명령어를 검토하고, 해당 아키텍처의 구문 (syntax)이 유효한지 확인하십시오,
...
모델들은 엄격한 JSON 스키마 (JSON schema)를 사용하여 응답하도록 지시받았습니다:
{
"processor_id": "{processor_id}",
...
4. 벤치마크 결과 (Benchmark Results)
Firmware 1 (알려진 프로세서)
이 펌웨어는 Ghidra가 지원하는 프로세서를 위해 생성되었습니다.
- qwen2.5-coder: 177개 파일 모두에 대해 보고서를 생성했습니다. 69개의 잠재적 후보 프로세서를 식별했습니다 (정답 포함).
- qwen3-coder:30b: 177개 파일 모두에 대해 보고서를 생성했습니다. 35개의 잠재적 후보 프로세서를 식별했습니다 (정답 포함).
- gemma4:26b: 심각한 포맷팅 실패 — 단 1개의 역어셈블 파일에 대해서만 유효한 보고서를 생성했습니다.
- dolphinMistral24b: 177개 파일 모두에 대해 보고서를 생성했습니다. 114개의 잠재적 후보 프로세서를 식별했습니다 (정답 포함).
- dolphin3-cyber: 177개 파일 모두에 대해 보고서를 생성했습니다. 168개의 잠재적 후보 프로세서를 식별했습니다 (정답 포함).
Firmware 2 (알려지지 않은 프로세서)
이 펌웨어는 Ghidra가 지원하지 않는 대상 아키텍처를 위해 생성되었습니다.
- qwen2.5-coder: 177개 파일 모두에 대해 보고서를 생성했습니다. 67개의 잠재적 후보 프로세서(오탐, false positives)를 식별했습니다.
- qwen3-coder:30b: 177개 파일 모두에 대해 보고서를 생성했습니다. 32개의 잠재적 후보 프로세서(오탐, false positives)를 식별했습니다.
- gemma4:26b: 완전히 실패했습니다 — 단 하나의 보고서도 생성하지 못했습니다.
- dolphinMistral24b: 177개 파일 모두에 대해 보고서를 생성했습니다. 105개의 잠재적 후보 프로세서(오탐, false positives)를 식별했습니다.
- dolphin3-cyber: 177개 파일 모두에 대해 보고서를 생성했습니다. 172개의 잠재적 후보 프로세서(오탐, false positives)를 식별했습니다.
Firmware 3 (베어메탈로 역어셈블된 ELF 파일)
로컬 모델들의 저조한 기본 성능을 고려하여, 이 테스트는 qwen3-coder:30b를 대상으로만 독점적으로 수행되었습니다.
qwen3-coder:30b: 177개 파일 모두에 대해 보고서를 생성했습니다. 42개의 잠재적 후보 프로세서(정답 포함)를 식별했습니다.
5. 결과가 왜 이렇게 좋지 않은가?
실증적 결과는 매우 높은 오탐(false positive) 비율을 보여줍니다. 모델들이 177개 중 35개에서 172개 사이의 후보를 검증함으로써, 선택적인 휴리스틱 필터(selective heuristic filter)로서의 역할을 수행하지 못했습니다. 몇 가지 주요 기술적 요인이 이러한 부족함을 설명합니다:
A. 근본 원인 및 가설
-
표면적 구문 검증 (Superficial Syntax Validation) vs. 의미론적 검증 (Semantic Verification):
코드 중심의 대부분의 LLM은 어셈블리를 주로 구문적 (syntactic) 수준에서 평가합니다. Ghidra가 역어셈블 (disassembly)을 강제할 때, 대상 아키텍처의 문법에 따라 유효한 명령어 문자열(예: MOV R0, R1)을 출력합니다. LLM은 구문적으로 유효한 명령어를 의미론적으로 논리적인 코드로 오인하며, 비논리적인 제어 흐름 (control flow), 불가능한 스택 프레임 할당 (stack frame allocations), 또는 무의미한 레지스터 사용과 같은 구조적 위험 신호 (red flags)를 무시합니다. -
컨텍스트 윈도우 크기 제약 및 문맥적 사각지대 (첫 50개 명령어):
컨텍스트를 첫 50개 명령어로 제한하는 것은 심각한 편향을 초래합니다. 로우 베어메탈 바이너리(raw bare-metal binaries), 특히 로우 바이트(raw bytes)로 파싱된 ELF 파일의 경우, 오프셋은 종종 인터럽트 벡터 (interrupt vectors), 패딩 (padding), 또는 로우 헤더 메타데이터 (\x7fELF)로 시작됩니다. 메타데이터를 역어셈블하면 무작위의 쓰레기 (garbage) 명령어가 생성됩니다. LLM은 이 쓰레기 데이터를 유효한 초기화 코드로 받아들이거나, 바이너리 하단에 위치한 실제 함수 프롤로그 (function prologues, 예: PUSH {LR}, 프레임 설정)를 놓치게 됩니다. -
높은 신뢰도 점수의 환각 (Hallucination of High Confidence Scores):
규모가 작고 양자화된 (quantized) 로컬 모델들은 보정된 불확실성 (calibrated uncertainty)이 부족합니다. 이들은 단지 50개 샘플 윈도우 내에 명시적인 .byte 미확인 지시어가 나타나지 않았다는 이유만으로, 매우 확률이 낮은 역어셈블 결과에 0.80에서 1.0 사이의 신뢰도 점수를 빈번하게 부여합니다. -
JSON 스키마 팽창 및 특수 토큰 유출:
gemma4:26b와 같은 모델들은 저수준 어셈블리 구문을 처리할 때 프롬프트 준수 (prompt-adherence) 능력이 저하되어, 내부 추론/채널 토큰(예: thought...)을 JSON 스트림에 유출함으로써 출력 파싱을 손상시키는 실패를 보였습니다.
B. 결과 개선을 위한 제안된 솔루션
이 접근 방식을 실행 가능한 산업용 파이프라인으로 전환하기 위해서는 다음과 같은 몇 가지 조정이 필요합니다:
-
휴리스틱 사전 필터링 및 지표 계산 (Heuristic Pre-Filtering & Metrics Computation) (하이브리드 정적 분석 (Hybrid Static Analysis) + LLM):
LLM을 호출하기 전에, 디스어셈블리 (disassembly) 출력물에 대해 수학적 휴리스틱 (heuristics)을 계산합니다: -
잘못된 명령어 비율 (Invalid Instruction Ratio): .byte 또는 ?? 지시어가 전체 출력의 5%를 초과하는 아키텍처 (architecture)는 거부합니다.
-
제어 흐름 밀도 (Control Flow Density): 데이터 이동 (data movement, MOV, LDR) 대비 제어 흐름 명령어 (control-flow instructions, JMP, CALL, BRANCH)의 비율을 측정합니다. 무작위적이거나 잘못된 디스어셈블리는 비정상적으로 낮거나 불규칙한 점프 밀도를 보입니다.
-
엔트로피 및 문자열 아티팩트 (Entropy & String Artifacts): 바이너리 섹션 전반에 걸쳐 엔트로피 (entropy)를 계산하여, 샘플링 전 정적 헤더 (static headers)를 건너뜁니다.
-
반례를 활용한 퓨샷 프롬프팅 (Few-Shot Prompting with Counter-Examples):
유효한 디스어셈블리 (일관된 스택 연산, 표준 함수 프롤로그 (function prologues), 구조화된 루프)와 유효하지 않거나 쓰레기 값인 디스어셈블리 (반복적인 옵코드 (opcodes), 데드 점프 (dead jumps), 비정상적인 즉치값 (immediate values))를 대조하는 명시적인 퓨샷 (Few-Shot) 예시를 사용하여 시스템 프롬프트를 업데이트합니다. -
동적 샘플 윈도잉 (Dynamic Sample Windowing) (메타데이터 건너뛰기):
처음 50개의 원시 명령어를 입력하는 대신, 탐지된 함수 진입점 (function entry points, 예: CALL 대상 또는 push/pop 진입 시퀀스로 식별됨)부터 시작하는 50개의 명령어를 추출합니다. 베어메탈 (bare metal)로 취급되는 ELF 바이너리의 경우, 알려진 헤더 길이에 일치하는 초기 오프셋 (offset)을 자동으로 건너뜁니다. -
JSON 출력 전 사고의 사슬 (Chain-of-Thought (CoT) Reasoning):
LLM이 즉시 원시 JSON을 출력하도록 강제하면 내부적인 분석 능력이 억제됩니다. 프롬프트 구조를 변경하여 최종 JSON 객체를 생성하기 전에 단계별 추론 단계를 요구하도록 하면 정확도가 크게 향상됩니다:
Plaintext
1. 제어 흐름 일관성 분석...
2. 레지스터 일관성 확인...
...
- 교차 아키텍처 제거 토너먼트 (Cross-Architecture Elimination Tournaments): LLM에게
이 벤치마크는 저수준 리버스 엔지니어링 (low-level reverse engineering) 휴리스틱 (heuristics)을 위해 현재의 로컬 LLM을 즉시 사용했을 때 나타나는 명확한 한계를 강조합니다. 통계적 사전 필터링 (statistical pre-filtering), 동적 윈도잉 (dynamic windowing), 그리고 구조화된 사고 사슬 (structured chain-of-thought) 프롬프팅 없이는, 로컬 모델들이 지나치게 허용적인 분류기 (classifiers)처럼 동작합니다.
정교한 프롬프트 엔지니어링 (prompt engineering), 동적 컨텍스트 샘플링 (dynamic context sampling), 또는 클라우드급 LLM이 이 격차를 메워 미지의 아키텍처를 신뢰성 있게 정확히 찾아낼 수 있을까요? 그것이 우리의 다음 평가 주제가 될 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기