Mysterie's blog

Aller au contenu | Aller au menu | Aller à la recherche

lundi, juillet 12 2010

Format string automatique

Cet été j'ai décidé d'apprendre le python, et pour que se soit fun je me suis mis en tête de faire un script qui automatise les format strings.

En plus c'est la mode:
http://esec.fr.sogeti.com/blog/index.php?2010/07/09/88-exploitation-de-format-string-avec-metasm
http://sh4ka.fr/codes/fmt_string_builder.html
ou pas:
http://www.ouah.org/REMOTEFMT-HOWTO
http://nibbles.tuxfamily.org/?p=887
http://www.redspin.com/blog/2009/11/25/automatic-format-string-exploitation/

Le script se décline en 3 fichiers:
main.py
warper.py (détecte ou se situe les arguments faillibles du programme et où placer le payload)
frmtStr.py (exploite la format string)

La format string est exploitable dans la cas où:

  • On est sur une archi x86 (32bit), sous linux
  • La pile est exécutable
  • Pas d'aslr (Quoi qu'avec un gros padding/nopsleed ca passe)
  • La section dynamic est +W
  • Le programme vulnérable n'est pas trop verbeux (ca peut fausser la détection)

En bref sur un wargame c'est parfait (ca permet de gagner du temps), sur un programme réel ca l'est moins.

Je vous conseille de regarder la vidéo en fullscreen (pas super lisible).

jeudi, juin 24 2010

Write Up – NDH 2010 – VM Windows

Pour les personnes n'ayant pas participé au capture the flag ou pour ceux qui n'ont pas exploité les services présent sur la machine Windows. Je mets à disposition les sources des épreuves que j'ai réalisé, et une solution pour chacunes d'elles.

Premier point d'entrée, utilisateur toto mot de passe azerty.

Deuxième point d'entrée, un remote buffer overflow avec réécriture du SEH. Sauf que sur la VM le binaire était full safeSEH, alors que je l'ai testé/exploité sur ma box, j'ai du merder à la compilation bref désolé pour ceux qui ont passé du temps dessus.

Troisième point d'entrée, sur le bureau de chaque challenger se trouvait un pdf. Le lecteur par défaut était adobe reader 8.0. Le pdf en question était crafté de façon à exploiter la faille cve-2009-0927.
J'ai ensuite utilisé un shellcode windows trouvé sur le net et retravaillé pour télécharger et exécuter un malware se trouvant à l'url http://192.168.3.200/tmp.exe
Étant donné les quelques problèmes de mise en place du ctf, l'url n'as pas été disponible tout de suite. Quatre équipes se sont infectées mais il est possible que bien plus d'équipes aient ouvert le pdf.

Le shellcode modifié :
source ici

Le pseudo botnet :
source ici

