Ubuntu32位缓冲区溢出示例

实验准备

  • Unbuntu 16.04 32位

实验一

“example.c”代码:
在这里插入图片描述

将“example.c”文件复制至“Home”目录下。

“Ctrl”+“Alt”+“T”调出命令行。

在命令行中输入代码:

gcc -o example example.c

将“example.c”编译为可执行文件“example”:

在这里插入图片描述
运行可执行文件“example”:

./example

在这里插入图片描述

通过对比“buf”输出可得知,此时“buf02”发生了缓冲区溢出。

实验二

“mem_distribute.c”代码:
在这里插入图片描述

将“mem_distribute.c”文件复制至“Home”目录下。

“Ctrl”+“Alt”+“T”调出命令行。

在命令行中输入代码:

gcc -o men mem_distribute.c

将“mem_distribute.c”编译为可执行文件“mem”:

在这里插入图片描述

运行可执行文件“mem”:

./mem

在这里插入图片描述

由此可见:

  • 可执行代码fun1, fun2, main存放在内存的低地址,且按照源代码中的顺序从低地址到高地址排列(先定义的函数的代码存放在内存的低地址)。

  • 全局变量(x, y, z)也存放内存的低地址,位于可执行代码之上(起始地址高于可执行代码的地址)。

  • 初始化的全局变量存放在较低的地址,而未初始化的全局变量位于较高的地址。

  • 局部变量位于内存高地址区(0xbfff efxx),字符串变量放在高地址,其它变量从低地址到高地址依次(先定义的放在低地址)存放。

  • 函数的入口参数的地址(> 0xbfff efxx)更高,位于函数的局部变量更高的地址之上。

  • main函数从环境中获得参数,因此,环境变量位于最高的地址。

  • 可以推断出,栈底(最高地址)位于0xc000 0000,环境变量和局部变量位于进程的栈区。

  • 函数的返回地址也位于进程的栈区。

实验三:IA32构架缓冲区溢出

“buffer_overflow.c”代码:
在这里插入图片描述

将“buffer_overflow.c”文件复制至“Home”目录下。

“Ctrl”+“Alt”+“T”调出命令行。

在命令行中输入代码:

gcc -fno-stack-protector -o buf buffer_overflow.c

将“buffer_overflow.c”编译为可执行文件“buf”:

在这里插入图片描述

运行可执行文件“buf”:

./buf

在这里插入图片描述
发生段错误。

使用“gdb”命令调试文件:

gdb buf
r

在这里插入图片描述
发生段错误。

使用“gdb”命令调试文件,找出错误原因

disas main

在这里插入图片描述

disas foo

在这里插入图片描述

在函数foo的入口、对strcpy的调用、出口及其他需要重点分析的位置设置断点

b*(foo+0)
b*(foo+18)
b*(foo+28)
display/i $eip

在这里插入图片描述

运行程序并在断点处观察寄存器的值

r
x/x $esp

在这里插入图片描述
函数入口处的堆栈指针esp指向的栈(地址为0xbffff0ac)保存了函数foo()返回到调用函数(main)的地址(0x080484ee),即“函数的返回地址”.

查看main的汇编代码核实结论

在地址为0x080484ee指令的前一条指令为call 0x804840b ,而地址0x804840b为函数foo()的第一条指令的地址,因此,函数入口处的堆栈保存的是被调用函数的返回地址。也可以用下面的gdb命令证实这一点:

x/2i 0x080484ee-5

在这里插入图片描述

记录堆栈指针esp的值,在此以A标记:A=$esp

c

在这里插入图片描述
查看执行汇编代码strcpy@plt之前堆栈的内容

由于C语言默认将参数逆序推入堆栈,因此,C函数strcpy(des, src)的src(全局变量Lbuffer的地址)先进栈(高地址),des(foo()中buff的首地址)后进栈(低地址)。

C函数strcpy(des, src) 的参数

x/x $esp

x/x 0x0804a040

在这里插入图片描述
Lbuffer 的地址0x804a040 保存在地址为0xbffff0ac的栈中,buff的首地址0xbfffef90保存在地址为0xbfffef80的栈中。

令B = buff的首地址,则buff的首地址与返回地址所在栈的离=A-B=0xbffff0ac - 0xbfffef90=0x1c=28。

