Python黑帽子-實現netcat基本功能(改進版)

前言

一個好的滲透測試人員,應該擁有強大的編程能力,而python就是一個很好的工具,我最近也再研究如何用python開發屬於自己的小工具,《python黑帽子》是一本很不錯的書籍。本系列博文是我在學習過程中,做的一些總結與拓展。

前置知識

netcat我就不過多介紹了,一句網絡中的瑞士軍刀已經說明一切了,這裏也有一篇關於netcat基本應用的·博文:
https://blog.csdn.net/chengtong222/article/details/64131358
我們還需要用到一些python中的庫:
socket、sys、getopt
接下來,我來初略的介紹這幾個包:

socket

看到這個包的名字,我想大家應該都知道它是用來幹什麼大的了,socket我們通常稱爲套接字,是用來建立網絡連接的利器。我們可以使用它很方便地創建一個tcp或者udp連接,下面是一個tcp客戶端的實例:

#! /usr/bin/env python
#-*- coding:utf-8 -*-

import socket

def client():
    HOST = '127.0.0.1' #遠程主機ip
    PORT = 9998 #遠程主機端口
    #創建一個tcp套接字
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    #連接遠程主機
    s.connect((HOST,PORT))
    #向遠程主機發送數據
    s.send('Hello, server')
    #接受來自遠程主機的數據,數據大小爲1024字節
    print s.recv(1024)
    for message in range(10):
        s.send(str(message)+'meesage have been send!')
        print s.recv(1024)
    s.close()#關閉該連接

if __name__ == '__main__':
    client()

我覺的就是創建套接字的時候,裏面的兩個參數有必要說明一下socket.AF_INET表示協議族爲ipv4,socket.SOCK_STREAM表示創建的是一個tcp連接

官方文檔:
socket.socket([family[, type[, proto]]])
Create a new socket using the given address family, socket type and protocol number. The address family should be AF_INET (the default), AF_INET6 or AF_UNIX. The socket type should be SOCK_STREAM (the default), SOCK_DGRAM or perhaps one of the other SOCK_ constants. The protocol number is usually zero and may be omitted in that case.
譯:
socket.socket([協議族[,連接類型[,協議號]]])
使用給定的地址簇,socket類型和協議號創建一個新的socket。地址簇應該是以下之一:AF_INET(默認),AF_INET6或者AF_UNIX。連接類型應該是SOCK_STREAM(默認),SOCK_DGRAM或者其他的SOCK_constants。協議號通常爲0(通常省略不寫)。

我們可以從上述實例中總結一下創建tcp客戶端的套路:
1.創建套接字:client_sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
2.連接遠程主機:client_sk.connect((ip,port))注意這裏傳入的是一個元組
3.接受數據或者發送數據
4.關閉連接
我們再來創建一個tcp服務端,tcp服務端比起客戶端稍微複雜一點:

#! /usr/bin/env python
#-*- coding:utf-8 -*-

import time
import socket


def server():
    HOST = '127.0.0.1'#監聽的ip地址
    PORT = 9998#監聽的端口號
    #創建一個套接字
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #綁定端口號與ip
    s.bind((HOST,PORT))
    #設置最大連接數爲5
    s.listen(5)
    while True:
        #接受連接
        cs,addr = s.accept()
        #不停的接受數據
        while True:
            message = cs.recv(1024)
            print message
            if not message:
                break
            message = time.ctime()+message
            cs.send(message)
        cs.close()#關閉套接字

if __name__ == '__main__':
    server()

同樣的我們來總結一下,創建tcp服務端的“套路”:
1.創建套接字
2.綁定監聽端口、ip
3.設置最大連接數量
4.等待連接
5.接收數據或者發送數據
6.關閉連接

sys

官方文檔在此:
https://docs.python.org/2/library/sys.html?highlight=sys#module-sys
這個模塊總常用的可能就是:
sys.exit()
sys.path
sys.stdin
sys.stdout
sys.stderr
sys.argv

getopt

官方手冊:
https://docs.python.org/2/library/getopt.html?highlight=getopt#module-getopt
getopt是專門用來處理命令行參數的,還是挺方便。我這裏就簡單的介紹一下它的用法:

getopt.getopt(args, options[, long_options])
Parses command line options and parameter list. args is the argument list to be parsed, without the leading reference to the running program. Typically, this means sys.argv[1:]. options is the string of option letters that the script wants to recognize, with options that require an argument followed by a colon (‘:’; i.e., the same format that Unix getopt() uses).

