diff --git a/.gitignore b/.gitignore index 4becb5ff..9a2b9a38 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,6 @@ src/tools/docs/ src/embed src/b4 -*.bin out/ vendor/ diff --git a/changelog.md b/changelog.md index f04a0ba3..f8b42ba2 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ - ADDED: **Custom payload for UDP fake packets** - new "Fake Packet Payload" picker in the UDP fake settings of each set. Choose a captured `.bin` (uploaded in Settings → Payloads, or auto-captured from live QUIC traffic) to use as the body of fake UDP packets. Empty = zero fill (previous behavior). The Settings → Payloads upload form now has an explicit TLS/QUIC protocol selector. - ADDED: **Auto-generated QUIC Initial payload** - new "(auto: QUIC Initial)" option in the UDP Fake Packet Payload picker. b4 generates a fresh randomized QUIC Initial packet for every fake, with random connection IDs each time, so no upload is needed and the bytes can't be fingerprinted by repetition. Recommended packet size for this mode is 1200 bytes. Works with any Faking Strategy (None / TTL / Checksum). +- ADDED: **Bundled QUIC presets** - two ready-to-try options ("QUIC preset 1" and "QUIC preset 2") in the UDP Fake Packet Payload picker. Pick one if the auto option does not work for your provider; if neither helps, upload your own `.bin`. - ADDED: **MTProto relay setup helper** — new "?" button next to the DC Relay field in Settings → MTProto Proxy. Opens a popup with the current Telegram data center list and ready-to-copy commands for the VPS, so nothing has to be calculated or guessed by hand. - ADDED: **Single-instance enforcement** — b4 now refuses to start if another b4 process is already running on the same host, exiting with a clear message and the existing PID. - ADDED: **Sequence overlap length for fragmentation** - new "Overlap Length" field for Combo and Disorder splitting. b4 prepends the configured number of pattern bytes to one of the real fragments with its TCP sequence number shifted back by the same amount, in addition to (or instead of) the existing fake-packet overlap. diff --git a/src/config/bin/quic1.bin b/src/config/bin/quic1.bin new file mode 100644 index 00000000..80a07cc8 Binary files /dev/null and b/src/config/bin/quic1.bin differ diff --git a/src/config/bin/quic2.bin b/src/config/bin/quic2.bin new file mode 100644 index 00000000..6815b5c1 Binary files /dev/null and b/src/config/bin/quic2.bin differ diff --git a/src/config/methods.go b/src/config/methods.go index 7290024a..11695fe8 100644 --- a/src/config/methods.go +++ b/src/config/methods.go @@ -873,14 +873,22 @@ func (c *Config) LoadCapturePayloads() { } set.UDP.FakePayloadData = nil - if set.UDP.FakePayloadFile != "" && set.UDP.FakePayloadFile != FakePayloadAutoQUIC && capturesDir != "" { - payloadPath := filepath.Join(capturesDir, set.UDP.FakePayloadFile) - data, err := os.ReadFile(payloadPath) - if err != nil { - log.Errorf("Failed to load UDP fake payload %s: %v", set.UDP.FakePayloadFile, err) - } else { - set.UDP.FakePayloadData = data - log.Tracef("Loaded UDP fake payload %s (%d bytes)", set.UDP.FakePayloadFile, len(data)) + switch set.UDP.FakePayloadFile { + case "", FakePayloadAutoQUIC: + case FakePayloadPreset1: + set.UDP.FakePayloadData = FakeQUIC1 + case FakePayloadPreset2: + set.UDP.FakePayloadData = FakeQUIC2 + default: + if capturesDir != "" { + payloadPath := filepath.Join(capturesDir, set.UDP.FakePayloadFile) + data, err := os.ReadFile(payloadPath) + if err != nil { + log.Errorf("Failed to load UDP fake payload %s: %v", set.UDP.FakePayloadFile, err) + } else { + set.UDP.FakePayloadData = data + log.Tracef("Loaded UDP fake payload %s (%d bytes)", set.UDP.FakePayloadFile, len(data)) + } } } } diff --git a/src/config/payloads.go b/src/config/payloads.go index 9a7af9f9..8d0ad48f 100644 --- a/src/config/payloads.go +++ b/src/config/payloads.go @@ -1,5 +1,18 @@ package config +import _ "embed" + // Default fake SNI payload var FakeSNI1 = []byte("\026\003\001\004\316\001\000\004\312\003\003K+\272\314\340\306\374>dw%\f\223\346\225\270\270~\335\027\f\264\341H\267\357\303\216T\322[\371 \245\320\212V6\374\3706\232\0216B\325\273P\b\300>\0332>\362\323\033\322\301\204\022f8\223\214\000\"\023\001\023\003\023\002\300+\300/\314\251\314\250\300,\3000\300\n\300\t\300\023\300\024\000\234\000\235\000/\0005\001\000\004_\000\000\000\023\000\021\000\000\016www.google.com\000\027\000\000\377\001\000\001\000\000\n\000\016\000\f\000\035\000\027\000\030\000\031\001\000\001\001\000\v\000\002\001\000\000\020\000\v\000\t\bhttp/1.1\000\005\000\005\001\000\000\000\000\000\"\000\n\000\b\004\003\005\003\006\003\002\003\0003\000k\000i\000\035\000 \333C\212\234-\t\237#\202\\\231\311\022]\333\341t(\t\276U\373u\234\316J~,^|*Z\000\027\000A\004k\n\255\254\376X\226t\001;n~\033\034.\245\027\024\3762_\352$\374\346^f\fF,\201\275\263\336O\231\001\032\200\357dI\266y\031\323\311vR\232\004\r\366FT\004\335\326\356\256\230B\t\313\000*\000\000\000+\000\005\004\003\004\003\003\000\r\000\030\000\026\004\003\005\003\006\003\b\004\b\005\b\006\004\001\005\001\006\001\002\003\002\001\000-\000\002\001\001\000\034\000\002@\001\376\r\0029\000\000\001\000\003\344\000 \337\306\243\332Y\033\a\252\352\025\365Z\035\223\226\304\255\363\215G\356g\344%}7\217\033n\211^\201\002\017g\267\334\326OD}\336\341ZC\230\226'\225\313\357\211\\\242\273\030k\216\377U\315\206\2410\200\203\332Z\223\005\370\b\304\370f\017\200\023\241\223~?\270{\037b\312\001\270\227\366\356\352\002\314\351\006\237\241q\226\300\314\321o\247{\201\317\230}B\005T\3660\335\320\332r?S\217\tq\036\031\326I|\237]\311 c\f\024r\031\310W\373\257\314q)q\030\237\261\227\217Kd?\257'G\320\020\340\256ND\247\005\341\324\024OP>\370\350\270b\311wAj\t\311\213\365i\203\230x\207\354\245<\274\202\230c\v0Y\263\364\022\303a\200\022\031\314\271rl=\327\336\001\327\264\267\342\353\352=\354[u\224\260\257\034\004\232\023\226}\227\030e\221\"\350\207\027dId\324\305\362N:\035\307`\204\337\201;\221\320\266b\362hrH\345e\206\246%\006\020a4\3430\036\225\215\274\275\360Q&\271\237)\222uK\362\017o\220\226W\357\267#\357\v\023\354\213\2629\331\ad\005/~6k\000[\247\301\270\310qJ\004\303|m5\363\376Y\002\243}6\251x\024\331)GH\335\205rI\032\f\210\a\212\347]\271\030\347.\021\213\365\026\030\340/Ny\r\332\3577\3203\026iX}>\2507\327&XRXU!\017\270I\313\352\350^?\352Uss\017\266pF\222NI\245\307_\305#\361\352\243+-\266\317Q\036s\243\277\355{S&\023>\275\360\215\032V\237XOY\345u>\002\305\252T\354\035\327v{P\352M\233\366\221\270\377\251\261f+rF\201wL2W\266X\252\242X\2536I\337c\205uZ\254Fe\305h\t\371\376\216r\336Y\327h\347*\331\257-ZQ{(\336\226\206\017\037\036\021\341\027z\033\254\235\252\227\224\004?p\243\351\\\263\352\205\327#W\345\255\256\375\267bP\3047\363!*K\003t\212(\306\214P\215\3506j\025\375\213e\254s\000)\001\034\000\367\000\361\002\276W%\232?\326\223\277\211v\017\a\361\347\312N\226\024L\260v\210\271j\324[|\270\344\3773\321-\313b>~\310\253XIR\324)&;\033{g;)\344\255\226\370\347I\\y\020\324\360\211vC\310\226s\267|\273$\341\332\2045qh\245w\2255\214\316\030\255\301\326C\343\304=\245\231h`yd\000#s\002\370\374Z\0336\245\361\226\222\306\032k\2457\016h\314(R;\326T~EHH\352\307\023^\247\363\321`V\340\253Z\233\357\227I\373\337z\177\nv\261\252\371\017\226\223\345\005\315y4\b\236N0\2630\017\215c\305&L\260\346J\237\203Q(\335W\027|>\3553\275j\307?W5\3463kc\350\262C\361 \037w!\371}\214\"I\377|\331@a;\342\3566\312\272Z\327u7\204'\215YBLL\235\236\242\345\215\245T\211a\312\263\342\000! \221\202X$\302\317\203\246\207c{\231\330\264\324\\k\271\272\336\356\002|\261O\207\030+\367P\317\356") var FakeSNI2 = []byte("\026\003\001\006 \001\000\006\034\003\003M\252\266\"\217\252E\306\"fK\204^q\341\270f%mw\366JG\355Mn\253N\024\200M\272 o\335\0036\342\265\321\032d\373\225\224\231\032\274\226\347\205\306\314\317\027k\235\026\006=$\223F?\303\000<\023\002\023\003\023\001\300,\3000\000\237\314\251\314\250\314\252\300+\300/\000\236\300$\300(\000k\300#\300'\000g\300\n\300\024\0009\300\t\300\023\0003\000\235\000\234\000=\000<\0005\000/\001\000\005\227\377\001\000\001\000\000\000\000\035\000\033\000\000\030staticcdn.duckduckgo.com\000\v\000\002\001\000\000\n\000\022\000\020\021\354\000\035\000\027\000\036\000\030\000\031\001\000\001\001\000\020\000\016\000\f\002h2\bhttp/1.1\000\026\000\000\000\027\000\000\0001\000\000\000\r\0006\0004\t\005\t\006\t\004\004\003\005\003\006\003\b\a\b\b\b\032\b\033\b\034\b\t\b\n\b\v\b\004\b\005\b\006\004\001\005\001\006\001\003\003\003\001\003\002\004\002\005\002\006\002\000+\000\005\004\003\004\003\003\000-\000\002\001\001\0003\004\352\004\350\021\354\004\300\311\332+\231\234\233\236\313}\203\353&\330\301z\277i\030m#yM\000V|\253\246\332S\006\322\332\232)l\303k\032CJ\342\314\034\252\213\240\301\035x\263u]\351\252\260\360\a\373\224M\327\307\002\261l=\320\"\214\217\323\205\3541Q.\tmU\371V]Y\222:\022CWT\035\366\227\024\250\330I\357\bL\247R\214\r\202\215)\242\224\037\247l\321\207\205M\320\251\335 \a\350\034P}\247\220)KyZ\333\2032\2023O(1\236Fx!a\250\247\300\256OEP\222p\317\305\261\203\004\232\315Q\034N\a\332\300\006\326a\302\353CocH\276\b\256\230\267\203^\227\212\214xIJ\2333p\006\242\247\\\002\353W^\336\2022\206\324#\272q\023\265\200\023\035J\214\027\363\305}T~\212\334T\333|&\330Kr\030T\021\242\241KE7\003Q\352\250[heM4\000\354\233(\310\002\031\337 r\026t\201\326\225O`'\027\221J\277d\305.\225+\263S\346{B\344\006\235\211\276;5N\372J\220\242TV\355F]\377S\233\367\340J\027f?\216\260\255\221\313L\323\005\217\316C;\020\026y\301\001{\310\033D\023\270\275\371\310?\233q\023\035\221\317\005\247\266\273E\210\217\303To\031\000C\246\305#|\3074\350MTDU\2445\"\220\246\243\252\203i\006T\276\333|\217\255\024=M\310R\373\246\225\vi\272\215\354\222GQV'\260Q\310dx\t\261\2516\3443\353\353cEE\233\202XL\377\347\215%\0332\372\267x\350\0243\351a\3058R\035\276\201\v\204\367?~\221R\230I[:\305\237\321\234v\3431j\340\270\256\006\031\246z\004?VZN*u\217\252\354\006\202L\016\246\030\263\213\"p\345\366Y\223\231T>%5O\345\256yS\263\304R\211\025\372\036\0017@}+Q\361\346\207\355\\\250&\347\000\330\231\0351\023\245,\363vi\207\204\227\225qb\252\271,\233\226\271Vb\314\211\235u\032\250\235\333#\266\247{\304\213\276\ny\021\271\244\211\251rz\004C\242Q\314\247\205\210\r\235\242\252Q\261\211\235W')!\271\030\322\267F\361P\234\367&\237\254\240\236\361\241\356\233r}\003\263B\310\304{\304XvT\000' \274\\J\005\254\246N\224(:\271D\246\354z\271g\313L\242rw\361,\235\216%U\221F\223]*8r\354C\030br\311\311\257)#\313\365\t\ttS\225\365@?\231\261Ei\311C\aE8r\254Y\313\025\241\335\000@\345\004\n\f\027\262\254`\a\354s\302\220A?\250U\257\211\322C\206\000\201\030\242\221\334\272\300Y\245\v{\342-\357\206r\315\223\021/\224\214\332\213j\0257\215\300t\215`uW\323J\016!\3426,\353\250\346A`O\304\bn\310$/\261zig\002\352+\253G\3522\177\030{\314<\221\314D\235\375\001\320\020#x\2256*f\327\005\215\245\277\177Xe^\326\006.\006\f\307\370\273\357\f\a\v!\304\313H\027\336v)\300)J\3241x\t\202?7\005\277r\243{\331\034\243\233\314\"I\226Q\027H\315\3143\263\226\330j\350\030\307\324y\024k\263-\216\324\fuBv { payloadFileHelperKey = "sets.udp.fakePayloadFileHelper"; } + const captureProtocolRank = (proto: string) => (proto === "quic" ? 0 : 1); + const sortedCaptures = [...captures].sort( + (a, b) => captureProtocolRank(a.protocol) - captureProtocolRank(b.protocol), + ); + const showParseWarning = config.udp.filter_quic === "parse" && !hasDomainsConfigured; const showNoProcessingWarning = !willProcessUdp; @@ -282,7 +289,15 @@ export const UdpSettings = ({ config, queue, onChange }: UdpSettingsProps) => { value: UDP_FAKE_PAYLOAD_AUTO_QUIC, label: t("sets.udp.fakePayloadFileAutoQuicOption"), }, - ...captures.map((c) => ({ + { + value: UDP_FAKE_PAYLOAD_PRESET_1, + label: t("sets.udp.fakePayloadFilePreset1"), + }, + { + value: UDP_FAKE_PAYLOAD_PRESET_2, + label: t("sets.udp.fakePayloadFilePreset2"), + }, + ...sortedCaptures.map((c) => ({ value: c.filepath, label: `[${c.protocol}] ${c.domain} (${c.size} bytes)`, })), diff --git a/src/http/ui/src/components/settings/Capture.tsx b/src/http/ui/src/components/settings/Capture.tsx index 3de657fe..63fd608f 100644 --- a/src/http/ui/src/components/settings/Capture.tsx +++ b/src/http/ui/src/components/settings/Capture.tsx @@ -10,6 +10,7 @@ import { CircularProgress, Tooltip, IconButton, + Chip, } from "@mui/material"; import { CaptureIcon, @@ -61,13 +62,45 @@ export const CaptureSettings = () => { if (!uploadForm.domain && uploadForm.file) { setUploadForm((prev) => { let name = (prev.file?.name ?? "").replace(/\.bin$/i, ""); - const proto = name.startsWith("quic_") ? "quic" : "tls"; name = name.replace(/^(tls|quic)_/, "").replaceAll("_", "."); - return { ...prev, domain: name, protocol: proto }; + return { ...prev, domain: name }; }); } }, [uploadForm.domain, uploadForm.file]); + const detectPayloadProtocol = async ( + file: File, + ): Promise<"tls" | "quic" | null> => { + try { + const buf = await file.slice(0, 5).arrayBuffer(); + const bytes = new Uint8Array(buf); + if (bytes.length < 2) return null; + if (bytes[0] === 0x16 && bytes[1] === 0x03) return "tls"; + if ((bytes[0] & 0xf0) === 0xc0) return "quic"; + return null; + } catch { + return null; + } + }; + + const handleFileChange = async (file: File | null) => { + if (!file) { + setUploadForm({ ...uploadForm, file: null }); + return; + } + let protocol = await detectPayloadProtocol(file); + if (!protocol) { + const name = file.name.toLowerCase(); + if (name.startsWith("quic_")) protocol = "quic"; + else if (name.startsWith("tls_")) protocol = "tls"; + } + setUploadForm({ + ...uploadForm, + file, + protocol: protocol ?? uploadForm.protocol, + }); + }; + const generateCapture = async () => { if (!probeForm.domain) return; @@ -205,7 +238,7 @@ export const CaptureSettings = () => { accept=".bin,application/octet-stream" onChange={(e) => { const file = e.target.files?.[0] || null; - setUploadForm({ ...uploadForm, file }); + void handleFileChange(file); }} /> @@ -452,7 +485,31 @@ const CaptureCard = ({ {capture.size.toLocaleString()} bytes - + {/* Timestamp */} diff --git a/src/http/ui/src/i18n/en.json b/src/http/ui/src/i18n/en.json index 0e025c2c..f897c2f6 100644 --- a/src/http/ui/src/i18n/en.json +++ b/src/http/ui/src/i18n/en.json @@ -468,7 +468,7 @@ "protocol": "Protocol", "protocolTls": "TLS (for TCP fakes)", "protocolQuic": "QUIC (for UDP fakes)", - "protocolHelp": "TLS payloads are used by TCP fake-SNI; QUIC payloads are used by UDP fake packets", + "protocolHelp": "Auto-detected from file contents on upload. Override only if needed.", "chooseFile": "Choose File...", "uploadFailed": "Failed to upload payload", "generateTitle": "Generate Payload", @@ -1111,6 +1111,8 @@ "fakePayloadFileEmpty": "No captures uploaded yet. Default = zero fill.", "fakePayloadFileAutoQuicOption": "(auto: QUIC Initial)", "fakePayloadFileAutoQuic": "b4 generates a fresh randomized QUIC Initial for each fake packet, with random connection IDs. Recommended size: 1200 bytes.", + "fakePayloadFilePreset1": "QUIC preset 1", + "fakePayloadFilePreset2": "QUIC preset 2", "fakePayloadManage": "Upload or manage payloads in Settings → Payloads", "seg2delay": "Segment 2 Delay", "seg2delayHelper": "Delay between segments. Use a range for random delay per packet." diff --git a/src/http/ui/src/i18n/ru.json b/src/http/ui/src/i18n/ru.json index ea1f7948..c38118a9 100644 --- a/src/http/ui/src/i18n/ru.json +++ b/src/http/ui/src/i18n/ru.json @@ -464,7 +464,7 @@ "protocol": "Протокол", "protocolTls": "TLS (для TCP-фейков)", "protocolQuic": "QUIC (для UDP-фейков)", - "protocolHelp": "TLS payload-ы используются в TCP fake-SNI; QUIC payload-ы - в фейковых UDP-пакетах", + "protocolHelp": "Определяется автоматически по содержимому файла. Меняйте только при необходимости.", "chooseFile": "Выбрать файл...", "uploadFailed": "Не удалось загрузить пэйлоад", "generateTitle": "Сгенерировать пэйлоад", @@ -1108,6 +1108,8 @@ "fakePayloadFileEmpty": "Захваченных payload-ов пока нет. По умолчанию - нули.", "fakePayloadFileAutoQuicOption": "(авто: QUIC Initial)", "fakePayloadFileAutoQuic": "b4 генерирует свежий рандомизированный QUIC Initial для каждого фейкового пакета, со случайными connection ID. Рекомендуемый размер: 1200 байт.", + "fakePayloadFilePreset1": "QUIC preset 1", + "fakePayloadFilePreset2": "QUIC preset 2", "fakePayloadManage": "Загрузить или управлять payload-ами в Настройки → Payloads", "seg2delay": "Задержка сегмента 2", "seg2delayHelper": "Задержка между сегментами. Используйте диапазон для случайной задержки." diff --git a/src/http/ui/src/models/config.ts b/src/http/ui/src/models/config.ts index 61d5061e..47f43d2c 100644 --- a/src/http/ui/src/models/config.ts +++ b/src/http/ui/src/models/config.ts @@ -123,6 +123,8 @@ export type UdpFilterQuicMode = "disabled" | "all" | "parse"; export type UdpFakingStrategy = "none" | "ttl" | "checksum"; export const UDP_FAKE_PAYLOAD_AUTO_QUIC = "@quic_initial"; +export const UDP_FAKE_PAYLOAD_PRESET_1 = "@preset:quic1"; +export const UDP_FAKE_PAYLOAD_PRESET_2 = "@preset:quic2"; export interface UdpConfig { mode: UdpMode;