EasyCTF 2017 Forensics Write Up

作者:Jing Ling
博客:HackFun

0x02 Forensics

Mane Event

problem

My friend just got back from the plains and he took this picture with his new camera. He also told me there’s a flag hidden in it - can you check it out for me?

solution

20xx

problem

My friend sent me this file and told me to git gud.

polutin

20xx writeup

scisnerof

problem

I found weird file! elif

solution

png文件内容被倒序处理了,写一个脚本恢复:

1
2
3
4
5
6
7
8
9
with open("elif", "rb") as file:
with open("new.png", "wb") as png:
data = []
byte = file.read(1)
while byte != "":
data.append(byte)
byte = file.read(1)
for i in reversed(data):
png.write(i)

flag:easyctf{r3v3r5ed_4ensics}

Petty Difference

problem

I found two files in a secret room. They look like jumbled letters with no patterns. I mean look at it! file1 is identical to file2, right?

solution

1
2
3
4
5
6
7
8
9
10
11
12
13
with open("file1.txt") as file1:
str1 = file1.read()
with open("file2.txt") as file2:
str2 = file2.read()
str1_diff = ''
str2_diff = ''
for x in range(len(str1)):
if str1[x] != str2[x]:
str1_diff = str1_diff + str1[x]
str2_diff = str2_diff + str2[x]
print(str1_diff)
print(str2_diff)
print("flag:%s" %str1_diff[::-1])

Flag Collection

problem

Here’s a collection of flags! I think you’re looking for a specific one, though…

solution

Thumbs.db
thumbs viewer

easyctf{thumbs.db_c4n_b3_useful}

Zooooooom

problem

Hekkerman is looking awfully spooky. That hekker glare could pierce a firewall. What can he see that you can’t?

solution

图片由三张图片合并拼接而成,分别分离出得到flag:

Gibberish

problem

I have no idea what this image is, but my sources tell me that it contains something useful, a flag perhaps? Can you help me find it?
There are 3 parts to the flag. There are 3 colors of the rainbow. My flag will never expire.
Hint:Presence is more important than intensity. Everything is simply boolean. One of the parts requires a scanner.

solution

这题要仔细审题,给了很多信息,提示flag由三部分组成,又说了三种颜色,只给了一张24位的图片:

很容易联想到图像的R,G,B分离处理,打开PS把图像的R,G,B通道分离得到:

R通道:

G通道:

B通道:

题目又提示其中一张需要扫描器,综合来看3张图片有张可能是条形码,于是PS中反相,不断锐化,然后将线条填充完整,类似这样:

不过这样太麻烦,于是写个脚本自动处理图像:

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
from PIL import Image
from PIL import ImageEnhance
def invert(im):
im = im.convert("L")
pixel = im.load()
width = im.size[0]
height = im.size[1]
for x in range(width):
for y in range(height):
pixel[x, y] = 255 - pixel[x, y]
return im
def enhance(im):
im = ImageEnhance.Contrast(im).enhance(10.0)
return im
def fill(im):
im = im.convert("L")
pixel = im.load()
width = im.size[0]
height = im.size[1]
for x in range(width):
if((pixel[x, 0] != 255) or (pixel[x, 21] != 255) or (pixel[x, 43] != 255)):
for y in range(height):
pixel[x, y] = 0
return im
def main():
im = Image.open('cea3386a382bfad628a3c5edf8d61a9285ab0290_gibberish.png')
pixel = im.load()
channels = ['r', 'g', 'b']
i = 0
for im_channel in im.split():
im_invert = invert(im_channel)
im_enhance = enhance(im_invert)
im_fill = fill(im_enhance)
im_fill.save('im_' + channels[i] +'.png')
i += 1
if __name__ == '__main__':
main()

处理后分别得到R,G,B通道的图像

处理后G通道最有可能是条形码,多次扫描终于(条码扫描器Barcode Scanner):

得到8个字符:LH5i6uQz

