ZNHOO Whatever you are, be a good one!

TCP Obfuscation - ptproxy

ptproxy

is almost dead. The repository is ancient whilist aisocks has dropped support for Python 3.4! Try simple obfs instead!


ABCs

ptproxy (author page) obfuscates TCP traffic with the help of Tor's PT (pluggable transports) prototol. This tool is independent of Tor and applies to any TCP traffic. In this post, I demonstrate how ptproxy obfuscate Shadowsocks traffic.

  1. To speed up TCP connection to VPS, try KCPtun.
  2. Before ptproxy

    app -> Shadowsocks client -> Shadowsocks server -> destination host

  3. With ptproxy

    app -> Shadowsocks client -> ptproxy client -> ptproxy server -> Shadowsocks server -> destination host

    In proxy chain, there are client/server node pairs. But each node (named as client or server) actually plays another pair of client/server functionalities at the same instant/simutaneously/concurrently. Each node forwards requests while listens for incoming request.

    The former client/server refer to literal name of chain node, while the later pair are that node's functionalities. A server functionality address is set at your will while a client functionality address must be set to address a server functionality.

  4. In this post, assume client resides on localhost while servers resides on VPS.
  5. New Async version requirs >=python=3.4. The ancient shell script version is deprecated.

Python3.4 on CentOS 6.5

# cd /var/tmp
# wget https://www.python.org/ftp/python/3.4.4/Python-3.4.4.tgz
# tar vxzf Python-3.4.4.tgz
# cd Python-3.4.4
# ./configure --prefix=/usr/local/
# make && make altinstall
# python3.4 -V
# ln -sv /usr/local/bin/python3.4 /usr/bin/python3.4
# rm Python-3.4.4.tgz

Configuration sample

{
    "role": "server",
    "state": ".",
    "local": "127.0.0.1:1080",
    "server": "0.0.0.0:23456",
    "ptexec": "obfs4proxy -logLevel ERROR -enableLogging",
    "ptname": "obfs4",
    "ptargs": "cert=AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;iat-mode=0",
    "ptserveropt": "",
    "ptproxy": ""
}
  1. It's localhost part (client) and VPS part (server).
  2. A directory to store ptproxy running states consisting of several Json files.

    We should modify obfs4_state.json to switch iat-mode.

  3. local literally must be a loopback address (127.0.0.1:port), traffic within localhost.

    For ptproxy client role, the loopback address is set (at your will) listening for local request. But for ptproxy server, it is an address to which traffic is forwarded.

  4. Similarly, server parameter should be an address for remote connection.

    For ptproxy client role, the remote address is set to ptproxy server's listening address on VPS which is, in return, equal to ptproxy server's server argument.

  5. That's the pluggable transport executable.
  6. ptname should be unique for each ptproxy client/server pair.
  7. For ptproxy server, ignore this.
  8. ptserveropt and ptproxy are usually left alone.

Server

  1. Get ptproxy

    # cd /opt/
    # git clone --branch master --depth 1 https://github.com/gumblex/ptproxy.git
    # git clone --branch master --depth 1 https://github.com/nibrag/aiosocks.git
    # cd ptproxy/
    # ln -sv /opt/aiosocks/aiosocks aiosocks
    

    I don't like 3rd-party code snippets (Aiosocks here) mingled with system packages. Just put it together with ptproxy.

  2. Server configuration

    # cd ptproxy
    # cp example.json server.json
    # vim server.json
    

    ptproxy server listens on VPS for incoming ptproxy client traffic while forwards that to Shadowsocks server.

    {
        "role": "server",
        "state": "/opt/ptproxy/pt_state",
        "local": "127.0.0.1:5555",
        "server": "0.0.0.0:5554",
        "ptexec": "/usr/local/bin/obfs4proxy -logLevel ERROR -enableLogging",
        "ptname": "obfs4",
        "ptargs": "cert=AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;iat-mode=0",
        "ptserveropt": "",
        "ptproxy": ""
    }
    
    1. The json configuration file does not allow comments. Remove them before launching.
    2. Do NOT use bash reserving words in configuration, i.e. ${HOME} which is unkown to ptproxy.
  3. Initial server startup

    ~ # cd /opt/ptproxy/
    ~ # mkdir pt_state; chown -R nobody: pt_state
    ~ # su nobody -s /bin/bash -c "python3.4 ptproxy.py -s server.json"
    

    Must make sure nobody has write access to pt_state directory.

    This is the very first launch. su nobody -s /bin/bash drop user to nobody instead of root. Useful information is printed on stdout for client side.

    2015-10-02 18:16:49 Starting PT…
    ===== Server information =====
    “server": “xxx.xxx.xxx.xxx:yyy",
    “ptname": “obfs4″,
    “ptargs": “cert=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx;iat-mode=0″,
    ==============================
    2015-10-02 18:16:49 PT started successfully.
    

    Meanwhile three state files are generated under /opt/ptproxy/pt_state. obfs4_state.json need modification for iat-mode, which means inter-arrival time controlling whether to confuscate packets' sending time by padding bits. By default, it is turned off.

    In order to be more robust, change to 1 (Enabled, ScrambleSuit-style with bulk throughput optimizations) or 2 (Paranoid, Each IAT write will send a length sampled from the length distribution. This may slow down performance). Details refer to Various IAT related changes. iat-mode of Tor bridges is turned off by default either.

  4. iat-mode

    To modify obfs4_state.json, the running instance must be killed first.

    # pkill -f "ptproxy.py"
    

    Attention, on the server side, modify obfs4_state.json. Leave server.json alone.

  5. Real launch by rc.local

    Add to /etc/rc.local (symlink to /etc/rc.d/rc.local) on CentOS:

    #!/bin/sh
    # /etc/rc.local
    
    PT_DIR="/opt/ptproxy"
    /bin/su nobody -s /bin/bash -c "/usr/bin/python3.4 ${PT_DIR}/ptproxy.py -s ${PT_DIR}/server.json &"
    

    To check the shell syntax, add set -x to the beginning of rc.local and sh /etc/rc.local. Errors will be printed.

    If this is the only shell commands in rc.local, just execute sh /etc/rc.local. Otherwise, reboot VPS. It's not recommended to launch ptproxy server directly on SSH terminal, which may result in service killed occasionally.

ptproxy client

# cd /opt/
# git clone --branch master --depth 1 https://github.com/gumblex/ptproxy.git
# git clone --branch master --depth 1 https://github.com/nibrag/aiosocks.git
# cd ptproxy/
# ln -sv /opt/aiosocks/aiosocks aiosocks
  1. Client configuration

    Edit ss-client.json:

    {
        "role": "client",
        "state": "/opt/ptproxy/pt_state",
        "local": "127.0.0.1:5553",
        "server": "ip-of-vps:5554",
        "ptexec": "/usr/local/bin/obfs4proxy -logLevel ERROR -enableLogging",
        "ptname": "obfs4",
        "ptargs": "cert=AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;iat-mode=0",
        "ptserveropt": "",
        "ptproxy": ""
    }
    

    The ptname and ptargs should be set according to the initial server launch output. If choose to change iat-mode, remember to update local obfs4_state.json as well.

  2. Shadowsocks client's side configuration

    {
        "server":"127.0.0.1",
        "server_port":5553,
        "local_address": "127.0.0.1",
        "local_port":1080,
        "password":"gh1Tsy71nw",
        "timeout":300,
        "method":"aes-256-cfb",
        "fast_open":false
    }
    

    Change the server and server_port to ptproxy's counterparts.

  3. Iptables

    Turn on ptproxy's server listening port

    # iptables -t nat -I OUTPUT 4 -d server-ip -p tcp -m multiport --dports 5554,5555 -j RETURN
    # iptables -I OUTPUT 5 -d server-ip -p tcp -m multiport --dports 5554,5555 -j RETURN
    

    5555 is Shadowsocks server port (just in case we turn off ptproxy) while 5554 is ptproxy server port.

    Choose the -I rulenum based on your case.

  4. Client startup

    # cd ~/ptproxy/ptproxy
    # su nobody -s /bin/bash -c "python3.4 ptproxy.py -c client.json"
    
  5. Client startup script

    #!/sbin/openrc-run
    # To obfsucating TCP traffic, esepcially for SOCKS5 Shadowsocks
    
    PT_DIR="/opt/ptproxy"
    
    depend() {
      after net.wlp3s0
      after net.enp0s25
      after dhcpcd
      before shadowsocks
    }
    
    start() {
      if [ "${RC_CMD}" = "restart" ];
      then
        ebegin "Waiting"
        eend $?
      fi
    
      ebegin "Starting ptproxy"
    
      if ! pgrep -u nobody -x -f "/usr/bin/python3.4 ${PT_DIR}/ptproxy.py -c ${PT_DIR}/client.json" >/dev/null 2>&1; then
        su nobody -s /bin/bash -c "/usr/bin/python3.4 ${PT_DIR}/ptproxy.py -c ${PT_DIR}/client.json >/dev/null &"
      fi
    
      eend $?
    }
    
    stop() {
      ebegin "Stoping ptproxy"
    
      if pgrep  -u nobody -x -f "/usr/bin/python3.4 ${PT_DIR}/ptproxy.py -c ${PT_DIR}/client.json" >/dev/null 2>&1; then
        pkill -TERM -u nobody -f "/usr/bin/python3.4 ${PT_DIR}/ptproxy.py -c ${PT_DIR}/client.json"
      fi
    
      eend $?
    }
    

    It's recommended to merge this init script with Shadowsocks init script.

Notes

  1. A ptproxy client instance is associated with a server instance by argument ptname.

    That is to say, a client/server pair are for one application's TCP obfuscation (Shadowsocks in this post).

  2. If want to obfuscate another application's TCP traffic, we should launch another pair of client/server instances with a new pair of client/server configuration files.

    Specially, change the state location. We may use another PT binary like obfsproxy/obfs3. We also should choose new port client/server ports etc. Most important, choose new ptname.

  3. If Tor is used with Bridges and PT. Likely an instance of obfsproxy/obfs4proxy exists on you local host. This time the Tor serves ptproxy's role.
  4. Run as nobody:
    1. su nobody -s /bin/bash -c "script here";
    2. Make sure nobody have write access to pt_state directory.

Refs

  1. ptproxy.git
  2. author page
  3. win7 config
  4. ptproxy + obfsproxy/scramblesuit