Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions _posts/2026-02-09-sockstrace-dns-proxification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# DNS Proxification in SocksTrace

Most SOCKS5 proxies (including Tor) only handle TCP traffic. DNS queries typically use UDP to servers like `8.8.8.8:53`. If those queries go through directly, they bypass the proxy entirely, leaking both the query and potentially your real IP address.

Getting DNS proxification right took three attempts.

## First Attempt: Intercept and Answer

The initial idea was straightforward. Intercept DNS at the syscall level and answer it ourselves.

We watched `sendmsg()` and checked whether the destination port was 53. If so, we extracted the queried domain, resolved it via Tor, and tried to send the DNS response back to the application directly.

This approach failed at the kernel boundary.

The DNS reply was blocked because the kernel dropped it. The source IP of the response didn't match the destination IP the application originally contacted. From the kernel's perspective, the packet was invalid, so it never reached userspace.

## Second Attempt: IP_TRANSPARENT

To work around this, we experimented with `IP_TRANSPARENT`.

`IP_TRANSPARENT` allows a process to bind and send packets using non-local source addresses, effectively impersonating the original DNS server. With this enabled, the kernel accepts the reply because the source and destination IPs now match what the application expects.

Technically, this fixed the problem.

Practically, it wasn't acceptable. It requires elevated privileges (`CAP_NET_ADMIN` or root).

## Third Attempt: Socket Hijacking

The final approach was to stop faking DNS responses and instead take control of the socket itself.

SocksTrace starts two local DNS servers, one for TCP and one for UDP. These servers resolve all incoming queries using Tor and return standard DNS responses.

When an application performs a DNS lookup, we intercept the `connect` syscall and hijack the socket at the kernel boundary. Rather than letting the socket reach the original resolver, we rebind the socket's communication path so that all subsequent reads and writes are transparently routed to our local DNS server.

From that point on, the socket behaves exactly as the application expects. The application continues unmodified, believes it is talking to a normal DNS resolver, and receives valid responses, while all resolution is enforced through Tor.

## Connectionless DNS Handling

Unlike TCP, UDP DNS does not require a `connect` syscall. Applications can send queries directly via `sendmsg()`, bypassing our socket-setup interception.

To detect this, SocksTrace checks whether a UDP packet sent to port 53 is using a socket that hasn't been previously connected. If so, we intercept the `sendmsg` syscall and rewrite the destination address in the application's memory before the syscall completes, redirecting its traffic to the local UDP DNS server.

## The Implementation

**For `connect()` syscalls** (when the app calls `connect()` on a socket to `8.8.8.8:53`):
We hijack the socket itself by directly connecting it to our local server:

```go
case "UDP":
if err := unix.Connect(localFd, localDNSAddrUDP); err != nil {
// handle error
}
```

**For `sendmsg()` syscalls** (when the app sends data with a destination address embedded):
We rewrite the destination address in the application's memory before the syscall completes:

```go
if err := writeSockaddrInet4(mem, namePtr, localDNSAddrUDP); err != nil {
// handle error
}
```

Either way, the app thinks it's talking to `8.8.8.8:53`, but the traffic goes to `127.0.0.1:randomport` instead. The local DNS server resolves the query through Tor using the SOCKS5 RESOLVE extension (command `0xF0`) and returns a standard DNS response.

## How Tor RESOLVE Works

The SOCKS5 protocol defines standard commands like CONNECT (0x01) for TCP connections. Tor extends this with RESOLVE (0xF0), which asks Tor to perform DNS resolution at the exit node.

The handshake is standard SOCKS5:

```
Client → Tor: VER=5, NMETHODS=1, METHODS=0x00 (No Auth)
Tor → Client: VER=5, METHOD=0x00
```

Then we send the RESOLVE request:

```
Client → Tor: VER=5, CMD=0xF0, RSV=0, ATYP=3 (Domain), LEN, DOMAIN, PORT=0
```

Tor performs the DNS lookup at the exit node and responds with the resolved IP:

```
Tor → Client: VER=5, REP=0, RSV=0, ATYP=1 (IPv4), IP, PORT=0
```

