una resueltos propias otra llamar lista globales funciones funcion ejercicios dentro como python c assembly ctypes x86-64

resueltos - Cyptos de Python y llamadas a funciones



lista de funciones de python (5)

Mi amigo produjo un pequeño ensamblador de prueba de concepto que funcionaba en x86. Decidí portarlo también para x86_64, pero de inmediato me encontré con un problema.

Escribí un pequeño trozo de programa en C, luego compilé y objdumped el código. Después de eso, lo inserté en mi script python, por lo tanto, el código x86_64 es correcto:

from ctypes import cast, CFUNCTYPE, c_char_p, c_long buffer = ''''.join(map(chr, [ #0000000000000000 <add>: 0x55, # push %rbp 0x48, 0x89, 0xe5, # mov %rsp,%rbp 0x48, 0x89, 0x7d, 0xf8, # mov %rdi,-0x8(%rbp) 0x48, 0x8b, 0x45, 0xf8, # mov -0x8(%rbp),%rax 0x48, 0x83, 0xc0, 0x0a, # add $0xa,%rax 0xc9, # leaveq 0xc3, # retq ])) fptr = cast(c_char_p(buffer), CFUNCTYPE(c_long, c_long)) print fptr(1234)

Ahora, ¿por qué este script sigue haciendo fallas de segmentación cada vez que lo ejecuto?

Todavía tengo una pregunta sobre mprotect y no flag de ejecución. Se dice que protege contra la mayoría de los ataques de seguridad básicos, como los desbordamientos del búfer. Pero, ¿cuál es la verdadera razón por la que está en uso? Podrías seguir escribiendo hasta que toques el texto. Luego, inserta tus instrucciones en una agradable zona PROT_EXEC. A menos que, por supuesto, use una protección de escritura en .text

Pero entonces, ¿por qué tener ese PROT_EXEC en todos lados? ¿No ayudaría tremendamente que su sección .text esté protegida contra escritura?


¿Python incluso permite tal uso? Debería aprenderlo entonces ...

Creo que el intérprete no espera que se cambie ningún registro. Intente guardar los registros que usa dentro de la función si planea usar su salida de ensamblador así.

Por cierto, la convención de llamadas de x86_64 es diferente a la normal x86. Puede tener problemas si pierde la alineación del puntero de pila y mezcla los objetos externos generados con otras herramientas.



Hice algunas investigaciones con mi amigo y descubrí que este es un problema específico de la plataforma. Sospechamos que en algunas plataformas malloc mmaps memory sin PROT_EXEC y en otras lo hace.

Por lo tanto, es necesario cambiar el nivel de protección con mprotect después.

Lo cojo, tomó un tiempo para descubrir qué hacer.

from ctypes import ( cast, CFUNCTYPE, c_long, sizeof, addressof, create_string_buffer, pythonapi ) PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC = 0, 1, 2, 4 mprotect = pythonapi.mprotect buffer = ''''.join(map(chr, [ #0000000000000000 <add>: 0x55, # push %rbp 0x48, 0x89, 0xe5, # mov %rsp,%rbp 0x48, 0x89, 0x7d, 0xf8, # mov %rdi,-0x8(%rbp) 0x48, 0x8b, 0x45, 0xf8, # mov -0x8(%rbp),%rax 0x48, 0x83, 0xc0, 0x0a, # add $0xa,%rax 0xc9, # leaveq 0xc3, # retq ])) pagesize = pythonapi.getpagesize() cbuffer = create_string_buffer(buffer)#c_char_p(buffer) addr = addressof(cbuffer) size = sizeof(cbuffer) mask = pagesize - 1 if mprotect(~mask&addr, mask&addr + size, PROT_READ|PROT_WRITE|PROT_EXEC) < 0: print "mprotect failed?" else: fptr = cast(cbuffer, CFUNCTYPE(c_long, c_long)) print repr(fptr(1234))


Hay un enfoque más simple que he pensado solo, pero recientemente eso no involucra a mprotect. Simplemente mmap el espacio ejecutable para el programa directamente. En estos días, Python tiene un módulo para hacer exactamente esto, aunque no encontré la forma de obtener la dirección del código. En resumen, asignaría memoria llamando a mmap en lugar de usar búferes de cadena y configurando el indicador de ejecución indirectamente. Esto es más fácil y seguro, puede estar seguro de que solo su código se puede ejecutar ahora.


Como lo mencionó Vincent , esto se debe a que la página asignada está marcada como no ejecutable. Los procesadores más nuevos admiten esta funcionalidad , y es utilizada como una capa adicional de seguridad por los SO que la soportan. La idea es protegerse contra ciertos ataques de desbordamiento de búfer. P.ej. Un ataque común es desbordar una variable de pila, reescribiendo la dirección de retorno para señalar el código que ha insertado. Con una pila no ejecutable, ahora esto solo produce una segfault, en lugar de un control del proceso. Ataques similares también existen para la memoria de pila.

Para evitarlo, debes modificar la protección. Esto solo se puede realizar en la memoria alineada con la página, por lo que probablemente deba cambiar su código a algo como el siguiente:

libc = CDLL(''libc.so'') # Some constants PROT_READ = 1 PROT_WRITE = 2 PROT_EXEC = 4 def executable_code(buffer): """Return a pointer to a page-aligned executable buffer filled in with the data of the string provided. The pointer should be freed with libc.free() when finished""" buf = c_char_p(buffer) size = len(buffer) # Need to align to a page boundary, so use valloc addr = libc.valloc(size) addr = c_void_p(addr) if 0 == addr: raise Exception("Failed to allocate memory") memmove(addr, buf, size) if 0 != libc.mprotect(addr, len(buffer), PROT_READ | PROT_WRITE | PROT_EXEC): raise Exception("Failed to set protection on buffer") return addr code_ptr = executable_code(buffer) fptr = cast(code_ptr, CFUNCTYPE(c_long, c_long)) print fptr(1234) libc.free(code_ptr)

Nota: Puede ser una buena idea desarmar la bandera ejecutable antes de liberar la página. La mayoría de las bibliotecas C en realidad no devuelven la memoria al sistema operativo cuando están listas, sino que las mantienen en su propio grupo. Esto podría significar que reutilizarán la página en otro lugar sin borrar el bit EXEC, evitando el beneficio de seguridad.

También tenga en cuenta que esto es bastante no portátil. Lo probé en Linux, pero no en ningún otro sistema operativo. No funcionará en Windows, comprar puede hacer en otras Unixes (BSD, OsX?).