mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
220 lines
11 KiB
Markdown
220 lines
11 KiB
Markdown
# ksmbd Surface d'attaque & Fuzzing du protocole SMB2/SMB3 (syzkaller)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## Aperçu
|
||
Cette page résume des techniques pratiques pour exercer et fuzz le serveur SMB in-kernel de Linux (ksmbd) en utilisant syzkaller. Elle se concentre sur l'expansion de la surface d'attaque du protocole via la configuration, la construction d'un harness stateful capable d'enchaîner des opérations SMB2, la génération de PDUs valides selon la grammaire, l'orientation des mutations vers des chemins de code faiblement couverts, et l'exploitation de fonctionnalités de syzkaller telles que focus_areas et ANYBLOB. Alors que la recherche originale énumère des CVEs spécifiques, nous mettons ici l'accent sur la méthodologie réutilisable et des extraits concrets que vous pouvez adapter à vos propres environnements.
|
||
|
||
Portée cible : SMB2/SMB3 sur TCP. Kerberos et RDMA sont intentionnellement hors-scope pour garder le harness simple.
|
||
|
||
---
|
||
|
||
## Étendre la surface d'attaque de ksmbd via la configuration
|
||
Par défaut, une configuration ksmbd minimale laisse de larges parties du serveur non testées. Activez les fonctionnalités suivantes pour faire passer le serveur par des parsers/handlers supplémentaires et atteindre des chemins de code plus profonds :
|
||
|
||
- Global-level
|
||
- Durable handles
|
||
- Server multi-channel
|
||
- SMB2 leases
|
||
- Per-share-level
|
||
- Oplocks (on by default)
|
||
- VFS objects
|
||
|
||
L'activation de ces éléments augmente l'exécution dans des modules tels que :
|
||
- smb2pdu.c (command parsing/dispatch)
|
||
- ndr.c (NDR encode/decode)
|
||
- oplock.c (oplock request/break)
|
||
- smbacl.c (ACL parsing/enforcement)
|
||
- vfs.c (VFS ops)
|
||
- vfs_cache.c (lookup cache)
|
||
|
||
Notes
|
||
- Les options exactes dépendent de l'espace utilisateur ksmbd de votre distro (ksmbd-tools). Consultez /etc/ksmbd/ksmbd.conf et les sections per-share pour activer durable handles, leases, oplocks et VFS objects.
|
||
- Multi-channel et durable handles modifient les machines d'état et les durées de vie, souvent faisant ressortir des bugs UAF/refcount/OOB sous concurrence.
|
||
|
||
---
|
||
|
||
## Ajustements d'authentification et de rate-limiting pour le fuzzing
|
||
SMB3 nécessite une session valide. Implémenter Kerberos dans les harnesses ajoute de la complexité, donc préférez NTLM/guest pour le fuzzing :
|
||
|
||
- Autorisez guest access et configurez map to guest = bad user afin que les utilisateurs inconnus retombent sur GUEST.
|
||
- Acceptez NTLMv2 (patch policy si désactivé). Cela simplifie le handshake tout en exerçant les chemins de code SMB3.
|
||
- Patcherez les strict credit checks lors d'expérimentations (les durcissements post-hardening pour CVE-2024-50285 ont rendu le crédit simultané plus strict). Sinon, les rate-limits peuvent rejeter des séquences fuzzées trop tôt.
|
||
- Augmentez le nombre max connections (par ex. à 65536) pour éviter les rejets précoces pendant un fuzzing à haut débit.
|
||
|
||
Attention : ces assouplissements facilitent uniquement le fuzzing. Ne déployez pas ces réglages en production.
|
||
|
||
---
|
||
|
||
## Stateful Harness : extraire des ressources et enchaîner des requêtes
|
||
SMB est stateful : de nombreuses requêtes dépendent d'identifiants renvoyés par des réponses antérieures (SessionId, TreeID, FileID pairs). Votre harness doit parser les réponses et réutiliser les IDs au sein du même programme pour atteindre des handlers profonds (par ex., smb2_create → smb2_ioctl → smb2_close).
|
||
|
||
Exemple d'extrait pour traiter un buffer de réponse (en sautant les +4B NetBIOS PDU length) et mettre en cache les IDs :
|
||
```c
|
||
// process response. does not contain +4B PDU length
|
||
void process_buffer(int msg_no, const char *buffer, size_t received) {
|
||
uint16_t cmd_rsp = u16((const uint8_t *)(buffer + CMD_OFFSET));
|
||
switch (cmd_rsp) {
|
||
case SMB2_TREE_CONNECT:
|
||
if (received >= TREE_ID_OFFSET + sizeof(uint32_t))
|
||
tree_id = u32((const uint8_t *)(buffer + TREE_ID_OFFSET));
|
||
break;
|
||
case SMB2_SESS_SETUP:
|
||
// first session setup response carries session_id
|
||
if (msg_no == 0x01 && received >= SESSION_ID_OFFSET + sizeof(uint64_t))
|
||
session_id = u64((const uint8_t *)(buffer + SESSION_ID_OFFSET));
|
||
break;
|
||
case SMB2_CREATE:
|
||
if (received >= CREATE_VFID_OFFSET + sizeof(uint64_t)) {
|
||
persistent_file_id = u64((const uint8_t *)(buffer + CREATE_PFID_OFFSET));
|
||
volatile_file_id = u64((const uint8_t *)(buffer + CREATE_VFID_OFFSET));
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
```
|
||
Conseils
|
||
- Gardez un seul processus fuzzer partageant l'authentification/état : meilleure stabilité et couverture avec les tables globales/de session de ksmbd. syzkaller injecte quand même de la concurrence en marquant les ops async, et réexécute en interne.
|
||
- Le reset_acc_state expérimental de syzkaller peut réinitialiser l'état global mais peut entraîner un fort ralentissement. Privilégiez la stabilité et concentrez-vous sur le fuzzing.
|
||
|
||
---
|
||
|
||
## Génération SMB2 dirigée par une grammaire (PDUs valides)
|
||
Convertissez les structures SMB2 des Microsoft Open Specifications en une grammaire pour fuzzer afin que votre générateur produise des PDUs structurellement valides, qui atteignent systématiquement les dispatchers et les IOCTL handlers.
|
||
|
||
Exemple (SMB2 IOCTL request):
|
||
```
|
||
smb2_ioctl_req {
|
||
Header_Prefix SMB2Header_Prefix
|
||
Command const[0xb, int16]
|
||
Header_Suffix SMB2Header_Suffix
|
||
StructureSize const[57, int16]
|
||
Reserved const[0, int16]
|
||
CtlCode union_control_codes
|
||
PersistentFileId const[0x4, int64]
|
||
VolatileFileId const[0x0, int64]
|
||
InputOffset offsetof[Input, int32]
|
||
InputCount bytesize[Input, int32]
|
||
MaxInputResponse const[65536, int32]
|
||
OutputOffset offsetof[Output, int32]
|
||
OutputCount len[Output, int32]
|
||
MaxOutputResponse const[65536, int32]
|
||
Flags int32[0:1]
|
||
Reserved2 const[0, int32]
|
||
Input array[int8]
|
||
Output array[int8]
|
||
} [packed]
|
||
```
|
||
Ce style impose des tailles/décalages de structure corrects et améliore considérablement la couverture par rapport à la mutation aveugle.
|
||
|
||
---
|
||
|
||
## Fuzzing dirigé avec focus_areas
|
||
Utilisez le paramètre expérimental focus_areas de syzkaller pour donner plus de poids à des fonctions/fichiers spécifiques qui ont actuellement une faible couverture. Exemple JSON:
|
||
```json
|
||
{
|
||
"focus_areas": [
|
||
{"filter": {"functions": ["smb_check_perm_dacl"]}, "weight": 20.0},
|
||
{"filter": {"files": ["^fs/smb/server/"]}, "weight": 2.0},
|
||
{"weight": 1.0}
|
||
]
|
||
}
|
||
```
|
||
Cela aide à construire des ACLs valides qui atteignent les chemins arithmetic/overflow dans smbacl.c. Par exemple, un Security Descriptor malveillant avec un dacloffset surdimensionné reproduit un integer-overflow.
|
||
|
||
Générateur de reproducer (Python minimal):
|
||
```python
|
||
def build_sd():
|
||
import struct
|
||
sd = bytearray(0x14)
|
||
sd[0x00] = 0x00; sd[0x01] = 0x00
|
||
struct.pack_into('<H', sd, 0x02, 0x0001)
|
||
struct.pack_into('<I', sd, 0x04, 0x78)
|
||
struct.pack_into('<I', sd, 0x08, 0x00)
|
||
struct.pack_into('<I', sd, 0x0C, 0x10000)
|
||
struct.pack_into('<I', sd, 0x10, 0xFFFFFFFF) # dacloffset
|
||
while len(sd) < 0x78:
|
||
sd += b'A'
|
||
sd += b"\x01\x01\x00\x00\x00\x00\x00\x00" # minimal DACL
|
||
sd += b"\xCC" * 64
|
||
return bytes(sd)
|
||
```
|
||
---
|
||
|
||
## Briser les plateaux de couverture avec ANYBLOB
|
||
syzkaller's anyTypes (ANYBLOB/ANYRES) permettent de réduire des structures complexes en blobs qui mutent de manière générique. Générez un nouveau corpus à partir de pcaps SMB publiques et convertissez les payloads en programmes syzkaller appelant votre pseudo-syscall (p.ex., syz_ksmbd_send_req):
|
||
```bash
|
||
# Extract SMB payloads to JSON
|
||
# tshark -r smb2_dac_sample.pcap -Y "smb || smb2" -T json -e tcp.payload > packets.json
|
||
```
|
||
|
||
```python
|
||
import json, os
|
||
os.makedirs("corpus", exist_ok=True)
|
||
|
||
with open("packets.json") as f:
|
||
data = json.load(f)
|
||
# adjust indexing to your tshark JSON structure
|
||
packets = [e["_source"]["layers"]["tcp.payload"] for e in data]
|
||
|
||
for i, pkt in enumerate(packets):
|
||
pdu = pkt[0]
|
||
pdu_size = len(pdu) // 2 # hex string length → bytes
|
||
with open(f"corpus/packet_{i:03d}.txt", "w") as f:
|
||
f.write(
|
||
f"syz_ksmbd_send_req(&(&(0x7f0000000340))=ANY=[@ANYBLOB=\"{pdu}\"], {hex(pdu_size)}, 0x0, 0x0)"
|
||
)
|
||
```
|
||
Cela permet de démarrer l'exploration et peut déclencher immédiatement des UAFs (p.ex., dans ksmbd_sessions_deregister) tout en augmentant la couverture de quelques pourcents.
|
||
|
||
---
|
||
|
||
## Sanitizers: Au-delà de KASAN
|
||
- KASAN reste le détecteur principal pour les bugs liés au tas (UAF/OOB).
|
||
- KCSAN produit souvent des faux positifs ou des data races de faible gravité sur cette cible.
|
||
- UBSAN/KUBSAN peut détecter des erreurs de bornes déclarées que KASAN manque en raison de la sémantique des indices de tableau. Exemple:
|
||
```c
|
||
id = le32_to_cpu(psid->sub_auth[psid->num_subauth - 1]);
|
||
struct smb_sid {
|
||
__u8 revision; __u8 num_subauth; __u8 authority[NUM_AUTHS];
|
||
__le32 sub_auth[SID_MAX_SUB_AUTHORITIES]; /* sub_auth[num_subauth] */
|
||
} __attribute__((packed));
|
||
```
|
||
Définir num_subauth = 0 déclenche une lecture OOB dans la structure de sub_auth[-1], détectée par les declared-bounds checks d'UBSAN.
|
||
|
||
---
|
||
|
||
## Remarques sur le débit et le parallélisme
|
||
- Un seul processus fuzzer (auth/state partagé) a tendance à être nettement plus stable pour ksmbd tout en mettant toujours en évidence des races/UAFs grâce à l'async executor interne de syzkaller.
|
||
- Avec plusieurs VMs, vous pouvez toujours atteindre plusieurs centaines de commandes SMB par seconde au total. Une couverture au niveau des fonctions d'environ ~60% de fs/smb/server et ~70% de smb2pdu.c est atteignable, bien que la couverture des transitions d'état soit sous-représentée par de tels métriques.
|
||
|
||
---
|
||
|
||
## Liste de contrôle pratique
|
||
- Activer durable handles, leases, multi-channel, oplocks et VFS objects dans ksmbd.
|
||
- Autoriser guest et map-to-guest ; accepter NTLMv2. Supprimer les credit limits et augmenter le max connections pour la stabilité du fuzzer.
|
||
- Construire un harness stateful qui met en cache SessionId/TreeID/FileIDs et enchaîne create → ioctl → close.
|
||
- Utiliser une grammaire pour SMB2 PDUs afin de maintenir la validité structurelle.
|
||
- Utiliser focus_areas pour surpondérer les fonctions faiblement couvertes (par ex., des chemins dans smbacl.c comme smb_check_perm_dacl).
|
||
- Seed avec ANYBLOB provenant de vrais pcaps pour casser les plateaux ; packer les seeds avec syz-db pour réutilisation.
|
||
- Lancer avec KASAN + UBSAN ; trier attentivement les rapports declared-bounds d'UBSAN.
|
||
|
||
---
|
||
|
||
## Références
|
||
- Doyensec – ksmbd Fuzzing (Part 2): https://blog.doyensec.com/2025/09/02/ksmbd-2.html
|
||
- syzkaller: https://github.com/google/syzkaller
|
||
- ANYBLOB/anyTypes (commit 9fe8aa4): https://github.com/google/syzkaller/commit/9fe8aa4
|
||
- Async executor change (commit fd8caa5): https://github.com/google/syzkaller/commit/fd8caa5
|
||
- syz-db: https://github.com/google/syzkaller/tree/master/tools/syz-db
|
||
- KASAN: https://docs.kernel.org/dev-tools/kasan.html
|
||
- UBSAN/KUBSAN: https://docs.kernel.org/dev-tools/ubsan.html
|
||
- KCSAN: https://docs.kernel.org/dev-tools/kcsan.html
|
||
- Microsoft Open Specifications (SMB): https://learn.microsoft.com/openspecs/
|
||
- Wireshark Sample Captures: https://wiki.wireshark.org/SampleCaptures
|
||
- Lecture de fond: pwning.tech “Tickling ksmbd: fuzzing SMB in the Linux kernel”; Dongliang Mu’s syzkaller notes
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|