본문으로 건너뛰기
5분 AI.
TRENDSHIFT MONTHLY #24 딥다이브 · 2026-10-23 분석

ghidra 딥다이브
— 실행 파일을 읽는 법에서 분석 도구를 설계하는 법까지

분석 앱·프레임워크

빠른 판단

무엇
실행 파일을 역어셈블·디컴파일하고 함수·타입·참조를 조사하는 확장 가능한 역공학 작업대
이럴 때
소스 없는 프로그램의 동작을 분석하고 이름·타입·참조 가설을 정리하거나 반복 분석을 스크립트로 돌릴 때
입력
컴파일된 실행 파일과 분석 설정
결과
어셈블리·의사 코드·함수 관계와 사용자가 정리한 분석 정보
소스 코드가 없는 프로그램을 어떻게 이해할까요? Ghidra는 실행 파일의 바이트를 명령어·함수·타입·C 형태의 의사 코드로 해석하는 소프트웨어 역공학 프레임워크입니다. Java 약 323만 줄, C/C++ 약 26만 줄, SLEIGH 약 31만 줄에 담긴 설계를 따라가며, 플러그인·중간 표현·분석 스케줄러·프로세스 통신을 배웁니다. NationalSecurityAgency/ghidra · 스타 93,306 · 포크 10,469 · Apache-2.0 · 2026-10-23 GitHub 기준. 소스 설명은 커밋 16400303 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

바이트에서 의미를 찾아, 사람이 검토할 수 있게 만드는 도구

Ghidra는 컴파일된 프로그램을 읽고, 그 동작에 대한 가설을 쌓아 가는 분석 작업대입니다. README에 따르면 NSA의 Research Directorate가 개발·유지하는 프로젝트이며, Windows·Linux·macOS에서 사용할 수 있습니다. 실행 파일을 불러온 뒤 명령어를 읽고, 함수 사이의 관계를 따라가고, 변수 이름과 타입을 수정하며 이해를 구체화합니다.

Ghidra가 만드는 의사 코드는 원본 소스의 복원본이 아니라, 기계어의 동작을 사람이 읽도록 다시 표현한 분석 결과입니다. 컴파일 과정에서 사라진 주석·원래 변수 이름·코드 배치는 일반적으로 되돌릴 수 없습니다. 최적화가 심하면 여러 소스 표현이 같은 기계어가 되기도 합니다.

용어 먼저
역공학 · 디스어셈블 · 디컴파일
역공학은 완성된 결과에서 구조와 동작을 알아내는 활동입니다. 디스어셈블은 기계어 바이트를 어셈블리 명령어로 해석하고, 디컴파일은 이를 변수·조건문·반복문 같은 높은 수준의 표현으로 재구성합니다. 세 단어는 서로 다른 범위를 가리킵니다.
한 문장 비유

설계도가 사라진 기계를 분해하고, 부품마다 이름표를 붙이는 작업대입니다

디스어셈블러는 기계 속 부품을 구별하고, 디컴파일러는 부품들이 함께 하는 일을 설명합니다. 분석가는 그 설명을 검토하면서 “이 축은 속도를 조절한다”는 식으로 이름과 의미를 붙입니다.

같은 부품 배치를 여러 방식으로 설계할 수 있듯, 의사 코드에도 여러 표현이 가능합니다. 따라서 출력의 문장 모양보다 실제 입출력·분기·메모리 접근을 확인하는 습관이 중요합니다.

입력과 출력으로 이해하기

단계무엇을 다루나독자가 얻는 것
가져오기ELF·PE 등 실행 파일 형식, 프로세서, 주소 공간바이트가 어느 메모리 주소에 놓이는지에 대한 모델
자동 분석명령어·함수 경계·참조·문자열·심볼탐색할 수 있는 함수와 연결 관계
디컴파일함수의 제어 흐름·데이터 흐름·타입 정보C 형태의 의사 코드와 고수준 분석 구조
사람의 보정함수 이름·인자·구조체·주석다음 분석에 반영할 더 나은 지식

예를 들어 문자열을 출력하는 작은 프로그램을 가져오면, 문자열을 참조하는 명령어에서 호출 함수를 찾을 수 있습니다. 그 함수를 선택하면 디컴파일 창에서 조건과 호출 순서를 확인합니다. 의미를 알아낸 함수에 이름을 붙이면 다른 호출 지점에서도 이해가 쉬워집니다.

분석 흐름을 설명하는 개념 예시이며 실제 출력은 환경에 따라 달라집니다.
실행 파일의 바이트
    → 어셈블리 명령어
    → p-code 연산
    → 제어 흐름·데이터 흐름 분석
    → C 형태 의사 코드
    → 분석가의 이름·타입 보정
이 글의 학습 목표
기능을 쓰는 사람에서 구조를 읽는 사람으로

바이너리 읽기를 시작점으로 삼되, 최종 목표는 서로 다른 CPU를 하나의 분석 엔진에 연결하는 방법, 기능을 플러그인으로 확장하는 방법, 긴 작업을 취소 가능하게 설계하는 방법을 설명할 수 있게 되는 것입니다.

2왜 주목받는가

인기 수치와, 코드에서 읽히는 지속적인 관심의 이유를 구분하기

2-1. 공개된 분석 엔진 자체가 큰 학습 자산이다

TrendShift MONTHLY #24 선정과 2026-10-23 GitHub 기준 스타 93,306개, 포크 10,469개는 관심의 규모를 보여 줍니다. 다만 이 숫자만으로 특정 기능이 이번 달 상승을 이끌었다고 판단할 수는 없습니다. 다음은 저장소 구조와 공식 기능에서 해석할 수 있는 관심 요인입니다.

먼저, 디스어셈블과 디컴파일의 핵심을 소스로 공부할 수 있습니다. Ghidra/Features/Decompiler/src/decompile/cpp/에는 타입 추론·제어 흐름 복구·출력 코드가, Ghidra/Processors/에는 CPU 언어 정의가 있습니다. 결과 화면 뒤의 알고리즘까지 추적할 수 있다는 점이 교육 자료로서 강점입니다.

이 저장소의 학습 가치는 분석 결과를 얻는 데서 끝나지 않고, 그 결과가 만들어지는 경로를 끝까지 따라갈 수 있다는 데 있습니다.

2-2. GUI에서 익힌 작업을 자동화로 이어 갈 수 있다

README는 대화형 사용과 자동 실행을 함께 지원한다고 설명합니다. 소스에도 AnalyzeHeadless·HeadlessAnalyzer와 Java 스크립트 기반 클래스 GhidraScript가 있습니다. PyGhidra는 CPython에서 JVM을 시작하고 Java 분석 객체를 사용하도록 연결합니다. 따라서 작은 GUI 실습에서 시작해 함수 목록 추출이나 여러 파일의 반복 분석으로 확장할 수 있습니다.

이것을 “AI가 자동으로 모든 의미를 알아낸다”고 해석하면 곤란합니다. 여기서 설명하는 분석 경로는 파일 로더, 명령어 의미 정의, 분석기, 디컴파일러의 조합입니다. 이름과 타입이 부족한 지점에서는 사람의 검토가 여전히 중요합니다.

2-3. 비교할 때는 가격표보다 학습 목적을 먼저 정한다

도구공식 자료에서 확인되는 특징학습 관점의 선택 기준
Ghidra공개 소스, 여러 프로세서 정의, 데스크톱 UI·자동 분석·스크립트Java 프레임워크부터 C++ 디컴파일러까지 함께 읽고 싶을 때
IDA Freex86/x86-64 지원, 클라우드 디컴파일러. 무료판은 IDAPython·C++ SDK 미제공IDA의 분석 화면을 익히는 용도. 무료판과 다른 제품군의 범위를 구분할 것
Binary Ninja디컴파일·디버깅·BNIL, C++·Python·Rust API, 플러그인중간 표현과 API 중심의 바이너리 분석을 비교하며 배울 때
radare2공식 도구군에 radare2·r2pipe·iaito, 문서와 공개 저장소명령 기반 탐색과 자동화 생태계를 비교할 때

비교 범위의 출처는 IDA Free 공식 안내, Binary Ninja 공식 소개, radare 공식 도구 목록입니다(2026-10-23 기준). 에디션별 기능과 이용 조건은 달라질 수 있습니다. 위 표는 속도·정확도 순위를 매긴 벤치마크가 아닙니다.

