[Forensics] ASIS CTF finals 2016 - p1ng


Average: 3.60
Rating Count: 5
You Rated: Not rated
Points
121
Solves
24
Category
Forensic

Description

p1ng is ASIS hand-drawn PNG.
http://asis-ctf.ir/tasks/p1ng.txz_76eca77720a65d95557a3850929abd0a8a18c636

We have a png file inspecting with binwalk we can see this strange compressed data:

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

kinyabitch@Debian ~/h/c/a/f/p1ng> binwalk p1ng/p1ng

DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 PNG image, 180 x 76, 8-bit/color RGBA, non-interlaced
99 0x63 Zlib compressed data, best compression
4987 0x137B Zlib compressed data, best compression
9484 0x250C Zlib compressed data, best compression
17713 0x4531 Zlib compressed data, best compression
22512 0x57F0 Zlib compressed data, best compression
29380 0x72C4 Zlib compressed data, best compression
36947 0x9053 Zlib compressed data, best compression
43723 0xAACB Zlib compressed data, best compression
51878 0xCAA6 Zlib compressed data, best compression
58000 0xE290 Zlib compressed data, best compression
65549 0x1000D Zlib compressed data, best compression
72231 0x11A27 Zlib compressed data, best compression
79133 0x1351D Zlib compressed data, best compression
85159 0x14CA7 Zlib compressed data, best compression
92012 0x1676C Zlib compressed data, best compression
98842 0x1821A Zlib compressed data, best compression
105524 0x19C34 Zlib compressed data, best compression
113043 0x1B993 Zlib compressed data, best compression
119801 0x1D3F9 Zlib compressed data, best compression
127259 0x1F11B Zlib compressed data, best compression
134259 0x20C73 Zlib compressed data, best compression
139926 0x22296 Zlib compressed data, best compression
146983 0x23E27 Zlib compressed data, best compression

if we inspect with a hex editor or even easier using pngsplit to split the png chunks we can find some unusual type chunks like fdAT, fcTL and acTL:

1
2
3
4
5
6

kinyabitch@Debian ~/h/c/a/f/p1ng> ls p1ng/qwe/
p1ng.0000.sig p1ng.0004.IDAT p1ng.0008.fdAT p1ng.0012.fdAT p1ng.0016.fdAT p1ng.0020.fdAT p1ng.0024.fdAT p1ng.0028.fdAT p1ng.0032.fdAT p1ng.0036.fdAT p1ng.0040.fdAT p1ng.0044.fdAT p1ng.0048.fdAT
p1ng.0001.IHDR p1ng.0005.fcTL p1ng.0009.fcTL p1ng.0013.fcTL p1ng.0017.fcTL p1ng.0021.fcTL p1ng.0025.fcTL p1ng.0029.fcTL p1ng.0033.fcTL p1ng.0037.fcTL p1ng.0041.fcTL p1ng.0045.fcTL p1ng.0049.IEND
p1ng.0002.acTL p1ng.0006.fdAT p1ng.0010.fdAT p1ng.0014.fdAT p1ng.0018.fdAT p1ng.0022.fdAT p1ng.0026.fdAT p1ng.0030.fdAT p1ng.0034.fdAT p1ng.0038.fdAT p1ng.0042.fdAT p1ng.0046.fdAT
p1ng.0003.fcTL p1ng.0007.fcTL p1ng.0011.fcTL p1ng.0015.fcTL p1ng.0019.fcTL p1ng.0023.fcTL p1ng.0027.fcTL p1ng.0031.fcTL p1ng.0035.fcTL p1ng.0039.fcTL p1ng.0043.fcTL p1ng.0047.fcTL

After some search on google I found this links https://wiki.mozilla.org/APNG_Specification and http://fileformats.wikia.com/wiki/Animated_Portable_Network_Graphics , this is a APNG it’s a png but animated! So our job here is to split the animation images and then maybe we can find the flag. There is a lot of tools online to do this but for the curiosity I managed to write one in python, some images were broken I needed to do some adjustments to the IDHR header to fix it:

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

