Skip to content

Integrate create-shadowcopy and skip-preauth-probe features - #6

Merged
aconite33 merged 13 commits into
masterfrom
integration/blacklantern-features
Jul 22, 2026
Merged

Integrate create-shadowcopy and skip-preauth-probe features#6
aconite33 merged 13 commits into
masterfrom
integration/blacklantern-features

Conversation

@aconite33

Copy link
Copy Markdown

Summary

Combines two feature branches into master:

  • feature/create-shadowcopyexamples/create_shadowcopy.py: VSS shadow copy management via WMI DCOM (create, list, expose, delete). Complements the raw-ntfs-parse toolkit for offline NTDS/hive extraction.
  • fix/skip-preauth-probe — Kerberos hardening across impacket/krb5/:
    • AS-REQ wire format aligned with Linux kinit to avoid honeypot fingerprinting
    • PA_ENC_TIMESTAMP included in first AS-REQ to skip the bare preauth probe
    • PA_PAC_REQUEST added to probe AS-REQ (removed once the preauth flow completes) for accounts without preauth
    • TGS-REQ wire format aligned with Linux kinit/kvno
    • KeyError fix when KDC does not offer AES256
    • Cross-realm ccache lookup fix + new examples/cross_realm_tgs.py
    • Sapphire ticket generation fix when authenticating via ccache (-k)

Both branches were rebased onto current master (after the recent fortra merge that added NEGOEX phase 1/2 and DFS support) so no upstream work is reverted.

Files changed

File Kind
examples/create_shadowcopy.py new (253 lines)
examples/cross_realm_tgs.py new (604 lines)
examples/ticketer.py modified
impacket/krb5/ccache.py modified
impacket/krb5/constants.py modified
impacket/krb5/kerberosv5.py modified

Total: +1,142 / -142 across 6 files.

Test plan

  • create_shadowcopy.py create <target> against a lab domain controller — confirm shadow copy is created and device path is printed
  • create_shadowcopy.py list <target> — confirm existing snapshots enumerate
  • Standard AS-REQ auth (GetUserSPNs.py, secretsdump.py -k) against a lab KDC — confirm no regressions
  • AS-REQ against an account with DONT_REQ_PREAUTH set — confirm the probe path still works
  • cross_realm_tgs.py against a trusted realm pair — confirm ticket acquisition
  • Sapphire ticket generation via ticketer.py -k (ccache auth path)
  • Ensure NEGOEX auth flow, DFS traversal in smbclient, and epm still work end-to-end

Notes

Uses --no-ff merges so each source branch has its own merge commit + preserved history on the integration branch.

aconite33 and others added 12 commits July 22, 2026 06:54
Standalone impacket script to create, list, and delete VSS shadow copies
on a remote machine using Win32_ShadowCopy over DCOM. No vssadmin process
is spawned on the target, avoiding process creation telemetry.
The original getKerberosTGT() sent an initial AS-REQ without
PA_ENC_TIMESTAMP to discover the KDC's supported etypes and salt,
then followed up with an authenticated AS-REQ. This two-step pattern
is detectable as "AS-REQ without pre-authentication data followed by
TGS-REQ" and fingerprints impacket on the wire.

This change computes the Kerberos salt locally using the standard AD
convention (REALM + sAMAccountName) and includes PA_ENC_TIMESTAMP in
the very first AS-REQ. If the salt guess is wrong (e.g. case mismatch
or renamed account), the correct salt is extracted from the
KDC_ERR_PREAUTH_FAILED error response's e-data and the request is
retried — still with PA_ENC_TIMESTAMP, so no bare probe ever hits
the wire.

The kerberoast_no_preauth path (AS-REP roasting) is preserved as a
separate early-return branch that intentionally omits PA_ENC_TIMESTAMP.
Rewrite getKerberosTGT() and sendReceive() so the Kerberos AS-REQ
is indistinguishable from MIT krb5 kinit on the wire:

- Transport: UDP first with TCP fallback on KRB_ERR_RESPONSE_TOO_BIG
- kdc-options: renewable_ok only (kinit default), overridable via
  KRBTGTFLAGS env var for delegation attacks
- sname type: NT_SRV_INST (2) for krbtgt, per RFC 4120
- etype list: broad 8-type list matching MIT default_enctype_list
- rtime: omitted (kinit only sets it with explicit renew lifetime)
- padata: two-step flow with PA_AS_FRESHNESS (150) and
  PA_REQ_ENC_PA_REP (149) instead of PA_PAC_REQUEST (128)

