[摘要]感谢 Oleg Gdalevich 和 CSDN 用户 zyg0(影子)对本文(程序)所做的贡献本文中引用的RFC文档内容归原作者所有转载本文请标明出处本文作者:吴滂本文中提及的程序可在 枕善居 http://www.mndsoft.com/blog/blogview.asp?logID=494...
感谢 Oleg Gdalevich 和 CSDN 用户 zyg0(影子)对本文(程序)所做的贡献
本文中引用的RFC文档内容归原作者所有
转载本文请标明出处
本文作者:吴滂
本文中提及的程序可在 枕善居 http://www.mndsoft.com/blog/blogview.asp?logID=494 下载
关于用vb的winsock穿透代理的讨论归讨论,一直没有什么源代码放出,现在我就放源出来,省的某些人拿这所谓的"技术"去骗钱.
由于缺乏测试环境,本程序只在我自己编写的代理模拟器上测试过,其结果和腾讯QQ,MSN,网易泡泡穿越该模拟器时得出的结果基本一致.因此,代码可能有错误的地方,请各位有条件的用户自行改正,请见谅!
首先,是基础知识,也就是RFC文档.这个是必看内容.我的程序就是基于这些文档写出.下面是各RFC的连接,为了准确,我先提供英文版的连接,在下面的介绍中再把关键部分翻译成中文.另外,要纠正一个错误.国内很多文章说socks5代理的用户名/密码校验标准在 RFC 1928里有说明,其实这是一个完全错误的说法(我很怀疑写那文章的人有没有看过RFC),socks5用户名/密码校验标准其实是在 RFC 1929 里面说明的.
RFC 1928 - socks5 代理标准
RFC 1929 - socks5 代理用户名/密码校验标准
RFC ???? - socks4 代理标准
RFC 2616 - HTTP1.1 标准
我们现在直入正题:先说socks5的TCP穿透(有了这个例子大家自己照这可以写UDP穿透)
首先和代理服务器连接-直接用winsock去connect指定的地址端口(通常是1080)即可.然后进入细节商议阶段.
细节商议--无用户名/密码校验
RFC 1928 中对于细节商议的第一步是这样描述的:
The client connects to the server, and sends a version
identifier/method selection message:
+----+----------+----------+
VER NMETHODS METHODS
+----+----------+----------+
1 1 1 to 255
+----+----------+----------+
The VER field is set to X''05'' for this version of the protocol. The
NMETHODS field contains the number of method identifier octets that
appear in the METHODS field.
The server selects from one of the methods given in METHODS, and
sends a METHOD selection message:
+----+--------+
VER METHOD
+----+--------+
1 1
+----+--------+
If the selected METHOD is X''FF'', none of the methods listed by the
client are acceptable, and the client MUST close the connection.
The values currently defined for METHOD are:
o X''00'' NO AUTHENTICATION REQUIRED ---------无用户密码 00
o X''01'' GSSAPI ---------??? GSSAPI ?
o X''02'' USERNAME/PASSWORD ---------有用户密码 02
o X''03'' to X''7F'' IANA ASSIGNED
o X''80'' to X''FE'' RESERVED FOR PRIVATE METHODS
o X''FF'' NO ACCEPTABLE METHODS ---------失败 255
The client and server then enter a method-specific sub-negotiation.
换言之,就是向服务器发送三个字节的Byte数组,由于是无须用户/密码校验,展开来写是 05 01 00
然后服务器返回两个字节的信息,第一个字节固定,第而个字节是说明,如果是16进制的FF(即十进制255)表示连接失败(o X''FF'' NO ACCEPTABLE METHODS)根据上面的列表,我们连接成功应该第二字节为 00.
然后我们进入第二步,请看以下RFC说明:
Once the method-dependent subnegotiation has completed, the client
sends the request details. If the negotiated method includes
encapsulation for purposes of integrity checking and/or
confidentiality, these requests MUST be encapsulated in the method-
dependent encapsulation.
The SOCKS request is formed as follows:
+----+-----+-------+------+----------+----------+
VER CMD RSV ATYP DST.ADDR DST.PORT
+----+-----+-------+------+----------+----------+
1 1 X''00'' 1 Variable 2
+----+-----+-------+------+----------+----------+
Where:
o VER protocol version: X''05'' ------------- 固定 05
o CMD
o CONNECT X''01'' ------------- TCP方式 01
o BIND X''02''
o UDP ASSOCIATE X''03'' ------------- UDP方式 03
o RSV RESERVED ------------- 固定 00
o ATYP address type of following address
o IP V4 address: X''01'' ------------- IPv4 01
o DOMAINNAME: X''03''
o IP V6 address: X''04''
o DST.ADDR desired destination address
o DST.PORT desired destination port in network octet
order
The SOCKS server will typically evaluate the request based on source
and destination addresses, and return one or more reply messages, as
appropriate for the request type.
发送 05 01 00 01 + 目的地址(4字节) + 目的端口(2字节),目的地址和端口都是16进制码(不是字符串)。
例202.103.190.27 - 7201
则发送的信息为:05 01 00 01 CA 67 BE 1B 1C 21
(CA=202 67=103 BE=190 1B=27 1C21=7201)
关于我是怎么把16进制码换成10进制的,请自己看程序
最后,接受服务器返回数据,看RFC:
+----+-----+-------+------+----------+----------+
VER REP RSV ATYP BND.ADDR BND.PORT
+----+-----+-------+------+----------+----------+
1 1 X''00'' 1 Variable 2
+----+-----+-------+------+----------+----------+
Where:
o VER protocol version: X''05'' ------------ 固定 05
o REP Reply field:
o X''00'' succeeded ------------ 若为 00 成功 其余可以都看成失败
o X''01'' general SOCKS server failure
o X''02'' connection not allowed by ruleset
o X''03'' Network unreachable
o X''04'' Host unreachable
o X''05'' Connection refused
o X''06'' TTL expired
o X''07'' Command not supported
o X''08'' Address type not supported
o X''09'' to X''FF'' unassigned
o RSV RESERVED
o ATYP address type of following address
o IP V4 address: X''01''
o DOMAINNAME: X''03''
o IP V6 address: X''04''
o BND.ADDR server bound address
o BND.PORT server bound port in network octet order
Fields marked RESERVED (RSV) must be set to X''00''.
可见,对于返回信息,只须判断第二字节是否为00.若为 00 连接成功,剩下的操作和直连一样,Winsock可直接用SendData 和 GetData 发送\接受数据.
下面介绍需要验证用户名/密码的socks5穿透
第一步还是发送三个字节,只是内容有变,展开来写为: 05 01 02
服务器返回信息也有所不同,正确的返回为 05 02
成功后发送用户/密码信息,请看RFC 说明:
Once the SOCKS V5 server has started, and the client has selected the
Username/Password Authentication protocol, the Username/Password
subnegotiation begins. This begins with the client producing a
Username/Password request:
+----+------+----------+------+----------+
VER ULEN UNAME PLEN PASSWD
+----+------+----------+------+----------+
1 1 1 to 255 1 1 to 255
+----+------+----------+------+----------+
The VER field contains the current version of the subnegotiation,
which is X''01''. The ULEN field contains the length of the UNAME field
that follows. The UNAME field contains the username as known to the
source operating system. The PLEN field contains the length of the
PASSWD field that follows. The PASSWD field contains the password
association with the given UNAME.
The server verifies the supplied UNAME and PASSWD, and sends the
following response:
+----+--------+
VER STATUS
+----+--------+
1 1
+----+--------+
A STATUS field of X''00'' indicates success. If the server returns a
`failure'' (STATUS value other than X''00'') status, it MUST close the
connection.
即 发送 01 + 用户名长度(一字节) + 转换成16进制码的用户名 + 密码长度(一字节) + 转换成16进制码的密码,关于如何把用户名和密码转换为10进制Byte数组,请自己看程序.
然后服务器返回两个字节的信息,只须判断第二字节,00 为成功,其余为失败.
剩下的步骤和无用户名密码校验是一样的,即
发送 05 01 00 01 + 目的地址(4字节) + 目的端口(2字节),目的地址和端口都是16进制码(不是字符串)。
例202.103.190.27 - 7201
则发送的信息为:05 01 00 01 CA 67 BE 1B 1C 21
(CA=202 67=103 BE=190 1B=27 1C21=7201)
关于我是怎么把16进制码换成10进制的,请自己看程序
最后接受服务器返回信息.对于返回信息,只须判断第二字节是否为00.若为 00 连接成功,剩下的操作和直连一样,Winsock可直接用SendData 和 GetData 发送\接受数据.
socks4的TCP穿透(事实上,socks4只支持TCP穿透)
无用户名/密码验证
请看 RFC 说明
1) CONNECT
The client connects to the SOCKS server and sends a CONNECT request when
it wants to establish a connection to an application server. The client
includes in the request packet the IP address and the port number of the
destination host, and userid, in the following format.
+----+----+----+----+----+----+----+----+----+----+....+----+
VN CD DSTPORT DSTIP USERID NULL
+----+----+----+----+----+----+----+----+----+----+....+----+
1 1 2 4 variable 1
VN is the SOCKS protocol version number and should be 4. CD is the
SOCKS command code and should be 1 for CONNECT request. NULL is a byte
of all zero bits.
我们首先还是连接服务器,然后根据RFC的格式发送数据给服务器.由于是无用户密码验证,我们需要发送9个字节的数据,展开写为 04 01 + 目标端口(2字节) + 目标IP(4字节) + 00,奇怪的是,表中的USERID部分似乎是没有用的,我参照过大量的C++代码,代码中都没有体现该部分.
至于如何转换目标端口和IP为相应的Byte数组,请自己看示例程序.消息发出后,服务器会返回信息,格式如下:
+----+----+----+----+----+----+----+----+
VN CD DSTPORT DSTIP
+----+----+----+----+----+----+----+----+
1 1 2 4
VN is the version of the reply code and should be 0. CD is the result
code with one of the following values:
90: request granted -------------- 成功
91: request rejected or failed -------------- 失败
92: request rejected becasue SOCKS server cannot connect to
identd on the client
93: request rejected because the client program and identd
report different user-ids
The remaining fields are ignored.
根据RFC的说法,代理服务器返回8字节的数据,我们只要判断第二字节是否为90即可,若是90连接成功,否则失败.剩下的操作和直连一样,Winsock可直接用SendData 和 GetData 发送\接受数据.
HTTP1.1 代理的穿透
由于RFC 2616过于冗长,加上HTTP代理穿透的步骤比socks简单,这里就不详细说明了,我只给出连接的步骤和发送数据格式.
第一步仍然是用Winsock去连接代理服务器.第二步为发送请求字符,其格式为:
无用户名/密码校验 格式:
"CONNECT" + 空格 + 目标连接地址 + ":" + 目标连接端口 + 空格 + "HTTP/1.1" + Chr(13) + Chr(10) + "Host:" + 空格 + 目标连接地址 + ":" + 目标连接端口 + Chr(13) + Chr(10) + Chr(13) + Chr(10)
用户名/密码验证格式:
"CONNECT" + 空格 + 目标连接地址 + ":" + 目标连接端口 + 空格 + "HTTP/1.1" + Chr(13) + Chr(10) + "Host:" + 空格 + 目标连接地址 + ":" + 目标连接端口 + Chr(13) + Chr(10) + "Authorization: Basic" + 空格 + 经Base64加密过后的[用户名:密码] + Chr(13) + Chr(10) + Chr(13) + Chr(10) + "Proxy-Authorization: Basic" + 空格 + 经Base64加密过后的[用户名:密码] + Chr(13) + Chr(10) + Chr(13) + Chr(10)
发送请求完毕后,将收到代理的回应,根据RFC说明(注意 Status-Line 和 Status-Code):
6 Response
After receiving and interpreting a request message, a server responds
with an HTTP response message.
Response = Status-Line ; Section 6.1
*(( general-header ; Section 4.5
response-header ; Section 6.2
entity-header ) CRLF) ; Section 7.1
CRLF
[ message-body ] ; Section 7.2
6.1 Status-Line
The first line of a Response message is the Status-Line, consisting
of the protocol version followed by a numeric status code and its
associated textual phrase, with each element separated by SP
characters. No CR or LF is allowed except in the final CRLF sequence.
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
6.1.1 Status Code and Reason Phrase
The Status-Code element is a 3-digit integer result code of the
attempt to understand and satisfy the request. These codes are fully
defined in section 10. The Reason-Phrase is intended to give a short
textual description of the Status-Code. The Status-Code is intended
for use by automata and the Reason-Phrase is intended for the human
user. The client is not required to examine or display the Reason-
Phrase.
The first digit of the Status-Code defines the class of response. The
last two digits do not have any categorization role. There are 5
values for the first digit:
- 1xx: Informational - Request received, continuing process
- 2xx: Success - The action was successfully received,
understood, and accepted
- 3xx: Redirection - Further action must be taken in order to
complete the request
- 4xx: Client Error - The request contains bad syntax or cannot
be fulfilled
- 5xx: Server Error - The server failed to fulfill an apparently
valid request
The individual values of the numeric status codes defined for
HTTP/1.1, and an example set of corresponding Reason-Phrase''s, are
presented below. The reason phrases listed here are only
recommendations -- they MAY be replaced by local equivalents without
affecting the protocol.
Status-Code =
"100" ; Section 10.1.1: Continue
"101" ; Section 10.1.2: Switching Protocols
"200" ; Section 10.2.1: OK
"201" ; Section 10.2.2: Created
"202" ; Section 10.2.3: Accepted
"203" ; Section 10.2.4: Non-Authoritative Information
"204" ; Section 10.2.5: No Content
"205" ; Section 10.2.6: Reset Content
"206" ; Section 10.2.7: Partial Content
"300" ; Section 10.3.1: Multiple Choices
"301" ; Section 10.3.2: Moved Permanently
"302" ; Section 10.3.3: Found
"303" ; Section 10.3.4: See Other
"304" ; Section 10.3.5: Not Modified
"305" ; Section 10.3.6: Use Proxy
"307" ; Section 10.3.8: Temporary Redirect
"400" ; Section 10.4.1: Bad Request
"401" ; Section 10.4.2: Unauthorized
"402" ; Section 10.4.3: Payment Required
"403" ; Section 10.4.4: Forbidden
"404" ; Section 10.4.5: Not Found
"405" ; Section 10.4.6: Method Not Allowed
"406" ; Section 10.4.7: Not Acceptable
"407" ; Section 10.4.8: Proxy Authentication Required
"408" ; Section 10.4.9: Request Time-out
"409" ; Section 10.4.10: Conflict
"410" ; Section 10.4.11: Gone
"411" ; Section 10.4.12: Length Required
"412" ; Section 10.4.13: Precondition Failed
"413" ; Section 10.4.14: Request Entity Too Large
"414" ; Section 10.4.15: Request-URI Too Large
"415" ; Section 10.4.16: Unsupported Media Type
"416" ; Section 10.4.17: Requested range not satisfiable
"417" ; Section 10.4.18: Expectation Failed
"500" ; Section 10.5.1: Internal Server Error
"501" ; Section 10.5.2: Not Implemented
"502" ; Section 10.5.3: Bad Gateway
"503" ; Section 10.5.4: Service Unavailable
"504" ; Section 10.5.5: Gateway Time-out
"505" ; Section 10.5.6: HTTP Version not supported
extension-code
可知,如果连接成功,服务器返回的信息是 "HTTP/" + 代理版本 + "200" + 描述("Connection established")
所以我们只要判断返回的信息是否以"http"开头,是否存在" 200 "字眼即可.
以下是关键函数的源代码:
Public Function ProxyStep(ProxyType As Integer, PStep As Integer)
Dim SendByte() As Byte
If ProxyType = 0 Then ''@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sock4代理
If PStep = 1 Then
ReDim SendByte(0 To 8) As Byte
SendByte(0) = 4 '' 04
SendByte(1) = 1 '' 01
SendByte(2) = Int(DestPort / 256)
SendByte(3) = DestPort Mod 256
SendByte(4) = GetIPByte(1, DestIP)
SendByte(5) = GetIPByte(2, DestIP)
SendByte(6) = GetIPByte(3, DestIP)
SendByte(7) = GetIPByte(4, DestIP)
SendByte(8) = 0 ''最后要以 0 结束
Form1.Winsock1.SendData SendByte()
ConnStep = PStep + 1
Exit Function
End If
If PStep = 2 Then ''代理回复,第二字节为 90 为成功,其余值为失败
If Asc(Mid(RevBuffer, 2, 1)) <> 90 Then
Debug.Print Asc(Mid(RevBuffer, 2, 1))
MsgBox "连接sock4代理失败!", 48, "错误"
Form1.Winsock1.Close
ConnStep = 0
Exit Function
Else
Form1.Label8.Caption = "连接目标服务器成功!"
ConnStep = -1
Form2.Show
Exit Function
End If
End If
End If
''*******************下面的例子有大量重复代码,是为了让大家更清楚地了解sock5穿透过程,大家可以拿回去自己优化 **********************************
If ProxyType = 1 Then ''@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sock5代理
Select Case PStep
Case 1
ReDim SendByte(0 To 2) As Byte ''第一步 无验证发送 05 01 00, 有验证发送 05 02 02
SendByte(0) = 5 '' 05
SendByte(1) = 1 ''01 ''在有用户密码验证时此字节是 1 还是 2 有诸多争论,现以腾讯QQ穿越代理模拟器时发送的数据为准,如有错误,请自己修改!
SendByte(2) = IIf(Form1.Check1.Value = 0, 0, 2) ''00 或 02
Form1.Winsock1.SendData SendByte()
ConnStep = PStep + 1
Exit Function
Case 2 ''代理回复
If Asc(Mid(RevBuffer, 2, 1)) = 255 Then ''FF (255) 为失败
MsgBox "连接代理失败!", 64
Form1.Winsock1.Close
ConnStep = 0
Exit Function
End If
If Asc(Mid(RevBuffer, 2, 1)) = 0 And Asc(Mid(RevBuffer, 1, 1)) = 5 Then ''若代理回复 05 00 为无验证连接成功
Form1.Label8.Caption = "连接成功!无验证"
ReDim SendByte(0 To 9) As Byte ''第二步 无验证 发送连接请求
SendByte(0) = 5
SendByte(1) = 1
SendByte(2) = 0
SendByte(3) = 1
SendByte(4) = GetIPByte(1, DestIP)
SendByte(5) = GetIPByte(2, DestIP)
SendByte(6) = GetIPByte(3, DestIP)
SendByte(7) = GetIPByte(4, DestIP)
SendByte(8) = Int(DestPort / 256) ''把10进制端口分成两个字节
SendByte(9) = DestPort Mod 256 ''把10进制端口分成两个字节
Form1.Winsock1.SendData SendByte()
ConnStep = ConnStep + 1
Exit Function
End If
If Asc(Mid(RevBuffer, 2, 1)) = 2 And Asc(Mid(RevBuffer, 1, 1)) = 5 Then ''第二步 有用户名密码验证 成功为 05 02
Form1.Label8.Caption = "连接成功!有验证"
ReDim SendByte(0 To 2 + Len(UserName) + Len(UserPassword)) As Byte
SendByte(0) = 1
SendByte(1) = Len(UserName)
MemCopy SendByte(2), ByVal UserName, Len(UserName) ''将用户名转换
SendByte(2 + Len(UserName)) = Len(UserPassword)
MemCopy SendByte(3 + Len(UserName)), ByVal UserPassword, Len(UserPassword) ''将密码转换
Form1.Winsock1.SendData SendByte()
ConnStep = ConnStep + 1
Exit Function
End If
Case 3
If Asc(Mid(RevBuffer, 2, 1)) <> 0 And Form1.Check1.Value = 1 Then ''有验证,验证失败 代理回复第二字节为 00 验证成功,其余值为失败
MsgBox "sock5代理校验用户名、密码失败!", 48, "错误"
Form1.Winsock1.Close
ConnStep = 0
Exit Function
End If
If Asc(Mid(RevBuffer, 2, 1)) = 0 And Form1.Check1.Value = 1 Then ''有验证,验证成功,回复值第二字节为 00 ,其余值为失败
Form1.Label8.Caption = "连接成功!有验证!"
ReDim SendByte(0 To 9) As Byte ''发送连接请求
SendByte(0) = 5
SendByte(1) = 1
SendByte(2) = 0
SendByte(3) = 1
SendByte(4) = GetIPByte(1, DestIP)
SendByte(5) = GetIPByte(2, DestIP)
SendByte(6) = GetIPByte(3, DestIP)
SendByte(7) = GetIPByte(4, DestIP)
SendByte(8) = Int(DestPort / 256) ''把10进制端口分成两个字节
SendByte(9) = DestPort Mod 256 ''把10进制端口分成两个字节
Form1.Winsock1.SendData SendByte()
ConnStep = ConnStep + 1
Exit Function
End If
If Asc(Mid(RevBuffer, 2, 1)) = 0 And Form1.Check1.Value = 0 Then
Form1.Label8.Caption = "连接目标服务器成功!" ''无验证的最后一步,代理回复第二字节为 00 成功,其余值为失败
ConnStep = -1
Form2.Show
Exit Function
End If
If Asc(Mid(RevBuffer, 2, 1)) <> 0 And Form1.Check1.Value = 0 Then
MsgBox "连接目标服务器失败!", 48, "错误" ''无验证的最后一步,代理回复第二字节为 00 成功,其余值为失败
ConnStep = 0
Form1.Winsock1.Close
Exit Function
End If
Case 4 ''只有有验证才会用到这一步
If Asc(Mid(RevBuffer, 2, 1)) <> 0 Then
MsgBox "sock5代理连接目标服务器失败!", 48, "错误"
ConnStep = 0
Form1.Winsock1.Close
Exit Function
Else
Form1.Label8.Caption = "连接目标服务器成功!"
ConnStep = -1
Form2.Show
Exit Function
End If
End Select
End If
If ProxyType = 2 Then ''@@@@@@@@@@@@@@@@@@@@@@@@HTTP1.1代理
If PStep = 1 Then ''无用户名密码验证
If Form1.Check1.Value = 0 Then
HTTPHeader = "CONNECT " & Form1.Text5.Text & ":" & Form1.Text6.Text & _
" HTTP/1.1" & Chr(13) & Chr(10) & "Host: " & Form1.Text5.Text & ":" & Form1.Text6.Text & Chr(13) & Chr(10) & Chr(13) & Chr(10)
ConnStep = PStep + 1
Form1.Winsock1.SendData HTTPHeader
Exit Function
End If
If Form1.Check1.Value = 1 Then '' 有用户名密码验证
HTTPHeader = "CONNECT " & Form1.Text5.Text & ":" & Form1.Text6.Text & _
" HTTP/1.1" & Chr(13) & Chr(10) & "Host: " & Form1.Text5.Text & ":" & _
Form1.Text6.Text & Chr(13) & Chr(10) & "Authorization: Basic " & StrtoBase64(Form1.Text3.Text & _
":" & Form1.Text4.Text) & Chr(13) & Chr(10) & Chr(13) & Chr(10) & "Proxy-Authorization: Basic " & _
StrtoBase64(Form1.Text3.Text & ":" & Form1.Text4.Text) & Chr(13) & Chr(10) & Chr(13) & Chr(10)
'' Chr(13) & Chr(10) 能否直接用vbCrLf ? 我不知道
Debug.Print HTTPHeader
ConnStep = PStep + 1
Form1.Winsock1.SendData HTTPHeader
Exit Function
End If
End If
If PStep = 2 Then ''代理服务器回复,格式:HTTP/[代理版本] [状态代码] [状态说明]
If LCase(Left(RevBuffer, 4)) = "http" And Mid(" 200 ", 1) <> 0 Then ''状态代码为 200 为成功
Form1.Label8.Caption = "连接目标服务器成功!"
Form2.Show
ConnStep = -1
Else
MsgBox "HTTP1.1代理连接目标服务器失败!", 48, "错误"
ConnStep = 0
Form1.Winsock1.Close
Exit Function
End If
End If
End If
End Function
……