# Volatility
#
# 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 

"""
@author:       Andrew Case
@license:      GNU General Public License 2.0 or later
@contact:      atcuno@gmail.com
@organization: Digital Forensics Solutions
"""

import volatility.obj as obj
import volatility.debug as debug
import volatility.plugins.linux.common as linux_common

class linux_lsmod(linux_common.AbstractLinuxCommand):
    """Gather loaded kernel modules"""

    def __init__(self, config, *args):

        linux_common.AbstractLinuxCommand.__init__(self, config, *args)
        self._config.add_option('SECTIONS', short_option = 'S', default = None, help = 'show section addresses', action = 'store_true')
        self._config.add_option('PARAMS', short_option = 'P', default = None, help = 'show module parameters', action = 'store_true')

    def get_param_val(self, param, _over = 0):

        ints = {
                self.get_profile_symbol("param_get_invbool", sym_type = "Pointer") : "int",
                self.get_profile_symbol("param_get_bool", sym_type = "Pointer") : "int",
                self.get_profile_symbol("param_get_int", sym_type = "Pointer") : "int",
                self.get_profile_symbol("param_get_ulong", sym_type = "Pointer") : "unsigned long",
                self.get_profile_symbol("param_get_long", sym_type = "Pointer") : "long",
                self.get_profile_symbol("param_get_uint", sym_type = "Pointer") : "unsigned int",
                self.get_profile_symbol("param_get_ushort", sym_type = "Pointer") : "unsigned short",
                self.get_profile_symbol("param_get_short", sym_type = "Pointer") : "short",
                self.get_profile_symbol("param_get_byte", sym_type = "Pointer") : "char",
               }

        getfn = param.get

        if getfn == 0:
            val = ""

        elif getfn == self.get_profile_symbol("param_array_get"):

            val = ""

            arr = param.arr
            overwrite = param.arr

            if arr.num:
                maxi = arr.num.dereference()
            else:
                maxi = arr.max

            for i in range(maxi):

                if i > 0:
                    val = val + ","

                arg = arr.elem + arr.elemsize * i
                overwrite.arg = arg

                mret = self.get_param_val(overwrite)
                val = val + str(mret or '')

        elif getfn == self.get_profile_symbol("param_get_string"):
            val = param.str.dereference_as("String", length = param.str.maxlen)

        elif getfn == self.get_profile_symbol("param_get_charp"):
            addr = obj.Object("Pointer", offset = param.arg, vm = self.addr_space)
            if addr == 0:
                val = "(null)"
            else:
                val = addr.dereference_as("String", length = 256)

        elif getfn.v() in ints:
            val = obj.Object(ints[getfn.v()], offset = param.arg, vm = self.addr_space)

            if getfn == self.get_profile_symbol("param_get_bool"):
                if val:
                    val = 'Y'
                else:
                    val = 'N'

            if getfn == self.get_profile_symbol("param_get_invbool"):
                if val:
                    val = 'N'
                else:
                    val = 'Y'

        else:
            print "Unknown get_fn: {0:#x}".format(getfn)
            return None

        return val

    def get_params(self, module):

        param_array = obj.Object(theType = 'Array', offset = module.kp, vm = self.addr_space, targetType = 'kernel_param', count = module.num_kp)

        params = ""

        for param in param_array:

            val = self.get_param_val(param)

            params = params + "{0}={1} ".format(param.name.dereference_as("String", length = 255), val)

        return params

    def get_sect_count(self, grp):
        idx = 0

        arr = obj.Object(theType = 'Array', offset = grp.attrs, vm = self.addr_space, targetType = 'Pointer', count = 25)

        while arr[idx]:
            idx = idx + 1

        return idx

    def get_sections(self, module):
        if hasattr(module.sect_attrs, "nsections"):
            num_sects = module.sect_attrs.nsections
        else:
            num_sects = self.get_sect_count(module.sect_attrs.grp)

        attrs = obj.Object(theType = 'Array', offset = module.sect_attrs.attrs.obj_offset, vm = self.addr_space, targetType = 'module_sect_attr', count = num_sects)

        sects = []

        for attr in attrs:
            name = attr.get_name()

            sects.append((name, attr.address))

        return sects

    def calculate(self):
        linux_common.set_plugin_members(self)
        modules_addr = self.get_profile_symbol("modules")

        modules = obj.Object("list_head", vm = self.addr_space, offset = modules_addr)

        # walk the modules list
        for module in modules.list_of_type("module", "list"):

            #if str(module.name) == "rootkit":
            #    continue

            if self._config.PARAMS:

                if not hasattr(module, "kp"):
                    debug.error("Gathering module parameters is not supported in this profile.")

                params = self.get_params(module)
            else:
                params = ""

            if self._config.SECTIONS:
                sections = self.get_sections(module)
            else:
                sections = []

            yield (module, sections, params)

    def render_text(self, outfd, data):

        for (module, sections, params)  in data:
            outfd.write("{0:s} {1:d}\n".format(module.name, module.init_size + module.core_size))

            # will be empty list if not set on command line
            for sect in sections:

                (name, address) = sect

                outfd.write("\t{0:30s} {1:#x}\n".format(name, address))

            # will be "" if not set, otherwise will be space seperated
            if params != "":

                for param in params.split():
                    outfd.write("\t{0:100s}\n".format(param))

    # returns a list of tuples of (name, .text start, .text end) for each module
    # include_list can contain a list of only the modules wanted by a plugin
    def get_modules(self, include_list = None):

        if not include_list:
            include_list = []

        ret = []

        for (module, _sections, _params) in self.calculate():

            if len(include_list) == 0 or str(module.name) in include_list:

                start = module.module_core
                end = start + module.core_size
                ret.append(("%s" % module.name, start, end))

        return ret


