Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 49 additions & 76 deletions common/sniff/quic.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ import (
)

func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
ja3Buffer, ok := metadata.SniffContext.(*buf.Buffer)
if ok {
metadata.SniffContext = nil
} else {
ja3Buffer = buf.NewSize(32 * 1024)
_ = ja3Buffer.WriteZeroN(5)
}
defer func() {
if ja3Buffer != nil {
ja3Buffer.Release()
}
}()
sniff:
reader := bytes.NewReader(packet)
typeByte, err := reader.ReadByte()
if err != nil {
Expand Down Expand Up @@ -62,7 +75,7 @@ func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, pack
return err
}

_, err = io.CopyN(io.Discard, reader, int64(srcConnIDLen))
_, err = reader.Seek(int64(srcConnIDLen), io.SeekCurrent)
if err != nil {
return err
}
Expand All @@ -72,7 +85,7 @@ func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, pack
return err
}

_, err = io.CopyN(io.Discard, reader, int64(tokenLen))
_, err = reader.Seek(int64(tokenLen), io.SeekCurrent)
if err != nil {
return err
}
Expand All @@ -82,22 +95,11 @@ func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, pack
return err
}

hdrLen := int(reader.Size()) - reader.Len()
hdrLen := len(packet) - reader.Len()
if hdrLen+int(packetLen) > len(packet) {
return os.ErrInvalid
}

_, err = io.CopyN(io.Discard, reader, 4)
if err != nil {
return err
}

pnBytes := make([]byte, aes.BlockSize)
_, err = io.ReadFull(reader, pnBytes)
if err != nil {
return err
}

var salt []byte
switch versionNumber {
case qtls.Version1:
Expand All @@ -122,33 +124,12 @@ func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, pack
return err
}
mask := make([]byte, aes.BlockSize)
block.Encrypt(mask, pnBytes)
newPacket := make([]byte, len(packet))
copy(newPacket, packet)
newPacket[0] ^= mask[0] & 0xf
for i := range newPacket[hdrLen : hdrLen+4] {
newPacket[hdrLen+i] ^= mask[i+1]
}
packetNumberLength := newPacket[0]&0x3 + 1
if hdrLen+int(packetNumberLength) > int(packetLen)+hdrLen {
return os.ErrInvalid
}
var packetNumber uint32
switch packetNumberLength {
case 1:
packetNumber = uint32(newPacket[hdrLen])
case 2:
packetNumber = uint32(binary.BigEndian.Uint16(newPacket[hdrLen:]))
case 3:
packetNumber = uint32(newPacket[hdrLen+2]) | uint32(newPacket[hdrLen+1])<<8 | uint32(newPacket[hdrLen])<<16
case 4:
packetNumber = binary.BigEndian.Uint32(newPacket[hdrLen:])
default:
return E.New("bad packet number length")
block.Encrypt(mask, packet[hdrLen+4:])
packet[0] ^= mask[0] & 0xf
packetNumberLength := int(packet[0]&0x3 + 1)
for i := range packetNumberLength {
packet[hdrLen+i] ^= mask[i+1]
}
extHdrLen := hdrLen + int(packetNumberLength)
copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:])
data := newPacket[extHdrLen : int(packetLen)+hdrLen]

var keyLabel string
var ivLabel string
Expand All @@ -165,13 +146,15 @@ func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, pack
iv := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, ivLabel, 12)
cipher := qtls.AEADAESGCMTLS13(key, iv)
nonce := make([]byte, int32(cipher.NonceSize()))
binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber))
decrypted, err := cipher.Open(newPacket[extHdrLen:extHdrLen], nonce, data, newPacket[:extHdrLen])
copy(nonce[len(nonce)-packetNumberLength:], packet[hdrLen:])