선택을 돕는 비유

같은 현미경이라도 조작법을 익히는 수업과 광학계를 설계하는 수업은 다릅니다. Ghidra는 도구 사용과 도구 내부 설계를 연결해서 배우기 좋은 현미경입니다. 화면의 편의성만 비교하면 이 장점이 잘 드러나지 않습니다.

2-4. 최신 릴리스와 개발 코드는 다른 기준이다

2026-10-23 GitHub 기준 최신 공개 릴리스는 Ghidra_12.2_build이며 공개일은 2026-10-22입니다. 반면 커밋 16400303의 Ghidra/application.properties에는 application.version=12.3, application.release.name=DEV가 적혀 있습니다.

즉, “최신 릴리스 12.2”와 “분석 코드 12.3 DEV”는 서로 다른 대상을 가리킵니다. 아래 API와 의존성 버전은 모두 후자를 기준으로 읽어야 합니다. 특정 최신 기능이 12.2 배포판에도 그대로 들어 있다고 가정하지 않습니다.

3기술 스택 전체 지도

웹 서비스의 구분을 데스크톱 분석 도구에 맞춰 다시 그리기

Ghidra의 중심은 Java 데스크톱 애플리케이션입니다. 여기서 프론트엔드는 분석 창과 상호작용, 백엔드는 분석 모델과 엔진을 뜻합니다. 브라우저와 HTTP 서버로 나뉘는 일반적인 웹앱 구성을 떠올리면 구조를 잘못 읽기 쉽습니다.

3-1. 백엔드: Java 모델과 C++ 분석 엔진

구성실제 버전·설정역할과 근거
Java최소·컴파일러 25Ghidra/application.properties의 application.java.min·application.java.compiler
C/C++네이티브 실행 파일 decompileGhidra/Features/Decompiler/buildNatives.gradle. 디컴파일러는 JVM 밖의 프로세스로 연결됨
SLEIGH프로세서 의미 정의 언어.slaspec·.sinc에서 명령어 비트 패턴과 p-code 의미 정의
저장 모델ProgramDB + DBHandle함수·메모리·심볼 등 분석 지식을 자체 데이터베이스 계층으로 관리
기반 라이브러리Gson 2.13.2, Log4j 2.26.1, JDOM 2.0.6.1Ghidra/Framework/Generic/build.gradle의 직접 의존성
그래프JUNG 2.1.1, JGraphT 1.5.1Ghidra/Framework/Graph/build.gradle. 관계 표현·그래프 처리 기반
파서 생성ANTLR 3.5.2, JavaCC 5.0각각 Ghidra/Framework/SoftwareModeling/build.gradle, Ghidra/Features/Base/build.gradle에 선언

버전은 저장소의 선언값이며, 전체 전이 의존성을 해석한 결과는 아닙니다. C++의 언어 표준이나 모든 컴파일러 버전은 위 파일 하나만으로 고정된다고 말할 수 없습니다.

역할 구분
분석 데이터베이스와 선택적 서버 기능
ProgramDB는 분석 중 얻은 이름·타입·참조를 보관하는 프로그램 모델입니다. Ghidra Server는 프로젝트 공유를 위한 별도 기능이고, BSim은 함수 유사도 검색 기능입니다. BSim의 PostgreSQL 지원이 있다고 해서 기본 GUI 실습에 PostgreSQL 서버가 필요한 것은 아닙니다.

3-2. 프론트엔드: Swing과 Docking

Ghidra/Framework/Docking/과 Ghidra/Framework/Gui/가 창·패널·액션 같은 기반을 제공합니다. PluginTool은 AbstractDockingTool을 확장합니다. 함수 목록, 바이트 보기, 디컴파일 창을 하나의 도구 안에서 연결하는 방식입니다.

그래서 React·Vue·Electron 버전을 찾는 것이 이 프로젝트의 UI를 이해하는 출발점은 아닙니다. 중요한 것은 어느 플러그인이 패널을 만들고, 어느 서비스가 현재 프로그램을 제공하며, 어떤 이벤트로 선택 위치를 공유하는가입니다.

Swing의 화면 이벤트는 이벤트 디스패치 스레드에서 처리합니다. 오래 걸리는 분석을 이 스레드에서 기다리면 창의 입력과 다시 그리기가 밀립니다. 실제로 AutoAnalysisManager.scheduleWorker()는 Swing 스레드에서 호출하는 경우를 검사합니다. 기본 원리는 Oracle의 Swing 스레드 설명과 연결해서 읽을 수 있습니다.

3-3. 스크립팅: Java와 CPython을 잇는 PyGhidra

항목소스의 선언해석할 때 주의할 점
PyGhidra 패키지3.3.0Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/__init__.py
Pythonrequires-python = ">= 3.9"pyproject.toml은 하한을 선언. Ghidra 설정의 지원 목록은 3.9~3.15
JPypeJpype1 >=1.5.2, !=1.6.0, !=1.7.0Python과 Java 객체를 연결. 최소 버전만 보고 제외 버전을 놓치지 말 것
타입 보조typing_extensions ~=4.16동일한 pyproject.toml에 선언. packaging은 버전 미고정
동봉 의존성JPype 1.5.2, packaging 25.0Ghidra/Features/PyGhidra/build.gradle의 배포용 파일 목록

패키지의 허용 버전 범위와 배포판에 동봉하는 파일의 버전은 다른 정보입니다. api.py의 start()는 JVM과 Headless 환경을 초기화합니다. 즉, PyGhidra는 Ghidra 분석 엔진을 Python으로 새로 구현한 라이브러리가 아닙니다.

3-4. 인프라·빌드·CI

Ghidra/application.properties의 실제 설정 일부
application.version=12.3
application.release.name=DEV
application.gradle.min=9.1
application.java.min=25
application.java.compiler=25
application.python.supported=3.15, 3.14, 3.13, 3.12, 3.11, 3.10, 3.9

README.md는 빌드에 Gradle 9.1.0 이상을 안내하고, Ghidra/RuntimeScripts/support/gradle/gradle-wrapper.properties는 배포 URL에 Gradle 9.8.0을 지정합니다. 최소 요구와 래퍼가 선택한 버전을 구별해야 합니다. settings.gradle은 Framework·Features·Processors 등 여러 하위 프로젝트를 포함합니다.

gradle/support/fetchDependencies.gradle에는 Maven 밖에서 받는 파일의 URL·SHA-256·목적지가 있습니다. 예를 들어 Z3_VER="4.13.0", postgresql-15.18.tar.gz를 확인할 수 있습니다. 이들은 특정 기능과 배포 준비를 위한 의존성이며, 모든 분석에서 함께 실행되는 서비스 목록은 아닙니다.

설정 파일확인되는 동작배울 점
.github/workflows/build-ghidra.ymlpush 시 Ubuntu에서 JDK 설정을 읽어 의존성 확보 후 buildGhidra --parallel실행 요구사항을 중앙 설정에서 읽어 CI와 맞추기
.github/workflows/build-ghidra-multi-platform-artifact.yml수동 실행. Ubuntu·macOS·Windows 빌드 후 산출물 병합플랫폼별 네이티브 파일을 배포 묶음으로 합치기
gradle/javaTestProject.gradletest·integrationTest·pcodeTest 등 정의검증 대상을 실행 환경과 비용에 맞춰 나누기
docker/Dockerfilealpine:3.20, openjdk21, 릴리스 디렉토리 복사컨테이너 설정도 애플리케이션 요구사항과 대조하기
실제 설정 사이의 불일치
Dockerfile은 현재 JDK 요구사항과 맞지 않습니다

해당 Dockerfile은 JAVA_HOME=/usr/lib/jvm/java-21-openjdk와 openjdk21을 사용하지만, 현재 코드와 README는 JDK 25를 요구합니다. 또한 파일 상단은 완성된 릴리스 디렉토리에서 빌드하는 이미지라고 설명합니다. 이를 소스 개발 환경의 검증된 즉시 실행 경로로 소개해서는 안 됩니다.

4아키텍처 심화 분석

한 파일이 분석 지식으로 바뀌는 경로와 네 가지 설계 원리

4-1. 시스템을 데이터 흐름으로 펼쳐 보기

