当前位置:   article > 正文

Linux让终端只接受键盘输入_shell模拟键盘输入

shell模拟键盘输入

  这篇博客主要是一个终端的骚操作,没有什么知识点,都是一些经验之谈,表述上甚至可能有错误。

怎么在程序里强行让用户只能键盘输入

  在Linux的程序里(其实Windows里也差不多)一般能键盘输入的,就能用重定向输入,但是,有办法让人不能轻易用重定向代替键盘输入。
  最简单的,ssh这个工具本身不支持直接使用参数输入密码(当然可以通过sshpass之类的工具在参数输入密码),而且ssh虽然支持重定向输入,但是只能把命令重定向过去,而无法把密码重定向过去。
  类似ssh这样强制只能键盘输入的程序怎么实现呢?
  举个最简单的例子(先用shell写一个最简单的实现):

#!/bin/bash

ptsname="/dev/$(ps | awk "\$1==\"$$\"{print \$2}")"
exec 200<"$ptsname"
inputdata=""
read -u 200 inputdata
exec 200<&-

echo "you input: $inputdata"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  运行一些看看效果:
在这里插入图片描述

  这样就写好了一个最简单的强制键盘输入的程序了,在使用echo "fu** you"重定向到这个shell脚本的时候,它并没有从标准输入读入,而是从终端读入,你可以看看ptsname变量的内容,就是当前终端的路径。
  这个shell脚本很简单,但是作为经验之谈,我稍微解释一下各行命令的作用:
  首先是ptsname="/dev/$(ps | awk "\$1==\"$$\"{print \$2}")"
  我们先看看ps的输出:
在这里插入图片描述
  可以看到,ps未使用任何参数的情况下可以获取到当前进程以及父进程的信息,在这张图片里,当前进程就是ps,父进程就是bash。而$$是代替当前进程号码,在这里也就是3158这个bash的进程号码(专业一点的说法是pid)。

有些老六会说$$是父进程的pid而不是当前进程的pid,其实不是,$$就是当前进程的pid,因为这些老六是这样用的:
echo "$$"
然后他就会说:“欸,获取到的是bash的pid而不是echo的pid”
我会说:“你在写命令行的时候,$$就被替换为当前进程的pid了,传入给echo命令的时候$$就是bash的pid,你让echo怎么搞才能输出echo的pid嘛。”

  然后重点关注3158   pts/0      00:00:00   bash这行,你看看第二个字符串,pts/0,这玩意就是当前进程使用的终端,具体可以通过ll /proc/$$/fd/看到,其中0就是标准输入,1是标准输出,2是标准错误,可以看到都是从终端读入的。
  你可能已经猜到了,当使用重定向时,0就不是指向/dev/pts/0了,可以写个简单的脚本看看:

#!/bin/bash

ls -l /proc/$$/fd/
  • 1
  • 2
  • 3

在这里插入图片描述
  脚本内容很简单,正所谓大道至简。但是这个用法揭示了重定向的实质。
  一开始什么重定向都没用,直接调用脚本,看到fd很正常,都是终端。但是当使用管道重定向输入后,0就指向管道了(如果用的是文件重定向输入,0会指向那个文件,你可以试试)。用了管道重定向输出后,1就指向到cat的管道了,当使用了2>&1表示把标准错误也重定向到标准输出后,2也指向到cat的管道了。
  所以说,之所以能够用重定向代替键盘输入,是因为它把文件描述符指向了管道或文件了。所以如果要强行只能在键盘输入,那很简单,反过来通过打开表示终端的文件只读取键盘输入就可以了。
  后面的exec 200<"$ptsname"就是在当前进程以读的方式打开ptsname表示的tty终端并且设置描述符为200;后面的read -u 200 inputdata就是从文件描述符为200的文件里读入一行数据到inputdata变量里,200正好就是tty的描述符,所以就是从终端读入了,而不是从标准输入读入;再后面的exec 200<&-是关闭200文件描述符;最后就给用户输出刚刚键盘输入的内容。