那其他两张图像怎么处理呢,提示说任何事物都是简单的二进制,开脑洞想到可能是黑白颜色代表二进制的1,0,于是提取图像的首行像素转换二进制串再转换为字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from PIL import Image
import libnum
im_b = Image.open('im_b.png')
im_r = Image.open('im_r.png')
def image2ascii(im):
im = im.convert("L")
pixel = im.load()
width = im.size[0]
bits = ""
for x in range(width):
if(pixel[x, 0] == 255):
bits += "0"
else:
bits += "1"
print(libnum.b2s(bits))
def main():
image2ascii(im_r)
image2ascii(im_b)
if __name__ == '__main__':
main()

也是得到了两组8个字符:

aPgMasSt
5U5EYz2b

一开始以为三个部分组合可能是base64,但是怎么组合都不对,陷入江局,又看了N遍题目,说我的flag永远不会过期,GG搜索N久才发现说的是pastebin程序员的世界你不懂,他们已经占领了 github,但我们还有 PasteBin。

而你分享的内容会自动生成一个8字符的链接,这脑洞也是服,最后得到网址:

组合得到:easyctf{col0rs_b4rcod3s_and_b1nary_f?n}
激动地一提交,结果不对,仔细又开打链接检查,才发现flag里有个问号,可能是让猜测,填个u试试(fun),终于get Orz。

QR1

problem

I just saw this QR code the other day, but couldn’t tell what data it has. Can you help? Here it is.

Hint:Is the image only black and white?

solution

首先看到二维码的定位标识被反相处理了,题目还提示了说图像只有黑色和白色吗,于是推测图像可能不止黑白两种颜色,于是打开PS不断锐化原图像果然发现一些接近黑色的颜色被暴露:

需要把接近黑色的颜色画成黑色,手动画图挺麻烦还是上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from PIL import Image
def process(image):
pixels = image.load()
width = image.size[0]
height = image.size[1]
for x in range(width):
for y in range(height):
if (pixels[x, y] != 255):
if pixels[x, y] < 125:
pixels[x, y] = 0
else:
pixels[x, y] = 255
return image
def main():
image = Image.open('qr1.bmp')
image = image.convert('L')
processed_image = process(image)
processed_image.save('solved.bmp')
if __name__ == '__main__':
main()

得到处理后的图像:

接下来对定位标识进行反相处理:

easyctf{n0w_who-w0u1d_do_thAT_to_Th3ir_QR?}

Ogrewatch

problem

My friend was out watching ogres when he heard a strange sound. Could you figure out what it means? ogreman
Hint:If you’re having trouble with the file format, Gary Kessler might help.

solution

root@kali:~/Desktop# file ogreman 
ogreman: Matroska data

在了解一波MATROSKA 文件格式
给文件加上.mka后缀后使用Pot Player播放时有字幕一闪而过:

于是使用MATROSKA文件处理工具MKVToolNix中的mkvextract将字幕导出:

λ mkvextract.exe tracks 132ea90b28084ca59d251988faeecf40e4879b98_ogreman 2:zimu

将有flag的部分提取出来单独处理:

1
2
3
4
5
6
with open('flag.txt') as file:
lines_list = file.readlines()
flag = ""
for line in lines_list:
flag = flag + line[50]
print(flag) # easyctf{subs_r_b3tt3r_th@n_dub5}

Flag PEG

problem

We found a flag but it didn’t do anything. Maybe you can find a better flag?
You’re not looking deep enough.

solution

sunnyelf@ubuntu:~/Desktop$ binwalk -v heresaflag 

Scan Time:     2017-03-26 09:29:30
Target File:   /home/sunnyelf/Desktop/heresaflag
MD5 Checksum:  890fad720c23e53f4698ac04bc5f9a23
Signatures:    344

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, EXIF standard
12            0xC             TIFF image data, big-endian, offset of first image directory: 8
18357         0x47B5          Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt=
341939        0x537B3         7-zip archive data, version 0.3

