Files
v2ray_simple/tlsLayer/sniff.go

711 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package tlsLayer
import (
"bytes"
"crypto/tls"
"flag"
"io"
"log"
"net"
"strings"
"github.com/hahahrfool/v2ray_simple/utils"
)
var PDD bool //print tls detect detail
var OnlyTest bool
func init() {
//log.SetOutput(os.Stdout) //主要是日志太多如果都能直接用管道放到文件中就好了默认不是Stdout所以优点尴尬操作麻烦点
flag.BoolVar(&PDD, "pdd", false, "print tls detect detail")
flag.BoolVar(&OnlyTest, "ot", false, "only detect tls, doesn't actually mark tls")
}
// 用于 探测 承载数据是否使用了tls, 它先与 底层tcp连接 进行 数据传输,然后查看传输到内容
// 可以参考 https://www.baeldung.com/linux/tcpdump-capture-ssl-handshake
type SniffConn struct {
net.Conn //这个 Conn本DetectConn 中不会用到只是为了能让CopyConn支持 net.Conn
W *DetectWriter
R *DetectReader
RawConn *net.TCPConn // 这个是为了让外界能够直接拿到底层的连接
}
func (cc *SniffConn) Read(p []byte) (int, error) {
return cc.R.Read(p)
}
func (cc *SniffConn) Write(p []byte) (int, error) {
return cc.W.Write(p)
}
//这个暂时没用到,先留着
func (cc *SniffConn) ReadFrom(r io.Reader) (int64, error) {
if cc.RawConn != nil {
return cc.RawConn.ReadFrom(r)
}
return 0, io.EOF
}
//可选两个参数传入优先使用rw 为nil的话 再使用oldConn作为 DetectConn 的 Read 和Write的 具体调用的主体
// is_secure 表示,是否使用更强的过滤手段(越强越浪费时间)
func NewSniffConn(oldConn net.Conn, rw io.ReadWriter, isclient bool, is_secure bool) *SniffConn {
var validOne io.ReadWriter = rw
if rw == nil {
validOne = oldConn
}
cc := &SniffConn{
Conn: oldConn,
W: &DetectWriter{
Writer: validOne,
},
R: &DetectReader{
Reader: validOne,
},
}
cc.W.isclient = isclient
cc.R.isclient = isclient
cc.W.is_secure = is_secure
cc.R.is_secure = is_secure
cc.W.peer = &cc.R.ComSniff
cc.R.peer = &cc.W.ComSniff
if netConn := oldConn.(*net.TCPConn); netConn != nil {
//log.Println("NewDetectConn: get netConn!") // 如果是客户端的socks5网页浏览的话这里一定能转成 TCPConn, 不信取消注释试试
cc.RawConn = netConn
}
return cc
}
//是 proxy.UserContainer 的子集
type UserHaser interface {
HasUserByBytes(bs []byte) bool
UserBytesLen() int
}
type ComSniff struct {
IsTls bool
DefinitelyNotTLS bool
SpecialCommandBytes []byte //目前规定使用uuid作为special command
UH UserHaser //为了在服务端能确认一串数据确实是有效的uuid需要使用 UserHaser
SniffedHostName string
isclient bool
is_secure bool
packetCount int
handShakePass bool //握手测试通过
handshakeVer uint16
handshakeFailReason int
cantBeTLS13 bool //clienthello如果没有 supported_versions项或者该项没有0304则不可能协商出tls1.3。如果协商出了则是错误的;
peer *ComSniff //握手是需要判断clienthello+serverhello的而它们一个是读一个是写所以要能够让它们相互访问到之前判断好的数据
}
const (
CutType_big byte = iota + 1
CutType_small
CutType_fit
)
func (c *ComSniff) incr() {
c.packetCount++
}
func (c *ComSniff) GetFailReason() int {
return c.handshakeFailReason
}
// 总之,如果读写都用同样的判断代码的话,客户端和服务端应该就能同步进行 相同的TLS判断
func (cd *ComSniff) commonDetect(p []byte, isRead bool) {
/*
我们把tls的细节放在这个注释里便于参考
首先是rfc文件
tls1.3标准 https://datatracker.ietf.org/doc/html/rfc8446
tls1.2标准 https://datatracker.ietf.org/doc/html/rfc5246
tls1.1标准 https://datatracker.ietf.org/doc/html/rfc4346
tls1.0标准: https://datatracker.ietf.org/doc/html/rfc2246
关于历代的演进,还可参考
https://program-think.medium.com/扫盲-https-和-ssl-tls-协议-4-历史版本的演变及-record-协议的细节-7400cefe7671
首先判断握手包,即第一个包
The Client Hello messages contain 01 in the sixth data byte of the TCP packet.
应该是这里定义的: https://datatracker.ietf.org/doc/html/rfc5246#section-7.4
//不过,首先要包在 Plaintext结构里
//https://datatracker.ietf.org/doc/html/rfc5246
struct {
ContentType type; //第一字节
ProtocolVersion version;//第2、3字节
uint16 length;// 第4、5字节
opaque fragment[TLSPlaintext.length];
} TLSPlaintext;
//ContentType 中handshake始终是22也就是说tls连接的hello字节第一个肯定是22
// 从tls1.0 到 1.3 这个情况都是一样的
tls1.0 ~ tls1.3: change_cipher_spec(20), alert(21), handshake(22),application_data(23)
enum {
hello_request(0), client_hello(1), server_hello(2),
certificate(11), server_key_exchange (12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20), (255)
} HandshakeType;
struct {
HandshakeType msg_type; // handshake type第六字节
uint24 length; // bytes in message第789字节
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case server_hello: ServerHello;
case certificate: Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done: ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
} body;
} Handshake;
然后见下面 TLSPlaintext 的定义,前五字节是 头部, 然后第六字节就是这个Handshake的第一字节client就是 client_hello, 就是1
In the SSL handshake message, the 10th and 11th bytes of the data contain the TLS version.
数一下ClientHello结构和 ServerHello结构 正好在第十字节的位置 开始
tls1.2:
struct {
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-2>;
CompressionMethod compression_methods<1..2^8-1>;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ClientHello;
"The presence of extensions can be detected by
determining whether there are bytes following the compression_methods
at the end of the ClientHello. "
就是说tls1.2中,如果读完 compression_methods后还存在字节则后面的字节都是 extensions
但是tls1.3的 clientHello的 ProtocolVersion是 legacy_version依然是0303
https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2
// 而后面在 supported_versions extension 里会包含 0304
“TLS 1.3 ClientHellos are identified as having
a legacy_version of 0x0303 and a supported_versions extension
present with 0x0304 as the highest version indicated therein.”
ProtocolVersion:
TLSv1.0 0x0301
TLSv1.1 0x0302
TLSv1.2 0x0303
TLSv1.3 0x0304
tls1.3:
struct {
ProtocolVersion legacy_version = 0x0303;
Random random; //byte[32]
opaque legacy_session_id<0..32>; //前1字节表示长度
CipherSuite cipher_suites<2..2^16-2>; //前两字节表示长度,因为 2^16=64k,正好== 256*256, 两字节就可以完整表示
opaque legacy_compression_methods<1..2^8-1>; //前1字节表示长度
Extension extensions<8..2^16-1>; //前两字节表示长度
} ClientHello;
这么算的话0303后面 至少需要跟 32 + 1 + 4 + 2 + 10 = 49 长度的数据
extensions: The actual "Extension"
format is defined in https://datatracker.ietf.org/doc/html/rfc8446#section-4.2
"In TLS 1.3, the use of certain extensions is mandatory, as functionality has moved into
extensions to preserve ClientHello compatibility with previous
versions of TLS. Servers MUST ignore unrecognized extensions."
"TLS 1.3 ClientHello messages always
contain extensions (minimally "supported_versions", otherwise, they
will be interpreted as TLS 1.2 ClientHello messages)"
这里的 extensions就不是用 present与否来判断了因为tls1.3强制规定必须存在extension
这个 <0..32> 到底是什么发现在rfc的上方定义了
https://datatracker.ietf.org/doc/html/rfc5246#section-4.3
Variable-length vectors are defined by specifying a subrange of legal
lengths, inclusively, using the notation <floor..ceiling>. When
these are encoded, the actual length precedes the vector's contents
in the byte stream. The length will be in the form of a number
consuming as many bytes as required to hold the vector's specified
maximum (ceiling) length. A variable-length vector with an actual
length field of zero is referred to as an empty vector.
就是说,首部第一字节要放一个 字节或多个字节来表示长度。一般小于256的长度显然就是占用一个字节。
数据部分:
tls1.2
https://datatracker.ietf.org/doc/html/rfc5246#section-6.2
struct {
uint8 major;
uint8 minor;
} ProtocolVersion;
enum {
change_cipher_spec(20), alert(21), handshake(22),
application_data(23), (255)
} ContentType;
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[TLSPlaintext.length];
} TLSPlaintext;
struct {
ContentType type; //23
ProtocolVersion version; //3,3
uint16 length; //第45字节
opaque fragment[TLSCompressed.length];
} TLSCompressed;
*/
n := len(p)
defer cd.incr()
p0 := p[0]
if cd.packetCount == 0 {
if n < 11 {
cd.DefinitelyNotTLS = true
cd.handshakeFailReason = 1
return
}
if p0 != 22 {
cd.DefinitelyNotTLS = true
cd.handshakeFailReason = 2
//只是不是握手信息, 有可能还是tls其它信息可能是close alert等情况这里应该再加一些处理否则有时网页会打不开刷新一下才能打开。
return
}
//version: p9, p10 , 3,1 3,2 3,3 3,4
if p[9] != 3 {
cd.DefinitelyNotTLS = true
cd.handshakeFailReason = 4
return
}
if p[10] == 0 || p[10] > 4 {
cd.DefinitelyNotTLS = true
cd.handshakeFailReason = 5
return
}
//第六字节是 Handshake.msg_type, 1是clienthello2是serverhello
var helloValue byte = 1
//我们 DetectConn中考察客户端浏览器传输来的流量 时 使用 Read 考察远程真实服务端发来的流量 时 使用Write;
// 无论 代理程序的客户端还是 代理程序的服务端都是如此
if !isRead {
helloValue = 2
}
if p[5] != helloValue { //第六字节,
cd.DefinitelyNotTLS = true
cd.handshakeFailReason = 3
return
}
if cd.is_secure && cd.isclient {
//VersionTLS10,VersionTLS11,VersionTLS12,VersionTLS13
handshakeVer := uint16(p[10]) | uint16(p[9])<<8
//client hello
if isRead {
if handshakeVer == tls.VersionTLS12 { //0303
pAfter := p[11:]
if len(pAfter) < 49 {
cd.DefinitelyNotTLS = true
cd.handshakeFailReason = 6
return
}
cd.sniff_hello(pAfter, true) //代码很长!
if cd.DefinitelyNotTLS {
return
}
}
cd.handshakeVer = handshakeVer
} else { //server hello
if cd.peer.handshakeVer == tls.VersionTLS12 { //0303
if handshakeVer == tls.VersionTLS12 {
pAfter := p[11:]
if len(pAfter) < 32 {
cd.DefinitelyNotTLS = true
cd.handshakeFailReason = 6
return
}
cd.sniff_hello(pAfter, false) //代码很长!
if cd.DefinitelyNotTLS {
return
}
} else {
//可能吗?
}
}
}
}
cd.handShakePass = true
return
} else if !cd.handShakePass {
cd.DefinitelyNotTLS = true
return
}
if !OnlyTest {
if !isRead {
// 如果前面握手符合 tls特征然后之后的命令却获得到了特殊命令的头部则我们直接认为这是服务端发给我们的特殊指令
// 这个过程依然是 在 客户端的 读取出 wrc的数据然后 调用 wlcdc.Write 时发生
// 就是说同样是Write服务端是在Write判断完后发送特殊指令
// 而客户端是在 Write的判断过程中检索特殊指令
// 说白了,就是在 服务端-> 客户端 这个方向发生的,
if cd.isclient && n >= len(cd.SpecialCommandBytes) {
if bytes.Equal(cd.SpecialCommandBytes, p[:len(cd.SpecialCommandBytes)]) {
if PDD {
log.Println("W 读到特殊命令!")
}
cd.IsTls = true
return
}
}
} else {
// Read也是同理也是发送特殊指令只不过Read的话是客户端向 服务端 发送特殊指令
// 这里是不会被黑客攻击的,因为事件发生在第二个或者更往后的 数据包中而vless的uuid检验则是从第一个就要开始检验。也不会遇到重放攻击因为tls每次加密的秘文都是不一样的。
//这里就是服务端来读取 特殊指令
if !cd.isclient {
ubl := cd.UH.UserBytesLen()
if n >= ubl {
if cd.UH.HasUserByBytes(p[:ubl]) {
bs := utils.GetBytes(ubl)
copy(bs, p[:ubl])
cd.SpecialCommandBytes = bs
if PDD {
log.Println("R 读到特殊命令! 剩余长度", n-ubl)
}
cd.IsTls = true
return
}
}
}
}
}
//下面的判断, 只会在 客户端的 Read和 服务端的 Write中 发生
p1 := p[1]
p2 := p[2]
//客户端Read 时 少数情况 第一字节21首部为 [21 3 3 0 26 0 0 0 0 0], 一般总长度为31
// 不过后来发现这就是xtls里面隔离出的 alert的那种情况
// 其它都是 能被捕捉到的。
// 23表示 数据层第二字节3是固定的然后第3字节 从1到4表示tls版本不过tls1.3 和 1.2这里都是 23 3 3
//见 https://datatracker.ietf.org/doc/html/rfc8446#section-5.1
// tls1.3 的 ContentType(23) 后面紧接着的是 legacy_record_version而这个必须设成 0x0303
// 也就是说和tls1.2一样而1.1的话是 0302, 1.0 是 0301总之第三字节只会是123
//
// 不过因为我们先过滤的握手包,所以是能够区分 tls1.3/纯1.2 的;只是在这里验证一下
//
if p0 == 23 && p1 == 3 && p2 > 0 && p2 < 4 {
if PDD {
str := "W"
if isRead {
str = "R"
}
log.Println(str, "got TLS!", n)
}
if !OnlyTest {
cd.IsTls = true
}
return
}
// 打印没过滤到的数据,一般而言,大多是非首包的 握手数据以22开头
if PDD || OnlyTest {
min := 10
if n < 10 {
min = n
}
str := "Write,"
if isRead {
str = "Read,"
}
log.Println(" ======== ", str, n, p[:min])
}
}
func commonFilterStep(err error, cd *ComSniff, p []byte, isRead bool) {
if !OnlyTest && (cd.DefinitelyNotTLS || cd.IsTls) { //确定了是TLS 或者肯定不是 tls了的话就直接return掉
return
}
if err != nil {
eStr := err.Error()
if strings.Contains(eStr, "use of closed") || strings.Contains(eStr, "reset by peer") || strings.Contains(eStr, "EOF") {
return
}
}
if !OnlyTest && cd.packetCount > 8 {
//都8个包了还没断定tls直接推定不是
cd.handshakeFailReason = -1
cd.DefinitelyNotTLS = true
return
}
if len(p) > 3 {
cd.commonDetect(p, isRead)
}
}
// DetectReader 对每个Read的数据进行分析判断是否是tls流量
type DetectReader struct {
io.Reader
ComSniff
}
// 总之,我们在客户端的 Read 操作,就是 我们试图使用 Read 读取客户的请求,然后试图发往 外界
// 所以在socks5后面 使用的这个 Read是读取客户端发送的请求比如 clienthello之类
// 服务端的 Read 操作,也是读 clienthello因为我们总是判断客户传来的数据
func (dr *DetectReader) Read(p []byte) (n int, err error) {
n, err = dr.Reader.Read(p)
commonFilterStep(err, &dr.ComSniff, p[:n], true)
if PDD {
if dr.DefinitelyNotTLS {
log.Println("R DefinitelyNotTLS, reasonNum: ", dr.handshakeFailReason)
}
}
/*
if dr.IsTls {
if dr.isclient {
//判断TLS成功后会向服务端发送特殊指令
//特殊指令是直接在main.go里发送的, 即调用Read者 负责发送特殊指令
// 此时特殊指令的发送不受Reader控制要在main函数中 Write 到 wrc中
return
} else {
// 服务端,能进入这里,就代表服务端接收到了特殊指令
//那么此时的p的前几字节都是特殊指令是没用的要跳过后面是可能会接东西的也可能不接因为tcp是粘包的
//后面如果接了任何东西,那么接的东西都是需要 直连发送的
// 但是不能把特殊指令去掉然后重新放到p中
// 因为Read是调用 底层的tls的tls会把 特殊指令 以及跟着的直连数据一起解密;
// 我们需要从TeeConn的Recorder中读取真实的数据, 也是在main.go里操作
return
}
}*/
return
}
// DetectReader 对每个Read的数据进行分析判断是否是tls流量
type DetectWriter struct {
io.Writer
ComSniff
}
// 直接写入,而不进行探测
func (dw *DetectWriter) SimpleWrite(p []byte) (n int, err error) {
n, err = dw.Writer.Write(p)
return
}
//用于通知 “我们要开始tls数据部分啦” 的 “特殊指令”该指令会被tls加密发送因此不用担心暴露
//var SpecialCommand = []byte{1, 2, 3, 4} //后来决定直接使用uuid作为特殊指令。也是加密传输的所以安全性一样。和普通vless+tls一样最怕的是中间人攻击。只要自己证书申请好就行。
//发现,数据基本就是 23 3 3 22 3 322 3 1 20 3 3
// 一个首包不为23 3 3 的包往往会出现在 1184长度的包的后面而且一般 1184长度的包 的开头是 22 3 3 0 122且总是在Write里面发生
// 所以可以直接推测这个就是握手包; 实测 22 3 3 0 122 开头的,无一例外都是 1184长度且后面接多个 开头任意的 Write
// 也有一些特殊情况,比如 22 3 1 首部的包,往往是 517 长度,后面也会紧跟着一些首部不为 22/23 的 Write
//
// 23 3 3 也是有可能 发生后面不为22/23的write长度 不等
//
// 总之,我们在客户端的 Write 操作,就是 外界试图使用我们的 Write 写入数据
// 所以在socks5后面 使用的这个 Write 应该是把 服务端的响应 写回 socks5比如 serverhello 之类
// 服务端的 Write 操作,也是读 serverhello
//
// 根据之前讨论23 3 3 就是 数据部分,TLSCiphertext
// https://halfrost.com/https_record_layer/
// 总之我们依然判断 23 3 3 好了没那么多技巧先判断是否存在握手包握手完成后遇到23 3 3 后,直接就
// 进入direct模式;
func (dw *DetectWriter) Write(p []byte) (n int, err error) {
//write和Read不一样Write是p现在就具有所有的已知信息所以我们先过滤然后再发送到远端
if dw.IsTls {
n, err = dw.Writer.Write(p)
return
}
commonFilterStep(nil, &dw.ComSniff, p, false)
// 经过判断之后,确认是 tls了则我们缓存这个记录 然后通过tls发送特殊指令
if dw.IsTls {
if dw.isclient {
// 客户端 DetectConn的Write被调用的话那就是从 服务端的 tls连接中 提取出了新数据准备通过socks5发往浏览器
//
//客户端判断出IsTLS那就是收到了特殊指令p的头部就是特殊指令p后面可能还跟 数据
// 然而,一旦后面跟了数据,就完蛋了,这说明特殊指令 和 直连数据连在一起 整个被tls处理 了
//所以关键点是在客户端的tls的前面 也要进行过滤
// 就是因为 我们需要splice所以原始数据和 tls解密过的数据 我们都要!
//客户端发现 浏览器 发送来的数据是 tls数据后 要使用tls 发送特殊指令,然后,
// 服务端 在收到 特殊指令之前,已经收到了 tls前面的握手数据已经处于 handshakePass的阶段
// 因此,服务端只要在 handshakePass 后面,把 原本的 net.TCPConn 做成一个 TeeReader, 然后给tls真正提供的
// 实际上是这个TeeReader这样tls只要Read则我们的TeeReader也会跟着 输出数据
// 然后就算tls读到了 直连数据 也没关系因为我们的TeeReader也读到了所以根本不必担心丢失数据的问题
// 总之就是客户端的wrc的 tls 和 服务端的 wlc 的tls我们都不直接提供tcp连接而是包一层
// 如果特殊指令后面跟着 直连数据一起被tls 读到了的话
// 那么 原始的数据会包含至少两个 tls record第一个tls record 解密出来 就是我们的特殊指令,而第二个以后的 record 则是我们要直连的数据
// 我们需要从TeeConn的Recorder中读取真实的数据
return
} else {
// 服务端Write被调用那就是 获取到了远程服务器的数据,准备发向 客户端,此时我们发送特殊命令
// 查看golang源码可以发现握手过程之后tls的发送是没有缓存的只要对 tls.Conn 调用一次Write就会调用一次或多次net.Conn.Write;
// 所以我们的 特殊指令 是完全包含在一个完整的 tls record 被发出的。
// 此处也是不需要保存 要直连发送的数据p 的在main.go里去直接操作就行
// 我们在这里发送特殊指令即可。
n, err = dw.Writer.Write(dw.SpecialCommandBytes)
if err == nil {
n = len(p)
}
return
}
}
n, err = dw.Writer.Write(p)
if PDD {
//log.Println("DetectWriter,W, 原本", len(p), "实际写入了", n, dw.Writer)
}
return
}
func GetTlsRecordNextIndex(p []byte) int {
if len(p) < 5 {
return -1
}
supposedLen := int(p[3])<<8 + int(p[4])
return 5 + supposedLen
}
func GetLastTlsRecordTailIndex(p []byte) (last_cursor int, count int) {
//log.Println("总长", len(p))
cursor := 0
for {
if cursor > len(p) {
return
}
index := GetTlsRecordNextIndex(p[cursor:])
//log.Println("此记录index", index, last_cursor, cursor)
count++
if last_cursor > len(p) {
return
} else if index < 0 {
return
}
last_cursor = cursor
cursor += index
}
}