二进制安全之栈溢出(十)
本篇没有新内容,只有一个新名词 off-by-one,利用方法还是之前的。
off-by-one漏洞
off-by-one漏洞是计数时由于边界条件判断失误导致结果多了一或少了一的错误,比如
- 在循环中进行比较的时候,本该使用
<=
,但却使用了<
- 没有考虑到一个序列是从 0 而不是 1 开始
- 忽略了字符串最后的结束标志
\0
在字符串 \0
场景下,off-by-one 可以覆盖字符串后面一个字节的数据,在部分场景下可能造成严重的问题。
漏洞分析
1#include <stdio.h>
2#include <string.h>
3void foo(char* arg);
4void bar(char* arg);
5void foo(char* arg) {
6 bar(arg);
7}
8void bar(char* arg) {
9 char buf[256];
10 strcpy(buf, arg);
11}
12
13int main(int argc, char *argv[]) {
14 if(strlen(argv[1])>256) { /* [3] */
15 printf("Attempted Buffer Overflow\n");
16 fflush(stdout);
17 return -1;
18 }
19 foo(argv[1]);
20 return 0;
21}
漏洞成因非常简单,main 函数中允许 256 长度的字符串,但是 buf 也是 256 字节的,这会导致 buf 后面一个字节的数据被覆盖。
编译参数是 gcc -fno-stack-protector -z execstack -g -o vuln main.c
在 strcpy
那一行下断点,然后运行,输入 256 长度的 A
。
代码和栈内存是这样的
1[-------------------------------------code-------------------------------------]
2 0x40067a <bar+25>: lea rax,[rbp-0x100]
3 0x400681 <bar+32>: mov rsi,rdx
4 0x400684 <bar+35>: mov rdi,rax
5=> 0x400687 <bar+38>: call 0x4004f0 <strcpy@plt>
6 0x40068c <bar+43>: nop
7 0x40068d <bar+44>: leave
8 0x40068e <bar+45>: ret
9 0x40068f <main>: push rbp
10Guessed arguments:
11arg[0]: 0x7fffffffe030 --> 0x0
12arg[1]: 0x7fffffffe52c ('A' <repeats 200 times>...)
13arg[2]: 0x7fffffffe52c ('A' <repeats 200 times>...)
14[------------------------------------stack-------------------------------------]
150000| 0x7fffffffe020 --> 0x0
160008| 0x7fffffffe028 --> 0x7fffffffe52c ('A' <repeats 200 times>...)
170016| 0x7fffffffe030 --> 0x0
180024| 0x7fffffffe038 --> 0x0
190032| 0x7fffffffe040 --> 0xff000000
200040| 0x7fffffffe048 --> 0xff000000ff000000
210048| 0x7fffffffe050 --> 0x0
220056| 0x7fffffffe058 --> 0x0
23[------------------------------------------------------------------------------]
24
25gdb-peda$ p &buf
26$1 = (char (*)[256]) 0x7fffffffe030
如果 buf 复制 256 字节数据,那它的内存地址就是 0x7fffffffe030 - (0x7fffffffe130 - 1)
了,0x7fffffffe130
地址上的数据将被覆盖,而上面寄存器也可以看出来,rbp 就是 0x7fffffffe130
地址。
1[----------------------------------registers-----------------------------------]
2RAX: 0x7fffffffe030 --> 0x0
3RBX: 0x0
4RCX: 0x8000100000000000
5RDX: 0x7fffffffe52c ('A' <repeats 200 times>...)
6RSI: 0x7fffffffe52c ('A' <repeats 200 times>...)
7RDI: 0x7fffffffe030 --> 0x0
8RBP: 0x7fffffffe130 --> 0x7fffffffe150 --> 0x7fffffffe170 --> 0x400700 (<__libc_csu_init>: push r15)
9RSP: 0x7fffffffe020 --> 0x0
10RIP: 0x400687 (<bar+38>: call 0x4004f0 <strcpy@plt>)
11R8 : 0x1000
12R9 : 0x7ffff7de7ab0 (<_dl_fini>: push rbp)
13R10: 0x309
14R11: 0x7ffff7a98720 (<strlen>: pxor xmm0,xmm0)
15R12: 0x400550 (<_start>: xor ebp,ebp)
16R13: 0x7fffffffe250 --> 0x2
17R14: 0x0
18R15: 0x0
19EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
栈在 0x7fffffffe130
附近是
10224| 0x7fffffffe100 --> 0x7fffffffe52c ('A' <repeats 200 times>...)
20232| 0x7fffffffe108 --> 0x0
30240| 0x7fffffffe110 --> 0x7fffffffe270 --> 0x7fffffffe62d ("USER=virusdefender")
40248| 0x7fffffffe118 --> 0x7fffffffe258 --> 0x7fffffffe4fe ("/home/virusdefender/Desktop/pwn/offbyone/vuln")
50256| 0x7fffffffe120 --> 0x7fffffffe52c ('A' <repeats 200 times>...)
60264| 0x7fffffffe128 --> 0x400770 (<__libc_csu_fini>: repz ret)
70272| 0x7fffffffe130 --> 0x7fffffffe150 --> 0x7fffffffe170 --> 0x400700 (<__libc_csu_init>: push r15)
80280| 0x7fffffffe138 --> 0x40065e (<foo+24>: nop)
90288| 0x7fffffffe140 --> 0x0
100296| 0x7fffffffe148 --> 0x7fffffffe52c ('A' <repeats 200 times>...)
110304| 0x7fffffffe150 --> 0x7fffffffe170 --> 0x400700 (<__libc_csu_init>: push r15)
120312| 0x7fffffffe158 --> 0x4006ec (<main+93>: mov eax,0x0)
覆盖前后的数据对比
1gdb-peda$ x/8xb 0x7fffffffe130
20x7fffffffe130: 0x50 0xe1 0xff 0xff 0xff 0x7f 0x00 0x00
3
4gdb-peda$ x/8xb 0x7fffffffe130
50x7fffffffe130: 0x00 0xe1 0xff 0xff 0xff 0x7f 0x00 0x00
也就是 0x7fffffffe150
会被覆盖为 0x7fffffffe100
,这个地址正好是 buf 内可控的地址。