[사용자] | GUI의 단일 파일 가져오기 | Headless / 자동 가져오기 API v v [ImporterUtilities] [ProgramLoader.Builder] | LoaderService로 후보 탐색 | LoaderService로 후보 탐색 | ImporterDialog에서 선택 | 힌트·선호 후보로 선택 +----------------+--------------+ v [ElfLoader·다른 Loader] ---- [언어·컴파일러 명세] | 메모리 배치·심볼·진입점 | v | [Program / ProgramDB] <--------------+ | 메모리·명령어·함수·타입·참조·주석 | +---- 변경 이벤트 ----> [AutoAnalysisManager] | | 우선순위 큐·분석 작업 | v | <------------------- [Analyzer들] | +---- [PluginTool + 서비스] ----> [Swing / Docking UI] | +---- [DecompInterface / DecompileProcess] | 표준 입출력 파이프 | 요청 + 분석 중 필요한 정보의 콜백 v [C++ decompile 프로세스] | p-code 분석 패스 v [C 형태 의사 코드 + 분석 구조] [선택적 확장] Ghidra Server: 프로젝트 공유 BSim: 함수 유사도 검색 Debugger 에이전트: GDB·LLDB 등과 연결

이 그림의 화살표는 학습용 요약입니다. 자동 분석이 모든 함수를 미리 디컴파일한다는 뜻은 아닙니다. DecompInterface.decompileFunction()은 특정 함수를 요청하는 별도의 API이고, 분석기 가운데 디컴파일 결과를 활용하는 구성도 있습니다.

Headless와 자동 가져오기 API의 중심은 Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/ProgramLoader.java입니다. 내부 Builder.getLoadSpec()은 LoaderService.getSupportedLoadSpecs()로 후보를 받고, 언어·컴파일러 힌트가 있으면 해당 선택기를 사용합니다. 힌트가 없으면 CHOOSE_THE_FIRST_PREFERRED를 사용합니다. GUI의 단일 파일 가져오기는 별도로 ImporterDialog에서 선택한 LoadSpec을 Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java의 importSingleFile()에 전달해 로더를 호출합니다.

Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java의 findSupportedLoadSpecs()는 ELF 헤더에서 기계 종류·비트 수·엔디언 정보를 읽어 후보를 좁힙니다. 파일 확장자가 같다고 항상 같은 언어로 해석하는 구조가 아닙니다. 잘못된 CPU나 엔디언 선택은 다음 분석 전체를 흔들 수 있습니다.

오래된 예제를 읽을 때
AutoImporter를 새 코드의 출발점으로 삼지 마세요

Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/AutoImporter.java는 @Deprecated(since = "12.0", forRemoval = true)이며 ProgramLoader 사용을 안내합니다. importByUsingBestGuess()의 구현도 실제로 ProgramLoader.builder()에 위임합니다. 예전 블로그의 API가 남아 있다는 사실과 권장 API라는 판단은 다릅니다.

4-2. 패턴 ①: 플러그인과 서비스 조회로 기능을 연결한다

큰 프로그램에서 모든 창이 서로의 구체 클래스를 직접 호출하면 결합이 급격히 커집니다. Ghidra는 PluginTool에 기능을 올리고, 서비스 인터페이스를 통해 필요한 능력을 찾는 구조를 사용합니다. 이를 플러그인 구조와 서비스 로케이터로 읽을 수 있습니다.

PluginTool.java의 실제 메서드
public <T> T getService(Class<T> c) {
    return serviceMgr.getService(c);
}

Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java의 addPlugins(Plugin[])는 플러그인의 initServices()를 먼저 호출합니다. 이어 resolveDependencies()로 미해결 의존성을 확인하고, 서비스 순서를 고려한 뒤 init()에서 서비스를 획득하게 합니다.

여기서 중요한 것은 “클래스를 동적으로 읽는다”보다 서비스 공개 → 의존성 확인 → 초기화의 생명주기입니다. 플러그인 파일만 추가한다고 기능이 완성되는 것이 아니라, 필요한 서비스가 언제 준비되는지까지 설계해야 합니다.

비유

각 부서가 모든 직원의 전화번호를 외우는 대신, 사내 안내 데스크에서 “프로그램 관리 서비스”를 찾는 방식입니다. 교체와 확장은 쉬워지지만, 안내 데스크에 등록되지 않은 서비스는 찾을 수 없습니다. 따라서 초기화 순서와 실패 보고가 구조의 일부가 됩니다.

배울 점: 생성자 인자만으로 의존성을 주입하는 방식과 비교해 보세요. 서비스 조회는 실행 중 기능 구성을 유연하게 하지만, 누락된 의존성을 실행 시점에 발견할 수 있으므로 검증 경로가 중요합니다.

4-3. 패턴 ②: CPU별 의미를 공통 중간 표현으로 옮긴다

프로세서가 바뀔 때마다 디컴파일러 전체를 새로 만들면 유지보수가 어렵습니다. Ghidra는 SLEIGH로 명령어의 비트 패턴과 의미를 정의하고, p-code라는 공통 표현을 사용합니다. CPU마다 다른 명령어를 공통 의미 표현으로 바꾸면, 여러 분석 기능이 같은 기반을 공유할 수 있습니다.

핵심 용어
중간 표현 (IR)과 p-code
IR은 원래 입력과 최종 출력 사이에 두는 공통 언어입니다. p-code는 레지스터·메모리·산술·분기 같은 명령어의 동작을 표현합니다. 화면에 보이는 어셈블리 한 줄이 p-code 한 줄과 일대일로 대응한다고 가정하면 안 됩니다.

입문용으로는 Ghidra/Processors/Toy/data/languages/toyInstructions.sinc가 좋습니다. 다음은 그 파일의 add 정의에서 공백과 줄바꿈을 정리한 것입니다.

:add rs, rt is $(INSTR_PHASE)
    op1215=0xc & op0811=0x0 & rs & rt {
    addflags(rs, rt);
    rs = rs + rt;
    resultflags(rs);
}

콜론 뒤는 표시할 명령어와 피연산자이고, is 뒤는 어떤 비트 패턴에서 이 정의가 선택되는지를 나타냅니다. 중괄호 안은 명령어의 동작을 정의합니다. 덧셈뿐 아니라 플래그 변화도 포함하므로, rs = rs + rt만 보면 명령어의 효과를 놓치게 됩니다.

toy_le.slaspec은 ENDIAN="little", SIZE="4"를 정하고 공통 정의를 포함합니다. toy.sinc에는 ram·register 주소 공간과 r0부터 pc까지의 레지스터가 있습니다. 언어 정의는 단순한 명령어 이름 사전보다 훨씬 넓은 모델입니다.

Java 쪽 연결 지점은 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java의 parse()와, 같은 디렉토리의 SleighInstructionPrototype.java의 getPcode()입니다. 후자는 의미 템플릿을 바탕으로 PcodeEmitObjects를 구성하고, 상대 주소와 후속 흐름을 정리해 연산을 반환합니다.

GUI와 DecompInterface를 통한 디컴파일에서는 C++ 엔진이 명령어의 p-code를 Java에 요청할 수 있습니다. DecompileCallback의 getPcode()는 명령어 프로토타입의 getPcodePacked()를 호출해 응답을 만듭니다. C++ 쪽에도 SLEIGH 번역 구현이 있지만, 공유되는 의미 정의와 실제 요청·응답 경로를 구분해 읽어야 합니다.

4-4. 패턴 ③: 변경 이벤트를 모아 우선순위 작업으로 처리한다

AutoAnalysisManager는 분석기들을 ClassSearcher.getInstances(Analyzer.class)로 찾고, canAnalyze(program)을 통과한 분석기를 종류별 목록에 둡니다. Analyzer 인터페이스에는 getPriority()·added()·removed()·analysisEnded()가 정의되어 있습니다.

같은 관리자의 createDomainObjectListener()는 함수 추가·함수 시그니처 변경·코드 추가 등의 이벤트를 받아 해당 분석 목록에 알립니다. 이어 AnalysisScheduler가 주소 집합을 모으고, AnalysisTask를 우선순위 큐에 예약합니다. 이를 관찰자 방식의 변경 감지와 명령 객체 기반 작업 처리의 조합으로 이해할 수 있습니다.

AnalysisScheduler.java의 핵심 조건을 그대로 발췌
if (!scheduled && (!addSet.isEmpty() || !removeSet.isEmpty())) {
    analysisMgr.schedule(
        new AnalysisTask(this, analysisMgr.getMessageLog()),
        getPriority());
    scheduled = true;
}

