Supports three packing modes: - append (default) — data after IEND marker - chunk — private pnZp PNG chunk - lsb — least significant bits of pixels Includes Python (pack.py/unpack.py) and PowerShell (pack.ps1/unpack.ps1) implementations with full cross-compatibility. Features: self-extract bootstrap, metadata one-liners (eXIf/tEXt), CRC32 integrity check, PNG clean mode.
194 lines
10 KiB
Markdown
194 lines
10 KiB
Markdown
# png-zip
|
||
|
||
Утилита для упаковки произвольных файлов внутрь PNG-изображений. Выходной файл — валидный PNG, который открывается в любом просмотрщике как обычная картинка.
|
||
|
||
Зависимости: Python 3.6+ (только стандартная библиотека).
|
||
|
||
## Режимы упаковки
|
||
|
||
| Режим | Флаг | Где хранится payload | Устойчивость |
|
||
|-------|------|---------------------|-------------|
|
||
| **append** (по умолчанию) | `-m append` | После IEND маркера | Базовые проверки `file`, imagemagick |
|
||
| **chunk** | `-m chunk` | Приватный PNG-чанк `pnZp` | Строгая PNG-валидация (libpng, pngcheck) |
|
||
| **lsb** | `-m lsb` | Младшие биты пикселей | CDR, re-encode, строгие валидаторы |
|
||
|
||
### append (по умолчанию)
|
||
PNG-файлы заканчиваются маркером IEND. Все PNG-декодеры игнорируют данные после него. Утилита дописывает сжатый payload после IEND.
|
||
|
||
```
|
||
[PNG: signature + chunks + IEND] [magic + имя файла + zlib-payload + CRC32]
|
||
```
|
||
|
||
### chunk
|
||
Payload упаковывается в приватный PNG-чанк `pnZp` перед IEND. Файл полностью валиден по спецификации PNG — приватные чанки разрешены и игнорируются декодерами. Если payload > 2 MB, разбивается на несколько чанков с sequence number.
|
||
|
||
```
|
||
[signature] [IHDR] ... [pnZp: payload] [IEND]
|
||
```
|
||
|
||
### lsb
|
||
Payload записывается в младшие биты (LSB) каждого байта пикселей. Визуально изображение неотличимо от оригинала (изменение ±1 в каждом канале). Поддерживаются 8-bit RGB и RGBA PNG без interlacing.
|
||
|
||
Ёмкость: `width × height × channels / 8` байт. Например, 1000×1000 RGB = ~375 KB.
|
||
|
||
## Использование
|
||
|
||
### Упаковка
|
||
|
||
```bash
|
||
# Append mode (по умолчанию)
|
||
python3 pack.py cover.png secret.tar.gz -o output.png
|
||
|
||
# Chunk mode — проходит строгую PNG-валидацию
|
||
python3 pack.py -m chunk cover.png secret.tar.gz -o output.png
|
||
|
||
# LSB mode — выживает CDR и re-encode
|
||
python3 pack.py -m lsb cover.png secret.tar.gz -o output.png
|
||
```
|
||
|
||
`output.png` выглядит как `cover.png`, но содержит `secret.tar.gz` внутри.
|
||
|
||
### Распаковка
|
||
|
||
```bash
|
||
python3 unpack.py output.png
|
||
```
|
||
|
||
Извлекает файл с оригинальным именем в текущую директорию. Режим определяется автоматически.
|
||
|
||
### Распаковка в заданную директорию
|
||
|
||
```bash
|
||
python3 unpack.py output.png -o ./extracted/
|
||
```
|
||
|
||
### Просмотр содержимого
|
||
|
||
```bash
|
||
python3 unpack.py output.png -l
|
||
```
|
||
|
||
```
|
||
secret.tar.gz (148230 bytes)
|
||
```
|
||
|
||
### Извлечь все вложенные файлы
|
||
|
||
```bash
|
||
python3 unpack.py output.png -a
|
||
```
|
||
|
||
Полезно для self-extract образов, где внутри несколько файлов (unpack.py + payload).
|
||
|
||
## Bootstrap: доставка на VM без ничего
|
||
|
||
Если на целевой машине нет `unpack.py` и передать можно только картинки:
|
||
|
||
### 1. На хосте — создаём bootstrap-образ
|
||
|
||
```bash
|
||
# Встроить unpack.py (для машин с Python)
|
||
python3 pack.py --self-extract cover.png secret.tar.gz -o bootstrap.png
|
||
|
||
# Встроить unpack.ps1 (для Windows без Python)
|
||
python3 pack.py --self-extract-ps cover.png secret.tar.gz -o bootstrap.png
|
||
|
||
# Встроить оба распаковщика
|
||
python3 pack.py --self-extract --self-extract-ps cover.png secret.tar.gz -o bootstrap.png
|
||
```
|
||
|
||
> `--self-extract` / `--self-extract-ps` работают только с append режимом.
|
||
|
||
При использовании любого `--self-extract` флага bootstrap-однострочники автоматически сохраняются в метаданных PNG — в eXIf (EXIF UserComment) и tEXt (Comment) чанках. Их можно посмотреть:
|
||
|
||
- **Windows**: правый клик → Свойства → Details → Comments (читает eXIf)
|
||
- **macOS/Linux**: `identify -verbose bootstrap.png | grep -A 20 Comment`
|
||
- **Любая ОС**: `exiftool bootstrap.png`
|
||
|
||
> Base64/EncodedCommand варианты генерируются динамически — в метаданных будет реальное имя выходного файла, а не `bootstrap.png`. Примеры ниже приведены для имени `bootstrap.png`.
|
||
|
||
### 2. Передаём `bootstrap.png` на VM
|
||
|
||
Это валидный PNG — пройдёт любую проверку на изображение.
|
||
|
||
### 3. На VM — извлекаем распаковщик однострочником
|
||
|
||
Однострочник извлекает **первый** встроенный файл (распаковщик). Замените `IMG` на имя вашего файла:
|
||
|
||
**Python (Linux / macOS / PowerShell):**
|
||
|
||
```bash
|
||
python3 -c "d=open('IMG','rb').read();i=d.find(b'PNGZIP\x00\x01');nl=int.from_bytes(d[i+8:i+10],'big');n=d[i+10:i+10+nl].decode();pl=int.from_bytes(d[i+10+nl:i+18+nl],'big');import zlib;open(n,'wb').write(zlib.decompress(d[i+18+nl:i+18+nl+pl]));print('Extracted:',n)"
|
||
```
|
||
|
||
**PowerShell (без Python):**
|
||
|
||
```powershell
|
||
$d=[IO.File]::ReadAllBytes('IMG');$t=[Text.Encoding]::GetEncoding(28591).GetString($d);$s='PNGZIP';$i=-1;do{$i=$t.IndexOf($s,$i+1,[StringComparison]::Ordinal)}while($i-ge0-and($d[$i+6]-ne0-or$d[$i+7]-ne1));$p=$i+8;$nl=$d[$p]*256+$d[$p+1];$p+=2;$n=[Text.Encoding]::UTF8.GetString($d,$p,$nl);$p+=$nl;$pl=[uint64]0;for($k=0;$k-lt8;$k++){$pl=$pl*256+$d[$p+$k]};$p+=8;$ms=New-Object IO.MemoryStream;$ms.Write($d,$p+2,$pl-6);[void]$ms.Seek(0,0);$ds=New-Object IO.Compression.DeflateStream($ms,[IO.Compression.CompressionMode]::Decompress);$os=New-Object IO.MemoryStream;$ds.CopyTo($os);[IO.File]::WriteAllBytes($n,$os.ToArray());Write-Host "Extracted: $n"
|
||
```
|
||
|
||
**Windows CMD** — готовые однострочники с base64/EncodedCommand (не нужно менять имя файла) выводятся при паковке и сохраняются в метаданных PNG. Скопируйте из:
|
||
- Вывода `pack.py` / `pack.ps1` в консоли
|
||
- Свойств файла: правый клик → Свойства → Details → Comments
|
||
|
||
### 4. Извлекаем основной файл
|
||
|
||
```bash
|
||
# Python
|
||
python3 unpack.py bootstrap.png
|
||
|
||
# PowerShell
|
||
powershell -File unpack.ps1 bootstrap.png
|
||
```
|
||
|
||
Дальше распаковщик уже есть на VM и можно передавать обычные packed-образы.
|
||
|
||
### Очистка PNG от встроенных данных
|
||
|
||
Удалить все payload'ы (append, chunk) и bootstrap-метаданные (eXIf, tEXt) из PNG:
|
||
|
||
```bash
|
||
python3 pack.py --clean packed.png -o clean.png
|
||
```
|
||
|
||
После очистки PNG возвращается к исходному виду (без trailing data, без pnZp-чанков, без bootstrap-комментариев).
|
||
|
||
## Советы
|
||
|
||
**Упаковка нескольких файлов** — сначала создайте архив:
|
||
```bash
|
||
tar czf bundle.tar.gz file1.txt file2.bin dir/
|
||
python3 pack.py cover.png bundle.tar.gz -o output.png
|
||
```
|
||
|
||
**Выбор обложки** — используйте любой PNG. Чем крупнее картинка, тем естественнее выглядит увеличение размера файла. Для LSB режима нужна достаточно большая картинка (ёмкость ≈ `W×H×channels/8` байт).
|
||
|
||
**Проверка целостности** — при распаковке автоматически проверяется CRC32. Если файл повреждён при передаче, будет предупреждение.
|
||
|
||
## PowerShell версия
|
||
|
||
Для Windows без Python есть полная PowerShell-реализация (`pack.ps1` / `unpack.ps1`) — см. [POWERSHELL.md](POWERSHELL.md).
|
||
|
||
Формат payload полностью совместим — можно паковать Python'ом, распаковывать PowerShell'ом и наоборот:
|
||
|
||
| Упаковка | Распаковка | |
|
||
|----------|-----------|---|
|
||
| `pack.py` | `unpack.ps1` | да |
|
||
| `pack.ps1` | `unpack.py` | да |
|
||
| `pack.py --self-extract` | Python однострочник | да |
|
||
| `pack.py --self-extract-ps` | PS однострочник | да |
|
||
| `pack.ps1 -SelfExtract` | PS однострочник | да |
|
||
|
||
> PowerShell версия поддерживает только append-режим. Для chunk и lsb нужен Python.
|
||
|
||
## Ограничения
|
||
|
||
- **append/chunk**: если система передачи пережимает PNG (ресайз, re-encode) — данные будут потеряны. Для file-based трансферов (SCP, cloud storage, вложения в email, передача «как файл» в мессенджерах) проблем нет.
|
||
- **lsb**: выживает re-encode без потерь (PNG→PNG), но не выживает lossy конвертацию (PNG→JPEG). Ёмкость ограничена размером изображения. Поддерживаются только 8-bit RGB/RGBA PNG без interlacing.
|
||
- Payload загружается в память целиком. Для файлов > 500 MB может потребоваться много RAM.
|
||
- Данные не шифруются. Для конфиденциальности — зашифруйте файл перед упаковкой:
|
||
```bash
|
||
gpg -c secret.tar.gz # создаст secret.tar.gz.gpg
|
||
python3 pack.py cover.png secret.tar.gz.gpg -o output.png
|
||
```
|