#!python
"""This is the user space program which is required for dumping images using the
winpmem driver.

   Copyright 2012 Michael Cohen <scudette@gmail.com>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
"""
from __future__ import print_function
__author__ = "Michael Cohen <scudette@gmail.com>"

import win32service
import win32file
import struct
import sys
import os
import time
import optparse

parser = optparse.OptionParser()
parser.add_option("-d", "--driver",
                  help="The driver location (winpmem.sys)", metavar="FILE")

parser.add_option("-f", "--filename", dest="filename", default="pmemdump.raw",
                  help="write image to FILE", metavar="FILE")

parser.add_option("-n", "--name", default="pmem",
                  help="The name of the device.")

parser.add_option("-m", "--mode", default="physical",
                  help="The acquisition mode. Can be (physical or iospace)")

parser.add_option("-l", "--load", default=False, action="store_true",
                  help="Only load the driver and immediately quit. "
                  "(Useful just before attaching with volatility)")

parser.add_option("-u", "--unload", default=False, action="store_true",
                  help="Unload the driver and immediately quit. ")


def CTL_CODE(DeviceType, Function, Method, Access):
    return (DeviceType<<16) | (Access << 14) | (Function << 2) | Method


# IOCTLS for interacting with the driver.
CTRL_IOCTRL = CTL_CODE(0x22, 0x101, 0, 3)
INFO_IOCTRL = CTL_CODE(0x22, 0x103, 0, 3)
INFO_IOCTRL_DEPRECATED = CTL_CODE(0x22, 0x100, 0, 3)


class Image(object):
    """This class abstracts the image."""
    buffer_size = 1024 * 1024

    def __init__(self, fd):
        self.fd = fd
        self.SetMode()
        self.ParseMemoryRuns()

        # Tell the driver what acquisition mode we want.
        self.GetInfo()
        #self.GetInfoDeprecated()

    def GetInfoDeprecated(self):
        result = win32file.DeviceIoControl(self.fd, INFO_IOCTRL_DEPRECATED, "",
                                           1024, None)
        fmt_string = "QQl"
        offset = struct.calcsize(fmt_string)

        cr3, kpcr, number_of_runs = struct.unpack_from(fmt_string, result)
        for x in range(number_of_runs):
            start, length = struct.unpack_from("QQ", result, x * 16 + offset)
            print("0x%X\t\t0x%X" % (start, length))

    FIELDS = (["CR3", "NtBuildNumber", "KernBase", "KDBG"] +
              ["KPCR%02d" % i for i in range(32)] +
              ["PfnDataBase", "PsLoadedModuleList", "PsActiveProcessHead"] +
              ["Padding%s" % i for i in range(0xff)] +
              ["NumberOfRuns"])

    def ParseMemoryRuns(self):
        self.runs = []

        result = win32file.DeviceIoControl(
            self.fd, INFO_IOCTRL, "", 102400, None)

        fmt_string = "Q" * len(self.FIELDS)
        self.memory_parameters = dict(zip(self.FIELDS, struct.unpack_from(
                    fmt_string, result)))

        self.dtb = self.memory_parameters["CR3"]
        self.kdbg = self.memory_parameters["KDBG"]

        offset = struct.calcsize(fmt_string)

        for x in range(self.memory_parameters["NumberOfRuns"]):
            start, length = struct.unpack_from("QQ", result, x * 16 + offset)
            self.runs.append((start, length))

    def GetInfo(self):
        for k, v in sorted(self.memory_parameters.items()):
            if k.startswith("Pad"):
                continue

            if not v: continue

            print("%s: \t%#08x (%s)" % (k, v, v))

        print("Memory ranges:")
        print("Start\t\tEnd\t\tLength")

        for start, length in self.runs:
            print("0x%X\t\t0x%X\t\t0x%X" % (start, start+length, length))

    def SetMode(self):
        if FLAGS.mode == "iospace":
            mode = 0
        elif FLAGS.mode == "physical":
            mode = 1
        elif FLAGS.mode == "pte":
            mode = 2
        elif FLAGS.mode == "pte_pci":
            mode = 3
        else:
            raise RuntimeError("Mode %s not supported" % FLAGS.mode)

        win32file.DeviceIoControl(
            self.fd, CTRL_IOCTRL, struct.pack("I", mode), 0, None)

    def PadWithNulls(self, outfd, length):
        while length > 0:
            to_write = min(length, self.buffer_size)
            outfd.write("\x00" * to_write)
            length -= to_write

    def DumpWithRead(self, output_filename):
        """Read the image and write all the data to a raw file."""
        with open(output_filename, "wb") as outfd:
            offset = 0
            for start, length in self.runs:
                if start > offset:
                    print("\nPadding from 0x%X to 0x%X\n" % (offset, start))
                    self.PadWithNulls(outfd, start - offset)

                offset = start
                end = start + length
                while offset < end:
                    to_read = min(self.buffer_size, end - offset)
                    win32file.SetFilePointer(self.fd, offset, 0)

                    _, data = win32file.ReadFile(self.fd, to_read)
                    outfd.write(data)

                    offset += to_read

                    offset_in_mb = offset/1024/1024
                    if not offset_in_mb % 50:
                        sys.stdout.write("\n%04dMB\t" % offset_in_mb)

                    sys.stdout.write(".")
                    sys.stdout.flush()

def main():
    """Load the driver and image the memory."""
    # Check the driver is somewhere
    if not FLAGS.driver or not os.access(FLAGS.driver, os.R_OK):
        print("You must specify a valid driver file.")
        sys.exit(-1)

    # Must have absolute path here.
    driver = os.path.join(os.getcwd(), FLAGS.driver)
    hScm = win32service.OpenSCManager(
        None, None, win32service.SC_MANAGER_CREATE_SERVICE)

    try:
        hSvc = win32service.CreateService(
            hScm, FLAGS.name, FLAGS.name,
            win32service.SERVICE_ALL_ACCESS,
            win32service.SERVICE_KERNEL_DRIVER,
            win32service.SERVICE_DEMAND_START,
            win32service.SERVICE_ERROR_IGNORE,
            driver,
            None, 0, None, None, None)
    except win32service.error as e:
        print(e)
        hSvc = win32service.OpenService(hScm, FLAGS.name,
                                        win32service.SERVICE_ALL_ACCESS)

    # Make sure the service is stopped.
    try:
        win32service.ControlService(hSvc, win32service.SERVICE_CONTROL_STOP)
    except win32service.error:
        pass

    if FLAGS.unload:
        print("unloaded winpmem driver.")
        return

    try:
        win32service.StartService(hSvc, [])
    except win32service.error as e:
        print("%s: will try to continue" % e)

    if FLAGS.load:
        fd = win32file.CreateFile(
            "\\\\.\\" + FLAGS.name,
            win32file.GENERIC_READ | win32file.GENERIC_WRITE,
            win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,
            None,
            win32file.OPEN_EXISTING,
            win32file.FILE_ATTRIBUTE_NORMAL,
            None)

        print(r"Loaded the winpmem driver. You can now attach "
              r"volatility to \\.\pmem")
        image = Image(fd)

        return

    try:
        fd = win32file.CreateFile(
            "\\\\.\\" + FLAGS.name,
            win32file.GENERIC_READ | win32file.GENERIC_WRITE,
            win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,
            None,
            win32file.OPEN_EXISTING,
            win32file.FILE_ATTRIBUTE_NORMAL,
            None)

        try:
            t = time.time()
            image = Image(fd)
            print("Imaging to %s" % FLAGS.filename)
            image.DumpWithRead(FLAGS.filename)
            print("\nCompleted in %s seconds" % (time.time() - t))
        finally:
            win32file.CloseHandle(fd)
    finally:
        try:
            win32service.ControlService(hSvc, win32service.SERVICE_CONTROL_STOP)
        except win32service.error:
            pass
        win32service.DeleteService(hSvc)
        win32service.CloseServiceHandle(hSvc)

if __name__ == "__main__":
    (FLAGS, args) = parser.parse_args()
    main()
