1P by GN⁺ | ★ favorite | 댓글 1개
  • 이 Ghidra 확장 프로그램은 프로그램의 일부를 오브젝트 파일로 내보내며, 내보낸 파일은 심볼과 재배치 테이블 같은 유효한 메타데이터를 포함해 툴체인에서 다시 처리할 수 있음
  • 주요 용도는 고급 바이너리 패치, 소프트웨어 포팅, 파일 형식 변환, 라이브러리 생성, 디컴파일 프로젝트에서 프로그램을 여러 오브젝트 파일로 나눠 재구현하는 작업임
  • 지원 조합은 COFF x86/x86_64, ELF x86/x86_64/MIPS, OMF x86이며, COFF MIPS·OMF x86_64·OMF MIPS는 지원하지 않음
  • 사용자는 Ghidra Listing에서 추출할 주소 범위를 선택하고 Relocation table synthesizer 분석기를 실행한 뒤, File > Export Program…에서 재배치 가능한 오브젝트 파일 exporter를 호출함
  • 재배치 테이블 합성은 Ghidra 데이터베이스 정확도에 의존하므로 잘못되거나 누락된 정보가 있으면 재배치가 깨지거나 누락될 수 있고, 내보내기 직전에 분석기를 다시 실행하는 것이 안전함

무엇을 하는 확장 프로그램인가

  • Object file exporter extension for Ghidra는 프로그램의 일부를 오브젝트 파일로 내보낼 수 있게 하는 Ghidra 확장 프로그램임
  • 생성된 오브젝트 파일은 심볼, 재배치 테이블 같은 유효한 메타데이터를 포함함
  • 이 메타데이터 덕분에 내보낸 오브젝트 파일을 툴체인에서 직접 재사용해 추가 처리할 수 있음

활용 사례

  • Advanced binary patching
    • 원본 부분과 수정된 부분을 수작업으로 맞추는 대신 링커를 활용해 함께 맞물리게 할 수 있음
  • Software ports
    • 프로그램에서 시스템 독립적인 코드를 분리하고 나머지를 대체할 수 있음
  • 프로그램이나 오브젝트 파일을 한 파일 형식에서 다른 파일 형식으로 변환할 수 있음
  • 프로그램 일부 추출라이브러리 생성
    • 프로그램 일부를 추출해 다른 맥락에서 재사용할 수 있음
  • 디컴파일 프로젝트에서는 프로그램을 여러 오브젝트 파일로 나누고, 이를 Ship of Theseus 방식으로 재구현할 수 있음

지원 아키텍처와 오브젝트 파일 형식

  • 지원 매트릭스는 다음과 같음
    • COFF: x86, x86_64 지원 / MIPS 미지원
    • ELF: x86, x86_64, MIPS 지원
    • OMF: x86 지원 / x86_64, MIPS 미지원

빌드와 설치

  • CLI 빌드 절차
    • 저장소를 clone함
    • GHIDRA_INSTALL_DIR 환경 변수를 Ghidra 설치 디렉터리로 지정함
    • gradle buildExtension을 실행함
    • 생성된 Ghidra 확장 아카이브는 dist/ 디렉터리에 만들어짐
  • GitHub Maven 저장소에서 패키지를 내려받으려면 인증이 필요함
    • read:packages 권한이 있는 GitHub classic token을 만들고 ${GRADLE_USER_HOME}/gradle.propertiesgithubToken=ghp_xxx를 추가함
    • 또는 gradle installStandaloneDeps를 실행해 submodule의 vendored dependency를 빌드하고 설치함
  • 설치 절차
    • releases page에서 확장을 내려받거나 로컬에서 빌드함
    • Ghidra에서 File > Install Extensions… 로 확장을 설치함
    • CodeBrowser 창의 File > Configure > Experimental에서 RelocationTableSynthesizedPlugin 플러그인을 활성화함

