처리중입니다. 잠시만 기다려주세요.
TTJ 코딩클래스
정규반 단과 자료실 테크 뉴스 코딩 퀴즈
테크 뉴스
Hacker News 2026.09.03 36

ImHex로 미지의 파일 포맷을 역공학하기: FEZ 세이브 파일에서 배우는 실전 워크플로

Hacker News 원문 보기

게임이나 애플리케이션이 남기는 세이브 파일, 설정 파일, 독자 규격의 바이너리를 열어보면 대개 사람이 읽을 수 없는 바이트 뭉치와 마주한다. 표준 포맷이라면 이미 파서가 존재하지만, 각 프로그램이 자체적으로 만든 커스텀 포맷은 그 파일을 읽고 쓰는 코드만이 의미를 안다. 오픈소스 헥스 에디터 ImHex의 개발자 WerWolv는 2012년 출시된 인디 게임 FEZ의 세이브 파일을 예제로, 이런 미지의 바이너리를 어떻게 한 조각씩 해독해 나가는지 정리했다. 핵심 도구는 ImHex에 내장된 '패턴 언어(Pattern Language)'로, 파일 구조를 코드처럼 선언해 각 바이트에 의미를 입히는 방식이다.

출발점: 파일을 관찰하고 코드를 찾는다

첫 단계는 대상 파일을 확보하고 눈으로 훑는 것이다. FEZ의 세이브 파일 SaveSlot2를 열어보면 평문 문자열과 규칙적인 패턴이 보이는데, 이는 파일이 압축이나 암호화가 되어 있지 않다는 신호다. 반면 파일 시작부에 포맷을 식별할 수 있는 매직 넘버(magic)가 없고, ImHex의 자동 인식도 실패한다. 즉 표준 포맷이 아니므로 관찰만으로는 여기서 막힌다. 다음 열쇠는 파일을 생성·해석하는 코드다. FEZ는 C#으로 작성되어 있어 System.Core.dll, mscorlib.dll 같은 .NET 라이브러리가 함께 배포되며, JetBrains Rider 같은 디컴파일러로 원본에 가까운 소스를 복원할 수 있다. 실무적으로 중요한 힌트는, 파일 이름 문자열("SaveSlot" + index)이나 파일 입출력 호출을 단서로 삼아 관련 코드를 찾아 들어가는 것이다. 실제로 EasyStorage의 PCSaveDevice 클래스 생성자에서 파일 이름을 조립하는 부분을 발견하고, BinaryWriter로 버퍼를 채워 저장하는 Save 함수까지 도달한다.

코드의 직렬화 규칙을 패턴으로 옮긴다

생성 코드를 찾으면 그다음은 기계적인 번역에 가깝다. 코드가 무엇을 어떤 순서로, 어떤 타입으로 기록하는지 그대로 패턴 파일에 옮기면 된다. 예컨대 Save 함수가 DateTime.Now.ToFileTime()을 기록하는데, 이는 1601년을 기준으로 100나노초 단위를 세는 64비트 리틀엔디언 값이다. 패턴에서는 단순히 s64로 읽어도 되지만, ImHex 표준 라이브러리의 type.time을 import하면 사람이 읽을 수 있는 실제 시각으로 표시된다. 또한 세이브 파일이 항상 0xA000바이트로 고정되고 짧으면 0으로 패딩된다는 코드상의 규칙은, 패턴의 [[fixed_size(0xA000)]] 속성으로 문서화할 수 있다. 여기서 눈여겨볼 점은 패턴 언어가 단순한 구조 선언을 넘어 조건과 함수 호출을 타입 정의 사이에 섞을 수 있다는 것이다. 세이브 버전 필드가 반드시 6이어야 한다는 검증 로직을 코드가 갖고 있다면, 패턴에서도 해당 값에 대해 assert를 걸어 호환되지 않는 파일을 걸러낼 수 있다.

