python3-編寫netcat 工具


前言

  該文章主要是描寫如何使用Python3來編寫網絡監聽工具。文章分為兩個部分。首先,先第一部分來解說需要使用的代碼功能與使用,為了讓不太熟悉Python3語言的朋友能夠更好的通過該文章學習。之后會帶領各位來整體編寫網絡監聽工具。

天象獨行

2020-06-08

  第一部分:

  1;sys.argv[]

  我們想想看,常見使用一款工具的時候是不是打開是這樣的格式:“工具名稱 [選項] [輸入內容]” 那么現在問題來了。我們輸入的“[輸入內容]”是如何進入到我們的代碼當中呢?在我們的Python語言中使用函數sys.argv[]來獲取命令行參數(從程序外部獲取參數)。

  接下來我們來演示一下,該函數如何使用:

  我們來看看下面代碼我們先了解以下他是一個什么類的數據:

#導入sys庫
import sys
#查看sys.argv是什么樣的格式
print(type(sys.argv))

  運行代碼,我們發現它是一個列表:

(Studying) aaron@MiWiFi-R3-srv:~/PycharmProjects/Studying$ python3.7 test.py sdf
<class 'list'>

  現在我們知道了sys.argv是一個列表格式的數據。那么,我們大概能夠理解。我們輸入的參數將會以列表的形式保存在“sys.argv”。那么我們如何提取存入的參數呢?熟悉的朋友已經了解了。接下來,我們來通過代碼來說明一下:

#導入sys庫
import sys
#輸出輸入參數
print("輸出第一個參數:%s" % sys.argv[0])
print("輸出第二個參數:%s" % sys.argv[1])
print("輸出第三個參數:%s" % sys.argv[2])
print("輸出第四個參數:%s" % sys.argv[3])

  運行代碼我們發現sys.argv[0]表示的是文件本身,sys.argv[1] 開始是我們輸入的第一位參數

(Studying) aaron@MiWiFi-R3-srv:~/PycharmProjects/Studying$ python3.7 test.py 參數1 參數2 參數3
輸出第一個參數:test.py
輸出第二個參數:參數1
輸出第三個參數:參數2
輸出第四個參數:參數3
(Studying) aaron@MiWiFi-R3-srv:~/PycharmProjects/Studying$ 

  

  2;getopt.getopt

  現在,我們已經了解了如何將外部的參數讀取以列表的形式保存下來。我們來想想看在我們輸入的參數當中有選項阿,那該怎么處理呢。我們可以慢慢提取出來。那這樣是否過於繁瑣呢。沒有有一種方法能夠幫我們處理這個問題呢。答案是有的。下面我們來看看getopt.getopt()方法。

  我們先了解一下getopt.getopt()方法的語法:

getopt.getopt(args,options[,long_options])

  參數說明:

  • args: 表示需要解析的命令行列表
  • options:字符串格式定義。(如果選項有冒號則表示該選項后面有參數)
  • long_options:列表格式定義。(如果long_option 選項有“=” 則表示選項后面有參數)

  現在我們已經知道了該方法的使用,下面我們來用代碼感受一下該方法的使用

#導入sys庫
import sys
import getopt

#輸出輸入參數
a = getopt.getopt(sys.argv[1:],'t:p:',["target=","port="])

print(a)

  我們來嘗試運行一下代碼,我們發現輸入的(選項和命令參數)是以元組的數據格式來保存的。

(Studying) aaron@MiWiFi-R3-srv:~/PycharmProjects/Studying$ python3.7 test.py -t 192.168.1.1 -p 80
([('-t', '192.168.1.1'), ('-p', '80')], [])
(Studying) aaron@MiWiFi-R3-srv:~/PycharmProjects/Studying$ python3.7 test.py --target 192.168.1.1 --port 80
([('--target', '192.168.1.1'), ('--port', '80')], [])
(Studying) aaron@MiWiFi-R3-srv:~/PycharmProjects/Studying$

  

  3;socket

  我們都知道,我們需要編寫的代碼是需要通過網絡來傳輸數據的,那么我們如何通過Python3來處理這個問題呢。這里我們需要使用socket模塊。接下來,我們來重點關注一下該模塊:

  在這之前,我們現了解一下“套接字(socket)”。所謂的“套接字”是一種計算機網絡數據結構,任何類型的通訊開始之前是一定要創建套接字。套接字主要是分為兩個部分:基於文件和面向網絡。下面我們特別說明面向網絡的套接字。我們知道在網絡當中,有TCP連接和UDP鏈接。面對兩種不同的方式,我們當然需要不同的套接字。

  • TCP/IP套接字
tcpSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  • UDP/IP套接字
udpSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

  套接字創建完成之后,我們可以利用套接字當中的方法來進行發送數據,監聽,接收數據:詳細方法如下圖:

 

 

   4;subprocess.check_output()

  方法的作用是執行指定命令。

  接下來,我們來用實例來看一下效果:很明顯,我們的命令“df -h”已經被執行。

>>> import subprocess
>>> subprocess.check_output("df -h",shell=True)
b'\xe6\x96\x87\xe4\xbb\xb6\xe7\xb3\xbb\xe7\xbb\x9f        \xe5\xae\xb9\xe9\x87\x8f  \xe5\xb7\xb2\xe7\x94\xa8  \xe5\x8f\xaf\xe7\x94\xa8 \xe5\xb7\xb2\xe7\x94\xa8% \xe6\x8c\x82\xe8\xbd\xbd\xe7\x82\xb9\nudev            3.9G     0  3.9G    0% /dev\ntmpfs           794M  1.5M  793M    1% /run\n/dev/sda1       110G   58G   47G   56% /\ntmpfs           3.9G   75M  3.9G    2% /dev/shm\ntmpfs           5.0M     0  5.0M    0% /run/lock\ntmpfs           3.9G     0  3.9G    0% /sys/fs/cgroup\ntmpfs           794M  8.0K  794M    1% /run/user/1000\n'
>>> 

    

  第二部分:

  好了,第一部分我們已經說明了編寫該工具着重需要注意的點。那么接下來我們來正式進入編寫程序的階段

  1;我們先試想一下,我們的netcat 該有什么樣的功能呢?文件上傳,執行命令,shell,監聽對吧。是不是還需要設定目標地址呢?還需要設定端口是把。好的,那接下來,我們再來想想。該如何去做是不是我們自己選擇的。執行命令是不是我們來輸入命令呢?好的。想到這兒,很明顯了。我們需要用到第一部分sys.argv[] 和 getopt.getopt() 兩個知識點呢?那么接下來我們看代碼:

  1.1 ;首先我們定義一些變量,這些變量就是用來區分不同的選項的。不明白的可以稍候繼續看下去:

#定義全局變量
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0

  1.2;這些變量好了,那么我們需要考慮的是如何將外部參數上傳到內部。詳細,我們看下面代碼:

opts,args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:",["help","listen","execute","target","port","command","upload"])

  我們通過sys.argv將外部參數送進來並且通過getopt.getopt()來整理這些輸入的參數。(注意,變量opts 用來存儲定義的參數,變量args用來存儲額外的參數)

  1.3;通過上面的代碼,我們已經能夠將外部參數已元組的形式保存在變量opts 當中了。那么,我們如何將數據保存下來並且使用呢。我們都知道不同的選項代表不同的功能。那么,變量opts當中存在什么選項就代表用戶想要執行什么樣的功能對不對?那么如何確定呢?可以通過for 循環來逐步提取出來進行對比。就可以發現opts變量中的選項了。好的,想法已經有了。下面我們落實到代碼上面:

    for o,a in opts:
        if o in ("-h","--help"):
            usage()
        elif o in ("-l","--listen"):
            listen = True
        elif o in ("-e","--execute"):
            execute =a
        elif o in ("-c","--commandshell"):
            command = True
        elif o in ("-u","--upload"):
            upload_destination = a
        elif o in ("-t","--target"):
            target = a
        elif o in ("-p","--port"):
            port = int(a)
        else:
            assert False , "Unhandled Option"

  上面代碼可以遍歷變量當中opts中的值並且分別放入到變量“o”,“a” 。在循環中,通過if語句對比in語句當中的內容,對比成功則執行對應的操作。

  2;現在我們已經成功的完成了輸入不同選項對應了不同的功能。那么下面我們開始補全這些功能。

  2.1;usage() 功能

  這里我們通過關鍵字def來定義一個功能函數,該功能函數主要是選項“-h”下的功能,用來提示如何使用該代碼。

def usage():
    print("BHP Net Tool")
    print("-----------------------------------------------------------------------------------------------")
    print("Usage: netcat_python_studying.py -t target_host -p port ")
    print("-l --listen    -listen on [host]:[port] for incoming connections")
    print("-e --execute=file_to_run    - execute the given file upon receiving a connection")
    print("-c --command    - initialize a command shell")
    print("-u --upload=destination    - upon receiving connection upload a file and write to [destination]")
    print("-----------------------------------------------------------------------------------------------")
    print("Examples: ")
    print("netcat_python_studying.py -t 192.168.1.1 -p 5555 -l -c")
    print("netcat_python_studying.py -t 192.168.1.1 -p 5555 -l -u=c:\\target.exe")
    print("netcat_python_studying.py -t 192.168.1.1 -p 5555 -l -e=\"cat /etc/passwd\"")
    print("echo 'sdfsdf' | ./netcat_python_studying.py -t 192.168.1.1 -p 123")
    sys.exit(0)

  2.2;run_command()

  代碼功能當中需要執行命令,那么我們需要定義一個執行命令的函數,代碼如下:

def run_command(command):
    #換行
    command = command.rstrip()

    #運行命令並將輸出返回
    try:
        output = subprocess.check_output(command,stderr=subprocess.STDOUT,shell=True)
    except:
        output = "Failed to execute command.\r\n"

    #將輸出發送
    return output

  (代碼解釋:數據由參數command進入到函數內部,首先經過方法rstrip() 處理數據結尾的空格符號,之后有方法subprocess.check_output來執行命名,最后返回執行的結果output。)

  2.3;server_loop()

  監聽功能是必要的,下面我們再來添加一下監聽功能的函數,詳細如下代碼:

def server_loop():
    global target

    #如果沒有定義目標,那么我們監聽所有接口
    if not len(target):
        target = "0.0.0.0"

    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind((target,port))

    server.listen(5)

    while True:
        client_socket,addr = server.accept()
        #分拆一個線程處理新的客戶端
        client_thread = threading.Thread(target=client_sender,args=(client_socket))
        client_thread.start()

  (代碼解析:這里我們通過全局變量target 來確定監聽的地址,這里需要創建一個TCP/IP套接字。創建完成之后使用方法bind來綁定端口地址,啟動監聽功能。設定一個死循環,並且啟動被動鏈接方法accept。一旦鏈接執行函數client_sender )

  注意:client_sender 函數自定義函數。主要功能是發送數據。

  2.4;client_sender()

  我們需要一個發送數據的功能,那么我們來自定義一個,詳細如下面代碼:

def client_sender(buffer):
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    try:
        #鏈接目標主機
        client.connect((target,port))

        if len(buffer):
            client.send(buffer)

        while True:
            #現在等待數據回傳
            recv_len = 1
            response = ""

            while recv_len:
                data = client.recv(4096)
                recv_len = len(data)
                response += data

                if recv_len < 4096:
                    break

            print(response)

            #等待更多的輸入
            buffer = input("")
            buffer += "\n"

            #發送出去
            client.send(buffer)

    except:
        print("[*] Exception! Exiting.")
        #關閉鏈接
        client.close()

  2.5;client_handler()

   現在我們來完善一下上傳文件功能,執行命令,shell 功能。詳細如下代碼:

def client_handler(client_socket):
    global upload
    global execute
    global command

    #檢測上傳文件
    if len(upload_destination):
        #讀取所有的字符並寫下目標
        file_buffer = ""
        #持續讀取數據知道沒有符合的數據
        while True:
            data = client_socket.recv(1024)

            if not data:
                break
            else:
                file_buffer += data

        ##現在我們接收這些數據並將他們寫出來
        try:
            file_descriptor = open(upload_destination,"wb")
            file_descriptor.write(file_buffer)
            file_descriptor.close()

            #確認文件已經寫出來
            client_socket.send("Successfully saved file to %s\r\n" % upload_destination)
        except:
            client_socket.send("Failed to save file to %s\r\n" % upload_destination)


    #檢查命令執行
    if len(execute):
        #運行命令
        output = run_command(execute)

        client_socket.send(output)

    #如果需要一個命令行shell,那么我們進入另一個循環
    if command:
        while True:
            #跳出一個窗口
            client_socket.send("<BHP:#> ")

            #現在我們接收文件直到發現換行符(enter key)
            cmd_buffer = ""
            while "\n" not in cmd_buffer:
                cmd_buffer += client_socket.recv(1024)

            #返還命令輸出
            response = run_command(cmd_buffer)

            #返回響應數據
            client_socket.send(response)

  2.6;現在我們的積木已經都有了(我喜歡將代碼程序比作積木,不同的積木有不同的形狀,不同的功能),那么我們現在來組建一下。

  外部程序參數進入的時候,我們來判斷一下,如果條件listen不滿足, target port 都有,我們就可以實行發送數據功能。如果listen啟動,那么我們開啟監聽的功能。現在思路有了。那么我們來寫下這串代碼:

    # 我們是進行監聽還是僅從標准輸入發送數據?
    if not listen and len(target) and port > 0:
        #從命令行讀取內存數據
        #這里將阻塞,所以不在標准輸入發送數據時發送CTRL-D
        buffer = sys.stdin.read()

        #發送數據
        client_sender(buffer)

    #我們開始監聽並准備上傳文件,執行命令
    #放置一個反彈shell
    #取決於上面的命令行選項
    if listen:
        server_loop()

  3;現在我們將代碼完整的展現一下:

import sys
import socket
import getopt
import threading
import subprocess

#定義全局變量
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0

