ctfshow misc28:GIF 高度修改解题与脚本实现

一、题目分析

下载并解压后得到 gif 文件。

题目提示:flag 在图片下面。

GIF 的 Logical Screen Descriptor 和每个 Image Descriptor 中都包含独立的高度字段(2 字节小端序)。题目正是把这些高度字段改小,从而隐藏图片下方的 flag。因此解题思路是解析 GIF 二进制结构,定位并还原所有高度字段。

二、解题脚本

运行脚本:

python 复制代码
#!/usr/bin/env python3
"""
ctfshow misc28 GIF 高度修改 + 帧提取 + 一键还原工具
原理:GIF 的 Logical Screen Descriptor 和每个 Image Descriptor 中都有独立的
高度字段(2 字节小端序)。题目把这些字段改小来隐藏下方的 flag。
本工具解析 GIF 二进制结构,精确定位并修改所有高度字段。
用法:
python misc28_solver.py misc28.gif              # 自动修复高度
python misc28_solver.py misc28.gif -H 250 -e    # 指定高度 + 提取帧
python misc28_solver.py misc28.gif --dry-run    # 仅分析结构
python misc28_solver.py misc28.gif -r           # 一键还原原始文件
"""
import argparse
import shutil
import struct
import sys
from pathlib import Path
============================================================
GIF 二进制解析器
============================================================
class GifParser:
"""解析 GIF 文件结构,精确定位所有高度字段"""
def __init__(self, data: bytes):
    self.data = bytearray(data)
    if self.data[:6] not in (b'GIF89a', b'GIF87a'):
        raise ValueError("不是有效的 GIF 文件")
def _u16_at(self, offset: int) -> int:
return struct.unpack_from('<H', self.data, offset)[0]
def _set_u16_at(self, offset: int, value: int):
struct.pack_into('<H', self.data, offset, value)
def find_heights(self) -> list[dict]:
"""
扫描 GIF 中所有高度字段的位置和值
返回: [{'offset': int, 'value': int, 'type': str, 'frame': int}, ...]
"""
results = []
# --- 逻辑屏幕描述符 (offset 6~12, 共 7 字节) ---
# bytes 6-7: width, bytes 8-9: height
screen_height = self._u16_at(8)
results.append({
    'offset': 8, 'value': screen_height,
    'type': 'screen', 'frame': 0,
    'desc': '逻辑屏幕描述符 height'
})
pos = 13  # 6(header) + 7(Logical Screen Descriptor)
--- 全局调色板 ---
packed = self.data[10]
if packed & 0x80:
table_entries = 2 ** ((packed & 0x07) + 1)
pos += 3 * table_entries
frame_idx = 0
--- 遍历所有数据块 ---
while pos < len(self.data):
marker = self.data[pos]
if marker == 0x2C:  # 图像描述符
    frame_idx += 1
    img_height = self._u16_at(pos + 7)
    results.append({
        'offset': pos + 7, 'value': img_height,
        'type': 'image', 'frame': frame_idx,
        'desc': f'图像描述符 height 帧#{frame_idx}'
    })
pos += 10  # 跳过描述符头
local_packed = self.data[pos - 1]
if local_packed & 0x80:  # 局部调色板
    entries = 2 ** ((local_packed & 0x07) + 1)
    pos += 3 * entries

pos += 1  # LZW 最小码长
while pos < len(self.data):  # 跳过 LZW 数据子块
    block_len = self.data[pos]
    pos += 1
    if block_len == 0:
        break
    pos += block_len