사용 흐름과 주의점

  • 기본 사용 절차
    • Listing 뷰에서 추출할 주소 집합을 선택함
    • one-shot mode로 제공되는 Relocation table synthesizer 분석기를 실행함
    • File > Export Program… 에서 재배치 가능한 오브젝트 파일 exporter를 호출함
  • 재구성된 재배치는 Window > Relocation table (synthesized) 에서 볼 수 있음
  • 상세 평가 리포트는 Analysis > Auto Analyze... 대화상자에서 Relocation table synthesizer 분석기의 Evaluation report policy 옵션을 설정해 활성화할 수 있음
  • 확장 프로그램을 쓰기 전에 전체 프로그램을 미리 모두 리버스 엔지니어링할 필요는 없음
  • 성공적인 delinking은 일반적으로 내보낼 부분과 외부 참조 안의 메타데이터에 크게 의존함
    • 재배치 위치로 쓰이는 함수와 포인터
    • 재배치 대상으로 쓰이는 심볼 footprint
    • 둘 사이의 참조
  • Relocation table synthesizer 분석기는 Ghidra 데이터베이스의 정확도에 의존함
    • 부정확하거나 누락된 정보는 분석 중 깨진 재배치나 누락된 재배치로 이어질 수 있음
  • 오브젝트 파일 exporter는 Relocation table synthesizer 분석 결과에 의존함
    • 의심스러운 경우 오브젝트 파일을 내보내기 직전에 분석기를 실행해 재배치 테이블이 최신인지 확인해야 함

동작 원리

  • 오브젝트 파일은 세 부분으로 구성됨
    • 재배치 가능한 섹션 바이트
    • 심볼 테이블

      • 재배치 테이블
      • 링커가 여러 오브젝트 파일에서 실행 파일을 만들 때 수행하는 작업
      • 섹션을 메모리에 배치함
      • 가상 주소 공간에서 심볼 주소를 계산함
      • 최종 심볼 주소를 기준으로 섹션 바이트에 재배치를 적용함
      • 일반적으로 이 과정이 끝나면 재배치 테이블은 폐기됨
      • 디버깅 심볼이 유지되지 않으면 심볼 테이블도 폐기되고, 재배치할 수 없는 섹션 바이트만 남음
      • 이 확장은 신중한 분석으로 해당 데이터를 다시 만들 수 있으며, 이를 통해 프로그램을 다시 오브젝트 파일로 delink할 수 있음

댓글과 토론

