一:stack pivot ( 64位程序栈劫持)
原理:
之前的文章讲过32位程序的栈劫持,原理一样
主要是利用leave ret
栈溢出时我们把rbp覆盖为fake rbp(假rbp),返回地址改为leave ret, 第一次函数正常执行leave ret进行mov rsp,rbp ; pop,rbp,此时rbp就指向了假的rbp,也就是我们构造栈的rbp位置,然后此时rsp指向返回地址里的是leave ret,pop eip,再次执行leave ret ,进行mov rsp,rbp ;pop rbp ,就把数据执行流劫持到我们构造的payload所在的栈中。由于此时pop 了一次rbp,rsp指向的位置为rbp+8的位置,这个位置是栈顶。所以减去偏移距离同时还要减去8。此时pop eip,就执行了栈顶的数据。完成了两次leave ret。成功劫持数据执行流来执行我们的payload。
栗题:
缓冲区大小0xE0,rbp长度为0x8,返回地址长度为0x8,read只能读入0xF0,0xF0=0xE0+0x8+0x8。刚刚覆盖rbp和返回地址。
动态调试看rbp距离我们写入地址的距离。
是在dca0处读入数据。
old rbp 与我们输入字符串的距离是我们想要的。能泄露的也是这个地址 。 d90 -ca0=F0,前边原理说过减过偏移距离后要再减去8。即F0-8。
先来泄露出canary值与栈顶的地址。
由于这个程序本身没有调用system,要想getshell的话先执行system binsh,所以需要先泄露libc地址。
程序有puts ,接下来找pop rdi,一般pop r15下半部分拆开单独拿出来就是pop rdi。 再找到leave ret ,main 函数地址。开始构造payload来泄露puts的libc。
泄露完libc程序会再次回到main函数,这次就会直接getshell,不过这次是控制程序泄露完libc再回到main函数,栈会被抬高。先判断抬高的栈距离我们第一次泄露的栈有多远。
先打印出原先的栈
再gdb调试输入aa看buf的地址为多少
之前的栈是f0a8,抬高的栈是efd8。偏移为0xD0字节。开始构造第二个payload。
exp:
二:SROP
sigreturn 系统调用号为0xF。
原理:
深一点讲:
因此我们可以跳过①②两个步骤,自己在栈上布置上下文
sigreturn系统调用代码的存放位置。
内核会根据上下文(Sigreturn Farme)的内容来恢复对应的寄存器。
例题:
main函数中的vuln函数:
可以看出调用read和write都是通过syscall来调用的,没有leave 只有retn,不用覆盖ebp,是buf缓冲区和rsp直接相连的。
可以看到该gadgets汇编代码是把rax设置为0xF,其实就是调用15号系统调用,也就是sigreturn。记下gadgets地址0x4004DA
动态调试看write能泄露的东西。
write和read的位置是一样的,dd60,查看write打印出的30个字节
我们可以通过这个地址来泄露栈地址,因为binsh是存在栈中,泄露出来栈计算偏移也就相当于我们知道了binsh的位置。
之后来调用SROP,将rax设置为59,rip设置为syscall,再将rdi设置为binsh的地址。就能getshell,在此之前先泄露栈地址。
gdb调试时可以看到
接下来泄露栈地址计算偏移。
泄露的地址后四位是ec68,动态调试write与read地址一样,后四位为eb50,偏移为ec68-eb50=0x118 ,那么binsh的位置就是栈地址减去0x118
接下来构造第二个payload
用binsh覆盖缓冲区,接着先调用gadget把rax设置为0xF,再调用syscall,然后syscall调用frame,frame也就是我们构造的上下文(Sigreturn Farme),发送即可 getshell。
除此之外,我们有时也可以通过调用setcontext函数来进行SROP,在Glibc2.27 环境下我们一般用setcontext+53来完成SROP。
但是当GLibc发生变化时,setcontext代码也会有所变化,这种方法一般都是结合堆来利用。
SROP利用方式总结:
首先要通过栈溢出控制栈的内容,结合堆的话就当前边没说。接着需要知道相应的地址。 有些题把59号系统调用exceve禁用了 ,这种情况下就需要通过orw(open read write)来得到flag。又可以通过mprotect来划出一片可读可写可执行的区域,利用 shellcode来orw,得到flag。
Comments | NOTHING