抓包分析Telnet远程登陆协议信令交互过程

* Part1. Telnet协议原理

1. 概述

Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议。Telnet协议的目的是提供一个相对通用的,双向的,面向八位字节的通信方法,允许界面终端设备和面向终端的过程能通过一个标准过程进行互相交互。应用Telnet协议能够把本地用户所使用的计算机变成远程主机系统的一个终端。

1.1 Telnet协议特点

1.1.1 适应异构

为了使多个操作系统间的Telnet交互操作成为可能,就必须详细了解异构计算机和操作系统。比如,一些操作系统需要每行文本用ASCII回车控制符(CR)结束,另一些系统则需要使用ASCII换行符(LF),还有一些系统需要用两个字符的序列回车-换行(CR-LF);再比如,大多数操作系统为用户提供了一个中断程序运行的快捷键,但这个快捷键在各个系统中有可能不同(一些系统使用CTRL+C,而另一些系统使用ESCAPE)。如果不考虑系统间的异构性,那么在本地发出的字符或命令,传送到远地并被远地系统解释后很可能会不准确或者出现错误。因此,Telnet协议必须解决这个问题。
为了适应异构环境,Telnet协议定义了数据和命令在Internet上的传输方式,此定义被称作网络虚拟终端NVT(Net Virtual Terminal)。它的应用过程如下:
Ø 对于发送的数据:客户机软件把来自用户终端的按键和命令序列转换为NVT格式,并发送到服务器,服务器软件将收到的数据和命令,从NVT格式转换为远地系统需要的格式;
Ø 对于返回的数据:远地服务器将数据从远地机器的格式转换为NVT格式,而本地客户机将将接收到的NVT格式数据再转换为本地的格式。

1.1.2. 传送远地命令

我们知道绝大多数操作系统都提供各种快捷键来实现相应的控制命令,当用户在本地终端键入这些快捷键的时候,本地系统将执行相应的控制命令,而不把这些快捷键作为输入。那么对于Telnet来说,它是用什么来实现控制命令的远地传送呢?
Telnet同样使用NVT来定义如何从客户机将控制功能传送到服务器。我们知道USASCII字符集包括95个可打印字符和33个控制码。当用户从本地键入普通字符时,NVT将按照其原始含义传送;当用户键入快捷键(组合键)时,NVT将把它转化为特殊的ASCII字符在网络上传送,并在其到达远地机器后转化为相应的控制命令。将正常ASCII字符集与控制命令区分主要有两个原因:
Ø 这种区分意味着Telnet具有更大的灵活性:它可在客户机与服务器间传送所有可能的ASCII字符以及所有控制功能;
Ø 这种区分使得客户机可以无二义性的指定信令,而不会产生控制功能与普通字符的混乱。

1.1.3. 数据流向

将Telnet设计为应用级软件有一个缺点,那就是:效率不高。这是为什么呢?下面给出Telnet中的数据流向:
数据信息被用户从本地键盘键入并通过操作系统传到客户机程序,客户机程序将其处理后返回操作系统,并由操作系统经过网络传送到远地机器,远地操作系统将所接收数据传给服务器程序,并经服务器程序再次处理后返回到操作系统上的伪终端入口点,最后,远地操作系统将数据传送到用户正在运行的应用程序,这便是一次完整的输入过程;输出将按照同一通路从服务器传送到客户机。
因为每一次的输入和输出,计算机将切换进程环境好几次,这个开销是很昂贵的。还好用户的键入速率并不算高,这个缺点我们仍然能够接受。

1.1.4. 强制命令

我们应该考虑到这样一种情况:假设本地用户运行了远地机器的一个无休止循环的错误命令或程序,且此命令或程序已经停止读取输入,那么操作系统的缓冲区可能因此而被占满,如果这样,远地服务器也无法再将数据写入伪终端,并且最终导致停止从TCP连接读取数据,TCP连接的缓冲区最终也会被占满,从而导致阻止数据流流入此连接。如果以上事情真的发生了,那么本地用户将失去对远地机器的控制。
为了解决此问题,Telnet协议必须使用外带信令以便强制服务器读取一个控制命令。我们知道TCP用紧急数据机制实现外带数据信令,那么Telnet只要再附加一个被称为数据标记(date mark)的保留八位组,并通过让TCP发送已设置紧急数据比特的报文段通知服务器便可以了,携带紧急数据的报文段将绕过流量控制直接到达服务器。作为对紧急信令的相应,服务器将读取并抛弃所有数据,直到找到了一个数据标记。服务器在遇到了数据标记后将返回正常的处理过程。

