Bypass DPI provider on a router with OpenWrt using only busybox

    image
    Hello everyone, in the light of the latest news from RosKomNadzor, I decided to take a look how things are with my provider’s locks. It turned out that Google’s DNS does not save, and blocking works by allocating an HTTP request to a banned site and then dropping packets from a found TCP session. However, after a little picking, it turned out that one busybox was enough to get around. Who cares - Wellcome under cat.



    Immediately make a reservation that the implementation of blocking depends on the provider and my method may not work for you. So, we will consider the example of the well-known site rutracker.ogr (domain and IP address in the post was changed to avoid).
    I started with a simple request to see how the provider generally responds.

    Homepage request
    wget -O / dev / null "rutracker.ogr"
    --2016-02-10 01:21:18 - http: //rutracker.ogr/
    Recognized rutracker.ogr (rutracker.ogr) ... 195.82.147.214
    Connection to rutracker.ogr (rutracker.ogr) | 195.82.147.214 |: 80 ... the connection is established.
    HTTP request sent. The reply is in process...
    
    Dump packages
    01: 25: 10.736021 IP 192.168.5.2.53724> 195.82.147.214.80: Flags [S], seq 778021515, win 29200, options [mss 1460, sackOK, TS val 40091571 ecr 0, nop, wscale 7], length 0
    01: 25: 10.771529 IP 195.82.147.214.80> 192.168.5.2.53724: Flags [S.], seq 866160985, ack 778021516, win 14600, options [mss 1400], length 0
    01: 25: 10.771562 IP 192.168.5.2.53724> 195.82.147.214.80: Flags [.], Ack 1, win 29200, length 0
    01: 25: 10.771701 IP 192.168.5.2.53724> 195.82.147.214.80: Flags [P.], seq 1: 141, ack 1, win 29200, length 140: HTTP: GET / HTTP / 1.1
    01: 25: 11.129078 IP 192.168.5.2.53724> 195.82.147.214.80: Flags [P.], seq 1: 141, ack 1, win 29200, length 140: HTTP: GET / HTTP / 1.1
    01: 25: 11.849176 IP 192.168.5.2.53724> 195.82.147.214.80: Flags [P.], seq 1: 141, ack 1, win 29200, length 140: HTTP: GET / HTTP / 1.1
    01: 25: 13.292495 IP 192.168.5.2.53724> 195.82.147.214.80: Flags [P.], seq 1: 141, ack 1, win 29200, length 140: HTTP: GET / HTTP / 1.1
    

    The client successfully connects, sends the request and that's all. A bunch of TCP forwarders before the timeout. DPI recognized the session and dropped it as banned. And then for some reason I pulled to try the same thing, but through telnet.

    Page request via telnet
    telnet rutracker.ogr 80
    Trying 195.82.147.214 ...
    Connected to rutracker.ogr.
    Escape character is '^]'.
    GET / HTTP / 1.1
    User-Agent: Wget / 1.16.3 (linux-gnu)
    Accept: * / *
    Accept-Encoding: identity
    Host: rutracker.ogr
    Connection: Keep-Alive
    HTTP / 1.1 301 Moved Permanently
    Server: nginx
    Date: Tue, 09 Feb 2016 22:29:50 GMT
    Content-Type: text / html
    Content-Length: 178
    Location: http: //rutracker.ogr/forum/index.php
    Connection: keep-alive
    
    301 Moved Permanently

    301 Moved Permanently


    nginx
    Dump packets upon request via telnet
    01: 33: 15.354300 IP 192.168.5.2.53782> 195.82.147.214.80: Flags [S], seq 4112340002, win 29200, options [mss 1460, sackOK, TS val 40236956 ecr 0, nop, wscale 7], length 0
    01: 33: 15.389921 IP 195.82.147.214.80> 192.168.5.2.53782: Flags [S.], seq 3472440534, ack 4112340003, win 14600, options [mss 1400], length 0
    01: 33: 15.389967 IP 192.168.5.2.53782> 195.82.147.214.80: Flags [.], Ack 1, win 29200, length 0
    01: 33: 16.226472 IP 192.168.5.2.53782> 195.82.147.214.80: Flags [P.], seq 1:17, ack 1, win 29200, length 16: HTTP: GET / HTTP / 1.1
    01: 33: 16.263181 IP 195.82.147.214.80> 192.168.5.2.53782: Flags [.], Ack 17, win 14600, length 0
    01: 33: 16.263214 IP 192.168.5.2.53782> 195.82.147.214.80: Flags [P.], seq 17: 139, ack 1, win 29200, length 122: HTTP
    01: 33: 16.298317 IP 195.82.147.214.80> 192.168.5.2.53782: Flags [.], Ack 139, win 14600, length 0                                                                                                    
    01: 33: 16.789180 IP 192.168.5.2.53782> 195.82.147.214.80: Flags [P.], seq 139: 141, ack 1, win 29200, length 2: HTTP                                                                                  
    01: 33: 16.827756 IP 195.82.147.214.80> 192.168.5.2.53782: Flags [.], Ack 141, win 14600, length 0                                                                                                    
    01: 33: 16.828043 IP 195.82.147.214.80> 192.168.5.2.53782: Flags [P.], seq 1: 383, ack 141, win 14600, length 382: HTTP: HTTP / 1.1 301 Moved Permanently                                                
    01: 33: 16.828067 IP 192.168.5.2.53782> 195.82.147.214.80: Flags [.], Ack 383, win 30016, length 0                                                                                                    
    01: 33: 20.376119 IP 192.168.5.2.53782> 195.82.147.214.80: Flags [F.], seq 141, ack 383, win 30016, length 0                                                                                          
    01: 33: 20.412142 IP 195.82.147.214.80> 192.168.5.2.53782: Flags [F.], seq 383, ack 142, win 14600, length 0                                                                                          
    01: 33: 20.412177 IP 192.168.5.2.53782> 195.82.147.214.80: Flags [.], Ack 384, win 30016, length 0                                                                                                    
    01: 33: 30.299143 IP 192.168.5.2.53780> 195.82.147.214.80: Flags [FP.], Seq 1515330593: 1515330733, ack 3791059975, win 29200, length 140: HTTP: GET / HTTP / 1.1                                        
    


    Actually at this moment it became clear that DPI is not as terrible as it is painted. If you look closely at the dumps, you can see that telnet sends the first line of the request in a separate packet. That is, DPI does not analyze the TCP stream, but the first packet with data from the client to the server and tries to collect Url from the path and field of the Host in order to break it through the database. If the first query line with the path and the line with the Host field are pulled apart in different packets, then DPI cannot correctly process such a session and skips it.

    The matter remained small. It was necessary to make some proxy, which would break the header of the first HTTP request in the TCP session (remember about Connection: Keep-Alive) into two packets, and the rest would simply be sent through. I also wanted this proxy to work on a router under OpenWrt in order to provide the entire home network. There are many ways to make such a proxy, I chose the most lazy, fastest and most jerky, as the goal was to make a proof of concept, not a boxed solution.
    As a proxy, I have a shell script that runs under tcpsvd (by default, the tcpsvd applet is not available in OpenWrt's busybox, so you need to rebuild it using the standard buildroot). Just in case, let me remind you that tcpsvd is such a thing that listens on the port and starts a child process when the client connects, redirecting its input / output to the socket.

    It turned out like this script (please do not hit with your feet, this is just a proof of concept)

    #!/bin/sh
    # костыль для склейки строк через символы перевода строки
    appendLine() 
    {
        if [[ ! -z "$1" ]]
        then
            echo -ne "$1\r\n$2"
        else
            echo -ne "$2"
        fi
    }
    header1=""
    header2=""
    host=""
    while [[ true ]]
    do
        # читаем строку
        read -r line
        # обрезаем оставшийся в строке 0x0D
        line=`echo "$line" | tr -d "\r"`
        # конец заголовка
        if [[ -z "$line" ]]
        then
            break
        fi
        # Все, что до Host: попадет в header1, остальное - в header2
        if [[ -z "$host" ]]
        then
            if [[ `echo "$line" | grep -c "Host:"` -eq "1" ]]
            then
                host=`echo "$line" | sed -re 's/^Host: (.*)\r?$/\1/'`
                header2=`appendLine "$header2" "$line"`
            else
                header1=`appendLine "$header1" "$line"`
            fi
        else
            header2=`appendLine "$header2" "$line"`
        fi
    done
    {
        # первая половина заголовка 
        echo -ne "$header1\r\n"
        # ждем секунду, чтобы netcat отправил пакет, если кто подскажет, как сделать это иначе - скажу спасибо
        sleep 1
        # вторая половина заголовка 
        echo -ne "$header2\r\n\r\n"
        # заголовок ушел, DPI пропустил сессию, остальное - просто прокидываем
        cat 2>/dev/null 
    } | nc "$host" 80
    


    Just in case, I’ll clarify that we wait for a second only at the beginning of a TCP session, and since browsers usually do not disconnect a session, but continue to send requests to it, there should be no obvious brakes.

    This crutch proxy starts like this: It remains only to redirect traffic from the browser to the proxy in any way. You can do this for example, like here , or you can generate a proxy auto-detection file from the list of banned sites. The second option is simpler, laziness again won and it turned out like this:
    tcpsvd 0.0.0.0 3128 ./proxy.sh




    { 
        echo -e "function FindProxyForURL(url, host)\n{\n\tif (" 
        wget "http://api.antizapret.info/group.php?data=domain" -O- | sed -re 's/^(.*)$/localHostOrDomainIs(host,"\1") || /'
        echo -e "false)\n{ return \"PROXY 192.168.5.1:3128\"; }\nreturn  \"DIRECT\";\n}"; 
    } > ~/antidpi.pac
    


    After slipping this file into the proxy settings, forbidden sites began to open without question.
    I will be glad if someone less lazy makes the proxy normal or, if I invented a bicycle, indicates a ready-made standard solution.
    That's all, thanks for your attention.

    Also popular now: