如何搭建 TCP 代理(三)

地球胖头鱼 2020-11-25
系统与内网安全 发布于 2020-11-25 09:54:55 阅读 81 评论 0

使用netcat探测dns请求

at的工具来主动监听传入的DNS请求,甚至发送回基本的DNS响应,这将允许你的手机再次开始与部分互联网进行连接。

Netcat是一个读写tcp和UDP网络连接数据的工具,如上所述,你的智能手机通过UDP端口53发送DNS请求,要使用netcat监听这些请求,请运行以下命令:

sudo ncat -nluvvv 53

在一个终端,该命令需要以sudo身份运行,因为它监听的是一个编号较低的敏感端口。

现在,当你试图用智能手机访问一个网站时,你应该会看到一个DNS请求打印在你的笔记本电脑的终端上。由于该请求并不是为方便人类阅读而设计的,因此它在大多数情况下看起来都像是乱码。然而,你应该仍然能够在其中找出你试图访问的网站的主机名。

你可能需要停止并启动ncat监听器几次才能使其正常工作,如果你仍然无法使其正常运行,那么只要你的智能手机发送的数据包出现在Wireshark中,你就不必担心。

现在,我们已经使用Wireshark和Netcat检查了你的智能手机的DNS请求。但是,你的笔记本电脑仍然不知道如何响应。它不会将DNS响应发送回你的智能手机,因此你的智能手机仍然无法实际加载你要访问的页面。不过,你可以使用netcat的一个更高级的选项发送回DNS响应。

使用Netcat发送回DNS响应

ncat UDP监听器具有–exec选项,你可以使用–exec指定当UDP监听器接收到数据包时netcat应该运行的命令。 Netcat将数据包的内容输入到stdin上–exec指定的命令中,并将命令写入标准输出的任何输出发送回数据包的原始发送者。例如,为了在监听器收到UDP数据包时运行Python脚本,可以运行:

sudo ncat -nluvvv --keep-open --exec "/usr/bin/python /PATH/TO/THE/SCRIPT.py" 53

让我们使用--exec响应智能手机的DNS请求,将以下Python脚本保存到笔记本电脑上。

import sys

# Read the first 2 bytes from the DNS request that netcat
# is piping in on stdin. These byte are the request ID and
# must be included at the start of the response so that the
# requester can match the response up with its original request
req_id = sys.stdin.read(2)
# Convert the bytes to hex
req_id_hex_str = ''.join(["%02X" % ord(c) for c in str(req_id)])
# This is hex for the DNS response body "robertheaton.com
# is at IP address 104.18.32.191". To give yourself some
# confidence that I'm telling the truth, run:
#
# python -c "print bytearray.fromhex('$COPY_THE_HEX_STRING_HERE')"
#
# in a terminal.
resp_body_str = "818000010002000000000c726f62657274686561746f6e03636f6d0000010001c00c000100010000012b0004681220bfc00c000100010000012b0004681221bf"

该脚本将我从Wireshark复制并粘贴的标准硬编码的,预先生成的DNS响应输出到stdout。此硬编码的响应告诉请求者(你的智能手机)robertheaton.com解析为IP地址104.18.32.191。为了确保请求者可以将我们的响应与原始请求匹配,脚本将读取请求的前2个字节(表示DNS请求ID),并将其添加到主响应正文中。之所以必须这样做,是因为UDP是无连接协议,并且不会为你的智能手机提供任何内置方式来将DNS响应与请求进行匹配。

你应该验证DNS响应十六进制字符串确实是我所声称的,你可以通过在终端中运行以下命令来确认响应是无害的:

python -c "print bytearray.fromhex('$COPY_THE_HEX_STRING_HERE')"

更好的方法是从Wireshark中复制你自己的DNS请求的十六进制代码,并在脚本中使用它。

你还应该检查自己是否信任脚本的其余部分,如果你使用sudo以root身份运行ncat,则该Python脚本也将以具有所有相关特权的root用户身份运行。不过请不要过分担心,我保证这一切都是完全良性和合法的。

要使用脚本将DNS响应发送回你的手机,请确保你的智能手机的DNS服务器仍然设置为你的笔记本电脑并运行:

