- 实验要求
- 实验步骤
- 函数 f00()
- 函数 f01()
- 函数 f02()
实验要求
C 程序 homework08.c
的主函数如下:
int main(int argc, char * argv[]) {
init_buf(Lbuffer, LEN);
switch(argc)
{
case 1: f00(); break;
case 2: f01(); break;
case 3: f02(); break;
default: f00(); break;
}
puts("Done.\nThe program exited normally.");
return 0;
}
在 32 位的 ubuntu16.04 系统中用 gcc - fno-stack-protector
编译该程序,得到的可执行程序见附件,通过 gdb 调试,对 f00()、f01()和 f02()进行分析:
-
函数 f00()、f01()和 f02()是否导致段错误。
-
如果函数 f00()、f01()和 f02()导致段错误,计算出被攻击的缓冲区首地址与函数的返回地址所在的栈地址的距离(即偏移 OFFSET), 给出溢出后函数的返回地址(用16 进制数表示)。
实验步骤
- 关闭地址随机化机制,执行程序。
输入命令 sudo sysctl -w kernel.randomize_va_space=0
以关闭地址随机化,然后运行编译好的可执行程序 ./homework08
,得到 Segmentation fault
段错误。
-
为了找出段错误的原因,使用
gdb ./homework08
进入 gdb 调试模式,在该模式下使用r
命令默认运行 f00() 函数,可见函数 f00()发生段错误,为了找出错误原因,继续使用 gdb 调试程序。反汇编 main:
函数 f00()
反汇编 f00:
在函数 f00 的入口、对 strcpy 的调用、出口位置设置断点,运行程序并在断点处观察寄存器的值,
-
第一个断点处,函数入口处的堆栈指针 esp 指向的栈(地址为
0xbfffef3c
)保存了函数 f00() 返回到调用函数(main)的地址(0x080485a8
),即“函数的返回地址” 。记录堆栈指针 e s p esp esp 的值,在此以 A A A 标记: A A A = $esp =
0xbfffef3c
-
第二个断点处,查看执行汇编代码
strcpy@plt
前堆栈的内容。Lbuffer 的地址0x0804a060
保存在地址为0xbfffeea4
的栈中, buff 的首地址0xbfffeeb5
保存在地址为0xbfffeea0
的栈中。C 语言默认将参数逆序推入堆栈,所以 C 函数
strcpy(des, src)
的 src(main() 中变量 Lbuffer 的地址)先进栈(高地址),des ( f00()中 buff 的首地址 )后进栈(低地址)。令 B B B = buff 的首地址,则 buff 的首地址与返回地址所在栈的距离 = A A A - B B B =
0xbfffef3c
-0xbfffeeb5
=0x87
=135
。- 因此,如果 Lbuffer 的内容超过 135 字节,则将发生缓冲区溢出,并且返回地址被改写。
- Lbuffer 的最后的 4 个字节为
FGHI
,因此, 执行 strcpy 之后,返回地址由原来的0x80484d1
变为FGHI
(0x49484746
),即返回地址被改写。
-
第三个断点处,执行的指令为
ret
。执行ret
后程序指针 e i p eip eip 的值为0x49484746
,即程序跳转到0x49484746
去执行,这是不可访问的内存地址,因此发生段错误。-
执行
ret
时把堆栈的内容(4个字节)弹出到指令寄存器 e i p eip eip, e s p esp esp 的值增加 4,然后跳转到 e i p eip eip 所保存的地址去继续执行:ret
指令让 e i p eip eip 等于 e s p esp esp 指向的内容,并且 e s p esp esp 等于 e s p + 4 esp+4 esp+4。 -
可见,执行ret之前的堆栈的内容为
FGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVW...
,前 4 字节为0x49484746
。可以推断执行ret
后将跳到地址0x49484746
去执行。 -
eip = 0x49484746
,正好是”FGHI”倒过来,这是由于 IA32 默认字节序为 little_endian(低字节存放在低地址)。
-
因此,f00 导致段错误,偏移 OFFSET = 0x87
= 135, 溢出后函数的返回地址 = 0x49484746
。
函数 f01()
反汇编 f01,并打断点:
同上述函数 f00() 步骤,可得以下步骤:
-
记录堆栈指针 e s p esp esp 的值,以 A A A 标记: A A A = $esp =
0xbfffef2c
; -
令 B B B = buff 的首地址 =
0xbfffee2a
,则 buff 的首地址与返回地址所在栈的距离 = A A A - B B B =0xbfffef2c
-0xbfffee2a
=0x102
=258
。 -
执行
ret
后程序指针 e i p eip eip 的值为0x42415a59
,即程序跳转到0x42415a59
去执行,这是不可访问的内存地址,因此发生段错误。
因此,f01 导致段错误,偏移 OFFSET = 0x102
= 258, 溢出后函数的返回地址 = 0x42415a59
。
函数 f02()
反汇编 f02,并打断点:
同上述函数 f00() 步骤,可得以下步骤:
-
记录堆栈指针 e s p esp esp 的值,以 A A A 标记: A A A = $esp =
0xbfffef2c
; -
令 B B B = buff 的首地址 =
0xbfffeaa5
,则 buff 的首地址与返回地址所在栈的距离 = A A A - B B B =0xbfffef2c
-0xbfffeaa5
=0x487
=1159
。 -
执行
ret
后程序指针 e i p eip eip 的值为0x080485b6
,即程序跳转到0x080485b6
去执行,这是可访问的内存地址,并且正好是 main 中调用 f02() 后的下一条指令的地址,因此未发生段错误,程序执行完正常退出。
因此,f02 未导致段错误。