# SkunkedApostle
Rust implementation of THC's "gsocket" protocol, with example implementation of gs-netcat.

It is just slop code to see what the LLM could do while I was kinda bored at work, but I think it is kinda neat to have and lets you use GSRN from your rust projects.

---

## About

A memory-safe Rust port of [`gsocket`][upstream-c], the Global Socket Relay
Network (GSRN) library. Lets two processes anywhere on the internet talk to
each other over an end-to-end encrypted tunnel, brokered by the public GSRN
relay at `gs.thc.org`. No port forwarding, no public IP, no NAT punch.

Wire-compatible with the C reference, so a Rust peer can talk to a C peer
on the other end (verified against the public GSRN). The whole workspace
is `#![forbid(unsafe_code)]` and ships 72 unit tests.

[upstream-c]: https://github.com/hackerschoice/gsocket

---

## Workspace

| Crate              | What it does                                                           |
| ------------------ | ---------------------------------------------------------------------- |
| `gsocket-proto`    | Wire-format codec for GSRN messages (LISTEN/CONNECT/START/PING/…) and the secret → address / SRP-password KDF |
| `gsocket-tls-srp`  | Hand-rolled TLS 1.2 with SRP-SHA1 key exchange and AES-256-CBC. We hand-roll because rustls doesn't implement SRP and the C reference uses OpenSSL's `SRP-AES-256-CBC-SHA` cipher suite |
| `gsocket-pktmgr`   | In-band packet manager (0xFB escape framing) and file-transfer state machines that ride inside the encrypted tunnel |
| `gsocket`          | Async Tokio client tying the above together: GSRN handshake + SRP-TLS handshake → an `AsyncRead + AsyncWrite` stream |
| `gs-netcat`        | The `nc`-style CLI: reverse shells, port forwards, SOCKS5 proxy, file transfer |

---

## Install the CLI

```sh
cargo install gs-netcat              # from crates.io
# or, from a checkout of this repo:
cargo install --path gs-netcat
```

Both ends share a secret. Generate one with `openssl rand -hex 16` (or
anything else) and use it on both sides.

## CLI quickstart

All commands take `-s SECRET` (or `GSOCKET_SECRET=…`); both ends must use
the same value.

### Reverse shell

```sh
# Server (whoever should run the shell):
gs-netcat -l -i -e "bash -il" -s "$SECRET"

# Client:
gs-netcat -i -s "$SECRET"
```

The server's `bash` runs in a PTY; on the client, `-i` puts the local
terminal into raw mode so keystrokes pass through directly.

### Port forwarding

Forward a remote service to a local port without opening any inbound
firewall hole:

```sh
# Server, exposing 192.168.1.10:22 over GSRN:
gs-netcat -l -d 192.168.1.10 -p 22 -s "$SECRET"

# Client, accepting on local port 2222:
gs-netcat -p 2222 -s "$SECRET"
ssh -p 2222 user@127.0.0.1
```

### SOCKS5

```sh
# Server:
gs-netcat -l -S -s "$SECRET"

# Client (binds local SOCKS5 on 1080):
gs-netcat -p 1080 -s "$SECRET"
curl --socks5 127.0.0.1:1080 https://example.com
```

### File transfer

Server runs in interactive/exec mode so the in-band packet manager is
enabled:

```sh
gs-netcat -l -i -e "sleep 3600" -s "$SECRET"
```

Then from the client:

```sh
# Upload one or more files
gs-netcat --put file1.tar.gz --put dir/file2.bin -s "$SECRET"

# List remote files
gs-netcat --ls '*.log' -s "$SECRET"

# Download a single file
gs-netcat --get archive.tar.gz --save-as ./local-copy.tar.gz -s "$SECRET"

# Receive uploads into a directory (loops to accept many)
gs-netcat -l --receive-dir ./incoming -s "$SECRET"
```

### Plain stdio

Without `-i`, `-e`, `-S`, or `-p`, `gs-netcat` just pipes stdin↔stdout
through the encrypted tunnel — the classic `nc` mode:

```sh
gs-netcat -l -s "$SECRET" > received.tar
gs-netcat   -s "$SECRET" < send.tar
```

### Other flags

| Flag                   | Meaning                                                          |
| ---------------------- | ---------------------------------------------------------------- |
| `-l, --listen`         | Listening side                                                   |
| `-w, --wait`           | Block until peer becomes available instead of failing            |
| `--host HOST`          | Override the GSRN hostname (default `gs.thc.org`)                |
| `-d, --dest IP`        | Destination IP for server-side port forward (default `127.0.0.1`) |
| `--bind ADDR`          | Local bind address for client-side port forward                  |

Run `gs-netcat --help` for the full list.

---

## Library usage

Add to your `Cargo.toml`:

```toml
[dependencies]
gsocket        = "0.1"
gsocket-proto  = "0.1"
tokio          = { version = "1", features = ["full"] }
```

End-to-end: derive an address from a shared secret, perform the GSRN
handshake, then run the SRP-TLS handshake to get an encrypted
`AsyncRead + AsyncWrite` stream.

