MISC学习实录(一)
因为在MiGuo的MISC课上睡大觉,所以被MiGuo惩罚来学MISC ε(┬┬﹏┬┬)3
图片隐写
1.RGB
把图片用PS打开提取RGB然后转16进制拿flag(MISC就是史)
2.LSB隐写
LSB隐写原理:
RGB 中的每个通道(红、绿、蓝)都是一个 0~255 的整数(即 8位):
255 = 11111111
128 = 10000000
17 = 00010001
最低位(最右边一位)叫做 LSB(Least Significant Bit),改动它对人眼几乎不可见。
LSB隐写的本质
图像中每个像素的颜色由RGB三个通道组成,每个通道是一个 0~255(8位) 的整数:
| 数字 |
二进制(8位) |
| 218 |
11011010 |
| 150 |
10010110 |
| 149 |
10010101 |
每个颜色的最后一位(最低有效位,即 LSB)可以轻微改变,而不会被人眼察觉。比如:
11011010(LSB是0) 改成 11011011(LSB是1)
10010110(LSB是0) 改成 10010111(LSB是1)
我们就可以用这些最低位来“编码”我们要隐藏的秘密数据的二进制位。
解题:
使用StegSolve打开图片,选择Analyse,选中RGB右侧最低位,翻到最上面拿到flag
3.附加信息
给了两个文件,hidetxt.png和hidezip.png,把hidezip.png扔进010找到zip文件的文件头 50 4B 03 04 然后将zip文件头前面的全部删除,再将文件重命名为hide.zip,解压得到flag
把hidetxt.png改为hide.txt,然后扔进010拖到最后拿到flag
4.宽高
图片的IHDR控制图片的长度和宽度,把图片扔进010,定位到IHDR 部分,然后改高度的16进制,得到flag
5.zlib
这个不会
6.IDAT
一个 PNG 文件格式为:
1
|
文件头(89 50 4E 47 0D 0A 1A 0A) + 数据块 + 数据块 + 数据块…… + 文件尾(00 00 00 00 49 45 4E 44 AE 42 60 82)
|
PNG 定义了两种类型的数据块,一种是称为关键数据块,这是标准的数据块,另一种叫做辅助数据块,这是可选的数据块。关键数据块定义了4个标准数据块,每个 PNG 文件都必须包含它们。
IHDR(文件头数据块)
第一块是文件头数据块(IHDR),它由第11——32字节组成(从0开始),包含有 PNG 文件中存储的图像数据的基本信息,数据从第 16字节开始,有13个字节,其前8字节分别用4个字节规定了图片的宽和高(十六进制,以像素为单位)。
IDAT(图像数据块)
它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。它采用 LZ77 算法的派生算法进行压缩,可以用 zlib 解压缩。
IDAT 隐写
IDAT 块只有当上一个块充满(正常length最大65524)时,才会继续一个新的块。程序读取图像的时候也会在第一个未满的块停止(查了下W3C标准,其实是PNG图片在压缩的时候会在最后一个块的标记位标明这是最后一个数据块)。所以如果某一块没有满但后面却还有 IDAT 块则说明后面的块是“假”的。
我们可以用 pngcheck -v [文件名] 去查看PNG文件数据块信息,然后利用 python zlib 解压多余IDAT块的内容,此时注意剔除长度、数据块类型及末尾的CRC校验值。
1
2
3
4
5
|
import zlib
import binascii
IDAT = " ".decode('hex') #双引号中填IDAT数据
result = binascii.hexlify(zlib.decompress(IDAT))
print(result)
|
将图片放进kali中,使用binwalk 分离,得到15AAFB,发现是625个二进制,每二十五为一行拆开,然后将1转为黑块,0转为白块,得到一个二维码,扫描得到flag
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
|
# 1. 导入图像处理所需库(需先安装 Pillow:pip install pillow)
from PIL import Image, ImageDraw
# 2. 定义您提供的 01 原始数据(每行对应图像的一行,共 25 行)
binary_data = [
"1111111000100001101111111",
"1000001011100101101000001",
"1011101010000000001011101",
"1011101001000000001011101",
"1011101011101101001011101",
"1000001010101101101000001",
"1111111010101010101111111",
"0000000010111011100000000",
"1101001100000101001110110",
"1111010101001000011100000",
"0000001010000000010010011",
"0100010011100111101110011",
"1100001110111110001100101",
"0001100111000010101000110",
"1000111101011000001010001",
"0110000011011101100100001",
"1100111001000010111111101",
"0000000011010100100011110",
"1111111011100001101011011",
"1000001000011001100011110",
"1011101000110100111110000",
"1011101011000111010011100",
"1011101001001110110110001",
"1000001011000110100011000",
"1111111011010110111011011"
]
# 3. 设置图像参数(可调整 block_size 改变图像大小)
block_size = 15 # 每个 0/1 对应 15x15 像素的方块(值越大图像越清晰)
image_width = len(binary_data[0]) * block_size # 图像宽度 = 每行字符数 × 方块大小
image_height = len(binary_data) * block_size # 图像高度 = 总行数 × 方块大小
# 4. 创建白色背景图像(模式:RGB,背景色:白色 #FFFFFF)
image = Image.new("RGB", (image_width, image_height), "white")
draw = ImageDraw.Draw(image) # 创建绘图对象,用于绘制黑色块
# 5. 遍历每个字符,绘制黑白块
for row_idx, row in enumerate(binary_data):
for col_idx, char in enumerate(row):
# 计算当前方块的左上角和右下角坐标
x1 = col_idx * block_size
y1 = row_idx * block_size
x2 = x1 + block_size
y2 = y1 + block_size
# 若字符为 "1",绘制黑色块(颜色:#000000);"0" 保留白色背景
if char == "1":
draw.rectangle([x1, y1, x2, y2], fill="black")
# 6. 保存并显示图像
image.save("binary_black_white.png") # 保存为 PNG 文件(路径:脚本运行目录)
image.show() # 自动打开图像查看(部分系统可能需要手动打开文件)
print("图像已生成并保存为:binary_black_white.png")
|