sudo ncat -nluvvv --keep-open --exec "/usr/bin/python /PATH/TO/THE/SCRIPT.py" 53

现在,在智能手机上访问robertheaton.com以外的网站,或从Wireshark复制的响应中的主机名,你应该发现它仍然无法正常工作。这是因为你的智能手机向笔记本电脑询问了诸如“facebook.com的IP地址是什么?”之类的问题,并且你的笔记本电脑发回了一个不合逻辑的“robertheaton.com的IP地址为104.18.32.191”。由于请求和响应的主机名不匹配,因此你的智能手机将完全忽略响应。

我们的原始DNS服务器可以使用的唯一主机名就是互联网上最重要的网站robertheaton.com。在你的智能手机上访问robertheaton.com,你应该会看到它正常加载。这是因为“robertheaton.com的IP地址是104.18.32.191”才是问题 “robertheaton.com的IP地址是什么?”的正确回复。

如何伪造DNS服务器?

随着dns欺骗概念的验证,我们现在准备构建和运行我们的完整的、虚假的DNS服务器。我用一个简短的Python脚本和强大的网络库scapy为我们编写了这样一个服务器。同样,这个脚本需要使用sudo来运行,以便允许它在端口53上监听。将该脚本复制到笔记本电脑上,并使用以下命令运行:

sudo python /PATH/TO/SCRIPT.py

此代码也在GitHub上:

import dns.resolver
import scapy.all as scapy
import netifaces as ni

def handle_packet_fn(iface, spoof_ip, spoof_domains):
    def handle_packet(packet):
        ip = packet.getlayer(scapy.IP)
        udp = packet.getlayer(scapy.UDP)

        # Ignore packets containing data we aren't interested
        # in.
        if hasattr(packet, 'qd') and packet.qd is not None:
            queried_host = packet.qd.qname[:-1].decode("utf-8")
            if queried_host is None:
                print("queried_host is None, dropping request")
                return

            # If the queried_host is one of the domains we want
            # to spoof, return the spoof_ip.
            if queried_host in spoof_domains:
                print("!!!! Spoofing DNS request for %s by %s !!!!"
                        % (queried_host, ip.src))
                resolved_ip = spoof_ip
            # Else use dns.resolver to make a real DNS "A record"
            # request, and return the result of that.
            else:
                print("Forwarding DNS request for %s by %s" %
                        (queried_host, ip.src))
                a_records = dns.resolver.query(queried_host, 'A')
                resolved_ip = a_records[0].address

            # Build the DNS answer
            dns_answer = scapy.DNSRR(
                rrname=queried_host + ".",
                ttl=330,
                type="A",
                rclass="IN",
                rdata=resolved_ip)
            # Build the DNS response by constructing the IP
            # packet, the UDP "datagram" that goes inside the
            # packet, and finally the DNS response that goes
            # inside the datagram.
            dns_response = \
                scapy.IP(src=ip.dst, dst=ip.src) / \
                scapy.UDP(
                    sport=udp.dport,
                    dport=udp.sport
                ) / \
                scapy.DNS(
                    id = packet[scapy.DNS].id,
                    qr = 1,
                    aa = 0,
                    rcode = 0,
                    qd = packet.qd,
                    an = dns_answer
                )

            print("Resolved DNS request for %s to %s for %s" %
                    (queried_host, resolved_ip, ip.src))

            # Use scapy to send our response back to your phone.
            scapy.send(dns_response, iface=iface)
        else:
            print("Ignoring unrecognized packet from %s" % ip.src)

    return handle_packet


def _get_local_ip(iface):
    ni.ifaddresses(iface)
    return ni.ifaddresses(iface)[ni.AF_INET][0]['addr']


def run(iface, local_ip, sniff_filter, spoof_domains):
    print("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#")
    print("-#-#-#-#-#-RUNNING DNS SPOOFER-#-#-#-#-#-")
    print("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#")
    print("Interface:\t\t\t%s" % iface)
    print("Resolving to IP:\t\t%s" % local_ip)
    print("Spoof domains:\t\t%s" % ', '.join(spoof_domains))
    print("BPF sniff filter:\t\t%s" % sniff_filter)
    print("")
    print("Waiting for DNS requests...")
    print("(Make sure the device you are targeting is set to use"\
            "your local IP (%s) as its DNS server)" % local_ip)

    scapy.sniff(iface=iface,
                filter=sniff_filter,
                prn=handle_packet_fn(iface, local_ip, spoof_domains))


IFACE= 'en0'
local_ip = _get_local_ip(IFACE)
# The local IP of your phone
client_ip = '192.168.42.74'

SPOOF_DOMAINS = ['nonhttps.com', 'www.nonhttps.com']
SNIFF_FILTER = ("udp port 53 && dst %s && src %s" %
    (local_ip, client_ip))

run(IFACE, local_ip, SNIFF_FILTER, SPOOF_DOMAINS)

启动DNS服务器后,请确保如上所述将智能手机的DNS服务器仍设置为笔记本电脑。在智能手机上访问一些网站,然后回头查看终端,你应该看到DNS请求来自你的智能手机,并被我们的DNS服务器记录。

我们服务器的行为取决于DNS请求所针对的主机名,如果请求的是我们的目标主机名(当前在脚本中设置为nonhttps.com,因为我们还不需要担心HTTPS),则我们的服务器将使用你笔记本电脑的本地IP地址进行响应。对于所有其他的,它会向一个真正的DNS服务器发出自己的真正的DNS请求,并使用真实答案以真相响应你的智能手机。

这意味着你的智能手机现在可以使用我们伪造的DNS服务器,针对除目标以外的所有主机名,将主机名准确解析为IP地址。除了我们当前的目标nonhttps.com之外,它的连网状态应该刚刚恢复。

我们已经证实,在智能手机上访问nonhttps.com。你应该看到我们的DNS服务器日志,它已经欺骗了DNS响应,并返回了笔记本电脑的本地IP地址。

然后,你的智能手机应尝试将其对nonhttps.com的HTTP请求发送到便携式计算机,而不是真正的nonhttps.com服务器。虽然,页面应该无法加载,但已经是个明显的进步了。

通过使用Wireshark,我们确认这确实是刚刚发生的事情。使用数据包筛选器tcp端口80 && dst $ YOUR_LAPTOPS_LOCAL_IP运行Wireshark,在智能手机上重新加载nonhttps.com,然后回头看Wireshark。你应该会看到智能手机尝试通过端口80上的TCP连接到笔记本电脑。TCP连接的“目标IP”应设置为笔记本电脑的本地IP,其“源IP”应设置为智能手机的IP。这证明我们已成功说服你的智能手机将其对nonhttps.com的TCP请求发送到你的笔记本电脑!

代码解析

现在,我们的DNS服务器正在工作,让我们仔细看看它的代码,你可能会发现在GitHub中打开代码很有用。

我们的服务器会监视所有通过笔记本电脑wi-fi接口的数据包,并对那些看起来像DNS请求的数据包运行一个回调函数。回调函数构建一个DNS响应,并将其发送回你的智能手机。

我们的服务器是使用scapy(Python网络库)构建的,我们通过向scapy传递一个过滤器表达式来挑选DNS流量(udp端口53 && dst $YOUR_LAPTOPS_LOCAL_IP && src $YOUR_PHONES_LOCAL_IP)。只要scapy看到匹配此过滤器的数据包,它就会在其上运行我们的回调函数。这个回调构造并向你的智能手机发送一个DNS响应,如前所述,响应包含笔记本电脑的本地IP地址或请求域的实际IP地址。

DNS响应还必须包含正确的主机名和DNS请求ID,以便你的智能手机能够匹配其原始请求的响应。正如我们在netcat的实验中所看到的,如果这些字段出错,你的智能手机将忽略我们的DNS响应。与我们的原始netcat脚本不同,我们的DNS服务器动态读取DNS请求的主机名和请求ID,并在响应中正确设置它们。

最后,我们不仅必须从头开始手动构建DNS响应,还必须构建将传输它的IP数据包。特别是,我们需要指定IP数据包的源IP地址和目标IP地址,以确保将其安全路由到你的智能手机。为此,我们将响应IP包的源设置为请求IP包的目的地,反之亦然。

在我们的TCP代理项目的这一部分中,我们将你的智能手机配置为将其DNS请求发送到你的笔记本电脑。我们构建了一个伪造的DNS服务器,使你的笔记本电脑可以通过欺骗响应来响应这些DNS请求。如果DNS请求是关于我们的目标主机名的,则我们的服务器会以你笔记本电脑的本地IP地址作为响应。

这意味着你的智能手机开始尝试与我们的目标应用程序构建TCP连接,而不是与你的笔记本电脑,它的功能与所有其他主机名一样。

接下来,我们将构建一个在笔记本电脑上运行的TCP服务器,并监听来自智能手机的这些传入TCP连接。该服务器将打印并保存这些请求的内容,将其转发到预期的目的地,并处理TLS加密和解密。然后,我们将拥有我们梦寐以求的通用TCP代理。

代理服务器

该代理将能够处理任何基于TCP的协议,而不仅仅是HTTP。

到目前为止,我们已经构建的虚假DNS服务器正在欺骗你的智能手机将TCP数据发送到你的笔记本电脑,接下来,我们将构建实际的TCP代理服务器,并在你的笔记本电脑上运行它。它的工作是监听你的手机发送的数据,将其转发到实际的远程服务器。最后,将所有来自服务器的响应转发回你的手机。换句话说,就是充当代理。

代理服务器的工作原理

我们的代理将运行在你的笔记本电脑上,监听端口80上的TCP连接。按照规定,是未加密的HTTP端口。当它从你的智能手机接收到一个TCP连接时,它将首先构建另一个TCP连接,这一次使用的是目标主机的远程服务器。其次,它将接收通过与你的智能手机连接接收到的任何数据,然后通过与远程服务器的新连接重新发送这些数据。第三,它将监听来自服务器的响应数据。第四步,也是最后一步,它会把这个响应数据传回你的智能手机,完成一个4步循环。你的智能手机将能够与远程服务器正常交谈,只需通过我们的代理稍微绕行。

我们将使用HTTP代替其他基于TCP的协议进行测试,因为它更容易。要使你的手机发出HTTP请求,你要做的就是访问一个网站。我们的代理不是“non-HTTP” ,而是 “non-HTTP-specific”。此外,我们的TCP代理的第一个版本将无法处理TLS加密。因此,我们将必须谨慎使用使用未加密HTTP而非HTTPS的网站进行测试。我们将在接下来添加TLS支持:

让我们仔细看看我们的4步循环的每个阶段:从智能手机,到笔记本电脑,到远程服务器,然后再返回。

此时,你的手机已经被诱骗将其目标主机名的TCP连接发送到你的笔记本电脑,剩下的就是确保我们的代理安全接收它们。

将笔记本电脑的信息传送到远程服务器

从笔记本电脑到远程服务器的第二步由我们的代理处理,当我们的代理从你的智能手机接收到TCP连接时,它将转身并启动第二个TCP连接,这一次是与目标远程服务器的连接。然后,它将从手机收到的所有数据重新发送到远程服务器。

如上所述,我们将对代理应该与之构建第二连接的远程服务器的主机名进行硬编码。我们还将注意确保此硬编码的主机名与我们的虚假DNS服务器中的硬编码的主机名匹配。

将服务器的信息到发送到笔记本电脑

一旦我们的代理服务器向远程服务器发送了从智能手机接收到的数据,剩下要做的就是将它从远程服务器接收到的响应数据发送回手机。将服务器的信息到发送到笔记本电脑的过程中,我们将确保代理可以接收来自远程服务器的响应数据。

将响应数据发送回你的手机

最后,我们的代理会将响应数据发送回你的手机。你的手机将以完全相同的形式接收数据,就像它直接与远程服务器通信一样,并且它将假定刚刚发生的一切都是完全正常的。

本文翻译自:https://robertheaton.com/2018/08/31/how-to...https://robertheaton.com/2018/08/31/how-to...

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!
请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!
地球胖头鱼
未填写
  • 作者发布文章189
  • 作者收获粉丝7
  • 作者收到点赞0
  • 所有文章被收藏了1
  • 博客总访问量排行第2
  • 博客总访问量3.4 万(每日更新)
查看所有博文