tty_struct attack
当用户打开 ptmx 驱动时 open("/dev/ptmx", O_RDWR)
,会分配一个 tty_struct
结构,它的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 struct tty_struct { int magic; struct kref kref ; struct device *dev ; struct tty_driver *driver ; const struct tty_operations *ops ; int index; struct ld_semaphore ldisc_sem ; struct tty_ldisc *ldisc ; struct mutex atomic_write_lock ; struct mutex legacy_mutex ; struct mutex throttle_mutex ; struct rw_semaphore termios_rwsem ; struct mutex winsize_mutex ; spinlock_t ctrl_lock; spinlock_t flow_lock; struct ktermios termios , termios_locked ; struct termiox *termiox ; char name[64 ]; struct pid *pgrp ; struct pid *session ; unsigned long flags; int count; struct winsize winsize ; unsigned long stopped:1 , flow_stopped:1 , unused:BITS_PER_LONG - 2 ; int hw_stopped; unsigned long ctrl_status:8 , packet:1 , unused_ctrl:BITS_PER_LONG - 9 ; unsigned int receive_room; int flow_change; struct tty_struct *link ; struct fasync_struct *fasync ; wait_queue_head_t write_wait; wait_queue_head_t read_wait; struct work_struct hangup_work ; void *disc_data; void *driver_data; spinlock_t files_lock; struct list_head tty_files ; #define N_TTY_BUF_SIZE 4096 int closing; unsigned char *write_buf; int write_cnt; struct work_struct SAK_work ; struct tty_port *port ; } __randomize_layout;
其中有一个 struct tty_operations
指针,而 tty_operations
结构体里是一些列对驱动操作的函数指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 struct tty_operations { struct tty_struct * (*lookup )(struct tty_driver *driver , struct file *filp , int idx ); int (*install)(struct tty_driver *driver, struct tty_struct *tty); void (*remove)(struct tty_driver *driver, struct tty_struct *tty); int (*open)(struct tty_struct * tty, struct file * filp); void (*close)(struct tty_struct * tty, struct file * filp); void (*shutdown)(struct tty_struct *tty); void (*cleanup)(struct tty_struct *tty); int (*write)(struct tty_struct * tty, const unsigned char *buf, int count); int (*put_char)(struct tty_struct *tty, unsigned char ch); void (*flush_chars)(struct tty_struct *tty); int (*write_room)(struct tty_struct *tty); int (*chars_in_buffer)(struct tty_struct *tty); int (*ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); long (*compat_ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); void (*set_termios)(struct tty_struct *tty, struct ktermios * old); void (*throttle)(struct tty_struct * tty); void (*unthrottle)(struct tty_struct * tty); void (*stop)(struct tty_struct *tty); void (*start)(struct tty_struct *tty); void (*hangup)(struct tty_struct *tty); int (*break_ctl)(struct tty_struct *tty, int state); void (*flush_buffer)(struct tty_struct *tty); void (*set_ldisc)(struct tty_struct *tty); void (*wait_until_sent)(struct tty_struct *tty, int timeout); void (*send_xchar)(struct tty_struct *tty, char ch); int (*tiocmget)(struct tty_struct *tty); int (*tiocmset)(struct tty_struct *tty, unsigned int set , unsigned int clear); int (*resize)(struct tty_struct *tty, struct winsize *ws); int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew); int (*get_icount)(struct tty_struct *tty, struct serial_icounter_struct *icount); int (*get_serial)(struct tty_struct *tty, struct serial_struct *p); int (*set_serial)(struct tty_struct *tty, struct serial_struct *p); void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m); #ifdef CONFIG_CONSOLE_POLL int (*poll_init)(struct tty_driver *driver, int line, char *options); int (*poll_get_char)(struct tty_driver *driver, int line); void (*poll_put_char)(struct tty_driver *driver, int line, char ch); #endif int (*proc_show)(struct seq_file *, void *); } __randomize_layout;
tty_struct
结构体的大小为 0x2E0
,在 kernel pwn 中,如果可以有 kmalloc-1k
我们就会考虑进行 tty_struct attack
(劫持 tty_operations
,控制执行流)
内核的利用并不是本篇文章的重点,接下来我们将关注 tty
本身在内核中的作用
仿真终端
tty
其实是 “电传打字机(Teletypewriter)” 的缩写(后来这种设备逐渐键盘和显示器取代),泛指计算机的终端(terminal)设备
在 Linux 或 UNIX 中,tty
变为了一个抽象设备(用于表示各种类型的终端设备):
有时它指的是一个物理输入设备(例如串口)
有时它指的是一个允许用户和系统交互的“虚拟仿真终端设备”
现代物理 IO 设备都采用“键盘+显示器”:
如果用户态程序要把内容输出到显示器,只要把这些内容写入到显示器对应的 tty
设备就可以了,然后由 tty
层负责匹配合适的驱动完成输出,这也是 Linux 控制台的工作原理:
显示器和键盘这类物理设备会被抽象为驱动 driver
而终端仿真程序 Terminal Emulator(虚拟终端)使用驱动接口完成进一步的抽象
本质上来讲键盘输入的字符是没有意义的,而终端仿真程序会对这些字符进行“格式化”,“适配”和“解释”,这个过程被称为行规程 Line Discipline
经过行规程的数据将会通过 tty
和用户层进行交互,同时返回输出数据
返回的数据也要经过行规程,然后被“翻译”为显示器驱动可以理解的形式并输出
在 Linux 中可以直接查看 tty
层的设备:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ➜ ~ ls /dev/tty* /dev/tty /dev/tty23 /dev/tty39 /dev/tty54 /dev/ttyS10 /dev/ttyS26 /dev/tty0 /dev/tty24 /dev/tty4 /dev/tty55 /dev/ttyS11 /dev/ttyS27 /dev/tty1 /dev/tty25 /dev/tty40 /dev/tty56 /dev/ttyS12 /dev/ttyS28 /dev/tty10 /dev/tty26 /dev/tty41 /dev/tty57 /dev/ttyS13 /dev/ttyS29 /dev/tty11 /dev/tty27 /dev/tty42 /dev/tty58 /dev/ttyS14 /dev/ttyS3 /dev/tty12 /dev/tty28 /dev/tty43 /dev/tty59 /dev/ttyS15 /dev/ttyS30 /dev/tty13 /dev/tty29 /dev/tty44 /dev/tty6 /dev/ttyS16 /dev/ttyS31 /dev/tty14 /dev/tty3 /dev/tty45 /dev/tty60 /dev/ttyS17 /dev/ttyS4 /dev/tty15 /dev/tty30 /dev/tty46 /dev/tty61 /dev/ttyS18 /dev/ttyS5 /dev/tty16 /dev/tty31 /dev/tty47 /dev/tty62 /dev/ttyS19 /dev/ttyS6 /dev/tty17 /dev/tty32 /dev/tty48 /dev/tty63 /dev/ttyS2 /dev/ttyS7 /dev/tty18 /dev/tty33 /dev/tty49 /dev/tty7 /dev/ttyS20 /dev/ttyS8 /dev/tty19 /dev/tty34 /dev/tty5 /dev/tty8 /dev/ttyS21 /dev/ttyS9 /dev/tty2 /dev/tty35 /dev/tty50 /dev/tty9 /dev/ttyS22 /dev/tty20 /dev/tty36 /dev/tty51 /dev/ttyprintk /dev/ttyS23 /dev/tty21 /dev/tty37 /dev/tty52 /dev/ttyS0 /dev/ttyS24 /dev/tty22 /dev/tty38 /dev/tty53 /dev/ttyS1 /dev/ttyS25
/dev/tty
:(控制终端)
代表当前 tty
设备
在当前的终端中输入 echo hello > /dev/tty
,都会直接显示在当前的终端中
/dev/tty1 ~ /dev/tty6
:(虚拟终端)
用于表示运行在内核态的软件仿真终端
可以把这些 tty
设备理解为对虚拟终端的一种抽象,使得用户程序能以操控文件的形式来与虚拟终端交互
/dev/tty0
:(虚拟终端)
代表当前虚拟终端
/dev/tty
主要是针对进程来说的,而 /dev/tty0
是针对整个系统来说的(所以 /dev/tty0
拥有更高的权限)
/dev/tty7 ~ /dev/tty63
:(其他终端)
用于表示运行在内核态的其他终端
这些 tty
是由其他的关键软件使用的(例如 Ubuntu 中 /dev/tty7
就是图形显示管理器)
/dev/ttyS0 ~ /dev/ttyS31
:(串行端口终端)
我们可以做一个实验来感受一下 /dev/tty1 ~ /dev/tty6
:
先在当前终端中输入 Ctrl + Alt + F4
切换到 /dev/tty4
上图显示的就是一个虚拟终端 Terminal Emulator,用户态的 Shell 运行在它上面
输入 Ctrl + Alt + F2
切换到桌面环境,输入 sudo echo "hello" > /dev/tty4
,然后返回 /dev/tty4
通过操作 /dev/tty4
文件,可以把用户态程序输入的内容输出到对应的虚拟终端上
可以认为 tty
是虚拟终端的一个抽象层:
tty
给用户态程序 Shell 提供了一种抽象,使其能够以操作文件的形式来控制各种终端
但是 tty
是运行在内核态中的,为了便于将终端仿真移入用户空间,同时仍保持 tty
子系统的完整,伪终端被发明了出来(被称为 pseudo-TTY
,简称 pty
)
每当你在系统中启动一个终端仿真器或使用任何类型的 shell 时,它都会与 pty
进行交互
当创建一个伪终端时,会在 /dev/pts
目录下创建一个设备文件(用于关联 pty
)
1 2 3 4 5 6 ➜ ~ ls -l /dev/pts 总用量 0 crw--w---- 1 yhellow tty 136, 0 10月 7 20:24 0 crw--w---- 1 yhellow tty 136, 1 10月 7 20:24 1 crw--w---- 1 yhellow tty 136, 2 10月 7 20:41 2 c--------- 1 root root 5, 2 10月 7 19:14 ptmx
1 2 3 4 5 ➜ ~ lsof /dev/ptmx COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME gnome-ter 4015 yhellow 19u CHR 5,2 0t0 87 /dev/ptmx gnome-ter 4015 yhellow 20u CHR 5,2 0t0 87 /dev/ptmx gnome-ter 4015 yhellow 21u CHR 5,2 0t0 87 /dev/ptmx
你可以在终端仿真器中输入 tty
来找到相关联的 pty
:
伪终端
伪终端(被称为 pseudo-tty
,简称 pty
)是指伪终端 master 和伪终端 slave 这一对字符设备,其中的 slave 对应 /dev/pts/
目录下的一个文件,而 master 则在内存中标识为一个文件描述符(fd):
master 端 - ptm
:是更接近用户显示器、键盘的一端(基于 VFS 的特殊文件)
slave 端 - pts
:是在虚拟终端上运行的 CLI(Command Line Interface,命令行接口)程序
伪终端本质上是运行在用户态的终端模拟器创建的一对字符设备:
/dev/ptmx
是一个字符设备文件,当进程打开 /dev/ptmx
文件时,进程会同时获得:
一个指向 pseudoterminal master(ptm)
的文件描述符
一个在 /dev/pts
目录中创建的 pseudoterminal slave(pts)
设备
建了一个伪终端对,并让 shell 运行在 slave 端:
当用户在终端模拟器中按下键盘按键时,它产生字节流并写入 master 中,shell 便可从 master 中读取输入(以读文件的形式)
然后 shell 和它的子程序将输出内容写入 slave 中,由 CLI 程序进行显示
可以认为 pty
是一种轻量级的虚拟终端:
是一些软件(如 ssh、screen、xterm 等)模拟的 Terminal Emulator
在 Linux 中右键打开的终端就是 pty
伪终端的运用
Telnet 和 SSH 都运用了伪终端技术(主要是远程登录部分):
每次用户通过客户端连接服务端的时候,服务端创建一个伪终端 master、slave 字符设备对
在 slave 端运行 login 程序,将 master 端的输入输出通过网络传送至客户端
客户端则将从网络收到的信息直接关联到键盘/显示器上
网络通信的方式还是依靠 NC 底层的 socket(TCP 协议发包)
将 socket 生成的 socketFD 重定位为 master 端的“标准输入”(把管道的 stdin
重定位为 socketFD,再把管道的 stdout
重定位为 master)
使 master 端和 socketFD 共用一个命名管道(命名管道相当于两个文件,一个用来读,一个用来写,它们底层使用同一个 inode
所以数据共享)
最后把 login 的 stdin stdout stderr
重定位到 slave 上