1.1.5. 选项协商

由于Telnet两端的机器和操作系统的异构性,使得Telnet不可能也不应该严格规定每一个telnet连接的详细配置,否则将大大影响Telnet的适应异构性。因此,Telnet采用选项协商机制来解决这一问题。
Telnet选项的范围很广:一些选项扩充了大方向的功能,而一些选项制涉及一些微小细节。例如:有一个选项可以控制Telnet是在半双工还是全双工模式下工作(大方向);还有一个选项允许远地机器上的服务器决定用户终端类型(小细节)。
Telnet选项的协商方式也很有意思,它对于每个选项的处理都是对称的,即任何一端都可以发出协商申请;任何一端都可以接受或拒绝这个申请。另外,如果一端试图协商另一端不了解的选项,接受请求的一端可简单的拒绝协商。因此,有可能将更新,更复杂的Telnet客户机服务器版本与较老的,不太复杂的版本进行交互操作。如果客户机和服务器都理解新的选项,可能会对交互有所改善。否则,它们将一起转到效率较低但可工作的方式下运行。所有的这些设计,都是为了增强适应异构性,可见Telnet的适应异构性对其的应用和发展是多么重要。

2. 原理

Telnet协议的主体由三个部分组成:
1.网络虚拟终端(NVT,Network Virtual Terminal);
2.操作协商;
3.协商有限自动机;

2.1网络虚拟终端(NVT)

NVT工作原理:

  • 顾名思义,网络虚拟终端(NVT)是一种虚拟的终端设备,它被客户和服务器所采用,用来建立数据表示和解释的一致性。

NVT的定义:

  1. NVT的组成
    网络虚拟终端NVT包括两个部分:
    Ø 输出设备:输出远程数据,一般为显示器
    Ø 输入设备:本地数据输入
  2. 在NVT上传输的数据格式
    在网络虚拟终端NVT上传输的数据采用8bit字节数据,其中最高位为0的字节用于一般数据,最高位为1的字节用于NVT命令
  3. NVT在TELNET中的使用
    TELNET使用了一种对称的数据表示:
    1.当每个客户机发送数据时,把它的本地终端的字符表示影射到NVT的字符表示上
    2.当接收数据时,又把NVT的表示映射到本地字符集合上。
  • 在通信开始时,通信双方都支持一个基本的NVT终端特性子集(只能区分何为数据,何为命令),以便在最低层次上通信;
  • 在这个基础上,双方通过NVT命令协商确定NVT的更高层次上的特性,实现对NVT功能的扩展。
  • 在TELNET中存在大量的子协议用于协商扩展基本的网络虚拟终端NVT的功能,由于终端类型的多样化,使得TELNET协议族变得庞大起来。

2.2. 操作协商

  1. 为什么要协商操作选项?
    当定义了网络虚拟终端设备NVT后,通信的双方就可以在一个较低的层次上实现数据通信,但基本的NVT设备所具有的特性是十分有限的,它只能接收和显示7位的ASCII码,没有最基本的编辑能力,所以简单的NVT设备是没有实际应用意义的;为此TELNET协议定义了一族协议用于扩展基本NVT的功能,目的是使NVT能够最大限度地达到用户终端所具有的功能。
    为了实现对多种终端特性的支持,TELNET协议规定在扩展NVT功能时采用协商的机制,只有通信双方通过协商后达成一致的特性才能使用,才能赋予NVT该项特性,这样就可以支持具有不同终端特性的终端设备可以互连,保证他们是工作在他们自己的能力以内。

  2. 操作协商命令格式
    TELNET的操作协商使用NVT命令,即最高位为1的字节流,每条NVT命令以字节IAC(0xFF)开始。原理如下:
    只要客户机或服务器要发送命令序列而不是数据流,它就在数据流中插入一个特殊的保留字符,该保留字符叫做“解释为命令”(IAC ,Interpret As Command) 字符。当接收方在一个入数据流中发现IAC字符时,它就把后继的字节处理为一个命令序列。下面列出了所有的Telnet NVT命令,其中很少用到。

