Home 이미지 포렌식 - PNG 구조 with CTF
Post
Cancel

이미지 포렌식 - PNG 구조 with CTF

File signature

89 50 4E 47 0D 0A 1A 0A

PNG 파일은 8 바이트의 시그니쳐를 갖는다.

Chunk

PNG 파일은 N개의 청크(chunk)로 구성된다. 청크의 구조는 아래와 같다.

1
2
3
4
5
6
7
{
  Length (4 byte),
  Chunk Type (4 byte),
  Chunk Data (length byte),
  CRC (4byte)
}

PNG의 CRC는 Chunk Type + Chunk Data를 crc32로 계산하면 된다.

청크 타입에는 여러가지가 있고, 그 중에서 모든 PNG 파일에 반드시 포함되어야 하는 청크로 다음의 세가지 청크가 있다.

  • IHDR
  • PLTE  : Color type : Indexed-color의 경우
  • IDAT
  • IEND

그 외의 청크는 PNG파일의 타입에 따라 요구되거나, 또는 메타데이터를 저장하는 데에 사용된다.

그 외의 보조 청크는 인코더를 생성하고 디코더를 해석한다.

  • 투명도 정보 : tRNS
  • 컬러 스페이스 정보 : cHRM, gAMA, iCCP, sBIT, sRGB
  • 텍스트 정보 : iTXt, tEXt, zTXt
  • 시간 정보 : tTME

다음은 010Editer 로 본 png 파일 구조다.
image

IHDR 아래 여러개의 IDAT이 있고 마지막에 IEND가 있는 것을 확인해 볼 수 있다.

PLTE 청크가 있는 경우 PNG 구조

image

PLTE 청크가 없는 경우 PNG 구조

image

IHDR

IHDR 청크는 이름(header)처럼 PNG 파일의 가장 앞에 위치하는 청크로, PNG 이미지의 크기, 필터링 방식, 압축 방식 등을 알려준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
 Length : 00 00 00 0D (13 byte),
 Chunk Type : IHDR,
 Chunk Data ( 13 byte ),
 {
   Width (4 byte),
   Height (4 byte),
   Bit depth (1 byte),
   Color Type (1 byte),
   Compression method (1 byte),
   Filter method (1 byte),
   Interlace method (1 byte),
 }
 CRC
}

IHDR 청크의 데이터 길이는 언제나 13바이트이다. image

Width & Height
이미지의 폭과 높이를 지정한다. 이미지 뷰어, 브라우저등은 이 값을 바탕으로 이미지 데이터를 디코딩하고 출력한다. 따라서 이 부분을 조작하면 이미지를 일그러뜨리거나 이미지의 아랫부분을 감추는 것 등도 가능하다. 이 부분이 망가졌을 경우 PNG 파일이 온전히 복구되지 않는다. 이 경우, CRC가 살아 있다면 다음과 같은 코드로 width와 height를 복구할 수 있다.

image

1
2
3
4
5
6
7
8
9
10
11
12
13
import os
import binascii
import struct

misc = open("ogo.png","rb").read()
print(misc[12:16])
print( misc[20:29])
for i in range(2000):
  for j in range(2000):
    data = misc[12:16] + struct.pack('>i',i) + struct.pack('>i',j)+ misc[24:29]
    crc32 = binascii.crc32(data) & 0xffffffff
    if crc32 == 0x01897642: # CRC32
      print(struct.pack('>i',i)+ struct.pack('>i',j))

Bit depth & Color type

Color type은 PNG 이미지의 색상을 어떻게 구성할 것인지를 정하고, Bit depth는 하나의 채널(channel)이 몇 비트로 구성될 지를 정한다. 이미지의 한 픽셀(pixel)은 하나 또는 여러개의 채널로 구성될 수 있다. 예를 들어 RGB Color type 이라면 한 픽셀은 3개의 채널 (R, G, B)를 갖는다.

PNG image typeColor typeAllowed bit depthsInterpretation
Grayscale01, 2, 4, 8, 16그레이 스케일 값을 갖는다
Truecolor28, 16RGB 값을 갖는다
Indexed-color31, 2, 4, 8팔레트에 따른 값을 갖는다
Grayscale with alpha48, 16그레이 스케일 + alpha 값을 갖는다
Truecolor with alpha68, 16RGB + alpha 값을 갖는다

