''' Simple setup file for using the Unicorn emulator
    Copyright (C) 2017-2019 eleemosynator

    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 3 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, see <https://www.gnu.org/licenses/>.
'''
# Generic tools for emulating scraps of code
import struct
from unicorn import *
from unicorn.x86_const import *
import capstone
import sys
from py_re_util import *

try:
    import pefile
except:
    pass

try:
    from elftools.elf.elffile import ELFFile
except:
    pass

def trim_asciiz(s):
    if s == '\0':
        return ''
    elif '\0' in s:
        return s[:s.find('\0')]
    else:
        return s

def show_strings(tag, strs, offsets=None):
    if offsets is None:
        offsets = sorted(strs.keys())
    print 'stack strings for ' + tag + ':'
    print '\n'.join([ ' %03x %s' % (k, strs[k]) for k in offsets ]) + '\n'

class Emu(object):
    def __init__(self):
        self.mu = None
        self.shim_base = 0
        self.shim_len = 0x4000

    def dis(self, va_start, va_end, max_insn = -1):
        for insn in self.md.disasm(self.mu.mem_read(va_start, va_end - va_start), va_start):
            if max_insn == 0:
                break
            max_insn -= 1
            print '0x%06x %s %s' % (insn.address, insn.mnemonic, insn.op_str)

    #setup memory map and reg names for mode
    def set_mode(self, mode):
        if mode == UC_MODE_32:
            self.md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_32)
            self.reg_rsp = UC_X86_REG_ESP
            self.reg_rbp = UC_X86_REG_EBP
            self.reg_rip = UC_X86_REG_EIP
        elif mode == UC_MODE_64:
            self.md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
            self.reg_rsp = UC_X86_REG_RSP
            self.reg_rbp = UC_X86_REG_RBP
            self.reg_rip = UC_X86_REG_RIP
        elif mode == UC_MODE_16:
            self.md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_16)
            self.reg_rsp = UC_X86_REG_SP
            self.reg_rbp = UC_X86_REG_BP
            self.reg_rip = UC_X86_REG_IP
        else:
            raise Exception('Unknown x86 mode: %d' % mode)
        self.mode = mode


    def init_pe(self, filename):
        if 'pefile' not in globals():
            raise Exception('emu_helper: Please install pefile before loading PE binaries')
        self.pe = pefile.PE(filename)
        machine = self.pe.FILE_HEADER.Machine
        if machine == 0x014c:
            self.set_mode(UC_MODE_32)
        elif machine == 0x8664:
            self.set_mode(UC_MODE_64)
        else:
            raise Exception('Uknown Machine type: 0x%04x' % machine)
        self.base = self.pe.OPTIONAL_HEADER.ImageBase
#        print 'loading base: 0x%08x' % self.base
        mem = self.pe.get_memory_mapped_image()
#        print 'image: %08x' % len(mem)
        # we'll map mem at base, then our shim and then stack
        self.shim_base = self.base + ((len(mem) + 0x1fff) & 0xfffff000)
        self.stack_len = 0x10000
        self.mu = Uc(UC_ARCH_X86, self.mode)
        self.mu.mem_map(self.base, self.shim_base - self.base + self.shim_len + self.stack_len)
        self.mu.mem_write(self.base, mem)
        self.rsp0 = self.shim_base + self.shim_len + self.stack_len
        self.init_stack()
        return

    def init_binary_pe(self, filename, base, mode_bits):
        if mode_bits == 32:
            self.set_mode(UC_MODE_32)
        elif mode_bits == 64:
            self.set_mode(UC_MODE_64)
        else:
            raise Exception('Unsupported mode buts: %d' % mode_bits)
        self.base = base
#        print 'loading base: 0x%08x' % self.base
        mem = open(filename, 'rb').read()
#        print 'image: %08x' % len(mem)
        # we'll map mem at base, then our shim and then stack
        self.shim_base = self.base + ((len(mem) + 0x1fff) & 0xfffff000)
        self.stack_len = 0x10000
        self.mu = Uc(UC_ARCH_X86, self.mode)
        self.mu.mem_map(self.base, self.shim_base - self.base + self.shim_len + self.stack_len)
        self.mu.mem_write(self.base, mem)
        self.rsp0 = self.shim_base + self.shim_len + self.stack_len
        self.init_stack()
        return

    def init_elf(self, filename):
        if 'ELFFile' not in globals():
            raise Exception('emu_helper: Please install pyelftools before loading ELF binaries')
        self.elf = ELFFile(open(filename, 'rb'))
        # loadable segments
        segs = filter(lambda x: x.header.p_type == 'PT_LOAD', self.elf.iter_segments())
        self.base = min(map(lambda x: x.header.p_vaddr, segs))
        # FIXME
        self.set_mode(UC_MODE_64)
        self.mu = Uc(UC_ARCH_X86, self.mode)
        mem_top = self.base
        for seg in segs:
            va = seg.header.p_vaddr
            vbot = va & ~0xfff
            vtop = (seg.header.p_vaddr + seg.header.p_memsz + 0xfff) & ~0xfff
            print 'map 0x%08x .. 0x%08x' % (vbot, vtop)
            self.mu.mem_map(vbot, vtop - vbot)
            self.mu.mem_write(va, seg.data())
            mem_top = max(mem_top, vtop)
        self.shim_base = mem_top
        self.stack_len = 0x10000
        print 'shim_base at: 0x%08x' % self.shim_base
        self.mu.mem_map(self.shim_base, self.shim_len + self.stack_len)
        self.rsp0 = self.shim_base + self.shim_len + self.stack_len
        self.init_stack()

    def init_stack(self, delta = 0):
        self.mu.reg_write(self.reg_rsp, self.rsp0 - delta)
        self.mu.reg_write(self.reg_rbp, self.rsp0)

    def get_stack_strings(self, va_start, va_end, rbp_offset_list):
        maxdata = max(rbp_offset_list)
        self.init_stack()
        rbp = self.rsp0
        self.mu.mem_write(rbp - maxdata, '\0' * maxdata)
        # we don't setup any args or check esp etc
        #self.dis(va_start, va_end)
        self.mu.emu_start(va_start, va_end)
        data = self.mu.mem_read(rbp - maxdata, maxdata)
        #hexdump(data)
        return { x:trim_asciiz(data[maxdata - x:]) for x in rbp_offset_list }

    def get_stack_blob(self, va_start, va_end, rbp_offset, size):
        self.init_stack();
        rbp = self.rsp0
        self.mu.mem_write(rbp - rbp_offset, '\0' * rbp_offset)
        self.mu.emu_start(va_start, va_end)
        return self.mu.mem_read(rbp - rbp_offset, size)


    def show_stack_strings(self, tag, va_start, va_end, rbp_offset_list):
        strs = self.get_stack_strings(va_start, va_end, rbp_offset_list)
        show_strings(tag, strs, rbp_offset_list)


if __name__ == '__main__':
    emu = Emu()
    emu.init_pe('notepad.exe')
    emu.show_stack_strings('main', 0x1013a0b, 0x1013c42, [ 0x10, 0xd8, 0xac, 0xc0, 0x84, 0x98 ])
    emu.show_stack_strings('munge_binary', 0x10146CB, 0x10149D3, [ 0x24, 0x14, 0x5c, 0x23c, 0x224, 0x200 ])
    #print get_stack_string(mu, 0x1013a0b, 0x1013a43, 0x10)
    #print get_stack_string(mu, 0x1013a43, 0x1013add, 0xd8)
    #print get_stack_string(mu, 0x1013add, 0x1013b38, 0xac)
    #print get_stack_string(mu, 0x1013b38, 0x1013bbd, 0xc0)
    #print get_stack_string(mu, 0x1013bbd, 0x1013bf5, 0x84)
    #print get_stack_string(mu, 0x1013bf5, 0x1013c42, 0x98)