Fix macOS tlsspoof
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
# tls spoof
|
||||||
|
|
||||||
|
idea from https://github.com/therealaleph/sni-spoofing-rust
|
||||||
@@ -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) {
|
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()
|
t.Helper()
|
||||||
listener, err := net.Listen("tcp4", "127.0.0.1:0")
|
listener, err := net.Listen(network, address)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
accepted := make(chan net.Conn, 1)
|
accepted := make(chan net.Conn, 1)
|
||||||
@@ -97,7 +105,7 @@ func dialLocalEchoServer(t *testing.T) (client net.Conn, serverPort uint16) {
|
|||||||
close(accepted)
|
close(accepted)
|
||||||
}()
|
}()
|
||||||
addr := listener.Addr().(*net.TCPAddr)
|
addr := listener.Addr().(*net.TCPAddr)
|
||||||
client, err = net.Dial("tcp4", addr.String())
|
client, err = net.Dial(network, addr.String())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
server := <-accepted
|
server := <-accepted
|
||||||
require.NotNil(t, server)
|
require.NotNil(t, server)
|
||||||
|
|||||||
@@ -48,11 +48,56 @@ func TestIntegrationSpoofer_WrongSequence(t *testing.T) {
|
|||||||
require.True(t, captured, "injected fake ClientHello must be observable on loopback")
|
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.
|
// Loopback bypasses TCP checksum validation, so wrong-sequence is used instead.
|
||||||
func TestIntegrationConn_InjectsThenForwardsRealCH(t *testing.T) {
|
func TestIntegrationConn_InjectsThenForwardsRealCH(t *testing.T) {
|
||||||
requireRoot(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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
serverReceived := make(chan []byte, 1)
|
serverReceived := make(chan []byte, 1)
|
||||||
@@ -69,7 +114,7 @@ func TestIntegrationConn_InjectsThenForwardsRealCH(t *testing.T) {
|
|||||||
|
|
||||||
addr := listener.Addr().(*net.TCPAddr)
|
addr := listener.Addr().(*net.TCPAddr)
|
||||||
serverPort := uint16(addr.Port)
|
serverPort := uint16(addr.Port)
|
||||||
client, err := net.Dial("tcp4", addr.String())
|
client, err := net.Dial(network, addr.String())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
client.Close()
|
client.Close()
|
||||||
|
|||||||
+26
-10
@@ -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) {
|
func buildSpoofFrame(method Method, src, dst netip.AddrPort, sendNext, receiveNext uint32, payload []byte) ([]byte, error) {
|
||||||
var sequence uint32
|
sequence, corrupt, err := resolveSpoofSequence(method, sendNext, payload)
|
||||||
corrupt := false
|
if err != nil {
|
||||||
switch method {
|
return nil, err
|
||||||
case MethodWrongSequence:
|
|
||||||
sequence = sendNext - uint32(len(payload))
|
|
||||||
case MethodWrongChecksum:
|
|
||||||
sequence = sendNext
|
|
||||||
corrupt = true
|
|
||||||
default:
|
|
||||||
return nil, E.New("tls_spoof: unknown method ", method)
|
|
||||||
}
|
}
|
||||||
return buildTCPSegment(src, dst, sequence, receiveNext, payload, corrupt), nil
|
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) {
|
func applyTCPChecksum(tcp header.TCP, srcAddr, dstAddr netip.Addr, payload []byte, corrupt bool) {
|
||||||
tcpLen := tcpHeaderLen + len(payload)
|
tcpLen := tcpHeaderLen + len(payload)
|
||||||
pseudo := header.PseudoHeaderChecksum(header.TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen))
|
pseudo := header.PseudoHeaderChecksum(header.TCPProtocolNumber, srcAddr.AsSlice(), dstAddr.AsSlice(), uint16(tcpLen))
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func newRawSpoofer(conn net.Conn, method Method) (Spoofer, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fd, sockaddr, err := openDarwinRawSocket(dst)
|
fd, sockaddr, err := openDarwinRawSocket(src, dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
return 0, 0, E.New("tls_spoof: connection ", src, "->", dst, " not found in pcblist_n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func openDarwinRawSocket(dst netip.AddrPort) (int, unix.Sockaddr, error) {
|
func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) {
|
||||||
if !dst.Addr().Is4() {
|
if dst.Addr().Is4() {
|
||||||
// macOS does not expose IPV6_HDRINCL; raw AF_INET6 injection would
|
return openIPv4RawSocket(dst)
|
||||||
// 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")
|
|
||||||
}
|
}
|
||||||
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 {
|
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)
|
frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel
|
// 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.
|
// 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
|
// Apple's rip_output swaps them back before transmission.
|
||||||
// apply to IPv6.
|
totalLen := binary.BigEndian.Uint16(frame[2:4])
|
||||||
if s.src.Addr().Is4() {
|
binary.NativeEndian.PutUint16(frame[2:4], totalLen)
|
||||||
totalLen := binary.BigEndian.Uint16(frame[2:4])
|
fragOff := binary.BigEndian.Uint16(frame[6:8])
|
||||||
binary.NativeEndian.PutUint16(frame[2:4], totalLen)
|
binary.NativeEndian.PutUint16(frame[6:8], fragOff)
|
||||||
fragOff := binary.BigEndian.Uint16(frame[6:8])
|
|
||||||
binary.NativeEndian.PutUint16(frame[6:8], fragOff)
|
|
||||||
}
|
|
||||||
err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr)
|
err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "sendto raw socket")
|
return E.Cause(err, "sendto raw socket")
|
||||||
|
|||||||
Reference in New Issue
Block a user