이 조건의 의미는 “알림 한 번마다 새 작업을 무조건 만들지 않는다”입니다. added()는 addSet.add(set)으로 범위를 누적하고, 이미 예약되어 있으면 중복 예약을 피합니다. 변경을 모아 처리하는 방식은 편집기·검색 색인·데이터 동기화에도 응용할 수 있습니다.

AutoAnalysisManager.schedule()은 queue.add(cmd, priority) 후 백그라운드 분석 시작을 요청합니다. 이 관리자의 설명에서 숫자가 작을수록 높은 우선순위라고 명시합니다. “우선순위가 크다”는 일상 언어를 그대로 수치 비교로 옮기지 마세요.

또한 Analyzer.java는 클래스 이름이 Analyzer로 끝나야 검색된다고 설명합니다. 인터페이스 구현만 확인하고 끝내지 말고, 확장 지점을 찾는 규약도 확인해야 한다는 사례입니다.

4-5. 패턴 ④: 외부 프로세스를 감싸는 파사드와 재초기화

Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java는 디컴파일러 프로세스를 사용하는 상위 인터페이스입니다. openProgram()으로 프로그램을 연결하고, decompileFunction()으로 함수를 요청하며, dispose()로 정리합니다.

파사드는 복잡한 하위 절차를 소수의 사용 지점으로 감싼다는 뜻입니다. 여기서는 실행 파일 경로·파이프 통신·초기 설정·콜백·취소 같은 세부 절차가 API 뒤에 있습니다. 같은 인스턴스의 요청 메서드는 synchronized이므로, 하나를 공유하면 무제한으로 병렬 처리된다고 생각하면 안 됩니다.

DecompInterface.decompileFunction() 내부의 요청 호출
decompProcess.sendCommandTimeout("decompileAt", timeoutSecs, activeSet);

DecompileProcess.setup() 내부의 프로세스·파이프 준비
nativeProcess = runtime.exec(exepath);
nativeIn = nativeProcess.getInputStream();
nativeOut = nativeProcess.getOutputStream();

즉, 핵심 디컴파일 연결을 단순히 “JNI로 C++ 함수를 직접 호출한다”고 설명하면 틀립니다. DecompileProcess는 별도의 실행 파일을 시작하고 표준 입출력 파이프를 사용합니다. 분석 도중 C++ 쪽이 프로그램의 바이트·타입 등 추가 정보를 요청하면 Java의 콜백 경로가 응답합니다.

C++ 수신부는 Ghidra/Features/Decompiler/src/decompile/cpp/ghidra_process.cc의 DecompileAt::rawAction()입니다. 요청 주소의 함수를 찾아 현재 분석 액션의 reset()·perform()을 수행하고, 설정에 따라 분석 구조와 C 형태의 출력을 직렬화합니다.

분석 패스 구성은 같은 디렉토리의 coreaction.cc, ActionDatabase::universalAction()에서 볼 수 있습니다. ActionHeritage, ActionDeadCode, ActionInferTypes 같은 작업과 반복 적용 그룹을 조립합니다. 디컴파일을 문자열 치환으로 이해하기보다 여러 분석을 순서와 반복 조건에 맞춰 적용하는 과정으로 보게 되는 지점입니다.

DecompInterface.initializeProcess()는 프로세스 준비 상태를 확인하고 필요하면 다시 확보해 언어·컴파일러·타입 설정을 등록합니다. 이 구조는 네이티브 도구 실패 후 다음 요청을 준비하는 데 도움이 됩니다. 다만 실패했던 함수의 결과까지 자동으로 성공 처리한다는 의미는 아닙니다. 호출자는 완료 상태와 오류를 확인해야 합니다.

분석 지식은 어디에 남는가

Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java의 createManagers()는 MemoryMapDB·CodeManager·FunctionManagerDB 등을 만듭니다. 모델의 메모리·명령어·함수를 한 프로그램 객체 아래에 묶는 구조입니다.

변경의 경계는 DomainObjectAdapterDB.startTransaction()·endTransaction()으로 관리합니다. 아래의 자체 저장 계층 Ghidra/Framework/DB/src/main/java/db/DBHandle.java에도 트랜잭션 API가 있습니다. 함수 이름을 고치거나 타입을 정의하는 작업이 단순한 화면 문자열 편집으로 끝나지 않는 이유입니다.

아키텍처 읽기 과제
화면의 한 동작을 저장 모델까지 연결해 보세요

함수 이름 변경을 예로 들어, UI 액션 → 프로그램 모델 변경 → 트랜잭션 → 이벤트 → 다른 창의 반영이라는 경로를 찾아보세요. 함수 호출만 따라가기보다 데이터가 바뀌는 곳과 그 사실을 알리는 곳을 함께 표시하면 큰 코드도 읽기 쉬워집니다.

5디렉토리 구조 해부

323만 줄의 Java를 처음부터 읽지 않기 위한 지도
ghidra/ ├── Ghidra/ │ ├── application.properties 버전·JDK·Python 지원 설정 │ ├── Framework/ │ │ ├── DB/ 자체 저장 계층 │ │ ├── Project/ 프로젝트·플러그인·서비스 │ │ ├── Docking/ 패널·도구 창 기반 │ │ ├── SoftwareModeling/ 언어·주소·명령어·Program 모델 │ │ ├── Graph/ 그래프 기반 │ │ └── Emulation/ p-code 에뮬레이션 기반 │ ├── Features/ │ │ ├── Base/ 가져오기·자동 분석·스크립트 │ │ ├── Decompiler/ Java 연결부 + C++ 엔진 │ │ ├── PyGhidra/ CPython 연동 │ │ ├── FunctionGraph/ 함수 흐름 시각화 │ │ ├── GhidraServer/ 프로젝트 공유 서버 │ │ └── BSim/ 함수 유사도 검색 │ ├── Processors/ CPU별 언어 명세·분석 확장 │ ├── Debug/ 디버거 모델·에이전트 │ ├── Extensions/ 확장 모듈 │ ├── RuntimeScripts/ GUI·Headless 실행 진입점 │ └── Test/ 공통·통합 테스트 관련 모듈 ├── GhidraBuild/ 빌드 보조·IDE 플러그인·골격 ├── GhidraDocs/ 시작 안내·교육·언어 문서 ├── GPL/ 별도 라이선스 구성 요소 ├── gradle/ 공통 빌드·의존성·테스트 설정 ├── docker/ 릴리스용 컨테이너 구성 └── .github/workflows/ CI와 산출물 조립

5-1. 세 축을 먼저 구분한다

축질문대표적인 읽기 대상
Framework여러 기능이 공통으로 쓰는 기반은?ProgramDB, 주소·타입 모델, 도킹, 서비스 관리
Features사용자가 실제로 수행하는 기능은?가져오기, 자동 분석, 디컴파일, 함수 그래프
Processors어떤 CPU의 명령어를 어떻게 해석하나?Toy, x86 등의 언어 정의와 전용 분석기

settings.gradle은 이 디렉토리들을 Gradle 하위 프로젝트로 포함합니다. 따라서 디렉토리 이름은 단순한 정리 방식에 그치지 않고 빌드 경계와 연결됩니다. 모듈의 build.gradle에서 api project(':Base') 같은 선언을 보면 다른 모듈과의 의존 관계를 확인할 수 있습니다.

Ghidra/Processors/ 바로 아래 디렉토리는 39개입니다. 이는 폴더 수이며, 지원 CPU·명령어 집합·변형 언어의 정확한 개수가 아닙니다. 한 모듈에 여러 비트 수·엔디언·컴파일러 구성이 들어갈 수 있습니다.

5-2. 모듈 안에서도 목적별로 갈라진다

5-3. 처음 읽을 파일 다섯 개

  1. Ghidra/Features/Base/ghidra_scripts/HelloWorldScript.java — 상속과 run()만으로 실행 진입점을 이해합니다.
  2. Ghidra/Features/Base/src/main/java/ghidra/app/services/Analyzer.java — 분석기가 지켜야 하는 계약을 읽습니다.
  3. Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisScheduler.java — 변경 범위 누적과 중복 예약 방지를 읽습니다.
  4. Ghidra/Processors/Toy/data/languages/toyInstructions.sinc — 간단한 명령어의 비트 패턴과 의미를 연결합니다.
  5. Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompInterface.java — Java 코드에서 네이티브 분석을 사용하는 경계를 읽습니다.
