【问题标题】:x86 Assembly: Data in the Text Sectionx86 汇编:文本部分中的数据
【发布时间】:2017-09-13 16:58:29
【问题描述】:

我不太明白如何将变量存储在文本部分以及如何操作它们。不应该所有变量都在 .data 部分中,并且不是 .text 部分的所有部分都是只读的吗?那么这段代码是如何工作的呢?

[代码取自 Shellcoder 手册]

Section .text
global _start

_start:
    jmp short GotoCall

shellcode:
    pop esi
    xor eax, eax
    mov byte [esi + 7], al
    lea ebx, [esi]
    mov long [esi + 8], ebx
    mov long [esi + 12], eax
    mov byte al, 0x0b
    mov ebx, esi
    lea ecx, [esi + 8]
    lea edx, [esi + 12]
    int 0x80

GotoCall:
    call shellcode
    db '/bin/shJAAAAKKKK'

【问题讨论】:

  • 漏洞利用的常见攻击向量是目标应用程序的堆栈内存。因此,此代码很可能通过某些缓冲区溢出情况存储在堆栈中,而缓冲区分配在堆栈上作为局部变量。所以写入那部分内存不是问题,执行堆栈数据可能会更有问题,some OS 将堆栈内存标记为不可执行(至少对于要求它的应用程序),然后这样的利用需要打破它第一的。如果这会在现代操作系统上进入普通的.text,它将在mov [esi + 7], al 上失败。
  • 但是应用程序本身(或利用有效负载工作而无需编写任何内容)可能会要求操作系统对内存的 [可执行] 部分进行写访问,特别是复杂的“公司”应用程序喜欢以奇怪的方式加载其他模块, 有时甚至通过网络动态下载代码片段,因此毒化这些代码通常比简单的“hello world”示例更容易。
  • 最后要注意的是..代码可以很容易地重写,首先将该字符串复制到堆栈中并在那里修补+使用它,所以您所需要的只是将您的有效负载交付到可执行内存中,堆栈是 100% 可写的并准备好用于临时数据 = 问题已解决。
  • 相关:segmentation fault with .text .data and main (main in .data section) 用于相反的问题。 (请注意,除非您在使用 NASM 时小心谨慎,否则 .data 实际上可能是可执行的。
  • 相关:x86_64 Assembly - Segfault when trying to edit a byte within an array in x64 assembly 用于尝试写入 .text 部分中的数组的段错误。

标签: assembly x86 shellcode


【解决方案1】:

好吧,数据和代码只是字节。只有你如何解释它们才能使它们成为它们的样子。代码可以解释为数据,反之亦然。在大多数情况下,它会产生无效的东西,但无论如何它是可能的。

节的属性依赖于链接器,它们中的大多数默认使.text节RO,但这并不意味着它不能改变。

整个例子是一个巧妙的方法来获取/bin/sh的地址,只需要使用call。基本上,call 将下一条指令的地址(下一个字节)放在堆栈上,在这种情况下,它将是该字符串的地址,因此 pop esi 将从堆栈中获取该地址并使用它。

【讨论】:

  • 编译器将只读数据(如static const char[] = "/bin/sh";)放入.rodata 部分,链接器将其与.text 部分一起放入可执行文件的the text segment。您通常不希望混合代码和常量(因为这会浪费拆分 L1I/L1D 缓存中的空间),但是正如您所指出的,将它们放在同一段的同一页面中是好的。
  • 更新:GNU ld 最近更改为将 .rodata 放在自己的段中,具有读取但 not 执行权限,以更好地防御 ROP 和 Spectre 攻击。
【解决方案2】:

顶级答案是,x86 机器不知道“.text”和“.data”部分。现代 x86 CPU 为操作系统提供了创建具有特定权限(如只读、无执行和读写)的虚拟地址空间的工具。

但是内存的内容只是字节,可以读取、写入或执行,CPU无法猜测内存的哪一部分是数据,哪部分是代码,它会很高兴地执行你指向的任何内容它到。

那些.text/.data/... 部分是编译器、链接器和操作系统(可执行加载器)支持的逻辑结构,它们共同合作为代码准备运行时环境,.text 现在是只读的,并且您需要将可写变量放入.data.bss 或类似的。某些操作系统和配置也可能提供不可执行的堆栈。

操作系统通常也有 API,因此应用程序可以更改权限或内存映射,或者使用它需要的属性分配更多内存(例如,如果 JIT 编译器无法首先将编译后的代码写入内存,那么它们将无处可去,然后执行)。

因此,如果您将在默认配置中使用常见 linux 上的代码示例,则很可能会出现段错误,因为 .text 将是只读的。许多这些“利用”书籍都有完整的专门章节如何为他们的示例编译+设置运行时环境,从而关闭了几个保护(ASLR、NX、...),从而允许他们的示例工作.

然后,真正的漏洞利用通常会使用应用程序中的一些错误/弱点将其有效负载注入某处。根据“某处”的敌意,真正的利用可能必须首先提升其权限以获得可写+可执行内存(或者必须以不写入代码部分并将其他内存用于变量的方式编写),除非应用程序由于其内部需求,它本身已经有了一些友好的开发环境。

请记住,操作系统和应用程序的编写方式并非确保漏洞能够正常工作,恰恰相反。每个漏洞通常针对特定版本的操作系统上的特定版本的应用程序,这是易受攻击的,并且预计它会在稍后的安全更新中破坏。因此,如果您知道您有可写和可执行的内存,您只需按原样利用它,而不必担心下一个版本会发生什么,届时他们将修复应用程序以保持其代码内存 RO。

【讨论】:

  • x86 可能不知道部分,但它知道 segments(在 ELF 意义上,而不是 x86 意义上),并且可以为不同的段允许不同的东西。由于链接器将段映射到段,因此效果实际上就像处理器知道段一样。
猜你喜欢
  • 2022-12-11
  • 1970-01-01
  • 1970-01-01
  • 2020-09-23
  • 1970-01-01
  • 2017-01-23
  • 2021-02-14
  • 1970-01-01
相关资源
最近更新 更多