视频加载失败

课程

22626 字
约 65 分钟

Answer

计算机系统基础exercises/Homework/RainClass/chapter03·更新于 2026-09-15

CSAPP P.197 计算机系统基础---3.9 越界访问和缓冲区溢出 - 知乎

  1. 以下是函数 getline 存在漏洞和问题的 C 语言代码实现,右边是其对应的反汇编部
char *getline()

{

    char buf [8] ;

    char *result ;

    gets(buf) ;

    result = malloc( strlen (buf) ) ;

    strcpy(result, buf) ;

    return result ;

}
1   0804840c <getline>:

2   804840c:    55                                push   %ebp

3   804840d:    89  e5                          mov    %esp,%ebp

4   804840f:    83  ec  28                     sub    $0x28,%esp

5   8048412:    89  5d  f4                     mov    %ebx,-0xc(%ebp)

6   8048415:    89  75  f8                     mov    %esi,-0x8(%ebp)

7   8048418:    89  7d  fc                     mov    %edi,-0x4(%ebp)

8   804841b:    8d  75  ec                     lea    -0x14(%ebp),%esi

9   804841e:    89  34  24                     mov    %esi,(%esp)

10  8048421:    e8  a3  ff  ff  ff            call   80483c9 <gets>

假定过程P调用了函数getline,其返回地址为0x80485 c8,为调用getline函数而执行call指令时,部分寄存器的内容如下: R [ebp] = 0xbffc 0800,R [esp] = 0xbffc 07f0,R [ebx] = 0x5,R[esi] = 0x10,R[edi] = 0x8。 执行程序时从标准输入读入的一行字符串为“0123456789 abcdef0123456789\n”, 此时,程序会发生段错误(segmentation fault)并终止执行,经调试确认错误是在执行getline的ret指令时发生的。 回答下列问题或完成下列任务。

(1)画出执行第7行指令后栈中的信息存放情况。要求给出存储地址和存储内容,并指出存储内容的含义(如返回地址、EBX旧值、局部变量、入口参数等)。

(2)画出执行第10行指令并调用gets函数后回到第10行的下一条指令执行时栈中的信息存放情况。

(3)当执行到getline的ret指令时,假如程序不发生段错误,则正确的返回地址是什么?发生段错误是因为执行getline的ret指令时得到了什么样的返回地址?

(4)执行完gets函数后,哪些寄存器的内容已被破坏?

(5)除了可能发生缓冲区溢出以外,getline的C代码还有哪些错误?

Answer

line 7

地址内容espebp备注
bf fc 08 04
bf fc 08 00程序开始时
bf fc 07 fc
bf fc 07 f8
bf fc 07 f4
bf fc 07 f008 04 85 c8程序开始时返回地址
bf fc 07 ecbf fc 08 00line2line3旧的ebp
bf fc 07 e800 00 00 08edi
bf fc 07 e400 00 00 10esi
bf fc 07 e000 00 00 05ebx
bf fc 07 dc
bf fc 07 d8
bf fc 07 d4
bf fc 07 d0
bf fc 07 cc
bf fc 07 c8
bf fc 07 c4line3
bf fc 07 c0

line 10 执行后

地址内容espebp备注
bf fc 08 04
bf fc 08 00程序开始时
bf fc 07 fc
bf fc 07 f8
bf fc 07 f4
bf fc 07 f008 04 85 c8
00 39 38 37

\0 9 8 7
程序开始时返回地址
bf fc 07 ec36 35 34 33
6 5 4 3
line2line3旧的ebp
bf fc 07 e832 31 30 66
2 1 0 f
edi
bf fc 07 e465 64 63 62
e d c b
esi
bf fc 07 e061 20 39 38
a space 9 8
ebx
bf fc 07 dc37 36 35 34
7 6 5 4
buf[7]buf[4]
bf fc 07 d833 32 31 30
3 2 1 0
(buf数组起始地址)
line 8buf[3]buf[0]
bf fc 07 d4注意小端方式
bf fc 07 d0注意结尾‘\0’
bf fc 07 cc注意strlen不包含空字符
bf fc 07 c8
bf fc 07 c4bf fc 07 d8line3esi
(buf作为函数gets的参数)
line 9
bf fc 07 c0

(3)正确的应该是 0x 08 04 85 c8; 得到了 0x 00 39 38 37 的返回地址

(4)edi esi ebx ,还有EBP的旧值

(5)1.strlen()函数不包含结尾的空字符,导致malloc所分配的空间不足,应为result = malloc(strlen(buf) + 1);

