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; int v2; int v3; void *v4; void *ptr; void *v6;
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); if ( ptr ) { if ( (unsigned int)sub_9F5(ptr, v2 - 1) ) { 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); 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 *)off_202010 + v3) = v4; *((_QWORD *)v4 + 2) = v6; *((_QWORD *)v4 + 1) = ptr; *(_DWORD *)v4 = ++unk_202024; 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; _BYTE *buf;
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; 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; int i;
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(*(void **)(*((_QWORD *)off_202010 + i) + 16LL)); free(*((void **)off_202010 + i)); *((_QWORD *)off_202010 + i) = 0LL; return 0LL; } printf("Can't find selected book!"); } else { printf("Wrong id"); } return 1LL; }
|
漏洞利用
泄露first_book地址