« Posts under Windows

CVE-2011-1281 Privilege escalation in CSRSS proof of concept

After one year without blogging (all my apologies) I’m back. A few days ago I’ve seen the pwnie awards nominations list, there were lot’s of interesting and sophisticated bug exploitation. But one attract my attention « Privilege escalation in CSRSS » discovered by Matthew ‘j00ru’ Jurczyk. If you want to undestand this vulnerability and the way to exploit it, read this excellent post http://j00ru.vexillium.org/?p=893. And if you’re not familiar with CSRSS I advice you to read this article or this one (in french).

So, for writing the PoC we have to follow this steps :

  1. Spray the shared WIN32K section, by creating a sufficient amount of USER objects. The section is then going to be mapped to every process running in the context of the local desktop, thus we can perform this step at this early point,
  2. Create N instances of a process, each of which will create a single zombie console and then go idle, (*)
  3. Kill all N instanes of the processes,
  4. Create 3N local threads, (**)
  5. Kill 2N threads (in the order described in the “Second Stage” section),
  6. Kill the remaining N threads,
  7. Emulate the win+u key presses, resulting in a new instance of UTILMAN.EXE being created,
  8. Call SendMessage(HWND_BROADCAST,WM_SYSCOMMAND,0xFFF7,0), triggering the execution of CreateRemoteThread on each of the N freed handles.

* – by creating a zombie console, we also mean replacing the original PropertiesProc address (used in kernel32!AllocConsole) with a custom pointer.
** – the technique is very time-sensitive. If any handle is picked / stored on the free-list between steps 3 and 4, than steps 5 and 6 might not succeed in setting up the expected free-list handle layout.

I wont speak about first step immediately for different reason.
Let’s start with step two « create a single zombie console », for me it’s the most hard part. We have to code AllocConsole and AllocConsoleInternal (I only scope Windows XP version for the moment). With AllocConsoleInternal we can control the PropRoutine & CtrlRoutine of the console. For conding this function I start googling with « AllocConsoleInternal + PropRoutine + CtrlRoutine » and reach this function definition :

BOOL APIENTRY AllocConsoleInternal(
 IN LPWSTR lpTitle,
 IN DWORD dwTitleLength,
 IN LPWSTR lpDesktop,
 IN DWORD dwDesktopLength,
 IN LPWSTR lpCurDir,
 IN DWORD dwCurDirLength,
 IN LPWSTR lpAppName,
 IN DWORD dwAppNameLength,
 IN LPTHREAD_START_ROUTINE CtrlRoutine,
 IN LPTHREAD_START_ROUTINE PropRoutine,
 IN OUT PCONSOLE_INFO pConsoleInfo)

With some call to ntdll!CsrAllocateCaptureBuffer, ntdll!CsrCaptureMessageBuffer for desktop, title and curent dir memory allocation. And then ntdll!CsrClientCallServer with allocConsole request we will reach winsrv!SrvAllocConsole and then spawn a console. For testing we lunch a broken console and kill his process and his parent process, after that we do a « right clic + proprieties/default » on the broken console and then we have a winsrv!InternalCreateCallbackThread executed with free handle! (the killed parent process handle precisely)

Step 3, 4, 5, 6, 8 is quite easy. Step 7 (WIN+U emulation) too but SetKeyboardState and PostMessage doesn’t work, we have to use keybd_event (depreciated) or SendInput to invoke utilman.exe.

Therefore, with all this steps we are able to get CSRSS to call CreateRemoteThread with a system process handle and a controlled start address. Now we need step one « Spray the shared WIN32K section of system process with USER object » and it’s done! For this we have to invoke ultiman (WIN+U) which spawn three new process :

-> ultiman.exe [NT AUTHORITY\SYSTEM]
+-> ultiman.exe /start [USER]
+–> narrator.exe /UM [USER]

Then we create user object like MessageBox with over long title (32Ko). But ultiman (system) doesn’t share the win32k section with other process at all times. After trying differents unsuccessful methods, I decided contact j00ru who give me the solution. We can inject user object in ultiman (system) if another user (regardless of his privileges) is logged on the machine at the same time. At this moment, I haven’t found explanation of this behaviour. I think it’s something in relation with Desktop/Winstation/Session, if you have some idea tell me.

Source of the poc :
http://mysterie.fr/prog/blog/allocConsole/

Thanks to j00ru for his help and all shared knowledge on his blog, hitb and so on!

VDM + #GP Trap Handler + IRET = CVE-2010-0232 (MS10-015)

Je profite des vacances pour jeter un oeil sur la faille trouvée par Tavis Ormandy dans le système de gestion du mode virtuel de Windows. Faille présente depuis Windows NT 3.1 (1993).

Pour pouvoir utiliser des exécutables 16bits(mode réel) dans un environnement 32bits(mode protégé) il faut passer par le mode virtuel. Pour cela Windows a crée un sous-système appelé « NTVDM » qui va interagir avec le binaire 16bits. Offrant à celui-ci la possibilité d’effectuer des instructions tels que CLI, STI, PUSHF, POPF, IRET… Le passage en V86 se fait par le biais de la fonction NtVdmControl().
Il existe déjà un article sur le V86 sous Windows ici. Passons à ce qui nous intéresse, l’exploitation de la faille se fait en 3 étapes.

1] Utiliser NtVdmControl requière le SeTcbPrivilege.

La solution est basique, il suffit d’invoquer un exécutable 16bits. Alors ntvdm.exe va être lancé à son tour pour servir d’interface entre le binaire 16bit et le système d’exploitation. On injecte une dll dans ntvdm.exe et le code qu’elle exécutera bénéficiera du SeTcbPrivilege car les contrôles pour ce token se font au niveau du processus. (EPROCESS->Flags.VdmAllowed).

Avant d’appeler la fonction NtVdmControl il faut donner au champ Vdm du TEB l’adresse d’une structure de type VDM_TIB:

// Thread Information Block for VDM Threads
// http://doxygen.reactos.org/dd/de7/vdm_8h_source.html
typedef struct _Vdm_Tib {
  ULONG Size; // Va être utile par la suite
  PVDM_INTERRUPTHANDLER VdmInterruptTable;
  PVDM_FAULTHANDLER VdmFaultTable;
  CONTEXT MonitorContext;
  CONTEXT VdmContext; // Va être utile par la suite
  VDMEVENTINFO EventInfo;
  VDM_PRINTER_INFO PrinterInfo;
  ULONG TempArea1[2];
  ULONG TempArea2[2];
  VDMTRACEINFO TraceInfo;
  ULONG IntelMSW;
  LONG NumTasks;
  PFAMILY_TABLE *pDpmFamTbls;
  BOOLEAN ContinueExecution;
} VdmTib = {0};

*NtCurrentTeb()->Reserved4 = &VdmTib; // TEB->Vdm

Ensuite pour initialiser la VDM il faut au préalable mapper le premier MB de mémoire virtuelle dans le processus ntvdm.exe. On peut maintenant appeler la fonction VdmpInitialize (via NtVdmControl(3)) Le binaire 16bit pourra donc adresser les 1MB de RAM grâce à la segmentation (cs << 4) + (eip & 0xffff).

Maintenant qu’on a initialisé la VDM on va pouvoir jouer avec, pour cela on va s’intéresser à nt!VdmpStartExecution. Cette fonction est invoquée via NtVdmControl(0, NULL). nt!NtVdmControl va d’abord appeler nt!VdmpGetVdmTib pour vérifier que notre VdmTib (TEB->Vdm) a une taille conforme. Tavis a utilisé une boucle qui incrémente la taille du champ TEB->Vdm->Size afin de trouver la bonne taille. (Et pour que l’exploit passe sur la plupart des versions de Windows). Sur Xp la taille est hardcoder.

nt!VdmpGetVdmTib+0x50:
805b7aad cmp dword ptr [eax],674h

Pourquoi cette valeur, c’est la taille de la structure VdmTib (Elle change suivant les versions de Windows). Avant de lancer l’exécution de la VDM je mets le champ VdmTib.Size = 0×674;

2] Modification du registre CS en ring3

