Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
fnprint — 바이너리의 함수를 바이트 형태가 아닌 동작 방식으로 매칭합니다. 마이크로실행을 통한 동작 기반 함수 핑거프린팅. | Kitploit
도구/GitHubGitHub/1rhino2/fnprint
Vulnerability AnalysisDynamic Code Analysis (DAST)Reverse EngineeringMalware AnalysisBinary AnalysisFirmware Analysis
GitHub1rhino2/fnprint

fnprint

바이너리의 함수를 바이트 형태가 아닌 동작 방식으로 매칭합니다. 마이크로실행을 통한 동작 기반 함수 핑거프린팅.

저장소 보기
444371일 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
웹사이트
공유

fnprint

fnprint는 바이너리 내의 함수를 바이트나 제어 흐름 그래프가 어떻게 생겼는지가 아니라 무엇을 하는지로 매칭합니다. 각 함수를 임의의 입력을 넣은 작은 에뮬레이터에서 실행하고, 발생하는 부작용을 기록한 뒤, 그 동작을 지문으로 해싱합니다. 동일하게 동작하는 두 함수는 다른 컴파일러나 다른 최적화 수준으로 빌드되었더라도 유사한 지문을 갖습니다.

이렇게 하는 이유: 바이트 시그니처(FLIRT, FunctionID)는 코드가 재컴파일되는 순간 깨지고, CFG 매처(BinDiff, Diaphora)는 -O0와 -O3 사이에서 흔들립니다. 동작은 둘 다 훨씬 잘 견딥니다.

현재는 x86-64 ELF만 지원합니다. 신뢰하기 전에 한계를 확인하세요.

시연

fnprint naming functions in a stripped, differently-compiled binary

스트립된 바이너리와 이미 이름을 알고 있는 것들의 코퍼스를 지정하세요:

root@kitploit:~
$ strip --strip-all mystery.so
$ nm mystery.so
nm: mystery.so: no symbols

$ fnprint index libz.so -o corpus.db          # a build you have symbols for
$ fnprint query mystery.so --corpus corpus.db
named 9 function(s):
  0x000022f9  100.0%  adler32_z
  0x00002a8d  100.0%  compress2
  0x0000ad5d  100.0%  inflateBackEnd
  0x0000adc4   86.7%  inflate_fast
  0x00002e67   80.5%  crc32_z
  ...

이 실행은 완전히 스트립된 -O0 빌드를 -O2 코퍼스에서 이름을 붙인 것입니다. 다른 최적화 수준, 심볼이 전혀 남지 않았는데도 이름이 제대로 복원됩니다. 확신하는 것에는 이름을 붙이고 나머지에 대해서는 조용히 있습니다.

또 다른 기능은 두 빌드를 비교하여 어떤 함수의 동작이 변경되었는지 알려주는 것으로, 벤더가 새 펌웨어를 출시했을 때 실제로 무엇이 바뀌었는지 알고 싶을 때 유용합니다:

root@kitploit:~
$ fnprint match old.so new.so
compared 84 functions present in both
  unchanged:  53
  changed:    1
  low-signal: 31 (too small to judge)

changed behavior (lowest similarity first):
   61.7%  deflate_stored

실제 n-day 작업에는 triage가 있습니다. 알려진 취약 버전의 함수로 코퍼스를 하나 만들고 패치된 버전으로 하나를 만든 뒤, 알 수 없는 빌드를 둘 모두에 대해 순위를 매깁니다. 취약한 쪽에 가깝고 패치된 쪽과 명확히 분리된 함수가 사람이 검토해야 할 대상이며, 단순히 해석해야 하는 단일 매치 점수가 아닙니다:

root@kitploit:~
$ fnprint index vuln.so    -o vuln.db
$ fnprint index patched.so -o patched.db
$ fnprint triage mystery.so --vuln vuln.db --patched patched.db
43 functions triaged: 1 look vulnerable, 0 patched, 42 inconclusive

review queue (vuln-leaning, strongest first):
   addr         vuln%  patched%  margin  matches
   0x000022f9  100.0     27.3   +72.7  adler32_z vs crc32_z

두 버전에서 동일한 42개의 함수는 의도적으로 inconclusive로 반환됩니다. 어느 쪽으로도 특정할 수 없으며 플래그가 지정되어서는 안 됩니다. --margin과 --min-sim은 두 쪽이 얼마나 분리되어야 확정할지를 제어합니다.