elif marker == 0x21:  # 扩展块
pos += 1
ext_label = self.data[pos]
pos += 1
while pos < len(self.data):
block_len = self.data[pos]
pos += 1
if block_len == 0:
break
pos += block_len
elif marker == 0x3B:  # 结束符
break
else:
pos += 1  # 安全兜底
return results
def modify_heights(self, target: int) -> list[dict]:
"""修改所有高度字段到目标值,返回修改记录"""
heights = self.find_heights()
changes = []
for h in heights:
old = h['value']
if old != target:
self.set_u16_at(h['offset'], target)
changes.append({**h, 'old_value': old, 'new_value': target})
return changes
def get_bytes(self) -> bytes:
return bytes(self.data)
帧提取
def extract_frames(gif_path: str, out_dir: str) -> int:
"""提取 GIF 所有帧为 PNG"""
from PIL import Image
Path(out_dir).mkdir(parents=True, exist_ok=True)
img = Image.open(gif_path)
n = getattr(img, 'n_frames', 1)
print(f"\n[*] 共 {n} 帧,提取中...")
for i in range(n):
img.seek(i)
path = Path(out_dir) / f'frame{i:02d}.png'
img.save(path)
print(f"  [+] {path}")
print(f"[✓] 帧提取完毕 -> {out_dir}/")
return n
主流程
def process(args):
inp = Path(args.input)
bak = inp.with_suffix('.gif.bak')
---- 还原模式 ----
if args.restore:
if bak.exists():
shutil.copy(bak, inp)
print(f"[✓] 已从备份还原: {inp}")
else:
print(f"[!] 未找到备份 {bak},无法还原")
return
if not inp.exists():
print(f"[!] 文件不存在: {inp}")
sys.exit(1)
with open(inp, 'rb') as f:
raw = f.read()
---- 自动备份 ----
if not bak.exists():
shutil.copy(inp, bak)
print(f"[] 备份已创建: {bak}")
---- 解析 ----
gif = GifParser(raw)
heights = gif.find_heights()
---- 试运行(不修改文件) ----
if args.dry_run:
print("\n--- GIF 结构 ---")
print(f"头: {gif.data[:6].decode()}")
print(f"大小: {len(raw)} bytes")
print(f"帧数: {max(h['frame'] for h in heights)}\n")
for h in heights:
print(f"  [{h['type']:6s}] @0x{h['offset']:04X} "
f"0x{h['value']:04X} ({h['value']:5d} px) {h['desc']}")
print(f"\n[] 共 {len(heights)} 个高度字段")
return
---- 确定目标高度 ----
target = args.height
if target is None:
vals = {h['value'] for h in heights}
target = max(min(vals) * 2, 250)  # 至少 250px
print(f"\n[] 原高度: {heights[0]['value']} (0x{heights[0]['value']:04X})")
print(f"[] 目标: {target} (0x{target:04X})")
---- 修改 ----
changes = gif.modify_heights(target)
print(f"[] 已修改 {len(changes)} 个高度字段:")
for c in changes:
print(f"  [{c['type']:6s}] 帧#{c['frame']:<2d} "
f"0x{c['old_value']:04X} -> 0x{c['new_value']:04X}")
---- 保存 ----
save = args.output or str(inp)
with open(save, 'wb') as f:
f.write(gif.get_bytes())
print(f"[✓] 已保存: {save}")
---- 提取帧 ----
if args.extract:
extract_frames(save, str(Path(save).parent / 'frames'))
print(f"\n[] 提示: 还原用 -> python {sys.argv[0]} {inp} --restore")
入口
if name == 'main':
parser = argparse.ArgumentParser(
description='ctfshow misc28: GIF 高度修改 + 帧提取 + 一键还原',
epilog="""
示例:
%(prog)s misc28.gif            自动修复高度
%(prog)s misc28.gif -H 250 -e  指定高度并提取帧
%(prog)s misc28.gif -n         仅分析结构不修改
%(prog)s misc28.gif -o fixed.gif  输出到指定文件
%(prog)s misc28.gif -r         一键还原原始文件
"""
)
parser.add_argument('input', help='GIF 文件路径')
parser.add_argument('-o', '--output', help='输出文件 (默认覆盖原文件)')
parser.add_argument('-H', '--height', type=int, help='目标高度 (默认自动计算)')
parser.add_argument('-e', '--extract', action='store_true', help='提取所有帧')
parser.add_argument('-n', '--dry-run', action='store_true', help='仅分析结构')
parser.add_argument('-r', '--restore', action='store_true', help='从备份还原')
process(parser.parse_args())

三、运行结果

运行完成后得到如下结果:

相关推荐
comedate1 小时前
SMLib 的 Examples 导读:PyAPI / SM_API / SMLib 三组示例怎么读
python·cad·smlib·examples·sm_api
云和数据.ChenGuang1 小时前
Python 重写(Override)深度解析:从 MRO 到 super() 的完整机制
java·开发语言·人工智能·python·langchain·1024程序员节
川石课堂软件测试2 小时前
学习软件测试需掌握的一些知识
数据库·python·sql·功能测试·网络协议·mysql·http
美狐美颜SDK开放平台2 小时前
视频美颜SDK试用DEMO阶段应该测试什么?直播APP接入实操指南
开发语言·人工智能·深度学习·音视频·美颜sdk·直播美颜sdk·第三方美颜sdk
dtq04242 小时前
C++小案例 - 日志实战
开发语言·c++
万化冥合2 小时前
PyBox屏幕键盘如何辅助演示键盘操作?
python·计算机外设
星夜离尘562 小时前
MATLAB ADC 码流触顶诊断:命中数、缺口分段与削顶不可辨识性
开发语言·matlab
车软学堂2 小时前
从测试转开发,到底要补什么
网络·人工智能·python·功能测试·单片机·车载系统·自动化
天空依然蔚蓝2 小时前
分享一个自动检测网站在线状态的python程序
python·运维开发·1024程序员节