我的朋友制作了一个适用于x86的小型概念验证汇编程序。我决定将它移植到x86_64,但我立刻遇到了问题。
我在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 -area。当然,除非你在.text
中使用写保护但是,为什么到处都有PROT_EXEC?它的.text部分是否会被写保护,这不是很有帮助吗?
答案 0 :(得分:8)
提到vincent,这是由于分配的页面被标记为不可执行。较新的处理器支持此functionality,并且它被支持它的操作系统用作增加的安全层。这个想法是为了防止某些缓冲区溢出攻击。例如。常见的攻击是溢出堆栈变量,重写返回地址以指向您插入的代码。使用不可执行的堆栈,现在只产生段错误,而不是控制进程。堆内存也存在类似的攻击。</ p>
要绕过它,你需要改变保护。这只能在页面对齐的内存上执行,因此您可能需要将代码更改为以下内容:
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?)。
答案 1 :(得分: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))
答案 2 :(得分: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
答案 3 :(得分:0)
python甚至允许这样的使用吗?我应该学习它......
我认为解释器不希望任何寄存器被更改。如果打算像这样使用汇编器输出,请尝试保存函数内使用的寄存器。
顺便说一句,x86_64的调用约定与常规x86不同。如果丢失堆栈指针对齐并混合使用其他工具生成的外部对象,则可能会遇到麻烦。
答案 4 :(得分:0)
我认为只有最简单的方法,但最近不涉及mprotect。明确地直接mmap程序的可执行空间。这些天python有一个完成这个的模块,虽然我没有找到获取代码地址的方法。简而言之,您将分配内存调用mmap而不是使用字符串缓冲区并间接设置执行标志。这样更简单,更安全,您可以确保现在只能执行您的代码。