本篇没有新内容,只有一个新名词 off-by-one,利用方法还是之前的。

off-by-one漏洞

off-by-one漏洞是计数时由于边界条件判断失误导致结果多了一或少了一的错误,比如

在字符串 \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 内可控的地址。

参考