Advertisement
spresec

Volatility apihooks.py - Inline interrupt hooks

Mar 20th, 2014
985
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 44.53 KB | None | 0 0
  1. # Volatility
  2. # Copyright (C) 2007-2013 Volatility Foundation
  3. #
  4. # Authors:
  5. # Michael Hale Ligh <michael.ligh@mnin.org>
  6. #
  7. # This file is part of Volatility.
  8. #
  9. # Volatility is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation; either version 2 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # Volatility is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with Volatility.  If not, see <http://www.gnu.org/licenses/>.
  21. #
  22.  
  23. import re, ntpath
  24. import volatility.utils as utils
  25. import volatility.obj as obj
  26. import volatility.debug as debug
  27. import volatility.win32.tasks as tasks
  28. import volatility.win32.modules as modules
  29. import volatility.plugins.malware.malfind as malfind
  30. import volatility.plugins.malware.idt as idt
  31. import volatility.plugins.overlays.basic as basic
  32. import volatility.plugins.procdump as procdump
  33. import volatility.exceptions as exceptions
  34.  
  35. try:
  36.     import distorm3
  37.     has_distorm3 = True
  38. except ImportError:
  39.     has_distorm3 = False
  40.  
  41. #--------------------------------------------------------------------------------
  42. # Constants
  43. #--------------------------------------------------------------------------------
  44.  
  45. # hook modes
  46. HOOK_MODE_USER = 1
  47. HOOK_MODE_KERNEL = 2
  48.  
  49. # hook types
  50. HOOKTYPE_IAT = 4
  51. HOOKTYPE_EAT = 8
  52. HOOKTYPE_INLINE = 16
  53. HOOKTYPE_NT_SYSCALL = 32
  54. HOOKTYPE_CODEPAGE_KERNEL = 64
  55. HOOKTYPE_IDT = 128
  56. HOOKTYPE_IRP = 256
  57. HOOKTYPE_WINSOCK = 512
  58.  
  59. # names for hook types
  60. hook_type_strings = {
  61.     HOOKTYPE_IAT             : "Import Address Table (IAT)",
  62.     HOOKTYPE_EAT             : "Export Address Table (EAT)",
  63.     HOOKTYPE_INLINE          : "Inline/Trampoline",
  64.     HOOKTYPE_NT_SYSCALL      : "NT Syscall",
  65.     HOOKTYPE_CODEPAGE_KERNEL : "Unknown Code Page Call",
  66.     HOOKTYPE_WINSOCK         : "Winsock Procedure Table Hook",
  67. }
  68.  
  69. WINSOCK_TABLE = [
  70.     '_WSPAccept',
  71.     '_WSPAddressToString',
  72.     '_WSPAsyncSelect',
  73.     '_WSPBind',
  74.     '_WSPCancelBlockingCall',
  75.     '_WSPCleanup',
  76.     '_WSPCloseSocket',
  77.     '_WSPConnect',
  78.     '_WSPDuplicateSocket',
  79.     '_WSPEnumNetworkEvents',
  80.     '_WSPEventSelect',
  81.     '_WSPGetOverlappedResult',
  82.     '_WSPGetPeerName',
  83.     '_WSPGetSockName',
  84.     '_WSPGetSockOpt',
  85.     '_WSPGetQOSByName',
  86.     '_WSPIoctl',
  87.     '_WSPJoinLeaf',
  88.     '_WSPListen',
  89.     '_WSPRecv',
  90.     '_WSPRecvDisconnect',
  91.     '_WSPRecvFrom',
  92.     '_WSPSelect',
  93.     '_WSPSend',
  94.     '_WSPSendDisconnect',
  95.     '_WSPSendTo',
  96.     '_WSPSetSockOpt',
  97.     '_WSPShutdown',
  98.     '_WSPSocket',
  99.     '_WSPStringToAddress',
  100. ]
  101.  
  102. #--------------------------------------------------------------------------------
  103. # Profile Modifications
  104. #--------------------------------------------------------------------------------
  105.  
  106. class MalwareWSPVTypes(obj.ProfileModification):
  107.     before = ['WindowsOverlay']
  108.     conditions = {'os': lambda x : x == 'windows',
  109.                   'memory_model': lambda x: x == '32bit'}
  110.     def modification(self, profile):
  111.         profile.vtypes.update({
  112.             '_SOCK_PROC_TABLE' : [ None, {
  113.             'Functions' : [ 0x0, ['array', 30, ['address']]],
  114.             }]})
  115.  
  116. #--------------------------------------------------------------------------------
  117. # Module Group Class
  118. #--------------------------------------------------------------------------------
  119.  
  120. class ModuleGroup(object):
  121.     """A class to assist with module lookups"""
  122.  
  123.     def __init__(self, mod_list):
  124.         """Initialize.
  125.  
  126.        @param mod_list: a list of _LDR_DATA_TABLE_ENTRY objects.
  127.        This can be a generator.
  128.        """
  129.  
  130.         self.mods = list(mod_list)
  131.         self.mod_name = {}
  132.         self.mod_fast = [(mod.DllBase, mod.DllBase + mod.SizeOfImage, mod) for mod in self.mods]
  133.  
  134.         for mod in self.mods:
  135.             name = str(mod.BaseDllName or '').lower()
  136.             if name in self.mod_name:
  137.                 self.mod_name[name].append(mod)
  138.             else:
  139.                 self.mod_name[name] = [mod]
  140.  
  141.     def find_module(self, address):
  142.         """Find a module by an address it contains.
  143.            
  144.        @param address: location in process or kernel AS to
  145.        find an owning module.
  146.  
  147.        When performing thousands of lookups, this method
  148.        is actually quicker than tasks.find_module.
  149.        """
  150.  
  151.         for base, end, mod in self.mod_fast:
  152.             if address >= base and address <= end:
  153.                 return mod
  154.  
  155.         return obj.NoneObject("")
  156.  
  157. #--------------------------------------------------------------------------------
  158. # Hook Class
  159. #--------------------------------------------------------------------------------
  160.  
  161. class Hook(object):
  162.     """A class for API hooks. It helps organize the many
  163.    pieces of information required to report on the hook."""
  164.  
  165.     def __init__(self, hook_type, hook_mode, function_name,
  166.                         function_address = None, hook_address = None,
  167.                         hook_module = None, victim_module = None):
  168.         """
  169.        Initalize a hook class instance.
  170.  
  171.        @params hook_type: one of the HOOK_TYPE_* constants
  172.        @params hook_mode: one of the HOOK_MODE_* constants
  173.  
  174.        @params function_name: name of the function being hooked
  175.  
  176.        @params function_address: address of the hooked function in
  177.            process or kernel memory.
  178.  
  179.        @params hook_address: address where the hooked function
  180.            actually points.
  181.  
  182.        @params hook_module: the _LDR_DATA_TABLE_ENTRY of the
  183.            hooking module (owner of the hook_address). note:
  184.            this can be None if the module cannot be identified.
  185.  
  186.        @params victim_module: the _LDR_DATA_TABLE_ENTRY of the
  187.            module being hooked (contains the function_address).
  188.            note: this can be a string if checking IAT hooks.
  189.  
  190.        """
  191.         self.hook_mode = hook_mode
  192.         self.hook_type = hook_type
  193.         self.function_name = function_name
  194.         self.function_address = function_address
  195.         self.hook_address = hook_address
  196.         self.hook_module = hook_module
  197.         self.victim_module = victim_module
  198.         # List of tuples: address, data pairs
  199.         self.disassembled_hops = []
  200.  
  201.     def add_hop_chunk(self, address, data):
  202.         """Support disassembly for multiple hops"""
  203.         self.disassembled_hops.append((address, data))
  204.  
  205.     def _module_name(self, module):
  206.         """Return a sanitized module name"""
  207.  
  208.         # The module can't be identified
  209.         if not module:
  210.             return '<unknown>'
  211.  
  212.         # The module is a string name like "ntdll.dll"
  213.         if isinstance(module, basic.String) or isinstance(module, str):
  214.             return str(module)
  215.  
  216.         # The module is a _LDR_DATA_TABLE_ENTRY
  217.         return str(module.BaseDllName or '') or str(module.FullDllName or '') or '<unknown>'
  218.  
  219.     @property
  220.     def Type(self):
  221.         """Translate the hook type into a string"""
  222.         return hook_type_strings.get(self.hook_type, "")
  223.  
  224.     @property
  225.     def Mode(self):
  226.         """Translate the hook mode into a string"""
  227.         if self.hook_mode == HOOK_MODE_USER:
  228.             return "Usermode"
  229.         else:
  230.             return "Kernelmode"
  231.  
  232.     @property
  233.     def Function(self):
  234.         """Return the function name if its available"""
  235.         return str(self.function_name) or '<unknown>'
  236.  
  237.     @property
  238.     def Detail(self):
  239.         """The detail depends on the hook type"""
  240.         if self.hook_type == HOOKTYPE_IAT:
  241.             return "{0}!{1}".format(self.VictimModule, self.Function)
  242.         elif self.hook_type == HOOKTYPE_EAT:
  243.             return "{0} at {1:#x}".format(self.Function, self.hook_address)
  244.         elif self.hook_type == HOOKTYPE_INLINE:
  245.             return "{0}!{1} at {2:#x}".format(self.VictimModule, self.Function, self.function_address)
  246.         else:
  247.             return self.Function
  248.  
  249.     @property
  250.     def HookModule(self):
  251.         """Name of the hooking module"""
  252.         return self._module_name(self.hook_module)
  253.  
  254.     @property
  255.     def VictimModule(self):
  256.         """Name of the victim module"""
  257.         return self._module_name(self.victim_module)
  258.  
  259. #--------------------------------------------------------------------------------
  260. # Whitelist Rules
  261. #--------------------------------------------------------------------------------
  262.  
  263. # The values of each dictionary item is a list of tuples which are regexes
  264. # in the format (process, srd_mod, dst_mod, function). If you specify
  265. # (".*", ".*", ".*", ".*") then you essentially whitelist all possible hooks
  266. # of the given type.
  267.  
  268. whitelist_rules = {
  269.     HOOK_MODE_USER | HOOKTYPE_IAT : [
  270.     # Ignore hooks that point inside C runtime libraries
  271.     (".*", ".*", "(msvcr|msvcp).+\.dll", ".*"),
  272.     # Ignore hooks of WMI that point inside advapi32.dll
  273.     (".*", "wmi.dll", "advapi32.dll", ".*"),
  274.     # Ignore hooks of winsock that point inside ws2 and   mswsock
  275.     (".*", "WSOCK32.dll", "(WS2_32|MSWSOCK)\.dll", ".*"),
  276.     # Ignore hooks of SCHANNEL* that point inside secur32.dll
  277.     (".*", "schannel.dll", "secur32.dll", ".*"),
  278.     # Ignore hooks of Secur32* that point inside SSPICLI
  279.     (".*", "Secur32.dll", "SSPICLI.DLL", ".*"),
  280.     # Ignore hooks that point inside known modules
  281.     (".*", ".*", "(kernel32|gdi32|advapi32|ntdll|shimeng|kernelbase|shlwapi|user32|cfgmgr32)", ".*"),
  282.     # Handle some known forwarded imports
  283.     (".*", ".*", ".*", "((Enter|Delete|Leave)CriticalSection|(Get|Set)LastError|Heap(ReAlloc|Free|Size|Alloc)|Rtl(Unwind|MoveMemory))"),
  284.     # Ignore sfc hooks going to sfc_os
  285.     (".*", "sfc\.dll", "sfc_os\.dll", ".*"),
  286.     # Ignore netapi32 hooks pointing at netutils or samcli
  287.     (".*", "netapi32\.dll", "(netutils|samcli)\.dll", ".*"),
  288.     (".*", "setupapi\.dll", "devrtl\.dll", ".*"),
  289.     ],
  290.     HOOK_MODE_USER | HOOKTYPE_EAT : [
  291.     # These modules have so many hooks its really not useful to check
  292.     (".*", "(msvcp|msvcr|mfc|wbemcomn|fastprox)", ".*", ".*"),
  293.     ],
  294.     HOOK_MODE_USER | HOOKTYPE_INLINE : [
  295.     # Ignore hooks in the pywin32 service process
  296.     ("pythonservice", ".*", ".*", ".*"),
  297.     # Many legit hooks land inside these modules
  298.     (".*", ".*", "(msvcr|advapi32|version|wbemcomn|ntdll|kernel32|kernelbase|sechost|ole32|shlwapi|user32|gdi32|ws2_32|shell32)", ".*"),
  299.     # Ignore hooks of the c runtime DLLs
  300.     (".*", "(msvc(p|r)\d{2}|mfc\d{2})\.dll", ".*", ".*"),
  301.     # This is a global variable
  302.     (".*", "msvcrt\.dll", ".*", "_acmdln"),
  303.     # Ignore hooks of MD5Final, MD5Init, MD5Update that point inside advapi32
  304.     (".*", ".*", "advapi32.dll", "MD5.+"),
  305.     # Ignore hooks of common firefox components
  306.     ("firefox\.exe", ".*", "(xul|mozcrt|nspr4)", ".*"),
  307.     # Ignore hooks created by Parallels VM software
  308.     (".*", "user32.dll", "prl_hook.dll", ".*"),
  309.     # Ignore DLL registration functions
  310.     (".*", ".*", ".*", "(DllCanUnloadNow|DllRegisterServer|DllUnregisterServer)"),
  311.     # Ignore netapi32 hooks pointing at netutils
  312.     (".*", "netapi32\.dll", "netutils\.dll", ".*"),
  313.     ],
  314.     HOOK_MODE_KERNEL | HOOKTYPE_IAT : [
  315.     (".*", ".*", "(win32k\.sys|hal\.dll|dump_wmilib\.sys|ntkrnlpa\.exe|ntoskrnl\.exe)", ".*"),
  316.     # Ignore hooks of the SCSI module which point inside the dump_scsiport module
  317.     (".*", "scsiport\.sys", "dump_scsiport\.sys", ".*"),
  318.     # Ignore other storage port hooks
  319.     (".*", "storport\.sys", "dump_storport\.sys", ".*"),
  320.     ],
  321.     HOOK_MODE_KERNEL | HOOKTYPE_EAT : [
  322.     ],
  323.     HOOK_MODE_KERNEL | HOOKTYPE_INLINE : [
  324.     # Ignore kernel hooks that point inside these modules
  325.     (".*", ".*", "(hal.dll|ndis.sys|ntkrnlpa.exe|ntoskrnl.exe)", ".*"),
  326.     ],
  327. }
  328.  
  329. class ApiHooks(procdump.ProcExeDump):
  330.     """Detect API hooks in process and kernel memory"""
  331.  
  332.     def __init__(self, config, *args, **kwargs):
  333.         procdump.ProcExeDump.__init__(self, config, *args, **kwargs)
  334.         config.remove_option("DUMP-DIR")
  335.  
  336.         config.add_option("NO-WHITELIST", short_option = 'N', default = False,
  337.                 action = 'store_true',
  338.                 help = 'No whitelist (show all hooks, can be verbose)')
  339.  
  340.         config.add_option("SKIP-KERNEL", short_option = 'R', default = False,
  341.                 action = 'store_true',
  342.                 help = 'Skip kernel mode checks')
  343.  
  344.         config.add_option("SKIP-PROCESS", short_option = 'P', default = False,
  345.                 action = 'store_true',
  346.                 help = 'Skip process checks')
  347.  
  348.         config.add_option("QUICK", short_option = 'Q', default = False,
  349.                 action = 'store_true',
  350.                 help = 'Work faster by only analyzing critical processes and dlls')
  351.  
  352.         self.compiled_rules = self.compile()
  353.  
  354.         # When the --quick option is set, we only scan the processes
  355.         # and dlls in these lists. Feel free to adjust them for
  356.         # your own purposes.
  357.         self.critical_process = ["explorer.exe", "svchost.exe", "lsass.exe",
  358.             "services.exe", "winlogon.exe", "csrss.exe", "smss.exe",
  359.             "wininit.exe", "iexplore.exe", "firefox.exe", "spoolsv.exe"]
  360.  
  361.         self.critical_dlls = ["ntdll.dll", "kernel32.dll", "ws2_32.dll",
  362.             "advapi32.dll", "secur32.dll", "crypt32.dll", "user32.dll",
  363.             "gdi32.dll", "shell32.dll", "shlwapi.dll", "lsasrv.dll",
  364.             "cryptdll.dll", "wsock32.dll", "mswsock.dll", "urlmon.dll",
  365.             "csrsrv.dll", "winsrv.dll", "wininet.dll"]
  366.  
  367.         # When scanning for calls to unknown code pages (UCP), only
  368.         # analyze the following drivers. This is based on an analysis of
  369.         # the modules rootkits are most likely to infect, but feel free
  370.         # to adjust it for your own purposes.
  371.         self.ucpscan_modules = ["tcpip.sys", "ntfs.sys", "fastfast.sys",
  372.             "wanarp.sys", "ndis.sys", "atapi.sys", "ntoskrnl.exe",
  373.             "ntkrnlpa.exe", "ntkrnlmp.exe"]
  374.  
  375.     @staticmethod
  376.     def is_valid_profile(profile):
  377.         return (profile.metadata.get('os', 'unknown') == 'windows' and
  378.                 profile.metadata.get('memory_model', '32bit') == '32bit')
  379.  
  380.     def compile(self):
  381.         """
  382.        Precompile the regular expression rules. Its quicker
  383.        if we do this once per plugin run, rather than once per
  384.        API hook that needs checking.
  385.        """
  386.         ret = dict()
  387.         for key, rules in whitelist_rules.items():
  388.             for rule in rules:
  389.                 ruleset = ((re.compile(rule[0], re.I), # Process name
  390.                             re.compile(rule[1], re.I), # Source module
  391.                             re.compile(rule[2], re.I), # Destination module
  392.                             re.compile(rule[3], re.I), # Function name
  393.                             ))
  394.                 if ret.has_key(key):
  395.                     ret[key].append(ruleset)
  396.                 else:
  397.                     ret[key] = [ruleset]
  398.         return ret
  399.  
  400.     def whitelist(self, rule_key, process, src_mod, dst_mod, function):
  401.         """Check if an API hook should be ignored due to whitelisting.
  402.  
  403.        @param rule_key: a key from the whitelist_rules dictionary which
  404.            describes the type of hook (i.e. Usermode IAT or Kernel Inline).
  405.  
  406.        @param process: name of the suspected victim process.
  407.  
  408.        @param src_mod: name of the source module whose function has been
  409.            hooked. this varies depending on whether we're dealing with IAT
  410.            EAT, inline, etc.
  411.  
  412.        @param dst_mod: name of the module that is the destination of the
  413.            hook pointer. this is usually the rootkit dll, exe, or sys,
  414.            however, in many cases there is no module name since the rootkit
  415.            is trying to be stealthy.
  416.  
  417.        @param function: name of the function that has been hooked.
  418.        """
  419.         # There are no whitelist rules for this hook type
  420.         if rule_key not in self.compiled_rules:
  421.             return False
  422.  
  423.         for rule in self.compiled_rules[rule_key]:
  424.             if (rule[0].search(process) != None and
  425.                     rule[1].search(src_mod) != None and
  426.                     rule[2].search(dst_mod) != None and
  427.                     rule[3].search(function) != None):
  428.                 return True
  429.  
  430.         return False
  431.  
  432.     def get_idt(self):
  433.         """
  434.        Build IDT to check for inline hooks that use interrupts, whitelist interrupts
  435.        that are handled by common kernel mode modules
  436.  
  437.        @returns: Dictionary of idt index and handler address
  438.        """
  439.         idt_data={}
  440.         for n, _, addr, module in idt.IDT(self._config).calculate():
  441.             for rule in self.compiled_rules[HOOK_MODE_KERNEL | HOOKTYPE_INLINE]:
  442.                 if module:
  443.                     module_name = str(module.BaseDllName or '')
  444.                 else:
  445.                     module_name = "UNKNOWN"
  446.                 if rule[2].search(module_name) is None:
  447.                     idt_data[n] = addr
  448.                 else:
  449.                     idt_data[n] = None
  450.         return idt_data
  451.  
  452.  
  453.     @staticmethod
  454.     def check_syscall(addr_space, module, module_group):
  455.         """
  456.        Enumerate syscall hooks in ntdll.dll. A syscall hook is one
  457.        that modifies the function prologue of an NT API function
  458.        (i.e. ntdll!NtCreateFile) or swaps the location of the sysenter
  459.        with a malicious address.
  460.  
  461.        @param addr_space: a process AS for the process containing the
  462.        ntdll.dll module.
  463.  
  464.        @param module: the _LDR_DATA_TABLE_ENTRY for ntdll.dll
  465.  
  466.        @param module_group: a ModuleGroup instance for the process.
  467.        """
  468.  
  469.         # Resolve the real location of KiFastSystem Call for comparison
  470.         KiFastSystemCall = module.getprocaddress("KiFastSystemCall")
  471.         KiIntSystemCall = module.getprocaddress("KiIntSystemCall")
  472.  
  473.         if not KiFastSystemCall or not KiIntSystemCall:
  474.             #debug.debug("Abort check_syscall, can't find KiFastSystemCall")
  475.             return
  476.  
  477.         # Add the RVA to make it absolute
  478.         KiFastSystemCall += module.DllBase
  479.         KiIntSystemCall += module.DllBase
  480.  
  481.         # Check each exported function if its an NT syscall
  482.         for _, f, n in module.exports():
  483.  
  484.             # Ignore forwarded exports
  485.             if not f:
  486.                 #debug.debug("Skipping forwarded export {0}".format(n or ''))
  487.                 continue
  488.  
  489.             function_address = module.DllBase + f
  490.  
  491.             if not addr_space.is_valid_address(function_address):
  492.                 #debug.debug("Function address {0:#x} for {1} is paged".format(
  493.                 #    function_address, n or ''))
  494.                 continue
  495.  
  496.             # Read enough of the function prologue for two instructions
  497.             data = addr_space.zread(function_address, 24)
  498.  
  499.             instructions = []
  500.  
  501.             for op in distorm3.Decompose(function_address, data, distorm3.Decode32Bits):
  502.                 if not op.valid:
  503.                     break
  504.                 if len(instructions) == 3:
  505.                     break
  506.                 instructions.append(op)
  507.  
  508.             i0 = instructions[0]
  509.             i1 = instructions[1]
  510.             i2 = instructions[2]
  511.  
  512.             # They both must be properly decomposed and have two operands  
  513.             if (not i0 or not i0.valid or len(i0.operands) != 2 or
  514.                     not i1 or not i1.valid or len(i1.operands) != 2):
  515.                 #debug.debug("Error decomposing prologue for {0} at {1:#x}".format(
  516.                 #    n or '', function_address))
  517.                 continue
  518.  
  519.             # Now check the instruction and operand types
  520.             if (i0.mnemonic == "MOV" and i0.operands[0].type == 'Register' and
  521.                     i0.operands[0].name == 'EAX' and i0.operands[1].type == 'Immediate' and
  522.                     i1.mnemonic == "MOV" and i1.operands[0].type == 'Register' and
  523.                     i1.operands[0].name == 'EDX' and i0.operands[1].type == 'Immediate'):
  524.  
  525.                 if i2.operands[0].type == "Register":
  526.                     # KiFastSystemCall is already in the register
  527.                     syscall_address = i1.operands[1].value
  528.                 else:
  529.                     # Pointer to where KiFastSystemCall is stored
  530.                     syscall_address = obj.Object('address',
  531.                         offset = i1.operands[1].value, vm = addr_space)
  532.  
  533.                 if syscall_address not in [KiFastSystemCall, KiIntSystemCall]:
  534.                     hook_module = module_group.find_module(syscall_address)
  535.                     hook = Hook(hook_type = HOOKTYPE_NT_SYSCALL,
  536.                                 hook_mode = HOOK_MODE_USER,
  537.                                 function_name = n or '',
  538.                                 function_address = function_address,
  539.                                 hook_address = syscall_address,
  540.                                 hook_module = hook_module,
  541.                                 victim_module = module,
  542.                                 )
  543.                     # Add the bytes that will later be disassembled in the
  544.                     # output to show exactly how the hook works. The first
  545.                     # hop is the ntdll!Nt* API and the next hop is the rootkit.
  546.                     hook.add_hop_chunk(function_address, data)
  547.                     hook.add_hop_chunk(syscall_address, addr_space.zread(syscall_address, 24))
  548.                     yield hook
  549.  
  550.     def check_ucpcall(self, addr_space, module, module_group):
  551.         """Scan for calls to unknown code pages.
  552.  
  553.        @param addr_space: a kernel AS
  554.  
  555.        @param module: the _LDR_DATA_TABLE_ENTRY to scan
  556.  
  557.        @param module_group: a ModuleGroup instance for the process.
  558.        """
  559.  
  560.         try:
  561.             dos_header = obj.Object("_IMAGE_DOS_HEADER",
  562.                 offset = module.DllBase, vm = addr_space)
  563.  
  564.             nt_header = dos_header.get_nt_header()
  565.         except (ValueError, exceptions.SanityCheckException), _why:
  566.             #debug.debug('get_nt_header() failed: {0}'.format(why))
  567.             return
  568.  
  569.         # Parse the PE sections for this driver
  570.         for sec in nt_header.get_sections(self._config.UNSAFE):
  571.  
  572.             # Only check executable sections
  573.             if not sec.Characteristics & 0x20000000:
  574.                 continue
  575.  
  576.             # Calculate the virtual address of this PE section in memory
  577.             sec_va = module.DllBase + sec.VirtualAddress
  578.  
  579.             # Extract the section's data and make sure its not all zeros
  580.             data = addr_space.zread(sec_va, sec.Misc.VirtualSize)
  581.  
  582.             if data == "\x00" * len(data):
  583.                 continue
  584.  
  585.             # Disassemble instructions in the section
  586.             for op in distorm3.DecomposeGenerator(sec_va, data, distorm3.Decode32Bits):
  587.  
  588.                 if (op.valid and ((op.flowControl == 'FC_CALL' and
  589.                         op.mnemonic == "CALL") or
  590.                         (op.flowControl == 'FC_UNC_BRANCH' and
  591.                         op.mnemonic == "JMP")) and
  592.                         op.operands[0].type == 'AbsoluteMemoryAddress'):
  593.  
  594.                     # This is ADDR, which is the IAT location
  595.                     const = op.operands[0].disp & 0xFFFFFFFF
  596.  
  597.                     # Abort if ADDR is not a valid address
  598.                     if not addr_space.is_valid_address(const):
  599.                         continue
  600.  
  601.                     # This is what [ADDR] points to - the absolute destination
  602.                     call_dest = obj.Object("address", offset = const, vm = addr_space)
  603.  
  604.                     # Abort if [ADDR] is not a valid address
  605.                     if not addr_space.is_valid_address(call_dest):
  606.                         continue
  607.  
  608.                     check1 = module_group.find_module(const)
  609.                     check2 = module_group.find_module(call_dest)
  610.  
  611.                     # If ADDR or [ADDR] point to an unknown code page
  612.                     if not check1 or not check2:
  613.                         hook = Hook(hook_type = HOOKTYPE_CODEPAGE_KERNEL,
  614.                                     hook_mode = HOOK_MODE_KERNEL,
  615.                                     function_name = "",
  616.                                     function_address = op.address,
  617.                                     hook_address = call_dest,
  618.                                     )
  619.                         # Add the location we found the call
  620.                         hook.add_hop_chunk(op.address,
  621.                             data[op.address - sec_va : op.address - sec_va + 24])
  622.  
  623.                         # Add the rootkit stub
  624.                         hook.add_hop_chunk(call_dest, addr_space.zread(call_dest, 24))
  625.                         yield hook
  626.  
  627.     def check_wsp(self, addr_space, module, module_group):
  628.         """
  629.        Check for hooks of non-exported WSP* functions. The
  630.        mswsock.dll module contains a global variable which
  631.        points to all the internal Winsock functions. We find
  632.        the function table by the reference from the exported
  633.        WSPStartup API.
  634.        
  635.        .text:6C88922E 8B 7D 50          mov     edi, [ebp+lpProcTable]
  636.        .text:6C889231 6A 1E             push    1Eh
  637.        .text:6C889233 59                pop     ecx
  638.        .text:6C889234 BE 40 64 8B 6C    mov     esi, offset _SockProcTable
  639.        .text:6C889239 F3 A5             rep movsd
  640.  
  641.        @param addr_space: process AS
  642.  
  643.        @param module: the _LDR_DATA_TABLE_ENTRY for mswsock.dll
  644.  
  645.        @param module_group: a ModuleGroup instance for the process.
  646.        """
  647.  
  648.         WSPStartup = module.getprocaddress("WSPStartup")
  649.  
  650.         if not WSPStartup:
  651.             #debug.debug("Abort check_wsp, can't find WSPStartup")
  652.             return
  653.  
  654.         WSPStartup += module.DllBase
  655.  
  656.         # Opcode pattern to look for
  657.         signature = "\x6A\x1E\x59\xBE"
  658.  
  659.         # Read enough bytes of the function to find our signature
  660.         data = addr_space.zread(WSPStartup, 300)
  661.  
  662.         if data == "\x00" * len(data):
  663.             #debug.debug("WSPStartup prologue is paged")
  664.             return
  665.  
  666.         offset = data.find(signature)
  667.  
  668.         if offset == -1:
  669.             #debug.debug("Can't find {0} in WSPStartup".format(repr(signature)))
  670.             return
  671.  
  672.         # Dereference the pointer as our _SockProcTable
  673.         p = obj.Object("address",
  674.             offset = WSPStartup + offset + len(signature),
  675.             vm = addr_space)
  676.  
  677.         p = p.dereference_as("_SOCK_PROC_TABLE")
  678.  
  679.         # Enumerate functions in the procedure table
  680.         for i, function_address in enumerate(p.Functions):
  681.  
  682.             function_owner = module_group.find_module(function_address)
  683.  
  684.             # The function points outside of mwsock, its hooked
  685.             if function_owner != module:
  686.  
  687.                 hook = Hook(hook_type = HOOKTYPE_WINSOCK,
  688.                             hook_mode = HOOK_MODE_USER,
  689.                             function_name = WINSOCK_TABLE[i],
  690.                             function_address = function_address,
  691.                             hook_module = function_owner,
  692.                             victim_module = module
  693.                             )
  694.                 hook.add_hop_chunk(function_address,
  695.                     addr_space.zread(function_address, 12))
  696.  
  697.                 yield hook
  698.             else:
  699.                 # The function points inside mwsock, check inline
  700.                 ret = self.check_inline(function_address, addr_space,
  701.                     module.DllBase, module.DllBase + module.SizeOfImage,
  702.                     self.idt)
  703.  
  704.                 if not ret:
  705.                     #debug.debug("Cannot analyze {0}".format(WINSOCK_TABLE[i]))
  706.                     continue
  707.  
  708.                 (hooked, data, hook_address) = ret
  709.  
  710.                 if hooked:
  711.                     hook_module = module_group.find_module(hook_address)
  712.                     if hook_module != module:
  713.                         hook = Hook(hook_type = HOOKTYPE_WINSOCK,
  714.                                     hook_mode = HOOK_MODE_USER,
  715.                                     function_name = WINSOCK_TABLE[i],
  716.                                     function_address = function_address,
  717.                                     hook_module = hook_module,
  718.                                     hook_address = hook_address,
  719.                                     victim_module = module
  720.                                     )
  721.                         hook.add_hop_chunk(function_address, data)
  722.                         hook.add_hop_chunk(hook_address, addr_space.zread(hook_address, 12))
  723.                         yield hook
  724.  
  725.     @staticmethod
  726.     def check_inline(va, addr_space, mem_start, mem_end, idt = None):
  727.         """
  728.        Check for inline API hooks. We check for direct and indirect
  729.        calls, direct and indirect jumps, and PUSH/RET combinations.
  730.  
  731.        @param va: the virtual address of the function to check
  732.  
  733.        @param addr_space: process or kernel AS where the function resides
  734.  
  735.        @param mem_start: base address of the module containing the
  736.            function being checked.
  737.  
  738.        @param mem_end: end address of the module containing the func
  739.            being checked.
  740.  
  741.        @param idt: whitelisted IDT interrupt handlers removed from the
  742.            dict. Not required for compatibility reasons.
  743.  
  744.        @returns: a tuple of (hooked, data, hook_address)
  745.        """
  746.  
  747.         data = addr_space.zread(va, 24)
  748.  
  749.         if data == "\x00" * len(data):
  750.             #debug.debug("Cannot read function prologue at {0:#x}".format(va))
  751.             return None
  752.  
  753.         outside_module = lambda x: x != None and (x < mem_start or x > mem_end)
  754.  
  755.         # Number of instructions disassembled so far
  756.         n = 0
  757.         # Destination address of hooks
  758.         d = None
  759.         # Save the last PUSH before a CALL
  760.         push_val = None
  761.         # Save the general purpose registers
  762.         regs = {}
  763.  
  764.         for op in distorm3.Decompose(va, data, distorm3.Decode32Bits):
  765.  
  766.             # Quit the loop when we have three instructions or when
  767.             # a decomposition error is encountered, whichever is first.
  768.             if not op.valid or n == 3:
  769.                 break
  770.  
  771.             if op.flowControl == 'FC_CALL':
  772.                 # Clear the push value
  773.                 if push_val:
  774.                     push_val = None
  775.                 if op.mnemonic == "CALL" and op.operands[0].type == 'AbsoluteMemoryAddress':
  776.                     # Check for CALL [ADDR]
  777.                     const = op.operands[0].disp & 0xFFFFFFFF
  778.                     d = obj.Object("unsigned int", offset = const, vm = addr_space)
  779.                     if outside_module(d):
  780.                         break
  781.                 elif op.operands[0].type == 'Immediate':
  782.                     # Check for CALL ADDR
  783.                     d = op.operands[0].value & 0xFFFFFFFF
  784.                     if outside_module(d):
  785.                         break
  786.                 elif op.operands[0].type == 'Register':
  787.                     # Check for CALL REG
  788.                     d = regs.get(op.operands[0].name)
  789.                     if d and outside_module(d):
  790.                         break
  791.             elif op.flowControl == 'FC_UNC_BRANCH' and op.mnemonic == "JMP":
  792.                 # Clear the push value
  793.                 if push_val:
  794.                     push_val = None
  795.                 if op.size > 2:
  796.                     if op.operands[0].type == 'AbsoluteMemoryAddress':
  797.                         # Check for JMP [ADDR]
  798.                         const = op.operands[0].disp & 0xFFFFFFFF
  799.                         d = obj.Object("unsigned int", offset = const, vm = addr_space)
  800.                         if outside_module(d):
  801.                             break
  802.                     elif op.operands[0].type == 'Immediate':
  803.                         # Check for JMP ADDR
  804.                         d = op.operands[0].value & 0xFFFFFFFF
  805.                         if outside_module(d):
  806.                             break
  807.                 elif op.size == 2 and op.operands[0].type == 'Register':
  808.                     # Check for JMP REG
  809.                     d = regs.get(op.operands[0].name)
  810.                     if d and outside_module(d):
  811.                         break
  812.             elif op.flowControl == 'FC_NONE':
  813.                 # Check for PUSH followed by a RET
  814.                 if (op.mnemonic == "PUSH" and
  815.                         op.operands[0].type == 'Immediate' and op.size == 5):
  816.                     # Set the push value
  817.                     push_val = op.operands[0].value & 0xFFFFFFFF
  818.                 # Check for moving imm values into a register
  819.                 if (op.mnemonic == "MOV" and op.operands[0].type == 'Register'
  820.                         and op.operands[1].type == 'Immediate'):
  821.                     # Clear the push value
  822.                     if push_val:
  823.                         push_val = None
  824.                     # Save the value put into the register
  825.                     regs[op.operands[0].name] = op.operands[1].value
  826.             elif op.flowControl == 'FC_RET':
  827.                 if push_val:
  828.                     d = push_val
  829.                     if outside_module(d):
  830.                         break
  831.                 # This causes us to stop disassembling when
  832.                 # reaching the end of a function
  833.                 break
  834.             elif op.flowControl == "FC_INT" and idt:
  835.                 # Clear the push value
  836.                 if push_val:
  837.                     push_val = None
  838.                 # Check for INT, ignore INT3
  839.                 if op.mnemonic == "INT" and op.size > 1 and op.operands[0].type == 'Immediate':
  840.                     # Check interrupt handler address
  841.                     d = idt[op.operands[0].value]
  842.                     if d and outside_module(d):
  843.                         break
  844.             n += 1
  845.  
  846.         # Check EIP after the function prologue
  847.         if outside_module(d):
  848.             return True, data, d
  849.         else:
  850.             return False, data, d
  851.  
  852.     def gather_stuff(self, _addr_space, module):
  853.         """Use the Volatility object classes to enumerate
  854.        imports and exports. This function can be overriden
  855.        to use pefile instead for speed testing"""
  856.  
  857.         # This is a dictionary where keys are the names of imported
  858.         # modules and values are lists of tuples (ord, addr, name).
  859.         imports = {}
  860.         exports = [(o, module.DllBase + f, n) for o, f, n in module.exports()]
  861.  
  862.         for dll, o, f, n in module.imports():
  863.             dll = dll.lower()
  864.             if dll in imports:
  865.                 imports[dll].append((o, f, n))
  866.             else:
  867.                 imports[dll] = [(o, f, n)]
  868.  
  869.         return imports, exports
  870.  
  871.     def get_hooks(self, hook_mode, addr_space, module, module_group):
  872.         """Enumerate IAT, EAT, Inline hooks. Also acts as a dispatcher
  873.        for NT syscall, UCP scans, and winsock procedure table hooks.
  874.  
  875.        @param hook_mode: one of the HOOK_MODE_* constants
  876.  
  877.        @param addr_space: a process AS or kernel AS
  878.  
  879.        @param module: an _LDR_DATA_TABLE_ENTRY for the module being
  880.        checked for hooks.
  881.  
  882.        @param module_group: a ModuleGroup instance for the process.
  883.        """
  884.  
  885.         # We start with the module base name. If that's not available,
  886.         # trim the full name down to its base name.
  887.         module_name = (str(module.BaseDllName or '') or
  888.                        ntpath.basename(str(module.FullDllName or '')))
  889.  
  890.         # Lowercase for string matching
  891.         module_name = module_name.lower()
  892.  
  893.         if hook_mode == HOOK_MODE_USER:
  894.             if module_name == "ntdll.dll":
  895.                 for hook in self.check_syscall(addr_space, module, module_group):
  896.                     yield hook
  897.             elif module_name == "mswsock.dll":
  898.                 for hook in self.check_wsp(addr_space, module, module_group):
  899.                     yield hook
  900.         else:
  901.             if module_name in self.ucpscan_modules:
  902.                 for hook in self.check_ucpcall(addr_space, module, module_group):
  903.                     yield hook
  904.  
  905.         imports, exports = \
  906.             self.gather_stuff(addr_space, module)
  907.  
  908.         for dll, functions in imports.items():
  909.  
  910.             valid_owners = module_group.mod_name.get(dll, [])
  911.             if not valid_owners:
  912.                 #debug.debug("Cannot find any modules named {0}".format(dll))
  913.                 continue
  914.  
  915.             for (_, f, n) in functions:
  916.  
  917.                 if not f:
  918.                     #debug.debug("IAT function {0} is paged or ordinal".format(n or ''))
  919.                     continue
  920.  
  921.                 if not addr_space.is_valid_address(f):
  922.                     continue
  923.  
  924.                 function_owner = module_group.find_module(f)
  925.  
  926.                 if function_owner not in valid_owners:
  927.                     hook = Hook(hook_type = HOOKTYPE_IAT,
  928.                                 hook_mode = hook_mode,
  929.                                 function_name = n or '',
  930.                                 hook_address = f,
  931.                                 hook_module = function_owner,
  932.                                 victim_module = dll, # only for IAT hooks
  933.                                 )
  934.                     # Add the rootkit code
  935.                     hook.add_hop_chunk(f, addr_space.zread(f, 24))
  936.                     yield hook
  937.  
  938.         for _, f, n in exports:
  939.  
  940.             if not f:
  941.                 #debug.debug("EAT function {0} is paged".format(n or ''))
  942.                 continue
  943.  
  944.             function_address = f
  945.  
  946.             if not addr_space.is_valid_address(function_address):
  947.                 continue
  948.  
  949.             # Get the module containing the function
  950.             function_owner = module_group.find_module(function_address)
  951.  
  952.             # This is a check for EAT hooks
  953.             if function_owner != module:
  954.                 hook = Hook(hook_type = HOOKTYPE_EAT,
  955.                             hook_mode = hook_mode,
  956.                             function_name = n or '',
  957.                             hook_address = function_address,
  958.                             hook_module = function_owner,
  959.                             )
  960.                 hook.add_hop_chunk(function_address,
  961.                     addr_space.zread(function_address, 24))
  962.                 yield hook
  963.  
  964.                 # No need to check for inline hooks if EAT is hooked
  965.                 continue
  966.  
  967.             ret = self.check_inline(function_address, addr_space,
  968.                 module.DllBase, module.DllBase + module.SizeOfImage,
  969.                 self.idt)
  970.  
  971.             if ret == None:
  972.                 #debug.debug("Cannot analyze {0}".format(n or ''))
  973.                 continue
  974.  
  975.             (hooked, data, dest_addr) = ret
  976.  
  977.             if not hooked:
  978.                 continue
  979.  
  980.             if not addr_space.is_valid_address(dest_addr):
  981.                 continue
  982.  
  983.             function_owner = module_group.find_module(dest_addr)
  984.             if function_owner != module:
  985.                 # only do this for kernel hooks
  986.                 #if params['mode'] == HOOK_MODE_KERNEL:
  987.                 #    if owner:
  988.                 #        if self.in_data_section(owner, status['destaddr']):
  989.                 #            continue
  990.  
  991.                 hook = Hook(hook_type = HOOKTYPE_INLINE,
  992.                             hook_mode = hook_mode,
  993.                             function_name = n or '',
  994.                             function_address = function_address,
  995.                             hook_address = dest_addr,
  996.                             hook_module = function_owner,
  997.                             victim_module = module,
  998.                             )
  999.                 # Add the function prologue
  1000.                 hook.add_hop_chunk(function_address, data)
  1001.                 # Add the first redirection
  1002.                 hook.add_hop_chunk(dest_addr, addr_space.zread(dest_addr, 24))
  1003.                 yield hook
  1004.  
  1005.     def calculate(self):
  1006.  
  1007.         addr_space = utils.load_as(self._config)
  1008.  
  1009.         if not has_distorm3:
  1010.             debug.error("Install distorm3 code.google.com/p/distorm/")
  1011.  
  1012.         if not self.is_valid_profile(addr_space.profile):
  1013.             debug.error("This command does not support the selected profile.")
  1014.  
  1015.         self.idt = self.get_idt()
  1016.  
  1017.         if not self._config.SKIP_PROCESS:
  1018.             for proc in self.filter_tasks(tasks.pslist(addr_space)):
  1019.                 process_name = str(proc.ImageFileName).lower()
  1020.  
  1021.                 if (self._config.QUICK and
  1022.                         process_name not in self.critical_process):
  1023.                     #debug.debug("Skipping non-critical process {0} ({1})".format(
  1024.                     #    process_name, proc.UniqueProcessId))
  1025.                     continue
  1026.  
  1027.                 process_space = proc.get_process_address_space()
  1028.                 if not process_space:
  1029.                     #debug.debug("Cannot acquire process AS for {0} ({1})".format(
  1030.                     #    process_name, proc.UniqueProcessId))
  1031.                     continue
  1032.  
  1033.                 module_group = ModuleGroup(proc.get_load_modules())
  1034.  
  1035.                 for dll in module_group.mods:
  1036.  
  1037.                     if not process_space.is_valid_address(dll.DllBase):
  1038.                         continue
  1039.  
  1040.                     dll_name = str(dll.BaseDllName or '').lower()
  1041.  
  1042.                     if (self._config.QUICK and
  1043.                             dll_name not in self.critical_dlls and
  1044.                             dll.DllBase != proc.Peb.ImageBaseAddress):
  1045.                         #debug.debug("Skipping non-critical dll {0} at {1:#x}".format(
  1046.                         #    dll_name, dll.DllBase))
  1047.                         continue
  1048.  
  1049.                     #debug.debug("Analyzing {0}!{1}".format(process_name, dll_name))
  1050.  
  1051.                     for hook in self.get_hooks(HOOK_MODE_USER,
  1052.                             process_space, dll, module_group):
  1053.                         yield proc, dll, hook
  1054.  
  1055.         if not self._config.SKIP_KERNEL:
  1056.             process_list = list(tasks.pslist(addr_space))
  1057.             module_group = ModuleGroup(modules.lsmod(addr_space))
  1058.  
  1059.             for mod in module_group.mods:
  1060.  
  1061.                 #module_name = str(mod.BaseDllName or '')
  1062.                 #debug.debug("Analyzing {0}".format(module_name))
  1063.  
  1064.                 kernel_space = tasks.find_space(addr_space,
  1065.                     process_list, mod.DllBase)
  1066.  
  1067.                 if not kernel_space:
  1068.                     #debug.debug("No kernel AS for {0} at {1:#x}".format(
  1069.                     #    module_name, mod.DllBase))
  1070.                     continue
  1071.  
  1072.                 for hook in self.get_hooks(HOOK_MODE_KERNEL,
  1073.                         kernel_space, mod, module_group):
  1074.                     yield None, mod, hook
  1075.  
  1076.     def render_text(self, outfd, data):
  1077.         for process, module, hook in data:
  1078.  
  1079.             if not self._config.NO_WHITELIST:
  1080.  
  1081.                 if process:
  1082.                     process_name = str(process.ImageFileName)
  1083.                 else:
  1084.                     process_name = ''
  1085.  
  1086.                 if self.whitelist(hook.hook_mode | hook.hook_type,
  1087.                                     process_name, hook.VictimModule,
  1088.                                     hook.HookModule, hook.Function):
  1089.                     #debug.debug("Skipping whitelisted function: {0} {1} {2} {3}".format(
  1090.                     #    process_name, hook.VictimModule, hook.HookModule,
  1091.                     #    hook.Function))
  1092.                     continue
  1093.  
  1094.             outfd.write("*" * 72 + "\n")
  1095.             outfd.write("Hook mode: {0}\n".format(hook.Mode))
  1096.             outfd.write("Hook type: {0}\n".format(hook.Type))
  1097.  
  1098.             if process:
  1099.                 outfd.write('Process: {0} ({1})\n'.format(
  1100.                     process.UniqueProcessId, process.ImageFileName))
  1101.  
  1102.             outfd.write("Victim module: {0} ({1:#x} - {2:#x})\n".format(
  1103.                 str(module.BaseDllName or '') or ntpath.basename(str(module.FullDllName or '')),
  1104.                 module.DllBase, module.DllBase + module.SizeOfImage))
  1105.  
  1106.             outfd.write("Function: {0}\n".format(hook.Detail))
  1107.             outfd.write("Hook address: {0:#x}\n".format(hook.hook_address))
  1108.             outfd.write("Hooking module: {0}\n\n".format(hook.HookModule))
  1109.  
  1110.             for n, info in enumerate(hook.disassembled_hops):
  1111.                 (address, data) = info
  1112.                 s = ["{0:#x} {1:<16} {2}".format(o, h, i)
  1113.                         for o, i, h in
  1114.                         malfind.Disassemble(data, int(address))
  1115.                     ]
  1116.                 outfd.write("Disassembly({0}):\n{1}".format(n, "\n".join(s)))
  1117.                 outfd.write("\n\n")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement