Wednesday, June 13, 2012

Comportement inattendu de LocalReAlloc sous XP SP3

Note: J'ai cherche (rapidement) si quelqu'un avait deja documente le comportement, mais je n'ai rien trouve. Si j'ai rate quelque chose, n'hesitez pas a m'envoyer les liens, je les rajouterai.

Ce week-end, je suis tombe sur une vulnerabilite qui m'a ammene a m'interesser au comportement de LocalReAlloc, et donc RtlReAllocateHeap. Le probleme se resumait a passer un pointeur de pile a des fonctions attendant un pointeur de tas. Vu que cela ne sonne pas tres bien en Francais, voici un morceau code illustrant cela:


Les appels a LocalFree ou LocalReAlloc dependent des branches et boucles internes a la fonction, et les tailles et index ont ete choisis par hasard.

Qui dit XP SP3, dit aussi cookie de tas, et verification des listes doublement chainees. Il ne sera donc pas surprenant de voir que LocalFree (RtlFreeHeap) va echouer la plupart du temps, sauf si vous etes tres doues. Voici un example des verifications qui peuvent vous attendre:


Au final, LocalFree rate, ne retourne pas NULL, et le pointeur passe n'est pas touche. Pour obtenir quelque chose d'interessant avec LocalFree, il faudrait passer la verification du cookie, et declencher une liberation vers le Lookaside par exemple.

Par contre, LocalReAlloc ne fonctionne pas pareil. D'apres le MSDN, si la reallocation rate, la fonction est sensee retourner NULL, avec un code d'erreur recuperable via GetLastError(). Mais en analysant la fonction, on se rend rapidement compte qu'il existe des chemins qui retournent directement le pointeur passe en cas d'erreur, sans meme verifier le cookie.

