我的朋友制作了一个在x86上工作的小型概念验证汇编程序.我决定将它移植到x86_64,但我立即遇到了问题.
我在C中编写了一小段程序,然后编译并objdumped代码.之后我将它插入到我的python脚本中,因此x86_64代码是正确的:
from ctypes import cast, CFUNCTYPE, c_char_p, c_long buffer = ''.join(map(chr, [ #0000000000000000: 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段是否被写保护不是很有帮助吗?
正如vincent所提到的,这是由于分配的页面被标记为不可执行.较新的处理器支持此功能,并且它被支持它的操作系统用作增加的安全层.这个想法是为了防止某些缓冲区溢出攻击.例如.常见的攻击是溢出堆栈变量,重写返回地址以指向您插入的代码.使用不可执行的堆栈,现在只产生段错误,而不是控制进程.堆内存也存在类似的攻击.
要绕过它,你需要改变保护.这只能在页面对齐的内存上执行,因此您可能需要将代码更改为如下所示:
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?).
与我的朋友做了一些研究,发现这是一个特定于平台的问题.我们怀疑在某些平台上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: 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))