Le botnet réécrit l'IAT d'explorer.exe et de tous les processus lancés par explorer. Afin de se cacher (hook de findFirstFile / findNextFile) et se connecte à irc.
Malgré le fait que le pdf soit sur le bureau et que quatre équipes soit infectées (dont l'équipe gagnante) personne n'a creusé, et personne n'a rejoint irc pour essayer de prendre le contrôle du bot, donc j'ai fais mumuse tout seul:
screen1
screen2
screen3

Viens ensuite quelques épreuves plus triviales comme un serveur java qui sérialise l'objet qu'il reçoit. Cet objet lit un fichier contenant de l'ascii art et renvoie son contenu sur le réseaux, le but étant de modifier l'objet afin d'afficher un fichier contenant le hash de l'utilisateur ascii sur chaque machine.
source ici
Il y avait aussi deux épreuves web mais je n'en suis pas l'auteur donc je n'en parlerai pas. :)

Et enfin un "Local Privilege Escalation", le binaire checksum.exe communique avec un driver. Il envoie le contenu du fichier qu'on lui donne au driver, celui-ci fait un checksum des données et les renvoie au programme. La communication entre eux se fait par la méthode METHOD_NEITHER.
Les challengers ont accès en lecture à checksum.sys. Il pourront remarquer que le driver contient des failles dont un déréférencement de pointeur (Adresse du buffer d'output non vérifié). Les symbols de windows 2003 sp2 était disponible en ftp. Je ne mettrai pas à disposition la source de checksum.exe car il est très crade:
sources ici
Une petite difficultée était présente, comme le font pas mal d'antivirus le code ioctl de gestion du checksum était aléatoire. En analysant le driver ou l'exécutable on retrouvais facilement le code. Le but était de freiner les challengers en cas d'exploitation massive, mais personne n'a exploité cette épreuve. :(

That's all folks !

mercredi, juin 23 2010

Write Up – NDH 2010 – Crackme

Le 19 et 20 Juin a eu lieu la 8eme édition de la nuit du hack. Pour cette occasion j'ai réalisé différentes épreuves dont des crackmes accessibles aux personnes participant au challenge public (par le wifi) et aux participants du Capture the flag.
Dans la section Crackme j'ai réalisé les niveaux 1, 3 et 4.

Crackme Niveau 1:

Le crackme n'a aucun mécanisme d'anti-debug. Ce qui lui est donné en entrée est comparé directement à une chaine en mémoire. Rien de bien compliqué en soit:

GetWindowText(hEdit, text, 255);
theSecret[0]++;
theSecret[2]++;
theSecret[4]++;
if(strlen(text) > 0 && !strcmp(text, theSecret)) {
    ShowWindow(hVrai, SW_SHOW);

    strcpy(text, getMD5(text));
    SetWindowText(hMd5, text);
    ShowWindow(hMd5, SW_SHOW);
    ShowWindow(hFaux, SW_HIDE);
} else {
    ShowWindow(hVrai, SW_HIDE);
    ShowWindow(hMd5, SW_HIDE);
    ShowWindow(hFaux, SW_SHOW);
}
theSecret[0]--;
theSecret[2]--;
theSecret[4]--;

La source du crackme:
cm1/

Crackme Niveau 3:

Le mot de passe du programme n'est pas stocké dans sa mémoire ou dans le fichier exécutable. Au chargement il crée 8 plages mémoires contenant des op code de saut assembleur (5 octets). Chaque plage contient 256 sauts, chaque saut équivaut à un caractère ascii. Dans chacune des plages un seul saut est valide et pointe vers une routine de décryptage les autres sauts pointent indirectement vers une routine qui affiche mot de passe faux.
J'utilise aussi l'instruction rdtsc pour calculer le temps effectué entre le première saut et le dernier afin de vérifier que le programme s'exécute en moins d'une seconde sinon c'est que le programme est débuggé et donc j'affiche mot de passe faux dans tous les cas.
La source du crackme:
cm2/

Crackme Niveau 4:

Ce niveau est destiné à des personnes ayant l'habitude de ce genre d'épreuve la difficulté principale de ce challenge est donc de débugger le programme (l'analyser). J'ai mi beaucoup de routine anti-débug. Tout d'abord à l'entrée du programme j'ai incrusté des instructions assembleur pour modifier la signature de l'exécutable (ces instructions ne sont jamais exécutées) le but est de faire croire que l'exécutable est packé c'est à dire qu'il est compressé par un autre logiciel.
Ensuite j'appel une fonction spéciale un peu partout dans le code de mon programme. (junk code)

void brack() {
    __asm {
        _emit 0xEB;  // 0xEB01 = jmp short relatif saute au dessus
        _emit 0x01;  de l'octet 0x1D. Donc ne fait rien au final
        _emit 0x1D;  // 0x1D fait croire au debugger que l'instruction qui suit est SBB
    }
}

Le programme crée ensuite un thread. La fonction du thread est la suivante:

while(1) {
    brack();
    Sleep(1000);
    __try {
        __asm {
            int 3;
        }
    }
    __except(eps = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) {
        brack();
        isDbg = false;
    }
    if(isDbg) {
        quit();
        break;
    }
    isDbg = true;
}

Dans le cas où un débugger serait présent au démarrage du programme ou s'il s'attache au programme durant son exécution, l'instruction int 3 (point d'arrêt) est exécutée le débugger la récupère. Le programme détecte ainsi qu'un débugger est présent et le mot de passe n'est jamais révélé.

La fonction IsDebuggerPresent est aussi appelée dans le programme. Elle est appelée indirectement (récupération de l'adresse de la fonction dynamique).

GetProcAddress(GetModuleHandle("Kernel32.dll"), "IsDebuggerPresent");

Et enfin j'utilise l'instruction rdtsc dans le programme pour vérifier que le temps d'exécution de celui ci n'est pas trop long.

Si le challenger contourne toute ces protections alors le programme alloue une plage mémoire, écrit à l'emplacement de cette plage des octets spécifiques (fonction de déchiffrement encodé), puis la décode avec un XOR. Lorsque l'utilisateur entre son mot de passe la fonction déchiffrée effectue plusieurs opérations aritmétriques sur le mot de passe:

  • Longueur de la chaine
  • Est-ce que la deuxième lettre est égale à la 5eme
  • OU / ET logique, XOR etc...

Par un calcul mathématique ou un peu de logique on retrouve assez simplement le passe. Il n'y a aucune collision une seule solution est valide.
La source du crackme:
cm3/

Au niveau des résultats sur 180 participants (public/ctf):

  • Level1 10 validations
  • Level3 5 validations
  • Level4 3 validations

Des solutions sont déjà disponibles sur le net:
http://w4kfu.free.fr/blog/index.php?post/2010/06/20/Write-up-NDH-2010-crackme-lvl-1
http://w4kfu.free.fr/blog/index.php?post/2010/06/20/Write-up-NDH-2010-crackme-lvl-2
http://w4kfu.free.fr/blog/index.php?post/2010/06/20/Write-up-NDH-2010-crackme-lvl-3

Et si vous êtes féru de crackme ce week end Eset NOD32 organise un challenge.

dimanche, juin 6 2010

Return Oriented Programming

Ce week-end se tenait à Maubeuge les rssil (Rencontres des Solutions de Sécurité et d'Informatique Libre).
Je vous encourage à y aller l'an prochain. Il y avait une très bonne ambiance, des conf intéressantes et j'ai pu y rencontrer pas mal de monde. Je tiens à remercier tout particulièrement les organisateurs de l'évènement pour leur accueil et leur organisation.

Pour ceux qui veulent récupérer mes slides ou tout simplement ceux qui n'ont pas pu assister à la conférence:
rop.pdf
rop.odp
rop.ppt

Et pour ceux qui sont intéressés par le sujet je vous conseil aussi la lecture de ce papier réalisé par Dino dai zovi il y a un mois.

Pour les autres conférences, les slides devraient être eux aussi en ligne d'ici peu. Ainsi que les épreuves du challenge ethical hacking (en tout cas les résolus).

dimanche, janvier 24 2010

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 = 0x674;

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:
+0x18 pour Xp sp3
+0x28 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/

- page 1 de 3