Using Bitwise NOT operations to obfuscate commands in PowerShell

May 24, 2024

Bitwise NOT commands are often used in PowerShell malware samples to obfuscate commands. A bitwise NOT operation flips all the bits in a given byte sequence.

<# 
Bitwise NOT operations will flip all bits  
As an example, consider the following:

$byte = 00000101 | binary for 5

Performing a bitwise NOT operation flips all of the bits

$byte = 11111010 | binary for -6 (two's complement)

#>

$dec = 5

-bnot $dec # will print out -6
PowerShell

There are many ways to use this operation in obfuscation and execution of malicious (and non-malicious!) commands. However, this post will focus on crafting custom Invoke-Expression commands using [System.Text.Encoding] to convert the bitwise NOT values to UTF8 strings.

Consider the following sample pulled from MalwareBazaar.

# Convert a Base64 string into a byte array
$bytesOfRes=[Convert]::FromBase64String($OutRes)

# Loop through the byte array
for ($i=0;$i -lt $bytesOfRes.Length;$i++){
  #Operate on $i byte
  $bytesOfRes[$i]=[Convert]::ToByte(([Convert]::ToString(-bnot ($bytesOfRes[$i]),2).Substring(24,8)),2)
}

#Convert the resulting byte array to a UTF8 string
$LastEx=[System.Text.Encoding]::UTF8.GetString($bytesOfRes)

# Use Get-Command to get execute the string via Invoke-Expression and a call operator
&((gcm *v?k?-?x?re*).name) $LastEx
PowerShell

This snippet uses a bitwise NOT operation as the basis for converting a byte array into a UTF8 string to execute a malicious command. A base 64 string is downloaded from a remote server. This string is decoded via the [Convert]::FromBase64String method. The method decodes a base 64 string into a UTF8 byte array.

[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("hello"))
# aGVsbG8=
[Convert]::FromBase64String("aGVsbG8=")
# 104
# 101
# 108
# 108
# 111
[System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String("aGVsbG8="))
# hello
PowerShell

After converting the string to a byte array, the for loop on line #5 operates on each byte element in the array. This is where the bulk of the magic happens.

# Expanded loop

# Replaces the current element with the result of the operation
$bytesOfRes[$i] = 
<#

     Convert the result to byte
            |
            |
            |            Convert the result to a binary string
            |                  |
            |                  |                    
            |                  |      Perform a bitwise NOT operation on $i byte element
            |                  |                    |
            |                  |                    |
            |                  |                    |    Get the last 8 bits of the result
            |                  |                    |                |              
            |                  |                    |                |                      #>
[Convert]::ToByte(([Convert]::ToString(-bnot ($bytesOfRes[$i]),2).substring(24,8)),2)

# Note: [Convert]::ToString($foo,2) will return a binary value 
# Hence the substring(24,8) on the bitwise NOT operation

}
PowerShell

As you can see, the current element in the loop is transformed via bitwise NOT operation into a binary value which is then converted into a byte value. The resulting byte array of the for loop is converted into a valid UTF8 string and then executed via a call operator and Invoke-Expression.

#Convert the resulting byte array to a UTF8 string
$LastEx=[System.Text.Encoding]::UTF8.GetString($bytesOfRes)

<# 
Use Get-Command to get execute the string via Invoke-Expression and a call operator

gcm is the alias for Get-Command. Using wildcard operators, the value "*v?k?-?x?re*" matches
the string name of Invoke-Expression. Using the name property of the returned Get-Command
object, it is possible to dynamically build an IEX command string that can be executed with
the '&' symbol, also called a call operator. 

#>
&((gcm *v?k?-?x?re*).name) $LastEx
PowerShell

To create a payload for execution, use the [System.Text.Encoding]::UTF8.GetBytes method to convert a string to a series of bytes. Then, use a bitwise NOT operation on the byte array to get the BNOT array.

# Create a byte array with our command
$ByteArray = [System.Text.Encoding]::UTF8.GetBytes("Write-Host 'Hello World'")

# Perform a bitwise NOT operation on each byte in the array
$BNOTArray = $ByteArray | %{-bnot $_ }

#Convert the BNOT array back to the original value using the same method as described above
$Command = $BNOTArray | %{[Convert]::ToByte(([Convert]::ToString(-bnot ($_),2)),2)}

# Convert the byte array to a string
$STRCommand = [System.Text.Encoding]::UTF8.GetString($Command)

# Use the call operator to invoke the command 
&((gcm *v?k?-?x?re*).name) $STRCommand
#Hello World

# In one line
&((gcm *v?k?-?x?re*).name) ([String]::new((($BNOTArray|%{[Convert]::ToString(-bnot $_,2)})|%{[Convert]::ToByte($_,2)})))
PowerShell

Leave a Reply

Your email address will not be published. Required fields are marked *