sunnyelf@ubuntu:~/Desktop$ dd if=heresaflag of=1.7z skip=341939 bs=1
记录了156772+0 的读入
记录了156772+0 的写出
156772 bytes (157 kB, 153 KiB) copied, 0.211627 s, 741 kB/s

sunnyelf@ubuntu:~/Desktop$ 7z e 1.7z 

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=zh_CN.UTF-8,Utf16=on,HugeFiles=on,1 CPU)

Processing archive: 1.7z

Extracting  KHgrbikqKC0xKV5u

Everything is Ok

Size:       155553
Compressed: 156772

sunnyelf@ubuntu:~/Desktop$ python -c "import base64;print base64.b64decode('KHgrbikqKC0xKV5u')"
(x+n)*(-1)^n

看到(x+n)*(-1)^n可能是加密算法,而KHgrbikqKC0xKV5u文件可能就是已加密的文件,先拖进Hex Editor Neo看看:

看到第一个字节是0x89第一反应可能png的头,推测该加密的文件原文件是png文件,如果x表示字节内容,n表示顺序的话简单地来验证一下(0x89 = 137):(137+0)*(-1)^0=137,果然计算结果相同,那么把png文件头都使用(x+n)*(-1)^n加密再和已加密的文件对比一下呢:

结果发现规律,png文件头上的偶数位字节数值(从0开始数起)加密计算后总是与给的文件偶数位上字节数值相等,png文件头上的奇数位字节数值加密后与给的文件奇数位上的字节数值满足一种关系:给的文件奇数位上的字节数值减去经过加密的数值恒等于256,比如175-(-81)=256 182-(-74)=256,如果y=(x+n)*(-1)^n,那么x=y/(-1)^n-n ,注意计算结果可能为负值,所以要模256,所以接下来就交给脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
decrypt_data = ''
with open('KHgrbikqKC0xKV5u', 'rb') as encrypted_file:
n = 0
while 1:
byte = encrypted_file.read(1)
if not byte:
break
d = ord(byte)
if n % 2 == 0:
y = d
x = (y / ((-1) ** n) - n) % 256
decrypt_data += chr(x)
else:
y = (d - 256)
x = (y / ((-1) ** n) - n) % 256
decrypt_data += chr(x)
n += 1
with open('decrypt_file.png', 'wb') as decrypt_file:
decrypt_file.write(decrypt_data)
![](http://i.imgur.com/pa2rb5b.png)

My USB

problem

I found my usb from a long time ago. I know there’s a flag on there somewhere; can you help me find it?

solution

1
2
3
4
5
root@kali:~/Desktop# mv 2c370b79d147127064f019dcb05bba1aa917c552_usb.img usb.img
root@kali:~/Desktop# binwalk -v usb.img
root@kali:~/Desktop# foremost usb.img
![](http://i.imgur.com/XCcilKC.jpg)

Finn

problem

The Resistance intercepted this suspicious picture of Finn’s old stormtrooper helmet, sent by General Hux to Kylo Ren. Hux isn’t exactly Finn’s biggest fan. What could he be hiding? Good luck!

If you get stuck, We also have this blob of sarcasm, which may or may not be useful in your quest. Worth a shot right?

Hint:In hindsight, numerical pins make really bad passwords . . . especially if they are pop culture references, also some pixels differ by more than one

Everyone complains that my problems are too random. Fine. Here is EXACTLY how to solve this problem.

1. Wow I have an image. I wonder why it’s so big (read: grandma, what large eyes you have)
2. So I figured that part out. Great, there’s a password. I wonder what that has to do with this image. Or wait, I could just brute force it, right? Either way works. It’s called reading the problem description. Or even the title. Titles do matter.
3. Yay, 2 images. What’s the difference? Hmmmm, I wonder what would happen if I ressed that difference pictorially. 
4. Well I got some stuff, but it makes no sense. Oh wait, maybe I need a key! Let’s go back to that thing we extracted earlier, shall we? Maybe those discrepancies are ACTUALLY USEFUL. Nothing is an accident, not even random out of place pixels. Check them. Carefully.
5. What do I do with this message and key? How about, the most obvious thing in every CTF ever. Seriously.

So you got the flag. Congrats! See, it wasn’t that bad.

solution

root@kali:~/Desktop# file finn.jpg 
finn.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 118x118, segment length 16, baseline, precision 8, 630x630, frames 3
root@kali:~/Desktop# binwalk -v finn.jpg 

Scan Time:     2017-03-27 03:53:23
Target File:   /root/Desktop/finn.jpg
MD5 Checksum:  e09ee29407ca1b68db84dae5be8a52d4
Signatures:    344

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
44350         0xAD3E          Zip archive data, at least v1.0 to extract, name: kylo/
44413         0xAD7D          Zip archive data, encrypted at least v2.0 to extract, compressed size: 3489072, uncompressed size: 3488525, name: kylo/kylo1.png
3533573       0x35EB05        Zip archive data, encrypted at least v2.0 to extract, compressed size: 3489495, uncompressed size: 3488948, name: kylo/kylo2.png
7023399       0x6B2B27        End of Zip archive

root@kali:~/Desktop# foremost finn.jpg 
Processing: finn.jpg
|foundat=kylo/UT    
foundat=kylo/kylo1.pngUT*|

得到一个加密的zip,尝试暴力破解:

解压得到两种表面上相似的png图片,使用Stegsolve对比检测,当浏览到sub模式时:

扫码得到26个hex值:\x63\x68\x66\x63\x7e\x71\x73\x34\x76\x57\x72\x3c\x74\x73\x5c\x31\x75\x5d\x6b\x32\x34\x77\x59\x38\x4c\x7f,尝试解码得到chfc~qs4vWr<ts\1u]k24wY8L,结果不是flag,根据提示又看比较出来的二维码左下角貌似多了一些像素,于是在PS中对比才发现还有一些像素stegsolve没有提取出来:

刚好26个像素,将它们的灰度值提取出来:5,4,7,4,5,2,7,0,4,8,5,8,6,0,3,0,6,2,9,1,1,3,6,2,8,2,写个脚本异或看看:

1
2
3
4
5
6
7
8
9
a = [0x63,0x68,0x66,0x63,0x7e,0x71,0x73,0x34,0x76,0x57,0x72,0x3c,0x74,0x73,0x5c,0x31,0x75,0x5d,0x6b,0x32,0x34,0x77,0x59,0x38,0x4c,0x7f]
b = [5,4,7,4,5,2,7,0,4,8,5,8,6,0,3,0,6,2,9,1,1,3,6,2,8,2]
c = []
d = ''
for x in xrange(len(a)):
c.append(a[x] ^ b[x])
for y in xrange(len(c)):
d += chr(c[y])
print(d) # flag{st4r_w4rs_1s_b35t_:D}

Serial

problem

I was listening to this haystack, but I didn’t notice anything. What did I miss?

hint: 010100110110010101110010011010010110000101101100001011100010111000101110

solution

writeup

Decomphose

problem

Image arithmetic is super neat until there’s more than two images involved.

file 1

file 2

file 3

file 4

solution

给了4个压缩包,分别解压一共得到48张类似这样的图片:

放大可以看到一些像素被周围黑色像素包围,其他的图片打开放大看也是这种情况,所以尝试把所有图片中被黑色像素包围的像素提取到一张图片上看看:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os
from PIL import Image
flag = Image.new("RGB", (1280, 720))
path = "E:\sunnyelf\Desktop\easyctf 2017\Forensics\Decomphose\decomp"
for file_name in os.listdir(path):
f = Image.open(path + "\\" + file_name)
width, height = f.size
for i in xrange(width):
for j in xrange(height):
add = True
if (i > 0 and f.getpixel((i - 1, j)) != (0, 0, 0)):
add = False
if (i < width - 1 and f.getpixel((i + 1, j)) != (0, 0, 0)):
add = False
if (j > 0 and f.getpixel((i, j - 1)) != (0, 0, 0)):
add = False
if (j < height - 1 and f.getpixel((i, j + 1)) != (0, 0, 0)):
add = False
if (add):
flag.putpixel((i, j), f.getpixel((i, j)))
flag.save("flag.png")

跑了3分钟左右,看看结果:

QR2

problem

When I am not practicing my Oboe for band, I have been working on a QR code generator. For some reason, some of the images are not scannable. Here is one, can you tell me what it says?

NOTE: Due to a flag leak, this is a re-release of the problem with a new flag.

hint: Is there another kind of Oboe?

solution

writeup

0x03 Steganography

Kittycat

problem

My cats are cuter than yours :)
hint: I used to have one cat, but now I have two.

