Files
png-zip/unpack.ps1
Struchkov Mark 6e54e0f411 png-zip: pack/unpack files inside PNG images
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.
2026-02-21 14:48:13 +03:00

91 lines
2.7 KiB
PowerShell

# unpack.ps1 — Extract file(s) hidden inside a PNG image
# Usage: powershell -File unpack.ps1 packed.png [-OutDir .] [-List]
param(
[Parameter(Mandatory, Position=0)][string]$Image,
[string]$OutDir = ".",
[switch]$List,
[switch]$All
)
Add-Type -TypeDefinition @"
using System;
public static class PngZipHelper2 {
static readonly uint[] crcTable;
static PngZipHelper2() {
crcTable = new uint[256];
for (uint i = 0; i < 256; i++) {
uint c = i;
for (int j = 0; j < 8; j++)
c = (c & 1) != 0 ? 0xEDB88320u ^ (c >> 1) : c >> 1;
crcTable[i] = c;
}
}
public static uint Crc32(byte[] data) {
uint crc = 0xFFFFFFFF;
foreach (byte b in data) crc = crcTable[(crc ^ b) & 0xFF] ^ (crc >> 8);
return crc ^ 0xFFFFFFFF;
}
}
"@
$d = [IO.File]::ReadAllBytes((Resolve-Path $Image))
$text = [Text.Encoding]::GetEncoding(28591).GetString($d)
$sig = 'PNGZIP'
$payloads = @()
$offset = 0
while ($true) {
# Find next 'PNGZIP' and validate \x00\x01 suffix via byte array
$i = $offset
while (($i = $text.IndexOf($sig, $i, [StringComparison]::Ordinal)) -ge 0) {
if ($d[$i+6] -eq 0 -and $d[$i+7] -eq 1) { break }
$i++
}
if ($i -lt 0) { break }
$p = $i + 8
# name
$nl = $d[$p] * 256 + $d[$p+1]; $p += 2
$name = [Text.Encoding]::UTF8.GetString($d, $p, $nl); $p += $nl
# compressed length (big-endian uint64)
[uint64]$pl = 0
for ($k = 0; $k -lt 8; $k++) { $pl = $pl * 256 + $d[$p+$k] }; $p += 8
# decompress zlib: skip 2-byte header, exclude 4-byte adler32
$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)
$raw = $os.ToArray()
$p += $pl
# CRC check
[uint32]$storedCrc = $d[$p]*16777216 + $d[$p+1]*65536 + $d[$p+2]*256 + $d[$p+3]
$p += 4
$actualCrc = [PngZipHelper2]::Crc32($raw)
if ($actualCrc -ne $storedCrc) { Write-Warning "CRC mismatch for $name" }
$payloads += @{ Name = $name; Data = $raw }
$offset = $p
}
if ($payloads.Count -eq 0) { Write-Error "No payload found"; exit 3 }
if ($List) {
foreach ($p in $payloads) { Write-Host " $($p.Name) ($($p.Data.Length) bytes)" }
return
}
$toExtract = if ($All) { $payloads } else { @($payloads[-1]) }
$null = New-Item -ItemType Directory -Force -Path $OutDir
foreach ($entry in $toExtract) {
$safeName = [IO.Path]::GetFileName($entry.Name)
$outPath = Join-Path $OutDir $safeName
[IO.File]::WriteAllBytes($outPath, $entry.Data)
Write-Host "Extracted: $outPath ($($entry.Data.Length) bytes)"
}