```rust
use gsocket::{
    client_handshake_with_prefix, handshake, server_handshake_with_prefix,
    GsConfig, Role,
};
use gsocket_proto::{GsSecret, StartRole};

async fn open_session(secret_str: &str, role: Role) -> anyhow::Result<()> {
    let secret = GsSecret::new(secret_str.to_string());
    let cfg = GsConfig::new(secret.derive_addr(), role);

    // 1. GSRN handshake — TCP to the relay, send LISTEN/CONNECT, wait
    //    for START.
    let relay = handshake(cfg).await?;

    // 2. SRP-TLS handshake over the matched stream. Whoever LISTENed
    //    becomes TLS server; whoever CONNECTed becomes TLS client.
    let wrapped = secret.derive_srp_password().wrapped().to_vec();
    let tls = match relay.role {
        StartRole::Client => {
            client_handshake_with_prefix(
                relay.stream,
                relay.initial_bytes,
                b"user".to_vec(), // SRP identity (any non-empty value)
                wrapped,
            )
            .await?
        }
        StartRole::Server => {
            server_handshake_with_prefix(relay.stream, relay.initial_bytes, wrapped)
                .await?
        }
    };

    // 3. `tls` is now an encrypted byte pipe to the peer.
    //    AsyncReadExt::read / AsyncWriteExt::write_all work as usual.
    let _ = tls;
    Ok(())
}
```

Why `_with_prefix`? The GSRN handshake may consume bytes that already
belong to the TLS layer (peer messages arrive coalesced with the final
GSRN START). `RelayStream::initial_bytes` carries those bytes; the
`_with_prefix` variant injects them at the start of the TLS read path.
If you don't need that — you're feeding TLS over a fresh stream — use
the plain `client_handshake` / `server_handshake`.

### File-transfer state machine

The `gsocket-pktmgr` crate exposes the framing and the `PutSender` /
`PutReceiver` state machines. Drive them with a TLS stream from
`gsocket` to send/receive files over the same encrypted tunnel:

```rust
use gsocket_pktmgr::{Pkt, PktEvent};
use gsocket_pktmgr::ft::{PutSender, PutReceiver};
```

See `gs-netcat/src/main.rs` (`ft_send_one_inner`, `ft_receive_loop`,
`ft_get_one`, `ft_ls`) for complete, working examples.

---

## Wire compatibility

The Rust port talks to the existing C `gs-netcat` over the public GSRN.
This was the design constraint, and a few things had to be preserved
bit-for-bit:

- **SRP password derivation** has a 33-character truncation of the
  SHA-256 hex string. The C code's fixed-size buffer caused this and
  every C release since has kept it. See
  `gsocket-proto/src/address.rs` and the `srp_password_preserves_c_truncation_quirk`
  test.
- **SRP secret wrapping** wraps the 33-byte derived value as
  `"Blah." + raw + ".blubb-SRPSEC"` before handing it to the SRP
  layer. Required by OpenSSL's SRP API in the C reference.
- **GSRN token linger** — re-LISTENs from the same logical process
  must reuse the same token within `GSRN_TOKEN_LINGER_SEC` (7 s) or
  the relay rejects. `GsConfig::new` generates one random token;
  clone the config to reuse it across re-LISTENs.
- **TLS layer** is TLS 1.2 with `SRP-AES-256-CBC-SHA` only. rustls
  doesn't support SRP at all; that's why this workspace ships its
  own hand-rolled TLS in `gsocket-tls-srp`. It is intentionally minimal
  — exactly one cipher suite — and not a general-purpose TLS stack.

---

## Building and testing

```sh
cargo build --workspace
cargo test  --workspace                   # 72 unit tests
cargo doc   --workspace --no-deps         # rustdoc
```

For interop tests against the C reference, build it in
`gsocket/tools/gs-netcat` from the upstream repo, then run a peer with
a shared secret and exercise the Rust client. The relay is at
`gs.thc.org`; everything goes through TCP/443.

---

## Safety

```toml
[workspace.lints.rust]
unsafe_code = "forbid"
```

No `unsafe` anywhere in the workspace. Cryptographic primitives come
from the `RustCrypto` ecosystem (`sha2`, `sha1`, `hmac`, `aes`, `cbc`)
and `num-bigint-dig` for SRP big-int math.

---

## License

BSD-2-Clause, matching the upstream C project.

This is an independent reimplementation of the [`gsocket`][upstream-c]
library by The Hacker's Choice. Wire-format and cryptographic protocol
details are theirs; the Rust implementation is original.

## Thanks

THC guys for being awesome and writing gsocket and running the GSRN infrastructure.  
My colleagues, for letting me fuck around on the computer for a bit.  
Whoever is paying for the starlink.  

## Issues/Complaints  

idk man. I might be able to help, maybe not. 

## Support my guys.

If you enjoyed this, supporting my guys would be appreciated. There is a fundraiser for the team running right now. [donation link](https://dzygaspaw.com/25th-strike?project=25th%20Strike).



This thing created somewhere in the Pokrovsk direction.  