2.代码应检查返回值是否为NULL

https://www.geeksforgeeks.org/fgets-gets-c-language/#

c - What does gets() save when it reads just a newline - Stack Overflow

gets函数 gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF, which it replaces with a null byte (aq\0aq). No check for buffer overrun is performed (see BUGS below).

MY

line 7

地址内容espebp备注
bf fc 08 04
bf fc 08 00程序开始时
bf fc 07 fc
bf fc 07 f8
bf fc 07 f408 04 85 c8返回地址
bf fc 07 f0bf fc 08 00程序开始时旧的ebp
bf fc 07 ecline2line3
bf fc 07 e800 00 00 08edi
bf fc 07 e400 00 00 10esi
bf fc 07 e000 00 00 05ebx
bf fc 07 dc
bf fc 07 d8
bf fc 07 d4
bf fc 07 d0
bf fc 07 cc
bf fc 07 c8
bf fc 07 c4line3
bf fc 07 c0

line 10 执行后

地址内容espebp备注
bf fc 08 04
bf fc 08 00程序开始时
bf fc 07 fc
bf fc 07 f8
bf fc 07 f408 04 85 c8
08 04 85 00
~ ~ ~ \0
返回地址
bf fc 07 f00A 39 38 37
\n 9 8 7
程序开始时旧的ebp
bf fc 07 ec36 35 34 33
6 5 4 3
line2line3
bf fc 07 e832 31 30 66
2 1 0 f
edi
bf fc 07 e465 64 63 62
e d c b
esi
bf fc 07 e061 20 39 38
a space 9 8
ebx
bf fc 07 dc37 36 35 34
7 6 5 4
bf fc 07 d833 32 31 30
3 2 1 0
&~ —> esi
(buf数组起始地址)
line 8
bf fc 07 d4注意小端方式
bf fc 07 d0注意结尾‘\0’
bf fc 07 cc注意strlen不包含空字符
bf fc 07 c8
bf fc 07 c4bf fc 07 d8line3esi
(buf作为函数gets的参数)
line 9
bf fc 07 c0

ASCII:ASCII码 - 基本ASCII码和扩展ASCII码,中文最全ASCII码对照表0~255

1   0804840c <getline>:

2   804840c:    55                                push   %ebp
// 保存调用者的 EBP, esp -= 4 esp = 0x bf fc 07 f0 - 4 = 0x bf fc 07 ec

3   804840d:    89  e5                          mov    %esp,%ebp
// 设置新的 EBP, ebp = esp = 0x bf fc 07 ec

4   804840f:    83  ec  28                     sub    $0x28,%esp
// 为局部变量 buf 分配空间, esp -= 0x28 = 40, esp = 0x bf fc 07 c4

5   8048412:    89  5d  f4                     mov    %ebx,-0xc(%ebp)
// 保存调用者的 EBX, ebp - 0xc = 0x bf fc 07 e0

6   8048415:    89  75  f8                     mov    %esi,-0x8(%ebp)
// 保存调用者的 ESI, ebp - 0x8 = 0x bf fc 07 e4

7   8048418:    89  7d  fc                     mov    %edi,-0x4(%ebp)
// 保存调用者的 EDI, ebp - 0x4 = 0x bf fc 07 e8

8   804841b:    8d  75  ec                     lea    -0x14(%ebp),%esi
// esi = ebp - 0x14 = (0x bf fc 07 d8)

9   804841e:    89  34  24                     mov    %esi,(%esp)
// 将 esi 的值保存, esp -= 4 esp = 0x bf fc 07 c0

10  8048421:    e8  a3  ff  ff  ff            call   80483c9 <gets>
地址内容espebp备注
bf fc 08 04
bf fc 08 00程序开始时
bf fc 07 fc
bf fc 07 f8
bf fc 07 f408 04 85 c8返回地址?
bf fc 07 f0bf fc 08 00程序开始时旧的ebp
bf fc 07 ecline2line3
bf fc 07 e800 00 00 08旧edi
bf fc 07 e400 00 00 10旧esi
bf fc 07 e000 00 00 05旧ebx
bf fc 07 dc
bf fc 07 d8?? ?? ?? ??&~ —> esi
(buf数组起始地址)
line 8
bf fc 07 d4注意小端方式
bf fc 07 d0注意结尾‘\0’
bf fc 07 cc注意strlen不包含空字符
bf fc 07 c8
bf fc 07 c4&? = bf fc 07 d8line3esi
(buf作为函数gets的参数)
line 9
bf fc 07 c0

