-
Notifications
You must be signed in to change notification settings - Fork 13
/
pml.py
executable file
·148 lines (123 loc) · 5.62 KB
/
pml.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/usr/bin/env python
import tempfile
import argparse
import time
import pprint
import re
import sys
import os
import sqlite3
def process_input(sql,lines):
main_count = 0
num_lines = len(lines)
current = {}
for line in lines:
main_count += 1
percentage_count = "{0:.0f}%".format(float(main_count)/num_lines * 100)
sys.stdout.write("\r Processing line "+str(main_count)+"/"+str(num_lines)+" ("+percentage_count+")")
sys.stdout.flush()
val = re.match('^\s*\*\s+Username\s+:\s+(.+)\s*$', line.strip())
if val != None:
insert_into_db(sql,current)
current = {}
current['Username'] = val.group(1).strip()
continue
val = re.match('^\s*\*\s+(Domain|NTLM|SHA1|Password)\s+:\s+(.+)\s*$', line.strip())
if val != None:
current[val.group(1).strip()] = unicode(val.group(2), errors='ignore')
# Build the SQL database schema
def build_db_schema(sql):
c = sql.cursor()
# Create the tables
c.execute('''CREATE TABLE creds
('domain' TEXT, 'username' TEXT, 'password' TEXT, 'ntlm' TEXT, 'sha1' TEXT, PRIMARY KEY ('domain','username','password','ntlm','sha1'))
''')
c.execute('''CREATE VIEW view_usercreds AS select distinct domain,username,password from creds where password != '' and username != '' and substr(username,-1) != '$' ''')
sql.commit()
return
def insert_into_db(sql,current):
c = sql.cursor()
fields = ['Domain','Username','NTLM','SHA1','Password']
for f in fields:
if f in current:
if current[f] == '(null)':
current[f] = ''
else:
current[f] = ''
if current['Username'] != '' and (current['Password'] != '' or current['NTLM'] != ''):
c.execute('replace into creds (domain,username,password,ntlm,sha1) VALUES (?,?,?,?,?)',
[current['Domain'],current['Username'],current['Password'],current['NTLM'],current['SHA1']])
sql.commit()
return
def banner():
sys.stdout.write("\n")
sys.stdout.write(" .mMMMMMm. MMm M WW W WW RRRRR\n")
sys.stdout.write(" mMMMMMMMMMMM. MM MM W W W R R\n")
sys.stdout.write(" /MMMM- -MM. MM MM W W W R R\n")
sys.stdout.write(" /MMM. _ \/ ^ M M M M W W W W RRRR\n")
sys.stdout.write(" |M. aRRr /W| M M M M W W W W R R\n")
sys.stdout.write(" \/ .. ^^^ wWWW| M M M W W R R\n")
sys.stdout.write(" /WW\. .wWWWW/ M M M W W R R\n")
sys.stdout.write(" |WWWWWWWWWWW/\n")
sys.stdout.write(" .WWWWWW. Quick & Dirty Mimikatz Log Parser\n")
sys.stdout.write(" stuart.morgan@mwrinfosecurity.com | @ukstufus\n")
sys.stdout.write("\n")
sys.stdout.flush()
def create_db(filename):
build = False
if filename == None:
# If the filename was not specified, create a new file with a temporary name
# and build the schema.
db_file = tempfile.NamedTemporaryFile(delete=False)
db_filename = db_file.name+'.'+time.strftime('%Y%m%d%H%M%S')+'.mimikatz.db'
db_file.close()
build = True
else:
# The name was provided.
db_filename = filename
if not os.path.isfile(db_filename):
# If the name does not exist, rebuild the schema
build = True
sql = sqlite3.connect(db_filename)
if build == True:
build_db_schema(sql)
return sql, db_filename
def display_totals(sql):
c = sql.cursor()
c.execute("select count(*) from creds")
print " Sets of credentials: "+str(c.fetchone()[0])
c.execute("select count(distinct username) from view_usercreds")
print " Unique 'user' usernames: "+str(c.fetchone()[0])
c.execute("select count(distinct password) from view_usercreds")
print " Unique 'user' passwords: "+str(c.fetchone()[0])
return
if __name__ == '__main__':
banner()
parser = argparse.ArgumentParser(description='Basic parser for mimikatz \'logonPasswords\' log files.')
parser.add_argument('-d', '--database', action='store', help='The location of the SQLite database. If this is not specified, a new one will be created. If it is specified and the file exists, it will be opened and used. If a name is specified that does not exist, it will be created.')
parser.add_argument('-i', '--input', action='store', required=True, help='The mimikatz log file to read. Specify \'-\' to read from STDIN.')
args = vars(parser.parse_args())
if 'database' in args and args['database'] != None:
if os.path.isfile(args['database']):
sql = sqlite3.connect(args['database'])
sys.stdout.write("Opening database: "+args['database']+"\n")
else:
sql, db_filename = create_db(args['database'])
sys.stdout.write("New database created: "+db_filename+"\n")
else:
sql, db_filename = create_db(None)
sys.stdout.write("No database file specified; new database created: "+db_filename+"\n")
if 'input' in args and args['input'] != None and os.path.isfile(args['input']):
sys.stdout.write("Reading from: "+args['input']+"\n")
f = open(args['input'], 'r')
lines = f.readlines()
f.close()
elif args['input'] == '-':
sys.stdout.write("Reading from STDIN")
lines = sys.stdin.readlines()
sys.stdout.write("\n")
process_input(sql,lines)
sys.stdout.write("\n\n")
display_totals(sql)
sql.close()
sys.exit(0)