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?

Était-ce utile?

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top