Skill: moxin1044/png-lsb-skills (version 1.0.1)

--- name: png-lsb-skills description: PNG图片LSB隐写分析、块信息解析与CRC校验工具，支持提取sRGB/gAMA/pHYs元数据并检测多种LSB隐藏模式；适合CTF比赛场景，当用户需要分析PNG图片结构、验证数据完整性、检测隐写信息或提取图像元数据时使用 dependency: python: - Pillow>=9.0.0 --- # PNG图片LSB隐写分析技能 ## 任务目标 - 本Skill用于：深度分析PNG图片的内部结构与隐写信息 - 能力包含： - PNG签名验证 - 块信息解析(IHDR/IDAT/IEND等所有chunk) - CRC校验(验证数据完整性) - 元数据提取(sRGB/gAMA/pHYs等) - LSB隐写分析(15种模式解码) - 触发条件：用户上传PNG图片并询问图片结构、隐写信息、数据完整性或元数据时 ## 前置准备 - 依赖说明：scripts脚本所需的依赖包及版本 ``` Pillow>=9.0.0 ``` ## 操作步骤 ### 步骤1：调用脚本分析PNG图片 使用以下命令分析PNG图片： ```bash python scripts/png_analyzer.py --png <图片路径> [--output <输出JSON路径>] ``` 参数说明： - `--png`：PNG图片路径（必需） - `--output`：输出JSON文件路径（可选，不指定则直接打印结果） ### 步骤2：查看分析结果 脚本将返回以下信息： **PNG签名验证** - 验证文件是否为有效的PNG格式 **块信息解析** - 遍历所有chunk，提取类型、长度、数据 - 对每个chunk进行CRC校验，标记校验结果 **元数据提取** - sRGB：色彩空间信息 - gAMA：Gamma值 - pHYs：DPI分辨率信息 **LSB隐写分析** - 提取前8行的最低有效位(LSB) - 按15种模式组合并尝试解码为ASCII字符 - 详见 [references/lsb_patterns.md](references/lsb_patterns.md) ### 步骤3：判断隐写信息 根据LSB分析结果判断： - 如果某个模式的解码结果中出现可读文本，可能存在隐写信息 - 重点关注连续的可打印字符(ASCII 32-126) - 不同模式对应不同的隐写算法 ## 资源索引 - 必要脚本：见 [scripts/png_analyzer.py](scripts/png_analyzer.py)（用途：PNG图片分析，参数：--png图片路径 --output输出路径） - 领域参考：见 [references/lsb_patterns.md](references/lsb_patterns.md)（何时读取：理解LSB隐写模式与解码方法时） ## 注意事项 - 仅支持PNG格式，其他格式会报错 - LSB分析默认提取前8行，适合常见隐写分析场景 - CRC校验失败可能表示文件损坏或被篡改 - 部分PNG图片可能不包含sRGB/gAMA/pHYs等元数据 - LSB解码结果需要人工判断是否为有意义的信息 ## 使用示例 **示例1：分析PNG图片结构** ```bash python scripts/png_analyzer.py --png ./image.png ``` **示例2：保存分析结果到JSON** ```bash python scripts/png_analyzer.py --png ./image.png --output ./result.json ``` **示例3：检测隐写信息** 用户上传PNG图片后，调用脚本分析，重点关注LSB分析部分的chars字段，查找可读文本。

path: scripts/png_analyzer.py
content: #!/usr/bin/env python3
"""
PNG图片分析工具
功能：PNG签名验证、块信息解析、CRC校验、元数据提取、LSB隐写分析
"""

import argparse
import json
import struct
import binascii
import io
import sys
from pathlib import Path

try:
    from PIL import Image
except ImportError:
    print("错误：未安装Pillow库，请运行: pip install Pillow")
    sys.exit(1)


