ciscn的一到真题,这题也是一道比较有意思的题目,虽然给出的漏洞非常的明显是栈溢出,但是我们要想拿到shell却不是那么简单的。
这一题涉及到了一些系统调用的知识,那谈到系统调用我们也要知道,x86和x32位的系统调用时有很大的区别的,当时在学习pwn基础的时候,我也只是学习到了一些x86下的系统调用ROP,但是很不巧这一题是x64位的题目,所以不能直接使用x86的方法。
1.传参方式不同
2.系统调用号不同
3.调用方式不同
传参方式:首先将系统调用号 传入 eax,然后将参数 从左到右 依次存入 ebx,ecx,edx寄存器中,返回值存在eax寄存器
调用号:sys_read 的调用号为3, sys_write 的调用号为 4,execve的调用号是0xb也就是11.
调用方式: 使用 int 80h 中断进行系统调用
传参方式:首先将系统调用号 传入 rax,然后将参数从左到右依次存入 rdi,rsi,rdx寄存器中,返回值存在rax寄存器
调用号:sys_read 的调用号为0,sys_write 的调用号 为1
stub_execve 的调用号为59,stub_rt_sigreturn 的调用号为15
调用方式: 使用 syscall 进行系统调用
有了这些前置知识我们再来看这一题
可以看到64位小端,而且开启了栈不可执行的保护。
可以清楚的看到存在栈溢出漏洞
漏洞点是一眼就能看出来,但是却没有很好的方法能拿到shell。
我们没有后门函数也没有函数可以利用来泄漏libc信息
但是该程序还有出题者自己定义的函数
这样看是看不出来有什么可以利用的点,我们来看它的汇编代码。
可以看到该函数其实调用了调用号为15的系统调用函数,调用号为15的系统调用正好也是上面我们讲到的stub_rt_sigreturn函数
在该函数的下方可以看到,有一个mov rax,3B ,0x3B即59,与59对应的系统调用函数就是stub_execve,这个函数使我们可以使用的函数,我们可以使用execve("/bin/sh",0,0),来拿到shell。
所以我们还缺少"/bin/sh",和一些可以控制rax,rid等寄存器的指令,因为我们要执行execve(“/bin/sh”,0,0)
大概的布局是这样的
rax=59
rdi=“/bin/sh”
rsi=0
rdx=0
syscall
缺少的"/bin/sh"我们可以利用vuln函数
因为该函数最后在打印我们的输入的时候,打印0x30个字节,而buf只能放下0x10个字节,也就是说它会多打印0x20个字节。
经过动态调试可以看到0x7fffffffdf40是存放我们输入的地址,那vuln函数就能打印到
0x7fffffffdf60,而这个地址里保存的值还是一个地址,而且是栈上的地址,在之前的
学习中也应该知道,目标主机一定是开启ASLR保护的,所以每次载入内存的地址是不一
样的,但是我们却可以计算偏移量找到我们的输入所在的地址值,这样"/bin/sh"的问题
就解决了。
偏移=0xe058-0xdf40=280
那我们现在还缺的就是pop rbx,pop rdi,pop rsi等指令
这些指令可以在,__libc_csu_init中进行寻找,
虽然没有直接的pop rbx,pop rdi,pop rsi等指令可以使用,但是我们可以利用多条指令来实现相同的功能
这两张图里的指令结合进行使用就可以达到想要的效果。
先pop rbx,rbp,r12,r13,r14,r15,在用mov rdx,r13 mov rsi,r14等指令将刚刚pop的寄存器的值赋给rdx,rsi等实现我们想要的功能。
还有一些值的注意的细节就是,mov edi,r15这条指令edi只有四个字节,而我们rdi里将要存储的是"/bin/sh",所以这条指令是不满足我们的功能的,所以我们还得重新寻找一个pop rdi,这里我们使用ROPgadget工具
可以看到刚好0x00000000004005a3符合要求
from pwn import * io=process("./ciscn_s_3") main_addr=0x00000000004004ED sys_execve_addr=0x00000000004004E2 pop_addr=0x000000000040059A mov_rdx_addr=0x0000000000400580 pop_rdi_addr=0x00000000004005a3 syscall_addr=0x0000000000400517 payload1=b"/bin/sh\x00"*2+p64(main_addr) io.sendline(payload1) io.recv(0x20) binsh_addr=u64(io.recv(8))-280 payload2=b"/bin/sh\x00"*2+p64(pop_addr)+p64(0)*2+p64(binsh_addr+0x50)+p64(0)*3+p64(mov_rdx_addr)+p64(sys_execve_addr)+p64(pop_rdi_addr)+p64(binsh_addr)+p64(syscall_addr) io.sendline(payload2) io.interactive()
这里的p64(binsh_addr+0x50)为什么要加上0x50
这串代码对应的就是call r12里的r12,可以看到这串指令的结尾是没有ret的,这样我们就接不上后面的指令,也就无法完成我们的攻击。
这里加上0x50指向的地址正好是
call调用一个函数后进行ret,成功的接上后面的操作,也就能成功的执行攻击拿到shell。