큰 저장소를 읽는 방법

도시 전체의 모든 도로를 외우는 대신, 집에서 학교까지 한 경로를 먼저 걸어 보는 방식입니다. 한 입력이 한 출력이 되는 길을 확보한 다음, 갈림길에 있는 인터페이스와 저장 모델로 범위를 넓히세요.

6학습 포인트 (기술별)

읽은 구조를 작은 설명과 코드로 바꾸기

6-1. Java: 상속보다 먼저 계약과 생명주기를 읽는다

GhidraScript는 스크립트의 실행 환경을 제공하고, 작성자는 run()에 작업을 넣습니다. HelloWorldScript.java는 이 구조를 가장 짧게 보여 줍니다. 다음 예제는 그 진입 방식에 함수 목록 순회를 더한 학습용 코드입니다. 파일 이름은 클래스와 같은 ListFunctions.java로 둡니다.

import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;

public class ListFunctions extends GhidraScript {
    @Override
    public void run() throws Exception {
        if (currentProgram == null) {
            println("프로그램을 먼저 여세요.");
            return;
        }

        FunctionIterator functions = currentProgram
            .getFunctionManager().getFunctions(true);
        int count = 0;
        while (functions.hasNext() && !monitor.isCancelled()) {
            Function function = functions.next();
            println(function.getEntryPoint() + "\t" + function.getName());
            count++;
        }
        println("순회한 함수 수: " + count);
    }
}

currentProgram은 현재 분석 프로그램, monitor는 진행·취소 확인 객체입니다. getFunctions(true)는 외부 함수를 제외하고 진입 주소의 오름차순으로 순회합니다. 여기서 얻는 함수는 현재 모델에 정의된 함수이며, 파일에 존재할 가능성이 있는 모든 함수를 완전하게 찾았다는 보장은 아닙니다.

실습 아이디어: 이름이 FUN_으로 시작하는 함수 수와 전체 수를 따로 출력하세요. 단, 이름 규칙은 분석 상태에 영향을 받습니다. “이름이 기본값이다”와 “악성이다” 같은 결론을 연결하지 마세요.

6-2. 자료구조: 주소 범위와 우선순위 큐를 제품 안에서 이해한다

AddressSet은 관련 주소 범위를 모으는 데 쓰이고, AnalysisScheduler는 추가·삭제 범위를 따로 유지합니다. 단순한 정수 목록 대신 주소 공간과 범위 개념을 다루는 이유를 생각해 보세요. 서로 다른 주소 공간에 있는 같은 수치가 같은 위치를 뜻하지 않을 수 있습니다.

실습 아이디어: 종이에 주소 범위 0x1000~0x100f와 0x1008~0x1017를 겹쳐 그려 보세요. 이어 같은 분석기에 알림이 두 번 왔을 때 범위를 합쳐 처리하는 것과 작업 두 개를 따로 만드는 것의 비용을 비교합니다. 이는 실제 실행 결과를 주장하는 예제가 아니라 범위 누적의 원리를 익히는 연습입니다.

읽을 질문: 작업의 순서가 필요한 이유는 무엇인가요? 명령어가 아직 정의되지 않았는데 함수 분석이 먼저 실행되면 어떤 정보가 부족할까요? 스케줄러를 읽을 때는 큐의 구현뿐 아니라 생산되는 정보 사이의 선후 관계를 확인하세요.

6-3. 컴파일러: 제어 흐름과 데이터 흐름을 구분한다

함수 분석의 두 지도
제어 흐름과 데이터 흐름
제어 흐름은 다음에 어느 코드 블록을 실행하는지의 지도이고, 데이터 흐름은 어떤 값이 어디서 만들어져 어디로 전달되는지의 지도입니다. 조건문의 모양을 복구하려면 전자가, 변수와 타입을 이해하려면 후자가 필요합니다.

coreaction.cc의 ActionHeritage와 ActionInferTypes를 한 번에 완독하려 하지 마세요. 먼저 “같은 레지스터가 여러 번 덮어써질 때 각각의 값을 어떻게 구분할까?”, “어떤 연산이 타입 추론의 단서가 될까?”라는 질문을 만듭니다. 그다음 관련 액션이 전체 순서 어디에 놓이는지 확인합니다.

값의 버전을 구분하는 학습용 개념 예시
원래 표현:  r = 1; r = r + 2;
값을 구분:  r_1 = 1; r_2 = r_1 + 2;

분기 뒤에는 어느 경로의 값인지 합치는 표현이 필요할 수 있습니다.
경로 A: x_A = 10
경로 B: x_B = 20
합류점: x_C = 경로에 따라 x_A 또는 x_B

위 표기는 실제 Ghidra 출력이 아니라 SSA를 이해하기 위한 설명입니다. SSA는 각 값의 정의를 한 번으로 구분하는 표현 방식입니다. 같은 화면 변수 이름을 여러 값으로 추적할 수 있어 상수 전파나 사용 관계 분석에 도움이 됩니다.

실습 아이디어: 직접 작성한 조건문 함수의 원본과 의사 코드를 나란히 놓고, 어떤 분기가 합쳐지거나 사라졌는지 표시하세요. 코드 모양이 다르다는 이유만으로 틀렸다고 판단하지 말고, 가능한 입력별 동작을 비교합니다.

6-4. Python 연동: 실행 환경과 자원 소유권을 함께 읽는다

Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/api.py의 program_context()는 프로그램과 소비자 객체를 얻고, finally에서 program.release(consumer)를 호출합니다. Python의 with가 Java 객체의 사용 종료와 이어지는 사례입니다.

이미 만든 Study 프로젝트와 /sample 프로그램을 여는 학습 예제
PyGhidra 설치 및 Ghidra 설치 경로 설정이 먼저 필요합니다.
import pyghidra

pyghidra.start()
project = pyghidra.open_project("study-projects", "Study")
try:
    with pyghidra.program_context(project, "/sample") as program:
        print(program.getName())
        print(program.getFunctionManager().getFunctionCount())
finally:
    project.close()

open_project()는 기본적으로 기존 프로젝트를 엽니다. 위 예제는 실행 파일 가져오기를 대신하지 않으며, 프로젝트 내부 경로도 실제 이름에 맞춰야 합니다. 학습 중에는 편의 함수가 자동으로 처리하는 일과 직접 관리하는 일을 표로 적어 보세요.

실습 아이디어: consume_program()과 program_context()를 비교해서, 예외가 발생해도 어느 코드가 프로그램 참조를 놓아주는지 설명해 보세요. Python의 가비지 컬렉션만 믿고 외부 실행 환경의 생명주기를 생략하면 안 되는 이유를 배울 수 있습니다.

6-5. 동시성·트랜잭션: 빠른 분석보다 먼저 일관성을 생각한다

확장 기능을 잘 만드는 첫 조건은 정상 결과뿐 아니라 취소·실패·정리의 경계를 정확하게 다루는 것입니다. 분석 도중 사용자가 취소할 수 있고, 네이티브 프로세스가 실패할 수 있으며, 모델 변경이 중간에 멈출 수도 있습니다.

모델을 수정하는 일반 Java 코드에서는 트랜잭션의 성공 여부를 명시해야 합니다. 아래는 이미 트랜잭션을 관리하는 스크립트 실행 틀과 구분해서 읽을 개념 예제입니다. 전제는 program이 열려 있고, 호출자가 변경 경계를 관리하는 환경이라는 것입니다.

int tx = program.startTransaction("분석 메모 갱신");
boolean commit = false;
try {
    // 여기에서 프로그램 모델을 수정합니다.
    commit = true;
}
finally {
    program.endTransaction(tx, commit);
}

핵심은 finally와 성공 플래그입니다. 실제 기능을 작성할 때는 상위 실행 틀이 이미 트랜잭션을 열었는지도 확인해야 합니다. 이 예제의 빈 본문만 복사하는 것으로 변경·되돌리기 동작이 검증되지는 않습니다.

6-6. 테스트: 무엇을 보장하는지 문장으로 바꿔 읽는다

Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerCachingTest.java는 새 함수로 이동하면 캐시 미스가 증가하는지, 이전 함수로 돌아오면 히트가 증가하는지 검사합니다. 성능과 관련된 기능도 시간 측정만으로 검증할 필요는 없다는 사례입니다.

Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/sleigh/grammar/SleighPreprocessorTest.java의 testExternal()은 입력·기대 출력 파일을 연결하고 결과를 줄 단위로 비교합니다. 파서 주변 기능에서는 사람이 읽을 수 있는 작은 기대 결과가 중요한 자산이 됩니다.

