使用 ttyutils 截获 UNIX/Linux 终端

8:21:00 PM 0 Comments

对于大多数 UNIX 和 Linux 用户,终端无疑是不可或缺的。本文将描述一个可以让 UNIX 终端会话完整受控的软件包,它叫做 Ttyutils。这篇文档将向大家介绍 Ttyutils 的基本使用方法以及它的实现机制。

在本文档中,UNIX 终端也泛指 Linux 终端。并且,本文中的终端不仅仅包括物理终端,也包括虚拟控制台,图形仿真终端,例如 xterm, 和具备网络能力的仿真终端,例如 putty。

为了有助更加形象的描述,这篇文档会使用截获,捕获,甚至偷窥这样的词语,但这是没有恶意的。

监控终端会话

在通常情况下,当用户从终端登录 UNIX 主机时,系统登录程序会记录用户的登录时间,主机名称等信息到系统 utmp 数据库和 wtmp 数据库,却不能跟踪用户进入 shell 之后的会话。

有一些程序,例如 script,可以记录用户在终端上的会话到一个文件中,然后在后来的某个时候通过那个文件重现用户当时的会话。script 的局限性在于它只能记录,而不能干涉用户的会话,例如当用户在命令行输入"rm -fr /"这样的命令时,它没有办法阻止。

在一些受控的环境中,系统管理员必须有能力实时的查看用户的会话,或者因为好奇,想偷窥远程登录到你的主机的用户在干些什么,并且,如果必要,还可以实时的禁止或者终止用户进行更多的操作,这是很有趣的。

在 UNIX 中,有很多使用终端作为人机交互接口的应用程序,例如常用的 shell, vi,还有一些特定于行业的应用程序,例如我们在银行里办理业务时,营业柜员使用的操作接口。如果能够通过截获和实时的分析程序的输出所引起的终端变化, 并可以根据这些变化的特征来触发特殊的动作将是非常有用的。例如,当用户在提示符下输入 vi 两个字符时,就自动的启动 vi 编辑器,这样就可以省去按下回车键的麻烦。

考虑这样的情况,有一个运行在终端上的重要软件包,例如金融机构的业务处理系统,需要对现有的业务进行改造,例如为了支持新的设备,但是却因为各种原因, 不能修改系统的代码。这可以通过对现有的系统进行监控和分析来做到,并且,因为没有直接修改现有的系统,可以很容易的移除或修改。

Ttyutils 是为这个目标设计的一个软件包,它主要包括三个实用程序:ttyexec,ttyadmin,和 ttylook。

安装 ttyutils

Ttyutils 软件包使用 RPM 包发布,首先从参考资源列出的地址下载 ttyutils 软件包的最新版本,然后使用下面的命令安装 :

# rpm -Uvh ./ttyutils-@version.@release.@platform.rpm

其中 @version,@release 和 @platform 分别是版本号,发行号,以及平台。

安装 RPM 软件包通常需要 root 权限。

安装完成后,你可能需要配置一下日志文件。Ttyutils 的日志采用 syslog 函数记录,采用的 facility 为 LOG_LOCAL3,默认情况下,在 Linux 平台,所有消息将输出到 /var/log/messages 文件中,可以通过配置 /etc/syslog.conf 来重定向到一个单独的文件中,例如下面是一个例子 :

# Save ttyutils messages to ttyutils.log
local3.* /var/log/ttyutils.log

对于不支持 local3 作为 facility 的系统,可以采用下面的语法 :

# Save ttyutils messages to ttyutils.log
*.debug /var/log/syslog.out

Ttyutils 软件包的程序支持 --logfile 选项,它可以将日志重定向到一个指定的文件中。

一切就绪,下面开始一个简单的会话。

登录两个终端 A 和 B,在终端 A 的 shell 提示符下输入 :

$ ttyexec
$ tty
/dev/pts/3

其中"/dev/pts/3"是 tty 命令的输出,可能会是其它的名字,记住这个名字,然后在终端 B 输入:

