Telnet協議底層研究及python中telnetlib核心源碼分析


Telnet協議是TCP/IP協議族中應用最廣泛的協議。它允許用戶(Telnet客戶端)通過一個協商過程來與一個遠程設備進行通信。Telnet協議是基於網絡虛擬終端NVT(Network Virtual Termina1)的實現,NVT是虛擬設備,連接雙方(客戶機和服務器)都必須把它們的物理終端和NVT進行相互轉換。

NVT需要顧及到所有的各種型號的機器,所以他定義的操作十分有限。為了能夠兼容所有的可能性,Telnet協議使用"用於擴展基本NVT功能的協議,提供了選項協商的機制"來解決兼容性問題。

也就是說,服務端與客戶端發送請求開啟某選項或拒絕啟動某選項的這種交互模式,實現通信。

以下是各常見指令所表示的含義。

最常用指令 十進制 十六進制 	字符信息	描述信息 
255   	0xff  	IAC    	命令解釋符				-- 每條指令的前綴都必須是它 
251   	0xfb  	WILL   	同意啟動(enable)選項		-- 發送方本身將激活選項 
252   	0xfc  	WONT   	拒絕啟動選項              -- 發送方本身想禁止選項			表示拒絕執行或繼續執行指示的選項。 
253   	0xfd  	DO     	認可選項請求              -- 發送方想叫接受端激活選項			表示另一方執行的請求,或確認您希望另一方執行,指示的選項。 
254   	0xfe  	DONT   	拒絕選項請求              -- 發送方想讓接受端去禁止選項  十進制 十六進制 	字符信息	描述信息 
236   	0xec  	EOF     文件結束符 
237   	0xed  	SUSP   	掛起當前進程(作業控制) 
238   	0xee  	ABORT   異常中止進程 
239   	0xef  	EOR     記錄結束符 
240   	0xf0  	SE      子選項結束				-- 表示子選項協商結束。 
241   	0xf1  	NOP     無操作 
242   	0xf2  	DM      數據標記 
243   	0xf3  	BRK     中斷 
244   	0xf4  	IP      中斷進程 
245   	0xf5  	AO      異常中止輸出 
246   	0xf6  	AYT     對方是否還在運行? 
247   	0xf7  	EC      轉義字符 
248   	0xf8  	EL      刪除行 
249   	0xf9  	GA      繼續進行 
250   	0xfa  	SB     	子選項開始				-- 表示接下來是協商子選項。  

子選項 十進制 十六進制 	字符信息	描述信息 
0		0x00			二進制傳輸 
1		0x01            回顯 
3		0x03            繼續抑制 
5		0x05            狀態 
6		0x06            計時標記 
10		0x0a            \n 
13		0x0d			\r 
18		0x12            退出登錄 
19		0x13            字節宏 
23		0x17            發送位置 
24		0x18            終端類型 
25		0x19            記錄結束 
26		0x20            數據輸入終端 
31		0x1F            窗口大小 
32		0x20	空格    終端速度 
33		0x21            遠程流量控制 
34		0x22            運行方式 
35		0x23	 #        X 顯示位置 
36		0x24            (老版本)環境變量 
37		0x25            認證 
39		0x27     '      輸出標記 
38		0x26            TACACS 用戶識別 
51		0x39            (新版本)環境變量

其中不同的操作系統,所返回的指令集是不同的,也可以通過對應的指令集,也可以稱為banner信息,使用FOFA等工具搜集相關信息進行登錄嘗試。

CentOS 7.6    
b"\xff\xfd\x18 \xff\xfd \xff\xfd#\xff\xfd'"    
b"\xff\xfc\x18\xff\xfc \xff\xfc#\xff\xfc'"    
b'\xff\xfb\x03\xff\xfd\x01\xff\xfd\x1f\xff\xfb\x05\xff\xfd!'    b'\xff\xfd\x03\xff\xfb\x01\xff\xfc\x1f\xff\xfe\x05\xff\xfc!'    
b'\xff\xfe\x01\r\nKernel 3.10.0-1160.42.2.el7.x86_64 on an x86_64 

Windows Server 2003    
b"\xff\xfd%\xff\xfb\x01\xff\xfb\x03\xff\xfd'\xff\xfd\x1f\xff\xfd\x00\xff\xfb\x00"    b"\xff\xfc%\xff\xfd\x01\xff\xfd\x03\xff\xfc'\xff\xfc\x1f"    
b'Welcome to Microsoft Telnet Service ' 

Routerbo    
b"\xff\xfd\x18\xff\xfd \xff\xfd#\xff\xfd'"    
b"\xff\xfb\x18\xff\xfc \xff\xfc#\xff\xfb'"    
b"\xff\xfa'\x01\xff\xf0\xff\xfa\x18\x01\xff\xf0" ff fa 27 01 ff f0 ff fa 18 01 ff f0

根據上面的指令集對照表,可以“翻譯”一條指令集做個樣例,其他的也可以對照着“密碼表”進行翻譯,大概就知道是怎么回事了。

# 注意這里的空格和`#`、`'`符號,也對應着十六進制數。 
b"\xff\xfd\x18 \xff\xfd \xff\xfd#\xff\xfd'" 

# 下面是將空格等字符補全的結果。 
b"\xff\xfd\x18\0x20\xff\xfd\0x20\xff\xfd\0x23\xff\xfd\0x27" 

# 開始翻譯 
# DO     	認可選項請求              -- 發送方想叫接受端激活選項			表示另一方執行的請求,或確認您希望另一方執行,指示的選項。 
DO 終端類型 終端速度 DO 終端速度 X 顯示位置 DO 輸出標記

