Fix macOS tlsspoof

This commit is contained in:
世界
2026-04-17 12:13:41 +08:00
parent 9dc8d445f2
commit 45f122b7bb
5 changed files with 122 additions and 30 deletions
+3
View File
@@ -0,0 +1,3 @@
# tls spoof
idea from https://github.com/therealaleph/sni-spoofing-rust
+10 -2
View File
@@ -84,8 +84,16 @@ func tcpdumpObserver(t *testing.T, iface string, port uint16, needle string, do
}
func dialLocalEchoServer(t *testing.T) (client net.Conn, serverPort uint16) {
return dialLocalEchoServerFamily(t, "tcp4", "127.0.0.1:0")
}
func dialLocalEchoServerIPv6(t *testing.T) (client net.Conn, serverPort uint16) {
return dialLocalEchoServerFamily(t, "tcp6", "[::1]:0")
}
func dialLocalEchoServerFamily(t *testing.T, network, address string) (client net.Conn, serverPort uint16) {
t.Helper()
listener, err := net.Listen("tcp4", "127.0.0.1:0")
listener, err := net.Listen(network, address)
require.NoError(t, err)
accepted := make(chan net.Conn, 1)
@@ -97,7 +105,7 @@ func dialLocalEchoServer(t *testing.T) (client net.Conn, serverPort uint16) {
close(accepted)
}()
addr := listener.Addr().(*net.TCPAddr)
client, err = net.Dial("tcp4", addr.String())
client, err = net.Dial(network, addr.String())
require.NoError(t, err)
server := <-accepted
require.NotNil(t, server)
+47 -2
View File
@@ -48,11 +48,56 @@ func TestIntegrationSpoofer_WrongSequence(t *testing.T) {
require.True(t, captured, "injected fake ClientHello must be observable on loopback")
}
func TestIntegrationSpoofer_IPv6_WrongChecksum(t *testing.T) {
requireRoot(t)
client, serverPort := dialLocalEchoServerIPv6(t)
spoofer, err := NewSpoofer(client, MethodWrongChecksum)
require.NoError(t, err)
defer spoofer.Close()
payload, err := hex.DecodeString(realClientHello)
require.NoError(t, err)
fake, err := rewriteSNI(payload, "letsencrypt.org")
require.NoError(t, err)
captured := tcpdumpObserver(t, loopbackInterface, serverPort, "letsencrypt.org", func() {
require.NoError(t, spoofer.Inject(fake))
}, 3*time.Second)
require.True(t, captured, "injected fake ClientHello must be observable on loopback")
}
func TestIntegrationSpoofer_IPv6_WrongSequence(t *testing.T) {
requireRoot(t)
client, serverPort := dialLocalEchoServerIPv6(t)
spoofer, err := NewSpoofer(client, MethodWrongSequence)
require.NoError(t, err)
defer spoofer.Close()
payload, err := hex.DecodeString(realClientHello)
require.NoError(t, err)
fake, err := rewriteSNI(payload, "letsencrypt.org")
require.NoError(t, err)
captured := tcpdumpObserver(t, loopbackInterface, serverPort, "letsencrypt.org", func() {
require.NoError(t, spoofer.Inject(fake))
}, 3*time.Second)
require.True(t, captured, "injected fake ClientHello must be observable on loopback")
}
// Loopback bypasses TCP checksum validation, so wrong-sequence is used instead.
func TestIntegrationConn_InjectsThenForwardsRealCH(t *testing.T) {
requireRoot(t)
runInjectsThenForwardsRealCH(t, "tcp4", "127.0.0.1:0")
}
listener, err := net.Listen("tcp4", "127.0.0.1:0")
func TestIntegrationConn_IPv6_InjectsThenForwardsRealCH(t *testing.T) {
requireRoot(t)
runInjectsThenForwardsRealCH(t, "tcp6", "[::1]:0")
}
func runInjectsThenForwardsRealCH(t *testing.T, network, address string) {
t.Helper()
listener, err := net.Listen(network, address)
require.NoError(t, err)
serverReceived := make(chan []byte, 1)
@@ -69,7 +114,7 @@ func TestIntegrationConn_InjectsThenForwardsRealCH(t *testing.T) {
addr := listener.Addr().(*net.TCPAddr)
serverPort := uint16(addr.Port)
client, err := net.Dial("tcp4", addr.String())
client, err := net.Dial(network, addr.String())
require.NoError(t, err)
t.Cleanup(func() {
client.Close()
+26 -10
View File
@@ -74,20 +74,36 @@ func encodeTCP(frame []byte, ipHeaderLen int, src, dst netip.AddrPort, seqNum, a
}
func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext uint32, payload []byte) ([]byte, error) {
var sequence uint32
corrupt := false
switch method {
case MethodWrongSequence:
sequence = sendNext - uint32(len(payload))
case MethodWrongChecksum:
sequence = sendNext
corrupt = true
default:
return nil, E.New("tls_spoof: unknown method ", method)
sequence, corrupt, err := resolveSpoofSequence(method, sendNext, payload)
if err != nil {
return nil, err
}
return buildTCPSegment(src, dst, sequence, receiveNext, payload, corrupt), nil
}
// buildSpoofTCPSegment returns a TCP segment without an IP header, for
// platforms where the kernel synthesises the IP header (darwin IPv6).
func buildSpoofTCPSegment(method Method, src, dst netip.AddrPort, sendNext, receiveNext uint32, payload []byte) ([]byte, error) {
sequence, corrupt, err := resolveSpoofSequence(method, sendNext, payload)
if err != nil {
return nil, err
}
segment := make([]byte, tcpHeaderLen+len(payload))
encodeTCP(segment, 0, src, dst, sequence, receiveNext, payload, corrupt)
return segment, nil
}
func resolveSpoofSequence(method Method, sendNext uint32, payload []byte) (uint32, bool, error) {
switch method {
case MethodWrongSequence:
return sendNext - uint32(len(payload)), false, nil
case MethodWrongChecksum:
return sendNext, true, nil
default:
return 0, false, E.New("tls_spoof: unknown method ", method)
}
}
func applyTCPChecksum(tcp header.TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) {
tcpLen := tcpHeaderLen + len(payload)
pseudo := header.PseudoHeaderChecksum(header.TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen))
+36 -16
View File
@@ -59,7 +59,7 @@ func newRawSpoofer(conn net.Conn, method Method) (Spoofer, error) {
if err != nil {
return nil, err
}
fd, sockaddr, err := openDarwinRawSocket(dst)
fd, sockaddr, err := openDarwinRawSocket(src, dst)
if err != nil {
return nil, err
}
@@ -119,31 +119,51 @@ func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) {
return 0, 0, E.New("tls_spoof: connection ", src, "->", dst, " not found in pcblist_n")
}
func openDarwinRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) {
if !dst.Addr().Is4() {
// macOS does not expose IPV6_HDRINCL; raw AF_INET6 injection would
// require either BPF link-layer writes or kernel-side IPv6 header
// synthesis, neither of which is implemented here.
return -1, nil, E.New("tls_spoof: IPv6 not supported on darwin")
func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) {
if dst.Addr().Is4() {
return openIPv4RawSocket(dst)
}
return openIPv4RawSocket(dst)
// macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP
// sockets, so the kernel builds the IPv6 header itself. Bind to the real
// connection's source address so in6_selectsrc returns it, and rely on
// in6p_cksum defaulting to -1 so the user-supplied TCP checksum is
// preserved (including deliberately corrupted ones).
fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP)
if err != nil {
return -1, nil, E.Cause(err, "open AF_INET6 SOCK_RAW")
}
err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()})
if err != nil {
unix.Close(fd)
return -1, nil, E.Cause(err, "bind AF_INET6 SOCK_RAW")
}
sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()}
return fd, sockaddr, nil
}
func (s *darwinSpoofer) Inject(payload []byte) error {
if !s.src.Addr().Is4() {
segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, payload)
if err != nil {
return err
}
err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr)
if err != nil {
return E.Cause(err, "sendto raw socket")
}
return nil
}
frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, payload)
if err != nil {
return err
}
// Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel
// expects ip_len and ip_off in host byte order, not network byte order.
// Apple's rip_output swaps them back before transmission. This does not
// apply to IPv6.
if s.src.Addr().Is4() {
totalLen := binary.BigEndian.Uint16(frame[2:4])
binary.NativeEndian.PutUint16(frame[2:4], totalLen)
fragOff := binary.BigEndian.Uint16(frame[6:8])
binary.NativeEndian.PutUint16(frame[6:8], fragOff)
}
// Apple's rip_output swaps them back before transmission.
totalLen := binary.BigEndian.Uint16(frame[2:4])
binary.NativeEndian.PutUint16(frame[2:4], totalLen)
fragOff := binary.BigEndian.Uint16(frame[6:8])
binary.NativeEndian.PutUint16(frame[6:8], fragOff)
err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr)
if err != nil {
return E.Cause(err, "sendto raw socket")