def analyze_png(png_path: str, output_json: str = None) -> dict:
    """
    分析PNG图片
    
    参数:
        png_path: PNG图片路径
        output_json: 输出JSON文件路径（可选）
    
    返回:
        分析结果字典
    """
    result = {
        'success': False,
        'png_signature': None,
        'chunks': [],
        'metadata': {},
        'lsb_analysis': {},
        'error': None
    }
    
    try:
        # 读取文件
        with open(png_path, 'rb') as f:
            data = f.read()
        
        # 1. PNG签名验证
        png_signature = b'\x89PNG\r\n\x1a\n'
        if data[:8] != png_signature:
            result['error'] = '不是有效的PNG文件：签名不匹配'
            return result
        
        result['png_signature'] = binascii.hexlify(data[:8]).decode('ascii')
        
        # 2. 块信息解析与CRC校验
        pos = 8
        srgb = None
        gamma = None
        dpi = None
        
        while pos + 8 <= len(data):
            # 解析chunk头部
            length = struct.unpack('>I', data[pos:pos+4])[0]
            chunk_type = data[pos+4:pos+8].decode('ascii', errors='replace')
            chunk_data = data[pos+8:pos+8+length]
            
            # 提取CRC
            if pos + 12 + length > len(data):
                result['error'] = f'块 {chunk_type} 数据不完整'
                break
            
            crc_val = struct.unpack('>I', data[pos+8+length:pos+12+length])[0]
            
            # 计算CRC
            crc_calc = binascii.crc32(data[pos+4:pos+8+length]) & 0xffffffff
            crc_ok = (crc_val == crc_calc)
            
            # 构建chunk信息
            chunk_info = {
                'type': chunk_type,
                'length': length,
                'crc': binascii.hexlify(struct.pack('>I', crc_val)).decode('ascii'),
                'crc_valid': crc_ok
            }
            
            # 提取元数据
            if chunk_type == 'sRGB' and length == 1:
                srgb = chunk_data[0]
                chunk_info['metadata'] = {'rendering_intent': srgb}
            
            if chunk_type == 'gAMA' and length == 4:
                gamma = round(struct.unpack('>I', chunk_data)[0] / 100000, 5)
                chunk_info['metadata'] = {'gamma': gamma}
            
            if chunk_type == 'pHYs' and length == 9:
                xdpi = struct.unpack('>I', chunk_data[:4])[0]
                ydpi = struct.unpack('>I', chunk_data[4:8])[0]
                unit = chunk_data[8]
                if unit == 1:
                    dpi = (int(xdpi * 0.0254), int(ydpi * 0.0254))
                    chunk_info['metadata'] = {'dpi': dpi, 'unit': 'meter'}
            
            result['chunks'].append(chunk_info)
            
            # 移动到下一个chunk
            pos += 12 + length
            
            # 遇到IEND结束
            if chunk_type == 'IEND':
                break
        
        # 保存元数据
        if srgb is not None:
            result['metadata']['srgb'] = srgb
        if gamma is not None:
            result['metadata']['gamma'] = gamma
        if dpi is not None:
            result['metadata']['dpi'] = dpi
        
        # 3. LSB隐写分析
        lsb_result = analyze_lsb(data)
        result['lsb_analysis'] = lsb_result
        
        result['success'] = True
        
    except FileNotFoundError:
        result['error'] = f'文件不存在: {png_path}'
    except Exception as e:
        result['error'] = f'分析异常: {str(e)}'
    
    # 输出到文件
    if output_json:
        with open(output_json, 'w', encoding='utf-8') as f:
            json.dump(result, f, ensure_ascii=False, indent=2)
    
    return result


