├── LICENSE ├── README.md └── ntlmrelayx-prettyloot.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 mpgn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # prettyloot 2 | 3 | Convert the loot directory of ntlmrelayx into an enum4linux like output after you dump the domain info using LDAP ! 4 | 5 | ![image](https://user-images.githubusercontent.com/5891788/73091230-4afb5780-3eda-11ea-913d-20f0f0f0a561.png) 6 | 7 | ### Usage 8 | 9 | ``` 10 | python3 ntlmrelayx-prettyloot.py /tmp/loot 11 | ``` 12 | 13 | ### Dumping domain info using ntlmrelayx without a domain account 14 | 15 | ``` 16 | # screen 1 => ntlmrelayx.py -t ldap://172.16.60.205 --no-da --no-acl -l /tmp/loot 17 | # screen 2 => responder -I eth0 18 | ``` 19 | 20 | ![image](https://user-images.githubusercontent.com/5891788/73091683-3e2b3380-3edb-11ea-84d7-fefb1f605d52.png) 21 | 22 | Credits to [@ditrizna](https://twitter.com/ditrizna/status/1103964505510416384) 23 | -------------------------------------------------------------------------------- /ntlmrelayx-prettyloot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author https://twitter.com/mpgn_x64 4 | # Example to get a loot dir with ntlmrelayx 5 | # screen 1 => ntlmrelayx.py -t ldap://172.16.60.205 --no-da --no-acl -l /tmp/loot 6 | # screen 2 => responder -I eth0 7 | 8 | import json 9 | import os.path 10 | import re 11 | import sys 12 | from time import strftime, gmtime 13 | 14 | def d2b(a): 15 | tbin = [] 16 | while a: 17 | tbin.append(a % 2) 18 | a //= 2 19 | 20 | t2bin = tbin[::-1] 21 | if len(t2bin) != 8: 22 | for x in range(6 - len(t2bin)): 23 | t2bin.insert(0, 0) 24 | return ''.join([str(g) for g in t2bin]) 25 | 26 | def convert(time): 27 | if time == 0: 28 | return "None" 29 | if time == -9223372036854775808: 30 | return "Not Set" 31 | sec = abs(time) // 10000000 32 | days = sec // 86400 33 | sec -= 86400*days 34 | hrs = sec // 3600 35 | sec -= 3600*hrs 36 | mins = sec // 60 37 | sec -= 60*mins 38 | result = "" 39 | if days > 1: 40 | result += "{0} days ".format(days) 41 | elif days == 1: 42 | result += "{0} day ".format(days) 43 | if hrs > 1: 44 | result += "{0} hours ".format(hrs) 45 | elif hrs == 1: 46 | result += "{0} hour ".format(hrs) 47 | if mins > 1: 48 | result += "{0} minutes ".format(mins) 49 | elif mins == 1: 50 | result += "{0} minute ".format(mins) 51 | return result 52 | 53 | def password_complexity(data): 54 | 55 | print(''' 56 | +-----------------------------------------+ 57 | | Password Policy Information | 58 | +-----------------------------------------+ 59 | ''') 60 | 61 | print("[+] Password Info for Domain:", data[0]['attributes']['dc'][0].upper()) 62 | print("\t[+] Minimum password length: ", data[0]['attributes']['instanceType'][0]) 63 | print("\t[+] Password history length:", data[0]['attributes']['pwdHistoryLength'][0]) 64 | 65 | password_properties = d2b(data[0]['attributes']['pwdProperties'][0]) 66 | print("\t[+] Password Complexity Flags:", password_properties) 67 | print("") 68 | print("\t\t[+] Domain Refuse Password Change:", password_properties[0]) 69 | print("\t\t[+] Domain Password Store Cleartext:", password_properties[1]) 70 | print("\t\t[+] Domain Password Lockout Admins:", password_properties[2]) 71 | print("\t\t[+] Domain Password No Clear Change:", password_properties[3]) 72 | print("\t\t[+] Domain Password No Anon Change:", password_properties[4]) 73 | print("\t\t[+] Domain Password Complex:", password_properties[5]) 74 | print("") 75 | print("\t[+] Maximum password age:", convert(data[0]['attributes']['maxPwdAge'][0])) 76 | print("\t[+] Minimum password age:", convert(data[0]['attributes']['minPwdAge'][0])) 77 | print("\t[+] Reset Account Lockout Counter:", convert(data[0]['attributes']['lockoutDuration'][0])) 78 | print("\t[+] Account Lockout Threshold:", data[0]['attributes']['lockoutThreshold'][0]) 79 | print("\t[+] Forced Log off Time:", convert(data[0]['attributes']['forceLogoff'][0])) 80 | 81 | def domain_info(data): 82 | print(''' 83 | +--------------------------------------+ 84 | | Getting Domain Sid For | 85 | +--------------------------------------+ 86 | ''') 87 | print('[+] Domain Name:', data[0]['attributes']['dc'][0]) 88 | print('Domain Sid:', data[0]['attributes']['objectSid'][0]) 89 | print('') 90 | return data[0]['attributes']['dc'][0] 91 | 92 | def user_info(users, dc): 93 | print(''' 94 | +------------------------+ 95 | | Users Infos | 96 | +------------------------+ 97 | ''') 98 | for user in users: 99 | desc = user['attributes'].get('description')[0] if user['attributes'].get('description') else "(null)" 100 | print("Account: " + dc + "\\" + user['attributes']['sAMAccountName'][0] + "\tName: " + user['attributes']['name'][0] + "\tDesc: " + desc) 101 | 102 | print("") 103 | for user in users: 104 | print("user:[" + user['attributes']['sAMAccountName'][0] + "]") 105 | print("") 106 | 107 | def groups_info(groups, dc): 108 | print(''' 109 | +------------------------+import os.path 110 | | Groups Infos | 111 | +------------------------+ 112 | ''') 113 | for group in groups: 114 | print("group:[" + group['attributes']['name'][0] + "]") 115 | 116 | for group in groups: 117 | if group['attributes'].get('member'): 118 | users = re.findall(r"^CN=([\w\s\-\_\{\}\.\$\#]+)", '\n'.join(group['attributes']['member']), re.M) 119 | if users: 120 | print("\n[+] Getting domain group memberships:") 121 | for user in users: 122 | if user == "S-1-5-11": 123 | user = "NT AUTHORITY\Authenticated Users" 124 | elif user == "S-1-5-4": 125 | user = "NT AUTHORITY\INTERACTIVE" 126 | elif user == "S-1-5-17": 127 | user = "NT AUTHORITY\IUSR" 128 | print("Group '" + group['attributes']['name'][0] + "' has member: " + dc + "\\" + user) 129 | 130 | if __name__ == "__main__": 131 | 132 | print(''' 133 | ntlmrelayx-prettyloot convert the loot directory of ntlmrelayx into an enum4linux output ! 134 | Example: ntlmrelayx.py -t ldap://dc-ip --no-da --no-acl -l /tmp/loot3 135 | By @mpgn_x64 136 | ''') 137 | 138 | if len(sys.argv) < 2: 139 | print("[-] Missing argument: ntlmrelayx-prettyloot.py /tmp/lootdir") 140 | sys.exit(0) 141 | if os.path.isfile(sys.argv[1] + '/domain_policy.json') == False: 142 | print("[-] Missing directory: ntlmrelayx-prettyloot.py", sys.argv[1]) 143 | sys.exit(0) 144 | 145 | with open(sys.argv[1] + '/domain_policy.json') as f: 146 | domain = json.load(f) 147 | with open(sys.argv[1] + '/domain_users.json') as f: 148 | users = json.load(f) 149 | with open(sys.argv[1] + '/domain_groups.json') as f: 150 | groups = json.load(f) 151 | 152 | dc = domain_info(domain) 153 | password_complexity(domain) 154 | user_info(users, dc.upper()) 155 | groups_info(groups, dc.upper()) 156 | --------------------------------------------------------------------------------