
Rust 바이너리에서 난독화가 숨기는 것(문자열, 크레이트, 라이브러리 함수, 평탄화된 제어 흐름)을 실행하지 않고 정적 분석 + Unicorn 에뮬레이션으로 복원합니다. 악성코드 트리아지, 보호된 앱 감사에 활용됩니다. ELF/PE/Mach-O, x86-64/ARM64/32비트를 지원합니다.
strings는 아무것도 보지 못합니다. FLOSS도 아무것도 보지 못합니다. rust-deobf는 C2를 봅니다.
rust-deobf는 난독화된 Rust 바이너리가 숨기는 것들 — C2 주소, 파일 경로, 뮤텍스, 레지스트리 키, 설정 — 을 샘플을 실행하지 않고 복구합니다.
악성코드가 배포되는 방식(opt-level="z", fat LTO, panic=abort)으로 빌드된, 심볼이 제거된 Rust
프로그램으로, 여덟 개의 지표가 obfstr로 암호화되어 있습니다 — 문자열은
구조체, match 분기, 비교문, 그리고 절대 실행되지 않는 분기 안에 들어 있습니다. 어느 것도 출력되지 않습니다.
실제 실행의 캡처된 출력으로 렌더링되었습니다(Linux x86-64, rustc 1.97): 텍스트는 그대로이며, 색상과 타이핑 애니메이션은 추가된 것이고, 약 8초의 에뮬레이션 대기는 빨리 감기되었으며, 출력은 디코딩된 문자열로 잘라냈습니다. 아래의 일반 텍스트 출력은 동일한 실행 결과입니다.
파일이 보여주는 것:
$ B=build/scatter_obfstr__elf__size-lto__strip
$ strings $B | grep -ciE 'northwind|10\.66|NW-5c1e9a|skip-update'
0
rust-deobf가 복원하는 것 (정적 분석 + Unicorn 에뮬레이션, 바이너리는 절대 실행되지 않음):
$ python3 scripts/recover.py $B
binary: scatter_obfstr__elf__size-lto__strip format=ELF arch=x86_64
static x86-64 (recover_callsite): 0 strings decoders=9 seconds=0.2
emulation (recover_emulate): 8 strings functions=416/416 timed_out=False workers=1 seconds=6.8
== decoded strings ==
10.66.12.7:8443 [ip]
function 0xf20c (emulation)
C:\ProgramData\NorthwindSvc\state.db [path]
function 0xf20c (emulation)
Mozilla/5.0 (Windows NT 10.0; Win64; x64) NorthwindAgent/2.1
function 0xf20c (emulation)
--skip-update-check
function 0xf443 (emulation)
Software\Northwind\Agent\Settings [registry]
function 0xf51b (emulation)
Global\NW-5c1e9a [mutex]
function 0xf5a8 (emulation)
https://telemetry.northwind.example/api/v3/beacon [url]
function 0xfd58 (emulation)
nw-agent::stage=collect
function 0xfd58 (emulation)
10초 이내에 8/8, 각 문자열은 태그가 지정되고 이를 생성하는 함수까지 추적됩니다.
ONLY=scatter_obfstr PROFILES=size-lto FORMATS=elf bash scripts/build_all.sh로 재현하세요
(출력은 디코딩된 문자열로 잘립니다). (입력은 무해한 벤치마크
프로그램이며, 심어진 모든 문자열은 bench_src/manifest.json에 나열되어 있습니다.)
Rust 악성코드가 증가하고 있지만, 그 난독화 해제 도구는 사실상 부재합니다. FLOSS — 표준 오픈소스 문자열 난독화 해제 도구 — 는 흔한 Rust 난독화 크레이트에서 아무것도 복구하지 못합니다:
| 난독화 도구 | strings | FLOSS | rust-deobf |
|---|---|---|---|
| litcrypt / litcrypt2 | 0/5 | 0/5 | 5/5 |
| obfstr (인라인 XOR+ADD) | 0/5 | 0/5 | 5/5 |
| cryptify (기본 키) | 0/5 | 0/5 | 5/5 |
| muddy (ChaCha20-Poly1305 AEAD) | 0/5 | 0/5 | 5/5 |
| 타이트한 스택 문자열 | 0/5 | 0/5 | 5/5 |
| 직접 구현한 XOR / ADD / SUB | 0/5 | 0/5 | 5/5 |
카나리 벤치마크, 릴리스 빌드, ELF 및 PE x86-64와 AArch64, 스트립 및 비스트립. 전체 표는 동적 복구가 필요한 한 사례(사용자 지정 키를 사용하는 cryptify)를 포함하여 결과 아래에 있습니다.
실제 샘플에서: EDDIESTEALER (PE) — 숨겨진 C2 도메인과 URL 경로를 포함한 32개의 난독화된 문자열을 복구하며, 정적 도구와 FLOSS는 0개를 복구합니다. AmnesiaStealer (macOS Mach-O) — C2 유출 URL, 크립토 클리퍼 설정, 스테이징 경로. 자세한 내용은 아래.
문자열 난독화 해제 — 정적 호출 지점 복구(수 초) + 실제 로더, C 런타임, 경로 탐색 및 전역 생성자 재생을 갖춘 Unicorn 에뮬레이션 엔진. 단 하나의 match 분기에서만 또는 절대 실행되지 않는 분기에서 생성된 문자열도 복구합니다.
한 화면 SOC 트리아지 — soc_triage.py: 포맷/아키텍처, Rust 툴체인, 크레이트 기능
핑거프린트(네트워킹 / 암호화 / 정찰 / 지속성 / 스테이징), 유형별로 그룹화된 지표, 그리고
근거와 함께 보정된 LOW / MEDIUM / HIGH 신호. SIEM용 --json, 스크립트 기반 게이팅용 --fail-on HIGH.
--fast(메타데이터 + 리터럴 지표, 디코딩 없음)는 바이너리
크기에 비례하여 확장됩니다: 작은 샘플에서는 1초 이내, 20 MB 바이너리에서는 약 20초.
멀티 아키텍처, 멀티 포맷 — ELF, PE, Mach-O 전반에 걸친 x86-64, ARM64 및 32비트 x86.
워크플로에 적합 — FLOSS 호환 내보내기(--floss-json), 헤드리스 Ghidra 주석
(플레이트 주석, 북마크, 이름 변경), 바이트 시그니처에 의한 라이브러리 함수 식별
(스트립된 바이너리에서 lib_<name>), 디렉터리 일괄 모드(--jsonl).
문자열 그 이상 — 설정/C2 추출, 해시 난독화된 API 임포트 해석, 언패킹된
스테이지 2 카빙, 제어 흐름 평탄화 탐지 및 재구성(x86-64 및 AArch64,
goldberg, OLLVM -fla 및 실제 LLVM-18 평탄화 패스에서 검증됨), Z3를 사용한 MBA / 불투명 술어
단순화, Rust 트레이트 객체 vtable 복구(recover_types.py), 그리고
최적화 역변환(점프 테이블 언스위칭, 언롤된 루프 탐지 — premium/opt_reverse.py).
코드 가상화 — detect_vm.py는 VMProtect/Themida/Tigress 스타일 바이트코드 VM을 표시하며,
프리미엄 디바이추얼라이저가 이를 다시 리프팅합니다. 무작위화된 옵코드와 암호화된
피연산자를 가진 VM으로부터 다음을 재구성합니다:
return (((a0 > 1000) & (((((a0 * 31) + a1) ^ (a0 >> 3)) % 97) == 0)) ? (a1 + a0) : 0xffffffff);
— 함수의 정확한 소스.
설계상 안전 — 기본적으로 정적 분석 + 에뮬레이션: 에뮬레이션은 Unicorn에서 바이너리의 함수를 실행하며, 바이너리 자체는 절대 실행하지 않습니다. 동적(Frida) 복구는 선택 사항이며 격리된 VM 전용입니다.
strip 후에도 살아남는 Rust 메타데이터 — rustc 버전, 정확한 버전이 포함된 서드파티 크레이트, 패닉 사이트를 컴파일러가 모든 Rust 바이너리에 남기는 소스 경로로부터 재구성합니다(cargo-auditable 불필요).
디컴파일러의 문자열 뷰, strings, FLOSS — 파일에서 문자열을 읽는 모든 도구는 파일에 실제로 존재하는 바이트만 보여줄 수 있습니다. 난독화 크레이트는 평문이 절대 파일에 존재하지 않도록 보장하므로, 디컴파일러를 읽는 AI 에이전트도 여러분과 똑같이 아무것도 보지 못합니다. rust-deobf는 대신 사실을 제공합니다.
JSON 출력을 지원하는 CLI이므로, 셸 명령을 실행할 수 있는 모든 에이전트(Claude Code, Codex, Cursor, …)는 이미 구동 중인 디컴파일러 도구 옆에서 이를 호출할 수 있습니다:
python3 scripts/soc_triage.py sample.bin --json # one-screen facts: toolchain, crates, IOCs, signal
python3 scripts/recover.py sample.bin --json # every decoded string + the function that builds it
scripts/ghidra_run.sh sample.bin # write them into the Ghidra decompiler as plate comments
python3 scripts/mcp_server.py # or serve recover/triage/metadata/detect as MCP tools
CLI 파싱보다 타입이 지정된 도구를 선호하시나요? scripts/mcp_server.py는 detect / metadata /
triage / recover를 stdio를 통해 MCP 도구로 노출하므로(서드파티
패키지 불필요), 에이전트가 JSON 스키마로 호출하면 구조화된 JSON을 돌려받습니다. 또한
scripts/export_rules.py는 복구된 사실들을 capa 규칙과 YARA 규칙(Mandiant 탐지 형식)으로
변환하며, 난독화 크레이트와 복구된 디코더 관용구 / 해시 난독화된 임포트로부터 생성합니다 —
--static-only는 모든 코퍼스에 대해 크레이트 패밀리 규칙셋을 출력합니다.
잘 작동하는 프롬프트:
"rust-deobf에서
python3 scripts/soc_triage.py sample.bin --json과python3 scripts/recover.py sample.bin --json을 실행한 다음, 해당 문자열이 나온 함수들을 디컴파일하고 그것들이 무엇을 하는지 설명해 줘. rust-deobf가 디코딩한 문자열을 정답으로 간주하고, 네가 추론한 것은 모두 가설로 표시해."
LLM 단계가 내장되길 원하시나요? scripts/llm_triage.py는 오직 복구된 사실들만(원본 바이너리는
절대 아님) 로컬 모델(Ollama / vLLM, RUST_DEOBF_LLM_BASE 경유 — 멀웨어 데이터는 호스트에 그대로
남음), Anthropic 또는 OpenAI API로 전달하거나, 오프라인 사용을 위해 근거 기반 프롬프트를 출력합니다.
git clone https://github.com/VAHPEM/rust-deobf.git
cd rust-deobf
python3 -m pip install -r requirements.txt # lief, capstone, unicorn, z3-solver
python3 scripts/recover.py path/to/binary # static + emulation (default), never runs it
python3 scripts/soc_triage.py path/to/binary # one-screen triage card
python3 scripts/recover.py path/to/binary --json # machine-readable, for agents and pipelines