$ ttylook --pts /dev/pts/3 -w

这时终端 A 的输出将会反应在终端 B,反之亦然。

其中,ttyexec 是监控程序,它的使用方法与 shell 中命令 exec 相似,被 ttyexec 启动的程序的输入输出将被 ttyexec 截获,如果没有参数,那么它会执行在 /etc/passwd 文件中为当前用户设置的 shell 程序。

当通过 ttyexec 在终端 A 监控了一个程序之后,ttyexec 自身不会记录任何关于会话的标准输入输出信息到某个文件或者其它地方,如果用户想查看被监控的程序的标准输出,需要使用在另外一个终端 B 上使用 ttylook 连接到那个 ttyexec,当连接建立以后,ttyexec 就会将截获的数据发送给 ttylook,而 ttylook 则将这些数据输出到终端 B 上。这个时候,终端 B 的屏幕和终端 A 的屏幕将会一样,并且如果不断开连接,将会实时的保持同步。这就相当于两个终端共享了一个用户会话。因为 ttyexec 支持多个 ttylook 连接,也就是说可以三个,四个,或者更多个终端共享同一个用户会话。

ttyadmin 是用于管理当前系统中运行的 ttyexec 进程的工具,对于熟悉 Ttyutils 的用户,它提供了一些很有用的功能。

使用 ttyexec 截获用户会话

ttyexec 是监控程序,被它监控的程序的标准输入输出将会被 ttyexec 截获,ttyexec 的用法很简单,只需要在命令行指定一个将被监控的程序名称即可,例如:

$ ttyexec vim

如果那个程序需要参数,直接跟在命令行后面即可,例如 :

$ ttyexec vim /etc/inittab

使用 ttyexec 不需要任何的特权。

如果没有在命令行中指定一个程序名称,那么 ttyexec 将会尝试去执行当前用户在 /etc/passwd 文件中登记的 shell 程序。

一个简单的技巧用来完整的监控用户从登录开始到退出登录之间的完整会话,就是在用户的 profile 文件最后加入下面的二行 :

ttyexec
exit $?

在本文档中,运行一个 ttyexec 实例就是为了监控一个用户在终端的会话,所以,有时候为了描述方便,也用一个 ttyexec 实例来代表一个监控的用户会话。

使用 ttylook 控制用户会话

当使用 ttyexec 监控了一个程序之后,如果想偷窥被监控终端上的会话,就需要在另外一个终端使用 ttylook 连接到 ttyexec。

使用 ttylook,必须要知道运行 ttyexec 的那个终端的终端名称,伪终端名称,或者进程 ID 中的至少一个。这可以很容易的从 ttyadmin 获得,看下图:


图 1 ttyadmin 截图

这是运行在 IBM AIX5.1 上的 ttyadmin 的截图,在主窗口中,每一行列出了当前系统中的一个 ttyexec 实例,信息包括运行它的用户 ( 图中故意模糊了 ),进程 ID,终端名称,伪终端名称,运行的时间和运行的命令。

在上图中我们可以看到当前系统有 13 个 ttyexec 实例在运行。

在上图中,如果用 ttylook 连接被监控的终端 /dev/pts/16,可以使用下面的命令中的任意一个,它们是等价的:

$ ttylook --tty /dev/pts/16
$ ttylook --pid 319688
$ ttylook --pts /dev/ttypf

在默认情况下,ttylook 在只读模式运行,也就是说,它只能偷窥被监控的终端,而不能操作被监控的终端,如果要操作被监控的终端,它必须通过命令行 --writable 进入可写模式。

但是进入了可写模式很危险,特别是你不希望让被监控终端上的用户发现的时候。所以默认是只读模式。

回顾一下,假设用户 1 在终端 A 在上运行着 ttyexec,用户 2 在终端 B 上运行着 ttylook,当 ttylook 连接到的 ttyexec 实例后,终端 A 和终端 B 的屏幕内容将实时同步,所以,用户 1 在被监控终端上的所有会话对于用户 2 而言就暴露无遗了。