def usage():
    print("BHP Net Tool")
    print("-----------------------------------------------------------------------------------------------")
    print("Usage: netcat_python_studying.py -t target_host -p port ")
    print("-l --listen    -listen on [host]:[port] for incoming connections")
    print("-e --execute=file_to_run    - execute the given file upon receiving a connection")
    print("-c --command    - initialize a command shell")
    print("-u --upload=destination    - upon receiving connection upload a file and write to [destination]")
    print("-----------------------------------------------------------------------------------------------")
    print("Examples: ")
    print("netcat_python_studying.py -t 192.168.1.1 -p 5555 -l -c")
    print("netcat_python_studying.py -t 192.168.1.1 -p 5555 -l -u=c:\\target.exe")
    print("netcat_python_studying.py -t 192.168.1.1 -p 5555 -l -e=\"cat /etc/passwd\"")
    print("echo 'sdfsdf' | ./netcat_python_studying.py -t 192.168.1.1 -p 123")
    sys.exit(0)

def main():
    global listen
    global port
    global execute
    global command
    global upload_destination
    global target

    if not len(sys.argv[1:]):
        usage()

    #讀取命令選項
    try:
        opts,args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:",["help","listen","execute","target","port","command","upload"])
    except getopt.GetoptError as err:
        print(str())
        usage()

    for o,a in opts:
        if o in ("-h","--help"):
            usage()
        elif o in ("-l","--listen"):
            listen = True
        elif o in ("-e","--execute"):
            execute =a
        elif o in ("-c","--commandshell"):
            command = True
        elif o in ("-u","--upload"):
            upload_destination = a
        elif o in ("-t","--target"):
            target = a
        elif o in ("-p","--port"):
            port = int(a)
        else:
            assert False , "Unhandled Option"

    # 我們是進行監聽還是僅從標准輸入發送數據?
    if not listen and len(target) and port > 0:
        #從命令行讀取內存數據
        #這里將阻塞,所以不在標准輸入發送數據時發送CTRL-D
        buffer = sys.stdin.read()

        #發送數據
        client_sender(buffer)

    #我們開始監聽並准備上傳文件,執行命令
    #放置一個反彈shell
    #取決於上面的命令行選項
    if listen:
        server_loop()

def client_sender(buffer):
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    try:
        #鏈接目標主機
        client.connect((target,port))

        if len(buffer):
            client.send(buffer)

        while True:
            #現在等待數據回傳
            recv_len = 1
            response = ""

            while recv_len:
                data = client.recv(4096)
                recv_len = len(data)
                response += data

                if recv_len < 4096:
                    break

            print(response)

            #等待更多的輸入
            buffer = input("")
            buffer += "\n"

            #發送出去
            client.send(buffer)

    except:
        print("[*] Exception! Exiting.")
        #關閉鏈接
        client.close()

def server_loop():
    global target

    #如果沒有定義目標,那么我們監聽所有接口
    if not len(target):
        target = "0.0.0.0"

    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind((target,port))

    server.listen(5)

    while True:
        client_socket,addr = server.accept()
        #分拆一個線程處理新的客戶端
        client_thread = threading.Thread(target=client_sender,args=(client_socket))
        client_thread.start()

def run_command(command):
    #換行
    command = command.rstrip()

    #運行命令並將輸出返回
    try:
        output = subprocess.check_output(command,stderr=subprocess.STDOUT,shell=True)
    except:
        output = "Failed to execute command.\r\n"

    #將輸出發送
    return output

def client_handler(client_socket):
    global upload
    global execute
    global command

    #檢測上傳文件
    if len(upload_destination):
        #讀取所有的字符並寫下目標
        file_buffer = ""
        #持續讀取數據知道沒有符合的數據
        while True:
            data = client_socket.recv(1024)

            if not data:
                break
            else:
                file_buffer += data

        ##現在我們接收這些數據並將他們寫出來
        try:
            file_descriptor = open(upload_destination,"wb")
            file_descriptor.write(file_buffer)
            file_descriptor.close()

            #確認文件已經寫出來
            client_socket.send("Successfully saved file to %s\r\n" % upload_destination)
        except:
            client_socket.send("Failed to save file to %s\r\n" % upload_destination)


    #檢查命令執行
    if len(execute):
        #運行命令
        output = run_command(execute)

        client_socket.send(output)

    #如果需要一個命令行shell,那么我們進入另一個循環
    if command:
        while True:
            #跳出一個窗口
            client_socket.send("<BHP:#> ")

            #現在我們接收文件直到發現換行符(enter key)
            cmd_buffer = ""
            while "\n" not in cmd_buffer:
                cmd_buffer += client_socket.recv(1024)

            #返還命令輸出
            response = run_command(cmd_buffer)

            #返回響應數據
            client_socket.send(response)

if __name__ == "__main__":
    main()

 

  


免責聲明!

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



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