Cyptos de Python y llamadas a funciones

votos
7

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?

Publicado el 08/11/2008 a las 20:11
fuente por usuario
En otros idiomas...                            


5 respuestas

votos
0

¿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 de esta manera.

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.

Respondida el 08/11/2008 a las 21:21
fuente por usuario

votos
4

Creo que no se puede ejecutar libremente ninguna memoria asignada sin antes establecerla como ejecutable. Nunca me probé, pero es posible que desee comprobar la función de Unix mprotect:

http://linux.about.com/library/cmd/blcmdl2_mprotect.htm

VirtualProtect parece hacer lo mismo en Windows:

http://msdn.microsoft.com/en-us/library/aa366898(VS.85).aspx

Respondida el 08/11/2008 a las 21:51
fuente por usuario

votos
7

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))
Respondida el 08/11/2008 a las 23:29
fuente por usuario

votos
8

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?).

Respondida el 09/11/2008 a las 00:06
fuente por usuario

votos
0

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.

Respondida el 28/11/2009 a las 12:22
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more