Commit Graph

42 Commits

Author SHA1 Message Date
Jacob Paullus dfe4d2ea7b exec-tools: fix output polling for long-running commands
Two compounding bugs in wmiexec / atexec / smbexec / dcomexec made
the output-retrieval loop return early on any command that didn't
finish within the first 100ms poll, leaking the temp file in ADMIN$
/ C$. Issue #22 reports the symptom for wmiexec (systeminfo, ping);
the same broken pattern existed in all four tools.

1. Wrong call site. The polling loop treated a sharing-violation
   on smbClient.Cat as the signal "command still running." Cat
   opens with FILE_SHARE_READ-compatible access — it succeeds
   against the writer on the first poll and returns the file as
   it currently exists (typically empty). The conflict actually
   lives on Rm: deleting the file requires DELETE access, which
   conflicts with the writer's share mode. Moved the sharing-
   violation check from Cat to Rm. Matches Impacket's wmiexec.py.

2. Dead string match. The check was
   strings.Contains(err.Error(), "STATUS_SHARING_VIOLATION"). The
   underlying smb2 library's NtStatus.Error() returns only the
   human-readable description ("A file cannot be opened because
   the share access flags are incompatible.") — the literal
   constant name never appears in err.Error(), so the check could
   never fire. Same issue for STATUS_OBJECT_NAME_NOT_FOUND in the
   not-found branch (further muddled by smb2/conn.go translating
   that NTSTATUS to os.ErrNotExist before wrapping).

Added typed helpers in pkg/smb (IsSharingViolation, IsNotFound)
that unwrap through *os.PathError and match on
*smb2.ResponseError.Code plus the os.ErrNotExist sentinel.
NTSTATUS values are hardcoded since the smb2/internal/erref
package can't be imported from outside the smb2 tree.

The dcomexec "broken / connection reset / use of closed" branch
stays string-matched — those errors come from net, not smb2.

Thanks to @aimogging in #22 for the diagnosis and proof-of-concept
in #29; this change applies the same Cat-vs-Rm insight across all
four exec tools and replaces the err.Error() substring matching
with typed-error helpers so future smb2 library changes don't
silently break the check again.

Verified against GOAD winterfell: wmiexec whoami / systeminfo /
ping -n 4 1.1.1.1 all return full output, exit 0, no ADMIN$
residue, both directly from the operator box and over SOCKS5H
proxy.
2026-05-12 01:30:10 -05:00
Jacob Paullus ae12780402 Merge pull request #27 from mandiant/fix-hive-resident-bounds
registry: harden hive parser against malformed inputs
2026-05-11 23:59:26 -05:00
Jacob Paullus 703070cbd1 registry: add tests for resident-length guards
Cover the GetValueData guard added in the previous commit with two
table-driven tests: the rejection path (dataLen ∈ {5, 8, 0xFF,
0x7FFFFFFF} with resident bit set) confirms the guard fires without
panicking and surfaces a helpful error; the happy path (dataLen ∈
{1, 2, 3, 4}) confirms the inline DataOffset bytes are returned
correctly so the guard didn't regress valid hives.

The matching guards in SetValueData (resident + non-resident write
paths) and enumSubKeys (riSig branch) require full NK/VK/sub-list
hive synthesis to exercise end-to-end; they're structurally identical
to the read-path guard and are validated by build/vet plus the lab
regression check on secretsdump. Worth a follow-up to extend the
synth helpers and cover them directly.
2026-05-11 23:54:22 -05:00
Jacob Paullus d6089390bd registry: harden hive parser against malformed inputs
Four parser-panic / silent-corruption bugs in pkg/registry/hive.go,
all reachable from attacker-controlled hive bytes:

1. GetValueData resident branch: VKRecord.DataLen lower 31 bits are
   read verbatim and used to slice a 4-byte buffer at [:dataLen]. A
   DataLen of 0x80000005 panics with "slice bounds out of range".
   Found by kajaaz using Zorya (issue #25); fix matches her suggested
   one-line bounds check.

2. SetValueData resident branch (structurally identical to #1): the
   existing len(newData) == dataLen check doesn't enforce the 4-byte
   cap, so a hostile dataLen=5 with a matching 5-byte newData slices
   one byte past the DataOffset field into the adjacent cell. Same
   guard.

3. SetValueData non-resident branch: vk.DataOffset is attacker-
   controlled and the code does copy(h.data[dataPos:dataPos+dataLen],
   newData) without ever validating the destination. Hostile offsets
   either panic on out-of-bounds or silently scribble over arbitrary
   hive bytes (a value of 0xFFFFF000 lands near the regf header). Route
   through readCell (which validates the cell header and bounds) and
   verify dataLen fits before mutating.

4. enumSubKeys riSig branch: readCell can return a slice shorter than
   the 4-byte cell header (minimum-size cell with no usable bytes), so
   the immediate subCell[0:2] / subCell[2:4] reads can panic on a
   malformed sub-list. The sibling code at line 397/437 already guards
   the analogous index; mirror it here.

Fixes #25 plus three structurally similar bugs surfaced while patching.
2026-05-11 23:48:10 -05:00
Jacob Paullus 3a8420dc5b Merge pull request #24 from Jah-yee/fix-ace-parsepanic
security: reject ACE with AceSize below minimum header size
2026-05-11 23:31:51 -05:00
Jacob Paullus 33758f735c Merge pull request #26 from mandiant/kerberos-proxy-leak
kerberos, dcerpc: tunnel KDC traffic through pkg/transport
2026-05-11 23:24:03 -05:00
Jacob Paullus 8eea029431 kerberos, dcerpc: tunnel KDC traffic through pkg/transport
The embedded gokrb5/v8 library hard-coded net.DialTimeout for AS/TGS
exchanges, bypassing -proxy and leaking the operator's source IP to the
KDC (UDP/88 first, TCP/88 fallback). The DCERPC Kerberos auth path used
a separate library (oiweiwei/gokrb5.fork/v9 via go-msrpc) that leaked the
same way.

Vendor jcmturner/gokrb5/v8 in-tree at pkg/third_party/gokrb5 with a
required KDCDialer first argument on every client constructor, so
proxy-bypass becomes a compile error. Wire kerberos.TransportKDCDialer
everywhere a gokrb5 client is built. Stamp udp_preference_limit=1 and
dns_lookup_kdc/realm=false unconditionally so KRB5 is TCP-only and the
OS resolver is never consulted; /etc/krb5.conf and $KRB5_CONFIG are
deliberately not read.

For DCERPC: set krbConfig.KDCDialer on every krb5.Config, pass
dcerpc.WithDialer(transport.ContextDialer{}) on every dcerpc.Dial, and
use the "ncacn_ip_tcp:" StringBinding prefix on the OXID-pivot dial so
go-msrpc's hard-coded pre-dial net.LookupIP is skipped (defers FQDN
resolution to the SOCKS5 proxy).

Verified against a live GOAD lab: 8 Kerberos-touching tools plus 5
NTLM/password/PtH regressions all operate through SOCKS5 with zero
direct packets to the AD subnet. Negative control (no -proxy)
immediately emits direct SYNs to the KDC, confirming both the leak
class and the fix.
2026-05-11 23:23:21 -05:00
Jah-yee 6657f2dd4a security: reject ACE with AceSize below minimum header size
When AceSize is less than 8, the fixed 4-byte header plus 4-byte Mask
are not contained in the ACE.  The existing len(data) < aceSize check
passes (len(data) may be >= 8) but later slices like data[4:8] and
data[offset:aceSize] panic with 'slice bounds out of range'.

Fixes #23.
2026-05-05 13:47:21 +08:00
Jacob Paullus 5d927b8e6b Merge pull request #18 from mandiant/dependabot/go_modules/github.com/Azure/go-ntlmssp-0.1.1
build(deps): bump github.com/Azure/go-ntlmssp from 0.0.0-20221128193559-754e69321358 to 0.1.1
2026-04-24 11:38:50 -05:00
Jacob Paullus 8d16dfe1b0 Merge pull request #20 from mandiant/fix-restore-svcctl-tree
relay: re-TreeConnect IPC$ in RemoteRegistry restore
2026-04-24 11:33:19 -05:00
Jacob Paullus 2a36bf72eb Merge pull request #19 from mandiant/match-impacket-access-mask
relay: retire two known-issue bogeys (samdump ACCESS_DENIED and winreg PIPE_NOT_AVAILABLE)
2026-04-24 11:25:43 -05:00
psycep cd8463346c relay: re-TreeConnect IPC$ in RemoteRegistry restore
After a successful relay samdump/secretsdump the deferred
restoreRemoteRegistryState tried to CreatePipe("svcctl") and got
STATUS_OBJECT_NAME_NOT_FOUND (0xc0000034). The attack downloads its
hives from ADMIN$ before returning, which switches the SMB session's
tree away from IPC$, and svcctl lives on IPC$.

Fixed by re-TreeConnect'ing to IPC$ at the top of
restoreRemoteRegistryState. The function is now tree-state-agnostic
and safe to invoke from any defer position without coupling to the
attack's tree management.

Verified live against GOAD srv02 with RemoteRegistry set to
Stopped+Manual and eddard.stark relayed via net use: attack
auto-starts the service, dumps SAM hashes, then cleanup stops the
service and srv02's final state is Stopped+Manual again (was
Running+Manual before this fix).
2026-04-23 23:37:14 -05:00
psycep abbdbc19be relay: retire two known-issue bogeys (samdump ACCESS_DENIED and winreg PIPE_NOT_AVAILABLE)
Two entries in KNOWN_ISSUES.md described the relay samdump/secretsdump
attacks as broken in ways they aren't, or fixable in ways we hadn't
tried. Verified both live against GOAD and retired them.

KNOWN_ISSUES #1 ("SMB Relay Registry Access Denied")

Reproduced by coercing WINTERFELL$ via PetitPotam to relay-samdump
against srv02: BaseRegOpenKey(SYSTEM\Select) returns 0x00000005 as
documented. Then reproduced the documented "workaround works" direction
with NORTH\administrator direct auth (dumps cleanly). Then the test
the entry never tried: relayed a user with admin on the target via
cmd /c net use \\relay\IPC$ /user:north\eddard.stark (Domain Admin) on
dc02, watched it flow through our relay to srv02. Result: dumped
Administrator, Guest, DefaultAccount, WDAGUtilityAccount, vagrant SAM
hashes cleanly.

So the relay transport does not drop privilege. The symptom the entry
captured was simply "relayed principal doesn't have admin on target",
which is the same precondition Impacket's ntlmrelayx samdump has, and
the same constraint a direct secretsdump has. Rewrote the entry to say
that plainly and flag the PetitPotam pitfall (DC$ machine accounts
aren't admin on member servers, so coercion-to-relay against member
servers with default inventory always hits this).

Small alignment-with-Impacket change while there: pkg/dcerpc/winreg/
remote.go and pkg/relay/secretsdump_attack.go now request
MAXIMUM_ALLOWED on the boot-key subkey opens instead of KEY_READ,
matching rrp.hBaseRegOpenKey's default in Impacket. KEY_READ demands
the full read bundle; MAXIMUM_ALLOWED returns a handle with whatever
the token actually has. Doesn't fix the ACCESS_DENIED on a no-admin
token, but reduces the surface for partial-access edge cases.

KNOWN_ISSUES #3 ("Intermittent PIPE_NOT_AVAILABLE on winreg")

Same failure mode the standalone secretsdump handles already: if
RemoteRegistry is stopped or disabled, opening the winreg named pipe
fails with STATUS_PIPE_NOT_AVAILABLE. Standalone tools/secretsdump
opens svcctl first, starts RemoteRegistry, runs the attack, then stops
the service (and restores SERVICE_DISABLED if it was disabled). The
relay path wasn't doing any of that.

Factored the "ensure started on entry / restore on exit" flow into
pkg/relay/remoteregistry.go and wired it into both relay samdump and
secretsdump attacks via TreeConnect("IPC$"), open svcctl, manage
RemoteRegistry, proceed with winreg. Failures to manage the service
are logged as warnings rather than returned as errors: if the relayed
token lacks SERVICE_* access, the attack still tries the winreg open
and often succeeds (if the service happens to be running already).

Verified live: set srv02 RemoteRegistry to Stopped+Manual, relayed
eddard.stark (Domain Admin via net use), watched:
  [*] Service RemoteRegistry is in stopped state
  [*] Starting service RemoteRegistry
  [*] Target system bootKey: 0x...
  [*] Dumping local SAM hashes
  Administrator:500:...:...:::  (etc)
  [*] Cleanup complete

Cleanup's attempt to stop the service after dumping gets a
STATUS_OBJECT_NAME_NOT_FOUND (pipe was closed by the post-attack
session teardown); that warning is cosmetic. The service's start-type
was preserved (Manual), only its current state stayed Running. Leaving
that as a minor follow-up rather than blocking the fix.

KNOWN_ISSUES.md renumbered to reflect the two retirements.
2026-04-23 23:23:53 -05:00
dependabot[bot] 959eea06c4 build(deps): bump github.com/Azure/go-ntlmssp
Bumps [github.com/Azure/go-ntlmssp](https://github.com/Azure/go-ntlmssp) from 0.0.0-20221128193559-754e69321358 to 0.1.1.
- [Release notes](https://github.com/Azure/go-ntlmssp/releases)
- [Commits](https://github.com/Azure/go-ntlmssp/commits/v0.1.1)

---
updated-dependencies:
- dependency-name: github.com/Azure/go-ntlmssp
  dependency-version: 0.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 21:25:19 +00:00
Jacob Paullus af32683775 Merge pull request #17 from mandiant/fix-hive-readcell-panic
registry/hive: reject malformed cell sizes instead of panicking
2026-04-23 15:30:16 -05:00
psycep 2ba52b5116 registry/hive: reject malformed cell sizes instead of panicking
readCell trusted the cell's size header without bounds-checking the
positive side, so any cell whose raw size field was 0 or a negative
value with absolute magnitude smaller than the 4-byte cell header
produced an inverted slice expression (h.data[pos+4 : pos+size] with
size < 4) and crashed the process. Reported as a runtime panic
`slice bounds out of range [5052:5048]` in secretsdump's cached
domain logon parse path.

Treat raw size >= 0 as free/invalid (return error) and require the
allocated size to be at least 4 bytes so the data slice is never
inverted. Regression test synthesizes a minimal hive with each of
the previously-panicking inputs.

Removes the now-resolved entry from KNOWN_ISSUES.md and renumbers
the remaining sections.
2026-04-23 15:23:59 -05:00
Jacob Paullus 4b90cdefd0 Merge pull request #16 from mandiant/drsuapi-ndr-rewrite
drsuapi: fix DsGetNCChanges V6 parser to actually extract NTDS hashes
2026-04-23 14:59:37 -05:00
psycep 8127aece41 drsuapi: fix DsGetNCChanges V6 parser to actually extract NTDS hashes
Rewrites the hand-rolled DRS_MSG_GETCHGREPLY_V6 parser as a declarative
NDR struct walker. The old parser used empirical byte offsets that were
wrong in several places and drifted out of alignment within the first few
REPLENTINFLIST entries, so secretsdump emitted zero NTDS hashes against
any modern DC (issue #9).

New files:
  pkg/dcerpc/drsuapi/ndr.go        - NDR Decoder primitives: alignment,
    conformant arrays, pointer referents, UTF-16LE strings, GUIDs, with
    a sticky error model so partial results survive downstream faults.
  pkg/dcerpc/drsuapi/getncchanges_v6.go - V6/V7/V9 reply parser driven
    by MS-DRSR struct definitions.

Correctness points that were wrong in the old parser:

1. V6 fixed header is 148 bytes, not 136. The old code omitted cNumValues
   + rgValues + dwDRSError and treated those 12 bytes as pNC's DSNAME.
2. DSNAME.StringName is a pure conformant array (NDRUniConformantArray),
   not conformant-varying. No Offset/ActualCount between NameLen and the
   WCHAR elements.
3. UPTODATE_VECTOR is V2_EXT on current DCs: 32-byte cursors (UUID + USN
   + DSTIME), not the V1 24-byte cursors the old parser hard-coded.
4. REPLENTINFLIST is serialized as a linked list via pNextEntInf. NDR
   uses strict DFS through the first-encountered pointer in each struct,
   which lays out all N fixed parts consecutively, then the non-pNext
   deferreds (pName, pAttr, pParent, pMeta) unwind bottom-up - the
   deepest node's appear first, the head's last. The parser iterates
   captured headers in reverse to match.
5. PROPERTY_META_DATA_EXT_VECTOR alignment: the array's MaxCount is
   hoisted to the front by NDR early conformance, but hoisted MaxCount
   uses only its primitive (4-byte) alignment. The struct's 8-byte
   alignment applies AFTER MaxCount, before cNumProps and the elements.
   Getting this wrong drifted every entry whose prior pParent UUID
   landed on a 4-aligned (non-8-aligned) position.

Verification against live GOAD sevenkingdoms.local DC: 17/17 NTDS
password hashes (Administrator, krbtgt, vagrant, KINGSLANDING\$, ESSOS\$,
NORTH\$, and all domain users) match impacket-secretsdump byte-for-byte.
Independent Python byte-level reference walker confirms the same 17
unicodePwd-bearing entries offline.

Also removes ~950 lines of the now-unreachable legacy V6 helpers
(parseGetNCChangesResponseV6, skipDSNAME, skipUpToDateVector,
skipPropertyMetaDataExtVector, parsePrefixTable, parseREPLENTINFLIST,
parseENTINF, findValidDSNAME, parseDSNAMEIntoObject, parseATTRBLOCK)
from getncchanges.go.

Closes #9.
2026-04-23 14:52:39 -05:00
Jacob Paullus 5c03c57191 Merge pull request #15 from mandiant/smbclient-list-snapshots
smbclient: add list_snapshots command
2026-04-22 13:51:55 -05:00
psycep 100500df41 smbclient: add list_snapshots command
Fixes #8. smbclient's interactive shell now supports list_snapshots,
which enumerates VSS shadow copies on the currently selected share
via FSCTL_SRV_ENUMERATE_SNAPSHOTS. Matches Impacket smbclient.py's
list_snapshots output format.

The response follows MS-SMB2 2.2.32 and requires a two-call size
probe: the first ioctl uses a 16-byte output buffer to read the
SRV_SNAPSHOT_ARRAY header (SnapShotArraySize tells us how large the
second buffer needs to be), then a second ioctl asks for the full
payload and parses the UTF-16LE NUL-separated @GMT-... tokens.

Changes:
- pkg/third_party/smb2/client.go: add an exported Ioctl method on
  *File as a thin wrapper over the internal ioctl helper, so FSCTL
  operations beyond FSCTL_PIPE_TRANSCEIVE don't need their own
  dedicated method in the vendored library.
- pkg/smb/client.go: EnumerateSnapshots() method on *Client. Handles
  the size-probe dance, parses the UTF-16LE token list via
  pkg/utf16le, returns []string. Empty slice when no snapshots exist.
- tools/smbclient/main.go: wire list_snapshots into the shell's
  command switch and help text.

Unblocks the VSS-based NTDS extraction path:
  use c$
  list_snapshots      # get @GMT-YYYY.MM.DD-HH.MM.SS
  get @GMT-...\Windows\NTDS\ntds.dit
  secretsdump -ntds ntds.dit -system SYSTEM
2026-04-22 13:46:16 -05:00
Jacob Paullus a0afb8d5b9 Merge pull request #14 from mandiant/version-centralize
version: centralize banner through flags.Banner()
2026-04-22 13:33:23 -05:00
psycep 9e78779102 version: centralize banner through flags.Banner()
Before this, 26 tools hardcoded the literal "gopacket vX.Y.Z-beta -
Copyright 2026 Google LLC" banner in print statements, totaling 34
occurrences across the tree. Every version bump meant updating the
same string 34 times.

Switches every tool to call flags.Banner(), which builds the banner
from flags.Version. The Version const in pkg/flags/flags.go is now
the single source of truth; future version bumps are a one-line
change.

Mechanical split:
- 3 common print patterns (fmt.Println, fmt.Fprintln os.Stderr,
  fmt.Fprintf with trailing \n\n) swept with sed.
- 6 heredoc-style flag.Usage functions had the banner line on a
  backtick-quoted format string. Each was split into a separate
  fmt.Fprintln(os.Stderr, flags.Banner()) followed by the existing
  Fprintf with the banner line trimmed off the format string.
- 9 tools needed a new pkg/flags import added.
- tools/describeTicket already imports github.com/jcmturner/gokrb5
  /v8/iana/flags for Kerberos flag constants, so pkg/flags is aliased
  as gopflags there to avoid the name collision.

Verified: go build, go vet, and go test ./pkg/transport/ all clean
under default, CGO_ENABLED=0, and GOOS=windows CGO_ENABLED=0. Spot-
checked -h output on samrdump, rpcmap, mssqlinstance, ping, and
describeTicket; each renders the banner identically to before.
2026-04-22 13:25:06 -05:00
Jacob Paullus dd189fbad1 Merge pull request #13 from mandiant/dist-prefix
install.sh: prefix cross-compile outputs with gopacket-
2026-04-22 12:36:12 -05:00
psycep cae9eb3c79 install.sh: prefix cross-compile outputs with gopacket-
The native install already renamed each tool to gopacket-<toolname>
before copying to /usr/local/bin, but the portable and windows cross-
compile targets dropped raw binaries like ping.exe, net.exe, reg.exe,
attrib.exe, services.exe into ./dist/. When a user copies one of those
to a Windows host and runs it from the same directory, cmd.exe's PATH
resolution picks up the local binary before the Windows built-in of
the same name, shadowing tools users rely on. The portable target on
Linux has the same risk for binaries named ping, net, etc.

Applies the same normalization (lowercase, underscores to hyphens)
and gopacket- prefix the native install already uses, but at build
time for the cross-compile targets. Native behavior is unchanged:
./bin/ still gets raw names so install_native can prefix on copy.
2026-04-22 12:28:37 -05:00
Jacob Paullus 38474ef821 Merge pull request #12 from mandiant/windows-build
build: support Windows and CGO_ENABLED=0 targets
2026-04-22 12:11:08 -05:00
psycep 4b8ea3361a docs: show --target flag in README Installation section
The Platform Support table already described the three build targets
but the Installation block still only showed the default invocation.
A reader could see that a windows target existed without knowing how
to actually build it. Adds example --target invocations for portable,
windows, and all, and softens the dependency paragraph since GCC and
libpcap are only required for the native target.
2026-04-22 12:03:47 -05:00
psycep 362639a77e install.sh: add --target flag for portable and windows builds
Extends the installer to produce CGO_ENABLED=0 static Linux binaries
and GOOS=windows .exe cross-compiles, not just the default native
cgo build. New flag:

  --target native    (default) Linux/macOS cgo + install (today's flow)
  --target portable  CGO_ENABLED=0, host OS, output to ./dist/portable/
  --target windows   GOOS=windows amd64, output to ./dist/windows/
  --target all       build every target in one run

When run with no --target and a TTY on stdin, the installer prints an
interactive menu that explains each target briefly (tool set, proxy
path, output location). Non-TTY invocations (CI, pipes) default
silently to native so existing automation keeps working unchanged.

Cross-compile targets never install to /usr/local/bin. They drop
binaries in ./dist/<target>/ for the user to copy to the right host.
The libpcap check and GCC check are now conditional: required for the
native target, skipped for portable/windows since those paths use
build-tag stubs for libpcap/raw-socket tools.
2026-04-22 11:20:48 -05:00
psycep 82b955638e docs: document three-way build matrix in Platform Support
Replaces the "Linux/macOS only, use WSL on Windows" note with a
table showing the three supported build configurations (default cgo,
CGO_ENABLED=0 Linux, Windows), which tools are available in each,
and which proxy path works. Also drops a stray reference to a
specific KNOWN_ISSUES section number that will drift over time.
2026-04-22 10:46:52 -05:00
psycep c3173c535a tools: add build-tag stubs for Windows and CGO_ENABLED=0 builds
Enables clean builds under GOOS=windows CGO_ENABLED=0 and under
CGO_ENABLED=0 on Linux. The three tools that can't be built in those
configurations now substitute a stub that prints a clear message
and exits 1, so go build ./... succeeds across all target platforms.

- tools/sniff: tagged !windows && cgo (needs libpcap via cgo).
  Stub (main_stub.go) covers windows || !cgo.
- tools/split: same tags as sniff (also pcap).
- tools/sniffer: tagged !windows (uses Unix raw sockets directly via
  syscall, not cgo). Stub (main_windows.go) covers Windows.

Verified:
- go build ./... clean (default, cgo=1 Linux)
- CGO_ENABLED=0 go build ./... clean (portable Linux)
- GOOS=windows CGO_ENABLED=0 go build ./... clean (Windows)
- go vet clean across all three configurations
- All 13 pkg/transport tests pass under CGO_ENABLED=0, confirming
  direct_portable.go's directDial path is correct.
2026-04-22 10:46:38 -05:00
Jacob Paullus 4890b97162 Merge pull request #11 from mandiant/module-rename
module: rename to github.com/mandiant/gopacket
2026-04-22 10:29:58 -05:00
psycep ce5ca7f159 module: rename to github.com/mandiant/gopacket
Fixes #6. The module was declared as `module gopacket` in go.mod,
which is not a canonical import path. External projects could not
`go get github.com/mandiant/gopacket` to use any of the 24
protocol packages as a library; the only workaround was to clone
the repo and add a `replace` directive to their own go.mod.

This commit:
- Sets `module github.com/mandiant/gopacket` in go.mod.
- Rewrites 434 import statements across 157 files from the bare
  `gopacket/...` prefix to `github.com/mandiant/gopacket/...`.

Pure mechanical change, no behavior difference. Makes the README's
library story (pkg/ directory, 24 reusable packages) actually
usable from external code.
2026-04-22 10:27:18 -05:00
Jacob Paullus 8f997a5157 Merge pull request #10 from mandiant/proxy-support
transport: add SOCKS5 -proxy flag with UDP guard and test coverage
2026-04-22 10:15:36 -05:00
psycep e3f315d57b version: bump to v0.1.1-beta
Bumps the version string across pkg/flags and all tool banners to
v0.1.1-beta. Marks the first feature release after the initial beta
(adds the -proxy socks flag and UDP-under-proxy guard landing in
this PR).

Note: the version string is currently duplicated 34 times across 27
tool binaries and flags.go. A follow-up PR should refactor every
tool to call flags.Banner() instead of hardcoding the literal, so
future bumps are one-line changes.
2026-04-22 10:14:45 -05:00
psycep f67bee32cb docs: document -proxy and UDP-under-proxy limitation
- README.md: expand "Proxychains Support" into "Proxy Support" with
  two subsections (proxychains via LD_PRELOAD, and the new -proxy
  SOCKS5 flag). Adds -proxy to the Common Flags table, adds a quick
  example, and updates the final Notes line so "all tools work
  through proxychains" also covers -proxy.
- KNOWN_ISSUES.md: add entry #8 "UDP Features Disabled Under -proxy"
  with a table mapping affected tools to concrete workarounds
  (supply -port directly, pass -dc-host / -dc-ip / -target-ip,
  supply -parent-dc). Renumbers "Remaining Gaps" to #9.
2026-04-22 09:54:09 -05:00
psycep bf605498aa tools: wire -proxy into hand-rolled flag tools
Four tools register their flags directly via the stdlib flag package
rather than going through flags.Parse, so they didn't inherit -proxy
and transport.Configure was never called (silent no-op for the user).

Wires each to flags.RegisterProxyFlag, a two-line integration that
registers -proxy and returns a finalizer to call after flag.Parse():

- tools/CheckLDAPStatus
- tools/DumpNTLMInfo
- tools/rpcmap (also adds the pkg/flags import)
- tools/mssqlinstance (also adds the pkg/flags import; -proxy will
  immediately error with ErrUDPUnderProxy since the tool's whole job
  is a UDP SQL Browser probe, but the error is surfaced clearly
  instead of silently bypassing)
2026-04-22 09:54:09 -05:00
psycep 990c8bbca0 tools: migrate flags.Parse tools to transport
Four tools that already used flags.Parse (and therefore already had
-proxy registered transparently) still reached out via net.Dial* or
ran DNS resolution past the proxy. Migrate the network calls:

- tools/GetADComputers: custom net.Resolver now dials via
  transport.DialContext, so UDP DNS through the DC surfaces
  ErrUDPUnderProxy cleanly rather than bypassing -proxy.
- tools/changepasswd: kpasswd TCP dial now uses transport.DialTimeout.
- tools/smbexec: getLocalIP() short-circuits when
  transport.IsProxyConfigured(). The "dial UDP to find the local
  source address" trick has no meaning when traffic flows through a
  SOCKS5 proxy.
- tools/raiseChild: the DNS forest-FQDN fallback (net.LookupHost) is
  skipped under -proxy with a message pointing the user to -parent-dc,
  since net.LookupHost goes to the OS resolver and would leak DNS.
2026-04-22 09:54:09 -05:00
psycep efccf92228 relay, tds: route outbound through transport
- pkg/relay/http_client.go, winrm_client.go: swap the http.Transport
  DialContext from &net.Dialer{Timeout: 10s}.DialContext to a closure
  that calls transport.DialContext. NTLM relay now respects -proxy.
- pkg/relay/socks.go: add a comment on the HTTP DNS passthrough path
  explaining why it intentionally uses net.DialTimeout rather than
  transport. That path is the outbound leg of our own SOCKS5 server,
  so routing it through -proxy would double-tunnel the operator's
  proxy through the operator's proxy.
- pkg/tds/sqlr.go: SQL Server Browser discovery (UDP 1434) now uses
  transport.DialUDP, which surfaces ErrUDPUnderProxy when a proxy is
  configured rather than silently bypassing it.
2026-04-22 09:54:09 -05:00
psycep 7a1914a077 flags: add -proxy flag and RegisterProxyFlag helper
Introduces three exports in pkg/flags:
- ProxyFlagUsage: the shared -proxy usage string, so tools that hand-roll
  their flag setup render identical help text to Parse().
- ConfigureProxy(url): calls transport.Configure and exits on error.
  Centralizes the "fail loud on bad -proxy" behavior.
- RegisterProxyFlag(): registers -proxy on the default flag.CommandLine
  and returns a finalizer to call after flag.Parse(). Two-line
  integration for tools that don't use flags.Parse().

Parse() now uses these internally. It also calls ConfigureProxy and
applies Debug/Timestamp unconditionally rather than returning early when
NArg == 0; tools that take all their config via flags (listeners, etc.)
were previously losing -proxy because the early return skipped Configure.
2026-04-22 09:54:09 -05:00
psycep 90038dcfc2 transport: add SOCKS5 proxy support with UDP-under-proxy guard
Splits pkg/transport into a router (tcp.go) plus two platform-specific
direct dialers: direct_libc.go preserves the existing cgo libc connect()
path on Unix (so proxychains can still hook it), and direct_portable.go
provides a pure-Go fallback for CGO_ENABLED=0 and Windows builds.

Adds proxy.go with:
- Configure(Options) / ConfigureProxy to wire a SOCKS5 URL (socks5 or
  socks5h), with ALL_PROXY / all_proxy env fallback read directly via
  os.Getenv (not proxy.FromEnvironment, whose sync.Once cache is not
  test-friendly).
- DialContext routing through the configured proxy, or directDial.
- DialUDP returning ErrUDPUnderProxy when proxied, rather than silently
  leaking UDP packets past the SOCKS5.
- IsProxyConfigured and ProxyURL for callers that need to short-circuit
  features that can't be tunneled.
- libcForwarder so the TCP leg to the SOCKS5 proxy itself still goes
  through libc, enabling proxychains -> gopacket -> -proxy chaining.

Tests:
- export_test.go exposes ResetForTest for test isolation (Configure
  panics on double-call, so each test resets package state).
- socks5_server_test.go hand-rolls a minimal in-process SOCKS5 server
  (CONNECT + no-auth, ~130 LOC, no new deps).
- proxy_test.go covers invalid scheme rejection, socks5/socks5h accept,
  ALL_PROXY env fallback, double-call panic, default state,
  DialUDP/DialContext UDP rejection, credential redaction, direct-path
  behavior when unconfigured, and an end-to-end round-trip through the
  in-process SOCKS5.
2026-04-22 09:54:09 -05:00
psycep 2c01188d2d Add Platform Support note: Linux/macOS only, use WSL on Windows 2026-04-19 19:34:01 -05:00
Jacob Paullus 616dd65d38 Add wiki link to README 2026-04-17 14:45:33 -05:00
Jacob Paullus 160b6a4280 Initial commit 2026-04-17 14:20:41 -05:00