7

我的朋友制作了一个适用于 x86 的小型概念验证汇编器。我决定也将它移植到 x86_64,但我立即遇到了问题。

我用 C 写了一小段程序,然后编译和 objdump 代码。之后我将它插入到我的 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 部分被写保护不是很有帮助吗?

4

5 回答 5

8

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

要绕过它,您需要更改保护。这只能在页面对齐的内存上执行,因此您可能需要将代码更改为如下所示:

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

于 2008-11-09T01:06:14.363 回答
7

和我的朋友做了一些研究,发现这是一个特定于平台的问题。我们怀疑在某些平台上 malloc mmaps 内存没有 PROT_EXEC 而在其他平台上它确实如此。

因此有必要在之后使用 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))
于 2008-11-09T00:29:39.740 回答
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

于 2008-11-08T22:51:24.777 回答
0

python甚至允许这样的用法吗?那我应该学...

我认为解释器不希望更改任何寄存器。如果您打算像这样使用汇编器输出,请尝试保存您在函数中使用的寄存器。

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

于 2008-11-08T22:21:43.417 回答
0

我只想到了一种更简单的方法,但最近不涉及 mprotect。直接为程序直接映射可执行空间。这些天 python 有一个模块可以做到这一点,尽管我没有找到获取代码地址的方法。简而言之,您将分配内存调用 mmap 而不是使用字符串缓冲区并间接设置执行标志。这更容易和更安全,您可以确定现在只有您的代码可以执行。

于 2009-11-28T13:22:43.813 回答