18
18
19
19
##############################################################################################
20
20
21
- V.2 CHANGELOG
21
+ V.3 CHANGELOG
22
+
23
+ * Finally Fast Forensics edition(tm)!!
24
+ * Improved client side SSL cert checking of server (secure++)
25
+ * Faster than doing a dlldump to you're local disk
26
+ * Better than doing a dlldump since you get something usefull
27
+ * Smarter than doign so since you should have 99/100 of the junk you don't care about out of you're way
22
28
23
29
* Added heaps of colors!
24
30
33
39
+ Block offset that has the modified code
34
40
+ Special dump mode for just modified code
35
41
36
- * Here are the needful dependencies (i.e. requirements.txt);
37
-
38
- colored==1.3.5
39
- gevent==1.2.2
40
- retry==0.9.2
41
- colorama==0.3.7
42
- pycrypto==2.6.1
43
- volatility==2.1
42
+ * Needful dependencies in the txt file
44
43
45
44
To use this with volatility place this .py anywhere, ensure you have volatility working.
46
45
For example the command line below will simply run the invterojithash against the input memory image
70
69
Enjoy!
71
70
################################################################################################
72
71
"""
73
- from gevent .monkey import patch_all
74
- patch_all ()
72
+ from gevent import monkey
73
+ monkey .patch_all ()
74
+
75
+ import urllib3 .contrib .pyopenssl
76
+ urllib3 .contrib .pyopenssl .inject_into_urllib3 ()
75
77
76
78
import volatility .addrspace
77
79
import volatility .commands as commands
78
80
import volatility .utils as utils
79
81
import volatility .win32 .tasks as tasks
80
82
import volatility .win32 .modules as modules
81
83
import os , time , base64 , sys , threading
82
- import json , urllib , urllib2
84
+ import json , urllib , urllib2 , urllib3
83
85
import gevent , struct , retry , traceback , colorama
84
- import ntpath
86
+ import ntpath , certifi
85
87
86
88
from os import environ
87
89
from retry import retry
@@ -155,9 +157,9 @@ class inVteroJitHash(commands.Command):
155
157
#JITHashServer = "http://localhost:7071/api/PageHash/x"
156
158
JITHashServer = "https://pdb2json.azurewebsites.net/api/PageHash/x"
157
159
158
-
159
160
# Tune this if you want to hit the server harder
160
- pool = Pool (16 )
161
+ pool = Pool ()
162
+ greenlits = []
161
163
162
164
total_miss = {}
163
165
null_hash = None
@@ -169,26 +171,28 @@ class inVteroJitHash(commands.Command):
169
171
MissList = []
170
172
TotalProgress = []
171
173
TotBytesValidated = 0
172
- TotBytesValidated = 0
173
174
TotalLastN = 0
174
175
TotPercent = 0.0
175
176
TotalBar = None
176
177
logg = None
177
178
DumpFolder = None
178
-
179
+ headers = {'Content-Type' :'application/json' , 'Accept' :'text/plain' }
180
+ http = urllib3 .PoolManager (maxsize = 512 , block = True , headers = headers , cert_reqs = 'CERT_REQUIRED' , ca_certs = certifi .where ())
179
181
def __init__ (self , config , * args ):
180
182
# no color on Windows yet, this keeps the output from looking insane with all the ANSI
181
183
if os .name == 'nt' :
182
- self .stream = AnsiToWin32 (sys .stdout ).stream
184
+ self .stream = AnsiToWin32 (sys .stdout , convert = True ).stream
183
185
init ()
184
186
#init(convert=True)
185
187
commands .Command .__init__ (self , config , * args )
186
188
config .add_option ('SuperVerbose' , short_option = 's' , help = 'Display per page validation results.' , action = 'store_true' , default = False )
187
189
config .add_option ('ExtraTotals' , short_option = 'x' , help = 'List of all misses per-module.' , action = 'store_true' , default = False )
188
190
config .add_option ('DumpFolder' , short_option = 'D' , help = 'Dump the failed blocks to a specified folder' , default = None )
189
191
config .add_option ('FailFile' , short_option = 'F' , help = 'Output file containing detailed information about unverifiable memory' , default = 'FailedValidation.txt' )
190
-
191
- os .system ('setterm -cursor off' )
192
+ if os .name is not 'nt' :
193
+ os .system ('setterm -cursor off' )
194
+ else :
195
+ os .system ('color 0f' )
192
196
193
197
# This method is a huge bit of code that should of been in volatility
194
198
# Anyhow, NX bit's need to be checked at every layer of the page table.
@@ -203,9 +207,9 @@ def is_nxd(cls, vaddr, addr_space):
203
207
Parameters
204
208
----------
205
209
vaddr : long
206
- A virtual address from IA32PAE or AMD64 compatible address spaces
210
+ A virtual address from IA32PAE or AMD64 compatible address spaces
207
211
addr_space : Addrspace
208
- An instance of the address space that contains our page table
212
+ An instance of the address space that contains our page table
209
213
210
214
Returns
211
215
-------
@@ -292,19 +296,11 @@ def pozt(self, LocalMod):
292
296
rvData = ""
293
297
try :
294
298
data = LocalMod ["json" ]
295
- moduleName = ""
296
-
297
- if data .has_key ("ModuleName" ):
298
- moduleName = data ["ModuleName" ]
299
-
300
- #LocalMod["Ctx"]["bar"].set_postfix_str('{:<}{:<}'.format('recv hashes: ', ntpath.basename(moduleName)))
301
-
302
- headers = {'Content-Type' :'application/json' , 'Accept' :'text/plain' }
303
-
304
299
dataEncoded = json .dumps (data )
305
- req = urllib2 .Request (self .JITHashServer , dataEncoded , headers )
306
- response = urllib2 .urlopen (req )
307
- rvData = response .read ()
300
+ #req = self.http.request('POST', self.JITHashServer, body=dataEncoded)
301
+ req = self .http .urlopen ('POST' , self .JITHashServer , headers = self .headers , body = dataEncoded )
302
+ #response = self.http.urlopen(req)
303
+ #rvData = req.data
308
304
except HTTPError as inst :
309
305
if inst .code == 204 :
310
306
return rvData
@@ -315,8 +311,9 @@ def pozt(self, LocalMod):
315
311
print ("{}{}" .format (fg ("hot_pink_1b" ), x ))
316
312
finally :
317
313
a = AsyncResult ()
318
- a .set (rvData )
314
+ a .set (req . data )
319
315
LocalMod ["resp" ] = a .get (block = True )
316
+ req .release_conn ()
320
317
self .output (LocalMod )
321
318
322
319
return LocalMod
@@ -325,8 +322,9 @@ def pozt(self, LocalMod):
325
322
# the entire execution. The completion routine render_text is for a minimal amount of reporting.
326
323
327
324
def calculate (self ):
328
- self .DumpFolder = (self ._config .DumpFolder or '' )
325
+ self .DumpFolder = (self ._config .DumpFolder or None )
329
326
self .logg = open (self ._config .FailFile , mode = "w+" , buffering = 8192 )
327
+ self .logg .write ("On Windows, use \" type [Filename]\" for best results (Win10) {} JIT hash log file\n " .format (fg ("cornflower_blue" )))
330
328
# get the null hash (at runtime in case a different hash is used etc..)
331
329
null_page = bytearray (4096 )
332
330
self .null_hash = self .HashPage (null_page )
@@ -343,16 +341,16 @@ def calculate(self):
343
341
taskCnt += 1
344
342
345
343
print ("{}{}{} [{}]{}" .format (fg ("chartreuse_1" ), "pdb2json JIT PageHash calls under way... endpoint " , fg ("hot_pink_1b" ), self .JITHashServer , fg ("sky_blue_1" ), attrs = ["bold" ]))
346
- bformat = "[ {elapsed:<}] {l_bar:< }{postfix:< }{bar}| "
347
- self .TotalBar = tqdm (desc = "TotalProgress{} " .format (fg ("light_sky_blue_3a " ), total = taskCnt , position = 0 , mininterval = 0.5 , bar_format = bformat ))
344
+ bformat = "{elapsed} {l_bar}{postfix}{bar}"
345
+ self .TotalBar = tqdm (desc = "{}TotalProgress " .format (fg ("cornflower_blue " ), total = taskCnt , position = 0 , mininterval = 0.5 , bar_format = bformat ))
348
346
# The timer is reset here since were not counting the coldstartup time
349
347
self .StartTime = time .time ()
350
348
for task in tasklist :
351
349
taski += 1
352
350
353
351
proc_as = task .get_process_address_space ()
354
352
mods = []
355
- # Volatility workaround as there is not a consistant interface I know of
353
+ # Volatility workaround as there is not a consistant interface I know of
356
354
# to handle AS the same way for kernel & user
357
355
if task .UniqueProcessId == 4 :
358
356
mods = list (modules .lsmod (addr_space ))
@@ -382,6 +380,7 @@ def calculate(self):
382
380
# significantly lower quality given that it can be modified at any time. The kernel data structure
383
381
# remains valid unless the attacker kills the process etc... In any event (hah) since this value has never changed
384
382
# I hard coded it here for simplicity. Perhaps I should enforce always using it, will circle back 360 on that.. :O
383
+
385
384
timevalue = mod .TimeDateStamp
386
385
#this should only work for kernel space modules
387
386
if timevalue == 0 and task .UniqueProcessId == 4 :
@@ -398,11 +397,23 @@ def calculate(self):
398
397
"HdrHash" : self .HashPage (proc_as .read (mod .DllBase , 4096 )),
399
398
"HashSet" : [{"Address" : a , "Hash" : h } for a , h in zip (hashAddr , hashVal )]
400
399
}
401
- LocalMod = dict ({"Module" :mod , "Ctx" :p , "ModBlockCount" :hashAddr .count , "json" :req_hdr , "AS" :addr_space })
402
- p ["TaskBlockCount" ] = p ["TaskBlockCount" ] + len (hashAddr )
403
- p ["ModContext" ].append (LocalMod )
404
- outputJobs = [self .pool .spawn (self .pozt , cx ) for cx in p ["ModContext" ]]
405
- gevent .wait (outputJobs )
400
+ if req_hdr ["ModuleName" ] is '' :
401
+ self .logg .write ("{}{}{}: Unable to scan anonymous executable memory. {:#x} length: {:#x}{}.\n " .format (bg ("black" ), fg ("yellow_2" ), TaskName , mod .DllBase , mod .SizeOfImage , fg ("cornflower_blue" )))
402
+ filename = "{}/{}-{:#x}" .format (self .DumpFolder , TaskName , mod .DllBase )
403
+ open (filename , 'w' ).close ()
404
+ for vpage in range (mod .DllBase , mod .DllBase + mod .SizeOfImage , 4096 ):
405
+ data = proc_as .read (vpage , 4096 )
406
+ if self .DumpFolder is not None and data is not None :
407
+ with open (filename , 'ab' ) as block :
408
+ block .write (bytearray (data ))
409
+ else :
410
+ LocalMod = dict ({"Module" :mod , "Ctx" :p , "ModBlockCount" :hashAddr .count , "json" :req_hdr , "AS" :addr_space })
411
+ p ["TaskBlockCount" ] = p ["TaskBlockCount" ] + len (hashAddr )
412
+ taskBar .update (1 )
413
+ self .pool .spawn (self .pozt , LocalMod )
414
+
415
+ #= [gevent.spawn(self.pozt, cx) for cx in p["ModContext"]]
416
+ #gevent.wait(outputJobs)
406
417
self .TotalBar .update (1 )
407
418
408
419
# Ulong64 would be nice, this is a needed workaround
@@ -436,21 +447,17 @@ def output(self, Local):
436
447
"""Output data in a nonstandard but fun and more appealing way."""
437
448
bar = Local ["Ctx" ]["bar" ]
438
449
try :
439
-
440
450
addr_space = Local ["AS" ]
441
-
442
451
task = Local ["Ctx" ]["Task" ]
443
452
req_hdr = Local ["json" ]
444
- r = Local ["resp" ]
445
- bar .update (1 )
446
-
453
+ r = Local ["resp" ]
447
454
rj = None
448
455
449
456
moduleName = ""
450
457
if req_hdr .has_key ("ModuleName" ):
451
458
moduleName = req_hdr ["ModuleName" ]
452
459
453
- info = "{:<}{} [{:<}]" .format (bg ( "black" ), fg ("spring_green_2b" ), ntpath .basename (moduleName ))
460
+ info = "{} [{:<}]" .format (fg ("spring_green_2b" ), ntpath .basename (moduleName ))
454
461
455
462
self .ScannedMods += 1
456
463
ModBlksValidated = 0
@@ -493,24 +500,26 @@ def output(self, Local):
493
500
494
501
if validPct < 100.0 :
495
502
TaskName = Local ["Ctx" ]["Name" ]
496
- self .logg .writelines (("\n Failures detected: " , infoLine ,"\t : " , TaskName , "\r \n " , "BlockAddrs:" ))
503
+ self .logg .writelines (("Failures detected: " , infoLine ,"\t : " , TaskName , "\r \n " , "BlockAddrs: " ))
497
504
498
505
#if self._config.SuperVerbose is True:
499
506
for mb in modMissedBlocks :
500
507
# by default skip headers
501
508
if mb != req_hdr ["BaseAddress" ]:
502
- self .logg .write ("0x{:x } " .format (mb ))
503
- if len ( self .DumpFolder ) > 0 :
509
+ self .logg .write ("{:#14x } " .format (mb ))
510
+ if self .DumpFolder is not None :
504
511
proc_as = task .get_process_address_space ()
505
512
if task .UniqueProcessId == 4 :
506
513
proc_as = addr_space
507
514
508
515
data = proc_as .read (mb , 4096 )
509
516
if data is not None :
510
- with open ("{}/{}-{:x}" .format (self .DumpFolder , TaskName , mb ), 'wb' ) as block :
517
+ with open ("{}/{}-{:# x}" .format (self .DumpFolder , TaskName , mb ), 'wb' ) as block :
511
518
block .write (bytearray (data ))
519
+ self .logg .write ('\n ' )
512
520
513
- bar .set_description_str ('{:<}' .format (infoLine ))
521
+ bar .set_postfix_str ('{:<}' .format (infoLine ))
522
+ bar .update (1 )
514
523
except :
515
524
print_exc ()
516
525
#update less frequently put this back in
@@ -522,7 +531,12 @@ def output(self, Local):
522
531
self .TotalBar .set_postfix_str ("{:<}[{:<2.3f}%]{:}[{:,}]{}{}[{:,}]{}" .format (self .PercentToColor (self .TotPercent ), self .TotPercent , fg ("white" ), self .TotBytesValidated , fg ("sky_blue_1" ), "/" , self .TotalBytesChecked , fg ("light_green" )))
523
532
524
533
def render_text (self , outfd , data ):
525
- os .system ('setterm -cursor on' )
534
+ if os .name is not 'nt' :
535
+ os .system ('setterm -cursor on' )
536
+
537
+ print "{}{}" .format (fg ("hot_pink_1b" ), "Join in progress of any outstanding async operations." )
538
+ gevent .joinall (self .pool )
539
+
526
540
if self .VirtualBlocksChecked == 0 :
527
541
print ("{}{}" .format (fg ("yellow_2" ), "error, nothing was processed" ))
528
542
else :
@@ -541,3 +555,5 @@ def render_text(self, outfd, data):
541
555
self .logg .writelines ((miss_info , "\n " ))
542
556
if self ._config .ExtraTotals is True :
543
557
print (miss_info )
558
+ if os .name is 'nt' :
559
+ os .system ('color' )
0 commit comments