Note Unlike GNU getopt(), after a non-option argument, all further arguments are considered also non-options. This is similar to the way non-GNU Unix systems work.
long_options, if specified, must be a list of strings with the names of the long options which should be supported. The leading ‘–’ characters should not be included in the option name. Long options which require an argument should be followed by an equal sign (‘=’). Optional arguments are not supported. To accept only long options, options should be an empty string. Long options on the command line can be recognized so long as they provide a prefix of the option name that matches exactly one of the accepted options. For example, if long_options is [‘foo’, ‘frob’], the option –fo will match as –foo, but –f will not match uniquely, so GetoptError will be raised.

The return value consists of two elements: the first is a list of (option, value) pairs; the second is the list of program arguments left after the option list was stripped (this is a trailing slice of args). Each option-and-value pair returned has the option as its first element, prefixed with a hyphen for short options (e.g., ‘-x’) or two hyphens for long options (e.g., ‘–long-option’), and the option argument as its second element, or an empty string if the option has no argument. The options occur in the list in the same order in which they were found, thus allowing multiple occurrences. Long and short options may be mixed.

getopt.getopt(參數列表,短格式參數字符串,長格式參數序列)

看一個例子:

import getopt
>>> args = '-a -b -cfoo -d bar a1 a2'.split()
>>> args
['-a', '-b', '-cfoo', '-d', 'bar', 'a1', 'a2']
>>> optlist, args = getopt.getopt(args, 'abc:d:')
>>> optlist
[('-a', ''), ('-b', ''), ('-c', 'foo'), ('-d', 'bar')]
>>> args
['a1', 'a2']

如果某一個選項後面有參數,那麼它的後面就會帶一個冒號。
再看一個長格式參數的例子:

s = '--condition=foo --testing --output-file abc.def -x a1 a2'

>>> args = s.split()
>>> args
['--condition=foo', '--testing', '--output-file', 'abc.def', '-x', 'a1', 'a2']
>>> optlist, args = getopt.getopt(args, 'x', [
...     'condition=', 'output-file=', 'testing'])
>>> optlist
[('--condition', 'foo'), ('--testing', ''), ('--output-file', 'abc.def'), ('-x', '')]
>>> args
['a1', 'a2']

可以看到長格式的如果某一個選項後面帶了參數的話,那麼它的後面會帶一個等號

threading

官方文檔:
https://docs.python.org/2/library/threading.html?highlight=threading#module-threading
這個模塊主要是用於創建多線程的,還是通過官方文檔來看看基本用法。

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
This constructor should always be called with keyword arguments. Arguments are:

group should be None; reserved for future extension when a ThreadGroup
class is implemented.

target is the callable object to be invoked by the run() method.
Defaults to None, meaning nothing is called.

name is the thread name. By default, a unique name is constructed of
the form “Thread-N” where N is a small decimal number.

args is the argument tuple for the target invocation. Defaults to ().

kwargs is a dictionary of keyword arguments for the target invocation.
Defaults to {}.

If the subclass overrides the constructor, it must make sure to invoke
the base class constructor (Thread.init()) before doing anything
else to the thread.
我們可以調用這個函數來創建一個線程threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
雖然這個函數給了這麼多的參數,但是其實我們一般只用的到三個吧,最多四個,target是我們創建一個線程必須要用到的關鍵字參數,它是我們這個線程需要執行的函數,也就是線程需要做的事,而args參數則是target函數需要的普通參數,而kwargs則是target需要的關鍵字參數。

我們通過一個實例來看一下吧:

import threading
import time

def sayhi(num): #定義每個線程要運行的函數

    print("running on number:%s" %num)

    time.sleep(3)

if __name__ == '__main__':

    t1 = threading.Thread(target=sayhi,args=(1,)) #生成一個線程實例
    t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一個線程實例

    t1.start() #啓動線程
    t2.start() #啓動另一個線程

    print(t1.getName()) #獲取線程名
    print(t2.getName())

很簡單吧,創建一個線程,啓動它,這樣就完成了一個線程的創建。

subprocess

subprocess也是系統編程的一部分,它是創建進程的,並用來取代一些舊的方法的。這裏我就不細講了,推薦一篇博文吧:
https://www.cnblogs.com/breezey/p/6673901.html
這裏再推薦一個包:multiporcessing,和多線程多進程有關,挺強大的

開始寫代碼

netcat的主要功能其實就是將網絡上的數據顯示到屏幕上,這也是它名字的來源,”net”“cat”,所以我們需要做的就是創建一個tcp客戶端和服務端,然後他們之間發送數據,並把這些發送的數據根據我們的需要顯示在屏幕上,我們自己寫的這個小型netcat只是實現了:文件上傳、文件下載、命令執行、獲取shell的功能。《python黑帽子》書上的代碼,我覺得其實並不算實現了文件上傳與下載的功能,而且使用起來感覺很不方便,它需要執某種功能時,必須通過調整服務端的參數才行,我覺得這樣有點不方便,於是我改進了一下,只需要客戶端指定參數,服務端的任務只是監聽連接,然後執行命令,一旦運行起來了就不再需要手動調整,我覺得這樣更加人性話,雖然代碼可能有點冗餘。

