32位汇编语言学习笔记(19)--缓冲区溢出实验

时间:2022-08-09 01:28:20


如下示例代码,摘录自《深入理解计算机系统》:

#include <stdio.h>

#include <stdlib.h>

 

size_t strlen(const char *s)

{

 intlen = 0;

 while (*(s++))

   len++;

 return len;

}

 

char *strcpy(char *dest, const char *src)

{

 char *result = dest;

 char c;

 do{

   c= *(src++);

   *(dest++) = c;

 }while (c);

 return result;

}

 

char *getline()

{

   char buf[8];

   char *result;

   gets(buf);

   result= malloc(strlen(buf));

   strcpy(result, buf);

   return(result);

}

 

int main(int argc, char *argv[])

{

  printf("Input>");

  puts(getline());

  return 0;

}

标黄色的代码是有问题的,如果读入的字符大于buf[8],会导致缓冲区溢出。

gcc -O1 -m32 -o bufovf bufovf.c

编译时,gcc会发出警告:

/tmp/ccWRMuOR.o: In function `getline':

bufovf.c:(.text+0x56):warning: the `gets' function is dangerous and should not be used.

objdump-d bufovf

Disassembly of section .plt:

……

080484d4 <getline>:

 80484d4:      55                     push  %ebp

 80484d5:      89 e5                  mov   %esp,%ebp

 80484d7:      83 ec 28               sub   $0x28,%esp //esp = esp – 0x28

 80484da:      89 5d f4               mov   %ebx,0xfffffff4(%ebp) //备份ebx

 80484dd:      89 75 f8               mov   %esi,0xfffffff8(%ebp) //备份esi

 80484e0:      89 7d fc               mov   %edi,0xfffffffc(%ebp) //备份edi

 80484e3:      8d 75 ec               lea   0xffffffec(%ebp),%esi //esi = buf

 80484e6:      89 34 24               mov   %esi,(%esp) //

 80484e9:      e8 76 fe ff ff         call  8048364 <gets@plt>//调用gets函数

 80484ee:      89 f7                  mov   %esi,%edi //edi = esi

 80484f0:      fc                     cld   //清除DF标志。

 80484f1:      b9 ff ff ff ff         mov   $0xffffffff,%ecx //ecx = -1

 80484f6:      b8 00 00 00 00         mov   $0x0,%eax //eax = 0

 80484fb:      f2 ae                  repnz scas %es:(%edi),%al//扫描buf的内容,当寻找到al的值(0)时,扫描结束。

 80484fd:      f7 d1                  not   %ecx//ecx取反

 80484ff:      83 e9 01               sub   $0x1,%ecx //ecx = ecx -1,得到buf的长度。

 8048502:      89 0c 24               mov   %ecx,(%esp)//把长度作为入参保存到栈中

 8048505:      e8 8a fe ff ff         call  8048394 <malloc@plt>//调用malloc

 804850a:      89 c3                  mov   %eax,%ebx//result = eax,保存malloc分配的内存地址

 804850c:      89 74 24 04            mov   %esi,0x4(%esp) //esp+4 = buffer,源内存地址

 8048510:      89 04 24               mov   %eax,(%esp) //esp = result,目的内存地址

 8048513:      e8 9c ff ff ff         call  80484b4 <strcpy>//调用strcpy函数

 8048518:      89 d8                  mov   %ebx,%eax //eax = result,作为返回值

 804851a:      8b 5d f4               mov   0xfffffff4(%ebp),%ebx

 804851d:      8b 75 f8               mov   0xfffffff8(%ebp),%esi

 8048520:      8b 7d fc               mov   0xfffffffc(%ebp),%edi

 8048523:      89 ec                  mov   %ebp,%esp

 8048525:      5d                     pop   %ebp

 8048526:      c3                     ret   

……


DFdirection flag)标志位于EFLAGS寄存器第10bit位,控制字符串指令(movs,cmps, scas, lodsstos)。设置DF标志位,会引起字符串指令自减(从高字节到低字节处理),清除DF标志位,会引起字符串指令自增(从低字节到高字节处理)。

std指令:用于设置DF标志位。

cld指令:用于清除DF标志位。

repnz指令:当ecx寄存器不是0值时并且ZF标志位是0时循环。

scas指令:扫描字符串,与repnz联合使用,每循环一次,ecx1

es是段寄存器,保存的是附加段(extra segment)的段号。edi装载的是buf的地址偏移,通过%es:(%edi),计算出buf的虚拟内存地址(根据段号算出段起始地址,再加上edi保存的地址偏移值),读取buf的内容。


下图是getline函数调用gets(buf);前的调用栈:

32位汇编语言学习笔记(19)--缓冲区溢出实验

从图中可以看出,如果gets函数写buf超过8个字节,就会导致缓冲区溢出,从而破坏栈上保存的数据。


如果输入的字节小于等于23(加上字符串结尾的0,小于等于24个字节),破坏保存包括ebp在内的几个寄存器的保存值,程序还能运行:

./bufovf
Input>helloworld1234567890$$$

helloworld1234567890$$$

当输入的字节数达到24个(加上字符串结尾的0,实际上是25个字节),不仅破坏掉了旧ebp值,还破坏了函数的返回地址,程序崩溃:

./bufovf
Input>helloworld1234567890$$$$
Segmentation fault