En mode protégé le Cpl (Current Privilege Level) d’un thread est indiqué par les deux bits de poids faible de ces segments CS, DS, ES, FS, GS et SS. C’est une manière simple de savoir s’il doit tourner en ring0(kernel) ou ring3(user). Mais en mode réel cela n’existe pas, l’adressage se fait de la façon suivante: (cs<<4) + (eip&0xffff) (segment+offset). Idem pour le mode virtuel qui nous laisse modifier le registre CS.

3] Une « trap frame » ne peut être forgée en ring3

La fonction nt!VdmpStartExecution va faire passer notre thread en mode virtuel pour cela elle va faire appelle à la fonction nt!VdmSwapContexts. Celle-ci va mettre à jour la trap frame crée par KiFastCallEntry avec notre VdmTib.VdmContext.
La trap frame contient l’état de tout les registres du thread avant le passage en ring0(kernel). Voici ce qui va se passer:

// J'ai simplifié au maximum
// http://doxygen.reactos.org/d9/d2a/vdmexec_8c_a76568763a2d5e9d5f49cdc36256148b0.html#a76568763a2d5e9d5f49cdc36256148b0
nt!VdmSwapContexts(PKTRAP_FRAME TrapFrame, PCONTEXT MonitorContext, PCONTEXT VdmContext) {
  TrapFrame->SegCs = VdmContext->SegCs;
  TrapFrame->HardwareSegSs = VdmContext->SegSs;
  TrapFrame->Eax = VdmContext->Eax;
  TrapFrame->Ebx = VdmContext->Ebx;
  TrapFrame->Ecx = VdmContext->Ecx;
  TrapFrame->Edx = VdmContext->Edx;
  TrapFrame->Esi = VdmContext->Esi;
  TrapFrame->Edi = VdmContext->Edi;
  TrapFrame->Ebp = VdmContext->Ebp;
  TrapFrame->HardwareEsp = VdmContext->Esp;
  TrapFrame->Eip = VdmContext->Eip;

  TrapFrame->SegCs |= RPL_MASK;
  TrapFrame->HardwareSegSs |= RPL_MASK;
  /* Check for bogus CS */
  if (TrapFrame->SegCs < KGDT_R0_CODE) {
    /* Set user-mode */
    TrapFrame->SegCs = KGDT_R3_CODE | RPL_MASK;
  }
}

Donc on contrôle notre trap frame. A la fin de la fonction nt!VdmpStartExecution l’instruction IRET fait basculer notre thread vers le ring3(user) et restaure ses registres avec ceux de la trap frame.
Le registre qui nous intéresse le plus est l’EFLAG qui contient TF=1 et VM=1. Le VM=1 va créer une task en mode virtuel, le TF=1 quand à lui sort de nul part:

VdmTib.VdmContext.EFlags    = EFLAGS_TF_MASK;

En effet le handler de #DB est la fonction KiTrap01 mais c’est la KiTrap0D qui est appelée. On a donc affaire à une exception de type #GP!
D’après Rob Collins, le comportement de l’instruction IRET avec TF=1 provoque un #GP mais les pentiums les plus récent ne provoque plus de #GP.

Bref une exception de type #GP est levée, on est encore en ring0 donc il n’y a pas de changement de privilège, la pile n’est pas changée et notre trap frame reste la même. On passe donc par le handler KiTrap0D. Celui-ci va effectuer des tests dont:

nt!KiTrap0D+0x22f:
804e116e mov eax,offset nt!Ki386BiosCallReturnAddress (805093fd)
804e1173 cmp eax,dword ptr [edx] ; edx = KTRAP_FRAME.HardwareEsp (VdmContext->eip)
...
804e1177 mov eax,dword ptr [edx+4] ; edx+4 = KTRAP_FRAME.HardwareSegSs (VdmContext->SegCs)
804e117a cmp ax,0Bh

Donc en faisant pointer VdmContext->eip sur l’adresse de Ki386BiosCallReturnAddress (qui a déclenché kitrap0d), et VdmContext->SegCs sur 0xB on est dirigé vers la fonction Ki386BiosCallReturnAddress. Dans le cas contraire KiTrap0D nous renvoie un « Access violation ».

