DLL Side-loading and Zero-width Spaces

My previous post discussed using appverif.exe and DLL side-loading to execute a payload on a system. The advantage of using a legitimate Microsoft signed binary such as appverif.exe to load and execute your DLL was to bypass anti-virus and application whitelisting. One drawback to this technique is that you are executing appverif.exe outside of its normal directory, C:\Windows\System32\. This should look immediately malicious. But, what if there was a way to execute appverif.exe or other System32 executables in a directory that looked like C:\Windows\System32\ but… wasn’t?

Zero-width Spaces

As I was thinking about “how can I make my DLL side-loading attack look like it’s running out of a legitimate directory,” I remembered a series of posts on Twitter by 0xdade that talked about using zero-width spaces (zwsp) in Linux file names. For those like me who are unfamiliar with what a zwsp is, it’s a character that when rendered basically takes up no “space.” It will look like nothing on your screen. It is supported by Unicode as U+200B.

How can this be abused, then? Well, Windows allows for all users to create new directories on C:\

So, if you want your DLL side-loading attack to look like it’s running out of C:\Windows\System32\ but isn’t, why no try and create a new Windows directory with a zwsp.

Testing in Command Prompt

To initially test this, I tried to create the new Windows directory in a command prompt. To get a zwsp, I copied it from here. I first tried putting the zwsp in front of Windows, which resulted in the directory looking like this in the dir output.

At first I thought it didn’t work and that Windows must have been converting the zwsp to a regular space. I then checked it in an Explorer browser, and, what do you know, it looks like I had two identical Windows directories!

Solving the issue of the two directories looking different in the dir output was simple: Put the zwsp after Windows. I should have thought of that the first time.

Using with DLL Side-loading

To try and combine this with DLL side-loading, I wanted a way to programmatically create, and then later remove, my zwsp directory. I didn’t want to worry about copy and pasting errors, and importantly, I didn’t want to worry about accidentally deleting the wrong C:\Windows\System32\ directory. I can only imagine how fun it would be to explain to a client that I broke their server trying to cleanup at the end of an engagement. I noticed in a Covenant shell that you are able to create and delete directories with zwsp’s, but it can be hard to tell which one is which at first glance.

I decided to do this in VBScript so I can pretend like I was using it in an initial access payload like an HTA or Office document macro. I don’t have Office installed on my personal system or in my test lab, so this will be done using an HTA. I think people can still use HTAs for payloads these days? Idk, I’m not sure anymore…

Anyway, creating a directory with the zwsp is simple in VBScript. You can use the hex value, which is 200b.

Dim zerowidth
Set zerowidth = CreateObject("Scripting.FileSystemObject")
folderName = "C:\" & ChrW("&H" & "200b") & "Windows"
system = folderName & "\System32"

' Create a new folder
zerowidth.CreateFolder folderName
zerowidth.CreateFolder system

The above code will create the Windows directory with the zwsp in front (to make it easier to determine what was the new directory in dir output), and then create a System32 sub directory.

Next, I needed to copy the executable I wanted to side-load into the directory. When I was looking for new executables to side-load, I found that nslookup.exe would load DNSAPI.dll that was placed in the same directory as it. My HTA copied nslookup.exe to the new zwsp directory with the following:

nslookup = "C:\Windows\System32\nslookup.exe"
zwspNslookup = system & "\nslookup.exe"

Set fso = CreateObject("Scripting.FileSystemObject")

fso.CopyFile nslookup, zwspNslookup

To create my malicious DNSAPI.dll, I used FireEye’s DueDLLigence project and added the following to it:

// nslookup.exe
// DNSAPI.dll
[DllExport("DnsFreeConfigStructure", CallingConvention = CallingConvention.StdCall)]
public static bool DnsFreeConfigStructure() { return false; }
[DllExport("DnsQueryConfigAllocEx", CallingConvention = CallingConvention.StdCall)]
public static bool DnsQueryConfigAllocEx()
	return false;
// end nslookup.exe

I also changed the ExecutionMethod in DueDLLigence.cs to CreateRemoteThread, and set the new process to be C:\Windows\System32\nslookup.exe (the regular directory, not the zwsp one). This is because when nslookup.exe loads your DLL, it will execute your shellcode, but the process will crash shortly after and your Covenant grunt will die before you can use it. This will make it look like your zwsp nslookup.exe starts, and then starts a new nslookup.exe process. It isn’t create, but it works now for my testing. In the future I should find a better candidate to use for this…

Then, use donut to get the shellcode of your Covenant grunt launcher in base64 and copy into DueDLLigence.

.\donut.exe -f 2 -o C:\Exclusions\grunt.b64 C:\Exclusions\GruntStager.exe

  [ Donut shellcode generator v0.9.3
  [ Copyright (c) 2019 TheWover, Odzhan

  [ Instance type : Embedded
  [ Module file   : "C:\Exclusions\GruntStager.exe"
  [ Entropy       : Random names + Encryption
  [ File type     : .NET EXE
  [ Target CPU    : x86+amd64
  [ AMSI/WDLP     : continue
  [ Shellcode     : "C:\Exclusions\grunt.b64"

Finally, build DueDLLigence in VisualStudio, rename DueDLLigence.dll to DNSAPI.dll, and then copy this to your zwsp directory. In my HTA, I embedded this DLL as a Base64 encoded string. The HTA then decoded it and wrote the decoded bytes as a file to the zwsp directory. I leave this to the reader to figure out how to do (it’s very easy).

I first tested my payload to make sure everything was being created and copied correctly, and everything worked fine. The dir output showed the new zwsp directory was created.

When looking at nslookup.exe in the zwsp directory, this is what it looked like in its properties. No space detected.

Cleanup with the HTA

In order to have the VBScript in my HTA cleanup after the new nslookup.exe process started, it had to do the following:

  • Save the PID of the zwsp nslookup.exe process
  • Wait a short amount of time for the new nslookup.exe process to start (I did ~2 seconds)
  • Kill the zwsp nslookup.exe process using its PID
  • Wait a bit again (~2 seconds) for the zwsp process to die, then delete all the files and directories under the zwsp Windows directory.

Executing the Payload

I launched the completed HTA payload, and got a new Covenant grunt back running in nslookup.exe.

I had Procmon running during execution, and you can see nslookup.exe start a new nslookup.exe process. Looking at the event properties, you’d be forgiven for not noticing that one nslookup.exe is in a different location than the other nslookup.exe.

Is This Actually Useful?

Maybe? Idk. It does help hide that your DLL side-loading attack is executed from outside C:\Windows\System32\. If a defender does have detection for something like that, receives an alert saying something like “cmd.exe executed outside System32”, then they check the log and see it in C:\Windows\System32\ because they can’t “see” a zwsp, maybe they’d write it off as a false positive? Maybe it would annoy them so much they completely ignore the rule as useless? Who knows! Regardless, I thought it was interesting…