Par example, si l'entete du chunk n'est pas marque comme etant occupe (ou du moins ce que NtDll pense etre l'entete du chunk), une erreur STATUS_INVALID_PARAMETER va etre levee, mais la fonction va quand meme retourner le pointeur passe (en version simplifiee).

Voici les quelques blocs en question:

...
...

Un rapide programme pour tester cela donne (a lancer sans que le tas soit en mode debug):


LocalReAlloc retourne un pointeur non NULL, le programme pense que la reallocation a ete un succes. Alors que q pointe sur la pile et toute copie vers q va deborder l'espace reserve pour p au dela de 8 octets.
En lancant le meme programme sous Windows 7, une erreur critique est detectee, et le programme termine.

En soi, cela ne constitue pas une vulnerabilite. Par contre, couple a une corruption de pointeur comme expliquee au debut (ou meme LSB d'un pointeur de tas), on se retrouve avec un programme pensant avoir un espace fraichement realloue sur le tas, alors qu'il va ecraser des donnees quelque part ailleurs (pile dans notre exemple).

Friday, May 4, 2012

To all the tin foil hat wearers

I can't help but notice that the vast majority of people can't process a piece of news such as the one that was posted recently on this blog. To quote Lao Tzu "Those who know do not speak, those who speak, do not know", or something in that regard (sweet irony for I am now speaking).

What are Skype supernodes? As explained in the Skype presentations given 6 years ago, Skype supernodes are a directory and routing service. They are basically the Yellow Pages of Skype, you send them your blobs, and you query them to get your contacts' blobs. If you are behind a NAT or firewall, they will route requests to you. Do they route voice calls (or IM)? No. Relay nodes take care of those if you can't communicate directly with the other end. There is a mutual exclusivity in that a node can't be a relay and a supernode at the same time. At this time, relays are still random machines in the "wild", and people from that "Skype Open Source Project" are full of shit.

Once again, as explained in Vanilla Skype, when you establish a session with a peer - through a relay or directly - each party sends to the other a half-AES key encrypted with the public RSA blob of the other party. Then the session is encrypted using that session key. This means that the end to end traffic is encrypted with something neither the supernode, nor the relay node, nor Skype, know stuff about: because they do *not* have your private RSA key. Does having centralized Supernodes ease wiretapping? No. Does it make the network more reliable, secure, and scalable? Most likely.

Read the slides (part 1 & part 2). If you don't understand them, too bad, you are missing out.

Tuesday, May 1, 2012

Skype does away with random supernodes

A major change in the Skype network architecture has occurred two or three weeks ago (at the time I wrote this), and has gone unnoticed as far as I know. The number of supernodes has dropped from 48k+ to 10k+, and all the supernodes are now hosted by Microsoft/Skype. Promotion of random eligible nodes to supernodes has stopped (through the setting of the global boolean 33h).

Ironically, those remaining supernodes run on grsec'ed Linux boxes (I hope Spender gets a sizeable donation from Microsoft). They can host a considerable amount of clients, ~100000.

At the same time, the number of online Skype users jumped (http://skypejournal.com/blog/2012/04/23/skype-topped-41-5-million-concurrent-users-online-today-chart/) and can now reach 41M at peak hours.

This will likely ensure that former outages (http://articles.latimes.com/2010/dec/23/business/la-fi-skype-20101223) don't happen again, and gives MS a better control over the network.

Edit: dead link, so here is the original graph from Skype Journal:
Edit: supernodes list as of May 1st 2012: http://pastebin.com/LgWsPUGe
Edit: Microsoft confirms (http://arstechnica.com/business/news/2012/05/skype-replaces-p2p-supernodes-with-linux-boxes-hosted-by-microsoft.ars):
As part of our ongoing commitment to continually improve the Skype user experience, we developed supernodes which can be located on dedicated servers within secure datacentres. This has not changed the underlying nature of Skype’s peer-to-peer (P2P) architecture, in which supernodes simply allow users to find one another (calls do not pass through supernodes). We believe this approach has immediate performance, scalability and availability benefits for the hundreds of millions of users that make up the Skype community.
I do think that this was the way to do things, and that their beliefs expressed in the last sentence are correct (if anyone cares what I think!). They didn't say how much they are going to give Spender though...

Friday, March 23, 2012

MS12-020 round up

Introduction

Tout le monde et sa grand mere a ecrit un billet sur MS12-020, et c'est donc mon tour. Le soucis, c'est que personne ne semble avoir vraiment compris la vulnerabilite, et par consequent la quantite d'inexactitudes, d'approximations et autres contresens me fait grincer des dents.

Les specifications du protocole RDP sont disponibles en ligne: http://msdn.microsoft.com/en-us/library/cc240445(PROT.10).aspx, et je vous invite a les lire si vous avez des problemes a trouver le sommeil, et la description du bug par Luigi est ici: http://aluigi.org/adv/termdd_1-adv.txt.

Je me suis donne 5 jours pour tenter d'exploiter la vulnerabilite, au dela la rentabilite pour une vulnerabilite corrigee devient moindre, et cela donne une idee du temps restant pour obtenir un exploit viable. En voici un condense, visant principalement XP et 2003. Au passage, Microsoft a donne une exploitabilite de 1 au bug - exploit stable sous 30 jours. Je n'ai pas passe 30 jours, gardez cela a l'esprit.

Vulnerabilite

Tout d'abord la vulnerabilite. Comme decouvert plus ou moins correctement par bon nombre de personnes, un flag n'etait pas mis a zero dans deux fonctions, rdpwd!NMAbortConnect et rdpwd!NM_Disconnect apres un appel a rdpwd!NMDetachUserReq. Le soucis, c'est qu'un chemin existe permettant d'appeler rdpwd!NMDetachUserReq deux fois, avec la possibilite d'un free dans le premier et d'un use-after-free dans le second. Le premier appel provient de rdpwd!NMAbortConnect, le second de rdpwd!NMAbortConnect -> rdpwd!SM_OnConnected -> rdpwd!SM_Disconnect -> rdpwd!NM_Disconnect.


La vulnerabilite est trouvee. Comment peut-on generer un appel a rdpwd!NMAbortConnect? rdpwd!NMAbortConnect est appele depuis rdpwd!NM_Connect, et il existe 5 xrefs menant a cette fonction.  Les deux premiers surviennent avant que le flag @ +1Ch ne soit mis a 1 et peuvent donc etre ecartes. Les trois suivants impliquent que rdpwd!MCSChannelJoinRequest retourne quelquechose != 0. Il y a plusieurs moyens de faire echouer cette fonction, certains plus pratiques que d'autres. On va ecarter la possibilite que nt!ExAllocatePoolWithTag retourne NULL (plus de pool disponible!), et se concentrer sur rdpwd!GetNewDynamicChannel.

Le code de rdpwd.sys permet de definir jusqu'a 31 canaux dans un PDU Client Core Data (cf: 2.2.1.3.2 Client Core Data (TS_UD_CS_CORE), 2.2.1.3.4 Client Network Data (TS_UD_CS_NET), 2.2.1.3.4.1 Channel Definition Structure (CHANNEL_DEF)), mais permet aussi de definir le nombre maximum de canaux autorises (cf: 3.2.5.3.3 Sending MCS Connect Initial PDU with GCC Conference Create Request, http://www.itu.int/rec/T-REC-T.125-199802-I/en). Si le nombre de canaux crees par defaut plus le nombre de canaux demandes depasse le maximum, la fonction rdpwd!GetNewDynamicChannel va echouer lors de la creation du nieme canal.

Trigger

Testons, avec maxChannelIds de targetParameters a 32, et un channelDefArray contenant 31 canaux (eax contient le code d'erreur):

Breakpoint 0 hit
RDPWD!NMAbortConnect:
f6f13062 8bff            mov     edi,edi
kd> r eax
eax=0000000f



C'est moche, contrairement a ce que 99.9% de la communaute pense, maxChannelIds peut prendre toute valeur entre 0 et 32 (et meme plus dans certains cas) et toujours declencher l'appel a rdpwd!NMAbortConnect. Donc si votre signature d'IDS ou votre "analyse" se fonde sur un maxChannelIds a 0 (ou <= 5), vous brassez du vent.

Maintenant, appel a rdpwd!NMAbortConnect ne signifie pas necessairement use-after-free. Si vous avez un client qui envoie des paquets propres, etc, vous pouvez declencher un abort a coup sur, mais pas de use-after-free. Il faut autre chose, que je vous laisserai decouvrir par vous meme.

Une fois la sequence de paquets correctement construite, observons les consequences:


Breakpoint 1 hit
RDPWD!MCSDetachUserRequest+0x24:
f6f2bc82 e8a7ffffff      call    RDPWD!StackBufferAlloc (f6f2bc2e)
kd> !pool @esi 2
Pool page e1582998 region is Paged pool
*e1582990 size:   58 previous size:   18  (Allocated) *TSmc
Pooltag TSmc : PDMCS - Hydra MCS Protocol Driver
kd> g
Breakpoint 1 hit
RDPWD!MCSDetachUserRequest+0x24:
f6f2bc82 e8a7ffffff      call    RDPWD!StackBufferAlloc (f6f2bc2e)
kd> !pool @esi 2
Pool page e1582998 region is Paged pool
*e1582990 size:   58 previous size:   18  (Free ) *TSmc
Pooltag TSmc : PDMCS - Hydra MCS Protocol Driver

Deuxieme appel a rdpwd!MCSDetachUserRequest, sauf que cette fois ci @esi pointe sur un chunk libere. Et la, c'est le drame. Voici le code de la fonction en question:


Comme vous pouvez le voir, @edi est lu du contenu de @esi plus precisement le premier PVOID. Sous achitecture 32 bits, le chunk en question est de taille 0x58, sous 64 bits 0xa0, et c'est important. Pourquoi? Parceque la taille du chunk entrainera une liberation vers un des lookasides (sauf s'ils sont tous plein), et cela signifie que les 4 premiers octets du chunk libre (8 sous 64) constituent un pointeur vers un autre chunk libre de meme taille. Cela donne:

kd> !pool @edi 2
Pool page e126afb0 region is Paged pool
*e126afa8 size:   58 previous size:   20  (Free ) *Sect (Protected)
Pooltag Sect : Section objects
kd> !pool poi(@edi) 2
Pool page e12a9330 region is Paged pool
*e12a9328 size:   58 previous size:  100  (Free ) *NtFd
Pooltag NtFd : DirCtrl.c, Binary : ntfs.sys

Bien entendu cela peut etre different en fonction du nombre d'entrees dans le lookaside, etc. Ce dernier pointeur P sera utilise dans termdd!IcaBufferAlloc, et passe a termdd!IcaGetPreviousSdLink, ou un pointeur sera lu *(P-14h+0Ch) et retourne, -8. Le probleme ici c'est que P-14h+0Ch pointe sur le nt!_POOL_HEADER du chunk libre, et donc le pointeur retourne sera les 4 premiers octets de l'entete du chunk libre (moins 8). Sous 64 bits *(P-28h+18h), et on a le meme probleme, sauf que cette fois-ci le tag du chunk constitue les 32 bits de poids fort du pointeur retourne.

kd> dd poi(@edi)-8 L 2
e12a9328  040b0420 6446744e
kd> dt nt!_POOL_HEADER e12a9328
   +0x000 PreviousSize     : 0y000100000 (0x20)
   +0x000 PoolIndex        : 0y0000010 (0x2)
   +0x002 BlockSize        : 0y000001011 (0xb)
   +0x002 PoolType         : 0y0000010 (0x2)
   +0x000 Ulong1           : 0x40b0420
   +0x004 PoolTag          : 0x6446744e
   +0x004 AllocatorBackTraceIndex : 0x744e
   +0x006 PoolTagHash      : 0x6446

Comme vous le voyez ici, on a un BlockSize de 0xb (*8=0x58 soit 0x50 octets plus 8 octets d'entete) et un PoolType de 0x2, ce qui explique les differents crashes 0x040bXXXX postes partout. Bien evidemment, les 16 bits de poids faible dependent de la taille du chunk precedent et varient d'un crash a un autre. Le pointeur lu sera utilise dans la sequence d'instruction devenue celebre:

.text:0001188C 8B 46 18                          mov     eax, [esi+18h]
.text:0001188F 83 38 00                          cmp     dword ptr [eax], 0
.text:00011892 75 27                             jnz     short loc_118BB


et un potentiel call [eax] un peu plus loin. Cette situation est la situation "ideale", et en fonction de la disposition du kernel pool, du contenu du lookaside, tout peut changer. Controler cela avec une connection RDP pre-auth, c'est loin d'etre gagne.

Exploitation

A ce point, j'ai pense a deux possibilites d'exploitation distinctes: remplir la memoire usermode du svchost.exe dans le contexte duquel tourne Terminal Services/RDP, ou racer l'allocation du chunk de 0x50 bytes entre sa liberation et son utilisation.

Il s'avere que la course n'est pas trop compliquee a gagner sur un 2003, principalement parceque Terminal Services supporte plus de connexions concurrentes que RDP sous XP. Cela donne le resultat poste sur Twitter. Les quatres premiers octets (ou 8 pour 64 bits) du chunk precedemment libre sont maintenant sous notre controle, car le chunk a ete realloue avant son utilisation. Le probleme maintenant est qu'il va se produire quatre dereferencements successifs du pointeur initial avant le call [eax]. Et je n'ai pas trouve de moyen de "survivre" a ces quatre dereferencements dans le temps passe. Cela necessiterait soit de leaker un pointeur, soit de copier des donnees sous notre controle a un endroit fixe ou connu.

Une autre idee toujours fondee sur le controle des 4 premiers octets du chunk est de reussir a passer l'appel a termdd!IcaBufferalloc, et d'obtenir une autre erreur dans les fonctions appelees ulterieurement (rdpwd!CreateDetachUserInd par exemple). Je ne suis arrive a rien ici non plus.

Au niveau usermode, l'instance de svchost.exe en question abrite des services RPC qui peuvent s'averer utiles mais requierent authentification ou ne sont pas accessibles par defaut a distance. Il existe d'autres methodes mais je me suis heurte a des problemes d'alignements, de timing, qui m'ont semble redhibitoires au final.

Conclusion

Evidemment, il me faudrait passer 25 jours supplementaires a travailler sur cette vulnerabilite pour tenter d'en epuiser les possibilites, mais MS12-020 ne me semble pas super exploitable a distance. Localement c'est une autre histoire... Quand j'aurais un peu plus de temps je regarderai Vista/7/2008.

Il est dommage de voir la quantite d'horreurs publiees sur le sujet, de diffs approximatifs, d'erreurs d'interpretations.

Wednesday, February 22, 2012

Coppersmith pour les nuls

Recemment je suis tombe sur un probleme "interessant" ayant trait a RSA. La facon dont c'etait implemente me laissant a penser que quelque chose n'allait pas, et meme si j'apprecie la cryptographie, je n'ai pas la science infuse et il m'a fallu chercher a droite et a gauche une solution adequate (et simple).

En voici la version anonymisee.

Considerons le probleme ou Bob le developpeur veut chiffrer une cle AES 128 avec du RSA - 1024 bits parceque Bob sait qu'en dessous c'est pas "sur". Bob n'est pas super au point avec les standards, et il ne dispose que de quelques API d'arithmetique modulaire. Toujours est-il que Bob reussit a generer ses nombres premiers, choisit e=3 (pour la simplicite?), et calcule d correctement. Maintenant Bob se doute que si il chiffre sa cle AES (m) directement avec e=3, cela ne va pas mener a grand chose puisque m^3 < n. Donc Bob complete son message avec des 1 (padding fixe). En python, cela ressemblerait a:



La problematique est la suivante: connaissant n et e de la cle RSA (parametres publiques), et le message chiffre m', peut-on recouvrer la cle AES?

Et bien cela tombe pile poil dans le domaine couvert par l'attaque de Coppersmith (sugere par @kyprizel) avec f(x)=(2^1023-2^128+x)^3-mprime. Maintenant le probleme est de trouver une implementation de Lenstra–Lenstra–Lovász, ou de le reimplementer soi-meme. La solution se trouve dans la bibliotheque Sage, et le Sage Notebook disponible en ligne si vous ne voulez pas installer les 400MB sous Linux, ou la VM VirtualBox sous Windows - et probablement ailleurs mais je me suis arrete a ce qui marchait.

Avec les quelques lignes de code suivantes sous Sage Notebook vous recupererez les 128 bits de la cle instantanement:



C'est moche pour Bob, il avait fait un effort ce coup-ci :(