def analyze_lsb(data: bytes) -> dict:
    """
    LSB隐写分析
    
    参数:
        data: PNG图片二进制数据
    
    返回:
        LSB分析结果
    """
    lsb_result = {
        'rows': [],
        'patterns': [],
        'error': None
    }
    
    try:
        # 使用PIL打开图片
        img = Image.open(io.BytesIO(data))
        
        # 转换为RGB或RGBA
        if img.mode not in ('RGB', 'RGBA'):
            img = img.convert('RGB')
        
        width, height = img.size
        pixels = img.load()
        
        # 提取前8行的LSB
        lsb_rows = {}
        for row in range(min(8, height)):
            R, G, B = [], [], []
            for col in range(width):
                px = pixels[col, row]
                if isinstance(px, int):
                    px = (px, px, px)
                r, g, b = px[:3]
                R.append(str(r & 1))
                G.append(str(g & 1))
                B.append(str(b & 1))
            lsb_rows[row] = {'R': R, 'G': G, 'B': B}
        
        lsb_result['rows'] = [{'row': k, 'bits': v} for k, v in lsb_rows.items()]
        
        # 定义LSB模式
        patterns = [
            ('RGB', lambda R, G, B: sum(zip(R, G, B), ())),
            ('BRG', lambda R, G, B: sum(zip(B, R, G), ())),
            ('RBG', lambda R, G, B: sum(zip(R, B, G), ())),
            ('BGR', lambda R, G, B: sum(zip(B, G, R), ())),
            ('GRB', lambda R, G, B: sum(zip(G, R, B), ())),
            ('GBR', lambda R, G, B: sum(zip(G, B, R), ())),
            ('RG0', lambda R, G, B: [R[i] if i % 3 == 0 else G[i] if i % 3 == 1 else '0' for i in range(len(R))]),
            ('R0B', lambda R, G, B: [R[i] if i % 3 == 0 else '0' if i % 3 == 1 else B[i] for i in range(len(R))]),
            ('0GB', lambda R, G, B: ['0' if i % 3 == 0 else G[i] if i % 3 == 1 else B[i] for i in range(len(R))]),
            ('R00', lambda R, G, B: [R[i] if i % 3 == 0 else '0' for i in range(len(R))]),
            ('0G0', lambda R, G, B: [G[i] if i % 3 == 1 else '0' for i in range(len(R))]),
            ('00B', lambda R, G, B: [B[i] if i % 3 == 2 else '0' for i in range(len(R))]),
            ('R', lambda R, G, B: R),
            ('G', lambda R, G, B: G),
            ('B', lambda R, G, B: B),
        ]
        
        # 分析每一行
        for row, bits in lsb_rows.items():
            R, G, B = bits['R'], bits['G'], bits['B']
            row_patterns = []
            
            for name, func in patterns:
                seq = func(R, G, B)
                binstr = ''.join(seq)
                
                # 解码为字符
                decs = []
                chars = []
                for i in range(0, len(binstr), 8):
                    byte = binstr[i:i+8]
                    if len(byte) == 8:
                        dec = int(byte, 2)
                        decs.append(dec)
                        if 32 <= dec <= 126:
                            chars.append(chr(dec))
                        else:
                            chars.append('.')
                
                row_patterns.append({
                    'name': name,
                    'binstr': binstr[:80] + ('...' if len(binstr) > 80 else ''),
                    'decs': decs[:20],
                    'chars': ''.join(chars)
                })
            
            lsb_result['patterns'].append({
                'row': row,
                'patterns': row_patterns
            })
    
    except Exception as e:
        lsb_result['error'] = str(e)
    
    return lsb_result


def main():
    parser = argparse.ArgumentParser(description='PNG图片分析工具')
    parser.add_argument('--png', required=True, help='PNG图片路径')
    parser.add_argument('--output', help='输出JSON文件路径（可选）')
    
    args = parser.parse_args()
    
    result = analyze_png(args.png, args.output)
    
    # 打印结果
    if result['success']:
        print("=" * 60)
        print("PNG图片分析结果")
        print("=" * 60)
        
        print(f"\n[PNG签名] {result['png_signature']}")
        
        print(f"\n[块信息] 共 {len(result['chunks'])} 个块:")
        for chunk in result['chunks']:
            status = "✓" if chunk['crc_valid'] else "✗"
            print(f"  {chunk['type']:6s} | 长度: {chunk['length']:8d} | CRC: {chunk['crc']} {status}")
        
        if result['metadata']:
            print(f"\n[元数据]")
            for key, value in result['metadata'].items():
                print(f"  {key}: {value}")
        
        if result['lsb_analysis'].get('patterns'):
            print(f"\n[LSB分析] 共 {len(result['lsb_analysis']['patterns'])} 行:")
            for row_data in result['lsb_analysis']['patterns'][:2]:  # 只显示前2行
                print(f"\n  第 {row_data['row']} 行:")
                for pattern in row_data['patterns'][:6]:  # 只显示前6个模式
                    chars = pattern['chars'][:40]
                    if any(c.isalpha() for c in chars):
                        print(f"    {pattern['name']:4s}: {chars}")
        
        print("\n" + "=" * 60)
        print("分析完成")
    else:
        print(f"错误: {result['error']}")
        sys.exit(1)


if __name__ == '__main__':
    main()


