Domanda

Il mio amico ha prodotto un piccolo assemblatore di prove concettuali che ha funzionato su x86. Ho deciso di portarlo anche per x86_64, ma ho subito riscontrato un problema.

Ho scritto un piccolo pezzo di programma in C, poi ho compilato e objdump il codice. Successivamente l'ho inserito nel mio script Python, quindi il codice x86_64 è corretto:

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)

Ora, perché questo script continua a presentare errori di segmentazione ogni volta che lo eseguo?

Ho ancora una domanda su mprotect e nessun flag di esecuzione. Si dice che protegga dalla maggior parte degli exploit di sicurezza di base come sovraccarichi del buffer. Ma qual è la vera ragione per cui è in uso? Potresti continuare a scrivere fino a quando non premi il .text, quindi inietta le tue istruzioni in una bella zona PROT_EXEC. A meno che, ovviamente, non utilizzi una protezione da scrittura in .text

Ma allora perché PROT_EXEC ha comunque ovunque? Non sarebbe di grande aiuto che la tua sezione .text sia protetta da scrittura?

È stato utile?

Soluzione

Come vincent , ciò è dovuto al fatto che la pagina allocata è contrassegnata come non eseguibile. I processori più recenti supportano questa funzionalità e viene utilizzata come ulteriore livello di sicurezza dai sistemi operativi che la supportano . L'idea è di proteggere da determinati attacchi di buffer overflow. Per esempio. Un attacco comune è l'overflow di una variabile dello stack, riscrivendo l'indirizzo di ritorno in modo che punti al codice inserito. Con uno stack non eseguibile questo ora produce solo un segfault, piuttosto che il controllo del processo. Attacchi simili esistono anche per la memoria heap.

Per aggirare il problema, è necessario modificare la protezione. Questo può essere eseguito solo sulla memoria allineata della pagina, quindi probabilmente dovrai cambiare il tuo codice in qualcosa del tipo seguente:

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: potrebbe essere una buona idea disinserire il flag eseguibile prima di liberare la pagina. La maggior parte delle librerie C in realtà non restituisce la memoria al sistema operativo una volta terminata, ma la mantiene nel proprio pool. Ciò potrebbe significare che riutilizzeranno la pagina altrove senza cancellare il bit EXEC, ignorando il vantaggio in termini di sicurezza.

Si noti inoltre che questo è abbastanza non portatile. L'ho provato su Linux, ma non su altri sistemi operativi. Non funzionerà su Windows, comprare potrebbe fare su altri unix (BSD, OsX?).

Altri suggerimenti

Ho fatto alcune ricerche con il mio amico e ho scoperto che si tratta di un problema specifico della piattaforma. Sospettiamo che su alcune piattaforme malloc la memoria mmaps senza PROT_EXEC e su altre lo faccia.

Pertanto è necessario modificare successivamente il livello di protezione con mprotect.

Lame cosa, ci è voluto un po 'per scoprire cosa fare.

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

Penso che non sia possibile eseguire liberamente qualsiasi memoria allocata senza prima impostarla come eseguibile. Non ho mai provato me stesso, ma potresti voler controllare la funzione unix mprotect :

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

VirtualProtect sembra fare la stessa cosa su Windows:

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

Python consente anche tale utilizzo? Dovrei impararlo allora ...

Penso che l'interprete non si aspetti che nessun registro venga modificato. Prova a salvare i registri che utilizzi all'interno della funzione se prevedi di utilizzare l'output dell'assemblatore in questo modo.

A proposito, la convenzione di chiamata di x86_64 è diversa dalla normale x86. Potresti avere problemi se perdi l'allineamento del puntatore dello stack e mescoli oggetti esterni generati con altri strumenti.

C'è solo un approccio più semplice che ho immaginato, ma recentemente non coinvolge mprotect. Semplicemente mmap lo spazio eseguibile per programma direttamente. In questi giorni python ha un modulo per fare esattamente questo, anche se non ho trovato il modo di ottenere l'indirizzo del codice. In breve, allocereste la memoria chiamando mmap invece di usare i buffer di stringa e impostando indirettamente il flag di esecuzione. Questo è più facile e sicuro, puoi essere sicuro che solo il tuo codice può essere eseguito ora.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top