포스트

[250724] 개발 일지 - bin 파일 병합

[250724] 개발 일지 - bin 파일 병합

1. 이전 편 요약

하츠네 미쿠 콜라보 메인보드의 바이오스 화면을 보고 화면을 내가 원하는 이미지로 바꾸어보는 프로젝트를 계획했다.

이후 각 보드의 바이오스 파일을 다운로드받아, UI 영역을 구성하는 이미지 파일들을 추출하는 것까지는 성공하는데….

2. 이제 병합해야 하는데…

이제 콜라보 보드에서 추출한 이미지를 내가 쓰는 보드의 이미지 파일에 덮어썼다.

modified_images 원본 파일만 넣기에는 용량이 너무 커서 바이오스 파일 내에 빈공 간이 없다 판단해 이미지 용량을 줄였다.

문제는 이 파일을 단순하게 .zip 파일로 압축해버린다던가 하면 당연히 안된다.1
즉, 고유한 방식으로 모든 파일들을 병합했다는 뜻인데, 이 방법은 인터넷을 찾아봐도 보이지 않았다.
하지만 인터넷에 이 파일이 어느 방식으로 구성되었는지를 분석한 자료가 있다.

https://winraid.level1techs.com/t/mod-the-text-color-theme-on-z97-asus-uefi-bios/30766/8

Asus_packer

1.2. 헤더 구조 분석

Packer Begin (0x00000000)

  • 패커의 시작 지점을 나타내는 마커

이미지 정보 섹션

  • Size of image: 이미지 전체 크기
  • Size of header: 헤더 크기 정보
  • Image type: 이미지 유형 식별자
  • Image number: 이미지 번호
  • Group or sector: 그룹 또는 섹터 정보

End of header

  • 헤더의 끝을 표시하는 마커

이미지 형식 정보2

  • PNG 또는 JPEG: FFFFDAA0FFFF 시그니처
  • BMP: 42004D0050000000FFFF 시그니처

정렬 규칙

  • 4바이트 정렬: 모든 이미지가 4바이트 경계에 정렬됨
  • 패딩: 크기가 4의 배수가 될 때까지 0x00으로 패딩 추가

hxddata 원본 바이오스 파일에서 추출한 이미지 데이터의 hxD 데이터

3. 에라이 프로그램을 만들자

이렇게까지 복잡할 질 바에 그냥 프로그램을 만들기로 결심한다. 그래서 아래와 같이 클래스와 서로 간의 의존 관계를 구축했다.

classDiagram
    direction TB
    
    class AsusMain {
        +main()
        +interactive_mode()
        +analyze_mode()
        +extract_mode()
        +repack_mode()
    }
    
    class FileUtils {
        +validate_file_path()
        +get_file_path_input()
        +get_command_line_file()
    }
    
    class AsusFileAnalyzer {
        +run_full_analysis()
        +analyze_magic_bytes()
        +find_patterns()
    }
    
    class AsusImageExtractor {
        +run_extraction()
        +detect_asus_packer_format()
        +extract_images_to_directory()
    }
    
    class AsusImageRepacker {
        +run_repack()
        +detect_modified_images()
        +repack_with_structure_preservation()
    }
    
    AsusMain --> FileUtils : "파일 검증 및 경로 처리"
    AsusMain --> AsusFileAnalyzer : "파일 분석 수행"
    AsusMain --> AsusImageExtractor : "이미지 추출 수행"
    AsusMain --> AsusImageRepacker : "이미지 리패킹 수행"
    
    AsusFileAnalyzer ..> FileUtils : "파일 정보 조회"
    AsusImageExtractor ..> FileUtils : "디렉터리 생성"
    AsusImageRepacker ..> FileUtils : "파일 경로 검증"
    
    note for AsusMain "메인 컨트롤러<br>사용자 인터페이스 제공"
    note for FileUtils "공통 유틸리티<br>파일 시스템 작업"
    note for AsusFileAnalyzer "독립적 분석 모듈<br>읽기 전용 작업"
    note for AsusImageExtractor "독립적 추출 모듈<br> ASUS 패키지 처리"
    note for AsusImageRepacker "독립적 리패킹 모듈<br>구조 보존 재구성"

3.1. 원본 파일 구조

일단 이 파일을 다시 리패키지하기 위해서 원래 파일의 구조를 이해해야한다.

block-beta
    columns 8
    
    A["📁 파일 시작<br/>0x00000000"]:8
    
    C["📦 ASUS Package<br/>Header (32 bytes)"]:8
    
    D["🏷️ ASUS 시그니처<br/>00 00 00 00 20 00 00 00<br/>FF FF 00 00 FF FF 00 00<br/>00 00 00 00 00 00 00 00<br/>00 00 00 00 00 00 00 00"]:8
    E["📋 이미지 #1 메타데이터<br/>(32 bytes)"]:8
    F["크기(4B)<br/>Little-Endian"]:2
    G["오프셋(4B)<br/>0x20 (고정)"]:2
    H["특별 패턴 #1<br/>(24 bytes)<br/>FF FF 0A 00 FF FF 00 40..."]:4

    I["🖼️ 이미지 #1 데이터<br/>(PNG/BMP/JPEG)"]:6
    J["패딩<br/>(4바이트 정렬)"]:2
    K["📋 이미지 #2 메타데이터<br/>(32 bytes)"]:8
    
    L["크기(4B)"]:2
    M["오프셋(4B)<br/>0x20"]:2
    N["특별 패턴 #2<br/>(24 bytes)<br/>00 FF FF 0A 00 FF FF 02..."]:4
    O["🖼️ 이미지 #2 데이터"]:6
    P["패딩"]:2
    Q["📋 이미지 #N 메타데이터<br/>..."]:8
    R["🖼️ 이미지 #N 데이터<br/>..."]:8
    
    S["📦 ASUS Package #2<br/>Header (32 bytes)"]:8
    T["📋 Package #2 Images<br/>..."]:8
    U["🔧 패키지 후<br/>데이터 영역<br/>(바이너리 데이터)"]:8
    
    V["📁 파일 끝<br/>EOF"]:8
    
    style A fill:#e1f5fe
    style C fill:#fff3e0
    style D fill:#fff3e0
    style E fill:#f3e5f5
    style I fill:#e8f5e8
    style S fill:#fff3e0
    style U fill:#e1f5fe
    style V fill:#e1f5fe

