Deterministic Cryptographically Authenticated Network Signatures on Windows NLA

Jason A. Donenfeld Jason at zx2c4.com
Thu Jun 27 16:26:12 CEST 2019


Hi,

Recently there's been a decent amount of Windows reverse engineering
and research in order to come up with some rather simple but important
results. I figure it might be useful to document parts of this
somewhere.

On Windows, when an interface connects to an endpoint, it generates a
"Network Signature", which is supposed to uniquely identify a network.
This is done through the conjunction of the nla and network list
services, and the kernel. For wifi, it uses aspects about the AP and
SSID. For wwan, it uses details from there. And in general, for layer
2 connections, it hashes the mac address of the default gateway.  This
is sort of a terrible way to identify a network, since mac addresses
can be spoofed. It looks like Microsoft has toyed with trying to make
this less bad, with various papers and patents and whatnot for
"authenticated dhcp" with some fancy/dated crypto, but it doesn't
appear that these have gone anywhere. For layer 3 networks, nla
doesn't really know what to do and so winds up just hashing the GUID
of the NetCfgInstanceId for the interface. This means that every
particular layer 3 interface instance implies only a single network.

This Network Signature stuff matters because Windows will
automatically fiddle with firewall rules based on which network you're
connected to. Forge a network, and you can perhaps get a victim to
have a more permissive firewall.

For WireGuard, we're using Wintun interfaces [1]. We could roll with a
single interface, which then would have the same Network Signature and
hence firewall settings, no matter which WireGuard peers its hooked up
with. Or, we could have a new interface for every new connection
event, but this means adding an unbounded amount of garbage to the
registry and cluttering people's interface names with things like
"Local Area Connection 28912", because this is the 28912th time you've
used WireGuard. Neither is so ideal.

Since Wintun is a layer 3 interface, it turns out "all" we need to do
is deterministically generate the GUID at adapter installation time. I
couldn't find any documented way of doing this. After reversing 3
kernel drivers and 5 userland DLLs, I found that Windows is using a
certain registry value as a sort of temporary storage in between two
phases that it runs serially. By using the super old and ugly
SetupAPI, we're actually able to split these phases up, so that we can
modify this registry key before the next phase sees it. This is sort
of like exploiting a race condition, except we're able to entirely
pause the race while making our modifications, so we have 100%
reliability. This winds up looking something like:

CallClassInstaller(DIF_REGISTER_COINSTALLERS)
key = OpenDevRegKey(DICS_FLAG_GLOBAL, DIREG_DRV)
key.SetString("NetSetupAnticipatedInstanceId", deterministicGUID)
CallClassInstaller(DIF_INSTALLINTERFACES)

At the time of writing, other than our own source code, there are no
hits for NetSetupAnticipatedInstanceId on Google, Bing, Yandex, or
Baidu, so this might be the first description of such a technique.

So, now that we can control the GUID and hence the NetworkSignature,
we have to decide what determines a network. It turns out that in
WireGuard, we can do this with much higher cryptographic assurance
than any of the crazy "authenticated dhcp" proposals of Microsoft.
Specifically, we know our own interface public key, the public keys of
everyone we're willing to talk to, and which IP addresses we'll accept
from those peers. If that doesn't perfectly define a network, I don't
know what else does.

Therefore, we sort the public keys, and sort the allowed IPs for each
one, and then hash together a big block that looks like:

 label || len(interface name) || interface name ||
 interface public key || number of peers ||
 peer public key || number of peer allowed ips ||
 len(allowed ip string) || allowed ip/cidr in canonical string notation ||
 len(allowed ip string) || allowed ip/cidr in canonical string notation ||
 len(allowed ip string) || allowed ip/cidr in canonical string notation ||
 ...
 peer public key || number of peer allowed ips ||
 len(allowed ip string) || allowed ip/cidr in canonical string notation ||
 len(allowed ip string) || allowed ip/cidr in canonical string notation ||
 len(allowed ip string) || allowed ip/cidr in canonical string notation ||
 ...
 ...

We pick a stable encoding for each of those elements (32-bit little
endian integers, canonical IP address strings, well-known label
identifier, etc), pass it all to BLAKE2, and mint ourselves a fully
network-determined GUID.

Hopefully this should eliminate a class of firewall attacks on Windows
systems when using WireGuard. Perhaps this post will help others out
somewhere down the line.

By the way, since Windows security is pretty complex and hard to get
right -- and hard to even keep all of it in your head at the same time
-- we're maintaining an attacksurface.md [2] file, which evolves over
time to contain our latest understanding. Hopefully this will help
people point out where we're wrong, and also what areas we've
neglected to consider. If something there piques your interest, don't
hesitate to get in touch with security {at} wireguard ^dot^ com.

Regards,
Jason

[1] https://www.wintun.net/
[2] https://git.zx2c4.com/wireguard-windows/about/attacksurface.md


More information about the WireGuard mailing list