Pergunta

Meu amigo produziu uma pequena montadora prova-de-conceito que trabalhou em x86. Eu decidi porta-lo para x86_64 bem, mas eu bati imediatamente um problema.

Eu escrevi um pequeno pedaço de programa em C, então compilado e objdumped o código. Depois que eu inseri-lo para o meu script python, portanto, o código x86_64 está correto:

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)

Agora, por que esse script continua fazendo falha de segmentação quando eu executá-lo?

Ainda tenho uma pergunta sobre mprotect e nenhuma bandeira execução. Diz-se para proteger contra a maioria das falhas de segurança básicas, como estouros de buffer. Mas o que é a verdadeira razão ele está em uso? Você pode apenas continuar a escrever até que você bateu o .text, em seguida, injetar suas instruções em um agradável, PROT_EXEC -área. A menos, claro, você usa uma proteção contra gravação em .text

Mas então, por que tem que PROT_EXEC em todos os lugares de qualquer maneira? Não seria apenas ajudar tremendamente que sua seção .text está protegido contra gravação?

Foi útil?

Solução

Como vincent mencionado, isto é devido à página alocada que está sendo marcado como não executável. processadores mais recentes suportam esta funcionalidade , e a sua utilização como uma camada adicional de segurança pelo sistema operativo do que suportam . A idéia é proteger contra certos ataques de buffer overflow. Por exemplo. Um ataque comum é a transbordar uma variável de pilha, reescrever o endereço de retorno para o ponto de código que você inseriu. Com uma pilha não-executável esta agora só produz um segfault, em vez do controlo do processo. Ataques semelhantes também existem para a memória heap.

Para contornar o problema, você precisa alterar a proteção. Isso só pode ser realizado na página alinhada memória, então você provavelmente precisará alterar seu código para algo como o abaixo:

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: Pode ser uma boa idéia para resetar o flag executável antes de liberar a página. A maioria das bibliotecas C realmente não voltar a memória para o sistema operacional quando feito, mas mantê-lo na sua própria piscina. Isto poderia significar que eles vão reutilizar a página em outro lugar sem limpar o bit EXEC, ignorando o benefício de segurança.

Além disso, note que este é bastante não-portáteis. Eu testei-o em linux, mas não em qualquer outro sistema operacional. Não vai funcionar no Windows, comprar pode fazer em outros unixes (BSD, a OSX?).

Outras dicas

feito algumas pesquisas com o meu amigo e descobriu que este é um problema específico da plataforma. Nós suspeitamos que em algumas plataformas malloc memória mmaps sem PROT_EXEC e em outros ele faz.

Por isso, é necessário alterar o nível de proteção com mprotect depois.

Lame coisa, levou um tempo para descobrir o que fazer.

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

Eu acho que você não pode executar livremente qualquer memória alocada sem primeiro defini-lo como executável. Eu nunca me tentou, mas você pode querer verificar a função mprotect unix:

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

VirtualProtect parece fazer a mesma coisa no Windows:

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

O python mesmo permitir tal uso? Eu deveria aprender isso então ...

Eu acho que o intérprete não esperar qualquer registo a ser alterado. Tente guardar os registros que você usa dentro da função, se você pretende usar sua saída assembler como esta.

Btw, convenção chamada de x86_64 é diferente do x86 regular. Você pode ter problemas se você perder o alinhamento ponteiro de pilha e misturar objetos externos gerados com outras ferramentas.

Há abordagem mais simples que eu percebi, mas só recentemente que não envolve mprotect. Claramente mmap o espaço executável para o programa diretamente. Estes dias Python tem um módulo para fazer exatamente isso, embora eu não encontrou maneira de obter o endereço do código. Em suma, você iria alocar memória chamando mmap em vez de usar tampões de cordas e definindo o sinalizador execução indiretamente. Isto é mais fácil e mais seguro, você pode ter certeza apenas o seu código pode ser executado agora.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top