// Pour simplifier au max la fonction Ki386BiosCallReturnAddress va faire:
80509415 mov esp,dword ptr [ebp+58h] ; esp = &KernelStack (VdmContext.Esi notre fausse pile)
80509418 add esp,4 ; esp += 4
...
80509421 mov dword ptr [ecx+18h],edi ; (KTHREAD)CurrentThread->InitialStack = &KernelStack+0x230

Donc on contrôle ESP et ces 8 premiers DWORD (Le reste est écrasé). C’est assez pour écraser le saved EIP et rediriger le flux d’exécution vers une fonction de notre dll. Par contre on peut voir aussi qu’avant d’arriver dans notre dll, Ki386BiosCallReturnAddress fou le bordel dans notre KTHREAD. Donc dès qu’on retourne dans notre dll on profite d’être en ring0 (cpl à 0 pour le registre cs) pour bloquer les interruptions et réparer notre KTHREAD.
Tavis scan les 512 premier DWORD de cette structure, j’ai pas trop capté l’utilité, surement car cette structure change en fonction des versions de windows. En tout cas il y a plus simple (je peux me tromper) la structure KTHREAD est modifiée au niveau du champ InitialStack.

offset du champ InitialStack dans la structure KTHREAD:
+0×18 pour Xp sp3
+0×28 pour Vista et Win Seven

Cela fait 2 valeurs à scanner, enfin une en utilisant GetVersionEx(). Ensuite on lui remet sa valeur par défaut. Dans le cas contraire au moment du changement de thread (nt!SwapContext) on se mange un joli bsod.

Le PoC de tavis est , le code est super clean cela vaut le coup d’oeil.
EDIT: Merci à ivanlef0u pour la relecture/correction :]

http://seclists.org/fulldisclosure/2010/Jan/341
http://cert.lexsi.com/weblog/index.php/2010/01/20/359-windows-de-nouveau-impacte-par-une-0-day-vdm

Des sites qui m’ont fait gagner du temps:

http://www.reactos.org/fr/index.html
http://msdn.msuiche.net/

Aujourd’hui c’est coloriage.

Je me suis demandé comment fonctionnait l’affichage sous windows (afficher un pixel à l’écran). Quand j’ai fait mes premiers pas en assembleur je me suis amusé à afficher une lettre, c’était sous win98 grâce au mode cga qu’on obtient avec ces instructions INT 10h / AH 6h. Puis j’ai essayé de lancer le même code sous XP et la c’est le drame, rien. Depuis windows xp & co il y a eu pas mal de changement et mon programme ne fonctionne plus. En réalité on ne peut plus lire/écrire directement dans la mémoire physique réservée au mode cga car:

  • Les systèmes d’exploitation actuels utilisent un mécanisme de mémoire virtuelle.
  • L’espace mémoire en question se situe après l’adresse 0x7ffeffff (Ring 0).

Je me suis mis à la recherche de la mémoire vidéo, le mode cga n’étant plus utilisé depuis longtemps. Une des façons de procéder est d’écrire un driver (pour accéder au noyau) et d’envoyer des requêtes au bus pci pour trouver la carte graphique et ses caractéristiques. Je me suis basé sur un article de rAsM à lire absolument si vous voulez comprendre le code. J’ai ajouté des KeAcquireSpinLock et KeReleaseSpinLock histoire de ne pas avoir de surprise avec les READ/WRITE_PORT.

Le code du driver, Le résultat:

Base address #0 (BAR0) // domaines de port I/O
Physical [0x1401 - 0x1411]
Base address #1 (BAR1) // 1er partie de la mémoire vidéo
Physical [0xf0000000 - 0xf8000000]
Base address #2 (BAR2) // 2eme partie de la mémoire vidéo
Physical [0xec000000 - 0xec800000]

Bien sûr se sont des adresses physique, on peu ensuite utiliser la fonction MmMapIoSpace pour y accéder mais sa reviendrai à faire une copie de la mémoire physique vers la mémoire virtuelle et on se retrouve avec DEUX buffer vidéo en ram :/.
Alors on vas essayer de retrouver le premier qui est déjà en mémoire. J’ai donc fait un programme en C qui affiche un pixel rose en haut à gauche de l’écran.

