Skip to content

Commit 3ac35de

Browse files
committed
Few perf changes and requirements updated, better SSL checking on client and removed locks for speed++
1 parent 4a400f5 commit 3ac35de

File tree

2 files changed

+81
-56
lines changed

2 files changed

+81
-56
lines changed

inVteroJitHash.py

Lines changed: 72 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818
1919
##############################################################################################
2020
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
2228
2329
* Added heaps of colors!
2430
@@ -33,14 +39,7 @@
3339
+ Block offset that has the modified code
3440
+ Special dump mode for just modified code
3541
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
4443
4544
To use this with volatility place this .py anywhere, ensure you have volatility working.
4645
For example the command line below will simply run the invterojithash against the input memory image
@@ -70,18 +69,21 @@
7069
Enjoy!
7170
################################################################################################
7271
"""
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()
7577

7678
import volatility.addrspace
7779
import volatility.commands as commands
7880
import volatility.utils as utils
7981
import volatility.win32.tasks as tasks
8082
import volatility.win32.modules as modules
8183
import os, time, base64, sys, threading
82-
import json, urllib, urllib2
84+
import json, urllib, urllib2, urllib3
8385
import gevent, struct, retry, traceback, colorama
84-
import ntpath
86+
import ntpath, certifi
8587

8688
from os import environ
8789
from retry import retry
@@ -155,9 +157,9 @@ class inVteroJitHash(commands.Command):
155157
#JITHashServer = "http://localhost:7071/api/PageHash/x"
156158
JITHashServer = "https://pdb2json.azurewebsites.net/api/PageHash/x"
157159

158-
159160
# Tune this if you want to hit the server harder
160-
pool = Pool(16)
161+
pool = Pool()
162+
greenlits = []
161163

162164
total_miss = {}
163165
null_hash = None
@@ -169,26 +171,28 @@ class inVteroJitHash(commands.Command):
169171
MissList = []
170172
TotalProgress = []
171173
TotBytesValidated = 0
172-
TotBytesValidated = 0
173174
TotalLastN = 0
174175
TotPercent = 0.0
175176
TotalBar = None
176177
logg = None
177178
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())
179181
def __init__(self, config, *args):
180182
# no color on Windows yet, this keeps the output from looking insane with all the ANSI
181183
if os.name == 'nt':
182-
self.stream = AnsiToWin32(sys.stdout).stream
184+
self.stream = AnsiToWin32(sys.stdout, convert=True).stream
183185
init()
184186
#init(convert=True)
185187
commands.Command.__init__(self, config, *args)
186188
config.add_option('SuperVerbose', short_option='s', help='Display per page validation results.', action='store_true', default=False)
187189
config.add_option('ExtraTotals', short_option='x', help='List of all misses per-module.', action='store_true', default=False)
188190
config.add_option('DumpFolder', short_option='D', help='Dump the failed blocks to a specified folder', default=None)
189191
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')
192196

193197
# This method is a huge bit of code that should of been in volatility
194198
# 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):
203207
Parameters
204208
----------
205209
vaddr : long
206-
A virtual address from IA32PAE or AMD64 compatible address spaces
210+
A virtual address from IA32PAE or AMD64 compatible address spaces
207211
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
209213
210214
Returns
211215
-------
@@ -292,19 +296,11 @@ def pozt(self, LocalMod):
292296
rvData = ""
293297
try:
294298
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-
304299
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
308304
except HTTPError as inst:
309305
if inst.code == 204:
310306
return rvData
@@ -315,8 +311,9 @@ def pozt(self, LocalMod):
315311
print("{}{}".format(fg("hot_pink_1b"), x))
316312
finally:
317313
a = AsyncResult()
318-
a.set(rvData)
314+
a.set(req.data)
319315
LocalMod["resp"] = a.get(block=True)
316+
req.release_conn()
320317
self.output(LocalMod)
321318

322319
return LocalMod
@@ -325,8 +322,9 @@ def pozt(self, LocalMod):
325322
# the entire execution. The completion routine render_text is for a minimal amount of reporting.
326323

327324
def calculate(self):
328-
self.DumpFolder = (self._config.DumpFolder or '')
325+
self.DumpFolder = (self._config.DumpFolder or None)
329326
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")))
330328
# get the null hash (at runtime in case a different hash is used etc..)
331329
null_page = bytearray(4096)
332330
self.null_hash = self.HashPage(null_page)
@@ -343,16 +341,16 @@ def calculate(self):
343341
taskCnt += 1
344342

345343
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))
348346
# The timer is reset here since were not counting the coldstartup time
349347
self.StartTime = time.time()
350348
for task in tasklist:
351349
taski += 1
352350

353351
proc_as = task.get_process_address_space()
354352
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
356354
# to handle AS the same way for kernel & user
357355
if task.UniqueProcessId == 4:
358356
mods = list(modules.lsmod(addr_space))
@@ -382,6 +380,7 @@ def calculate(self):
382380
# significantly lower quality given that it can be modified at any time. The kernel data structure
383381
# remains valid unless the attacker kills the process etc... In any event (hah) since this value has never changed
384382
# I hard coded it here for simplicity. Perhaps I should enforce always using it, will circle back 360 on that.. :O
383+
385384
timevalue = mod.TimeDateStamp
386385
#this should only work for kernel space modules
387386
if timevalue == 0 and task.UniqueProcessId == 4:
@@ -398,11 +397,23 @@ def calculate(self):
398397
"HdrHash": self.HashPage(proc_as.read(mod.DllBase, 4096)),
399398
"HashSet": [{"Address": a, "Hash": h} for a, h in zip(hashAddr, hashVal)]
400399
}
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)
406417
self.TotalBar.update(1)
407418

408419
# Ulong64 would be nice, this is a needed workaround
@@ -436,21 +447,17 @@ def output(self, Local):
436447
"""Output data in a nonstandard but fun and more appealing way."""
437448
bar = Local["Ctx"]["bar"]
438449
try:
439-
440450
addr_space = Local["AS"]
441-
442451
task = Local["Ctx"]["Task"]
443452
req_hdr = Local["json"]
444-
r = Local["resp"]
445-
bar.update(1)
446-
453+
r = Local["resp"]
447454
rj = None
448455

449456
moduleName = ""
450457
if req_hdr.has_key("ModuleName"):
451458
moduleName = req_hdr["ModuleName"]
452459

453-
info = "{:<}{}[{:<}]".format(bg("black"), fg("spring_green_2b"), ntpath.basename(moduleName))
460+
info = "{}[{:<}]".format(fg("spring_green_2b"), ntpath.basename(moduleName))
454461

455462
self.ScannedMods += 1
456463
ModBlksValidated = 0
@@ -493,24 +500,26 @@ def output(self, Local):
493500

494501
if validPct < 100.0:
495502
TaskName = Local["Ctx"]["Name"]
496-
self.logg.writelines(("\nFailures detected: ", infoLine,"\t: ", TaskName, "\r\n", "BlockAddrs:" ))
503+
self.logg.writelines(("Failures detected: ", infoLine,"\t: ", TaskName, "\r\n", "BlockAddrs: "))
497504

498505
#if self._config.SuperVerbose is True:
499506
for mb in modMissedBlocks:
500507
# by default skip headers
501508
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:
504511
proc_as = task.get_process_address_space()
505512
if task.UniqueProcessId == 4:
506513
proc_as = addr_space
507514

508515
data = proc_as.read(mb, 4096)
509516
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:
511518
block.write(bytearray(data))
519+
self.logg.write('\n')
512520

513-
bar.set_description_str('{:<}'.format(infoLine))
521+
bar.set_postfix_str('{:<}'.format(infoLine))
522+
bar.update(1)
514523
except:
515524
print_exc()
516525
#update less frequently put this back in
@@ -522,7 +531,12 @@ def output(self, Local):
522531
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")))
523532

524533
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+
526540
if self.VirtualBlocksChecked == 0:
527541
print ("{}{}".format(fg("yellow_2"), "error, nothing was processed"))
528542
else:
@@ -541,3 +555,5 @@ def render_text(self, outfd, data):
541555
self.logg.writelines((miss_info, "\n"))
542556
if self._config.ExtraTotals is True:
543557
print (miss_info)
558+
if os.name is 'nt':
559+
os.system('color')

requirements.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
tqdm==4.19.5
2+
certifi==2018.1.18
3+
colored==1.3.5
4+
gevent==1.2.2
5+
retry==0.9.2
6+
urllib3==1.22
7+
colorama==0.3.7
8+
pycrypto==2.6.1
9+
volatility==2.1

0 commit comments

Comments
 (0)