Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Volatility
- # Copyright (C) 2007-2013 Volatility Foundation
- #
- # Authors:
- # Michael Hale Ligh <michael.ligh@mnin.org>
- #
- # This file is part of Volatility.
- #
- # Volatility 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.
- #
- # Volatility 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 Volatility. If not, see <http://www.gnu.org/licenses/>.
- #
- import re, ntpath
- import volatility.utils as utils
- import volatility.obj as obj
- import volatility.debug as debug
- import volatility.win32.tasks as tasks
- import volatility.win32.modules as modules
- import volatility.plugins.malware.malfind as malfind
- import volatility.plugins.malware.idt as idt
- import volatility.plugins.overlays.basic as basic
- import volatility.plugins.procdump as procdump
- import volatility.exceptions as exceptions
- try:
- import distorm3
- has_distorm3 = True
- except ImportError:
- has_distorm3 = False
- #--------------------------------------------------------------------------------
- # Constants
- #--------------------------------------------------------------------------------
- # hook modes
- HOOK_MODE_USER = 1
- HOOK_MODE_KERNEL = 2
- # hook types
- HOOKTYPE_IAT = 4
- HOOKTYPE_EAT = 8
- HOOKTYPE_INLINE = 16
- HOOKTYPE_NT_SYSCALL = 32
- HOOKTYPE_CODEPAGE_KERNEL = 64
- HOOKTYPE_IDT = 128
- HOOKTYPE_IRP = 256
- HOOKTYPE_WINSOCK = 512
- # names for hook types
- hook_type_strings = {
- HOOKTYPE_IAT : "Import Address Table (IAT)",
- HOOKTYPE_EAT : "Export Address Table (EAT)",
- HOOKTYPE_INLINE : "Inline/Trampoline",
- HOOKTYPE_NT_SYSCALL : "NT Syscall",
- HOOKTYPE_CODEPAGE_KERNEL : "Unknown Code Page Call",
- HOOKTYPE_WINSOCK : "Winsock Procedure Table Hook",
- }
- WINSOCK_TABLE = [
- '_WSPAccept',
- '_WSPAddressToString',
- '_WSPAsyncSelect',
- '_WSPBind',
- '_WSPCancelBlockingCall',
- '_WSPCleanup',
- '_WSPCloseSocket',
- '_WSPConnect',
- '_WSPDuplicateSocket',
- '_WSPEnumNetworkEvents',
- '_WSPEventSelect',
- '_WSPGetOverlappedResult',
- '_WSPGetPeerName',
- '_WSPGetSockName',
- '_WSPGetSockOpt',
- '_WSPGetQOSByName',
- '_WSPIoctl',
- '_WSPJoinLeaf',
- '_WSPListen',
- '_WSPRecv',
- '_WSPRecvDisconnect',
- '_WSPRecvFrom',
- '_WSPSelect',
- '_WSPSend',
- '_WSPSendDisconnect',
- '_WSPSendTo',
- '_WSPSetSockOpt',
- '_WSPShutdown',
- '_WSPSocket',
- '_WSPStringToAddress',
- ]
- #--------------------------------------------------------------------------------
- # Profile Modifications
- #--------------------------------------------------------------------------------
- class MalwareWSPVTypes(obj.ProfileModification):
- before = ['WindowsOverlay']
- conditions = {'os': lambda x : x == 'windows',
- 'memory_model': lambda x: x == '32bit'}
- def modification(self, profile):
- profile.vtypes.update({
- '_SOCK_PROC_TABLE' : [ None, {
- 'Functions' : [ 0x0, ['array', 30, ['address']]],
- }]})
- #--------------------------------------------------------------------------------
- # Module Group Class
- #--------------------------------------------------------------------------------
- class ModuleGroup(object):
- """A class to assist with module lookups"""
- def __init__(self, mod_list):
- """Initialize.
- @param mod_list: a list of _LDR_DATA_TABLE_ENTRY objects.
- This can be a generator.
- """
- self.mods = list(mod_list)
- self.mod_name = {}
- self.mod_fast = [(mod.DllBase, mod.DllBase + mod.SizeOfImage, mod) for mod in self.mods]
- for mod in self.mods:
- name = str(mod.BaseDllName or '').lower()
- if name in self.mod_name:
- self.mod_name[name].append(mod)
- else:
- self.mod_name[name] = [mod]
- def find_module(self, address):
- """Find a module by an address it contains.
- @param address: location in process or kernel AS to
- find an owning module.
- When performing thousands of lookups, this method
- is actually quicker than tasks.find_module.
- """
- for base, end, mod in self.mod_fast:
- if address >= base and address <= end:
- return mod
- return obj.NoneObject("")
- #--------------------------------------------------------------------------------
- # Hook Class
- #--------------------------------------------------------------------------------
- class Hook(object):
- """A class for API hooks. It helps organize the many
- pieces of information required to report on the hook."""
- def __init__(self, hook_type, hook_mode, function_name,
- function_address = None, hook_address = None,
- hook_module = None, victim_module = None):
- """
- Initalize a hook class instance.
- @params hook_type: one of the HOOK_TYPE_* constants
- @params hook_mode: one of the HOOK_MODE_* constants
- @params function_name: name of the function being hooked
- @params function_address: address of the hooked function in
- process or kernel memory.
- @params hook_address: address where the hooked function
- actually points.
- @params hook_module: the _LDR_DATA_TABLE_ENTRY of the
- hooking module (owner of the hook_address). note:
- this can be None if the module cannot be identified.
- @params victim_module: the _LDR_DATA_TABLE_ENTRY of the
- module being hooked (contains the function_address).
- note: this can be a string if checking IAT hooks.
- """
- self.hook_mode = hook_mode
- self.hook_type = hook_type
- self.function_name = function_name
- self.function_address = function_address
- self.hook_address = hook_address
- self.hook_module = hook_module
- self.victim_module = victim_module
- # List of tuples: address, data pairs
- self.disassembled_hops = []
- def add_hop_chunk(self, address, data):
- """Support disassembly for multiple hops"""
- self.disassembled_hops.append((address, data))
- def _module_name(self, module):
- """Return a sanitized module name"""
- # The module can't be identified
- if not module:
- return '<unknown>'
- # The module is a string name like "ntdll.dll"
- if isinstance(module, basic.String) or isinstance(module, str):
- return str(module)
- # The module is a _LDR_DATA_TABLE_ENTRY
- return str(module.BaseDllName or '') or str(module.FullDllName or '') or '<unknown>'
- @property
- def Type(self):
- """Translate the hook type into a string"""
- return hook_type_strings.get(self.hook_type, "")
- @property
- def Mode(self):
- """Translate the hook mode into a string"""
- if self.hook_mode == HOOK_MODE_USER:
- return "Usermode"
- else:
- return "Kernelmode"
- @property
- def Function(self):
- """Return the function name if its available"""
- return str(self.function_name) or '<unknown>'
- @property
- def Detail(self):
- """The detail depends on the hook type"""
- if self.hook_type == HOOKTYPE_IAT:
- return "{0}!{1}".format(self.VictimModule, self.Function)
- elif self.hook_type == HOOKTYPE_EAT:
- return "{0} at {1:#x}".format(self.Function, self.hook_address)
- elif self.hook_type == HOOKTYPE_INLINE:
- return "{0}!{1} at {2:#x}".format(self.VictimModule, self.Function, self.function_address)
- else:
- return self.Function
- @property
- def HookModule(self):
- """Name of the hooking module"""
- return self._module_name(self.hook_module)
- @property
- def VictimModule(self):
- """Name of the victim module"""
- return self._module_name(self.victim_module)
- #--------------------------------------------------------------------------------
- # Whitelist Rules
- #--------------------------------------------------------------------------------
- # The values of each dictionary item is a list of tuples which are regexes
- # in the format (process, srd_mod, dst_mod, function). If you specify
- # (".*", ".*", ".*", ".*") then you essentially whitelist all possible hooks
- # of the given type.
- whitelist_rules = {
- HOOK_MODE_USER | HOOKTYPE_IAT : [
- # Ignore hooks that point inside C runtime libraries
- (".*", ".*", "(msvcr|msvcp).+\.dll", ".*"),
- # Ignore hooks of WMI that point inside advapi32.dll
- (".*", "wmi.dll", "advapi32.dll", ".*"),
- # Ignore hooks of winsock that point inside ws2 and mswsock
- (".*", "WSOCK32.dll", "(WS2_32|MSWSOCK)\.dll", ".*"),
- # Ignore hooks of SCHANNEL* that point inside secur32.dll
- (".*", "schannel.dll", "secur32.dll", ".*"),
- # Ignore hooks of Secur32* that point inside SSPICLI
- (".*", "Secur32.dll", "SSPICLI.DLL", ".*"),
- # Ignore hooks that point inside known modules
- (".*", ".*", "(kernel32|gdi32|advapi32|ntdll|shimeng|kernelbase|shlwapi|user32|cfgmgr32)", ".*"),
- # Handle some known forwarded imports
- (".*", ".*", ".*", "((Enter|Delete|Leave)CriticalSection|(Get|Set)LastError|Heap(ReAlloc|Free|Size|Alloc)|Rtl(Unwind|MoveMemory))"),
- # Ignore sfc hooks going to sfc_os
- (".*", "sfc\.dll", "sfc_os\.dll", ".*"),
- # Ignore netapi32 hooks pointing at netutils or samcli
- (".*", "netapi32\.dll", "(netutils|samcli)\.dll", ".*"),
- (".*", "setupapi\.dll", "devrtl\.dll", ".*"),
- ],
- HOOK_MODE_USER | HOOKTYPE_EAT : [
- # These modules have so many hooks its really not useful to check
- (".*", "(msvcp|msvcr|mfc|wbemcomn|fastprox)", ".*", ".*"),
- ],
- HOOK_MODE_USER | HOOKTYPE_INLINE : [
- # Ignore hooks in the pywin32 service process
- ("pythonservice", ".*", ".*", ".*"),
- # Many legit hooks land inside these modules
- (".*", ".*", "(msvcr|advapi32|version|wbemcomn|ntdll|kernel32|kernelbase|sechost|ole32|shlwapi|user32|gdi32|ws2_32|shell32)", ".*"),
- # Ignore hooks of the c runtime DLLs
- (".*", "(msvc(p|r)\d{2}|mfc\d{2})\.dll", ".*", ".*"),
- # This is a global variable
- (".*", "msvcrt\.dll", ".*", "_acmdln"),
- # Ignore hooks of MD5Final, MD5Init, MD5Update that point inside advapi32
- (".*", ".*", "advapi32.dll", "MD5.+"),
- # Ignore hooks of common firefox components
- ("firefox\.exe", ".*", "(xul|mozcrt|nspr4)", ".*"),
- # Ignore hooks created by Parallels VM software
- (".*", "user32.dll", "prl_hook.dll", ".*"),
- # Ignore DLL registration functions
- (".*", ".*", ".*", "(DllCanUnloadNow|DllRegisterServer|DllUnregisterServer)"),
- # Ignore netapi32 hooks pointing at netutils
- (".*", "netapi32\.dll", "netutils\.dll", ".*"),
- ],
- HOOK_MODE_KERNEL | HOOKTYPE_IAT : [
- (".*", ".*", "(win32k\.sys|hal\.dll|dump_wmilib\.sys|ntkrnlpa\.exe|ntoskrnl\.exe)", ".*"),
- # Ignore hooks of the SCSI module which point inside the dump_scsiport module
- (".*", "scsiport\.sys", "dump_scsiport\.sys", ".*"),
- # Ignore other storage port hooks
- (".*", "storport\.sys", "dump_storport\.sys", ".*"),
- ],
- HOOK_MODE_KERNEL | HOOKTYPE_EAT : [
- ],
- HOOK_MODE_KERNEL | HOOKTYPE_INLINE : [
- # Ignore kernel hooks that point inside these modules
- (".*", ".*", "(hal.dll|ndis.sys|ntkrnlpa.exe|ntoskrnl.exe)", ".*"),
- ],
- }
- class ApiHooks(procdump.ProcExeDump):
- """Detect API hooks in process and kernel memory"""
- def __init__(self, config, *args, **kwargs):
- procdump.ProcExeDump.__init__(self, config, *args, **kwargs)
- config.remove_option("DUMP-DIR")
- config.add_option("NO-WHITELIST", short_option = 'N', default = False,
- action = 'store_true',
- help = 'No whitelist (show all hooks, can be verbose)')
- config.add_option("SKIP-KERNEL", short_option = 'R', default = False,
- action = 'store_true',
- help = 'Skip kernel mode checks')
- config.add_option("SKIP-PROCESS", short_option = 'P', default = False,
- action = 'store_true',
- help = 'Skip process checks')
- config.add_option("QUICK", short_option = 'Q', default = False,
- action = 'store_true',
- help = 'Work faster by only analyzing critical processes and dlls')
- self.compiled_rules = self.compile()
- # When the --quick option is set, we only scan the processes
- # and dlls in these lists. Feel free to adjust them for
- # your own purposes.
- self.critical_process = ["explorer.exe", "svchost.exe", "lsass.exe",
- "services.exe", "winlogon.exe", "csrss.exe", "smss.exe",
- "wininit.exe", "iexplore.exe", "firefox.exe", "spoolsv.exe"]
- self.critical_dlls = ["ntdll.dll", "kernel32.dll", "ws2_32.dll",
- "advapi32.dll", "secur32.dll", "crypt32.dll", "user32.dll",
- "gdi32.dll", "shell32.dll", "shlwapi.dll", "lsasrv.dll",
- "cryptdll.dll", "wsock32.dll", "mswsock.dll", "urlmon.dll",
- "csrsrv.dll", "winsrv.dll", "wininet.dll"]
- # When scanning for calls to unknown code pages (UCP), only
- # analyze the following drivers. This is based on an analysis of
- # the modules rootkits are most likely to infect, but feel free
- # to adjust it for your own purposes.
- self.ucpscan_modules = ["tcpip.sys", "ntfs.sys", "fastfast.sys",
- "wanarp.sys", "ndis.sys", "atapi.sys", "ntoskrnl.exe",
- "ntkrnlpa.exe", "ntkrnlmp.exe"]
- @staticmethod
- def is_valid_profile(profile):
- return (profile.metadata.get('os', 'unknown') == 'windows' and
- profile.metadata.get('memory_model', '32bit') == '32bit')
- def compile(self):
- """
- Precompile the regular expression rules. Its quicker
- if we do this once per plugin run, rather than once per
- API hook that needs checking.
- """
- ret = dict()
- for key, rules in whitelist_rules.items():
- for rule in rules:
- ruleset = ((re.compile(rule[0], re.I), # Process name
- re.compile(rule[1], re.I), # Source module
- re.compile(rule[2], re.I), # Destination module
- re.compile(rule[3], re.I), # Function name
- ))
- if ret.has_key(key):
- ret[key].append(ruleset)
- else:
- ret[key] = [ruleset]
- return ret
- def whitelist(self, rule_key, process, src_mod, dst_mod, function):
- """Check if an API hook should be ignored due to whitelisting.
- @param rule_key: a key from the whitelist_rules dictionary which
- describes the type of hook (i.e. Usermode IAT or Kernel Inline).
- @param process: name of the suspected victim process.
- @param src_mod: name of the source module whose function has been
- hooked. this varies depending on whether we're dealing with IAT
- EAT, inline, etc.
- @param dst_mod: name of the module that is the destination of the
- hook pointer. this is usually the rootkit dll, exe, or sys,
- however, in many cases there is no module name since the rootkit
- is trying to be stealthy.
- @param function: name of the function that has been hooked.
- """
- # There are no whitelist rules for this hook type
- if rule_key not in self.compiled_rules:
- return False
- for rule in self.compiled_rules[rule_key]:
- if (rule[0].search(process) != None and
- rule[1].search(src_mod) != None and
- rule[2].search(dst_mod) != None and
- rule[3].search(function) != None):
- return True
- return False
- def get_idt(self):
- """
- Build IDT to check for inline hooks that use interrupts, whitelist interrupts
- that are handled by common kernel mode modules
- @returns: Dictionary of idt index and handler address
- """
- idt_data={}
- for n, _, addr, module in idt.IDT(self._config).calculate():
- for rule in self.compiled_rules[HOOK_MODE_KERNEL | HOOKTYPE_INLINE]:
- if module:
- module_name = str(module.BaseDllName or '')
- else:
- module_name = "UNKNOWN"
- if rule[2].search(module_name) is None:
- idt_data[n] = addr
- else:
- idt_data[n] = None
- return idt_data
- @staticmethod
- def check_syscall(addr_space, module, module_group):
- """
- Enumerate syscall hooks in ntdll.dll. A syscall hook is one
- that modifies the function prologue of an NT API function
- (i.e. ntdll!NtCreateFile) or swaps the location of the sysenter
- with a malicious address.
- @param addr_space: a process AS for the process containing the
- ntdll.dll module.
- @param module: the _LDR_DATA_TABLE_ENTRY for ntdll.dll
- @param module_group: a ModuleGroup instance for the process.
- """
- # Resolve the real location of KiFastSystem Call for comparison
- KiFastSystemCall = module.getprocaddress("KiFastSystemCall")
- KiIntSystemCall = module.getprocaddress("KiIntSystemCall")
- if not KiFastSystemCall or not KiIntSystemCall:
- #debug.debug("Abort check_syscall, can't find KiFastSystemCall")
- return
- # Add the RVA to make it absolute
- KiFastSystemCall += module.DllBase
- KiIntSystemCall += module.DllBase
- # Check each exported function if its an NT syscall
- for _, f, n in module.exports():
- # Ignore forwarded exports
- if not f:
- #debug.debug("Skipping forwarded export {0}".format(n or ''))
- continue
- function_address = module.DllBase + f
- if not addr_space.is_valid_address(function_address):
- #debug.debug("Function address {0:#x} for {1} is paged".format(
- # function_address, n or ''))
- continue
- # Read enough of the function prologue for two instructions
- data = addr_space.zread(function_address, 24)
- instructions = []
- for op in distorm3.Decompose(function_address, data, distorm3.Decode32Bits):
- if not op.valid:
- break
- if len(instructions) == 3:
- break
- instructions.append(op)
- i0 = instructions[0]
- i1 = instructions[1]
- i2 = instructions[2]
- # They both must be properly decomposed and have two operands
- if (not i0 or not i0.valid or len(i0.operands) != 2 or
- not i1 or not i1.valid or len(i1.operands) != 2):
- #debug.debug("Error decomposing prologue for {0} at {1:#x}".format(
- # n or '', function_address))
- continue
- # Now check the instruction and operand types
- if (i0.mnemonic == "MOV" and i0.operands[0].type == 'Register' and
- i0.operands[0].name == 'EAX' and i0.operands[1].type == 'Immediate' and
- i1.mnemonic == "MOV" and i1.operands[0].type == 'Register' and
- i1.operands[0].name == 'EDX' and i0.operands[1].type == 'Immediate'):
- if i2.operands[0].type == "Register":
- # KiFastSystemCall is already in the register
- syscall_address = i1.operands[1].value
- else:
- # Pointer to where KiFastSystemCall is stored
- syscall_address = obj.Object('address',
- offset = i1.operands[1].value, vm = addr_space)
- if syscall_address not in [KiFastSystemCall, KiIntSystemCall]:
- hook_module = module_group.find_module(syscall_address)
- hook = Hook(hook_type = HOOKTYPE_NT_SYSCALL,
- hook_mode = HOOK_MODE_USER,
- function_name = n or '',
- function_address = function_address,
- hook_address = syscall_address,
- hook_module = hook_module,
- victim_module = module,
- )
- # Add the bytes that will later be disassembled in the
- # output to show exactly how the hook works. The first
- # hop is the ntdll!Nt* API and the next hop is the rootkit.
- hook.add_hop_chunk(function_address, data)
- hook.add_hop_chunk(syscall_address, addr_space.zread(syscall_address, 24))
- yield hook
- def check_ucpcall(self, addr_space, module, module_group):
- """Scan for calls to unknown code pages.
- @param addr_space: a kernel AS
- @param module: the _LDR_DATA_TABLE_ENTRY to scan
- @param module_group: a ModuleGroup instance for the process.
- """
- try:
- dos_header = obj.Object("_IMAGE_DOS_HEADER",
- offset = module.DllBase, vm = addr_space)
- nt_header = dos_header.get_nt_header()
- except (ValueError, exceptions.SanityCheckException), _why:
- #debug.debug('get_nt_header() failed: {0}'.format(why))
- return
- # Parse the PE sections for this driver
- for sec in nt_header.get_sections(self._config.UNSAFE):
- # Only check executable sections
- if not sec.Characteristics & 0x20000000:
- continue
- # Calculate the virtual address of this PE section in memory
- sec_va = module.DllBase + sec.VirtualAddress
- # Extract the section's data and make sure its not all zeros
- data = addr_space.zread(sec_va, sec.Misc.VirtualSize)
- if data == "\x00" * len(data):
- continue
- # Disassemble instructions in the section
- for op in distorm3.DecomposeGenerator(sec_va, data, distorm3.Decode32Bits):
- if (op.valid and ((op.flowControl == 'FC_CALL' and
- op.mnemonic == "CALL") or
- (op.flowControl == 'FC_UNC_BRANCH' and
- op.mnemonic == "JMP")) and
- op.operands[0].type == 'AbsoluteMemoryAddress'):
- # This is ADDR, which is the IAT location
- const = op.operands[0].disp & 0xFFFFFFFF
- # Abort if ADDR is not a valid address
- if not addr_space.is_valid_address(const):
- continue
- # This is what [ADDR] points to - the absolute destination
- call_dest = obj.Object("address", offset = const, vm = addr_space)
- # Abort if [ADDR] is not a valid address
- if not addr_space.is_valid_address(call_dest):
- continue
- check1 = module_group.find_module(const)
- check2 = module_group.find_module(call_dest)
- # If ADDR or [ADDR] point to an unknown code page
- if not check1 or not check2:
- hook = Hook(hook_type = HOOKTYPE_CODEPAGE_KERNEL,
- hook_mode = HOOK_MODE_KERNEL,
- function_name = "",
- function_address = op.address,
- hook_address = call_dest,
- )
- # Add the location we found the call
- hook.add_hop_chunk(op.address,
- data[op.address - sec_va : op.address - sec_va + 24])
- # Add the rootkit stub
- hook.add_hop_chunk(call_dest, addr_space.zread(call_dest, 24))
- yield hook
- def check_wsp(self, addr_space, module, module_group):
- """
- Check for hooks of non-exported WSP* functions. The
- mswsock.dll module contains a global variable which
- points to all the internal Winsock functions. We find
- the function table by the reference from the exported
- WSPStartup API.
- .text:6C88922E 8B 7D 50 mov edi, [ebp+lpProcTable]
- .text:6C889231 6A 1E push 1Eh
- .text:6C889233 59 pop ecx
- .text:6C889234 BE 40 64 8B 6C mov esi, offset _SockProcTable
- .text:6C889239 F3 A5 rep movsd
- @param addr_space: process AS
- @param module: the _LDR_DATA_TABLE_ENTRY for mswsock.dll
- @param module_group: a ModuleGroup instance for the process.
- """
- WSPStartup = module.getprocaddress("WSPStartup")
- if not WSPStartup:
- #debug.debug("Abort check_wsp, can't find WSPStartup")
- return
- WSPStartup += module.DllBase
- # Opcode pattern to look for
- signature = "\x6A\x1E\x59\xBE"
- # Read enough bytes of the function to find our signature
- data = addr_space.zread(WSPStartup, 300)
- if data == "\x00" * len(data):
- #debug.debug("WSPStartup prologue is paged")
- return
- offset = data.find(signature)
- if offset == -1:
- #debug.debug("Can't find {0} in WSPStartup".format(repr(signature)))
- return
- # Dereference the pointer as our _SockProcTable
- p = obj.Object("address",
- offset = WSPStartup + offset + len(signature),
- vm = addr_space)
- p = p.dereference_as("_SOCK_PROC_TABLE")
- # Enumerate functions in the procedure table
- for i, function_address in enumerate(p.Functions):
- function_owner = module_group.find_module(function_address)
- # The function points outside of mwsock, its hooked
- if function_owner != module:
- hook = Hook(hook_type = HOOKTYPE_WINSOCK,
- hook_mode = HOOK_MODE_USER,
- function_name = WINSOCK_TABLE[i],
- function_address = function_address,
- hook_module = function_owner,
- victim_module = module
- )
- hook.add_hop_chunk(function_address,
- addr_space.zread(function_address, 12))
- yield hook
- else:
- # The function points inside mwsock, check inline
- ret = self.check_inline(function_address, addr_space,
- module.DllBase, module.DllBase + module.SizeOfImage,
- self.idt)
- if not ret:
- #debug.debug("Cannot analyze {0}".format(WINSOCK_TABLE[i]))
- continue
- (hooked, data, hook_address) = ret
- if hooked:
- hook_module = module_group.find_module(hook_address)
- if hook_module != module:
- hook = Hook(hook_type = HOOKTYPE_WINSOCK,
- hook_mode = HOOK_MODE_USER,
- function_name = WINSOCK_TABLE[i],
- function_address = function_address,
- hook_module = hook_module,
- hook_address = hook_address,
- victim_module = module
- )
- hook.add_hop_chunk(function_address, data)
- hook.add_hop_chunk(hook_address, addr_space.zread(hook_address, 12))
- yield hook
- @staticmethod
- def check_inline(va, addr_space, mem_start, mem_end, idt = None):
- """
- Check for inline API hooks. We check for direct and indirect
- calls, direct and indirect jumps, and PUSH/RET combinations.
- @param va: the virtual address of the function to check
- @param addr_space: process or kernel AS where the function resides
- @param mem_start: base address of the module containing the
- function being checked.
- @param mem_end: end address of the module containing the func
- being checked.
- @param idt: whitelisted IDT interrupt handlers removed from the
- dict. Not required for compatibility reasons.
- @returns: a tuple of (hooked, data, hook_address)
- """
- data = addr_space.zread(va, 24)
- if data == "\x00" * len(data):
- #debug.debug("Cannot read function prologue at {0:#x}".format(va))
- return None
- outside_module = lambda x: x != None and (x < mem_start or x > mem_end)
- # Number of instructions disassembled so far
- n = 0
- # Destination address of hooks
- d = None
- # Save the last PUSH before a CALL
- push_val = None
- # Save the general purpose registers
- regs = {}
- for op in distorm3.Decompose(va, data, distorm3.Decode32Bits):
- # Quit the loop when we have three instructions or when
- # a decomposition error is encountered, whichever is first.
- if not op.valid or n == 3:
- break
- if op.flowControl == 'FC_CALL':
- # Clear the push value
- if push_val:
- push_val = None
- if op.mnemonic == "CALL" and op.operands[0].type == 'AbsoluteMemoryAddress':
- # Check for CALL [ADDR]
- const = op.operands[0].disp & 0xFFFFFFFF
- d = obj.Object("unsigned int", offset = const, vm = addr_space)
- if outside_module(d):
- break
- elif op.operands[0].type == 'Immediate':
- # Check for CALL ADDR
- d = op.operands[0].value & 0xFFFFFFFF
- if outside_module(d):
- break
- elif op.operands[0].type == 'Register':
- # Check for CALL REG
- d = regs.get(op.operands[0].name)
- if d and outside_module(d):
- break
- elif op.flowControl == 'FC_UNC_BRANCH' and op.mnemonic == "JMP":
- # Clear the push value
- if push_val:
- push_val = None
- if op.size > 2:
- if op.operands[0].type == 'AbsoluteMemoryAddress':
- # Check for JMP [ADDR]
- const = op.operands[0].disp & 0xFFFFFFFF
- d = obj.Object("unsigned int", offset = const, vm = addr_space)
- if outside_module(d):
- break
- elif op.operands[0].type == 'Immediate':
- # Check for JMP ADDR
- d = op.operands[0].value & 0xFFFFFFFF
- if outside_module(d):
- break
- elif op.size == 2 and op.operands[0].type == 'Register':
- # Check for JMP REG
- d = regs.get(op.operands[0].name)
- if d and outside_module(d):
- break
- elif op.flowControl == 'FC_NONE':
- # Check for PUSH followed by a RET
- if (op.mnemonic == "PUSH" and
- op.operands[0].type == 'Immediate' and op.size == 5):
- # Set the push value
- push_val = op.operands[0].value & 0xFFFFFFFF
- # Check for moving imm values into a register
- if (op.mnemonic == "MOV" and op.operands[0].type == 'Register'
- and op.operands[1].type == 'Immediate'):
- # Clear the push value
- if push_val:
- push_val = None
- # Save the value put into the register
- regs[op.operands[0].name] = op.operands[1].value
- elif op.flowControl == 'FC_RET':
- if push_val:
- d = push_val
- if outside_module(d):
- break
- # This causes us to stop disassembling when
- # reaching the end of a function
- break
- elif op.flowControl == "FC_INT" and idt:
- # Clear the push value
- if push_val:
- push_val = None
- # Check for INT, ignore INT3
- if op.mnemonic == "INT" and op.size > 1 and op.operands[0].type == 'Immediate':
- # Check interrupt handler address
- d = idt[op.operands[0].value]
- if d and outside_module(d):
- break
- n += 1
- # Check EIP after the function prologue
- if outside_module(d):
- return True, data, d
- else:
- return False, data, d
- def gather_stuff(self, _addr_space, module):
- """Use the Volatility object classes to enumerate
- imports and exports. This function can be overriden
- to use pefile instead for speed testing"""
- # This is a dictionary where keys are the names of imported
- # modules and values are lists of tuples (ord, addr, name).
- imports = {}
- exports = [(o, module.DllBase + f, n) for o, f, n in module.exports()]
- for dll, o, f, n in module.imports():
- dll = dll.lower()
- if dll in imports:
- imports[dll].append((o, f, n))
- else:
- imports[dll] = [(o, f, n)]
- return imports, exports
- def get_hooks(self, hook_mode, addr_space, module, module_group):
- """Enumerate IAT, EAT, Inline hooks. Also acts as a dispatcher
- for NT syscall, UCP scans, and winsock procedure table hooks.
- @param hook_mode: one of the HOOK_MODE_* constants
- @param addr_space: a process AS or kernel AS
- @param module: an _LDR_DATA_TABLE_ENTRY for the module being
- checked for hooks.
- @param module_group: a ModuleGroup instance for the process.
- """
- # We start with the module base name. If that's not available,
- # trim the full name down to its base name.
- module_name = (str(module.BaseDllName or '') or
- ntpath.basename(str(module.FullDllName or '')))
- # Lowercase for string matching
- module_name = module_name.lower()
- if hook_mode == HOOK_MODE_USER:
- if module_name == "ntdll.dll":
- for hook in self.check_syscall(addr_space, module, module_group):
- yield hook
- elif module_name == "mswsock.dll":
- for hook in self.check_wsp(addr_space, module, module_group):
- yield hook
- else:
- if module_name in self.ucpscan_modules:
- for hook in self.check_ucpcall(addr_space, module, module_group):
- yield hook
- imports, exports = \
- self.gather_stuff(addr_space, module)
- for dll, functions in imports.items():
- valid_owners = module_group.mod_name.get(dll, [])
- if not valid_owners:
- #debug.debug("Cannot find any modules named {0}".format(dll))
- continue
- for (_, f, n) in functions:
- if not f:
- #debug.debug("IAT function {0} is paged or ordinal".format(n or ''))
- continue
- if not addr_space.is_valid_address(f):
- continue
- function_owner = module_group.find_module(f)
- if function_owner not in valid_owners:
- hook = Hook(hook_type = HOOKTYPE_IAT,
- hook_mode = hook_mode,
- function_name = n or '',
- hook_address = f,
- hook_module = function_owner,
- victim_module = dll, # only for IAT hooks
- )
- # Add the rootkit code
- hook.add_hop_chunk(f, addr_space.zread(f, 24))
- yield hook
- for _, f, n in exports:
- if not f:
- #debug.debug("EAT function {0} is paged".format(n or ''))
- continue
- function_address = f
- if not addr_space.is_valid_address(function_address):
- continue
- # Get the module containing the function
- function_owner = module_group.find_module(function_address)
- # This is a check for EAT hooks
- if function_owner != module:
- hook = Hook(hook_type = HOOKTYPE_EAT,
- hook_mode = hook_mode,
- function_name = n or '',
- hook_address = function_address,
- hook_module = function_owner,
- )
- hook.add_hop_chunk(function_address,
- addr_space.zread(function_address, 24))
- yield hook
- # No need to check for inline hooks if EAT is hooked
- continue
- ret = self.check_inline(function_address, addr_space,
- module.DllBase, module.DllBase + module.SizeOfImage,
- self.idt)
- if ret == None:
- #debug.debug("Cannot analyze {0}".format(n or ''))
- continue
- (hooked, data, dest_addr) = ret
- if not hooked:
- continue
- if not addr_space.is_valid_address(dest_addr):
- continue
- function_owner = module_group.find_module(dest_addr)
- if function_owner != module:
- # only do this for kernel hooks
- #if params['mode'] == HOOK_MODE_KERNEL:
- # if owner:
- # if self.in_data_section(owner, status['destaddr']):
- # continue
- hook = Hook(hook_type = HOOKTYPE_INLINE,
- hook_mode = hook_mode,
- function_name = n or '',
- function_address = function_address,
- hook_address = dest_addr,
- hook_module = function_owner,
- victim_module = module,
- )
- # Add the function prologue
- hook.add_hop_chunk(function_address, data)
- # Add the first redirection
- hook.add_hop_chunk(dest_addr, addr_space.zread(dest_addr, 24))
- yield hook
- def calculate(self):
- addr_space = utils.load_as(self._config)
- if not has_distorm3:
- debug.error("Install distorm3 code.google.com/p/distorm/")
- if not self.is_valid_profile(addr_space.profile):
- debug.error("This command does not support the selected profile.")
- self.idt = self.get_idt()
- if not self._config.SKIP_PROCESS:
- for proc in self.filter_tasks(tasks.pslist(addr_space)):
- process_name = str(proc.ImageFileName).lower()
- if (self._config.QUICK and
- process_name not in self.critical_process):
- #debug.debug("Skipping non-critical process {0} ({1})".format(
- # process_name, proc.UniqueProcessId))
- continue
- process_space = proc.get_process_address_space()
- if not process_space:
- #debug.debug("Cannot acquire process AS for {0} ({1})".format(
- # process_name, proc.UniqueProcessId))
- continue
- module_group = ModuleGroup(proc.get_load_modules())
- for dll in module_group.mods:
- if not process_space.is_valid_address(dll.DllBase):
- continue
- dll_name = str(dll.BaseDllName or '').lower()
- if (self._config.QUICK and
- dll_name not in self.critical_dlls and
- dll.DllBase != proc.Peb.ImageBaseAddress):
- #debug.debug("Skipping non-critical dll {0} at {1:#x}".format(
- # dll_name, dll.DllBase))
- continue
- #debug.debug("Analyzing {0}!{1}".format(process_name, dll_name))
- for hook in self.get_hooks(HOOK_MODE_USER,
- process_space, dll, module_group):
- yield proc, dll, hook
- if not self._config.SKIP_KERNEL:
- process_list = list(tasks.pslist(addr_space))
- module_group = ModuleGroup(modules.lsmod(addr_space))
- for mod in module_group.mods:
- #module_name = str(mod.BaseDllName or '')
- #debug.debug("Analyzing {0}".format(module_name))
- kernel_space = tasks.find_space(addr_space,
- process_list, mod.DllBase)
- if not kernel_space:
- #debug.debug("No kernel AS for {0} at {1:#x}".format(
- # module_name, mod.DllBase))
- continue
- for hook in self.get_hooks(HOOK_MODE_KERNEL,
- kernel_space, mod, module_group):
- yield None, mod, hook
- def render_text(self, outfd, data):
- for process, module, hook in data:
- if not self._config.NO_WHITELIST:
- if process:
- process_name = str(process.ImageFileName)
- else:
- process_name = ''
- if self.whitelist(hook.hook_mode | hook.hook_type,
- process_name, hook.VictimModule,
- hook.HookModule, hook.Function):
- #debug.debug("Skipping whitelisted function: {0} {1} {2} {3}".format(
- # process_name, hook.VictimModule, hook.HookModule,
- # hook.Function))
- continue
- outfd.write("*" * 72 + "\n")
- outfd.write("Hook mode: {0}\n".format(hook.Mode))
- outfd.write("Hook type: {0}\n".format(hook.Type))
- if process:
- outfd.write('Process: {0} ({1})\n'.format(
- process.UniqueProcessId, process.ImageFileName))
- outfd.write("Victim module: {0} ({1:#x} - {2:#x})\n".format(
- str(module.BaseDllName or '') or ntpath.basename(str(module.FullDllName or '')),
- module.DllBase, module.DllBase + module.SizeOfImage))
- outfd.write("Function: {0}\n".format(hook.Detail))
- outfd.write("Hook address: {0:#x}\n".format(hook.hook_address))
- outfd.write("Hooking module: {0}\n\n".format(hook.HookModule))
- for n, info in enumerate(hook.disassembled_hops):
- (address, data) = info
- s = ["{0:#x} {1:<16} {2}".format(o, h, i)
- for o, i, h in
- malfind.Disassemble(data, int(address))
- ]
- outfd.write("Disassembly({0}):\n{1}".format(n, "\n".join(s)))
- outfd.write("\n\n")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement