我能否执行存储在数据段中的代码(ELF二进制文件)?

3
为了理解二进制(虚拟内存布局、执行等等),我编写了一个 C 代码,声明了一个包含可执行代码字节的全局字符串,然后通过在 main() 函数中声明一个指针 PTR,它是栈上保留的本地内存区域,距离 main() 函数的返回地址2个 WORDS。因此,我所做的就是将返回地址的地址分配给该指针 (PTR = (int*)&PTR+2),然后覆盖该地址的内容为可执行代码的地址(静态字符串)。

现在的问题是,每当我编译并执行时,都会收到“segmentation fault”的错误消息。

可执行代码不进行任何内存输入/输出操作(只是一堆 NOPs)。

使用 GDB 确认过程完美无误:返回地址已更改为字符串的地址,但返回从未发生。

我知道可执行代码被映射到虚拟内存中标记为 RW 的页面上(.data 和 .bss 段),因此可能没有办法执行这样的代码,除非将代码注入到可执行的内存区域(标记为 RE 的页面)中。这是我对这个问题的理论,欢迎您提供更多详细信息。
char code[]="\x90\x90\x90\x90\x90\x90\x90\x90"; //a static string contains executable code

int main()
{
int *return_address; //Pointer to the return address - uninitialized
return_address = (int *)&return_address + 2; //Initializing the return address - according to stack layout
(*return_address) = (int)code; //Overwriting the return address with the code's address
}

你可能需要在调用mprotect系统调用时添加PROT_EXEC标志... 在许多体系结构上(包括x86),数据页面上没有x权限,可以通过在应用程序运行时(在gdb中停止)读取/proc/$PID/maps文件来检查Linux中的内存映射,其中$PID是目标进程的pid。您将看到具有rw-权限的数据/bss段已加载。 - osgx
你看过旧内核上的地图文件了吗?这里有一篇关于技术的维基页面:https://en.wikipedia.org/wiki/Executable_space_protection,需要在CPU和内核中使用“NX bit” / “XD bit”技术来为硬件的页面级翻译添加“可执行”位(操作系统可能具有3位rwx权限;但硬件没有x位)。在32位x86模式下也应该开启PAE。(详细信息请参见https://en.wikipedia.org/wiki/NX_bit) - osgx
afr0ck,你真正的问题是什么?你还需要更详细的链接吗? - osgx
链接器可能会更改节的标志:https://dev59.com/_Wsz5IYBdhLWcg3wfn0x - osgx
如果您想关闭问题并标记为已解决,可以选择(或不选择)点击正确答案左侧的“V”形按钮(接受它)。或者您可能希望等待其他人提供更好的答案。 - osgx
显示剩余5条评论
1个回答

3
我收到了一个分段错误。
这是数据执行预防的硬件控制 (https://en.wikipedia.org/wiki/Executable_space_protection#Linux) - 如果在页表中没有设置 'x' (执行) 位,就不能跳转到数据页。所有位都被列为可写代码的内存映射,在/proc/$pid/maps / /proc/$pid/smaps文件中,'rw-'表示无法执行的数据,'r--'表示只读数据,'r-x'表示普通代码。
如果想要执行数据,应该对希望成为代码的数据部分调用mprotect系统调用,并使用PROT_EXEC标志。
在x86世界中,这被完全实现为Pentium 4(Prescott)及更新版本(Core、Core2、Core i*、core m)/ Athlon 64 / Opteron及其更新版本的 "NX bit" / "XD bit" feature 特性。如果操作系统以32位模式工作,则必须启用PAE才能在页面表中拥有此位。在x86_64模式(64位)下始终支持NX/XD位。
关于支持的第一个变体是在2004年左右添加到linux中的:http://linuxgazette.net/107/pramode.html 在2007年,您可能会遇到过时的硬件、旧内核或未启用PAE的32位模式内核。
有关NX/XD位的信息:https://en.wikipedia.org/wiki/NX_bit 有时“rwx”模式可能会被禁止,请检查https://en.wikipedia.org/wiki/W^X

对于NX系统之前的系统,基于x86的段寄存器有解决方案,可以部分禁用内存空间的执行。

我是否可以在不出现分段错误的情况下执行上述程序?

你可以:

  • 通过调用mprotect使数据页可执行,使用PROT_READ|PROT_EXEC
  • 将elf文件的数据段标记为可执行(需要深入hack ld脚本 - 默认值在ld --verbose中)
  • 使所有页面包括.data和堆栈都可执行(而不仅仅是堆栈)
    使用ld或gcc的-z execstack
  • 将shellcode移动到elf文件的文本数据中
  • 尝试在内核中禁用nx/xd位(难度大,可能需要重新编译)
  • 使用未启用PAE选项的32位操作系统(内核)(构建时选项)。
  • 使用较旧的没有NX/XD功能的CPU

PS:根据法律,有人可能(但不应该)使用spray在可执行页面中获取自己写的代码在远程进程中运行(https://en.wikipedia.org/wiki/JIT_spraying)。这将无法在RISC和Google NaCL上工作(它们没有可变长度指令编码或跳转到指令中间),也无法在具有反spray功能的现代JIT上工作。 - osgx
Ubuntu的NX/XD页面:https://wiki.ubuntu.com/Security/Features#nx “64位和32位-server以及-generic-pae内核都使用PAE寻址编译。从Ubuntu 9.10开始,在32位内核上运行时,对于缺少NX的处理器,部分模拟了这种保护措施..从Ubuntu 11.04开始,内核忽略BIOS NX设置。” 还有颜色矩阵。 - osgx
1
原来使用 mprotect 是非常容易的,只需要通过起始地址和长度指定一个区域即可。唯一需要注意的是起始地址必须是页面对齐的(addr & ~4095)。请注意,当步入 mprotect 调用时,GDB(至少是 rr)会出现异常。 - fuujuhi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接