mirror of
https://github.com/bol-van/zapret2.git
synced 2026-03-14 06:13:09 +00:00
start
This commit is contained in:
866
lua/zapret-antidpi.lua
Normal file
866
lua/zapret-antidpi.lua
Normal file
@@ -0,0 +1,866 @@
|
||||
--[[
|
||||
|
||||
NFQWS2 ANTIDPI LIBRARY
|
||||
|
||||
--lua-init=@zapret-lib.lua --lua-init=@zapret-antidpi.lua
|
||||
--lua-desync=func1:arg1[=val1]:arg2[=val2] --lua-desync=func2:arg1[=val1]:arg2[=val2] .... --lua-desync=funcN:arg1[=val1]:arg2[=val2]
|
||||
|
||||
BLOBS
|
||||
|
||||
blobs can be 0xHEX, field name in desync or global var
|
||||
standard way to bring binary data to lua code is using the "--blob" parameter of nfqws2
|
||||
dynamic blobs can be inside desync table. one function can prepare data for next functions.
|
||||
|
||||
STANDARD FUNCTION ARGS
|
||||
|
||||
standard direction :
|
||||
|
||||
* dir = in|out|any
|
||||
|
||||
standard fooling :
|
||||
|
||||
* ip_ttl=N - set ipv.ip_ttl to N
|
||||
* ip6_ttl=N - set ip6.ip6_hlim to N
|
||||
* ip_autottl=delta,min-max - set ip.ip_ttl to auto discovered ttl
|
||||
* ip6_autottl=delta,min-max - set ip.ip_ttl to auto discovered ttl
|
||||
|
||||
* ip6_hopbyhop[=hex] - add hopbyhop ipv6 header with optional data. data size must be 6+N*8. all zero by default.
|
||||
* ip6_hopbyhop2 - add 2 hopbyhop ipv6 headers with optional data. data size must be 6+N*8. all zero by default.
|
||||
* ip6_destopt[=hex] - add destopt ipv6 header with optional data. data size must be 6+N*8. all zero by default.
|
||||
* ip6_routing[=hex] - add routing ipv6 header with optional data. data size must be 6+N*8. all zero by default.
|
||||
* ip6_ah[=hex] - add authentication ipv6 header with optional data. data size must be 6+N*4. 0000 + 4 random bytes by default.
|
||||
|
||||
* tcp_seq=N - add N to tcp.th_seq
|
||||
* tcp_ack=N - add N to tcp.th_ack
|
||||
* tcp_ts=N - add N to timestamp value
|
||||
* tcp_md5[=hex] - add MD5 header with optional 16-byte data. all zero by default.
|
||||
* tcp_flags_set=<list> - set tcp flags in comma separated list
|
||||
* tcp_unflags_set=<list> - unset tcp flags in comma separated list
|
||||
|
||||
* fool - custom fooling function : fool_func(dis, fooling_options)
|
||||
|
||||
standard reconstruct :
|
||||
|
||||
* badsum - make L4 checksum invalid
|
||||
|
||||
standard rawsend :
|
||||
|
||||
* repeats - how many time send the packet
|
||||
* ifout - override outbound interface (if --bind_fix4, --bind-fix6 enabled)
|
||||
* fwmark - override fwmark. desync mark bit(s) will be set unconditionally
|
||||
|
||||
standard payload :
|
||||
|
||||
* payload - comma separarated list of allowed payload types. if not present - allow non-empty known payloads.
|
||||
|
||||
standard ip_id :
|
||||
|
||||
* ip_id - seq|rnd|zero|none
|
||||
* ip_id_conn - in 'seq' mode save current ip_id in track.lua_state to use it between packets
|
||||
|
||||
standard ipfrag :
|
||||
|
||||
* ipfrag - ipfrag function name. "ipfrag2" by default if empty
|
||||
* ipfrag2 : ipfrag_pos_udp - udp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 8
|
||||
* ipfrag2 : ipfrag_pos_tcp - tcp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 32
|
||||
* ipfrag2 : ipfrag_next - next protocol field in ipv6 fragment extenstion header of the second fragment. same as first by default.
|
||||
* ipfrag2 : ipfrag_disorder - send fragments from last to first
|
||||
|
||||
]]
|
||||
|
||||
|
||||
-- dummy test function. does nothing.
|
||||
-- no args
|
||||
function pass(ctx, desync)
|
||||
DLOG("pass")
|
||||
end
|
||||
|
||||
-- prints desync to DLOG
|
||||
function pktdebug(ctx, desync)
|
||||
DLOG("desync:")
|
||||
var_debug(desync)
|
||||
end
|
||||
|
||||
-- drop packet
|
||||
-- standard args : direction
|
||||
function drop(ctx, desync)
|
||||
direction_cutoff_opposite(ctx, desync, "any")
|
||||
if direction_check(desync, "any") then
|
||||
DLOG("drop")
|
||||
return VERDICT_DROP
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dup"
|
||||
-- standard args : direction, fooling, ip_id, rawsend, reconstruct
|
||||
function send(ctx, desync)
|
||||
direction_cutoff_opposite(ctx, desync, "any")
|
||||
if direction_check(desync, "any") then
|
||||
DLOG("send")
|
||||
local dis = deepcopy(desync.dis)
|
||||
apply_fooling(desync, dis)
|
||||
apply_ip_id(desync, dis, nil, "none")
|
||||
-- it uses rawsend, reconstruct and ipfrag options
|
||||
rawsend_dissect_ipfrag(dis, desync_opts(desync))
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--orig"
|
||||
-- apply modification to current packet
|
||||
-- standard args : direction, fooling, ip_id, rawsend, reconstruct
|
||||
function pktmod(ctx, desync)
|
||||
direction_cutoff_opposite(ctx, desync, "any")
|
||||
if direction_check(desync, "any") then
|
||||
-- apply to current packet
|
||||
apply_fooling(desync)
|
||||
apply_ip_id(desync, nil, nil, "none")
|
||||
DLOG("pktmod: applied")
|
||||
return VERDICT_MODIFY
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--domcase"
|
||||
-- standard args : direction
|
||||
function http_domcase(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 host_range = resolve_multi_pos(desync.dis.payload,desync.l7payload,"host,endhost")
|
||||
if #host_range == 2 then
|
||||
local host = string.sub(desync.dis.payload,host_range[1],host_range[2]-1)
|
||||
local newhost="", i
|
||||
for i = 1, #host do
|
||||
newhost=newhost..((i%2)==0 and string.lower(string.sub(host,i,i)) or string.upper(string.sub(host,i,i)))
|
||||
end
|
||||
DLOG("http_domcase: "..host.." => "..newhost)
|
||||
desync.dis.payload = string.sub(desync.dis.payload, 1, host_range[1]-1)..newhost..string.sub(desync.dis.payload, host_range[2])
|
||||
return VERDICT_MODIFY
|
||||
else
|
||||
DLOG("http_domcase: cannot find host range")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--hostcase"
|
||||
-- standard args : direction
|
||||
-- arg : spell=<str> . spelling of the "Host" header. must be exactly 4 chars long
|
||||
function http_hostcase(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 spell = desync.arg.spell or "host"
|
||||
if #spell ~= 4 then
|
||||
error("http_hostcase: invalid host spelling '"..spell.."'")
|
||||
else
|
||||
local hdis = http_dissect_req(desync.dis.payload)
|
||||
if hdis.headers.host then
|
||||
DLOG("http_hostcase: 'Host:' => '"..spell.."'")
|
||||
desync.dis.payload = string.sub(desync.dis.payload,1,hdis.headers.host.pos_start-1)..spell..string.sub(desync.dis.payload,hdis.headers.host.pos_header_end+1)
|
||||
return VERDICT_MODIFY
|
||||
else
|
||||
DLOG("http_hostcase: 'Host:' header not found")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--methodeol"
|
||||
-- standard args : direction
|
||||
function http_methodeol(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)
|
||||
local ua = hdis.headers["user-agent"]
|
||||
if ua then
|
||||
if (ua.pos_end - ua.pos_value_start) < 2 then
|
||||
DLOG("http_methodeol: 'User-Agent:' header is too short")
|
||||
else
|
||||
DLOG("http_methodeol: applied")
|
||||
desync.dis.payload="\r\n"..string.sub(desync.dis.payload,1,ua.pos_end-2)..(string.sub(desync.dis.payload,ua.pos_end+1) or "");
|
||||
return VERDICT_MODIFY
|
||||
end
|
||||
else
|
||||
DLOG("http_methodeol: 'User-Agent:' header not found")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--synack-split"
|
||||
-- standard args : rawsend, reconstruct, ipfrag
|
||||
-- arg : mode=syn|synack|acksyn . "synack" by default
|
||||
function synack_split(ctx, desync)
|
||||
if desync.dis.tcp then
|
||||
if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK) == (TH_SYN + TH_ACK) then
|
||||
local mode = desync.arg.mode or "synack"
|
||||
local options = desync_opts(desync)
|
||||
if mode=="syn" then
|
||||
local dis = deepcopy(desync.dis)
|
||||
dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_ACK))
|
||||
DLOG("synack_split: sending SYN")
|
||||
if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end
|
||||
return VERDICT_DROP
|
||||
elseif mode=="synack" then
|
||||
local dis = deepcopy(desync.dis)
|
||||
dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_ACK))
|
||||
DLOG("synack_split: sending SYN")
|
||||
if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end
|
||||
dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_SYN))
|
||||
DLOG("synack_split: sending ACK")
|
||||
if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end
|
||||
return VERDICT_DROP
|
||||
elseif mode=="acksyn" then
|
||||
local dis = deepcopy(desync.dis)
|
||||
dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_SYN))
|
||||
DLOG("synack_split: sending ACK")
|
||||
if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end
|
||||
dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_ACK))
|
||||
DLOG("synack_split: sending SYN")
|
||||
if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end
|
||||
return VERDICT_DROP
|
||||
else
|
||||
error("synack_split: bad mode '"..mode.."'")
|
||||
end
|
||||
else
|
||||
instance_cutoff(ctx) -- mission complete
|
||||
end
|
||||
else
|
||||
instance_cutoff(ctx)
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=synack"
|
||||
-- standard args : rawsend, reconstruct, ipfrag
|
||||
function synack(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)
|
||||
dis.tcp.th_flags = bitor(dis.tcp.th_flags, TH_ACK)
|
||||
DLOG("synack: sending")
|
||||
rawsend_dissect_ipfrag(dis, desync_opts(desync))
|
||||
else
|
||||
instance_cutoff(ctx) -- mission complete
|
||||
end
|
||||
else
|
||||
instance_cutoff(ctx)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- nfqws1 : "--wssize"
|
||||
-- arg : wsize=N . tcp window size
|
||||
-- arg : scale=N . tcp option scale factor
|
||||
function wsize(ctx, desync)
|
||||
if desync.dis.tcp then
|
||||
if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK) == (TH_SYN + TH_ACK) then
|
||||
if wsize_rewrite(desync.dis, desync.arg) then
|
||||
return VERDICT_MODIFY
|
||||
end
|
||||
else
|
||||
instance_cutoff(ctx) -- mission complete
|
||||
end
|
||||
else
|
||||
instance_cutoff(ctx)
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--wsize"
|
||||
-- standard args : direction
|
||||
-- arg : wsize=N . tcp window size
|
||||
-- arg : scale=N . tcp option scale factor
|
||||
-- arg : forced_cutoff=<list> - comma separated list of payloads that trigger forced wssize cutoff. by default - any non-empty payload
|
||||
function wssize(ctx, desync)
|
||||
if not desync.dis.tcp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
local verdict = VERDICT_PASS
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
if direction_check(desync) then
|
||||
if wsize_rewrite(desync.dis, desync.arg) then
|
||||
verdict = VERDICT_MODIFY
|
||||
end
|
||||
if #desync.dis.payload>0 and (not desync.arg.forced_cutoff or in_list(desync.arg.forced_cutoff, desync.l7payload)) then
|
||||
DLOG("wssize: forced cutoff")
|
||||
instance_cutoff(ctx)
|
||||
end
|
||||
end
|
||||
return verdict
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=syndata"
|
||||
-- standard args : fooling, rawsend, reconstruct, ipfrag
|
||||
-- arg : blob=<blob> - fake payload. must fit to single packet. no segmentation possible. default - 16 zero bytes.
|
||||
-- arg : tls_mod=<list> - comma separated list of tls mods : rnd,rndsni,sni=<str>,dupsid,padencap
|
||||
function 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)
|
||||
dis.payload = blob(desync, desync.arg.blob, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
apply_fooling(desync, dis)
|
||||
if desync.arg.tls_mod then
|
||||
dis.payload = tls_mod(dis.payload, desync.arg.tls_mod, nil)
|
||||
end
|
||||
if b_debug then DLOG("syndata: "..hexdump_dlog(dis.payload)) end
|
||||
if rawsend_dissect_ipfrag(dis, desync_opts(desync)) then
|
||||
return VERDICT_DROP
|
||||
end
|
||||
else
|
||||
instance_cutoff(ctx) -- mission complete
|
||||
end
|
||||
else
|
||||
instance_cutoff(ctx)
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=fake"
|
||||
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct, ipfrag
|
||||
-- arg : blob=<blob> - fake payload
|
||||
-- arg : tls_mod=<list> - comma separated list of tls mods : rnd,rndsni,sni=<str>,dupsid,padencap
|
||||
function fake(ctx, desync)
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
-- by default process only outgoing known payloads
|
||||
if direction_check(desync) and payload_check(desync) then
|
||||
if replay_first(desync) then
|
||||
if not desync.arg.blob then
|
||||
error("fake: 'blob' arg required")
|
||||
end
|
||||
local fake_payload = blob(desync, desync.arg.blob)
|
||||
if desync.reasm_data and desync.arg.tls_mod then
|
||||
fake_payload = tls_mod(fake_payload, desync.arg.tls_mod, desync.reasm_data)
|
||||
end
|
||||
-- check debug to save CPU
|
||||
if b_debug then DLOG("fake: "..hexdump_dlog(fake_payload)) end
|
||||
rawsend_payload_segmented(desync,fake_payload)
|
||||
else
|
||||
DLOG("fake: not acting on further replay pieces")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=multisplit"
|
||||
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct, ipfrag
|
||||
-- arg : pos=<posmarker list> . position marker list. for example : "1,host,midsld+1,-10"
|
||||
-- arg : seqovl=N . decrease seq number of the first segment by N and fill N bytes with pattern (default - all zero)
|
||||
-- arg : seqovl_pattern=<blob> . override pattern
|
||||
function multisplit(ctx, desync)
|
||||
if not desync.dis.tcp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
-- by default process only outgoing known payloads
|
||||
local data = desync.reasm_data or desync.dis.payload
|
||||
if #data>0 and direction_check(desync) and payload_check(desync) then
|
||||
if replay_first(desync) then
|
||||
local spos = desync.arg.pos or "2"
|
||||
-- check debug to save CPU
|
||||
if b_debug then DLOG("multisplit: split pos: "..spos) end
|
||||
local pos = resolve_multi_pos(data, desync.l7payload, spos)
|
||||
if b_debug then DLOG("multisplit: resolved split pos: "..table.concat(zero_based_pos(pos)," ")) end
|
||||
delete_pos_1(pos) -- cannot split at the first byte
|
||||
if #pos>0 then
|
||||
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
|
||||
if i==0 and desync.arg.seqovl and tonumber(desync.arg.seqovl)>0 then
|
||||
seqovl = tonumber(desync.arg.seqovl)
|
||||
local pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00"
|
||||
part = pattern(pat,1,seqovl)..part
|
||||
end
|
||||
if b_debug then DLOG("multisplit: sending part "..(i+1).." "..(pos_start-1).."-"..(pos_end-1).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,pos_start-1-seqovl) then
|
||||
return VERDICT_PASS
|
||||
end
|
||||
end
|
||||
replay_drop_set(desync)
|
||||
return VERDICT_DROP
|
||||
else
|
||||
DLOG("multisplit: no valid split positions")
|
||||
end
|
||||
else
|
||||
DLOG("multisplit: not acting on further replay pieces")
|
||||
end
|
||||
-- drop replayed packets if reasm was sent successfully in splitted form
|
||||
if replay_drop(desync) then
|
||||
return VERDICT_DROP
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=multidisorder"
|
||||
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct, ipfrag
|
||||
-- arg : pos=<postmarker list> . position marker list. example : "1,host,midsld+1,-10"
|
||||
-- arg : seqovl=N . decrease seq number of the second segment in the original order by N and fill N bytes with pattern (default - all zero). N must be less than the first split pos.
|
||||
-- arg : seqovl_pattern=<blob> . override pattern
|
||||
function multidisorder(ctx, desync)
|
||||
if not desync.dis.tcp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
-- by default process only outgoing known payloads
|
||||
local data = desync.reasm_data or desync.dis.payload
|
||||
if #data>0 and direction_check(desync) and payload_check(desync) then
|
||||
if replay_first(desync) then
|
||||
local spos = desync.arg.pos or "2"
|
||||
-- check debug to save CPU
|
||||
if b_debug then DLOG("multidisorder: split pos: "..spos) end
|
||||
local pos = resolve_multi_pos(data, desync.l7payload, spos)
|
||||
if b_debug then DLOG("multidisorder: resolved split pos: "..table.concat(zero_based_pos(pos)," ")) end
|
||||
delete_pos_1(pos) -- cannot split at the first byte
|
||||
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)
|
||||
local seqovl=0
|
||||
if i==1 and desync.arg.seqovl then
|
||||
seqovl = resolve_pos(data, desync.l7payload, desync.arg.seqovl)
|
||||
if not seqovl then
|
||||
DLOG("multidisorder: seqovl cancelled because could not resolve marker '"..desync.arg.seqovl.."'")
|
||||
seqovl = 0
|
||||
else
|
||||
seqovl = seqovl - 1
|
||||
if seqovl>=(pos[1]-1) then
|
||||
DLOG("multidisorder: seqovl cancelled because seqovl "..seqovl.." is not less than the first split pos "..(pos[1]-1))
|
||||
seqovl = 0
|
||||
else
|
||||
local pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00"
|
||||
part = pattern(pat,1,seqovl)..part
|
||||
end
|
||||
end
|
||||
end
|
||||
if b_debug then DLOG("multidisorder: sending part "..(i+1).." "..(pos_start-1).."-"..(pos_end-1).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,pos_start-1-seqovl) then
|
||||
return VERDICT_PASS
|
||||
end
|
||||
end
|
||||
replay_drop_set(desync)
|
||||
return VERDICT_DROP
|
||||
else
|
||||
DLOG("multidisorder: no valid split positions")
|
||||
end
|
||||
else
|
||||
DLOG("multidisorder: not acting on further replay pieces")
|
||||
end
|
||||
-- drop replayed packets if reasm was sent successfully in splitted form
|
||||
if replay_drop(desync) then
|
||||
return VERDICT_DROP
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=hostfakesplit"
|
||||
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct. FOOLING AND REPEATS APPLIED ONLY TO FAKES.
|
||||
-- arg : host=<str> - hostname template. generate hosts like "random.template". example : e8nzn.vk.com
|
||||
-- arg : midhost=<posmarker> - additionally split segment containing host at specified posmarker. must be within host+1 .. endhost-1 or split won't happen. example : "midsld"
|
||||
-- arg : nofake1, nofake2 - do not send individual fakes
|
||||
-- arg : disorder_after=<posmarker> - send after_host part in 2 disordered segments. if posmarker is empty string use marker "-1"
|
||||
function hostfakesplit(ctx, desync)
|
||||
if not desync.dis.tcp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
-- by default process only outgoing known payloads
|
||||
local data = desync.reasm_data or desync.dis.payload
|
||||
if #data>0 and direction_check(desync) and payload_check(desync) then
|
||||
if replay_first(desync) then
|
||||
local pos = resolve_range(data, desync.l7payload, "host,endhost-1", true)
|
||||
if pos then
|
||||
if b_debug then DLOG("hostfakesplit: resolved host range: "..table.concat(zero_based_pos(pos)," ")) end
|
||||
|
||||
-- do not apply fooling to original parts except tcp_ts_up but apply ip_id
|
||||
local part, fakehost
|
||||
local opts_orig = {rawsend = rawsend_opts_base(desync), reconstruct = {}, ipfrag = {}, ipid = desync.arg, fooling = {tcp_ts_up = desync.arg.tcp_ts_up}}
|
||||
local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync), ipfrag = {}, ipid = desync.arg, fooling = desync.arg}
|
||||
|
||||
part = string.sub(data,1,pos[1]-1)
|
||||
if b_debug then DLOG("hostfakesplit: sending before_host part 0-"..(pos[1]-2).." len="..#part.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,0, opts_orig) then return VERDICT_PASS end
|
||||
|
||||
fakehost = genhost(pos[2]-pos[1]+1, desync.arg.host)
|
||||
|
||||
if not desync.arg.nofake1 then
|
||||
if b_debug then DLOG("hostfakesplit: sending fake host part (1) "..(pos[1]-1).."-"..(pos[2]-1).." len="..#fakehost.." : "..hexdump_dlog(fakehost)) end
|
||||
if not rawsend_payload_segmented(desync,fakehost,pos[1]-1, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
local midhost
|
||||
if desync.arg.midhost then
|
||||
midhost = resolve_pos(data,desync.l7payload,desync.arg.midhost)
|
||||
if not midhost then
|
||||
DLOG("hostfakesplit: cannot resolve midhost marker '"..desync.arg.midhost.."'")
|
||||
end
|
||||
DLOG("hosfakesplit: midhost marker resolved to "..midhost)
|
||||
if midhost<=pos[1] or midhost>pos[2] then
|
||||
DLOG("hostfakesplit: midhost is not inside the host range")
|
||||
midhost = nil
|
||||
end
|
||||
end
|
||||
-- if present apply ipfrag only to real host parts. fakes and parts outside of the host must be visible to DPI.
|
||||
if midhost then
|
||||
part = string.sub(data,pos[1],midhost-1)
|
||||
if b_debug then DLOG("hostfakesplit: sending real host part 1 "..(pos[1]-1).."-"..(midhost-2).." len="..#part.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,pos[1]-1, opts_orig) then return VERDICT_PASS end
|
||||
|
||||
part = string.sub(data,midhost,pos[2])
|
||||
if b_debug then DLOG("hostfakesplit: sending real host part 2 "..(midhost-1).."-"..(pos[2]-1).." len="..#part.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,midhost-1, opts_orig) then return VERDICT_PASS end
|
||||
else
|
||||
part = string.sub(data,pos[1],pos[2])
|
||||
if b_debug then DLOG("hostfakesplit: sending real host part "..(pos[1]-1).."-"..(pos[2]-1).." len="..#part.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,pos[1]-1, opts_orig) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
if not desync.arg.nofake2 then
|
||||
if b_debug then DLOG("hostfakesplit: sending fake host part (2) "..(pos[1]-1).."-"..(pos[2]-1).." len="..#fakehost.." : "..hexdump_dlog(fakehost)) end
|
||||
if not rawsend_payload_segmented(desync,fakehost,pos[1]-1, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
local disorder_after_pos
|
||||
if desync.arg.disorder_after then
|
||||
disorder_after_pos = resolve_pos(data, desync.l7payload, desync.arg.disorder_after=="" and "-1" or desync.arg.disorder_after)
|
||||
if disorder_after_pos then
|
||||
-- pos[2] points to the last letter of the host starting from 1
|
||||
if disorder_after_pos<=(pos[2]+1) then
|
||||
DLOG("hostfakesplit: disorder_after marker '"..(disorder_after_pos-1).."' resolved to pos not after after_host pos "..pos[2])
|
||||
disorder_after_pos = nil
|
||||
end
|
||||
|
||||
else
|
||||
DLOG("hostfakesplit: could not resolve disorder_after marker '"..desync.arg.disorder_after.."'")
|
||||
end
|
||||
end
|
||||
if disorder_after_pos then
|
||||
part = string.sub(data,disorder_after_pos)
|
||||
if b_debug then DLOG("hostfakesplit: sending after_host part (2) "..(disorder_after_pos-1).."-"..(#data-1).." len="..#part.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,disorder_after_pos-1, opts_orig) then return VERDICT_PASS end
|
||||
|
||||
part = string.sub(data,pos[2]+1,disorder_after_pos-1)
|
||||
if b_debug then DLOG("hostfakesplit: sending after_host part (1) "..pos[2].."-"..(disorder_after_pos-2).." len="..#part.." : "..hexdump_dlog(part)) end
|
||||
else
|
||||
part = string.sub(data,pos[2]+1)
|
||||
if b_debug then DLOG("hostfakesplit: sending after_host part "..pos[2].."-"..(#data-1).." len="..#part.." : "..hexdump_dlog(part)) end
|
||||
end
|
||||
if not rawsend_payload_segmented(desync,part,pos[2], opts_orig) then return VERDICT_PASS end
|
||||
|
||||
replay_drop_set(desync)
|
||||
return VERDICT_DROP
|
||||
else
|
||||
DLOG("hostfakesplit: host range cannot be resolved")
|
||||
end
|
||||
else
|
||||
DLOG("hostfakesplit: not acting on further replay pieces")
|
||||
end
|
||||
-- drop replayed packets if reasm was sent successfully in splitted form
|
||||
if replay_drop(desync) then
|
||||
return VERDICT_DROP
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=fakedsplit"
|
||||
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct. FOOLING AND REPEATS APPLIED ONLY TO FAKES.
|
||||
-- arg : pos=<posmarker> - split position marker
|
||||
-- arg : nofake1, nofake2, nofake3, nofake4 - do not send individual fakes
|
||||
-- arg : pattern=<blob> . fill fake parts with this pattern
|
||||
-- arg : seqovl=N . decrease seq number of the first segment by N and fill N bytes with pattern (default - all zero)
|
||||
-- arg : seqovl_pattern=<blob> . override seqovl pattern
|
||||
function fakedsplit(ctx, desync)
|
||||
if not desync.dis.tcp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
-- by default process only outgoing known payloads
|
||||
local data = desync.reasm_data or desync.dis.payload
|
||||
if #data>0 and direction_check(desync) and payload_check(desync) then
|
||||
if replay_first(desync) then
|
||||
local spos = desync.arg.pos or "2"
|
||||
local pos = resolve_pos(data, desync.l7payload, spos)
|
||||
if pos then
|
||||
if pos == 1 then
|
||||
DLOG("multidisorder: split pos resolved to 0. cannot split.")
|
||||
else
|
||||
if b_debug then DLOG("fakedsplit: resolved split pos: "..tostring(pos-1)) end
|
||||
|
||||
-- do not apply fooling to original parts except tcp_ts_up but apply ip_id
|
||||
local fake, fakepat, part, pat
|
||||
local opts_orig = {rawsend = rawsend_opts_base(desync), reconstruct = {}, ipfrag = {}, ipid = desync.arg, fooling = {tcp_ts_up = desync.arg.tcp_ts_up}}
|
||||
local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync), ipfrag = {}, ipid = desync.arg, fooling = desync.arg}
|
||||
|
||||
fakepat = desync.arg.pattern and blob(desync,desync.arg.pattern) or "\x00"
|
||||
|
||||
-- first fake
|
||||
fake = pattern(fakepat,1,pos-1)
|
||||
|
||||
if not desync.arg.nofake1 then
|
||||
if b_debug then DLOG("fakedsplit: sending fake part 1 (1) : 0-"..(pos-2).." len="..#fake.." : "..hexdump_dlog(fake)) end
|
||||
if not rawsend_payload_segmented(desync,fake,0, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
-- first real
|
||||
part = string.sub(data,1,pos-1)
|
||||
local seqovl=0
|
||||
if desync.arg.seqovl and tonumber(desync.arg.seqovl)>0 then
|
||||
seqovl = tonumber(desync.arg.seqovl)
|
||||
pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00"
|
||||
part = pattern(pat,1,seqovl)..part
|
||||
end
|
||||
if b_debug then DLOG("fakedsplit: sending real part 1 : 0-"..(pos-2).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,-seqovl, opts_orig) then return VERDICT_PASS end
|
||||
|
||||
-- first fake again
|
||||
if not desync.arg.nofake2 then
|
||||
if b_debug then DLOG("fakedsplit: sending fake part 1 (2) : 0-"..(pos-2).." len="..#fake.." : "..hexdump_dlog(fake)) end
|
||||
if not rawsend_payload_segmented(desync,fake,0, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
-- second fake
|
||||
fake = pattern(fakepat,pos,#data-pos+1)
|
||||
if not desync.arg.nofake3 then
|
||||
if b_debug then DLOG("fakedsplit: sending fake part 2 (1) : "..(pos-1).."-"..(#data-1).." len="..#fake.." : "..hexdump_dlog(fake)) end
|
||||
if not rawsend_payload_segmented(desync,fake,pos-1, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
-- second real
|
||||
part = string.sub(data,pos)
|
||||
if b_debug then DLOG("fakedsplit: sending real part 2 : "..(pos-1).."-"..(#data-1).." len="..#part.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,pos-1, opts_orig) then return VERDICT_PASS end
|
||||
|
||||
-- second fake again
|
||||
if not desync.arg.nofake4 then
|
||||
if b_debug then DLOG("fakedsplit: sending fake part 2 (2) : "..(pos-1).."-"..(#data-1).." len="..#fake.." : "..hexdump_dlog(fake)) end
|
||||
if not rawsend_payload_segmented(desync,fake,pos-1, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
replay_drop_set(desync)
|
||||
return VERDICT_DROP
|
||||
end
|
||||
else
|
||||
DLOG("fakedsplit: cannot resolve pos '"..desync.arg.pos.."'")
|
||||
end
|
||||
else
|
||||
DLOG("fakedsplit: not acting on further replay pieces")
|
||||
end
|
||||
-- drop replayed packets if reasm was sent successfully in splitted form
|
||||
if replay_drop(desync) then
|
||||
return VERDICT_DROP
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=fakeddisorder"
|
||||
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct. FOOLING AND REPEATS APPLIED ONLY TO FAKES.
|
||||
-- arg : pos=<posmarker> - split position marker
|
||||
-- arg : nofake1, nofake2, nofake3, nofake4 - do not send individual fakes
|
||||
-- arg : pattern=<blob> . fill fake parts with this pattern
|
||||
-- arg : seqovl=N . decrease seq number of the second segment by N and fill N bytes with pattern (default - all zero). N must be less than the split pos.
|
||||
-- arg : seqovl_pattern=<blob> . override seqovl pattern
|
||||
function fakeddisorder(ctx, desync)
|
||||
if not desync.dis.tcp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
-- by default process only outgoing known payloads
|
||||
local data = desync.reasm_data or desync.dis.payload
|
||||
if #data>0 and direction_check(desync) and payload_check(desync) then
|
||||
if replay_first(desync) then
|
||||
local spos = desync.arg.pos or "2"
|
||||
local pos = resolve_pos(data, desync.l7payload, spos)
|
||||
if pos then
|
||||
if pos == 1 then
|
||||
DLOG("multidisorder: split pos resolved to 0. cannot split.")
|
||||
else
|
||||
if b_debug then DLOG("fakeddisorder: resolved split pos: "..tostring(pos-1)) end
|
||||
|
||||
-- do not apply fooling to original parts except tcp_ts_up but apply ip_id
|
||||
local fake, part, pat
|
||||
local opts_orig = {rawsend = rawsend_opts_base(desync), reconstruct = {}, ipfrag = {}, ipid = desync.arg, fooling = {tcp_ts_up = desync.arg.tcp_ts_up}}
|
||||
local opts_fake = {rawsend = rawsend_opts(desync), reconstruct = reconstruct_opts(desync), ipfrag = {}, ipid = desync.arg, fooling = desync.arg}
|
||||
|
||||
fakepat = desync.arg.pattern and blob(desync,desync.arg.pattern) or "\x00"
|
||||
|
||||
-- second fake
|
||||
fake = pattern(fakepat,pos,#data-pos+1)
|
||||
if not desync.arg.nofake1 then
|
||||
if b_debug then DLOG("fakeddisorder: sending fake part 2 (1) : "..(pos-1).."-"..(#data-1).." len="..#fake.." : "..hexdump_dlog(fake)) end
|
||||
if not rawsend_payload_segmented(desync,fake,pos-1, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
-- second real
|
||||
part = string.sub(data,pos)
|
||||
local seqovl = 0
|
||||
if desync.arg.seqovl then
|
||||
seqovl = resolve_pos(data, desync.l7payload, desync.arg.seqovl)
|
||||
if seqovl then
|
||||
seqovl = seqovl - 1
|
||||
if seqovl>=(pos-1) then
|
||||
DLOG("fakeddisorder: seqovl cancelled because seqovl "..seqovl.." is not less than the split pos "..(pos-1))
|
||||
seqovl = 0
|
||||
else
|
||||
local pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00"
|
||||
part = pattern(pat,1,seqovl)..part
|
||||
end
|
||||
else
|
||||
DLOG("fakeddisorder: seqovl cancelled because could not resolve marker '"..desync.arg.seqovl.."'")
|
||||
seqovl = 0
|
||||
end
|
||||
end
|
||||
if b_debug then DLOG("fakeddisorder: sending real part 2 : "..(pos-1).."-"..(#data-1).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,pos-1-seqovl, opts_orig) then return VERDICT_PASS end
|
||||
|
||||
-- second fake again
|
||||
if not desync.arg.nofake2 then
|
||||
if b_debug then DLOG("fakeddisorder: sending fake part 2 (2) : "..(pos-1).."-"..(#data-1).." len="..#fake.." : "..hexdump_dlog(fake)) end
|
||||
if not rawsend_payload_segmented(desync,fake,pos-1, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
-- first fake
|
||||
fake = pattern(fakepat,1,pos-1)
|
||||
if not desync.arg.nofake3 then
|
||||
if b_debug then DLOG("fakeddisorder: sending fake part 1 (1) : 0-"..(pos-2).." len="..#fake.." : "..hexdump_dlog(fake)) end
|
||||
if not rawsend_payload_segmented(desync,fake,0, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
-- first real
|
||||
part = string.sub(data,1,pos-1)
|
||||
if b_debug then DLOG("fakeddisorder: sending real part 1 : 0-"..(pos-2).." len="..#part.." : "..hexdump_dlog(part)) end
|
||||
if not rawsend_payload_segmented(desync,part,0, opts_orig) then return VERDICT_PASS end
|
||||
|
||||
-- first fake again
|
||||
if not desync.arg.nofake4 then
|
||||
if b_debug then DLOG("fakeddisorder: sending fake part 1 (2) : 0-"..(pos-2).." len="..#fake.." : "..hexdump_dlog(fake)) end
|
||||
if not rawsend_payload_segmented(desync,fake,0, opts_fake) then return VERDICT_PASS end
|
||||
end
|
||||
|
||||
replay_drop_set(desync)
|
||||
return VERDICT_DROP
|
||||
end
|
||||
else
|
||||
DLOG("fakeddisorder: cannot resolve pos '"..desync.arg.pos.."'")
|
||||
end
|
||||
else
|
||||
DLOG("fakeddisorder: not acting on further replay pieces")
|
||||
end
|
||||
-- drop replayed packets if reasm was sent successfully in splitted form
|
||||
if replay_drop(desync) then
|
||||
return VERDICT_DROP
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : not available
|
||||
-- standard args : direction, payload, fooling, ip_id, rawsend, reconstruct, ipfrag
|
||||
-- arg : pos=<postmarker list> . position marker list. 2 pos required, only 2 first pos used. example : "host,endhost"
|
||||
-- arg : seqovl=N . decrease seq number of the first segment by N and fill N bytes with pattern (default - all zero)
|
||||
-- arg : seqovl_pattern=<blob> . override pattern
|
||||
function tcpseg(ctx, desync)
|
||||
if not desync.dis.tcp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
if not desync.arg.pos then
|
||||
error("tcpseg: no pos specified")
|
||||
end
|
||||
-- by default process only outgoing known payloads
|
||||
local data = desync.reasm_data or desync.dis.payload
|
||||
if #data>0 and direction_check(desync) and payload_check(desync) then
|
||||
if replay_first(desync) then
|
||||
if b_debug then DLOG("tcpseg: pos: "..desync.arg.pos) end
|
||||
-- always returns 2 positions or nil or causes error
|
||||
local pos = resolve_range(data, desync.l7payload, desync.arg.pos)
|
||||
if pos then
|
||||
-- check debug to save CPU
|
||||
if b_debug then DLOG("tcpseg: resolved range: "..table.concat(zero_based_pos(pos)," ")) end
|
||||
local part = string.sub(data,pos[1],pos[2])
|
||||
local seqovl=0
|
||||
if desync.arg.seqovl and tonumber(desync.arg.seqovl)>0 then
|
||||
seqovl = tonumber(desync.arg.seqovl)
|
||||
local pat = desync.arg.seqovl_pattern and blob(desync,desync.arg.seqovl_pattern) or "\x00"
|
||||
part = pattern(pat,1,seqovl)..part
|
||||
end
|
||||
if b_debug then DLOG("tcpseg: sending "..(pos[1]-1).."-"..(pos[2]-1).." len="..#part.." seqovl="..seqovl.." : "..hexdump_dlog(part)) end
|
||||
rawsend_payload_segmented(desync,part,pos[1]-1-seqovl)
|
||||
else
|
||||
DLOG("tcpseg: range cannot be resolved")
|
||||
end
|
||||
else
|
||||
DLOG("tcpseg: not acting on further replay pieces")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=udplen"
|
||||
-- standard args : direction, payload
|
||||
-- arg : min=N . do not act on payloads smaller than N bytes
|
||||
-- arg : max=N . do not act on payloads larger than N bytes
|
||||
-- arg : increment=N . 2 by default. negative increment shrinks the packet, positive grows it.
|
||||
-- arg : pattern=<blob> . used to fill extra bytes when length increases
|
||||
-- arg : pattern_offset=N . offset in the pattern. 0 by default
|
||||
function udplen(ctx, desync)
|
||||
if not desync.dis.udp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
if direction_check(desync) and payload_check(desync) then
|
||||
local len = #desync.dis.payload
|
||||
if (desync.arg.min and #desync.dis.payload < tonumber(desync.arg.min)) then
|
||||
DLOG("udplen: payload size "..len.." is less than the minimum size "..desync.arg.min)
|
||||
elseif (desync.arg.max and #desync.dis.payload > tonumber(desync.arg.max)) then
|
||||
DLOG("udplen: payload size "..len.." is more than the maximum size "..desync.arg.max)
|
||||
else
|
||||
local inc = desync.arg.increment and tonumber(desync.arg.increment) or 2
|
||||
if inc>0 then
|
||||
local pat = desync.arg.pattern and blob(desync,desync.arg.pattern) or "\x00"
|
||||
local pat_offset = desync.arg.pattern_offset and (tonumber(desync.arg.pattern_offset)+1) or 1
|
||||
desync.dis.payload = desync.dis.payload .. pattern(pat, pat_offset, inc)
|
||||
DLOG("udplen: "..len.." => "..#desync.dis.payload)
|
||||
return VERDICT_MODIFY
|
||||
elseif inc<0 then
|
||||
if (len+inc)<1 then
|
||||
DLOG("udplen: will not shrink to zero length")
|
||||
else
|
||||
desync.dis.payload = string.sub(desync.dis.payload,1,len+inc)
|
||||
DLOG("udplen: "..len.." => "..#desync.dis.payload)
|
||||
end
|
||||
return VERDICT_MODIFY
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- nfqws1 : "--dpi-desync=tamper" for dht proto
|
||||
-- standard args : direction
|
||||
-- arg : dn=N - message starts from "dN". 2 by default
|
||||
function dht_dn(ctx, desync)
|
||||
if not desync.dis.udp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
direction_cutoff_opposite(ctx, desync)
|
||||
if desync.l7payload=="dht" and direction_check(desync) then
|
||||
local N = tonumber(desync.arg.dn) or 2
|
||||
-- remove "d1" from the start not breaking bencode
|
||||
local prefix = "d"..tostring(N)..":"..string.rep("0",N).."1:x"
|
||||
desync.dis.payload = prefix..string.sub(desync.dis.payload,2)
|
||||
DLOG("dht_dn: tampered dht to start with '"..prefix.."' instead of 'd1:'")
|
||||
return VERDICT_MODIFY
|
||||
end
|
||||
end
|
||||
980
lua/zapret-lib.lua
Normal file
980
lua/zapret-lib.lua
Normal file
@@ -0,0 +1,980 @@
|
||||
HEXDUMP_DLOG_MAX = HEXDUMP_DLOG_MAX or 32
|
||||
NOT3=bitnot(3)
|
||||
NOT7=bitnot(7)
|
||||
math.randomseed(os.time())
|
||||
|
||||
-- prepare standard rawsend options from desync
|
||||
-- repeats - how many time send the packet
|
||||
-- ifout - override outbound interface (if --bind_fix4, --bind-fix6 enabled)
|
||||
-- fwmark - override fwmark. desync mark bit(s) will be set unconditionally
|
||||
function rawsend_opts(desync)
|
||||
return {
|
||||
repeats = desync.arg.repeats,
|
||||
ifout = desync.arg.ifout or desync.ifout,
|
||||
fwmark = desync.arg.fwmark or desync.fwmark
|
||||
}
|
||||
end
|
||||
-- only basic options. no repeats
|
||||
function rawsend_opts_base(desync)
|
||||
return {
|
||||
ifout = desync.arg.ifout or desync.ifout,
|
||||
fwmark = desync.arg.fwmark or desync.fwmark
|
||||
}
|
||||
end
|
||||
|
||||
-- prepare standard reconstruct options from desync
|
||||
-- badsum - make L4 checksum invalid
|
||||
-- ip6_preserve_next - use next protocol fields from dissect, do not auto fill values. can be set from code only, not from args
|
||||
-- ip6_last_proto - last ipv6 "next" protocol. used only by "reconstruct_ip6hdr". can be set from code only, not from args
|
||||
function reconstruct_opts(desync)
|
||||
return {
|
||||
badsum = desync.arg.badsum
|
||||
}
|
||||
end
|
||||
|
||||
-- combined desync opts
|
||||
function desync_opts(desync)
|
||||
return {
|
||||
rawsend = rawsend_opts(desync),
|
||||
reconstruct = reconstruct_opts(desync),
|
||||
ipfrag = desync.arg,
|
||||
ipid = desync.arg,
|
||||
fooling = desync.arg
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
-- convert binary string to hex data
|
||||
function string2hex(s)
|
||||
local ss = ""
|
||||
for i = 1, #s do
|
||||
if i>1 then
|
||||
ss = ss .. " "
|
||||
end
|
||||
ss = ss .. string.format("%02X", string.byte(s, i))
|
||||
end
|
||||
return ss
|
||||
end
|
||||
function has_nonprintable(s)
|
||||
return s:match("[^ -\\r\\n\\t]")
|
||||
end
|
||||
function make_readable(v)
|
||||
if type(v)=="string" then
|
||||
return string.gsub(v,"[^ -]",".");
|
||||
else
|
||||
return tostring(v)
|
||||
end
|
||||
end
|
||||
-- return hex dump of a binary string if it has nonprintable characters or string itself otherwise
|
||||
function str_or_hex(s)
|
||||
if has_nonprintable(s) then
|
||||
return string2hex(s)
|
||||
else
|
||||
return s
|
||||
end
|
||||
end
|
||||
-- print to DLOG any variable. tables are expanded in the tree form, unprintables strings are hex dumped
|
||||
function var_debug(v)
|
||||
local function dbg(v,level)
|
||||
if type(v)=="table" then
|
||||
for key, value in pairs(v) do
|
||||
DLOG(string.rep(" ",2*level).."."..key)
|
||||
dbg(v[key],level+1)
|
||||
end
|
||||
elseif type(v)=="string" then
|
||||
DLOG(string.rep(" ",2*level)..type(v).." "..str_or_hex(v))
|
||||
else
|
||||
DLOG(string.rep(" ",2*level)..type(v).." "..make_readable(v))
|
||||
end
|
||||
end
|
||||
dbg(v,0)
|
||||
end
|
||||
|
||||
-- make hex dump
|
||||
function hexdump(s,max)
|
||||
local l = max<#s and max or #s
|
||||
local ss = string.sub(s,1,l)
|
||||
return string2hex(ss)..(#s>max and " ... " or " " )..make_readable(ss)..(#s>max and " ... " or "" )
|
||||
end
|
||||
-- make hex dump limited by HEXDUMP_DLOG_MAX chars
|
||||
function hexdump_dlog(s)
|
||||
return hexdump(s,HEXDUMP_DLOG_MAX)
|
||||
end
|
||||
|
||||
-- make copy of an array recursively
|
||||
function deepcopy(orig, copies)
|
||||
copies = copies or {}
|
||||
local orig_type = type(orig)
|
||||
local copy
|
||||
if orig_type == 'table' then
|
||||
if copies[orig] then
|
||||
copy = copies[orig]
|
||||
else
|
||||
copy = {}
|
||||
copies[orig] = copy
|
||||
for orig_key, orig_value in next, orig, nil do
|
||||
copy[deepcopy(orig_key, copies)] = deepcopy(orig_value, copies)
|
||||
end
|
||||
setmetatable(copy, deepcopy(getmetatable(orig), copies))
|
||||
end
|
||||
else -- number, string, boolean, etc
|
||||
copy = orig
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
-- check if string 'v' in comma separated list 's'
|
||||
function in_list(s, v)
|
||||
if s then
|
||||
for elem in string.gmatch(s, "[^,]+") do
|
||||
if elem==v then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- blobs can be 0xHEX, field name in desync or global var
|
||||
-- if name is nil - return def
|
||||
function blob(desync, name, def)
|
||||
if not name or #name==0 then
|
||||
if def then
|
||||
return def
|
||||
else
|
||||
error("empty blob name")
|
||||
end
|
||||
end
|
||||
local blob
|
||||
if string.sub(name,1,2)=="0x" then
|
||||
blob = parse_hex(string.sub(name,3))
|
||||
if not blob then
|
||||
error("invalid hex string : "..name)
|
||||
end
|
||||
else
|
||||
blob = desync[name]
|
||||
if not blob then
|
||||
-- use global var if no field in dissect table
|
||||
blob = _G[name]
|
||||
if not blob then
|
||||
error("blob '"..name.."' unavailable")
|
||||
end
|
||||
end
|
||||
end
|
||||
return blob
|
||||
end
|
||||
|
||||
-- repeat pattern as needed to extract part of it with any length
|
||||
-- pat="12345" len=10 offset=4 => "4512345123"
|
||||
function pattern(pat, offset, len)
|
||||
if not pat or #pat==0 then
|
||||
error("pattern: bad or empty pattern")
|
||||
end
|
||||
local off = (offset-1) % #pat
|
||||
local pats = divint((len + #pat - 1), #pat) + (off==0 and 0 or 1)
|
||||
return string.sub(string.rep(pat,pats),off+1,off+len)
|
||||
end
|
||||
|
||||
-- decrease by 1 all number values in the array
|
||||
function zero_based_pos(a)
|
||||
if not a then return nil end
|
||||
local b={}
|
||||
for i,v in ipairs(a) do
|
||||
b[i] = type(a[i])=="number" and a[i] - 1 or a[i]
|
||||
end
|
||||
return b
|
||||
end
|
||||
|
||||
-- delete elements with number value 1
|
||||
function delete_pos_1(a)
|
||||
local i=1
|
||||
while i<=#a do
|
||||
if type(a[i])=="number" and a[i] == 1 then
|
||||
table.remove(a,i)
|
||||
else
|
||||
i = i+1
|
||||
end
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
-- find pos of the next eol and pos of the next non-eol character after eol
|
||||
function find_next_line(s, pos)
|
||||
local p1, p2
|
||||
p1 = string.find(s,"[\r\n]",pos)
|
||||
if p1 then
|
||||
p2 = p1
|
||||
p1 = p1-1
|
||||
if string.sub(s,p2,p2)=='\r' then p2=p2+1 end
|
||||
if string.sub(s,p2,p2)=='\n' then p2=p2+1 end
|
||||
if p2>#s then p2=nil end
|
||||
else
|
||||
p1 = #s
|
||||
end
|
||||
return p1,p2
|
||||
end
|
||||
|
||||
function http_dissect_header(header)
|
||||
local p1,p2
|
||||
p1,p2 = string.find(header,":")
|
||||
if p1 then
|
||||
p2=string.find(header,"[^ \t]",p2+1)
|
||||
return string.sub(header,1,p1-1), p2 and string.sub(header,p2) or "", p1-1, p2 or #header
|
||||
end
|
||||
return nil
|
||||
end
|
||||
-- make table with structured http header representation
|
||||
function http_dissect_headers(http, pos)
|
||||
local eol,pnext,header,value,idx,headers,pos_endheader,pos_startvalue
|
||||
headers={}
|
||||
while pos do
|
||||
eol,pnext = find_next_line(http,pos)
|
||||
header = string.sub(http,pos,eol)
|
||||
if #header == 0 then break end
|
||||
header,value,pos_endheader,pos_startvalue = http_dissect_header(header)
|
||||
if header then
|
||||
headers[string.lower(header)] = { header = header, value = value, pos_start = pos, pos_end = eol, pos_header_end = pos+pos_endheader-1, pos_value_start = pos+pos_startvalue-1 }
|
||||
end
|
||||
pos=pnext
|
||||
end
|
||||
return headers
|
||||
end
|
||||
-- make table with structured http request representation
|
||||
function http_dissect_req(http)
|
||||
if not http then return nil; end
|
||||
local eol,pnext,req,hdrpos
|
||||
local pos=1
|
||||
-- skip methodeol empty line(s)
|
||||
while pos do
|
||||
eol,pnext = find_next_line(http,pos)
|
||||
req = string.sub(http,pos,eol)
|
||||
pos=pnext
|
||||
if #req>0 then break end
|
||||
end
|
||||
hdrpos = pos
|
||||
if not req or #req==0 then return nil end
|
||||
pos = string.find(req,"[ \t]")
|
||||
if not pos then return nil end
|
||||
local method = string.sub(req,1,pos-1);
|
||||
pos = string.find(req,"[^ \t]",pos+1)
|
||||
if not pos then return nil end
|
||||
pnext = string.find(req,"[ \t]",pos+1)
|
||||
if not pnext then pnext = #http + 1 end
|
||||
local uri = string.sub(req,pos,pnext-1)
|
||||
return { method = method, uri = uri, headers = http_dissect_headers(http,hdrpos) }
|
||||
end
|
||||
|
||||
-- convert comma separated list of tcp flags to tcp.th_flags bit field
|
||||
function parse_tcp_flags(s)
|
||||
local flags={FIN=TH_FIN, SYN=TH_SYN, RST=TH_RST, PSH=TH_PUSH, PUSH=TH_PUSH, ACK=TH_ACK, URG=TH_URG, ECE=TH_ECE, CWR=TH_CWR}
|
||||
local f=0
|
||||
local s_upper = string.upper(s)
|
||||
for flag in string.gmatch(s_upper, "[^,]+") do
|
||||
if flags[flag] then
|
||||
f = bitor(f,flags[flag])
|
||||
else
|
||||
error("tcp flag '"..flag.."' is invalid")
|
||||
end
|
||||
end
|
||||
return f
|
||||
end
|
||||
|
||||
-- find first tcp options of specified kind in dissect.tcp.options
|
||||
function find_tcp_option(options, kind)
|
||||
if options then
|
||||
for i, opt in pairs(options) do
|
||||
if opt.kind==kind then return i end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- find first ipv6 extension header of specified protocol in dissect.ip6.exthdr
|
||||
function find_ip6_exthdr(exthdr, proto)
|
||||
if exthdr then
|
||||
for i, hdr in pairs(exthdr) do
|
||||
if hdr.type==proto then return i end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- insert ipv6 extension header at specified index. fix next proto chain
|
||||
function insert_ip6_exthdr(ip6, idx, header_type, data)
|
||||
local prev
|
||||
if not ip6.exthdr then ip6.exthdr={} end
|
||||
if not idx then
|
||||
-- insert to the end
|
||||
idx = #ip6.exthdr+1
|
||||
elseif idx<0 or idx>(#ip6.exthdr+1) then
|
||||
error("insert_ip6_exthdr: invalid index "..idx)
|
||||
end
|
||||
if idx==1 then
|
||||
prev = ip6.ip6_nxt
|
||||
ip6.ip6_nxt = header_type
|
||||
else
|
||||
prev = ip6.exthdr[idx-1].next
|
||||
ip6.exthdr[idx-1].next = header_type
|
||||
end
|
||||
table.insert(ip6.exthdr, idx, {type = header_type, data = data, next = prev})
|
||||
end
|
||||
-- delete ipv6 extension header at specified index. fix next proto chain
|
||||
function del_ip6_exthdr(ip6, idx)
|
||||
if idx<=0 or idx>#ip6.exthdr then
|
||||
error("delete_ip6_exthdr: nonexistent index "..idx)
|
||||
end
|
||||
local nxt = ip6.exthdr[idx].next
|
||||
if idx==1 then
|
||||
ip6.ip6_nxt = nxt
|
||||
else
|
||||
ip6.exthdr[idx-1].next = nxt
|
||||
end
|
||||
table.remove(ip6.exthdr, idx)
|
||||
end
|
||||
-- fills next proto fields in ipv6 header and extension headers
|
||||
function fix_ip6_next(ip6, last_proto)
|
||||
if ip6.exthdr and #ip6.exthdr>0 then
|
||||
for i=1,#ip6.exthdr do
|
||||
if i==1 then
|
||||
-- first header
|
||||
ip6.ip6_nxt = ip6.exthdr[i].type
|
||||
end
|
||||
ip6.exthdr[i].next = i==#ip6.exthdr and (last_proto or IPPROTO_NONE) or ip6.exthdr[i+1].type
|
||||
end
|
||||
else
|
||||
-- no headers
|
||||
ip6.ip6_nxt = last_proto or IPPROTO_NONE
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- parse autottl : delta,min-max
|
||||
function parse_autottl(s)
|
||||
if s then
|
||||
local delta,min,max = string.match(s,"([-+]?%d+),(%d+)-(%d+)")
|
||||
min = tonumber(min)
|
||||
max = tonumber(max)
|
||||
delta = tonumber(delta)
|
||||
if not delta or min>max then
|
||||
error("parse_autottl: invalid value '"..s.."'")
|
||||
end
|
||||
return {delta=delta,min=min,max=max}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- calculate ttl value based on incoming_ttl and parsed attl definition (delta,min-max)
|
||||
function autottl(incoming_ttl, attl)
|
||||
local function hop_count_guess(incoming_ttl)
|
||||
-- 18.65.168.125 ( cloudfront ) 255
|
||||
-- 157.254.246.178 128
|
||||
-- 1.1.1.1 64
|
||||
-- guess original ttl. consider path lengths less than 32 hops
|
||||
|
||||
local orig
|
||||
|
||||
if incoming_ttl>223 then
|
||||
orig=255
|
||||
elseif incoming_ttl<128 and incoming_ttl>96 then
|
||||
orig=128
|
||||
elseif incoming_ttl<64 and incoming_ttl>32 then
|
||||
orig=64
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
return orig-incoming_ttl
|
||||
end
|
||||
-- return guessed fake ttl value. 0 means unsuccessfull, should not perform autottl fooling
|
||||
local function autottl_eval(hop_count, attl)
|
||||
local d,fake
|
||||
|
||||
d = hop_count + attl.delta
|
||||
|
||||
if d<attl.min then fake=attl.min
|
||||
elseif d>attl.max then fake=attl.max
|
||||
else fake=d
|
||||
end
|
||||
|
||||
if attl.delta<0 and fake>=hop_count or attl.delta>=0 and fake<hop_count then return nil end
|
||||
return fake
|
||||
end
|
||||
local hops = hop_count_guess(incoming_ttl)
|
||||
if not hops then return nil end
|
||||
return autottl_eval(hops,attl)
|
||||
end
|
||||
|
||||
-- apply standard header mods :
|
||||
|
||||
-- ip_ttl=N - set ipv.ip_ttl to N
|
||||
-- ip6_ttl=N - set ip6.ip6_hlim to N
|
||||
-- ip_autottl=delta,min-max - set ip.ip_ttl to auto discovered ttl
|
||||
-- ip6_autottl=delta,min-max - set ip.ip_ttl to auto discovered ttl
|
||||
|
||||
-- ip6_hopbyhop[=hex] - add hopbyhop ipv6 header with optional data. data size must be 6+N*8. all zero by default.
|
||||
-- ip6_hopbyhop2 - add 2 hopbyhop ipv6 headers with optional data. data size must be 6+N*8. all zero by default.
|
||||
-- ip6_destopt[=hex] - add destopt ipv6 header with optional data. data size must be 6+N*8. all zero by default.
|
||||
-- ip6_routing[=hex] - add routing ipv6 header with optional data. data size must be 6+N*8. all zero by default.
|
||||
-- ip6_ah[=hex] - add authentication ipv6 header with optional data. data size must be 6+N*4. 0000 + 4 random bytes by default.
|
||||
|
||||
-- tcp_seq=N - add N to tcp.th_seq
|
||||
-- tcp_ack=N - add N to tcp.th_ack
|
||||
-- tcp_ts=N - add N to timestamp value
|
||||
-- tcp_md5[=hex] - add MD5 header with optional 16-byte data. all zero by default.
|
||||
-- tcp_flags_set=<list> - set tcp flags in comma separated list
|
||||
-- tcp_unflags_set=<list> - unset tcp flags in comma separated list
|
||||
-- tcp_ts_up - move timestamp tcp option to the top if it's present. this allows linux not to accept badack segments without badseq. this is very strange discovery but it works.
|
||||
|
||||
-- fool - custom fooling function : fool_func(dis, fooling_options)
|
||||
function apply_fooling(desync, dis, fooling_options)
|
||||
local function prepare_bin(hex,def)
|
||||
local bin = parse_hex(hex)
|
||||
if not bin then error("apply_fooling: invalid hex string '"..hex.."'") end
|
||||
return #bin>0 and bin or def
|
||||
end
|
||||
local function ttl_discover(arg_ttl,arg_autottl)
|
||||
local ttl
|
||||
if arg_autottl and desync.track then
|
||||
if desync.track.incoming_ttl then
|
||||
-- use lua_cache to store discovered autottl
|
||||
if type(desync.track.lua_state.autottl_cache)~="table" then desync.track.lua_state.autottl_cache={} end
|
||||
if type(desync.track.lua_state.autottl_cache[desync.func_instance])~="table" then desync.track.lua_state.autottl_cache[desync.func_instance]={} end
|
||||
if not desync.track.lua_state.autottl_cache[desync.func_instance].autottl_found then
|
||||
desync.track.lua_state.autottl_cache[desync.func_instance].autottl = autottl(desync.track.incoming_ttl,parse_autottl(arg_autottl))
|
||||
if desync.track.lua_state.autottl_cache[desync.func_instance].autottl then
|
||||
desync.track.lua_state.autottl_cache[desync.func_instance].autottl_found = true
|
||||
DLOG("apply_fooling: discovered autottl "..desync.track.lua_state.autottl_cache[desync.func_instance].autottl)
|
||||
else
|
||||
DLOG("apply_fooling: could not discover autottl")
|
||||
end
|
||||
elseif desync.track.lua_state.autottl_cache[desync.func_instance].autottl then
|
||||
DLOG("apply_fooling: using cached autottl "..desync.track.lua_state.autottl_cache[desync.func_instance].autottl)
|
||||
end
|
||||
ttl=desync.track.lua_state.autottl_cache[desync.func_instance].autottl
|
||||
else
|
||||
DLOG("apply_fooling: cannot apply autottl because incoming ttl unknown")
|
||||
end
|
||||
end
|
||||
if not ttl and tonumber(arg_ttl) then
|
||||
ttl = tonumber(arg_ttl)
|
||||
end
|
||||
return ttl
|
||||
end
|
||||
local function move_ts_top()
|
||||
local tsidx = find_tcp_option(dis.tcp.options, TCP_KIND_TS)
|
||||
if tsidx and tsidx>1 then
|
||||
table.insert(dis.tcp.options, 1, dis.tcp.options[tsidx])
|
||||
table.remove(dis.tcp.options, tsidx + 1)
|
||||
end
|
||||
end
|
||||
-- take default fooling from desync.arg
|
||||
if not fooling_options then fooling_options = desync.arg end
|
||||
-- use current packet if dissect not given
|
||||
if not dis then dis = desync.dis end
|
||||
if dis.tcp then
|
||||
if tonumber(fooling_options.tcp_seq) then
|
||||
dis.tcp.th_seq = dis.tcp.th_seq + fooling_options.tcp_seq
|
||||
end
|
||||
if tonumber(fooling_options.tcp_ack) then
|
||||
dis.tcp.th_ack = dis.tcp.th_ack + fooling_options.tcp_ack
|
||||
end
|
||||
if fooling_options.tcp_flags_unset then
|
||||
dis.tcp.th_flags = bitand(dis.tcp.th_flags, bitnot(parse_tcp_flags(fooling_options.tcp_flags_unset)))
|
||||
end
|
||||
if fooling_options.tcp_flags_set then
|
||||
dis.tcp.th_flags = bitor(dis.tcp.th_flags, parse_tcp_flags(fooling_options.tcp_flags_set))
|
||||
end
|
||||
if tonumber(fooling_options.tcp_ts) then
|
||||
local idx = find_tcp_option(dis.tcp.options,TCP_KIND_TS)
|
||||
if idx and (dis.tcp.options[idx].data and #dis.tcp.options[idx].data or 0)==8 then
|
||||
dis.tcp.options[idx].data = bu32(u32(dis.tcp.options[idx].data)+fooling_options.tcp_ts)..string.sub(dis.tcp.options[idx].data,5)
|
||||
else
|
||||
DLOG("apply_fooling: timestamp tcp option not present or invalid")
|
||||
end
|
||||
end
|
||||
if fooling_options.tcp_md5 then
|
||||
if find_tcp_option(dis.tcp.options,TCP_KIND_MD5) then
|
||||
DLOG("apply_fooling: md5 option already present")
|
||||
else
|
||||
table.insert(dis.tcp.options,{kind=TCP_KIND_MD5, data=prepare_bin(fooling_options.tcp_md5,brandom(16))})
|
||||
end
|
||||
end
|
||||
if fooling_options.tcp_ts_up then
|
||||
move_ts_top(dis.tcp.options)
|
||||
end
|
||||
end
|
||||
if dis.ip6 then
|
||||
local bin
|
||||
if fooling_options.ip6_hopbyhop_x2 then
|
||||
bin = prepare_bin(fooling_options.ip6_hopbyhop2_x2,"\x00\x00\x00\x00\x00\x00")
|
||||
insert_ip6_exthdr(dis.ip6,nil,IPPROTO_HOPOPTS,bin)
|
||||
insert_ip6_exthdr(dis.ip6,nil,IPPROTO_HOPOPTS,bin)
|
||||
elseif fooling_options.ip6_hopbyhop then
|
||||
bin = prepare_bin(fooling_options.ip6_hopbyhop,"\x00\x00\x00\x00\x00\x00")
|
||||
insert_ip6_exthdr(dis.ip6,nil,IPPROTO_HOPOPTS,bin)
|
||||
end
|
||||
-- for possible unfragmentable part
|
||||
if fooling_options.ip6_destopt then
|
||||
bin = prepare_bin(fooling_options.ip6_destopt,"\x00\x00\x00\x00\x00\x00")
|
||||
insert_ip6_exthdr(dis.ip6,nil,IPPROTO_DSTOPTS,bin)
|
||||
end
|
||||
if fooling_options.ip6_routing then
|
||||
bin = prepare_bin(fooling_options.ip6_routing,"\x00\x00\x00\x00\x00\x00")
|
||||
insert_ip6_exthdr(dis.ip6,nil,IPPROTO_ROUTING,bin)
|
||||
end
|
||||
-- for possible fragmentable part
|
||||
if fooling_options.ip6_destopt2 then
|
||||
bin = prepare_bin(fooling_options.ip6_destopt2,"\x00\x00\x00\x00\x00\x00")
|
||||
insert_ip6_exthdr(dis.ip6,nil,IPPROTO_DSTOPTS,bin)
|
||||
end
|
||||
if fooling_options.ip6_ah then
|
||||
-- by default truncated authentication header - only 6 bytes
|
||||
bin = prepare_bin(fooling_options.ip6_ah,"\x00\x00"..brandom(4))
|
||||
insert_ip6_exthdr(dis.ip6,nil,IPPROTO_AH,bin)
|
||||
end
|
||||
end
|
||||
if dis.ip then
|
||||
local ttl = ttl_discover(fooling_options.ip_ttl,fooling_options.ip_autottl)
|
||||
if ttl then dis.ip.ip_ttl = ttl end
|
||||
end
|
||||
if dis.ip6 then
|
||||
local ttl = ttl_discover(fooling_options.ip6_ttl,fooling_options.ip6_autottl)
|
||||
if ttl then dis.ip6.ip6_hlim = ttl end
|
||||
end
|
||||
|
||||
if fooling_options.fool and #fooling_options.fool>0 then
|
||||
if type(_G[fooling_options.fool])=="function" then
|
||||
DLOG("apply_fooling: calling '"..fooling_options.fool.."'")
|
||||
_G[fooling_options.fool](dis, fooling_options)
|
||||
else
|
||||
error("apply_fooling: fool function '"..tostring(fooling_options.fool).."' does not exist")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- assign dis.ip.ip_id value according to policy in ipid_options or desync.arg. apply def or "seq" policy if no ip_id options
|
||||
-- ip_id=seq|rnd|zero|none
|
||||
-- ip_id_conn - in 'seq' mode save current ip_id in track.lua_state to use it between packets
|
||||
-- remember ip_id in desync
|
||||
function apply_ip_id(desync, dis, ipid_options, def)
|
||||
-- use current packet if dissect not given
|
||||
if not dis then dis = desync.dis end
|
||||
if dis.ip then -- ip_id is ipv4 only, ipv6 doesn't have it
|
||||
-- take default ipid options from desync.arg
|
||||
if not ipid_options then ipid_options = desync.arg end
|
||||
local mode = ipid_options.ip_id or def or "seq"
|
||||
if mode == "seq" then
|
||||
if desync.track and ipid_options.ip_id_conn then
|
||||
dis.ip.ip_id = desync.track.lua_state.ip_id or dis.ip.ip_id
|
||||
desync.track.lua_state.ip_id = dis.ip.ip_id + 1
|
||||
else
|
||||
dis.ip.ip_id = desync.ip_id or dis.ip.ip_id
|
||||
desync.ip_id = dis.ip.ip_id + 1
|
||||
end
|
||||
elseif mode == "zero" then
|
||||
dis.ip.ip_id = 0
|
||||
elseif mode == "rnd" then
|
||||
dis.ip.ip_id = math.random(1,0xFFFF)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- return length of ipv4 or ipv6 header without options and extension headers. should be 20 for ipv4 and 40 for ipv6.
|
||||
function l3_base_len(dis)
|
||||
if dis.ip then
|
||||
return IP_BASE_LEN
|
||||
elseif dis.ip6 then
|
||||
return IP6_BASE_LEN
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
-- return length of ipv4 options or summary length of all ipv6 extension headers
|
||||
-- ip6_exthdr_last_idx - count lengths for headers up to this index
|
||||
function l3_extra_len(dis, ip6_exthdr_last_idx)
|
||||
local l=0
|
||||
if dis.ip then
|
||||
if dis.ip.options then
|
||||
l = bitand(#dis.ip.options+3,NOT3)
|
||||
end
|
||||
elseif dis.ip6 and dis.ip6.exthdr then
|
||||
local ct
|
||||
if ip6_exthdr_last_idx and ip6_exthdr_last_idx<=#dis.ip6.exthdr then
|
||||
ct = ip6_exthdr_last_idx
|
||||
else
|
||||
ct = #dis.ip6.exthdr
|
||||
end
|
||||
for i=1, ct do
|
||||
if dis.ip6.exthdr[i].type == IPPROTO_AH then
|
||||
-- length in 32-bit words
|
||||
l = l + bitand(3+2+#dis.ip6.exthdr[i].data,NOT3)
|
||||
else
|
||||
-- length in 64-bit words
|
||||
l = l + bitand(7+2+#dis.ip6.exthdr[i].data,NOT7)
|
||||
end
|
||||
end
|
||||
end
|
||||
return l
|
||||
end
|
||||
-- return length of ipv4/ipv6 header with options/extension headers
|
||||
function l3_len(dis)
|
||||
return l3_base_len(dis)+l3_extra_len(dis)
|
||||
end
|
||||
-- return length of tcp/udp headers without options. should be 20 for tcp and 8 for udp.
|
||||
function l4_base_len(dis)
|
||||
if dis.tcp then
|
||||
return TCP_BASE_LEN
|
||||
elseif dis.udp then
|
||||
return UDP_BASE_LEN
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
-- return length of tcp options or 0 if not tcp
|
||||
function l4_extra_len(dis)
|
||||
local l=0
|
||||
if dis.tcp and dis.tcp.options then
|
||||
for i=1, #dis.tcp.options do
|
||||
l = l + 1
|
||||
if dis.tcp.options[i].kind~=TCP_KIND_NOOP and dis.tcp.options[i].kind~=TCP_KIND_END then
|
||||
l = l + 1
|
||||
if dis.tcp.options[i].data then l = l + #dis.tcp.options[i].data end
|
||||
end
|
||||
end
|
||||
-- 4 byte aligned
|
||||
l = bitand(3+l,NOT3)
|
||||
end
|
||||
return l
|
||||
end
|
||||
-- return length of tcp header with options or base length of udp header - 8 bytes
|
||||
function l4_len(dis)
|
||||
return l4_base_len(dis)+l4_extra_len(dis)
|
||||
end
|
||||
-- return summary extra length of ipv4/ipv6 and tcp headers. 0 if no options, no ext headers
|
||||
function l3l4_extra_len(dis)
|
||||
return l3_extra_len(dis)+l4_extra_len(dis)
|
||||
end
|
||||
-- return summary length of ipv4/ipv6 and tcp/udp headers
|
||||
function l3l4_len(dis)
|
||||
return l3_len(dis)+l4_len(dis)
|
||||
end
|
||||
-- return summary length of ipv4/ipv6 , tcp/udp headers and payload
|
||||
function packet_len(dis)
|
||||
return l3l4_len(dis) + #dis.payload
|
||||
end
|
||||
|
||||
function rawsend_dissect_ipfrag(dis, options)
|
||||
if options and options.ipfrag and options.ipfrag.ipfrag then
|
||||
local frag_func = options.ipfrag.ipfrag=="" and "ipfrag2" or options.ipfrag.ipfrag
|
||||
if type(_G[frag_func]) ~= "function" then
|
||||
error("rawsend_dissect_ipfrag: ipfrag function '"..tostring(frag_func).."' does not exist")
|
||||
end
|
||||
local fragments = _G[frag_func](dis, options.ipfrag)
|
||||
|
||||
-- allow ipfrag function to do extheader magic with non-standard "next protocol"
|
||||
-- NOTE : dis.ip6 must have valid next protocol fields !!!!!
|
||||
local reconstruct_frag = options.reconstruct and deepcopy(options.reconstruct) or {}
|
||||
reconstruct_frag.ip6_preserve_next = true
|
||||
|
||||
if fragments then
|
||||
if options.ipfrag.ipfrag_disorder then
|
||||
for i=#fragments,1,-1 do
|
||||
DLOG("sending ip fragment "..i)
|
||||
-- C function
|
||||
if not rawsend_dissect(fragments[i], options.rawsend, reconstruct_frag) then return false end
|
||||
end
|
||||
else
|
||||
for i, d in pairs(fragments) do
|
||||
DLOG("sending ip fragment "..i)
|
||||
-- C function
|
||||
if not rawsend_dissect(d, options.rawsend, reconstruct_frag) then return false end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
-- ipfrag failed. send unfragmented
|
||||
end
|
||||
-- C function
|
||||
return rawsend_dissect(dis, options and options.rawsend, options and options.reconstruct)
|
||||
end
|
||||
|
||||
-- send dissect with tcp segmentation based on mss value. appply specified rawsend options.
|
||||
function rawsend_dissect_segmented(desync, dis, mss, options)
|
||||
local discopy = deepcopy(dis)
|
||||
apply_ip_id(desync, discopy, options and options.ipid)
|
||||
apply_fooling(desync, discopy, options and options.fooling)
|
||||
|
||||
if dis.tcp then
|
||||
local extra_len = l3l4_extra_len(dis)
|
||||
if extra_len >= mss then return false end
|
||||
local max_data = mss - extra_len
|
||||
if #discopy.payload > max_data then
|
||||
local pos=1
|
||||
local len
|
||||
|
||||
while pos <= #dis.payload do
|
||||
len = #dis.payload - pos + 1
|
||||
if len > max_data then len = max_data end
|
||||
discopy.payload = string.sub(dis.payload,pos,pos+len-1)
|
||||
if not rawsend_dissect_ipfrag(discopy, options) then
|
||||
-- stop if failed
|
||||
return false
|
||||
end
|
||||
discopy.tcp.th_seq = discopy.tcp.th_seq + len
|
||||
pos = pos + len
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
-- no reason to segment
|
||||
return rawsend_dissect_ipfrag(discopy, options)
|
||||
end
|
||||
|
||||
-- send specified payload based on existing L3/L4 headers in the dissect. add seq to tcp.th_seq.
|
||||
function rawsend_payload_segmented(desync, payload, seq, options)
|
||||
options = options or desync_opts(desync)
|
||||
local dis = deepcopy(desync.dis)
|
||||
if payload then dis.payload = payload end
|
||||
if dis.tcp and seq then
|
||||
dis.tcp.th_seq = dis.tcp.th_seq + seq
|
||||
end
|
||||
return rawsend_dissect_segmented(desync, dis, desync.tcp_mss, options)
|
||||
end
|
||||
|
||||
|
||||
-- check if desync.outgoing comply with arg.dir or def if it's not present or "out" of they are not present both. dir can be "in","out","any"
|
||||
function direction_check(desync, def)
|
||||
local dir = desync.arg.dir or def or "out"
|
||||
return desync.outgoing and desync.arg.dir~="in" or not desync.outgoing and dir~="out"
|
||||
end
|
||||
-- if dir "in" or "out" cutoff current desync function from opposite direction
|
||||
function direction_cutoff_opposite(ctx, desync, def)
|
||||
local dir = desync.arg.dir or def or "out"
|
||||
if dir=="out" then
|
||||
-- cutoff in
|
||||
instance_cutoff(ctx, false)
|
||||
elseif dir=="in" then
|
||||
-- cutoff out
|
||||
instance_cutoff(ctx, true)
|
||||
end
|
||||
end
|
||||
-- check if desync payload type comply with payload type list in arg.payload
|
||||
-- if arg.payload is not present - check if desync payload is not "empty" and not "unknown" (nfqws1 behavior without "--desync-any-protocol" option)
|
||||
function payload_check(desync)
|
||||
if desync.arg.payload and desync.arg.payload~="known" then
|
||||
if not in_list(desync.arg.payload, "all") and not in_list(desync.arg.payload, desync.l7payload) then
|
||||
DLOG("payload_check: payload '"..desync.l7payload.."' does not pass '"..desync.arg.payload.."' filter")
|
||||
return false
|
||||
end
|
||||
else
|
||||
if desync.l7payload=="empty" or desync.l7payload=="unknown" then
|
||||
DLOG("payload_check: payload filter accepts only known protocols")
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- return name of replay drop field in track.lua_state for the current desync function instance
|
||||
function replay_drop_key(desync)
|
||||
return desync.func_instance .. "_replay_drop"
|
||||
end
|
||||
-- set/unset replay drop flag in track.lua_state for the current desync function instance
|
||||
function replay_drop_set(desync, v)
|
||||
if desync.track then
|
||||
if v == nil then v=true end
|
||||
local rdk = replay_drop_key(desync)
|
||||
if v then
|
||||
if desync.replay then desync.track.lua_state[replay_drop_key] = true end
|
||||
else
|
||||
desync.track.lua_state[replay_drop_key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
-- auto unset replay drop flag if desync is not replay or it's the last replay piece
|
||||
-- return true if the caller should return VERDICT_DROP
|
||||
function replay_drop(desync)
|
||||
if desync.track then
|
||||
local drop = desync.replay and desync.track.lua_state[replay_drop_key]
|
||||
if not desync.replay or desync.replay_piece_last then
|
||||
-- replay stopped or last piece of reasm
|
||||
replay_drop_set(desync, false)
|
||||
end
|
||||
if drop then
|
||||
DLOG("dropping replay packet because reasm was already sent")
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
-- true if desync is not replay or it's the first replay piece
|
||||
function replay_first(desync)
|
||||
return not desync.replay or desync.replay_piece==1
|
||||
end
|
||||
|
||||
-- generate random host
|
||||
-- template "google.com", len=16 : h82aj.google.com
|
||||
-- template "google.com", len=11 : .google.com
|
||||
-- template "google.com", len=10 : google.com
|
||||
-- template "google.com", len=7 : gle.com
|
||||
-- no template, len=6 : b8c54a
|
||||
-- no template, len=7 : u9a.edu
|
||||
-- no template, len=10 : jgha7c.com
|
||||
function genhost(len, template)
|
||||
if template and #template>0 then
|
||||
if len <= #template then
|
||||
return string.sub(template,#template-len+1)
|
||||
elseif len==(#template+1) then
|
||||
return "."..template
|
||||
else
|
||||
return brandom_az(1)..brandom_az09(len-#template-2).."."..template
|
||||
end
|
||||
else
|
||||
if len>=7 then
|
||||
local tlds = {"com","org","net","edu","gov","biz"}
|
||||
local tld = tlds[math.random(#tlds)]
|
||||
return brandom_az(1)..brandom_az09(len-#tld-1-1).."."..tld
|
||||
else
|
||||
return brandom_az(1)..brandom_az09(len-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- arg : wsize=N . tcp window size
|
||||
-- arg : scale=N . tcp option scale factor
|
||||
-- return : true of changed anything
|
||||
function wsize_rewrite(dis, arg)
|
||||
local b = false
|
||||
if arg.wsize then
|
||||
local wsize = tonumber(arg.wsize)
|
||||
DLOG("window size "..dis.tcp.th_win.." => "..wsize)
|
||||
dis.tcp.th_win = tonumber(arg.wsize)
|
||||
b = true
|
||||
end
|
||||
if arg.scale then
|
||||
local scale = tonumber(arg.scale)
|
||||
local i = find_tcp_option(dis.tcp.options, TCP_KIND_SCALE)
|
||||
if i then
|
||||
local oldscale = u8(dis.tcp.options[i].data)
|
||||
if scale>oldscale then
|
||||
DLOG("not increasing scale factor")
|
||||
elseif scale<oldscale then
|
||||
DLOG("scale factor "..oldscale.." => "..scale)
|
||||
dis.tcp.options[i].data = bu8(scale)
|
||||
b = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return b
|
||||
end
|
||||
|
||||
-- standard fragmentation to 2 ip fragments
|
||||
-- function returns 2 dissects with fragments
|
||||
-- option : ipfrag_pos_udp - udp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 8
|
||||
-- option : ipfrag_pos_tcp - tcp frag position. ipv4 : starting from L4 header. ipb6: starting from fragmentable part. must be multiple of 8. default 32
|
||||
-- option : ipfrag_next - next protocol field in ipv6 fragment extenstion header of the second fragment. same as first by default.
|
||||
-- option : ipfrag_disorder - send fragments from last to first
|
||||
function ipfrag2(dis, ipfrag_options)
|
||||
local function frag_idx(exthdr)
|
||||
-- fragment header after hopbyhop, destopt, routing
|
||||
-- allow second destopt header to be in fragmentable part
|
||||
-- test case : --lua-desync=send:ipfrag:ipfrag_pos_tcp=40:ip6_hopbyhop:ip6_destopt:ip6_destopt2
|
||||
-- WINDOWS may not send second ipv6 fragment with next protocol 60 (destopt)
|
||||
-- test case windows : --lua-desync=send:ipfrag:ipfrag_pos_tcp=40:ip6_hopbyhop:ip6_destopt:ip6_destopt2:ipfrag_next=255
|
||||
if exthdr then
|
||||
local first_destopts
|
||||
for i=1,#exthdr do
|
||||
if exthdr[i].type==IPPROTO_DSTOPTS then
|
||||
first_destopts = i
|
||||
break
|
||||
end
|
||||
end
|
||||
for i=#exthdr,1,-1 do
|
||||
if exthdr[i].type==IPPROTO_HOPOPTS or exthdr[i].type==IPPROTO_ROUTING or (exthdr[i].type==IPPROTO_DSTOPTS and i==first_destopts) then
|
||||
return i+1
|
||||
end
|
||||
end
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
local pos
|
||||
local dis1, dis2
|
||||
local l3
|
||||
|
||||
if dis.tcp then
|
||||
pos = ipfrag_options.ipfrag_pos_tcp or 32
|
||||
elseif dis.udp then
|
||||
pos = ipfrag_options.ipfrag_pos_udp or 8
|
||||
else
|
||||
pos = ipfrag_options.ipfrag_pos or 32
|
||||
end
|
||||
|
||||
DLOG("ipfrag2")
|
||||
|
||||
if not pos then
|
||||
error("ipfrag2: no frag position")
|
||||
end
|
||||
l3 = l3_len(dis)
|
||||
if bitand(pos,7)~=0 then
|
||||
error("ipfrag2: frag position must be multiple of 8")
|
||||
end
|
||||
if (pos+l3)>0xFFFF then
|
||||
error("ipfrag2: too high frag offset")
|
||||
end
|
||||
local plen = l3 + l4_len(dis) + #dis.payload
|
||||
if (pos+l3)>=plen then
|
||||
DLOG("ipfrag2: ip frag pos exceeds packet length. ipfrag cancelled.")
|
||||
return nil
|
||||
end
|
||||
|
||||
if dis.ip then
|
||||
-- ipv4 frag is done by both lua and C part
|
||||
-- lua code must correctly set ip_len, IP_MF and ip_off and provide full unfragmented payload
|
||||
-- ip_len must be set to valid value as it would appear in the fragmented packet
|
||||
-- ip_off must be set to fragment offset and IP_MF bit must be set if it's not the last fragment
|
||||
-- C code constructs unfragmented packet then moves everything after ip header according to ip_off and ip_len
|
||||
|
||||
-- ip_id must not be zero or fragment will be dropped
|
||||
local ip_id = dis.ip.ip_id==0 and math.random(1,0xFFFF) or dis.ip.ip_id
|
||||
dis1 = deepcopy(dis)
|
||||
-- ip_len holds the whole packet length starting from the ip header. it includes ip, transport headers and payload
|
||||
dis1.ip.ip_len = l3 + pos -- ip header + first part up to frag pos
|
||||
dis1.ip.ip_off = IP_MF -- offset 0, IP_MF - more fragments
|
||||
dis1.ip.ip_id = ip_id
|
||||
dis2 = deepcopy(dis)
|
||||
dis2.ip.ip_off = bitrshift(pos,3) -- offset = frag pos, IP_MF - not set
|
||||
dis2.ip.ip_len = plen - pos -- unfragmented packet length - frag pos
|
||||
dis2.ip.ip_id = ip_id
|
||||
end
|
||||
|
||||
if dis.ip6 then
|
||||
-- ipv6 frag is done by both lua and C part
|
||||
-- lua code must insert fragmentation extension header at any desirable position, fill fragment offset, more fragments flag and ident
|
||||
-- lua must set up ip6_plen as it would appear in the fragmented packet
|
||||
-- C code constructs unfragmented packet then moves fragmentable part as needed
|
||||
|
||||
local idxfrag = frag_idx(dis.ip6.exthdr)
|
||||
local l3extra = l3_extra_len(dis, idxfrag-1) + 8 -- all ext headers before frag + 8 bytes for frag header
|
||||
local ident = math.random(1,0xFFFFFFFF)
|
||||
|
||||
dis1 = deepcopy(dis)
|
||||
insert_ip6_exthdr(dis1.ip6, idxfrag, IPPROTO_FRAGMENT, bu16(IP6F_MORE_FRAG)..bu32(ident))
|
||||
dis1.ip6.ip6_plen = l3extra + pos
|
||||
dis2 = deepcopy(dis)
|
||||
insert_ip6_exthdr(dis2.ip6, idxfrag, IPPROTO_FRAGMENT, bu16(pos)..bu32(ident))
|
||||
-- only next proto of the first fragment is considered by standard
|
||||
-- fragments with non-zero offset can have different "next protocol" field
|
||||
-- this can be used to evade protection systems
|
||||
if ipfrag_options.ipfrag_next then
|
||||
dis2.ip6.exthdr[idxfrag].next = tonumber(ipfrag_options.ipfrag_next)
|
||||
end
|
||||
dis2.ip6.ip6_plen = plen - IP6_BASE_LEN + 8 - pos -- packet len without frag + 8 byte frag header - ipv6 base header
|
||||
end
|
||||
|
||||
return {dis1,dis2}
|
||||
end
|
||||
581
lua/zapret-tests.lua
Normal file
581
lua/zapret-tests.lua
Normal file
@@ -0,0 +1,581 @@
|
||||
-- nfqws2 C functions tests
|
||||
-- to run : --lua-init=@zapret-lib.lua --lua-init=@zapret-tests.lua --lua-init="test_all()"
|
||||
|
||||
function test_assert(b)
|
||||
assert(b, "test failed")
|
||||
end
|
||||
|
||||
function test_run(tests)
|
||||
for k,f in pairs(tests) do
|
||||
f()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function test_all()
|
||||
test_run({test_crypto, test_bin, test_ipstr, test_dissect, test_csum, test_resolve, test_rawsend})
|
||||
end
|
||||
|
||||
|
||||
function test_crypto()
|
||||
test_run({test_random, test_aes, test_aes_gcm, test_hkdf, test_hash})
|
||||
end
|
||||
|
||||
function test_random()
|
||||
local rnds={}
|
||||
for i=1,20 do
|
||||
local rnd = bcryptorandom(math.random(10,20))
|
||||
print("random: "..string2hex(rnd))
|
||||
test_assert(not rnds[rnd]) -- should not be repeats
|
||||
rnds[rnd] = true
|
||||
end
|
||||
end
|
||||
|
||||
function test_hash()
|
||||
local hashes={}
|
||||
for i=1,5 do
|
||||
local rnd = brandom(math.random(5,64))
|
||||
print("data: "..string2hex(rnd))
|
||||
for k,sha in pairs({"sha256","sha224"}) do
|
||||
local hsh = hash(sha, rnd)
|
||||
print(sha..": "..string2hex(hsh))
|
||||
local hsh2 = hash(sha, rnd)
|
||||
test_assert(hsh==hsh2)
|
||||
test_assert(not hashes[hsh])
|
||||
hashes[hsh] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function test_hkdf()
|
||||
local nblob = 2
|
||||
local okms = {}
|
||||
for nsalt=1,nblob do
|
||||
local salt = brandom(math.random(10,20))
|
||||
for nikm=1,nblob do
|
||||
local ikm = brandom(math.random(5,10))
|
||||
for ninfo=1,nblob do
|
||||
local info = brandom(math.random(5,10))
|
||||
local okm_prev
|
||||
for k,sha in pairs({"sha256","sha224"}) do
|
||||
for k,okml in pairs({8, 16, 50}) do
|
||||
local okm_prev
|
||||
local okm
|
||||
print("* hkdf "..sha)
|
||||
print("salt: "..string2hex(salt))
|
||||
print("ikm : "..string2hex(ikm))
|
||||
print("info: "..string2hex(info))
|
||||
print("okml: "..tostring(okml))
|
||||
okm = hkdf(sha, salt, ikm, info, okml)
|
||||
test_assert(okm)
|
||||
print("okm: "..string2hex(okm))
|
||||
if okms[okm] then
|
||||
print("duplicate okm !")
|
||||
end
|
||||
okms[okm] = true
|
||||
|
||||
test_assert(not okm_prev or okm_prev==string.sub(okm, 1, #okm_prev))
|
||||
okm_prev = okm
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function test_aes()
|
||||
local clear_text="test "..brandom_az09(11)
|
||||
local iv, key, encrypted, decrypted
|
||||
|
||||
for key_size=16,32,8 do
|
||||
local key = brandom(key_size)
|
||||
|
||||
print()
|
||||
print("* aes test key_size "..tostring(key_size))
|
||||
|
||||
print("clear text: "..clear_text);
|
||||
|
||||
print("* encrypting")
|
||||
encrypted = aes(true, key, clear_text)
|
||||
print("encrypted: "..str_or_hex(encrypted))
|
||||
|
||||
print("* decrypting everything good")
|
||||
decrypted = aes(false, key, encrypted)
|
||||
print("decrypted: "..str_or_hex(decrypted))
|
||||
print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" )
|
||||
test_assert(decrypted==clear_text)
|
||||
|
||||
print("* decrypting bad payload with good key")
|
||||
decrypted = aes(false, key, brandom(16))
|
||||
print("decrypted: "..str_or_hex(decrypted))
|
||||
print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" )
|
||||
test_assert(decrypted~=clear_text)
|
||||
|
||||
print("* decrypting good payload with bad key")
|
||||
decrypted = aes(false, brandom(key_size), encrypted)
|
||||
print("decrypted: "..str_or_hex(decrypted))
|
||||
print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" )
|
||||
test_assert(decrypted~=clear_text)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function test_aes_gcm()
|
||||
local authenticated_data = "authenticated message "..brandom_az09(math.random(10,50))
|
||||
local clear_text="test message "..brandom_az09(math.random(10,50))
|
||||
local iv, key, encrypted, atag, decrypted, atag2
|
||||
|
||||
for key_size=16,32,8 do
|
||||
iv = brandom(12)
|
||||
key = brandom(key_size)
|
||||
|
||||
print()
|
||||
print("* aes_gcm test key_size "..tostring(key_size))
|
||||
|
||||
print("clear text: "..clear_text);
|
||||
print("authenticated data: "..authenticated_data);
|
||||
|
||||
print("* encrypting")
|
||||
encrypted, atag = aes_gcm(true, key, iv, clear_text, authenticated_data)
|
||||
print("encrypted: "..str_or_hex(encrypted))
|
||||
print("auth tag: "..string2hex(atag))
|
||||
|
||||
print("* decrypting everything good")
|
||||
decrypted, atag2 = aes_gcm(false, key, iv, encrypted, authenticated_data)
|
||||
print("decrypted: "..str_or_hex(decrypted))
|
||||
print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" )
|
||||
test_assert(decrypted==clear_text)
|
||||
print("auth tag: "..string2hex(atag2))
|
||||
print( atag==atag2 and "TAG OK" or "TAG ERROR" )
|
||||
test_assert(atag==atag2)
|
||||
|
||||
print("* decrypting bad payload with good key/iv and correct authentication data")
|
||||
decrypted, atag2 = aes_gcm(false, key, iv, brandom(#encrypted), authenticated_data)
|
||||
print("decrypted: "..str_or_hex(decrypted))
|
||||
print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" )
|
||||
test_assert(decrypted~=clear_text)
|
||||
print("auth tag: "..string2hex(atag2))
|
||||
print( atag==atag2 and "TAG OK" or "TAG ERROR" )
|
||||
test_assert(atag~=atag2)
|
||||
|
||||
print("* decrypting good payload with good key/iv and incorrect authentication data")
|
||||
decrypted, atag2 = aes_gcm(false, key, iv, encrypted, authenticated_data.."x")
|
||||
print("decrypted: "..str_or_hex(decrypted))
|
||||
print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" )
|
||||
test_assert(decrypted==clear_text)
|
||||
print("auth tag: "..string2hex(atag2))
|
||||
print( atag==atag2 and "TAG OK" or "TAG ERROR" )
|
||||
test_assert(atag~=atag2)
|
||||
|
||||
print("* decrypting good payload with bad key, good iv and correct authentication data")
|
||||
decrypted, atag2 = aes_gcm(false, brandom(key_size), iv, encrypted, authenticated_data)
|
||||
print("decrypted: "..str_or_hex(decrypted))
|
||||
print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" )
|
||||
test_assert(decrypted~=clear_text)
|
||||
print("auth tag: "..string2hex(atag2))
|
||||
print( atag==atag2 and "TAG OK" or "TAG ERROR" )
|
||||
test_assert(atag~=atag2)
|
||||
|
||||
print("* decrypting good payload with good key, bad iv and correct authentication data")
|
||||
decrypted, atag2 = aes_gcm(false, key, brandom(12), encrypted, authenticated_data)
|
||||
print("decrypted: "..str_or_hex(decrypted))
|
||||
print( decrypted==clear_text and "DECRYPT OK" or "DECRYPT ERROR" )
|
||||
test_assert(decrypted~=clear_text)
|
||||
print("auth tag: "..string2hex(atag2))
|
||||
print( atag==atag2 and "TAG OK" or "TAG ERROR" )
|
||||
test_assert(atag~=atag2)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function test_ub()
|
||||
for k,f in pairs({{u8,bu8,0xFF,8}, {u16,bu16,0xFFFF,16}, {u24,bu24,0xFFFFFF,24}, {u32,bu32,0xFFFFFFFF,32}}) do
|
||||
local v = math.random(0,f[3])
|
||||
local pos = math.random(1,20)
|
||||
local s = brandom(pos-1)..f[2](v)..brandom(20)
|
||||
local v2 = f[1](s,pos)
|
||||
print("u"..tostring(f[4]).." pos="..tostring(pos).." "..tostring(v).." "..tostring(v2))
|
||||
test_assert(v==v2)
|
||||
end
|
||||
end
|
||||
|
||||
function test_bit()
|
||||
local v, v2, v3, v4, b1, b2, pow
|
||||
|
||||
v = math.random(0,0xFFFFFFFFFFFF)
|
||||
b1 = math.random(1,15)
|
||||
|
||||
v2 = bitrshift(v, b1)
|
||||
pow = 2^b1
|
||||
v3 = divint(v, pow)
|
||||
print(string.format("rshift(0x%X,%u) = 0x%X 0x%X/%u = 0x%X", v,b1,v2, v,pow,v3))
|
||||
test_assert(v2==v3)
|
||||
|
||||
v2 = bitlshift(v, b1)
|
||||
pow = 2^b1
|
||||
v3 = v * pow
|
||||
print(string.format("lshift(0x%X,%u) = 0x%X 0x%X*%u = 0x%X", v,b1,v2, v,pow,v3))
|
||||
test_assert(v2==v3)
|
||||
|
||||
v2 = math.random(0,0xFFFFFFFFFFFF)
|
||||
v3 = bitxor(v, v2)
|
||||
v4 = bitor(v, v2) - bitand(v, v2)
|
||||
print(string.format("xor(0x%X,0x%X) = %X or/and/minus = %X", v, v2, v3, v4))
|
||||
test_assert(v3==v4)
|
||||
|
||||
b2 = b1 + math.random(1,31)
|
||||
v2 = bitget(v, b1, b2)
|
||||
pow = 2^(b2-b1+1) - 1
|
||||
v3 = bitand(bitrshift(v,b1), pow)
|
||||
print(string.format("bitget(0x%X,%u,%u) = 0x%X bitand/bitrshift/pow = 0x%X", v, b1, b2, v2, v3))
|
||||
test_assert(v2==v3)
|
||||
|
||||
v4 = math.random(0,pow)
|
||||
v2 = bitset(v, b1, b2, v4)
|
||||
v3 = bitor(bitlshift(v4, b1), bitand(v, bitnot(bitlshift(pow, b1))))
|
||||
print(string.format("bitset(0x%X,%u,%u,0x%X) = 0x%X bitand/bitnot/bitlshift/pow = 0x%X", v, b1, b2, v4, v2, v3))
|
||||
test_assert(v2==v3)
|
||||
end
|
||||
|
||||
function test_bin()
|
||||
test_run({test_ub, test_bit})
|
||||
end
|
||||
|
||||
|
||||
function test_ipstr()
|
||||
local s_ip, ip, s_ip2
|
||||
|
||||
s_ip = string.format("%u.%u.%u.%u", math.random(0,255), math.random(0,255), math.random(0,255), math.random(0,255));
|
||||
ip = pton(s_ip)
|
||||
s_ip2 = ntop(ip)
|
||||
print("IP: "..s_ip)
|
||||
print("IPBIN: "..string2hex(ip))
|
||||
print("IP2: "..s_ip2)
|
||||
test_assert(s_ip==s_ip2)
|
||||
|
||||
s_ip = string.format("%x:%x:%x:%x:%x:%x:%x:%x", math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF), math.random(1,0xFFFF));
|
||||
ip = pton(s_ip)
|
||||
s_ip2 = ntop(ip)
|
||||
print("IP: "..s_ip)
|
||||
print("IPBIN: "..string2hex(ip))
|
||||
print("IP2: "..s_ip2)
|
||||
test_assert(s_ip==s_ip2)
|
||||
end
|
||||
|
||||
|
||||
function test_dissect()
|
||||
local dis, raw1, raw2
|
||||
|
||||
for i=1,20 do
|
||||
print("* dissect test "..tostring(i))
|
||||
|
||||
local ip_tcp = {
|
||||
ip = {
|
||||
ip_tos = math.random(0,255),
|
||||
ip_id = math.random(0,0xFFFF),
|
||||
ip_off = 0,
|
||||
ip_ttl = math.random(0,255),
|
||||
ip_p = IPPROTO_TCP,
|
||||
ip_src = brandom(4),
|
||||
ip_dst = brandom(4),
|
||||
options = brandom(math.random(0,40))
|
||||
},
|
||||
tcp = {
|
||||
th_sport = math.random(0,0xFFFF),
|
||||
th_dport = math.random(0,0xFFFF),
|
||||
th_seq = math.random(0,0xFFFFFFFF),
|
||||
th_ack = math.random(0,0xFFFFFFFF),
|
||||
th_x2 = math.random(0,0xF),
|
||||
th_flags = math.random(0,0xFF),
|
||||
th_win = math.random(0,0xFFFF),
|
||||
th_urp = math.random(0,0xFFFF),
|
||||
options = {
|
||||
{ kind = 1 },
|
||||
{ kind = 0xE0, data = brandom(math.random(1,10)) },
|
||||
{ kind = 1 },
|
||||
{ kind = 0xE1, data = brandom(math.random(1,10)) },
|
||||
{ kind = 0 }
|
||||
}
|
||||
},
|
||||
payload = brandom(math.random(0, 20))
|
||||
}
|
||||
raw1 = reconstruct_dissect(ip_tcp)
|
||||
print("IP+TCP : "..string2hex(raw1))
|
||||
dis1 = dissect(raw1);
|
||||
raw2 = reconstruct_dissect(dis1)
|
||||
dis2 = dissect(raw2);
|
||||
print("IP+TCP2: "..string2hex(raw2))
|
||||
print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" )
|
||||
test_assert(raw1==raw2)
|
||||
|
||||
local ip6_udp = {
|
||||
ip6 = {
|
||||
ip6_flow = 0x60000000 + math.random(0,0xFFFFFFF),
|
||||
ip6_hlim = math.random(1,0xFF),
|
||||
ip6_src = brandom(16),
|
||||
ip6_dst = brandom(16),
|
||||
exthdr = {
|
||||
{ type = IPPROTO_HOPOPTS, data = brandom(6+8*math.random(0,2)) },
|
||||
{ type = IPPROTO_AH, data = brandom(6+4*math.random(0,4)) }
|
||||
}
|
||||
},
|
||||
udp = {
|
||||
uh_sport = math.random(0,0xFFFF),
|
||||
uh_dport = math.random(0,0xFFFF)
|
||||
},
|
||||
payload = brandom(math.random(0, 20))
|
||||
}
|
||||
|
||||
raw1 = reconstruct_dissect(ip6_udp)
|
||||
print("IP6+UDP : "..string2hex(raw1))
|
||||
dis1 = dissect(raw1);
|
||||
raw2 = reconstruct_dissect(dis1)
|
||||
dis2 = dissect(raw2);
|
||||
print("IP6+UDP2: "..string2hex(raw2))
|
||||
print( raw1==raw2 and "DISSECT OK" or "DISSECT FAILED" )
|
||||
test_assert(raw1==raw2)
|
||||
end
|
||||
end
|
||||
|
||||
function test_csum()
|
||||
local payload = brandom(math.random(10,20))
|
||||
local ip4b, ip6b, raw, tcpb, udpb, dis1, dis2
|
||||
local ip = {
|
||||
ip_tos = math.random(0,255),
|
||||
ip_id = math.random(0,0xFFFF),
|
||||
ip_len = math.random(0,0xFFFF),
|
||||
ip_off = 0,
|
||||
ip_ttl = math.random(0,255),
|
||||
ip_p = IPPROTO_TCP,
|
||||
ip_src = brandom(4),
|
||||
ip_dst = brandom(4),
|
||||
options = brandom(4*math.random(0,10))
|
||||
}
|
||||
ip4b = reconstruct_iphdr(ip)
|
||||
raw = bu8(0x40 + 5 + #ip.options/4) ..
|
||||
bu8(ip.ip_tos) ..
|
||||
bu16(ip.ip_len) ..
|
||||
bu16(ip.ip_id) ..
|
||||
bu16(ip.ip_off) ..
|
||||
bu8(ip.ip_ttl) ..
|
||||
bu8(ip.ip_p) ..
|
||||
bu16(0) ..
|
||||
ip.ip_src .. ip.ip_dst ..
|
||||
ip.options
|
||||
raw = csum_ip4_fix(raw)
|
||||
print( raw==ip4b and "IP4 RECONSTRUCT+CSUM OK" or "IP4 RECONSTRUCT+CSUM FAILED" )
|
||||
test_assert(raw==ip4b)
|
||||
|
||||
|
||||
local tcp = {
|
||||
th_sport = math.random(0,0xFFFF),
|
||||
th_dport = math.random(0,0xFFFF),
|
||||
th_seq = math.random(0,0xFFFFFFFF),
|
||||
th_ack = math.random(0,0xFFFFFFFF),
|
||||
th_x2 = math.random(0,0xF),
|
||||
th_flags = math.random(0,0xFF),
|
||||
th_win = math.random(0,0xFFFF),
|
||||
th_urp = math.random(0,0xFFFF),
|
||||
options = {
|
||||
{ kind = 1 },
|
||||
{ kind = 0xE0, data = brandom(math.random(1,10)) },
|
||||
{ kind = 1 },
|
||||
{ kind = 0xE1, data = brandom(math.random(1,10)) },
|
||||
{ kind = 0 }
|
||||
}
|
||||
}
|
||||
tcpb = reconstruct_tcphdr(tcp)
|
||||
raw = bu16(tcp.th_sport) ..
|
||||
bu16(tcp.th_dport) ..
|
||||
bu32(tcp.th_seq) ..
|
||||
bu32(tcp.th_ack) ..
|
||||
bu8(l4_len({tcp = tcp}) * 4 + tcp.th_x2) ..
|
||||
bu8(tcp.th_flags) ..
|
||||
bu16(tcp.th_win) ..
|
||||
bu16(0) ..
|
||||
bu16(tcp.th_urp) ..
|
||||
bu8(tcp.options[1].kind)..
|
||||
bu8(tcp.options[2].kind)..bu8(2 + #tcp.options[2].data)..tcp.options[2].data ..
|
||||
bu8(tcp.options[3].kind)..
|
||||
bu8(tcp.options[4].kind)..bu8(2 + #tcp.options[4].data)..tcp.options[4].data ..
|
||||
bu8(tcp.options[5].kind)
|
||||
raw = raw .. string.rep(bu8(TCP_KIND_NOOP), bitand(4-bitand(#raw,3),3))
|
||||
print( raw==tcpb and "TCP RECONSTRUCT OK" or "TCP RECONSTRUCT FAILED" )
|
||||
test_assert(raw==tcpb)
|
||||
|
||||
raw = reconstruct_dissect({ip=ip, tcp=tcp, payload=payload})
|
||||
dis1 = dissect(raw)
|
||||
tcpb = csum_tcp_fix(ip4b,tcpb,payload)
|
||||
dis2 = dissect(ip4b..tcpb..payload)
|
||||
print( dis1.tcp.th_sum==dis2.tcp.th_sum and "TCP+IP4 CSUM OK" or "TCP+IP4 CSUM FAILED" )
|
||||
test_assert(dis1.tcp.th_sum==dis2.tcp.th_sum)
|
||||
|
||||
|
||||
local ip6 = {
|
||||
ip6_flow = 0x60000000 + math.random(0,0xFFFFFFF),
|
||||
ip6_hlim = math.random(1,0xFF),
|
||||
ip6_src = brandom(16),
|
||||
ip6_dst = brandom(16),
|
||||
exthdr = {
|
||||
{ type = IPPROTO_HOPOPTS, data = brandom(6+8*math.random(0,2)) }
|
||||
}
|
||||
}
|
||||
ip6.ip6_plen = packet_len({ip6=ip6,tcp=tcp,payload=payload}) - IP6_BASE_LEN
|
||||
ip6b = reconstruct_ip6hdr(ip6, {ip6_last_proto=IPPROTO_TCP})
|
||||
raw = bu32(ip6.ip6_flow) ..
|
||||
bu16(ip6.ip6_plen) ..
|
||||
bu8(ip6.exthdr[1].type) ..
|
||||
bu8(ip6.ip6_hlim) ..
|
||||
ip6.ip6_src .. ip6.ip6_dst ..
|
||||
bu8(IPPROTO_TCP) ..
|
||||
bu8((#ip6.exthdr[1].data+2)/8 - 1) ..
|
||||
ip6.exthdr[1].data
|
||||
print( raw==ip6b and "IP6 RECONSTRUCT OK" or "IP6 RECONSTRUCT FAILED" )
|
||||
test_assert(raw==ip6b)
|
||||
|
||||
raw = reconstruct_dissect({ip6=ip6, tcp=tcp, payload=payload})
|
||||
dis1 = dissect(raw)
|
||||
tcpb = csum_tcp_fix(ip6b,tcpb,payload)
|
||||
dis2 = dissect(ip6b..tcpb..payload)
|
||||
print( dis1.tcp.th_sum==dis2.tcp.th_sum and "TCP+IP6 CSUM OK" or "TCP+IP6 CSUM FAILED" )
|
||||
test_assert(dis1.tcp.th_sum==dis2.tcp.th_sum)
|
||||
|
||||
|
||||
ip.ip_p = IPPROTO_UDP
|
||||
ip4b = reconstruct_iphdr(ip)
|
||||
ip6.ip6_plen = packet_len({ip6=ip6,udp=udp,payload=payload}) - IP6_BASE_LEN
|
||||
ip6b = reconstruct_ip6hdr(ip6, {ip6_last_proto=IPPROTO_UDP})
|
||||
|
||||
local udp = {
|
||||
uh_sport = math.random(0,0xFFFF),
|
||||
uh_dport = math.random(0,0xFFFF),
|
||||
uh_ulen = UDP_BASE_LEN + #payload
|
||||
}
|
||||
|
||||
udpb = reconstruct_udphdr(udp)
|
||||
raw = bu16(udp.uh_sport) ..
|
||||
bu16(udp.uh_dport) ..
|
||||
bu16(udp.uh_ulen) ..
|
||||
bu16(0)
|
||||
print( raw==udpb and "UDP RECONSTRUCT OK" or "UDP RECONSTRUCT FAILED" )
|
||||
test_assert(raw==udpb)
|
||||
|
||||
raw = reconstruct_dissect({ip=ip, udp=udp, payload=payload})
|
||||
dis1 = dissect(raw)
|
||||
udpb = csum_udp_fix(ip4b,udpb,payload)
|
||||
dis2 = dissect(ip4b..udpb..payload)
|
||||
print( dis1.udp.uh_sum==dis2.udp.uh_sum and "UDP+IP4 CSUM OK" or "UDP+IP4 CSUM FAILED" )
|
||||
test_assert(dis1.udp.uh_sum==dis2.udp.uh_sum)
|
||||
|
||||
raw = reconstruct_dissect({ip6=ip6, udp=udp, payload=payload})
|
||||
dis1 = dissect(raw)
|
||||
udpb = csum_udp_fix(ip6b,udpb,payload)
|
||||
dis2 = dissect(ip6b..udpb..payload)
|
||||
print( dis1.udp.uh_sum==dis2.udp.uh_sum and "UDP+IP6 CSUM OK" or "UDP+IP6 CSUM FAILED" )
|
||||
test_assert(dis1.udp.uh_sum==dis2.udp.uh_sum)
|
||||
end
|
||||
|
||||
function test_resolve()
|
||||
local pos
|
||||
|
||||
pos = zero_based_pos(resolve_multi_pos(fake_default_tls,"tls_client_hello","1,extlen,sniext,host,sld,midsld,endsld,endhost,-5"))
|
||||
test_assert(pos)
|
||||
print("resolve_multi_pos tls : "..table.concat(pos," "))
|
||||
pos = zero_based_pos(resolve_range(fake_default_tls,"tls_client_hello","host,endhost"))
|
||||
test_assert(pos)
|
||||
print("resolve_range tls : "..table.concat(pos," "))
|
||||
pos = resolve_pos(fake_default_tls,"tls_client_hello","midsld")
|
||||
test_assert(pos)
|
||||
print("resolve_pos tls : "..pos - 1)
|
||||
pos = resolve_pos(fake_default_tls,"tls_client_hello","method")
|
||||
test_assert(not pos)
|
||||
print("resolve_pos tls non-existent : "..tostring(pos))
|
||||
|
||||
pos = zero_based_pos(resolve_multi_pos(fake_default_http,"http_req","method,host,sld,midsld,endsld,endhost,-5"))
|
||||
test_assert(pos)
|
||||
print("resolve_multi_pos http : "..table.concat(pos," "))
|
||||
pos = resolve_pos(fake_default_http,"http_req","sniext")
|
||||
test_assert(not pos)
|
||||
print("resolve_pos http non-existent : "..tostring(pos))
|
||||
end
|
||||
|
||||
function test_rawsend()
|
||||
local ip, ip6, udp, dis, ddis, raw_ip, raw_udp, raw
|
||||
local payload = brandom(math.random(100,1200))
|
||||
|
||||
ip = {
|
||||
ip_tos = 0,
|
||||
ip_id = math.random(0,0xFFFF),
|
||||
ip_off = 0,
|
||||
ip_ttl = 1,
|
||||
ip_p = IPPROTO_UDP,
|
||||
ip_src = pton("192.168.1.1"),
|
||||
ip_dst = pton("192.168.1.2")
|
||||
}
|
||||
udp = {
|
||||
uh_sport = math.random(0,0xFFFF),
|
||||
uh_dport = math.random(0,0xFFFF)
|
||||
}
|
||||
dis = {ip = ip, udp = udp, payload = payload}
|
||||
print("send ipv4 udp")
|
||||
test_assert(rawsend_dissect(dis, {repeats=3}))
|
||||
|
||||
ddis = ipfrag2(dis, {ipfrag_pos_udp = 80})
|
||||
for k,d in pairs(ddis) do
|
||||
print("send ipv4 udp frag "..k)
|
||||
test_assert(rawsend_dissect(d))
|
||||
end
|
||||
|
||||
raw_ip = reconstruct_iphdr(ip)
|
||||
raw_udp = reconstruct_udphdr({uh_sport = udp.uh_sport, uh_dport = udp.uh_dport, uh_ulen = UDP_BASE_LEN + #payload})
|
||||
raw_udp = csum_udp_fix(raw_ip,raw_udp,payload)
|
||||
raw = raw_ip .. raw_udp .. payload
|
||||
print("send ipv4 udp using pure rawsend without dissect")
|
||||
test_assert(rawsend(raw, {repeats=5}))
|
||||
|
||||
ip6 = {
|
||||
ip6_flow = 0x60000000,
|
||||
ip6_hlim = 1,
|
||||
ip6_src = pton("fdce:3124:164a:5318::1"),
|
||||
ip6_dst = pton("fdce:3124:164a:5318::2")
|
||||
}
|
||||
dis = {ip6 = ip6, udp = udp, payload = payload}
|
||||
print("send ipv6 udp")
|
||||
test_assert(rawsend_dissect(dis, {repeats=3}))
|
||||
|
||||
ddis = ipfrag2(dis, {ipfrag_pos_udp = 80})
|
||||
for k,d in pairs(ddis) do
|
||||
print("send ipv6 udp frag "..k)
|
||||
test_assert(rawsend_dissect(d))
|
||||
end
|
||||
|
||||
ip6.exthdr={{ type = IPPROTO_HOPOPTS, data = "\x00\x00\x00\x00\x00\x00" }}
|
||||
print("send ipv6 udp with hopbyhop ext header")
|
||||
test_assert(rawsend_dissect(dis, {repeats=3}))
|
||||
|
||||
ddis = ipfrag2(dis, {ipfrag_pos_udp = 80})
|
||||
for k,d in pairs(ddis) do
|
||||
print("send ipv6 udp frag "..k.." with hopbyhop ext header")
|
||||
test_assert(rawsend_dissect(d))
|
||||
end
|
||||
|
||||
table.insert(ip6.exthdr, { type = IPPROTO_DSTOPTS, data = "\x00\x00\x00\x00\x00\x00" })
|
||||
table.insert(ip6.exthdr, { type = IPPROTO_DSTOPTS, data = "\x00\x00\x00\x00\x00\x00" })
|
||||
ip6.ip6_flow = 0x60001234;
|
||||
ddis = ipfrag2(dis, {ipfrag_pos_udp = 80})
|
||||
for k,d in pairs(ddis) do
|
||||
print("send ipv6 udp frag "..k.." with hopbyhop, destopt ext headers in unfragmentable part and another destopt ext header in fragmentable part")
|
||||
test_assert(rawsend_dissect(d, {fwmark = 0x50EA}))
|
||||
end
|
||||
|
||||
fix_ip6_next(ip6) -- required to forge next proto in the second fragment
|
||||
ip6.ip6_flow = 0x6000AE38;
|
||||
ddis = ipfrag2(dis, {ipfrag_pos_udp = 80, ipfrag_next = IPPROTO_TCP})
|
||||
for k,d in pairs(ddis) do
|
||||
print("send ipv6 udp frag "..k.." with hopbyhop, destopt ext headers in unfragmentable part and another destopt ext header in fragmentable part. forge next proto in fragment header of the second fragment to TCP")
|
||||
-- reconstruct dissect using next proto fields in the dissect. do not auto fix next proto chain.
|
||||
-- by default reconstruct fixes next proto chain
|
||||
test_assert(rawsend_dissect(d, {fwmark = 0x409A, repeats=2}, {ip6_preserve_next = true}))
|
||||
end
|
||||
end
|
||||
82
lua/zapret-wgobfs.lua
Normal file
82
lua/zapret-wgobfs.lua
Normal file
@@ -0,0 +1,82 @@
|
||||
-- test case : nfqws2 --qnum 200 --debug --lua-init=@zapret-wgobfs.lua --in-range=a --out-range=a --lua-desync=wgobfs:secret=mycoolpassword
|
||||
-- encrypt standard wireguard messages - initiation, response, cookie - and change udp packet size
|
||||
-- do not encrypt data messages and keepalives
|
||||
-- wgobfs adds maximum of 30+padmax bytes to udp size
|
||||
-- reduce MTU of wireguard interface to avoid ip fragmentation !
|
||||
-- without knowing the secret encrypted packets should be crypto strong white noise with no signature
|
||||
-- arg : secret - shared secret. any string. must be the same on both peers
|
||||
-- arg : padmin - min random garbage bytes. 0 by default
|
||||
-- arg : padmax - max random garbage bytes. 16 by default
|
||||
function wgobfs(ctx, desync)
|
||||
local padmin = desync.arg.padmin and tonumber(desync.arg.padmin) or 0
|
||||
local padmax = desync.arg.padmax and tonumber(desync.arg.padmax) or 16
|
||||
local function genkey()
|
||||
-- cache key in lua_state of conntrack is present
|
||||
if desync.track and desync.track.lua_state.wgobfs_key then
|
||||
key = desync.track.lua_state.wgobfs_key
|
||||
end
|
||||
if not key then
|
||||
key = hkdf("sha256", "wgobfs_salt", desync.arg.secret, nil, 16)
|
||||
if desync.track then
|
||||
desync.track.lua_state.wgobfs_key = key
|
||||
end
|
||||
end
|
||||
return key
|
||||
end
|
||||
local function maybe_encrypted_payload(payload)
|
||||
for k,plsize in pairs({2+12+16+148, 2+12+16+92, 2+12+16+64}) do
|
||||
if #payload>=(plsize+padmin) and #payload<=(plsize+padmax) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
local function wg_payload_from_size(payload)
|
||||
if #payload==148 then return "wireguard_initiation"
|
||||
elseif #payload==92 then return "wireguard_response"
|
||||
elseif #payload==64 then return "wireguard_cookie"
|
||||
else return nil
|
||||
end
|
||||
end
|
||||
|
||||
if not desync.dis.udp then
|
||||
instance_cutoff(ctx)
|
||||
return
|
||||
end
|
||||
if not desync.arg.secret or #desync.arg.secret==0 then
|
||||
error("wgobfs requires secret")
|
||||
end
|
||||
if padmin>padmax then
|
||||
error("wgobfs: padmin>padmax")
|
||||
end
|
||||
if desync.l7payload=="wireguard_initiation" or desync.l7payload=="wireguard_response" or desync.l7payload=="wireguard_cookie" and #desync.dis.payload<65506 then
|
||||
DLOG("wgobfs: encrypting '"..desync.l7payload.."'. size "..#desync.dis.payload)
|
||||
local key = genkey()
|
||||
-- in aes-gcm every message require it's own crypto secure random iv
|
||||
-- encryption more than one message with the same iv is considered catastrophic failure
|
||||
-- iv must be sent with encrypted message
|
||||
local iv = bcryptorandom(12)
|
||||
local encrypted, atag = aes_gcm(true, key, iv, bu16(#desync.dis.payload)..desync.dis.payload..brandom(math.random(padmin,padmax)), nil)
|
||||
desync.dis.payload = iv..atag..encrypted
|
||||
return VERDICT_MODIFY
|
||||
end
|
||||
|
||||
if desync.l7payload=="unknown" and maybe_encrypted_payload(desync.dis.payload) then
|
||||
local key = genkey()
|
||||
local iv = string.sub(desync.dis.payload,1,12)
|
||||
local atag = string.sub(desync.dis.payload,13,28)
|
||||
local decrypted, atag2 = aes_gcm(false, key, iv, string.sub(desync.dis.payload,29))
|
||||
if atag==atag2 then
|
||||
local plen = u16(decrypted)
|
||||
if plen>(#decrypted-2) then
|
||||
DLOG("wgobfs: bad decrypted payload data")
|
||||
else
|
||||
desync.dis.payload = string.sub(decrypted, 3, 3+plen-1)
|
||||
if b_debug then DLOG("wgobfs: decrypted '"..(wg_payload_from_size(desync.dis.payload) or "unknown").."' message. size "..plen) end
|
||||
return VERDICT_MODIFY
|
||||
end
|
||||
else
|
||||
DLOG("wgobfs: decrypt auth tag mismatch")
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user