위에서 말한 두 가지 사항을 이용하여 파일 압축 로직을 다음과 같이 정리하였다.

graph TD
    A[프로그램 시작] --> B[원본 파일 로드]
    B --> C[ASUS 패키지 구조 분석]
    C --> D[수정된 이미지 감지]
    D --> E{크기 변화 있음?}
    
    E -->|No| F[바이트 단위 직접 교체]
    E -->|Yes| G[구조 보존 재구성]
    
    F --> H[원본 복사 후 교체]
    G --> I[완전한 구조 재구성]
    
    H --> J[결과 파일 저장]
    I --> J

특히나 구조를 보존한 채로 재구성하는 과정은 다음과 같다.

1️⃣ 패키지 전 데이터 완전 보존

1
2
3
4
5
pkg_start = package['header_offset']
if current_pos < pkg_start:
    preserved_data = self.data[current_pos:pkg_start]
    new_data.extend(preserved_data)
    print(f"📋 패키지 전 데이터 보존: {len(preserved_data)} bytes") 

ASUS 패키지와 패키지 사이의 모든 바이너리 데이터를 바이트 단위로 완전 보존하는 것이 핵심이다.
그렇지 않다면 바이오스에서 이미지를 아예 못 띄울지도 모를테니 말이다.

2️⃣ ASUS 헤더 완전 보존

1
2
3
4
header_end = package['header_end']
original_header = self.data[pkg_start:header_end]
new_data.extend(original_header)
print(f"🏷️ ASUS 헤더 보존: {len(original_header)} bytes")

이 파일에는 가장 앞에 32바이트가량의 ASUS 시그니처 헤더가 존재한다.3 이를 원본 그대로 보존시킨다.

4️⃣ 원본 구조 완전 모방 - 핵심 알고리즘

🔢 이미지 순서 보존

1
2
# 원본 순서 유지를 위해 번호 순으로 정렬
sorted_images = sorted(package['images'], key=lambda x: x['number'])

🧱 메타데이터 구조 복원

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for img_info in sorted_images:
    # 1. 메타데이터 엔트리 (8바이트)
    size_bytes = struct.pack('<I', len(img_data))      # 이미지 크기 (4바이트)
    offset_bytes = struct.pack('<I', 0x20)             # 고정 오프셋 (4바이트)
    
    new_data.extend(size_bytes)
    new_data.extend(offset_bytes)
    
    # 2. 특별한 24바이트 패턴 (ASUS 고유 구조)
    if img_info['number'] == 1:
        # 첫 번째 이미지용 특별 패턴
        special_pattern = bytes.fromhex("FFFF0A00FFFF004000000000300009040000000000000000")
    else:
        # 나머지 이미지용 특별 패턴  
        special_pattern = bytes.fromhex("00FFFF0A00FFFF0200000000300009040000000000000000")
    
    new_data.extend(special_pattern)

🖼️ 이미지 데이터 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
# 수정된 이미지인지 확인
if abs_offset in modified_images:
    # 수정된 이미지 사용
    img_data = modified_images[abs_offset]['new_data']
    pkg_replaced_count += 1
    print(f"🔄 교체: 이미지 #{img_info['number']}")
else:
    # 원본 이미지 보존
    img_data = img_info['data']
    print(f"✅ 보존: 이미지 #{img_info['number']}")

# 이미지 데이터 추가
new_data.extend(img_data)

📏 4바이트 정렬 패딩

1
2
3
4
padding = (4 - (len(img_data) % 4)) % 4
if padding > 0:
    new_data.extend(b'\x00' * padding)
    print(f"🔧 패딩 추가: {padding} bytes (4바이트 정렬)")

5️⃣ 패키지 후 데이터 보존

1
2
3
4
if current_pos < len(self.data):
    remaining_data = self.data[current_pos:]
    new_data.extend(remaining_data)
    print(f"📋 패키지 후 데이터 보존: {len(remaining_data)} bytes")

  1. 만약 이게 정답이었다면, 코드를 짤 생각도 없이, 인터넷에 방법이 공유되고도 남았을 것이라 생각한다. ↩︎

  2. 파일을 HxD로 보았을 때, 파일의 맨 앞에 해당 파일에 대한 확장자 정보들을 포함한 메타데이터를 담는다. ↩︎

  3. header ↩︎

이 포스트는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

인기 태그