Problem URL: https://github.com/SECCON/SECCON2015_online_CTF/tree/master/Stegano/100_Steganography%202

sunrise.zip includes a PNG file.
At first glance, this file has no problem but there is strange chunk at last IDAT part.

This chunk seems to be not shown because the picture is simple flying photography.
I extend its height by rewriting IHDR. Then flag appear.

I created following script to analyze and rewrite the picture.

png_analyzer.py
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import sys
import struct
import binascii

class PNG:

def __init__(self, binary):
self.binary = binary

def is_PNG(self):
return struct.unpack_from(">Q", self.binary, 0)[0] == 9894494448401390090

def chunk(self, offset):
chunk = struct.unpack_from(">I4s", self.binary, offset)
chunk += struct.unpack_from(">I", self.binary, offset + chunk[0] + 8)
return chunk

def chunk_data(self, chunk, offset):
if chunk[1] == b'IHDR':
return self.IHDR(chunk, offset)
elif chunk[1] == b'sRGB':
return self.sRGB(chunk, offset)
elif chunk[1] == b'gAMA':
return self.gAMA(chunk, offset)
elif chunk[1] == b'pHYs':
return self.pHYs(chunk, offset)
elif chunk[1] == b'tEXt':
return self.tEXt(chunk, offset)
elif chunk[1] == b'tIME':
return self.tIME(chunk, offset)
elif chunk[1] == b'IDAT':
return self.IDAT(chunk, offset)
elif chunk[1] == b'IEND':
return self.IEND(chunk, offset)
else:
return False

def CRC32(self, chunk, offset):
crc_data = chunk[1]
crc_data += struct.unpack_from(">%ds" % chunk[0], self.binary, offset + 8)[0]
return binascii.crc32(crc_data)

def IHDR(self, chunk, offset):
labels = ('width', 'height', 'bit depth', 'colour type', 'compression method', 'filter method', 'interlace method')
values = struct.unpack_from(">2I5B", self.binary, offset + 8)
return dict(zip(labels, values))

def sRGB(self, chunk, offset):
labels = ('rendering intent',)
values = struct.unpack_from(">B", self.binary, offset + 8)
return dict(zip(labels, values))

def gAMA(self, chunk, offset):
labels = ('image gamma',)
values = struct.unpack_from(">I", self.binary, offset + 8)
return dict(zip(labels, values))

def pHYs(self, chunk, offset):
labels = ('pixels per unit, X axis', 'pixels per unit, Y axis', 'unit specifier')
values = struct.unpack_from(">IIB", self.binary, offset + 8)
return dict(zip(labels, values))

def tEXt(self, chunk, offset):
offset += 8
data_offset = 0
keyword = ''
while(True):
c = struct.unpack_from(">s", self.binary, offset + data_offset)
if c[0] == b'\x00': break
keyword += str(c[0])
data_offset += 1
data_offset += 1
text_string = ''
while(data_offset < chunk[0]):
c = struct.unpack_from(">s", self.binary, offset + data_offset)
text_string += str(c[0])
data_offset += 1
return {'keyword': keyword, 'text string': text_string}

def tIME(self, chunk, offset):
labels = ('year', 'month', 'day', 'hour', 'minute', 'second')
values = struct.unpack_from(">H5B", self.binary, offset + 8)
return dict(zip(labels, values))

def IDAT(self, chunk, offset):
labels = ('image data',)
values = ('TODO',)
return dict(zip(labels, values))

def IEND(self, chunk, offset):
return ()

with open('./sunrise.png', 'rb') as f:
png = PNG(f.read())
if not png.is_PNG():
print('ERROR: This is not png')
sys.exit(1)

offset = 8
while(True):
print('--------------------')
chunk = png.chunk(offset)
print(chunk)
if(chunk[1] == b'IEND'):
break
chunk_data = png.chunk_data(chunk, offset)
print(chunk_data)
crc = png.CRC32(chunk, offset)
if chunk[2] != crc:
print('WARNING: CRC error. expected: %d actual: %d' % (chunk[2], crc))
if chunk_data == False:
break
offset += chunk[0] + 12
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
% file sunrise.png
sunrise.png: PNG image data, 3024 x 4032, 8-bit/color RGBA, non-interlaced

% python3.6 png_analyzer.py | head
--------------------
(13, b'IHDR', 2686130754)
{'width': 3024, 'height': 4032, 'bit depth': 8, 'colour type': 6, 'compression method': 0, 'filter method': 0, 'interlace method': 0}
--------------------
(1, b'sRGB', 2932743401)
{'rendering intent': 0}
--------------------
(4, b'gAMA', 201089285)
{'image gamma': 45455}
--------------------

# rewrite the height from 0fC0 to 10C0

% python3.6 png_analyzer.py | head
--------------------
(13, b'IHDR', 2686130754)
{'width': 3024, 'height': 4288, 'bit depth': 8, 'colour type': 6, 'compression method': 0, 'filter method': 0, 'interlace method': 0}
WARNING: CRC error. expected: 2686130754 actual: 831387237
--------------------
(1, b'sRGB', 2932743401)
{'rendering intent': 0}
--------------------
(4, b'gAMA', 201089285)
{'image gamma': 45455}

# rewrite the CRC in IHDR chunk to 318DF665

% python3.6 png_analyzer.py | head
--------------------
(13, b'IHDR', 831387237)
{'width': 3024, 'height': 4288, 'bit depth': 8, 'colour type': 6, 'compression method': 0, 'filter method': 0, 'interlace method': 0}
--------------------
(1, b'sRGB', 2932743401)
{'rendering intent': 0}
--------------------
(4, b'gAMA', 201089285)
{'image gamma': 45455}