Files
volatilityfoundation-volati…/volatility/plugins/netscan.py
T
2012-02-26 23:26:52 +00:00

273 lines
11 KiB
Python

# Volatility
#
# Authors:
# Michael Hale Ligh <michael.hale@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import volatility.utils as utils
import volatility.commands as commands
import volatility.scan as scan
import volatility.obj as obj
import volatility.cache as cache
import socket
import itertools
tcp_states = [
"", # This must be empty, so the first enum starts at 1
"CLOSED",
"LISTENING",
"SYN_SENT",
"SYN_RCVD",
"ESTABLISHED",
"FIN_WAIT1",
"FIN_WAIT2",
"CLOSE_WAIT",
"CLOSING",
"LAST_ACK",
"TIME_WAIT",
"DELETE_TCB",
]
# Python's socket.AF_INET6 is 0x1e but MSFT's is 0x17..and we need to use MSFT's
AF_INET = 2
AF_INET6 = 0x17
# Compensate for Windows python not supporting socket.inet_ntop and some
# Linux systems (i.e. OpenSuSE 11.2 w/ Python 2.6) not supporting IPv6.
def inet_ntop(address_family, packed_ip):
def inet_ntop4(packed_ip):
if not isinstance(packed_ip, str):
raise TypeError("must be string, not {0}".format(type(packed_ip)))
if len(packed_ip) != 4:
raise ValueError("invalid length of packed IP address string")
return "{0}.{1}.{2}.{3}".format(*[ord(x) for x in packed_ip])
def inet_ntop6(packed_ip):
if not isinstance(packed_ip, str):
raise TypeError("must be string, not {0}".format(type(packed_ip)))
if len(packed_ip) != 16:
raise ValueError("invalid length of packed IP address string")
words = []
for i in range(0, 16, 2):
words.append((ord(packed_ip[i]) << 8) | ord(packed_ip[i + 1]))
# Replace a run of 0x00s with None
numlen = [(k, len(list(g))) for k, g in itertools.groupby(words)]
max_zero_run = sorted(sorted(numlen, key = lambda x: x[1], reverse = True), key = lambda x: x[0])[0]
words = []
for k, l in numlen:
if (k == 0) and (l == max_zero_run[1]) and not (None in words):
words.append(None)
else:
for i in range(l):
words.append(k)
# Handle encapsulated IPv4 addresses
encapsulated = ""
if (words[0] is None) and (len(words) == 3 or (len(words) == 4 and words[1] == 0xffff)):
words = words[:-2]
encapsulated = inet_ntop4(packed_ip[-4:])
# If we start or end with None, then add an additional :
if words[0] is None:
words = [None] + words
if words[-1] is None:
words += [None]
# Join up everything we've got using :s
return ":".join(["{0:x}".format(w) if w is not None else "" for w in words]) + encapsulated
if address_family == socket.AF_INET:
return inet_ntop4(packed_ip)
elif address_family == socket.AF_INET6:
return inet_ntop6(packed_ip)
raise socket.error("[Errno 97] Address family not supported by protocol")
# String representations of INADDR_ANY and INADDR6_ANY
inaddr_any = inet_ntop(socket.AF_INET, '\0' * 4)
inaddr6_any = inet_ntop(socket.AF_INET6, '\0' * 16)
class PoolScanUdpEndpoint(scan.PoolScanner):
"""PoolScanner for Udp Endpoints"""
def object_offset(self, found, address_space):
return found + (address_space.profile.get_obj_size("_POOL_HEADER") -
address_space.profile.get_obj_offset("_POOL_HEADER", "PoolTag"))
checks = [ ('PoolTagCheck', dict(tag = "UdpA")),
# Seen as 0xa8 on Vista SP0, 0xb0 on Vista SP2, and 0xb8 on 7
# Seen as 0x150 on Win7 SP0 x64
('CheckPoolSize', dict(condition = lambda x: x >= 0xa8)),
('CheckPoolType', dict(non_paged = True, paged = True, free = True)),
('CheckPoolIndex', dict(value = 0)),
]
class PoolScanTcpListener(scan.PoolScanner):
"""PoolScanner for Tcp Listeners"""
def object_offset(self, found, address_space):
return found + (address_space.profile.get_obj_size("_POOL_HEADER") -
address_space.profile.get_obj_offset("_POOL_HEADER", "PoolTag"))
checks = [ ('PoolTagCheck', dict(tag = "TcpL")),
# Seen as 0x120 on Win7 SP0 x64
('CheckPoolSize', dict(condition = lambda x: x >= 0xa8)),
('CheckPoolType', dict(non_paged = True, paged = True, free = True)),
('CheckPoolIndex', dict(value = 0)),
]
#class PoolScanRawEndpoint(scan.PoolScanner):
# """PoolScanner for Raw Endpoints"""
# checks = [ ('PoolTagCheck', dict(tag = "RawE")),
# ('CheckPoolSize', dict(condition = lambda x: x == 0x90)),
# ('CheckPoolType', dict(non_paged = True, paged = True, free = True)),
# ('CheckPoolIndex', dict(value = 0)),
# ]
class PoolScanTcpEndpoint(scan.PoolScanner):
"""PoolScanner for TCP Endpoints"""
def object_offset(self, found, address_space):
return found + (address_space.profile.get_obj_size("_POOL_HEADER") -
address_space.profile.get_obj_offset("_POOL_HEADER", "PoolTag"))
checks = [ ('PoolTagCheck', dict(tag = "TcpE")),
# Seen as 0x1f0 on Vista SP0, 0x1f8 on Vista SP2 and 0x210 on 7
# Seen as 0x320 on Win7 SP0 x64
('CheckPoolSize', dict(condition = lambda x: x >= 0x1f0)),
('CheckPoolType', dict(non_paged = True, paged = True, free = True)),
('CheckPoolIndex', dict(value = 0)),
]
class Netscan(commands.Command):
"""Scan a Vista, 2008 or Windows 7 image for connections and sockets"""
def enumerate_listeners(self, theObject):
"""
Enumerate the listening IPv4 and IPv6 information.
Unlike XP, where you needed to create two sockets (one for IPv4 and
one for IPv4), starting with Vista, Windows supports dual-stack sockets
(http://msdn.microsoft.com/en-us/library/bb513665.aspx) which allows one
socket to be created that can use both protocols. This is why our plugin
prints an IPv4 address for all IPv6 sockets, however its also possible
to create an IPv6 only socket by calling setsockopt with IPV6_V6ONLY.
"""
# These pointers are dereferenced in kernel space since we set
# native_vm when the objects were created.
LocalAddr = theObject.LocalAddr.dereference()
InetAF = theObject.InetAF.dereference()
Owner = theObject.Owner.dereference()
# We only handle IPv4 and IPv6 sockets at the moment
if InetAF.AddressFamily != AF_INET and InetAF.AddressFamily != AF_INET6:
raise StopIteration
if LocalAddr != None:
inaddr = LocalAddr.pData.dereference().dereference().v()
if InetAF.AddressFamily == AF_INET:
laddr = inet_ntop(socket.AF_INET, theObject.obj_native_vm.zread(inaddr, 4))
yield "v4", laddr, inaddr_any, Owner
else:
laddr = inet_ntop(socket.AF_INET6, theObject.obj_native_vm.zread(inaddr, 16))
yield "v6", laddr, inaddr6_any, Owner
else:
yield "v4", inaddr_any, inaddr_any, Owner
if InetAF.AddressFamily == AF_INET6:
yield "v6", inaddr6_any, inaddr6_any, Owner
@cache.CacheDecorator("tests/netscan")
def calculate(self):
# Virtual kernel space for dereferencing pointers
vspace = utils.load_as(self._config)
# Physical space for scanning
pspace = utils.load_as(self._config, astype = 'physical')
for offset in PoolScanTcpListener().scan(pspace):
tcpentry = obj.Object('_TCP_LISTENER', offset = offset,
vm = pspace, native_vm = vspace)
lport = socket.ntohs(tcpentry.Port)
# For TcpL, the state is always listening and the remote port is zero
state = "LISTENING"
rport = 0
for ver, laddr, raddr, owner in self.enumerate_listeners(tcpentry):
yield tcpentry.obj_offset, "TCP" + ver, laddr, lport, raddr, rport, state, owner, tcpentry.CreateTime
for offset in PoolScanTcpEndpoint().scan(pspace):
tcpentry = obj.Object('_TCP_ENDPOINT', offset = offset,
vm = pspace, native_vm = vspace)
# These pointers are dereferenced in kernel space since we set
# native_vm when the objects were created.
AddrInfo = tcpentry.AddrInfo.dereference()
InetAF = tcpentry.InetAF.dereference()
Owner = tcpentry.Owner.dereference()
lport = socket.ntohs(tcpentry.LocalPort)
rport = socket.ntohs(tcpentry.RemotePort)
try:
state = tcp_states[tcpentry.State + 1]
except IndexError:
state = hex(tcpentry.State)
l_inaddr = AddrInfo.Local.pData.dereference().dereference().v()
r_inaddr = AddrInfo.Remote.dereference().v()
if InetAF.AddressFamily == AF_INET:
proto = "TCPv4"
laddr = inet_ntop(socket.AF_INET, vspace.zread(l_inaddr, 4))
raddr = inet_ntop(socket.AF_INET, vspace.zread(r_inaddr, 4))
elif InetAF.AddressFamily == AF_INET6:
proto = "TCPv6"
laddr = inet_ntop(socket.AF_INET6, vspace.zread(l_inaddr, 16))
raddr = inet_ntop(socket.AF_INET6, vspace.zread(r_inaddr, 16))
else:
continue
yield tcpentry.obj_offset, proto, laddr, lport, raddr, rport, state, Owner, tcpentry.CreateTime
for offset in PoolScanUdpEndpoint().scan(pspace):
udpentry = obj.Object('_UDP_ENDPOINT', offset = offset,
vm = pspace, native_vm = vspace)
lport = socket.ntohs(udpentry.Port)
# For UdpA, the state is always blank and the remote end is asterisks
state = ""
raddr = rport = "*"
for ver, laddr, _, owner in self.enumerate_listeners(udpentry):
yield udpentry.obj_offset, "UDP" + ver, laddr, lport, raddr, rport, state, owner, udpentry.CreateTime
def render_text(self, outfd, data):
outfd.write("{0:<10} {1:<8} {2:<30} {3:<20} {4:<16} {5:<8} {6:<14} {7}\n".format(
"Offset(P)", "Proto", "Local Address", "Foreign Address", "State", "Pid", "Owner", "Created"))
for offset, proto, laddr, lport, raddr, rport, state, p, ctime in data:
lendpoint = "{0}:{1}".format(laddr, lport)
rendpoint = "{0}:{1}".format(raddr, rport)
process = p.ImageFileName if p.UniqueProcessId < 0xFFFF else ""
outfd.write("{0:<#10x} {1:<8} {2:<30} {3:<20} {4:<16} {5:<8} {6:<14} {7}\n".format(
offset, proto, lendpoint, rendpoint, state, p.UniqueProcessId, process, ctime if ctime.v() else ""))