mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
197 lines
6.2 KiB
Go
197 lines
6.2 KiB
Go
package tlsspoof
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"net"
|
|
"net/netip"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/sagernet/sing-tun/gtcpip/header"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const PlatformSupported = true
|
|
|
|
// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical
|
|
// to the values used by common/process/searcher_darwin_shared.go.
|
|
const (
|
|
darwinXinpgenSize = 24
|
|
darwinXsocketOffset = 104
|
|
darwinXinpcbForeignPort = 16
|
|
darwinXinpcbLocalPort = 18
|
|
darwinXinpcbVFlag = 44
|
|
darwinXinpcbForeignAddr = 48
|
|
darwinXinpcbLocalAddr = 64
|
|
darwinXinpcbIPv4Offset = 12
|
|
|
|
darwinTCPExtraSize = 208
|
|
|
|
darwinXtcpcbSndNxtOffset = 56
|
|
darwinXtcpcbRcvNxtOffset = 80
|
|
)
|
|
|
|
// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel.
|
|
// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is
|
|
// no ABI-stable way to read it, so we key off the kernel version.
|
|
var darwinStructSize = sync.OnceValues(func() (int, error) {
|
|
value, err := syscall.Sysctl("kern.osrelease")
|
|
if err != nil {
|
|
return 0, E.Cause(err, "sysctl kern.osrelease")
|
|
}
|
|
major, _, ok := strings.Cut(value, ".")
|
|
if !ok {
|
|
return 0, E.New("unexpected kern.osrelease format: ", value)
|
|
}
|
|
n, err := strconv.ParseInt(major, 10, 64)
|
|
if err != nil {
|
|
return 0, E.Cause(err, "parse kern.osrelease major version: ", value)
|
|
}
|
|
if n >= 22 {
|
|
return 408, nil
|
|
}
|
|
return 384, nil
|
|
})
|
|
|
|
type darwinSpoofer struct {
|
|
method Method
|
|
src netip.AddrPort
|
|
dst netip.AddrPort
|
|
rawFD int
|
|
rawSockAddr unix.Sockaddr
|
|
sendNext uint32
|
|
receiveNext uint32
|
|
}
|
|
|
|
func newRawSpoofer(conn net.Conn, method Method) (Spoofer, error) {
|
|
_, src, dst, err := tcpEndpoints(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fd, sockaddr, err := openDarwinRawSocket(src, dst)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sendNext, receiveNext, err := readDarwinTCPSequence(src, dst)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return nil, err
|
|
}
|
|
return &darwinSpoofer{
|
|
method: method,
|
|
src: src,
|
|
dst: dst,
|
|
rawFD: fd,
|
|
rawSockAddr: sockaddr,
|
|
sendNext: sendNext,
|
|
receiveNext: receiveNext,
|
|
}, nil
|
|
}
|
|
|
|
// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches
|
|
// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end
|
|
// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n.
|
|
func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) {
|
|
buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n")
|
|
if err != nil {
|
|
return 0, 0, E.Cause(err, "sysctl net.inet.tcp.pcblist_n")
|
|
}
|
|
structSize, err := darwinStructSize()
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
itemSize := structSize + darwinTCPExtraSize
|
|
for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize {
|
|
inpcb := buffer[i : i+darwinXsocketOffset]
|
|
xtcpcb := buffer[i+structSize : i+itemSize]
|
|
localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2])
|
|
remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2])
|
|
if localPort != src.Port() || remotePort != dst.Port() {
|
|
continue
|
|
}
|
|
versionFlag := inpcb[darwinXinpcbVFlag]
|
|
var localAddr, remoteAddr netip.Addr
|
|
switch {
|
|
case versionFlag&0x1 != 0:
|
|
localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4]))
|
|
remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4]))
|
|
case versionFlag&0x2 != 0:
|
|
localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16]))
|
|
remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16]))
|
|
default:
|
|
continue
|
|
}
|
|
if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() {
|
|
continue
|
|
}
|
|
sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4])
|
|
receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4])
|
|
return sendNext, receiveNext, nil
|
|
}
|
|
return 0, 0, E.New("tls_spoof: connection ", src, "->", dst, " not found in pcblist_n")
|
|
}
|
|
|
|
func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) {
|
|
if dst.Addr().Is4() {
|
|
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.
|
|
ip := header.IPv4(frame)
|
|
ip.SetTotalLengthDarwinRaw(ip.TotalLength())
|
|
ip.SetFlagsFragmentOffsetDarwinRaw(ip.Flags(), ip.FragmentOffset())
|
|
err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr)
|
|
if err != nil {
|
|
return E.Cause(err, "sendto raw socket")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *darwinSpoofer) Close() error {
|
|
if s.rawFD < 0 {
|
|
return nil
|
|
}
|
|
err := unix.Close(s.rawFD)
|
|
s.rawFD = -1
|
|
return err
|
|
}
|