Also adds missing EncryptionTypes (19, 20, 25, 26) and
PreAuthenticationDataTypes (149, 150) to constants.
Older domains may only support RC4. The probe AS-REQ advertises the
broad kinit etype list, but the KDC returns only its supported etypes
in the PREAUTH_REQUIRED response. Pick the first KDC-supported etype
instead of assuming our preference is available.
Match getKerberosTGS() fields to what kvno produces on the wire:

- kdc-options: add canonicalize flag (renewable+canonicalize)
- etype list: kinit-style AES-first broad list, remove DES-CBC-MD5
- sname type: NT_PRINCIPAL (1) matching kvno behavior
Accounts with DONT_REQUIRE_PREAUTH return an AS-REP directly from
the probe. Without PA_PAC_REQUEST the KDC response could be missing
enc-part, causing PyAsn1Error. Add PA_PAC_REQUEST alongside the
kinit-style PA_AS_FRESHNESS and PA_REQ_ENC_PA_REP padata.
Two fixes:
- Move step 2 (authenticated AS-REQ) out of the password-only else
  block into its own if-preAuth-is-True block, fixing enc-part error
  when using NTLM hash or AES key authentication
- Remove PA_PAC_REQUEST from probe AS-REQ to match kinit exactly,
  fixing extra UDP-to-TCP fallback caused by larger KDC response
CCache.parseFile() failed to find service tickets and TGTs in
cross-realm scenarios because it hardcoded the lookup realm to the
ccache's default principal realm. When a ticket was stored under a
different realm (e.g. HOST/target@CHILD.DOMAIN vs the ccache's
PARENT.DOMAIN), the lookup missed it entirely.

Add fallback searches that scan all credentials by SPN prefix
regardless of realm. This fixes atexec, dcomexec, smbexec, wmiexec,
and psexec when using cross-domain Kerberos tickets.
New tool: cross_realm_tgs.py
- Follows Kerberos referral chains to obtain service tickets across
  realm boundaries automatically
- Requests multiple service types (default: cifs,host) in one run,
  saving all tickets to a single ccache file
- Handles multi-hop referrals with automatic KDC resolution via SRV
  records or manual -kdc-map

ccache.py: Fix cross-realm credential lookup
- Add fallback search by hostname when exact SPN+realm match fails
- Add fallback search for any krbtgt ticket when home-realm TGT is
  not found
- Fixes atexec/dcomexec/smbexec/wmiexec/psexec with cross-domain
  Kerberos tickets

kerberosv5.py: Fix preauth flow for hash/key auth
- Move step 2 AS-REQ into correct scope so NTLM hash and AES key
  authentication work with the kinit-aligned flow
- Remove PA_PAC_REQUEST from probe to match kinit wire format
Three bugs prevented sapphire tickets from working with the -k (ccache)
flow:

1. oldSessionKey was never assigned in the ccache code path, causing
   _extract_reply_ticket_times to fail with an unbound variable error.
   When loading from ccache there is no key exchange, so oldSessionKey
   equals sessionKey.

2. _extract_reply_ticket_times and the cipher-validation checks tried
   to read the enc-part etype from the ccache-sourced AS_REP structure,
   which may contain a stale/placeholder etype (e.g. DES etype 1) that
   is absent from impacket's _enctype_table, raising a KeyError. These
   checks are unnecessary for the -k path — the real cipher negotiation
   happens during S4U2Self+U2U — so skip them entirely.

3. saveTicket loaded the existing KRB5CCNAME ccache and appended the
   new ticket, carrying over the original TGT (e.g. DomainA\userA).
   Downstream tools then picked the first matching krbtgt credential
   instead of the sapphire ticket, causing cross-realm service tickets
   to be issued for the wrong principal. Always create a fresh ccache
   so the saved file contains only the forged ticket.

Tested with sapphire ticket generation using a ccache TGT, followed by
cross-realm TGS referral (DomainA → forest root → DomainB) to obtain
cifs/ and host/ service tickets under the impersonated identity.
1. impacket/krb5/ccache.py: the cross-realm TGT fallback matched ANY
   krbtgt credential in the cache, which made parseFile("unexistent_domain")
   return a TGT for whatever realm happened to be cached. Restrict the
   scan to referral TGTs whose target realm equals the requested domain
   (krbtgt/<domain>@*). Fixes tests/misc/test_ccache.py::test_ccache_parseFile.

2. tests/misc/test_ticketer.py: build_options() constructs a minimal
   SimpleNamespace of the option flags ticketer.py accesses. The ccache
   auth commit added self.__options.k to createBasicTicket() without
   adding a default to the fixture, so the mock raised AttributeError.
   Add k=False to the fixture (matches the argparse default).
@aconite33
aconite33 merged commit 9eab328 into master Jul 22, 2026
12 checks passed
@aconite33
aconite33 deleted the integration/blacklantern-features branch July 22, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant