Open gleeda opened 9 years ago
dnscache.py:
import struct
import re
import volatility.commands as commands
import volatility.utils as utils
import volatility.obj as obj
import volatility.win32.tasks as tasks
import volatility.plugins.heap as heapscan
DNSREC = {
0 : "QUESTION",
1 : "ANSWER",
2 : "AUTHORITY",
3 : "ADDITIONAL"
}
DNS_TYPES = dict(
DNS_TYPE_A = 0x0001,
DNS_TYPE_NS = 0x0002,
DNS_TYPE_MD = 0x0003,
DNS_TYPE_MF = 0x0004,
DNS_TYPE_CNAME = 0x0005,
DNS_TYPE_SOA = 0x0006,
DNS_TYPE_MB = 0x0007,
DNS_TYPE_MG = 0x0008,
DNS_TYPE_MR = 0x0009,
DNS_TYPE_NULL = 0x000a,
DNS_TYPE_WKS = 0x000b,
DNS_TYPE_PTR = 0x000c,
DNS_TYPE_HINFO = 0x000d,
DNS_TYPE_MINFO = 0x000e,
DNS_TYPE_MX = 0x000f,
DNS_TYPE_TEXT = 0x0010,
DNS_TYPE_RP = 0x0011,
DNS_TYPE_AFSDB = 0x0012,
DNS_TYPE_X25 = 0x0013,
DNS_TYPE_ISDN = 0x0014,
DNS_TYPE_RT = 0x0015,
DNS_TYPE_NSAP = 0x0016,
DNS_TYPE_NSAPPTR = 0x0017,
DNS_TYPE_SIG = 0x0018,
DNS_TYPE_KEY = 0x0019,
DNS_TYPE_PX = 0x001a,
DNS_TYPE_GPOS = 0x001b,
DNS_TYPE_AAAA = 0x001c,
DNS_TYPE_LOC = 0x001d,
DNS_TYPE_NXT = 0x001e,
DNS_TYPE_EID = 0x001f,
DNS_TYPE_NIMLOC = 0x0020,
DNS_TYPE_SRV = 0x0021,
DNS_TYPE_ATMA = 0x0022,
DNS_TYPE_NAPTR = 0x0023,
DNS_TYPE_KX = 0x0024,
DNS_TYPE_CERT = 0x0025,
DNS_TYPE_A6 = 0x0026,
DNS_TYPE_DNAME = 0x0027,
DNS_TYPE_SINK = 0x0028,
DNS_TYPE_OPT = 0x0029,
DNS_TYPE_UINFO = 0x0064,
DNS_TYPE_UID = 0x0065,
DNS_TYPE_GID = 0x0066,
DNS_TYPE_UNSPEC = 0x0067,
DNS_TYPE_ADDRS = 0x00f8,
DNS_TYPE_TKEY = 0x00f9,
DNS_TYPE_TSIG = 0x00fa,
DNS_TYPE_IXFR = 0x00fb,
DNS_TYPE_AXFR = 0x00fc,
DNS_TYPE_MAILB = 0x00fd,
DNS_TYPE_MAILA = 0x00fe,
DNS_TYPE_ALL = 0x00ff,
DNS_TYPE_ANY = 0x00ff,
)
dnstypes = {
'_DNS_RECORD_FLAGS' : [ 4, {
'Section' : [ 0, ['BitField', dict(start_bit = 0, end_bit = 2)]],
'Delete' : [ 0, ['BitField', dict(start_bit = 2, end_bit = 3)]],
'CharSet' : [ 0, ['BitField', dict(start_bit = 3, end_bit = 5)]],
'Unused' : [ 0, ['BitField', dict(start_bit = 5, end_bit = 8)]],
'Reserved' : [ 0, ['BitField', dict(start_bit = 8, end_bit = 32)]],
} ],
'_DNS_RECORD' : [ None, {
'pNext' : [ 0x00, ['pointer', ['_DNS_RECORD']]],
'pName' : [ 0x04, ['pointer', ['unsigned short']]],
'wType' : [ 0x08, ['unsigned short']],
'wDataLength' : [ 0x0a, ['unsigned short']],
'Flags' : [ 0x0c, ['_DNS_RECORD_FLAGS']],
'dwTtl' : [ 0x10, ['unsigned long']],
'dwReserved' : [ 0x14, ['unsigned long']],
'Data' : [ 0x18, ['unsigned long']], # this can be various size, depends on datalength
} ]}
class dnscache(commands.Command):
#""" Checks entries from dnscache"""
def __init__(self, config, *args):
commands.Command.__init__(self, config, *args)
def dword_to_ip(self,value):
a = value & 0xff
b = (value & 0xff00) >> 8
c = (value & 0xff0000) >> 16
d = (value & 0xff000000) >> 24
ip = "%d.%d.%d.%d" % (a,b,c,d)
return ip
def get_name_by_flag(self, flags, val):
if flags.has_key(val):
return flags[val]
else:
return [f for f in flags if (flags[f] == val)]
def get_dns_stuff(self,entry,dns):
data = ""
datalen = dns.wDataLength.v()
ttl = dns.dwTtl.v()
record_type = self.get_name_by_flag(DNS_TYPES,dns.wType.v())[0]
if record_type == 'DNS_TYPE_A':
data = self.dword_to_ip(dns.Data.v())
elif record_type in ['DNS_TYPE_PTR','DNS_TYPE_CNAME','DNS_TYPE_DNAME','DNS_TYPE_NS']:
data = self.get_record_name(entry,dns.Data.v())
flags = dns.Flags.dereference_as('_DNS_RECORD_FLAGS')
section = DNSREC[int(flags.Section)]
record_name = self.get_record_name(entry,dns.pName.v())
return record_name, record_type, ttl, datalen, section, data
def get_record_name(self,entry,data, length=128):
record_name = entry.obj_vm.zread(data,length)
record_name = re.findall(r'(([\x20-\x80]\x00){6,128}\x00\x00)',record_name)
if len(record_name) > 0:
record_name = record_name[0][0].replace('\x00','')
else:
record_name = "-"
return record_name
def get_loaded_mods(self, eproc):
if eproc.UniqueProcessId:
if eproc.Peb.Ldr.InLoadOrderModuleList:
for entry in eproc.Peb.Ldr.InLoadOrderModuleList.list_of_type("_LDR_DATA_TABLE_ENTRY", "InLoadOrderLinks"):
yield entry
def calculate(self):
cache_found = False
address_space = utils.load_as(self._config)
address_space.profile.add_types(dnstypes)
for eproc in tasks.pslist(address_space):
pid = eproc.UniqueProcessId.v()
if str(eproc.ImageFileName) != 'svchost.exe':
continue
if 'networkservice' not in eproc.Peb.ProcessParameters.CommandLine.v().lower():
continue
break
found_resolver_lib = False
mods = self.get_loaded_mods(eproc)
for mod in mods:
if str(mod.BaseDllName).lower() != "dnsrslvr.dll":
continue
found_resolver_lib = True
break
if found_resolver_lib == False:
return
for procname, pid, heap, heap_segments, heap_freelists, heap_virtual_blocks in heapscan.HeapScan(self._config).calculate(pid):
for segment in heap_segments:
if (cache_found):
break
entry_offs = segment.FirstEntry.v()
while 1:
entry = obj.Object('_HEAP_ENTRY',offset = entry_offs, vm = heap.obj_vm)
if not entry:
break
if entry.Size == 0:
break
if entry.Size == 0x7:
block_data = entry.obj_vm.zread(entry.v()+8,block_size)
pointer = block_data[4:8]
pointer = struct.unpack("<L",pointer)[0]
if pointer != 0:
offset = pointer - entry.v() - 8
data = block_data[offset:offset+20].replace('\x00','')
if data == 'localhost':
cache_found = True
break
block_size = (entry.Size-1) * 8
entry_offs += block_size + 8
if cache_found == False:
return
while 1:
entry = obj.Object('_HEAP_ENTRY',offset = entry_offs, vm = heap.obj_vm)
if not entry:
break
if entry.Size == 0:
break
block_size = (entry.Size-1) * 8
entry_offs += block_size + 8
block_data = entry.obj_vm.zread(entry.v()+8,block_size)
main_name = block_data[0x1c:].replace('\x00','')
pointer = struct.unpack("<L",block_data[0x10:0x14])[0]
if pointer != 0:
dns = obj.Object('_DNS_RECORD',vm = entry.obj_vm,offset = pointer)
record_name,record_type,ttl,datalen,section,data = self.get_dns_stuff(entry,dns)
yield record_name,record_type,ttl,datalen,section,data
nextdns = dns.pNext.dereference_as('_DNS_RECORD')
while nextdns:
record_name,record_type,ttl,datalen,section,data = self.get_dns_stuff(entry,nextdns)
yield record_name,record_type,ttl,datalen,section,data
nextdns = nextdns.pNext.dereference_as('_DNS_RECORD')
def render_text(self, outfd, data):
outfd.write("{0:<40} {1:<16} {2:<8} {3:<8} {4:<12} {5}\n".format("Name", "Type", "TTL", "DataLen", "Section", "Data"))
for record_name,record_type,ttl,datalen,section,data in data:
outfd.write("{0:<40} {1:<16} {2:<8} {3:<8} {4:<12} {5}\n".format(record_name,record_type,ttl,datalen,section,data))
dnscachedump.py:
import struct
import re
import volatility.commands as commands
import volatility.utils as utils
import volatility.obj as obj
import volatility.win32.tasks as tasks
import volatility.debug as debug
DNSREC = {
0 : "QUESTION",
1 : "ANSWER",
2 : "AUTHORITY",
3 : "ADDITIONAL"
}
DNS_TYPES = dict(
DNS_TYPE_A = 0x0001,
DNS_TYPE_NS = 0x0002,
DNS_TYPE_MD = 0x0003,
DNS_TYPE_MF = 0x0004,
DNS_TYPE_CNAME = 0x0005,
DNS_TYPE_SOA = 0x0006,
DNS_TYPE_MB = 0x0007,
DNS_TYPE_MG = 0x0008,
DNS_TYPE_MR = 0x0009,
DNS_TYPE_NULL = 0x000a,
DNS_TYPE_WKS = 0x000b,
DNS_TYPE_PTR = 0x000c,
DNS_TYPE_HINFO = 0x000d,
DNS_TYPE_MINFO = 0x000e,
DNS_TYPE_MX = 0x000f,
DNS_TYPE_TEXT = 0x0010,
DNS_TYPE_RP = 0x0011,
DNS_TYPE_AFSDB = 0x0012,
DNS_TYPE_X25 = 0x0013,
DNS_TYPE_ISDN = 0x0014,
DNS_TYPE_RT = 0x0015,
DNS_TYPE_NSAP = 0x0016,
DNS_TYPE_NSAPPTR = 0x0017,
DNS_TYPE_SIG = 0x0018,
DNS_TYPE_KEY = 0x0019,
DNS_TYPE_PX = 0x001a,
DNS_TYPE_GPOS = 0x001b,
DNS_TYPE_AAAA = 0x001c,
DNS_TYPE_LOC = 0x001d,
DNS_TYPE_NXT = 0x001e,
DNS_TYPE_EID = 0x001f,
DNS_TYPE_NIMLOC = 0x0020,
DNS_TYPE_SRV = 0x0021,
DNS_TYPE_ATMA = 0x0022,
DNS_TYPE_NAPTR = 0x0023,
DNS_TYPE_KX = 0x0024,
DNS_TYPE_CERT = 0x0025,
DNS_TYPE_A6 = 0x0026,
DNS_TYPE_DNAME = 0x0027,
DNS_TYPE_SINK = 0x0028,
DNS_TYPE_OPT = 0x0029,
DNS_TYPE_UINFO = 0x0064,
DNS_TYPE_UID = 0x0065,
DNS_TYPE_GID = 0x0066,
DNS_TYPE_UNSPEC = 0x0067,
DNS_TYPE_ADDRS = 0x00f8,
DNS_TYPE_TKEY = 0x00f9,
DNS_TYPE_TSIG = 0x00fa,
DNS_TYPE_IXFR = 0x00fb,
DNS_TYPE_AXFR = 0x00fc,
DNS_TYPE_MAILB = 0x00fd,
DNS_TYPE_MAILA = 0x00fe,
DNS_TYPE_ALL = 0x00ff,
DNS_TYPE_ANY = 0x00ff,
)
dnstypes = {
'_DNS_RECORD_FLAGS' : [ 4, {
'Section' : [ 0, ['BitField', dict(start_bit = 0, end_bit = 2)]],
'Delete' : [ 0, ['BitField', dict(start_bit = 2, end_bit = 3)]],
'CharSet' : [ 0, ['BitField', dict(start_bit = 3, end_bit = 5)]],
'Unused' : [ 0, ['BitField', dict(start_bit = 5, end_bit = 8)]],
'Reserved' : [ 0, ['BitField', dict(start_bit = 8, end_bit = 32)]],
} ],
'_DNS_RECORD' : [ None, {
'pNext' : [ 0x00, ['pointer', ['_DNS_RECORD']]],
'pName' : [ 0x04, ['pointer', ['unsigned short']]],
'wType' : [ 0x08, ['unsigned short']],
'wDataLength' : [ 0x0a, ['unsigned short']],
'Flags' : [ 0x0c, ['_DNS_RECORD_FLAGS']],
'dwTtl' : [ 0x10, ['unsigned long']],
'dwReserved' : [ 0x14, ['unsigned long']],
'Data' : [ 0x18, ['unsigned long']], # this can be various size, depends on datalength
} ]}
class DNSCacheDumpRecords(obj.ProfileModification):
conditions = {'os': lambda x: x == 'windows'}
def modification(self, profile):
profile.vtypes.update(dnstypes)
class dnscachedump(commands.Command):
#""" Checks entries from dnscache"""
def __init__(self, config, *args):
commands.Command.__init__(self, config, *args)
def dword_to_ip(self, value):
a = value & 0xff
b = (value & 0xff00) >> 8
c = (value & 0xff0000) >> 16
d = (value & 0xff000000) >> 24
ip = "%d.%d.%d.%d" % (a,b,c,d)
return ip
def get_name_by_flag(self, flags, val):
if flags.has_key(val):
return flags[val]
else:
return [f for f in flags if (flags[f] == val)]
def get_dns_stuff(self, entry, dns):
data = ""
datalen = dns.wDataLength.v()
ttl = dns.dwTtl.v()
record_type = self.get_name_by_flag(DNS_TYPES,dns.wType.v())[0]
if record_type == 'DNS_TYPE_A':
data = self.dword_to_ip(dns.Data.v())
elif record_type in ['DNS_TYPE_PTR','DNS_TYPE_CNAME','DNS_TYPE_DNAME','DNS_TYPE_NS']:
data = self.get_record_name(entry,dns.Data.v())
flags = dns.Flags.dereference_as('_DNS_RECORD_FLAGS')
section = DNSREC[int(flags.Section)]
record_name = self.get_record_name(entry,dns.pName.v())
return record_name, record_type, ttl, datalen, section, data
def get_record_name(self, entry, data, length=128):
record_name = entry.obj_vm.zread(data,length)
record_name = re.findall(r'(([\x20-\x80]\x00){6,128}\x00\x00)',record_name)
if len(record_name) > 0:
record_name = record_name[0][0].replace('\x00','')
else:
record_name = "-"
return record_name
def calculate(self):
cache_found = False
resolver_lib_found = False
self._task = None
address_space = utils.load_as(self._config)
for task in tasks.pslist(address_space):
if str(task.ImageFileName) in 'svchost.exe':
task_command_line = task.Peb.ProcessParameters.CommandLine.v().lower()
if 'networkservice' in task_command_line and not 'restricted' in task_command_line:
self._task = task
if self._task is not None:
pid = self._task.UniqueProcessId.v()
task_space = self._task.get_process_address_space()
pages = task_space.get_available_pages()
if self._config.verbose:
debug.info("FOUND TARGET PID {0} {1}".format(pid, self._task.Peb.ProcessParameters.CommandLine.v().lower()))
else:
debug.error("Unable to find target process.")
return
for mod in self._task.get_load_modules():
if str(mod.BaseDllName).lower() not in "dnsrslvr.dll":
continue
resolver_lib_found = True
break
if not resolver_lib_found:
return
else:
if self._config.verbose:
debug.info("FOUND TARGET DLL")
if pages:
for p in pages:
if cache_found:
break
data = task_space.read(p[0], p[1])
if data == None:
if self._config.verbose:
debug.info("Memory Not Accessible: Virtual Address: 0x{0:x} File Offset: 0x{1:x} Size: 0x{2:x}".format(p[0], task.obj_offset, p[1]))
else:
entry_offs = p[0]
while 1:
entry = obj.Object('_HEAP_ENTRY', offset=entry_offs, vm=task_space)
if not entry:
break
if entry.Size == 0:
break
if entry.Size == 0x7:
block_data = entry.obj_vm.zread(entry.v()+8,block_size)
pointer = block_data[4:8]
if len(pointer) < 4:
pointer = 0
else:
pointer = struct.unpack("<L",pointer)[0]
if pointer != 0:
offset = pointer - entry.v() - 8
data = block_data[offset:offset+20].replace('\x00','')
if data == 'localhost':
cache_found = True
debug.info("Cache Entry Found at Virtual Address 0x{0:x}".format(p[0]))
break
block_size = (entry.Size-1) * 8
entry_offs += block_size + 8
if not cache_found:
return
while 1:
entry = obj.Object('_HEAP_ENTRY', offset=entry_offs, vm=task_space)
if not entry:
break
if entry.Size == 0:
break
block_size = (entry.Size-1) * 8
entry_offs += block_size + 8
block_data = entry.obj_vm.zread(entry.v()+8,block_size)
main_name = block_data[0x1c:].replace('\x00','')
pointer = struct.unpack("<L",block_data[0x10:0x14])[0]
if pointer != 0:
dns = obj.Object('_DNS_RECORD', vm=entry.obj_vm, offset=pointer)
record_name,record_type,ttl,datalen,section,data = self.get_dns_stuff(entry,dns)
yield record_name,record_type,ttl,datalen,section,data
nextdns = dns.pNext.dereference_as('_DNS_RECORD')
while nextdns:
record_name,record_type,ttl,datalen,section,data = self.get_dns_stuff(entry,nextdns)
yield record_name,record_type,ttl,datalen,section,data
nextdns = nextdns.pNext.dereference_as('_DNS_RECORD')
else:
debug.error("Unable to read pages for task.")
def render_text(self, outfd, data):
if data is not None:
outfd.write("{0:<40} {1:<16} {2:<8} {3:<8} {4:<12} {5}\n".format("Name", "Type", "TTL", "DataLen", "Section", "Data"))
for record_name,record_type,ttl,datalen,section,data in data:
outfd.write("{0:<40} {1:<16} {2:<8} {3:<8} {4:<12} {5}\n".format(record_name,record_type,ttl,datalen,section,data))
Are you still working on this? If not, I'm happy to take over (or start from scratch if the code is broken on 2.5)
Actually, I'm not working on this at all, I just put it here so we wouldn't forget. If you'd like to take it over, please do :-)
An attempt to solve this problem. https://github.com/mnemonic-no/dnscache
Any feedback is greatly appreciated.
@geirskjo looks nicely done, good work. do you mind if we tweet it and start getting some feedback from the community for you?
I don't mind at all. Any feedback would be great.
plans to revise this for the current codebase: https://code.google.com/p/volatility/issues/detail?id=124
Original issue: