Skip to content

Commit bd84002

Browse files
viardantcddmp
authored andcommitted
Improves RID Cycling performance (SID lookups batching)
1 parent e120593 commit bd84002

File tree

2 files changed

+39
-32
lines changed

2 files changed

+39
-32
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ I made it for educational purposes for myself and to overcome issues with enum4l
2929
## Differences
3030
Some things are implemented differently compared to the original enum4linux. These are the important differences:
3131
- RID cycling is not part of the default enumeration (```-A```) but can be enabled with ```-R```
32+
- RID cycling can be achieved faster, by grouping multiple SID lookups in the same rpcclient call
3233
- parameter naming is slightly different (e.g. ```-A``` instead of ```-a```)
3334

3435
## Credits
@@ -97,7 +98,7 @@ options:
9798
-O Get OS information via RPC
9899
-L Get additional domain info via LDAP/LDAPS (for DCs only)
99100
-I Get printer information via RPC
100-
-R Enumerate users via RID cycling
101+
-R Enumerate users via RID cycling. Optionally, specifies lookup request size.
101102
-N Do an NetBIOS names lookup (similar to nbtstat) and try to retrieve workgroup from output
102103
-w DOMAIN Specify workgroup/domain manually (usually found automatically)
103104
-u USER Specify username to use (default "")

