Débbuger un exécutable Mach-o en contournant les protections ptrace

Date de publication : — dernière modification :

ptrace est un appel système présent sur moults systèmes UNIX et compatibles UNIX. La version XNU de ptrace inclut une requête nommée PT_DENY_ATTACH qui permet d'empêcher qu'on s'attache à un exécutable.

De nombreux développeurs ont commencé à intégrer ce genre de protection dans leurs logiciels. Pourtant, cette "protection" se contourne aisément dans LLDB, et c'est l'objet de cet article.

Anatomie du syscall ptrace

La fonction ptrace() a le prototype suivant :

int ptrace(int request, pid_t pid, caddr_t addr, int data);

Le premier paramètre, request, peut contenir plusieurs valeurs, mais une nous intéresse. PT_DENY_ATTACH permet d'empêcher qu'un debugger s'attache au processus.

Pour comprendre mieux ce qui se passe quand nous utilisons PT_DENY_ATTACH, il faut creuser dans le fichier mach_process.c du noyau XNU. C'est dans ce fichier qu'est défini la fonction ptrace, et en particulier le comportement de la fonction quand on utilise cet argument particulier :

if (uap->req == PT_DENY_ATTACH) {
    proc_lock(p);
    if (ISSET(p->p_lflag, P_LTRACED)) {
        proc_unlock(p);
        KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_PROC, BSD_PROC_FRCEXIT) | DBG_FUNC_NONE,
                      p->p_pid, W_EXITCODE(ENOTSUP, 0), 4, 0, 0);
        exit1(p, W_EXITCODE(ENOTSUP, 0), retval);
        /* drop funnel before we return */
        thread_exception_return();
        /* NOTREACHED */
    }
    SET(p->p_lflag, P_LNOATTACH);
    proc_unlock(p);

    return(0);
}

On peut y voir que ptrace() va stopper le programme avec ENOTSUP comme code d'erreur, ce qui d'après le fichier errno.h correspond à la valeur 45 :

#define ENOTSUP     45      /* Operation not supported */

Contourner la protection avec LLDB

Pour cet article, nous considérerons le programme suivant :

#include <stdio.h>
#include <sys/types.h>
#include <sys/ptrace.h>

int main(int argc, char *argv[]) {
    printf("Meh.\n");
    ptrace(PT_DENY_ATTACH, 0, 0, 0);
    printf("Ok.\n");
    return 0;
}

Je l'ai compilé à l'aide de clang avec la commande suivante :

clang ptrace-dbg.c -o ptrace-dbg

Ce programme affiche quand il est exécuté normalement (sans debugger) "Meh." puis "OK." :

L'exécution normale du programme

Et comme prévu, à cause de ptrace, quand on lance le programme via LLDB, au moment où nous appellons ptrace dans le programme :

L'exécution du programme dans LLDB

On recommence alors notre aventure dans LLDB. Avant de lancer un petit run, on va faire un breakpoint sur la fonction ptrace() en tapant b ptrace :

Création du breakpoint sur ptrace

On va lancer notre programme avec run, qui va immédiatement se bloquer au breakpoint :

Le programme stoppé au breakpoint

On va lire ensuite nos registres avec register read :

Les registres au breakpoint

Vous remarquerez que rdi a pour valeur 0x1F, ce qui correspond en décimal au nombre 31. Or 31, c'est la valeur de PT_DENY_ATTACH. Il faut donc modifier la valeur de rdi par un petit register write rdi 0. On peut alors continuer l'exécution de notre programme avec continue :

Le registre rdi modifié permet l'exécution du programme

Contourner la protection avec otool et un éditeur hexadécimal

otool est un outil extrêmement puissant pour inspecter des exécutables Mach-O. Nous allons l'utiliser ici pour connaître les opcodes utilisés pour faire l'appel à ptrace(). Pour extraire les opcodes avec leur équivalent en assembleur, on va exécuter la commande otool -tVj ./ptrace-dbg. Cependant nous allons grep "ptrace" pour limiter le résultat de la commande otool à ce qui nous intéresse vraiment :

otool

On identifie sur la seconde colomne les opcodes incriminés. Maintenant, à l'aide d'un éditeur hexadécimal (j'utilise Hex Fiend), nous allons chercher ces opcodes, et les remplacer par 0x90, ce qui correspond à l'instruction NOP sur l'architecture x86_64 :

Hex Fiend

Une fois la modification faite, nous pouvons lancer notre programme dans LLDB sans devoir faire la manipulation abordée antérieurement :

Patching successfull !

Bibliographie