├── main.go └── README.md /main.go: -------------------------------------------------------------------------------- 1 | // `rstlss` is an example of an unprivileged Linux process using a BPF filter 2 | // to block certain TCP packets coming to the socket. 3 | // 4 | // Inspired by awesome blog post "eBPF, Sockets, Hop Distance and manually 5 | // writing eBPF assembly" by Marek Majkowski: 6 | // https://blog.cloudflare.com/epbf_sockets_hop_distance/ 7 | // 8 | // CC0, No Rights Reserved. -- Leonid Evdokimov 9 | 10 | package main 11 | 12 | import ( 13 | "golang.org/x/net/bpf" 14 | "golang.org/x/sys/unix" 15 | "io" 16 | "net" 17 | "net/http" 18 | "os" 19 | "syscall" 20 | "time" 21 | "unsafe" 22 | ) 23 | 24 | // DNS resolve workaround for android in pure go 25 | // this only work before any Lookup call and net.dnsReadConfig() failed 26 | //go:linkname defaultNS net.defaultNS 27 | var defaultNS []string 28 | 29 | func main() { 30 | if _, err := os.Stat("/etc/resolv.conf"); os.IsNotExist(err) { 31 | // Ugly hack for Android, good enough for proof-of-concept. 32 | dns := os.Getenv("RSTLSS_DNS") 33 | if dns != "" { 34 | defaultNS = []string{dns} 35 | } 36 | } 37 | 38 | // https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure 39 | // IP headers are also reachable at some magic offset 40 | // https://github.com/torvalds/linux/blob/ead751507de86d90fa250431e9990a8b881f713c/include/uapi/linux/filter.h#L84 41 | filter, err := bpf.Assemble([]bpf.Instruction{ 42 | bpf.LoadAbsolute{Off: 13, Size: 1}, // load flags[RST] 43 | bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x04, SkipTrue: 1}, // check RST 44 | bpf.RetConstant{Val: 65535}, // ACCEPT 45 | bpf.RetConstant{Val: 0}, // DROP 46 | }) 47 | if err != nil { 48 | panic(err) 49 | } 50 | prog := unix.SockFprog{ 51 | Len: uint16(len(filter)), 52 | Filter: (*unix.SockFilter)(unsafe.Pointer(&filter[0])), 53 | } 54 | dialer := &net.Dialer{ 55 | Control: func(network, address string, c syscall.RawConn) error { 56 | return c.Control(func(fd uintptr) { 57 | if os.Getenv("RSTLSS") == "" { 58 | return 59 | } 60 | err := unix.SetsockoptSockFprog(int(fd), unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, &prog) 61 | if err != nil { 62 | panic(err) 63 | } 64 | }) 65 | }, 66 | } 67 | tr := &http.Transport{ 68 | Proxy: http.ProxyFromEnvironment, 69 | DialContext: dialer.DialContext, 70 | MaxIdleConns: 100, 71 | IdleConnTimeout: 90 * time.Second, 72 | TLSHandshakeTimeout: 10 * time.Second, 73 | ExpectContinueTimeout: 1 * time.Second, 74 | } 75 | client := &http.Client{Transport: tr} 76 | resp, err := client.Get("https://rutracker.org/robots.txt") 77 | if err != nil { 78 | panic(err) 79 | } 80 | defer resp.Body.Close() 81 | _, err = io.Copy(os.Stdout, resp.Body) 82 | if err != nil { 83 | panic(err) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rstlss (RST-less TCP) 2 | ===================== 3 | 4 | `rstlss` is an example of an unprivileged Linux process using a BPF filter to 5 | block certain TCP packets coming to the socket. 6 | 7 | This specific example shows how to circumvent one-sided [TCP reset attack](https://en.wikipedia.org/wiki/TCP_reset_attack) 8 | by an _on-path_ DPI box [blocking](http://isitblockedinrussia.com/?host=https%3A%2F%2Frutracker.org) 9 | `https://rutracker.org` (as _Inappropriate TCP Resets Considered Harmful_). :-) 10 | 11 | _On-path_ DPI box is assumed to be _passive_, being unable to _drop_ packets, 12 | just being able to inject some. 13 | 14 | _One-sided TCP reset attack_ means that RST packet is injected only towards the 15 | "client" endpoints and the "server" does not get another RST. E.g. some networks 16 | in [Uganda block OpenVPN/TCP](https://ooni.torproject.org/post/uganda-social-media-tax/#vpn-blocking) 17 | with two-sided TCP reset attacks. One has to control the server as well to 18 | mitigate two-sided attack. 19 | 20 | ## Example 21 | 22 | Following tests were done on 2019-04-19 from AS8997, OJSC Rostelecom. The 23 | vantage point observes blocking of HTTPS websites by means of SNI-based 24 | detection and one-sided RST injection to block connections. 25 | 26 | ### Desktop Linux 27 | 28 | It just runs and just works: 29 | 30 | ``` 31 | $ go build . 32 | $ ./rstlss 33 | panic: Get https://rutracker.org/robots.txt: read tcp 192.168.100.223:34176->195.82.146.214:443: read: connection reset by peer 34 | 35 | goroutine 1 [running]: 36 | main.main() 37 | /home/darkk/go/src/github.com/darkk/rstlss/main.go:72 +0x69a 38 | $ RSTLSS=1 ./rstlss | grep -C 3 rutracker 39 | Allow: /forum/viewforum.php?f= 40 | Allow: /forum/viewtopic.php 41 | Disallow: / 42 | Host: rutracker.org 43 | 44 | User-agent: Adsbot-Google 45 | User-agent: Googlebot-Image 46 | $ 47 | ``` 48 | 49 | ### Android 50 | 51 | The binary is run with awesome [Termux](https://termux.com/) after 52 | cross-compiling the binary at the desktop: 53 | 54 | ``` 55 | $ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build . 56 | ``` 57 | 58 | Android PoC needs DNS server specified manually due to 59 | [golang/go#8877](https://github.com/golang/go/issues/8877) and 60 | [`net.dns1` hidden](https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri) 61 | since Android 8.0. One can get DNS server from _Network Details_: 62 | 63 | ``` 64 | $ RSTLSS_DNS=192.168.100.1:53 ./rstlss 65 | panic: Get https://rutracker.org/robots.txt: read tcp 192.168.100.136:39882->195.82.146.214:443: read: connection reset by peer 66 | 67 | goroutine 1 [running]: 68 | main.main() 69 | /home/darkk/go/src/github.com/darkk/rstlss/main.go:72 +0x544 70 | $ RSTLSS_DNS=192.168.100.1:53 RSTLSS=1 ./rstlss | grep -C 3 rutracker 71 | Allow: /forum/viewforum.php?f= 72 | Allow: /forum/viewtopic.php 73 | Disallow: / 74 | Host: rutracker.org 75 | 76 | User-agent: Adsbot-Google 77 | User-agent: Googlebot-Image 78 | $ 79 | ``` 80 | 81 | ## Outro 82 | 83 | This technique does not protect from malicious data being injected and 84 | effectively terminating the connection (e.g. HTTP redirect, TLS Alert or random 85 | garbage for authenticated TLS connection). Doing MAC within 4096 opcodes limit 86 | of a BPF program (~11 opcodes per dword) is left as an exercise for the reader. 87 | --------------------------------------------------------------------------------