실제 데이터의 대부분은 몇 가지 기본 빌딩 블록의 반복이다. FEZ 포맷에는 String→Bool 키-값 쌍의 목록이 등장하는데, 먼저 쌍의 개수가 기록되고 그 수만큼 직렬화된 쌍이 이어진다. 널일 수 있는 값은 존재 여부를 나타내는 bool을 먼저 쓰고 참일 때만 실제 값을 기록하는데, 이는 패턴 언어의 템플릿 구조체 Object로 깔끔하게 표현된다. 문자열은 길이를 7BitEncodedInt 형식으로 먼저 쓰고 데이터가 뒤따른다. 이 인코딩은 겁먹을 필요 없이, 각 바이트의 최상위 비트(MSB)를 '다음 바이트가 더 있는가'의 플래그로 쓰고 나머지 7비트에 실제 값을 담는 방식일 뿐이다. 패턴에서는 [[format]]으로 디코딩된 정수를 화면에 표시하고 [[transform]]으로 다른 코드가 그 값을 변수처럼 읽게 만들 수 있다. C#의 enum 정의는 거의 그대로 복사해 붙여넣어 정수 대신 이름 있는 값으로 표시할 수 있고, 중첩된 직렬화는 LevelSaveData 같은 새 구조체로 파고들면 된다.

일반화할 수 있는 워크플로

이 글이 FEZ 하나에만 국한되지 않는 이유는, 저자가 마지막에 일반적인 절차로 정리하기 때문이다. 먼저 파일 자체를 관찰해 압축·암호화·매직 유무를 파악하고 ImHex의 매직 감지나 binwalk 같은 도구로 알려진 포맷인지 확인한다. 이미 알려진 포맷이면 파서나 명세가 존재할 수 있다. 그렇지 않으면 파일을 읽거나 쓰는 코드를 찾아야 하는데, 소스가 있으면 좋고 없으면 언어에 맞는 디컴파일러를 쓴다. .NET에는 Rider, 네이티브 바이너리에는 Ghidra·IDA·Binary Ninja, JVM 계열에는 Recaf가 언급된다. 그 코드에서 정수·불리언·문자열 같은 기본 building block을 식별하고, 이를 패턴 파일로 옮기며 실제 데이터로 검증한다.

한국 실무자 입장에서 이 접근의 가치는 두 가지다. 하나는 패턴 파일이 일회성 해독 도구가 아니라 살아있는 문서라는 점이다. 발견한 사실을 코드로 남기면 같은 포맷을 다시 만났을 때 재사용할 수 있고, assert와 고정 크기 같은 제약을 담아 검증까지 겸한다. 다른 하나는 이 방법론의 명백한 한계다. FEZ가 유독 쉬웠던 것은 C# 바이너리가 원본에 가깝게 디컴파일되고, 파일이 암호화·난독화되지 않았기 때문이다. 최적화된 네이티브 바이너리, 강한 난독화, 암호화된 데이터를 만나면 코드 분석 자체가 훨씬 험난해지며, 이 글은 그런 경우까지 다루지는 않는다. 다만 '파일을 관찰하고, 파서 코드를 찾고, 빌딩 블록을 식별하고, 패턴으로 문서화·검증한다'는 뼈대는 대상이 어렵든 쉽든 동일하게 적용된다는 점이 핵심이다. 참고로 예제에 쓰인 일부 기능은 정식 릴리스가 아닌 나이틀리 빌드에만 있으며, v1.38.1 이하에서 문제가 있으면 업그레이드가 권장된다.

이 뉴스가 유용했나요?

이 기술을 직접 배워보세요

AI 도구, 직접 활용해보세요

AI 시대, 코딩으로 수익을 만드는 방법을 배울 수 있습니다.

AI 활용 강의 보기

"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"

실제 수강생 후기
  • 비전공자도 6개월이면 첫 수익
  • 20년 경력 개발자 직강
  • 자동화 프로그램 + 소스코드 제공

매일 AI·개발 뉴스를 받아보세요

주요 테크 뉴스를 매일 아침 이메일로 전해드립니다.

스팸 없이, 언제든 구독 취소 가능합니다.