我還是先貼上書裏的代碼吧,需要的請自取:

#! /usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import os
import socket
import getopt
import subprocess
import threading

listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0


#工具使用方法概述
def useage():
    print "BHP Net Tool\n"
    print "useage: netcat.py -t target_host -p port"
    print "-l  --listen"
    print "-e --execute=file_to_run"
    print "-c --command"
    print "-u --upload=destination"
    sys.exit(0)


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

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

    try:
        opts,args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:",
            ["help","listen","execute","target","port","commandshell","upload"])
    except getopt.GetoptError as err:
        print str(err)
        useage()
    print opts
    for opt,value in opts:
            if opt in ("-h","--help"):
                useage()
            elif opt in ("-l","--listen"):
                listen = True
            elif opt in ("-e","--execute"):
                execute = value
            elif opt in("-c","--commandshell"): 
                command = True
            elif opt in ("-u","--upload"):
                upload_destination = value
            elif opt in ("-t","--target"):
                target = value
            elif opt in ("-p","--port"):
                port = int(value)
            else:
                assert False,"Unhandled Option"

    if not listen and len(target) and port > 0:
        buffer = sys.stdin.read()
        client_sender(buffer)

    if listen:
        server_loop()

def client_sender(buffer):
    cs = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        cs.connect((target,port))

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

        while True:
            recv_len = 1
            response = ""
            while recv_len:
                res = cs.recv(4096)
                recv_len = len(res)
                response += res
                if recv_len < 4096:
                    break

            print response

            buffer = raw_input("")
            buffer += '\n'

            cs.send(buffer)
    except:
        print "[*] Exception! Exiting."
    cs.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_handler,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("Success 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)


    if command:
        while True:
            client_socket.send("<BHP:#>")

            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()

下面是我改進過後的代碼:

#! /usr/bin/env python
# -*- coding:utf-8 -*-

'''
You can use this tool to do somethind interesing!!!
'''

import socket
import getopt
import threading
import subprocess
import sys
import time

listen = False
shell = False
upload_des = ""
upload_src = ""
execute = ""
target = ""
port = 0

def help_message():
    print 
    print "You can connect a host like this:mytool.py -t target_ip -p port"
    print "[*]example:mytool.py -t 127.0.0.1 -p 7777"
    print 
    print "-t    specify the ip you wanna connect or listen"
    print
    print "-p    specify the port you wanna connect or listen"
    print
    print "-l    start to listen the connection"
    print
    print "-c    get a shell"
    print 
    print "-e    execute command from user"
    print "[*]example: mytool.py -t 127.0.0.1 -p 7777 -e ipconfig"
    print
    print "-u    upload files"
    print "[*]example:mytool.py -t 127.0.0.1 -p 7777 -u c:/test.txt->d:/test.txt"


def main():
    global listen
    global shell
    global port
    global execute
    global target
    global upload_src
    global upload_des
    #解析參數
    try:
        opts,args = getopt.getopt(sys.argv[1:],"t:p:lce:u:h",
            ["target=","port=","listen","commandshell","execute","upload","help"])
    except Exception as e:
        print str(e)
        help_message()
        sys.exit(0)

    for opt,value in opts:
        if opt in ["-h","--help"]:
            help_message()
        elif opt in ["-t","--target"]:
            target = value
        elif opt in ["-p","--port"]:
            port = int(value)
        elif opt in ["-c","--commandshell"]:
            shell = True
        elif opt in ["-e","--execute"]:
            execute = value
        elif opt in ["-u","--upload"]:
            upload_src = value.split(">")[0]
            print upload_src
            upload_des = value.split(">")[1]
        elif opt in ["-l","--listen"]:
            listen = True
    #判斷以服務端運行還是以客戶端運行
    if listen:
        server()
    elif not listen and len(target) and port > 0:
        client()
#客戶端邏輯
def client():
    global shell
    global port
    global execute
    global upload_src
    global target
    data = ""
    client_sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        client_sk.connect((target,port))
    except:
        print "[*]Connecting error!"
    #將客戶端的參數發往服務端
    params = " ".join(sys.argv[1:])
    client_sk.send(params)
    #print params
    #是否上傳文件
    if upload_src:
        time.sleep(5)
        data = file_read(upload_src)
        client_sk.send(data)
        data = ""
        while True:
            data_tmp = client_sk.recv(1024)
            data += data_tmp
            if len(data_tmp) < 1024:
                break
        print data
    #是否直接執行命令
    if execute:
        while True:
            data_tmp = client_sk.recv(1024)
            data += data_tmp
            if len(data_tmp) < 1024:
                break
        print data
    #是否獲得一個shell
    if shell:
        print client_sk.recv(1024)
        while True:
            data = ""
            command = raw_input()
            command += '\n'
            client_sk.send(command)
            while True:
                data_tmp = client_sk.recv(1024)
                data += data_tmp
                if len(data_tmp) < 1024:
                    print data
                    break
            print client_sk.recv(1024)
    client_sk.close()

#服務端邏輯
def server():
    global target
    global port
    #如果未指定監聽,則監聽所有接口
    if not len(target):
        target = "0.0.0.0"

    s_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s_socket.bind((target,port))
    s_socket.listen(5)

    while True:
        client_sk, addr =   s_socket.accept()
        client_thread = threading.Thread(target=client_handler,args = (client_sk,addr))
        client_thread.start()
#處理客戶端連接
def client_handler(client_sk,addr):
    global listen
    global shell
    global port
    global execute
    global upload_src
    global listen
    global upload_des

    print "Connected by"+str(addr)
    data = ""

    #接受來自客戶端的運行參數
    while True:
        data_tmp = client_sk.recv(4096)
        print data_tmp
        data += data_tmp
        if len(data_tmp) < 4096:
            break
    print data
    data_list = data.split()
    #解析來自客戶端的參數
    try:
        opts,args = getopt.getopt(data_list,"t:p:lce:u:h",
            ["target=","port=","listen","commandshell","execute=","upload","help"])
        print opts
    except Exception as e:
        print str(e)
        help_message()
        sys.exit(0)

    for opt,value in opts:
        if opt in ["-c","--commandshell"]:
            shell = True
        elif opt in ["-e","--execute"]:
            execute = value
            print execute
        elif opt in ["-u","--upload"]:
            upload_des = value.split(">")[1]
            print upload_des

    if upload_des:
        data = ""
        time.sleep(5)
        while True:
            data_tmp = client_sk.recv(4096)
            data += data_tmp
            if len(data_tmp) < 4096:
                break
        if file_write(upload_des,data):
            client_sk.send("Successfuly upload file to {0}\r\n".format(upload_des))
        else:
            client_sk.send("Failed to upload file to {0}\r\n".format(upload_des))

    if execute:
        output = run_command(execute)
        client_sk.send(output)

    if shell:
        symbol = "<Mask#:>"
        client_sk.send(symbol)
        while True:
            data = ""
            while "\n" not in data:
                data_tmp = client_sk.recv(1024)
                data += data_tmp
            print data
            output = run_command(data)
            #print output
            client_sk.send(output)#將運行結果回顯
            #time.sleep(2)
            client_sk.send(symbol)





def file_write(path,data):
    with open(path,"wb") as file:
        file.write(data)
    return True

def file_read(path):
    with open(path,"rb") as file:
        data = file.read()
    return data

def run_command(command):
    try:
        #執行命令
        output = subprocess.check_output(command,stderr=subprocess.STDOUT,shell = True)
    except:
        output = "Failed to execute command.\r\n"

    return output





if __name__ == "__main__":
    main()

我覺得main函數沒什麼好說的
我們先來看一下client函數:

def client():
    global shell
    global port
    global execute
    global upload_src
    global target
    data = ""
    client_sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        client_sk.connect((target,port))
    except:
        print "[*]Connecting error!"
    #將客戶端的參數發往服務端
    params = " ".join(sys.argv[1:])
    client_sk.send(params)
    #print params
    #是否上傳文件
    if upload_src:
        time.sleep(5)
        data = file_read(upload_src)
        client_sk.send(data)
        data = ""
        while True:
            data_tmp = client_sk.recv(1024)
            data += data_tmp
            if len(data_tmp) < 1024:
                break
        print data
    #是否直接執行命令
    if execute:
        while True:
            data_tmp = client_sk.recv(1024)
            data += data_tmp
            if len(data_tmp) < 1024:
                break
        print data
    #是否獲得一個shell
    if shell:
        print client_sk.recv(1024)
        while True:
            data = ""
            command = raw_input()
            command += '\n'
            client_sk.send(command)
            while True:
                data_tmp = client_sk.recv(1024)
                data += data_tmp
                if len(data_tmp) < 1024:
                    print data
                    break
            print client_sk.recv(1024)
    client_sk.close()

這裏是兩個改進的點,由於最開始上傳文件的功能實現的很扯淡,居然是自己輸入然後上傳,這有點名不副實,所以我改動了一下,可以選擇本地文件然後上傳,具體操作也很簡單就是以“rb”打開指定文件,然後將數據發往遠端,還有就是我將客戶端的參數都傳到了服務端,這樣服務端就知道客戶端想要做什麼事了。其它地方改動都不是很大,我也就不一一介紹了,歡迎隨時與我交流。

效果展示

這裏寫圖片描述

每天進步一點點

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章