缓冲区溢出基础:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
char str[10];
strcpy(str, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
return 0;
}
这里strcpy的时候,字符长度超出了str所允许的长度,发生了溢出。当我们运行这个代码的时候,程序报告Segmentation fault 错误。我们启动gdb查看它的寄存器以及内存数据。
启动后,info reg 查看。发现在x86_64架构下的CentOS寄存器名字用r替换了原来的e(例如,eax-->rax),并且多了8个通用寄存器(r8-->r15)。
(gdb) info reg
rax 0x0 0
rbx 0x0 0
rcx 0x41 65
rdx 0x0 0
rsi 0x40061a 4195866
rdi 0x7fffffffe0d2 140737488347346
rbp 0x4141414141414141 0x4141414141414141
rsp 0x7fffffffe0c8 0x7fffffffe0c8
r8 0x4141414141414141 4702111234474983745
r9 0x4141414141414141 4702111234474983745
r10 0x4141414141414141 4702111234474983745
r11 0x3173a89050 212393824336
r12 0x4003e0 4195296
r13 0x7fffffffe1a0 140737488347552
r14 0x0 0
r15 0x0 0
rip 0x4004f2 0x4004f2 <main+46>
eflags 0x10246 [ PF ZF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
我们发现 rip 并没有发生改变,并且当我们增加程序 strcpy 中 A 的长度,并不会溢出到 rip ,他只在 r8 到 r10循环溢出。这样的架构起到了一个很好的保护作用。但是我们的缓冲区栈攻击,即简单的修改 rip 失败了。
如果是32位LINUX即可实现溢出 eip 的攻击,并且获得 root 权限。
高级栈攻击分析:
首先来看我们在熟悉不过的 printf 函数
#include <stdio.h>
int main(int argc, char *argv[]) {
printf(argv[1]);
return 0;
}
发现当我们传入以下参数时:
./main Test%s
得到如下显示:
Test愬$�[
这里%s预期是读取一个以'\0'结尾的字符数组,但是,在内存中,printf 的内部指针的后面并没有保存一个变量,因为我们并没有进行参数的传递,所以输出了未初始化的内存内容。
简单的示意图:
栈顶内存低区 <--------- 栈增长方向 栈底内存高区
返回地址 格式串的地址(printf 内部指针) 第一个变量地址 第二个变量地址 。。。。。。。。。。
观察 rbp 和 rsp (分别为扩展基址指针,扩展栈指针)。来看下一个代码:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
int canary = 1;
char temp[40];
strcpy(temp, argv[1]);
printf(temp);
printf("\n");
printf("Canary at 0x%08x = 0x%08x\n", &canary, canary);
return 0;
}
输入:
这里有两个变量,gdb 查询栈的地址范围:
rbp 0x7fffffffe080 0x7fffffffe080
rsp 0x7fffffffe040 0x7fffffffe040
(无论运行多少次,这两个寄存器之间的差值均为64KB),并且这两个变量的地址为:
(gdb) p &temp
$6 = (char (*)[40]) 0x7fffffffe050
(gdb) p &canary
$7 = (int *) 0x7fffffffe07c
在这两个之间,并且canary 高于 temp,说明的栈的特性。
现在gdb:
(gdb) run "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA %08x %08x %08x %08x %08x %08x %08x"
发现:
(gdb) p canary
$5 = 0x41414141
说明temp溢出,并且把栈内的canary覆盖了
我们可以从一下代码中得到启示,从任意内存读取地址:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
char *addr;
char cmd[100];
addr = getenv(argv[1]);
sprintf(cmd, "%s is located at %p\n", argv[1], addr);
printf(cmd);
return 0;
}
运行:
[X-White@localhost shell]$ ./test1 SHELL
SHELL is located at 0x7fff92d7055d
当然我们每次运行,这个地址都不一样。
gdb:
(gdb) p addr
$1 = 0x7fffffffe51c "/bin/bash"
发现 addr 正好就是 /bin/bash 文件。由于每次运行地址均不一样,所以我们可以做做一个重定向,从而获得该地址。
由于是X86_64架构,所以系统在寄存器,寻址等方式上都进行了相应的扩展。例如当用如下命令时:
[X-White@localhost shell]$ nm ./test
00000000006007c0 d _DYNAMIC
0000000000600958 d _GLOBAL_OFFSET_TABLE_
00000000004006c8 R _IO_stdin_used
w _Jv_RegisterClasses
00000000006007a0 d __CTOR_END__
0000000000600798 d __CTOR_LIST__
00000000006007b0 D __DTOR_END__
00000000006007a8 d __DTOR_LIST__
0000000000400790 r __FRAME_END__
00000000006007b8 d __JCR_END__
00000000006007b8 d __JCR_LIST__
0000000000600994 A __bss_start
0000000000600990 D __data_start
0000000000400680 t __do_global_ctors_aux
00000000004004c0 t __do_global_dtors_aux
00000000004006d0 R __dso_handle
w __gmon_start__
0000000000600794 d __init_array_end
0000000000600794 d __init_array_start
00000000004005e0 T __libc_csu_fini
00000000004005f0 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
0000000000600994 A _edata
00000000006009a8 A _end
00000000004006b8 T _fini
0000000000400408 T _init
0000000000400470 T _start
000000000040049c t call_gmon_start
0000000000600998 b completed.6347
0000000000600990 W data_start
00000000006009a0 b dtor_idx.6349
0000000000400530 t frame_dummy
0000000000400554 T main
U printf@@GLIBC_2.2.5
U putchar@@GLIBC_2.2.5
U strcpy@@GLIBC_2.2.5
发现前方的地址是16位,而非8位。
因此,无法利用在32位平台下的攻击方式,攻击失败。
本文只是分析利一下X86_64架构下的地址内部结构,至于攻击方式就是后话了。