使用 ttyadmin 管理 ttyexec 实例

在系统中通常会有多个 ttyexec 实例在运行,前面的 ttyadmin 截图已经大致说明了 ttyadmin 的用途,但它除了可以列出当前系统中所有的 ttyexec 实例外,还有一些其它的用途,例如它可以很容易的启动 ttylook,查看 ttyexec 实例的事件表等等。

使用 ttyexec 的事件触发

如果只是希望监控用户在终端上的会话的话,那么只需要使用前面描述的内容即可,但是如果还想得到更多,那么就可能需要这里描述的内容- 事件触发。

ttyexec 的一个有用的机制是事件触发,它是根据被监控程序的标准输出引起终端屏幕的变化来工作的,每当被监控程序改变终端屏幕的内容时,ttyexec 会根据更新后的屏幕内容与事件表中的事件条件进行匹配,如果成功,那么就会执行一个事件定义的程序。

事件表

在 ttyexec 内容维护了一个事件表,每个节点是一个事件,每个事件由 4 个部分组成:

  • 事件的名称;
  • 事件的组名称;
  • 事件的匹配器;
  • 事件的动作;

事件名称 是事件唯一的标识, 事件组名称 是对多个事件的划分,这两个名称主要用于事件的管理。

事件匹配器 由一个或多个 事件条件 组成,每个 事件条件 又根据类型的不同而不同,我们介绍完虚拟终端后再回头描述事件条件。

事件动作 是一个程序的名称和它的参数 ( 如果有的话 ),它是一个标准的可执行程序,在事件的匹配器得到匹配时执行。

虚拟终端

ttyexec 内部运行着一个虚拟的终端,它可以正确的解析发送到实际终端的那些控制序列,并在内存中重构一个和真实终端屏幕一样大小的屏幕,它的内容和用户在实际终端上看到的是一样的,但是对于用户而言,它是不可见的。

每当被监控的程序改变终端屏幕的内容时,ttyexec 的虚拟终端也会同步更新自己的屏幕,然后它开始检查事件表,如果发现了匹配的事件,就执行为事件定义的事件动作。

事件匹配

在 ttyexec 的事件表的每个事件中, 事件匹配器 由一到多个 事件条件 组成, 事件条件 是真正要去和虚拟终端屏幕比对的单元, 事件匹配器 只有在所有事件条件都匹配的情况下才是匹配的。

每个 事件条件 的类型有两种,第一种是根据光标的位置,第二种是根据屏幕上的数据。

光标位置是指光标到达某个指定的坐标时,条件就得到了满足,这种类型的条件需要两个参数,坐标的行和列。

屏幕的内容是指屏幕某个区域的内容达到预期的值,这种类型的条件需要四个参数,行,起始列,长度,以及一个正则表达式。

因为一个 事件匹配器 可以包含多个条件,并且只有匹配器中的所有条件都得到满足时,匹配器才算匹配,所以可以定义出很精确的匹配,当然也可以定义很模糊的匹配。

事件表维护

因为事件需要根据不同的应用场合而进行独立的设置,所以必须有能力去管理 ttyexec 的事件表,这需要了解一些基本的计算机编程。

有两种途径来管理 ttyexec 的事件表,第一种是通过 ttyexec 的配置文件,叫做 ttyexec.lua,这个文件使用标准的 Lua 程序语言。这种方式称为静态事件管理。

第二种方式是使用 ttyutils 提供的编程接口,外部程序可以动态的维护 ttyexec 事件表,这种方式称为动态事件管理。动态事件管理很强很实用,因为这样我们就可以在事件触发之后的程序中再去动态的管理 ttyexec 的事件表,这可以为事件提供了上下文关系。

在一个复杂的环境中,通常利用静态管理来维护 ttyexec 的初始事件,而在适当的时候利用动态事件管理来动态的维护 ttyexec 的事件表。

RPC 服务

