PWN学习之off-by-one

off-by-one 原理

严格来说,off-by-one漏洞是一种特殊的溢出漏洞,指程序向缓冲区中写入时,写入的字节数超过了缓冲区本身的大小,并且只越界了一个字节。这种漏洞的产生往往与边界验证不严或字符串操作有关,当然也有可能写入的size正好就只多了一个字节:

  • 使用循环语句向缓冲区中写入数据时,循环的次数设置错误导致多写入一个字节
  • 字符串操作不合适,比如忽略了字符串末尾的\x00

一般而言,单字节溢出很难利用。但因为Linux中的堆管理机制ptmalloc验证的松散型,基于Linux堆的off-by-one漏洞利用起来并不复杂,而且威力强大。需要说明的是,off-by-one是可以基于各种缓冲区的,如栈、bss段等。但堆上的off-by-one在CTF中比较常见,下面以Asis CTF 2016 b00ks为实例进行分析,该例子正是由于忽略字符串末尾的\x00溢出而导致任意字符串的读写。

题目分析

程序简介

该程序是一个图书馆管理系统,可以添加、删除、查询、修改图书。

1
2
3
4
5
6
7
8
9
Welcome to ASISCTF book library
Enter author name: kangel

1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit

查看保护

Full RELRO说明got不可写,因此我们需要修改__free_hook函数劫持程序流,后面会详细介绍。

栈保护未开启,也没有栈溢出可以利用。

1
2
3
4
5
6
7
➜  Asis_2016_b00ks checksec b00ks    
[*] '/mnt/hgfs/ctf-challenges/pwn/heap/off_by_one/Asis_2016_b00ks/b00ks'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

程序分析

堆结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
signed __int64 sub_F55()
{
void *v0; // rdi
int v2; // [rsp+0h] [rbp-20h]
int v3; // [rsp+4h] [rbp-1Ch]
void *v4; // [rsp+8h] [rbp-18h]
void *ptr; // [rsp+10h] [rbp-10h]
void *v6; // [rsp+18h] [rbp-8h]

v2 = 0;
printf("\nEnter book name size: ", *(_QWORD *)&v2);
__isoc99_scanf("%d", &v2);
if ( v2 >= 0 )
{
printf("Enter book name (Max 32 chars): ", &v2);
ptr = malloc(v2); //malloc 存书名
if ( ptr )
{
if ( (unsigned int)sub_9F5(ptr, v2 - 1) ) //sub_9F5漏洞函数
{
printf("fail to read name");
}
else
{
v2 = 0;
printf("\nEnter book description size: ", *(_QWORD *)&v2);
__isoc99_scanf("%d", &v2);
if ( v2 >= 0 )
{
v6 = malloc(v2); //malloc 存description
if ( v6 )
{
printf("Enter book description: ", &v2);
v0 = v6;
if ( (unsigned int)sub_9F5(v6, v2 - 1) )
{
printf("Unable to read description");
}
else
{
v3 = sub_B24(v0);
if ( v3 == -1 )
{
printf("Library is full");
}
else
{
v4 = malloc(0x20uLL); //存图书基本信息
if ( v4 )
{
*((_DWORD *)v4 + 6) = v2; //书描述大小,QWORD==DWORD*2
*((_QWORD *)off_202010 + v3) = v4; //图书信息指针
*((_QWORD *)v4 + 2) = v6; //书描述指针
*((_QWORD *)v4 + 1) = ptr; //书名指针
*(_DWORD *)v4 = ++unk_202024; //图书ID
return 0LL;
}
printf("Unable to allocate book struct");
}
}
}
else
{
printf("Fail to allocate memory", &v2);
}
}
else
{
printf("Malformed size", &v2);
}
}
}
else
{
printf("unable to allocate enough space");
}
}
else
{
printf("Malformed size", &v2);
}
if ( ptr )
free(ptr);
if ( v6 )
free(v6);
if ( v4 )
free(v4);
return 1LL;
}

漏洞函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
signed __int64 __fastcall sub_9F5(_BYTE *a1, int a2)
{
int i; // [rsp+14h] [rbp-Ch]
_BYTE *buf; // [rsp+18h] [rbp-8h]

if ( a2 <= 0 )
return 0LL;
buf = a1;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == 10 )
break;
++buf;
if ( i == a2 )
break;
}
*buf = 0; //漏洞位置,NULL溢出
return 0LL;
}

free函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
signed __int64 sub_BBD()
{
int v1; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]

i = 0;
printf("Enter the book id you want to delete: ");
__isoc99_scanf("%d", &v1);
if ( v1 > 0 )
{
for ( i = 0; i <= 19 && (!*((_QWORD *)off_202010 + i) || **((_DWORD **)off_202010 + i) != v1); ++i )
;
if ( i != 20 )
{
free(*(void **)(*((_QWORD *)off_202010 + i) + 8LL)); //free第一个参数,书名指针
free(*(void **)(*((_QWORD *)off_202010 + i) + 16LL)); //free第二个参数,描述指针
free(*((void **)off_202010 + i)); //free第三个参数,图书信息指针
*((_QWORD *)off_202010 + i) = 0LL;
return 0LL;
}
printf("Can't find selected book!");
}
else
{
printf("Wrong id");
}
return 1LL;
}

漏洞利用

泄露first_book地址

-------------本文结束感谢您的阅读-------------