隐写术是关于信息隐藏,即不让计划的接收者之外的任何人知道信息的传递事件(而不只是信息的内容)的一门技巧与科学。英文写作Steganography,而本套教程内容将带大家了解一下CTF赛场上常见的图片隐写方式,以及解决方法。有必要强调的是,隐写术与密码编码是完全不同的概念。
本次图片隐写实验包括四大部分
首先这里需要明确一下我这里所说的文件结构是什么意思。文件结构特指的是图片文件的文件结构。我们这里主要讲的是PNG图片的文件结构。
PNG,图像文件存储格式,其设计目的是试图替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。是一种位图文件(bitmap file)存储格式,读作“ping”。PNG用来存储灰度图像时,灰度图像的深度可多到16位,存储彩色图像时,彩色图像的深度可多到48位,并且还可存储多到16位的α通道数据。
对于一个正常的PNG图片来讲,其文件头总是由固定的字节来表示的,以16进制表示即位 89 50 4E 47 0D 0A 1A 0A,这一部分称作文件头。
标准的PNG文件结构应包括:
如图中蓝色的部分即IHDR数据块。
IDAT 数据块
它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。这是一个可以存在多个数据块类型的数据块。它的作用就是存储着图像真正的数据。
因为它是可以存在多个的,所以即使我们写入一个多余的IDAT也不会多大影响肉眼对图片的观察
下面进行实验Part 2 基于文件结构的隐写
背景知识中,我们了解到,图片的高度,宽度的值存放于PNG图片的文件头数据块,那么我们就是可以通过修改PNG图片的高度值,来对部分信息进行隐藏的。
- 实验:
- 在实验机中找到隐写术目录,打开图片隐写,打开图片隐写第二部分文件夹
- 在该文件夹找到 hight.png,
- 双击打开图片,我们先确认一下图片内容并没有什么异常
- 正如前文所说,我们这个实验部分讲的是图片高度值被修改引起的的隐写方式,所以我们010Editor
- 在010Editor运行PNG模板,这样方便于我们修改PNG图片的高度值
- 找到PNG图片高度值对应的地方,然后修改为一个较大的值,并重新计算,修改CRC校验值,并保存文件
- 打开保存后的图片,发现底部看到了之前被隐写的信息
用010Editor打开图片,运行PNG模板
10 editor呢?因为这个16进制编辑器,有模版功能,当我们运行模版后,可以轻易的找到图片的各个数据块的位置以及内容。
10 editor这个16进制编辑器,有模版功能,当我们运行模版后,可以轻易的找到图片的各个数据块的位置以及内容。
找到PNG图片高度值所对应的位置,并修改为一个较大的值
我们找到IHDR数据块,并翻到struct IHDR Ihdr位置,修改height的值到一个较大的值,如从700修改到800。
使用CRC Calculator重新计算CRC校验值
输入参数,然后点击Calculator计算,得到CRC值
为什么要重新计算CRC校验值呢?防止图片被我们修改后,自身的CRC校验报错,导致图片不能正常打开。
修改相应的CRC校验值,为我们重新计算后数值
这个实验,我们进行了PNG图片高度修改以及CRC校验值的重计算,那么请大家以下问题
在背景知识中,我们提到了一个重要的概念就是图片的IDAT块是可以存在多个的,这导致了我们可以将隐写西信息以IDAT块的形似加入图片。
- 实验:
- 在实验机中找到隐写术目录,打开图片隐写,打开图片隐写第二部分文件夹
- 在该文件夹找到 hidden.png,
- 双击打开图片,我们先确认一下图片内容并没有什么异常
- 使用pngcheck先对图片检测
- 在pngcheck的检测下,我们会发现异常信息,我们对异常的块进行提取
- 编写脚本,提取异常信息
前景知识
pngcheck可以验证PNG图片的完整性(通过检查内部CRC-32校验和&bra;比特&ket;)和解压缩图像数据;它能够转储几乎所有任选的块级别信息在该图像中的可读数据。
我们使用pngcheck -v hidden.png 如此的命令对图片进行检测
使用pngcheck对图片进行检测
我们使用命令:
pngcheck -v hidden.png
对图片的文件结构进行检测。
发现异常,并判断异常的原因
我们会发现,图片的的数据块形式是如下的
Type: IHDR
Size: 13
CRC : 5412913F
Pos : 33
Type: IDAT
Size: 10980
CRC : 98F96EEB
Pos : 11025
Type: IEND
Size: 0
CRC : AE426082
我们会惊讶的发现pos为11025的size居然为0,这是一块有问题的地方,我们可以怀疑,这一块是隐写的信息。
编写脚本并提取内容
#!/usr/bin/python
from struct import unpack
from binascii import hexlify, unhexlify
import sys, zlib
# Returns [Position, Chunk Size, Chunk Type, Chunk Data, Chunk CRC]
def getChunk(buf, pos):
a = []
a.append(pos)
size = unpack('!I', buf[pos:pos+4])[0]
# Chunk Size
a.append(buf[pos:pos+4])
# Chunk Type
a.append(buf[pos+4:pos+8])
# Chunk Data
a.append(buf[pos+8:pos+8+size])
# Chunk CRC
a.append(buf[pos+8+size:pos+12+size])
return a
def printChunk(buf, pos):
print 'Pos : '+str(pos)+''
print 'Type: ' + str(buf[pos+4:pos+8])
size = unpack('!I', buf[pos:pos+4])[0]
print 'Size: ' + str(size)
#print 'Cont: ' + str(hexlify(buf[pos+8:pos+8+size]))
print 'CRC : ' + str(hexlify(buf[pos+size+8:pos+size+12]).upper())
print
if len(sys.argv)!=2:
print 'Usage: ./this Stegano_PNG'
sys.exit(2)
buf = open(sys.argv[1]).read()
pos=0
print "PNG Signature: " + str(unpack('cccccccc', buf[pos:pos+8]))
pos+=8
chunks = []
for i in range(3):
chunks.append(getChunk(buf, pos))
printChunk(buf, pos)
pos+=unpack('!I',chunks[i][1])[0]+12
decompressed = zlib.decompress(chunks[1][3])
# Decompressed data length = height x (width * 3 + 1)
print "Data length in PNG file : ", len(chunks[1][3])
print "Decompressed data length: ", len(decompressed)
height = unpack('!I',(chunks[0][3][4:8]))[0]
width = unpack('!I',(chunks[0][3][:4]))[0]
blocksize = width * 3 + 1
filterbits = ''
for i in range(0,len(decompressed),blocksize):
bit = unpack('2401c', decompressed[i:i+blocksize])[0]
if bit == '\x00': filterbits+='0'
elif bit == '\x01': filterbits+='1'
else:
print 'Bit is not 0 or 1... Default is 0 - MAGIC!'
sys.exit(3)
s = filterbits
endianess_filterbits = [filterbits[i:i+8][::-1] for i in xrange(0, len(filterbits), 8)]
flag = ''
for x in endianess_filterbits:
if x=='00000000': break
flag += unhexlify('%x' % int('0b'+str(x), 2))
print 'Flag: ' + flag
脚本如上,flag DrgnS{WhenYouGazeIntoThePNGThePNGAlsoGazezIntoYou}.
保存后,重新打开图片,我们就能看到被隐藏的内容