远程过程调用 (remote process call)RPC 服务是 ttyexec 为进程间通讯提供的接口,注意它和 SUN RPC 没有任何的关系,并且不能跨网络使用。

当事件触发时,事件动作程序被启用,事件动作程序是一个标准的应用程序,它可以做任何它喜欢的事情,但是有时候,它需要和 ttyexec 交互,例如它可能需要获取当前终端屏幕的内容, ttyexec 为这样的情况提供了准备,称为 RPC 服务。

Ttyexec 提供了下面的服务 :

  • snap: 获取当前终端屏幕的快照;
  • feed: 发送数据作为实终端的标准输入;
  • suspend/resume_stdio: 悬挂 / 恢复实终端标准输入;
  • suspend/resume_stdout: 悬挂 / 恢复实终端标准输出;
  • insert/remove_event: 添加 / 删除事件表中的事件;

下面用一个例子描述如何编写一个 C 程序来使用上面提供的 feed 服务,包括从源代码编写到编译,连接完整的步骤。

#include <ttyutils.h>

int
main (int argc, char *argv[])
{
TRpcTarget target;

if (!t_init ())
return 1;

target.rt_timeout = 3;
target.rt_type = RPC_TARGET_PTS;
target.rt_pts = "/dev/pts/3";

if (!t_rpc_call_set_target (&target))
{
t_println ("set rpc target failed.");
return 1;
}

if (!t_rpc_call_feed ("hello", 5))
{
g_warning (_("rpc call failed."));
return 1;
}

t_uninit ();
return 0;
}

将上面的代码保存到文件 example.c,然后到用下面的命令编译它 :

$ cc -o example example.c `pkg-config --cflags --libs ttyutils-1`

第 1 行包含了头文件 <ttyutils.h>,这是每个外挂程序应该做的。

第 6 行声明了一个 TRpcTarget 结构的变量,这个结构描述如下 :

typedef enum
{
RPC_TARGET_TTY,
RPC_TARGET_PTS,
RPC_TARGET_PID,
RPC_TARGET_PATH
} TRpcTargetType;

typedef struct _TRpcTarget TRpcTarget;
struct _TRpcTarget
{
TRpcTargetType rt_type;
guint32 rt_timeout;
union
{
gchar *t_tty;
gchar *t_pts;
pid_t t_pid;
gchar *t_path;
} rt_target;
};
#define rt_tty rt_target.t_tty
#define rt_pts rt_target.t_pts
#define rt_pid rt_target.t_pid
#define rt_path rt_target.t_path

第 8 行调用了 t_init() 函数,要使用 ttyexec 接口的程序应该首先调用这个函数 ( 在调用其它接口函数之前 )。

第 11 行到 13 行设置 RPC 的目标,这里我们选择的是使用伪终端名称 "/dev/pts/3"。

上面 TRcTargetType 的枚举定义了我们可以使用的连接方式,它们分别是 :

RPC_TARGET_TTY ttyexec 实例使用的终端名称

RPC_TARGET_PTS ttyexec 实例运行命令的伪终端名称

RPC_TARGET_PID ttyexec 的进程 ID

RPC_TARGET_PATH ttyexec 为 RPC 服务建立的 UNIX 套接字名称

结构 TRpcTarget 中的联合体 rt_target 中的每个成员分别对应于每种不同的连接方式。为了方便的访问到 rt_target 中的成员,还分别定义了一些简化的宏 (rt_tty 等等 )。

第 15 到 19 行调用 t_rpc_call_set_target() 函数,它用我们上面设置好的 target 结构体作为参数。

t_println() 是一个调试用的函数,当编译程序时定义了 DEBUG 或者 T_DEBUG 时,它的功能和 printf() 类似,当没有定义上面的两个宏时,它不生成任何代码。

第 21 到 25 行调用函数 t_rpc_call_feed (),这个函数访问 ttyexec 提供的 feed 服务,它的作用是发送一些数据到 ttyexec 作为标准输入。

g_warning() 是 glib 提供的一个函数,它的作用是输出一条警告消息。

