<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hack - Aber's BLOG</title><link>https://aber.sh/corpus/Hack/</link><description>知不知，上；不知知，病。夫唯病病，是以不病。圣人不病，以其病病，是以不病。</description><atom:link href="http://aber.sh/corpus/Hack/feed.xml" rel="self"></atom:link><language>zh-hans</language><lastBuildDate>Fri, 03 Apr 2020 16:26:51 +0000</lastBuildDate><item><title>使用WebSocket进行网络穿透(续)</title><link>http://aber.sh/articles/Fuck-GFW-WebSocket-/</link><description>&lt;p&gt;继上一篇&lt;a href="/articles/Fuck-GFW-WebSocket/"&gt;使用WebSocket进行网络穿透&lt;/a&gt;，实际使用中，UDP转发并不常用，并且使用WebSocket转发UDP包也会导致较高的延迟（我当时之所以有这个需求是因为学校网络限制）。&lt;/p&gt;
&lt;p&gt;所以为了更简单的实现，更短的延迟，在此重新设计一种新的基于WebSocket的TCP转发协议。&lt;/p&gt;
&lt;p&gt;代码实现在&lt;a href="https://github.com/abersheeran/websocks"&gt;websocks&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id="_1"&gt;协商&lt;/h3&gt;
&lt;p&gt;当客户端与服务端建立连接时, 需将 Basic 格式的身份认证信息通过 &lt;code&gt;Authorization&lt;/code&gt; 头发送到服务器。&lt;/p&gt;
&lt;p&gt;服务端响应应按照HTTP标准，如身份验证失败则返回401，身份验证成功但被限制访问则返回403，成功则返回101。&lt;/p&gt;
&lt;h3 id="_2"&gt;转发&lt;/h3&gt;
&lt;p&gt;为了复用单条 WebSocket 连接, 约定如下三种状态, 组成一次完整的请求周期如下：&lt;/p&gt;
&lt;h4 id="_3"&gt;请求连接&lt;/h4&gt;
&lt;p&gt;websocket使用文本帧传递JSON格式的字符串，请求连接的服务器HOST与PORT。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;HOST&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;example.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;PORT&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;服务器成功或失败之后，返回一个状态&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ALLOW&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;若成功, 则转至转发状态.&lt;/p&gt;
&lt;p&gt;若失败, 则直接转至结束连接状态.&lt;/p&gt;
&lt;h4 id="_4"&gt;转发&lt;/h4&gt;
&lt;p&gt;在此过程中, 仅使用二进制帧, 进行转发数据。&lt;/p&gt;
&lt;h4 id="_5"&gt;结束连接&lt;/h4&gt;
&lt;p&gt;客户端或服务端主动断开连接，&lt;strong&gt;双方均需&lt;/strong&gt;使用文本帧发送或者响应&lt;strong&gt;一次&lt;/strong&gt;&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;STATUS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CLOSED&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;当结束连接之后，客户端可重新使用此条WebSocket连接进行如上周期。&lt;/p&gt;</description><pubDate>Mon, 21 Oct 2019 13:32:05 +0000</pubDate><guid>http://aber.sh/articles/Fuck-GFW-WebSocket-/</guid></item><item><title>使用WebSocket进行网络穿透</title><link>http://aber.sh/articles/Fuck-GFW-WebSocket/</link><description>&lt;p&gt;网络穿透的本质就是代理，而想要稳定的翻墙，必须要把代理伪装成一个正常的网络请求，而这一点上，在拜读了Shadowsocks的源码之后，我觉得它还不够，因为Shadowsocks的连接只能让GFW知道这个是个未知的协议。虽然SSR的混淆做的比较好，然而Breakwa11都删库了，他那个神仙代码我实在是没法维护，还是自己写吧。&lt;/p&gt;
&lt;p&gt;在之前的一篇&lt;a href="/articles/Fuck-Campus-Network/"&gt;绕开校园网计费&lt;/a&gt;里，我写了一个Sock5代理，但单纯的Socks5代理是无法翻墙的，因为GFW能轻易的分析出你是一个代理，从而封掉你的海外IP。&lt;/p&gt;
&lt;p&gt;在研究完了Shadowsocks的混淆代码之后，我把目光盯上了WebSocket。&lt;/p&gt;
&lt;h2 id="websocket"&gt;WebSocket&lt;/h2&gt;
&lt;p&gt;WebSocket是一种基于TCP的长连接，它会使用一次HTTP的报文握手，在那之后便是WebSocket自身规定的方式进行通讯了。它是个在各种网站都可能被用到的协议。&lt;strong&gt;既然它常见，那么就安全&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="websocket_1"&gt;为什么要使用WebSocket&lt;/h3&gt;
&lt;p&gt;使用WebSocket的原因除了它常见以外，还有就是可以利用各种CDN服务，将你的WebSocket进行代理，而让真实的服务器IP隐藏在重重CDN之后。哪怕真实的服务器IP被封锁了，你的代理也不会失效。&lt;/p&gt;
&lt;p&gt;并且标准Websocket的协议设计，使得客户端发送到服务端的信息都有一次性的掩码进行混淆，使得我们不需要做二次处理，只需要稍微处理一下服务器发送到客户端的数据即可。&lt;/p&gt;
&lt;h3 id="websocket_2"&gt;WebSocket 浅析&lt;/h3&gt;
&lt;p&gt;在使用 WebSocket 之前，先了解一下 WebSocket。&lt;/p&gt;
&lt;h3 id="http"&gt;HTTP 握手&lt;/h3&gt;
&lt;p&gt;客户端发送到服务端的 HTTP 请求中，请求头必须包含如下四个字段。&lt;code&gt;Connection: Upgrade&lt;/code&gt; 指定此次请求需要从 HTTP 协议升级，&lt;code&gt;Upgrade&lt;/code&gt; 指定此次请求升级到 &lt;code&gt;websocket&lt;/code&gt;， &lt;code&gt;Sec-WebSocket-Version&lt;/code&gt; 指定 WebSocket 的版本(一般都是 13)，&lt;code&gt;Sec-WebSocket-Key&lt;/code&gt; 是一个 16 byte 长度的随机字符串经过 base64 处理后的结果。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Upgrade&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Upgrade&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;Sec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dGhlIHNhbXBsZSBub25jZQ&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;Sec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;服务端视情况决定是否允许升级到 websocket，如果不同意，则返回一个常规 HTTP 响应即可。如果同意，则返回状态码为 101 的 HTTP 响应。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Sec-WebSocket-Accept&lt;/code&gt; 的值为客户端发送的 &lt;code&gt;Sec-WebSocket-Key&lt;/code&gt; 拼接 &lt;code&gt;"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"&lt;/code&gt; 的字符串的 sha-1 值的 base64 编码结果——用伪代码编写应该是 &lt;code&gt;base64encode(sha1(SecWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;握手阶段完成后，后续所有的数据传输都与 HTTP 协议无关了，使用纯粹的 WebSocket 报文进行交互。&lt;/p&gt;
&lt;h4 id="websocket_3"&gt;WebSocket 报文结构&lt;/h4&gt;
&lt;p&gt;&lt;img alt="" src="/upload/WebSocket.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fin：1bit，用于标记当前数据帧是不是最后一个数据帧，单条消息可能会分成多个数据帧来传递。（如果只需要一个数据帧，第一个数据帧也就是最后一个）&lt;/li&gt;
&lt;li&gt;RSV1，RSV2，RSV3：各自1bit，用于扩展用途，一般全部置0即可。&lt;/li&gt;
&lt;li&gt;Opcode：4bit，操作码，用于描述此数据帧的类型：&lt;ul&gt;
&lt;li&gt;0x0：标示当前数据帧为分片的数据帧，也就是当一个消息需要分成多个数据帧来传送的时候，需要将opcode设置位0x0。&lt;/li&gt;
&lt;li&gt;0x1：标示当前数据帧传递的内容是文本，编码为UTF-8。&lt;/li&gt;
&lt;li&gt;0x2：标示当前数据帧传递的内容是二进制数据。&lt;/li&gt;
&lt;li&gt;0x8：标示请求关闭连接。&lt;/li&gt;
&lt;li&gt;0x9：标示此数据帧为ping帧。&lt;/li&gt;
&lt;li&gt;0xA：标示此数据帧为pong帧，仅在接收ping帧后发送pong。&lt;/li&gt;
&lt;li&gt;0x3~0x7以及0xB~0xF：留作其他用途。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MASK：1bit，标示数据有没有使用掩码。在RFC中规定，服务端发送给客户端的数据帧不能使用掩码，客户端发送给服务端的数据帧必须使用掩码。&lt;/li&gt;
&lt;li&gt;Payload length：7bit的无符号整数，用于标识Payload data的长度。&lt;br /&gt;
值在0-125之间时，这就是Payload data的长度。&lt;br /&gt;
值为126时，使用后续2个bytes也就是16 bit的无符号整数来标识Payload data的长度。&lt;br /&gt;
值为127时，使用后续8个bytes也就是64 bit的无符号整数用于标识Payload data的长度。&lt;/li&gt;
&lt;li&gt;Masking-key：数据掩码。如果Mask位为0，则该部分可以省略；如果Mask位为1，则为32 bit (4 byte)的掩码。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="_1"&gt;自定义协议&lt;/h2&gt;
&lt;p&gt;当一个WebSocket连接建立起来之后，我们需要自定义协议，来让服务端知道需要干什么。&lt;/p&gt;
&lt;h3 id="_2"&gt;身份验证&lt;/h3&gt;
&lt;p&gt;当客户端与服务端建立连接时, 需将 Basic 格式的身份认证信息通过 &lt;code&gt;Authorization&lt;/code&gt; 头发送到服务器。&lt;/p&gt;
&lt;p&gt;服务端响应应按照HTTP标准，如身份验证失败则返回401，身份验证成功但被限制访问则返回403，成功则返回101。&lt;/p&gt;
&lt;h3 id="_3"&gt;请求代理&lt;/h3&gt;
&lt;p&gt;当有数据流需要走代理时，应做如下的请求。&lt;/p&gt;
&lt;h4 id="_4"&gt;客户端请求&lt;/h4&gt;
&lt;p&gt;客户端发送一个如下的数据包给服务端，用以约定此连接是代理TCP还是UDP。当约定为UDP代理时，DST.ADDR与DST.PORT为任意值均可，服务端无需解析此部分。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;VERSION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;CMD&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TCP&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UDP&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ADDR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;目标的地址&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;PORT&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;目标的端口&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;VERSION: 协议版本号，此处为'01'&lt;/li&gt;
&lt;li&gt;CMD: 指定代理方式&lt;ol&gt;
&lt;li&gt;"TCP"&lt;/li&gt;
&lt;li&gt;"UDP"&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;ADDR: 目标的地址&lt;/li&gt;
&lt;li&gt;PORT: 目标的端口(number类型)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="_5"&gt;服务端响应&lt;/h4&gt;
&lt;p&gt;服务端根据客户端请求进行响应——如果是TCP方法，则尝试连接客户端指定的地址和端口；如果是UDP方法，则视自身情况而定是否同意UDP转发。&lt;br /&gt;
当服务端对此次请求处理完毕后，视情况而定回复一个数据包。&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;VERSION&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;STATUS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SUCCESS&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;STATUS: 为&lt;code&gt;SUCCESS&lt;/code&gt;时，客户端可进行下一步。否则，客户端认为服务器无法做到此次请求的要求，立刻关闭连接。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="_6"&gt;数据传输&lt;/h3&gt;
&lt;p&gt;当进行完以上两步后，可进行正式的数据传输。数据传输阶段使用二进制帧。&lt;/p&gt;
&lt;h4 id="tcp"&gt;TCP&lt;/h4&gt;
&lt;p&gt;对于TCP方法，客户端直接将数据发送到此WebSocket连接即可，而服务端也只需要单纯的转发。&lt;/p&gt;
&lt;h4 id="udp"&gt;UDP&lt;/h4&gt;
&lt;p&gt;对于UDP方法，客户端需要将UDP数据进行封装&lt;/p&gt;
&lt;pre&gt;
+------+----------+----------+----------+
| ATYP | DST.ADDR | DST.PORT |   DATA   |
+------+----------+----------+----------+
|  1   | Variable |    2     | Variable |
+------+----------+----------+----------+
&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ATYP: 指定DST.ADDR的类型&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IPV4: X'01'&lt;/li&gt;
&lt;li&gt;域名: X'03'&lt;/li&gt;
&lt;li&gt;IPV6: X'04'&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DST.ADDR: 该数据包渴望到达的目标地址&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;DST.PORT: 该数据包渴望到达的目标端口&lt;/li&gt;
&lt;li&gt;DATA: 实际要传输的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而服务端进行解包转发后，所接收到的UDP包也解析为同样格式，再发给客户端。&lt;/p&gt;
&lt;h2 id="_7"&gt;题外话&lt;/h2&gt;
&lt;p&gt;本协议实现的开始，只是我想突破学校的网络封锁。后来想着，用了别人的软件那么久，也该自己写一个，以防不测了。于是就有了此文。&lt;br /&gt;
代码实现在&lt;a href="https://github.com/abersheeran/websocks"&gt;websocks&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;但现在已经不住学校寝室了，所以网络环境不再受限，且 UDP 跑在 WebSocket 上的确延迟很高，于是我又编写了&lt;a href="/articles/Fuck-GFW-WebSocket-/"&gt;使用WebSocket进行网络穿透(续)&lt;/a&gt;仅实现 TCP 的转发，至于 UDP 协议，正在使用 UDP 协议重构中。&lt;/p&gt;</description><pubDate>Sun, 27 May 2018 00:00:00 +0000</pubDate><guid>http://aber.sh/articles/Fuck-GFW-WebSocket/</guid></item></channel></rss>