@amwps290
This commit is contained in:
Xingyu Wang 2021-07-05 10:38:09 +08:00
parent 024dd65cdd
commit 1734a7cc07

View File

@ -2,15 +2,17 @@
[#]: via: "https://jvns.ca/blog/2021/05/17/how-to-look-at-the-stack-in-gdb/"
[#]: author: "Julia Evans https://jvns.ca/"
[#]: collector: "lujun9972"
[#]: translator: " "
[#]: reviewer: " "
[#]: translator: "amwps290"
[#]: reviewer: "wxy"
[#]: publisher: " "
[#]: url: " "
使用 GDB 查看程序栈空间
使用 GDB 查看程序栈空间
======
昨天我和一些人在闲聊的时候,他们说他们并不了解栈是如何工作的,而且也不知道如何去查看栈空间。
![](https://img.linux.net.cn/data/attachment/album/202107/05/103738f00boylephggpeyh.jpg)
昨天我和一些人在闲聊的时候,他们说他们并不真正了解栈是如何工作的,而且也不知道如何去查看栈空间。
这是一个快速教程,介绍如何使用 GDB 查看 C 程序的栈空间。我认为这对于 Rust 程序来说也是相似的。但我这里仍然使用 C 语言,因为我发现用它更简单,而且用 C 语言也更容易写出错误的程序。
@ -51,13 +53,13 @@ int main() {
### 第一步:启动 GDB
像这样启动 GDB
像这样启动 GDB
```
$ gdb ./test
```
它打印出一些 GPL 东西,然后给出一个提示符。让我们在 `main` 函数这里设置一个断点
它打印出一些 GPL 信息,然后给出一个提示符。让我们在 `main` 函数这里设置一个断点
```
(gdb) b main
@ -83,7 +85,6 @@ Breakpoint 1, main () at test.c:4
让我们从了解我们的变量开始。它们每个都在内存中有一个地址,我们可以像这样打印出来:
```
(gdb) p &x
$3 = (int *) 0x7fffffffe27c
@ -93,13 +94,13 @@ $2 = (char **) 0x7fffffffe280
$4 = (char (*)[10]) 0x7fffffffe28e
```
因此,如果我们查看栈的地址,那我们应该能够看到所有的这些变量!
因此,如果我们查看那些地址的堆栈,那我们应该能够看到所有的这些变量!
### 概念:栈指针
我们将需要使用栈指针,因此我将尽力对其进行快速解释。
有一个名为 ESP 的 x86 寄存器,称为“栈指针”。 基本上,它是当前函数的栈起始地址。 在 GDB 中,您可以使用 `$sp` 来访问它。 当您调用新函数或从函数返回时,栈指针的值会更改。
有一个名为 ESP 的 x86 寄存器,称为“<ruby>栈指针<rt>stack pointer</rt></ruby>”。 基本上,它是当前函数的栈起始地址。 在 GDB 中,你可以使用 `$sp` 来访问它。 当你调用新函数或从函数返回时,栈指针的值会更改。
### 第三步:在 `main` 函数开始的时候,我们查看一下在栈上的变量
@ -128,19 +129,17 @@ $7 = (void *) 0x7fffffffe270
0x7fffffffe300: 0xf9ce816d 0x7533d7c8 0xa91a816d 0x7533c789
```
我已粗体显示了 stack_stringheap_string 和 x 变量的位置,并改变了颜色(译者注:可以在原网页查看)
我已粗体显示了 `stack_string``heap_string``x` 变量的位置,并改变了颜色:
* `x` 是红色字体,并且起始地址是 `0x7fffffffe27c`
* `heap_string` 是蓝色字体,起始地址是 `0x7fffffffe280`
* `stack_string` 是紫色字体,起始地址是 `0x7fffffffe28e`
我想在此处将一些变量的位置加粗了一点点可能会有点偏差,但这大概是它们所在的位置。
您可能会在这里注意到的一件奇怪的事情是 x 的值是 0x5555但是我们将 x 设置为 10 那是因为直到我们的 `main` 函数运行之后才真正设置 `x` ,而我们现在才到了 `main` 最开始的地方。
你可能会在这里注意到的一件奇怪的事情是 `x` 的值是 0x5555但是我们将 `x` 设置为 `10` 那是因为直到我们的 `main` 函数运行之后才真正设置 `x` ,而我们现在才到了 `main` 最开始的地方。
### 第三步:运行到第十行代码后,再次查看一下我们的堆栈
让我们跳过几行,等待变量实际设置为其初始化值。 到第 10 行时,`x` 应该设置为10。
让我们跳过几行,等待变量实际设置为其初始化值。 到第 10 行时,`x` 应该设置为 `10`
首先我们需要设置另一个断点:
@ -159,15 +158,13 @@ Breakpoint 2, main () at test.c:11
11 printf("Enter a string for the stack: ");
```
好的! 让我们再来看看堆栈里的内容! `gdb` 在这里格式化字节的方式略有不同,实际上我也不太关心这些(译者注:可以查看 GDB 手册中 x 命令,可以指定 c 来控制输出的格式)。 这里提醒一下您,我们的变量在栈上的位置:
好的! 让我们再来看看堆栈里的内容! `gdb` 在这里格式化字节的方式略有不同,实际上我也不太关心这些LCTT 译注:可以查看 GDB 手册中 `x` 命令,可以指定 `c` 来控制输出的格式)。 这里提醒一下你,我们的变量在栈上的位置:
* `x` 是红色字体,并且起始地址是 `0x7fffffffe27c`
* `heap_string` 是蓝色字体,起始地址是 `0x7fffffffe280`
* `stack_string` 是紫色字体,起始地址是 `0x7fffffffe28e`
```
(gdb) x/80x $sp
0x7fffffffe270: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
@ -186,9 +183,9 @@ Breakpoint 2, main () at test.c:11
### `stack_string` 在内存中是如何表示的
现在(第10行`stack_string` 被设置为 “stack”。 让我们看看它在内存中的表示方式。
现在(第 10 行),`stack_string` 被设置为字符串`stack`。 让我们看看它在内存中的表示方式。
我们可以像这样打印出字符串中的字节(译者注:可以通过 c 选项直接显示为字符)
我们可以像这样打印出字符串中的字节LCTT 译注:可以通过 `c` 选项直接显示为字符)
```
(gdb) x/10x stack_string
@ -196,8 +193,7 @@ Breakpoint 2, main () at test.c:11
0x7fffffffe296: 0x00 0x00
```
"stack" 是一个长度为 5 的字符串,相对应 5 个 ASCII 码-`0x73`, `0x74`, `0x61`, `0x63`, 和 `0x6b`。`0x73` 是字符 `s` 的 ASCII 码。 `0x74``t` 的 ASCII 码。等等...
`stack` 是一个长度为 5 的字符串,相对应 5 个 ASCII 码- `0x73`、`0x74`、`0x61`、`0x63` 和 `0x6b`。`0x73` 是字符 `s` 的 ASCII 码。 `0x74``t` 的 ASCII 码。等等...
同时我们也使用 `x/1s` 可以让 GDB 以字符串的方式显示:
@ -206,13 +202,11 @@ Breakpoint 2, main () at test.c:11
0x7fffffffe28e: "stack"
```
### `heap_string``stack_string` 有何不同
你已经注意到了 `stack_string``heap_string` 在栈上的表示非常不同:
* `stack_string` 是一段字符串内容("stack")
* `stack_string` 是一段字符串内容`stack`
* `heap_string` 是一个指针,指向内存中的某个位置
这里是 `heap_string` 变量在内存中的内容:
@ -221,7 +215,7 @@ Breakpoint 2, main () at test.c:11
0xa0 0x92 0x55 0x55 0x55 0x55 0x00 0x00
```
这些字节实际上应该是从右向左读:因为 X86 是小端模式,因此,`heap_string` 中所存放的内存地址 `0x5555555592a0`
这些字节实际上应该是从右向左读:因为 x86 是小端模式,因此,`heap_string` 中所存放的内存地址 `0x5555555592a0`
另一种方式查看 `heap_string` 中存放的内存地址就是使用 `p` 命令直接打印
@ -234,9 +228,9 @@ $6 = 0x5555555592a0 ""
`x` 是一个 32 位的整数,可由 `0x0a 0x00 0x00 0x00` 来表示。
我们还是需要反向来读取这些字节(和我们读取 `heap_string` 需要反过来读是一样的),因此这个数表示的是 `0x000000000a` 或者是 `0x0a`, 它是一个 10;
我们还是需要反向来读取这些字节(和我们读取 `heap_string` 需要反过来读是一样的),因此这个数表示的是 `0x000000000a` 或者是 `0x0a`,它是一个数字 `10`;
这就让我把把 x 设置成了 10
这就让我把把 `x` 设置成了 `10`
### 第四步:从标准输入读取
@ -256,7 +250,6 @@ gets(heap_string);
Breakpoint 3 at 0x555555555205: file test.c, line 16.
```
然后继续执行程序:
```
@ -266,14 +259,14 @@ Continuing.
我们输入两个字符串,为栈上存储的变量输入 `123456789012` 并且为在堆上存储的变量输入 `bananas`;
### 让我们先来看一下 `stack_string` (这里有一个缓存区溢出)
### 让我们先来看一下 `stack_string`(这里有一个缓存区溢出)
```
(gdb) x/1s stack_string
0x7fffffffe28e: "123456789012"
```
这看起来相当正常,对吗?我们输入了 `12345679012`,然后现在它也被设置成了 `12345679012`(译注:实测 gcc 8.3 环境下,会直接段错误)。
这看起来相当正常,对吗?我们输入了 `12345679012`,然后现在它也被设置成了 `12345679012`LCTT 译注:实测 gcc 8.3 环境下,会直接段错误)。
但是现在有一些很奇怪的事。这是我们程序的栈空间的内容。有一些紫色高亮的内容。
@ -290,7 +283,7 @@ Continuing.
0x7fffffffe2b8: 0x00 0xa0 0xfc 0xf7 0x01 0x00 0x00 0x00
```
令人奇怪的是 **stack_string 只支持 10 个字节**。但是现在当我们输入了 13 个字符以后,发生了什么?
令人奇怪的是 **`stack_string` 只支持 10 个字节**。但是现在当我们输入了 13 个字符以后,发生了什么?
这是一个典型的缓冲区溢出,`stack_string` 将自己的数据写在了程序中的其他地方。在我们的案例中,这还没有造成问题,但它会使你的程序崩溃,或者更糟糕的是,使你面临非常糟糕的安全问题。
@ -313,7 +306,7 @@ fish: Job 1, './test' terminated by signal SIGABRT (Abort)
这里我猜是 `stack_string` 已经到达了这个函数栈的底部,因此额外的字符将会被写在另一块内存中。
当你故意去使用这个安全漏洞时,它被称为“堆栈粉碎”,我也不完全了解这是如何检测到的
当你故意去使用这个安全漏洞时,它被称为“堆栈粉碎”,而且不知何故有东西在检测这种情况的发生
我也觉得这很有趣,虽然程序被杀死了,但是当缓冲区溢出发生时它不会立即被杀死——在缓冲区溢出之后再运行几行代码,程序才会被杀死。 好奇怪!
@ -348,7 +341,7 @@ fish: Job 1, './test' terminated by signal SIGABRT (Abort)
我们已经讨论过栈和堆是不同的内存区域,但是你怎么知道它们在内存中的位置呢?
每个进程都有一个名为 `/proc/$PID/maps` 的文件,它显示了每个进程的内存映射。 在这里可以看到其中的栈和堆。
每个进程都有一个名为 `/proc/$PID/maps` 的文件,它显示了每个进程的内存映射。 在这里可以看到其中的栈和堆。
```
$ cat /proc/24963/maps
@ -374,9 +367,7 @@ $ cat /proc/24963/maps
* 从函数返回一个指向栈上字符串的指针,看看哪里出了问题。 为什么返回指向栈上字符串的指针是不好的?
* 尝试在 C 中引起堆栈溢出,并尝试通过在 gdb 中查看堆栈溢出来准确理解会发生什么!
* 查看 Rust 程序中的堆栈并尝试找到变量!
* 在 [噩梦课程][1] 中尝试一些缓冲区溢出挑战。每个问题的答案写在 README 文件中,因此如果您不想被宠坏,请避免先去看答案。 所有这些挑战的想法是给你一个二进制文件,你需要弄清楚如何导致缓冲区溢出以使其打印出 “flag” 字符串。
* 在 [噩梦课程][1] 中尝试一些缓冲区溢出挑战。每个问题的答案写在 README 文件中,因此如果你不想被宠坏,请避免先去看答案。 所有这些挑战的想法是给你一个二进制文件,你需要弄清楚如何导致缓冲区溢出以使其打印出 `flag` 字符串。
--------------------------------------------------------------------------------
@ -385,7 +376,7 @@ via: https://jvns.ca/blog/2021/05/17/how-to-look-at-the-stack-in-gdb/
作者:[Julia Evans][a]
选题:[lujun9972][b]
译者:[amwps290](https://github.com/amwps290)
校对:[校对者ID](https://github.com/校对者ID)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出