extHdrLen := hdrLen + int(packetNumberLength)
data := packet[extHdrLen : int(packetLen)+hdrLen]
decrypted, err := cipher.Open(packet[extHdrLen:extHdrLen], nonce, data, packet[:extHdrLen])
if err != nil {
return err
}
var frameType byte
var fragments []qCryptoFragment
decryptedReader := bytes.NewReader(decrypted)
const (
frameTypePadding = 0x00
Expand Down Expand Up @@ -245,9 +228,17 @@ func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, pack
if err != nil {
return err
}
index := len(decrypted) - decryptedReader.Len()
fragments = append(fragments, qCryptoFragment{offset, length, decrypted[index : index+int(length)]})
_, err = decryptedReader.Seek(int64(length), io.SeekCurrent)

start := int(5 + offset)
end := start + int(length)
// Ensure ja3DataBuf has enough space
if n := end - ja3Buffer.Len(); n > 0 {
if err := ja3Buffer.WriteZeroN(n); err != nil {
return err
}
}

_, err = decryptedReader.Read(ja3Buffer.Range(start, end))
if err != nil {
return err
}
Expand All @@ -273,39 +264,21 @@ func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, pack
return os.ErrInvalid
}
}
if metadata.SniffContext != nil {
fragments = append(fragments, metadata.SniffContext.([]qCryptoFragment)...)
metadata.SniffContext = nil
}
var frameLen uint64
for _, fragment := range fragments {
frameLen += fragment.length
}
buffer := buf.NewSize(5 + int(frameLen))
defer buffer.Release()
buffer.WriteByte(0x16)
binary.Write(buffer, binary.BigEndian, uint16(0x0303))
binary.Write(buffer, binary.BigEndian, uint16(frameLen))
var index uint64
var length int
find:
for {
for _, fragment := range fragments {
if fragment.offset == index {
buffer.Write(fragment.payload)
index = fragment.offset + fragment.length
length++
continue find
}
}
break
}
head := ja3Buffer.Range(0, 5)
head[0] = 0x16
binary.BigEndian.PutUint16(head[1:3], 0x0303)
frameLen := ja3Buffer.Len() - 5
binary.BigEndian.PutUint16(head[3:5], uint16(frameLen))
metadata.Protocol = C.ProtocolQUIC
fingerprint, err := ja3.Compute(buffer.Bytes())
fingerprint, err := ja3.Compute(ja3Buffer.Bytes())
if err != nil {
metadata.Protocol = C.ProtocolQUIC
packet = packet[hdrLen+int(packetLen):]
if len(packet) > 0 {
goto sniff
}
metadata.SniffContext = ja3Buffer
ja3Buffer = nil
metadata.Client = C.ClientChromium
metadata.SniffContext = fragments
return E.Cause1(ErrNeedMoreData, err)
}
metadata.Domain = fingerprint.ServerName
Expand Down
17 changes: 17 additions & 0 deletions common/sniff/quic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,20 @@ func FuzzSniffQUIC(f *testing.F) {
require.Error(t, err)
})
}

