Types de Python et appels de fonction
Question
Mon ami a produit un petit assembleur de validation de principe fonctionnant sous x86. J'ai décidé de le porter également pour x86_64, mais j'ai immédiatement rencontré un problème.
J'ai écrit un petit morceau de programme en C, puis compilé et objdumpé le code. Après cela, je l'ai inséré dans mon script python. Le code x86_64 est donc correct:
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)
Maintenant, pourquoi ce script continue-t-il à faire l'erreur de segmentation chaque fois que je l'exécute?
J'ai encore une question sur mprotect et l'absence d'indicateur d'exécution. Il est dit de protéger contre la plupart des exploits de sécurité de base tels que les dépassements de tampon. Mais quelle est la vraie raison pour laquelle il est utilisé? Vous pouvez simplement continuer à écrire jusqu'à ce que vous ayez frappé le texte, puis injecter vos instructions dans une belle zone PROT_EXEC. Sauf si, bien sûr, vous utilisez une protection en écriture dans .text
Mais alors, pourquoi avoir ce PROT_EXEC partout quand même? Cela ne vous aiderait-il pas énormément que votre section .text soit protégée en écriture?
La solution
Comme vincent a été mentionné, cela est dû au fait que la page attribuée a été marquée comme non exécutable. Les nouveaux processeurs prennent en charge cette fonctionnalité et sont utilisés comme couche de sécurité supplémentaire par les systèmes d'exploitation qui le prennent en charge. . L'idée est de se protéger contre certaines attaques par dépassement de tampon. Par exemple. Une attaque courante consiste à déborder d'une variable de pile, en réécrivant l'adresse de retour pour pointer vers le code que vous avez inséré. Avec une pile non exécutable, cela ne produit plus qu'un segfault, plutôt qu'un contrôle du processus. Des attaques similaires existent également pour la mémoire de tas.
Pour le contourner, vous devez modifier la protection. Cette opération ne peut être effectuée que sur une mémoire alignée sur une page. Vous devrez donc probablement modifier votre code, comme suit:
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)
Remarque: il peut être judicieux de désactiver l'indicateur d'exécutable avant de libérer la page. La plupart des bibliothèques C ne rendent pas réellement la mémoire au système d'exploitation une fois terminé, mais la conservent dans leur propre pool. Cela pourrait signifier qu'ils réutiliseraient la page ailleurs sans effacer le bit EXEC, sans passer par la sécurité.
Notez également que ceci est relativement peu portable. Je l'ai testé sur Linux, mais pas sur un autre système d'exploitation. Cela ne fonctionnera pas sur Windows, mais peut-être sur d’autres systèmes Unix (BSD, OsX?).
Autres conseils
Fait des recherches avec mon ami et découvre qu’il s’agit d’un problème spécifique à la plate-forme. Nous pensons que sur certaines plates-formes, malloc mmaps n'a pas de mémoire PROT_EXEC et sur d'autres, c'est le cas.
Il est donc nécessaire de modifier ultérieurement le niveau de protection avec mprotect.
C'est triste, il a fallu un certain temps pour savoir quoi faire.
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))
Je pense que vous ne pouvez pas exécuter librement une mémoire allouée sans la définir au préalable comme exécutable. Je n'ai jamais essayé moi-même, mais vous voudrez peut-être vérifier la fonction unix mprotect
:
http://linux.about.com/library/cmd/blcmdl2_mprotect.htm
VirtualProtect
semble faire la même chose sous Windows:
http://msdn.microsoft.com/ en-us / library / aa366898 (VS.85) .aspx
Python permet-il même un tel usage? Je devrais l'apprendre alors ...
Je pense que l'interprète ne s'attend à aucun changement de registre. Essayez de sauvegarder les registres que vous utilisez dans la fonction si vous envisagez d’utiliser votre sortie assembleur comme ceci.
Btw, la convention d’appel de x86_64 est différente de celle de x86 classique. Si vous perdez l’alignement du pointeur de la pile et que vous mélangez des objets externes générés avec d’autres outils, vous risquez de rencontrer des problèmes.
L’approche la plus simple que j’ai choisie, mais récemment, n’implique pas l’utilisation de mprotect. Mappez simplement l’espace exécutable du programme directement. De nos jours, python a un module permettant de faire exactement cela, bien que je n’aie pas trouvé le moyen d’obtenir l’adresse du code. En bref, vous alloueriez mmap en appelant en mémoire au lieu d'utiliser des tampons de chaîne et de définir l'indicateur d'exécution indirectement. C’est plus facile et plus sûr, vous pouvez être sûr que seul votre code peut être exécuté maintenant.