path: references/lsb_patterns.md
content: # LSB隐写模式说明

## 概览
LSB（Least Significant Bit，最低有效位）隐写是一种将信息隐藏在图像像素最低位的技术。本工具支持15种LSB提取模式，用于检测不同隐写算法隐藏的信息。

## LSB原理
每个像素的RGB通道值范围是0-255，用8位二进制表示。最低位（第0位）的变化对人眼几乎不可见，因此可以用来隐藏信息。

示例：
- 原始像素值：255 (11111111)
- 修改后：254 (11111110) - 最低位从1改为0
- 视觉差异：几乎无法察觉

## 支持的LSB模式

### 1. 双通道组合模式（6种）
这些模式从RGB三个通道中选取两个进行组合：

| 模式 | 组合顺序 | 说明 |
|------|---------|------|
| RGB | R→G→B→R→G→B... | 按红绿蓝顺序循环提取 |
| BRG | B→R→G→B→R→G... | 按蓝红绿顺序循环提取 |
| RBG | R→B→G→R→B→G... | 按红蓝绿顺序循环提取 |
| BGR | B→G→R→B→G→R... | 按蓝绿红顺序循环提取 |
| GRB | G→R→B→G→R→B... | 按绿红蓝顺序循环提取 |
| GBR | G→B→R→G→B→R... | 按绿蓝红顺序循环提取 |

**适用场景**：常见的LSB隐写工具通常使用RGB或BGR顺序。

### 2. 单通道+零填充模式（6种）
这些模式只使用一个通道，其他位置用0填充：

| 模式 | 提取规则 | 说明 |
|------|---------|------|
| RG0 | R,G,0,R,G,0... | 只使用R和G通道，第三位填0 |
| R0B | R,0,B,R,0,B... | 只使用R和B通道，第二位填0 |
| 0GB | 0,G,B,0,G,B... | 只使用G和B通道，第一位填0 |
| R00 | R,0,0,R,0,0... | 只使用R通道，其他位填0 |
| 0G0 | 0,G,0,0,G,0... | 只使用G通道，其他位填0 |
| 00B | 0,0,B,0,0,B... | 只使用B通道，其他位填0 |

**适用场景**：某些隐写算法只在特定通道隐藏信息。

### 3. 单通道模式（3种）
这些模式直接提取单个通道的所有LSB位：

| 模式 | 通道 | 说明 |
|------|------|------|
| R | 红色通道 | 提取所有像素红色通道的LSB |
| G | 绿色通道 | 提取所有像素绿色通道的LSB |
| B | 蓝色通道 | 提取所有像素蓝色通道的LSB |

**适用场景**：简单的单通道隐写。

## 解码流程

### 位序列转字节
每8个LSB位组成一个字节：

```
位序列: 01001000 01100101 01101100 01101100 01101111
十进制:    72      101     108     108     111
字符:      H        e       l       l       o
```

### 可打印字符判断
ASCII码32-126为可打印字符，其他字符显示为"."。

## 使用建议

1. **优先检查RGB和BGR模式**：这是最常见的隐写顺序
2. **关注连续可读文本**：如果某个模式的解码结果中有连续的可读文本，很可能存在隐写信息
3. **检查多行**：隐写信息可能分布在多行像素中
4. **注意文件头**：某些隐写工具会在开头添加标识符

## 常见隐写工具的LSB模式

| 工具 | 典型模式 | 备注 |
|------|---------|------|
| Steghide | RGB | 开源隐写工具 |
| OpenStego | RGB | 支持加密 |
| LSB-Steganography | RGB | Python实现 |
| Invisible Secrets | BGR | 商业软件 |

## 示例

**检测到隐写信息的特征**：
- 某个模式的chars字段出现连续的可读文本
- 文本开头可能有标识符（如"PK"表示ZIP文件）
- 文本内容可能是Base64编码

**正常图片的特征**：
- 所有模式的chars字段都是乱码或点号
- 没有明显的可读文本模式

## 注意事项

1. LSB隐写分析只能检测信息存在，不能保证解码出有意义的内容
2. 某些隐写工具使用加密或压缩，解码后仍是乱码
3. 分析结果需要人工判断是否为真实的隐写信息
4. 仅分析前8行像素，如需完整分析请修改脚本参数


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)