堆概述#
malloc_chunk 数据结构
allocated chunk | free chunk |
---|---|
prev_size (if previous chunk is free) | part of previous chunk |
chunk size | N | M | P | |
user data | fd: pointer to next chunk |
bk: pointer to previous chunk | |
unused space |
因为 chunk 的大小都是 8 字节对齐的,所以 chunk size 的最低 3 位被用于标志位。
- N:PREV_INUSE,表示前一个 chunk 是否被分配
- M:IS_MMAPPED,表示是否是通过 mmap 分配的
- P:NON_MAIN_ARENA,表示是否是通过非主 arena 分配的
Off-By-One#
off-by-one 指单字节缓冲区溢出,是一种特殊的缓冲区溢出,通常发生在字符串处理函数中,例如 strcpy
、strcat
、gets
等。当输入的字符串长度等于缓冲区长度时,这些函数会将字符串的结尾符 \0
写入缓冲区的末尾,但是由于字符串的长度已经等于缓冲区的长度,所以 \0
将会被写入缓冲区之外的内存,导致缓冲区溢出。
Asis CTF 2016 b00ks
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
题目是一个选单图书管理系统,可以创建、删除、编辑、打印图书信息。
__int64 __fastcall main(int a1, char **a2, char **a3)
{
struct _IO_FILE *v3; // rdi
int v5; // [rsp+1Ch] [rbp-4h]
setvbuf(stdout, 0LL, 2, 0LL);
v3 = stdin;
setvbuf(stdin, 0LL, 1, 0LL);
welcome(v3);
change_name(v3);
while ( 1 )
{
v5 = menu(v3);
if ( v5 == 6 )
break;
switch ( v5 )
{
case 1:
create_book(v3);
break;
case 2:
delete_book(v3);
break;
case 3:
edit_book(v3);
break;
case 4:
print_books(v3);
break;
case 5:
change_name(v3);
break;
default:
v3 = (struct _IO_FILE *)"Wrong option";
puts("Wrong option");
break;
}
}
puts("Thanks to use our library software");
return 0LL;
}
漏洞在于其中自定义的 my_read
函数会在输入的最后多加一个 \0
。此时如果后续地址被其他内容填充,会导致 \0
被覆盖。
__int64 __fastcall my_read(_BYTE *ptr, int len)
{
int i; // [rsp+14h] [rbp-Ch]
if ( len <= 0 )
return 0LL;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, ptr, 1uLL) != 1 )
return 1LL;
if ( *ptr == 10 )
break;
++ptr;
if ( i == len )
break;
}
*ptr = 0; // off-by-one
return 0LL;
}
book 的结构
struct book
{
int id;
char *name;
char *description;
int size;
}
.bss 段上的结构为
char author name[0x20]
struct book books[]
所以第一次写入 name 后再创建 book,会导致 name 最后的 \0
被覆盖,此时会连带后面的 book 指针信息一并输出。此外如果使用 change_name 功能会使得 book1 的指针最低位被覆盖为 0,这样刚好会导致原本指向 book1 的指针指向 book1 的 description,而我们可以通过 edit_book 功能修改 description,也就能够实现伪造 book。
我们可以伪造一个 book1,他的 description 指向 book2 的 description,这样就可以通过 print_books 和 edit_book 来实现任意地址读写。
Chunk Extend & Overlapping#
如果我们能够控制一个 chunk 的 size,那么就可以通过修改 size 来实现 chunk 的扩展。此时会导致 chunk 的重叠,在进行 free 操作时会导致后续的 chunk 被一起释放。之后如果再次分配到这个 chunk,就可以对第二块 chunk 的内容进行修改。
HITCON Training heapcreator
程序是一个选单式的堆管理程序,可以创建、删除、修改、查看堆块。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
menu();
read(0, buf, 4uLL);
switch ( atoi(buf) )
{
case 1:
create_heap();
break;
case 2:
edit_heap();
break;
case 3:
show_heap();
break;
case 4:
delete_heap();
break;
case 5:
exit(0);
default:
puts("Invalid Choice");
break;
}
}
}
程序有一个 heap 结构体,结构为
struct heap
{
unsigned __int64 size;
char *content;
}
heaparray 为 heap 的指针数组,最多有 10 个堆块。
创建 heap 时会从堆中分配一个 heap 结构体,然后再分配一个 content 的内存块。而 edit_heap 中存在 off-by-one 漏洞,读取时会多读一个字节。
unsigned __int64 edit_heap()
{
int id; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
id = atoi(buf);
if ( id < 0 || id > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + id) )
{
printf("Content of heap : ");
read_input(*((void **)*(&heaparray + id) + 1), *(_QWORD *)*(&heaparray + id) + 1LL); // off-by-one
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}