第 27 行调用 t_uninit() 函数,它的作用是释放资源,应该在程序退出之前调用。





回页首


Ttyutils 内部原理描述

前面描述了如何使用 ttyutils 软件包中的程序来监控终端的会话,读者可能好奇它的内部是如何工作的,这一部分将描述这些内容。

实现 ttyexec

ttyexec 是一个监控程序,它通过建立一个伪终端来运行一个应用程序,从而达到监控那个应用程序的目的。所以,要理解 ttyexec 的实现,必须理解伪终端。

伪终端

伪终端这个名词暗示它是一个虚拟的终端,应用程序可以将其作为一个终端设别使用,但是它不是一个真实的终端,一个伪终端由两端构成,分别称为伪终端主设备 和从设备,任何写到伪终端主设备的输入都会作为从设备端的输入,反之亦然。事实上所有从设备端的输入都来自于主设备上的用户进程。这看起来就像一个流管 道,但从设备上的终端行规程使我们拥有普通管道之外的其他处理能力。

文件 /dev/ptmx 是一个字符设备,它用来建立伪终端主从设备。

截获进程输入输出

ttyexec 通过建立一个伪终端,然后调用 fork。子进程建立了一个新的对话,打开一个相应的伪终端从设备,将它复制成标准输入、标准输出和标准出错,然后调用 exec。伪终端从设备成为子进程的控制终端。

对于伪终端从设备之上的用户进程来说,其标准输入、标准输出和标准出错都能当作终端设备使用。用户进程能够调用所有输入 / 输出函数。

虚拟终端

当 ttyexec 从伪终端主设备接收到数据后,这些数据是原始的 Escape 序列,它还需要传递给内置的 Escape 解析器,最后在内存中构造一个和屏幕上相同的虚拟终端。

注意虚拟终端,伪终端和实际终端之间的区别,下面的图描述了它们之间的关系:


图 2 ttyexec 内部关系

当使用 ttyexec 执行其它命令时, ttyexec 实际上拥有了两个终端设备,一个是继承于 shell 的实终端 ( 也可能是伪终端,例如在终端仿真程序中运行 ttyexec),另外一个是 ttyexec 建立的伪终端,后者将被 ttyexec 执行的命令使用。

当在使用 ttyadmin 时,得到的输出中有一栏名为 TTY,还有一栏名为 PTS,分别指的就是上面的实终端和伪终端。

ttyexec 拥有两个终端设备,其中伪终端被用于执行的命令,因为伪终端的一端的输出将会是另一端的输入,也就是说,被 ttyexec 执行的命令的所有输出将是 ttyexec 的输入,所以, ttyexec 截获了被执行的命令的全部终端输出。

同样,因为用户从真实终端的数据要通过 ttyexec 转发到伪终端上执行的命令,所以 ttyexec 也截获了用户的所有输入,并且可以模拟用户的输入发送数据到伪终端,这可以用于自动填充数据这样的场合。

通过虚拟终端在内存中重构一个终端屏幕,可以实时的跟踪屏幕的变化,并且根据当前屏幕的特征来触发事件,完成特定的事情。

伪终端是双向通讯设备,所以 ttyexec 可以输入数据到伪终端来模拟设备的标准输入 ( 键盘输入 ),ttyexec 提供的 feed RPC 服务就利用了这个特征。

需要澄清的一点是,虽然 ttyexec 能够控制被执行命令在终端上的的输入和输出,但不能改变被执行命令的内部程序逻辑。

支持 ttylook

Ttyexec 通过内置了一个网络服务器来支持 ttylook,这个服务在 ttyexec 中称为 lookd,它利用 UNIX socket 等待和 ttylook 建立一个双向的通讯,将截获的数据发送给 ttylook 即可,这项服务不能跨网络使用。

因为是双向通讯,所以 ttylook 也可以发送数据给 ttyexec 来写入伪终端主设备作为被 ttyexec 执行的程序的标准输入。

支持 RPC 服务

