Un soir, une discussion avec Ronald et Sean nous a mene sur le sujet des doubles free() sous Unix, et je me suis rendu compte que je n'avais jamais exploite un double free() sous Windows. Bien que la problematique ne soit pas extremement complexe, il semblerait qu'il n'y ait pas des masses de documentation sur le sujet, a part une entree de sh0ck qui se retrouve massacree un peu partout.
Bien entendu, de nombreux elements entrent en compte:
- Y a-t-il ecriture des donnees entrent les deux free()?
- Le Heap en question a-t-il un Lookaside?
- Est-ce que le Lookaside a atteint sa profondeur maximale avant le 1er free()? Apres?
- Le Chunk libere est-il VirtualAllloc()'ed?
Quelques inexactitudes sur le sujet:
Tout d'abord: le free() d'un chunk deja marque comme libere (Chunk.Flag & 1 == 0) n'a aucun effet. Donc contrairement a ce qu'affirment des papiers comme
Towards Automatically Generating Double-Free Vulnerability Signatures Using Petri Nets (1), meme s'il y a possibilite d'ecriture dans la partie data du chunk entre les deux free(), il ne se passera rien. S'il y a liberation, ecriture puis allocation, c'est une histoire differente (unlink sur un LIST_ENTRY maitrise qui donne un Write4) et ce n'est pas un double free().
Ensuite, si le chunk est VirtualAlloc'ed() - c'est usuellement le cas si la taille/8 est > 0xfe00 qui est le seuil par defaut - le 1er free() va retourner la page au gestionnaire de memoire de Windows qui va decommit la zone rapidement. Cela entrainera une violation d'acces lors du second free().
Nous sommes donc restreints aux cas ou le chunk est libere vers la Lookaside puisque dans ce cas la le chunk est toujours marque comme occupe. Donc:
- Le Heap en question doit avoir un Lookaside
- Le free() doit etre sur un chunk dont la taille est < 1024
- L'entree correspondante du Lookaside doit avoir au moins 1 place disponible (la profondeur par defaut est 4 la plupart du temps) sinon il sera libere vers la FreeList[]
Apres, il existe plusieurs possibilites, que l'on trouvera decrites dans une
entree du blog de Symantec par Matt Connover.
- Premier free() vers le Lookaside qui se retrouve plein entrainera un second free dans la FreeList[] (et potentiel coallesce) avec ecrasement du LIST_ENTRY apres premiere allocation, et une entree de Lookaside pointant vers le chunk libre (de la FreeList[]) suivant.
- Premier et second free() vers le Lookaside, qui entrainera une primitive WriteN lors de la troisieme allocation d'un chunk de la taille en question.
Au final, un double free() ca s'exploite relativement bien dans XP et 2003, mais pas comme le decrivent certains.
Edit: petite correction dans le 1er cas.
(1): "
After it has been freed, the programmer mistakenly uses temp and writes X in the first 4 bytes and Y in the second 4 bytes of temp. Then the programmer frees temp again. The system will then try to insert temp into the free list for a second time right before where it is already inserted."