Tuesday, September 24, 2013

Porter du code C en Python

Ce billet a pour but de décrire comment porter du code C en Python en utilisant CTYPES. L’intérêt d’utiliser Python et CTYPES et de pouvoir facilement expérimenter et créer du code capable d’interagir directement avec le système d’exploitation, ou si on souhaite écrire du code bas niveau portable sur plusieurs architecture, ce qui peut s’avérer très pratique pour appliquer des attaques de type Injection DLL, Injection de code, Hooking, etc.

Ce billet s’est inspiré de l’excellent livre « Gray Hat Python » qui aborder l’utilisation de Python pour faire Fuzzing, Reversing, Injection DLL, …

L’exemple surlequel se base le billet est l’activation de privilège Windows, la méthode SetPrivilege permet d’activer le privilège voulu. Petit rappel les privilèges sont les droits d’un compte pour réaliser des actions systèmes, comme lancer un débogueur ou charger un périphérique. 

Pour afficher vos privilèges, utilisez la commande whoami.exe /priv.

Le code C suivant est extrait de la documentation Microsoft, voir lien suivant http://msdn.microsoft.com/en-us/library/windows/desktop/aa446619%28v=vs.85%29.aspx

#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "cmcfg32.lib")

BOOL SetPrivilege(
    HANDLE hToken,          // access token handle
    LPCTSTR lpszPrivilege,  // name of privilege to enable/disable
    BOOL bEnablePrivilege   // to enable or disable privilege
    )
{
    TOKEN_PRIVILEGES tp;
    LUID luid;

    if ( !LookupPrivilegeValue(
            NULL,            // lookup privilege on local system
            lpszPrivilege,   // privilege to lookup
            &luid ) )        // receives LUID of privilege
    {
        printf("LookupPrivilegeValue error: %u\n", GetLastError() );
        return FALSE;
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if (bEnablePrivilege)
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.

    if ( !AdjustTokenPrivileges(
           hToken,
           FALSE,
           &tp,
           sizeof(TOKEN_PRIVILEGES),
           (PTOKEN_PRIVILEGES) NULL,
           (PDWORD) NULL) )
    {
          printf("AdjustTokenPrivileges error: %u\n", GetLastError() );
          return FALSE;
    }

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)

    {
          printf("The token does not have the specified privilege. \n");
          return FALSE;
    }

    return TRUE;
}

La première chose à laquelle on est confrontée est la déclaration de variables.
Python dispose de la librairie WINTYPES contenant la définition d'un grand nombre de variables Windows.

>>> hToken = HANDLE()
Traceback (most recent call last):
File "", line 1, in
NameError: name 'HANDLE' is not defined
>>> from ctypes.wintypes import *
>>> from ctypes import *
>>> hToken = HANDLE()
>>> 

Pour déclarer une structure C, il suffit de déclarer une classe Python de la manière suivante.

typedef struct _LUID {
   DWORD LowPart;
   LONG HighPart;
} LUID, *PLUID;

class LUID(Structure):
_fields_ = [
("LowPart", DWORD),
("HighPart", LONG),
]