ttyexec 通过内置的一个网络服务器来支持 RPC 服务,这个服务在 ttyexec 中称为 RPCD,它利用 UNIX socket 等待和应用程序建立一个双向的通讯,然后根据应用程序的请求,返回相应的响应,这项服务不能跨网络使用。

因为 RPC 请求和响应的格式是由 ttyexec 内部定义的,为了方便开发人员使用,Ttyutils 提供了一个库,对这些细节进行了封装,所以在应用程序中使用 RPC 服务是非常简单的事情。

支持事件触发

事件触发的根本在于前面提到的虚拟终端,在 ttyexec 中,维护了一个事件表,每当虚拟终端发生变化时,它将会去用当前虚拟终端屏幕的内容去逐项匹配事件表中的事件,如果能够匹配,那么将触发一个动作。

因为终端的变化非常的频繁,如果事件表中有大量的事件,那么速度会受到一定的影响,但是为了确保实时性,这样的匹配是需要的,具体事件表能够加载多少事件没有限制,这个由管理人员根据系统的处理能力而定。

实现 ttylook

Ttylook 是 ttyexec lookd 服务器的客户端,它实现了两项功能:

1.从 ttyexec 接收数据,然后写到 用户终端

2.从 用户终端 接收数据,然后发送到 ttyexec。

这里 用户终端 是指运行 ttylook 的终端。

接收 ttyexec 的数据

ttylook 在启动时,将通过 UNIX socket 和 ttyexec 建立连接。

ttyexec 接收了这个连接之后,它会将所有被监控程序的标准输出数据 ( 输出到终端屏幕上的数据 ) 传送给 ttylook,然后 ttylook 简单的将这些数据写到用户的终端屏幕。

注意 ttylook 不理睬在 socket 之间传送的数据内容,这些数据直接被写到用户终端屏幕,所以如果运行 ttyexec 和 ttylook 的终端类型不同,那么可能会出现屏幕的内容错乱。

发送数据给 ttyexec

ttylook 在启动时,会将用户终端的标准输入 ( 键盘 ) 至于关闭回显模式,这样,用户在终端上的输入在屏幕上是不可见的。

但是 ttylook 会得到这些输入数据,然后将这些数据传送给 ttyexec,ttyexec 会将这些数据作为用户的输入写到被监控的进程,这就好像运行 ttyexec 的用户输入了数据一样。

实现 ttyadmin

当 ttyexec 启动的时候,它会将它自身的信息注册到一个系统区域,称为黄页,这个黄页是用一片共享内存实现的,ttyexec 注册的信息包括它的进程 ID,终端和伪终端名称,lookd 服务和 rpcd 服务帮定的地址,等等,每个 ttyexec 在黄叶中占据一项。

当 ttyexec 退出的时候,它会将自身的注册信息从黄页中删除。

Ttyadmin 主要是管理这个黄页的程序。

查看 ttyexec 实例

在 ttyadmin 启动时,它建立一个全屏幕的窗口,然后查询黄页,得到当前系统中所有 ttyexec 实例的注册信息,并将这些数据缓存到自己的内部表中。

然后它格式化得到的信息,显示在主窗口中。

后续的所有操作 ( 例如排序 ) 都是基于这个内部表,但是允许用户去重读黄页 ( 当前的实现是按下空格键或者设定一个更新超时 )。

管理 ttyexec 实例

ttyadmin 提供了对 ttyexec 的管理能力,这些能力可以在运行 ttyadmin 时通过帮助获得,这里要说明的是,这些管理功能主要是基于黄页中的信息。

虽然 ttyadmin 可以查看和管理 ttyexec,但是它和 ttyexec 没有直接的关系。

ttyexec 也为 ttyadmin 提供了一些管理功能 ( 例如查询事件表的内容 ),这是通过 ttyexec 的 RPC 服务实现的,在这种情况下,ttyadmin 只能算是 ttyexec 的一个 RPC 客户端。

Some say he’s half man half fish, others say he’s more of a seventy/thirty split. Either way he’s a fishy bastard.