Fixing corporate VPN, one route at a time
One of the lesser known perks of working in a corporate environment albeit remotely is that you can’t connect to devices on LAN(at-least for me). This educational post shows how to circumvent that restriction for educational purposes.
One of the first things to notice is that whenever I ping any of my devices on LAN, I don’t get any response and this is only when the VPN is on.
Hmm, so the basic hunch is that when the VPN turns on, it creates an entry in the route table that superseeds the existing one taking in all the traffic to the VPNs interface.
Let’s take a look at our route table, shall we?
1 | IPv4 Route Table |
For this write up: the VPN’s network is 172.16.42.0/24 and my LAN is 192.168.2.0/24
Tada! We see our culprit.
Although, there is an entry in the table for 192.168.2.0/24 on the LAN’s interface(on 192.168.2.187) but because the metric is lower for the entry on VPN’s interface(on 172.16.42.129), it takes precedence and we get:
1 | Tracing route to machine [192.168.2.1] |
What if we just delete the said entry, can we then ping any device on LAN?
1 | > route delete 192.168.2.0 METRIC 55 IF 6 |
It’s a success. Hooray! Or is it?
Well the success is short lived as the entry is added back nearly instantly.
Can we do better?
Let’s write a simple loop that keeps deleting this entry again and again.
1 | @echo off |
It works but we do have intermittent connection issues for the period when the entry is added to the route table.
1 | > ping 192.168.2.1 -t |
What should we do? Let’s dig in.
If we can deny the ability of the VPN to create an entry in the route table then we won’t have any issues to face with.
On Windows, to create an entry in the route table, we have to use Iphlpapi.dll!CreateIpForwardEntry
, taking a look on the function header
1 | IPHLPAPI_DLL_LINKAGE DWORD CreateIpForwardEntry( |
with the structure defined as
1 | typedef struct _MIB_IPFORWARDROW { |
The variable names are self explanatory so I won’t explain them.
Let’s take a look if our target process calls this function.
What should we do about it?
Let’s do user-mode hooking!
Below a simple diagram on how does user-mode hooking works. Let’s assume that FncA calls FncB and then gives control back to FncA.
graph LR B[FncB] --> A[FncA] A[FncA] --> B[FncB]
And, instead, we want to call FncC instead of FncB. How can I achieve that?
I can in practice place a hook on FncB such that we will perform a jump to FncC and execute that instead of FncB.
graph LR A[FncA] --> B[FncB] B -.-> C[FncC] C -.-> A
The dashed line shows that FncB won’t be executed rather the control will be passed on to FncC and FncC can decide what to do. That’s how most AVs work these days to detect malicious things during runtime.
Now that we know what we can do with user-mode hooking.
Let’s write a hook for it, our function should have the same parameters and it’s types. Let’s name our function as hk_CreateIpForwardEntry
.
Our function hk_CreateIpForwardEntry
will have a simple mechanism, we will monitor for calls containing specific dwForwardDest
and dwForwardMask
, which are Network Destination
and Netmask
respectively(see route table above) and if the request to create a route entry contains those we will simply return NO_ERROR
and the VPN service will think that the route is added to the table.
We know that our VPN will try to create an entry with dwForwardDest
set to 192.168.2.0
and dwForwardMask
set to 255.255.255.0
. We need to convert them to DWORDs with little-endian ordering;192.168.2.0
-> 174272
255.255.255.0
-> 16777215
We will use OutputDebugString
to output logs from our hk_CreateIpForwardEntry
and use DbgView
to view them.
After changing some memory bytes of the running VPN Service (heh), we see the output from our own bytes:Hooked CreateIpForwardEntry @FUNC_ADDRESS
But the entry is already present in the route table so the VPN service won’t try to create it again, so let’s delete it to trigger the process :D
And, now we got a reliable way to access devices on our LAN :D.
Source code is not going to be provided for obvious reasons.