import os
import sys
import struct
import binascii

directory = "outp/"
signature = ""
ihdr_header = ""
fdat_chunk = ""
idat_chunk = ""
iend_chunk = ""
ihdrs = []

parts = []

PNG_SIGN = b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"


def is_png(png):
"""Test if @png is valid png file by checking signature

@png can be str of the filename, a file-like object, or a bytes object.
"""
if isinstance(png, str):
with open(png, "rb") as f:
png = f.read(8)

if hasattr(png, "read"):
png = png.read(8)

return png[:8] == PNG_SIGN


def chunks(png):
"""Yield chunks from png.

@png can be a string of filename, a file-like object, or a bytes bject.
"""
if not is_png(png):
# convert to png
if isinstance(png, bytes):
with io.BytesIO(png) as f:
with io.BytesIO() as f2:
PIL.Image.open(f).save(f2, "PNG", optimize=True)
png = f2.getvalue()
else:
with io.BytesIO() as f2:
PIL.Image.open(png).save(f2, "PNG", optimize=True)
png = f2.getvalue()

if isinstance(png, str):
# file name
with open(png, "rb") as f:
png = f.read()

if hasattr(png, "read"):
# file like
png = png.read()

return chunks_read(png)


def make_chunk(type, data):
"""Create chunk with @type and chunk data @data.

It will calculate length and crc for you. Return bytes.

@type is str and @data is bytes.
"""
out = struct.pack("!I", len(data))
data = type.encode("latin-1") + data
a = '%08x' % (binascii.crc32(data) % (1<<32))
out += data + a.decode('hex')
return out


def chunks_read(b):
"""Parse PNG bytes into different chunks, yielding (type, data).

@type is a string of chunk type.
@data is the bytes of the chunk. Including length, type, data, and crc.
"""
# skip signature
i = 8
# yield chunks

while i < len(b):
data_len, = struct.unpack("!I", b[i:i + 4])
type = b[i + 4:i + 8].decode("latin-1")
yield type, b[i:i + data_len + 12]
i += data_len + 12
# 6
if __name__ == '__main__':
i = 0
t = 0
frame_chunks = []
frames = []
for ctype, data in list(chunks('p1ng/p1ng')):
if ctype == "IHDR":
ihdr_header = data
hdr = ihdr_header
elif ctype == "acTL": # ignore Animation Control Chunk
continue
elif ctype == "fcTL": # https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
"""
"""
c = struct.unpack("!IIIIHHbb", data[12:-4])
width = 180
height = 76
if i in [11,12,13,2,9]:
width = c[0]
height = c[1]
if i in [7,13,21,22]:
width -= 1
#height = c[1]
if i == 6:
width -= 2
print c
ihdr = make_chunk("IHDR", struct.pack("!II", width + c[2], height+ c[3]) + hdr[16:-4])
ihdrs.append(ihdr)
#i += 1
elif ctype == "IDAT":
parts.append(("IDAT", data))
i += 1
elif ctype == "fdAT": # https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk
parts.append(("IDAT", make_chunk("IDAT", data[12:-4])))
i += 1
elif ctype == "IEND":
iend_chunk= data
break

if not os.path.exists(directory):
os.makedirs(directory)
for i in range(len(parts)):
#parts[i] = PNG_SIGN + parts[0] + pallets[i] + parts[i] + parts[-1]
f = open(directory + 'p1ng%d.png' % i, 'w+')
if i == 0:
ihdrs[i] = ihdr_header
f.write(PNG_SIGN + ihdrs[i] + parts[i][1] + iend_chunk)
f.close()

After running the script you can get 22 imgs splited into a directory, after joining them you can construct the flag:
ASIS{As_l0n9_4s_CTF_3x1sts_th3r3_w1ll_b3_ASIS_4nd_4s_l0n9_4s_ASIS_3x1sts_th3r3_w1ll_b3_PNG!}