表1 TELNET 命令
在这里插入图片描述

其中常用的TELNET选项协商如下:
WILL (option code) 251 指示希望开始执行,或者确认现在正在操作指示的选项。
WONT (option code) 252 指出拒绝执行或继续招待所指示的选项。
DO (option code) 253 指出要求对方执行,或者确认希望对方执行指示的选项。
DONT (option code) 254 指出要求对方停止执行,或者确诊要求对方停止执行指示的选项。

那么对于接收方和发送方有以下几种组合:
在这里插入图片描述

选项协商需要3个字节:
(1)IAC;

  • 注:只要客户机或服务器要发送命令序列而不是数据流,它就在数据流中插入一个特殊的保留字符,该保留字符叫做“解释为命令”(IAC ,Interpret As Command) 字符。当接收方在一个入数据流中发现IAC字符时,它就把后继的字节处理为一个命令序列。

(2)然后是WILL、DO、WONT或DONT;
(3)最后一个标识字节用来指明操作的选项。

常用的选项代码如下:
在这里插入图片描述

  • 通常情况下,客户机向服务器发送字符,而服务器将其回显到用户的终端上;但是,如果网络的时延引起回显速度太慢,用户可能更愿意让本地系统回显字符。

在客户机允许本地系统回显前,它要向服务器发送以下序列:
IAC DONT ECHO
服务器收到请求后,发出3个字符的响应:
IAC WONT ECHO
表示服务器已经按请求同意关闭回显。

2.3. 子选项协商

除了“打开”或“关闭”以外,有些选项还需要更多的信息;
例如对于指明终端类型来说,客户必须发送一个字符串来标识终端类型,所以要定义子选项协商。

RFC 1091定义了终端类型的子选项协商。

举个例子:

  • 客户发送字节序列来请求打开选项:
    < IAC,WILL,24>
  • 24是终端类型的选项标识符。如果服务器同意该请求,响应为:
    < IAC,DO,24 >
  • 接着服务器发送
    < IAC,SB,24,1,IAC,SE>
    请求客户给出其终端类型。
    SB是子选项开始命令,下一个字节24表示该子选项为终端类型选项。下一个字节1表示:发送你的终端类型,SE是子选项结束命令。
  • 客户的响应为:
    < IAC,SB,24,0,‘I’,‘B’,‘M’,‘P’,‘C’, IAC,SE>
    第四个字节0的含义是“我的终端类型为”。

3. 实现

3.1 整个协议软件分为三个模块

各模块的功能如下:

  1. 与本地用户的输入/输出模块:处理用户输入/输出;
  2. 与远地系统的输入/输出模块:处理与远程系统输入/输出;
  3. TELNET协议模块:实现TELNET协议,维护协议状态机。

telnet客户机要做两件事:

  1. 读取 用户在键盘上键入的字符 ——>并通过tcp连接把他们发送到远程服务器上
  2. 读取 从tcp连接上收到的字符,并显示在 用户的终端上

3.2 Telnet远程登录服务分为4个过程

  1. 本地与远程主机建立连接。该过程实际上是建立一个TCP连接,用户必须知道远程主机的IP地址或域名;

  2. 将本地终端上输入的用户名和口令及以后输入的任何命令或字符以NVT(Net Virtual Terminal)格式传送到远程主机。该过程实际上是从本地主机向远程主机发送一个IP数据包;

  3. 将远程主机 输出的NVT格式的数据 转化为 本地所接受的格式送回本地终端,包括输入命令回显和命令执行结果;

  4. 最后,本地终端对远程主机进行撤消连接。该过程是撤销一个TCP连接。

3.3 数据传输

  1. 虽然Telnet连接建立在全双工的TCP连接基础之上,但是,在默认情况下,Telnet采用半双工的方式来传输数据。
  2. 在从客户机到服务器端的传输过程中,一个换行或回车符意味着结束了用户的输入,于是,服务器方可以开始传输数据。
  3. 在从服务器方向客户端传输数据的过程中,Telnet的Go Ahead(GA)命令,表明客户端可以开始传输数据。
  4. 虽然数据的传输是半双工的,但是,控制信号的发送可以是同时双向的。

———————————————————————————

  • 下半部分为抓包分析

