
Généralement, les gens sont plutôt étonnés lorsque je leur dis que mon browser n'est pas Firefox mais Opera. La question qui revient le plus souvent : "Bah pourquoi ?".
Je ne me souviens plus exactement à quel moment j'ai fait le switch, mais ça devait certainement être pas longtemps après le 500e crash de Firefox pour cause de mémoire insuffisante (version 2 j'avoue, il parait que ça s'est amélioré depuis). Opera est rapide, fiable, mature, peu gourmand. L'archétype du produit bien sous tout rapport, qui juste marche, mais qui n'a pas connu le succès mérité sans qu'on sache vraiment pourquoi (un peu comme la Dreamcast quoi). Pourtant, ça m'a fait à peu près le même effet de passer de Firefox à Opera que de Debian à Gentoo, c'est vous dire le gouffre.
Et la sécurité dans tout ça ? Chaque release de Firefox corrige sont petit lot de "crashes with evidence of memory corruptions" (vous noterez le sens de l'euphémisme de la fondation Mozilla). Tout le monde s'en fout. Dans Internet Explorer, deux 0-days et c'est la panique. Sur le fond, je suis assez d'accord : il est plus sécurisé de surfer avec FF qu'avec IE, puisque dans la mesure où IE a le plus de parts de marchés, les codes d'exploit présents dans les kits le cibleront en priorité. Aucune raison technique, simple question de rentabilité. Donc encore mieux avec Opera. En résumé : personne n'utilise Opera, donc tout le monde aurait intérêt à l'utiliser (je suis fan de ce genre de paradoxe :) Opera contient peut-être des vulnérabilités énormes, mais il n'en demeure pas moins qu'il est moins risqué de surfer avec lui que FF ou IE. Point. Bien sûr qu'il y a des 0-days dans Opera, mais certainement moins que dans les autres browsers. Et qu'on ne me parle pas de Chrome.
Pendant qu'on y est, j'ai découvert une vuln hier (le concept est pourtant pas neuf, mais j'étais passé complètement à côté). Laissez-moi vous présenter le DNS prefetching. Quand vous ouvrez une page avec FF 3.5, il résout automatiquement les domaines des liens présents dans la page, même si vous n'avez cliqué nulle part. Démonstration :
<html> <body> <a href="http://mon.superdomaine.fr">gé mm pa cliké !!!</a> </body> </html>
La simple ouverture dans FF provoque la résolution du domaine :
Bien pratique pour remplacer les vieillissantes notifications mail (NB : ne marche plus sur GMail en raison du récent passage à HTTPS).
La simple ouverture dans Opera provoque... bah rien. CQFD.
Avant avec QEMU, la mise en réseau de VM entre elles, ça n'était pas simple, contrairement à VMWare qui gère ça nativement. Mais depuis la version 0.10, QEMU supporte nativement VDE (Virtual Distributed Ethernet), rendant ainsi l'opération très facile (avant il fallait passer par le wrapper vdeqemu, qui marchait plus ou moins bien). Sous Gentoo, le USE flag vde est disponible pour les paquets app-emulation/qemu et app-emulation/kvm (versions ~x86), correspondant à l'option de compilation --enable-vde.
On commence par configurer les tap :
# tunctl -u sylv1 Set 'tap0' persistent and owned by uid 42 # tunctl -u sylv1 Set 'tap1' persistent and owned by uid 42 # ifconfig tap0 192.168.0.1 # ifconfig tap1 192.168.0.2
On crée ensuite le switch virtuel (NDS : chez moi, l'option -m 777 est nécessaire sinon QEMU ne démarre pas en raison de mauvaises permissions) :
# vde_switch -d -F -m 777 -t tap0,tap1
Ne reste plus qu'à lancer chaque VM :
$ qemu -loadvm 1 -m 128 -net nic,macaddr=52:54:00:12:34:56 -net tap,ifname=tap0,script=no -net vde malware.qcow2 $ qemu -loadvm 1 -m 128 -net nic,macaddr=52:54:00:12:34:57 -net tap,ifname=tap1,script=no -net vde malware.qcow2
Lors de la configuration réseau, Windows devrait vous dire que le nom de machine existe en double :)
L'info n'est ni neuve ni révolutionnaire mais j'avoue que j'étais passé à côté jusqu'à aujourd'hui : les distribs GNU/Linux sont en train de migrer vers l'utilisation de SHA-512 dans /etc/shadow. Sous Gentoo stable x86, c'est par exemple le cas depuis le 08/03/2009 (22/03/2009 sur hardened) ; dans /usr/portage/sys-auth/pambase, voir le ChangeLog et le nouveau USE flag sha512 dans metadata.xml. D'après ce que j'ai pu lire, Ubuntu 8.10 et Fedora 9 sont aussi passés à SHA-512 par défaut. Remarquez l'option sha512 sur la ligne pam_unix.so dans /etc/pam.d/system-auth.
Désormais, si vous changez votre mot de passe, il sera condensé avec SHA-512 (préfixe $6$) au lieu de MD5 (préfixe $1$). NDS : il existe aussi le préfixe $5$ pour SHA-256.
Vous pouvez générer manuellement le hash qui sera mis dans /etc/shadow via la ligne Python suivante : (utile car un bug dans mkpasswd empêche de spécifier un salt de seulement 8 caractères) :
python -c "import crypt; print crypt.crypt('monpass', '\$6\$mon-salt')"
Une fois tous les mots de passe mis à jour vers SHA-512, reste maintenant à tester l'ami John :
# john /etc/shadow No password hashes loaded
Tiens :)
Pour un résumé des talks, je suppose que vous savez où aller :)
Prêts à attaquer le SSTIC 2009 :)
La keynote sur la sûreté dans les avions
Injection de code malicieux dans une Java Card
Conférence de Florent sur le data tainting appliqué aux malware
Désobfuscation automatique de binaire
Solution du challenge SSTIC 2009
Traçage de traîtres en multimédia
La Barbie Hacker de Marie Barel
Virtualisation matérielle pour protéger le noyau
Projet SEC&SI de création d'un OS sécurisé pour Mme Michu
Une conf marrante sur les attaques via LaTeX
La box dont on ne doit pas prononcer le nom...
A quand une conf sur la compromission physique des équipements d'hôtels ?
IpMorph, unification de la mystification de prise d'empreinte
Kolumbo : analyse dynamique depuis le noyau
Emanations compromettantes électromagnétiques (une conf remarquable sur le fond comme sur la forme)
Je n'avais encore jamais joué avec les capabilities sous Linux. Il n'est jamais trop tard pour bien faire :)
Sur une distro GNU/Linux de base, un plus ou moins grand nombre de binaires setuid root sont présents. Par exemple, sur mon système, j'avais (cherchez l'intrus :)
# find / -type f -perm -004000 /usr/libexec/lockspool /usr/libexec/dbus-daemon-launch-helper /usr/lib/misc/ssh-keysign /usr/lib/misc/xscreensaver/sonar /usr/lib/misc/glibc/pt_chown /usr/bin/smbumount /usr/bin/Xorg /usr/bin/rsh /usr/bin/chage /usr/bin/rcp /usr/bin/tshark /usr/bin/chsh /usr/bin/newgrp /usr/bin/expiry /usr/bin/chfn /usr/bin/gpasswd /usr/bin/sudo /usr/bin/smbmnt /usr/bin/lppasswd /usr/bin/rlogin /usr/bin/sudoedit /usr/bin/dumpcap /usr/kde/3.5/bin/kgrantpty /usr/kde/3.5/bin/fileshareset /usr/kde/3.5/bin/start_kdeinit /sbin/unix_chkpwd /bin/mount /bin/passwd /bin/su /bin/umount /bin/ping /bin/.backdoor
La plupart des gens considèrent le bit suid sur un binaire appartant à root comme le Mal. Ce n'est pas toujours le cas. Par exemple, depuis la version 0.99.7, Wireshark vous permet de sniffer sur eth0 en tant que simple utilisateur, à partir du moment où vous faites partie du groupe wireshark. En fait, tout le code demandant des privilèges élevés est simplement passé dans dumpcap qui est setuid root, groupe wireshark seulement :
$ l `which dumpcap` -r-sr-s--- 1 root wireshark 55K avril 13 23:47 /usr/bin/dumpcap
Cela vous évite donc de lancer la GUI Wireshark et ses 1.5 millions de lignes de code en root (NDS : tout cela est indiqué sous Gentoo lorsque vous installez le paquet). Le bit suid a donc augmenté la sécurité de votre système.
Mais il est aussi vrai qu'il suffit d'une vuln dans un binaire setuid et c'est game over ; on cherche donc à avoir le plus petit nombre possible de tels binaires. On peut même n'en avoir aucun, et c'est là où les capabilities entrent en jeu.
Pour que ça marche, il vous faut :
On va prendre l'exemple hyper bateau du binaire ping. L'envoi d'un paquet ICMP nécessite l'ouverture d'une socket en mode RAW, opération privilégiée sous Linux. Pour que tout le monde puisse quand même utiliser ping, le binaire est donc setuid root :
$ l `which ping` -rws--x--x 1 root root 36K août 31 2008 /bin/ping
On supprime le bit suid, le ping n'est plus possible :
# chmod -s `which ping` $ ping www.free.fr ping: icmp open socket: Operation not permitted
Un strace nous indique là où ça coince :
$ strace ping www.free.fr 2>&1 | grep EPERM socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = -1 EPERM (Operation not permitted)
Ne reste plus qu'à regarder à quelle capability ça correspond :
$ egrep -i "socket|raw" /usr/include/linux/capability.h [...] /* Allow use of RAW sockets */ #define CAP_NET_RAW 13 [...]
On modifie maintenant les attributs du fichier pour lui rajouter la capability CAP_NET_RAW. Le ping remarche bien :
# setcap CAP_NET_RAW=ep `which ping` $ ping www.free.fr PING www.free.fr (212.27.48.10) 56(84) bytes of data. 64 bytes from www.free.fr (212.27.48.10): icmp_seq=1 ttl=120 time=33.2 ms [...]
Les flags ep correspondent à "Effective" et "Permitted" (cf la doc pour les détails).
A ce point, on a juste remplacé le bit suid par la capability. On aimerait aller plus loin, en attribuant par exemple la capability en fonction de l'utilisateur. C'est possible en appelant setcap avec les flags ei ("Effective" et "Inheritable") au lieu de ep :
# setcap CAP_NET_RAW=ei `which ping`
Il faut ensuite ajouter la ligne suivante dans /etc/pam.d/login :
auth required pam_cap.so
Enfin, il faut modifier le fichier /etc/security/capability.conf pour indiquer quel user se verra attribuer la capability :
$ grep sylv1 /etc/security/capability.conf cap_net_raw sylv1
Le ping marchera ainsi pour le user sylv1, mais pas pour les autres. En répétant la manip pour les autres binaires, vous obtiendrez ainsi un système sans binaire setuid root. A noter que l'utilisation des capabilities est un domaine d'actualité, par exemple Gentoo devrait par exemple travailler dessus lors du prochain Google Summer of Code.
On vient de voir que les capabilities peuvent être utilisées afin de restreindre les privilèges. Ils peuvent aussi être utilisés pour les augmenter ; si cela est fait intelligemment, cela peut aussi contribuer à améliorer la sécurité du système. Exemple : pour modifier votre conf réseau, vous ouvrez un shell root et lancez ifconfig ou route. Si vous laissez ce shell ouvert sans verrouiller votre session, c'est fini. Alors que vous auriez pu mettre la bonne capability, en l'occurence CAP_NET_ADMIN, et rester dans votre shell standard (évidemment, cela n'est applicable que si vous êtes seul sur le système). A méditer...
Mais ce n'est pas tout. Vous vous souvenez peut-être du MISC 13 (mai-juin 2004, et oui ça remonte :) où la page 50 parlait des capabilities. C'était pour introduire la notion de capability bounding set, un masque où chaque bit représente une capability : si un bit est à 0, la capability associée n'est disponible pour aucun processus. Vous pouvez donc utiliser /proc/sys/kernel/cap-bound (liée à la variable cap_bset du noyau) pour diminuer les capabilities de façon non-réversible (jusqu'au prochain reboot quoi). Problème : si vous oubliez aussi de désactiver CAP_SYS_RAWIO en plus de CAP_SYS_MODULE (chargement de module noyau), un attaquant peut accéder en dur à /dev/mem et écrire la valeur qu'il souhaite dans cap_bset. L'attaque ne date vraiment pas d'hier puisque l'article de MISC semble lui-même tiré d'un article datant de 2000.
De nos jours, c'est-à-dire depuis Linux 2.6.25, /proc/sys/kernel/cap-bound n'existe plus ; ce n'est plus un attribut global mais par thread, qui se transmet par fork/exec. Autrement dit, si vous voulez supprimer une capability pour tout le système, il faudra restreindre dès init. Regardons dans /usr/src/linux/include/linux/init_task.h :
#define INIT_TASK(tsk) \
{
[...]
.cap_bset = CAP_INIT_BSET,
[...]
Un peu plus haut :
#ifdef CONFIG_SECURITY_FILE_CAPABILITIES # define CAP_INIT_BSET CAP_FULL_SET #else # define CAP_INIT_BSET CAP_INIT_EFF_SET #endif
Donc il faut modifier CAP_FULL_SET, défini dans /usr/src/linux/include/linux/capability.h :
# define CAP_FULL_SET ((kernel_cap_t){{ ~0, ~0 }})
Par défaut, le cap_bset vaut donc 0xffffffff (toutes les capabilities activées). Si on veut par exemple désactiver le chargement des modules pour tous, même root, on peut donc y rajouter CAP_SYS_MODULE (oui, je sais, c'est moins idiot de désactiver les modules dans la conf du noyau, c'est juste pour l'exemple :) :
# define CAP_FULL_SET ((kernel_cap_t){{ ~CAP_TO_MASK(CAP_SYS_MODULE), ~0 }})
N'oubliez pas aussi de désactiver CAP_SYS_RAWIO :)
Références :
Petit souvenir d'un week-end à Aix-en-Provence :
Wikipedia : "Un cardeur est un ouvrier qui carde, c'est-à-dire qui démêle des fibres textiles et les peigne à l'aide d'une carde."
Quoi, vous aviez pensé à autre chose :)
Conficker, c'est un peu le Security Failed à lui tout seul. Des millions de machines infectées partout dans le monde. Pour éviter la propagation du ver à tout un parc de machines en une demi-journée, il suffisait pourtant :
Ces deux points font partie de tous les guides de sécurisation Windows depuis 10 ans. Que Mme Michu se fasse infecter sa clé USB, passe encore, mais il y a d'autres contextes dans lesquels on comprend moins... Bonne chance avec la variante C.
On va mainteanant tester notre ami Conficker sous XP avec WehnTrust. On l'installe, on reboote, on lance la bête. Résultat :
On passe un coup de MSRT pour être sûr :
Pas si compliqué que ça la sécurité finalement...
S'il vous arrive d'analyser du malware de temps à autres, il vous est peut-être déjà arrivé de faire ceci :
wget_ie http://site/vx.exe
file vx.exe vx.exe: MS-DOS executable PE for MS Windows (console) Intel 80386 32-bit, UPX compressed
upx -d vx.exe [...] Unpacked 1 file.
Oui, mais en fait non. Le raisonnement ci-dessus part du principe suivant : si upx -d marche, alors il s'agissait bien d'un binaire compressé avec UPX et le binaire unpacké aura le même comportement que le binaire packé. Sauf que qu'est-ce qui vous dit que le binaire UPX n'a pas été bidouillé ? Par exemple en ajoutant du code qui s'exécute avant le point d'entrée pour modifier des octets en mémoire juste avant que l'algorithme de décompression ne passe par là ? Par exemple en ajoutant un callback TLS ?
L'idée de ce billet blog m'est venue lorsque j'ai travaillé sur le level 2 du Khallenge 2008 et que je me suis rendu compte que le callback TLS du binaire original (celui qui pose les bases d'une détection de debugger réalisée un peu plus bas dans le code) n'était pas conservé par la décompression UPX. Plutôt marrant.
Bon, je ne vais pas m'étendre outre mesure sur le mécanisme de Thread Local Storage sous Windows. Il s'agit d'une fonctionnalité du loader permettant d'exécuter du code avant le point d'entrée défini dans l'en-tête PE. C'est par exemple utilisé par les malware pour déchiffrer le code situé à l'entry point (cf analyse de PE GRUM par N. Brulez dans MISC 31). Les structures sont les suivantes :
Un bon schéma valant mieux qu'on long discours :
On peut ajouter un callback TLS à un binaire UPX pour par exemple :
On part d'un programme très simple qui affiche une message box :
#include <windows.h>
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MessageBox(NULL, "good", "tls", MB_OK);
return 0;
}
On compile et on compresse en UPX. Toute la suite se fait dans un éditeur hexa. Première étape : trouver un endroit où stocker nos différentes structures. En parcourant vite fait le binaire, on trouve de la place à l'offset cf0 :
On y place notre structure IMAGE_TLS_DIRECTORY :
Le champ AddressOfCallBacks pointe vers l'offset d08 où on place maintenant notre tableau de pointeurs vers callbacks. On n'en a besoin que d'un seul :
Que voulons-nous faire dans ce callback ? On va prendre l'exemple très simple de la modification de la chaine "good" qui se situe à l'offset 7ae, soit à l'adresse virtuelle 4075AE. Un simple MOV sur le contenu présent à cette adresse suffit (oui, je sais c'est particulièrement peu élégant comme méthode, mais c'est juste pour l'exemple...) :
55 PUSH EBP 89 E5 MOV EBP, ESP 36 C705 AE754000 6576696C MOV DWORD PTR SS:[004075AE], 6C697665 b8 0000 0000 MOV EAX, 0 C9 LEAVE C3 RET
En résumé, on obtient ceci :
On peut ainsi modifier tout octet dans la version UPXée du binaire (attention, pour patcher certaines zones, il faut aussi patcher le checksum UPX), de façon à obtenir un binaire décompressé en mémoire différent de celui décompressé en statique avec upx -d.
Tout est en place, il ne reste plus qu'à modifier l'en-tête optionnelle PE pour rendre la structure IMAGE_DIRECTORY_ENTRY_TLS non nulle et la faire pointer sur la structure définie plus haut :
On obtient ainsi le binaire à télécharger en annexe. Si on va un peu vite à l'analyse et qu'on exécute la version décompressée avec upx -d, il affiche le message défini dans le source, à savoir "good" :
En revanche, quand on exécute le binaire UPX lui-même sous XP, surprise :
Archive : sylv1_tls.zip
Ayant acquis il y a quelques temps un nouveau laptop, je m'étais empressé de le chiffrer avec du DM-Crypt/LUKS et j'avais voulu jouer un peu avec ce système de (dé)chiffrement de disque à la volée et la technique de cold boot. NDS : la plupart des infos qui suivent sont plus ou moins obsolètes depuis la publication des sources du cold boot original ; c'est donc plus pour garder une trace de ce que j'avais fait à l'époque.
La première étape consiste à récupérer un dump de la RAM. J'avais utilisé MsRamDump qui fait très bien l'affaire. Dans ce dump on trouve tout un tas de choses très intéressantes comme le mot de passe root utilisé pour redémarrer le système, à tel point que je n'ai pas pu m'empêcher de grepper sur ma passphrase pour voir si elle s'y trouvait en clair : c'était bien sûr très naïf (ou pas...). La vraie question est donc : "comment récupérer ma clé de chiffrement AES dans ce dump ?", ce qui ramène à la question "mais au fait, elle ressemble à quoi ma clé de chiffrement ?".
Petit tour très rapide dans le monde DM-Crypt/LUKS. A chaque démarrage de l'OS (j'ai bien dit "OS", il ne s'agit pas d'un chiffrement pré-boot au sens strict comme TrueCrypt), un prompt "Enter LUKS passphrase:" me demande ma passphrase pour déchiffrer ma partition root. cryptsetup est l'outil de gestion des clés LUKS. Regardons ses sources de plus près :
$ grep "Enter LUKS passphrase:" -R cryptsetup-1.0.5 cryptsetup-1.0.5/lib/setup.c
La chaîne est présente deux fois, dont une pour __crypt_luks_open() (l'autre pour __crypt_luks_format()) qui semble être la bonne piste. Nous voyons immédiatement que notre chère master key se trouve dans la variable struct luks_masterkey *mk. Pour info :
struct luks_masterkey {
size_t keyLength;
char key[];
};
La fonction __crypt_luks_open() fait appel à LUKS_open_any_key() (dans luks/keymanage.c) qui se charge d'énumérer les slots de clé associés à une partition (il est possible de stocker LUKS_NUMKEYS = 8 versions chiffrées avec une passphrase de la clé sur chaque partition) et de débloquer la master key le cas échéant (son checksum étant stocké dans l'en-tête de la partition, cf specs pour les détails). Elle passe ensuite la main à son backend DM-Crypt qui se chargera d'effectuer les opérations de chiffrement de façon transparente.
Bref, pour récupérer la clé, il suffit de poser un printf dans __crypt_luks_open() entre LUKS_open_any_key() et LUKS_dealloc_masterkey(mk). On note au passage l'appel à safe_free(password) pointant vers un rassurant memset(data, 0, n) qui explique le pourquoi du paragraphe 2 :) Une fois la main passée à DM-Crypt, la clé est elle aussi shredée de la mémoire par la fonction LUKS_dealloc_masterkey(). Ce qu'on va trouver dans le dump c'est donc la clé stockée par le composant DM-Crypt du noyau :
$ find /usr/src/linux -name '*dm-crypt*'
/usr/src/drivers/md/dm-crypt.c
$ cat /usr/src/drivers/md/dm-crypt.c
[...]
struct crypt_config {
[...]
u8 key[0];
[...]
OK donc maintenant j'ai la master key de la partition qui s'affiche au démarrage :
On va donc pouvoir regarder comment la récupérer dans le dump. Déjà, notre mk étant connue, on peut écrire un petit prog pour parcourir un fichier de dump et voir à peu près où elle se trouve en mémoire, histoire de faire connaissance :
$ ./searchknownluksmk ramdump [sylv1] Found LUKS key at offset 922802228 [sylv1] Found LUKS key at offset 922802692 [sylv1] Found LUKS key at offset 923103124 [sylv1] Found LUKS key at offset 923138452 [sylv1] Found LUKS key at offset 923608116 [sylv1] Found LUKS key at offset 923608580
Surprise, la clé est présente à plusieurs endroits :) Raisonnons maintenant dans le "bon" sens et cherchons à déterminer une signature dans le dump permettant de retrouver la clé. Bon, là j'avoue que je me suis pas cassé la tête : j'ai juste remarqué que la clé était présente à l'offset 0x8c après la signature "aes\x00aes)". Je n'ai aucune idée si c'est générique ou spécifique à mon système (si quelqu'un peut tester, je suis preneur, surtout que ça ne correspond pas exactement à ce que d'autres ont trouvé avant moi) :
En tout cas, cela nous donne le petit programme fourni en annexe pour retrouver une master key AES-256 (pour être plus générique sur la taille, on peut aller chercher les infos dans l'en-tête LUKS de la partition, mais là j'ai pas le temps :).
$ ./searchluksmk ramdump [sylv1] Found sig at 0xa56f08, LUKS master key is: 0123ABCD...
Une fois la clé connue, il suffit de modifier la source de cryptsetup pour forcer l'utilisation de cette master key lors de l'ouverture du périphérique (toujours dans __crypt_luks_open()) :
A partir du dump mémoire, on récupère ainsi la main sur le filesystem sans connaître la passphrase :
# cryptsetup luksOpen /dev/sda8 ineednopass Enter LUKS passphrase: *simple appui sur entree* [sylv1] Forcing master key to 0123ABCD.... Command successful. # mount /dev/mapper/ineednopass /mnt/test # ls /mnt/test bin dev home lost+found mnt proc sbin tmp var boot etc lib media opt root sys usr # echo gagné :) gagné :)
Petite remarque au passage : si vous n'utilisez pas le mode "clé chiffrée en gpg sur périphérique usb avec passphrase", votre partition /boot reste en clair, ce qui permet donc à votre voisin de rooter votre bécane en patchant le noyau et/ou votre initramfs pendant que vous avez le dos tourné.
Source : searchluksmk.c
Le contenu du blog est publié sous la licence CC-BY-SA.