This is how DNS resolution stays anonymous. The lookup happens at the Tor exit node, not on your local network. Your ISP never sees the DNS query.
70 changes: 70 additions & 0 deletions _posts/2026-02-09-sockstrace-webrtc-unix-sockets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# WebRTC Proxy Leaks and Unix Socket Enforcement

## WebRTC Route Discovery and Proxy Leak Semantics

SocksTrace identified a WebRTC privacy vulnerability caused by a technique used during ICE (Interactive Connectivity Establishment) negotiation.

Browsers issue short-lived `connect()` calls on UDP sockets to Google DNS addresses (`8.8.8.8:53`) to determine the default local route. No packets are sent and the socket is immediately closed, but the syscalls bypass proxy routing and expose local addressing information.

### The Connected UDP Trick

These are `connect()` calls on UDP sockets used to discover which local address would be used to route to the internet in general. This is often called the "connected UDP trick."

From a network monitoring perspective, nothing happens. Wireshark would show no packets. But from a syscall monitoring perspective, the application is making direct network calls that bypass the proxy and reveal local routing information.

### What Counts as a Proxy Leak?

In practice, "proxy leak" can refer to multiple classes of behavior:

1. Outbound packets that do not go through the configured proxy.
2. Inbound or listening behavior that exposes network reachability outside the expected proxied path.
3. Any direct interaction with the host network stack that bypasses the proxy, even if no packets are emitted.

Wireshark is excellent for class (1). Port scanners and reachability tests can detect class (2). SocksTrace can detect all three classes because it monitors syscalls and socket behavior directly.

The WebRTC route-discovery issue described above is class (3): no packets are transmitted, but privacy-sensitive network stack interaction still occurs outside the proxy path.

### Browser Responses

**Brave (Tor mode)** was affected by this issue. Brave acknowledged it and mitigated it by disabling WebRTC in Tor mode. The report was accepted and resulted in a **$400 bug bounty**.

**The Tor Browser Team** had already mitigated this issue in Mullvad Browser by disabling the "connected UDP trick" entirely, because leaking information about the user's local network, even without transmitting traffic outside the proxy, poses a privacy risk and could be used for fingerprinting.

This made it straightforward to confirm that SocksTrace was detecting a genuine proxy leak rather than a false positive. Mullvad Browser does not expose local addresses during ICE negotiation.

### Why Syscall Monitoring Matters

This illustrates a key advantage of SocksTrace. By monitoring syscalls directly, it can detect subtle privacy issues that tools like Wireshark would never reveal.

The `connect()` syscall on a UDP socket doesn't send any packets. It just tells the kernel to associate that socket with a destination address, which causes the kernel to select a local source address based on routing. No data leaves the machine, so network sniffers see nothing.

But the syscall still happened. The application still bypassed the proxy. And local routing information was still exposed. SocksTrace catches this because it operates at the syscall level, not the packet level.

## Unix Domain Socket Enforcement

Unix domain sockets use filesystem paths instead of network addresses. They cannot leak over the network because they only exist locally on the machine.

### Advantages of Unix Sockets

**Performance.** Unix sockets bypass the TCP/IP stack entirely. There's no network layer processing, no routing decisions, no packet fragmentation. Data is copied directly between processes.

**Lower resource overhead.** No need to allocate ports, maintain connection state for the network stack, or handle TCP retransmission logic. The kernel handles it as a simple IPC mechanism.

**Security.** Unix sockets cannot be accessed from another machine. Access is controlled by filesystem permissions.

### Enforcement

The `--enforce-unix-socks` flag enables a whitelist approach. Only `AF_UNIX` connections are allowed. Everything else is blocked.

```go
if enforceUnixSocks {
if addr.Family != unix.AF_UNIX {
logger.Warn().Msgf("%s blocked (only Unix sockets allowed): %s", syscallName, addr.String())
return 0, 0, 0
}
}
```

This is useful when Tor is configured to listen on a Unix socket (e.g., `/run/tor/socks`) instead of a TCP port. By forcing applications to only use Unix sockets for SOCKS connections, any attempt to open an IPv4 or IPv6 connection is blocked.

The address parser handles both filesystem paths and abstract sockets (paths starting with null bytes).