실습 아이디어: 두 테스트에서 각각 하나의 검증 문장을 골라 “어떤 사전 상태에서, 어떤 행동 뒤에, 무엇이 같아야 하는가”로 다시 적어 보세요. 이름에 테스트가 들어간 파일을 세는 것보다 기능의 계약을 이해하는 데 도움이 됩니다.

7하드웨어 / 시스템 요구사항

공식 하한, 코드의 설정, 학습용 권장을 따로 보기

7-1. 기본 실행의 공식 요구사항

다음 하한은 커밋 16400303의 GhidraDocs/GettingStarted.md 기준입니다. 운영체제 목록에 적힌 하한만으로 최신 JDK와 모든 네이티브 구성 요소의 호환성이 보장되는 것은 아닙니다. 실제 배포판의 안내도 함께 확인해야 합니다.

항목공식 문서의 값초보자에게 의미하는 것
RAM최소 4GB분석 파일과 디컴파일 작업 크기에 따라 더 필요할 수 있음
저장 공간설치 바이너리용 1GB프로젝트·캐시·소스 빌드 공간까지 포함한 수치가 아님
WindowsWindows 10 빌드 1809 이상64비트 JDK와 플랫폼 네이티브 파일의 호환성을 확인
Linux / macOSLinux, macOS 10.13 이상문서의 OS 목록. 네이티브 파일과 JDK의 별도 제약도 고려
JDK25, 64비트현재 코드 설정과 README도 같은 최소 버전을 가리킴
Python전체 안내 3.9~3.15PyGhidra는 3.9~3.15, Debugger 안내는 3.7~3.14로 범위가 다름

Debugger와 PyGhidra를 함께 쓰려면 두 범위의 공통 부분을 확인해야 합니다. 문서상 Python 지원 범위와 선택한 JPype·디버거 패키지가 실제 환경에 설치 가능한지는 별도로 확인할 문제입니다.

7-2. 공부를 위한 현실적인 준비

학습 규모이 글의 준비 권장이유
작은 C 예제 분석RAM 8GB 이상, 여유 공간 수 GBIDE·브라우저·분석 창을 함께 열 여유. 성능 측정값은 아님
여러 파일·큰 프로젝트RAM 16GB 이상부터 작업량에 맞춰 조정프로젝트 저장·인덱스·디컴파일 작업의 메모리 여유
Ghidra 자체 빌드JDK·Gradle·Python·C/C++ 도구와 충분한 디스크의존성 다운로드·생성 코드·플랫폼 산출물이 추가됨

위 권장은 공식 최소 사양과 구분한 학습 환경 제안입니다. GPU는 인용한 최소 요구 목록에 없습니다. 기본 실습을 위해 특정 GPU 성능이나 CUDA 환경을 준비할 근거는 없습니다. CPU 코어 수에 비례해 모든 분석 단계가 같은 비율로 빨라지는 것도 아닙니다.

7-3. JVM 힙과 전체 메모리는 다르다

Ghidra/RuntimeScripts/support/analyzeHeadless는 MAXMEM_DEFAULT=2G를 지정합니다. 환경변수 GHIDRA_MAXMEM 또는 Headless 전용 GHIDRA_HEADLESS_MAXMEM으로 바꿀 수 있습니다. launch.sh가 이 값을 JVM의 -Xmx로 전달합니다.

JVM 힙 상한을 2GB로 설정해도, C++ 디컴파일러와 운영체제를 포함한 전체 메모리 사용량이 2GB로 제한되지는 않습니다. 별도 프로세스와 JVM의 다른 메모리 영역도 있기 때문입니다. GUI의 ghidraRun은 MAXMEM_DEFAULT가 비어 있으므로 Headless의 기본값을 GUI에도 그대로 적용하면 안 됩니다.

배포판에서 쓰는 Headless 실행 옵션의 학습 예시
mkdir -p study-projects
GHIDRA_HEADLESS_MAXMEM=4G ./support/analyzeHeadless \
  study-projects Study \
  -import samples/sample \
  -analysisTimeoutPerFile 120 \
  -max-cpu 2

-analysisTimeoutPerFile은 파일별 자동 분석 시간 제한 옵션이고, -max-cpu는 분석에서 사용할 CPU 수를 제한하는 옵션입니다. 모든 스크립트·입출력·전체 프로세스에 대한 강제 종료 시간으로 해석하지 마세요. 작은 실습부터 완료 상태와 로그를 읽으며 값을 조정합니다.

7-4. 사용과 개발은 준비물이 다르다

공식 배포판을 사용하는 실습은 전체 소스 빌드를 요구하지 않습니다. 자체 빌드에는 README 기준 GCC 또는 Clang과 make가 필요하고, Windows에는 Visual Studio 2017 이상 또는 C++ Build Tools의 MSVC·Windows SDK·C++ ATL 구성이 안내되어 있습니다.

일부 빌드 타깃이 FreeBSD·OpenBSD를 포함한다는 사실과, 시작 안내가 공식 지원 플랫폼으로 Windows·Linux·macOS를 나열한다는 사실도 구분해야 합니다. 빌드 정의에 타깃 문자열이 있다는 것만으로 모든 환경에서 배포·실행이 검증되었다고 결론 내리지 않습니다.

8직접 해볼 수 있는 실습 과제

각 과제마다 입력·관찰 항목·완료 기준을 정하기

연습 대상은 직접 작성한 작은 프로그램으로 통일합니다. 실행 파일 자체를 실행하지 않아도 가져오기·함수 탐색·디컴파일을 공부할 수 있습니다. 아래 명령과 스크립트는 학습 환경에서 수행할 예시이며, 사용하는 배포판의 JDK·API 요구사항을 먼저 맞춥니다.

실습의 완료 기준은 도구가 한 번 실행되는 것이 아니라, 입력이 어떤 분석 결과로 바뀌었는지 근거를 들어 설명하는 것입니다.

과제 1

내 함수의 기계어와 의사 코드 비교 입문

목표: C 함수·어셈블리·의사 코드가 서로 다른 표현이라는 것을 확인합니다. 다음 파일을 sample.c로 준비합니다.

#include <stdio.h>

int adjust(int value) {
    if (value < 10) {
        return value + 3;
    }
    return value * 2;
}

int main(int argc, char **argv) {
    printf("%d\n", adjust(argc));
    return 0;
}

Linux 또는 WSL의 GCC 환경에서는 다음처럼 두 파일을 만들 수 있습니다. 다른 환경에서는 해당 플랫폼의 C 컴파일러와 옵션을 사용합니다.

gcc -O0 -g -o sample-debug sample.c
gcc -O2 -o sample-opt sample.c
  1. 새 프로젝트를 만들고 두 파일을 가져옵니다. 자동으로 선택된 파일 형식·프로세서·컴파일러 구성을 기록합니다.
  2. 자동 분석 후 adjust와 main을 찾아, Listing과 디컴파일 창을 함께 봅니다.
  3. 조건문과 함수 호출이 두 빌드에서 어떻게 표현되는지 비교합니다. 인라인 여부는 실제 결과를 보고 판단합니다.

완료 기준: 조건 비교와 반환값 계산에 대응하는 위치를 찾고, 원본과 다른 표현 두 가지를 설명합니다. -g가 있는 빌드와 없는 빌드의 이름 정보 차이도 구분합니다.

막히면: 원래 함수 이름이 보이지 않을 수 있습니다. 출력 문자열의 참조와 호출 관계를 이용하세요. 최적화에서 특정 함수가 반드시 사라진다고 가정하지 않습니다.

과제 2

함수 목록을 스크립트로 읽기 입문

목표: GUI에서 본 함수 목록을 Program API로 읽습니다. 6절의 ListFunctions.java를 Script Manager에서 실행하고, 주소·이름·총 개수를 관찰합니다.

  1. 프로그램을 연 상태와 열지 않은 상태의 동작을 비교합니다.
  2. 프로그램 안에서 함수 하나의 이름을 바꾼 뒤 다시 목록을 출력합니다.
  3. 이름 앞부분별 개수를 집계하는 코드를 더하고, 원본 목록에서 표본을 골라 검산합니다.

