일부 게임 파일에서 PNG가 추출되어 이미지가 일부 왜곡되어 나타납니다. 예를 들어 다음은 Skyrim의 Textures 파일에서 추출 된 두 개의 PNG입니다.
이것이 PNG 형식에있어 특이한 변형입니까? 그러한 PNG를 제대로 보려면 어떤 수정이 필요합니까?
일부 게임 파일에서 PNG가 추출되어 이미지가 일부 왜곡되어 나타납니다. 예를 들어 다음은 Skyrim의 Textures 파일에서 추출 된 두 개의 PNG입니다.
이것이 PNG 형식에있어 특이한 변형입니까? 그러한 PNG를 제대로 보려면 어떤 수정이 필요합니까?
답변:
다음은 tillberg의 추가 연구 덕분에“복원 된”이미지입니다.
예상 한대로 약 0x4020 바이트마다 5 바이트 블록 마커가 있습니다. 형식은 다음과 같습니다.
struct marker {
uint8_t tag; /* 1 if this is the last marker in the file, 0 otherwise */
uint16_t len; /* size of the following block (little-endian) */
uint16_t notlen; /* 0xffff - len */
};
마커가 읽 히면 다음 marker.len
바이트는 파일의 일부인 블록을 형성합니다. marker.notlen
제어 변수는 다음과 같습니다 marker.len + marker.notlen == 0xffff
. 마지막 블록은 그런 것입니다 marker.tag == 1
.
구조는 다음과 같습니다. 여전히 알 수없는 값이 있습니다.
struct file {
uint8_t name_len; /* number of bytes in the filename */
/* (not sure whether it's uint8_t or uint16_t) */
char name[name_len]; /* filename */
uint32_t file_len; /* size of the file (little endian) */
/* eg. "40 25 01 00" is 0x12540 bytes */
uint16_t unknown; /* maybe a checksum? */
marker marker1; /* first block marker (tag == 0) */
uint8_t data1[marker1.len]; /* data of the first block */
marker marker2; /* second block marker (tag == 0) */
uint8_t data2[marker2.len]; /* data of the second block */
/* ... */
marker lastmarker; /* last block marker (tag == 1) */
uint8_t lastdata[lastmarker.len]; /* data of the last block */
uint32_t unknown2; /* end data? another checksum? */
};
나는 마지막에 무엇이 있는지 알지 못했지만 PNG가 패딩을 허용하므로 너무 드라마틱하지 않습니다. 그러나 인코딩 된 파일 크기는 마지막 4 바이트를 무시해야한다는 것을 분명히 나타냅니다 ...
파일 시작 직전에 모든 블록 마커에 액세스 할 수 없었기 때문에 끝에서 시작하여 블록 마커를 찾으려고하는이 디코더를 작성했습니다. 전혀 강력하지는 않지만 테스트 이미지에서 효과적이었습니다.
#include <stdio.h>
#include <string.h>
#define MAX_SIZE (1024 * 1024)
unsigned char buf[MAX_SIZE];
/* Usage: program infile.png outfile.png */
int main(int argc, char *argv[])
{
size_t i, len, lastcheck;
FILE *f = fopen(argv[1], "rb");
len = fread(buf, 1, MAX_SIZE, f);
fclose(f);
/* Start from the end and check validity */
lastcheck = len;
for (i = len - 5; i-- > 0; )
{
size_t off = buf[i + 2] * 256 + buf[i + 1];
size_t notoff = buf[i + 4] * 256 + buf[i + 3];
if (buf[i] >= 2 || off + notoff != 0xffff)
continue;
else if (buf[i] == 1 && lastcheck != len)
continue;
else if (buf[i] == 0 && i + off + 5 != lastcheck)
continue;
lastcheck = i;
memmove(buf + i, buf + i + 5, len - i - 5);
len -= 5;
i -= 5;
}
f = fopen(argv[2], "wb+");
fwrite(buf, 1, len, f);
fclose(f);
return 0;
}
이것은 0x4022
두 번째 이미지에서 바이트 를 제거한 다음 바이트 를 제거하여 얻을 수있는 것입니다 0x8092
.
이미지를 실제로 "수리"하는 것은 아닙니다. 나는 시행 착오로 이것을했다. 그러나 16384 바이트마다 예기치 않은 데이터가 있다는 것을 알려줍니다. 내 생각에 이미지는 일종의 파일 시스템 구조로 압축되어 있으며 예기치 않은 데이터는 단순히 데이터를 읽을 때 제거해야 할 블록 마커 입니다.
블록 마커의 위치와 크기를 정확히 모르지만 블록 크기 자체는 2 ^ 14 바이트입니다.
이미지 직전과 직후에 나타나는 16 진 덤프 (수십 바이트)를 제공 할 수 있다면 도움이 될 것입니다. 이것은 블록의 시작 또는 끝에 어떤 종류의 정보가 저장되는지에 대한 힌트를 제공합니다.
물론 추출 코드에 버그가있을 가능성도 있습니다. 파일 작업에 16384 바이트의 버퍼를 사용하는 경우 먼저 확인합니다.
Sam의 제안에 따라 https://github.com/tillberg/skyrim 에서 James 코드를 포크 하고 Skyrim Textures BSA 파일에서 n_letter.png를 성공적으로 추출 할 수있었습니다.
BSA 헤더가 제공하는 "file_size"는 실제 최종 파일 크기가 아닙니다. 여기에는 약간의 헤더 정보와 쓸데없는 데이터가 무작위로 흩어져 있습니다.
헤더는 다음과 같습니다.
헤더 바이트를 제거하기 위해 다음과 같이했습니다.
f.seek(file_offset)
data = f.read(file_size)
header_size = 1 + len(folder_path) + len(filename) + 12
d = data[header_size:]
거기에서 실제 PNG 파일이 시작됩니다. PNG 8 바이트 시작 순서에서 쉽게 확인할 수 있습니다.
PNG 헤더를 읽고 IDAT 청크에 전달 된 길이를 IEND 청크까지 바이트 수를 측정 할 때 암시 된 데이터 길이와 비교하여 여분의 바이트가 어디에 있는지 알아 내려고했습니다. (자세한 내용은 github에서 bsa.py 파일을 확인하십시오)
n_letter.png의 청크 크기는 다음과 같습니다.
IHDR: 13 bytes
pHYs: 9 bytes
iCCP: 2639 bytes
cHRM: 32 bytes
IDAT: 60625 bytes
IEND: 0 bytes
파이썬에서 string.find ()를 사용하여 바이트 수를 계산하여 IDAT 청크와 IEND 청크 사이의 실제 거리를 측정했을 때 실제 IDAT 길이는 60640 바이트임을 알았습니다. 여기에 15 바이트가 더 있습니다. .
일반적으로, 대부분의 "레터"파일은 총 16KB의 파일 크기마다 5 바이트가 추가로 존재합니다. 예를 들어, 약 73KB의 o_letter.png에는 추가 20 바이트가 있습니다. 비전 스 크라이 블과 같은 더 큰 파일은 대부분 같은 패턴을 따르지만 일부 파일에는 홀수 (52 바이트, 12 바이트 또는 32 바이트)가 추가되었습니다. 무슨 일인지 모르겠다.
n_letter.png 파일의 경우 5 바이트 세그먼트를 제거 할 올바른 오프셋 (주로 시행 착오에 의한)을 찾을 수있었습니다.
index = 0x403b
index2 = 0x8070
index3 = 0xc0a0
pngdata = (
d[0 : (index - 5)] +
d[index : (index2 - 5)] +
d[index2 : (index3 - 5)] +
d[index3 : ] )
pngfile.write(pngdata)
제거 된 5 바이트 세그먼트는 다음과 같습니다.
at 000000: 00 2A 40 D5 BF (<-- included at end of 12 bytes above)
at 00403B: 00 30 40 CF BF
at 008070: 00 2B 40 D4 BF
at 00C0A0: 01 15 37 EA C8
가치있는 것을 위해, 나는 다른 시퀀스와의 유사성 때문에 알려지지 않은 12 바이트 세그먼트의 마지막 5 바이트를 포함 시켰습니다.
16KB마다 아니지만 0x4030 바이트 간격으로 나타납니다.
위의 지수에서 완벽하게 일치하지는 않는 것을 막기 위해 결과 PNG에서 IDAT 청크의 zlib 압축 풀기를 테스트하여 통과했습니다.
실제로 간헐적 5 바이트는 zlib 압축의 일부입니다.
에 설명 된대로 http://drj11.wordpress.com/2007/11/20/a-use-for-uncompressed-pngs/ ,
01 리틀 엔디안 비트 문자열 1 00 00000. 1은 최종 블록을 나타내고, 00은 비 압축 블록을 나타내며 00000은 블록의 시작을 옥텟에 맞추기위한 5 비트의 패딩 (비 압축 블록에 필요함) , 매우 편리합니다). 05 00 fa ff 압축되지 않은 블록의 데이터 옥텟 수 (5). 리틀 엔디안 16 비트 정수와 1의 보수 (!)로 저장됩니다.
.. 00은 '다음'블록 (끝이 아닌)을 나타내며 다음 4 바이트는 블록 길이와 그 역수입니다.
보다 안정적인 소스는 물론 RFC 1951 (압축 데이터 형식 사양 정의), 섹션 3.2.4입니다.
바이너리 모드 대신 텍스트 모드 (PNG 데이터에 나타나는 줄 끝이 엉망이 될 수 있음)에서 파일의 데이터를 읽을 수 있습니까?
libpng
합니까? (b) Skyrim PNG 를 읽는 등 입증 된 PNG 로더를 사용 합니까? 즉, PNG 로더의 버그입니까?