line 7

地址内容espebp备注
bf fc 08 04
bf fc 08 00程序开始时
bf fc 07 fc
bf fc 07 f8
bf fc 07 f408 04 85 c8返回地址
bf fc 07 f0bf fc 08 00程序开始时旧的ebp
bf fc 07 ecline2line3
bf fc 07 e800 00 00 08edi
bf fc 07 e400 00 00 10esi
bf fc 07 e000 00 00 05ebx
bf fc 07 dc
bf fc 07 d8
bf fc 07 d4
bf fc 07 d0
bf fc 07 cc
bf fc 07 c8
bf fc 07 c4line3
bf fc 07 c0

line 10 执行后

地址内容espebp备注
bf fc 08 04
bf fc 08 00程序开始时
bf fc 07 fc
bf fc 07 f8
bf fc 07 f408 04 85 c8
08 04 85 00
~ ~ ~ \0
返回地址
bf fc 07 f000 39 38 37
\n 9 8 7
程序开始时旧的ebp
bf fc 07 ec36 35 34 33
6 5 4 3
line2line3
bf fc 07 e832 31 30 66
2 1 0 f
edi
bf fc 07 e465 64 63 62
e d c b
esi
bf fc 07 e061 20 39 38
a space 9 8
ebx
bf fc 07 dc37 36 35 34
7 6 5 4
bf fc 07 d833 32 31 30
3 2 1 0
&~ —> esi
(buf数组起始地址)
line 8
bf fc 07 d4注意小端方式
bf fc 07 d0注意结尾‘\0’
bf fc 07 cc注意strlen不包含空字符
bf fc 07 c8
bf fc 07 c4bf fc 07 d8line3esi
(buf作为函数gets的参数)
line 9
bf fc 07 c0

ASCII:ASCII码 - 基本ASCII码和扩展ASCII码,中文最全ASCII码对照表0~255

(3)正确的应该是 0x 08 04 85 c8; 得到了 0x 08 04 85 00 的返回地址

(4)edi esi ebx

(5)1.strlen()函数不包含结尾的空字符,导致malloc所分配的空间不足,应为result = malloc(strlen(buf) + 1);

2.代码应检查返回值是否为NULL

GPT

(1) 执行第 7 行指令后栈中的信息存放情况

第 7 行指令mov %edi,-0x4(%ebp) 将寄存器 EDI 的值保存到栈中 -0x4(%ebp) 的位置。

栈布局:

栈在 ESP 上减少了 0x28 个字节,且按照汇编的调用规则,从低地址到高地址依次存放局部变量、保存的寄存器值和返回地址。

地址内容含义
0xbffc07f40x080485c8返回地址 (caller 的下一指令)
0xbffc07f00xbffc0800调用者的 EBP
0xbffc07ec0x5EBX 的旧值
0xbffc07e80x10ESI 的旧值
0xbffc07e40x8EDI 的旧值
0xbffc07e0-未使用的局部变量空间 (局部变量)

(2) 执行第 10 行指令并调用 gets 函数后,回到第 10 行的下一条指令时栈中的信息存放情况

第 10 行指令call 80483c9 <gets> gets 函数读取输入,并存储在 buf 对应的内存中。

输入数据 “0123456789 abcdef0123456789\n” 被 gets 写入后:

地址内容含义
0xbffc07f40x080485c8返回地址
0xbffc07f00xbffc0800调用者的 EBP
0xbffc07ec0x5EBX 的旧值
0xbffc07e80x10ESI 的旧值
0xbffc07e40x8EDI 的旧值
0xbffc07d4"0123456789 abcdef0123456789\0"buf 内容,覆盖了内存

缓冲区溢出情况: 输入字符串超出了 buf 的大小(8 字节),导致覆盖了 EBP 和返回地址等关键数据。


(3) getline 的 ret 指令:正常返回地址与错误返回地址

正常返回地址: 在 getlineret 指令执行时,返回地址应为 0x080485c8(caller 的下一指令地址)。

错误返回地址: 由于缓冲区溢出,gets 将输入覆盖了返回地址,实际返回地址被写成了字符串的一部分(如 "abcdef01" 对应的地址可能为 0x61626364)。这是导致段错误的原因。


(4) 执行完 gets 函数后,哪些寄存器的内容已被破坏?

gets 函数会:

  • 改变寄存器 EAX(存储结果指针)。
  • 改变寄存器 ECXEDX(临时使用)。
  • 其他寄存器可能在函数调用期间被调用者保存,但需注意缓冲区溢出会破坏栈中的内容,包括保存的 EBP

