Obfuscation, reflective injection and domain fronting; oh my!
We detect and respond to a lot of red team activity at Expel. Each engagement is a great opportunity for our SOC analysts to gain additional experience responding to an attacker (albeit a simulated one). Red team engagements help any security team stay ahead in a world with continuously evolving attacker tradecraft.
When going head-to-head with a red team, we encounter a broad range of attacks. During a recent red team simulation we detected and responded to the execution of a suspicious VBscript file. Acquiring malicious files gives us the opportunity to extract deeper details that can be invaluable.
In this post I’ll walk you through our initial detection and then show you how we:
- Determined the logic implemented by the VBscript and its payload
- Extracted key details of the payload via base64dump.py and pecheck.py
- Decompiled the payload with JetBrains DotPeek
- Followed the chain of obfuscation to reach the red team PoshC2 implant
- Analyzed the red team implant for attacker IOCs
Then I’ll share the details of the capabilities the file contained as well as the insights we gathered coming out of this exercise.
Spotting something suspicious: malware detection
Malware analysis is “like a box of chocolates,” in that you never know what you’re going to encounter as you inspect the details of malicious code. During this red team engagement with an Expel customer, the CrowdStrike EDR Platform alerted on the execution of a suspicious VBScript file.
Expel Workbench Alert Details 1
Expel Workbench Alert Details 2
So, we dove in to take a deeper look.
CrowdStrike Detection Details
For this CrowdStrike alert, a VBScript file named settings.vbs was launched with the command-line version of the Windows Script Host, cscript.exe. CrowdStrike Overwatch observed that the cscript.exe process reflectively injected a library named SharpDLL.dll.
Reflective injection inserts an executable library file into the address space of a process from memory instead of from on disk. This method doesn’t rely on the LoadLibrary Windows API call, which only works with libraries files located on disk.
The Expel Global Response team, which provides Expel with advanced IR capabilities during critical incidents, noticed two additional recorded activities for the cscript.exe process:
- Several .NET Framework Libraries (examples below) were loaded
- A DNS request for paypal.com (this will be explored more later on)
CrowdStrike Detection Disk Operations
CrowdStrike Detection DNS Request
These recorded activities were extremely suspicious and signaled to us that it was time to conduct an investigation. That’s when I began my analysis.
Analyzing the file in three phases
When I looked at the contents of the settings.vbs file, I noticed it began with following comment block:
Beginning of settings.vbs
None of the script functionality contained in the rest of the settings.vbs file relates to this comment block, which is part of its attempt to achieve a surface appearance of performing printer and network administrative activities. When looking at the first section of code executed by the script, note that the first steps taken determine which version of .NET the process executing the script should configure itself to load in.
settings.vbs .NET Version Selection
If present in a process when the .NET framework is loaded, the COMPLUS_Version environment variable will force a certain version of the .NET framework to be loaded. Based on the presence of a particular 4.0 version of the .NET framework, determined by checking for the existence of a Windows Registry key, the script will set this environment variable to either v4.0.30319 or v2.0.50727.
The next action taken by the script is the initialization of two large base64 encoded strings, wpad_1 and wpad_2.
settings.vbs Base64 Strings
Both of these strings are passed through the ProxySettingConfiguration function, which decodes a provided base64 string. This function was the first strong evidence that the script was generated using the DotNetToJScript tool.
DotNetToJScript is described as “a tool to create a JScript file which loads a .NET v2 assembly from memory” created by James Forshaw. This function is almost exactly the same as the Base64ToStream function in the vbs_template.txt file in the DotNetToJScript project source code.
DotNetToJScript Base64 Decode Function
settings.vbs Base64 Decode Function
The decoded base64 strings are then deserialized using the deserialize_2 function. Serialization is “the process of converting an object into a stream of bytes to store the object or transmit it to memory, a database, or a file” according to Microsoft C# Programming Documentation. Deserialize, the reverse of this process, returns the byte stream into its original form.
settings.vbs Decode and Deserialize Strings
After undergoing the decoding and deserialize process, the wpad_1 variable becomes the following:
As part of the .NET deserialize process, the script host process will attempt to load the 220.127.116.11 version of the Microsoft.PowerShell.Editor (The PowerShell ISE). This is likely some type of check on the current system the script is executing on, supported by the error check that happens immediately after in the code (
If Err.Number <> 0).
Powershell ISE Check Failing on Fresh Windows 10 VM
If this check passes, the code then moves onto its main finale of decoding and deserializing the larger base64 string in the wpad_2 variable. Seeing that there was an MZ header present in the second base64 string, and evidence of DotNetToJScript being used, I used a collection of Python scripts from Didier Stevens to continue my analysis in three phases.
Phase 1: Settings.vbs → uqatarcu.dll:
The base64dump.py and pecheck.py Python scripts by Didier Stevens make the process of locating a Windows portable executable (“PE”) file inside a base64 string much easier. After extracting the base64 string for the wpad_2 variable in settings.vbs into a text file, this script is used to expedite its analysis.
Running the following command using the two scripts will:
- Decode the base64 string in wpad_2.txt while ignoring any whitespace or double quotes in the string
- Search for the first occurrence of the MZ Windows PE file signature
- Pass the decoded results starting at the search hit to pecheck.py to validate and parse the PE header
- Output the extracted information from the PE header
base64dump.py [options] [file]
Options used in blog post
-w, --ignorewhitespace ignore whitespace
-i IGNORE, --ignore=IGNORE characters to ignore
-s SELECT, --select=SELECT select item nr for dumping (a for all)
-c CUT, --cut=CUT cut data
-d, --dump perform dump
base64dump.py -w -I 22 -s 1 -c "['MZ']:" -d wpad_2.txt | pecheck.py
The resulting verified PE file includes the following information:
uqatarcu.dll PE File Information
uqatarcu.dll Hashes and Overlay Details
Then with the overlay offset known (extra bytes at the end of the parsed PE file), the following command will write out the first dll, uqatarcu.dll, with the extra overlay removed.
base64dump.py -w -I 22 -s 1 -c "['MZ']:0xb9c00l" -d wpad_2.txt > uqatarcu.dll
Phase 2: uqatarcu.dll → Microsoft.dll and enclosed base64:
The beautiful thing about C#\.NET malware analysis, being an interpreted language instead of a compiled programming language, is that binary files can be automatically decompiled back into their original source code. JetBrains DotPeek is a program that will automatically do this decompilation for you.
Opening up uqatarcu.dll in JetBrains DotPeek shows that it imports the classic 3 function combo for loading shellcode: VirtualAlloc, VirtualProtect and CreateThread.
uqatarcu.dl Windows API imports
Along with two more base64 strings, s1 and s2.
uqatarcu.dll s1 and s2
The s1 string contains base64 encoded 32-bit shellcode and s2 contains 64-bit shellcode. The DLL examines the byte size of a pointer to determine the correct architecture to use, and will deploy the result to a dynamically allocated section of memory. After updating the allocated memory permissions to PAGE_EXECUTE_READWRITE, CreateThread is called with the beginning of this memory block (IntPtr num) as its starting address.
uqatarcu.dll 32 or 64-bit
uqatarcu.dll Deploy Shellcode
Proceeding with the 64-bit version of the next stage for analysis, the contents of the s1 string, there are four hits this time for MZ in the decoded base64 string. However, only the final MZ hit is fully validated by pecheck.py. The cut parameter base64dump.py makes it easy to specify after which search hit of MZ we want to start passing the decoded string to pecheck.py. The number placed after the search term ending bracket specifies this in the commands below:
base64dump.py -w -I 22 -s 1 -c "['MZ']1:" -d b64_uqatarcu_s1.txt | pecheck.py
uqatarcu.dll s1 First MZ Match
base64dump.py -w -I 22 -s 1 -c "['MZ']2:" -d b64_uqatarcu_s1.txt | pecheck.py
uqatarcu.dll s1 Second MZ Match
base64dump.py -w -I 22 -s 1 -c "['MZ']3:" -d b64_uqatarcu_s1.txt | pecheck.py
uqatarcu.dll s1 Third MZ Match
base64dump.py -w -I 22 -s 1 -c "['MZ']4:" -d b64_uqatarcu_s1.txt | pecheck.py
uqatarcu.dll s1 Forth MZ Match
The cut data that was validated as a PE file by pecheck contains some interesting attributes for the file name and description:
Microsoft.dll PE File Information
This next DLL layer then can be extracted to disk with the following command:
base64dump.py -w -I 22 -s 1 -c "['MZ']4:" -d b64_uqatarcu_s1.txt > Microsoft.dll
This DLL is also a C#\.NET binary, and loading it up in DotPeek reveals the following interesting code section:
Microsoft.dll ShellCode Routine
While this .NET source code makes it clear another base64 string is being decoded and executed, the location of it is not as straightforward. The binary does not contain any calls to the RunCS function as well as any base64 strings. Since a majority of the s1 string from uqatarcu.dll was bypassed as a result of the cut parameter “[‘MZ’]4:” and the thread starting address was before the fourth MZ search hit, I decided to return to the s1 string to extract all available strings.
base64dump.py -w -I 22 -s 1 -S b64_uqatarcu_s1.txt
When scrolling through this output, the presence of an encapsulated base64 string visually stands out.
uqatarcu.dll Encapsulated Base64
Phase 3: Encapsulated base64 → dropper_cs.exe:
The base64 string found within the s1 string was successfully parsed by pecheck as a valid PE file. The PE header file information contains a very interesting filename.
base64dump.py -w -I 22 -s 1 -c "['MZ']:" -d b64_from_b64_uqatarcu_s1.txt | pecheck.py
dropper_cs PE File Information
The binary can be further examined by generating a copy of it.
base64dump.py -w -I 22 -s 1 -c "['MZ']:" -d b64_from_b64_uqatarcu_s1.txt > dropper_cs.exe
“dropper_cs.exe” contains a number of notable strings including the domain seen being resolved during its runtime (paypal.com) and strong references to the PoshC2 implant:
This final payload for this layered piece of malware is again written IN C#\.NET. Loading it up in DotPeek provides a clear picture of command and control program functionality.
Following the program logic reveals what is actually going on with the DNS resolution of paypal.com – domain fronting. Domain fronting leverages the way content delivery networks work in order to mask the true destination domain of an external network communication by operating at the application level. The DNS resolution and initial communication setup occurs for the high-reputation domain, while the host header – the true destination – is then set to the attacker controlled domain located on the same CDN.
The dropper_cs payload beacon was configured to appear to be communicating with paypal.com, which is set in the baseURL and address strings. After the initial DNS resolution, web requests for the beacon will actually end up being routed to update-crl.azureedge.net by setting this as the HTTP host header value with
dropper_cs Domain Fronting Related Code 1
dropper_cs Domain Fronting Related Code 2
Based on CDN reporting tools, https://www.paypal.com:443 would resolve to the Akamai CDN. While the azuredge.net subdomain is located on the Microsoft Azure infrastructure, Azure provides the option to select from a number of top CDNs, including Akamai.
CDN Report for paypal.com
The layers of obfuscation contained in settings.vbs were worked through in order to reveal its true nature. None of the PE files and shellcode encapsulated in the vbs file ever hit the hard-drive, but rather are reflectively loaded into the script host process memory. The end result of our analysis gives us the source of the beacon payload and the real c2 domain.
Settings.vbs –> uqatarcu.dll (32/64 bit branch) –> SharpRunner.dll (other ShellCode in Memory Space) –> dropper_cs.exe
Insights from this malware examination
Like I mentioned in the beginning of this post – it’s important to come out of red team engagements having learned something new that can help our customers in real life. Here’s what I learned after exercising my detective muscles and untangling malware code in this simulation:
- Malware analysis takes persistence to peel back the layers
- Reaching the core of a malicious payload can provide invaluable insight
- With the right CDN, domain fronting is still a viable option for malicious actors
We’re working on another blog post that explores a suspicious login case study, so stay tuned for our upcoming content. Until then, check out our other blog posts for more lessons learned from alert investigations.
A note about domain fronting
Domain fronting is dependent on having both a domain on the same CDN as the domain it’s masking as, and the domain fronting technique being possible on the CDN. While Google and Amazon have shut down the ability to perform domain fronting on their CDN services, this technique still works on Azure and other platforms. Domain fronting is not only leveraged by hackers to help blend-in inside a company network, but also used by non-malicious internet users to bypass Internet censorship. There is an argument that keeping it available is essential for Internet Freedom (Domain Fronting Is Critical to the Open Web).
Time will tell if domain fronting remains an option for those with malicious and non-malicious intentions, but companies worried about it being used by malicious actors to help hide in their networks aren’t powerless to detect it.
Domain fronting can be detected by comparing the host field of the HTTP header with the HTTPS SNI field of the web request. This process will require SSL inspection, which is the ability to view the encrypted HTTP data, or a next-gen firewall product that directly provides this detection.