L'équipe de réponse à incident a pu capturer un dump mémoire complet d'un processus .NET suspect exécuté sur le serveur SCADA compromis.
Explorez le fichier fourni pour extraire des informations sur l'identité des attaquants, leur mode opératoire et leurs intentions.
À la suite d'une intrusion, les autorités portuaires ont constaté un comportement anormal des portes maritimes. L'analyse du serveur SCADA a révélé l'injection d'un programme C# malveillant chargé de lire les messages AIS.
L'équipe de réponse à incident vous a mis à disposition un dump mémoire .NET du processus malveillant.
Votre mission consiste à analyser le dump mémoire post-mortem fourni afin de déterminer l'intention des attaquants derrière cette compromission.
L'objectif de cet exercice est de comprendre comment les attaquants ont réussi à exécuter des commandes sur le serveur SCADA grâce au programme C# injecté.
Étant donné que le code source n'est pas fourni, nous allons principalement utiliser des outils d'analyse mémoire adaptés comme dotnet-dump.
L'investigation est de type post-mortem, il faut donc cibler les objets statiques persistants qui définissent le mode opératoire de l'attaquant.
En utilisant la commande dumpheap -stat, nous pouvons lister les classes présentes dans la mémoire du processus .NET.
dotnet-dump analyze ScadaCommWkr.dmp
> dumpheap -stat
Statistics:
MT Count TotalSize Class Name
789348bc9b50 1 24 System.Collections.Generic.GenericEqualityComparer<System.String>
789348bcb5f8 1 24 System.OrdinalCaseSensitiveComparer
...
789348c4bdb0 1 80 System.Collections.Generic.Dictionary<System.Int32, System.Threading.CancellationTokenSource>
78934943db70 2 80 FluentModbusConfig+ModbusEvent[]
7893496e3108 1 80 System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>+AsyncStateMachineBox<Program+<>c__DisplayClass10_2+<<Main>b__1>d>
789349083f70 3 88 System.Reflection.ParameterInfo[]
789348bf5d68 3 96 System.LazyHelper
...
7893496e4ac8 4 96 FluentModbus.ModbusClient+<>c__DisplayClass19_0
7893496e4c38 1 96 System.Collections.Generic.Dictionary<System.Int32, System.Threading.CancellationTokenSource>+Entry[]
7893496e0958 1 96 Program+<>c__DisplayClass10_2+<<Main>b__1>d
78934926e088 1 104 System.IO.StreamWriter
78934906e3f0 2 112 System.RuntimeType+RuntimeTypeCache+MemberInfoCache<System.Reflection.RuntimePropertyInfo>
78934922ea38 3 120 System.Threading.ThreadPoolWorkQueue+WorkStealingQueue
7893496e2668 3 120 System.Threading.TimerQueue[]
789348b2f608 1 128 System.OutOfMemoryException
789348b2f708 1 128 System.StackOverflowException
789348b2f808 1 128 System.ExecutionEngineException
789348bf1c90 2 128 System.Func<System.String>
7893490845c0 2 128 System.Reflection.InvokerEmitUtil+InvokeFunc_RefArgs
7893490858b8 4 128 System.Diagnostics.Tracing.EventSourceAttribute[]
789349225d40 4 128 Microsoft.Win32.SafeHandles.SafeWaitHandle
En examinant les classes listées, nous pouvons repérer plusieurs structures dont System, FluentModbus, Microsoft, mais celui qui nous intéresse particulièrement est FluentModbusConfig, qui n'est pas une classe standard de la bibliothèque FluentModbus.
En utilisant la commande dumpheap -type FluentModbusConfig, nous pouvons localiser les instances de cette classe.
> dumpheap -type FluentModbusConfig
Address MT Size
78533a8e0ac0 789348c48df0 32
78533a8e0ae0 78934943db70 24
78533a8e0af8 789348c44e10 48
78533a8e0ba0 78934943db70 56
78533a8e0bd8 789348c44e10 48
78533a8e0c80 789348c44e10 48
Statistics:
MT Count TotalSize Class Name
789348c48df0 1 32 System.Collections.Generic.List<FluentModbusConfig+ModbusEvent>
78934943db70 2 80 FluentModbusConfig+ModbusEvent[]
789348c44e10 3 144 FluentModbusConfig+ModbusEvent
Total 6 objects, 256 bytes
Plusieurs objets FluentModbusConfig+ModbusEvent sont présents en mémoire. En examinant le contenu de chacun d'eux, nous pouvons remarquer des structures intéressantes.
> do 78533a8e0af8
Name: FluentModbusConfig+ModbusEvent
MethodTable: 0000789348c44e10
EEClass: 0000789348d3d898
Tracked Type: false
Size: 48(0x30) bytes
File: /home/scada/scada-wkr/ScadaCommWkr/bin/Debug/net8.0/ScadaCommWkr.dll
Fields:
MT Field Offset Type VT Attr Value Name
0000789348c43c00 4000018 8 System.Byte[] 0 instance 000078533a8e0b28 <Field>k__BackingField
0000789348b2d7c8 4000019 10 System.String 0 instance 0000789346a00560 <Tag>k__BackingField
0000789348bca3d0 400001a 18 ...Private.CoreLib]] 1 instance 000078533a8e0b10 <DL>k__BackingField
0000789348bca3d0 400001b 20 ...Private.CoreLib]] 1 instance 000078533a8e0b18 <DT>k__BackingField
L'analyse révèle des objets contenant :
- Un tableau de bytes (Field)
- Une chaîne de caractères (Tag)
- Deux autres champs (DL et DT)
En inspectant Field avec la commande dumparray, nous pouvons obtenir des informations intéressantes comme la taille et le contenu du tableau de bytes.
> dumparray 000078533a8e0b28
Name: System.Byte[]
MethodTable: 0000789348c43c00
EEClass: 0000789348c43b90
Size: 56(0x38) bytes
Array: Rank 1, Number of elements 32, Type Byte
Element Methodtable: 0000789348ad1a58
[0] 000078533a8e0b38
[1] 000078533a8e0b39
...
[30] 000078533a8e0b56
[31] 000078533a8e0b57
Le tableau de bytes contient 32 éléments. En examinant le contenu du tableau, nous pouvons observer une structure de bytes qui semble être un hash sha256 : 09d16a8e07e8c2580de60a5a76f95d2172c32e9c3fce8d7c889ad5f62aad9f15.
> db 000078533a8e0b38 -c 32
000078533a8e0b38: 09 d1 6a 8e 07 e8 c2 58 0d e6 0a 5a 76 f9 5d 21 ..j....X...Zv.]!
000078533a8e0b48: 72 c3 2e 9c 3f ce 8d 7c 88 9a d5 f6 2a ad 9f 15 r...?..|....*...
De plus, nous observons un objet de type String Tag. En l'examinant avec do, nous pouvons lire son contenu et découvrir qu'il s'agit d'une commande : EMERGENCY_OPEN.
> do 0000789346a00560
Name: System.String
MethodTable: 0000789348b2d7c8
EEClass: 0000789348b15ca8
Tracked Type: false
Size: 50(0x32) bytes
File: /usr/lib/dotnet/shared/Microsoft.NETCore.App/8.0.21/System.Private.CoreLib.dll
String: EMERGENCY_OPEN
Fields:
MT Field Offset Type VT Attr Value Name
0000789348ab0980 40002d1 8 System.Int32 1 instance 14 _stringLength
0000789348abad30 40002d2 c System.Char 1 instance 45 _firstChar
0000789348b2d7c8 40002d0 b0 System.String 0 static 0000789346a00008 Empty
En répétant cette analyse pour les autres objets FluentModbusConfig+ModbusEvent, on peut extraire d'autres paires Hash/Tag :
| Hash | Tag |
|---|---|
09 d1 6a 8e 07 e8 c2 58 0d e6 0a 5a 76 f9 5d 21 72 c3 2e 9c 3f ce 8d 7c 88 9a d5 f6 2a ad 9f 15 |
EMERGENCY_OPEN |
82 4f 57 50 31 b3 ac 37 55 e0 e1 b1 a8 3e df 4f bb a7 89 d0 0a 7c b5 7b 91 96 7c d3 aa bc 1f 60 |
EMERGENCY_CLOSE |
ad 37 af 3b cc 33 85 a5 f4 6e 50 82 d7 70 c0 e0 38 0d 29 c1 4f 72 d2 e7 b6 c3 f5 5a bc 55 11 c1 |
SCHEDULE_OPERATION |
Une fois les paires Hash/Tag extraites, nous pouvons tenter de retrouver les secrets statiques utilisés par les attaquants pour générer ces hash. En utilisant des outils comme Hashcat, nous pouvons essayer de retrouver les valeurs originales.
cat <<EOF >> hashs.txt
09d16a8e07e8c2580de60a5a76f95d2172c32e9c3fce8d7c889ad5f62aad9f15
824f575031b3ac3755e0e1b1a83edf4fbba789d00a7cb57b91967cd3aabc1f60
ad37af3bcc3385a5f46e5082d770c0e0380d29c14f72d2e7b6c3f55abc5511c1
EOF
hashcat --potfile-disable -m 1400 -a 3 hashs.txt ?d?d?d?d?d?d?d?d?d --status -o found.txt
cat found.txt
Ce script utilise Hashcat pour effectuer une attaque par force brute sur les hash extraits, en supposant que les MMSI sont composés de 9 chiffres (0-9).
Cela permet d'identifier les MMSI ayant déclenché les actions spécifiques :
| Tag | MMSI |
|------------------|-------------|
| EMERGENCY_OPEN | 338123456 |
| EMERGENCY_CLOSE | 366654321 |
| SCHEDULE_OPERATION | 367000999 |
Enfin, pour déterminer les paramètres de l'opération planifiée (délai d'ouverture et durée de fermeture), on peut examiner les champs DL et DT de l'objet FluentModbusConfig+ModbusEvent correspondant à SCHEDULE_OPERATION, toujours avec la commande do.
> do 78533a8e0c80
Name: FluentModbusConfig+ModbusEvent
MethodTable: 0000789348c44e10
EEClass: 0000789348d3d898
Tracked Type: false
Size: 48(0x30) bytes
File: /home/scada/scada-wkr/ScadaCommWkr/bin/Debug/net8.0/ScadaCommWkr.dll
Fields:
MT Field Offset Type VT Attr Value Name
0000789348c43c00 4000018 8 System.Byte[] 0 instance 000078533a8e0cb0 <Field>k__BackingField
0000789348b2d7c8 4000019 10 System.String 0 instance 0000789346a005d0 <Tag>k__BackingField
0000789348bca3d0 400001a 18 ...Private.CoreLib]] 1 instance 000078533a8e0c98 <DL>k__BackingField
0000789348bca3d0 400001b 20 ...Private.CoreLib]] 1 instance 000078533a8e0ca0 <DT>k__BackingField
En examinant le contenu de DL et DT avec la commande dd, nous pouvons lire les valeurs brutes stockées dans ces champs en ignorant les 4 premiers octets qui correspondent au drapeau nullable qui se rajoute lorsqu'on utilise un objet Nullable en C#.
# Examiner le champ DL
> dd 000078533a8e0c98
000078533a8e0c98: 00000001 00000006 00000001 00000001 00000000 00000000 48c43c00 00007893
000078533a8e0cb8: 00000020 00000000 3baf37ad a58533cc 82506ef4 e0c070d7 c1290d38 e7d2724f
000078533a8e0cd8: 5af5c3b6 c11155bc 00000000 00000000 4943dce0 00007893 00000000 00000000
000078533a8e0cf8: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
000078533a8e0d18: 4943da68 00007893 00000000 00000000 48c454b0 00007893 3a8e0d48 00007853
000078533a8e0d38: 3a8dff78 00007853 00000000 00000000 48c45ae8 00007893 00000000 00000000
000078533a8e0d58: 00000000 00000000 3a8e11d0 00007853 00000000 00000000 00000000 00000000
000078533a8e0d78: 48c4ae40 00007893 3a8e0d28 00007853 00000000 00000000 48c09188 00007893
# Examiner le champ DT
> dd 000078533a8e0ca0
000078533a8e0ca0: 00000001 00000001 00000000 00000000 48c43c00 00007893 00000020 00000000
000078533a8e0cc0: 3baf37ad a58533cc 82506ef4 e0c070d7 c1290d38 e7d2724f 5af5c3b6 c11155bc
000078533a8e0ce0: 00000000 00000000 4943dce0 00007893 00000000 00000000 00000000 00000000
000078533a8e0d00: 00000000 00000000 00000000 00000000 00000000 00000000 4943da68 00007893
000078533a8e0d20: 00000000 00000000 48c454b0 00007893 3a8e0d48 00007853 3a8dff78 00007853
000078533a8e0d40: 00000000 00000000 48c45ae8 00007893 00000000 00000000 00000000 00000000
000078533a8e0d60: 3a8e11d0 00007853 00000000 00000000 00000000 00000000 48c4ae40 00007893
000078533a8e0d80: 3a8e0d28 00007853 00000000 00000000 48c09188 00007893 00000000 00000000
En analysant les valeurs hexadécimales, nous pouvons en déduire que :
- DL : 6
- DT : 1
Ainsi, l'opération planifiée consiste à ouvrir une porte maritime après un délai de 6 heures pour une durée de 1 heure avant de la refermer automatiquement.