route add no longer works when I connected to VPN via cisco anyconnect client

Turns out it was Cisco AnyConnect client that is monitoring routing table.

The C++ function CHostConfigMgr::StartInterfaceAndRouteMonitoring() was doing the job. You might either modify the function to make it return immediately (and fix the checksum verification in vpnagentd) or try this solution with a new function name _ZN14CHostConfigMgr32StartInterfaceAndRouteMonitoringEv


Intro and other options

It's been like 5.5 years, so I'm mostly leaving this answer for people, trying to solve same problems with modern Cisco Anyconnect 4.x. In my case Anyconnect was wrapping traffic to local Kubernetes cluster brought up by Minikube on macOS.

Methods like _ZN27CInterfaceRouteMonitorLinux20routeCallbackHandlerEv or __ZN25CInterfaceRouteMonitorMac20routeCallbackHandlerEv described in:

  • https://superuser.com/a/1174704/465915
  • https://superuser.com/a/870752/465915

Does not seem to work any longer. Also, a lot of guides are related to fixing OS X firewall represented in previous versions by ipfw utility like here, so they are irrelevant.

Patching vpnagentd

In 2019 we're still battling both problems for companies avoid split routing and bring up unreasonable firewall rules. Here the fix.

You need to patch calling of method CHostConfigMgr::StartInterfaceAndRouteMonitoring() in vpnagentd binary, which in my version located at 0x09cbf6. It's a simple jmp qword command, which is never being returned to this place, so single nop can be enough. However, it could be worth completely wiping the command with 6 nops.

Here the Python script which can automate this procedure for you, however any disassembly utility can help you there. In my original research and hack I used radare2 which is quite handy for people who do not perform such actions on a daily basis:

#!/usr/bin/python3

MAGIC_OFFSET = "0x09cbf6"
MAGIC_BYTE = 144

def eff_anyconnect(file):

    print("Opening {} to patch it".format(file))
    with open(file, "rb+") as f:
        print("Going to {}".format(MAGIC_OFFSET))

        print("Current command to call method for watching our routing table")
        f.seek(int(MAGIC_OFFSET, 16))
        print(hex(int.from_bytes(f.read(6), "big")))

        f.seek(int(MAGIC_OFFSET, 16))
        f.write(bytes([MAGIC_BYTE]))

        print("NOP any longer:")
        f.seek(int(MAGIC_OFFSET, 16))
        print(hex(int.from_bytes(f.read(6), "big")))

eff_anyconnect("/your/path/to/cisco/bin/vpnagentd")

Next step, after you patch the binary, you should kill current vpnagent process and reconnect to your VPN. You might find that desired routes are still affected, but the hack above will unlock the routing table, so you can override routes.

Routes

I would suggest adding -static ones, those are not interfered by AnyConnect at all, while non-static still being duplicated by tunneled ones. I have no good solution here, for my case single static route was enough:

sudo route -n delete $(minikube ip) 
sudo route -n add $(minikube ip) -interface bridge100 -static

Firewall

Final, firewall step. That's pretty straightforward, you need to check what are the rules denying or allowing only AnyConnect tagged ones. In my case, everything what is not tagged was blocked, so I created a file with following entries:

nat on utun1 proto {tcp, udp, icmp} from 192.168.64.0/24 to any -> utun1 
pass in log on bridge0 inet all flags S/SA keep state tag cisco_anyconnect_vpn_pass
pass in log on bridge100 inet all flags S/SA keep state tag cisco_anyconnect_vpn_pass

Mind the following:

  • utun1 is your AnyConnect interface
  • bridge0 and bridge100 are your Minikube/Docker bridges. For some reason AnyConnect renames bridges.
  • 192.168.64.0/24 is your Minikube subnet.

Then run:

sudo pfctl -e enable packet-filtering
sudo pfctl -f <your_file_with_rules> -v

From now on, until next reconnect you should be good in terms of routes and firewall.

Tags:

Cisco