//Rien de complexe pour le programme.
HDC hScreenDC;
hScreenDC = GetDC(0); // screen
DWORD color = 0x00FF00FF;  // rose :}
SetPixel(hScreenDC, 0, 0, color);
ReleaseDC(0, hScreenDC);

Et ensuite je débugge tout ça, donc dans notre programme on commence par un appel a la fonction SetPixel qui se trouve dans gdi32.dll. La GDI permet de faire le lien entre les applications et les pilotes graphiques. et à la fin de gdi32!SetPixel on as un appel à une petite routine assez spécifique:

mov     eax, 111Ah
mov     edx, 7FFE0300h
call    dword ptr [edx] ; ntdll!KiFastSystemCall

Pour ceux qui n’ont pas compris la subtilité de ses quelques instructions, en fait on a un appel à KiFastSystemCall() avec comme argument 0x111A. Puis vient le SYSENTER et on entre dans le noyau (pas directement bien sûr), la fonction KiFastCallEntry() récupère notre argument puis on passe par la fonction KiSystemService() qui va se charger de trouver la fonction équivalente a 0x111A. Ce numéro ne représente pas vraiment une fonction, je pense qu’il y a un 0×1000 AND 0x111A, pour indiquer si on a affaire à la SSDT ou à la SSDT Shadow.

0: kd> dd nt!KeServiceDescriptorTableShadow l 0x8
805614c0    804e48b0   00000000    0000011c    80517fc4
805614d0    bf999c00   00000000    0000029b    bf99a910

On voit bien que le nombre de fonction dans la SSDT Shadow est de 0x29b, donc 0x111A c’est trop grand. Par contre après avoir viré ce 0×1000 on se retrouve avec le numéro 0x11A et là ça le fait.

0: kd> dd bf999c00 l 0x11b
...
bf99a060    bf949a50    bf841b72    bf8751a3
0: kd> ln bf8751a3
(bf8751a3)   win32k!NtGdiSetPixel

Le nom de la fonction est assez explicite, elle fait appel a de moult autre fonctions dans win32k.sys. je trace un peu tout ça pour en ressortir les fonctions qui nous intéressent.

// arbre d'appel
win32k!NtGdiSetPixel
 -> win32k!SURFACE::pfnBitBlt
  -> win32k!SpBitBlt
   -> win32k!OffBitBlt
    -> win32k!CLIPOBJ_vOffset
     -> win32k!EngBitBlt
      -> win32k!PDEVOBJ::vSync
       -> win32k!vDIBSolidBlt
        -> win32k!vSolidFillRect1
// code qui rox
mov eax,dword ptr [ebp+18h] ; eax=00ff00ff code de la couleur
mov dword ptr [edi],eax ; edi=f9234000 adresse pointant dans la mémoire vidéo.

Je vérifie que cette adresse corresponde bien à l’adresse physique 0xf0000000.

0: kd> !vtop 0 f9234000
X86VtoP: Virt f9234000, pagedir d4a6000
X86VtoP: PDE d4a6f90 - 01014163
X86VtoP: PTE 10148d0 - f000017b
X86VtoP: Mapped phys f0000000
Virtual address f9234000 translates to physical address f0000000.

Woot! Je ne sais pas comment procède windows pour retrouver l’adresse 0xf9234000 (si quelqu’un peu m’éclairer). Par contre je m’aperçois qu’après f9234000+1D4C00 (1D4C00 = 800*600*4 la résolution de l’écran) la mémoire est à zéro, de 0xf9408c00 à 0xf9434000 se qui fait 177ko de libre. Vu la place je pense à la technique du Shadow Walker, le principe serai de faire croire au système que les pages non utilisées de la mémoire vidéo sont swappées. On pourrai y cacher du code ou des datas. Mais cette technique commence à dater et nécessite un hook dans l’idt ce qui peut être détectable. Et dans le cas où l’utilisateur changerai sa résolution d’écran il y aurai surement un conflit, dommage…

UA-7839874-2