对于强制键盘输入的程序可有破解之法

  必须有,哪里有压迫,哪里就有反抗。原理很简单,它不是要从键盘输入吗,好,我就模拟从键盘输入!要模拟键盘输入只需要模拟终端输入即可。那这个问题不是变得很简单了吗,直接用C创建一个终端,然后给发送内容即可。废话不多说,上代码(这个是一个没有任何判断输出逻辑的代码,如果想做出sshpass的效果,需要把子进程的输出也重定向到从终端,然后搞一个子线程来做判断逻辑):

#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>

// 参数为:
// 实际上要执行的程序 参数1 参数2 参数3 ...
int main(int argc, char *argv[])
{
	int master_fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);	// 创建虚拟终端主终端
	char *slave_name;
	int slave;
	pid_t pid;
	char buf[65];
	ssize_t n;
	if (master_fd == -1) {
		return 1;
	}
	if (grantpt(master_fd) == -1) {
		close(master_fd);
		return 1;
	}
	if (unlockpt(master_fd) == -1) {
		close(master_fd);
		return 1;
	}
	slave_name = ptsname(master_fd);	// 获取从终端名字(也就是文件名/dev/pts/0之类的东西)
	if (slave_name == NULL) {
		close(master_fd);
		return 1;
	}
	pid = fork();
	if (pid < 0) {
		close(master_fd);
		return 1;
	}
	if (pid == 0) {	// 子进程,在这里执行从终端
		close(master_fd);	// 从终端里不需要主终端的句柄
		if (setsid() == -1) {
			_exit(1);
		}
		slave = open(slave_name, O_RDWR);
		if (slave == -1) {
			_exit(1);
		}
		dup2(slave, STDIN_FILENO);
		execvp(argv[1], argv + 1);
	}
	// 下面父进程负责一直等待用户标准输入,并且把内容传给子进程
	while ((n = read(STDIN_FILENO, buf, sizeof(buf) - 1)) > 0) {	// 其实大小没必要-1,但是为了打日志不被坑加上
		buf[n] = '\0';	// 其实没必要,因为后面write的时候会明确长度,但是这样可以方便出问题时打日志不会被坑
		write(master_fd, buf, n);	// 把标准输入获取到的东西给虚拟终端传过去
	}
	write(master_fd, "\x04", 1);	// 发送键盘按下ctrl+d的控制符号,为什么是4可以看ANSI编码,4代表EOT(传输结束)
	wait(NULL);	// 给子进程收尸
	close(master_fd);
	return 0;
}
  • 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
  • 58
  • 59
  • 60
  • 61
  • 62

  稍微解释一下,master_fd是主终端句柄,slave是从终端句柄。这两个终端有一个特点,就是它们两可以通过这两个句柄交互:主终端往master_fd写东西,从终端就可以从slave读到;从终端往slave写东西,主终端就能从master_fd读到。所以dup2(slave, STDIN_FILENO)是把从终端读到主终端写到master_fd的内容重定向到从终端的标准输入。而我的逻辑是主终端获取到标准输入的时候,就写到master_fd。这样就相当于把这个程序的标准输入重定向到从终端的标准输入了。
  让我们试试威力(我把代码保存为pass.c):
在这里插入图片描述
  一开始是直接调用脚本,键盘输入hello后输出;
  第二次是用重定向想输入内容为xxx,但是失败了,还是要求键盘输入;
  第三次恼羞成怒,用这个代码编译了一个模拟终端输入的程序,然后就能重定向了。
  你可能已经发现了,这个代码不仅能解决我这个强制键盘输入脚本这个例子,通过不同的参数,可以解决不同的程序。比如ssh:
在这里插入图片描述
  第一次是没有用pass的对照,一回车就提示要键盘输入密码了;
  第二次是用了pass的效果。
  没错,就是这么简单的C代码,就可以模拟在键盘输入,从而达到模拟给ssh输入密码的效果(红色划掉的地方就是密码),因为ssh做了判断,不能在它显示能够root@127.0.0.1’s password:之前就输入密码,我这个程序没有写判断逻辑(感兴趣的话你可以自己加上,多不了几行代码,我这只是个最简单的例子),所以我通过sleep来粗略地控制流程。因为ssh默认是有回显的,所以你能看到在它下面的输出里也包含了模拟输入的命令内容。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/56445
推荐阅读
相关标签
  

闽ICP备14008679号