Pregunta

Mi amigo produjo un pequeño ensamblador de prueba de concepto que funcionó en x86. Decidí portarlo también para x86_64, pero inmediatamente tuve un problema.

Escribí un pequeño programa en C, luego compilé y objdumpedé el código. Después de eso lo inserté en mi script de 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    <*>xa,%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 un error de segmentación cada vez que lo ejecuto?

Todavía tengo una pregunta sobre mprotect y ninguna marca de ejecución. Se dice que protege contra la mayoría de las vulnerabilidades de seguridad básicas como desbordamientos de búfer. ¿Pero cuál es la verdadera razón por la que está en uso? Podrías seguir escribiendo hasta que toques el .text, luego inyectar tus instrucciones en un agradable área PROT_EXEC. A menos que, por supuesto, utilice una protección contra escritura en .text

Pero entonces, ¿por qué tener ese PROT_EXEC en todas partes? ¿No sería de gran ayuda que tu sección .text esté protegida contra escritura?

¿Fue útil?

Solución

Como vincent , esto se debe a que la página asignada está marcada como no ejecutable Los procesadores más nuevos admiten esta funcionalidad , y los sistemas operativos que la soportan como una capa adicional de seguridad . La idea es proteger 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, esto ahora solo produce un defecto de seguridad, en lugar del control del proceso. También existen ataques similares para la memoria de almacenamiento dinámico.

Para evitarlo, debe 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 lo 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 desactivar el indicador ejecutable antes de liberar la página. La mayoría de las bibliotecas C en realidad no devuelven la memoria al sistema operativo cuando se hace, pero lo 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 he probado en Linux, pero no en ningún otro sistema operativo. No funcionará en Windows, la compra puede funcionar en otros Unixes (BSD, OsX?).

Otros consejos

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

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

Cojo, tardé un tiempo en averiguar 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    <*>xa,%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))

Creo que no puede ejecutar libremente ninguna memoria asignada sin configurarla primero como ejecutable. Nunca lo intenté yo mismo, 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/ es-es / library / aa366898 (VS.85) .aspx

¿Python incluso permite tal uso? Entonces debería aprenderlo ...

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 x86 normal. Puede tener problemas si pierde la alineación del puntero de la pila y mezcla objetos externos generados con otras herramientas.

Hay un enfoque más simple que he imaginado solo pero recientemente que no involucra mprotect. Claramente 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 buffers de cadena y establecer 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top