예를 들어, 흔히 웹에서 볼 수 있는 투명도가 있는 RGBA 이미지 (#FFFFFF00 꼴로 색상을 나타내는) 의 경우, Color type은 6, Bit depth는 8이 된다.

Indexed-color의 경우는 추가로 파일에 PLTE 청크가 존재하여야 한다. 해당 청크에서 사용할 팔레트를 지정한다.

주목할 만한 것은 하나의 채널이 1바이트보다 작은 단위의 비트로도 구성될 수 있다는 것이다.

  • Compression method 현재까지 PNG에서 표준으로 정의된 압축 방식은 0: DEFLATE 한 가지다.

  • Filter method 현재까지 PNG에서 표준으로 정의된 필터링 방식은 0, 한 가지다. 이 필터링 방식에 대해서는 IDAT 청크를 설명할 때 살펴보자.

  • Interlace method 인터레이스는 웹 페이지 등에 이미지를 표시할 때 이미지 로딩이 완료되기 전 먼저 해상도가 낮은 이미지를 보여주기 위하여 사용된다.

현재까지 PNG에서 표준으로 정의된 인터레이스 방식은, 0 (No interlace), 1 (Adam7 interlace) 두 가지다.


IDAT

IDAT 청크는 실제로 이미지 데이터가 들어가는 부분이다.

오리지널 픽셀 데이터는 필터링과 압축을 거쳐서 IDAT 청크에 저장된다.

한 PNG 파일은 여러 개의 IDAT 청크를 가질 수 있는데, 이는 데이터를 적절한 사이즈로 나누어 전송하기 위한 것으로, 일반적으로 한 IDAT 청크당 65536 바이트의 데이터 크기를 갖는다.

착각하기 쉬운 것은, 하나의 IDAT 청크가 이미지의 특정 부분을 나타내지는 않는다는 점이다. PNG는 전체 이미지 데이터를 한꺼번에 압축한 뒤, 여러 IDAT 청크에 나누어 담는 방식을 사용한다. 따라서, 모든 IDAT 청크가 있어야만 이미지 디코딩이 가능하다. 반대로 말하면, 하나의 IDAT 청크라도 사라지면 압축된 이미지를 디코딩 할 수 없다.

  • 필터링

필터링은 원시 이미지 데이터의 압축률을 높이기 위하여 데이터를 가공하는 작업이다.

현재 유일한 PNG 표준 필터링 방식(filter 0)은 이미지의 각 행(row, scanline)에 대하여 5가지 필터 타입 중 한 가지를 정하여 바이트 단위로 적용한다. 이 때 어떠한 필터 타입을 적용하는 가는 휴리스틱적으로 결정한다.

필터링할 바이트를 x라고 했을 때, x에 대한 상대적 위치에 따라 다음의 a, b, c를 정의한다.

NamePosition
x필터링할 바이트
ax 바로 왼쪽 픽셀에서 x에 대응되는 바이트
bx 바로 위 행(row, scanline)에서 x에 대응되는 바이트
cb 바로 왼쪽 픽셀에서 b에 대응되는 바이트
1
2
c b
a x

간단하게 그림으로 나타내면 위와 같다. 이때 대응된다라는 것은 픽셀에서 같은 위치에 있는 값이라는 뜻이다.

가령 color type: RGB, bit depth: 16인 경우를 생각해보면, 한 픽셀은 6바이트로 구성되는데,

1
2
A1B2C3 D4E5F6
012345 567890

필터링 하고자하는 x가 우측 하단의 8이라고 하면, a=3, b=5, c=2가 된다.

1
만약 bit depth가 8보다 작다면, 필터링 하는 단위가 여러 채널이 될 수 있다. 이 경우는 픽셀 단위로 대응되는 바이트를 찾는 것이 아니라, 바이트 단위로 찾게 된다. (즉, 왼쪽 바이트, 위쪽 바이트를 사용한다)

와 같이 정의되는 값을 바탕으로, 아래의 5가지 필터 타입을 적용한다.

TypeNameFilter FunctionReconstruction Function
0NoneFilt(x) = Orig(x)Recon(x) = Filt(x)
1SubFilt(x) = Orig(x) - Orig(a)Recon(x) = Filt(x) + Recon(a)
2UpFilt(x) = Orig(x) - Orig(b)Recon(x) = Filt(x) + Recon(b)
3AverageFilt(x) = Orig(x) - floor((Orig(a) + Orig(b)) / 2)Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2)
4PaethFilt(x) = Orig(x) - PaethPredictor(Orig(a), Orig(b), Orig(c ))Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c ))

IEND

1
2
3
4
5
6
{
 Length : 00 00 00 00 (0 byte),
 Chunk Type : IEND,
 Chunk Data (0 byte),
 CRC
}

IEND 청크는 이미지의 맨 뒤에 위치하는 청크로 PNG 파일의 끝을 나타낸다. 데이터를 담는 목적으로 사용하지 않으므로 않으므로 Length 값은 언제나 0이다.


[참고 자료]

[1] https://ryanking13.github.io/2018/03/24/png-structure.html
[2] https://www.w3.org/TR/PNG/
[3] https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=nms200299&logNo=221637277602
[4] https://blog.naver.com/hoon0917sjs/220146244609
[5] https://ctf-wiki.mahaloz.re/misc/picture/png/

This post is licensed under CC BY 4.0 by the author.

2021 Black Hat

Password Crack - Chorme