* Part2. 抓包分析信令交互过程

  • 注:个人建议在抓包之前用管理员身份打开com命令框,输入 arp -sa和ipconfig /flushdns 来清除缓存,以防数据过于冗杂而无法找到想要的数据包。

1. 抓包

通过telnet协议与远程服务器连接后,在wireshark过滤框输入telnet,抓取到如下数据包:
在这里插入图片描述
展开Telnet栏:
在这里插入图片描述

2. 分析

逐条数据点击展开Telnet,可知信令交互过程如下图:
在这里插入图片描述

  • 注:SB代表子协议协商开始,SE代表子协议协商结束

———————————————————————————

* Part3. 用php 模拟telnet远程登录协议

  • 注:源码除了php,还用到了html和bootstrap

1. Telnet 类文件源码

<?php
class Telnet{
	private $host;
	private $port;
	private $timeout;

	private $socket=NULL;
	private $buffer=NULL;
	private $prompt;
	private $errno;
	private $errstr;

	private $NULL;
	private $DC1;
	private $WILL;
	private $WONT;
	private $DO;
	private $DONT;
	private $IAC;

	private $flobal_buffer='';
	const TELNET_ERROR=FALSE;
	const TELNET_OK=TRUE;

public function __construct($host='127.0.0.1',$port='23',$timeout=10){
	$this->host=$host;
	$this->port=$port;
	$this->timeout=$timeout;
	$this->NULL=chr(0);
	$this->DC1=chr(17);
	$this->WILL=chr(251);
	$this->WONT=chr(252);
	$this->DO=chr(253);
	$this->DONT=chr(254);
	$this->IAC=chr(255);
	$this->connect();
}
public function __destruct(){
	$this->disconnect();
	$this->buffer=NULL;
	$this->global_buffer=NULL;
}
public function connect(){
	if(!preg_match('/([0-9]{1,3}\\.){3,3}[0-9]{1,3}/',$this->host)){
		$ip=gethostbyname($this->host);
		if($this->host==$ip){
			throw new Exception("Cannot resolve $this->host");}
		else 
			$this->host=$ip;
	}
	$this->socket=fsockopen($this->host,$this->port,$this->errno,$this->errstr,$this->timeout);
	if(!$this->socket){
		throw new Exception("connot connect to $this->host on port $this->port");
	}
	return self::TELNET_OK;
}
public function disconnect(){
	if($this->socket){
		if(!fclose($this->socket)){
			throw new Exception ("Error while closing telnet socket");
		}
		$this->socket=NULL;
	}
	return self::TELNET_OK;
}
public function exec($command){
	$this->write($command);
	$this->waitPrompt();
	return $this->getBuffer();
}
public function login($username,$password){
	try{
		$this->setPrompt('login:');
		$this->waitPrompt();
		$this->write($username);
		$this->setPrompt('Password:');
		$this->waitPrompt();
		$this->write($password);
		$this->setPrompt();
		$this->waitPrompt();
	}catch(Exception $e){
		throw new Exception("Login failed");
	}
	return self::TELNET_OK;
}
public function setPrompt($s='$'){
	$this->prompt=$s;
	return self::TELNET_OK;
}
public function getc(){
	$c=fgetc($this->socket);
	$this->global_buffer.=$c;
	return $c;
}
protected function clearBuffer(){
	$this->buffer='';
}
protected function readTo($prompt){
	if(!$this->socket){
		throw new Exception("Telnet connection closed");
	}
	$this->clearBuffer();
	$until_t=time()+$this->timeout;
	do{
		if(time()>$until_t){
			throw new Exception("couldn't find the requested: '$promt' within{$this->timeout}seconds");
		}
		$c=$this->getc();
		if($c===false){
			throw new Exception("conot find the requested:'".$prompt."',it was not in the data returned from server:".$this->buffer);
		}
		if($c==$this->IAC){
			if($this->negotiateTelnetOptions()){
				continue;
			}
		}
		$this->buffer.=$c;
		if((substr($this->buffer,strlen($this->buffer)-strlen($prompt)))==$prompt){
			return self::TELNET_OK;
		}
	}while($c!=$this->NULL||$c!=$this->DC1);
}
protected function write($buffer,$addNewLine=true){
	if(!$this->socket){
		throw new Exception("Telnet connection closed");
	}
	$this->clearBuffer();
	if($addNewLine==true){
		$buffer.="\n";
	}
	$this->global_buffer.=$buffer;
	if(!fwrite($this->socket,$buffer)<0){
		throw new Exception("Error writing to socket");
	}
	return self::TELNET_OK;
}
protected function getBuffer(){
	$buf=explode("\n",$this->buffer);
	unset($buf[count($buf)-1]);
	$buf=implode("\n",$buf);
	return trim($buf);
}
public function getGlocalBuffer(){
	return $this->global_buffer;
}
protected function negotiateTelnetOptions(){
	$c=$this->getc();
	if($c!=$this->IAC){
		if(($c==$this->DO)||($c==$this->DONT)){
			$opt=$this->getc();
			fwrite($this->socket,$this->IAC.$this->WONT.$opt);
		}
		else if(($c==$this->WILL)||($c==$this->WONT)){
			$opt=$this->getc();
			fwrite($this->socket,$this->IAC.$this->DONT.$opt);
		}else{
			throw new Exception('Error:unknowm control character'.ord($c));
		}
	}
	else{
		throw new Exception('Error:Something Wicked Happened');
	}
	return self::TELNET_OK;
}
protected function waitPrompt(){
	return $this->readTo($this->prompt);
}
}

