Home > python, Uncategorized > Construyendo tipos de datos de C con ctypes

Construyendo tipos de datos de C con ctypes

Seguimos con estos post dedicados a python y su libreria ctypes. En este caso voy a dar algunos ejemplos para saber que tipo de datos usar en cada variable que criemos.

Hago esto… porque yo como muchos reversers estoy acostumbrado a ver todo como BYTE, WORD, DWORD. Ya cuando me agregan palabras como punteros o integer, ya me rompe la paciencia 😛 por eso vamos a ver que tamaño le da a cada cosa nuestra querida librería de python.

Esta es la lista de estructura de datos que nos provee el librito “Gray Hat Python“:

C Type Python Type ctypes Type
char 1-character string c_char
wchar_t 1-character Unicode string c_wchar
char int/long c_byte
char int/long c_ubyte
short int/long c_short
unsigned short int/long c_ushort
int int/long c_int
unsigned int int/long c_uint
long int/long c_long
unsigned long int/long c_ulong
long long int/long c_longlong
unsigned long long int/long c_ulonglong
float float c_float
double float c_double
char * (NULL terminated) string or none c_char_p
wchar_t * (NULL terminated) unicode or none c_wchar_p
void * int/long or none c_void_p


Probemos este código para obtener los tamaños asignados a cada variable en nuestra maquina (no creo que difiera si se trata de una maquina x86 de 32 bits, pero…)

from ctypes import *

print "Caracteres ASCII/Unicode:"
print "[+] c_char:\t%d" % sizeof(c_char('c'))
print "[+] c_wchar:\t%d\n" % sizeof(c_wchar('c'))

print "Numeros BYTE, WORD, DWORD, QWORD"
print "[+] c_byte:\t%d" % sizeof(c_byte(66))
print "[+] c_short:\t%d" % sizeof(c_short(1))
print "[+] c_int:\t%d" % sizeof(c_int(1))
print "[+] c_long:\t%d" % sizeof(c_long(1))
print "[+] c_longlong:\t%d" % sizeof(c_longlong(4))
print "[+] c_float:\t%d" % sizeof(c_float(3))
print "[+] c_double:\t%d\n" % sizeof(c_double(4))

print "Punteros a direcciones de 32 bits:"
print "[+] punteros\t%d" % sizeof(c_char_p("hola mundo"))

lo único que hace este código es usar sizeof para mostrar el tamaño de los diferentes tipos de datos… no muestro la parte de los unsigned porque el tamaño en ese caso es el mismo, solo cambia el alcance como siempre 😉

miremos la salida:

lo que mas nos importaba es la parte de los números, para saber como representar los datos en memoria. Con esta listita podemos sacar en claro lo siguiente

  • c_ubyte: BYTE
  • c_ushort: WORD
  • c_uint: DWORD

estos son los mas importantes. Ya con esto podemos hacer nuestra magia… Ahora que ya vimos como crear los datos básicos, en la próxima entrega voy a dejar bien en claro como crear estructuras y uniones y que son cada cosa. Hasta la próxima.

Vamos a definir una estructura:

from ctypes import *
class barley_amount(Union):
   _fields_ = [
   ("barley_long", c_long),
   ("barley_int", c_int),
   ("barley_char", c_char * 8),
   ]

my_barley = barley_amount(66)
print "[-] Barley amount as a int: %d" % my_barley.barley_int
print "[-] Barley amount as a char: %s" % my_barley.barley_char

my_barley.barley_int = my_barley.barley_int + 1
print "[+] Barley amount as a int: %d" % my_barley.barley_int
print "[+] Barley amount as a char: %s" % my_barley.barley_char

Este programa genera la siguiente salida:

presten especial a la linea marcada en el código, ahí se incrementa al entero… Pero consecuentemente al incrementar el entero también cambiamos el carácter.

Si miran en la tabla ascii pueden notar que 66. corresponde a “B” y 67. a “C”. Acá lo que hicimos fue incrementar el entero, pero también incrementamos el carácter. Esto sucede porque en una unión todos los valores comparten la misma zona de memoria.

El tamaño que ocupa este tipo de datos es el mas grande de todos los argumentos… Un ejemplo para que se entienda:

acá podemos ver que el tamaño completo de la “unión” es el tamaño del argumento mas grande. No la tengo muy clara con esto, y la verdad es que no se me ocurren muchas ventajas que puede tener usar uniones (si alguno tiene un ejemplo copado, agradecería un comentario al respecto). Para lo único que se me puede ocurrir usarlo es para acceder la misma variable como si fuera de varios tipos sin necesidad de hacer un casting.

