最近,我发现这个paper描述了使用Apple的NEPacketTunnelProvider扩展程序对iOS
的嗅探机制,我很好奇,它让我想从技术的角度来理解它。由于我通常不在这样的深度网络层工作,所以我无法以我想要的细节来理解它。由于iOS的Charles Proxy必须在不需要受监督的设备的情况下执行非常相似的操作,因此我认为2016年发表的论文的作者如今仍可以使用。
作者声称:“一切都像IP数据包解析,
IP包或解析DNS响应必须自己实现。”我想完全理解这一点,所以我尝试自己构建它。我为NetworkExtension
构建了packetFlow
和消息循环NEPacketTunnelProvider
。我能够获得ip数据报并尝试解析它们。我使用了与源ip和目标ip,传输协议和ip版本相对应的大小的无符号整数。处理有效载荷,我的解析器使用ptr.load(fromByteOffset: <offset>, as:<DataType>.self)
,其中ptr
是UnsafeRawPointer
来访问数据包流信息,因为data
可能超出了{{1 }},我不知道如何以正确的方式访问和存储有效载荷。
此外,我发现源IP始终为UInt64
(设置为接口的NEIPv4Settings地址),而目标ip始终为192.168.20.1
(我的虚拟NEDNSSettings服务器)。这使我想到了第一个问题:这些DNS查询吗?数据报包会要求有关实际目标的任何其他信息吗?那是否意味着我必须以某种方式执行对DNS服务器的请求,然后将数据包重新路由到我将从该DNS查询中获得的目标?
下一步将是实现TCP / UDP处理,对吗?我目前的解析方法能够区分192.168.2.1
,UDP
和TCP
(即使我在上一本书中尚未进行研究)。因此,我将遍历数据报并查找它们是否需要ICMP
或UPD
会话/连接并传输数据报。我目前从概念上看这些问题:如何知道用于TCP / UPD连接/会话的源/目标端口?据我所知,此信息不是IP数据包本身的一部分(因为它是我们在传输层级别而不是网络层级别需要的一些信息)。
此外,我在github上找到了一个名为Specht的项目。它使用一个名为NEKit的自写库,该库以某种方式也使用了TCP
方法。当我正确理解他们的方法时,他们设法通过编写一些观察器机制来构建本地代理服务器以处理请求,但是由于我对网络和敏捷性还比较陌生,所以我不确定我是否完全理解这一点。正确,或者我是否还没有找到所有这些TCP / UDP和/或DNS逻辑。这个项目与纸面查理和查尔斯代理的方法可比吗?
最后一个问题:在大多数情况下,Charles代理能够显示目标的主机名。我目前只能看到目标ip地址(不是真正的目标ip地址,而是我的DNS服务器的地址)。如何将主机名视为可读的文本?查尔斯会以某种方式进行NEPacketTunnelProvider
吗? Charles是否从数据报中获取该信息?
我知道我对这个主题缺少很多知识,并且出于测试的原因要构建类似的东西,但我仍然非常有雄心,但是我仍然很愿意深入研究该主题,并且感到我已经理解了一些关键要点,但不幸的是还不足以解决这个难题……也许您可以给我一些更多的提示,以使您更好地理解。如果甚至有一种更简便的方法来存档类似的行为(以查看主机名级别上的传出连接),我也会对它们感兴趣:-)
答案 0 :(得分:2)
我已经使用 NEPacketTunnelProvider 和 Network Extension 发布了一个 Beta Proxyman iOS (website) - 网络嗅探器,所以我可能已经回答过您的一些问题。
<块引用>IP包,IP图,DNS,如何解析?
幸运的是,还有另一种方法可以设置 NEPacketTunnelProvider 来为您提供 HTTP 消息,而不是 IP 包(它级别太低,您必须处理解析器、DNS 等)
HTTP Message 更容易解析,因为有很多可靠的库(例如来自 nodeJS 的 http-parser)
<块引用>这是一个很难回答的问题,我会把它分成小块:
<块引用>中间人/代理服务器
首先,您需要一个有效的 MitM 代理服务器,它能够代理和拦截 HTTP/HTTPS 流量。您可以使用 SwiftNIO 或 CocoaAsyncSocket 来实现它。
<块引用>它是如何工作的?
通常,数据流可能如下所示:
互联网 -> iPhone -> 你的网络扩展 (VPN) -> 转发到你的本地代理服务器(在网络扩展中) -> 中间人/代理服务器开始拦截或监控流量 -> 保存到本地数据库(在共享容器组中)-> 再次转发到目标服务器。
从主应用程序中,您可以通过读取本地数据库来接收数据。
我们需要本地数据库的原因是网络扩展和主应用程序是两个不同的进程,所以它们不能像普通应用程序一样直接通信。
<块引用>给我看代码?
在 Network 扩展中,让我们在 Host:Port 处启动一个代理服务器,然后初始化 NetworkSetting,就像示例一样:
private func initTunnelSettings(proxyHost: String, proxyPort: Int) -> NEPacketTunnelNetworkSettings {
let settings: NEPacketTunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
/* proxy settings */
let proxySettings: NEProxySettings = NEProxySettings()
proxySettings.httpServer = NEProxyServer(
address: proxyHost,
port: proxyPort
)
proxySettings.httpsServer = NEProxyServer(
address: proxyHost,
port: proxyPort
)
proxySettings.autoProxyConfigurationEnabled = false
proxySettings.httpEnabled = true
proxySettings.httpsEnabled = true
proxySettings.excludeSimpleHostnames = true
proxySettings.exceptionList = [
"192.168.0.0/16",
"10.0.0.0/8",
"172.16.0.0/12",
"127.0.0.1",
"localhost",
"*.local"
]
settings.proxySettings = proxySettings
/* ipv4 settings */
let ipv4Settings: NEIPv4Settings = NEIPv4Settings(
addresses: [settings.tunnelRemoteAddress],
subnetMasks: ["255.255.255.255"]
)
ipv4Settings.includedRoutes = [NEIPv4Route.default()]
ipv4Settings.excludedRoutes = [
NEIPv4Route(destinationAddress: "192.168.0.0", subnetMask: "255.255.0.0"),
NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"),
NEIPv4Route(destinationAddress: "172.16.0.0", subnetMask: "255.240.0.0")
]
settings.ipv4Settings = ipv4Settings
/* MTU */
settings.mtu = 1500
return settings
}
然后启动 VPN,
let networkSettings = initTunnelSettings(proxyHost: ip, proxyPort: port)
// Start
setTunnelNetworkSettings(networkSettings) { // Handle success }
然后将包转发到您的本地代理服务器:
let endpoint = NWHostEndpoint(hostname: proxyIP, port: proxyPort)
self.connection = self.createTCPConnection(to: endpoint, enableTLS: false, tlsParameters: nil, delegate: nil)
packetFlow.readPackets {[weak self] (packets, protocols) in
guard let strongSelf = self else { return }
for packet in packets {
strongSelf.connection.write(packet, completionHandler: { (error) in
})
}
// Repeat
strongSelf.readPackets()
}
从那里,您的本地服务器可以接收包然后转发到目标服务器。
不要忘记将所有流量日志保存到本地数据库,然后通知主应用重新加载它。
<块引用>最后一个问题:Charles 代理在大多数情况下能够显示目标的主机名。我目前只能看到目标 ip 地址(这不是真正的目标 ip 地址,而是我的 DNS 服务器的地址)。我如何才能将主机名视为人类可读的文本? Charles 会以某种方式执行 nslookup 吗? Charles 是否从数据报中获取该信息?
由于我们不处理IP Package,所以不需要实现DNS Resolver。如果你需要一个DNS,你可以像下面的代码一样配置:
let dnsSettings = NEDNSSettings(servers: ["8.8.8.8", "1.1.1.1"])
settings.dnsSettings = dnsSettings
当我们收到 HTTP 消息包时,您可以免费获取主机名(来自请求的 URL 或主机头)
希望我的回答能帮到你。