summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs76
-rw-r--r--src/pkt.rs138
-rw-r--r--src/platform/linux.rs43
-rw-r--r--src/platform/windows.rs4
4 files changed, 214 insertions, 47 deletions
diff --git a/src/main.rs b/src/main.rs
index e8d2f78..319866f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -38,54 +38,38 @@ Press Ctrl+C or close this window to stop.
static RUNNING: AtomicBool = AtomicBool::new(true);
static DELAY_MS: OnceLock<u64> = OnceLock::new();
+static OPT_FAKE: OnceLock<bool> = OnceLock::new();
+
fn delay_ms() -> u64 {
*DELAY_MS.get().expect("DELAY_MS not initialized")
}
-fn split_packet(pkt: &pkt::PktView, start: u32, end: Option<u32>,
- out_buf: &mut Vec<u8>) -> Result<()> {
- use etherparse::*;
-
- let ip = &pkt.ip;
- let tcp = &pkt.tcp;
- let payload = tcp.payload();
-
- let end = end.unwrap_or(payload.len().try_into()?);
-
- if start > end || payload.len() < end as usize {
- return Err(anyhow!("invalid index"));
- }
-
- let opts = tcp.options();
- let mut tcp_hdr = tcp.to_header();
- tcp_hdr.sequence_number += start;
-
- // TODO: refactor this to reuse IP header with no copy
- let builder = match ip {
- IpSlice::Ipv4(hdr) =>
- PacketBuilder::ip(IpHeaders::Ipv4(
- hdr.header().to_header(),
- hdr.extensions().to_header()
- )),
-
- IpSlice::Ipv6(hdr) =>
- PacketBuilder::ip(IpHeaders::Ipv6(
- hdr.header().to_header(),
- Default::default()
- ))
- }.tcp_header(tcp_hdr).options_raw(opts)?;
+fn split_packet(
+ view: &pkt::PktView,
+ start: u32,
+ end: Option<u32>,
+ out_buf: &mut Vec<u8>
+) -> Result<()> {
+ pkt::split_packet_0(view, start, end, out_buf, None, None)
+}
- let payload = &payload[start as usize..end as usize];
+fn send_segment(
+ view: &pkt::PktView,
+ start: u32,
+ end: Option<u32>,
+ buf: &mut Vec<u8>
+) -> Result<()> {
+ use platform::send_to_raw;
- out_buf.clear();
- builder.write(out_buf, payload)?;
+ pkt::fake_clienthello(view, start, end, buf)?;
+ send_to_raw(buf)?;
+ split_packet(view, start, end, buf)?;
+ send_to_raw(buf)?;
Ok(())
}
fn split_packet_1(view: &pkt::PktView, order: &[u32], buf: &mut Vec<u8>) -> Result<()> {
- use platform::send_to_raw;
-
let mut it = order.iter().copied();
let Some(mut first) = it.next() else {
@@ -93,19 +77,16 @@ fn split_packet_1(view: &pkt::PktView, order: &[u32], buf: &mut Vec<u8>) -> Resu
};
for next in it {
- split_packet(view, first, Some(next), buf)?;
- send_to_raw(buf)?;
+ send_segment(view, first, Some(next), buf)?;
std::thread::sleep(std::time::Duration::from_millis(delay_ms()));
first = next;
}
- split_packet(view, first, None, buf)?;
- send_to_raw(buf)?;
+ send_segment(view, first, None, buf)?;
Ok(())
}
-
/// Return Ok(true) if packet is handled
fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> {
#[cfg(target_os = "linux")]
@@ -168,6 +149,10 @@ Options:
--nft-command <string> (linux only, default: nft)
--loglevel <debug|info|warning|error> (default: warning)
--no-splash Do not print splash messages
+
+ --fake Enable fake clienthello injection
+ --fake-ttl <u8> Override ttl of fake clienthello (default: 8)
+
-h, --help Show this help"#
);
}
@@ -175,6 +160,8 @@ Options:
fn parse_args_1() -> Result<()> {
let mut delay_ms: u64 = 0;
let mut no_splash: bool = false;
+ let mut fake: bool = false;
+ let mut fake_ttl: u8 = 8;
#[cfg(debug_assertions)]
let mut log_level: log::LogLevel = LogLevel::Debug;
@@ -196,6 +183,9 @@ fn parse_args_1() -> Result<()> {
"--loglevel" => { log_level = take_value(&mut args, argv)?; }
"--no-splash" => { no_splash = true; }
+ "--fake" => { fake = true; }
+ "--fake-ttl" => { fake_ttl = take_value(&mut args, argv)?; }
+
#[cfg(target_os = "linux")]
"--queue-num" => { queue_num = take_value(&mut args, argv)?; }
@@ -209,6 +199,8 @@ fn parse_args_1() -> Result<()> {
DELAY_MS.set(delay_ms).map_err(|_| anyhow!("DELAY_MS already initialized"))?;
log::set_no_splash(no_splash).map_err(|e| anyhow!("{e}"))?;
log::set_log_level(log_level).map_err(|e| anyhow!("{e}"))?;
+ OPT_FAKE.set(fake).map_err(|_| anyhow!("OPT_FAKE already initialized"))?;
+ pkt::OPT_FAKE_TTL.set(fake_ttl).map_err(|_| anyhow!("OPT_FAKE_TTL already initialized"))?;
#[cfg(target_os = "linux")]
platform::QUEUE_NUM.set(queue_num).map_err(|_| anyhow!("QUEUE_NUM already initialized"))?;
diff --git a/src/pkt.rs b/src/pkt.rs
index 749385f..1a329cd 100644
--- a/src/pkt.rs
+++ b/src/pkt.rs
@@ -17,6 +17,76 @@
use anyhow::Result;
use etherparse::{IpSlice, TcpSlice};
+use anyhow::anyhow;
+use std::sync::OnceLock;
+
+/// www.microsoft.com
+/// Stolen from github.com/bol-van/zapret/blob/master/nfq/desync.c
+const DEFAULT_FAKE_TLS_CLIENTHELLO: &'static [u8] = &[
+ 0x16, 0x03, 0x01, 0x02, 0xa3, 0x01, 0x00, 0x02, 0x9f, 0x03, 0x03, 0x41,
+ 0x88, 0x82, 0x2d, 0x4f, 0xfd, 0x81, 0x48, 0x9e, 0xe7, 0x90, 0x65, 0x1f,
+ 0xba, 0x05, 0x7b, 0xff, 0xa7, 0x5a, 0xf9, 0x5b, 0x8a, 0x8f, 0x45, 0x8b,
+ 0x41, 0xf0, 0x3d, 0x1b, 0xdd, 0xe3, 0xf8, 0x20, 0x9b, 0x23, 0xa5, 0xd2,
+ 0x21, 0x1e, 0x9f, 0xe7, 0x85, 0x6c, 0xfc, 0x61, 0x80, 0x3a, 0x3f, 0xba,
+ 0xb9, 0x60, 0xba, 0xb3, 0x0e, 0x98, 0x27, 0x6c, 0xf7, 0x38, 0x28, 0x65,
+ 0x80, 0x5d, 0x40, 0x38, 0x00, 0x22, 0x13, 0x01, 0x13, 0x03, 0x13, 0x02,
+ 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x2c, 0xc0, 0x30,
+ 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
+ 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x02, 0x34, 0x00, 0x00, 0x00, 0x16,
+ 0x00, 0x14, 0x00, 0x00, 0x11, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63,
+ 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17,
+ 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x00,
+ 0x0c, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x01, 0x00, 0x01,
+ 0x01, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74,
+ 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x22, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03, 0x05, 0x03,
+ 0x06, 0x03, 0x02, 0x03, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x6b,
+ 0x00, 0x69, 0x00, 0x1d, 0x00, 0x20, 0x69, 0x15, 0x16, 0x29, 0x6d, 0xad,
+ 0xd5, 0x68, 0x88, 0x27, 0x2f, 0xde, 0xaf, 0xac, 0x3c, 0x4c, 0xa4, 0xe4,
+ 0xd8, 0xc8, 0xfb, 0x41, 0x87, 0xf4, 0x76, 0x4e, 0x0e, 0xfa, 0x64, 0xc4,
+ 0xe9, 0x29, 0x00, 0x17, 0x00, 0x41, 0x04, 0xfe, 0x62, 0xb9, 0x08, 0xc8,
+ 0xc3, 0x2a, 0xb9, 0x87, 0x37, 0x84, 0x42, 0x6b, 0x5c, 0xcd, 0xc9, 0xca,
+ 0x62, 0x38, 0xd3, 0xd9, 0x99, 0x8a, 0xc4, 0x2d, 0xc6, 0xd0, 0xa3, 0x60,
+ 0xb2, 0x12, 0x54, 0x41, 0x8e, 0x52, 0x5e, 0xe3, 0xab, 0xf9, 0xc2, 0x07,
+ 0x81, 0xdc, 0xf8, 0xf2, 0x6a, 0x91, 0x40, 0x2f, 0xcb, 0xa4, 0xff, 0x6f,
+ 0x24, 0xc7, 0x4d, 0x77, 0x77, 0x2d, 0x6f, 0xe0, 0x77, 0xaa, 0x92, 0x00,
+ 0x2b, 0x00, 0x05, 0x04, 0x03, 0x04, 0x03, 0x03, 0x00, 0x0d, 0x00, 0x18,
+ 0x00, 0x16, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x04, 0x08, 0x05,
+ 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01,
+ 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01,
+ 0x00, 0x1b, 0x00, 0x07, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0xfe,
+ 0x0d, 0x01, 0x19, 0x00, 0x00, 0x01, 0x00, 0x03, 0x21, 0x00, 0x20, 0x62,
+ 0xe8, 0x83, 0xd8, 0x97, 0x05, 0x8a, 0xbe, 0xa1, 0xf2, 0x63, 0x4e, 0xce,
+ 0x93, 0x84, 0x8e, 0xcf, 0xe7, 0xdd, 0xb2, 0xe4, 0x87, 0x06, 0xac, 0x11,
+ 0x19, 0xbe, 0x0e, 0x71, 0x87, 0xf1, 0xa6, 0x00, 0xef, 0xd8, 0x6b, 0x27,
+ 0x5e, 0xc0, 0xa7, 0x5d, 0x42, 0x4e, 0x8c, 0xdc, 0xf3, 0x9f, 0x1c, 0x51,
+ 0x62, 0xef, 0xff, 0x5b, 0xed, 0xc8, 0xfd, 0xee, 0x6f, 0xbb, 0x88, 0x9b,
+ 0xb1, 0x30, 0x9c, 0x66, 0x42, 0xab, 0x0f, 0x66, 0x89, 0x18, 0x8b, 0x11,
+ 0xc1, 0x6d, 0xe7, 0x2a, 0xeb, 0x96, 0x3b, 0x7f, 0x52, 0x78, 0xdb, 0xf8,
+ 0x6d, 0x04, 0xf7, 0x95, 0x1a, 0xa8, 0xf0, 0x64, 0x52, 0x07, 0x39, 0xf0,
+ 0xa8, 0x1d, 0x0d, 0x16, 0x36, 0xb7, 0x18, 0x0e, 0xc8, 0x44, 0x27, 0xfe,
+ 0xf3, 0x31, 0xf0, 0xde, 0x8c, 0x74, 0xf5, 0xa1, 0xd8, 0x8f, 0x6f, 0x45,
+ 0x97, 0x69, 0x79, 0x5e, 0x2e, 0xd4, 0xb0, 0x2c, 0x0c, 0x1a, 0x6f, 0xcc,
+ 0xce, 0x90, 0xc7, 0xdd, 0xc6, 0x60, 0x95, 0xf3, 0xc2, 0x19, 0xde, 0x50,
+ 0x80, 0xbf, 0xde, 0xf2, 0x25, 0x63, 0x15, 0x26, 0x63, 0x09, 0x1f, 0xc5,
+ 0xdf, 0x32, 0xf5, 0xea, 0x9c, 0xd2, 0xff, 0x99, 0x4e, 0x67, 0xa2, 0xe5,
+ 0x1a, 0x94, 0x85, 0xe3, 0xdf, 0x36, 0xa5, 0x83, 0x4b, 0x0a, 0x1c, 0xaf,
+ 0xd7, 0x48, 0xc9, 0x4b, 0x8a, 0x27, 0xdd, 0x58, 0x7f, 0x95, 0xf2, 0x6b,
+ 0xde, 0x2b, 0x12, 0xd3, 0xec, 0x4d, 0x69, 0x37, 0x9c, 0x13, 0x9b, 0x16,
+ 0xb0, 0x45, 0x52, 0x38, 0x77, 0x69, 0xef, 0xaa, 0x65, 0x19, 0xbc, 0xc2,
+ 0x93, 0x4d, 0xb0, 0x1b, 0x7f, 0x5b, 0x41, 0xff, 0xaf, 0xba, 0x50, 0x51,
+ 0xc3, 0xf1, 0x27, 0x09, 0x25, 0xf5, 0x60, 0x90, 0x09, 0xb1, 0xe5, 0xc0,
+ 0xc7, 0x42, 0x78, 0x54, 0x3b, 0x23, 0x19, 0x7d, 0x8e, 0x72, 0x13, 0xb4,
+ 0xd3, 0xcd, 0x63, 0xb6, 0xc4, 0x4a, 0x28, 0x3d, 0x45, 0x3e, 0x8b, 0xdb,
+ 0x84, 0x4f, 0x78, 0x64, 0x30, 0x69, 0xe2, 0x1b
+];
+
+pub static OPT_FAKE_TTL: OnceLock<u8> = OnceLock::new();
+
+fn fake_ttl() -> u8 {
+ *OPT_FAKE_TTL.get().expect("OPT_FAKE_TTL not initialized")
+}
pub struct PktView<'a> {
pub ip: IpSlice<'a>,
@@ -32,3 +102,71 @@ impl<'a> PktView<'a> {
Ok(Self { ip, tcp })
}
}
+
+/// Write TCP/IP packet (payload = view.tcp.payload[start..Some(end)])
+/// to out_buf, explicitly clearing before.
+///
+/// If payload or ttl is given, override view's one.
+pub fn split_packet_0(
+ view: &PktView,
+ start: u32,
+ end: Option<u32>,
+ out_buf: &mut Vec<u8>,
+ payload: Option<&[u8]>,
+ ttl: Option<u8>
+) -> Result<()> {
+ use etherparse::*;
+
+ let ip = &view.ip;
+ let tcp = &view.tcp;
+ let payload = payload.unwrap_or(tcp.payload());
+
+ let end = end.unwrap_or(payload.len().try_into()?);
+
+ if start > end || payload.len() < end as usize {
+ return Err(anyhow!("invalid index"));
+ }
+
+ let opts = tcp.options();
+ let mut tcp_hdr = tcp.to_header();
+ tcp_hdr.sequence_number += start;
+
+ let builder = match ip {
+ IpSlice::Ipv4(hdr) => {
+ let mut ip_hdr = hdr.header().to_header();
+ if let Some(t) = ttl { ip_hdr.time_to_live = t; };
+
+ PacketBuilder::ip(IpHeaders::Ipv4(
+ ip_hdr,
+ hdr.extensions().to_header()
+ ))
+ },
+
+ IpSlice::Ipv6(hdr) => {
+ let mut ip6_hdr = hdr.header().to_header();
+ if let Some(t) = ttl { ip6_hdr.hop_limit = t; };
+
+ PacketBuilder::ip(IpHeaders::Ipv6(
+ ip6_hdr,
+ Default::default()
+ ))
+ }
+ }.tcp_header(tcp_hdr).options_raw(opts)?;
+
+ let payload = &payload[start as usize..end as usize];
+
+ out_buf.clear();
+ builder.write(out_buf, payload)?;
+
+ Ok(())
+}
+
+pub fn fake_clienthello(
+ view: &PktView,
+ start: u32,
+ end: Option<u32>,
+ out_buf: &mut Vec<u8>
+) -> Result<()> {
+ split_packet_0(view, start, end, out_buf,
+ Some(DEFAULT_FAKE_TLS_CLIENTHELLO), Some(fake_ttl()))
+}
diff --git a/src/platform/linux.rs b/src/platform/linux.rs
index cf6e3dd..02b174b 100644
--- a/src/platform/linux.rs
+++ b/src/platform/linux.rs
@@ -36,6 +36,8 @@ const DPIBREAK_CHAIN: &str = "DPIBREAK";
pub static QUEUE_NUM: OnceLock<u16> = OnceLock::new();
pub static NFT_COMMAND: OnceLock<String> = OnceLock::new();
+const INJECT_MARK: u32 = 0xD001;
+
fn queue_num() -> u16 {
*QUEUE_NUM.get().expect("QUEUE_NUM not initialized")
}
@@ -143,6 +145,15 @@ fn install_iptables_rules(ipt: &IPTables) -> Result<()> {
};
ipt.new_chain("mangle", DPIBREAK_CHAIN).map_err(iptables_err)?;
+
+ // prevent inf loop
+ ipt.insert(
+ "mangle",
+ DPIBREAK_CHAIN,
+ &format!("-m mark --mark {:#x} -j RETURN", INJECT_MARK),
+ 1
+ ).map_err(iptables_err)?;
+
ipt.append("mangle", DPIBREAK_CHAIN, &rule).map_err(iptables_err)?;
log_println!(LogLevel::Info, "{}: new chain {} on table mangle", ipt.cmd, DPIBREAK_CHAIN);
@@ -198,6 +209,26 @@ fn install_nft_rules() -> Result<()> {
}
}
},
+ // prevent inf loop
+ {
+ "add": {
+ "rule": {
+ "family": "inet",
+ "table": DPIBREAK_TABLE,
+ "chain": DPIBREAK_CHAIN,
+ "expr": [
+ {
+ "match": {
+ "left": { "meta": { "key": "mark" }},
+ "op": "==",
+ "right": INJECT_MARK
+ }
+ },
+ { "return": null }
+ ]
+ }
+ }
+ },
{
"add": {
"rule": {
@@ -325,16 +356,20 @@ use socket2::{Domain, Protocol, Socket, Type};
static RAW4: LazyLock<Mutex<Socket>> = LazyLock::new(|| {
let sock = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::TCP))
.expect("create raw4");
- sock.set_header_included_v4(true)
- .expect("IP_HDRINCL");
+
+ sock.set_header_included_v4(true).expect("IP_HDRINCL");
+ sock.set_mark(INJECT_MARK).expect("SO_MARK");
+
Mutex::new(sock)
});
static RAW6: LazyLock<Mutex<Socket>> = LazyLock::new(|| {
let sock = Socket::new(Domain::IPV6, Type::RAW, Some(Protocol::TCP))
.expect("create raw6");
- sock.set_header_included_v6(true)
- .expect("IP_HDRINCL");
+
+ sock.set_header_included_v6(true).expect("IP_HDRINCL");
+ sock.set_mark(INJECT_MARK).expect("SO_MARK");
+
Mutex::new(sock)
});
diff --git a/src/platform/windows.rs b/src/platform/windows.rs
index 1708038..4d75e0e 100644
--- a/src/platform/windows.rs
+++ b/src/platform/windows.rs
@@ -28,7 +28,8 @@ pub static WINDIVERT_HANDLE: LazyLock<Mutex<WinDivert<NetworkLayer>>> = LazyLock
const FILTER: &str = "outbound and tcp and tcp.DstPort == 443 \
and tcp.Payload[0] == 22 \
- and tcp.Payload[5] == 1"; // handshake, clienthello
+ and tcp.Payload[5] == 1 \
+ and !impostor"; // to prevent inf loop
let h = match WinDivert::network(FILTER, 0, prelude::WinDivertFlags::new()) {
Ok(h) => {
@@ -73,6 +74,7 @@ pub fn send_to_raw(pkt: &[u8]) -> Result<()> {
p.address.set_outbound(true);
p.address.set_ip_checksum(true);
p.address.set_tcp_checksum(true);
+ p.address.set_impostor(true); // to prevent inf loop
lock_handle().send(&p)?;