2008-11-08 86 views
7

我的朋友制作了一个x86工作的小概念验证汇编程序。我决定将它移植到x86_64上,但是我立即遇到了问题。Python的ctypes和函数调用

我在C写一小块程序,然后编译和objdumped的代码。之后,我把它插入到我的python脚本,因此x86_64的代码是正确的:

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) 

现在,为什么这个剧本一直在做分段错误,每当我跑了吗?

我还有一个关于mprotect的和没有执行标志的问题。据说可以防止缓冲区溢出等大多数基本的安全漏洞攻击。但是它使用的真正原因是什么?你可以继续写作,直到你点击.text,然后将你的指示注入一个很好的PROT_EXEC区域。除非,当然,您使用的.text

但随后写保护,为什么有PROT_EXEC无处不在呢?难道它只是极大地帮助你的.text部分被写保护?

回答

8

如前所述vincent,这是由于分配的网页被标记为不可执行。较新的处理器支持这个functionality,它被用作支持它的操作系统的一个附加的安全层。这个想法是防止某些缓冲区溢出攻击。例如。常见的攻击是溢出堆栈变量,重写返回地址以指向您插入的代码。对于不可执行的堆栈,现在只产生段错误,而不是对进程的控制。堆内存也存在类似的攻击。

为了解决它,你需要改变保护。释放页面之前,它可能是一个好主意,取消设置可执行标志:这只能在页对齐的内存来执行,所以你可能需要对你的代码更改为类似下面:

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) 

注。大多数C库在完成时并不实际将内存返回给操作系统,而是将其保留在自己的池中。这可能意味着他们将在不清除EXEC位的情况下重用其他页面,从而绕过安全优势。

另请注意,这是相当不便携的。我在linux上测试过它,但没有在任何其他操作系统上测试过。它不会在Windows上工作,购买可能会在其他unix(BSD,OsX?)上执行。

+0

更好的答案。 valloc是有用的,就像注意到EXEC位在这之后没有被清除。但我可能对这两方面都不感兴趣。 – Cheery 2008-11-09 08:45:43

0

python甚至允许这样的用法吗?我应该学习它,然后...

我认为翻译不指望任何登记改变。如果您打算像这样使用汇编输出,请尝试保存在函数内部使用的寄存器。

顺便说一句,x86_64的呼叫约定是比普通的x86不同。如果您丢失堆栈指针对齐并混合使用其他工具生成的外部对象,则可能会遇到麻烦。

+0

ctypes注意我的调用约定是正确的,代码已经被gcc输出就足够了。至于如何改变寄存器,我认为x86_64的调用约定是说子程序可以自由地改变大多数寄存器。 – Cheery 2008-11-09 09:00:44

4

我想你不能自由地执行任何分配的内存,而无需先将其设置为可执行文件。我从来没有尝试过自己,但你可能要检查的UNIX功能mprotect

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

VirtualProtect似乎做在Windows同样的事情:

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

+0

尽管我之前在其他地方发现过,但这确实是正确的,但稍有不同。我在我自己的答案中解释它。 – Cheery 2008-11-09 00:22:09

7

做了一些研究和我的朋友并发现这是一个特定于平台的问题。我们怀疑在某些平台上没有PROT_EXEC的malloc mmaps内存以及其他的平台上。

因此,有必要在事后改变与mprotect的保护级别。

跛脚的事情,花了一段时间找出该怎么做。

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

绝对最好的例子曾经见过这个话题! – mtasic85 2009-11-05 13:14:07

0

有更简单的方法,我只,但最近不涉及mprotect的盘算。显然直接mmap程序的可执行空间。现在python有一个模块来做这件事,尽管我没有找到获得代码地址的方法。总之,你会分配调用mmap的内存,而不是使用字符串缓冲区并间接设置执行标志。这更简单和更安全,您可以确保只有您的代码可以现在执行。