針對於CentOS和Windows,不需要返回指定內容即可看到提示登錄的信息。而針對Routerbo等機器,需要發送相關指令與之交互,才能看到提示登錄的消息。

因此,我們可以通過進一步分析,來捕獲登錄特征,利用socket實現通用的登錄接口,而實現我們最終的服務爆破的目的。

查看telnet源碼進行分析

    def process_rawq(self):
        """Transfer from raw queue to cooked queue.

        Set self.eof when connection is closed.  Don't block unless in
        the midst of an IAC sequence.

        """
        buf = [b'', b'']
        try:
	    # self.rawq初始值是空字節,首次執行的時候會跳過然后執行到socket.recv的時候賦值,就進入到while循環中直到取完所有的值。
            while self.rawq:
	        # 每次讀一個字節self.irawq自動加1
	        # self.rawq_getchar()每次是取一個十六進制數
                c = self.rawq_getchar()

		# self.iacseq初始值是空字節
		# 當self.iacseq不為空的時候
                if not self.iacseq:
                    if c == theNULL:
                        continue
                    if c == b"\021":
                        continue

		    # IAC就是0xff,不是0xff,就拼接到buf列表中的第一個元素中
                    if c != IAC:
		        # 如果是login等字符,就會拼接到buf[self.sb]中
		        # 當self.irawq+=1后大於等於self.rawq后,說明self.rawq已經被讀完了
		        # 最后將buf[self.sb]中的值返回給客戶端,也就是拼接到的比如login:返回給客戶端,客戶端就可以根據返回的值進行判斷,是login還是username或者登錄失敗等。
		        # self.sb 初始值是0
                        buf[self.sb] = buf[self.sb] + c
                        continue
                    else:
			# 拼接指令集中0xff的指令
                        self.iacseq += c
				
		# 當self.iacseq長度為1時,說明self.iacseq是0xff,
                elif len(self.iacseq) == 1:
                    # 判斷取到的變量為c的指令在不在DO, DONT, WILL, WONT中。
					
                    if c in (DO, DONT, WILL, WONT):
			# 如果在其中,那么拼接成`0xff`+(DO, DONT, WILL, WONT)中的一個,比如`0xff0xfd`並繼續讀取下一個。
                        self.iacseq += c
                        continue

		    # 否則初始化self.iacseq為空字節
                    self.iacseq = b''

                    if c == IAC:
 			# 如果取出的指令是`0xff`,拼接到buf[self.sb]中,self.sb為0,正常情況不太可能走到這里,除非發生粘包等問題,導致接收到連續兩個0xff。
                        buf[self.sb] = buf[self.sb] + c
                    else:
			# 如果取出的指令為SB,子選項協商開始,把self.sb設為1。
                        if c == SB: # SB ... SE start.
                            self.sb = 1
                            self.sbdataq = b''
			# 子選項協商結束
                        elif c == SE:
                            self.sb = 0
                            self.sbdataq = self.sbdataq + buf[1]
                            buf[1] = b''
                        if self.option_callback:
                            # Callback is supposed to look into
                            # the sbdataq
                            self.option_callback(self.sock, c, NOOPT)
                        else:
                            self.msg('IAC %d not recognized' % ord(c))

		# 當self.iacseq長度為2時,說明self.iacseq是\xff\xfd或\fb等四個中的一個,
                elif len(self.iacseq) == 2:
		    # 而cmd就是0xfb 0xfd等四個中的一個
                    cmd = self.iacseq[1:2]

		    # 這里就將0xff0xf(a, b)等設為空字節了,用於進行下一次循環判斷
                    self.iacseq = b''

		    # 這里的c就是新取出的值,不在0xff 0xfb 0xfd等四個中的一個,而是一個子選項
                    opt = c
					
		    # ##############################################################################
		    # 重點來了,當cmd是(DO, DONT)中的一個,回復0xff + 0xfe + opt(子選項),通殺所有系統!!!
		    # ##############################################################################
                    if cmd in (DO, DONT):
                        self.msg('IAC %s %d',
                            cmd == DO and 'DO' or 'DONT', ord(opt))
                        if self.option_callback:
                            self.option_callback(self.sock, cmd, opt)
                        else:
                            self.sock.sendall(IAC + WONT + opt)

		    # ##############################################################################
		    # 重點來了,當cmd是(WILL, WONT)中的一個,回復0xff + 0xfc + opt(子選項),通殺所有系統!!!
		    # ##############################################################################
                    elif cmd in (WILL, WONT):
                        self.msg('IAC %s %d',
                            cmd == WILL and 'WILL' or 'WONT', ord(opt))
                        if self.option_callback:
                            self.option_callback(self.sock, cmd, opt)
                        else:
                            self.sock.sendall(IAC + DONT + opt)

        except EOFError: # raised by self.rawq_getchar()
            self.iacseq = b'' # Reset on EOF
            self.sb = 0
            pass
        self.cookedq = self.cookedq + buf[0]
        self.sbdataq = self.sbdataq + buf[1] 

核心總結下來就是:

  1. 每次讀取判斷字符是0xff還是0xfb 0xfc 0xfd等,拼接成長度為2的一個字符串。
  2. 當長度為2的時候,判斷執行的是DO, DONT還是WILL, WONT
    1. DO, DONT 是拼接 0xfb 0xfe + 子選項發送給服務端。
    2. WILL, WONT是拼接 0xfb 0xfc + 子選項發送給服務端。
  3. 最后將除了0xff的字符串拼接到buf列表中,返回給客戶端。
  4. 客戶端可以通過返回值判斷接下來是輸入用戶名還是密碼,或是登錄成功或失敗。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM