#!/usr/bin/env python

from struct import unpack
from pefile import PE
from memutil import virt2phys, PagedOutException, offsets, read_range
from hexutil import hd

def read_section(memdump,pdba,sect,img_base):
    """Read the contents of a PE section from memory. This must be done
    a page at a time, as adjacent virtual memory addresses may reference
    completely different areas of physical memory.
    Arguments:
        memdump - file pointer to the memory dump image
        pdba - address of the page directory
        sect - a Section object from pefile
        img_base - the executable's base address (necessary to calculate
                section offsets)
    Returns: a string containing the contents of the section
    """

    accum = ''
    # DUBIOUS DECISION: should we read SizeOfRawData bytes or Misc_VirtualSize?
    section_start = img_base + sect.VirtualAddress
    section_end = img_base + sect.VirtualAddress + sect.SizeOfRawData
    return read_range(memdump,pdba,section_start,section_end)

def reconstruct_PE(memdump,pe,pdba):
    """Rebuild an on-disk PE file from an image in memory.
    Arguments:
        memdump - a file pointer to the memory dump
        pe - a PE object from pefile
        pdba - the address of the page directory for this process
    Returns: a string representing the executable
    """
    
    img_base = pe.OPTIONAL_HEADER.ImageBase
    rebuilt_exe = ''

    # Add the pe header:
    # Dubious decision: the PE header is in the first page at ImageBase
    img_base_real = virt2phys(memdump,pdba,img_base)
    memdump.seek(img_base_real)
    rebuilt_exe += memdump.read(4096)
    
    for sect in pe.sections:
        print "DEBUG: Starting section %s" % sect.Name
        section_data = read_section(memdump,pdba,sect,img_base)
        # If there is a gap, fill it with zeroes
        if sect.PointerToRawData > len(rebuilt_exe):
            rebuilt_exe += '\0' * (sect.PointerToRawData - len(rebuilt_exe))
        # If there is overlap, later section takes precedence, ie, truncate
        elif sect.PointerToRawData < len(rebuilt_exe):
            rebuilt_exe = rebuilt_exe[:sect.PointerToRawData]
        rebuilt_exe += section_data
    return rebuilt_exe

def unpack_le(str):
    """Silly helper function to convert 4 characters to a little-endian unsigned int"""
    return unpack("<L", str)[0]

if __name__ == "__main__":
    from optparse import OptionParser

    usage = "usage: %prog [options] <memory dump> <EPROCESS offset>"
    parser = OptionParser(usage=usage)
    parser.add_option("-e", "--executable", dest="exename",
                      help="write reconstructed executable out to FILE [default: [EPROCESS].exe]",
                      metavar='FILE')
    parser.add_option("-o", "--operating-system", dest="osname", default="XPSP2",
            help=("operating system memory dump comes from"
                  " [default: %%default, options: %s]" % ",".join(offsets.keys())))
    
    (options, args) = parser.parse_args()

    if len(args) != 2:
        import sys
        parser.print_help()
        sys.exit(1)

    print "Done parsing arguments."
    
    memdump = open(args[0], 'rb')
    eproc_offset = int(args[1], 0)
    offs = offsets[options.osname]
    if options.exename:
        output_file = open(options.exename, 'wb')
    else:
        output_file = open(str(eproc_offset) + '.exe', 'wb')

    memdump.seek(eproc_offset)
    eproc_struct = memdump.read(offs["EPROC_SIZE"])

    pdba = unpack_le(eproc_struct[offs["PDBA_OFFSET"]:offs["PDBA_OFFSET"]+4])

    peb_addr_virt = unpack_le(eproc_struct[offs["PEB_OFFSET"]:offs["PEB_OFFSET"]+4])
    peb_addr_real = virt2phys(memdump, pdba, peb_addr_virt)
    print "DEBUG: PEB found at %x (%x)" % (peb_addr_virt,peb_addr_real)
    memdump.seek(peb_addr_real + offs["PEB_IMG_BASE"])
    img_base_virt = unpack_le(memdump.read(4))
    img_base_real = virt2phys(memdump, pdba, img_base_virt)

    memdump.seek(img_base_real)
    pe_page = memdump.read(4096)

    pe = PE(data=pe_page, fast_load=True)
    output_file.write(reconstruct_PE(memdump, pe, pdba))
    output_file.close()
    memdump.close()   
