Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for tls impersonate #126

Merged
merged 7 commits into from
Jun 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ jobs:
- name: Test
run: go test ./...

- name: Build
run: go build example/main.go
- name: Testing Examples
run: |
go run -race example/simple/main.go
go run -race example/impersonate/main.go
49 changes: 49 additions & 0 deletions example/impersonate/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"context"
"crypto/tls"
"log"

"github.com/projectdiscovery/fastdialer/fastdialer"
"github.com/projectdiscovery/fastdialer/fastdialer/ja3/impersonate"
)

func main() {
options := fastdialer.DefaultOptions

// Create new dialer using NewDialer(opts fastdialer.options)
fd, err := fastdialer.NewDialer(fastdialer.DefaultOptions)
if err != nil {
log.Fatal(err)
}

// Configure Cache if required
// memory based (also support Hybrid and Disk Cache)
options.CacheType = fastdialer.Memory
options.CacheMemoryMaxItems = 100

ctx := context.Background()

target := "www.projectdiscovery.io"

conn, err := fd.DialTLSWithConfigImpersonate(ctx, "tcp", target+":443", &tls.Config{InsecureSkipVerify: true}, impersonate.Random, nil)
if err != nil || conn == nil {
log.Fatalf("couldn't connect to target: %s", err)
}
defer conn.Close()
log.Println("connected to the target")

// To look up Host/ Get DNS details use
data, err := fd.GetDNSData(target)
if err != nil || data == nil {
log.Fatalf("couldn't retrieve dns data: %s", err)
}

// To Print All Type of DNS Data use
jsonData, err := data.JSON()
if err != nil {
log.Fatalf("failed to marshal json: %s", err)
}
log.Println(jsonData)
}
21 changes: 8 additions & 13 deletions example/main.go → example/simple/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import (
)