func TestSniffQUICIncompleteServerName(t *testing.T) {
// 2 packets
pkts, err := hex.DecodeString("ca0000000108d799f48baf472fc3004047005411dcdaa24bd4c3cdbef653940a1ea0a671bb8c23222a8502bfcbd0a335af016df330b1190e4b1efd420aa82c4414a7dfd7b1aa63aa7bd0269ec8ed0d24895a467b737fcc514488a8142241b81c4952fb32e9f123f3f8d1be9eecc1340d7fa747c6f4838810209d51ad453fcec084ffaa382f9fed010a0585f8ce73c36dc6c628df940f72fb5b8e3e06f4612128d3b8f87725770d81305489a3a23cd443fcc668fece0ec9a909c82cfdb66819ce031422c3edd49ea72b6216efb940b8c186e2510b0f93d4cf3b6434ed9f5817c5a5a19fa861d24883bd48ba296df81e887fcb9ef18013ab1070df268049f3d35e8ddfffd2b100e4eb687176a02e7975085caa138e9df7cb6f9862356faafba3300f586df09af3b4e3eec15b6d1f14b34fe4eeb8e762ff482f3b7b0595c16d3eb20cf9c892b3f7c6449e51ffa33f3dc877d32e572e10ba4d5a320117110b150bec4a9434103de963aa2de1b22eaa992b59a8583c7a2d4e5743ef98a1b1f5f4aaeb2b49dbe441c331e7cb028e70432ce0890f78d40671f13d5feccc4ab264ae903affaa951897128d598ed8e8542294aa03abb0a2a7ec970ab874387b8777827c4847bb29ff93d0427c1fcc581407465611e4400bfeab39de4c4f65572365156421503706d66138317e514214db27303ad8b5b17eb443e959faad1ccb3d3def5f3843b241dd9a60378cd907a02cc5c6e8527715c7734079cf9143586ed471587690816133fa8f09b8421f2de6ff1f5cecc134ac404e53a367b57fd4f3c85fe7e121e7094fbf93c06d427cec30cd519ee43a6018a8bb8717c05d7660b379101a182bfd3018d485d5b46e81d39445ccd4d8baefdd7590e664f2289f11bbad3d35ea6d70e157aaa7f9e91a692794b85c5b86db33863151468d32792ecfdb2aec52784ded5aa431fa9a7337ddd9e95313a897fe5ca6ef3c7e0cf3d41f7f104506f8695f2ba307715022e5e98472d6ff24125004d14db2c7a4cc71ca39a9874bfb93b64dd54ef451097059bbdfa96aca6c6c01e1f000b8d35bdead1502874e5d80ac00a79593b6d7e2fbee406c2212b4abd7b7e9f0037518de232cd6559443f4e3f0f03c17cef616a74992f65754bb6699b08c0eb2ec59508086e496e070c6239a73f11a9d14a727b10188ca97ebf04851fa475ed1774836a94cfb6543c33888dfc250100e9992b0cca8bd27ae8552541f1b3d81546f15b740e4f07b41af864769fe17290eb7076c0c1f938ce56a45ead6deb6c89164f829543414a1b8cd2361c3d9b22cf85d4ad0d4221dcc87533506e32c1cfdc8240636b669f97f7c6e150ea3753fcb63a7b9eedb5615b257651e090b567a20fdc5c167ad58fa940985572db004db2b14709a6663c1fb79dc045e45cb7228f11eab4c60a48fffc702cd74dcf5aabded8355a5ae1a5e6325a3afff608e53af3aa944437139c438e6200c75bb27c8a8ce7ce9f9ec9723c4423e9e49c807b12484783ed79fc501b544f03e4b29bdf0c74eb055dbac9b78fe87a66efc819f9d2ea637dd478b920b7196232b970ed092050ba0621329aa2b3e26184176137a9c6283e4cc2c03c07e28d2da7b0485da51f131686f4dd6b23735c66316142f48781885c35c9940054a4b9187a20914bedd1c48e94af358994da5c6060dd224b0ba84ddd5e95f395fe941d464e2cccd857a180de756d32407540c3611b649c7a71cbf5a492bf5c8d4e112a743498ce83ea3e94bcaa6c00000000108d799f48baf472fc3004047005411dcdaa24bd4c3cdbef653940a1ea0a671bb8c23222a8502bfcbd0a335af016df330b1190e4b1efd420aa82c4414a7dfd7b1aa63aa7bd0269ec8ed0d24895a467b737fcc514488e7151df796764293639665547941eaaa7d83dfc9d42952583a821611921cba9e0276f9244f197e011b6c2d898255802c81c8792dd68d03cd8982ee6146c8734814e9640e0999312f97e898db60642ceca21700be9e698b5e577062148d1ac840e737f7bc4f18f9ffd13e656fca1517c96d0e0c607b67d7e75795f62131255b44c2178d896192348d5f053060fa21c855236401e853e435fbc71a428c8e126cd8393cb860193fd6304d9576e8a8ce7f6959739afa7d7ffaf86516afb061ade1eef603e8e5c66f002cacbef25546e84b59be707c547837f7b49471a59325ad58a4f189a525a3a354a4be5ffd3db34ce0585ec470f78f0ff6eb807989d5998e2fccc80bd651de654da3581b6c8a858b56c4df47c3041c50d74f0b3a3a5a325882cd356d0c46d6db74559cef934a1655c65a06daff218244f5f4e95b96794e45d371a03afee2c89768fe3d7bc134844f45440620a88d6ddb5d07b7f6c9b2adfbf5743c5c586520504febf14ee4b216f8186ee1bf9802ccebd4dac519bd337a554940a3873cd21ef32f3d4d4fdfd3eaabb172b5314490b3587a9ca2dc12637fa80dcd526859bb83d54f67dedeb173470e052beec9edba4b41444d31253accb3381237179a5381d437fc4d47c0a82f1c9da8c76ad3eee9417bf69acb8d530ac824e65d796b84bee40b14daa9e80c11e466f93233e1768ecc6c0237e71b901f1faa093bbd9e5324b2a160d02f09d4590392279a15c73edc4d1d7501ebad992fc8f1c9dce96c190879225dbbb09d20532f9dcbb845e3484c87cb8c3b06fe892b07bdc60926e3d4b5c9024acde5abed0a72686d546da535a2122cfd6d6488a491dc9612b90f3f6709835e83f59fd612c2259c3ac944fdae87ffd744176a92fbea1efd037565a39d5bc38991cfe6617135b43cd6c6484a3b228e10d3f8184227a7ee63121be20f099c4264461d52aecf4b8ee1a4d0ec41717f9a68e1394544cf3be0a3a171ece35c879b67afdaac49c12731e6dc5ccaa49fa72610901036a7f0752951077cd9f1d77eb6e4f12c16dab08e1a9655fda3ba0e80c34c767f40e61491f706ab9d1b50f48273ca4e37708d075909a3e2c4b173510114d8377b5300994f9a4f794db2781b98c2e9d0056fdbe87b249beb3e7301c86efe6b32242b4c7fabef280a1ca7bdd6abb38624cbebb01b6b75b07d0cd9090b2c296d0e330b9008788dd6f7e1af1dd63b02408f1a67b57be3316152270bf90c36e9f74322c9caba5adab3309e5bd7c44d3c2ac77329e167dafda7395e37313723fb550a6ab96729a9fa8f9e4a3e5957bffa3302617d48fd38196635309928c491ca974a112768de16043fc618001780b49a0bac2ea9fc2607ebd1f25b8ef65a6cab42d16c9184e23af496ac5cfab274d9557006d9c52bb56cd8c019ddf085f260a0582ef465c73f97fedc02271dda53eec20e6aa50503357ed1847f7d976899cb24e5dd42dd87ae48db3c9335b38957fc0f86a6ceee590b1cbee41fc6ace3189886bd86f9b082e66fb263f1a98dd8e564bff9ec412ae79e29fd519ac96e9c5b446b436b78a6ca555c496ca5c5e73c2c1c5837b13f7a953d57738440d4ed60efd4701bbbbcad79e1697b5724695f52dab8bce02e7f")
require.NoError(t, err)
// The first packet ServerName is incomplete
pkt1 := make([]byte, 1250)
copy(pkt1, pkts)
var metadata adapter.InboundContext
err = sniff.QUICClientHello(context.Background(), &metadata, pkt1)
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
err = sniff.QUICClientHello(context.Background(), &metadata, pkts)
require.NoError(t, err)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Domain, "play.google.com")
require.Equal(t, metadata.Client, C.ClientChromium)
}