enum4linux-ng.py

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,8 +2077,9 @@ class RidCycleParams:
20772077
"groups", "machines" and/or a domain sid. By default enumerated_input is an empty dict
20782078
and will be filled up during the tool run.
20792079
'''
2080-
def __init__(self, rid_ranges, known_usernames):
2080+
def __init__(self, rid_ranges, batch_size, known_usernames):
20812081
self.rid_ranges = rid_ranges
2082+
self.batch_size = batch_size
20822083
self.known_usernames = known_usernames
20832084
self.enumerated_input = {}
20842085

@@ -2127,7 +2128,7 @@ def run(self):
21272128
# Run...
21282129
for sid in sids_list:
21292130
print_info(f"Trying SID {sid}")
2130-
rid_cycler = self.rid_cycle(sid, self.cycle_params.rid_ranges)
2131+
rid_cycler = self.rid_cycle(sid, self.cycle_params.rid_ranges, self.cycle_params.batch_size)
21312132
for result in rid_cycler:
21322133
# We need the top level key to find out whether we got users, groups, machines or the domain_sid...
21332134
top_level_key = list(result.retval.keys())[0]
@@ -2216,41 +2217,45 @@ def enum_sids(self, users):
22162217
return Result(sids, f"Found {len(sids)} SID(s)")
22172218
return Result(None, "Could not get any SIDs")
22182219

2219-
def rid_cycle(self, sid, rid_ranges):
2220+
def rid_cycle(self, sid, rid_ranges, batch_size):
22202221
'''
22212222
Takes a SID as first parameter well as list of RID ranges (as tuples) as second parameter and does RID cycling.
22222223
'''
22232224
for rid_range in rid_ranges:
22242225
(start_rid, end_rid) = rid_range
22252226

2226-
for rid in range(start_rid, end_rid+1):
2227+
for rid_base in range(start_rid, end_rid+1, batch_size):
2228+
target_sids = " ".join(list(map(lambda x: f'{sid}-{x}', range(rid_base, rid_base+batch_size))))
22272229
#FIXME: Could we get rid of error_filter=False?
2228-
result = SambaRpcclient(['lookupsids', f'{sid}-{rid}'], self.target, self.creds).run(log='RID Cycling', error_filter=False)
2230+
result = SambaRpcclient(['lookupsids', target_sids], self.target, self.creds).run(log='RID Cycling', error_filter=False)
22292231

2230-
# Example: S-1-5-80-3139157870-2983391045-3678747466-658725712-1004 *unknown*\*unknown* (8)
2231-
match = re.search(r"(S-\d+-\d+-\d+-[\d-]+\s+(.*)\s+[^\)]+\))", result.retmsg)
2232-
if match:
2233-
sid_and_user = match.group(1)
2234-
entry = match.group(2)
2235-
2236-
# Samba servers sometimes claim to have user accounts
2237-
# with the same name as the UID/RID. We don't report these.
2238-
if re.search(r"-(\d+) .*\\\1 \(", sid_and_user):
2239-
continue
2240-
2241-
# "(1)" = User, "(2)" = Domain Group,"(3)" = Domain SID,"(4)" = Local Group
2242-
# "(5)" = Well-known group, "(6)" = Deleted account, "(7)" = Invalid account
2243-
# "(8)" = Unknown, "(9)" = Machine/Computer account
2244-
if "(1)" in sid_and_user:
2245-
yield Result({"users":{str(rid):{"username":entry}}}, f"Found user '{entry}' (RID {rid})")
2246-
elif "(2)" in sid_and_user:
2247-
yield Result({"groups":{str(rid):{"groupname":entry, "type":"domain"}}}, f"Found domain group '{entry}' (RID {rid})")
2248-
elif "(3)" in sid_and_user:
2249-
yield Result({"domain_sid":f"{sid}-{rid}"}, f"Found domain SID {sid}-{rid}")
2250-
elif "(4)" in sid_and_user:
2251-
yield Result({"groups":{str(rid):{"groupname":entry, "type":"builtin"}}}, f"Found builtin group '{entry}' (RID {rid})")
2252-
elif "(9)" in sid_and_user:
2253-
yield Result({"machines":{str(rid):{"machine":entry}}}, f"Found machine '{entry}' (RID {rid})")
2232+
split_result = result.retmsg.splitlines()
2233+
for line in range(len(split_result)):
2234+
# Example: S-1-5-80-3139157870-2983391045-3678747466-658725712-1004 *unknown*\*unknown* (8)
2235+
match = re.search(r"(S-\d+-\d+-\d+-[\d-]+\s+(.*)\s+[^\)]+\))", split_result[line])
2236+
if match:
2237+
sid_and_user = match.group(1)
2238+
entry = match.group(2)
2239+
rid = rid_base + line
2240+
2241+
# Samba servers sometimes claim to have user accounts
2242+
# with the same name as the UID/RID. We don't report these.
2243+
if re.search(r"-(\d+) .*\\\1 \(", sid_and_user):
2244+
continue
2245+
2246+
# "(1)" = User, "(2)" = Domain Group,"(3)" = Domain SID,"(4)" = Local Group
2247+
# "(5)" = Well-known group, "(6)" = Deleted account, "(7)" = Invalid account
2248+
# "(8)" = Unknown, "(9)" = Machine/Computer account
2249+
if "(1)" in sid_and_user:
2250+
yield Result({"users":{str(rid):{"username":entry}}}, f"Found user '{entry}' (RID {rid})")
2251+
elif "(2)" in sid_and_user:
2252+
yield Result({"groups":{str(rid):{"groupname":entry, "type":"domain"}}}, f"Found domain group '{entry}' (RID {rid})")
2253+
elif "(3)" in sid_and_user:
2254+
yield Result({"domain_sid":f"{sid}-{rid}"}, f"Found domain SID {sid}-{rid}")
2255+
elif "(4)" in sid_and_user:
2256+
yield Result({"groups":{str(rid):{"groupname":entry, "type":"builtin"}}}, f"Found builtin group '{entry}' (RID {rid})")
2257+
elif "(9)" in sid_and_user:
2258+
yield Result({"machines":{str(rid):{"machine":entry}}}, f"Found machine '{entry}' (RID {rid})")
22542259

22552260
### Shares Enumeration
22562261

@@ -2770,7 +2775,7 @@ def run(self):
27702775
# RID Cycling - init parameters
27712776
if self.args.R:
27722777
rid_ranges = self.prepare_rid_ranges()
2773-
self.cycle_params = RidCycleParams(rid_ranges, self.args.users)
2778+
self.cycle_params = RidCycleParams(rid_ranges, self.args.R, self.args.users)
27742779

27752780
# Shares Brute Force - init parameters
27762781
if self.args.shares_file:
@@ -2784,6 +2789,7 @@ def run(self):
27842789
print_info(f"Timeout .......... {self.target.timeout} second(s)")
27852790
if self.args.R:
27862791
print_info(f"RID Range(s) ..... {self.args.ranges}")
2792+
print_info(f"RID Req Size ..... {self.args.R}")
27872793
print_info(f"Known Usernames .. '{self.args.users}'")
27882794

27892795
# The enumeration starts with a service scan. Currently this scans for
@@ -3210,7 +3216,7 @@ def check_arguments():
32103216
parser.add_argument("-O", action="store_true", help="Get OS information via RPC")
32113217
parser.add_argument("-L", action="store_true", help="Get additional domain info via LDAP/LDAPS (for DCs only)")
32123218
parser.add_argument("-I", action="store_true", help="Get printer information via RPC")
3213-
parser.add_argument("-R", action="store_true", help="Enumerate users via RID cycling")
3219+
parser.add_argument("-R", default=0, const=1, nargs='?', type=int, help="Enumerate users via RID cycling. Optionally, specifies lookup request size.")
32143220
parser.add_argument("-N", action="store_true", help="Do an NetBIOS names lookup (similar to nbtstat) and try to retrieve workgroup from output")
32153221
parser.add_argument("-w", dest="domain", default='', type=str, help="Specify workgroup/domain manually (usually found automatically)")
32163222
parser.add_argument("-u", dest="user", default='', type=str, help="Specify username to use (default \"\")")

0 commit comments

Comments
 (0)