작동 방식

각 함수에 대해:

  • 바이너리를 매핑하고 인자 레지스터에 쓰레기 값을 넣은 채로 함수로 점프합니다.
  • 우리가 설정하지 않은 메모리 읽기는 결정론적 값을 반환하고 페이지는 즉석에서 매핑됩니다. 와일드 포인터가 실행을 절대 크래시시키지 않으며, 동일한 입력은 항상 동일한 트레이스를 제공합니다. 이것은 Godefroid의 마이크로실행 기법입니다.
  • 다른 함수에 대한 호출은 스텁 처리(기록 후 건너뜀)되어 libc로 빠지지 않고 실행이 이 함수에 집중됩니다.
  • 아키텍처 중립적인 효과 스트림을 기록합니다: 어떤 인자 버퍼와 구조체 필드를 읽고 쓰는지, 어떤 값 클래스를 쓰는지(입력의 복사본, 작은 상수, 포인터), 어떤 호출을 하는지, 어떤 분기를 타는지, 무엇을 반환하는지. 절대 주소는 버려지고 오프셋과 형태만 유지됩니다.
  • 이 스트림은 shingle과 minhash 시그니처로 변환됩니다. 유사도는 일치하는 minhash 슬롯의 비율로, 두 함수의 동작이 얼마나 겹치는지를 추정합니다. LSH 밴드 인덱스는 쿼리가 모든 것을 모든 것과 비교하지 않도록 합니다.

학습 데이터도, 모델도 없습니다. 같은 아이디어는 학술 문헌에서 Blanket Execution(Egele et al, USENIX Security 2014)으로 등장합니다; fnprint는 실제로 사용할 수 있는 CLI를 갖춘 실용적이고 유지 관리되는 접근입니다.

설치

rust 툴체인과 unicorn + capstone 라이브러리가 필요합니다.

root@kitploit:~
# debian/ubuntu/kali
sudo apt install libunicorn-dev libcapstone-dev

cargo install --path cli
# or just
cargo build --release   # binary at target/release/fnprint

사용법

root@kitploit:~
fnprint index <binary> [-o out.db]      fingerprint every function, optionally to a db
fnprint match <a> <b>                   diff two binaries (or .db files) by behavior
fnprint query <target> --corpus <db>    name unknown functions from a corpus
fnprint triage <t> --vuln <db> --patched <db>   rank a build against vuln vs patched corpora
fnprint eval <a> <b>                     accuracy metrics using symbol names as truth
fnprint dump <binary> <func>            print the recorded effect trace (debugging)

match와 query는 ELF 또는 index로 만든 .db를 받으므로, 코퍼스를 한 번 지문 채취하고 재사용할 수 있습니다.

기계 출력

모든 명령은 전역 --format을 받습니다:

root@kitploit:~
fnprint query mystery.so --corpus corpus.db --format json > names.json
fnprint query mystery.so --corpus corpus.db --format r2   > fnprint.r2

json은 스크립트(그리고 contrib/의 Ghidra 임포트 스텁)를 위한 안정적인 스키마이고, r2는 rizin/radare2 내에서 . fnprint.r2로 실행하는 afn 이름 변경 명령을 출력합니다. 대상의 심볼 이름은 둘 중 어디로 가든 새니타이즈되므로, 조작된 이름이 r2 명령을 주입할 수 없습니다. 스키마와 설정은 docs/integrations.md에 있습니다.

인덱싱은 기본적으로 단일 프로세스입니다. FNPRINT_SHARDS=N fnprint index ...는 대규모 인덱스를 N개의 격리된 워커에 분산합니다; 코퍼스는 N에 관계없이 바이트 단위로 동일합니다. 크고 함수가 많은 바이너리에서만 도움이 되므로 요청하지 않으면 꺼져 있습니다.

정확도

rank-1 정확도란: 빌드 A의 함수에 대해 빌드 B의 모든 함수를 유사도로 순위를 매길 때, 최상위 히트가 올바른 것인지 여부입니다. 이것이 바로 스트립된 이름 복원 작업입니다. zlib 1.3.1(84개 함수)에서 측정했으며, bench/run.sh로 재현 가능합니다:

pairrank-1precision
gcc O0 -> O197.1%93.8%
gcc O0 -> O293.1%83.3%
gcc O0 -> O391.3%100.0%
gcc/clang O097.7%97.1%
gcc O2 -> O356.5%66.7%
gcc/clang O259.1%50.0%

