Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Hacking et Forensic
  3. Débogage sous Windows
Extrait - Hacking et Forensic Développez vos propres outils en Python (2ième édition)
Extraits du livre
Hacking et Forensic Développez vos propres outils en Python (2ième édition)
1 avis
Revenir à la page d'achat du livre

Débogage sous Windows

Introduction

Dans ce chapitre, nous allons utiliser PyDbg inspiré du livre Gray Hat Python de Justin Seitz, chapitre 4. Nous sommes dans la limite du Hacking et du Forensic. PyDbg va nous aider à pister les données dans les programmes, à débugger mais aussi à effectuer du fuzzing, du hook d’application...

Nous travaillerons donc ici sous Windows et pour ma part, sous Linux, j’utiliserai une machine virtuelle VirtualBox pour écrire et tester les exercices et exemples de ce chapitre.

Une connaissance préalable des systèmes Windows, de l’assembleur et bien sûr de Python est requise.

Nous devons savoir associer le debugger à un processus soit en ouvrant l’exécutable et en le lançant ensuite, soit en l’attachant (l’exécutable est déjà lancé).

Nous allons attacher le processus quand cela nous permettra de ne pas prendre en compte le démarrage de l’application et de nous focaliser sur une partie du code spécifique.

Lorsque nous ouvrirons le programme dans le debugger, nous contrôlerons le processus dès le démarrage, nous pourrons donc voir ce qui est chargé par exemple, ce qui est très utile lors d’analyse de malwares ou virus.

Le module ctypes de Python

Le module ctypes permet d’interagir avec des librairies écrites en langage C.

Nous utiliserons dans la suite le module ctypes pour notre debugger.

Le module ctypes fournit des méthodes pour charger des librairies écrites en C et appeler les fonctions de ces librairies, des types de données compatibles avec le langage C pour passer et récupérer des variables avec ces fonctions.

Le module ctypes est disponible à partir de Python 2.5 et supérieur.

Le module ctypes exporte un objet cdll qui permet de charger une librairie. Dans l’environnement Windows, nous disposons en plus des objets windll et oledll.

L’objet cdll fournit une méthode LoadLibrary() qui permet de charger une librairie dynamique.


From ctypes import * 
cdll.LoadLibrary("libc6.so.6") # Linux 
print windll.kernel32 # windows 
print cdll.msvcrt # windows 
libc=cdll.msvcrt
 

Une fois que nous avons chargé une librairie via la méthode LoadLibrary(), nous l’affectons à un objet qui va permettre d’appeler les fonctions définies par la librairie.

Cet objet agit en wrapper en permettant d’accéder aux fonctions de la librairie en passant par des pointeurs.


print windll.kernel32.GetModuleHandleA # Affiche <_FuncPtr object 
at 0x...> 
print hex(windll.kernel32.GetModuleHandleA(None)) # appel de la 
fonction GetModule HandleA de la windll 
 

Le module...

Première approche

Comment créer un processus sous Windows dans un debugger ?

La fonction s’appelle CreateProcessA(), à laquelle nous pouvons transmettre des paramètres.

Nous pourrons trouver sur Internet, et en particulier sur le site de MSDN, des détails sur cette fonction si nous le souhaitons.

Nous ne verrons ici que les paramètres à transmettre qui nous seront utiles pour la suite.

Les paramètres utilisés sont :

  • lpApplicationName

  • lpCommandLine

  • dwCreationFlags

  • lpStartupInfo

  • lpProcessInformation

Les autres paramètres pourront être mis à NULL.

Les deux premiers paramètres vont nous permettre de donner le chemin de l’application et les commandes à lui passer (en ligne de commande) si nécessaire.

dwCreationFlags nous servira à indiquer au processus qu’il devra démarrer avec le debugger (en mode debug donc) et les deux derniers paramètres sont des pointeurs sur des structures (STARTUPINFO et PROCESS_INFORMA-TION) qui détermineront la façon dont le processus devra démarrer et nous donnera des informations après le démarrage du processus.

Nous allons donc créer en Python trois programmes, un script qui va définir les deux structures dont nous avons parlé ci-dessus (mes_definitions_debugger.py), un script qui sera le debugger (mon_debugger.py) et un script de test pour vérifier le bon fonctionnement des deux scripts précédents.

mes_definitions_debugger.py


from ctypes import * 
 
WORD = c_ushort 
DWORD = c_ulong 
LPBYTE = POINTER(c_ubyte) 
LPTSTR = POINTER(c_char) 
HANDLE = c_void_p 
 