因此,如果Lbuffer的内容超过28字节,则将发生缓冲区溢出并返回地址被改写。

Lbuffer的长度为32字节,其中最后的4个字节为“ABCD”,因此,执行strcpy之后,返回地址由原来的0x0804840b变为”ABCD”(0x44434241),即返回地址被改写。

继续执行到下一个断点:

c

在这里插入图片描述
即将执行的指令为ret。

执行ret时把堆栈的内容(4个字节)弹出到指令寄存器eip,esp的值增加4,然后跳转到eip所保存的地址去继续执行:ret指令让eip等于esp指向的内容,并且 esp等于esp+4.

x/x $esp
x/s $esp

在这里插入图片描述
执行ret之前的堆栈的内容为”ABCD”,即0x44434241。可以推断执行ret后将跳到地址0x44434241去执行。

si
x/s $esp
x/s $eip

在这里插入图片描述
程序指针eip的值为0x44434241,是不可访问的地址,因此发生段错误。

eip=0x44434241,正好是”ABCD”倒过来,这是由于IA32默认字节序为little_endian(低字节存放在低地址)。

通过修改Lbuffer的内容(将ABCD改成期望的地址),就可以设置需要的返回地址,从而可以将eip变为可以控制的地址,也就是说可以控制程序的执行流程。

调试重点

在函数的3个关键之处设置断点:

  • 第一条汇编语句:在此记下函数的返回地址(A=$esp本身的值) (会动态变化)
  • 调用strcpy对应的汇编语句:记下smallbuf的起始地址=$esp指向的内存的值=B (会动态变化),与A相减可以得到产生缓冲区溢出所需的字节数(偏移offset)= A-B
  • ret语句:查看esp指向的内容,确定被修改后的返回地址。

实验四

homework

./homework

在这里插入图片描述
在这里插入图片描述

发生段错误。

反汇编main函数:

disas main

在这里插入图片描述
设置断点“1”、“1 2”:

set args 1
r
set args 1 2
r

在这里插入图片描述
得出“foo”、“foo01”产生溢出,“foo02”无溢出。

foo

反汇编“foo”:

disas foo

在这里插入图片描述
在函数foo的入口、对strcpy的调用、出口及其他需要重点分析的位置设置断点:

b*(foo+0)
b*(foo+24)
b*(foo+34)
display/i $eip

在这里插入图片描述

运行程序并在断点处观察寄存器的值:

r
x/x $esp

在这里插入图片描述

函数入口处的堆栈指针esp指向的栈(地址为0xbffff08c)保存了函数foo()返回到调用函数(main)的地址(0x0804859a),即“函数的返回地址”。

c

在这里插入图片描述
此时进入第二个断点<foo+24>:

x/x $esp

在这里插入图片描述

查看缓存器内的值:

x/x 0x0804a040

在这里插入图片描述

p(0xbffff08c-0xbfffefa2)
x/x 0x0804a040+234

在这里插入图片描述
Lbuffer 的地址0x804a040 保存在地址为0xbfffef94的栈中,
buff的首地址0xbfffefa2保存在地址为0xbfffef90的栈中。

Buff的首地址与返回地址所在栈的距离为234。

继续执行到下一个断点:

c

在这里插入图片描述

即将执行的指令为ret。

执行ret时把堆栈的内容(4个字节)弹出到指令寄存器eip,esp的值增加4,然后跳转到eip所保存的地址去继续执行:ret指令让eip等于esp指向的内容,并且 esp等于esp+4。

x/x $esp
x/s $esp

在这里插入图片描述

执行ret之前的堆栈的内容为”ABCD……”,即0x44434241。可以推断执行ret后将跳到地址0x44434241去执行。

si
x/s $esp
x/s $eip

在这里插入图片描述

程序指针eip的值为0x44434241,是不可访问的地址,因此发生段错误。

eip=0x44434241,正好是”ABCD……”倒过来,这是由于IA32默认字节序为little_endian(低字节存放在低地址)。

通过修改Lbuffer的内容(将ABCD改成期望的地址),就可以设置需要的返回地址,从而可以将eip变为可以控制的地址,也就是说可以控制程序的执行流程。

foo01

