magisk-zapret2/zapret2/lua/custom_funcs.lua
loop-uh 97cea9510c lf
2026-07-22 14:35:10 +03:00

3276 lines
No EOL
123 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- AGGRESSIVE HTTP BYPASS for stubborn DPI (like porno365)
-- Combines multiple techniques: fake flood + disorder + host splitting
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- arg : fakes=N - number of fake packets to send (default 5)
-- arg : ttl_start=N - starting TTL for fakes (default 1)
-- arg : ttl_step=N - TTL increment for each fake (default 1)
-- arg : split_host - additionally split inside hostname
-- arg : disorder - send parts in reverse order
function http_aggressive(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
-- Parse HTTP request
local hdis = http_dissect_req(data)
if not hdis or not hdis.headers.host then
DLOG("http_aggressive: cannot parse HTTP request or no Host header")
return
end
local host_pos = hdis.headers.host
-- pos_end points to end of line (before \r\n), pos_value_start to start of value
local host_value = string.sub(data, host_pos.pos_value_start, host_pos.pos_end)
DLOG("http_aggressive: detected Host: "..host_value)
-- Options for fake packets (will die before reaching server)
local opts_fake = {
rawsend = rawsend_opts(desync),
reconstruct = reconstruct_opts(desync),
ipfrag = {},
ipid = desync.arg,
fooling = desync.arg
}
-- Options for real packets (no fooling except tcp_ts_up)
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {tcp_ts_up = desync.arg.tcp_ts_up}
}
local num_fakes = tonumber(desync.arg.fakes) or 5
local ttl_start = tonumber(desync.arg.ttl_start) or 1
local ttl_step = tonumber(desync.arg.ttl_step) or 1
-- Generate fake HTTP request with different host
local fake_host = "www.google.com"
local fake_data = string.sub(data, 1, host_pos.pos_value_start-1) ..
fake_host ..
string.sub(data, host_pos.pos_end+1)
-- STEP 1: Send multiple fake packets with low TTL (will die before DPI or server)
for i=1,num_fakes do
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_data
-- Set low TTL so packet dies before reaching server
local ttl = ttl_start + (i-1) * ttl_step
if fake_dis.ip then
fake_dis.ip.ip_ttl = ttl
end
if fake_dis.ip6 then
fake_dis.ip6.ip6_hlim = ttl
end
-- Add badseq to confuse DPI further
if fake_dis.tcp then
fake_dis.tcp.th_ack = fake_dis.tcp.th_ack - 66000
end
if b_debug then DLOG("http_aggressive: sending fake #"..i.." TTL="..ttl) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
end
-- STEP 2: Split real request into parts
-- Split points: before "Host:", middle of host, after host value
local split_positions = {}
-- Always split before "Host:" header
table.insert(split_positions, host_pos.pos_start)
-- Optionally split in the middle of hostname
if desync.arg.split_host then
local host_mid = host_pos.pos_value_start + math.floor(#host_value / 2)
if host_mid > host_pos.pos_value_start and host_mid < host_pos.pos_end then
table.insert(split_positions, host_mid)
end
end
-- Split after Host header value
table.insert(split_positions, host_pos.pos_end + 1)
-- Sort positions
table.sort(split_positions)
-- Create parts
local parts = {}
local prev_pos = 1
for i, pos in ipairs(split_positions) do
if pos > prev_pos and pos <= #data then
table.insert(parts, {
data = string.sub(data, prev_pos, pos-1),
offset = prev_pos - 1
})
prev_pos = pos
end
end
-- Add remaining part
if prev_pos <= #data then
table.insert(parts, {
data = string.sub(data, prev_pos),
offset = prev_pos - 1
})
end
if b_debug then
DLOG("http_aggressive: split into "..#parts.." parts")
for i, p in ipairs(parts) do
DLOG("http_aggressive: part "..i.." offset="..p.offset.." len="..#p.data)
end
end
-- STEP 3: Send parts (optionally in disorder)
if desync.arg.disorder and #parts > 1 then
-- Send in reverse order (disorder)
for i=#parts,1,-1 do
if b_debug then DLOG("http_aggressive: sending part "..i.." (disorder)") end
if not rawsend_payload_segmented(desync, parts[i].data, parts[i].offset, opts_orig) then
return VERDICT_PASS
end
end
else
-- Send in normal order
for i=1,#parts do
if b_debug then DLOG("http_aggressive: sending part "..i) end
if not rawsend_payload_segmented(desync, parts[i].data, parts[i].offset, opts_orig) then
return VERDICT_PASS
end
end
end
-- STEP 4: Send more fakes after real data (sandwich technique)
for i=1,math.floor(num_fakes/2) do
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_data
if fake_dis.ip then
fake_dis.ip.ip_ttl = ttl_start
end
if fake_dis.ip6 then
fake_dis.ip6.ip6_hlim = ttl_start
end
if fake_dis.tcp then
fake_dis.tcp.th_ack = fake_dis.tcp.th_ack - 66000
end
if b_debug then DLOG("http_aggressive: sending trailing fake #"..i) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
end
replay_drop_set(desync)
return VERDICT_DROP
else
DLOG("http_aggressive: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- HTTP SYNDATA - Send HTTP request in SYN packet (most aggressive)
-- This bypasses DPI that only inspects data after handshake
-- standard args : fooling, rawsend, reconstruct, ipfrag
-- arg : blob=<blob> - HTTP request template (optional, will use current payload if available)
function http_syndata(ctx, desync)
if desync.dis.tcp then
if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK)==TH_SYN then
local dis = deepcopy(desync.dis)
-- Try to get HTTP request from conntrack or use template
local http_req = desync.arg.blob and blob(desync, desync.arg.blob) or
"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
dis.payload = http_req
apply_fooling(desync, dis)
if b_debug then DLOG("http_syndata: sending SYN with HTTP payload len="..#http_req) end
if rawsend_dissect_ipfrag(dis, desync_opts(desync)) then
return VERDICT_DROP
end
else
instance_cutoff(ctx)
end
else
instance_cutoff(ctx)
end
end
-- HTTP with multiple disorder splits at critical positions
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- Splits at: method, path, host header name, host value (multiple cuts)
function http_multidisorder(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
-- Generate split positions for HTTP
local positions = {}
-- Split at position 1 (after first byte)
table.insert(positions, 2)
-- Find key positions in HTTP request
local method_end = string.find(data, " ")
if method_end then
table.insert(positions, method_end)
table.insert(positions, method_end + 1)
end
-- Find Host: header
local host_start = string.find(data, "\r\nHost: ", 1, true)
if host_start then
table.insert(positions, host_start + 2) -- before "Host:"
table.insert(positions, host_start + 8) -- after "Host: "
-- Find end of host value
local host_end = string.find(data, "\r\n", host_start + 8, true)
if host_end then
-- Split in middle of host value
local mid = host_start + 8 + math.floor((host_end - host_start - 8) / 2)
table.insert(positions, mid)
table.insert(positions, host_end)
end
end
-- Remove duplicates and sort
local unique_pos = {}
local seen = {}
for _, p in ipairs(positions) do
if p > 1 and p <= #data and not seen[p] then
table.insert(unique_pos, p)
seen[p] = true
end
end
table.sort(unique_pos)
if b_debug then DLOG("http_multidisorder: split positions: "..table.concat(unique_pos, ",")) end
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {tcp_ts_up = desync.arg.tcp_ts_up}
}
-- Create and send parts in reverse order
local parts = {}
local prev = 1
for _, pos in ipairs(unique_pos) do
if pos > prev then
table.insert(parts, {string.sub(data, prev, pos-1), prev-1})
prev = pos
end
end
if prev <= #data then
table.insert(parts, {string.sub(data, prev), prev-1})
end
-- Send in reverse order (disorder)
for i=#parts,1,-1 do
if b_debug then DLOG("http_multidisorder: sending part "..i.." offset="..parts[i][2].." len="..#parts[i][1]) end
if not rawsend_payload_segmented(desync, parts[i][1], parts[i][2], opts_orig) then
return VERDICT_PASS
end
end
replay_drop_set(desync)
return VERDICT_DROP
else
DLOG("http_multidisorder: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- Улучшенный methodeol - добавляет больше мусора в начало
function http_methodeol_v2(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local hdis = http_dissect_req(desync.dis.payload)
local ua = hdis.headers["user-agent"]
if ua then
-- Добавляем несколько пустых строк и пробелы
local garbage = "\r\n \r\n\t\r\n"
desync.dis.payload = garbage .. string.sub(desync.dis.payload,1,ua.pos_end-2) .. (string.sub(desync.dis.payload,ua.pos_end+1) or "")
DLOG("http_methodeol_v2: applied with extra garbage")
return VERDICT_MODIFY
end
end
end
-- Methodeol + изменение регистра Host
function http_methodeol_hostcase(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local payload = desync.dis.payload
-- Меняем Host: на HoSt:
payload = string.gsub(payload, "\r\nHost:", "\r\nHoSt:")
-- Добавляем мусор в начало
payload = "\r\n" .. payload
desync.dis.payload = payload
DLOG("http_methodeol_hostcase: applied")
return VERDICT_MODIFY
end
end
-- AGGRESSIVE TLS BYPASS for stubborn DPI (like browserleaks.com)
-- Combines multiple techniques: fake flood + SNI split + disorder + seqovl
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- arg : fakes=N - number of fake packets to send (default 6)
-- arg : ttl_start=N - starting TTL for fakes (default 1)
-- arg : ttl_step=N - TTL increment for each fake (default 1)
-- arg : seqovl=N - sequence overlap size (default 0)
-- arg : seqovl_pattern=<blob> - pattern for seqovl
-- arg : fake_sni=<str> - SNI for fake packets (default www.google.com)
-- arg : split_sni - split inside SNI
-- arg : badseq - use badseq fooling on fakes
-- arg : md5sig - use md5sig fooling on fakes
function tls_aggressive(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="tls_client_hello" and direction_check(desync) then
if replay_first(desync) then
DLOG("tls_aggressive: processing TLS Client Hello len="..#data)
-- Options for fake packets (will die before reaching server)
local opts_fake = {
rawsend = rawsend_opts(desync),
reconstruct = reconstruct_opts(desync),
ipfrag = {},
ipid = desync.arg,
fooling = desync.arg
}
-- Options for real packets (no fooling except tcp_ts_up)
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {tcp_ts_up = desync.arg.tcp_ts_up}
}
local num_fakes = tonumber(desync.arg.fakes) or 6
local ttl_start = tonumber(desync.arg.ttl_start) or 1
local ttl_step = tonumber(desync.arg.ttl_step) or 1
local fake_sni = desync.arg.fake_sni or "www.google.com"
-- Generate fake TLS Client Hello with different SNI using tls_mod
local fake_blob = desync.arg.blob and blob(desync, desync.arg.blob) or blob(desync, "fake_default_tls")
local fake_data = fake_blob
if desync.arg.tls_mod then
fake_data = tls_mod(fake_data, desync.arg.tls_mod, desync.reasm_data)
else
-- Default: randomize and set fake SNI
fake_data = tls_mod(fake_data, "rnd,dupsid,sni="..fake_sni, desync.reasm_data)
end
-- STEP 1: Send multiple fake packets with low TTL
for i=1,num_fakes do
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_data
local ttl = ttl_start + (i-1) * ttl_step
if fake_dis.ip then
fake_dis.ip.ip_ttl = ttl
end
if fake_dis.ip6 then
fake_dis.ip6.ip6_hlim = ttl
end
-- Apply fooling based on args
if fake_dis.tcp then
if desync.arg.badseq then
fake_dis.tcp.th_ack = fake_dis.tcp.th_ack - 66000
end
end
if b_debug then DLOG("tls_aggressive: sending fake #"..i.." TTL="..ttl.." len="..#fake_data) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
end
-- STEP 2: Determine split positions for TLS Client Hello
-- Key positions: start, after record header, SNI positions
local split_positions = {}
-- Always split at position 1 (minimal split)
table.insert(split_positions, 2)
-- Try to find SNI in TLS Client Hello using resolve_multi_pos
local sni_positions = resolve_multi_pos(data, "tls_client_hello", "host,midsld,endhost")
if sni_positions and #sni_positions >= 2 then
for _, pos in ipairs(sni_positions) do
if pos > 1 and pos <= #data then
table.insert(split_positions, pos)
end
end
if b_debug then DLOG("tls_aggressive: found SNI positions: "..table.concat(sni_positions, ",")) end
else
-- Fallback: split at fixed positions
local tls_header_end = 6 -- After TLS record header
if tls_header_end < #data then
table.insert(split_positions, tls_header_end)
end
-- Split at ~1/3 and ~2/3 of data
local third = math.floor(#data / 3)
if third > 5 then
table.insert(split_positions, third)
table.insert(split_positions, third * 2)
end
end
-- Also try sniext positions
local sniext_positions = resolve_multi_pos(data, "tls_client_hello", "sniext,sniext+1,sniext+2")
if sniext_positions then
for _, pos in ipairs(sniext_positions) do
if pos > 1 and pos <= #data then
table.insert(split_positions, pos)
end
end
end
-- Remove duplicates and sort
local unique_pos = {}
local seen = {}
for _, p in ipairs(split_positions) do
if p > 1 and p <= #data and not seen[p] then
table.insert(unique_pos, p)
seen[p] = true
end
end
table.sort(unique_pos)
if b_debug then DLOG("tls_aggressive: split positions: "..table.concat(unique_pos, ",")) end
-- Create parts
local parts = {}
local prev = 1
for _, pos in ipairs(unique_pos) do
if pos > prev then
table.insert(parts, {string.sub(data, prev, pos-1), prev-1})
prev = pos
end
end
if prev <= #data then
table.insert(parts, {string.sub(data, prev), prev-1})
end
if b_debug then DLOG("tls_aggressive: created "..#parts.." parts") end
-- STEP 3: Send parts in reverse order (disorder) with optional seqovl
local seqovl = tonumber(desync.arg.seqovl) or 0
local seqovl_pat = desync.arg.seqovl_pattern and blob(desync, desync.arg.seqovl_pattern) or "\x00"
for i=#parts,1,-1 do
local part_data = parts[i][1]
local part_offset = parts[i][2]
-- Apply seqovl to second part (in original order, which is second-to-last in disorder)
if i == (#parts - 1) and seqovl > 0 and part_offset >= seqovl then
part_data = pattern(seqovl_pat, 1, seqovl) .. part_data
part_offset = part_offset - seqovl
if b_debug then DLOG("tls_aggressive: applied seqovl="..seqovl.." to part "..i) end
end
if b_debug then DLOG("tls_aggressive: sending part "..i.." offset="..part_offset.." len="..#part_data) end
if not rawsend_payload_segmented(desync, part_data, part_offset, opts_orig) then
return VERDICT_PASS
end
end
-- STEP 4: Send trailing fakes (sandwich)
for i=1,math.floor(num_fakes/2) do
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_data
if fake_dis.ip then
fake_dis.ip.ip_ttl = ttl_start
end
if fake_dis.ip6 then
fake_dis.ip6.ip6_hlim = ttl_start
end
if fake_dis.tcp and desync.arg.badseq then
fake_dis.tcp.th_ack = fake_dis.tcp.th_ack - 66000
end
if b_debug then DLOG("tls_aggressive: sending trailing fake #"..i) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
end
replay_drop_set(desync)
return VERDICT_DROP
else
DLOG("tls_aggressive: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- TLS with extreme multi-split at SNI and surrounding areas
-- Splits TLS Client Hello into many small pieces around SNI
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- arg : seqovl=N - sequence overlap (default 211)
-- arg : seqovl_pattern=<blob> - pattern for seqovl
function tls_multisplit_sni(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="tls_client_hello" and direction_check(desync) then
if replay_first(desync) then
-- Get extensive split positions around SNI
local spos = "1,host-2,host,host+1,host+2,midsld-1,midsld,midsld+1,sld,sld+1,sld+2,endhost-2,endhost-1,endhost,sniext,sniext+1,sniext+2"
if b_debug then DLOG("tls_multisplit_sni: split pos: "..spos) end
local pos = resolve_multi_pos(data, desync.l7payload, spos)
if b_debug then DLOG("tls_multisplit_sni: resolved split pos: "..table.concat(zero_based_pos(pos)," ")) end
delete_pos_1(pos)
if #pos>0 then
local seqovl = tonumber(desync.arg.seqovl) or 211
local seqovl_pat = desync.arg.seqovl_pattern and blob(desync, desync.arg.seqovl_pattern) or blob(desync, "fake_default_tls")
-- Send in disorder order (last to first)
for i=#pos,0,-1 do
local pos_start = pos[i] or 1
local pos_end = i<#pos and pos[i+1]-1 or #data
local part = string.sub(data,pos_start,pos_end)
local part_seqovl = 0
-- Apply seqovl to second part (in original order)
if i==1 and seqovl > 0 then
part_seqovl = seqovl
if part_seqovl >= (pos[1]-1) then
DLOG("tls_multisplit_sni: seqovl cancelled, too large")
part_seqovl = 0
else
part = pattern(seqovl_pat, 1, part_seqovl) .. part
end
end
if b_debug then DLOG("tls_multisplit_sni: sending part "..(i+1).." "..(pos_start-1).."-"..(pos_end-1).." len="..#part.." seqovl="..part_seqovl) end
if not rawsend_payload_segmented(desync, part, pos_start-1-part_seqovl) then
return VERDICT_PASS
end
end
replay_drop_set(desync)
return VERDICT_DROP
else
DLOG("tls_multisplit_sni: no valid split positions")
end
else
DLOG("tls_multisplit_sni: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- TLS Fake flood + standard multidisorder
-- Sends many fakes before applying standard multidisorder
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- arg : fakes=N - number of fakes (default 11)
-- arg : pos=<posmarker list> - split positions for multidisorder
-- arg : badseq/md5sig - fooling for fakes
function tls_fake_flood(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="tls_client_hello" and direction_check(desync) then
if replay_first(desync) then
local num_fakes = tonumber(desync.arg.fakes) or 11
local fake_blob = desync.arg.blob and blob(desync, desync.arg.blob) or blob(desync, "fake_default_tls")
-- Apply tls_mod to make fake look real but with different SNI
local fake_data = tls_mod(fake_blob, "rnd,dupsid,sni=www.google.com", desync.reasm_data)
local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync)}
-- Send fake flood with varying TTL
for i=1,num_fakes do
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_data
-- Vary TTL: 1,2,3,1,2,3,1,2,3...
local ttl = ((i-1) % 3) + 1
if fake_dis.ip then fake_dis.ip.ip_ttl = ttl end
if fake_dis.ip6 then fake_dis.ip6.ip6_hlim = ttl end
-- Apply fooling
if fake_dis.tcp then
if desync.arg.badseq then
fake_dis.tcp.th_ack = fake_dis.tcp.th_ack - 66000
end
if desync.arg.md5sig then
-- md5sig applied through reconstruct
end
end
if b_debug then DLOG("tls_fake_flood: sending fake #"..i.." TTL="..ttl) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
end
-- Now send real data with multidisorder
local spos = desync.arg.pos or "1,midsld"
local pos = resolve_multi_pos(data, desync.l7payload, spos)
delete_pos_1(pos)
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {tcp_ts_up = desync.arg.tcp_ts_up}
}
if #pos>0 then
for i=#pos,0,-1 do
local pos_start = pos[i] or 1
local pos_end = i<#pos and pos[i+1]-1 or #data
local part = string.sub(data,pos_start,pos_end)
if b_debug then DLOG("tls_fake_flood: sending part "..(i+1).." len="..#part) end
if not rawsend_payload_segmented(desync, part, pos_start-1, opts_orig) then
return VERDICT_PASS
end
end
else
-- No valid positions, send as-is
if not rawsend_payload_segmented(desync, data, 0, opts_orig) then
return VERDICT_PASS
end
end
-- Send more fakes after (sandwich)
for i=1,math.floor(num_fakes/3) do
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_data
if fake_dis.ip then fake_dis.ip.ip_ttl = 1 end
if fake_dis.ip6 then fake_dis.ip6.ip6_hlim = 1 end
if b_debug then DLOG("tls_fake_flood: sending trailing fake #"..i) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
end
replay_drop_set(desync)
return VERDICT_DROP
else
DLOG("tls_fake_flood: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- ============================================================================
-- SOFT/GENTLE TLS STRATEGIES - less aggressive, won't break sites
-- ============================================================================
-- Simple fake only - no splitting, just send fakes before real data
-- Works for many sites without breaking TLS
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- arg : fakes=N - number of fakes (default 6)
-- arg : ttl=N - TTL for fakes (default 3)
-- arg : sni=<str> - SNI for fakes (default www.google.com)
function tls_fake_simple(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="tls_client_hello" and direction_check(desync) then
if replay_first(desync) then
local num_fakes = tonumber(desync.arg.fakes) or 6
local ttl = tonumber(desync.arg.ttl) or 3
local fake_sni = desync.arg.sni or "www.google.com"
local fake_blob = desync.arg.blob and blob(desync, desync.arg.blob) or blob(desync, "fake_default_tls")
local fake_data = tls_mod(fake_blob, "rnd,dupsid,sni="..fake_sni, desync.reasm_data)
local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync)}
-- Send fakes
for i=1,num_fakes do
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_data
if fake_dis.ip then fake_dis.ip.ip_ttl = ttl end
if fake_dis.ip6 then fake_dis.ip6.ip6_hlim = ttl end
if b_debug then DLOG("tls_fake_simple: sending fake #"..i.." TTL="..ttl) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
end
-- Send real data as-is (no splitting!)
DLOG("tls_fake_simple: sending real data len="..#data)
-- Don't drop, let original packet through
else
DLOG("tls_fake_simple: not acting on further replay pieces")
end
end
end
-- Gentle split - only 2 parts, no disorder
-- Splits at midsld position only
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- arg : pos=<posmarker> - single split position (default midsld)
function tls_split_gentle(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="tls_client_hello" and direction_check(desync) then
if replay_first(desync) then
local spos = desync.arg.pos or "midsld"
local pos = resolve_pos(data, desync.l7payload, spos)
if pos and pos > 1 and pos < #data then
if b_debug then DLOG("tls_split_gentle: split at "..spos.." = "..(pos-1)) end
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {tcp_ts_up = desync.arg.tcp_ts_up}
}
-- Part 1: start to split pos
local part1 = string.sub(data, 1, pos-1)
if b_debug then DLOG("tls_split_gentle: sending part 1 len="..#part1) end
if not rawsend_payload_segmented(desync, part1, 0, opts_orig) then
return VERDICT_PASS
end
-- Part 2: split pos to end
local part2 = string.sub(data, pos)
if b_debug then DLOG("tls_split_gentle: sending part 2 len="..#part2) end
if not rawsend_payload_segmented(desync, part2, pos-1, opts_orig) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
else
DLOG("tls_split_gentle: cannot resolve pos '"..spos.."' or invalid")
end
else
DLOG("tls_split_gentle: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- Fake + gentle split combo
-- Send fakes then split at one position
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- arg : fakes=N - number of fakes (default 4)
-- arg : ttl=N - TTL for fakes (default 2)
-- arg : pos=<posmarker> - split position (default midsld)
function tls_fake_split(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="tls_client_hello" and direction_check(desync) then
if replay_first(desync) then
local num_fakes = tonumber(desync.arg.fakes) or 4
local ttl = tonumber(desync.arg.ttl) or 2
local spos = desync.arg.pos or "midsld"
local fake_blob = desync.arg.blob and blob(desync, desync.arg.blob) or blob(desync, "fake_default_tls")
local fake_data = tls_mod(fake_blob, "rnd,dupsid,sni=www.google.com", desync.reasm_data)
local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync)}
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {tcp_ts_up = desync.arg.tcp_ts_up}
}
-- Send fakes first
for i=1,num_fakes do
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_data
if fake_dis.ip then fake_dis.ip.ip_ttl = ttl end
if fake_dis.ip6 then fake_dis.ip6.ip6_hlim = ttl end
if b_debug then DLOG("tls_fake_split: sending fake #"..i) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
end
-- Now split at one position
local pos = resolve_pos(data, desync.l7payload, spos)
if pos and pos > 1 and pos < #data then
local part1 = string.sub(data, 1, pos-1)
local part2 = string.sub(data, pos)
if b_debug then DLOG("tls_fake_split: sending part 1 len="..#part1) end
if not rawsend_payload_segmented(desync, part1, 0, opts_orig) then
return VERDICT_PASS
end
if b_debug then DLOG("tls_fake_split: sending part 2 len="..#part2) end
if not rawsend_payload_segmented(desync, part2, pos-1, opts_orig) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
else
-- Can't split, send as-is
DLOG("tls_fake_split: can't split, sending as-is")
if not rawsend_payload_segmented(desync, data, 0, opts_orig) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
end
else
DLOG("tls_fake_split: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- Disorder with only 3 parts (gentle)
-- Less fragmentation = works with more servers
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- arg : pos1=<posmarker> - first split (default host)
-- arg : pos2=<posmarker> - second split (default endhost)
function tls_disorder_gentle(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="tls_client_hello" and direction_check(desync) then
if replay_first(desync) then
local spos1 = desync.arg.pos1 or "host"
local spos2 = desync.arg.pos2 or "endhost"
local pos1 = resolve_pos(data, desync.l7payload, spos1)
local pos2 = resolve_pos(data, desync.l7payload, spos2)
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {tcp_ts_up = desync.arg.tcp_ts_up}
}
if pos1 and pos2 and pos1 > 1 and pos2 > pos1 and pos2 <= #data then
if b_debug then DLOG("tls_disorder_gentle: split at "..spos1.."="..(pos1-1)..", "..spos2.."="..(pos2-1)) end
local part1 = string.sub(data, 1, pos1-1)
local part2 = string.sub(data, pos1, pos2-1)
local part3 = string.sub(data, pos2)
-- Send in disorder: 3, 2, 1
if b_debug then DLOG("tls_disorder_gentle: sending part 3 (last) len="..#part3) end
if not rawsend_payload_segmented(desync, part3, pos2-1, opts_orig) then
return VERDICT_PASS
end
if b_debug then DLOG("tls_disorder_gentle: sending part 2 (middle) len="..#part2) end
if not rawsend_payload_segmented(desync, part2, pos1-1, opts_orig) then
return VERDICT_PASS
end
if b_debug then DLOG("tls_disorder_gentle: sending part 1 (first) len="..#part1) end
if not rawsend_payload_segmented(desync, part1, 0, opts_orig) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
else
DLOG("tls_disorder_gentle: can't resolve positions, falling back to simple split")
-- Fallback: split at position 2
local part1 = string.sub(data, 1, 1)
local part2 = string.sub(data, 2)
-- Disorder: 2, 1
if not rawsend_payload_segmented(desync, part2, 1, opts_orig) then
return VERDICT_PASS
end
if not rawsend_payload_segmented(desync, part1, 0, opts_orig) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
end
else
DLOG("tls_disorder_gentle: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- ============================================================================
-- MGTS HTTP BYPASS STRATEGIES - специально для обхода умного DPI МГТС
-- ============================================================================
-- HTTP seqovl Host Override
-- Отправляем фейковый Host с seqovl, потом реальный который "перезаписывает"
-- DPI видит первый (фейковый), сервер принимает второй (реальный) по TCP reassembly
-- standard args : direction, payload, rawsend, reconstruct
-- arg : fake_host=<str> - фейковый хост (default google.com)
-- arg : seqovl=N - размер перекрытия (default = длина Host value)
function http_seqovl_host(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
local hdis = http_dissect_req(data)
if not hdis or not hdis.headers.host then
DLOG("http_seqovl_host: no Host header found")
return
end
local host_pos = hdis.headers.host
local real_host = string.sub(data, host_pos.pos_value_start, host_pos.pos_end)
local fake_host = desync.arg.fake_host or "google.com"
DLOG("http_seqovl_host: real_host='"..real_host.."' fake_host='"..fake_host.."'")
-- Создаём фейковый HTTP запрос с fake_host
-- Нужно чтобы fake_host был той же длины что и real_host для точного seqovl
if #fake_host < #real_host then
fake_host = fake_host .. string.rep("x", #real_host - #fake_host)
elseif #fake_host > #real_host then
fake_host = string.sub(fake_host, 1, #real_host)
end
local fake_data = string.sub(data, 1, host_pos.pos_value_start-1) ..
fake_host ..
string.sub(data, host_pos.pos_end+1)
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
fooling = {}
}
-- Позиция начала Host value
local host_value_pos = host_pos.pos_value_start - 1 -- 0-based
-- STEP 1: Отправляем часть ДО Host value
local before_host = string.sub(data, 1, host_pos.pos_value_start-1)
if b_debug then DLOG("http_seqovl_host: sending before_host len="..#before_host) end
if not rawsend_payload_segmented(desync, before_host, 0, opts_orig) then
return VERDICT_PASS
end
-- STEP 2: Отправляем ФЕЙКОВЫЙ Host value (DPI увидит это первым)
if b_debug then DLOG("http_seqovl_host: sending FAKE host '"..fake_host.."'") end
if not rawsend_payload_segmented(desync, fake_host, host_value_pos, opts_orig) then
return VERDICT_PASS
end
-- STEP 3: Отправляем РЕАЛЬНЫЙ Host value с тем же offset (seqovl!)
-- TCP стек сервера должен принять этот пакет и перезаписать фейковый
if b_debug then DLOG("http_seqovl_host: sending REAL host '"..real_host.."' (seqovl)") end
if not rawsend_payload_segmented(desync, real_host, host_value_pos, opts_orig) then
return VERDICT_PASS
end
-- STEP 4: Отправляем остаток ПОСЛЕ Host value
local after_host = string.sub(data, host_pos.pos_end+1)
if b_debug then DLOG("http_seqovl_host: sending after_host len="..#after_host) end
if not rawsend_payload_segmented(desync, after_host, host_pos.pos_end, opts_orig) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
else
DLOG("http_seqovl_host: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- HTTP with IP fragmentation
-- Разбивает IP пакет на фрагменты - DPI может не уметь их собирать
-- standard args : direction, payload, rawsend
-- arg : frag_size=N - размер первого фрагмента (default 24 - разрежет внутри Host)
function http_ipfrag(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
local frag_size = tonumber(desync.arg.frag_size) or 24
DLOG("http_ipfrag: fragmenting at IP level, frag_size="..frag_size)
-- Используем встроенную IP фрагментацию
local opts = {
rawsend = rawsend_opts(desync),
reconstruct = {},
ipfrag = {
ipfrag_pos_tcp = frag_size
}
}
if rawsend_dissect_ipfrag(desync.dis, opts) then
replay_drop_set(desync)
return VERDICT_DROP
end
else
DLOG("http_ipfrag: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- HTTP Host case modification
-- Меняет регистр "Host:" header - некоторые DPI чувствительны к регистру
-- standard args : direction, payload
-- arg : case=<str> - "lower", "upper", "mixed", "space" (default mixed)
function http_hostmod(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local case_type = desync.arg.case or "mixed"
local new_host_header
if case_type == "lower" then
new_host_header = "host"
elseif case_type == "upper" then
new_host_header = "HOST"
elseif case_type == "mixed" then
new_host_header = "HoSt"
elseif case_type == "space" then
new_host_header = "Host " -- пробел после
elseif case_type == "tab" then
new_host_header = "Host\t" -- таб после
else
new_host_header = case_type -- custom
end
-- Найти и заменить "Host:" на новый вариант
local host_start = string.find(desync.dis.payload, "\r\nHost:", 1, true)
if host_start then
host_start = host_start + 2 -- skip \r\n
local new_payload = string.sub(desync.dis.payload, 1, host_start-1) ..
new_host_header .. ":" ..
string.sub(desync.dis.payload, host_start + 5) -- skip "Host:"
desync.dis.payload = new_payload
DLOG("http_hostmod: changed 'Host:' to '"..new_host_header..":' ")
return VERDICT_MODIFY
else
DLOG("http_hostmod: Host header not found")
end
end
end
-- HTTP with absolute URL
-- Использует абсолютный URL в запросе: GET http://host/path HTTP/1.1
-- Некоторые DPI не парсят URL, только Host header
-- standard args : direction, payload
-- arg : fake_host=<str> - фейковый хост для Host header (default google.com)
function http_absolute_url(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local hdis = http_dissect_req(desync.dis.payload)
if not hdis or not hdis.headers.host then
DLOG("http_absolute_url: cannot parse HTTP")
return
end
local real_host = string.sub(desync.dis.payload, hdis.headers.host.pos_value_start, hdis.headers.host.pos_end)
local fake_host = desync.arg.fake_host or "google.com"
local path = hdis.path or "/"
-- Создаём новый запрос с абсолютным URL
-- GET http://real_host/path HTTP/1.1\r\nHost: fake_host\r\n...
local abs_url = "http://" .. real_host .. path
local new_request = hdis.method .. " " .. abs_url .. " " .. hdis.version .. "\r\n"
-- Добавляем headers, но меняем Host на фейковый
for name, header in pairs(hdis.headers) do
if name == "host" then
new_request = new_request .. "Host: " .. fake_host .. "\r\n"
else
local header_value = string.sub(desync.dis.payload, header.pos_value_start, header.pos_end)
new_request = new_request .. header.name .. ": " .. header_value .. "\r\n"
end
end
new_request = new_request .. "\r\n"
-- Добавляем body если есть
if hdis.body_start and hdis.body_start <= #desync.dis.payload then
new_request = new_request .. string.sub(desync.dis.payload, hdis.body_start)
end
desync.dis.payload = new_request
DLOG("http_absolute_url: rewrote to absolute URL, Host header now '"..fake_host.."'")
return VERDICT_MODIFY
end
end
-- HTTP Triple seqovl attack
-- Отправляет 3 версии Host value с одинаковым seq number
-- 1. Fake host (DPI кеширует)
-- 2. Garbage (сбивает DPI)
-- 3. Real host (сервер принимает последний по TCP)
-- standard args : direction, payload, rawsend, reconstruct
-- arg : fake_host=<str> - фейковый хост
function http_triple_seqovl(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
local hdis = http_dissect_req(data)
if not hdis or not hdis.headers.host then
DLOG("http_triple_seqovl: no Host header")
return
end
local host_pos = hdis.headers.host
local real_host = string.sub(data, host_pos.pos_value_start, host_pos.pos_end)
local host_len = #real_host
local fake_host = desync.arg.fake_host or "www.google.com"
-- Подгоняем длину
if #fake_host < host_len then
fake_host = fake_host .. string.rep(".", host_len - #fake_host)
else
fake_host = string.sub(fake_host, 1, host_len)
end
local garbage = string.rep("X", host_len)
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
fooling = {}
}
local host_value_pos = host_pos.pos_value_start - 1
-- Отправляем часть ДО Host value
local before_host = string.sub(data, 1, host_pos.pos_value_start-1)
if not rawsend_payload_segmented(desync, before_host, 0, opts_orig) then
return VERDICT_PASS
end
-- АТАКА: 3 пакета с одинаковым seq
-- 1. Fake host
if b_debug then DLOG("http_triple_seqovl: [1] FAKE host '"..fake_host.."'") end
if not rawsend_payload_segmented(desync, fake_host, host_value_pos, opts_orig) then
return VERDICT_PASS
end
-- 2. Garbage (сбивает кеш DPI)
if b_debug then DLOG("http_triple_seqovl: [2] GARBAGE") end
if not rawsend_payload_segmented(desync, garbage, host_value_pos, opts_orig) then
return VERDICT_PASS
end
-- 3. Real host (последний - сервер примет его)
if b_debug then DLOG("http_triple_seqovl: [3] REAL host '"..real_host.."'") end
if not rawsend_payload_segmented(desync, real_host, host_value_pos, opts_orig) then
return VERDICT_PASS
end
-- Отправляем остаток
local after_host = string.sub(data, host_pos.pos_end+1)
if not rawsend_payload_segmented(desync, after_host, host_pos.pos_end, opts_orig) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- HTTP Disorder + seqovl combo for MGTS
-- Комбинирует disorder с seqovl специально для Host header
-- standard args : direction, payload, rawsend
-- arg : fake_host=<str>
function http_mgts_combo(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
local hdis = http_dissect_req(data)
if not hdis or not hdis.headers.host then
DLOG("http_mgts_combo: no Host header")
return
end
local host_pos = hdis.headers.host
local real_host = string.sub(data, host_pos.pos_value_start, host_pos.pos_end)
local fake_host = desync.arg.fake_host or "www.google.com"
-- Подгоняем длину
local host_len = #real_host
if #fake_host < host_len then
fake_host = fake_host .. string.rep("x", host_len - #fake_host)
else
fake_host = string.sub(fake_host, 1, host_len)
end
local opts = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
fooling = {}
}
-- Разбиваем на 4 части:
-- 1. До "Host: "
-- 2. "Host: " + fake_host (seqovl с реальным)
-- 3. real_host (перезаписывает)
-- 4. После host value до конца
local part1 = string.sub(data, 1, host_pos.pos_start - 1) -- до "Host:"
local part2_fake = "Host: " .. fake_host
local part3_real = real_host
local part4 = string.sub(data, host_pos.pos_end + 1) -- после host value
local pos_host_start = host_pos.pos_start - 1 -- 0-based
local pos_host_value = host_pos.pos_value_start - 1
local pos_after_host = host_pos.pos_end -- 0-based (после последнего символа host)
-- DISORDER: отправляем в обратном порядке
-- 4 -> 3 -> 2 -> 1
if b_debug then DLOG("http_mgts_combo: [4] after_host len="..#part4) end
if not rawsend_payload_segmented(desync, part4, pos_after_host, opts) then
return VERDICT_PASS
end
-- Реальный host (будет принят сервером)
if b_debug then DLOG("http_mgts_combo: [3] REAL '"..part3_real.."'") end
if not rawsend_payload_segmented(desync, part3_real, pos_host_value, opts) then
return VERDICT_PASS
end
-- Фейковый "Host: fake" с seqovl (DPI увидит)
if b_debug then DLOG("http_mgts_combo: [2] FAKE '"..part2_fake.."' (seqovl)") end
if not rawsend_payload_segmented(desync, part2_fake, pos_host_start, opts) then
return VERDICT_PASS
end
-- Начало запроса
if b_debug then DLOG("http_mgts_combo: [1] before_host len="..#part1) end
if not rawsend_payload_segmented(desync, part1, 0, opts) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- Combined: Fake + gentle disorder
-- Best of both: fakes confuse DPI, gentle disorder doesn't break server
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct
-- arg : fakes=N - number of fakes (default 3)
-- arg : ttl=N - TTL for fakes (default 2)
function tls_fake_disorder_gentle(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="tls_client_hello" and direction_check(desync) then
if replay_first(desync) then
local num_fakes = tonumber(desync.arg.fakes) or 3
local ttl = tonumber(desync.arg.ttl) or 2
local fake_blob = desync.arg.blob and blob(desync, desync.arg.blob) or blob(desync, "fake_default_tls")
local fake_data = tls_mod(fake_blob, "rnd,dupsid,sni=www.google.com", desync.reasm_data)
local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync)}
local opts_orig = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {tcp_ts_up = desync.arg.tcp_ts_up}
}
-- Send fakes
for i=1,num_fakes do
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_data
if fake_dis.ip then fake_dis.ip.ip_ttl = ttl end
if fake_dis.ip6 then fake_dis.ip6.ip6_hlim = ttl end
if b_debug then DLOG("tls_fake_disorder_gentle: sending fake #"..i) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
end
-- Gentle disorder: split at host and endhost only
local pos1 = resolve_pos(data, desync.l7payload, "host")
local pos2 = resolve_pos(data, desync.l7payload, "endhost")
if pos1 and pos2 and pos1 > 1 and pos2 > pos1 and pos2 <= #data then
local part1 = string.sub(data, 1, pos1-1)
local part2 = string.sub(data, pos1, pos2-1)
local part3 = string.sub(data, pos2)
-- Disorder: 3, 2, 1
if not rawsend_payload_segmented(desync, part3, pos2-1, opts_orig) then return VERDICT_PASS end
if not rawsend_payload_segmented(desync, part2, pos1-1, opts_orig) then return VERDICT_PASS end
if not rawsend_payload_segmented(desync, part1, 0, opts_orig) then return VERDICT_PASS end
else
-- Fallback: just split at position 2
local part1 = string.sub(data, 1, 1)
local part2 = string.sub(data, 2)
if not rawsend_payload_segmented(desync, part2, 1, opts_orig) then return VERDICT_PASS end
if not rawsend_payload_segmented(desync, part1, 0, opts_orig) then return VERDICT_PASS end
end
replay_drop_set(desync)
return VERDICT_DROP
else
DLOG("tls_fake_disorder_gentle: not acting on further replay pieces")
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
--[[
multisplit_tls - multisplit с динамической генерацией TLS паттерна
Вместо статичного файла seqovl_pattern генерирует fake TLS на лету
с помощью tls_mod, подставляя нужный SNI (например www.google.com)
ВАЖНО: Если payload не является валидным TLS, функция НЕ применяет
tls_mod и использует fallback-паттерн или пропускает пакет без изменений.
Аргументы:
pos - позиции разреза (как в обычном multisplit): "2", "1,midsld", etc.
seqovl - размер overlap в байтах (рекомендуется 650-700)
sni - SNI для fake TLS (по умолчанию "www.google.com")
tls_rnd - если указан, рандомизировать random/session_id в fake TLS
tls_dupsid - если указан, скопировать session_id из реального пакета
nodrop - не дропать оригинальный пакет
fallback - что делать если payload не TLS: "pass" (пропустить), "split" (резать без seqovl), "pattern" (использовать raw pattern)
Пример использования:
--lua-desync=multisplit_tls:seqovl=680:sni=www.google.com:tls_rnd:tls_dupsid:pos=2
┌─────────────────────────────────────────────────────────────────┐
КАК РАБОТАЕТ SEQOVL │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Обычный пакет: [TLS ClientHello с реальным SNI] │
│ seq=1000, len=500 │
│ │
С seqovl=650: [FAKE TLS (650 байт)][Реальный TLS] │
│ seq=350, len=1150 │
│ │
│ ТСПУ видит: SNI из FAKE TLS (www.google.com) │
│ Сервер получает: Реальный TLS (ТСПУ не может заблокировать) │
│ │
│ Почему? TCP overlap - сервер берёт данные с большим seq │
└─────────────────────────────────────────────────────────────────┘
--]]
-- Вспомогательная функция: проверяет, является ли payload валидным TLS Client Hello
local function is_valid_tls_client_hello(data)
if not data or #data < 6 then
return false
end
-- TLS Record Header: ContentType (1 byte) + Version (2 bytes) + Length (2 bytes)
-- ContentType = 0x16 (Handshake)
-- Version = 0x0301 (TLS 1.0), 0x0302 (TLS 1.1), 0x0303 (TLS 1.2), 0x0304 (TLS 1.3)
local content_type = string.byte(data, 1)
local version_major = string.byte(data, 2)
local version_minor = string.byte(data, 3)
-- Проверяем что это Handshake record
if content_type ~= 0x16 then
return false
end
-- Проверяем версию TLS (0x03 0x01-0x04)
if version_major ~= 0x03 then
return false
end
if version_minor < 0x01 or version_minor > 0x04 then
return false
end
-- Проверяем Handshake Type (должен быть 0x01 = Client Hello)
-- Handshake header начинается с позиции 6 (после TLS record header)
if #data >= 6 then
local handshake_type = string.byte(data, 6)
if handshake_type ~= 0x01 then
return false
end
end
return true
end
function multisplit_tls(ctx, desync)
-- Только для TCP
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
-- Получаем данные для обработки (reassembled TLS или текущий payload)
local data = desync.reasm_data or desync.dis.payload
-- ============================================
-- ЗАЩИТА: Пропускаем MTProto и Telegram
-- seqovl ломает соединения с Telegram!
-- ============================================
-- 1. Пропускаем MTProto (это НЕ TLS, seqovl его ломает)
if desync.l7payload == "mtproto_initial" or desync.l7payload == "mtproto" then
if b_debug then
DLOG("multisplit_tls: SKIP MTProto traffic - not compatible with seqovl")
end
return -- Выходим без вердикта, пакет пройдёт как есть
end
-- 2. Пропускаем домены Telegram (seqovl с ними не работает)
if desync.hostname then
local telegram_domains = {
"telegram.org",
"t.me",
"telegram.me",
"tdesktop.com",
"telegra.ph"
}
for _, domain in ipairs(telegram_domains) do
if string.find(desync.hostname, domain, 1, true) then
if b_debug then
DLOG("multisplit_tls: SKIP Telegram domain: " .. desync.hostname)
end
return -- Выходим без вердикта
end
end
end
-- Проверяем условия: есть данные, правильное направление, нужный payload
if #data > 0 and direction_check(desync) and payload_check(desync) then
if replay_first(desync) then
-- Позиции для разреза (по умолчанию "2" = после 1-го байта)
local spos = desync.arg.pos or "2"
if b_debug then
DLOG("multisplit_tls: split pos: " .. spos)
end
-- Вычисляем реальные позиции разреза
local pos = resolve_multi_pos(data, desync.l7payload, spos)
if b_debug then
DLOG("multisplit_tls: resolved: " .. table.concat(zero_based_pos(pos), " "))
end
-- Нельзя резать на позиции 1
delete_pos_1(pos)
if #pos > 0 then
-- ============================================
-- КЛЮЧЕВАЯ ПРОВЕРКА: является ли payload TLS?
-- ============================================
local payload_is_tls = is_valid_tls_client_hello(data)
local reasm_is_tls = desync.reasm_data and is_valid_tls_client_hello(desync.reasm_data)
local can_apply_tls_mod = payload_is_tls or reasm_is_tls
-- Также проверяем l7payload от движка
if desync.l7payload == "tls_client_hello" then
can_apply_tls_mod = true
end
if b_debug then
DLOG("multisplit_tls: payload_is_tls=" .. tostring(payload_is_tls) ..
" reasm_is_tls=" .. tostring(reasm_is_tls) ..
" l7payload=" .. tostring(desync.l7payload) ..
" can_apply_tls_mod=" .. tostring(can_apply_tls_mod))
end
-- Отправляем части пакета
for i = 0, #pos do
local pos_start = pos[i] or 1
local pos_end = i < #pos and pos[i + 1] - 1 or #data
local part = string.sub(data, pos_start, pos_end)
local seqovl = 0
-- SEQOVL применяется только к ПЕРВОЙ части (i == 0)
if i == 0 and desync.arg.seqovl and tonumber(desync.arg.seqovl) > 0 then
seqovl = tonumber(desync.arg.seqovl)
-- ============================================
-- БЕЗОПАСНАЯ ГЕНЕРАЦИЯ ПАТТЕРНА
-- ============================================
local pat = nil
if can_apply_tls_mod then
-- Payload IS TLS - можем применять tls_mod
-- Берём базовый fake TLS (встроенный, ~680 байт)
local base_tls = blob(desync, "fake_default_tls")
-- Собираем список модификаций
local mods = {}
-- SNI (по умолчанию www.google.com)
local sni = desync.arg.sni or "www.google.com"
table.insert(mods, "sni=" .. sni)
-- Рандомизация random bytes и session id
if desync.arg.tls_rnd then
table.insert(mods, "rnd")
end
-- Копирование session id из реального пакета
-- ТОЛЬКО если reasm_data валидный TLS
if desync.arg.tls_dupsid and reasm_is_tls then
table.insert(mods, "dupsid")
elseif desync.arg.tls_dupsid and not reasm_is_tls then
-- dupsid запрошен но reasm_data не TLS - пропускаем dupsid
if b_debug then
DLOG("multisplit_tls: skipping dupsid - reasm_data is not valid TLS")
end
end
local modlist = table.concat(mods, ",")
if b_debug then
DLOG("multisplit_tls: applying tls_mod: " .. modlist)
end
-- Применяем модификации к fake TLS
-- Третий параметр нужен для dupsid - передаём только если это TLS
local reasm_for_mod = reasm_is_tls and desync.reasm_data or nil
local fake_tls = tls_mod(base_tls, modlist, reasm_for_mod)
if fake_tls and #fake_tls > 0 then
-- Создаём паттерн нужной длины (seqovl байт)
pat = pattern(fake_tls, 1, seqovl)
if b_debug then
DLOG("multisplit_tls: seqovl=" .. seqovl ..
" fake_tls_len=" .. #fake_tls ..
" sni=" .. sni)
end
else
-- tls_mod вернул nil или пустую строку - используем fallback
if b_debug then
DLOG("multisplit_tls: tls_mod returned empty, using raw blob")
end
pat = pattern(base_tls, 1, seqovl)
end
else
-- ============================================
-- PAYLOAD НЕ TLS - FALLBACK РЕЖИМ
-- ============================================
local fallback_mode = desync.arg.fallback or "pass"
if b_debug then
DLOG("multisplit_tls: payload is NOT valid TLS, fallback=" .. fallback_mode)
end
if fallback_mode == "pass" then
-- Пропускаем пакет без изменений
DLOG("multisplit_tls: fallback=pass, letting packet through unchanged")
return VERDICT_PASS
elseif fallback_mode == "split" then
-- Делаем split но без seqovl
DLOG("multisplit_tls: fallback=split, splitting without seqovl")
seqovl = 0
-- pat остаётся nil, seqovl = 0 - просто разрежем без overlap
else -- "pattern" или любое другое значение
-- Используем raw blob без tls_mod модификаций
-- Это безопасно - просто добавляем байты перед пакетом
local raw_blob = blob(desync, "fake_default_tls")
if raw_blob and #raw_blob > 0 then
pat = pattern(raw_blob, 1, seqovl)
DLOG("multisplit_tls: fallback=pattern, using raw blob without tls_mod")
else
-- Даже blob не доступен - делаем split без seqovl
DLOG("multisplit_tls: no blob available, splitting without seqovl")
seqovl = 0
end
end
end
-- Добавляем паттерн ПЕРЕД реальными данными (если есть)
if pat and #pat > 0 then
part = pat .. part
else
-- Нет паттерна - обнуляем seqovl
seqovl = 0
end
end
if b_debug then
DLOG("multisplit_tls: sending part " .. (i + 1) ..
" pos=" .. (pos_start - 1) .. "-" .. (pos_end - 1) ..
" len=" .. #part .. " seqovl=" .. seqovl)
end
-- Отправляем сегмент с уменьшенным seq (для overlap)
if not rawsend_payload_segmented(desync, part, pos_start - 1 - seqovl) then
return VERDICT_PASS
end
end
replay_drop_set(desync)
return desync.arg.nodrop and VERDICT_PASS or VERDICT_DROP
else
DLOG("multisplit_tls: no valid split positions")
end
else
DLOG("multisplit_tls: not acting on further replay pieces")
end
-- Дропаем replayed пакеты если успешно отправили split
if replay_drop(desync) then
return desync.arg.nodrop and VERDICT_PASS or VERDICT_DROP
end
end
end
-- ============================================================================
-- NEW HTTP BYPASS STRATEGIES - Для обхода умного DPI (MTS/MGTS)
-- ============================================================================
-- HTTP Garbage Prefix - Много мусора перед запросом
-- DPI часто парсит только начало пакета, мусор может сбить парсер
-- standard args : direction, payload
-- arg : mode=<str> - "crlf", "spaces", "tabs", "mixed", "nulls" (default mixed)
-- arg : amount=N - количество мусора в байтах (default 50)
function http_garbage_prefix(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local mode = desync.arg.mode or "mixed"
local amount = tonumber(desync.arg.amount) or 50
local garbage = ""
if mode == "crlf" then
-- Много \r\n подряд
garbage = string.rep("\r\n", math.floor(amount / 2))
elseif mode == "spaces" then
-- Пробелы с \r\n
garbage = string.rep(" \r\n", math.floor(amount / 3))
elseif mode == "tabs" then
-- Табы с \r\n
garbage = string.rep("\t\r\n", math.floor(amount / 3))
elseif mode == "mixed" then
-- Смешанный мусор: пробелы, табы, \r\n
for i = 1, math.floor(amount / 4) do
local r = i % 4
if r == 0 then garbage = garbage .. "\r\n"
elseif r == 1 then garbage = garbage .. " \r\n"
elseif r == 2 then garbage = garbage .. "\t\r\n"
else garbage = garbage .. " \r\n"
end
end
elseif mode == "headers" then
-- Фейковые заголовки в начале (невалидные)
garbage = "X-Fake: garbage\r\n" ..
"X-Ignore: me\r\n" ..
string.rep("\r\n", 5)
end
desync.dis.payload = garbage .. desync.dis.payload
DLOG("http_garbage_prefix: added "..#garbage.." bytes of '"..mode.."' garbage")
return VERDICT_MODIFY
end
end
-- HTTP Pipeline Fake - Отправить два запроса, первый фейковый
-- DPI может кэшировать домен из первого запроса
-- standard args : direction, payload, rawsend
-- arg : fake_host=<str> - хост для фейкового запроса (default www.google.com)
function http_pipeline_fake(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
local fake_host = desync.arg.fake_host or "www.google.com"
-- Создаём фейковый HTTP запрос (очень короткий)
local fake_request = "GET / HTTP/1.1\r\n" ..
"Host: " .. fake_host .. "\r\n" ..
"Connection: keep-alive\r\n" ..
"\r\n"
local opts = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
fooling = {}
}
-- Отправляем ФЕЙКОВЫЙ запрос сначала
if b_debug then DLOG("http_pipeline_fake: sending FAKE request to "..fake_host) end
if not rawsend_payload_segmented(desync, fake_request, 0, opts) then
return VERDICT_PASS
end
-- Потом РЕАЛЬНЫЙ запрос с offset = длина фейкового
if b_debug then DLOG("http_pipeline_fake: sending REAL request") end
if not rawsend_payload_segmented(desync, data, #fake_request, opts) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- HTTP Header Shuffle - Перемешать заголовки и добавить фейковые Host
-- Добавляет фейковый Host ДО реального, надеясь что DPI возьмёт первый
-- standard args : direction, payload
-- arg : fake_host=<str>
-- arg : add_x_host - также добавить X-Host с реальным хостом
function http_header_shuffle(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local hdis = http_dissect_req(desync.dis.payload)
if not hdis or not hdis.headers.host then
DLOG("http_header_shuffle: no Host header")
return
end
local host_pos = hdis.headers.host
local real_host = string.sub(desync.dis.payload, host_pos.pos_value_start, host_pos.pos_end)
local fake_host = desync.arg.fake_host or "www.google.com"
-- Находим конец первой строки (после GET / HTTP/1.1)
local first_line_end = string.find(desync.dis.payload, "\r\n", 1, true)
if not first_line_end then
DLOG("http_header_shuffle: cannot find end of first line")
return
end
-- Собираем новый запрос:
-- 1. Первая строка (GET / HTTP/1.1)
-- 2. FAKE Host header
-- 3. Остальные заголовки (включая реальный Host)
local new_payload = string.sub(desync.dis.payload, 1, first_line_end + 1) .. -- включая \r\n
"Host: " .. fake_host .. "\r\n" .. -- Фейковый Host ПЕРВЫМ
string.sub(desync.dis.payload, first_line_end + 2) -- остаток
-- Опционально добавить X-Host с реальным хостом
if desync.arg.add_x_host then
-- Вставляем перед реальным Host
local real_host_pos = string.find(new_payload, "\r\nHost: "..real_host, 1, true)
if real_host_pos then
new_payload = string.sub(new_payload, 1, real_host_pos + 1) ..
"X-Real-Host: " .. real_host .. "\r\n" ..
string.sub(new_payload, real_host_pos + 2)
end
end
desync.dis.payload = new_payload
DLOG("http_header_shuffle: added fake Host '"..fake_host.."' before real '"..real_host.."'")
return VERDICT_MODIFY
end
end
-- HTTP Method Obfuscation - Обфускация HTTP метода
-- Некоторые DPI парсят только GET/POST, другие методы могут не блокироваться
-- standard args : direction, payload
-- arg : method=<str> - "lowercase", "padding", "fake" (default lowercase)
function http_method_obfuscate(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local mode = desync.arg.method or "lowercase"
local payload = desync.dis.payload
if mode == "lowercase" then
-- GET -> get (некоторые серверы принимают)
payload = string.gsub(payload, "^GET ", "get ")
payload = string.gsub(payload, "^POST ", "post ")
payload = string.gsub(payload, "^HEAD ", "head ")
elseif mode == "padding" then
-- GET -> GET (доп пробелы)
payload = string.gsub(payload, "^GET ", "GET ")
payload = string.gsub(payload, "^POST ", "POST ")
elseif mode == "fake" then
-- Добавить фейковый метод перед реальным
payload = "X " .. payload
elseif mode == "case" then
-- GET -> GeT
payload = string.gsub(payload, "^GET ", "GeT ")
payload = string.gsub(payload, "^POST ", "PoSt ")
end
desync.dis.payload = payload
DLOG("http_method_obfuscate: applied mode '"..mode.."'")
return VERDICT_MODIFY
end
end
-- HTTP Absolute URI - Использовать абсолютный URI в запросе
-- GET http://real-host.com/ HTTP/1.1 вместо GET / HTTP/1.1
-- Host header ставим фейковый
-- standard args : direction, payload
-- arg : fake_host=<str>
function http_absolute_uri_v2(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local hdis = http_dissect_req(desync.dis.payload)
if not hdis or not hdis.headers.host then
DLOG("http_absolute_uri_v2: cannot parse")
return
end
local host_pos = hdis.headers.host
local real_host = string.sub(desync.dis.payload, host_pos.pos_value_start, host_pos.pos_end)
local fake_host = desync.arg.fake_host or "www.google.com"
-- Ищем начало пути (после GET )
local method_end = string.find(desync.dis.payload, " ", 1, true)
local path_end = string.find(desync.dis.payload, " HTTP/", 1, true)
if method_end and path_end and path_end > method_end then
local method = string.sub(desync.dis.payload, 1, method_end - 1)
local path = string.sub(desync.dis.payload, method_end + 1, path_end - 1)
-- Создаём абсолютный URI
local abs_uri = "http://" .. real_host .. path
-- Новый запрос с абсолютным URI и фейковым Host
local new_payload = method .. " " .. abs_uri ..
string.sub(desync.dis.payload, path_end)
-- Заменяем Host на фейковый
new_payload = string.gsub(new_payload,
"\r\nHost: " .. real_host,
"\r\nHost: " .. fake_host)
desync.dis.payload = new_payload
DLOG("http_absolute_uri_v2: uri="..abs_uri.." fake_host="..fake_host)
return VERDICT_MODIFY
end
end
end
-- HTTP Split At Host Byte - Побайтовый split внутри Host value
-- Разрезает Host value на отдельные байты и отправляет с задержкой
-- standard args : direction, payload, rawsend
-- arg : max_parts=N - максимум частей для Host (default 5)
function http_host_bytesplit(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
local hdis = http_dissect_req(data)
if not hdis or not hdis.headers.host then
DLOG("http_host_bytesplit: no Host header")
return
end
local host_pos = hdis.headers.host
local max_parts = tonumber(desync.arg.max_parts) or 5
local opts = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
fooling = {}
}
-- Часть 1: До Host value
local before = string.sub(data, 1, host_pos.pos_value_start - 1)
if b_debug then DLOG("http_host_bytesplit: [1] before len="..#before) end
if not rawsend_payload_segmented(desync, before, 0, opts) then
return VERDICT_PASS
end
-- Части 2..N: Host value побайтово
local host_value = string.sub(data, host_pos.pos_value_start, host_pos.pos_end)
local chunk_size = math.max(1, math.floor(#host_value / max_parts))
local pos = 0
local part_num = 2
while pos < #host_value do
local chunk_end = math.min(pos + chunk_size, #host_value)
local chunk = string.sub(host_value, pos + 1, chunk_end)
local offset = host_pos.pos_value_start - 1 + pos
if b_debug then DLOG("http_host_bytesplit: ["..part_num.."] chunk='"..chunk.."'") end
if not rawsend_payload_segmented(desync, chunk, offset, opts) then
return VERDICT_PASS
end
pos = chunk_end
part_num = part_num + 1
end
-- Последняя часть: После Host value
local after = string.sub(data, host_pos.pos_end + 1)
if b_debug then DLOG("http_host_bytesplit: ["..part_num.."] after len="..#after) end
if not rawsend_payload_segmented(desync, after, host_pos.pos_end, opts) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- HTTP Fake Continuation - Отправить фейковый "продолжение" соединения
-- DPI может не парсить keep-alive запросы
-- standard args : direction, payload, rawsend
-- arg : fake_host=<str>
function http_fake_continuation(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
local fake_host = desync.arg.fake_host or "www.google.com"
local opts = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
fooling = {}
}
-- Отправляем "фейковый ответ" (DPI может подумать что это продолжение)
local fake_response = "HTTP/1.1 200 OK\r\n" ..
"Content-Length: 0\r\n" ..
"Connection: keep-alive\r\n" ..
"\r\n"
-- Сначала "ответ"
if b_debug then DLOG("http_fake_continuation: sending fake response") end
if not rawsend_payload_segmented(desync, fake_response, 0, opts) then
return VERDICT_PASS
end
-- Потом реальный запрос
if b_debug then DLOG("http_fake_continuation: sending real request") end
if not rawsend_payload_segmented(desync, data, #fake_response, opts) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- HTTP Version Downgrade - Понизить версию HTTP
-- Некоторые DPI не парсят HTTP/1.0
-- standard args : direction, payload
-- arg : version=<str> - "1.0" или "0.9" (default 1.0)
function http_version_downgrade(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local version = desync.arg.version or "1.0"
local payload = desync.dis.payload
if version == "1.0" then
payload = string.gsub(payload, "HTTP/1.1", "HTTP/1.0")
-- Убираем Host header для HTTP/1.0 (опционально)
-- payload = string.gsub(payload, "\r\nHost: [^\r\n]+", "")
elseif version == "0.9" then
-- HTTP/0.9: только "GET /path" без версии и заголовков
local path_start = string.find(payload, " ", 1, true)
local path_end = string.find(payload, " HTTP/", 1, true)
if path_start and path_end then
local method = string.sub(payload, 1, path_start - 1)
local path = string.sub(payload, path_start + 1, path_end - 1)
payload = method .. " " .. path .. "\r\n"
end
end
desync.dis.payload = payload
DLOG("http_version_downgrade: changed to HTTP/"..version)
return VERDICT_MODIFY
end
end
-- HTTP Pipeline Fake v2 - Фейковый запрос умирает, реальный доходит
-- Фейковый запрос отправляется с badsum/низким TTL - сервер его отбросит
-- DPI видит фейковый хост, сервер получает только реальный запрос
-- standard args : direction, payload, rawsend, fooling
-- arg : fake_host=<str> - хост для фейкового запроса (default www.google.com)
-- arg : ttl=N - TTL для фейкового пакета (default 1)
-- arg : badsum - использовать неверную checksum для fake (рекомендуется)
function http_pipeline_fake_v2(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
local fake_host = desync.arg.fake_host or "www.google.com"
local fake_ttl = tonumber(desync.arg.ttl) or 1
-- Создаём фейковый HTTP запрос
local fake_request = "GET / HTTP/1.1\r\n" ..
"Host: " .. fake_host .. "\r\n" ..
"Connection: keep-alive\r\n" ..
"\r\n"
-- Опции для ФЕЙКОВОГО пакета (badsum/низкий TTL - не дойдёт до сервера)
local opts_fake = {
rawsend = rawsend_opts(desync),
reconstruct = reconstruct_opts(desync),
ipfrag = {},
fooling = {
badsum = desync.arg.badsum or true, -- неверная checksum
}
}
-- Опции для РЕАЛЬНОГО пакета (нормальные)
local opts_real = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
fooling = {}
}
-- Отправляем ФЕЙКОВЫЙ запрос (с badsum - сервер отбросит, DPI увидит)
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_request
-- Устанавливаем низкий TTL
if fake_dis.ip then
fake_dis.ip.ip_ttl = fake_ttl
end
if fake_dis.ip6 then
fake_dis.ip6.ip6_hlim = fake_ttl
end
if b_debug then DLOG("http_pipeline_fake_v2: sending FAKE (TTL="..fake_ttl..", badsum) to "..fake_host) end
rawsend_dissect(fake_dis, opts_fake.rawsend)
-- Отправляем РЕАЛЬНЫЙ запрос (нормальный, дойдёт до сервера)
if b_debug then DLOG("http_pipeline_fake_v2: sending REAL request") end
if not rawsend_payload_segmented(desync, data, 0, opts_real) then
return VERDICT_PASS
end
replay_drop_set(desync)
return VERDICT_DROP
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- HTTP Fake Header Inject - Добавляет фейковый X-Host заголовок
-- Сервер игнорирует X-Host, но DPI может его прочитать
-- standard args : direction, payload
-- arg : fake_host=<str>
function http_fake_xhost(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local fake_host = desync.arg.fake_host or "www.google.com"
local payload = desync.dis.payload
-- Находим конец первой строки
local first_line_end = string.find(payload, "\r\n", 1, true)
if first_line_end then
-- Вставляем X-Host ПЕРЕД настоящим Host (DPI может взять первый "Host"-подобный)
-- Некоторые DPI ищут просто "Host:" без проверки что это заголовок
local fake_header = "X-Host: " .. fake_host .. "\r\n" ..
"X-Forwarded-Host: " .. fake_host .. "\r\n"
payload = string.sub(payload, 1, first_line_end + 1) ..
fake_header ..
string.sub(payload, first_line_end + 2)
desync.dis.payload = payload
DLOG("http_fake_xhost: added fake X-Host: "..fake_host)
return VERDICT_MODIFY
end
end
end
-- HTTP with OOB byte - Добавляет TCP OOB байт перед запросом
-- Некоторые DPI не обрабатывают urgent data правильно
-- standard args : direction, payload
function http_oob_prefix(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
-- Добавляем мусорный байт в начало - сервера часто игнорируют лишние байты
-- перед GET/POST
desync.dis.payload = "\n" .. desync.dis.payload
DLOG("http_oob_prefix: added \\n prefix")
return VERDICT_MODIFY
end
end
-- Безопасный methodeol - только добавляет \r\n в начало, ничего не обрезает
-- Некоторые серверы принимают \r\n перед GET
function http_methodeol_safe(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
-- Просто добавляем \r\n в начало (без обрезания User-Agent)
desync.dis.payload = "\r\n" .. desync.dis.payload
DLOG("http_methodeol_safe: added \\r\\n prefix only")
return VERDICT_MODIFY
end
end
-- Ещё безопаснее - добавить пустую строку внутри заголовков (не в начале)
-- Вставляет пустой X-заголовок который nginx игнорирует
function http_inject_safe_header(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local payload = desync.dis.payload
-- Находим позицию перед Host:
local host_pos = string.find(payload, "\r\nHost:", 1, true)
if host_pos then
-- Вставляем безопасный заголовок перед Host
payload = string.sub(payload, 1, host_pos + 1) ..
"X-Padding: " .. string.rep("x", 50) .. "\r\n" ..
string.sub(payload, host_pos + 2)
desync.dis.payload = payload
DLOG("http_inject_safe_header: added X-Padding header")
return VERDICT_MODIFY
end
end
end
-- ============================================================================
-- SAFE METHODEOL VARIANTS - обходят DPI, не ломают сервер
-- ============================================================================
-- Вариант 1: Только \r\n в начало (без обрезания User-Agent)
-- Многие HTTP серверы игнорируют \r\n перед GET
function http_methodeol_safe(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
-- Только \r\n в начало, ничего не обрезаем
desync.dis.payload = "\r\n" .. desync.dis.payload
DLOG("http_methodeol_safe: added \\r\\n prefix")
return VERDICT_MODIFY
end
end
-- Вариант 2: Пробел перед GET (некоторые серверы принимают)
-- " GET / HTTP/1.1" вместо "GET / HTTP/1.1"
function http_space_prefix(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
desync.dis.payload = " " .. desync.dis.payload
DLOG("http_space_prefix: added space prefix")
return VERDICT_MODIFY
end
end
-- Вариант 3: \n вместо \r\n (Unix-style line ending)
function http_lf_prefix(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
desync.dis.payload = "\n" .. desync.dis.payload
DLOG("http_lf_prefix: added \\n prefix")
return VERDICT_MODIFY
end
end
-- Вариант 4: Таб перед GET
function http_tab_prefix(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
desync.dis.payload = "\t" .. desync.dis.payload
DLOG("http_tab_prefix: added tab prefix")
return VERDICT_MODIFY
end
end
-- Вариант 5: Добавить безопасный X-заголовок (100% совместимо)
-- X-Padding header игнорируется сервером но сбивает парсер DPI
function http_xpadding(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local payload = desync.dis.payload
-- Находим конец первой строки (GET / HTTP/1.1\r\n)
local first_line_end = string.find(payload, "\r\n", 1, true)
if first_line_end then
-- Вставляем длинный X-заголовок сразу после первой строки
local padding = string.rep("x", 100) -- 100 символов мусора
payload = string.sub(payload, 1, first_line_end + 1) ..
"X-Pad: " .. padding .. "\r\n" ..
string.sub(payload, first_line_end + 2)
desync.dis.payload = payload
DLOG("http_xpadding: added X-Pad header with 100 bytes")
return VERDICT_MODIFY
end
end
end
-- Вариант 6: Несколько \r\n подряд (агрессивнее)
-- arg : count=N - количество \r\n (default 3)
function http_multi_crlf(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local count = tonumber(desync.arg.count) or 3
local prefix = string.rep("\r\n", count)
desync.dis.payload = prefix .. desync.dis.payload
DLOG("http_multi_crlf: added "..count.." x \\r\\n prefix")
return VERDICT_MODIFY
end
end
-- Вариант 7: Комбинация - \r\n + пробелы
function http_mixed_prefix(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
-- \r\n потом пробелы потом таб
desync.dis.payload = "\r\n \t" .. desync.dis.payload
DLOG("http_mixed_prefix: added mixed whitespace prefix")
return VERDICT_MODIFY
end
end
-- HTTP combo bypass v2 - исправлена ошибка с tcp_seq
function http_combo_bypass(ctx, desync)
if not desync.dis.tcp then
instance_cutoff(ctx)
return
end
direction_cutoff_opposite(ctx, desync)
local data = desync.reasm_data or desync.dis.payload
if #data>0 and desync.l7payload=="http_req" and direction_check(desync) then
if replay_first(desync) then
local fake_host = desync.arg.fake_host or "www.iana.org"
local repeats = tonumber(desync.arg.repeats) or 15
local prefix = desync.arg.prefix or "\r\n"
local hostcase = desync.arg.hostcase or "HoSt"
-- 1. ОТПРАВЛЯЕМ FAKE ПАКЕТЫ
local fake_http = "GET / HTTP/1.1\r\nHost: " .. fake_host ..
"\r\nUser-Agent: Mozilla/5.0\r\nAccept: */*\r\n" ..
"Connection: keep-alive\r\n\r\n"
local opts_fake = {
rawsend = rawsend_opts(desync),
reconstruct = reconstruct_opts(desync),
ipfrag = {},
fooling = { badsum = true }
}
opts_fake.rawsend.repeats = repeats
local fake_dis = deepcopy(desync.dis)
fake_dis.payload = fake_http
if fake_dis.ip then fake_dis.ip.ip_ttl = 2 end
if fake_dis.ip6 then fake_dis.ip6.ip6_hlim = 2 end
DLOG("http_combo_bypass: sending "..repeats.." fake packets")
rawsend_dissect(fake_dis, opts_fake.rawsend)
-- 2. МОДИФИЦИРУЕМ PAYLOAD
local modified = data
if prefix and #prefix > 0 then
modified = prefix .. modified
end
if hostcase then
modified = string.gsub(modified, "Host:", hostcase..":", 1)
end
-- 3. ОТПРАВЛЯЕМ КАК ЕДИНЫЙ ПАКЕТ (без split)
local opts_real = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
fooling = {}
}
local real_dis = deepcopy(desync.dis)
real_dis.payload = modified
DLOG("http_combo_bypass: sending modified request, len="..#modified)
rawsend_dissect(real_dis, opts_real.rawsend)
replay_drop_set(desync)
return VERDICT_DROP
end
if replay_drop(desync) then
return VERDICT_DROP
end
end
end
-- Упрощённая версия - только \r\n + hostcase, без split
-- Для серверов которые чувствительны к split
function http_simple_bypass(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync)
if desync.l7payload=="http_req" and direction_check(desync) then
local prefix = desync.arg.prefix or "\r\n"
local hostcase = desync.arg.hostcase or "HoSt"
local payload = desync.dis.payload
-- Добавляем \r\n в начало
payload = prefix .. payload
-- Меняем Host: на HoSt:
payload = string.gsub(payload, "Host:", hostcase..":", 1)
desync.dis.payload = payload
DLOG("http_simple_bypass: prefix + hostcase applied")
return VERDICT_MODIFY
end
end
-- ============================================================================
-- DISCORD BYPASS STRATEGIES - Экспериментальные техники обхода DPI
-- Автор: lua-strategy-creator agent
-- ============================================================================
--[[
Название: discord_window_collapse
Описание: Атака через Window=0 для сброса сессии DPI.
Отправляем первый фрагмент, затем ACK с Window=0.
DPI видит нулевое окно и может сбросить сессию по таймауту,
после чего отправляем оставшиеся данные.
Использование: --lua-desync=discord_window_collapse
Параметры:
- pos: позиция разбиения (default: "host" для TLS SNI)
- delay: задержка перед отправкой второй части в мс (опционально)
]]
function discord_window_collapse(ctx, desync)
-- Проверка наличия TCP
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
-- Отключаем обработку в обратном направлении
direction_cutoff_opposite(ctx, desync, "out")
-- Проверяем направление и тип payload
if not direction_check(desync, "out") then return end
if not payload_check(desync, "tls_client_hello") then return end
-- Работаем только с первым пакетом
if not replay_first(desync) then
if replay_drop(desync) then
return VERDICT_DROP
end
return
end
local data = desync.reasm_data or desync.dis.payload
if #data < 10 then return end
DLOG("discord_window_collapse: processing TLS Client Hello len="..#data)
-- Определяем позицию разбиения (по умолчанию - начало SNI)
local split_marker = desync.arg.pos or "host"
local split_pos = resolve_pos(data, desync.l7payload, split_marker)
if not split_pos or split_pos <= 1 or split_pos >= #data then
-- Fallback: разбиваем на позиции 50 (примерно после TLS заголовков)
split_pos = math.min(50, math.floor(#data / 2))
DLOG("discord_window_collapse: using fallback split_pos="..split_pos)
else
DLOG("discord_window_collapse: resolved '"..split_marker.."' to pos="..split_pos)
end
local base_seq = desync.dis.tcp.th_seq
-- Опции для отправки
local opts = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {}
}
-- ШАГ 1: Отправляем первую часть данных (до SNI)
local part1 = string.sub(data, 1, split_pos - 1)
local pkt1 = deepcopy(desync.dis)
pkt1.payload = part1
pkt1.tcp.th_flags = bitor(TH_ACK, TH_PSH)
DLOG("discord_window_collapse: sending part1 len="..#part1)
rawsend_dissect(pkt1, opts.rawsend, opts.reconstruct)
-- ШАГ 2: Отправляем ACK с Window=0 (сигнал о переполнении буфера)
-- DPI может интерпретировать это как "соединение приостановлено"
local ack_pkt = deepcopy(desync.dis)
ack_pkt.payload = ""
ack_pkt.tcp.th_seq = base_seq + #part1
ack_pkt.tcp.th_flags = TH_ACK
ack_pkt.tcp.th_win = 0 -- Нулевое окно!
DLOG("discord_window_collapse: sending Window=0 ACK")
rawsend_dissect(ack_pkt, opts.rawsend, opts.reconstruct)
-- ШАГ 3: Отправляем Window Update (восстанавливаем окно)
local update_pkt = deepcopy(desync.dis)
update_pkt.payload = ""
update_pkt.tcp.th_seq = base_seq + #part1
update_pkt.tcp.th_flags = TH_ACK
update_pkt.tcp.th_win = 65535 -- Восстанавливаем окно
DLOG("discord_window_collapse: sending Window Update")
rawsend_dissect(update_pkt, opts.rawsend, opts.reconstruct)
-- ШАГ 4: Отправляем вторую часть данных (с SNI)
-- К этому моменту DPI мог сбросить состояние сессии
local part2 = string.sub(data, split_pos)
local pkt2 = deepcopy(desync.dis)
pkt2.payload = part2
pkt2.tcp.th_seq = base_seq + #part1
pkt2.tcp.th_flags = bitor(TH_ACK, TH_PSH)
DLOG("discord_window_collapse: sending part2 len="..#part2)
rawsend_dissect(pkt2, opts.rawsend, opts.reconstruct)
replay_drop_set(desync)
return VERDICT_DROP
end
--[[
Название: discord_router_alert
Описание: Добавляем IP Option Router Alert (type=148/0x94).
Аппаратные DPI на маршрутизаторах часто пропускают пакеты
с Router Alert option через fast path без глубокой инспекции,
так как это опция для RSVP/IGMP.
Использование: --lua-desync=discord_router_alert
Параметры:
- split: дополнительно разбить payload (default: false)
- pos: позиция разбиения если split=true
]]
function discord_router_alert(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync, "out")
if not direction_check(desync, "out") then return end
if not payload_check(desync, "tls_client_hello") then return end
if not replay_first(desync) then
if replay_drop(desync) then
return VERDICT_DROP
end
return
end
local data = desync.reasm_data or desync.dis.payload
if #data < 10 then return end
DLOG("discord_router_alert: processing TLS len="..#data)
-- Опции для отправки
local opts = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {}
}
-- Создаём IP Option Router Alert
-- Format: Type(1) + Length(1) + Value(2)
-- Type = 0x94 (148) = копировать при фрагментации + класс 0 + номер 20
-- Length = 4
-- Value = 0x0000 (Router shall examine packet)
local router_alert_option = string.char(0x94, 0x04, 0x00, 0x00)
if desync.dis.ip then
-- IPv4: добавляем Router Alert option
local pkt = deepcopy(desync.dis)
-- Добавляем опцию к существующим или создаём новые
if pkt.ip.options then
pkt.ip.options = pkt.ip.options .. router_alert_option
else
pkt.ip.options = router_alert_option
end
-- IP Header Length увеличивается (options добавляют байты)
-- winws2 пересчитает автоматически при reconstruct
DLOG("discord_router_alert: added Router Alert IP option")
if desync.arg.split then
-- Дополнительно разбиваем на части
local split_marker = desync.arg.pos or "host"
local split_pos = resolve_pos(data, desync.l7payload, split_marker) or math.floor(#data / 2)
if split_pos > 1 and split_pos < #data then
local base_seq = pkt.tcp.th_seq
-- Часть 1 с Router Alert
local pkt1 = deepcopy(pkt)
pkt1.payload = string.sub(data, 1, split_pos - 1)
rawsend_dissect(pkt1, opts.rawsend, opts.reconstruct)
-- Часть 2 с Router Alert
local pkt2 = deepcopy(pkt)
pkt2.payload = string.sub(data, split_pos)
pkt2.tcp.th_seq = base_seq + split_pos - 1
rawsend_dissect(pkt2, opts.rawsend, opts.reconstruct)
DLOG("discord_router_alert: sent 2 parts with Router Alert")
replay_drop_set(desync)
return VERDICT_DROP
end
end
-- Отправляем единым пакетом с Router Alert
rawsend_dissect(pkt, opts.rawsend, opts.reconstruct)
replay_drop_set(desync)
return VERDICT_DROP
elseif desync.dis.ip6 then
-- IPv6: используем Hop-by-Hop Options с Router Alert
-- Это сложнее, создаём extension header
local pkt = deepcopy(desync.dis)
-- Hop-by-Hop Options Header с Router Alert
-- Next Header (1) + Hdr Ext Len (1) + Options
-- Router Alert Option: Type=5, Len=2, Value=0x0000
local hop_by_hop_data = string.char(
0x05, 0x02, 0x00, 0x00, -- Router Alert option
0x01, 0x00 -- PadN для выравнивания до 8 байт
)
-- Вставляем Hop-by-Hop header (должен быть первым после IPv6)
insert_ip6_exthdr(pkt.ip6, 1, IPPROTO_HOPOPTS, hop_by_hop_data)
fix_ip6_next(pkt.ip6, IPPROTO_TCP)
DLOG("discord_router_alert: added IPv6 Hop-by-Hop Router Alert")
rawsend_dissect(pkt, opts.rawsend, opts.reconstruct)
replay_drop_set(desync)
return VERDICT_DROP
end
end
--[[
Название: discord_ecn_exploit
Описание: Используем ECN (Explicit Congestion Notification) флаги.
ECE и CWR флаги в TCP + ECN биты в IP ToS заставляют
некоторые DPI идти по fast path, так как ECN-трафик
требует приоритетной обработки.
Использование: --lua-desync=discord_ecn_exploit
Параметры:
- split: разбить payload на части (default: true)
- pos: позиция разбиения
- disorder: отправить в обратном порядке
]]
function discord_ecn_exploit(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync, "out")
if not direction_check(desync, "out") then return end
if not payload_check(desync, "tls_client_hello") then return end
if not replay_first(desync) then
if replay_drop(desync) then
return VERDICT_DROP
end
return
end
local data = desync.reasm_data or desync.dis.payload
if #data < 10 then return end
DLOG("discord_ecn_exploit: processing TLS len="..#data)
local opts = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {}
}
-- Функция для применения ECN marking
local function apply_ecn(pkt)
-- TCP: добавляем ECE (ECN-Echo) флаг
-- CWR можно добавить, но он обычно только в ответ
pkt.tcp.th_flags = bitor(pkt.tcp.th_flags, TH_ECE)
-- IP: устанавливаем ECN bits в ToS/Traffic Class
-- ECN использует 2 младших бита поля ToS:
-- 00 = Not-ECT, 01 = ECT(1), 10 = ECT(0), 11 = CE (Congestion Experienced)
-- Используем CE (11) - сигнал о перегрузке
if pkt.ip then
-- Сохраняем DSCP (старшие 6 бит), устанавливаем ECN=CE (11)
pkt.ip.ip_tos = bitor(bitand(pkt.ip.ip_tos, 0xFC), 0x03)
elseif pkt.ip6 then
-- Для IPv6 ECN в Traffic Class (те же 2 младших бита)
pkt.ip6.ip6_flow = bitor(bitand(pkt.ip6.ip6_flow, 0xFFCFFFFF), 0x00300000)
end
end
local do_split = desync.arg.split ~= "false" and desync.arg.split ~= "0"
if do_split then
local split_marker = desync.arg.pos or "host"
local split_pos = resolve_pos(data, desync.l7payload, split_marker)
if not split_pos or split_pos <= 1 or split_pos >= #data then
split_pos = math.min(50, math.floor(#data / 2))
end
local base_seq = desync.dis.tcp.th_seq
local part1 = string.sub(data, 1, split_pos - 1)
local part2 = string.sub(data, split_pos)
local pkt1 = deepcopy(desync.dis)
pkt1.payload = part1
apply_ecn(pkt1)
local pkt2 = deepcopy(desync.dis)
pkt2.payload = part2
pkt2.tcp.th_seq = base_seq + #part1
apply_ecn(pkt2)
if desync.arg.disorder then
-- Отправляем в обратном порядке
DLOG("discord_ecn_exploit: sending disorder with ECN, part2 first")
rawsend_dissect(pkt2, opts.rawsend, opts.reconstruct)
rawsend_dissect(pkt1, opts.rawsend, opts.reconstruct)
else
DLOG("discord_ecn_exploit: sending split with ECN")
rawsend_dissect(pkt1, opts.rawsend, opts.reconstruct)
rawsend_dissect(pkt2, opts.rawsend, opts.reconstruct)
end
else
-- Без разбиения - просто добавляем ECN
local pkt = deepcopy(desync.dis)
apply_ecn(pkt)
DLOG("discord_ecn_exploit: sending single packet with ECN")
rawsend_dissect(pkt, opts.rawsend, opts.reconstruct)
end
replay_drop_set(desync)
return VERDICT_DROP
end
--[[
Название: discord_timestamp_travel
Описание: Отправляем TCP пакет с Timestamp из "прошлого".
DPI с PAWS (Protection Against Wrapped Sequences) проверкой
может отбросить такой пакет как невалидный, но сервер
без строгой PAWS проверки примет.
Использование: --lua-desync=discord_timestamp_travel
Параметры:
- offset_sec: смещение timestamp в секундах назад (default: 7200 = 2 часа)
- split: разбить на части
- pos: позиция разбиения
]]
function discord_timestamp_travel(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync, "out")
if not direction_check(desync, "out") then return end
if not payload_check(desync, "tls_client_hello") then return end
if not replay_first(desync) then
if replay_drop(desync) then
return VERDICT_DROP
end
return
end
local data = desync.reasm_data or desync.dis.payload
if #data < 10 then return end
DLOG("discord_timestamp_travel: processing TLS len="..#data)
local opts = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {}
}
-- Смещение в миллисекундах (TCP timestamp обычно в ms)
local offset_sec = tonumber(desync.arg.offset_sec) or 7200 -- 2 часа по умолчанию
local offset_ms = offset_sec * 1000
-- Функция для модификации TCP Timestamp
local function modify_timestamp(pkt, time_offset)
if not pkt.tcp.options then
-- Создаём timestamp option если его нет
-- Kind=8, Length=10, TSval(4 bytes), TSecr(4 bytes)
local now = os.time() * 1000 -- Примерное значение
local old_ts = now - time_offset
-- Упаковываем в big-endian
local tsval = string.char(
bitand(bitright(old_ts, 24), 0xFF),
bitand(bitright(old_ts, 16), 0xFF),
bitand(bitright(old_ts, 8), 0xFF),
bitand(old_ts, 0xFF)
)
local tsecr = string.char(0, 0, 0, 0) -- TSecr = 0 для исходящих
pkt.tcp.options = {{kind = 8, data = tsval .. tsecr}}
return true
end
-- Ищем существующий timestamp option
local ts_idx = find_tcp_option(pkt.tcp.options, 8)
if ts_idx then
local opt = pkt.tcp.options[ts_idx]
if opt.data and #opt.data >= 4 then
-- Читаем текущий TSval (первые 4 байта)
local b1, b2, b3, b4 = string.byte(opt.data, 1, 4)
local current_ts = b1 * 16777216 + b2 * 65536 + b3 * 256 + b4
-- Вычитаем offset (уходим в "прошлое")
local new_ts = current_ts - time_offset
if new_ts < 0 then new_ts = 0 end
-- Упаковываем обратно
local new_tsval = string.char(
bitand(bitright(new_ts, 24), 0xFF),
bitand(bitright(new_ts, 16), 0xFF),
bitand(bitright(new_ts, 8), 0xFF),
bitand(new_ts, 0xFF)
)
-- Сохраняем TSecr (оставшиеся 4 байта)
local tsecr = string.sub(opt.data, 5, 8)
if #tsecr < 4 then tsecr = string.char(0,0,0,0) end
opt.data = new_tsval .. tsecr
DLOG("discord_timestamp_travel: modified TSval "..current_ts.." -> "..new_ts)
return true
end
end
return false
end
local do_split = desync.arg.split
if do_split then
local split_marker = desync.arg.pos or "host"
local split_pos = resolve_pos(data, desync.l7payload, split_marker)
if not split_pos or split_pos <= 1 or split_pos >= #data then
split_pos = math.min(50, math.floor(#data / 2))
end
local base_seq = desync.dis.tcp.th_seq
-- Первая часть - с нормальным timestamp
local pkt1 = deepcopy(desync.dis)
pkt1.payload = string.sub(data, 1, split_pos - 1)
-- Вторая часть - с timestamp из "прошлого"
local pkt2 = deepcopy(desync.dis)
pkt2.payload = string.sub(data, split_pos)
pkt2.tcp.th_seq = base_seq + split_pos - 1
modify_timestamp(pkt2, offset_ms)
DLOG("discord_timestamp_travel: sending split with time-traveled timestamp")
rawsend_dissect(pkt1, opts.rawsend, opts.reconstruct)
rawsend_dissect(pkt2, opts.rawsend, opts.reconstruct)
else
-- Весь пакет с timestamp из прошлого
local pkt = deepcopy(desync.dis)
modify_timestamp(pkt, offset_ms)
DLOG("discord_timestamp_travel: sending packet with old timestamp")
rawsend_dissect(pkt, opts.rawsend, opts.reconstruct)
end
replay_drop_set(desync)
return VERDICT_DROP
end
--[[
Название: discord_urgent_sni
Описание: Используем URG флаг + Urgent Pointer на середину SNI.
URG данные обрабатываются вне очереди (out-of-band).
DPI может неправильно интерпретировать границы данных
когда urgent pointer указывает в середину важных данных.
Использование: --lua-desync=discord_urgent_sni
Параметры:
- urgent_pos: позиция urgent pointer (default: "midsld" - середина домена)
- urgent_byte: байт для urgent data (default: 0x00)
]]
function discord_urgent_sni(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync, "out")
if not direction_check(desync, "out") then return end
if not payload_check(desync, "tls_client_hello") then return end
if not replay_first(desync) then
if replay_drop(desync) then
return VERDICT_DROP
end
return
end
local data = desync.reasm_data or desync.dis.payload
if #data < 10 then return end
DLOG("discord_urgent_sni: processing TLS len="..#data)
local opts = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {}
}
-- Определяем позицию для Urgent Pointer
local urgent_marker = desync.arg.urgent_pos or "midsld"
local urgent_pos = resolve_pos(data, desync.l7payload, urgent_marker)
if not urgent_pos or urgent_pos <= 0 or urgent_pos > #data then
-- Fallback: середина payload
urgent_pos = math.floor(#data / 2)
DLOG("discord_urgent_sni: using fallback urgent_pos="..urgent_pos)
else
DLOG("discord_urgent_sni: resolved '"..urgent_marker.."' to pos="..urgent_pos)
end
-- Urgent pointer указывает на байт ПОСЛЕ urgent данных
-- Если urp=5, то байты 1-5 - urgent, байт 6+ - обычные
local pkt = deepcopy(desync.dis)
-- Устанавливаем URG флаг
pkt.tcp.th_flags = bitor(pkt.tcp.th_flags, TH_URG)
-- Urgent Pointer - смещение от начала данных до конца urgent части
-- Указываем на середину SNI, чтобы запутать DPI
pkt.tcp.th_urp = urgent_pos
-- Опционально: вставляем urgent byte в начало payload
-- Это OOB данные которые не должны влиять на TLS handshake
if desync.arg.urgent_byte then
local urg_byte = string.char(tonumber(desync.arg.urgent_byte) or 0)
-- Вставляем urgent byte, корректируем urgent pointer
pkt.payload = urg_byte .. data
pkt.tcp.th_urp = 1 -- Urgent pointer на первый байт
DLOG("discord_urgent_sni: inserted urgent byte at start")
end
DLOG("discord_urgent_sni: sending with URG flag, urp="..pkt.tcp.th_urp)
rawsend_dissect(pkt, opts.rawsend, opts.reconstruct)
replay_drop_set(desync)
return VERDICT_DROP
end
--[[
Название: discord_ultimate_combo
Описание: Комбинированная атака: обратный порядок сегментов (disorder) +
ECN marking + случайные IP ID + разбиение на SNI.
Максимально запутывает DPI множеством аномалий одновременно.
Использование: --lua-desync=discord_ultimate_combo
Параметры:
- pos: позиция первого разбиения (default: "host")
- pos2: позиция второго разбиения (default: "endhost")
- fakes: количество fake пакетов с низким TTL (default: 2)
- ttl: TTL для fake пакетов (default: 3)
]]
function discord_ultimate_combo(ctx, desync)
if not desync.dis.tcp then
instance_cutoff_shim(ctx, desync)
return
end
direction_cutoff_opposite(ctx, desync, "out")
if not direction_check(desync, "out") then return end
if not payload_check(desync, "tls_client_hello") then return end
if not replay_first(desync) then
if replay_drop(desync) then
return VERDICT_DROP
end
return
end
local data = desync.reasm_data or desync.dis.payload
if #data < 20 then return end
DLOG("discord_ultimate_combo: processing TLS len="..#data)
local opts_real = {
rawsend = rawsend_opts_base(desync),
reconstruct = {},
ipfrag = {},
ipid = desync.arg,
fooling = {}
}
-- Параметры
local pos1_marker = desync.arg.pos or "host"
local pos2_marker = desync.arg.pos2 or "endhost"
local num_fakes = tonumber(desync.arg.fakes) or 2
local fake_ttl = tonumber(desync.arg.ttl) or 3
-- Определяем позиции разбиения
local pos1 = resolve_pos(data, desync.l7payload, pos1_marker)
local pos2 = resolve_pos(data, desync.l7payload, pos2_marker)
-- Валидация позиций
if not pos1 or pos1 <= 1 then pos1 = math.floor(#data / 3) end
if not pos2 or pos2 <= pos1 then pos2 = math.floor(#data * 2 / 3) end
if pos2 >= #data then pos2 = #data - 1 end
if pos1 >= pos2 then pos1 = pos2 - 10 end
if pos1 < 2 then pos1 = 2 end
DLOG("discord_ultimate_combo: split positions: "..pos1..", "..pos2)
local base_seq = desync.dis.tcp.th_seq
-- Разбиваем на 3 части
local part1 = string.sub(data, 1, pos1 - 1)
local part2 = string.sub(data, pos1, pos2 - 1)
local part3 = string.sub(data, pos2)
-- Функция для применения ECN и случайного IP ID
local function apply_combo_mods(pkt, add_ecn)
-- Случайный IP ID
if pkt.ip then
pkt.ip.ip_id = math.random(1, 65535)
end
-- ECN marking
if add_ecn then
pkt.tcp.th_flags = bitor(pkt.tcp.th_flags, TH_ECE)
if pkt.ip then
pkt.ip.ip_tos = bitor(bitand(pkt.ip.ip_tos, 0xFC), 0x02) -- ECT(0)
end
end
end
-- ШАГ 1: Отправляем fake пакеты с низким TTL
if num_fakes > 0 then
local fake_blob = blob(desync, "fake_default_tls")
if fake_blob then
for i = 1, num_fakes do
local fake_pkt = deepcopy(desync.dis)
fake_pkt.payload = fake_blob
if fake_pkt.ip then
fake_pkt.ip.ip_ttl = fake_ttl
fake_pkt.ip.ip_id = math.random(1, 65535)
end
if fake_pkt.ip6 then
fake_pkt.ip6.ip6_hlim = fake_ttl
end
DLOG("discord_ultimate_combo: sending fake #"..i.." TTL="..fake_ttl)
rawsend_dissect(fake_pkt, opts_real.rawsend, opts_real.reconstruct)
end
end
end
-- ШАГ 2: Отправляем реальные части в ОБРАТНОМ порядке (disorder)
-- Это ключевая техника - DPI видит части не по порядку
-- Часть 3 (последняя) - отправляем ПЕРВОЙ
local pkt3 = deepcopy(desync.dis)
pkt3.payload = part3
pkt3.tcp.th_seq = base_seq + #part1 + #part2
pkt3.tcp.th_flags = bitor(TH_ACK, TH_PSH)
apply_combo_mods(pkt3, true)
DLOG("discord_ultimate_combo: sending part3 (disorder) len="..#part3.." seq_offset="..(#part1 + #part2))
rawsend_dissect(pkt3, opts_real.rawsend, opts_real.reconstruct)
-- Часть 2 (средняя, содержит SNI) - отправляем ВТОРОЙ
local pkt2 = deepcopy(desync.dis)
pkt2.payload = part2
pkt2.tcp.th_seq = base_seq + #part1
pkt2.tcp.th_flags = bitor(TH_ACK, TH_PSH)
apply_combo_mods(pkt2, true)
DLOG("discord_ultimate_combo: sending part2 (disorder) len="..#part2.." seq_offset="..#part1)
rawsend_dissect(pkt2, opts_real.rawsend, opts_real.reconstruct)
-- Часть 1 (первая) - отправляем ПОСЛЕДНЕЙ
local pkt1 = deepcopy(desync.dis)
pkt1.payload = part1
pkt1.tcp.th_flags = bitor(TH_ACK, TH_PSH)
apply_combo_mods(pkt1, false) -- Первая часть без ECN для вариативности
DLOG("discord_ultimate_combo: sending part1 (disorder) len="..#part1.." seq_offset=0")
rawsend_dissect(pkt1, opts_real.rawsend, opts_real.reconstruct)
replay_drop_set(desync)
return VERDICT_DROP
end