2008-11-08 26 views
7

Mein Freund produzierte einen kleinen Proof-of-Concept-Assembler, der auf x86 arbeitete. Ich entschied mich, es auch für x86_64 zu portieren, aber ich traf sofort ein Problem.Python Ctypes und Funktionsaufrufe

Ich schrieb ein kleines Stück Programm in C, dann kompilierte und objdumped den Code. Danach habe ich es mein Python-Skript eingefügt, damit der x86_64-Code korrekt ist:

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) 

Nun, warum diese Skript Segmentierungsfehler immer wieder tun, wenn ich es laufen?

Ich habe noch eine Frage über mprotect und keine Ausführungsflagge. Es wird gesagt, dass es gegen die meisten grundlegenden Sicherheitsexplosionen wie Pufferüberläufe schützt. Aber was ist der wahre Grund dafür? Du könntest einfach weiterschreiben, bis du auf den .text triffst, dann sprichst du deine Anweisungen in einen schönen PROT_EXEC-Bereich. Es sei denn natürlich, verwenden Sie einen Schreibschutz in. Text

Aber dann, warum haben diese PROT_EXEC überall sowieso? Wäre es nicht nur enorm hilfreich, wenn Ihr .text-Bereich schreibgeschützt ist?

Antwort

8

Wie vincent erwähnt, ist dies aufgrund der zugeordneten Seite als nicht ausführbar markiert werden. Neuere Prozessoren unterstützen diese functionality, und es wird als zusätzliche Sicherheitsschicht von Betriebssystemen verwendet, die es unterstützen. Die Idee ist, sich vor bestimmten Pufferüberlaufangriffen zu schützen. Z.B. Ein üblicher Angriff besteht darin, eine Stapelvariable zu überfließen, indem die Rücksprungadresse so umgeschrieben wird, dass sie auf den Code zeigt, den Sie eingefügt haben. Bei einem nicht ausführbaren Stack erzeugt dies jetzt nur einen segfault und nicht die Kontrolle über den Prozess. Ähnliche Angriffe gibt es auch für den Heap-Speicher.

Um es zu umgehen, müssen Sie den Schutz ändern.Dies kann nur auf Seite ausgerichtet Speichern durchgeführt werden, so müssen Sie wahrscheinlich Ihren Code so etwas wie die folgenden ändern:

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) 

Hinweis: Es kann eine gute Idee sein, die ausführbare Flagge unscharf zu schalten, bevor Sie die Seite zu befreien . Die meisten C-Bibliotheken geben den Arbeitsspeicher nicht wirklich an das Betriebssystem zurück, wenn sie ausgeführt werden, behalten ihn jedoch in ihrem eigenen Pool. Dies könnte bedeuten, dass sie die Seite an anderer Stelle wiederverwenden, ohne das EXEC-Bit zu löschen, wodurch der Sicherheitsvorteil umgangen wird.

Beachten Sie auch, dass dies nicht tragbar ist. Ich habe es auf Linux getestet, aber nicht auf anderen Betriebssystemen. Es funktioniert nicht auf Windows, kaufen Sie möglicherweise auf andere Unixe (BSD, OsX?).

+0

Noch bessere Antwort. valloc ist nützlich, wie zu beachten ist, dass das EXEC-Bit danach nicht gelöscht wird. Aber ich bin vielleicht an keinem Aspekt interessiert. – Cheery

0

Ermöglicht Python sogar eine solche Verwendung? Ich sollte es dann lernen ...

Ich denke, der Interpreter erwartet nicht, dass irgendein Register geändert wird. Versuchen Sie, die Register zu speichern, die Sie in der Funktion verwenden, wenn Sie Ihre Assembler-Ausgabe wie folgt verwenden möchten.

Btw, Anrufkonvention von x86_64 ist anders als normale x86. Sie können Probleme haben, wenn Sie die Stapelzeigerausrichtung verlieren und externe Objekte mischen, die mit anderen Werkzeugen erstellt wurden.

+0

ctypes sorgt dafür, dass meine Aufrufkonventionen korrekt sind, es reicht, dass der Code von gcc ausgegeben wurde. Als was zu wechselnden Registern kommt, dachte ich, dass die Aufrufkonventionen von x86_64 sagen, dass Subroutine die meisten Register frei ändern kann. – Cheery

4

Ich denke, dass Sie nicht zugewiesenen Speicher frei ausführen können, ohne es zuerst als ausführbar zu setzen. Ich habe nie versucht, mich, aber Sie könnten die Unix-Funktion mprotect überprüfen möchten:

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

VirtualProtect scheint zu tun, die gleiche Sache auf Fenster:

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

+0

Obwohl ich es anderswo vorher gefunden habe, ist das wirklich richtig, aber mit leichten Abweichungen. Ich erkläre es in meiner eigenen Antwort. – Cheery

7

Geschehen einige der Forschung mit meinem Freund und herausgefunden, dass dies ein plattformspezifisches Problem ist. Wir vermuten, dass auf einigen Plattformen malloc mmap Speicher ohne PROT_EXEC und auf anderen Plattformen es tut.

Daher muss die Schutzstufe mit mprotect nachträglich geändert werden.

Lahme Sache, dauerte eine Weile, um herauszufinden, was zu tun ist.

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

absolut das beste Beispiel, das jemals zu diesem Thema gesehen wurde! – mtasic85

0

Es gibt einfachere Ansatz, den ich nur gedacht habe, aber vor kurzem, dass mprotect nicht beteiligt ist. Einfach den ausführbaren Speicherplatz für das Programm direkt angeben. Heutzutage hat Python ein Modul, um genau das zu tun, obwohl ich keine Möglichkeit gefunden habe, die Adresse des Codes zu bekommen. Kurz gesagt, Sie würden Speicher zuordnen, der mmap aufruft, anstatt Zeichenfolgenpuffer zu verwenden und das Ausführungs-Flag indirekt zu setzen. Dies ist einfacher und sicherer, Sie können sicher sein, dass nur Ihr Code jetzt ausgeführt werden kann.