[250804] 개발 일지 - MSI용 리패키지 만들기
[250804] 개발 일지 - MSI용 리패키지 만들기
이번 포스트는
Click BIOS X
버전에 해당하는 이야기다.
이전 버전인Click BIOS 5
는 해당하지 않는다.
1. 개요
바로 이전 글에서 분석한 구조를 바탕으로 리패키지 프로그램을 구성했었다.
하지만 어째서인지 해외에서 개발된 #imageext.py
로 다시 언패킹을 하니 MSI_Pack
으로 시작하는 폴더가 생성되지 않았다.
오늘은 그 문제의 원인을 분석하고, 해결해보는 과정을 기록하고자 한다.
2. 원인 분석
일단 가장 먼저 리패키지한 파일과 원본 파일에서의 구조적인 차이가 발생헀을 가능성이 가장 높다고 생각했다. 따라서 원본 파일과 리패키지한 파일을 서로 바이너리 단에서 재분석했다.
2.1. 원본 파일과 리패키징 파일의 재분석
- 원본 파일
1
2
3
4
5
6
7
8
9
[전체 파일 구조]
┌─────────────────────────────────────────────────────────────┐
│ $MsI$ (5bytes) │ 패딩 (2bytes) │ 연속된 이미지 엔트리들 │
└─────────────────────────────────────────────────────────────┘
[각 이미지 엔트리 구조]
┌──────────────────────────────────────────────────────────┐
│ 이미지번호 (2bytes) │ 이미지크기 (4bytes) │ 이미지데이터 │
└──────────────────────────────────────────────────────────┘
- 리패키지된 파일
1
2
3
┌─────────────────────────────────────────────────────────────┐
│ $MsI$ │ 헤더 │ 이미지1 │ $MsI$ │ 헤더 │ 이미지2 │ ... │
└─────────────────────────────────────────────────────────────┘
위와 같이 $MSI$
시그니처를 각 파일마다 반복해서 쓰는 구조가 아니라는 것을 알 수 있었다.
2.2. 기타 자잘한 문제들
- MSI Packer 시그니처를 찾지 못한 파일들은 msi_entries가 비어있음
- 하지만 추출된 이미지들은 올바른 image_nr{번호}_off0x{오프셋} 형식
- 변경 감지가 전혀 작동하지 않음
1
2
3
4
# 잘못된 접근 - MSI 분석 결과에만 의존
if 'msi_entries' in analysis_results and analysis_results['msi_entries']:
# MSI 엔트리가 없으면 변경 감지 실패
return None
3. 해결
우선 각 파일을 Hex 단위로 분석하면 이러한 차이가 존재한다.
1
2
3
4
5
원본: 24 4D 73 49 24 8E 00 01 00 1A 01 00 00 [이미지데이터...]
└─ $MsI$ ─┘└패딩 ┘└번호┘└크기┘
잘못된 리패킹: 24 4D 73 49 00 00 00 00 1A 01 00 00 00 [데이터...]
└─ $MsI$ ─┘ └─ 잘못된 헤더 ──┘
따라서 아래와 같이 올바른 구조로 생성하도록 조정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def _detect_modified_images_from_filenames(self, extract_dir, original_file):
"""파일명 패턴 기반 변경 감지"""
changes = {}
# 1. 추출된 이미지 파일들 스캔
for filename in os.listdir(extract_dir):
if filename.startswith('image_nr'):
# 2. 파일명에서 오프셋 추출
match = re.search(r'image_nr(\d+)_off0x([0-9A-Fa-f]+)', filename)
if match:
image_nr = int(match.group(1))
offset = int(match.group(2), 16)
# 3. 원본 파일에서 해당 오프셋 데이터 읽기
with open(original_file, 'rb') as f:
f.seek(offset)
original_data = f.read(extracted_size)
# 4. 바이트 단위 비교
if extracted_data != original_data:
changes[image_nr] = {
'filename': filename,
'original_size': len(original_data),
'new_size': len(extracted_data),
'size_diff': len(extracted_data) - len(original_data)
}
...
def _create_msi_binary_correct_format(self, sorted_entries, changes_map):
binary_data = bytearray()
# ✅ 올바른 전체 헤더 (한 번만)
binary_data.extend(b'$MsI$') # 5바이트 시그니처
binary_data.extend(b'\x8E\x00') # 2바이트 패딩 (원본과 동일)
# ✅ 연속된 이미지 엔트리들
for entry in sorted_entries:
# 이미지 번호 (2바이트)
binary_data.extend(struct.pack('<H', entry['image_number']))
# 이미지 크기 (4바이트)
binary_data.extend(struct.pack('<I', len(image_data)))
# 이미지 데이터
binary_data.extend(image_data)
return bytes(binary_data)
이후에 테스트한 구조는 다음과 같다.
1
2
3
4
5
6
7
# 원본 파일 헤더
24 4D 73 49 24 8E 00 01 00 1A 01 00 00...
└─ $MsI$ ─┘└패딩┘└ #1 ┘└크기┘
# 수정된 리패킹 파일 헤더
24 4D 73 49 24 8E 00 01 00 1A 01 00 00...
└─ $MsI$ ─┘└패딩┘└ #1 ┘└크기┘
또한 #imageext.py
로 다시 언패킹을 시도하면 아래와 같이 MSI Pack
으로 시작하는 폴더가 생성되며 파일 개수도 정상적으로 추출됨을 알 수 있다.
따라서 문제가 잘 해결되었음을 알 수 있다.
이 포스트는 저작권자의 CC BY 4.0 라이센스를 따릅니다.