(5) 除了缓冲区溢出,getline 的 C 代码还有哪些错误?

  1. malloc 的大小错误malloc(strlen(buf)) 未包含字符串末尾的 \0,导致内存分配不足。应改为 malloc(strlen(buf) + 1)
  2. 未验证 malloc 成功malloc 返回可能为 NULL,应检查返回值是否有效。
  3. 未释放内存: 返回的指针需要在调用方手动释放,否则会导致内存泄漏。
  4. gets 函数的危险性gets 不会检查输入大小,应改用 fgets 限制最大输入长度。

修正后的安全版本:

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

char *getline_safe() {
    char buf[8];
    char *result;

    if (!fgets(buf, sizeof(buf), stdin)) {
        return NULL; // 输入失败
    }

    // 去除换行符
    buf[strcspn(buf, "\n")] = '\0';

    // 为字符串分配内存,并包含 '\0'
    result = malloc(strlen(buf) + 1);
    if (!result) {
        perror("malloc failed");
        exit(EXIT_FAILURE);
    }

    strcpy(result, buf);
    return result;
}

Copilot

(1) 执行第7行指令后栈中的信息存放情况

执行第7行指令后,栈中的信息如下:

地址内容含义
0xbffc07f00x80485c8返回地址
0xbffc07f40xbffc0800调用者的 EBP
0xbffc07f80x5调用者的 EBX
0xbffc07fc0x10调用者的 ESI
0xbffc08000x8调用者的 EDI
0xbffc08040x804841b局部变量 buf 的地址

(2) 执行第10行指令并调用gets函数后回到第10行的下一条指令执行时栈中的信息存放情况

执行第10行指令并调用 gets 函数后,栈中的信息如下:

地址内容含义
0xbffc07f00x80485c8返回地址
0xbffc07f40xbffc0800调用者的 EBP
0xbffc07f80x5调用者的 EBX
0xbffc07fc0x10调用者的 ESI
0xbffc08000x8调用者的 EDI
0xbffc08040x804841b局部变量 buf 的地址
0xbffc0808‘0’buf 的内容
0xbffc0809‘1’buf 的内容
0xbffc080a‘2’buf 的内容
0xbffc080b‘3’buf 的内容
0xbffc080c‘4’buf 的内容
0xbffc080d‘5’buf 的内容
0xbffc080e‘6’buf 的内容
0xbffc080f‘7’buf 的内容
0xbffc0810‘8’buf 的内容
0xbffc0811‘9’buf 的内容
0xbffc0812’ ’buf 的内容
0xbffc0813‘a’buf 的内容
0xbffc0814‘b’buf 的内容
0xbffc0815‘c’buf 的内容
0xbffc0816‘d’buf 的内容
0xbffc0817‘e’buf 的内容
0xbffc0818‘f’buf 的内容
0xbffc0819‘0’buf 的内容
0xbffc081a‘1’buf 的内容
0xbffc081b‘2’buf 的内容
0xbffc081c‘3’buf 的内容
0xbffc081d‘4’buf 的内容
0xbffc081e‘5’buf 的内容
0xbffc081f‘6’buf 的内容
0xbffc0820‘7’buf 的内容
0xbffc0821‘8’buf 的内容
0xbffc0822‘9’buf 的内容
0xbffc0823‘\n’buf 的内容

(3) 当执行到getline的ret指令时,假如程序不发生段错误,则正确的返回地址是什么?发生段错误是因为执行getline的ret指令时得到了什么样的返回地址?

  • 正确的返回地址应该是 0x80485c8
  • 发生段错误是因为执行 getlineret 指令时,返回地址被 gets 函数读取的超长输入覆盖,导致返回地址变成了无效地址。

(4) 执行完gets函数后,哪些寄存器的内容已被破坏?

  • 执行完 gets 函数后,eaxecxedx 寄存器的内容可能已被破坏,因为这些是调用者保存寄存器。

(5) 除了可能发生缓冲区溢出以外,getline的C代码还有哪些错误?

  • malloc 的大小应该是 strlen(buf) + 1,以确保有足够的空间存储字符串和终止符 \0
  • gets 函数本身是不安全的,应该使用 fgets 或其他安全的输入函数来避免缓冲区溢出。
Profile Image of the Author
Sonder
好想要技术
这是公告标题
这只是一个公告
分类
标签
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.16.7
文章许可
CC BY-NC-SA 4.0
文章目录