2. 实现远程登陆模块源码

  • 该源码为php和html混写,同时用到了bootstrp生成栅格 ( 意思就是你直接拔下来肯定没法跑,得做相应配置 )
<!DOCTYPE html>
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html';charest=gb2312">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../css/bootstrap.min.css" media="screen">
    <title>Telnet Test Form</title>
</head>
<body>
<form method="POST" class="form-horizontal" enctype="multipart/form-data" name="example">

  <div class="container">
    <div class="span12" style="background: #E0E0E0">
    <br>
    <br>
       <div class="span8 offset2" style="background: #D0D0D0">
       <legend class="text-center"><em>Telnet远程登陆协议</em></legend>
       <div class="control-group">
            <label class="control-label" for="inputName">用户名</label>
            <div class="controls">
                <input type="text" name="user" id="user" required>
            </div>
        </div>
        <div class="control-group">
            <label class="control-label" for="inputPassword">密码</label>
            <div class="controls">
                <input type="password" name="pwd" id="pwd" required>
            </div>
        </div>
        <div class="control-group">
            <label class="control-label" for="inputEmail">ָ指令</label>
            <div class="controls">
                <input type="text" name="order" id="order" required>
            </div>
        </div>
        <div class="control-group" align="center">
                <button type="submit" class="btn btn-primary" name="Submit" value="ִSubmit">ִ执行</button>&nbsp;&nbsp;&nbsp;
                <button type="reset" class="btn" name="Reset" value="Reset">重置</button>
            
        </div>

        <ul class="nav nav-list"><li class="divider"></li></ul>
       </div>

       <div class="span8 offset2" style="background: #D0D0D0">
       <legend class="text-center"><em>ָ指令回显</em></legend>
<?php
       //error_reporting(E_ALL || ~E_NOTICE);
if(@$_POST['Submit']==true){
         require 'Telnet.php';
         $inst = new Telnet();
	 $inst->login($_POST['user'],$_POST['pwd']);
	 $final = $_POST['order'];
	 $finall = $final."|sed 's/\\x1b\\[[0-9;]*m//g'";
	 $finalll = htmlspecialchars($finall);
	 $execve = $inst->exec($finalll);
	echo $execve;
         }
         else {
            echo PHP_EOL;
            echo"...........";
         }
?>
<br><br><br><br><br><br><br>
       </div>
<div class="span8 offset2" style="background:#E0E0E0">
<br><br><br><br>
<div>
       
    </div>
  </div>
<script>
$(document).ready(function(){
	$("reset").off().on("click",function(){
		$("user").val("");
		$("pwd").val("");
		$("order").val("");
	});
});
<script>

</form>


    <script src="http://code.jquery.com/jquery.js"></script>
    <script src="../js/bootstrap.min.js"></script>

    
</body>
</html>

3. 模拟结果

在这里插入图片描述
点击执行:
在这里插入图片描述
指令回显结果与终端上执行相同指令的结果相同
在这里插入图片描述

码字不易~有幸有所帮助的话记得点赞收藏哦 ~ o^ - ^o

参考资料:http://blog.sina.com.cn/s/blog_6acda51b0100obq0.html

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狱典司

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值