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

Copyright (C) 2011-2014  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
'''

import atexit
import datetime
import optparse
import sys
import traceback

from inception import cfg, util, terminal, sound, memory
from inception.exceptions import InceptionException


CORE_MODULES = ['test', 'unload']

term = terminal.Terminal()


def banner():
    '''
    Print obligatory awesome ASCII banner
    '''
    if (term.width() > 79):
        print('''
 _|  _|      _|    _|_|_|  _|_|_|_|  _|_|_|    _|_|_|  _|    _|_|    _|      _|
 _|  _|_|    _|  _|        _|        _|    _|    _|    _|  _|    _|  _|_|    _|
 _|  _|  _|  _|  _|        _|_|_|    _|_|_|      _|    _|  _|    _|  _|  _|  _|
 _|  _|    _|_|  _|        _|        _|          _|    _|  _|    _|  _|    _|_|
 _|  _|      _|    _|_|_|  _|_|_|_|  _|          _|    _|    _|_|    _|      _|
''')
    else:
        print('''
INCEPTION
        ''')

    term.write('v.{0} (C) Carsten Maartmann-Moe {1}'.format(
        cfg.version, datetime.date.today().strftime('%Y')), indent=False
    )
    term.write('Download: {0} | Twitter: @breaknenter'.format(cfg.url),
               indent=False)
    print()


def add_options(parser):
    parser.add_option('-i', '--interface',
                      dest='interface',
                      help='set the interface to attack through. The default '
                      'is FireWire.',
                      default='firewire')
    parser.add_option('-f', '--filename',
                      dest='filename',
                      help='use a file instead of FireWire data as input; '
                      'for example to facilitate attacks on VMware machine '
                      'memory files (.vmem) and to ease testing and '
                      'signature creation. Must be used with the "file" '
                      'interface.')
    parser.add_option('-v', '--verbose',
                      action='store_true',
                      dest='verbose',
                      help='verbose mode - among other things, this prints '
                      'read data to stdout, useful for debugging.')
    parser.add_option('-d', '--delay',
                      dest='delay',
                      help='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 {0} seconds.'.format(cfg.delay),
                      default=cfg.delay)
    parser.add_option('--sound',
                      action='store_true',
                      dest='sound',
                      help='sound, inception style.')


def main(argv):

    try:
        cfg.encoding = sys.getdefaultencoding()
        cfg.os = util.detectos()

        # Register cleanup function
        atexit.register(util.cleanup)
        
        banner()
        
        # Parse arguments and options
        description = 'Inception is a physical memory manipulation ' \
                      'and hacking tool exploiting PCI-based DMA.'
        usage = 'Usage: %prog module [options]'
        parser = optparse.OptionParser(usage=usage, description=description)
        add_options(parser)
        try:
            prog = sys.argv[0]
            command = sys.argv[1]

            if not command.startswith('-'):
                module = __import__('inception.modules.{0}'
                                    .format(command),
                                    fromlist=['inception.modules'])
                group = optparse.OptionGroup(parser, 'Module description and '
                                             'options', module.info)
                module.add_options(group)
                parser.add_option_group(group)
            else:
                parser.epilog = 'For module-specific help, type: {0} [module '
                'name] -h/--help'.format(prog)
        except IndexError as e:
            parser.print_help()
            sys.exit()
        except ImportError as e:
            term.fail(e)

        opts, args = parser.parse_args()

        if opts.sound:
            cfg.eggs.append(sound.play('resources/rien.mp3'))

        # Load the selected interface
        try:
            interface = __import__('inception.interfaces.{0}'
                                   .format(opts.interface),
                                   fromlist=['inception.interfaces'])
        except ImportError as e:
            term.fail(e)

        # Initialize the interface, and wrap in memory space wrapper
        memspace = None
        if command not in CORE_MODULES:
            device, memsize = interface.initialize(opts, module)
            memspace = memory.MemorySpace(device, memsize)

        # Here we go
        module.run(opts, memspace)

        # Successful
        term.info('BRRRRRRRAAAAAWWWWRWRRRMRMRMMRMRMMMMM!!!')
        if opts.sound:
            sound.play('resources/inception.wav')

    # Catch inception-related errors and print them as warnings
    # TODO unless in debug mode?
    except InceptionException as e:
        term.error(e)

    # Catch whatever that hasn't been catched elsewhere
    except Exception as e:
        term.warn('Something went dreadfully wrong, full stack trace below: '
                  '{0}'.format(e))
        term.separator()
        traceback.print_exc()
        term.separator()

    # Catch user interrupts
    except KeyboardInterrupt:
        print()  # New line
        term.warn('User aborted')
        pass

    if memspace:
        memspace.release()

    sys.exit()


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