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.
91 lines
2.7 KiB
PowerShell
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)"
|
|
}
|