DEBUG_PROCESS = 0x00000001 
CREATE_NEW_CONSOLE = 0x00000010 
class STARTUPINFO(Structure): 
      _fields_ = [ 
               ("cb", DWORD), 
               ("lpReserved",LPTSTR), 
               ("lpDesktop",LPTSTR), 
               ("lpTitle",LPTSTR), 
               ("dwX",DWORD), 
               ("dwY",DWORD), 
               ("dwXSize",DWORD), ...

État des registres

Un des principaux atouts d’un débogueur est de pouvoir visualiser le contenu des registres. Quand une exception apparaît, nous devons être capables de déterminer l’état de la pile à n’importe quel endroit et à n’importe quel moment. 

Pour ce faire, nous devons donc obtenir les informations du thread courant.

La fonction OpenThread() va nous y aider en lui indiquant les paramètres adéquats. Cette fonction est très semblable à OpenProcess() mais au lieu de lui transmettre le PID, nous devrons lui transmettre le TID (Thread Identifier).

1. Énumération des threads

Nous devons donc être capables d’énumérer tous les threads du processus. Nous avons pour cela à notre disposition la fonction CreateToolhelp32Snapshot() de la DLL kernel32.dll. Nous pourrons donc obtenir par exemple la liste des processus, des threads et les DLL chargés.

Le paramètre dwFlags indiquera à la fonction ce que nous désirons regarder (threads, DLL, processus, heap).

Nous mettrons donc ce paramètre à TH32CS_SNAPTHREAD (valeur 0x00000004) pour voir les threads.

L’autre paramètre, th32ProcessID, est simplement le PID du processus.

Quand la fonction réussit, elle nous retourne le handle de l’objet snapshot.

Une fois que nous avons la liste des threads, nous pouvons...

Les événements du debugger

Nous allons revenir sur la fonction WaitForDebugEvent() qui nous retourne la structure DEBUG_EVENT.

Cette structure contient beaucoup d’information dont dwDebugEventCode qui va particulièrement nous intéresser, elle va nous indiquer quel type d’événement a eu lieu.

En regardant la valeur de dwDebugEventCode, nous pourrons ainsi déterminer l’événement.

Code d’événement

Valeur du code

Valeur de l’union u

0x1

EXCEPTION_DEBUG_EVENT

u.Exception

0x2

CREATE_THREAD_DEBUG_EVENT

u.CreateThread

0x3

CREATE_PROCESS_DEBUG_EVENT

u.CreateProcessInfo

0x4

EXIT_THREAD_DEBUG_EVENT

u.ExitThread

0x5

EXIT_PROCESS_DEBUG_EVENT

u.ExitProcess

0x6

LOAD_DLL_DEBUG_EVENT

u.LoadDll

0x7

UNLOAD_DLL_DEBUG_EVENT

u.UnloadDll

0x8

OUTPUT_DEBUG_STRING_EVENT

u.DebugString

0x9

RIP_EVENT

u.RipInfo

Ajoutons donc maintenant quelques définitions à mon_debugger3.py qui devient donc mon_debugger_final.py

Nous utiliserons mon_debugger_final.py jusqu’à la fin de ce chapitre, des fonctions que nous verrons ultérieurement y ont déjà été placées.

mon_debugger_final.py


from ctypes import * 
from mes_definitions_debugger_final import * 
 
import sys 
import time 
kernel32 = windll.kernel32 
 
class debugger(): 
 
    def __init__(self): 
        self.h_process       =     None 
        self.pid             =     None 
        self.debugger_active =     False 
        self.h_thread        =     None 
        self.context         =     None 
        self.breakpoints     =     {} 
        self.first_breakpoint=     True 
        self.hardware_breakpoints = {} 
         
 
        system_info = SYSTEM_INFO() 
        kernel32.GetSystemInfo(byref(system_info)) 
        self.page_size = system_info.dwPageSize 
         
 
        self.guarded_pages...

Les points d’arrêt (breakpoints)

1. Points d’arrêt logiciel

Pour placer des points d’arrêt, nous devons être capables d’écrire et de lire dans la mémoire.

La fonction ReadProcessMemory() va nous y aider ainsi que WriteProcessMemory().

Grâce à ces deux fonctions, nous allons pouvoir inspecter la mémoire.

Les paramètres à leur fournir sont lpBaseAddress (adresse où nous voulons commencer à lire ou écrire), lpBuffer (pointeur vers la donnée que nous voulons lire ou écrire), nSize (nombre total d’octets que nous voulons lire ou écrire).

En utilisant ces deux fonctions, nous pourrons utiliser aisément dans notre debugger les points d’arrêt.

Généralement, les points d’arrêt sont placés sur un appel de fonction. Pour l’exercice, nous utiliserons l’appel à printf().

Pour déterminer l’adresse virtuelle d’une fonction, nous utiliserons GetProcessAddress() qui sera exporté de kernel32.dll.

Nous aurons besoin de l’en-tête de la fonction sur l’appel de laquelle nous désirons placer le point d’arrêt, GetModuleHandle() nous y aidera.

Vous pouvez aller voir les définitions de read_process_memory(), write_process_memory(), bp_set() et func_resolve() dans mon_debugger_final.py si vous souhaitez plus de détails là-dessus.

Pour nous permettre de tester le script qui va boucler...

La bibliothèque PyDbg

Nous avons vu dans les sections précédentes comment placer des points d’arrêt, mais comment faire avec PyDbg ?

Une fonction bp_set(address, descriptor= ’’ ’’, restore= True,handler=None) va nous y aider.

L’argument address est l’adresse où nous voulons placer le point d’arrêt logiciel.

Le paramètre descriptor est optionnel et peut être utilisé pour donner un nom au point d’arrêt.

Le paramètre restore détermine si le point d’arrêt doit automatiquement être remis à zéro après avoir récupéré l’en-tête et le paramètre handler spécifie quelle fonction appeler.

Toutes les informations du contexte, des threads et processus vont être renseignées par cette classe lors de l’appel à la fonction.

Nous allons utiliser le script boucle_printf.py des sections précédentes et grâce à un nouveau script, nous allons lire les valeurs du compteur et les remplacer par une valeur aléatoire comprise entre 1 et 100.

Nous allons donc observer, lire et manipuler des données du processus cible en temps réel.

printf_random.py


from pydbg import * 
from pydbg.defines import * 
 
import struct 
import random 
 
def printf_randomizer(dbg): 
     
    # Lecture de la valeur du compteur en ESP + 0x8 comme un DWORD 
    parameter_addr = dbg.context.Esp + 0x8 
    counter = dbg.read_process_memory(parameter_addr,4) 
    counter = struct.unpack("L",counter)[0] 
    print "Counter: %d" % int(counter) 
    random_counter = random.randint(1,100) 
    random_counter = struct.pack("L",random_counter)[0] 
    dbg.write_process_memory(parameter_addr,random_counter) 
    return DBG_CONTINUE 
 
dbg = pydbg() 
pid = raw_input("Enter the printf_loop.py PID: ") 
dbg.attach(int(pid)) 
printf_address = dbg.func_resolve("msvcrt","printf") 
dbg.bp_set(printf_address,description="printf_address", 
handler=pri ntf_randomizer) 
dbg.run()
 

Résultat obtenu pour boucle_printf.py


C:\Documents and Settings\fasm\Mes documents\livre_python\ ...

Mise en pratique : Hooking

Énoncé

Prérequis : PyDbg

But : créer un hook d’Internet Explorer.

Énoncé :

Un hook (littéralement crochet ou hameçon) permet à l’utilisateur d’un logiciel de personnaliser le fonctionnement de ce dernier, en lui faisant réaliser des actions supplémentaires à des moments déterminés.

Nous voudrions donc « sniffer » les connexions SSL d’Internet Explorer qui utilise CryptoAPI de Microsoft.

Solution

hook_ssl_pydbg.py


from pydbg import * 
from pydbg.defines import * 
import utils 
import sys 
import re 
import struct 
 
 
def ssl_sniff_encryptmessage( dbg, args ): 
    buffer = "" 
    addr_bufflen=dbg.context.Esp + 0x2C 
    bufflen=struct.unpack('L',dbg.read_process_memory(addr_bufflen , 4))[0] 
    addr=dbg.context.Esp + 0x34 
    addr_buffer = struct.unpack('L',dbg.read_process_memory(addr, 4))[0] 
    try: 
      buffer=dbg.read_process_memory(addr_buffer,bufflen) 
      buffer=re.sub('.*gzip.*\r\n','',buffer) 
      dbg.write_process_memory(addr_buffer, buffer) 
    except: 
      pass 
    print "Requete: \n\n%s"...