《Python 黑帽子》学习笔记 - 原书 netcat 代码分析 - Day 7

本文详述了Python实现netcat的功能,包括数据传输、文件上传、命令执行和命令行shell。分析了代码实现逻辑,特别是客户端和服务端的交互,以及在测试过程中遇到的socket.error 10054问题,并提出了处理此类异常的通用方法。同时,提到了原书代码的优化需求。

简单介绍了 netcat 的功能和运用后,开始学习理解该书中实现的 netcat 代码。

功能

  • 从标准输入中读取数据,并通过网络发送数据。
  • 上传文件。
  • 执行命令。
  • 命令行 shell.

实现

  1. 解析命令行选项和参数,根据选项设置相应功能标识及参数变量,如果选项和参数不正确打印帮助信息。
  2. 依据功能标识变量,设计调用功能函数的逻辑流程。主体包括两部分,向外连接和本地监听。
  3. 向外连接部分,自身作为客户端,向服务端发送和接收数据。
  4. 本地监听部分,自身作为服务端,绑定端口监听,对连接进来的客户端,新创建客户端处理线程,处理客户端请求。
  5. 客户端处理线程,根据上传文件、执行命令、命令行 shell 等功能,调用相应功能函数,完成数据发送和接收。
  6. 命令行 shell 功能,创建一个 shell 的子进程,传入客户端发送过来的 shell 命令给子进程,并把执行结果发送给客户端。
  7. 执行命令功能,会创建一个子进程,根据 execute 选项的参数执行相应命令,命令可以为程序或脚本。

原书代码附笔记最后,也可以在下面的地址下载。

https://nostarch.com/blackhatpython

测试

对原书 netcat 的测试,这里选取 “命令行 shell ” 和 “执行命令” 两个功能进行测试。

“命令行 shell ” 功能:

test-shell

“执行命令” 功能:

test-exec-calc

要注意的是,对于 sys.stdin.read(), 需要发送 ctrl + d (Linux) 或 ctrl + z (Windows) 以避免从标准输入中读取数据,程序才能往下继续执行。

原书 netcat 的代码要优化的地方非常多,而且有些代码会让 Python 进程死掉,用了 N 次 taskkill /f /im python.exe 来结束进程,重新测试。搞得自己都没信心再往下跟,举个测试上传文件功能的例子,如图:

test-upload

服务端运行

python bhpnet.py -l -p2222 -u1.txt

等待客户端连接,并接受客户端发送的数据,存为文件。客户端用

nc 127.0.0.1 2222

连接服务端,并发送数据。或者用 echo 和管道直接发送数据。

echo -ne "aaaaaaaasssssssss\r\naaaaa\nss\n" | nc 127.0.0.1 2222

代码为:

# check for upload
    if len(upload_destination):

        # read in all the bytes of the file and write to our destination

        file_buffer = ""

        # keep reading data until none is available
        while True:
            data = client_socket.recv(1024)

            if not data:
                break
            else:
                file_buffer += data

        # now we take these bytes and write them out
        try:
            file_descriptor = open(upload_destination, "wb")
            file_descriptor.write(file_buffer)
            file_descriptor.close()

            # acknowledge that we wrote the file out
            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)

异常发生在

data = client_socket.recv()

语句,调试的时候,当客户端主动强制断开连接后(Windows 终端下 ctrl+c),socket.recv() 函数产生异常,错误为 [Errno 10054]. 而且这里的 data 不会为空,socket 不管是阻塞还是非阻塞,socket.recv() 的返回值都不会为空字符,所以用 if not data: 作为跳出循环的判断不合适。

D:\myProjects\Black-Hat-Python\venv-p2\Scripts\python.exe D:/myProjects/Black-Hat-Python/codes-p2/bhpnet.py -l -p2222 -u1.txt
Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python27\Lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "C:\Python27\Lib\threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "D:/myProjects/Black-Hat-Python/codes-p2/bhpnet.py", line 124, in client_handler
    data = client_socket.recv(1024)
error: [Errno 10054] 

socket.error: [Errno 10054] 的原因,是由于远程主机强迫关闭了一个现有的连接,比如没有调用 socket.close() 或 socket 超时。在写爬虫的时候也经常会遇到,比如,服务器发现你的爬虫了,会强制断开你的连接,你的爬虫程序就可能出现 10054 错误。

解决这种情况的常用方法是:

  • 读入新数据;
  • 判断有没有完整的新信息(如数据结束标志,新的数据请求等);
  • 处理新消息。

也是其他 TCP 的 socket 编程都普遍采用的方法,即在调用 socket.send()socket.recv(), 要设置自己的结束字符来判断数据是否收发完毕。

Python2 代码

#!/opt/local/bin/python2.7

import sys
import socket
import getopt
import threading
import subprocess


# define some global variables
listen             = False
command            = False
upload             = False
execute            = ""
target             = ""
upload_destination = ""
port               = 0

# this runs a command and returns the output
def run_command(command):

        # trim the newline
        command = command.rstrip()

        # run the command and get the output back
        try:
                output = subprocess.check_output(command,stderr=subprocess.STDOUT, shell=True)
        except:
                output = "Failed to execute command.\r\n"

        # send the output back to the client
        return output

# this handles incoming client connections
def client_handler(client_socket):
        global upload
        global execute
        global command

        # check for upload
        if len(upload_destination):

                # read in all of the bytes and write to our destination
                file_buffer = ""

                # keep reading data until none is available
                while True:
                        data = client_socket.recv(1024)

                        if not data:
                                break
                        else:
                                file_buffer += data

                # now we take these bytes and try to write them out
                try:
                        file_descriptor = open(upload_destination,"wb")
                        file_descriptor.write(file_buffer)
                        file_descriptor.close()

                        # acknowledge that we wrote the file out
                        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)



        # check for command execution
        if len(execute):

                # run the command
                output = run_command(execute)

                client_socket.send(output)


        # now we go into another loop if a command shell was requested
        if command:

                while True:
                        # show a simple prompt
                        client_socket.send("<BHP:#> ")

                        # now we receive until we see a linefeed (enter key)
                        cmd_buffer = ""
                        while "\n" not in cmd_buffer:
                                cmd_buffer += client_socket.recv(1024)


                        # we have a valid command so execute it and send back the results
                        response = run_command(cmd_buffer)

                        # send back the response
                        client_socket.send(response)

# this is for incoming connections
def server_loop():
        global target
        global port

        # if no target is defined we listen on all interfaces
        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()

                # spin off a thread to handle our new client
                client_thread = threading.Thread(target=client_handler,args=(client_socket,))
                client_thread.start()


# if we don't listen we are a client....make it so.
def client_sender(buffer):

        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        try:
                # connect to our target host
                client.connect((target,port))

                # if we detect input from stdin send it 
                # if not we are going to wait for the user to punch some in

                if len(buffer):

                        client.send(buffer)

                while True:

                        # now wait for data back
                        recv_len = 1
                        response = ""

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

                                if recv_len < 4096:
                                        break

                        print response, 

                        # wait for more input
                        buffer = raw_input("")
                        buffer += "\n"                        

                        # send it off
                        client.send(buffer)


        except:
                # just catch generic errors - you can do your homework to beef this up
                print "[*] Exception! Exiting."

                # teardown the connection                  
                client.close()  




def usage():
        print "Netcat Replacement"
        print
        print "Usage: bhpnet.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
        print "Examples: "
        print "bhpnet.py -t 192.168.0.1 -p 5555 -l -c"
        print "bhpnet.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe"
        print "bhpnet.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\""
        print "echo 'ABCDEFGHI' | ./bhpnet.py -t 192.168.11.12 -p 135"
        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()

        # read the commandline options
        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(err)
                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"


        # are we going to listen or just send data from stdin
        if not listen and len(target) and port > 0:

                # read in the buffer from the commandline
                # this will block, so send CTRL-D if not sending input
                # to stdin
                buffer = sys.stdin.read()

                # send data off
                client_sender(buffer)   

        # we are going to listen and potentially 
        # upload things, execute commands and drop a shell back
        # depending on our command line options above
        if listen:
                server_loop()

main()       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值