Codebase list pywerview / 1710484d-9ce9-4f68-a790-a22859d730b9/upstream
Import upstream version 0.4.0 Kali Janitor 2 years ago
17 changed file(s) with 849 addition(s) and 399 deletion(s). Raw diff Collapse all Expand all
6060 * Python 3.6
6161 * impacket >= 0.9.22
6262 * ldap3 >= 2.8.1
63 * gssapi (Which requires `libkrb5-dev`)
6364
6465 ## FUNCTIONALITIES
6566
6869
6970 Here's the list of available commands:
7071
71 $ ./pywerview.py --help
72 $ pywerview.py --help
7273 usage: pywerview.py [-h]
73 {get-adobject,get-netuser,get-netgroup,get-netcomputer,get-netdomaincontroller,get-netfileserver,get-dfsshare,get-netou,get-netsite,get-netsubnet,get-netgpo,get-domainpolicy,get-gpttmpl,get-netgpogroup,get-netgroupmember,get-netsession,get-localdisks,get-netdomain,get-netshare,get-netloggedon,get-netlocalgroup,invoke-checklocaladminaccess,get-netprocess,get-userevent,invoke-userhunter,invoke-processhunter,invoke-eventhunter}
74 {get-adobject,get-adserviceaccount,get-objectacl,get-netuser,get-netgroup,get-netcomputer,get-netdomaincontroller,get-netfileserver,get-dfsshare,get-netou,get-netsite,get-netsubnet,get-netdomaintrust,get-netgpo,get-netpso,get-domainpolicy,get-gpttmpl,get-netgpogroup,find-gpocomputeradmin,find-gpolocation,get-netgroupmember,get-netsession,get-localdisks,get-netdomain,get-netshare,get-netloggedon,get-netlocalgroup,invoke-checklocaladminaccess,get-netprocess,get-userevent,invoke-userhunter,invoke-processhunter,invoke-eventhunter}
7475 ...
7576
7677 Rewriting of some PowerView's functionalities in Python
8182 Subcommands:
8283 Available subcommands
8384
84 {get-adobject,get-netuser,get-netgroup,get-netcomputer,get-netdomaincontroller,get-netfileserver,get-dfsshare,get-netou,get-netsite,get-netsubnet,get-netgpo,get-domainpolicy,get-gpttmpl,get-netgpogroup,find-gpocomputeradmin,find-gpolocation,get-netgroupmember,get-netsession,get-localdisks,get-netdomain,get-netshare,get-netloggedon,get-netlocalgroup,invoke-checklocaladminaccess,get-netprocess,get-userevent,invoke-userhunter,invoke-processhunter,invoke-eventhunter}
85 get-adobject Takes a domain SID, samAccountName or name, and return
86 the associated object
85 {get-adobject,get-adserviceaccount,get-objectacl,get-netuser,get-netgroup,get-netcomputer,get-netdomaincontroller,get-netfileserver,get-dfsshare,get-netou,get-netsite,get-netsubnet,get-netdomaintrust,get-netgpo,get-netpso,get-domainpolicy,get-gpttmpl,get-netgpogroup,find-gpocomputeradmin,find-gpolocation,get-netgroupmember,get-netsession,get-localdisks,get-netdomain,get-netshare,get-netloggedon,get-netlocalgroup,invoke-checklocaladminaccess,get-netprocess,get-userevent,invoke-userhunter,invoke-processhunter,invoke-eventhunter}
86 get-adobject Takes a domain SID, samAccountName or name, and return the associated object
87 get-adserviceaccount
88 Returns a list of all the gMSA of the specified domain (you need privileged account to retrieve passwords)
89 get-objectacl Takes a domain SID, samAccountName or name, and return the ACL of the associated object
8790 get-netuser Queries information about a domain user
88 get-netgroup Get a list of all current domain groups, or a list of
89 groups a domain user is member of
91 get-netgroup Get a list of all current domain groups, or a list of groups a domain user is member of
9092 get-netcomputer Queries informations about domain computers
9193 get-netdomaincontroller
9294 Get a list of domain controllers for the given domain
93 get-netfileserver Return a list of file servers, extracted from the
94 domain users' homeDirectory, scriptPath, and
95 profilePath fields
96 get-dfsshare Return a list of all fault tolerant distributed file
97 systems for a given domain
95 get-netfileserver Return a list of file servers, extracted from the domain users' homeDirectory, scriptPath, and profilePath fields
96 get-dfsshare Return a list of all fault tolerant distributed file systems for a given domain
9897 get-netou Get a list of all current OUs in the domain
9998 get-netsite Get a list of all current sites in the domain
10099 get-netsubnet Get a list of all current subnets in the domain
100 get-netdomaintrust Returns a list of all the trusts of the specified domain
101101 get-netgpo Get a list of all current GPOs in the domain
102 get-domainpolicy Returns the default domain or DC policy for the
103 queried domain or DC
104 get-gpttmpl Helper to parse a GptTmpl.inf policy file path into a
105 custom object
106 get-netgpogroup Parses all GPOs in the domain that set "Restricted
107 Group" or "Groups.xml"
102 get-netpso Get a list of all current PSOs in the domain
103 get-domainpolicy Returns the default domain or DC policy for the queried domain or DC
104 get-gpttmpl Helper to parse a GptTmpl.inf policy file path into a custom object
105 get-netgpogroup Parses all GPOs in the domain that set "Restricted Group" or "Groups.xml"
108106 find-gpocomputeradmin
109 Takes a computer (or OU) and determine who has
110 administrative access to it via GPO
111 find-gpolocation Takes a username or a group name and determine the
112 computers it has administrative access to via GPO
107 Takes a computer (or OU) and determine who has administrative access to it via GPO
108 find-gpolocation Takes a username or a group name and determine the computers it has administrative access to via GPO
113109 get-netgroupmember Return a list of members of a domain group
114 get-netsession Queries a host to return a list of active sessions on
115 the host (you can use local credentials instead of
116 domain credentials)
117 get-localdisks Queries a host to return a list of active disks on the
118 host (you can use local credentials instead of domain
119 credentials)
110 get-netsession Queries a host to return a list of active sessions on the host (you can use local credentials instead of domain credentials)
111 get-localdisks Queries a host to return a list of active disks on the host (you can use local credentials instead of domain credentials)
120112 get-netdomain Queries a host for available domains
121 get-netshare Queries a host to return a list of available shares on
122 the host (you can use local credentials instead of
123 domain credentials)
124 get-netloggedon This function will execute the NetWkstaUserEnum RPC
125 call to query a given host for actively logged on
126 users
127 get-netlocalgroup Gets a list of members of a local group on a machine,
128 or returns every local group. You can use local
129 credentials instead of domain credentials, however,
130 domain credentials are needed to resolve domain SIDs.
113 get-netshare Queries a host to return a list of available shares on the host (you can use local credentials instead of domain credentials)
114 get-netloggedon This function will execute the NetWkstaUserEnum RPC call to query a given host for actively logged on users
115 get-netlocalgroup Gets a list of members of a local group on a machine, or returns every local group. You can use local credentials instead of domain credentials, however, domain credentials are needed
116 to resolve domain SIDs.
131117 invoke-checklocaladminaccess
132 Checks if the given user has local admin access on the
133 given host
134 get-netprocess This function will execute the 'Select * from
135 Win32_Process' WMI query to a given host for a list of
136 executed process
137 get-userevent This function will execute the 'Select * from
138 Win32_Process' WMI query to a given host for a list of
139 executed process
118 Checks if the given user has local admin access on the given host
119 get-netprocess This function will execute the 'Select * from Win32_Process' WMI query to a given host for a list of executed process
120 get-userevent This function will execute the 'SELECT * from Win32_NTLogEvent' WMI query to a given host for a list of executed process
140121 invoke-userhunter Finds which machines domain users are logged into
141122 invoke-processhunter
142 Searches machines for processes with specific name, or
143 ran by specific users
144 invoke-eventhunter Searches machines for events with specific name, or
145 ran by specific users
123 Searches machines for processes with specific name, or ran by specific users
124 invoke-eventhunter Searches machines for events with specific name, or ran by specific users
146125
147126 Take a look at the [wiki](https://github.com/the-useless-one/pywerview/wiki) to
148127 see a more detailed usage of every command.
154133 is `USELESSDOMAIN`. In every command, I must use __`uselessdomain.local`__ as
155134 an argument, and __not__ `USELESSDOMAIN`.
156135
136 ## GLOBAL ARGUMENTS
137
138 ### LOGGING
139
140 You can provide a logging level to `pywerview` modules by using `-l` or `--logging-level` options. Supported levels are:
141
142 * `CRITICAL`: Only critical errors are displayed **(default)**
143 * `WARNING` Warnings are displayed, along with citical errors
144 * `DEBUG`: Debug level (caution: **very** verbose)
145 * `ULTRA`: Extreme debugging level (caution: **very very** verbose)
146
147 (level names are case insensitive)
148
149 ### Kerberos authentication
150
151 Kerberos authentication is now (partially) supported, which means you can
152 pass the ticket and other stuff. To authenticate via Kerberos:
153
154 1. Point the `KRB5CCNAME` environment variable to your cache credential file.
155 2. Use the `-k` option in your function call, or the `do_kerberos` in your
156 library call.
157
158 ```console
159 $ klist stormtroopers.ccache
160 Ticket cache: FILE:stormtroopers.ccache
161 Default principal: [email protected]
162
163 Valid starting Expires Service principal
164 10/03/2022 16:46:45 11/03/2022 02:46:45 ldap/[email protected]
165 renew until 11/03/2022 16:43:17
166 $ KRB5CCNAME=stormtroopers.ccache python3 pywerview.py get-netcomputer -t srv-ad.contoso.com -u stormtroopers -k
167 dnshostname: centos.contoso.com
168
169 dnshostname: debian.contoso.com
170
171 dnshostname: Windows7.contoso.com
172
173 dnshostname: Windows10.contoso.com
174
175 dnshostname: SRV-MAIL.contoso.com
176
177 dnshostname: SRV-AD.contoso.com
178 ```
179
180 If your cache credential file contains a corresponding TGS, or a TGT for your
181 calling user, Kerberos authentication will be used.
182
183 __SPN patching is partial__. Right now, we're in a mixed configuration where we
184 use `ldap3` for LDAP commands and `impacket` for the other protocols (SMB,
185 RPC). That is because `impacket`'s LDAP implementation has several problems,
186 such as mismanagement of non-ASCII characters (which is problematic for us
187 baguette-eaters).
188
189 `ldap3` uses `gssapi` to authenticate with Kerberos, and `gssapi` needs the
190 full hostname in the SPN of a ticket, otherwise it throws an error. It would
191 be possible to patch an SPN with an incomplete hostname, however it's not done
192 for now.
193
194 For any functions that only rely on `impacket` (SMB or RPC functions), you can
195 use tickets with SPNs with an incomplete hostname. In the following example, we
196 use an LDAP ticket with an incomplete hostname for an SMB function, without any
197 trouble. You just have to make sure that the `--computername` argument matches
198 this incomplete hostname in the SPN:
199
200 ```console
201 $ klist skywalker.ccache
202 Ticket cache: FILE:skywalker.ccache
203 Default principal: [email protected]
204
205 Valid starting Expires Service principal
206 13/04/2022 14:26:59 14/04/2022 00:26:58 ldap/[email protected]
207 renew until 14/04/2022 14:23:29
208 $ KRB5CCNAME=skywalker.ccache python3 pywerview.py get-localdisks --computername srv-ad -u skywalker -k
209 disk: A:
210
211 disk: C:
212
213 disk: D:
214 ```
215
216 To recap:
217
218 | SPN in the ticket | Can be used with LDAP functions | Can be used with SMB/RPC functions |
219 | :-----------------------------------: | :-----------------------------: | :--------------------------------: |
220 | `ldap/[email protected]` | ✔️ | ✔️ |
221 | `cifs/[email protected]` | ✔️ | ✔️ |
222 | `ldap/[email protected]` | ❌ | ✔️ |
223
224 ### TLS CONNECTION
225
226 You can force a connection to the LDAPS port by using the `--tls` switch. It
227 can be necessary with some functions, for example when retrieving gMSA
228 passwords with `get-adserviceaccount`:
229
230 ```console
231 $ python3 pywerview.py get-adserviceaccount -t srv-ad.contoso.com -u 'SRV-MAIL$' --hashes $NT_HASH --resolve-sids
232 distinguishedname: CN=gMSA-01,CN=Managed Service Accounts,DC=contoso,DC=com
233 objectsid: S-1-5-21-863927164-4106933278-53377030-3115
234 samaccountname: gMSA-01$
235 msds-groupmsamembership: CN=SRV-MAIL,CN=Computers,DC=contoso,DC=com
236 description:
237 enabled: True
238 $ python3 pywerview.py get-adserviceaccount -t srv-ad.contoso.com -u 'SRV-MAIL$' --hashes $NT_HASH --resolve-sids --tls
239 distinguishedname: CN=gMSA-01,CN=Managed Service Accounts,DC=contoso,DC=com
240 objectsid: S-1-5-21-863927164-4106933278-53377030-3115
241 samaccountname: gMSA-01$
242 msds-managedpassword: 69730ce3914ac6[redacted]
243 msds-groupmsamembership: CN=SRV-MAIL,CN=Computers,DC=contoso,DC=com
244 description:
245 enabled: True
246 ```
247
248 ### JSON OUTPUT
249
250 Pywerview can print results in json format by using the `--json` switch.
251
157252 ## TODO
158253
159254 * Many, many more PowerView functionalities to implement. I'll now focus on
160255 forest functions, then inter-forest trust functions
161256 * Lots of rewrite due to the last version of PowerView
162 * Implement a debugging mode (for easier troubleshooting)
163257 * Gracefully fail against Unix machines running Samba
164 * Support Kerberos authentication
165258 * Perform range cycling in `get-netgroupmember`
166259 * Manage request to the Global Catalog
167260 * Try to fall back to `tcp/139` for RPC communications if `tcp/445` is closed
185278
186279 PywerView - A Python rewriting of PowerSploit's PowerView
187280
188 Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
281 Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
189282
190283 This program is free software: you can redistribute it and/or modify it
191284 under the terms of the GNU General Public License as published by the
0 import logging
1 logging.getLogger().setLevel(100)
2
1414 # You should have received a copy of the GNU General Public License
1515 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1616
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1818
1919 from pywerview.functions.net import NetRequester
2020 from pywerview.functions.gpo import GPORequester
2222 from pywerview.functions.hunting import UserHunter, ProcessHunter, EventHunter
2323
2424 def get_adobject(domain_controller, domain, user, password=str(),
25 lmhash=str(), nthash=str(), queried_domain=str(), queried_sid=str(),
26 queried_name=str(), queried_sam_account_name=str(), ads_path=str(),
27 attributes=list(), custom_filter=str()):
28 requester = NetRequester(domain_controller, domain, user, password,
29 lmhash, nthash)
25 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
26 queried_domain=str(), queried_sid=str(), queried_name=str(),
27 queried_sam_account_name=str(), ads_path=str(), attributes=list(),
28 custom_filter=str()):
29 requester = NetRequester(domain_controller, domain, user, password,
30 lmhash, nthash, do_kerberos, do_tls)
3031 return requester.get_adobject(queried_domain=queried_domain,
3132 queried_sid=queried_sid, queried_name=queried_name,
3233 queried_sam_account_name=queried_sam_account_name,
3334 ads_path=ads_path, attributes=attributes, custom_filter=custom_filter)
3435
36 def get_adserviceaccount(domain_controller, domain, user, password=str(),
37 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
38 queried_domain=str(), queried_sid=str(), queried_name=str(),
39 queried_sam_account_name=str(), ads_path=str(), resolve_sids=False):
40 requester = NetRequester(domain_controller, domain, user, password,
41 lmhash, nthash, do_kerberos, do_tls)
42 return requester.get_adserviceaccount(queried_domain=queried_domain,
43 queried_sid=queried_sid, queried_name=queried_name,
44 queried_sam_account_name=queried_sam_account_name,
45 ads_path=ads_path, resolve_sids=resolve_sids)
46
3547 def get_objectacl(domain_controller, domain, user, password=str(),
36 lmhash=str(), nthash=str(), queried_domain=str(), queried_sid=str(),
37 queried_name=str(), queried_sam_account_name=str(), ads_path=str(),
38 sacl=False, rights_filter=str(), resolve_sids=False,
39 resolve_guids=False, custom_filter=str()):
40 requester = NetRequester(domain_controller, domain, user, password,
41 lmhash, nthash)
48 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
49 queried_domain=str(), queried_sid=str(), queried_name=str(),
50 queried_sam_account_name=str(), ads_path=str(), sacl=False,
51 rights_filter=str(), resolve_sids=False, resolve_guids=False,
52 custom_filter=str()):
53 requester = NetRequester(domain_controller, domain, user, password,
54 lmhash, nthash, do_kerberos, do_tls)
4255 return requester.get_objectacl(queried_domain=queried_domain,
4356 queried_sid=queried_sid, queried_name=queried_name,
4457 queried_sam_account_name=queried_sam_account_name,
4760 custom_filter=custom_filter)
4861
4962 def get_netuser(domain_controller, domain, user, password=str(), lmhash=str(),
50 nthash=str(), queried_username=str(), queried_domain=str(), ads_path=str(),
51 admin_count=False, spn=False, unconstrained=False, allow_delegation=False,
52 preauth_notreq=False, custom_filter=str(),
53 attributes=[]):
54 requester = NetRequester(domain_controller, domain, user, password,
55 lmhash, nthash)
63 nthash=str(), do_kerberos=False, do_tls=False, queried_username=str(),
64 queried_domain=str(), ads_path=str(), admin_count=False, spn=False,
65 unconstrained=False, allow_delegation=False, preauth_notreq=False,
66 custom_filter=str(), attributes=[]):
67 requester = NetRequester(domain_controller, domain, user, password,
68 lmhash, nthash, do_kerberos, do_tls)
5669 return requester.get_netuser(queried_username=queried_username,
5770 queried_domain=queried_domain, ads_path=ads_path, admin_count=admin_count,
5871 spn=spn, unconstrained=unconstrained, allow_delegation=allow_delegation,
6073 attributes=attributes)
6174
6275 def get_netgroup(domain_controller, domain, user, password=str(),
63 lmhash=str(), nthash=str(), queried_groupname='*', queried_sid=str(),
64 queried_username=str(), queried_domain=str(), ads_path=str(),
65 admin_count=False, full_data=False, custom_filter=str()):
66 requester = NetRequester(domain_controller, domain, user, password,
67 lmhash, nthash)
76 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
77 queried_groupname='*', queried_sid=str(), queried_username=str(),
78 queried_domain=str(), ads_path=str(), admin_count=False,
79 full_data=False, custom_filter=str()):
80 requester = NetRequester(domain_controller, domain, user, password,
81 lmhash, nthash, do_kerberos, do_tls)
6882 return requester.get_netgroup(queried_groupname=queried_groupname,
6983 queried_sid=queried_sid, queried_username=queried_username,
7084 queried_domain=queried_domain, ads_path=ads_path, admin_count=admin_count,
7185 full_data=full_data, custom_filter=custom_filter)
7286
7387 def get_netcomputer(domain_controller, domain, user, password=str(),
74 lmhash=str(), nthash=str(), queried_computername='*', queried_spn=str(),
75 queried_os=str(), queried_sp=str(), queried_domain=str(), ads_path=str(),
88 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
89 queried_computername='*', queried_spn=str(), queried_os=str(),
90 queried_sp=str(), queried_domain=str(), ads_path=str(),
7691 printers=False, unconstrained=False, ping=False, full_data=False,
7792 custom_filter=str(), attributes=[]):
7893 requester = NetRequester(domain_controller, domain, user, password,
79 lmhash, nthash)
94 lmhash, nthash, do_kerberos, do_tls)
8095 return requester.get_netcomputer(queried_computername=queried_computername,
8196 queried_spn=queried_spn, queried_os=queried_os, queried_sp=queried_sp,
8297 queried_domain=queried_domain, ads_path=ads_path, printers=printers,
8499 custom_filter=custom_filter, attributes=attributes)
85100
86101 def get_netdomaincontroller(domain_controller, domain, user, password=str(),
87 lmhash=str(), nthash=str(), queried_domain=str()):
88 requester = NetRequester(domain_controller, domain, user, password,
89 lmhash, nthash)
102 lmhash=str(), nthash=str(), do_kerberos=False,
103 do_tls=False, queried_domain=str()):
104 requester = NetRequester(domain_controller, domain, user, password,
105 lmhash, nthash, do_kerberos, do_tls)
90106 return requester.get_netdomaincontroller(queried_domain=queried_domain)
91107
92108 def get_netfileserver(domain_controller, domain, user, password=str(),
93 lmhash=str(), nthash=str(), queried_domain=str(), target_users=list()):
94 requester = NetRequester(domain_controller, domain, user, password,
95 lmhash, nthash)
109 lmhash=str(), nthash=str(), do_kerberos=False,
110 do_tls=False, queried_domain=str(), target_users=list()):
111 requester = NetRequester(domain_controller, domain, user, password,
112 lmhash, nthash, do_kerberos, do_tls)
96113 return requester.get_netfileserver(queried_domain=queried_domain,
97114 target_users=target_users)
98115
99116 def get_dfsshare(domain_controller, domain, user, password=str(),
100 lmhash=str(), nthash=str(), version=['v1', 'v2'], queried_domain=str(),
101 ads_path=str()):
102 requester = NetRequester(domain_controller, domain, user, password,
103 lmhash, nthash)
117 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
118 version=['v1', 'v2'], queried_domain=str(), ads_path=str()):
119 requester = NetRequester(domain_controller, domain, user, password,
120 lmhash, nthash, do_kerberos, do_tls)
104121 return requester.get_dfsshare(version=version, queried_domain=queried_domain, ads_path=ads_path)
105122
106123 def get_netou(domain_controller, domain, user, password=str(), lmhash=str(),
107 nthash=str(), queried_domain=str(), queried_ouname='*', queried_guid=str(),
108 ads_path=str(), full_data=False):
109 requester = NetRequester(domain_controller, domain, user, password,
110 lmhash, nthash)
124 nthash=str(), do_kerberos=False, do_tls=False, queried_domain=str(),
125 queried_ouname='*', queried_guid=str(), ads_path=str(), full_data=False):
126 requester = NetRequester(domain_controller, domain, user, password,
127 lmhash, nthash, do_kerberos, do_tls)
111128 return requester.get_netou(queried_domain=queried_domain,
112129 queried_ouname=queried_ouname, queried_guid=queried_guid, ads_path=ads_path,
113130 full_data=full_data)
114131
115132 def get_netsite(domain_controller, domain, user, password=str(), lmhash=str(),
116 nthash=str(), queried_domain=str(), queried_sitename=str(),
117 queried_guid=str(), ads_path=str(), ads_prefix='CN=Sites,CN=Configuration',
118 full_data=False):
119 requester = NetRequester(domain_controller, domain, user, password,
120 lmhash, nthash)
133 nthash=str(), do_kerberos=False, do_tls=False, queried_domain=str(),
134 queried_sitename=str(), queried_guid=str(), ads_path=str(),
135 ads_prefix='CN=Sites,CN=Configuration', full_data=False):
136 requester = NetRequester(domain_controller, domain, user, password,
137 lmhash, nthash, do_kerberos, do_tls)
121138 return requester.get_netsite(queried_domain=queried_domain,
122139 queried_sitename=queried_sitename, queried_guid=queried_guid,
123140 ads_path=ads_path, ads_prefix=ads_prefix, full_data=full_data)
124141
125142 def get_netsubnet(domain_controller, domain, user, password=str(),
126 lmhash=str(), nthash=str(), queried_domain=str(), queried_sitename=str(),
127 ads_path=str(), ads_prefix='CN=Sites,CN=Configuration', full_data=False):
128 requester = NetRequester(domain_controller, domain, user, password,
129 lmhash, nthash)
143 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
144 queried_domain=str(), queried_sitename=str(), ads_path=str(),
145 ads_prefix='CN=Sites,CN=Configuration', full_data=False):
146 requester = NetRequester(domain_controller, domain, user, password,
147 lmhash, nthash, do_kerberos, do_tls)
130148 return requester.get_netsubnet(queried_domain=queried_domain,
131149 queried_sitename=queried_sitename, ads_path=ads_path, ads_prefix=ads_prefix,
132150 full_data=full_data)
133151
134152 def get_netdomaintrust(domain_controller, domain, user, password=str(),
135 lmhash=str(), nthash=str(), queried_domain=str()):
136 requester = NetRequester(domain_controller, domain, user, password,
137 lmhash, nthash)
153 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False, queried_domain=str()):
154 requester = NetRequester(domain_controller, domain, user, password,
155 lmhash, nthash, do_kerberos, do_tls)
138156 return requester.get_netdomaintrust(queried_domain=queried_domain)
139157
140158 def get_netgroupmember(domain_controller, domain, user, password=str(),
141 lmhash=str(), nthash=str(), queried_groupname=str(), queried_sid=str(),
142 queried_domain=str(), ads_path=str(), recurse=False, use_matching_rule=False,
159 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
160 queried_groupname=str(), queried_sid=str(), queried_domain=str(),
161 ads_path=str(), recurse=False, use_matching_rule=False,
143162 full_data=False, custom_filter=str()):
144163 requester = NetRequester(domain_controller, domain, user, password,
145 lmhash, nthash)
164 lmhash, nthash, do_kerberos, do_tls)
146165 return requester.get_netgroupmember(queried_groupname=queried_groupname,
147166 queried_sid=queried_sid, queried_domain=queried_domain,
148167 ads_path=ads_path, recurse=recurse,
150169 full_data=full_data, custom_filter=custom_filter)
151170
152171 def get_netsession(target_computername, domain, user, password=str(),
153 lmhash=str(), nthash=str()):
154 requester = NetRequester(target_computername, domain, user, password,
155 lmhash, nthash)
172 lmhash=str(), nthash=str(), do_kerberos=False):
173 requester = NetRequester(target_computername, domain, user, password,
174 lmhash, nthash, do_kerberos)
156175 return requester.get_netsession()
157176
158177 def get_netshare(target_computername, domain, user, password=str(),
159 lmhash=str(), nthash=str()):
160 requester = NetRequester(target_computername, domain, user, password,
161 lmhash, nthash)
178 lmhash=str(), nthash=str(), do_kerberos=False):
179 requester = NetRequester(target_computername, domain, user, password,
180 lmhash, nthash, do_kerberos)
162181 return requester.get_netshare()
163182
164183 def get_localdisks(target_computername, domain, user, password=str(),
165 lmhash=str(), nthash=str()):
166 requester = NetRequester(target_computername, domain, user, password,
167 lmhash, nthash)
184 lmhash=str(), nthash=str(), do_kerberos=False):
185 requester = NetRequester(target_computername, domain, user, password,
186 lmhash, nthash, do_kerberos)
168187 return requester.get_localdisks()
169188
170189 def get_netdomain(domain_controller, domain, user, password=str(),
171 lmhash=str(), nthash=str()):
172 requester = NetRequester(domain_controller, domain, user, password,
173 lmhash, nthash)
190 lmhash=str(), nthash=str(), do_kerberos=False,
191 do_tls=False):
192 requester = NetRequester(domain_controller, domain, user, password,
193 lmhash, nthash, do_kerberos, do_tls)
174194 return requester.get_netdomain()
175195
176196 def get_netloggedon(target_computername, domain, user, password=str(),
177 lmhash=str(), nthash=str()):
178 requester = NetRequester(target_computername, domain, user, password,
179 lmhash, nthash)
197 lmhash=str(), nthash=str(), do_kerberos=False):
198 requester = NetRequester(target_computername, domain, user, password,
199 lmhash, nthash, do_kerberos)
180200 return requester.get_netloggedon()
181201
182202 def get_netlocalgroup(target_computername, domain_controller, domain, user,
183 password=str(), lmhash=str(), nthash=str(), queried_groupname=str(),
184 list_groups=False, recurse=False):
185 requester = NetRequester(target_computername, domain, user, password,
186 lmhash, nthash, domain_controller)
203 password=str(), lmhash=str(), nthash=str(), do_kerberos=False,
204 do_tls=False, queried_groupname=str(), list_groups=False,
205 recurse=False):
206 requester = NetRequester(target_computername, domain, user, password,
207 lmhash, nthash, do_kerberos, do_tls, domain_controller)
187208 return requester.get_netlocalgroup(queried_groupname=queried_groupname,
188209 list_groups=list_groups, recurse=recurse)
189210
190211 def get_netprocess(target_computername, domain, user, password=str(),
191 lmhash=str(), nthash=str()):
192 requester = NetRequester(target_computername, domain, user, password,
193 lmhash, nthash)
212 lmhash=str(), nthash=str(), do_kerberos=False):
213 requester = NetRequester(target_computername, domain, user, password,
214 lmhash, nthash, do_kerberos)
194215 return requester.get_netprocess()
195216
196217 def get_userevent(target_computername, domain, user, password=str(),
197 lmhash=str(), nthash=str(), event_type=['logon', 'tgt'],
198 date_start=5):
199 requester = NetRequester(target_computername, domain, user, password,
200 lmhash, nthash)
218 lmhash=str(), nthash=str(), do_kerberos=False,
219 event_type=['logon', 'tgt'], date_start=5):
220 requester = NetRequester(target_computername, domain, user, password,
221 lmhash, nthash, do_kerberos)
201222 return requester.get_userevent(event_type=event_type,
202223 date_start=date_start)
203224
204225 def get_netgpo(domain_controller, domain, user, password=str(),
205 lmhash=str(), nthash=str(), queried_gponame='*',
206 queried_displayname=str(), queried_domain=str(), ads_path=str()):
207 requester = GPORequester(domain_controller, domain, user, password,
208 lmhash, nthash)
226 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
227 queried_gponame='*', queried_displayname=str(), queried_domain=str(),
228 ads_path=str()):
229 requester = GPORequester(domain_controller, domain, user, password,
230 lmhash, nthash, do_kerberos, do_tls)
209231 return requester.get_netgpo(queried_gponame=queried_gponame,
210232 queried_displayname=queried_displayname,
211233 queried_domain=queried_domain, ads_path=ads_path)
212234
213235 def get_netpso(domain_controller, domain, user, password=str(),
214 lmhash=str(), nthash=str(), queried_psoname='*',
215 queried_displayname=str(), queried_domain=str(), ads_path=str()):
216 requester = GPORequester(domain_controller, domain, user, password,
217 lmhash, nthash)
236 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
237 queried_psoname='*', queried_displayname=str(), queried_domain=str(),
238 ads_path=str()):
239 requester = GPORequester(domain_controller, domain, user, password,
240 lmhash, nthash, do_kerberos, do_tls)
218241 return requester.get_netpso(queried_psoname=queried_psoname,
219242 queried_displayname=queried_displayname,
220243 queried_domain=queried_domain, ads_path=ads_path)
221244
222245 def get_domainpolicy(domain_controller, domain, user, password=str(),
223 lmhash=str(), nthash=str(), source='domain', queried_domain=str(),
224 resolve_sids=False):
225 requester = GPORequester(domain_controller, domain, user, password,
226 lmhash, nthash)
246 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
247 source='domain', queried_domain=str(), resolve_sids=False):
248 requester = GPORequester(domain_controller, domain, user, password,
249 lmhash, nthash, do_kerberos, do_tls)
227250
228251 return requester.get_domainpolicy(source=source, queried_domain=queried_domain,
229252 resolve_sids=resolve_sids)
230253
231254 def get_gpttmpl(gpttmpl_path, domain_controller, domain, user, password=str(), lmhash=str(),
232 nthash=str()):
233 requester = GPORequester(domain_controller, domain, user, password,
234 lmhash, nthash)
255 nthash=str(), do_kerberos=False, do_tls=False):
256 requester = GPORequester(domain_controller, domain, user, password,
257 lmhash, nthash, do_kerberos, do_tls)
235258
236259 return requester.get_gpttmpl(gpttmpl_path)
237260
238261 def get_netgpogroup(domain_controller, domain, user, password=str(), lmhash=str(),
239 nthash=str(), queried_gponame='*', queried_displayname=str(),
240 queried_domain=str(), ads_path=str(), resolve_sids=False):
241 requester = GPORequester(domain_controller, domain, user, password,
242 lmhash, nthash)
262 nthash=str(), do_kerberos=False, do_tls=False, queried_gponame='*',
263 queried_displayname=str(), queried_domain=str(), ads_path=str(),
264 resolve_sids=False):
265 requester = GPORequester(domain_controller, domain, user, password,
266 lmhash, nthash, do_kerberos, do_tls)
243267
244268 return requester.get_netgpogroup(queried_gponame=queried_gponame,
245269 queried_displayname=queried_displayname,
248272 resolve_sids=resolve_sids)
249273
250274 def find_gpocomputeradmin(domain_controller, domain, user, password=str(), lmhash=str(),
251 nthash=str(), queried_computername=str(),
252 queried_ouname=str(), queried_domain=str(),
253 recurse=False):
254 requester = GPORequester(domain_controller, domain, user, password,
255 lmhash, nthash)
275 nthash=str(), do_kerberos=False, do_tls=False, queried_computername=str(),
276 queried_ouname=str(), queried_domain=str(), recurse=False):
277 requester = GPORequester(domain_controller, domain, user, password,
278 lmhash, nthash, do_kerberos, do_tls)
256279
257280 return requester.find_gpocomputeradmin(queried_computername=queried_computername,
258281 queried_ouname=queried_ouname,
260283 recurse=recurse)
261284
262285 def find_gpolocation(domain_controller, domain, user, password=str(), lmhash=str(),
263 nthash=str(), queried_username=str(), queried_groupname=str(),
264 queried_localgroup=str(), queried_domain=str()):
265 requester = GPORequester(domain_controller, domain, user, password,
266 lmhash, nthash)
286 nthash=str(), do_kerberos=False, do_tls=False, queried_username=str(),
287 queried_groupname=str(), queried_localgroup=str(),
288 queried_domain=str()):
289 requester = GPORequester(domain_controller, domain, user, password,
290 lmhash, nthash, do_kerberos, do_tls)
267291 return requester.find_gpolocation(queried_username=queried_username,
268292 queried_groupname=queried_groupname,
269293 queried_localgroup=queried_localgroup,
270294 queried_domain=queried_domain)
271295
272296 def invoke_checklocaladminaccess(target_computername, domain, user, password=str(),
273 lmhash=str(), nthash=str()):
274 misc = Misc(target_computername, domain, user, password, lmhash, nthash)
297 lmhash=str(), nthash=str(), do_kerberos=False):
298 misc = Misc(target_computername, domain, user, password, lmhash, nthash, do_kerberos)
275299
276300 return misc.invoke_checklocaladminaccess()
277301
278302 def invoke_userhunter(domain_controller, domain, user, password=str(),
279 lmhash=str(), nthash=str(), queried_computername=list(),
280 queried_computerfile=None, queried_computerfilter=str(),
281 queried_computeradspath=str(), unconstrained=False,
282 queried_groupname=str(), target_server=str(),
303 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
304 queried_computername=list(), queried_computerfile=None,
305 queried_computerfilter=str(), queried_computeradspath=str(),
306 unconstrained=False, queried_groupname=str(), target_server=str(),
283307 queried_username=str(), queried_useradspath=str(),
284308 queried_userfilter=str(), queried_userfile=None,
285309 threads=1, admin_count=False, allow_delegation=False,
287311 stealth=False, stealth_source=['dfs', 'dc', 'file'],
288312 show_all=False, foreign_users=False):
289313 user_hunter = UserHunter(domain_controller, domain, user, password,
290 lmhash, nthash)
291
314 lmhash, nthash, do_kerberos, do_tls)
292315 return user_hunter.invoke_userhunter(queried_computername=queried_computername,
293316 queried_computerfile=queried_computerfile,
294317 queried_computerfilter=queried_computerfilter,
304327 foreign_users=foreign_users)
305328
306329 def invoke_processhunter(domain_controller, domain, user, password=str(),
307 lmhash=str(), nthash=str(), queried_computername=list(),
308 queried_computerfile=None, queried_computerfilter=str(),
309 queried_computeradspath=str(), queried_processname=list(),
310 queried_groupname=str(), target_server=str(),
330 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
331 queried_computername=list(), queried_computerfile=None,
332 queried_computerfilter=str(), queried_computeradspath=str(),
333 queried_processname=list(), queried_groupname=str(), target_server=str(),
311334 queried_username=str(), queried_useradspath=str(),
312335 queried_userfilter=str(), queried_userfile=None, threads=1,
313336 stop_on_success=False, queried_domain=str(), show_all=False):
314337 process_hunter = ProcessHunter(domain_controller, domain, user, password,
315 lmhash, nthash)
338 lmhash, nthash, do_kerberos, do_tls)
316339
317340 return process_hunter.invoke_processhunter(queried_computername=queried_computername,
318341 queried_computerfile=queried_computerfile,
327350 queried_domain=queried_domain, show_all=show_all)
328351
329352 def invoke_eventhunter(domain_controller, domain, user, password=str(),
330 lmhash=str(), nthash=str(), queried_computername=list(),
331 queried_computerfile=None, queried_computerfilter=str(),
332 queried_computeradspath=str(), queried_groupname=str(),
333 target_server=str(), queried_username=str(),
353 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
354 queried_computername=list(), queried_computerfile=None,
355 queried_computerfilter=str(), queried_computeradspath=str(),
356 queried_groupname=str(), target_server=str(), queried_username=str(),
334357 queried_useradspath=str(), queried_userfilter=str(),
335358 queried_userfile=None, threads=1, queried_domain=str(),
336359 search_days=3):
337360 event_hunter = EventHunter(domain_controller, domain, user, password,
338 lmhash, nthash)
361 lmhash, nthash, do_kerberos, do_tls)
339362
340363 return event_hunter.invoke_eventhunter(queried_computername=queried_computername,
341364 queried_computerfile=queried_computerfile,
1414 # You should have received a copy of the GNU General Public License
1515 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1616
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
18
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
18
19 import logging
1920 import argparse
21 import json
22 import datetime
2023 from pywerview.cli.helpers import *
2124 from pywerview.functions.hunting import *
2225
2427 # Main parser
2528 parser = argparse.ArgumentParser(description='Rewriting of some PowerView\'s functionalities in Python')
2629 subparsers = parser.add_subparsers(title='Subcommands', description='Available subcommands', dest='submodule')
27
30
2831 # hack for python < 3.9 : https://stackoverflow.com/questions/23349349/argparse-with-required-subparser
2932 subparsers.required = True
33
34 # Logging parser
35 logging_parser = argparse.ArgumentParser(add_help=False)
36 logging_parser.add_argument('-l', '--logging-level', dest='logging_level', type=str.upper,
37 choices=['CRITICAL', 'WARNING', 'DEBUG', 'ULTRA'], default='CRITICAL',
38 help='SDTERR logging level: '
39 'CRITICAL: Only critical errors are displayed (default), '
40 'WARNING: Warnings are displayed, along with citical errors, '
41 'DEBUG: Debug level (caution: very verbose), '
42 'ULTRA: Extreme debugging level (caution: very very verbose)')
43
44 # json parser
45 json_output_parser = argparse.ArgumentParser(add_help=False)
46 json_output_parser.add_argument('--json', dest='json_output', action='store_true',
47 help='Print results in JSON format')
3048
3149 # TODO: support keberos authentication
3250 # Credentials parser
3351 credentials_parser = argparse.ArgumentParser(add_help=False)
3452 credentials_parser.add_argument('-w', '--workgroup', dest='domain',
3553 default=str(), help='Name of the domain we authenticate with')
36 credentials_parser.add_argument('-u', '--user', required=True,
54 credentials_parser.add_argument('-u', '--user',
3755 help='Username used to connect to the Domain Controller')
3856 credentials_parser.add_argument('-p', '--password',
3957 help='Password associated to the username')
40 credentials_parser.add_argument('--hashes', action='store', metavar = 'LMHASH:NTHASH',
58 credentials_parser.add_argument('--hashes', action='store', metavar='LMHASH:NTHASH',
4159 help='NTLM hashes, format is [LMHASH:]NTHASH')
60 credentials_parser.add_argument('-k', action='store_true', dest='do_kerberos',
61 help='Use Kerberos authentication. Grabs credentials from ccache file '
62 '(KRB5CCNAME) based on target parameters. If valid credentials '
63 'cannot be found, it will use the ones specified in the command '
64 'line')
4265
4366 # AD parser, used for net* functions running against a domain controller
4467 ad_parser = argparse.ArgumentParser(add_help=False, parents=[credentials_parser])
4568 ad_parser.add_argument('-t', '--dc-ip', dest='domain_controller',
4669 required=True, help='IP address of the Domain Controller to target')
70 ad_parser.add_argument('--tls', action='store_true', dest='do_tls',
71 help='Force TLS connection to the Domain Controller')
4772
4873 # Target parser, used for net* functions running against a normal computer
4974 target_parser = argparse.ArgumentParser(add_help=False, parents=[credentials_parser])
79104
80105 # Parser for the get-adobject command
81106 get_adobject_parser = subparsers.add_parser('get-adobject', help='Takes a domain SID, '\
82 'samAccountName or name, and return the associated object', parents=[ad_parser])
107 'samAccountName or name, and return the associated object',
108 parents=[ad_parser, logging_parser, json_output_parser])
83109 get_adobject_parser.add_argument('--sid', dest='queried_sid',
84110 help='SID to query (wildcards accepted)')
85111 get_adobject_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
94120 default=[], help='Object attributes to return')
95121 get_adobject_parser.set_defaults(func=get_adobject)
96122
123 # Parser for the get-adserviceaccount command
124 get_adserviceaccount_parser = subparsers.add_parser('get-adserviceaccount', help='Returns a list of all the '\
125 'gMSA of the specified domain. To retrieve passwords, you need a privileged account and '\
126 'a TLS connection to the LDAP server (use the --tls switch).',
127 parents=[ad_parser, logging_parser, json_output_parser])
128 get_adserviceaccount_parser.add_argument('--sid', dest='queried_sid',
129 help='SID to query (wildcards accepted)')
130 get_adserviceaccount_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
131 help='samAccountName to query (wildcards accepted)')
132 get_adserviceaccount_parser.add_argument('--name', dest='queried_name',
133 help='Name to query (wildcards accepted)')
134 get_adserviceaccount_parser.add_argument('-d', '--domain', dest='queried_domain',
135 help='Domain to query')
136 get_adserviceaccount_parser.add_argument('-a', '--ads-path',
137 help='Additional ADS path')
138 get_adserviceaccount_parser.add_argument('--resolve-sids', dest='resolve_sids',
139 action='store_true', help='Resolve SIDs when querying PrincipalsAllowedToRetrieveManagedPassword')
140 get_adserviceaccount_parser.set_defaults(func=get_adserviceaccount)
141
97142 # Parser for the get-objectacl command
98143 get_objectacl_parser = subparsers.add_parser('get-objectacl', help='Takes a domain SID, '\
99 'samAccountName or name, and return the ACL of the associated object', parents=[ad_parser])
144 'samAccountName or name, and return the ACL of the associated object',
145 parents=[ad_parser, logging_parser, json_output_parser])
100146 get_objectacl_parser.add_argument('--sid', dest='queried_sid',
101147 help='SID to query (wildcards accepted)')
102148 get_objectacl_parser.add_argument('--sam-account-name', dest='queried_sam_account_name',
121167
122168 # Parser for the get-netuser command
123169 get_netuser_parser = subparsers.add_parser('get-netuser', help='Queries information about '\
124 'a domain user', parents=[ad_parser])
170 'a domain user', parents=[ad_parser, logging_parser, json_output_parser])
125171 get_netuser_parser.add_argument('--username', dest='queried_username',
126172 help='Username to query (wildcards accepted)')
127173 get_netuser_parser.add_argument('-d', '--domain', dest='queried_domain',
146192
147193 # Parser for the get-netgroup command
148194 get_netgroup_parser = subparsers.add_parser('get-netgroup', help='Get a list of all current '\
149 'domain groups, or a list of groups a domain user is member of', parents=[ad_parser])
195 'domain groups, or a list of groups a domain user is member of',
196 parents=[ad_parser, logging_parser, json_output_parser])
150197 get_netgroup_parser.add_argument('--groupname', dest='queried_groupname',
151198 default='*', help='Group to query (wildcards accepted)')
152199 get_netgroup_parser.add_argument('--sid', dest='queried_sid',
165212
166213 # Parser for the get-netcomputer command
167214 get_netcomputer_parser = subparsers.add_parser('get-netcomputer', help='Queries informations about '\
168 'domain computers', parents=[ad_parser])
215 'domain computers', parents=[ad_parser, logging_parser, json_output_parser])
169216 get_netcomputer_parser.add_argument('--computername', dest='queried_computername',
170217 default='*', help='Computer name to query')
171218 get_netcomputer_parser.add_argument('-os', '--operating-system', dest='queried_os',
192239
193240 # Parser for the get-netdomaincontroller command
194241 get_netdomaincontroller_parser = subparsers.add_parser('get-netdomaincontroller', help='Get a list of '\
195 'domain controllers for the given domain', parents=[ad_parser])
242 'domain controllers for the given domain', parents=[ad_parser, logging_parser, json_output_parser])
196243 get_netdomaincontroller_parser.add_argument('-d', '--domain', dest='queried_domain',
197244 help='Domain to query')
198245 get_netdomaincontroller_parser.set_defaults(func=get_netdomaincontroller)
199246
200247 # Parser for the get-netfileserver command
201248 get_netfileserver_parser = subparsers.add_parser('get-netfileserver', help='Return a list of '\
202 'file servers, extracted from the domain users\' homeDirectory, scriptPath, and profilePath fields', parents=[ad_parser])
249 'file servers, extracted from the domain users\' homeDirectory, scriptPath, and profilePath fields',
250 parents=[ad_parser, logging_parser, json_output_parser])
203251 get_netfileserver_parser.add_argument('--target-users', nargs='+',
204252 metavar='TARGET_USER', help='A list of users to target to find file servers (wildcards accepted)')
205253 get_netfileserver_parser.add_argument('-d', '--domain', dest='queried_domain',
208256
209257 # Parser for the get-dfsshare command
210258 get_dfsshare_parser = subparsers.add_parser('get-dfsshare', help='Return a list of '\
211 'all fault tolerant distributed file systems for a given domain', parents=[ad_parser])
259 'all fault tolerant distributed file systems for a given domain', parents=[ad_parser, logging_parser, json_output_parser])
212260 get_dfsshare_parser.add_argument('-d', '--domain', dest='queried_domain',
213261 help='Domain to query')
214262 get_dfsshare_parser.add_argument('-v', '--version', nargs='+', choices=['v1', 'v2'],
219267
220268 # Parser for the get-netou command
221269 get_netou_parser = subparsers.add_parser('get-netou', help='Get a list of all current '\
222 'OUs in the domain', parents=[ad_parser])
270 'OUs in the domain', parents=[ad_parser, logging_parser, json_output_parser])
223271 get_netou_parser.add_argument('--ouname', dest='queried_ouname',
224272 default='*', help='OU name to query (wildcards accepted)')
225273 get_netou_parser.add_argument('--guid', dest='queried_guid',
234282
235283 # Parser for the get-netsite command
236284 get_netsite_parser = subparsers.add_parser('get-netsite', help='Get a list of all current '\
237 'sites in the domain', parents=[ad_parser])
285 'sites in the domain', parents=[ad_parser, logging_parser, json_output_parser])
238286 get_netsite_parser.add_argument('--sitename', dest='queried_sitename',
239287 help='Site name to query (wildcards accepted)')
240288 get_netsite_parser.add_argument('--guid', dest='queried_guid',
249297
250298 # Parser for the get-netsubnet command
251299 get_netsubnet_parser = subparsers.add_parser('get-netsubnet', help='Get a list of all current '\
252 'subnets in the domain', parents=[ad_parser])
300 'subnets in the domain', parents=[ad_parser, logging_parser, json_output_parser])
253301 get_netsubnet_parser.add_argument('--sitename', dest='queried_sitename',
254302 help='Only return subnets for the specified site name (wildcards accepted)')
255303 get_netsubnet_parser.add_argument('-d', '--domain', dest='queried_domain',
262310
263311 # Parser for the get-netdomaintrust command
264312 get_netdomaintrust_parser = subparsers.add_parser('get-netdomaintrust', help='Returns a list of all the '\
265 'trusts of the specified domain', parents=[ad_parser])
313 'trusts of the specified domain', parents=[ad_parser, logging_parser, json_output_parser])
266314 get_netdomaintrust_parser.add_argument('-d', '--domain', dest='queried_domain',
267315 help='Domain to query')
268316 get_netdomaintrust_parser.set_defaults(func=get_netdomaintrust)
269317
270318 # Parser for the get-netgpo command
271319 get_netgpo_parser = subparsers.add_parser('get-netgpo', help='Get a list of all current '\
272 'GPOs in the domain', parents=[ad_parser])
320 'GPOs in the domain', parents=[ad_parser, logging_parser, json_output_parser])
273321 get_netgpo_parser.add_argument('--gponame', dest='queried_gponame',
274322 default='*', help='GPO name to query for (wildcards accepted)')
275323 get_netgpo_parser.add_argument('--displayname', dest='queried_displayname',
282330
283331 # Parser for the get-netpso command
284332 get_netpso_parser = subparsers.add_parser('get-netpso', help='Get a list of all current '\
285 'PSOs in the domain', parents=[ad_parser])
333 'PSOs in the domain', parents=[ad_parser, logging_parser, json_output_parser])
286334 get_netpso_parser.add_argument('--psoname', dest='queried_psoname',
287335 default='*', help='pso name to query for (wildcards accepted)')
288336 get_netpso_parser.add_argument('--displayname', dest='queried_displayname',
295343
296344 # Parser for the get-domainpolicy command
297345 get_domainpolicy_parser = subparsers.add_parser('get-domainpolicy', help='Returns the default domain or DC '\
298 'policy for the queried domain or DC', parents=[ad_parser])
346 'policy for the queried domain or DC', parents=[ad_parser, logging_parser, json_output_parser])
299347 get_domainpolicy_parser.add_argument('--source', dest='source', default='domain',
300348 choices=['domain', 'dc'], help='Extract domain or DC policy (default: %(default)s)')
301349 get_domainpolicy_parser.add_argument('-d', '--domain', dest='queried_domain',
306354
307355 # Parser for the get-gpttmpl command
308356 get_gpttmpl_parser = subparsers.add_parser('get-gpttmpl', help='Helper to parse a GptTmpl.inf policy '\
309 'file path into a custom object', parents=[ad_parser])
357 'file path into a custom object', parents=[ad_parser, logging_parser, json_output_parser])
310358 get_gpttmpl_parser.add_argument('--gpt-tmpl-path', type=str, required=True,
311359 dest='gpttmpl_path', help='The GptTmpl.inf file path name to parse')
312360 get_gpttmpl_parser.set_defaults(func=get_gpttmpl)
313361
314362 # Parser for the get-netgpogroup command
315363 get_netgpogroup_parser = subparsers.add_parser('get-netgpogroup', help='Parses all GPOs in the domain '\
316 'that set "Restricted Group" or "Groups.xml"', parents=[ad_parser])
364 'that set "Restricted Group" or "Groups.xml"', parents=[ad_parser, logging_parser, json_output_parser])
317365 get_netgpogroup_parser.add_argument('--gponame', dest='queried_gponame',
318366 default='*', help='GPO name to query for (wildcards accepted)')
319367 get_netgpogroup_parser.add_argument('--displayname', dest='queried_displayname',
328376
329377 # Parser for the find-gpocomputeradmin command
330378 find_gpocomputeradmin_parser = subparsers.add_parser('find-gpocomputeradmin', help='Takes a computer (or OU) and determine '\
331 'who has administrative access to it via GPO', parents=[ad_parser])
379 'who has administrative access to it via GPO', parents=[ad_parser, logging_parser, json_output_parser])
332380 find_gpocomputeradmin_parser.add_argument('--computername', dest='queried_computername',
333381 default=str(), help='The computer to determine who has administrative access to it')
334382 find_gpocomputeradmin_parser.add_argument('--ouname', dest='queried_ouname',
342390
343391 # Parser for the find-gpolocation command
344392 find_gpolocation_parser = subparsers.add_parser('find-gpolocation', help='Takes a username or a group name and determine '\
345 'the computers it has administrative access to via GPO', parents=[ad_parser])
393 'the computers it has administrative access to via GPO', parents=[ad_parser, logging_parser, json_output_parser])
346394 find_gpolocation_parser.add_argument('--username', dest='queried_username',
347395 default=str(), help='The username to query for access (no wildcard)')
348396 find_gpolocation_parser.add_argument('--groupname', dest='queried_groupname',
355403 find_gpolocation_parser.set_defaults(func=find_gpolocation)
356404
357405 # Parser for the get-netgroup command
358 get_netgroupmember_parser = subparsers.add_parser('get-netgroupmember', help='Return a list of members of a domain group', parents=[ad_parser])
406 get_netgroupmember_parser = subparsers.add_parser('get-netgroupmember', help='Return a list of members of a domain group',
407 parents=[ad_parser, logging_parser, json_output_parser])
359408 get_netgroupmember_parser.add_argument('--groupname', dest='queried_groupname',
360409 help='Group to query, defaults to the \'Domain Admins\' group (wildcards accepted)')
361410 get_netgroupmember_parser.add_argument('--sid', dest='queried_sid',
375424
376425 # Parser for the get-netsession command
377426 get_netsession_parser = subparsers.add_parser('get-netsession', help='Queries a host to return a '\
378 'list of active sessions on the host (you can use local credentials instead of domain credentials)', parents=[target_parser])
427 'list of active sessions on the host (you can use local credentials instead of domain credentials)',
428 parents=[target_parser, logging_parser, json_output_parser])
379429 get_netsession_parser.set_defaults(func=get_netsession)
380430
381431 #Parser for the get-localdisks command
382432 get_localdisks_parser = subparsers.add_parser('get-localdisks', help='Queries a host to return a '\
383 'list of active disks on the host (you can use local credentials instead of domain credentials)', parents=[target_parser])
433 'list of active disks on the host (you can use local credentials instead of domain credentials)',
434 parents=[target_parser, logging_parser, json_output_parser])
384435 get_localdisks_parser.set_defaults(func=get_localdisks)
385436
386437 #Parser for the get-netdomain command
387438 get_netdomain_parser = subparsers.add_parser('get-netdomain', help='Queries a host for available domains',
388 parents=[ad_parser])
439 parents=[ad_parser, logging_parser, json_output_parser])
389440 get_netdomain_parser.set_defaults(func=get_netdomain)
390441
391442 # Parser for the get-netshare command
392443 get_netshare_parser = subparsers.add_parser('get-netshare', help='Queries a host to return a '\
393 'list of available shares on the host (you can use local credentials instead of domain credentials)', parents=[target_parser])
444 'list of available shares on the host (you can use local credentials instead of domain credentials)',
445 parents=[target_parser, logging_parser, json_output_parser])
394446 get_netshare_parser.set_defaults(func=get_netshare)
395447
396448 # Parser for the get-netloggedon command
397449 get_netloggedon_parser = subparsers.add_parser('get-netloggedon', help='This function will '\
398450 'execute the NetWkstaUserEnum RPC call to query a given host for actively logged on '\
399 'users', parents=[target_parser])
451 'users', parents=[target_parser, logging_parser, json_output_parser])
400452 get_netloggedon_parser.set_defaults(func=get_netloggedon)
401453
402454 # Parser for the get-netlocalgroup command
403455 get_netlocalgroup_parser = subparsers.add_parser('get-netlocalgroup', help='Gets a list of '\
404456 'members of a local group on a machine, or returns every local group. You can use local '\
405457 'credentials instead of domain credentials, however, domain credentials are needed to '\
406 'resolve domain SIDs.', parents=[target_parser])
458 'resolve domain SIDs.', parents=[target_parser, logging_parser, json_output_parser])
407459 get_netlocalgroup_parser.add_argument('--groupname', dest='queried_groupname',
408460 help='Group to list the members of (defaults to the local \'Administrators\' group')
409461 get_netlocalgroup_parser.add_argument('--list-groups', action='store_true',
410462 help='If set, returns a list of the local groups on the targets')
411463 get_netlocalgroup_parser.add_argument('-t', '--dc-ip', dest='domain_controller',
412464 default=str(), help='IP address of the Domain Controller (used to resolve domain SIDs)')
465 get_netlocalgroup_parser.add_argument('--tls', action='store_true', dest='do_tls',
466 help='Force TLS connection to the Domain Controller')
413467 get_netlocalgroup_parser.add_argument('-r', '--recurse', action='store_true',
414468 help='If the group member is a domain group, try to resolve its members as well')
415469 get_netlocalgroup_parser.set_defaults(func=get_netlocalgroup)
416470
417471 # Parser for the invoke-checklocaladminaccess command
418472 invoke_checklocaladminaccess_parser = subparsers.add_parser('invoke-checklocaladminaccess', help='Checks '\
419 'if the given user has local admin access on the given host', parents=[target_parser])
473 'if the given user has local admin access on the given host',
474 parents=[target_parser, logging_parser, json_output_parser])
420475 invoke_checklocaladminaccess_parser.set_defaults(func=invoke_checklocaladminaccess)
421476
422477 # Parser for the get-netprocess command
423478 get_netprocess_parser = subparsers.add_parser('get-netprocess', help='This function will '\
424479 'execute the \'Select * from Win32_Process\' WMI query to a given host for a list of '\
425 'executed process', parents=[target_parser])
480 'executed process', parents=[target_parser, logging_parser, json_output_parser])
426481 get_netprocess_parser.set_defaults(func=get_netprocess)
427482
428483 # Parser for the get-userevent command
429484 get_userevent_parser = subparsers.add_parser('get-userevent', help='This function will '\
430 'execute the \'Select * from Win32_Process\' WMI query to a given host for a list of '\
431 'executed process', parents=[target_parser])
485 'execute the \'SELECT * from Win32_NTLogEvent\' WMI query to a given host for a list of '\
486 'executed process', parents=[target_parser, logging_parser, json_output_parser])
432487 get_userevent_parser.add_argument('--event-type', nargs='+', choices=['logon', 'tgt'],
433488 default=['logon', 'tgt'], help='The type of event to search for: logon, tgt, or all (default: all)')
434489 get_userevent_parser.add_argument('--date-start', type=int,
437492
438493 # Parser for the invoke-userhunter command
439494 invoke_userhunter_parser = subparsers.add_parser('invoke-userhunter', help='Finds '\
440 'which machines domain users are logged into', parents=[ad_parser, hunter_parser])
495 'which machines domain users are logged into', parents=[ad_parser, hunter_parser, logging_parser])
441496 invoke_userhunter_parser.add_argument('--unconstrained', action='store_true',
442497 help='Query only computers with unconstrained delegation')
443498 invoke_userhunter_parser.add_argument('--admin-count', action='store_true',
463518
464519 # Parser for the invoke-processhunter command
465520 invoke_processhunter_parser = subparsers.add_parser('invoke-processhunter', help='Searches machines '\
466 'for processes with specific name, or ran by specific users', parents=[ad_parser, hunter_parser])
521 'for processes with specific name, or ran by specific users',
522 parents=[ad_parser, hunter_parser, logging_parser])
467523 invoke_processhunter_parser.add_argument('--processname', dest='queried_processname',
468524 nargs='+', default=list(), help='Names of the process to hunt')
469525 invoke_processhunter_parser.add_argument('--stop-on-success', action='store_true',
474530
475531 # Parser for the invoke-eventhunter command
476532 invoke_eventhunter_parser = subparsers.add_parser('invoke-eventhunter', help='Searches machines '\
477 'for events with specific name, or ran by specific users', parents=[ad_parser, hunter_parser])
533 'for events with specific name, or ran by specific users',
534 parents=[ad_parser, hunter_parser, logging_parser])
478535 invoke_eventhunter_parser.add_argument('--search-days', dest='search_days',
479536 type=int, default=3, help='Number of days back to search logs for (default: %(default)s)')
480537 invoke_eventhunter_parser.set_defaults(func=invoke_eventhunter)
481538
482539 args = parser.parse_args()
540
541 # setup the main logger
542 logger = logging.getLogger('pywerview_main_logger')
543 logging.addLevelName(5, 'ULTRA')
544 logger.setLevel(args.logging_level)
545 console_handler = logging.StreamHandler()
546 console_handler.setLevel(args.logging_level)
547 formatter = logging.Formatter('[%(levelname)s] %(name)s - %(funcName)s : %(message)s')
548 console_handler.setFormatter(formatter)
549 logger.addHandler(console_handler)
550
483551 if args.hashes:
484552 try:
485553 args.lmhash, args.nthash = args.hashes.split(':')
490558 else:
491559 args.lmhash = args.nthash = str()
492560
493 if args.password is None and not args.hashes:
561 if args.password is None and args.hashes is None and not args.do_kerberos:
494562 from getpass import getpass
495563 args.password = getpass('Password:')
496564
497565 parsed_args = dict()
498566 for k, v in vars(args).items():
499 if k not in ('func', 'hashes', 'submodule'):
567 if k not in ('func', 'hashes', 'submodule', 'logging_level', 'json_output'):
500568 parsed_args[k] = v
501569
502 #try:
570 starting_time = datetime.datetime.now()
503571 results = args.func(**parsed_args)
504 #except Exception, e:
505 #print >>sys.stderr, repr(e)
506 #sys.exit(-1)
572 ending_time = datetime.datetime.now()
573
574 try:
575 json_output = args.json_output
576 except AttributeError:
577 json_output = False
507578
508579 if results is not None:
509 try:
510 for x in results:
511 print(x, '\n')
512 # for example, invoke_checklocaladminaccess returns a bool
513 except TypeError:
514 print(results)
515
580 if json_output:
581 results_json = {'cmd' : {'submodule' : args.submodule, 'args' : parsed_args,
582 'starting_time': starting_time, 'ending_time': ending_time}}
583 try:
584 objects_json = [x.to_json() for x in results]
585 except TypeError:
586 try:
587 objects_json = [results.to_json()]
588 except AttributeError:
589 objects_json = results
590 results_json['results'] = objects_json
591 print(json.dumps(results_json, default=str))
592 else:
593 try:
594 print('\n\n'.join(str(x) for x in results))
595 except TypeError:
596 print(results)
597
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
16
17 import logging
18 import binascii
19 from Cryptodome.Hash import MD4
20 from impacket.examples.ntlmrelayx.attacks.ldapattack import MSDS_MANAGEDPASSWORD_BLOB
21 from impacket.ldap.ldaptypes import SR_SECURITY_DESCRIPTOR
1622
1723 __uac_flags = {0x0000001: 'SCRIPT',
1824 0x0000002: 'ACCOUNTDISABLE',
7581 try:
7682 int_value = int(raw_value)
7783 except ValueError:
84 self._logger.warning('Unable to convert raw flag value to int')
7885 return raw_value
7986
8087 parsed_flags = list()
8794 try:
8895 return dictionary[int(raw_value)]
8996 except (ValueError, KeyError):
97 self._logger.warning('Unable to convert raw value to int')
9098 return raw_value
9199
92100 def format_useraccountcontrol(raw_value):
96104 try:
97105 int_value = int(raw_value)
98106 except ValueError:
107 self._logger.warning('Unable to convert raw ace acess mask value to int')
99108 return raw_value
100109
101110 activedirectoryrights = list()
106115 activedirectoryrights += __format_flag(raw_value, __access_mask)
107116
108117 return activedirectoryrights
118
119
120 def format_managedpassword(raw_value):
121 blob = MSDS_MANAGEDPASSWORD_BLOB()
122 blob.fromString(raw_value)
123 return binascii.hexlify(MD4.new(blob['CurrentPassword'][:-2]).digest()).decode('utf8')
124
125 def format_groupmsamembership(raw_value):
126 sid = list()
127 sr = SR_SECURITY_DESCRIPTOR(data=raw_value)
128 for dacl in sr['Dacl']['Data']:
129 sid.append(dacl['Ace']['Sid'].formatCanonical())
130 return sid
109131
110132 def format_ace_flags(raw_value):
111133 return __format_flag(raw_value, __ace_flags)
121143
122144 def format_trustattributes(raw_value):
123145 return __format_flag(raw_value, __trust_attrib)
124
146
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
17 import codecs
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
16
1817 from bs4 import BeautifulSoup
1918 from io import BytesIO
2019
6564 file_name = '\\'.join(gpttmpl_path_split[4:])
6665
6766 smb_connection = SMBConnection(remoteName=target, remoteHost=target)
68 # TODO: kerberos login
69 smb_connection.login(self._user, self._password, self._domain,
70 self._lmhash, self._nthash)
71
67 if self._do_kerberos:
68 smb_connection.kerberosLogin(self._user, self._password, self._domain,
69 self._lmhash, self._nthash)
70 else:
71 smb_connection.login(self._user, self._password, self._domain,
72 self._lmhash, self._nthash)
73
74 self._logger.debug('Get File: Share = {0}, file_name ={1}'.format(share, file_name))
7275 smb_connection.connectTree(share)
7376 smb_connection.getFile(share, file_name, content_io.write)
7477 try:
7578 content = content_io.getvalue().decode('utf-16le')[1:].replace('\r', '')
7679 except UnicodeDecodeError:
80 self._logger.warning('Unicode error: trying utf-8')
7781 content = content_io.getvalue().decode('utf-8').replace('\r', '')
7882
7983 gpttmpl_final = GptTmpl(list())
110114 try:
111115 privilege_rights_policy = gpttmpl.privilegerights
112116 except AttributeError:
117 self._logger.critical('Could not parse privilegerights from the DC policy, SIDs will not be resolved')
113118 return gpttmpl
114119
115120 members = inspect.getmembers(privilege_rights_policy, lambda x: not(inspect.isroutine(x)))
116121 with NetRequester(self._domain_controller, self._domain, self._user,
117 self._password, self._lmhash, self._nthash) as net_requester:
122 self._password, self._lmhash, self._nthash, self._do_kerberos, self._do_tls) as net_requester:
118123 for attr in privilege_rights_policy._attributes_dict:
119124 attribute = privilege_rights_policy._attributes_dict[attr]
120125 if not isinstance(attribute, list):
129134 try:
130135 resolved_sid = net_requester.get_adobject(queried_sid=sid, queried_domain=self._queried_domain)[0]
131136 except IndexError:
137 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(sid))
132138 resolved_sid = sid
133139 else:
134140 resolved_sid = resolved_sid.distinguishedname.split(',')[:2]
135141 resolved_sid = resolved_sid[1] + '\\' + resolved_sid[0]
136142 resolved_sid = resolved_sid.replace('CN=', '')
143 finally:
137144 resolved_sids.append(resolved_sid)
138145 if len(resolved_sids) == 1:
139146 resolved_sids = resolved_sids[0]
155162 file_name = '\\'.join(groupsxml_path_split[4:])
156163
157164 smb_connection = SMBConnection(remoteName=target, remoteHost=target)
158 # TODO: kerberos login
159 smb_connection.login(self._user, self._password, self._domain,
160 self._lmhash, self._nthash)
161
165 if self._do_kerberos:
166 smb_connection.kerberosLogin(self._user, self._password, self._domain,
167 self._lmhash, self._nthash)
168 else:
169 smb_connection.login(self._user, self._password, self._domain,
170 self._lmhash, self._nthash)
171
172 self._logger.debug('Get File: Share = {0}, file_name ={1}'.format(share, file_name))
162173 smb_connection.connectTree(share)
163174 try:
164175 smb_connection.getFile(share, file_name, content_io.write)
165176 except SessionError:
177 self._logger.warning('Error while getting the file {}, skipping...'.format(file_name))
166178 return list()
167179
168180 content = content_io.getvalue().replace(b'\r', b'')
170182 for group in groupsxml_soup.find_all('Group'):
171183 members = list()
172184 memberof = list()
173
185
174186 raw_xml_member = group.Properties.find_all('Member')
175187 if not raw_xml_member:
176188 continue
222234 return list()
223235
224236 membership = group_membership._attributes_dict
225
237
226238 for ma,mv in membership.items():
227239 if not mv:
228240 continue
274286 results += self._get_groupsgpttmpl(gpttmpl_path, gpo_display_name)
275287 except SessionError:
276288 # If the GptTmpl file doesn't exist, we skip this
289 self._logger.warning('Error while getting the file {}, skipping...'.format(gpttmpl_path,))
277290 pass
278291
279292 if resolve_sids:
284297 resolved_members = list()
285298 resolved_memberof = list()
286299 with NetRequester(self._domain_controller, self._domain, self._user,
287 self._password, self._lmhash, self._nthash) as net_requester:
300 self._password, self._lmhash, self._nthash, self._do_kerberos, self._do_tls) as net_requester:
288301 for member in members:
289302 try:
290303 resolved_member = net_requester.get_adobject(queried_sid=member, queried_domain=self._queried_domain)[0]
291304 resolved_member = resolved_member.distinguishedname
292305 except IndexError:
306 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(member))
293307 resolved_member = member
294308 finally:
295309 resolved_members.append(resolved_member)
300314 resolved_member = net_requester.get_adobject(queried_sid=member, queried_domain=self._queried_domain)[0]
301315 resolved_member = resolved_member.distinguishedname
302316 except IndexError:
317 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(member))
303318 resolved_member = member
304319 finally:
305320 resolved_memberof.append(resolved_member)
315330 raise ValueError('You must specify either a computer name or an OU name')
316331
317332 net_requester = NetRequester(self._domain_controller, self._domain, self._user,
318 self._password, self._lmhash, self._nthash)
333 self._password, self._lmhash, self._nthash, self._do_kerberos,
334 self._do_tls)
319335 if queried_computername:
320336 computers = net_requester.get_netcomputer(queried_computername=queried_computername,
321337 queried_domain=queried_domain,
357373 gpo_computer_admin.add_attributes({'objectname' : obj.name})
358374 gpo_computer_admin.add_attributes({'objectdn' : obj.distinguishedname})
359375 gpo_computer_admin.add_attributes({'objectsid' : obj.objectsid})
360 gpo_computer_admin.add_attributes({'isgroup' : (obj.samaccounttype != '805306368')})
376 gpo_computer_admin.add_attributes({'isgroup' : (obj.samaccounttype != 805306368)})
361377
362378 results.append(gpo_computer_admin)
363379
365381 groups_to_resolve = [gpo_computer_admin.objectsid]
366382 while groups_to_resolve:
367383 group_to_resolve = groups_to_resolve.pop(0)
368
384
369385 group_members = net_requester.get_netgroupmember(queried_sid=group_to_resolve,
370386 queried_domain=self._queried_domain,
371387 full_data=True)
378394 gpo_computer_admin.add_attributes({'objectname' : group_member.samaccountname})
379395 gpo_computer_admin.add_attributes({'objectdn' : group_member.distinguishedname})
380396 gpo_computer_admin.add_attributes({'objectsid' : group_member.objectsid})
381 gpo_computer_admin.add_attributes({'isgroup' : (group_member != '805306368')})
397 gpo_computer_admin.add_attributes({'isgroup' : (group_member.samaccounttype != 805306368)})
382398
383399 results.append(gpo_computer_admin)
384400
391407 queried_localgroup=str(), queried_domain=str()):
392408 results = list()
393409 net_requester = NetRequester(self._domain_controller, self._domain, self._user,
394 self._password, self._lmhash, self._nthash)
410 self._password, self._lmhash, self._nthash, self._do_kerberos,
411 self._do_tls)
395412 if queried_username:
396413 try:
397414 user = net_requester.get_netuser(queried_username=queried_username,
399416 except IndexError:
400417 raise ValueError('Username \'{}\' was not found'.format(queried_username))
401418 else:
402 target_sid = [user.objectsid]
419 target_sid = [user.objectsid]
403420 object_sam_account_name = user.samaccountname
404421 object_distinguished_name = user.distinguishedname
405422 elif queried_groupname:
434451 queried_domain=self._queried_domain)[0].objectsid
435452 except IndexError:
436453 # We may have the name of the group, but not its sam account name
454 self._logger.warning('We may have the name of the group, but not its sam account name.')
437455 try:
438456 object_group_sid = net_requester.get_adobject(queried_name=object_group.samaccountname,
439457 queried_domain=self._queried_domain)[0].objectsid
440458 except IndexError:
441459 # Freak accident when someone is a member of a group, but
442460 # we can't find the group in the AD
461 self._logger.warning('Freak accident when someone is a member of a group, but we can\'t find the group in the AD,'
462 'see DEBUG level for more info')
463 self._logger.debug('Dumping the mysterious object = {}'.format(object_group))
443464 continue
444465
445466 target_sid.append(object_group_sid)
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1616
1717 import random
1818 import multiprocessing
2525
2626 class Hunter(NetRequester):
2727 def __init__(self, target_computer, domain=str(), user=(), password=str(),
28 lmhash=str(), nthash=str(), domain_controller=str(), queried_domain=str()):
28 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
29 domain_controller=str(), queried_domain=str()):
2930 NetRequester.__init__(self, target_computer, domain, user, password,
30 lmhash, nthash, domain_controller)
31 lmhash, nthash, do_kerberos, do_tls, domain_controller)
3132 self._target_domains = list()
3233 self._target_computers = list()
3334 self._target_users = list()
8889 self._target_users.append(rpcobj.TargetUser(attributes))
8990 elif target_server:
9091 with NetRequester(target_server, domain, user, password, lmhash,
91 nthash, domain_controller) as target_server_requester:
92 nthash, do_kerberos, do_tls, domain_controller) as target_server_requester:
9293 for x in target_server_requester.get_netlocalgroup(recurse=True):
9394 if x.isdomain and not x.isgroup:
9495 attributes = {'memberdomain': x.name.split('/')[0].lower(),
138139 self._parent_pipes.append(parent_pipe)
139140 worker = worker_class(worker_pipe, self._domain, self._user,
140141 self._password, self._lmhash, self._nthash,
141 *worker_args)
142 self._do_kerberos, self._do_tls, *worker_args)
142143
143144 worker.start()
144145 self._workers.append(worker)
154155 rlist, wlist, _ = select.select(self._parent_pipes, write_watch_list, list())
155156
156157 for readable in rlist:
157 jobs_done += 1
158 jobs_done += 1
158159 results = readable.recv()
159160 for result in results:
160161 yield result
199200
200201 if foreign_users:
201202 with Misc(self._domain_controller, self._domain, self._user,
202 self._password, self._lmhash, self._nthash) as misc_requester:
203 self._password, self._lmhash, self._nthash, self._do_kerberos, self._do_tls) as misc_requester:
203204 domain_sid = misc_requester.get_domainsid(queried_domain)
204205 domain_short_name = misc_requester.convert_sidtont4(domain_sid).split('\\')[0]
205206 else:
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1616
1717 from impacket.dcerpc.v5.rpcrt import DCERPCException
1818 from impacket.dcerpc.v5 import scmr, drsuapi
1919
2020 from pywerview.requester import LDAPRPCRequester
2121 import pywerview.functions.net
22
23 import struct
2422
2523 class Misc(LDAPRPCRequester):
2624 @LDAPRPCRequester._rpc_connection_init(r'\drsuapi')
5149 def get_domainsid(self, queried_domain=str()):
5250
5351 with pywerview.functions.net.NetRequester(self._domain_controller, self._domain, self._user,
54 self._password, self._lmhash, self._nthash) as r:
52 self._password, self._lmhash, self._nthash,
53 self._do_kerberos, self._do_tls) as r:
5554 domain_controllers = r.get_netdomaincontroller(queried_domain=queried_domain)
5655
5756 if domain_controllers:
5857 primary_dc = domain_controllers[0]
5958 domain_sid = primary_dc.objectsid
60
59
6160 # we need to retrieve the domain sid from the controller sid
6261 domain_sid = '-'.join(domain_sid.split('-')[:-1])
6362 else:
7978
8079 return True
8180
82 class Utils():
83 @staticmethod
84 def convert_sidtostr(raw_sid):
85 str_sid = 'S-{0}-{1}'.format(raw_sid[0], raw_sid[1])
86 for i in range(8, len(raw_sid), 4):
87 str_sid += '-{}'.format(str(struct.unpack('<I', raw_sid[i:i+4])[0]))
88 return str_sid
89
90 @staticmethod
91 def convert_guidtostr(raw_guid):
92 str_guid = str()
93 str_guid += '{}-'.format(hex(struct.unpack('<I', raw_guid[0:4])[0])[2:].zfill(8))
94 str_guid += '{}-'.format(hex(struct.unpack('<H', raw_guid[4:6])[0])[2:].zfill(4))
95 str_guid += '{}-'.format(hex(struct.unpack('<H', raw_guid[6:8])[0])[2:].zfill(4))
96 str_guid += '{}-'.format(raw_guid.hex()[16:20])
97 str_guid += raw_guid.hex()[20:]
98 return str_guid
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
17 import socket
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
16
1817 from datetime import datetime, timedelta
1918 from impacket.dcerpc.v5.ndr import NULL
2019 from impacket.dcerpc.v5 import wkst, srvs, samr
4948 return self._ldap_search(object_filter, adobj.ADObject, attributes=attributes)
5049
5150 @LDAPRPCRequester._ldap_connection_init
51 def get_adserviceaccount(self, queried_domain=str(), queried_sid=str(),
52 queried_name=str(), queried_sam_account_name=str(),
53 ads_path=str(), resolve_sids=False):
54 filter_objectclass = '(ObjectClass=msDS-GroupManagedServiceAccount)'
55 attributes = ['samaccountname', 'distinguishedname', 'objectsid', 'description',
56 'msds-managedpassword', 'msds-groupmsamembership', 'useraccountcontrol']
57
58 if not self._ldap_connection.server.ssl:
59 self._logger.warning('LDAP connection is not encrypted, we can\'t ask '\
60 'for msds-managedpassword, removing from list of attributes')
61 attributes.remove('msds-managedpassword')
62
63 for attr_desc, attr_value in (('objectSid', queried_sid), ('name', escape_filter_chars(queried_name)),
64 ('samAccountName', escape_filter_chars(queried_sam_account_name))):
65 if attr_value:
66 object_filter = '(&({}={}){})'.format(attr_desc, attr_value, filter_objectclass)
67 break
68 else:
69 object_filter = '(&(name=*){})'.format(filter_objectclass)
70
71 adserviceaccounts = self._ldap_search(object_filter, adobj.GMSAAccount, attributes=attributes)
72 sid_resolver = NetRequester(self._domain_controller, self._domain, self._user, self._password, self._lmhash, self._nthash)
73
74 # In this loop, we resolve SID (if true) and we populate 'enabled' attribute
75 for i, adserviceaccount in enumerate(adserviceaccounts):
76 if resolve_sids:
77 results = list()
78 for sid in getattr(adserviceaccount, 'msds-groupmsamembership'):
79 try:
80 resolved_sid = sid_resolver.get_adobject(queried_sid=sid, queried_domain=self._queried_domain,
81 attributes=['distinguishedname'])[0].distinguishedname
82 except IndexError:
83 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(sid))
84 resolved_sid = sid
85 results.append(resolved_sid)
86 adserviceaccounts[i].add_attributes({'msds-groupmsamembership': results})
87 adserviceaccounts[i].add_attributes({'Enabled': 'ACCOUNTDISABLE' not in adserviceaccount.useraccountcontrol})
88 adserviceaccounts[i]._attributes_dict.pop('useraccountcontrol')
89 return adserviceaccounts
90
91 @LDAPRPCRequester._ldap_connection_init
5292 def get_objectacl(self, queried_domain=str(), queried_sid=str(),
5393 queried_name=str(), queried_sam_account_name=str(),
5494 ads_path=str(), sacl=False, rights_filter=str(),
68108 base_dn = ','.join(self._base_dn.split(',')[-2:])
69109 guid_map = {'{00000000-0000-0000-0000-000000000000}': 'All'}
70110 with NetRequester(self._domain_controller, self._domain, self._user, self._password,
71 self._lmhash, self._nthash) as net_requester:
111 self._lmhash, self._nthash, self._do_kerberos, self._do_tls) as net_requester:
72112 for o in net_requester.get_adobject(ads_path='CN=Schema,CN=Configuration,{}'.format(base_dn),
73113 attributes=['name', 'schemaIDGUID'], custom_filter='(schemaIDGUID=*)'):
74114 guid_map['{{{}}}'.format(o.schemaidguid)] = o.name
100140
101141 if resolve_sids:
102142 sid_resolver = NetRequester(self._domain_controller, self._domain,
103 self._user, self._password, self._lmhash, self._nthash)
143 self._user, self._password, self._lmhash, self._nthash,
144 self._do_kerberos, self._do_tls)
104145 sid_mapping = adobj.ADObject._well_known_sids.copy()
105146 else:
106147 sid_resolver = None
139180 queried_domain=self._queried_domain, attributes=['distinguishedname'])[0]
140181 resolved_sid = resolved_sid.distinguishedname
141182 except IndexError:
183 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(converted_sid))
142184 resolved_sid = attributes['securityidentifier']
143185 finally:
144186 sid_mapping[converted_sid] = resolved_sid
198240
199241 # RFC 4515, section 3
200242 # However if we escape *, we can no longer use wildcard within `--groupname`
201 # Maybe we can raise a warning here ?
202243 if not '*' in queried_groupname:
203244 queried_groupname = escape_filter_chars(queried_groupname)
245 else:
246 self._logger.warning('"*" detected in "{}", if it also contains "(",")" or "\\", '
247 'script will probably crash ("invalid filter"). '
248 'Don\'t use wildcard with these characters'.format(queried_groupname))
204249
205250 if queried_username:
251 self._logger.debug('Queried username = {}'.format(queried_username))
206252 results = list()
207253 sam_account_name_to_resolve = [queried_username]
208254 first_run = True
249295 group_search_filter += '(objectCategory=group)'
250296
251297 if queried_sid:
298 self._logger.debug('Queried SID = {}'.format(queried_username))
252299 group_search_filter += '(objectSid={})'.format(queried_sid)
253300 elif queried_groupname:
301 self._logger.debug('Queried groupname = {}'.format(queried_groupname))
254302 group_search_filter += '(name={})'.format(queried_groupname)
255303
256304 if full_data:
456504 try:
457505 # `--groupname` option is supplied
458506 if _groupname:
507 self._logger.debug('Queried groupname = {}'.format(queried_groupname))
459508 groups = self.get_netgroup(queried_groupname=_groupname,
460509 queried_domain=self._queried_domain,
461510 full_data=True)
462511
463512 # `--groupname` option is missing, falling back to the "Domain Admins"
464513 else:
514 self._logger.debug('No groupname provided, falling back to the "Domain Admins"'.format(queried_groupname))
465515 if _sid:
466516 queried_sid = _sid
467517 else:
468518 with pywerview.functions.misc.Misc(self._domain_controller,
469519 self._domain, self._user,
470520 self._password, self._lmhash,
471 self._nthash) as misc_requester:
521 self._nthash, self._do_kerberos,
522 self._do_tls) as misc_requester:
472523 queried_sid = misc_requester.get_domainsid(queried_domain) + '-512'
524 self._logger.debug('Found Domains Admins SID = {}'.format(queried_sid))
473525 groups = self.get_netgroup(queried_sid=queried_sid,
474526 queried_domain=self._queried_domain,
475527 full_data=True)
490542 try:
491543 for member in group.member:
492544 # RFC 4515, section 3
545 self._logger.warning('Member name = "{}" will be escaped'.format(member))
493546 member = escape_filter_chars(member, encoding='utf-8')
494547 dn_filter = '(distinguishedname={}){}'.format(member, custom_filter)
495548 members += self.get_netuser(custom_filter=dn_filter, queried_domain=self._queried_domain)
496549 members += self.get_netgroup(custom_filter=dn_filter, queried_domain=self._queried_domain, full_data=True)
497550 # The group doesn't have any members
498551 except AttributeError:
552 self._logger.debug('The group doesn\'t have any members')
499553 continue
500554
501555 for member in members:
508562 try:
509563 member_domain = member_dn[member_dn.index('DC='):].replace('DC=', '').replace(',', '.')
510564 except IndexError:
565 self._logger.warning('Exception was raised while handling member_dn, falling back to empty string')
511566 member_domain = str()
512567 is_group = (member.samaccounttype != 805306368)
513568
744799 except AttributeError:
745800 # Here, the member is a foreign security principal
746801 # TODO: resolve it properly
802 self._logger.warning('The member is a foreign security principal, SID will not be resolved')
747803 attributes['name'] = '{}\\{}'.format(member_domain, ad_object.objectsid)
748804 attributes['isgroup'] = 'group' in ad_object.objectclass
749805 try:
750 # TODO: Now, lastlogon is raw, convert here or within rpc __str__ ?
751806 attributes['lastlogon'] = ad_object.lastlogon
752807 except AttributeError:
808 self._logger.warning('lastlogon is not set, falling back to empty string')
753809 attributes['lastlogon'] = str()
754810 except IndexError:
755811 # We did not manage to resolve this SID against the DC
812 self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(member_sid))
756813 attributes['isdomain'] = False
757814 attributes['isgroup'] = False
758815 attributes['name'] = attributes['sid']
780837 domain_member_attributes['server'] = attributes['name']
781838 domain_member_attributes['sid'] = domain_member.objectsid
782839 try:
783 # TODO : Same here, must convert the timestamp
784840 domain_member_attributes['lastlogin'] = ad_object.lastlogon
785841 except AttributeError:
842 self._logger.warning('lastlogon is not set, falling back to empty string')
786843 domain_member_attributes['lastlogin'] = str()
787844 results.append(rpcobj.RPCObject(domain_member_attributes))
788845
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1616
1717 import inspect
18 import struct
19 import pyasn1
18 import logging
19
2020 from impacket.ldap.ldaptypes import ACE, ACCESS_ALLOWED_OBJECT_ACE, ACCESS_MASK, LDAP_SID, SR_SECURITY_DESCRIPTOR
2121
2222 import pywerview.functions.misc as misc
5656 'S-1-5': 'NT Authority'}
5757
5858 def __init__(self, attributes):
59 logger = logging.getLogger('pywerview_main_logger.ADObject')
60 logger.ULTRA = 5
61 self._logger = logger
62
5963 self._attributes_dict = dict()
6064 self.add_attributes(attributes)
6165
6266 def add_attributes(self, attributes):
67 self._logger.log(self._logger.ULTRA,'ADObject instancied with the following attributes : {}'.format(attributes))
6368 for attr in attributes:
6469 self._attributes_dict[attr.lower()] = attributes[attr]
6570
8388 max_length = len(attr)
8489 for attr in self._attributes_dict:
8590 attribute = self._attributes_dict[attr]
91 self._logger.log(self._logger.ULTRA,'Trying to print : attribute name = {0} / value = {1}'.format(attr, attribute))
8692 if isinstance(attribute, list):
8793 if any(isinstance(x, bytes) for x in attribute):
8894 attribute = ['{}...'.format(x.hex()[:97]) for x in attribute]
105111 attribute = ('\n' + str(attribute)).replace('\n', '\n\t')
106112
107113 s += '{}: {}{}\n'.format(attr, ' ' * (max_length - len(attr)), attribute)
108 #if not member.startswith('_'):
109 ##print(len(member[1]))
110 ## print(member)
111 ## ??
112 #if member in ('logonhours', 'msds-generationid'):
113 #value = member[1]
114 #member_value = [x for x in value]
115
116 ## Attribute is a SID
117 #elif member in ('objectsid', 'ms-ds-creatorsid', 'securityidentifier'):
118 #init_value = member[1]
119 #member_value = misc.Utils.convert_sidtostr(init_value)
120
114
121115 s = s[:-1]
122116 return s
123
117
124118 def __repr__(self):
125119 return str(self)
126120
121 def to_json(self):
122 return self._attributes_dict
123
127124 class ACE(ADObject):
128125
129126 def __init__(self, attributes):
159156 class Trust(ADObject):
160157
161158 def __init__(self, attributes):
159 logger = logging.getLogger('pywerview_main_logger.Trust')
160 self._logger = logger
162161 ADObject.__init__(self, attributes)
163162 trust_attributes = self.trustattributes
164163 trust_direction = self.trustdirection
181180 max_length = len('trustattributes')
182181
183182 for attr in self._attributes_dict:
183 self._logger.log(self._logger.ULTRA,'Trying to print : attribute name = {0} / value = {1}'.format(attr, self._attributes_dict[attr]))
184184 if attr in ('trustpartner', 'trustdirection', 'trusttype', 'whenchanged', 'whencreated'):
185185 attribute = self._attributes_dict[attr]
186186 elif attr == 'trustattributes':
187187 attribute = ', '.join(self._attributes_dict[attr])
188188 else:
189 self._logger.debug('Ignoring : attribute name = {0}'.format(attr, self._attributes_dict[attr]))
189190 continue
190191 s += '{}: {}{}\n'.format(attr, ' ' * (max_length - len(attr)), attribute)
191192
200201 pass
201202
202203 class GptTmpl(ADObject):
203 pass
204 def to_json(self):
205 json_dict = {}
206 for k, v in self._attributes_dict.items():
207 json_dict[k] = v.to_json()
208 return json_dict
204209
205210 class GPOGroup(ADObject):
206211 pass
214219 class GPOLocation(ADObject):
215220 pass
216221
222 class GMSAAccount(ADObject):
223 pass
224
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1616
17 import logging
1718 import inspect
1819
1920 class RPCObject:
2021 def __init__(self, obj):
22 logger = logging.getLogger('pywerview_main_logger.RPCObject')
23 logger.ULTRA = 5
24 self._logger = logger
25
2126 attributes = dict()
2227 try:
2328 for key in obj.fields.keys():
2732 self.add_attributes(attributes)
2833
2934 def add_attributes(self, attributes):
35 self._logger.log(self._logger.ULTRA,'RPCObject instancied with the following attributes : {}'.format(attributes))
3036 for key, value in attributes.items():
3137 key = key.lower()
3238 if key in ('wkui1_logon_domain', 'wkui1_logon_server',
3339 'wkui1_oth_domains', 'wkui1_username',
3440 'sesi10_cname', 'sesi10_username'):
3541 value = value.rstrip('\x00')
36
42
3743 setattr(self, key.lower(), value)
3844
3945 def __str__(self):
4551 if len(member[0]) > max_length:
4652 max_length = len(member[0])
4753 for member in members:
54 self._logger.log(self._logger.ULTRA,'Trying to print : attribute name = {0} / value = {1}'.format(member[0], member[1]))
4855 if not member[0].startswith('_'):
4956 s += '{}: {}{}\n'.format(member[0], ' ' * (max_length - len(member[0])), member[1])
5057
5360
5461 def __repr__(self):
5562 return str(self)
63
64 def to_json(self):
65 members = inspect.getmembers(self, lambda x: not(inspect.isroutine(x)))
66 results = dict()
67 for member in members:
68 if not member[0].startswith('_'):
69 results[member[0]]=member[1]
70 return(results)
5671
5772 class TargetUser(RPCObject):
5873 pass
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
16
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
16
17 import sys
18 import logging
1719 import socket
1820 import ntpath
1921 import ldap3
22 import os
23 import tempfile
2024
2125 from ldap3.protocol.formatters.formatters import *
2226
2327 from impacket.smbconnection import SMBConnection
28 from impacket.smbconnection import SessionError
29 from impacket.krb5.ccache import CCache, Credential, CountedOctetString
30 from impacket.krb5 import constants
31 from impacket.krb5.types import Principal
2432 from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
2533 from impacket.dcerpc.v5 import transport, wkst, srvs, samr, scmr, drsuapi, epm
2634 from impacket.dcerpc.v5.dcom import wmi
3240
3341 class LDAPRequester():
3442 def __init__(self, domain_controller, domain=str(), user=(), password=str(),
35 lmhash=str(), nthash=str()):
43 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False):
3644 self._domain_controller = domain_controller
3745 self._domain = domain
3846 self._user = user
3947 self._password = password
4048 self._lmhash = lmhash
4149 self._nthash = nthash
50 self._do_kerberos = do_kerberos
51 self._do_tls = do_tls
4252 self._queried_domain = None
4353 self._ads_path = None
4454 self._ads_prefix = None
4555 self._ldap_connection = None
4656 self._base_dn = None
4757
58 logger = logging.getLogger('pywerview_main_logger.LDAPRequester')
59 self._logger = logger
60
4861 def _get_netfqdn(self):
4962 try:
5063 smb = SMBConnection(self._domain_controller, self._domain_controller)
5164 except socket.error:
65 self._logger.warning('Socket error when opening the SMB connection')
5266 return str()
67
68 self._logger.debug('SMB loging parameters : user = {0} / password = {1} / domain = {2} '
69 '/ LM hash = {3} / NT hash = {4}'.format(self._user, self._password,
70 self._domain, self._lmhash,
71 self._nthash))
5372
5473 smb.login(self._user, self._password, domain=self._domain,
5574 lmhash=self._lmhash, nthash=self._nthash)
5877
5978 return fqdn
6079
80 def _patch_spn(self, creds, principal):
81 self._logger.debug('Patching principal to {}'.format(principal))
82
83 from pyasn1.codec.der import decoder, encoder
84 from impacket.krb5.asn1 import TGS_REP, Ticket
85
86 # Code is ~~based on~~ stolen from https://github.com/SecureAuthCorp/impacket/pull/1256
87 tgs = creds.toTGS(principal)
88 decoded_st = decoder.decode(tgs['KDC_REP'], asn1Spec=TGS_REP())[0]
89 decoded_st['ticket']['sname']['name-string'][0] = 'ldap'
90 decoded_st['ticket']['sname']['name-string'][1] = self._domain_controller.lower()
91 decoded_st['ticket']['realm'] = self._queried_domain.upper()
92
93 new_creds = Credential(data=creds.getData())
94 new_creds.ticket = CountedOctetString()
95 new_creds.ticket['data'] = encoder.encode(decoded_st['ticket'].clone(tagSet=Ticket.tagSet, cloneValueFlag=True))
96 new_creds.ticket['length'] = len(new_creds.ticket['data'])
97 new_creds['server'].fromPrincipal(Principal(principal, type=constants.PrincipalNameType.NT_PRINCIPAL.value))
98
99 return new_creds
100
61101 def _create_ldap_connection(self, queried_domain=str(), ads_path=str(),
62102 ads_prefix=str()):
63103 if not self._domain:
64 self._domain = self._get_netfqdn()
104 if self._do_kerberos:
105 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
106 self._domain = ccache.principal.realm['data'].decode('utf-8')
107 else:
108 try:
109 self._domain = self._get_netfqdn()
110 except SessionError as e:
111 self._logger.critical(e)
112 sys.exit(-1)
65113
66114 if not queried_domain:
67 queried_domain = self._get_netfqdn()
115 if self._do_kerberos:
116 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
117 queried_domain = ccache.principal.realm['data'].decode('utf-8')
118 else:
119 try:
120 queried_domain = self._get_netfqdn()
121 except SessionError as e:
122 self._logger.critical(e)
123 sys.exit(-1)
68124 self._queried_domain = queried_domain
69125
70126 base_dn = str()
85141 # base_dn is no longer used within `_create_ldap_connection()`, but I don't want to break
86142 # the function call. So we store it in an attriute and use it in `_ldap_search()`
87143 self._base_dn = base_dn
88
144
89145 # Format the username and the domain
90146 # ldap3 seems not compatible with USER@DOMAIN format
91 user = '{}\\{}'.format(self._domain, self._user)
147 if self._do_kerberos:
148 user = '{}@{}'.format(self._user, self._domain.upper())
149 else:
150 user = '{}\\{}'.format(self._domain, self._user)
92151
93152 # Call custom formatters for several AD attributes
94153 formatter = {'userAccountControl': fmt.format_useraccountcontrol,
98157 'msDS-MaximumPasswordAge': format_ad_timedelta,
99158 'msDS-MinimumPasswordAge': format_ad_timedelta,
100159 'msDS-LockoutDuration': format_ad_timedelta,
101 'msDS-LockoutObservationWindow': format_ad_timedelta}
102
103 # Choose between password or pth
104 if self._lmhash and self._nthash:
105 lm_nt_hash = '{}:{}'.format(self._lmhash, self._nthash)
106
107 ldap_server = ldap3.Server('ldap://{}'.format(self._domain_controller),
108 formatter=formatter)
109 ldap_connection = ldap3.Connection(ldap_server, user, lm_nt_hash,
110 authentication=ldap3.NTLM, raise_exceptions=True)
111
160 'msDS-LockoutObservationWindow': format_ad_timedelta,
161 'msDS-GroupMSAMembership': fmt.format_groupmsamembership,
162 'msDS-ManagedPassword': fmt.format_managedpassword}
163
164 if self._do_tls:
165 ldap_scheme = 'ldaps'
166 self._logger.debug('LDAPS connection forced')
167 else:
168 ldap_scheme = 'ldap'
169 ldap_server = ldap3.Server('{}://{}'.format(ldap_scheme, self._domain_controller), formatter=formatter)
170 ldap_connection_kwargs = {'user': user, 'raise_exceptions': True}
171
172 # We build the authentication arguments depending on auth mode
173 if self._do_kerberos:
174 self._logger.debug('LDAP authentication with Keberos')
175 ldap_connection_kwargs['authentication'] = ldap3.SASL
176 ldap_connection_kwargs['sasl_mechanism'] = ldap3.KERBEROS
177
178 # Verifying if we have the correct TGS/TGT to interrogate the LDAP server
179 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
180 principal = 'ldap/{}@{}'.format(self._domain_controller.lower(), self._queried_domain.upper())
181
182 # We look for the TGS with the right SPN
183 creds = ccache.getCredential(principal, anySPN=False)
184 if creds:
185 self._logger.debug('TGS found in KRB5CCNAME file')
186 if creds['server'].prettyPrint().lower() != creds['server'].prettyPrint():
187 self._logger.debug('SPN not in lowercase, patching SPN')
188 new_creds = self._patch_spn(creds, principal)
189 # We build a new CCache with the new ticket
190 ccache.credentials.append(new_creds)
191 temp_ccache = tempfile.NamedTemporaryFile()
192 ccache.saveFile(temp_ccache.name)
193 cred_store = {'ccache': 'FILE:{}'.format(temp_ccache.name)}
194 else:
195 cred_store = dict()
196 else:
197 self._logger.debug('TGS not found in KRB5CCNAME, looking for '
198 'TGS with alternative SPN')
199 # If we don't find it, we search for any SPN
200 creds = ccache.getCredential(principal, anySPN=True)
201 if creds:
202 # If we find one, we build a custom TGS
203 self._logger.debug('Alternative TGS found, patching SPN')
204 new_creds = self._patch_spn(creds, principal)
205 # We build a new CCache with the new ticket
206 ccache.credentials.append(new_creds)
207 temp_ccache = tempfile.NamedTemporaryFile()
208 ccache.saveFile(temp_ccache.name)
209 cred_store = {'ccache': 'FILE:{}'.format(temp_ccache.name)}
210 else:
211 # If we don't find any, we hope for the best (TGT in cache)
212 self._logger.debug('Alternative TGS not found, using KRB5CCNAME as is '
213 'while hoping it contains a TGT')
214 cred_store = dict()
215 ldap_connection_kwargs['cred_store'] = cred_store
216 self._logger.debug('LDAP binding parameters: server = {0} / user = {1} '
217 '/ Kerberos auth'.format(self._domain_controller, user))
218 else:
219 self._logger.debug('LDAP authentication with NTLM')
220 ldap_connection_kwargs['authentication'] = ldap3.NTLM
221 if self._lmhash and self._nthash:
222 ldap_connection_kwargs['password'] = '{}:{}'.format(self._lmhash, self._nthash)
223 self._logger.debug('LDAP binding parameters: server = {0} / user = {1} '
224 '/ hash = {2}'.format(self._domain_controller, user, ldap_connection_kwargs['password']))
225 else:
226 ldap_connection_kwargs['password'] = self._password
227 self._logger.debug('LDAP binding parameters: server = {0} / user = {1} '
228 '/ password = {2}'.format(self._domain_controller, user, ldap_connection_kwargs['password']))
229
230 try:
231 ldap_connection = ldap3.Connection(ldap_server, **ldap_connection_kwargs)
112232 try:
113233 ldap_connection.bind()
114 except ldap3.core.exceptions.LDAPStrongerAuthRequiredResult:
115 # We need to try SSL (pth version)
116 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller),
117 formatter=formatter)
118 ldap_connection = ldap3.Connection(ldap_server, user, lm_nt_hash,
119 authentication=ldap3.NTLM, raise_exceptions=True)
120
121 ldap_connection.bind()
122
123 else:
124 ldap_server = ldap3.Server('ldap://{}'.format(self._domain_controller),
125 formatter=formatter)
126 ldap_connection = ldap3.Connection(ldap_server, user, self._password,
127 authentication=ldap3.NTLM, raise_exceptions=True)
128
234 except ldap3.core.exceptions.LDAPSocketOpenError as e:
235 self._logger.critical(e)
236 if self._do_tls:
237 self._logger.critical('TLS negociation failed, this error is mostly due to your host '
238 'not supporting SHA1 as signing algorithm for certificates')
239 sys.exit(-1)
240 except ldap3.core.exceptions.LDAPStrongerAuthRequiredResult:
241 # We need to try TLS
242 self._logger.warning('Server returns LDAPStrongerAuthRequiredResult, falling back to LDAPS')
243 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller), formatter=formatter)
244 ldap_connection = ldap3.Connection(ldap_server, **ldap_connection_kwargs)
129245 try:
130246 ldap_connection.bind()
131 except ldap3.core.exceptions.LDAPStrongerAuthRequiredResult:
132 # We nedd to try SSL (password version)
133 ldap_server = ldap3.Server('ldaps://{}'.format(self._domain_controller),
134 formatter=formatter)
135 ldap_connection = ldap3.Connection(ldap_server, user, self._password,
136 authentication=ldap3.NTLM, raise_exceptions=True)
137
138 ldap_connection.bind()
247 except ldap3.core.exceptions.LDAPSocketOpenError as e:
248 self._logger.critical(e)
249 self._logger.critical('TLS negociation failed, this error is mostly due to your host '
250 'not supporting SHA1 as signing algorithm for certificates')
251 sys.exit(-1)
139252
140253 self._ldap_connection = ldap_connection
141254
142255 def _ldap_search(self, search_filter, class_result, attributes=list(), controls=list()):
143256 results = list()
144
145 # if no attribute name specified, we return all attributes
257
258 # if no attribute name specified, we return all attributes
146259 if not attributes:
147 attributes = ldap3.ALL_ATTRIBUTES
148
149 try:
150 # Microsoft Active Directory set an hard limit of 1000 entries returned by any search
151 search_results=self._ldap_connection.extend.standard.paged_search(search_base=self._base_dn,
152 search_filter=search_filter, attributes=attributes,
153 controls=controls, paged_size=1000, generator=True)
154 # TODO: for debug only
155 except Exception as e:
156 import sys
157 print('Except: ', sys.exc_info()[0])
158
159 # Skip searchResRef
160 for result in search_results:
161 if result['type'] != 'searchResEntry':
162 continue
163 results.append(class_result(result['attributes']))
260 attributes = ldap3.ALL_ATTRIBUTES
261
262 self._logger.debug('search_base = {0} / search_filter = {1} / attributes = {2}'.format(self._base_dn,
263 search_filter,
264 attributes))
265
266 # Microsoft Active Directory set an hard limit of 1000 entries returned by any search
267 search_results=self._ldap_connection.extend.standard.paged_search(search_base=self._base_dn,
268 search_filter=search_filter, attributes=attributes,
269 controls=controls, paged_size=1000, generator=True)
270
271 try:
272 # Skip searchResRef
273 for result in search_results:
274 if result['type'] != 'searchResEntry':
275 continue
276 results.append(class_result(result['attributes']))
277
278 except ldap3.core.exceptions.LDAPAttributeError as e:
279 self._logger.critical(e)
280 sys.exit(-1)
281
282 if not results:
283 self._logger.debug('Query returned an empty result')
164284
165285 return results
166286
190310 try:
191311 self._ldap_connection.unbind()
192312 except AttributeError:
313 self._logger.warning('Error when unbinding')
193314 pass
194315 self._ldap_connection = None
195316
196317 class RPCRequester():
197318 def __init__(self, target_computer, domain=str(), user=(), password=str(),
198 lmhash=str(), nthash=str()):
319 lmhash=str(), nthash=str(), do_kerberos=False):
199320 self._target_computer = target_computer
200321 self._domain = domain
201322 self._user = user
202323 self._password = password
203324 self._lmhash = lmhash
204325 self._nthash = nthash
326 self._do_kerberos = do_kerberos
205327 self._pipe = None
206328 self._rpc_connection = None
207329 self._dcom = None
208330 self._wmi_connection = None
331
332 logger = logging.getLogger('pywerview_main_logger.RPCRequester')
333 self._logger = logger
209334
210335 def _create_rpc_connection(self, pipe):
211336 # Here we build the DCE/RPC connection
230355 rpctransport = transport.SMBTransport(self._target_computer, 445, self._pipe,
231356 username=self._user, password=self._password,
232357 domain=self._domain, lmhash=self._lmhash,
233 nthash=self._nthash)
358 nthash=self._nthash, doKerberos=self._do_kerberos)
234359
235360 rpctransport.set_connect_timeout(10)
236361 dce = rpctransport.get_dce_rpc()
240365
241366 try:
242367 dce.connect()
243 except socket.error:
368 except Exception as e:
369 self._logger.critical('Error when creating RPC connection')
370 self._logger.critical(e)
244371 self._rpc_connection = None
245372 else:
246373 dce.bind(binding_strings[self._pipe[1:]])
249376 def _create_wmi_connection(self, namespace='root\\cimv2'):
250377 try:
251378 self._dcom = DCOMConnection(self._target_computer, self._user, self._password,
252 self._domain, self._lmhash, self._nthash)
253 except DCERPCException:
379 self._domain, self._lmhash, self._nthash, doKerberos=self._do_kerberos)
380 except Exception as e:
381 self._logger.critical('Error when creating WMI connection')
382 self._logger.critical(e)
254383 self._dcom = None
255384 else:
256385 i_interface = self._dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
301430
302431 class LDAPRPCRequester(LDAPRequester, RPCRequester):
303432 def __init__(self, target_computer, domain=str(), user=(), password=str(),
304 lmhash=str(), nthash=str(), domain_controller=str()):
433 lmhash=str(), nthash=str(), do_kerberos=False, do_tls=False,
434 domain_controller=str()):
305435 # If no domain controller was given, we assume that the user wants to
306436 # target a domain controller to perform LDAP requests against
307437 if not domain_controller:
308438 domain_controller = target_computer
309439 LDAPRequester.__init__(self, domain_controller, domain, user, password,
310 lmhash, nthash)
440 lmhash, nthash, do_kerberos, do_tls)
311441 RPCRequester.__init__(self, target_computer, domain, user, password,
312 lmhash, nthash)
442 lmhash, nthash, do_kerberos)
443
444 logger = logging.getLogger('pywerview_main_logger.LDAPRPCRequester')
445 self._logger = logger
446
313447 def __enter__(self):
314448 try:
315449 LDAPRequester.__enter__(self)
1212 # You should have received a copy of the GNU General Public License
1313 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1414
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
15 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1616
1717 from multiprocessing import Process, Pipe
1818
2121 import pywerview.objects.rpcobjects as rpcobj
2222
2323 class HunterWorker(Process):
24 def __init__(self, pipe, domain, user, password, lmhash, nthash):
24 def __init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos):
2525 Process.__init__(self)
2626 self._pipe = pipe
2727 self._domain = domain
2929 self._password = password
3030 self._lmhash = lmhash
3131 self._nthash = nthash
32 self._do_kerberos = do_kerberos
3233
3334 def terminate(self):
3435 self._pipe.close()
4142 self._pipe.send(result)
4243
4344 class UserHunterWorker(HunterWorker):
44 def __init__(self, pipe, domain, user, password, lmhash, nthash, foreign_users,
45 stealth, target_users, domain_short_name, check_access):
46 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash)
45 def __init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos,
46 foreign_users, stealth, target_users, domain_short_name, check_access):
47 HunterWorker.__init__(self, pipe, domain, user, password, lmhash,
48 nthash, do_kerberos)
4749 self._foreign_users = foreign_users
4850 self._stealth = stealth
4951 self._target_users = target_users
5759 # First, we get every distant session on the target computer
5860 distant_sessions = list()
5961 with NetRequester(target_computer, self._domain, self._user, self._password,
60 self._lmhash, self._nthash) as net_requester:
62 self._lmhash, self._nthash, self._do_kerberos) as net_requester:
6163 if not self._foreign_users:
6264 distant_sessions += net_requester.get_netsession()
6365 if not self._stealth:
98100
99101 if self._check_access:
100102 with Misc(target_computer, self._domain, self._user, self._password,
101 self._lmhash, self._nthash) as misc_requester:
103 self._lmhash, self._nthash, self._do_kerberos) as misc_requester:
102104 attributes['localadmin'] = misc_requester.invoke_checklocaladminaccess()
103105 else:
104106 attributes['localadmin'] = str()
108110 return results
109111
110112 class ProcessHunterWorker(HunterWorker):
111 def __init__(self, pipe, domain, user, password, lmhash, nthash, process_name,
112 target_users):
113 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash)
113 def __init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos,
114 process_name, target_users):
115 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos)
114116 self._process_name = process_name
115117 self._target_users = target_users
116118
119121
120122 distant_processes = list()
121123 with NetRequester(target_computer, self._domain, self._user, self._password,
122 self._lmhash, self._nthash) as net_requester:
124 self._lmhash, self._nthash, self._do_kerberos) as net_requester:
123125 distant_processes = net_requester.get_netprocess()
124126
125127 for process in distant_processes:
135137 return results
136138
137139 class EventHunterWorker(HunterWorker):
138 def __init__(self, pipe, domain, user, password, lmhash, nthash, search_days,
139 target_users):
140 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash)
140 def __init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos,
141 search_days, target_users):
142 HunterWorker.__init__(self, pipe, domain, user, password, lmhash, nthash, do_kerberos)
141143 self._target_users = target_users
142144 self._search_days = search_days
143145
146148
147149 distant_processes = list()
148150 with NetRequester(target_computer, self._domain, self._user, self._password,
149 self._lmhash, self._nthash) as net_requester:
151 self._lmhash, self._nthash, self._do_kerberos) as net_requester:
150152 distant_events = net_requester.get_userevent(date_start=self._search_days)
151153
152154 for event in distant_events:
156158 results.append(event)
157159
158160 return results
161
1414 # You should have received a copy of the GNU General Public License
1515 # along with PywerView. If not, see <http://www.gnu.org/licenses/>.
1616
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2021
17 # Yannick Méheut [yannick (at) meheut (dot) org] - Copyright © 2022
1818
1919 from pywerview.cli.main import main
2020
00 impacket>=0.9.22
11 bs4
22 lxml
3 pyasn1
4 ldap3>=2.8.1
5 gssapi
00 [metadata]
1 description-file = README.md
1 description_file = README.md
44 long_description = open('README.md').read()
55
66 setup(name='pywerview',
7 version='0.3.3',
7 version='0.4.0',
88 description='A Python port of PowerSploit\'s PowerView',
99 long_description=long_description,
1010 long_description_content_type='text/markdown',
2626 install_requires=[
2727 'impacket>=0.9.22',
2828 'bs4',
29 'lxml'
29 'lxml',
30 'pyasn1',
31 'ldap3>=2.8.1',
32 'gssapi'
3033 ],
3134 entry_points = {
3235 'console_scripts': ['pywerview=pywerview.cli.main:main'],