전체 표와 두 번째 라이브러리(lua)는 bench/NUMBERS.md에 있습니다.

eval은 recall@3 / recall@5와 기권율도 보고합니다. rank-1만으로는 많은 것을 숨기기 때문입니다. gcc O0 -> O2에서 최상위 히트가 맞을 확률은 93%이지만 올바른 함수가 상위 5위 안에 있을 확률은 96.6%이므로, 작은 검토 예산으로 대부분의 격차를 메울 수 있습니다. 또한 확신이 없는 쌍에 대해서는 추측하는 대신 기권(확신 있는 "동일" 판정을 거부)하므로, 동일 임계값에서 재현율이 낮은 동안에도 정밀도가 높게 유지됩니다.

솔직한 해석: 적어도 한 쪽이 어느 정도 동작적 풍부함을 가질 때(-O0/-O1이 있는 것, 또는 -O0에서의 크로스 컴파일러 쌍) 80-98% 범위에 들어갑니다. 양쪽 모두 고도로 최적화되면 관찰할 수 있는 동작이 얇아져 동전 던지기에 가까워집니다. 이것이 단일 패스, 학습 없는 매처의 어려운 한계이며, 이 도구는 그렇지 않은 척하지 않습니다.

잘 못하는 것

  • 아주 작은 함수. thunk와 한 줄짜리 접근자는 지문 채취에 충분한 동작을 하지 않으므로 보류합니다("low-signal" 및 "with enough signal" 카운트가 그것입니다).
  • 순수 계산. 버퍼를 읽고 숫자를 반환하는 두 체크섬은 비슷해 보입니다. 외부에서 보면 거의 실제로 그렇기 때문입니다.
  • 실제 사전 조건 뒤에 숨은 깊은 로직. 쓰레기 입력으로 하는 마이크로실행은 함수의 진입 동작을 실행합니다. 쓰레기 입력으로 도달하지 못하는 상태에 묻힌 변경은 match에 나타나지 않습니다. 구조적 및 초기 경로 변경을 잡지, 모든 깊은 수정을 잡지는 않습니다.
  • 위 수치에서 보듯 양쪽 모두의 과도한 최적화.
  • 심한 난독화(특히 VM 기반)는 이를 망가뜨립니다.

선행 기술, 그리고 이 도구의 위치

  • FLIRT / FunctionID / Lumina: 바이트 시그니처. 정확하고 빠르지만 재컴파일 시 깨짐.
  • BinDiff / Diaphora: 그래프 구조. 좋지만 최적화와 아키텍처를 넘나들 때 취약함.
  • Ghidra BSim: 디컴파일러 특징 벡터. 정신적으로 더 가깝지만 단일 아키텍처에 가까움.
  • Asm2Vec / SAFE / jTrans: 학습된 임베딩. 강력하지만 학습이 필요하고 아무도 학습하지 않은 아키텍처로 일반화되지 않음.
  • 마이크로실행(Godefroid, 2014)과 Blanket Execution(Egele et al, 2014): 이 접근의 학술적 뿌리. 유지 관리되는 도구로 출시된 적은 없음.

fnprint는 학습이 필요 없는, 동작 우선 옵션입니다. 효과 모델은 이미 아키텍처 중립적이며, 이는 CPU 간 매칭을 위한 토대입니다.

로드맵

  • arm64와 mips. x86에서 함수를 지문 채취하고 스트립된 라우터 펌웨어에서 찾을 수 있도록. 효과 모델은 이미 아키텍처 중립적이며, 이는 대부분 아키텍처별 에뮬레이터 배관 작업입니다.
  • 더 똑똑한 경로 커버리지. 순진한 분기 뒤집기는 코드에 있지만(explore_depth, 기본 꺼짐) 불가능한 경로에 빌드별 노이즈를 추가하고 테스트에서 크로스 빌드 정확도를 해쳤으므로, 제 역할을 하려면 경로 일관성 필터가 필요합니다.
  • pe 및 mach-o 로더.
  • rizin/radare2 내보내기는 지금 제공됩니다(--format r2); 실제 ghidra 플러그인이 다음입니다. 그동안 contrib/에 실험적인 jython 임포트 스텁이 있습니다.

라이선스

MIT. LICENSE를 참조하세요.

도구 다운로드