Vendría a ser el ejemplo que usamos, accediendo a la misma variable como integer, float o como array de caracteres 😉

Ahora veamos las estructuras. Vamos a usar el mismo ejemplo que antes y vamos a chequear los tamaños. Cambiamos el código anterior por este:

class barley_amount(Structure):
_fields_ = [
("barley_long", c_long),
("barley_int", c_int),
("barley_char", c_char * 8),
]

De esta forma definimos una estructura con los mismo elementos que tenia nuestra unión. Si chequeamos el tamaño tenemos lo siguiente:

las estructuras tienen un tamaño igual a la suma de todos sus elementos. Esto es así porque guardan cada dato uno atrás del otro en memoria… Tranquilamente se puede acceder a cada valor usando un puntero al primer valor y sumando el offset correspondiente.

Estas estructuras y uniones nos sirven para pasarla como argumento a diferentes API’s… Como por ejemplo CreateProcess:

BOOL WINAPI CreateProcess(
__in_opt     LPCTSTR lpApplicationName,
__inout_opt  LPTSTR lpCommandLine,
__in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in         BOOL bInheritHandles,
__in         DWORD dwCreationFlags,
__in_opt     LPVOID lpEnvironment,
__in_opt     LPCTSTR lpCurrentDirectory,
__in         LPSTARTUPINFO lpStartupInfo,
__out        LPPROCESS_INFORMATION lpProcessInformation
);

Vemos que como ultimo argumento se le pasa un puntero a una estructura PROCESS_INFORMATION para que guarde diferentes valores (fíjense que dice __out :idea:). Entonces lo que tendríamos que hacer, es crear la estructura y después pasarla por referencia con el método byref().

Miremos un ejemplo con CreateFile:

from ctypes import *

kernel = windll.kernel32

class STARTUPINFO(Structure):
_fields_ = [
("cb", c_uint),
("lpReserver", POINTER(c_char)),
("lpDesktop", POINTER(c_char)),
("lpTitle", POINTER(c_char)),
("dwX", c_uint),
("dwY", c_uint),
("dwXSize", c_uint),
("dwYSize", c_uint),
("dwXCountChars", c_uint),
("dwFillAttribute", c_uint),
("dwFlags", c_uint),
("wShowWindow", c_ushort),
("cbReserved2", c_ushort),
("lpReserved2", POINTER(c_ubyte)),
("hStdInput", c_void_p),
("hStdOutput", c_void_p),
("hStdError", c_void_p),
]

class PROCESS_INFORMATION(Structure):
_fields_ = [
("hProcess", c_void_p),
("hThread", c_void_p),
("dwProcessId", c_uint),
("dwThreadId", c_uint),
]

Ahi tenemos las dos estructuras que hay que pasarle a CreateProcessA… fijense que los argumentos que tienen “lp” adelante, significa “long pointer” por lo tanto es un puntero, por eso se lo pasa con POINTER(). Con respecto a los HANDLES, no tengo la certeza de porque los puso como puntero a void, pero calculo que es… porque puede apuntar a un puntero de 32 o 64 bits dependiendo de la arquitectura.

Como siempre, si alguno tiene la respuesta, favor de dejarme un comentario 😉

startupinfo = STARTUPINFO()
process_information = PROCESS_INFORMATION()

startupinfo.dwFlags = 0x1
startupinfo.wShowWindow = 0x0

startupinfo.cb = sizeof(startupinfo)
if kernel.CreateProcessA("%windir%\system32\calc.exe",
                         None,
                         None,
                         None,
                         None,
                         None,
                         None,
                         None,
                         byref(startupinfo),
                         byref(process_information)):

   print "[*] We have successfully launched the process!"
   print "[*] PID: 0x%.4x" % process_information.dwProcessId
else:
   print "[*] Error: 0x%.8x" % kernel.GetLastError()

Este programa lo unico que hace es launchear la calculadora y devolvernos el PID, aca una imagen de su funcionamiento:

lo unico que hizo es arrancar la calculadora y devolvernos el PID del proceso. Chequeen que el mismo es leido desde la estructura PROCESS_INFORMATION que le pasamos como referencia a la API. La linea marcada es la correspondiente al argumento dwCreationFlags, en este caso es NULL por eso arranca la calculadora.

Por favor, abstenerse cualquier comentario del tipo: “eso se puede hacer con os.system” porque puedo llegar a prender fuego la computadora… Espero que se entienda el objetivo de cada post 😀

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: