Every language needs an RC4 implementation. Despite its insecurities, RC4 is widely used due to its simple algorithm and the minimal amount of code it takes to implement it. Some people have even tried to fit implementations into single tweets. It’s commonly used by malware due to its low overhead, and I’m actually shocked that RosettaCode doesn’t have an entry for RC4.
The only PowerShell implementation I’m aware of is Remko Weijnen’s code here, and as far as I know .NET doesn’t include an RC4 implementation that we can take advantage of. This post will cover a ‘proper’-(esque) implementation of RC4, a practical ‘minimized’ version, and a version that I collaborated with some PowerShell madmen on in the quest to get it under 140 characters for a tweet.
RC4 Background
Read the Wikipedia page if you’re actually curious.
Proper Implementation
Without further ado:
https://gist.github.com/HarmJ0y/4edc5bf4cccb0aef5553a860a3e433e3
Yes, there’s not a completely proper PRGA implementation, but this was partly to take advantage of PowerShell’s pipelining. This was the approach that made the most sense to me. It requires byte arrays for the -InputObject and -Key, so here’s how you use it:
$Enc = [System.Text.Encoding]::ASCII $Data = $Enc.GetBytes('This is a test! This is only a test.') $Key = $Enc.GetBytes('SECRET') ($Data | ConvertTo-Rc4ByteStream -Key $Key | ForEach-Object { "{0:X2}" -f $_ }) -join ' '
Note that it can accept an -InputObject byte array on the pipeline, or by calling the parameter, your choice.
Minimization Take 1
The minimization idea came about from doing training prep, where we wanted a practical RC4 implementation for decrypting, or executing, malware communications. For malware staging, space and (some) obfuscation matters, so proper script ‘etiquette’ can mostly be thrown out the window. The optimization for this function is heavily due to @lee_holmes, @mattifestation, @secabstraction, and @tifkin_, which will come to properly crazy fruition in the last section.
Here’s the code:
$R={$D,$K=$Args;$S=0..255;0..255|%{$J=($J+$S[$_]+$K[$_%$K.Length])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H=($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I];$_-bxor$S[($S[$I]+$S[$H])%256]}}
For the optimization, we’re taking advantage of a few things- I’ll try to outline a few here as it was an interesting thought exercise:
- Uninitialized variables are assumed to be $Null/0 – $J in the KSA, $I and $H (replacing $J) in the PRGA.
- We obviously dropped pipeline support, and used nameless/lambda functions (@mattifestation‘s idea) to cut down a bit more.
- Spaces? Who need spaces?
PowerShell has its own form of Lambda functions – anonymous script blocks that can serve as functions without a formal name. There’s more information on PowerShell and Lambda functions from @mattifestation. These functions can be invoked with the call operator (&) like the following:
$Enc = [System.Text.Encoding]::ASCII $Data = $Enc.GetBytes('This is a test! This is only a test.') $Key = $Enc.GetBytes('SecretPassword') (& $R $data $key | ForEach-Object { "{0:X2}" -f $_ }) -join ' '
Getting Weird With PowerShell
So how can we cut this down even more to fit into a tweet? Matt had the great idea of converting the ASCII representation of our logic to bytes and then repacking those bytes as UNICODE. Since an ASCII character is encoded as 8 bits/1 byte and UNICODE is encoded with 16 bits/2 bytes, we can pack two ASCII characters into a single UNICODE encoding. Here’s how we can accomplish this in PowerShell:
$Bytes = ([System.Text.Encoding]::ASCII).GetBytes("dir C:\ ") $UnicodeBytes = ([System.Text.Encoding]::UNICODE).GetString($Bytes)
So we can cut our script down to len(script)/2 + len(decoding logic). Unfortunately, we were only able to get the logic down to 141 characters with piping to IEX, so I tweeted out the version that just echoed the V3+ algorithm (if anyone can shave another few characters off, let us know). Before running, $D needs to be initialized as the data array and $K initialized as the key array. The weird % *es bit is a PowerShell v3.0+ shortcut for ForEach-Object -MemberName *es, which returns the GetBytes() method for the [Text.Encoding]::Unicode instantiation. This is a quick way to call [System.Text.Encoding]::Unicode.GetBytes(‘UNICODE’).
-join[Char[]]([Text.Encoding]::Unicode|% *es '匤〽⸮㔲㬵⠤匤簩笥䨤⠽䨤孓弤⭝䬤╟䬤䌮畯瑮⥝㈥㘵孓弤ⱝ匤嵊孓䨤ⱝ匤嵟㭽䐤╼⑻㵉⬫䤤㈥㘵㵈⭈匤嵉┩㔲㬶匤嵉孓䠤㵝匤嵈孓䤤㭝弤戭潸⑲孓孓䤤⭝匤嵈┩㔲崶⁽')|IEX
The UNICODE packing technique seems like it might have additional use cases for offensive obfuscation, but this is an exercise left to the reader.