-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwrapper.py
More file actions
executable file
·223 lines (181 loc) · 7.04 KB
/
wrapper.py
File metadata and controls
executable file
·223 lines (181 loc) · 7.04 KB
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"""
Houses the wrapper for wmi-client.
There are a handful of injection vulnerabilities in this, so don't expose it
directly to end-users.
"""
import csv
import sh
import re
from StringIO import StringIO
def getEventDict(estr):
fields = estr.split("\x01")
return {
'Category' : fields[0],
'CategoryString' : fields[1],
'ComputerName' : fields[2],
'Data' : fields[3],
'EventCode' : fields[4],
'EventIdentifier' : fields[5],
'EventType' : fields[6],
'InsertionStrings' : fields[7],
'Logfile' : fields[8],
#'Message' : fields[9],
'RecordNumber' : fields[10],
'SourceName' : fields[11],
'TimeGenerated' : fields[12],
'TimeWritten' : fields[13],
'Type' : fields[14],
'User' : fields[15]
}
class WmiClientWrapper(object):
"""
Wrap wmi-client. Creating an instance of the wrapper will make a security
context through which all future queries will be executed. It's basically
just a convenient way to remember the username, password and host.
There are a handful of injection vulnerabilities in this, so don't expose
it directly to end-users.
"""
def __init__(self, username="Administrator", password=None, host=None, delimiter="\01"):
assert username
assert password
assert host # assume host is up
# store the credentials for later
self.username = username
self.password = password
self.host = host
self.delimiter = delimiter
def _make_credential_args(self):
"""
Makes credentials that get passed to wmic. This assembles a list of
arguments.
"""
arguments = []
# the format is user%pass
# NOTE: this is an injection vulnerability
userpass = "--user={username}%{password}".format(
username=self.username,
password=self.password,
)
arguments.append(userpass)
# the format for ip addresses and host names is //
hostaddr = "//{host}".format(host=self.host)
arguments.append(hostaddr)
return arguments
def _setup_params(self):
"""
Makes extra configuration that gets passed to wmic.
"""
return ["--delimiter={delimiter}".format(delimiter=self.delimiter)]
def _construct_query(self, klass):
"""
Makes up a WMI query based on a given class.
"""
# NOTE: this is an injection vulnerability
queryx = "SELECT * FROM {klass}".format(klass=klass)
return queryx
def query(self, klass):
"""
Executes a query using the wmi-client command.
"""
# i don't want to have to repeat the -U stuff
credentials = self._make_credential_args()
# Let's make the query construction independent, but also if there's a
# space then it's probably just a regular query.
if " " not in klass:
queryx = self._construct_query(klass)
else:
queryx = klass
# and these are just configuration
setup = self._setup_params()
# construct the arguments to wmic
arguments = setup + credentials + [queryx]
# execute the command
output = sh.wmic(*arguments)
# just to be sure? sh is weird sometimes.
output = str(output)
# and now parse the output
return WmiClientWrapper._parse_wmic_output(output, delimiter=self.delimiter)
@classmethod
def _parse_wmic_output(cls, output, delimiter="\01"):
"""
Parses output from the wmic command and returns json.
"""
# remove newlines and whitespace from the beginning and end
output = output.strip()
# Quick parser hack- make sure that the initial file or section is also
# counted in the upcoming split.
if output[:7] == "CLASS: ":
output = "\n" + output
# There might be multiple files in the output. Track how many there
# should be so that errors can be raised later if something is
# inconsistent.
expected_sections_count = output.count("\nCLASS: ")
# Split up the file into individual sections. Each one corresponds to a
# separate csv file.
sections = output.split("\nCLASS: ")
# The split causes an empty string as the first member of the list and
# it should be removed because it's junk.
if sections[0] == "":
sections = sections[1:]
assert len(sections) is expected_sections_count
items = []
for section in sections:
# remove the first line because it has the query class
strio = StringIO(section)
strings = strio.read().splitlines()[2:]
#section = "@@@".join(section.splitlines()[2:])
event_parts = []
for string in strings:
pstr = string.strip()
if not pstr:
continue
if (pstr[:5]).isdigit():
if event_parts:
event = ' '.join(event_parts)
ev = getEventDict(event)
items.append(ev)
event_parts = []
event_parts.append(pstr)
else:
event_parts.append(pstr)
#moredata = list(csv.DictReader(strio, delimiter=delimiter))
#print 'MOREDATA', moredata
# walk the dictionaries!
return WmiClientWrapper._fix_dictionary_output(items)
@classmethod
def _fix_dictionary_output(cls, incoming):
"""
The dictionary doesn't exactly match the traditional python-wmi output.
For example, there's "True" instead of True. Integer values are also
quoted. Values that should be "None" are "(null)".
This can be fixed by walking the tree.
The Windows API is able to return the types, but here we're just
guessing randomly. But guessing should work in most cases. There are
some instances where a value might happen to be an integer but has
other situations where it's a string. In general, the rule of thumb
should be to cast to whatever you actually need, instead of hoping that
the output will always be an integer or will always be a string..
"""
if isinstance(incoming, list):
output = []
for each in incoming:
output.append(cls._fix_dictionary_output(each))
elif isinstance(incoming, dict):
output = dict()
for (key, value) in incoming.items():
if key == "TimeGenerated":
output[key] = value.split('.')[0]
elif value == "(null)":
output[key] = None
elif value == "True":
output[key] = True
elif value == "False":
output[key] = False
elif isinstance(value, str) and len(value) > 1 and value[0] == "(" and value[-1] == ")":
# convert to a list with a single entry
output[key] = [value[1:-1]]
elif isinstance(value, str):
output[key] = value
elif isinstance(value, dict):
output[key] = cls._fix_dictionary_output(value)
return output