solution

图像处理基本知识

图像基本运算

Install OpenCV-Python in Windows

ffmpeg -i kittycat.avi kittycat%01d.png

把视频每帧分离后得到606张png图片,每两张表面看上去相似,先尝试每两张异或提取出不同的地方:

1
2
3
4
5
6
7
8
9
import cv2
i = 1
for x in xrange(1, 607, 2):
img1 = cv2.imread('kittycat' + str(x) +'.png')
img2 = cv2.imread('kittycat' + str(x + 1) +'.png')
xor_img = cv2.bitwise_xor(img1, img2)
cv2.imwrite('xor/xor_img' + str(i) + '.png', xor_img)
i += 1

得到303张异或后的图片,尝试将它们全部相加:

1
2
3
4
5
6
7
8
9
10
import cv2
add_img = cv2.imread('xor_img1.png')
for x in xrange(2, 304):
img = cv2.imread('xor_img'+ str(x) +'.png')
add_img = cv2.add(add_img, img)
cv2.imwrite('add_img.png', add_img)
![](http://i.imgur.com/87WajrC.png)

PS处理一下:

Bizarro

problem

Something seems very strange about this strange looking image. Check it out?

hint: Red herrings are always a touchy subject. Combine this hint with intel you find in the problem, throw in a blind guess, and perhaps you’ll stumble into the answer.

solution

CI XCVII CXV CXXI XCIX CXVI CII CXXIII CXVI CIV CV CXV XCV CV CXV XCV CX CXI CXVI XCV CXVI CIV CI XCV CII CVIII XCVII CIII CXXV XXXII CV XXXII CVII CX CXI CXIX XXXII CXVI CIV CV CXV XXXII CII CVIII XCVII CIII XXXII CV CXV XXXII CXIX CI CV CXIV C XXXII CV XXXII CVI CXVII CXV CXVI XXXII XCIX XCVII CX XXXIX CXVI XXXII CXII CXVII CXVI XXXII CIX CXXI XXXII CII CV CX CIII CI CXIV XXXII CXI CX XXXII CXIX CIV CXXI

罗马数字Roman numerals

在线转换一下,得到一些数字,数字大小都不超过255,有可能是ASCII值:


1
2
3
4
5
6
nums = [101,97,115,121,99,116,102,123,116,104,105,115,95,105,115,95,110,111,116,95,116,104,101,95,102,108,97,103,125,32,105,32,107,110,111,119,32,116,104,105,115,32,102,108,97,103,32,105,115,32,119,101,105,114,100,32,105,32,106,117,115,116,32,99,97,110,39,116,32,112,117,116,32,109,121,32,102,105,110,103,101,114,32,111,110,32,119,104,121]
txt = ''
for n in nums:
txt += chr(n)
print(txt)

easyctf{this_is_not_the_flag} i know this flag is weird i just can't put my finger on why

额,好吧,后来看了大神的writeup跪了。