18 Stimmen

Wie kann ich inline-Maschinencode in Python unter Linux aufrufen?

Ich versuche, Inline-Maschinencode aus reinem Python-Code unter Linux aufzurufen. Zu diesem Zweck bette ich den Code in ein Byte-Literal ein

code = b"\x55\x89\xe5\x5d\xc3"

und rufen dann mprotect() über ctypes um die Ausführung der Seite, die den Code enthält, zu ermöglichen. Schließlich versuche ich, die ctypes um den Code aufzurufen. Hier ist mein vollständiger Code:

#!/usr/bin/python3

from ctypes import *

# Initialise ctypes prototype for mprotect().
# According to the manpage:
#     int mprotect(const void *addr, size_t len, int prot);
libc = CDLL("libc.so.6")
mprotect = libc.mprotect
mprotect.restype = c_int
mprotect.argtypes = [c_void_p, c_size_t, c_int]

# PROT_xxxx constants
# Output of gcc -E -dM -x c /usr/include/sys/mman.h | grep PROT_
#     #define PROT_NONE 0x0
#     #define PROT_READ 0x1
#     #define PROT_WRITE 0x2
#     #define PROT_EXEC 0x4
#     #define PROT_GROWSDOWN 0x01000000
#     #define PROT_GROWSUP 0x02000000
PROT_NONE = 0x0
PROT_READ = 0x1
PROT_WRITE = 0x2
PROT_EXEC = 0x4

# Machine code of an empty C function, generated with gcc
# Disassembly:
#     55        push   %ebp
#     89 e5     mov    %esp,%ebp
#     5d        pop    %ebp
#     c3        ret
code = b"\x55\x89\xe5\x5d\xc3"

# Get the address of the code
addr = addressof(c_char_p(code))

# Get the start of the page containing the code and set the permissions
pagesize = 0x1000
pagestart = addr & ~(pagesize - 1)
if mprotect(pagestart, pagesize, PROT_READ|PROT_WRITE|PROT_EXEC):
    raise RuntimeError("Failed to set permissions using mprotect()")

# Generate ctypes function object from code
functype = CFUNCTYPE(None)
f = functype(addr)

# Call the function
print("Calling f()")
f()

Dieser Code schlägt in der letzten Zeile fehl.

  1. Warum bekomme ich einen Segfault? Die mprotect() Aufruf signalisiert Erfolg, so dass es mir erlaubt sein sollte, Code auf der Seite auszuführen.

  2. Gibt es eine Möglichkeit, den Code zu korrigieren? Kann ich den Maschinencode tatsächlich in reinem Python und innerhalb des aktuellen Prozesses aufrufen?

(Einige weitere Bemerkungen: Ich versuche nicht wirklich, ein Ziel zu erreichen - ich versuche zu verstehen, wie die Dinge funktionieren. Ich habe auch versucht, die 2*pagesize 代わりに pagesize en el mprotect() aufzurufen, um auszuschließen, dass meine 5 Bytes Code auf eine Seitengrenze fallen - was ohnehin unmöglich sein sollte. Ich habe Python 3.1.3 für die Tests verwendet. Mein Rechner ist ein 32-bit i386 System. Ich weiß, dass eine mögliche Lösung darin besteht, ein ELF-Shared Object aus reinem Python-Code zu erstellen und es über ctypes aber das ist nicht die Antwort, die ich suche :)

bearbeiten : Die folgende C-Version des Codes funktioniert einwandfrei:

#include <sys/mman.h>

char code[] = "\x55\x89\xe5\x5d\xc3";
const int pagesize = 0x1000;

int main()
{
    mprotect((int)code & ~(pagesize - 1), pagesize,
             PROT_READ|PROT_WRITE|PROT_EXEC);
    ((void(*)())code)();
}

Bearbeiten 2 : Ich habe den Fehler in meinem Code gefunden. Die Zeile

addr = addressof(c_char_p(code))

erstellt zunächst eine ctypes char* die auf den Anfang der bytes Instanz code . addressof() auf diesen Zeiger angewendet wird, gibt nicht die Adresse zurück, auf die dieser Zeiger zeigt, sondern die Adresse des Zeigers selbst.

Der einfachste Weg, den ich gefunden habe, um die Adresse des Codebeginns zu erhalten, ist

addr = addressof(cast(c_char_p(code), POINTER(c_char)).contents)

Für Hinweise auf eine einfachere Lösung wären wir dankbar :)

Wenn Sie diese Zeile korrigieren, wird der obige Code "funktionieren" (d.h. er tut nichts, anstatt einen Segfault auszulösen...).

7voto

samplebias Punkte 35688

Ich habe eine schnelle Fehlersuche durchgeführt und es stellt sich heraus, dass der Zeiger auf die code ist nicht richtig konstruiert, und irgendwo im Inneren mischt ctypes Dinge durcheinander, bevor es den Funktionszeiger an ffi_call() die den Befehl Code.

Hier ist die Zeile in ffi_call_unix64() (ich arbeite mit 64-Bit), wo der Funktionszeiger gespeichert wird in %r11 :

57   movq    %r8, %r11               /* Save a copy of the target fn.

Wenn ich Ihren Code ausführe, wird der folgende Wert in %r11 kurz vor er den Anruf versucht:

(gdb) x/5b $r11
0x7ffff7f186d0: -108    24      -122    0       0

Hier ist die Lösung, um den Zeiger zu konstruieren und die Funktion aufzurufen:

raw = b"\x55\x89\xe5\x5d\xc3"
code = create_string_buffer(raw)
addr = addressof(code)

Wenn ich es jetzt ausführe, sehe ich die richtigen Bytes an dieser Adresse, und die Funktion wird gut ausgeführt:

(gdb) x/5b $r11
0x7ffff7f186d0: 0x55    0x89    0xe5    0x5d    0xc3

3voto

Nemo Punkte 67866

Sie müssen möglicherweise den Befehlscache leeren .

Sie ist unklar (jedenfalls für mich), ob mprotect() dies automatisch tut.

[Update]

Hätte ich die Dokumentation für cacheflush() gelesen, hätte ich natürlich gesehen, dass es nur auf MIPS anwendbar ist (laut der Manpage).

Angenommen, es handelt sich um eine x86-Anwendung, müssen Sie möglicherweise den Befehl WBINVD (oder CLFLUSH) aufrufen.

Im Allgemeinen muss selbstmodifizierender Code den i-Cache leeren, aber soweit ich weiß, gibt es keine auch nur annähernd portable Möglichkeit, dies zu tun.

2voto

Nicholas Riley Punkte 41936

Ich würde vorschlagen, dass Sie zuerst versuchen, Ihren Code in C zum Laufen zu bringen, dann übersetzen Sie nach ctypes . Es gibt auch etwas wie CorePy wenn Sie nur Assembler von Python aus ausführen wollen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X