Files
png-zip/README.md
Struchkov Mark cb0a6bae22 docs: restructure bootstrap section with two extraction methods
Method A (two-step): extract unpacker first, then run it.
Method B (direct): extract file with single one-liner using
rfind/LastIndexOf — bypasses Windows Execution Policy.
2026-03-01 23:04:20 +03:00

209 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 — два способа извлечения
#### Способ A: двухшаговый (распаковщик → файл)
**Шаг 1.** Извлечь распаковщик — однострочник ищет **первый** payload. Замените `IMG` на имя файла:
**Python:**
```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:**
```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"
```
**Шаг 2.** Извлечь основной файл:
```bash
python3 unpack.py bootstrap.png # Python
powershell -File unpack.ps1 bootstrap.png # PowerShell
```
Дальше распаковщик уже есть на VM и можно передавать обычные packed-образы.
#### Способ B: прямое извлечение файла (без распаковщика)
Однострочник ищет **последний** payload — это сам файл. Не нужен `unpack.py` / `unpack.ps1`, обходит Windows Execution Policy. Замените `IMG` на имя файла:
**Python:**
```bash
python3 -c "d=open('IMG','rb').read();i=d.rfind(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:**
```powershell
$d=[IO.File]::ReadAllBytes('IMG');$t=[Text.Encoding]::GetEncoding(28591).GetString($d);$s='PNGZIP';$i=$d.Length;do{$i=$t.LastIndexOf($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
### Очистка 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
```