设置断点“1”:

set args 1
r

在这里插入图片描述

反汇编“foo01”:

disas foo01

在这里插入图片描述

在函数foo01的入口、对strcpy的调用、出口及其他需要重点分析的位置设置断点:

b*(foo01+0)
b*(foo01+77)
b*(foo01+87)
display/i $eip

在这里插入图片描述

运行程序并在断点处观察寄存器的值:

r
x/x $esp

在这里插入图片描述

函数入口处的堆栈指针esp指向的栈(地址为0xbffff08c)保存了函数foo01()返回到调用函数(main)的地址(0x080485a1),即“函数的返回地址”。

c

在这里插入图片描述

此时进入第二个断点<foo01+24>:

x/x $esp

在这里插入图片描述

查看缓存器内的值:

x/x 0xbffff04f

在这里插入图片描述

p(0xbffff08c-0xbffff070)
x/x 0xbffff04f+28

在这里插入图片描述

Lbuffer 的地址0xbffff04f保存在地址为0xbffff034的栈中,
buff的首地址0xbffff070保存在地址为0xbffff030的栈中。

Buff的首地址与返回地址所在栈的距离为28。

继续执行到下一个断点:

c

在这里插入图片描述

即将执行的指令为ret。

执行ret时把堆栈的内容(4个字节)弹出到指令寄存器eip,esp的值增加4,然后跳转到eip所保存的地址去继续执行:ret指令让eip等于esp指向的内容,并且 esp等于esp+4。

x/x $esp
x/s $esp

在这里插入图片描述

执行ret之前的堆栈的内容为”ABCD”,即0x44434241。可以推断执行ret后将跳到地址0x44434241去执行。

si
x/s $esp
x/s $eip

在这里插入图片描述

程序指针eip的值为0x44434241,是不可访问的地址,因此发生段错误。

eip=0x44434241,正好是”ABCD……”倒过来,这是由于IA32默认字节序为little_endian(低字节存放在低地址)。

通过修改Lbuffer的内容(将ABCD……改成期望的地址),就可以设置需要的返回地址,从而可以将eip变为可以控制的地址,也就是说可以控制程序的执行流程。

foo02

设置断点“1 2”:

set args 1 2
r

在这里插入图片描述
run时显示正常退出,foo02无溢出。

反汇编“foo02”:

disas foo02

在这里插入图片描述

在函数foo02的入口、对strcpy的调用、出口及其他需要重点分析的位置设置断点:

b*(foo02+0)
b*(foo02+77)
b*(foo02+87)
display/i $eip

在这里插入图片描述

运行程序并在断点处观察寄存器的值:

r
x/x $esp

在这里插入图片描述

函数入口处的堆栈指针esp指向的栈(地址为0xbffff08c)保存了函数foo()返回到调用函数(main)的地址(0x0804859a),即“函数的返回地址”。

c

在这里插入图片描述

此时进入第二个断点<foo02+24>:

x/x $esp

在这里插入图片描述

查看缓存器内的值:

x/x 0xbffff05f

在这里插入图片描述

p(0xbffff08c-0xbffff04f)
x/x 0xbffff05f+61

在这里插入图片描述

Lbuffer 的地址0xbffff05f保存在地址为0xbffff034的栈中,
buff的首地址0xbffff04f保存在地址为0xbffff030的栈中。

Buff的首地址与返回地址所在栈的距离为61。

继续执行到下一个断点:

c

在这里插入图片描述

即将执行的指令为ret。

执行ret时把堆栈的内容(4个字节)弹出到指令寄存器eip,esp的值增加4,然后跳转到eip所保存的地址去继续执行:ret指令让eip等于esp指向的内容,并且 esp等于esp+4。

x/x $esp
x/s $esp

在这里插入图片描述

执行ret之前的堆栈的内容为”\250\205\004\b\003”,即0x040485a8。可以推断执行ret后将跳到地址0x040485a8去执行。

si
x/s $esp
x/s $eip

在这里插入图片描述

程序指针eip的值为0x040485a8,是可访问的地址,正常执行,未发生段错误。

eip=0x040485a8,正好是”\250\205\004\b\003”倒过来,这是由于IA32默认字节序为little_endian(低字节存放在低地址)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值