완료 기준: 화면의 함수 이름 수정이 출력에 반영되는 이유를 currentProgram → FunctionManager → Function 경로로 설명합니다.

추가 질문: 분석기가 아직 발견하지 못한 함수가 있다면 이 스크립트가 찾아줄까요? 이 코드는 기존 모델 순회이므로, 함수 발견 알고리즘을 추가한 것과 다릅니다.

과제 3

선택한 함수의 디컴파일 결과 요청 중급

목표: 성공·실패·자원 정리를 포함한 API 사용을 익힙니다. 함수 안의 주소를 선택하고 DecompileSelection.java를 실행합니다.

import ghidra.app.script.GhidraScript;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.program.model.listing.Function;

public class DecompileSelection extends GhidraScript {
    @Override
    public void run() throws Exception {
        if (currentProgram == null || currentAddress == null) {
            println("프로그램과 함수 안의 주소를 선택하세요.");
            return;
        }
        Function function = getFunctionContaining(currentAddress);
        if (function == null) {
            println("선택한 주소를 포함하는 함수가 없습니다.");
            return;
        }

        DecompInterface decompiler = new DecompInterface();
        try {
            if (!decompiler.openProgram(currentProgram)) {
                println(decompiler.getLastMessage());
                return;
            }
            DecompileResults result = decompiler
                .decompileFunction(function, 30, monitor);
            if (result.decompileCompleted() &&
                    result.getDecompiledFunction() != null) {
                println(result.getDecompiledFunction().getC());
            }
            else {
                println("완료되지 않음: " + result.getErrorMessage());
                println("시간 초과: " + result.isTimedOut());
                println("취소 여부: " + result.isCancelled());
            }
        }
        finally {
            decompiler.dispose();
        }
    }
}

완료 기준: openProgram() 실패와 함수 디컴파일 실패를 구분하고, 중간에 반환해도 dispose()가 호출되는 이유를 설명합니다. 30은 함수 디컴파일 요청의 초 단위 제한이며 파일별 자동 분석 제한과 다릅니다.

소스 추적: DecompileResults.java의 decompileCompleted()·isTimedOut()·isCancelled()를 읽고, 결과 문자열이 없을 때 확인할 상태를 정리합니다.

과제 4

반복 분석을 Headless 작업으로 바꾸기 중급

목표: 화면에서 하던 읽기 작업을 재현 가능한 명령으로 옮깁니다. 샘플 파일은 samples/, ListFunctions.java는 study-scripts/에 둔다고 가정합니다. 다음은 배포판 루트에서 실행하는 Bash 계열 셸의 예시입니다.

mkdir -p study-projects
./support/analyzeHeadless study-projects BatchStudy \
  -import samples \
  -recursive \
  -scriptPath study-scripts \
  -postScript ListFunctions.java \
  -analysisTimeoutPerFile 120 \
  -max-cpu 2

-postScript는 가져오기·분석 뒤의 스크립트 실행 지점입니다. 자동 분석이 시간 초과나 오류로 끝나면 함수 목록도 불완전할 수 있으므로, 출력 숫자와 분석 로그를 함께 읽습니다. 명령은 프로젝트 데이터를 생성하므로 기존 학습 프로젝트와 다른 이름을 사용하세요.

완료 기준: 파일별 이름·함수 수·분석 완료 상태를 기록하고, GUI 결과와 표본을 대조합니다. 서로 다른 파일을 가져온 결과가 섞이지 않도록 프로그램 이름도 출력에 넣어 보세요.

확장: 같은 파일을 반복 처리할 때 새로 가져올지 기존 프로젝트를 처리할지 구분하는 실행 절차를 문서화합니다. 반복 실행 가능성은 단순히 명령을 한 줄로 만드는 것보다 넓은 문제입니다.

과제 5

Toy 명령어에서 분석기까지 설계 추적 고급

목표: 프로세서 정의와 공통 분석의 경계를 설명합니다. toyInstructions.sinc에서 add·sub를 고르고 피연산자, 비트 패턴, 플래그 효과를 표로 만듭니다.

  1. toy.sinc에서 각 레지스터와 주소 공간의 정의를 찾아 연결합니다.
  2. SleighInstructionPrototype.getPcode()에서 의미 템플릿이 연산으로 바뀌는 경로를 표시합니다.
  3. Ghidra/Processors/Toy/src/main/java/ghidra/app/plugin/core/analysis/ToyAnalyzer.java의 canAnalyze()를 읽고, Toy 전용 분석기의 적용 범위를 설명합니다.
  4. 별도 실습 확장을 설계한다면 필요한 언어 파일·분석기 이름·등록 규약·검증 입력을 목록으로 적습니다.

완료 기준: “명령어를 해석할 수 있다”, “의미를 p-code로 표현한다”, “그 의미를 이용해 추가 사실을 발견한다”를 각각 다른 단계로 설명합니다.

검증 계획: 0·양수·음수·오버플로가 생길 수 있는 경계값을 골라 결과와 플래그의 기대값을 먼저 작성하세요. 그 후에야 실제 구현 결과를 비교할 기준이 생깁니다.

9관련 기술 심화 학습 로드맵

8주 동안 사용법·모델·엔진을 순서대로 연결하기

주당 4~6시간을 공부하는 입문자를 가정한 제안입니다. Java와 C가 모두 처음이라면 앞의 두 주를 늘리세요. 한 번에 전체 엔진을 읽기보다, 매주 설명 가능한 작은 결과물 하나를 남기는 편이 학습의 진척을 확인하기 쉽습니다.

주차학습 주제·읽을 곳만들 결과물
1주C 함수·포인터·컴파일, 디버그 심볼과 최적화. GhidraDocs/GettingStarted.md 같은 C 코드를 두 옵션으로 빌드한 파일과 환경 기록
2주레지스터·스택·분기·호출, GUI의 Listing·문자열 참조·디컴파일 창 샘플 함수 하나의 실행 경로와 인자·반환값 설명
3주Java 상속·인터페이스·반복자·예외. HelloWorldScript와 GhidraScript 함수 주소와 이름을 출력하고 취소를 확인하는 스크립트
4주ProgramLoader·ElfLoader·ProgramDB, 파일 형식과 메모리 모델 파일 헤더 → LoadSpec → 프로그램 모델의 흐름도
5주Analyzer·AnalysisScheduler·AutoAnalysisManager 이벤트 두 번이 예약 작업으로 합쳐지는 과정을 그린 표
6주Toy SLEIGH·p-code, 제어 흐름·데이터 흐름·SSA의 기초 명령어 두 개의 패턴·효과·경계값 기대 결과표
7주DecompInterface·DecompileProcess·DecompileResults 함수 요청에서 C++ 처리까지의 순서도와 실패 상태표
8주Headless·PyGhidra·플러그인 생명주기, 테스트의 사전 상태와 기대 결과 샘플 여러 개의 분석 보고서와 재현 절차

배경 지식이 다르면 입구도 바꾼다

매주 확인할 질문
입력·출력·상태·실패를 한 문장씩 설명할 수 있는가

“어떤 입력을 받는가?”, “어떤 모델이 바뀌는가?”, “어디에 결과가 남는가?”, “실패하거나 취소되면 누가 정리하는가?”에 답해 보세요. 이 질문은 디컴파일러뿐 아니라 데이터 파이프라인과 개발 도구를 공부할 때도 그대로 쓸 수 있습니다.

10핵심 키워드 사전