typedef struct _LUID_AND_ATTRIBUTES {
   LUID Luid;
   DWORD Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;

class LUID_AND_ATTRIBUTES(Structure):
_fields_ = [
("Luid", LUID),
("Attributes", DWORD),
]


Si vous ne connaissez pas les composants d’une structure, la documentation Microsoft est souvent suffisante, comment vous pouvez facilement le voir, il suffit de déclarer une classe héritant de la classe Structure. Les éléments sont créés en déclarant une liste
_fields_.
Pour ce qui est des arrays, exemple de la structure suivante,
 
typedef struct _TOKEN_PRIVILEGES {
   DWORD PrivilegeCount;
   LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

Il suffit de multiplier le type par la taille de l’array.

class TOKEN_PRIVILEGES(Structure):
_fields_ = [
("PrivilegeCount", DWORD),
("Privileges", ANYSIZE_ARRAY*LUID_AND_ATTRIBUTES)
]

Pour les variables globales, il est bien sûr nécessaire de les assigner avant (code propre), ou de remplacer la variable globale par sa valeur (déconseiller car rendant la relecture du code très difficile).


TOKEN_ADJUST_PRIVILEGES = 0x0020
TOKEN_QUERY = 0x0008
SE_PRIVILEGE_ENABLED = 0x00000002
ANYSIZE_ARRAY = 1
ERROR_NOT_ALL_ASSIGNED = 0x514

Maintenant que nous avons tous nos paramètres et nos types de variables créés, on peut commencer à programmer la logique de notre fonction. Comme on peut le constater, deux méthodes sont utilisées pour récupérer les privilèges voulues :

LookupPrivilegeValue, AdjustTokenPrivileges.

Ces deux méthodes sont extraites du fichier DLL Advapi32.dll, pour cela il suffit de consulter la documentation MSDN. Pour appeler nos méthodes, nous utilisons le fonctionnement normal de CTYPES :


>>> advapi32 = windll.advapi32>>> advapi32

Les conventions d’appel des méthodes sont détaillées dans la documentation CTYPES, pour notre cas, il suffit d’utiliser le nom de la fonction.
Un détail important à indiquer, sur Windows pour quelques fonctions (les méthodes manipulant des string), il existe une méthode utilisant l’encodage ASCII (se termine par ‘A’) et une similaire utilisant l’encodage UNICODE (se termine par ‘W’).
Finalement, pour manipuler une variable en utilisant son pointeur, nous utilisons la méthode
byref().


>>> advapi32 = windll.advapi32
>>> lpszPrivilege = u'SeDebugPrivilege'
>>> advapi32.LookupPrivilegeValue( None, lpszPrivilege, byref(luid) )
Traceback (most recent call last):
File "", line 1, in
File "C:\Python27\lib\ctypes\__init__.py", line 366, in __getattr__
func = self.__getitem__(name)
File "C:\Python27\lib\ctypes\__init__.py", line 371, in __getitem__
func = self._FuncPtr((name_or_ordinal, self))
AttributeError: function 'LookupPrivilegeValue' not found
>>> advapi32.LookupPrivilegeValueA( None, lpszPrivilege, byref(luid) )
0
>>> advapi32.LookupPrivilegeValueW( None, lpszPrivilege, byref(luid) )
1

Pour la méthode
GetLastError(), cette dernière existe déjà et vous pouvez l’utiliser directement. Vous avez maintenant toutes les billes pour écrire votre code en Python et l’utiliser facilement dans vos scripts.
Voici donc le code final de notre méthode :

def SetPrivilege( hToken, lpszPrivilege, bEnablePrivilege):
   tp = TOKEN_PRIVILEGES()
   luid = LUID()

   if not advapi32.LookupPrivilegeValueW( None, lpszPrivilege, byref(luid) ):
      print "LookupPrivilegeValue error: 0x%08x\n" % GetLastError()
      return False

   tp.PrivilegeCount = 1;
   tp.Privileges[0].Luid = luid

   if bEnablePrivilege:
      tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
   else:
      tp.Privileges[0].Attributes = 0

   if not advapi32.AdjustTokenPrivileges( hToken, False, byref(tp), sizeof(TOKEN_PRIVILEGES), None, None):
      print "AdjustTokenPrivileges error: 0x%08x\n", GetLastError()
      return False

   if GetLastError() == ERROR_NOT_ALL_ASSIGNED:
      print "The token does not have the specified privilege. \n"
      return False

   return True

Pour tester notre méthode nous utiliserons la commande système whoami /PRIV, voici donc le code complet de test :

import sys
from ctypes import *
from ctypes.wintypes import *
import os

PAGE_READWRITE = 0x04
PROCESS_ALL_ACCESS = (0x000F0000 | 0x00100000 | 0xFFF )
VIRTUAL_MEM = (0x1000 | 0x2000 )
TOKEN_ADJUST_PRIVILEGES = 0x0020
TOKEN_QUERY = 0x0008
SE_PRIVILEGE_ENABLED = 0x00000002
ANYSIZE_ARRAY = 1
ERROR_SUCCESS = 0
ERROR_NOT_ALL_ASSIGNED = 0x514

advapi32 = windll.advapi32
kernel32 = windll.kernel32

class LUID(Structure):
_fields_ = [
("LowPart", DWORD),
("HighPart", LONG),
]

class LUID_AND_ATTRIBUTES(Structure):
_fields_ = [
("Luid", LUID),
("Attributes", DWORD),
]

class TOKEN_PRIVILEGES(Structure):
_fields_ = [
("PrivilegeCount", DWORD),
("Privileges", ANYSIZE_ARRAY*LUID_AND_ATTRIBUTES)
]

def SetPrivilege( hToken, lpszPrivilege, bEnablePrivilege):
   tp = TOKEN_PRIVILEGES()
   luid = LUID()

   if not advapi32.LookupPrivilegeValueW( None, lpszPrivilege, byref(luid) ):
      print "LookupPrivilegeValue error: 0x%08x\n" % GetLastError()
      return False

   tp.PrivilegeCount = 1;
   tp.Privileges[0].Luid = luid

   if bEnablePrivilege:
      tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
   else:
      tp.Privileges[0].Attributes = 0

   if not advapi32.AdjustTokenPrivileges( hToken, False, byref(tp), sizeof(TOKEN_PRIVILEGES), None, None):
      print "AdjustTokenPrivileges error: 0x%08x\n", GetLastError()
      return False

   if GetLastError() == ERROR_NOT_ALL_ASSIGNED:
      print "The token does not have the specified privilege. \n"
      return False

   return True


hCurrentProcess = kernel32.GetCurrentProcess()
hToken = HANDLE()

if not advapi32.OpenProcessToken(hCurrentProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, byref(hToken)):
   print "[-] Error: OpenProcessToken"
   sys.exit(0)

SE_DEBUG_NAME = u'SeDebugPrivilege'

whoami_priv = lambda : os.system('whoami /priv')

whoami_priv()
if SetPrivilege( hToken, SE_DEBUG_NAME, True):
   print "[+] %s activated" % SE_DEBUG_NAME
   whoami_priv()
else:
   print "[-] %s was not activated" % SE_DEBUG_NAME

Quand nous exécutons la commande avec des droits limités, nous avons l’affichage suivant:

C:\Users\Forest>python.exe SetPrivilege.py

Informations de privilèges----------------------

Nom de privilège Description État
============================= ============================================ =========
SeShutdownPrivilege Arrêter le système Désactivé
SeChangeNotifyPrivilege Contourner la vérification de parcours Activé
SeUndockPrivilege Retirer l'ordinateur de la station d'accueil Désactivé
SeIncreaseWorkingSetPrivilege Augmenter une plage de travail de processus Désactivé
SeTimeZonePrivilege Changer le fuseau horaire Désactivé
The token does not have the specified privilege.

[-] SeDebugPrivilege was not activated


En utilisant les droits administrateurs, nous avons l’affichage suivant, comme vous pouvez le constater
SeDebugPrivilege a été activé

C:\Users\Forest>python.exe SetPrivilege.py

Informations de privilèges----------------------

Nom de privilège Description État
=============================== ========================================================= =========
SeIncreaseQuotaPrivilege Ajuster les quotas de mémoire pour un processus Désactivé
SeSecurityPrivilege Gérer le journal d'audit et de sécurité Désactivé
SeTakeOwnershipPrivilege Prendre possession de fichiers ou d'autres objets Désactivé
SeLoadDriverPrivilege Charger et décharger les pilotes de périphériques Désactivé
SeSystemProfilePrivilege Performance système du profil Désactivé
SeSystemtimePrivilege Modifier l'heure système Désactivé
SeProfileSingleProcessPrivilege Processus unique du profil Désactivé
SeIncreaseBasePriorityPrivilege Augmenter la priorité de planification Désactivé
SeCreatePagefilePrivilege Créer un fichier d'échange Désactivé
SeBackupPrivilege Sauvegarder les fichiers et les répertoires Désactivé
SeRestorePrivilege Restaurer les fichiers et les répertoires Désactivé
SeShutdownPrivilege Arrêter le système Désactivé
SeDebugPrivilege Déboguer les programmes Désactivé
SeSystemEnvironmentPrivilege Modifier les valeurs de l'environnement du microprogramme Désactivé
SeChangeNotifyPrivilege Contourner la vérification de parcours Activé
SeRemoteShutdownPrivilege Forcer l'arrêt à partir d'un système distant Désactivé
SeUndockPrivilege Retirer l'ordinateur de la station d'accueil Désactivé
SeManageVolumePrivilege Effectuer les tâches de maintenance de volume Désactivé
SeImpersonatePrivilege Emprunter l'identité d'un client après l'authentification Activé
SeCreateGlobalPrivilege Créer des objets globaux Activé
SeIncreaseWorkingSetPrivilege Augmenter une plage de travail de processus Désactivé
SeTimeZonePrivilege Changer le fuseau horaire Désactivé
SeCreateSymbolicLinkPrivilege Créer des liens symboliques Désactivé
[+] SeDebugPrivilege activated

Informations de privilèges----------------------

Nom de privilège Description État
=============================== ========================================================= =========
SeIncreaseQuotaPrivilege Ajuster les quotas de mémoire pour un processus Désactivé
SeSecurityPrivilege Gérer le journal d'audit et de sécurité Désactivé
SeTakeOwnershipPrivilege Prendre possession de fichiers ou d'autres objets Désactivé
SeLoadDriverPrivilege Charger et décharger les pilotes de périphériques Désactivé
SeSystemProfilePrivilege Performance système du profil Désactivé
SeSystemtimePrivilege Modifier l'heure système Désactivé
SeProfileSingleProcessPrivilege Processus unique du profil Désactivé
SeIncreaseBasePriorityPrivilege Augmenter la priorité de planification Désactivé
SeCreatePagefilePrivilege Créer un fichier d'échange Désactivé
SeBackupPrivilege Sauvegarder les fichiers et les répertoires Désactivé
SeRestorePrivilege Restaurer les fichiers et les répertoires Désactivé
SeShutdownPrivilege Arrêter le système Désactivé
SeDebugPrivilege Déboguer les programmes Activé
SeSystemEnvironmentPrivilege Modifier les valeurs de l'environnement du microprogramme Désactivé
SeChangeNotifyPrivilege Contourner la vérification de parcours Activé
SeRemoteShutdownPrivilege Forcer l'arrêt à partir d'un système distant Désactivé
SeUndockPrivilege Retirer l'ordinateur de la station d'accueil Désactivé
SeManageVolumePrivilege Effectuer les tâches de maintenance de volume Désactivé
SeImpersonatePrivilege Emprunter l'identité d'un client après l'authentification Activé
SeCreateGlobalPrivilege Créer des objets globaux Activé
SeIncreaseWorkingSetPrivilege Augmenter une plage de travail de processus Désactivé
SeTimeZonePrivilege Changer le fuseau horaire Désactivé
SeCreateSymbolicLinkPrivilege Créer des liens symboliques Désactivé


No comments:

Post a Comment