14

When I connected to a VPN server via Cisco AnyConnect client, my virtualbox routing information is gone.

# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         *               0.0.0.0         U     0      0        0 cscotun0
default         172.21.157.1    0.0.0.0         UG    256    0        0 eth0
172.23.36.90    172.21.157.1    255.255.255.255 UGH   0      0        0 eth0
172.23.236.0    *               255.255.254.0   U     0      0        0 cscotun0

Then I tried to restore it via:

# ip route add 192.168.56.0/24 via 192.168.56.1 src 192.168.56.1

The command succeed without error, but from route command it doesn't add anything

# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         *               0.0.0.0         U     0      0        0 cscotun0
default         172.21.157.1    0.0.0.0         UG    256    0        0 eth0
172.23.36.90    172.21.157.1    255.255.255.255 UGH   0      0        0 eth0
172.23.236.0    *               255.255.254.0   U     0      0        0 cscotun0

Any ideas? I've configured apparmor to block vpnagentd from running either iptables or modprobe command, if that's related.

daisy
  • 54,555

4 Answers4

10

Turns out it was the Cisco AnyConnect client that was monitoring the 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

slm
  • 369,824
daisy
  • 54,555
9

Intro and other options

It's been like 5.5 years, so I'm mostly leaving this answer for people, trying to solve the 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:

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 avoiding split routing and bringing up unreasonable firewall rules. Here's the fix.

You need to patch the calling of the method CHostConfigMgr::StartInterfaceAndRouteMonitoring() in vpnagentd binary, which in my version is 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 that 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.

slm
  • 369,824
vladfau
  • 191
2

vpnagentd has a method inside of the binary which will prevent unauthorized routes from being inserted into the table.

This is pushed from the firewall to the client and can be systematically patched out of the client.

I've created a python script which will take care of the disassembly and patching automatically as the offsets change between versions. (Linux systems only currently.)

https://github.com/garrettskj/ac_patcher

  • I can confirm that this works for anyconnect-linux64-4.9.06037 using radare2 5.5.1. But anyconnect-linux64-4.6.01103 does not work. – Tik0 Mar 12 '21 at 13:34
1

The following works for me on Linux with anyconnect 4.3.

Anyconnect keeps track of the ip routes on a system with the agent that is installed with anyconnect. When anyconnect is connected and I try to add an ip route (in the "main" table), the routes either never get added or get deleted right away. There are ways around this by modifying the agent as mentioned in other answers.

However I did not use the option of modifying the agent. anyconnect seems to only keep track of the "main" route table and it seems to ignore other route tables and seems to ignore policy routing, so I used these features to get around the problem. See "man ip-route" and search for TABLE_ID to get a list of Linux kernel route tables.

For this example, let's assume:

  • You have a host linux system running anyconnect and connecting to a VPN server.
  • The linux system is directly connected to network 192.168.2.0/24 via Ethernet interface enp0s31f6.
  • The linux system has ip .10 on the network 192.168.2.0/24.
  • The 192.168.2.0/24 subnet has a router at 192.168.2.1 which is connected to the Internet.
  • The linux system also has a container running and there is a veth between the linux system and the container.
  • We can think of the container as simply a process running in a separate Linux mount and network namespace for example.
  • The veth uses subnet 192.168.3.0/24 (.1 = host, .2 = container).
  • To access the Internet, the container goes through the host and is Network Address Translated (NAT) via iptables MASQUERADE. Let's assume all of the above is setup and works fine. The problem occurs when we connect via anyconnect.

The following will allow:

  • The linux system to communicate directly with the local LAN 192.168.2.0/24.
  • The container to communicate with the Internet directly (not via the anyconnect tunnel).
  • The linux system and the container to communicate directly.
  • The linux system to continue to use the anyconnect tunnel for all other traffic.

Allow the linux system to communicate directly with the local LAN:

ip rule add type unicast to 192.168.2.0/24 table 110 pref 30010
ip route add 192.168.2.0/24 dev enp0s31f6 table 110

Allow the container to communicate with the Internet directly: (Do this in the host namespace. This will also allow the host and container to communicate.)

ip rule add type unicast from 192.168.3.0/24 table 100 pref 30000
ip route add default via 192.168.2.1 table 100
ip rule add type unicast to 192.168.3.0/24 table 120 pref 30020
ip route add 192.168.3.0/24 dev veth0 table 120

Besides for routes, anyconnect also sets up some firewall entries. Although anyconnect sets up firewall rules, it doesn't monitor these rules (i.e. it won't notice when the rules change). The following example completely bypasses the security that anyconnect sets up. These firewall entries should be refined a bit because they are too permissive for some situations, but the following allowed me to quickly see if I could get around the anyconnect setup problem.

iptables --table filter --insert INPUT 1 --source 0.0.0.0/0 --destination 0.0.0.0/0 --jump ACCEPT
iptables --table filter --insert OUTPUT 1 --source 0.0.0.0/0 --destination 0.0.0.0/0 --jump ACCEPT
iptables --table filter --insert FORWARD 1 --source 0.0.0.0/0 --destination 0.0.0.0/0 --jump ACCEPT

Note that when anyconnect disconnects it seems to remove its config (routes and iptables). The "disconnect" can be when you stop the connection normally. However, I also think it can be when it looses the connection and reconnects due to link issues. When it reconnects it re-establishes its config and it puts its iptables entries at the begining of the filter table chains which move our entries down and makes them useless. We must then re-apply our entries. Note also that when anyconnect disconnects it restores the /etc/resolv.conf which (like openconnect) in turn messes up the bind mount in our container (separate network and mount namespaces). We have to reapply the bind mount.

DericS
  • 711