Created
July 30, 2025 19:37
-
-
Save wv-jessejjohnson/73780b5fae05c91bbb8abafaafeaedab to your computer and use it in GitHub Desktop.
TCP Fingerprinting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package fingerprint | |
| import ( | |
| "context" | |
| "fmt" | |
| "net" | |
| "syscall" | |
| "time" | |
| ) | |
| // TCPFingerprinter handles TCP-level fingerprinting | |
| type TCPFingerprinter struct { | |
| profile *TCPProfile | |
| } | |
| // CustomDialer provides TCP fingerprinting capabilities | |
| type CustomDialer struct { | |
| tcpProfile *TCPProfile | |
| baseDialer *net.Dialer | |
| } | |
| func NewTCPFingerprinter(profile *TCPProfile) *TCPFingerprinter { | |
| return &TCPFingerprinter{ | |
| profile: profile, | |
| } | |
| } | |
| func NewCustomDialer(profile *TCPProfile) *CustomDialer { | |
| return &CustomDialer{ | |
| tcpProfile: profile, | |
| baseDialer: &net.Dialer{ | |
| Timeout: 30 * time.Second, | |
| KeepAlive: 30 * time.Second, | |
| }, | |
| } | |
| } | |
| func (cd *CustomDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { | |
| // Use base dialer to establish connection | |
| conn, err := cd.baseDialer.DialContext(ctx, network, address) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Apply TCP fingerprinting options | |
| if tcpConn, ok := conn.(*net.TCPConn); ok { | |
| if err := cd.applyTCPOptions(tcpConn); err != nil { | |
| conn.Close() | |
| return nil, err | |
| } | |
| } | |
| return conn, nil | |
| } | |
| func (cd *CustomDialer) applyTCPOptions(conn *net.TCPConn) error { | |
| // Get raw connection for socket options | |
| rawConn, err := conn.SyscallConn() | |
| if err != nil { | |
| return err | |
| } | |
| var sockErr error | |
| err = rawConn.Control(func(fd uintptr) { | |
| sockErr = cd.setSocketOptions(int(fd)) | |
| }) | |
| if err != nil { | |
| return err | |
| } | |
| return sockErr | |
| } | |
| func (cd *CustomDialer) setSocketOptions(fd int) error { | |
| // Set TCP window size | |
| if cd.tcpProfile.WindowSize > 0 { | |
| if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, cd.tcpProfile.WindowSize); err != nil { | |
| return err | |
| } | |
| if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_SNDBUF, cd.tcpProfile.WindowSize); err != nil { | |
| return err | |
| } | |
| } | |
| // Set MSS (Maximum Segment Size) | |
| if cd.tcpProfile.MSS > 0 { | |
| // Note: MSS setting is platform-specific and may require different approaches | |
| // This is a simplified example | |
| if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_MAXSEG, cd.tcpProfile.MSS); err != nil { | |
| // MSS setting might not be available on all platforms | |
| // Continue without error in production | |
| } | |
| } | |
| // Set TCP_NODELAY (disable Nagle's algorithm) | |
| if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 1); err != nil { | |
| return err | |
| } | |
| // Set SO_KEEPALIVE | |
| if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil { | |
| return err | |
| } | |
| // Platform-specific TCP options would go here | |
| // For example, on Linux: | |
| // - TCP_WINDOW_CLAMP | |
| // - TCP_USER_TIMEOUT | |
| // - TCP_TIMESTAMP | |
| // - TCP_SACK | |
| return nil | |
| } | |
| // Platform-specific implementations would be in separate files: | |
| // tcp_linux.go, tcp_windows.go, tcp_darwin.go | |
| // LinuxTCPOptions applies Linux-specific TCP options | |
| func (cd *CustomDialer) applyLinuxTCPOptions(fd int) error { | |
| // TCP Window Scaling | |
| if cd.tcpProfile.WindowScaling { | |
| // This would use Linux-specific socket options | |
| // syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_WINDOW_CLAMP, value) | |
| } | |
| // TCP Timestamps | |
| if cd.tcpProfile.Timestamps { | |
| // syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_TIMESTAMP, 1) | |
| } | |
| // TCP Selective Acknowledgment | |
| if cd.tcpProfile.SelectiveAck { | |
| // syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_SACK_ENABLE, 1) | |
| } | |
| return nil | |
| } | |
| // WindowsTCPOptions applies Windows-specific TCP options | |
| func (cd *CustomDialer) applyWindowsTCPOptions(fd int) error { | |
| // Windows has different socket options and registry settings | |
| // that would need to be configured differently | |
| return nil | |
| } | |
| // DarwinTCPOptions applies macOS-specific TCP options | |
| func (cd *CustomDialer) applyDarwinTCPOptions(fd int) error { | |
| // macOS/Darwin specific TCP stack options | |
| return nil | |
| } | |
| // TCPStackDetection provides methods to detect and mimic different TCP stacks | |
| type TCPStackDetection struct { | |
| knownStacks map[string]*TCPSignature | |
| } | |
| type TCPSignature struct { | |
| Name string | |
| WindowSize int | |
| MSS int | |
| TTL int | |
| Options []TCPOption | |
| WindowScaling bool | |
| Timestamps bool | |
| SelectiveAck bool | |
| ECN bool | |
| } | |
| type TCPOption struct { | |
| Kind uint8 | |
| Length uint8 | |
| Data []byte | |
| } | |
| func NewTCPStackDetection() *TCPStackDetection { | |
| tsd := &TCPStackDetection{ | |
| knownStacks: make(map[string]*TCPSignature), | |
| } | |
| tsd.loadKnownSignatures() | |
| return tsd | |
| } | |
| func (tsd *TCPStackDetection) loadKnownSignatures() { | |
| // Windows 10/11 signature | |
| tsd.knownStacks["windows10"] = &TCPSignature{ | |
| Name: "Windows 10/11", | |
| WindowSize: 65535, | |
| MSS: 1460, | |
| TTL: 128, | |
| WindowScaling: true, | |
| Timestamps: true, | |
| SelectiveAck: true, | |
| ECN: false, | |
| } | |
| // Linux signature | |
| tsd.knownStacks["linux"] = &TCPSignature{ | |
| Name: "Linux", | |
| WindowSize: 29200, | |
| MSS: 1460, | |
| TTL: 64, | |
| WindowScaling: true, | |
| Timestamps: true, | |
| SelectiveAck: true, | |
| ECN: true, | |
| } | |
| // macOS signature | |
| tsd.knownStacks["macos"] = &TCPSignature{ | |
| Name: "macOS", | |
| WindowSize: 65535, | |
| MSS: 1460, | |
| TTL: 64, | |
| WindowScaling: true, | |
| Timestamps: true, | |
| SelectiveAck: true, | |
| ECN: false, | |
| } | |
| // FreeBSD signature | |
| tsd.knownStacks["freebsd"] = &TCPSignature{ | |
| Name: "FreeBSD", | |
| WindowSize: 65535, | |
| MSS: 1460, | |
| TTL: 64, | |
| WindowScaling: true, | |
| Timestamps: true, | |
| SelectiveAck: true, | |
| ECN: false, | |
| } | |
| } | |
| func (tsd *TCPStackDetection) GetSignature(stackName string) *TCPSignature { | |
| if sig, exists := tsd.knownStacks[stackName]; exists { | |
| return sig | |
| } | |
| return tsd.knownStacks["linux"] // Default | |
| } | |
| func (tsd *TCPStackDetection) CreateProfileFromSignature(sig *TCPSignature) *TCPProfile { | |
| return &TCPProfile{ | |
| Name: sig.Name, | |
| WindowSize: sig.WindowSize, | |
| MSS: sig.MSS, | |
| TTL: sig.TTL, | |
| WindowScaling: sig.WindowScaling, | |
| Timestamps: sig.Timestamps, | |
| SelectiveAck: sig.SelectiveAck, | |
| } | |
| } | |
| // Advanced TCP fingerprinting techniques | |
| type AdvancedTCPFingerprinter struct { | |
| stackDetection *TCPStackDetection | |
| evasionMode bool | |
| } | |
| func NewAdvancedTCPFingerprinter() *AdvancedTCPFingerprinter { | |
| return &AdvancedTCPFingerprinter{ | |
| stackDetection: NewTCPStackDetection(), | |
| evasionMode: false, | |
| } | |
| } | |
| func (atf *AdvancedTCPFingerprinter) SetEvasionMode(enabled bool) { | |
| atf.evasionMode = enabled | |
| } | |
| func (atf *AdvancedTCPFingerprinter) GetRandomProfile() *TCPProfile { | |
| stacks := []string{"windows10", "linux", "macos", "freebsd"} | |
| randomStack := stacks[time.Now().UnixNano()%int64(len(stacks))] | |
| sig := atf.stackDetection.GetSignature(randomStack) | |
| profile := atf.stackDetection.CreateProfileFromSignature(sig) | |
| // Apply evasion techniques if enabled | |
| if atf.evasionMode { | |
| atf.applyEvasionTechniques(profile) | |
| } | |
| return profile | |
| } | |
| func (atf *AdvancedTCPFingerprinter) applyEvasionTechniques(profile *TCPProfile) { | |
| // Randomize window size within realistic ranges | |
| baseWindow := profile.WindowSize | |
| variation := baseWindow / 10 // ±10% variation | |
| profile.WindowSize = baseWindow + int(time.Now().UnixNano()%int64(variation*2)) - variation | |
| // Randomize MSS slightly | |
| if profile.MSS > 0 { | |
| profile.MSS += int(time.Now().UnixNano()%40) - 20 // ±20 bytes | |
| if profile.MSS < 536 { | |
| profile.MSS = 536 // Minimum MSS | |
| } | |
| if profile.MSS > 1460 { | |
| profile.MSS = 1460 // Standard Ethernet MSS | |
| } | |
| } | |
| // Occasionally disable some features for evasion | |
| if time.Now().UnixNano()%10 == 0 { | |
| profile.WindowScaling = false | |
| } | |
| if time.Now().UnixNano()%8 == 0 { | |
| profile.Timestamps = false | |
| } | |
| if time.Now().UnixNano()%12 == 0 { | |
| profile.SelectiveAck = false | |
| } | |
| } | |
| // TCPFingerprintMatcher helps identify and match TCP fingerprints | |
| type TCPFingerprintMatcher struct { | |
| database map[string]*TCPSignature | |
| } | |
| func NewTCPFingerprintMatcher() *TCPFingerprintMatcher { | |
| return &TCPFingerprintMatcher{ | |
| database: make(map[string]*TCPSignature), | |
| } | |
| } | |
| func (tfm *TCPFingerprintMatcher) AnalyzePacket(windowSize, mss, ttl int, options []TCPOption) string { | |
| // Simple fingerprint matching based on common patterns | |
| // Windows detection | |
| if ttl == 128 && windowSize == 65535 { | |
| if mss == 1460 { | |
| return "Windows 10/11" | |
| } | |
| return "Windows (version unknown)" | |
| } | |
| // Linux detection | |
| if ttl == 64 && windowSize > 25000 && windowSize < 35000 { | |
| return "Linux" | |
| } | |
| // macOS detection | |
| if ttl == 64 && windowSize == 65535 && mss == 1460 { | |
| return "macOS" | |
| } | |
| // FreeBSD detection | |
| if ttl == 64 && windowSize == 65535 { | |
| return "FreeBSD" | |
| } | |
| return "Unknown" | |
| } | |
| // Connection pool with TCP fingerprinting | |
| type FingerprintedConnPool struct { | |
| pools map[string]*ConnectionPool | |
| dialer map[string]*CustomDialer | |
| } | |
| type ConnectionPool struct { | |
| connections chan net.Conn | |
| maxConns int | |
| profile *TCPProfile | |
| } | |
| func NewFingerprintedConnPool() *FingerprintedConnPool { | |
| return &FingerprintedConnPool{ | |
| pools: make(map[string]*ConnectionPool), | |
| dialer: make(map[string]*CustomDialer), | |
| } | |
| } | |
| func (fcp *FingerprintedConnPool) GetConnection(profileName string, address string) (net.Conn, error) { | |
| pool, exists := fcp.pools[profileName] | |
| if !exists { | |
| return nil, fmt.Errorf("profile %s not found", profileName) | |
| } | |
| // Try to get existing connection from pool | |
| select { | |
| case conn := <-pool.connections: | |
| // Test if connection is still alive | |
| if fcp.isConnAlive(conn) { | |
| return conn, nil | |
| } | |
| conn.Close() | |
| default: | |
| // No available connections, create new one | |
| } | |
| // Create new connection with fingerprinting | |
| dialer := fcp.dialer[profileName] | |
| if dialer == nil { | |
| return nil, fmt.Errorf("dialer for profile %s not found", profileName) | |
| } | |
| ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | |
| defer cancel() | |
| return dialer.DialContext(ctx, "tcp", address) | |
| } | |
| func (fcp *FingerprintedConnPool) ReturnConnection(profileName string, conn net.Conn) { | |
| pool, exists := fcp.pools[profileName] | |
| if !exists || !fcp.isConnAlive(conn) { | |
| conn.Close() | |
| return | |
| } | |
| select { | |
| case pool.connections <- conn: | |
| // Successfully returned to pool | |
| default: | |
| // Pool is full, close connection | |
| conn.Close() | |
| } | |
| } | |
| func (fcp *FingerprintedConnPool) isConnAlive(conn net.Conn) bool { | |
| // Simple connection liveness check | |
| conn.SetReadDeadline(time.Now().Add(time.Millisecond)) | |
| var buf [1]byte | |
| _, err := conn.Read(buf[:]) | |
| conn.SetReadDeadline(time.Time{}) | |
| // If we get a timeout, connection is likely alive | |
| if netErr, ok := err.(net.Error); ok && netErr.Timeout() { | |
| return true | |
| } | |
| return false | |
| } | |
| func (fcp *FingerprintedConnPool) AddProfile(name string, profile *TCPProfile, maxConns int) { | |
| pool := &ConnectionPool{ | |
| connections: make(chan net.Conn, maxConns), | |
| maxConns: maxConns, | |
| profile: profile, | |
| } | |
| fcp.pools[name] = pool | |
| fcp.dialer[name] = NewCustomDialer(profile) | |
| } | |
| // Network interface and routing manipulation for advanced evasion | |
| type NetworkEvasion struct { | |
| interfaces []NetworkInterface | |
| } | |
| type NetworkInterface struct { | |
| Name string | |
| IP net.IP | |
| Gateway net.IP | |
| MTU int | |
| } | |
| func NewNetworkEvasion() *NetworkEvasion { | |
| ne := &NetworkEvasion{} | |
| ne.discoverInterfaces() | |
| return ne | |
| } | |
| func (ne *NetworkEvasion) discoverInterfaces() { | |
| interfaces, err := net.Interfaces() | |
| if err != nil { | |
| return | |
| } | |
| for _, iface := range interfaces { | |
| if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { | |
| continue | |
| } | |
| addrs, err := iface.Addrs() | |
| if err != nil { | |
| continue | |
| } | |
| for _, addr := range addrs { | |
| if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { | |
| if ipnet.IP.To4() != nil { | |
| netIface := NetworkInterface{ | |
| Name: iface.Name, | |
| IP: ipnet.IP, | |
| MTU: iface.MTU, | |
| } | |
| ne.interfaces = append(ne.interfaces, netIface) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| func (ne *NetworkEvasion) SelectRandomInterface() *NetworkInterface { | |
| if len(ne.interfaces) == 0 { | |
| return nil | |
| } | |
| index := time.Now().UnixNano() % int64(len(ne.interfaces)) | |
| return &ne.interfaces[index] | |
| } | |
| // IP fragmentation and packet manipulation for evasion | |
| type PacketManipulator struct { | |
| fragmentationEnabled bool | |
| maxFragmentSize int | |
| } | |
| func NewPacketManipulator() *PacketManipulator { | |
| return &PacketManipulator{ | |
| fragmentationEnabled: false, | |
| maxFragmentSize: 1400, | |
| } | |
| } | |
| func (pm *PacketManipulator) EnableFragmentation(maxSize int) { | |
| pm.fragmentationEnabled = true | |
| pm.maxFragmentSize = maxSize | |
| } | |
| func (pm *PacketManipulator) DisableFragmentation() { | |
| pm.fragmentationEnabled = false | |
| } | |
| // TCP sequence number randomization | |
| type SequenceRandomizer struct { | |
| baseSequence uint32 | |
| increment uint32 | |
| } | |
| func NewSequenceRandomizer() *SequenceRandomizer { | |
| now := time.Now().UnixNano() | |
| return &SequenceRandomizer{ | |
| baseSequence: uint32(now), | |
| increment: uint32(now % 10000), | |
| } | |
| } | |
| func (sr *SequenceRandomizer) NextSequence() uint32 { | |
| sr.baseSequence += sr.increment | |
| return sr.baseSequence | |
| } | |
| func (sr *SequenceRandomizer) RandomizeIncrement() { | |
| sr.increment = uint32(time.Now().UnixNano() % 10000) | |
| } | |
| // Utility functions for TCP fingerprinting | |
| func CalculateChecksum(data []byte) uint16 { | |
| var sum uint32 | |
| // Process pairs of bytes | |
| for i := 0; i < len(data)-1; i += 2 { | |
| sum += uint32(data[i])<<8 + uint32(data[i+1]) | |
| } | |
| // Handle odd byte | |
| if len(data)%2 == 1 { | |
| sum += uint32(data[len(data)-1]) << 8 | |
| } | |
| // Add carry | |
| for (sum >> 16) > 0 { | |
| sum = (sum & 0xFFFF) + (sum >> 16) | |
| } | |
| return ^uint16(sum) | |
| } | |
| func GenerateRandomTCPOptions() []TCPOption { | |
| options := []TCPOption{} | |
| // MSS option | |
| options = append(options, TCPOption{ | |
| Kind: 2, | |
| Length: 4, | |
| Data: []byte{0x05, 0xB4}, // 1460 bytes | |
| }) | |
| // Window scale option | |
| options = append(options, TCPOption{ | |
| Kind: 3, | |
| Length: 3, | |
| Data: []byte{0x08}, // Scale factor 8 | |
| }) | |
| // SACK permitted option | |
| options = append(options, TCPOption{ | |
| Kind: 4, | |
| Length: 2, | |
| Data: []byte{}, | |
| }) | |
| // Timestamp option | |
| timestamp := uint32(time.Now().Unix()) | |
| options = append(options, TCPOption{ | |
| Kind: 8, | |
| Length: 10, | |
| Data: []byte{ | |
| byte(timestamp >> 24), | |
| byte(timestamp >> 16), | |
| byte(timestamp >> 8), | |
| byte(timestamp), | |
| 0, 0, 0, 0, // Echo timestamp (0 for SYN) | |
| }, | |
| }) | |
| return options | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment