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