|
| 1 | +package tlsspoof |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/binary" |
| 5 | + |
| 6 | + tf "github.com/sagernet/sing-box/common/tlsfragment" |
| 7 | + E "github.com/sagernet/sing/common/exceptions" |
| 8 | +) |
| 9 | + |
| 10 | +const ( |
| 11 | + recordLengthOffset = 3 |
| 12 | + handshakeLengthOffset = 6 |
| 13 | +) |
| 14 | + |
| 15 | +// server_name extension layout (RFC 6066 §3). Offsets are relative to the |
| 16 | +// SNI host name (index returned by the parser): |
| 17 | +// |
| 18 | +// ... uint16 extension_type = 0x0000 (host_name - 9) |
| 19 | +// ... uint16 extension_data_length (host_name - 7) |
| 20 | +// ... uint16 server_name_list_length (host_name - 5) |
| 21 | +// ... uint8 name_type = host_name (host_name - 3) |
| 22 | +// ... uint16 host_name_length (host_name - 2) |
| 23 | +// sni host_name (host_name) |
| 24 | +const ( |
| 25 | + extensionDataLengthOffsetFromSNI = -7 |
| 26 | + listLengthOffsetFromSNI = -5 |
| 27 | + hostNameLengthOffsetFromSNI = -2 |
| 28 | +) |
| 29 | + |
| 30 | +func rewriteSNI(record []byte, fakeSNI string) ([]byte, error) { |
| 31 | + if len(fakeSNI) > 0xFFFF { |
| 32 | + return nil, E.New("fake SNI too long: ", len(fakeSNI), " bytes") |
| 33 | + } |
| 34 | + serverName := tf.IndexTLSServerName(record) |
| 35 | + if serverName == nil { |
| 36 | + return nil, E.New("not a ClientHello with SNI") |
| 37 | + } |
| 38 | + |
| 39 | + delta := len(fakeSNI) - serverName.Length |
| 40 | + out := make([]byte, len(record)+delta) |
| 41 | + copy(out, record[:serverName.Index]) |
| 42 | + copy(out[serverName.Index:], fakeSNI) |
| 43 | + copy(out[serverName.Index+len(fakeSNI):], record[serverName.Index+serverName.Length:]) |
| 44 | + |
| 45 | + err := patchUint16(out, recordLengthOffset, delta) |
| 46 | + if err != nil { |
| 47 | + return nil, E.Cause(err, "patch record length") |
| 48 | + } |
| 49 | + err = patchUint24(out, handshakeLengthOffset, delta) |
| 50 | + if err != nil { |
| 51 | + return nil, E.Cause(err, "patch handshake length") |
| 52 | + } |
| 53 | + for _, off := range []int{ |
| 54 | + serverName.ExtensionsListLengthIndex, |
| 55 | + serverName.Index + extensionDataLengthOffsetFromSNI, |
| 56 | + serverName.Index + listLengthOffsetFromSNI, |
| 57 | + serverName.Index + hostNameLengthOffsetFromSNI, |
| 58 | + } { |
| 59 | + err = patchUint16(out, off, delta) |
| 60 | + if err != nil { |
| 61 | + return nil, E.Cause(err, "patch length at offset ", off) |
| 62 | + } |
| 63 | + } |
| 64 | + return out, nil |
| 65 | +} |
| 66 | + |
| 67 | +func patchUint16(data []byte, offset, delta int) error { |
| 68 | + patched := int(binary.BigEndian.Uint16(data[offset:])) + delta |
| 69 | + if patched < 0 || patched > 0xFFFF { |
| 70 | + return E.New("uint16 out of range: ", patched) |
| 71 | + } |
| 72 | + binary.BigEndian.PutUint16(data[offset:], uint16(patched)) |
| 73 | + return nil |
| 74 | +} |
| 75 | + |
| 76 | +func patchUint24(data []byte, offset, delta int) error { |
| 77 | + original := int(data[offset])<<16 | int(data[offset+1])<<8 | int(data[offset+2]) |
| 78 | + patched := original + delta |
| 79 | + if patched < 0 || patched > 0xFFFFFF { |
| 80 | + return E.New("uint24 out of range: ", patched) |
| 81 | + } |
| 82 | + data[offset] = byte(patched >> 16) |
| 83 | + data[offset+1] = byte(patched >> 8) |
| 84 | + data[offset+2] = byte(patched) |
| 85 | + return nil |
| 86 | +} |
0 commit comments