找不到堆栈上的变量

逆向工程 字符串 漏洞分析
2021-06-17 02:26:02

我无法在堆栈上找到变量。我正在使用 Fedora 25 x64,但使用 32 位程序,顺便说一句。

C程序:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

char* text1 = "AAAAAAAAAAAAA";
int target;
char* text2 = "BBBBBBBBBBBBB";
void vuln(char *string)
{

  printf(string);

  if(target) {
      printf("you have modified the target :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
  printf("Program name: %s", argv[0]);
}

/* 在进入 main 并执行 x/500wx $esp 以检查 gdb 中的堆栈后,这是一个片段:

0xffffcc00: 0x00000000  0x00000000  0x00000000  0xf7fe0ca0
0xffffcc10: 0xf7df12af  0xf7fd765f  0x00000000  0x00000000
0xffffcc20: 0x00000000  0x00000000  0x00000000  0x0d696910
0xffffcc30: 0x00000000  0x00000000  0xf7fe0b79  0xf7de1ef0
0xffffcc40: 0x00000961  0xf7fd13d8  0x7c96f087  0xf7fe1399
0xffffcc50: 0x00000001  0x00000004  0xf7deb534  0x00000961
0xffffcc60: 0xf7deb604  0xf7fd13d8  0xffffccbc  0xffffccb8
0xffffcc70: 0x00000003  0x00000000  0xf7ffcfcc  0xf7fd764c
0xffffcc80: 0xf7deb534  0x7c96f087  0xf7deb604  0xf7de1f12
0xffffcc90: 0x03e4b784  0xffffccb8  0xffffcd48  0x00000961
0xffffcca0: 0x00000000  0x00000000  0x00000000  0x00000000
0xffffccb0: 0x00000000  0x00000000  0x00000000  0x00000000
(gdb) print &text1
$2 = (char **) 0x804a01c <text1>
(gdb) print text1
$3 = 0x8048544 'A' <repeats 13 times>
(gdb) 

如您所见,我的 text1 变量的地址在该0x804区域中,而我的堆栈在该区域中,0xffffcc这就是我完全迷失的原因。您可能会看到我正在尝试做的事情,但我正在尝试定位 0x414141 和目标,然后是 0x42424242,但是在 esp 附近的堆栈区域中的任何地方都没有 41s 或 42s。我目前正在对格式字符串漏洞进行自我教育,但此时,我什至无法在堆栈中找到变量。有什么我想念的吗?谢谢。

2个回答

您无法text1在程序运行时堆栈上定位的原因是,在运行时text1data在虚拟内存中运行的进程段中,而不是堆栈中。为了将引用text1写入堆栈,text1必须将其作为参数传递给被调用的函数。

当一个函数被调用并在该函数的运行时堆栈上创建一个新的堆栈帧时,堆栈上的内存也会分配给该函数中声明的任何局部变量。然而text1text2并且target是全局变量声明的任何函数之外。这样做的直接后果是不会在堆栈上text1text2分配内存 target相反,text1并且text2将在进程的data段中,并且target将在bss段中。为了理解原因,熟悉 ELF 和System V 应用程序二进制接口是必不可少的。

虚拟内存中的 x86 Linux 进程布局

对于某些情况,以下是 Gustavo Duarte 题为“内存中程序剖析”的文章中 x86 Linux 系统虚拟内存中进程布局的图表

虚拟内存中的进程布局

查看此图将有助于阐明您所看到的内存地址的重要性。在 x86 Linux 系统上,堆栈在虚拟内存中位于高位并向下增长。这就是为什么在检查堆栈时会看到诸如0xffffcc10and 之类的内存地址的原因0xffffccb0全局变量在虚拟内存中的位置text1text2并且target会更靠近程序入口点,因为databss段与text内存低相邻鉴于此,0x804a01cfor的内存地址是text1有意义的。

ELF 和 System V ABI

来自 ABI 的第 4 节(目标文件):

一个可执行文件包含一个适合执行的程序;该文件指定函数如何exec创建程序的过程映像。

目标文件由汇编器和链接编辑器创建,是旨在直接在处理器上执行的程序的二进制表示。

一旦程序被编译、组装和链接,它本质上就是对它作为一个过程的样子的描述。这意味着可以对可执行二进制文件进行静态分析,以了解程序运行时的情况。当分析从上面提供的源代码构建的二进制文件时,可以观察到值text1text2位于以下.rodata部分:

$ readelf -x .rodata <ELF BINARY NAME>

Hex dump of section '.rodata':
  0x08048538 03000000 01000200 41414141 41414141 ........AAAAAAAA
  0x08048548 41414141 41004242 42424242 42424242 AAAAA.BBBBBBBBBB
  0x08048558 42424200 796f7520 68617665 206d6f64 BBB.you have mod
  0x08048568 69666965 64207468 65207461 72676574 ified the target
  0x08048578 203a2900 50726f67 72616d20 6e616d65  :).Program name
  0x08048588 3a202573 00                         : %s.

根据 ABI (4-19),该.rodata部分保存只读数据,这些数据通常对过程映像中的不可写段有贡献。不可写过程映像段的示例是上面提到textdata段。这意味着当程序被加载到内存时text1并且text2将位于程序指令所在的位置附近,即text段。指令内存地址与堆栈上的内存地址text1内存地址看起来更相似text2

target变量是一个未初始化的全局变量,因此其数据将保存在bss段中。

函数的参数写入堆栈

如果您希望指向这些变量的指针出现在运行时堆栈中,则必须将它们作为参数传递给在整个流程执行过程中的某个时间点调用的函数,因为函数的参数通常在调用者的参数构建中写入堆栈区域,如下图所示。

具有多个框架的堆栈布局(来自CSAPP):

http://csapp.cs.cmu.edu/2e/ics2/asm/frame.ppt

例如,不是argv[1]作为参数传递vuln函数,text1而是传递全局变量text1然后在vuln被调用之前将指向的指针保存在堆栈中

或者,在 shell 中执行程序时,您可以将任意数量的“A”(或任何其他 ASCII 字符)作为参数传递给命令行,而不是将“A”硬编码为全局变量的值。这将导致您传递的任何值都存储在argv[1]其中作为vuln.

应该注意的是,由于它们的全局范围,text1,text2data可以在任何函数中引用而无需作为参数传递,但在格式字符串漏洞的上下文中,printf 这并不是特别有用。

想要查询更多的信息

有关printfx86 Linux 环境中行为方式的更多信息,可以查看有关 stackoverflow 的以下问题的答案,其中用户printf以非标准方式调用“ELF32 binary, little endian or not?”

“计算机系统:程序员的视角”中的第 3.7 节(标题为“过程”)涵盖了汇编级别的函数调用和堆栈,并提供了几个有用的图表。

当您转储堆栈时,您主要在哪里?这些变量是全局初始化变量,它们不会在堆栈上,直到它们在函数调用之前被推送到那里。在此之前,它们将位于程序的 bss 或 data 部分。打印 main 的反汇编,您应该会在调用函数之前看到对您的字符串的引用被压入堆栈。这可能是 push op 或 mov op btw,具体取决于您的编译器设置。