红队开发:利用Golang突破ja3检测

关于什么是ja3以及ja3如何生成的等基础知识,可以自行谷歌学习,几个关键点:

  1. 1. 可以理解为ja3就是tls的指纹。

  2. 2. 同一平台下同客户端默认情况下,这个东西是固定的。

  3. 3. 默认发起请求时,无法更改,需要一些手段。

一般自己写一些网络请求的功能,一般会用一些库啊之类的,然后你的代码最终编译运行,平台基本也就那些固定的,基本上是符合上面的模式,比如之前有针对Cobaltstrike的JA3指纹检测,所以有时候还是可以搞一搞这方面的对抗检测。

如何查看ja3

1.通过wireshark

2.通过代码验证

package main

import (
    "crypto/tls"
    "fmt"
    "io/ioutil"
    "net/http"
)

func req() {
    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{
                InsecureSkipVerify: true,
            },
        },
    }
    req, _ := http.NewRequest("GET", "https://ja3er.com/json", nil)
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
    }

    defer resp.Body.Close()
    content, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(content))
}
/// 能不能用不知道,by gpt

如何修改

如果你需要自定义 JA3 指纹,可以考虑以下几种方案:

- 使用第三方库 utls
uTLS 是一个对标准库 TLS 的封装,允许你修改 ClientHello 的各个部分。通过 uTLS,你可以模拟其他客户端(例如浏览器)的 ClientHello,从而改变 JA3 指纹。

- 修改标准库或实现自定义 TLS
理论上,你可以 fork 标准库的 crypto/tls 模块并修改 ClientHello 构造逻辑,但这将带来维护上的额外成本,而且很容易与 Go 语言后续版本不兼容。

- 使用代理或中间件
另外一种方法是通过在 TLS 层前加一层代理,将 ClientHello 进行修改后再转发给目标服务器,不过这通常比使用 uTLS 更复杂。

//上述方案byGPT

其实我们如果自己想在代码中用起来的话,直接用三方库就好了:

  • • https://github.com/CUCyber/ja3transport

  • • https://github.com/Danny-Dasilva/CycleTLS

  • • https://github.com/refraction-networking/utls

挑一个自己想用的,如果你想要深度定制,那你就按gpt说的第二条,自己去改。

其实上述库中的方案,还是中转了一下tls通信,在其中修改了自定义的ja3,有兴趣可以看看代码学习一下。

第一个库感觉用起来可能最方便一点(其实里面是封装了别的库):

import(
  tls "github.com/refraction-networking/utls"
)
....
....
    config := tls.Config{
        InsecureSkipVerify: true,
    }
    ja3string := "771,49195-49199-49196-49200-52393-52392-49161-49171-49162-49172-49170-4865-4866-4867,5-10-11-13-65281-23-18-43-51,29-23-24-25,0"
    tr, _ := ja3transport.NewTransportWithConfig(ja3string, &config)
    client := &http.Client{
        Transport: tr,
    }
    req, _ := http.NewRequest("GET", "https://192.168.8.1", nil)
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
    }

    defer resp.Body.Close()
    content, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(content))
    ....
    ....