이 글의 개요는 다음과 같습니다. Lao Xu의 아이디어를 따라 점차적으로 자신만의 JA3 지문을 만들어 보세요.
공식적으로 JA3 지문이 무엇인지 이해하기 전에 먼저 HTTPS 핸드셰이크 프로세스를 검토해 보겠습니다. 이는 다음 사항을 이해하는 데 도움이 됩니다.
TLS 핸드셰이크 프로세스를 명확히 하기 위해 2,000줄이 넘는 코드를 코딩했습니다. 이 글에서는 주로 HTTPS 단방향 인증과 양방향 인증 프로세스(TLS1.3)를 분석합니다.
단방향 인증에서는 클라이언트에 인증서가 필요하지 않으며 서버 인증서가 합법적인지 확인하기만 하면 됩니다. Handshake 과정과 주고받는 메시지는 다음과 같다.
양방향 인증에서는 서버와 클라이언트 모두 상대방 인증서의 적법성을 확인해야 합니다. Handshake 과정과 주고받는 메시지는 다음과 같다.
단방향 인증과 양방향 인증의 비교:
단방향 인증과 양방향 인증에서는 총 데이터가 3번만 송수신되며, 한 번에 전송되는 데이터에는 하나 이상의 메시지가 포함됩니다.
clientHelloMsg
및 serverHelloMsg
암호화되지 않음, 이후에 전송되는 모든 메시지는 암호화됩니다clientHelloMsg
和serverHelloMsg
未经过加密,之后发送的消息均做了加密处理
Client和Server会各自计算两次密钥,计算时机分别是读取到对方的HelloMsg
和finishedMsg
之后
双向认证和单向认证相比,服务端多发送了certificateRequestMsgTLS13
消息
双向认证和单向认证相比,客户端多发送了certificateMsgTLS13
和certificateVerifyMsg
HelloMsg
및 finishedMsg
🎜🎜🎜🎜🎜🎜이후 서버는 단방향 인증에 비해 certificateRequestMsgTLS13
message 🎜🎜🎜🎜🎜🎜단방향 인증에 비해 클라이언트는 certificateMsgTLS13
및certificateVerifyMsg두 개의 메시지 🎜단방향 인증이든 양방향 인증이든, 클라이언트의 기본 정보에 대한 서버의 이해는 전적으로 클라이언트가 서버에 적극적으로 전달하는 것에 달려 있으며, 더 중요한 정보는 客户端支持的TLS版本
、客户端支持的加密套件(cipherSuites)
、客户端支持的签名算法和客户端支持的密钥交换协议以及其对应的公钥
。这些信息均在包含clientHelloMsg
中,而这些信息也是生成JA3指纹的关键信息,并且clientHelloMsg
和serverHelloMsg
암호화되지 않음입니다. 암호화되지 않았다는 것은 수정의 어려움이 감소했다는 것을 의미하며, 이는 또한 우리가 자체 JA3 지문을 사용자 정의할 수 있는 가능성을 제공한다는 것을 의미합니다.
"HTTPS 핸드셰이크 프로세스에 대해 더 자세히 알고 싶다면 다음 문서를 읽어보세요.
명확하게 하기 위해 2,000줄 이상의 코드를 코딩합니다.
2,000줄 이상의 코드를 코딩합니다. TLS 핸드셰이크 프로세스(계속)
”
전에 너무 많이 말씀드렸는데, JA3 지문이 정확히 무엇인가요? Open Sourcing JA3 기사에 따르면 Lao Xu는 이를 JA3가 TLS 클라이언트 지문을 온라인으로 식별하는 방법으로 이해했습니다.
该방법 적용于收集clientHelloMsg
数据包中以下字段的十进字节值:TLS 버전
、허용되는 암호
、확장 목록
、타원 곡선
및타원 곡선 형식
-”이것은 md5의 md5를 사용하는 데 사용되는 32글자입니다.clientHelloMsg
数据包中以下字段的十进制字节值:TLS Version
、Accepted Ciphers
、List of Extensions
、Elliptic Curves
和Elliptic Curve Formats
。然后,它将这些值串联起来,使用“,”来分隔各个字段,同时使用“-”来分隔各个字段中的值。最后,计算这些字符串的md5哈希值,即得到易于使用和共享的长度为32字符的指纹。
为了更近一步描述清楚这些数据的来源,老许将John Althouse
文章中的抓包图结合Go源码中的clientHelloMsg
John Althouse文章中的抓包图结合Go源码中的<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px; 여백 왼쪽: 2px;배경 색상: rgba(27, 31, 35, 0.05); 글꼴 계열: " operator mono consolas monaco menlo monospace break-all rgb>clientHelloMsg
结构体做了字段一一映射。细心的同文可能已经发现了,根据前文描述JA3指纹总共有5个数据字段,而上图却只映射了4个.那是因为TLS의 확장자 字段比较多,老许就不一一整理了.试,有兴趣深入研究的同文可以过这个单元测试进行调试分析。https://github.com/Isites/go-coder/blob/master/http2/tls/handsh/msg_test.goJA3指纹用途
“md5虽然不安全,但是JA3选择md5作为哈希的主要原因是为了更好的向后兼容
”
很明显,JA3指纹也有其类似用途。举个简单的例子,攻击者构建了一个可执行文件,那么该文件的JA3指纹很有可能是唯一的。因此,我们能通过JA3指纹识别出一些恶意软件。
在本小节的最后,老许给大家推荐一个网站,该网站挂出了很多恶意JA3指纹列表。
https://sslbl.abuse.ch/ja3-fingerprints/
前文提到clientHelloMsg
和serverHelloMsg
未经过加密,这为定制自己专属的JA3指纹提供了可能,而在github上面有一个库(https://github.com/refraction-networking/utls)可以在一定程度上修改clientHelloMsg
。下面我们将通过这个库构建一个自己专属的JA3指纹。
// 关键import import ( xtls "github.com/refraction-networking/utls" "crypto/tls" ) // 克隆一个Transport tr := http.DefaultTransport.(*http.Transport).Clone() // 自定义DialTLSContext函数,此函数会用于创建tcp连接和tls握手 tr.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) { dialer := net.Dialer{} // 创建tcp连接 con, err := dialer.DialContext(ctx, network, addr) if err != nil { return nil, err } // 根据地址获取host信息 host, _, err := net.SplitHostPort(addr) if err != nil { return nil, err } // 构建tlsconf xtlsConf := &xtls.Config{ ServerName: host, Renegotiation: xtls.RenegotiateNever, } // 构建tls.UConn xtlsConn := xtls.UClient(con, xtlsConf, xtls.HelloCustom) clientHelloSpec := &xtls.ClientHelloSpec{ // hellomsg中的最大最小tls版本 TLSVersMax: tls.VersionTLS12, TLSVersMin: tls.VersionTLS10, // ja3指纹需要的CipherSuites CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, }, CompressionMethods: []byte{ 0, }, // ja3指纹需要的Extensions Extensions: []xtls.TLSExtension{ &xtls.RenegotiationInfoExtension{Renegotiation: xtls.RenegotiateOnceAsClient}, &xtls.SNIExtension{ServerName: host}, &xtls.UtlsExtendedMasterSecretExtension{}, &xtls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []xtls.SignatureScheme{ xtls.ECDSAWithP256AndSHA256, xtls.PSSWithSHA256, xtls.PKCS1WithSHA256, xtls.ECDSAWithP384AndSHA384, xtls.ECDSAWithSHA1, xtls.PSSWithSHA384, xtls.PSSWithSHA384, xtls.PKCS1WithSHA384, xtls.PSSWithSHA512, xtls.PKCS1WithSHA512, xtls.PKCS1WithSHA1}}, &xtls.StatusRequestExtension{}, &xtls.NPNExtension{}, &xtls.SCTExtension{}, &xtls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, // ja3指纹需要的Elliptic Curve Formats &xtls.SupportedPointsExtension{SupportedPoints: []byte{1}}, // uncompressed // ja3指纹需要的Elliptic Curves &xtls.SupportedCurvesExtension{ Curves: []xtls.CurveID{ xtls.X25519, xtls.CurveP256, xtls.CurveP384, xtls.CurveP521, }, }, }, } // 定义hellomsg的加密套件等信息 err = xtlsConn.ApplyPreset(clientHelloSpec) if err != nil { return nil, err } // TLS握手 err = xtlsConn.Handshake() if err != nil { return nil, err } fmt.Println("当前请求使用协议:", xtlsConn.HandshakeState.ServerHello.AlpnProtocol) return xtlsConn, err }
上述代码总结起来分为三步。
创建TCP连接
构建clientHelloMsg
需要的信息
完成TLS握手
有了上述代码后,我们通过请求https://ja3er.com/json
来得到自己的JA3指纹。
c := http.Client{ Transport: tr, } resp, err := c.Get("https://ja3er.com/json") if err != nil { fmt.Println(err) return } bts, err := ioutil.ReadAll(resp.Body) resp.Body.Close() fmt.Println(string(bts), err)
最后得到的JA3指纹如下。
我们已经得到了第一个JA3指纹,这个时候对代码稍加改动以期得到专属
的JA3指纹。例如我们将2333
这个数值加入到CipherSuites
列表中,最后得到结果如下。
最终,JA3指纹又发生了变化,并且可称得上是自己专属的指纹。不用我说,看标题就应该知道问题还没有结束。从前面请求得到JA3指纹的结果图也可以看出来,当前使用的协议为http1.1
,因此老许从某度中找了一个支持http2的链接继续验证。
看过Go发起HTTP2.0请求流程析(前篇)这篇文章的同学应该知道,http2连接在建立时需要发送PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
这么一个字符串。很明显,在自定义了DialTLSContext
函数之后相关流程缺失。此时,我们该如何构建http2的专属指纹呢?
通过DialTLSContext
拨号之后只能得到一个已经完成TLS握手的连接,此时它还不支持http2的数据帧
、多路复用
等特性。所以,我们需要自己构建一个支持http2各种特性的连接。
下面,我们通过golang.org/x/net/http2
来完成自定义TLS握手流程后的http2请求。
// 手动拨号,得到一个已经完成TLS握手后的连接 con, err := tr.DialTLSContext(context.Background(), "tcp", "dss0.bdstatic.com:443") if err != nil { fmt.Println("DialTLSContext", err) return } // 构建一个http2的连接 tr2 := http2.Transport{} // 这一步很关键,不可缺失 h2Con, err := tr2.NewClientConn(con) if err != nil { fmt.Println("NewClientConn", err) return } req, _ := http.NewRequest("GET", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/newzhidao-da1cf444b0.png", nil) // 向一个支持http2的链接发起请求并读取请求状态 resp2, err := h2Con.RoundTrip(req) if err != nil { fmt.Println("RoundTrip", err) return } io.CopyN(io.Discard, resp2.Body, 2<<10) resp2.Body.Close() fmt.Println("响应code: ", resp2.StatusCode)
结果如下。
可以看到,最终在自定义JA3指纹后,http2的请求也能正常读取。至此,在支持http2的请求中构建专属的JA3指纹就完成了(生成JA3指纹的信息在clientHelloMsg
中,完成本部分仅是为了确保从发起请求到读取响应都能够正常进行)。
몇 가지 추가 단어로 http2 요청을 수동으로 완료하려면 NewClientConn
큰 제한이 있습니다. 예를 들어 연결의 수명 주기를 직접 관리해야 하고 자동으로 다시 연결할 수 없는 경우 등이 있습니다. 물론, 이것들은 모두 나중을 위한 것입니다. 이것이 실제로 필요하다면 개발자는 go 소스 코드에서 net 패키지를 포크하고 스스로 유지 관리해야 할 수도 있습니다.
위 내용은 用Go构建你专属的JA3指纹의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!