Merge pull request #7 from Harrison-Wells-Cyber/codex/fix-tunnel-error-in-powershell-proxy-xwmkt5

Add DNS-over-TCP relay and fallback, accept legacy OAEP-SHA1 handshake, and serialize secure-frame sends
This commit is contained in:
Harrison-Wells-Cyber
2026-07-23 07:51:33 -07:00
committed by GitHub
2 changed files with 30 additions and 17 deletions
+12 -1
View File
@@ -111,6 +111,13 @@ the original destination with `SO_ORIGINAL_DST`, sends that host:port through th
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.
By default that is `0.0.0.0:443`, so another web server or reverse proxy cannot
bind port 443 on the same IP at the same time. If you need another service on
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:
```powershell
@@ -148,7 +155,11 @@ ldapsearch -x -H ldap://127.0.0.1:1389 -D 'user@example.local' -W -b 'DC=example
Transparent redirect mode is the recommended test-environment workflow right now.
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 a UDP DNS listener and forwards raw DNS queries through the enrolled agent to the internal DNS server reachable from the agent host.
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
+18 -16
View File
@@ -276,24 +276,26 @@ namespace PSProxy.Agent
{
if (f.Payload == null) f.Payload = new byte[0];
if (f.Payload.Length > MaxPayload) throw new InvalidOperationException("frame payload too large");
byte[] plainFrame = EncodePlainFrame(f);
byte[] seq = U64BE(sendSeq);
byte[] plain = Concat(seq, plainFrame);
byte[] padded = Pkcs7Pad(plain, 16);
byte[] iv = RandomBytes(16);
byte[] ct;
using (var aes = Aes.Create())
{
aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; aes.Key = encKey; aes.IV = iv;
using (var enc = aes.CreateEncryptor()) { ct = enc.TransformFinalBlock(padded, 0, padded.Length); }
}
byte[] body = Concat(iv, ct);
byte[] tag;
using (var h = new HMACSHA256(macKey)) { tag = h.ComputeHash(Concat(seq, body)); }
byte[] rec = Concat(body, tag);
byte[] len = new byte[4]; WriteI32BE(len, 0, rec.Length);
lock (sendLock)
{
// The sequence number is part of both plaintext and HMAC input, so
// frame construction must be serialized with the write/increment.
byte[] plainFrame = EncodePlainFrame(f);
byte[] seq = U64BE(sendSeq);
byte[] plain = Concat(seq, plainFrame);
byte[] padded = Pkcs7Pad(plain, 16);
byte[] iv = RandomBytes(16);
byte[] ct;
using (var aes = Aes.Create())
{
aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; aes.Key = encKey; aes.IV = iv;
using (var enc = aes.CreateEncryptor()) { ct = enc.TransformFinalBlock(padded, 0, padded.Length); }
}
byte[] body = Concat(iv, ct);
byte[] tag;
using (var h = new HMACSHA256(macKey)) { tag = h.ComputeHash(Concat(seq, body)); }
byte[] rec = Concat(body, tag);
byte[] len = new byte[4]; WriteI32BE(len, 0, rec.Length);
tls.Write(len, 0, len.Length); tls.Write(rec, 0, rec.Length); tls.Flush();
sendSeq++;
}