func main() {

// refer fastdialer/options.go for options and customization
options := fastdialer.DefaultOptions

// Create new dialer using NewDialer(opts fastdialer.options)
fd, err := fastdialer.NewDialer(fastdialer.DefaultOptions)
if err != nil {
panic(err)
log.Fatal(err)
}

// Configure Cache if required
Expand All @@ -26,19 +24,17 @@ func main() {

ctx := context.Background()

// To dial and create connection use
// To create connection over TLS or older versions use
// fd.DialTLS() or fd.DialZTLS()
conn, err := fd.Dial(ctx, "tcp", "www.projectdiscovery.io:80")
target := "www.projectdiscovery.io"

conn, err := fd.DialTLS(ctx, "tcp", target+":443")
if err != nil || conn == nil {
log.Fatalf("couldn't connect to target: %s", err)
} else {
fmt.Println("Connected: TCP stream created with www.projectdiscovery.io:80")
}
conn.Close()
defer conn.Close()
log.Println("connected to the target")

// To look up Host/ Get DNS details use
data, err := fd.GetDNSData("www.projectdiscovery.io")
data, err := fd.GetDNSData(target)
if err != nil || data == nil {
log.Fatalf("couldn't retrieve dns data: %s", err)
}
Expand All @@ -48,6 +44,5 @@ func main() {
if err != nil {
log.Fatalf("failed to marshal json: %s", err)
}
fmt.Println(jsonData)

fmt.Print(jsonData)
}
49 changes: 43 additions & 6 deletions fastdialer/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (
"strings"
"time"

"github.com/projectdiscovery/fastdialer/fastdialer/ja3/impersonate"
"github.com/projectdiscovery/hmap/store/hybrid"
"github.com/projectdiscovery/networkpolicy"
retryabledns "github.com/projectdiscovery/retryabledns"
cryptoutil "github.com/projectdiscovery/utils/crypto"
iputil "github.com/projectdiscovery/utils/ip"
ptrutil "github.com/projectdiscovery/utils/ptr"
utls "github.com/refraction-networking/utls"
"github.com/zmap/zcrypto/encoding/asn1"
ztls "github.com/zmap/zcrypto/tls"
"golang.org/x/net/proxy"
Expand Down Expand Up @@ -109,7 +112,7 @@ func NewDialer(options Options) (*Dialer, error) {

// Dial function compatible with net/http
func (d *Dialer) Dial(ctx context.Context, network, address string) (conn net.Conn, err error) {
conn, err = d.dial(ctx, network, address, false, false, nil, nil)
conn, err = d.dial(ctx, network, address, false, false, nil, nil, impersonate.None, nil)
return
}

Expand All @@ -130,7 +133,12 @@ func (d *Dialer) DialZTLS(ctx context.Context, network, address string) (conn ne

// DialTLS with encrypted connection
func (d *Dialer) DialTLSWithConfig(ctx context.Context, network, address string, config *tls.Config) (conn net.Conn, err error) {
conn, err = d.dial(ctx, network, address, true, false, config, nil)
conn, err = d.dial(ctx, network, address, true, false, config, nil, impersonate.None, nil)
return
}

func (d *Dialer) DialTLSWithConfigImpersonate(ctx context.Context, network, address string, config *tls.Config, impersonate impersonate.Strategy, identity *impersonate.Identity) (conn net.Conn, err error) {
conn, err = d.dial(ctx, network, address, true, false, config, nil, impersonate, identity)
return
}

Expand All @@ -141,12 +149,12 @@ func (d *Dialer) DialZTLSWithConfig(ctx context.Context, network, address string
if err != nil {
return nil, err
}
return d.dial(ctx, network, address, true, false, stdTLSConfig, nil)
return d.dial(ctx, network, address, true, false, stdTLSConfig, nil, impersonate.None, nil)
}
return d.dial(ctx, network, address, false, true, nil, config)
return d.dial(ctx, network, address, false, true, nil, config, impersonate.None, nil)
}

func (d *Dialer) dial(ctx context.Context, network, address string, shouldUseTLS, shouldUseZTLS bool, tlsconfig *tls.Config, ztlsconfig *ztls.Config) (conn net.Conn, err error) {
func (d *Dialer) dial(ctx context.Context, network, address string, shouldUseTLS, shouldUseZTLS bool, tlsconfig *tls.Config, ztlsconfig *ztls.Config, impersonateStrategy impersonate.Strategy, impersonateIdentity *impersonate.Identity) (conn net.Conn, err error) {
var hostname, port, fixedIP string

if strings.HasPrefix(address, "[") {
Expand Down Expand Up @@ -225,7 +233,36 @@ func (d *Dialer) dial(ctx context.Context, network, address string, shouldUseTLS
case !iputil.IsIP(hostname):
tlsconfigCopy.ServerName = hostname
}
conn, err = tls.DialWithDialer(d.dialer, network, hostPort, tlsconfigCopy)
if impersonateStrategy == impersonate.None {
conn, err = tls.DialWithDialer(d.dialer, network, hostPort, tlsconfigCopy)
} else {
conn, err := d.dialer.DialContext(ctx, network, hostPort)
if err != nil {
return conn, err
}
// clone existing tls config
uTLSConfig := &utls.Config{
InsecureSkipVerify: tlsconfigCopy.InsecureSkipVerify,
ServerName: tlsconfigCopy.ServerName,
MinVersion: tlsconfigCopy.MinVersion,
MaxVersion: tlsconfigCopy.MaxVersion,
CipherSuites: tlsconfigCopy.CipherSuites,
}
var uTLSConn *utls.UConn
if impersonateStrategy == impersonate.Random {
uTLSConn = utls.UClient(conn, uTLSConfig, utls.HelloRandomized)
} else if impersonateStrategy == impersonate.Custom {
uTLSConn = utls.UClient(conn, uTLSConfig, utls.HelloCustom)
clientHelloSpec := utls.ClientHelloSpec(ptrutil.Safe(impersonateIdentity))
if err := uTLSConn.ApplyPreset(&clientHelloSpec); err != nil {
return nil, err
}
}
if err := uTLSConn.Handshake(); err != nil {
return nil, err
}
return uTLSConn, nil
}
} else if shouldUseZTLS {
ztlsconfigCopy := ztlsconfig.Clone()
switch {
Expand Down
11 changes: 11 additions & 0 deletions fastdialer/ja3/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ja3

import "fmt"

// ErrExtensionNotExist is returned when an extension is not supported by the library
type ErrExtensionNotExist string

// Error is the error value which contains the extension that does not exist
func (e ErrExtensionNotExist) Error() string {
return fmt.Sprintf("Extension does not exist: %s\n", string(e))
}
62 changes: 62 additions & 0 deletions fastdialer/ja3/extmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// ja3 is a package for creating JA3 fingerprints from TLS clients.
// The original extension map and numeric id=>tls extension mapping is from /~https://github.com/CUCyber
package ja3

import (
utls "github.com/refraction-networking/utls"
"golang.org/x/exp/maps"
)

// extMap maps extension values to the TLSExtension object associated with the
// number. Some values are not put in here because they must be applied in a
// special way. For example, "10" is the SupportedCurves extension which is also
// used to calculate the JA3 signature. These JA3-dependent values are applied
// after the instantiation of the map.
var defaultExtensionMap = map[string]utls.TLSExtension{
"0": &utls.SNIExtension{},
"5": &utls.StatusRequestExtension{},
// These are applied later
// "10": &tls.SupportedCurvesExtension{...}
// "11": &tls.SupportedPointsExtension{...}
"13": &utls.SignatureAlgorithmsExtension{
SupportedSignatureAlgorithms: []utls.SignatureScheme{
utls.ECDSAWithP256AndSHA256,
utls.PSSWithSHA256,
utls.PKCS1WithSHA256,
utls.ECDSAWithP384AndSHA384,
utls.PSSWithSHA384,
utls.PKCS1WithSHA384,
utls.PSSWithSHA512,
utls.PKCS1WithSHA512,
utls.PKCS1WithSHA1,
},
},
"16": &utls.ALPNExtension{
AlpnProtocols: []string{"h2", "http/1.1"},
},
"18": &utls.SCTExtension{},
"21": &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle},
"23": &utls.UtlsExtendedMasterSecretExtension{},
"28": &utls.FakeRecordSizeLimitExtension{},
"35": &utls.SessionTicketExtension{},
"43": &utls.SupportedVersionsExtension{Versions: []uint16{
utls.GREASE_PLACEHOLDER,
utls.VersionTLS13,
utls.VersionTLS12,
utls.VersionTLS11,
utls.VersionTLS10}},
"44": &utls.CookieExtension{},
"45": &utls.PSKKeyExchangeModesExtension{
Modes: []uint8{
utls.PskModeDHE,
}},
"51": &utls.KeyShareExtension{KeyShares: []utls.KeyShare{}},
"13172": &utls.NPNExtension{},
"65281": &utls.RenegotiationInfoExtension{
Renegotiation: utls.RenegotiateOnceAsClient,
},
}

func getExtensionMap() map[string]utls.TLSExtension {
return maps.Clone(defaultExtensionMap)
}
3 changes: 3 additions & 0 deletions fastdialer/ja3/impersonate/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// impersonate package contains strategy to impersonate a client and define an alias for the internal
// client tls spefications
package impersonate
20 changes: 20 additions & 0 deletions fastdialer/ja3/impersonate/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package impersonate

import (
utls "github.com/refraction-networking/utls"
)

// Strategy is the type of strategy to use for impersonation
type Strategy uint8

const (
// None is the default strategy which use the default client hello spec
None Strategy = iota
// Random is the strategy which use a random client hello spec
Random
// JA3 or Raw is the strategy which parses a client hello spec from ja3 full string
Custom
)

// Identity contains the structured client hello spec
type Identity utls.ClientHelloSpec
Loading