1. 程式人生 > >Python黑帽子-實現netcat基本功能(改進版)

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”開啟指定檔案,然後將資料發往遠端,還有就是我將客戶端的引數都傳到了服務端,這樣服務端就知道客戶端想要做什麼事了。其它地方改動都不是很大,我也就不一一介紹了,歡迎隨時與我交流。

效果展示

這裡寫圖片描述

每天進步一點點