화면에서 본 용어를 소스의 책임과 연결하기
용어쉬운 설명
SRE소프트웨어 역공학. 완성된 프로그램에서 구조와 동작을 이해하는 활동입니다. 이 문맥에서는 사이트 신뢰성 엔지니어링을 뜻하지 않습니다.
바이너리바이트 형태의 데이터입니다. 여기서는 주로 컴파일된 실행 파일이나 라이브러리를 가리킵니다.
ELF / PE실행 파일을 담는 형식입니다. 코드·데이터·심볼 등을 어디에 저장하고 어떻게 해석할지 알려주는 구조입니다.
디스어셈블기계어를 사람이 읽을 수 있는 어셈블리 명령어로 해석하는 과정입니다. 코드와 데이터를 구별하는 문제가 함께 있습니다.
디컴파일기계어의 동작을 조건문·변수·함수 호출 같은 높은 수준의 표현으로 재구성하는 과정입니다.
의사 코드동작을 이해하도록 표현한 코드 모양의 결과입니다. 원본과 동일하거나 그대로 컴파일된다는 보장은 없습니다.
심볼주소와 이름을 연결하는 정보입니다. 디버그 정보나 분석가의 이름 지정은 프로그램을 이해하는 데 도움을 줍니다.
엔디언여러 바이트로 된 값을 저장하는 순서입니다. 같은 바이트도 순서 규칙에 따라 다른 수가 됩니다.
주소 공간주소가 속하는 영역입니다. 레지스터와 RAM처럼 서로 다른 영역을 같은 숫자만으로 혼동하지 않게 합니다.
호출 규약함수 인자를 어디에 두고 반환값을 어떻게 전달하며 어떤 레지스터를 보존할지 정한 약속입니다.
LoadSpec어떤 로더와 언어·컴파일러 구성으로 파일을 가져올지에 대한 후보 정보입니다.
Program / ProgramDB분석 대상의 메모리·함수·타입·참조 등을 담는 모델과 그 데이터베이스 구현입니다.
SLEIGHCPU 명령어의 비트 패턴과 의미를 정의하는 언어입니다. 프로세서 지원과 공통 분석을 잇습니다.
p-code명령어의 효과를 공통 연산으로 표현하는 Ghidra의 중간 표현입니다. 여러 CPU의 동작을 비슷한 틀로 다룹니다.
Varnodep-code에서 값을 표현하는 단위입니다. 레지스터·메모리·임시 값 등과 연결되며 소스 변수와 일대일 대응하지 않을 수 있습니다.
기본 블록보통 중간에 다른 경로로 갈라지지 않는 연속 명령어 묶음입니다. 제어 흐름 그래프의 기본 단위가 됩니다.
제어 흐름 그래프기본 블록을 정점으로, 실행이 이동할 수 있는 관계를 간선으로 그린 지도입니다.
SSA값이 정의되는 시점을 구분하는 중간 표현 방식입니다. 각 값의 출처와 사용 관계를 추적하기 쉽게 합니다.
분석 패스큰 분석 과정 안의 한 작업 단계입니다. 타입 추론·불필요한 코드 제거 같은 작업을 조합하거나 반복합니다.
Analyzer프로그램과 주소 범위에 대해 특정 분석을 수행하는 확장 계약입니다. 적용 가능 여부·우선순위·취소 처리를 함께 봅니다.
플러그인도구에 기능을 더하는 모듈입니다. Ghidra에서는 서비스 공개·의존성 확인·초기화 같은 생명주기가 있습니다.
파사드복잡한 하위 절차를 쓰기 쉬운 인터페이스로 감싸는 구조입니다. DecompInterface가 좋은 읽기 대상입니다.
IPC서로 다른 프로세스가 정보를 주고받는 방법입니다. Java와 C++ 디컴파일러 연결에서는 파이프가 핵심입니다.
Headless화면 없이 수행하는 실행 방식입니다. 명령 옵션과 스크립트로 반복 분석 작업을 만들 수 있습니다.
PyGhidra / JPypeCPython에서 Ghidra의 Java 기능을 사용하는 구성과 그 연결 기술입니다. Python 프로세스 안에 JVM을 시작합니다.
트랜잭션관련 변경을 하나의 완료·취소 경계로 다루는 단위입니다. 모델의 일관성과 변경 관리에 중요합니다.
TaskMonitor긴 작업의 진행과 취소를 다루는 객체입니다. 반복 작업도 주기적으로 취소 요청을 확인해야 합니다.
Ghidra Server분석 프로젝트를 공유하기 위한 서버 기능입니다. 개인 GUI 분석의 필수 설치 항목은 아닙니다.
BSim함수의 유사성을 검색하는 기능입니다. 기본 프로젝트 데이터베이스와 목적을 구분해야 합니다.

용어를 외운 뒤에는 짝을 만들어 설명해 보세요. “SLEIGH는 의미를 정의하고 p-code는 그 의미를 공통 연산으로 표현한다”, “ProgramDB는 지식을 저장하고 Analyzer는 지식을 더한다”처럼 관계로 말하면 소스 구조가 더 잘 기억됩니다.

11참고 링크

공식 설명·고정된 코드·현황 수치를 구분해 다시 찾아보기

11-1. 공식 저장소와 시작 문서

11-2. 본문의 핵심 코드로 바로 가기

11-3. 빌드 설정과 관련 기술

11-4. 현재 상태 (2026-10-23 확인)

항목값·기준
선정TrendShift MONTHLY #24
공식 저장소NationalSecurityAgency/ghidra
설명소프트웨어 역공학 프레임워크
스타 / 포크93,306 / 10,469 — 2026-10-23 GitHub 기준
생성일2019-03-01 — GitHub 저장소 생성일이며 최초 개발 시작일을 뜻하지 않음
최근 push2026-10-22 — GitHub 기준
최신 릴리스Ghidra_12.2_build — 2026-10-22 공개
분석 코드의 최근 커밋16400303 — 2026-10-21. BigFloat·AbstractComplexDataType 관련 변경 병합
코드의 자체 버전12.3 DEV — Ghidra/application.properties
주 언어Java — GitHub 기준. 네이티브 디컴파일러와 프로세서 명세도 큰 비중을 차지함
파일 수20,621개 — .git 제외 일반 파일 전체
주요 코드 규모Java 3,233,678줄 · C/C++ 261,912줄 · SLEIGH 311,059줄 · Python 48,177줄
라이선스최상위 LICENSE는 Apache-2.0. GPL/·licenses/ 등 구성 요소별 고지도 별도 존재

11-5. 언어별 규모와 집계 범위

분류집계 확장자파일 수 / 줄 수
Java.java15,828개 / 3,233,678줄
C/C++.c .cc .cpp .h .hh .hpp430개 / 261,912줄
SLEIGH.slaspec .sinc358개 / 311,059줄
Python.py146개 / 48,177줄
Gradle.gradle183개 / 15,719줄
XML.xml281개 / 46,509줄

커밋 16400303 기준 파일의 줄바꿈 문자 수를 합산한 값입니다. 주석·빈 줄·테스트·예제·생성된 소스·저장소 안의 타사 코드를 포함합니다. XML 형태의 .cspec·.ldefs 등은 확장자가 다르므로 XML 행에 포함하지 않습니다. 위 합계를 순수 구현량이나 기능 수, 품질의 척도로 해석하면 안 됩니다.

집계 원리는 다음과 같습니다. 읽기 전용으로 파일 내용을 세는 예제이며, 저장소 루트를 현재 디렉토리로 두는 것을 전제로 합니다.

from pathlib import Path
from collections import defaultdict

extensions = {".java", ".c", ".cc", ".cpp", ".h", ".hh", ".hpp",
              ".py", ".sinc", ".slaspec", ".gradle", ".xml"}
counts = defaultdict(lambda: [0, 0])
for path in Path(".").rglob("*"):
    if ".git" in path.parts or not path.is_file():
        continue
    if path.suffix in extensions:
        counts[path.suffix][0] += 1
        counts[path.suffix][1] += path.read_bytes().count(b"\n")
for extension, result in sorted(counts.items()):
    print(extension, result)
실습 전에 알아둘 한계
의사 코드·버전·실행 환경의 차이를 기억하세요

의사 코드는 분석 가설입니다. 최적화·누락된 타입·간접 호출·잘못된 함수 경계에 따라 표현과 정확성이 달라집니다. 중요한 동작은 어셈블리·참조·타입·입출력 조건과 함께 확인해야 합니다.

공개 릴리스와 개발 커밋은 다릅니다. 최신 릴리스 정보는 12.2, 본문 소스와 설정은 16400303의 12.3 DEV 기준입니다. API·JDK·Python 의존성을 실제 사용하는 배포판과 대조하세요. 특히 Dockerfile의 JDK 21 설정은 현재 요구 JDK 25와 불일치합니다.

실행 환경에 따라 결과가 달라질 수 있습니다. 빌드·실행 호환성과 실습 결과는 사용하는 배포판·플랫폼·입력 파일에 따라 달라집니다. 도구 간 성능·정확도를 비교하려면 같은 조건의 별도 측정이 필요합니다. 줄 수는 규모의 참고값이며, 트렌딩 선정만으로 인기 상승의 원인을 특정할 수 없습니다.