Hacker News 의견들
  • 여기서 보니 반갑다. 정말 멋진 프로젝트라고 생각하고, MS COFF 지원 추가를 도왔음
    다만 내 초기 PR은 이미 있던 ELF 지원보다 꽤 부족했기 때문에 문제가 생긴다면 아마 내 탓일 수 있음. 그래도 개선되는 게 보인다
    아직 큰 작업에 써보진 않았지만, Visual Studio 2003으로 컴파일한 Hello World 실행 파일을 델링크해서 Linux x86의 GCC+glibc로 다시 링크하고, 다시 MinGW+msvcrt로 재링크해본 게 제일 재미있었음
    Hello World보다 큰 건 아직 어렵고, 특히 Ghidra를 잘 모르는 편이라 큰 바이너리에서 델링크할 범위를 고르는 좋은 방법도 아직 못 찾았다
    마침 오늘 이 도구의 Nixpkgs 파생 패키지가 병합돼서 NixOS unstable에서는 ghidra.withExtensions로 설치 가능함. 위치는 ghidra-extensions.ghidra-delinker-extension
    단 며칠 전 새 버전이 나왔는데 PR을 리베이스하지 않아서 지금은 구버전이고, 곧 업데이트를 올려볼 예정임

    • 델링크할 대상을 추적하는 한 가지 방법은 프로그램 트리 안의 폴더와 프래그먼트를 쓰는 것임
      예를 들어 원래 실행 파일을 구성하던 여러 목적 파일의 이름과 범위를 알아낸 Ghidra 프로그램이 있다면, 해당 폴더나 프래그먼트를 우클릭 > Select Addresses로 통째 선택할 수 있음
      재배치 합성 분석기와 내보내기도 각각 스크립트화하거나 프로그램의 트리 관리자를 통해 자동화할 수 있어서, 원하는 범위를 손으로 선택하고 분석기와 내보내기를 수동 실행할 필요가 줄어듦
  • 꽤 흥미로워 보이고, 몇 년 전에 접었던 게임 리버스 엔지니어링 프로젝트를 다시 들여다보고 싶어짐
    이걸 어떻게 쓰고 그 출력물을 어떻게 활용하는지 끝까지 보여주는 완전한 예제가 있으면 좋겠음

  • 실행 파일의 어느 섹션을 내보낼지 알아내는 데 일이 얼마나 드는지 궁금함
    2008~2015년쯤의 비교적 현대적인 Win32 게임을 목적 파일로 내보낸 뒤, 몇 시간 안에 다시 완전한 실행 파일로 컴파일/링크하는 게 현실적인가?

    • 변수나 함수 중간을 자르지만 않으면 꽤 자유롭게 내보낼 수 있고, 원래 목적 파일 경계를 꼭 따라갈 필요는 없음
      무엇을 내보낼지는 별개 문제라 프로그램에 대한 지식이 필요함. 디버그 심볼이 있으면 훨씬 쉬워지고, 없더라도 내보낼 수 있을 만큼 Ghidra 데이터베이스를 정확히 만들고 나면 보통 어디에 뭐가 있는지 감이 생김
      제출 글의 사용자 사례에서는 7월 초에 처음 이슈를 올렸고, 8월 중순쯤에는 기능적으로 동일하게 동작하는 재링크 실행 파일을 얻었음
      다만 당시 COFF 내보내기에 고쳐야 할 버그가 많았고 i386 분석기도 손볼 부분이 있었기 때문에, 이제는 다른 사람이 같은 문제에 덜 부딪히길 기대함
      시간이 얼마나 걸릴지는 모르지만, 디버그 심볼이 있고 정말 운이 좋은 경우가 아니라면 몇 시간보다는 더 걸릴 가능성이 큼. 숙련된 리버스 엔지니어라면 그 시간 안에 뭔가 실행되는 수준까지는 만들 수 있겠지만, 첫 로딩 화면 중간에 죽을 수도 있고, 끝나기 전까지 언제 끝날지 모르는 작업에 가깝다
  • 멋져 보인다. 언젠가 기존 프로그램을 더 쉽게 분해해서 원하는 방식으로 쓰는 데 도움을 줄 수 있을지도 모르겠음
    Meta의 LLM Compiler나 LLM을 이용한 디컴파일 같은 연구가 있는데, 프로그램을 쪼개고 일부를 대체하는 일은 LLM이 탐색하고 스스로 개선하기에도 데이터가 풍부한 흥미로운 영역일 수 있음
    학습 관점에서도 흥미로운 토큰이 많이 숨어 있고, 그걸로 할 수 있는 일도 많아 보임

  • 솔직히 마법처럼 들림. 이게 어떻게 가능한지 이해해봐야겠음

    • 간단히 말하면 목적 파일은 재배치 가능한 섹션 바이트, 재배치 테이블, 심볼 테이블 세 부분으로 이뤄짐
      링커가 여러 목적 파일에서 실행 파일을 만들 때는 섹션을 메모리에 배치하고, 가상 주소 공간에서 심볼 주소를 계산한 뒤, 최종 심볼 주소를 바탕으로 섹션 바이트에 재배치를 적용함
      델링크의 요령은 그 재배치가 어디에 적용됐는지 찾아내서 되돌리고, 다시 재배치 가능한 바이트를 얻는 것임. 그다음 되돌린 내용을 바탕으로 재배치 테이블과 심볼 테이블을 만들고 포장하면 목적 파일이 나옴
      정말 어려운 부분은 재배치 지점을 찾아내는 분석임. 대부분은 Ghidra에 맡기지만, 참조를 재배치 지점으로 바꾸는 작업이 필요함. x86에서는 비교적 쉽고 MIPS에서는 악몽처럼 어려움. 여기에 필요한 데이터를 모으고 목적 파일을 직렬화하는 작업까지 자동화하려고 이 확장을 만든 것임
    • 모든 CPU 아키텍처에서 쉽게 되진 않겠지만, 보기만큼 터무니없는 일은 아님. 기본적으로 목적 파일과 실행 파일 또는 공유 라이브러리의 차이는 크지 않음
      Microsoft 플랫폼을 제외하면 실제 파일 형식이 같은 경우도 많고, 예를 들어 ELF가 그렇다
      큰 차이는 재배치임. 목적 파일에는 세밀한 재배치 정보가 있지만 실행 파일에는 보통 없음. Windows의 실행 이미지에는 실행 파일이 재배치될 때 고쳐야 할 코드와 데이터 주소를 가리키는 최소한의 재배치만 있고, 이미지 자체는 이미지 기준 주소 전체 단위로만 재배치 가능함
      반면 목적 파일에는 심볼 단위 재배치가 들어 있음. 이 정보를 정확히 재구성하려면 디스어셈블리에 심볼에 대한 꽤 정확한 정보를 붙여야 함
      또 다른 큰 차이는 목적 파일이 아직 링크되지 않았다는 것임. 어떤 심볼도 해석되지 않은 상태다. 이건 오히려 고치기 쉬운데, 델링크 중 현재 범위 밖의 심볼이면 대체로 미해결 심볼로 바꾸면 됨. 이후 재링크할 때 다른 목적 파일이나 라이브러리가 그 심볼을 제공해야 다시 연결됨
      진입점이 없는 것 같은 작은 차이도 있지만 큰 의미는 없음
      따라서 실행 이미지나 공유 객체에서 목적 파일을 잘라낼 경계는 사실상 임의적임. 원래 컴파일될 때는 번역 단위로 나뉘었겠지만, 링크 단계에서는 그 경계를 크게 신경 쓰지 않음. 물론 일치하는 디컴파일을 원한다면 목적 파일 경계가 틀리면 매우 어려워질 수 있으니 가능하면 알아내는 편이 좋음
      이 문제를 실제로 다뤄봤지만 세부사항을 약간 틀릴 수도 있으니 참고 정도로 봐야 함. 목적 파일에 대한 블로그 글을 써보려 했고, 이미 괜찮은 글도 몇 개 있음
  • 이 과정이 완전히 안전한지 궁금함. 항상 성공이 보장되는지, 아니면 분석이 보수적으로 동작하는지 알고 싶음
    예를 들어 ELF에 어떤 조각, 데이터, 기능이 빠져 있으면 델링크가 실패하는 식인가?

    • 대답하기 복잡함
      내 분석기는 적어도 내보내려는 부분에 대해서는 정확한 Ghidra 데이터베이스에 의존함. 고쳐야 하는 여러 문제를 로그로 남기려 꽤 노력했지만, 존재하지 않는 것은 볼 수 없음
      특히 누락된 참조와 잘린 변수는 탐지되지 않고, 기묘한 미정의 동작으로 이어질 수 있음
      이런 문제 일부를 추적하는 방법은 있음. 지금까지 찾은 가장 좋은 방법은 실행 파일을 다른 기준 주소로 재링크하고 원래 프로그램의 주소 범위를 매핑하지 않도록 하는 것임. 그러면 놓친 절대 재배치 지점에서 세그멘테이션 오류가 나고 디버깅할 수 있음. 다만 대상에 MMU가 있을 때만 가능함
      잘린 변수는 특히 의심하지 못하면 추적이 매우 까다로움. 손상되는 것은 잘린 변수 뒤의 메모리이기 때문임. 정수를 포인터로 착각한 경우도 추적하기 어렵다. 정수 값이 대상 심볼이 배치된 주소에 따라 달라져 프로그램 동작이 들쭉날쭉해지기 때문이고, 특히 주소 공간의 아주 낮은 곳에 로드되는 프로그램에서 문제가 큼
      그래도 Ghidra 데이터베이스가 충분히 정확하고, 원래 쓰인 것과 같은 목적 파일 형식으로 다시 내보낸 뒤 같은 플랫폼과 같은 도구 체인을 사용한다면, 메가바이트 단위의 프로그램 코드와 데이터를 성공적으로 델링크할 수 있음. 링커가 한 일이라면 되돌리는 것도 가능해야 한다고 봄
      반대로 Linux i386 ELF 실행 파일에서 COFF 목적 파일로 델링크해 i386 Windows 도구 체인에서 쓰는 식으로, 원래 프로그램의 플랫폼과 도구 체인에 맞지 않는 교차 델링크를 시작하면 이야기가 달라짐. 내보내기가 재배치를 표현할 수 있으면 동작하는 재배치 가능 목적 파일을 얻을 수도 있지만, ABI 불일치와도 싸워야 함. 가능은 하지만 첫 프로젝트로 추천하진 않음
      요약하면 무엇을 하느냐와 Ghidra 데이터베이스의 정확도에 따라 “그냥 동작함”부터 “크툴루에게 자비를 비는” 수준까지 다양함
    • 질문 의도대로라면 완전히 안전하지도 않고, 그럴 수도 없음
      예를 들어 int getSpecialArrayElement(char *array, uint64 key) { i = computeOffset(key); return array[i]; } 같은 함수를 생각해보면 됨
      computeOffset은 임의로 복잡할 수 있고, 난독화를 원한다면 일부러 분석하기 어렵게 만들 수도 있음. 이 함수가 메모리의 임의 위치에 접근하는 것을 막는 것은 없음
      가능한 모든 입력을 전부 시도해보지 않는 한, 링커가 이미 메모리에 정의해 둔 심볼에 접근하는지 알 수 없고, computeOffset에는 그런 시도를 방해하는 튜링 함정도 들어갈 수 있음
  • 예전에 상상만 해보고 실제로 만들지는 않았던 아이디어와 연결하면 흥미로울 듯함. 디버그 정보에서 헤더 파일을 생성하고, 필요하면 LLM으로 정리하게 하는 것임

    • 실제로 그런 시도가 몇 가지 있음. Microsoft Program Database용으로는 이게 있음
      https://github.com/wbenny/pdbex
      LLM으로 정리하는 부분은, 아직 리버스 엔지니어링에 LLM 모델을 적용해 큰 성공을 거둔 사례가 많아 보이지 않음. 어쩌면 이 영역이 LLM 구조의 한계가 드러나는 곳일 수도 있겠다는 생각도 듦
      전문가는 아니지만 걸어야 한다면 많은 리버스 엔지니어링 용례에서는 확산 모델 쪽이 더 흥미로울 것 같음
      정확히 같은 건 아니지만 Binary Ninja에는 Sidekick이라는 기능이 있어 LLM으로 디스어셈블리를 정리하려고 함. 개인적으로는 별로 인상적이지 않았지만 누군가에게는 유용할 수도 있음
    • paholeELF DWARF 정보에서 컴파일 가능한 C 헤더 파일을 만들어줌
      여기서는 LLM이 별로 관련 없어 보임. 헤더 파일이 실행 파일에서 내보낸 모든 타입을 원래 값과 함께 올바르게 담고 있어서 쓸 수 있거나, 아니면 틀렸거나 불완전한 것임. LLM이 뭔가를 더 지어내게 해도 도움이 되지 않음
      Ghidra에도 데이터 구조를 내보내는 기본 기능이 있고, DWARF 구조에서 생성할 수 있음. 우클릭 -> Export to C header
    • 몇 년 전에 DWARF 정보에서 C API용 타입 인식 퍼저를 자동 생성하고 삽입하는 도구를 만들었음: https://github.com/intel/fffc
      헤더 생성과, 이후 타입 제약에 맞게 수정할 수 있는 변이자 생성도 그 일부였음
      LLM 쪽을 덧붙이면 익명 구조체 같은 것에 이름을 붙이는 정도는 가능해 보이지만 좋은 생각인지는 모르겠음. 더 흥미로운 건 문서화 목적으로 알려진 타입 제약을 LLM이 말로 풀어 요약하게 하는 시도일 수 있음
    • 약간 다른 얘기지만, 내보낸 목적 파일에 대해 Ghidra 데이터베이스 내용을 바탕으로 디버깅 심볼을 생성해 디버깅 경험을 개선하는 방안을 생각해본 적이 있음
      아직 구현하지 않은 이유는 지금까지는 없어도 버틸 수 있었기 때문임. 게다가 꽤 깊은 토끼굴처럼 들리고, 지금 들어가 있는 토끼굴도 이미 충분히 큼
  • 정말 멋져 보이고, 전에 생각해둔 게임 모딩 아이디어와도 관련 있음. Tenchu 디컴파일 블로그 시리즈도 좋았음

    • 언젠가 이 프로젝트로 돌아가야 함. 버전 추적 세션을 너무 연속으로 많이 해서 쉬어야 했고, 그 와중에 델링크 사이드 퀘스트가 계속 걷잡을 수 없이 커지고 있음
  • 지금 하는 일에는 당장 쓸 곳이 없지만, 예전에 했다면 정말 유용했을 도구로 보임
    조만간 시험해볼 시간이나 기회가 생기면 좋겠음