Check - Writeup by AI
题目信息
| 项目 | 内容 |
|---|---|
| 题目名称 | Check |
| 题目来源 | 攻防世界 |
| 题目类型 | Misc / 隐写术 |
| 题目文件 | check.png (153.2KB) |
| 解题环境 | Python 3.x + Pillow + exifread |
题目描述
本题提供了一张 PNG 图片,要求分析其中隐藏的信息并提取出 flag。这是一道典型的隐写术题目,考察对 LSB 隐写和 HTML 实体编码的识别与解码能力。
考点分析
核心考点
| 考点 | 说明 | 重要性 |
|---|---|---|
| PNG 文件结构 | 了解 PNG 文件的基本结构和块 (chunk) 组织方式 | ⭐⭐⭐ |
| LSB 隐写原理 | 理解最低有效位 (Least Significant Bit) 隐写技术 | ⭐⭐⭐⭐⭐ |
| HTML 实体编码 | 识别和解码 HTML 十六进制实体编码 (&#xHH; 格式) |
⭐⭐⭐⭐ |
| Python 图像处理 | 使用 PIL/Pillow 库处理和分析图像数据 | ⭐⭐⭐ |
| 多重编码识别 | 发现 LSB 中隐藏的 HTML 实体编码并逐层解码 | ⭐⭐⭐⭐⭐ |
技术要点
-
LSB 隐写特点:
- 修改像素颜色值的最后一位二进制数
- 人眼无法察觉这种微小的颜色变化
- 每个像素的 RGB 三个通道都可以隐藏 1 bit 数据
- 对于 510×340 的图片,可隐藏约 520,200 bits 数据
-
HTML 十六进制实体编码:
- 格式:
&#x+ 十六进制数字 +; - 例如:
f表示字符 'f' (ASCII 码 0x66) - 常用于 Web 开发中转义特殊字符
- 格式:
解题思路
整体流程
否
是
是
否
获取题目文件
文件基本信息检查
PNG 块结构分析
EXIF 元数据检查
是否发现线索?
LSB 隐写分析
直接提取 flag
发现 HTML 实体编码?
解码 HTML 实体
其他隐写方法
提取 flag
详细分析步骤
步骤 1: 文件基本信息检查
bash
文件大小:156925 bytes (153.25 KB)
文件头:89504e470d0a1a0a0000000d49484452
文件类型:PNG 图片 ✓
确认是标准 PNG 文件,文件头签名正确。
步骤 2: PNG 块结构分析
PNG 文件由多个块 (chunk) 组成:
| 块名 | 长度 | 说明 |
|---|---|---|
| IHDR | 13 bytes | 图片头信息(宽度、高度、颜色模式等) |
| IDAT | 65536 bytes | 压缩的图像数据块(第 1 块) |
| IDAT | 65536 bytes | 压缩的图像数据块(第 2 块) |
| IDAT | 25772 bytes | 压缩的图像数据块(第 3 块) |
| IEND | 0 bytes | 图片结束标记 |
检查结果:未发现异常的文本块(tEXt、iTXt、zTXt),排除简单的文本隐藏。
步骤 3: EXIF 元数据检查
PNG 文件通常不包含 EXIF 信息,本题中也未发现有用线索。
步骤 4: LSB 隐写分析(关键步骤)
LSB 提取原理:
对于每个像素的 RGB 值:
python
# 示例:像素 (R=173, G=216, B=199)
R = 173 = 1010110[1] # LSB = 1
G = 216 = 1101100[0] # LSB = 0
B = 199 = 1100011[1] # LSB = 1
# 提取的比特流:1, 0, 1...
提取过程:
- 打开图片并转换为 RGB 模式
- 读取所有像素数据
- 提取每个像素 RGB 通道的最低位
- 将比特流每 8 位重组为一个字节
- 转换为 ASCII 字符串
关键发现 :
在 LSB 数据中发现 HTML 十六进制实体编码序列:
flag{h0w_4bouT...
步骤 5: HTML 实体解码
识别到编码格式后,进行逐字符解码:
| HTML 实体 | 十六进制 | 十进制 | 字符 |
|---|---|---|---|
f |
0x66 | 102 | f |
l |
0x6c | 108 | l |
a |
0x61 | 97 | a |
g |
0x67 | 103 | g |
{ |
0x7b | 123 | { |
h |
0x68 | 104 | h |
0 |
0x30 | 48 | 0 |
w |
0x77 | 119 | w |
| ... | ... | ... | ... |
详细步骤
环境准备
安装必要的 Python 库:
bash
pip install exifread Pillow
完整解题代码
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CTF Misc 2-Check Solve Script - 优化版
题目类型:图片隐写/文件分析
来源:攻防世界 Misc 类题目
"""
import os
import struct
from PIL import Image
import exifread
import re
def check_file_info(file_path):
"""检查文件基本信息"""
print("=" * 60)
print("【1】文件基本信息")
print("=" * 60)
file_size = os.path.getsize(file_path)
print(f"文件大小:{file_size} bytes ({file_size / 1024:.2f} KB)")
with open(file_path, 'rb') as f:
header = f.read(20)
print(f"文件头 (16 进制): {header[:16].hex()}")
if header[:8] == b'\x89PNG\r\n\x1a\n':
print("✓ 文件类型:PNG 图片")
else:
print(f"文件类型:未知 (签名:{header[:8]})")
def analyze_png_chunks(file_path):
"""分析 PNG 文件块结构"""
print("\n" + "=" * 60)
print("【2】PNG 文件块分析")
print("=" * 60)
with open(file_path, 'rb') as f:
f.read(8) # 跳过 PNG 签名
chunks = []
while True:
try:
length_data = f.read(4)
if len(length_data) < 4:
break
length = struct.unpack('>I', length_data)[0]
chunk_type = f.read(4)
if len(chunk_type) < 4:
break
chunk_name = chunk_type.decode('utf-8', errors='ignore')
chunk_data = f.read(length)
crc_data = f.read(4)
chunks.append({
'name': chunk_name,
'length': length,
'data': chunk_data
})
print(f"块:{chunk_name:4s}, 长度:{length:6d} bytes")
if chunk_name in ['tEXt', 'iTXt', 'zTXt']:
print(f" └─ 发现文本块!尝试解析...")
try:
null_pos = chunk_data.find(b'\x00')
if null_pos > 0:
keyword = chunk_data[:null_pos].decode('latin-1')
text = chunk_data[null_pos+1:].decode('latin-1')
print(f" 关键字:{keyword}")
print(f" 内容:{text}")
except Exception as e:
print(f" 解析失败:{e}")
except Exception as e:
print(f"解析错误:{e}")
break
def check_exif(file_path):
"""检查 EXIF 信息"""
print("\n" + "=" * 60)
print("【3】EXIF 元数据分析")
print("=" * 60)
try:
with open(file_path, 'rb') as f:
exif_data = exifread.process_file(f, details=False)
if exif_data:
print(f"找到 {len(exif_data)} 个 EXIF 字段:")
for tag in sorted(exif_data.keys()):
if tag not in ['JPEGThumbnail', 'TIFFThumbnail']:
print(f" {tag}: {exif_data[tag]}")
else:
print("未发现 EXIF 信息")
except Exception as e:
print(f"读取 EXIF 失败:{e}")
def extract_lsb_data(file_path):
"""提取 LSB 数据"""
try:
img = Image.open(file_path)
if img.mode != 'RGB' and img.mode != 'RGBA':
img = img.convert('RGB')
pixels = list(img.getdata())
all_bits = []
for pixel in pixels:
r, g, b = pixel[:3]
all_bits.extend([r & 1, g & 1, b & 1])
data = []
for i in range(0, min(len(all_bits), 200000) - 7, 8):
byte = 0
for j in range(8):
byte = (byte << 1) | all_bits[i + j]
data.append(byte)
return bytes(data).decode('latin-1', errors='ignore')
except Exception as e:
print(f"LSB 提取失败:{e}")
return ""
def decode_html_entities(text):
"""解码 HTML 十六进制实体"""
entities = re.findall(r'&#x([0-9a-fA-F]+);', text)
if entities:
decoded = ''.join([chr(int(e, 16)) for e in entities])
return decoded
return ""
def lsb_analysis_optimized(file_path):
"""优化的 LSB 分析"""
print("\n" + "=" * 60)
print("【4】LSB 隐写分析")
print("=" * 60)
try:
img = Image.open(file_path)
print(f"图片尺寸:{img.size[0]} x {img.size[1]}")
print(f"图片模式:{img.mode}")
extracted_str = extract_lsb_data(file_path)
if not extracted_str:
print("未提取到有效数据")
return None
html_pattern = r'(&#x[0-9a-fA-F]+;)+'
matches = re.findall(html_pattern, extracted_str)
if matches:
print(f"\n✓ 发现 HTML 实体编码序列 ({len(matches)} 组)")
longest_match = max(re.finditer(html_pattern, extracted_str), key=lambda m: len(m.group()))
full_encoded = longest_match.group()
print(f"\n提取的 HTML 实体序列:")
display_len = min(200, len(full_encoded))
print(f"{full_encoded[:display_len]}..." if len(full_encoded) > display_len else full_encoded)
decoded = decode_html_entities(full_encoded)
print(f"\n解码结果:")
print(decoded)
flag_match = re.search(r'flag\{[^}]+\}', decoded)
if flag_match:
print(f"\n🎯 成功提取 FLAG: {flag_match.group()}")
return flag_match.group()
else:
print("\n未发现 HTML 实体编码")
if 'flag' in extracted_str.lower():
flag_match = re.search(r'flag\{[^}]+\}', extracted_str)
if flag_match:
print(f"\n🎯 直接发现 FLAG: {flag_match.group()}")
return flag_match.group()
return None
except Exception as e:
print(f"LSB 分析失败:{e}")
return None
def main():
"""主函数"""
file_path = "check.png"
if not os.path.exists(file_path):
print(f"错误:文件 {file_path} 不存在")
return
print("\n")
print("╔" + "=" * 58 + "╗")
print("║" + " " * 12 + "CTF Misc 2-Check 解题脚本 (优化版)" + " " * 12 + "║")
print("╚" + "=" * 58 + "╝")
check_file_info(file_path)
analyze_png_chunks(file_path)
check_exif(file_path)
flag = lsb_analysis_optimized(file_path)
print("\n" + "=" * 60)
if flag:
print(f"✅ 成功获取 FLAG: {flag}")
else:
print("分析完成!请检查以上结果寻找 flag。")
print("=" * 60 + "\n")
if __name__ == "__main__":
main()
总结
技术要点回顾
| 技术点 | 掌握程度 | 说明 |
|---|---|---|
| PNG 文件结构 | ✅ 掌握 | 理解 IHDR、IDAT、IEND 等块的作用 |
| LSB 隐写提取 | ✅ 掌握 | 能从 RGB 通道提取最低有效位 |
| HTML 实体识别 | ✅ 掌握 | 快速识别 &#xHH; 编码格式 |
| 正则表达式 | ✅ 掌握 | 使用正则匹配和提取编码序列 |
| Python 图像处理 | ✅ 掌握 | 熟练使用 PIL/Pillow 处理图片 |
解题关键点
-
双重隐藏技术:
- 第一层:LSB 隐写(隐藏在像素最低位)
- 第二层:HTML 实体编码(隐藏真实内容)
-
正确的解码顺序:
原始图片 → LSB 提取 → HTML 实体编码 → 解码 → 明文 flag -
编码识别能力:
- 看到
&#x开头要立即想到 HTML 实体编码 - 这是 Web 安全中常见的编码方式
- 看到