Exemplo n.º 1
0
class Console(cmd.Cmd):
    def __init__(self, options):
        cmd.Cmd.__init__(self)
        self.__options = options
        if not options.isWindows():
            import readline
            old_delims = readline.get_completer_delims()
            old_delims = old_delims.replace('-', '')
            old_delims = old_delims.replace('/', '')
            readline.set_completer_delims(old_delims)
            #ää Fix completion on mac os
            import rlcompleter
            if 'libedit' in readline.__doc__:
                readline.parse_and_bind("bind ^I rl_complete")
            else:
                readline.parse_and_bind("tab: complete")

        self.__rs = RopperService(self.__options.ropper_options,
                                  callbacks=CallbackClass(self))
        self.__currentFileName = ''
        self.__cprinter = ConsolePrinter()
        self.__dataPrinter = {}
        self.__updatePrompt()

    @property
    def cprinter(self):
        return self.__cprinter

    @property
    def currentFileName(self):
        if not self.__currentFileName:
            raise RopperError('No binary loaded')
        return self.__currentFileName

    @property
    def currentFile(self):
        return self.__rs.getFileFor(self.currentFileName)

    def cmdloop(self):
        try:
            cmd.Cmd.cmdloop(self)
        except KeyboardInterrupt:
            print()
            self.cmdloop()

    def emptyline(self):
        pass

    def __getDataPrinter(self, type):
        p = self.__dataPrinter.get(type)
        if not p:
            p = FileDataPrinter.create(type)
            self.__dataPrinter[type] = p

        return p

    def start(self):
        if self.__options.version:
            self.__printVersion()
            return

        if self.__options.clear_cache:
            self.__rs.clearCache()

        if self.__options.file and self.__options.asm is None:
            for file in self.__options.file:
                self.__loadFile(file)
            if len(self.__options.file) > 1:
                self.do_file('1')

        if self.__options.console:
            self.cmdloop()

        self.__handleOptions(self.__options)

    def __updatePrompt(self):
        if self.__currentFileName:
            name = getFileNameFromPath(self.__currentFileName)
            self.prompt = cstr(
                '(%s/%s/%s)> ' %
                (name, str(self.currentFile.type), self.currentFile.arch),
                Color.RED)
        else:
            self.prompt = cstr('(ropper)> ', Color.RED)

    def __loadFile(self, file):

        self.__rs.addFile(file,
                          raw=self.__options.raw,
                          arch=self.__options.arch)
        self.__options.arch = None

        self.__currentFileName = file
        self.__updatePrompt()
        if self.__options.I is not None:
            self.__rs.setImageBaseFor(file, self.__options.I)
        if not self.__options.no_load and self.__options.console:
            self.__loadGadgets()

        #self.__binary.printer = FileDataPrinter.create(self.__binary.type)

    def __printGadget(self, gadget, detailed=False):
        if detailed:
            self.__cprinter.println(gadget)
        else:
            self.__cprinter.println(gadget.simpleString())

    def __printData(self, data):
        cf = self.currentFile
        self.__getDataPrinter(cf.type).printData(cf.loader, data)

    def __printVersion(self):
        self.__cprinter.println("Version: Ropper %s" %
                                '.'.join([str(x) for x in ropper.VERSION]))
        self.__cprinter.println("Author: Sascha Schirra")
        self.__cprinter.println("Website: http://scoding.de/ropper\n")

    def __printHelpText(self, cmd, desc):
        self.__cprinter.println('{}  -  {}\n'.format(cmd, desc))

    def __printError(self, error):
        self.__cprinter.printError(error)

    def __printInfo(self, info):
        self.__cprinter.printInfo(cstr(info))

    def __printSeparator(self, before='', behind=''):
        self.__cprinter.println(before + '-' * 40 + behind)

    def __setASLR(self, enable):
        self.currentFile.loader.setASLR(enable)

    def __setNX(self, enable):
        self.currentFile.loader.setNX(enable)

    def __set(self, option, enable):
        if option == 'aslr':
            self.__setASLR(enable)
        elif option == 'nx':
            self.__setNX(enable)
        else:
            raise ArgumentError('Invalid option: {}'.format(option))

    def __asm(self, code, arch, format):
        r = Ropper()
        if format == 'R':
            f = 'raw'
        elif format == 'H':
            f = 'hex'
        elif format == 'S':
            f = 'string'
        else:
            raise RopperError('wrong format: %s' % f)

        self.__cprinter.println(self.__rs.asm(code, arch, f))

    def __disasm(self, code, arch):
        r = Ropper()
        self.__cprinter.println(self.__rs.disasm(code, arch))

    def __searchJmpReg(self, regs):
        regs = regs.split(',')
        gadgets = self.__rs.searchJmpReg(name=self.currentFileName, regs=regs)
        self.__printGadgets([g for g in gadgets.values()][0],
                            header='JMP Instructions')

    def __searchOpcode(self, opcode):
        gadgets = self.__rs.searchOpcode(name=self.currentFileName,
                                         opcode=opcode)
        self.__printGadgets([g for g in gadgets.values()][0], header='Opcode')

    def __searchInstructions(self, code):
        gadgets = self.__rs.searchInstructions(name=self.currentFileName,
                                               code=code)
        self.__printGadgets([g for g in gadgets.values()][0],
                            header='Instructions')

    def __searchPopPopRet(self):
        pprs = self.__rs.searchPopPopRet(self.currentFileName)
        self.__printGadgets([g for g in pprs.values()][0],
                            header='POP;POP;RET Instructions')

    def __loadGadgetsForAllFiles(self):
        self.__rs.loadGadgetsFor()

    def __loadGadgets(self):
        self.__searchGadgetsFor(self.currentFileName)

    def __searchGadgetsFor(self, binary):

        self.__rs.loadGadgetsFor(binary)
        return self.__rs.getFileFor(binary).gadgets

    def __printGadgets(self,
                       gadgets,
                       category=None,
                       header='Gadgets',
                       detailed=False):
        self.__getDataPrinter(self.currentFile.type).printTableHeader(header)

        counter = 0
        for g in gadgets:
            if not category or category == g.category[0]:
                self.__printGadget(g, detailed=detailed)
                counter += 1

        self.__cprinter.println('\n%d gadgets found' % counter)

    def __printProgress(self, gadget, gnr, count):
        if gnr >= 0:
            self.__cprinter.printProgress('clearing up...', float(gnr) / count)
        else:
            self.__cprinter.printProgress('clearing up...', 1)
            self.__cprinter.finishProgress()

    def __loadAllGadgets(self):
        self.__rs.loadGadgetsFor()

    def __printGadgetsFromCurrentFile(self):
        gadgets = self.currentFile.gadgets
        self.__printGadgets(gadgets, detailed=self.__options.detailed)

    def __search(self, filter, quality=None):
        self.__printInfo('Searching for gadgets: ' + filter)
        old = None
        for fc, gadget in self.__rs.search(filter, quality):
            if fc != old:
                old = fc
                self.__cprinter.println()
                self.__printInfo('File: %s' % fc)

            self.__printGadget(gadget, self.__options.detailed)
        self.__cprinter.println()

    def __generateChain(self, command):
        split = command.split(' ')
        try:
            old = self.__rs.options.color
            generator = split[0]
            options = {}
            if len(split) > 1:
                for option in split[1:]:
                    if option.count('=') == 0 or option.count('=') > 1:
                        raise RopperError(
                            'Wrong option format. An option has to be set in the following format: option=value'
                        )
                    key, value = option.split('=')
                    options[key] = value
            try:

                self.__rs.options.color = False
                chain = self.__rs.createRopChain(generator,
                                                 str(self.currentFile.arch),
                                                 options)

                #generator = RopChain.get(self.__binaries, self.__gadgets, split[0], self.__ropchainInfoCallback, unhexlify(self.__options.badbytes))

                self.__printInfo('generating rop chain')
                # self.__printSeparator(behind='\n\n')

                self.__cprinter.println(chain)
                # self.__printSeparator(before='\n\n')
                self.__printInfo('rop chain generated!')
            except RopperError as e:
                self.__rs.options.color = old
                self.__printError(e)
        except BaseException as e:
            self.__rs.options.color = old
            print(traceback.format_exc())

            raise e
        self.__rs.options.color = old

    def __ropchainInfoCallback(self, message):
        if message.startswith('[*]'):
            self.__cprinter.puts('\r' + message)
        self.__cprinter.printInfo(message)

    def __setarch(self, arch):
        if self.currentFile:
            self.__rs.setArchitectureFor(self.currentFileName, arch)
            self.__updatePrompt()
        else:
            self.__printError('No file loaded')

    def __printStrings(self, string, sec=None):
        strings = self.__rs.searchString(string=string,
                                         name=self.currentFileName)
        strings = [s for s in strings.values()][0]
        strings = [(cstr(toHex(addr), Color.RED), cstr(s))
                   for addr, s in strings]
        printTable('Strings', (cstr('Address'), cstr('Value')), strings)

    def __disassembleAddress(self, addr, length):
        ds = self.__rs.disassAddress(self.currentFileName, addr, length)
        if len(ds.split('\n')) < length:
            self.__cprinter.printInfo(
                'Cannot disassemble specified count of instructions')
        self.__getDataPrinter(
            self.currentFile.type).printTableHeader('Instructions')
        self.__cprinter.println(ds)

    def __printSectionInHex(self, section):
        section = self.currentFile.loader.getSection(section)
        if section.bytes:
            printHexFormat(section.bytes, section.virtualAddress,
                           not self.__rs.options.color)
        else:
            self.__printInfo('No bytes to print')

    @safe_cmd
    def __handleOptions(self, options):
        if options.sections:
            self.__printData('sections')
        elif options.analyse:
            self.__loadGadgets()
            #self.do_analyse(options.analyse)
        elif options.semantic:
            self.__loadGadgets()
            self.do_semantic(options.semantic)
        elif options.symbols:
            self.__printData('symbols')
        elif options.segments:
            self.__printData('segments')
        elif options.dllcharacteristics:
            self.__printData('dll_characteristics')
        elif options.imagebase:
            self.__printData('image_base')
        elif options.e:
            self.__printData('entry_point')
        elif options.imports:
            self.__printData('imports')
        elif options.asm is not None:
            format = 'H'
            if options.file is not None:
                with open(options.file[0]) as f:
                    code = f.read()
                if len(options.asm) > 0:
                    format = options.asm[0]
            else:
                code = options.asm[0]
                if len(options.asm) == 2:
                    code = options.asm[0]
                    format = options.asm[1]
            arch = 'x86'
            if options.arch:
                arch = options.arch
            self.__asm(code, arch, format)
        elif options.disasm:
            code = options.disasm
            arch = 'x86'
            if options.arch:
                arch = options.arch
            self.__disasm(code, arch)
        elif options.set:
            self.__set(options.set, True)
        elif options.unset:
            self.__set(options.unset, False)
        elif options.info:
            self.__printData('information')
        elif options.ppr:
            self.__searchPopPopRet()
        elif options.jmp:
            self.__searchJmpReg(options.jmp)
        elif options.stack_pivot:
            self.__loadGadgets()
            self.__printGadgets(self.currentFile.gadgets, Category.STACK_PIVOT)
        elif options.opcode:
            self.__searchOpcode(self.__options.opcode)
        elif options.instructions:
            self.__searchInstructions(self.__options.instructions)
        elif options.string:
            self.__printStrings(options.string, options.section)
        elif options.hex and options.section:
            self.__printSectionInHex(options.section)
        elif options.disassemble_address:
            split = options.disassemble_address.split(':')
            length = 1
            if not isHex(split[0]):
                raise RopperError('Number have to be in hex format 0x....')

            if len(split) > 1:
                if split[1][1:].isdigit() or (
                        len(split[1]) >= 3 and split[1][1] == '-'
                        and split[1][2:].isdigit()):  # is L\d or L-\d
                    length = int(split[1][1:])
                else:
                    raise RopperError(
                        'Length have to be in the following format L + Number e.g. L3'
                    )
            self.__disassembleAddress(int(split[0], 16), length)
        # elif options.checksec:
        #   self.__checksec()
        elif options.chain:
            self.__loadGadgetsForAllFiles()
            self.__generateChain(options.chain)
        elif self.__options.file:
            self.__loadGadgets()
            if options.search:
                self.__search(options.search, options.quality)
            else:
                self.__printGadgetsFromCurrentFile()


####### cmd commands ######

    @safe_cmd
    def do_show(self, text):
        if len(text) == 0:
            self.help_show()
            return

        self.__printData(text)

    def help_show(self):
        desc = 'shows informations about the loaded file'
        if self.__getDataPrinter(self.currentFile.type):
            desc += ('Available informations:\n' + ('\n'.join(
                self.__getDataPrinter(
                    self.currentFile.type).availableInformations)))
        self.__printHelpText('show <info>', desc)

    def complete_show(self, text, line, begidx, endidx):
        if self.__getDataPrinter(self.currentFile.type):
            return [
                i for i in self.__getDataPrinter(
                    self.currentFile.type).availableInformations
                if i.startswith(text)
            ]

    @safe_cmd
    def do_close(self, text):

        if text.isdigit():
            idx = int(text)
            if len(self.__rs.files) > idx - 1:
                self.__rs.removeFile(self.__rs.files[idx - 1].loader.fileName)
                if len(self.__rs.files) != 0:
                    self.__currentFileName = self.__rs.files[0].loader.fileName
                else:
                    self.__currentFileName = None
                self.__updatePrompt()
            else:
                self.__cprinter.printError('Index is too small or to large')
        elif text == 'all':
            for file in self.__rs.files:
                self.__rs.removeFile(file.loader.fileName)
            self.__currentFileName = None
            self.__updatePrompt()
        else:
            self.help_close()

    def help_close(self):
        self.__printHelpText(
            'close idx/all',
            'closes opened files\nidx - index of file which should be closed\nall - closes all files'
        )

    @safe_cmd
    def do_file(self, text):
        if len(text) == 0:
            data = []
            for index, binary in enumerate(self.__rs.files):
                if self.currentFileName == binary.loader.fileName:
                    data.append(
                        (cstr(index + 1), cstr(binary.loader.fileName + '*'),
                         cstr(binary.arch), cstr(binary.loaded)))
                else:
                    data.append((cstr(index + 1), cstr(binary.loader.fileName),
                                 cstr(binary.arch), cstr(binary.loaded)))

            printTable('Opened Files', (cstr('No.'), cstr('Name'),
                                        cstr('Architecture'), cstr('Loaded')),
                       data)

        elif text.isdigit():
            idx = int(text) - 1
            if idx >= len(self.__rs.files):
                raise RopperError('Index is too small or to large')
            self.__currentFileName = self.__rs.files[idx].loader.fileName
            self.__updatePrompt()
            self.__printInfo('File \'%s\' selected.' % self.currentFileName)
        else:
            self.__loadFile(text)
            self.__printInfo('File loaded.')

    def complete_file(self, text, line, begidx, endidx):
        file = text
        cwd = '.'
        path = ''
        if '/' in file:
            cwd = file[:file.rindex('/') + 1]
            file = file[file.rindex('/') + 1:]
            path = cwd

        return [path + i for i in os.listdir(cwd) if i.startswith(file)]

    def help_file(self):
        self.__printHelpText(
            'file [<file>|<idx>]',
            '\nno argument shows all opened files\n<file> - loads the file <file>\n<idx> - select the file with number <idx>'
        )

    @safe_cmd
    def do_set(self, text):
        if not text:
            self.help_set()
            return

        self.__set(text, True)

    def help_set(self):
        desc = """Sets options.
Options:
aslr\t- Sets the ASLR-Flag (PE)
nx\t- Sets the NX-Flag (ELF|PE)"""
        self.__printHelpText('set <option>', desc)

    def complete_set(self, text, line, begidx, endidx):
        return [i for i in ['aslr', 'nx'] if i.startswith(text)]

    @safe_cmd
    def do_unset(self, text):
        if not text:
            self.help_unset()
            return

        self.__set(text, False)

    def help_unset(self):
        desc = """Clears options.
Options:
aslr\t- Clears the ASLR-Flag (PE)
nx\t- Clears the NX-Flag (ELF|PE)"""
        self.__printHelpText('unset <option>', desc)

    def complete_unset(self, text, line, begidx, endidx):
        return self.complete_set(text, line, begidx, endidx)

    @safe_cmd
    def do_gadgets(self, text):

        if not self.currentFile.loaded:
            self.__printInfo('Gadgets have to be loaded with load')
            return

        self.__printGadgets(self.currentFile.gadgets,
                            detailed=self.__rs.options.detailed)

    def help_gadgets(self):
        self.__printHelpText('gadgets', 'shows all loaded gadgets')

    @safe_cmd
    def do_load(self, text):
        if text == 'all':
            self.__loadAllGadgets()
        else:
            self.__loadGadgets()
            self.__printInfo('gadgets loaded.')

    def help_load(self):
        self.__printHelpText(
            'load [all]',
            '\nall - loads gadgets of all opened files\nwithout argument loads gadgets of current file'
        )

    @safe_cmd
    def do_ppr(self, text):
        self.__searchPopPopRet()

    def help_ppr(self):
        self.__printHelpText('ppr', 'shows all pop,pop,ret instructions')

    @safe_cmd
    def do_search(self, text):
        if len(text) == 0:
            self.help_search()
            return
        match = re.match('/\d+/', text)
        qual = None
        if match:
            qual = int(match.group(0)[1:-1])
            text = text[len(match.group(0)):].strip()
        self.__search(text, qual)

    def help_search(self):
        desc = 'search gadgets.\n\n'
        desc += '/quality/\tThe quality of the gadget (1 = best).'
        desc += 'The better the quality the less instructions are between the found intruction and ret\n'
        desc += '?\t\tany character\n%\t\tany string\n\n'
        desc += 'Example:\n'
        desc += 'search mov e?x\n\n'
        desc += '0x000067f1: mov edx, dword ptr [ebp + 0x14]; mov dword ptr [esp], edx; call eax;\n'
        desc += '0x00006d03: mov eax, esi; pop ebx; pop esi; pop edi; pop ebp; ret ;\n'
        desc += '0x00006d6f: mov ebx, esi; mov esi, dword ptr [esp + 0x18]; add esp, 0x1c; ret ;\n'
        desc += '0x000076f8: mov eax, dword ptr [eax]; mov byte ptr [eax + edx], 0; add esp, 0x18; pop ebx; ret ;\n\n\n'
        desc += 'search mov [%], edx\n\n'
        desc += '0x000067ed: mov dword ptr [esp + 4], edx; mov edx, dword ptr [ebp + 0x14]; mov dword ptr [esp], edx; call eax;\n'
        desc += '0x00006f4e: mov dword ptr [ecx + 0x14], edx; add esp, 0x2c; pop ebx; pop esi; pop edi; pop ebp; ret ;\n'
        desc += '0x000084b8: mov dword ptr [eax], edx; ret ;\n'
        desc += '0x00008d9b: mov dword ptr [eax], edx; add esp, 0x18; pop ebx; ret ;\n\n\n'
        desc += 'search /1/ mov [%], edx\n\n'
        desc += '0x000084b8: mov dword ptr [eax], edx; ret ;\n'

        self.__printHelpText('search [/<quality>/] <string>', desc)

    @safe_cmd
    def do_inst(self, text):
        if len(text) == 0:
            self.help_inst()
            return

        self.__searchInstructions(text)

    def help_inst(self):
        self.__printHelpText('inst <instructions>',
                             'searchs instructions in executable sections')

    @safe_cmd
    def do_opcode(self, text):
        if len(text) == 0:
            self.help_opcode()
            return

        self.__searchOpcode(text)

    def help_opcode(self):
        self.__printHelpText(
            'opcode <opcode>',
            'searchs opcode in executable sections\nExample:\nopcode ffe4\nopcode ff4?\nopcode ff??\n\nNot allowed:\nopcode ff?4'
        )

    @safe_cmd
    def do_imagebase(self, text):
        if len(text) == 0:
            self.__rs.setImageBaseFor(self.currentFileName, None)
            self.__printInfo('Imagebase reseted')
        elif isHex(text):
            self.__rs.setImageBaseFor(self.currentFileName, int(text, 16))
            self.__printInfo('Imagebase set to %s' % text)
        else:
            self.help_imagebase()

    def help_imagebase(self):
        self.__printHelpText(
            'imagebase [<base>]',
            'sets a new imagebase. An empty imagebase sets the imagebase to the original value.'
        )

    @safe_cmd
    def do_type(self, text):
        if len(text) == 0:
            self.help_type()
            return

        self.do_settings('type %s' % text)

    def help_type(self):
        self.__printHelpText(
            'type <type>',
            'sets the gadget type (rop, jop, sys, all, default:all)')

    @safe_cmd
    def do_jmp(self, text):
        if len(text) == 0:
            self.help_jmp()
            return

        self.__searchJmpReg(text)

    def help_jmp(self):
        self.__printHelpText('jmp <reg[,reg...]>',
                             'searchs jmp reg instructions')

    @safe_cmd
    def do_detailed(self, text):
        self.do_settings('detailed %s' % text)

    def help_detailed(self):
        self.__printHelpText('detailed [on|off]',
                             'sets detailed gadget output')

    def complete_detailed(self, text, line, begidx, endidx):
        return [i for i in ['on', 'off'] if i.startswith(text)]

    @safe_cmd
    def do_settings(self, text):
        if len(text):
            try:
                splits = text.strip().split(' ')
                if len(splits) == 1:
                    if splits[0] == 'color':
                        self.__rs.options[splits[0]] = True
                    else:
                        self.__rs.options[splits[0]] = None
                elif len(splits) == 2:
                    if splits[1] in ['on', 'off']:
                        self.__rs.options[
                            splits[0]] = True if splits[1] == 'on' else False
                    elif splits[0] in ('inst_count', 'count_of_findings'):
                        self.__rs.options[splits[0]] = int(splits[1])
                    else:
                        self.__rs.options[splits[0]] = splits[1]

                else:
                    raise RopperError('Invalid setting')
            except TypeError as e:
                raise RopperError(e)
            except AttributeError as e:
                raise RopperError(e)
        else:
            data = []
            desc = {
                'cfg_only':
                'if on gadgets are filtered for use in CFG exploits (only PE)',
                'all':
                'If on shows all found gadgets including double gadgets',
                'color':
                'If on output is colored',
                'badbytes':
                'Gadget addresses are not allowed to contain this bytes',
                'type':
                'The file is scanned for this type of gadgets. (rop, jop, sys, all)',
                'detailed':
                'If on the gadgets will be printed with more detailed information',
                'inst_count':
                'The max count of instructions in a gadgets',
                'count_of_findings':
                'The max count of findings which will be printed with semantic search (0 = undefined, default: 5)',
                'multiprocessing':
                'If on multiple processes will be used for gadget scanning (not supported on Windows.)'
            }
            for key, value in self.__rs.options.items():
                if isinstance(value, bool):
                    data.append((cstr(key), cstr('on' if value else 'off'),
                                 cstr(desc.get(key, ''))))
                else:
                    data.append((cstr(key), cstr(value), cstr(desc[key])))

            printTable('Settings',
                       (cstr('Name'), cstr('Value'), cstr('Description')),
                       data)

    def help_settings(self):
        self.__printHelpText(
            'settings',
            'shows the current settings or set the settings\nHow to set:\nsettings badbytes 00 - sets badbytes to 00\nsettings badbytes - sets badbytes to default (empty)'
        )

    @safe_cmd
    def do_badbytes(self, text):
        if len(text) == 0:
            self.__printInfo('badbytes cleared')

        self.do_settings('badbytes %s' % text)

        # for binary in self.__binaries:
        #     if binary.loaded:
        #         self.__gadgets[binary] = ropper.filterBadBytes(binary.gadgets, self.__options.badbytes)

        #         if not self.__options.all:
        #             self.__gadgets[binary] = ropper.deleteDuplicates(self.__gadgets[binary])

        self.__cprinter.printInfo('Filter gadgets')

    def help_badbytes(self):
        self.__printHelpText(
            'badbytes [bytes]',
            'sets/clears bad bytes\n\n Example:\nbadbytes 000a0d  -- sets 0x00, 0x0a and 0x0d as badbytes'
        )

    @safe_cmd
    def do_color(self, text):
        if self.__options.isWindows():
            self.__printInfo('No color support for windows')
            return
        self.do_settings('color %s' % text)

    def help_color(self):
        self.__printHelpText('color [on|off]', 'sets colorized output')

    def complete_color(self, text, line, begidx, endidx):
        return [i for i in ['on', 'off'] if i.startswith(text)]

    @safe_cmd
    def do_ropchain(self, text):
        if len(text) == 0:
            self.help_ropchain()
            return
        if not self.currentFile.loaded:
            self.do_load(text)

        gadgets = []
        for binary in self.__rs.files:
            gadgets.append(binary.gadgets)
        self.__generateChain(text)

    def help_ropchain(self):
        self.__printHelpText(
            'ropchain <generator>[ argname=arg[ argname=arg...]]',
            'uses the given generator and create a ropchain with args\n\nAvailable generators:\nexecve\nargs: cmd (optional)\navailable: x86, x86_64\nOS: linux\n\nmprotect\nargs: address, size\navailable: x86, x86_64\nOS: linux\n\nvirtualprotect\nargs: address (IAT)(optional)\navailable: x86\nOS: Windows\n\nExamples:\nropchain execve\nropchain mprotect address=0xbfff0000 size=0x21000'
        )

    def do_quit(self, text):
        exit(0)

    def help_quit(self):
        self.__printHelpText('quit', 'quits the application')

    @safe_cmd
    def do_arch(self, text):
        if not text:
            self.help_arch()
            return
        self.__setarch(text)

    def help_arch(self):
        self.__printHelpText(
            'arch <arch>', 'sets the architecture <arch> for the loaded file')

    @safe_cmd
    def do_string(self, text):

        self.__printStrings(text)

    def help_string(self):
        self.__printHelpText(
            'string [<string>]',
            'Looks for string <string> in section <section>. If no string is given all strings are printed.'
        )

    @safe_cmd
    def do_hex(self, text):
        if not text:
            self.help_hex()
            return
        self.__printSectionInHex(text)

    def help_hex(self):
        self.__printHelpText('hex <section>',
                             'Prints the section <section> in hex format')

    @safe_cmd
    def do_asm(self, text):
        if not text:
            self.help_asm()
            return

        text = text.strip()
        format = 'H'
        if text[-2:] in (' H', ' R', ' S'):
            format = text[-1:]
            text = text[:-2]

        arch = None
        if text.startswith('-a'):
            text = text[3:]
            index = text.index(' ')
            arch = text[:index]
            text = text[index:]
            arch = arch

        if not arch:
            if self.__currentFileName:
                arch = str(self.currentFile.arch)
            else:
                arch = 'x86'

        self.__asm(text, arch, format)

    def help_asm(self):
        self.__printHelpText(
            'asm [-a <arch>] <code> [<format>]',
            'assembles the given code. \n Format:\nR - Raw\nS - String\nH - Hex\nDefault: H'
        )

    @safe_cmd
    def do_disasm(self, text):
        if not text:
            self.help_disasm()
            return
        arch = None
        if text.startswith('-a'):
            text = text[3:]
            index = text.index(' ')
            arch = text[:index]
            text = text[index:].strip()
            arch = getArchitecture(arch)
        if not arch:
            if self.__currentFileName:
                arch = str(self.currentFile.arch)
            else:
                arch = 'x86'

        self.__disasm(text, arch)

    def help_disasm(self):
        self.__printHelpText(
            'disasm <bytes>',
            'disassembles the given bytes.\nExample:\ndisasm ffe4')

    @safe_cmd
    def do_disasm_address(self, text):
        split = text.split(' ')
        length = 1
        if not isHex(split[0]):
            self.__cprinter.printError(
                'Number have to be in hex format 0x....')
            return
        if len(split) > 1:
            if split[1][1:].isdigit() or (
                    len(split[1]) >= 3 and split[1][1] == '-'
                    and split[1][2:].isdigit()):  # is L\d or L-\d
                length = int(split[1][1:])
            else:
                self.__cprinter.printError(
                    'Length have to be in the following format L + Number e.g. L3'
                )
                return

        addr = int(split[0], 16)
        self.__disassembleAddress(addr, length)

    def help_disasm_address(self):
        self.__printHelpText(
            'disassembleAddress <address> [<length>]',
            'Disassembles instruction at address <address>. The count of instructions to disassemble can be specified (0x....:L...)\nExample:\ndisasm_address 0x8048cd8\ndisasm_address 0x8048cd8 L2\ndisasm_address 0x8048cd8 L-2'
        )

    @safe_cmd
    def do_stack_pivot(self, text):
        if self.currentFile.loaded:
            self.__printGadgets(self.currentFile.gadgets, Category.STACK_PIVOT)
        else:
            self.__printInfo(
                'No gadgets loaded. Please load gadgets with \'load\'')

    def help_stack_pivot(self):
        self.__printHelpText('stack_pivot', 'Prints all stack pivot gadgets')

    def do_EOF(self, text):
        self.__cprinter.println('')
        self.do_quit(text)

    @safe_cmd
    def do_clearcache(self, text):
        self.__rs.clearCache()

    def help_clearcache(self):
        self.__printHelpText('clearcache', 'Clears the cache')

    @safe_cmd
    def do_semantic(self, text):
        if not text:
            self.help_semantic()
            return
        if not self.currentFile.analysed:
            self.__rs.analyseGadgets(self.currentFile)
        constraint = None
        constraints = text.split(';')

        split = constraints[-1].split(' ')
        stableRegs = []
        for s in split:
            if s.startswith('!'):
                stableRegs.append(s[1:])
            else:
                constraint = s.strip()
        constraints[-1] = constraint
        for c in range(len(constraints)):
            constraints[c] = constraints[c].strip()

        self.__printInfo('Searching for gadgets: ' + text)
        old = None
        found = False
        analysedCount = None
        count = 0
        for fc, gadget in self.__rs.semanticSearch(constraints,
                                                   stableRegs=stableRegs):
            if fc != old:
                old = fc
                self.__cprinter.println()
                self.__printInfo('File: %s' % fc)
            found = True
            self.__printGadget(gadget, self.__options.detailed)
            count += 1

        self.__cprinter.printInfo('%d gadgets found' % count)

        self.__cprinter.println()

    def help_semantic(self):
        self.__printHelpText(
            'semantic',
            'Searchs gadgets\nsemantic <constraint>[; <constraint>][ !<stable reg>*]\n\nExample:\nsemantc eax==ebx; ecx==1 !edx !esi\n\nValid constraints:\nreg==reg\nreg==number\nreg==[reg]\nreg<+|-|*|/>=<reg|number|[reg]>'
        )
Exemplo n.º 2
0
class GeneralTests(unittest.TestCase):

    def setUp(self):
        self.rs = RopperService()
        self.rs.addFile('test-binaries/ls-x86_64')
        self.rs.loadGadgetsFor()

    def test_search(self):

        found_gadgets = self.rs.searchdict('mov [rax]')[FILE]
        self.assertEqual(len(found_gadgets), 2)

        found_gadgets = self.rs.searchdict('mov [r?x]')[FILE]
        self.assertEqual(len(found_gadgets), 12)

        found_gadgets = self.rs.searchdict('mov [r?x%]')[FILE]
        self.assertGreater(len(found_gadgets), 12)


    def test_badbytes(self):

        self.rs.options.badbytes = 'adfd'

        badbytes = 'adfd'
        
        gadget = self.rs.files[0].gadgets[0]
        self.assertNotEqual(gadget.lines[0][0], 0x1adfd)

        self.rs.options.badbytes = '52f8'
        gadgets = self.rs.searchPopPopRet()
        self.assertNotEqual(int(gadgets[FILE][0].lines[0][0]), 0x52f8)

        self.rs.options.badbytes =  'b1c7'
        gadgets = self.rs.searchJmpReg(['rsp'])
        self.assertNotEqual(gadgets[FILE][0].lines[0][0], 0xb1c7)

        with self.assertRaises(AttributeError):
            self.rs.options.badbytes = 'b1c'
            

        with self.assertRaises(AttributeError):
            self.rs.options.badbytes = 'qwer'
            

    def test_opcode_failures(self):
        r = RopperService()

        if version_info.major == 3 and version_info.minor >= 2:
            # Wrong question mark position
            with self.assertRaisesRegex(RopperError,'A \? for the highest 4 bit of a byte is not supported.*'):
                self.rs.searchOpcode('ff?4')
            # Wrong lengh
            with self.assertRaisesRegex(RopperError,'The length of the opcode has to be a multiple of two'):
                self.rs.searchOpcode('ff4')
            # Unallowed character
            with self.assertRaisesRegex(RopperError,'Invalid characters in opcode string'):
                self.rs.searchOpcode('ff4r')
        else:
            # Wrong question mark position
            with self.assertRaisesRegexp(RopperError,'A \? for the highest 4 bit of a byte is not supported.*'):
                self.rs.searchOpcode('ff?4')
            # Wrong lengh
            with self.assertRaisesRegexp(RopperError,'The length of the opcode has to be a multiple of two'):
                self.rs.searchOpcode('ff4')
            # Unallowed character
            with self.assertRaisesRegexp(RopperError,'Invalid characters in opcode string'):
                self.rs.searchOpcode('ff4r')
Exemplo n.º 3
0
class Console(cmd.Cmd):

    def __init__(self, options):
        cmd.Cmd.__init__(self)
        self.__options = options
        if not options.isWindows():
            import readline
            old_delims = readline.get_completer_delims()
            old_delims = old_delims.replace('-', '')
            old_delims = old_delims.replace('/', '')
            readline.set_completer_delims(old_delims)
            #ää Fix completion on mac os
            import rlcompleter
            if 'libedit' in readline.__doc__:
                readline.parse_and_bind("bind ^I rl_complete")
            else:
                readline.parse_and_bind("tab: complete")

        self.__rs = RopperService(self.__options.ropper_options, callbacks=CallbackClass(self))
        self.__currentFileName = ''
        self.__cprinter = ConsolePrinter()
        self.__dataPrinter = {}
        self.__updatePrompt()

    @property
    def cprinter(self):
        return self.__cprinter

    @property
    def currentFileName(self):
        if not self.__currentFileName:
            raise RopperError('No binary loaded')
        return self.__currentFileName

    @property
    def currentFile(self):
        return self.__rs.getFileFor(self.currentFileName)

    def cmdloop(self):
        try:
            cmd.Cmd.cmdloop(self)
        except KeyboardInterrupt:
            print()
            self.cmdloop()

    def emptyline(self):
        pass

    def __getDataPrinter(self, type):
        p = self.__dataPrinter.get(type)
        if not p:
            p = FileDataPrinter.create(type)
            self.__dataPrinter[type] = p

        return p

    def start(self):
        if self.__options.version:
            self.__printVersion()
            return

        if self.__options.clear_cache:
            self.__rs.clearCache()

        if self.__options.file and self.__options.asm is None:
            for file in self.__options.file:
                self.__loadFile(file)
            if len(self.__options.file) > 1:
                self.do_file('1')

        if self.__options.console:
            self.cmdloop()

        self.__handleOptions(self.__options)

    def __updatePrompt(self):
        if self.__currentFileName:
            name = getFileNameFromPath(self.__currentFileName)
            self.prompt = cstr('(%s/%s/%s)> ' % (name, str(self.currentFile.type),self.currentFile.arch), Color.RED)
        else:
            self.prompt = cstr('(ropper)> ', Color.RED)

    def __loadFile(self, file):

        self.__rs.addFile(file, raw=self.__options.raw,
                          arch=self.__options.arch)
        self.__options.arch = None

        self.__currentFileName = file
        self.__updatePrompt()
        if self.__options.I is not None:
            self.__rs.setImageBaseFor(file, self.__options.I)
        if not self.__options.no_load and self.__options.console:
            self.__loadGadgets()

        #self.__binary.printer = FileDataPrinter.create(self.__binary.type)

    def __printGadget(self, gadget, detailed=False):
        if detailed:
            self.__cprinter.println(gadget)
        else:
            self.__cprinter.println(gadget.simpleString())

    def __printData(self, data):
        cf = self.currentFile
        self.__getDataPrinter(cf.type).printData(cf.loader, data)

    def __printVersion(self):
        self.__cprinter.println("Version: Ropper %s" %
                                '.'.join([str(x) for x in ropper.VERSION]))
        self.__cprinter.println("Author: Sascha Schirra")
        self.__cprinter.println("Website: http://scoding.de/ropper\n")

    def __printHelpText(self, cmd, desc):
        self.__cprinter.println('{}  -  {}\n'.format(cmd, desc))

    def __printError(self, error):
        self.__cprinter.printError(error)

    def __printInfo(self, info):
        self.__cprinter.printInfo(cstr(info))

    def __printSeparator(self, before='', behind=''):
        self.__cprinter.println(before + '-' * 40 + behind)

    def __setASLR(self, enable):
        self.currentFile.loader.setASLR(enable)

    def __setNX(self, enable):
        self.currentFile.loader.setNX(enable)

    def __set(self, option, enable):
        if option == 'aslr':
            self.__setASLR(enable)
        elif option == 'nx':
            self.__setNX(enable)
        else:
            raise ArgumentError('Invalid option: {}'.format(option))

    def __asm(self, code, arch, format):
        r = Ropper()
        if format == 'R':
            f = 'raw'
        elif format == 'H':
            f = 'hex'
        elif format == 'S':
            f = 'string'
        else:
            raise RopperError('wrong format: %s' % f)

        self.__cprinter.println(self.__rs.asm(code, arch, f))

    def __disasm(self, code, arch):
        r = Ropper()
        self.__cprinter.println(self.__rs.disasm(code, arch))

    def __searchJmpReg(self, regs):
        regs = regs.split(',')
        gadgets = self.__rs.searchJmpReg(name=self.currentFileName, regs=regs)
        self.__printGadgets([g for g in gadgets.values()][0], header='JMP Instructions')

    def __searchOpcode(self, opcode):
        gadgets = self.__rs.searchOpcode(
            name=self.currentFileName, opcode=opcode)
        self.__printGadgets([g for g in gadgets.values()][0], header='Opcode')

    def __searchInstructions(self, code):
        gadgets = self.__rs.searchInstructions(
            name=self.currentFileName, code=code)
        self.__printGadgets([g for g in gadgets.values()]
                            [0], header='Instructions')

    def __searchPopPopRet(self):
        pprs = self.__rs.searchPopPopRet(self.currentFileName)
        self.__printGadgets([g for g in pprs.values()][0],
                            header='POP;POP;RET Instructions')

    def __loadGadgetsForAllFiles(self):
        self.__rs.loadGadgetsFor()

    def __loadGadgets(self):
        self.__searchGadgetsFor(self.currentFileName)

    def __searchGadgetsFor(self, binary):

        self.__rs.loadGadgetsFor(binary)
        return self.__rs.getFileFor(binary).gadgets

    def __printGadgets(self, gadgets, category=None, header='Gadgets', detailed=False):
        self.__getDataPrinter(self.currentFile.type).printTableHeader(header)

        counter = 0
        for g in gadgets:
            if not category or category == g.category[0]:
                self.__printGadget(g, detailed=detailed)
                counter += 1

        self.__cprinter.println('\n%d gadgets found' % counter)



    def __printProgress(self, gadget, gnr, count):
        if gnr >= 0:
            self.__cprinter.printProgress('clearing up...', float(gnr) / count)
        else:
            self.__cprinter.printProgress('clearing up...', 1)
            self.__cprinter.finishProgress()

    def __loadAllGadgets(self):
        self.__rs.loadGadgetsFor()

    def __printGadgetsFromCurrentFile(self):
        gadgets = self.currentFile.gadgets
        self.__printGadgets(gadgets, detailed=self.__options.detailed)

    def __search(self, filter, quality=None):
        self.__printInfo('Searching for gadgets: ' + filter)
        old = None
        for fc, gadget in self.__rs.search(filter, quality):
            if fc != old:
                old = fc
                self.__cprinter.println()
                self.__printInfo('File: %s' % fc)

            self.__printGadget(gadget, self.__options.detailed)
        self.__cprinter.println()

    def __generateChain(self, command):
        split = command.split(' ')
        try:
            old = self.__rs.options.color
            generator = split[0]
            options = {}
            if len(split) > 1:
                for option in split[1:]:
                    if option.count('=') == 0 or option.count('=') > 1:
                        raise RopperError('Wrong option format. An option has to be set in the following format: option=value')
                    key, value = option.split('=')
                    options[key] = value
            try:

                self.__rs.options.color = False
                chain = self.__rs.createRopChain(generator, str(self.currentFile.arch) ,options)

                #generator = RopChain.get(self.__binaries, self.__gadgets, split[0], self.__ropchainInfoCallback, unhexlify(self.__options.badbytes))

                self.__printInfo('generating rop chain')
                # self.__printSeparator(behind='\n\n')

                self.__cprinter.println(chain)
                # self.__printSeparator(before='\n\n')
                self.__printInfo('rop chain generated!')
            except RopperError as e:
                self.__rs.options.color = old
                self.__printError(e)
        except BaseException as e:
            self.__rs.options.color = old
            print( traceback.format_exc())

            raise e
        self.__rs.options.color = old

    def __ropchainInfoCallback(self, message):
        if message.startswith('[*]'):
            self.__cprinter.puts('\r' + message)
        self.__cprinter.printInfo(message)

    def __setarch(self, arch):
        if self.currentFile:
            self.__rs.setArchitectureFor(self.currentFileName, arch)
            self.__updatePrompt()
        else:
            self.__printError('No file loaded')

    def __printStrings(self, string, sec=None):
        strings = self.__rs.searchString(
            string=string, name=self.currentFileName)
        strings = [s for s in strings.values()][0]
        strings = [(cstr(toHex(addr), Color.RED), cstr(s))
                   for addr, s in strings]
        printTable('Strings', (cstr('Address'), cstr('Value')), strings)

    def __disassembleAddress(self, addr, length):
        ds = self.__rs.disassAddress(self.currentFileName, addr, length)
        if len(ds.split('\n')) < length:
            self.__cprinter.printInfo(
                'Cannot disassemble specified count of instructions')
        self.__getDataPrinter(
            self.currentFile.type).printTableHeader('Instructions')
        self.__cprinter.println(ds)

    def __printSectionInHex(self, section):
        section = self.currentFile.loader.getSection(section)
        if section.bytes:
            printHexFormat(section.bytes, section.virtualAddress,
                       not self.__rs.options.color)
        else:
            self.__printInfo('No bytes to print')

    @safe_cmd
    def __handleOptions(self, options):
        if options.sections:
            self.__printData('sections')
        elif options.analyse:
            self.__loadGadgets()
            #self.do_analyse(options.analyse)
        elif options.semantic:
            self.__loadGadgets()
            self.do_semantic(options.semantic)
        elif options.symbols:
            self.__printData('symbols')
        elif options.segments:
            self.__printData('segments')
        elif options.dllcharacteristics:
            self.__printData('dll_characteristics')
        elif options.imagebase:
            self.__printData('image_base')
        elif options.e:
            self.__printData('entry_point')
        elif options.imports:
            self.__printData('imports')
        elif options.asm is not None:
            format = 'H'
            if options.file is not None:
                with open(options.file[0]) as f:
                    code = f.read()
                if len(options.asm) > 0:
                    format = options.asm[0]
            else:
                code = options.asm[0]
                if len(options.asm) == 2:
                    code = options.asm[0]
                    format = options.asm[1]
            arch = 'x86'
            if options.arch:
                arch = options.arch
            self.__asm(code, arch, format)
        elif options.disasm:
            code = options.disasm
            arch = 'x86'
            if options.arch:
                arch = options.arch
            self.__disasm(code, arch)
        elif options.set:
            self.__set(options.set, True)
        elif options.unset:
            self.__set(options.unset, False)
        elif options.info:
            self.__printData('information')
        elif options.ppr:
            self.__searchPopPopRet()
        elif options.jmp:
            self.__searchJmpReg(options.jmp)
        elif options.stack_pivot:
            self.__loadGadgets()
            self.__printGadgets(self.currentFile.gadgets, Category.STACK_PIVOT)
        elif options.opcode:
            self.__searchOpcode(self.__options.opcode)
        elif options.instructions:
            self.__searchInstructions(self.__options.instructions)
        elif options.string:
            self.__printStrings(options.string, options.section)
        elif options.hex and options.section:
            self.__printSectionInHex(options.section)
        elif options.disassemble_address:
            split = options.disassemble_address.split(':')
            length = 1
            if not isHex(split[0]):
                raise RopperError('Number have to be in hex format 0x....')

            if len(split) > 1:
                if split[1][1:].isdigit() or (len(split[1]) >= 3 and split[1][1] == '-' and split[1][2:].isdigit()):  # is L\d or L-\d
                    length = int(split[1][1:])
                else:
                    raise RopperError(
                        'Length have to be in the following format L + Number e.g. L3')
            self.__disassembleAddress(int(split[0], 16), length)
        # elif options.checksec:
         #   self.__checksec()
        elif options.chain:
            self.__loadGadgetsForAllFiles()
            self.__generateChain(options.chain)
        elif self.__options.file:
            self.__loadGadgets()
            if options.search:
                self.__search(options.search, options.quality)
            else:
                self.__printGadgetsFromCurrentFile()


####### cmd commands ######
    @safe_cmd
    def do_show(self, text):
        if len(text) == 0:
            self.help_show()
            return

        self.__printData(text)

    def help_show(self):
        desc = 'shows informations about the loaded file'
        if self.__getDataPrinter(self.currentFile.type):
            desc += ('Available informations:\n' +
                     ('\n'.join(self.__getDataPrinter(self.currentFile.type).availableInformations)))
        self.__printHelpText(
            'show <info>', desc)

    def complete_show(self, text, line, begidx, endidx):
        if self.__getDataPrinter(self.currentFile.type):
            return [i for i in self.__getDataPrinter(self.currentFile.type).availableInformations if i.startswith(
                    text)]

    @safe_cmd
    def do_close(self, text):

        if text.isdigit():
            idx = int(text)
            if len(self.__rs.files) > idx - 1:
                self.__rs.removeFile(self.__rs.files[idx - 1].loader.fileName)
                if len(self.__rs.files) != 0:
                    self.__currentFileName = self.__rs.files[0].loader.fileName
                else:
                    self.__currentFileName = None
                self.__updatePrompt()
            else:
                self.__cprinter.printError('Index is too small or to large')
        elif text == 'all':
            for file in self.__rs.files:
                self.__rs.removeFile(file.loader.fileName)
            self.__currentFileName = None
            self.__updatePrompt()
        else:
            self.help_close()

    def help_close(self):
        self.__printHelpText(
            'close idx/all', 'closes opened files\nidx - index of file which should be closed\nall - closes all files')

    @safe_cmd
    def do_file(self, text):
        if len(text) == 0:
            data = []
            for index, binary in enumerate(self.__rs.files):
                if self.currentFileName == binary.loader.fileName:
                    data.append(
                        (cstr(index + 1), cstr(binary.loader.fileName + '*'), cstr(binary.arch),cstr(binary.loaded)))
                else:
                    data.append(
                        (cstr(index + 1), cstr(binary.loader.fileName), cstr(binary.arch),cstr(binary.loaded)))

            printTable('Opened Files', (cstr('No.'),
                                        cstr('Name'), cstr('Architecture'),cstr('Loaded')), data)

        elif text.isdigit():
            idx = int(text) - 1
            if idx >= len(self.__rs.files):
                raise RopperError('Index is too small or to large')
            self.__currentFileName = self.__rs.files[idx].loader.fileName
            self.__updatePrompt()
            self.__printInfo('File \'%s\' selected.' % self.currentFileName)
        else:
            self.__loadFile(text)
            self.__printInfo('File loaded.')

    def complete_file(self, text, line, begidx, endidx):
        file = text
        cwd = '.'
        path = ''
        if '/' in file:
            cwd = file[:file.rindex('/') + 1]
            file = file[file.rindex('/') + 1:]
            path = cwd

        return [path + i for i in os.listdir(cwd) if i.startswith(file)]

    def help_file(self):
        self.__printHelpText(
            'file [<file>|<idx>]', '\nno argument shows all opened files\n<file> - loads the file <file>\n<idx> - select the file with number <idx>')

    @safe_cmd
    def do_set(self, text):
        if not text:
            self.help_set()
            return

        self.__set(text, True)

    def help_set(self):
        desc = """Sets options.
Options:
aslr\t- Sets the ASLR-Flag (PE)
nx\t- Sets the NX-Flag (ELF|PE)"""
        self.__printHelpText('set <option>', desc)

    def complete_set(self, text, line, begidx, endidx):
        return [i for i in ['aslr', 'nx'] if i.startswith(text)]

    @safe_cmd
    def do_unset(self, text):
        if not text:
            self.help_unset()
            return

        self.__set(text, False)

    def help_unset(self):
        desc = """Clears options.
Options:
aslr\t- Clears the ASLR-Flag (PE)
nx\t- Clears the NX-Flag (ELF|PE)"""
        self.__printHelpText('unset <option>', desc)

    def complete_unset(self, text, line, begidx, endidx):
        return self.complete_set(text, line, begidx, endidx)

    @safe_cmd
    def do_gadgets(self, text):

        if not self.currentFile.loaded:
            self.__printInfo('Gadgets have to be loaded with load')
            return

        self.__printGadgets(self.currentFile.gadgets, detailed=self.__rs.options.detailed)

    def help_gadgets(self):
        self.__printHelpText('gadgets', 'shows all loaded gadgets')

    @safe_cmd
    def do_load(self, text):
        if text == 'all':
            self.__loadAllGadgets()
        else:
            self.__loadGadgets()
            self.__printInfo('gadgets loaded.')

    def help_load(self):
        self.__printHelpText(
            'load [all]', '\nall - loads gadgets of all opened files\nwithout argument loads gadgets of current file')

    @safe_cmd
    def do_ppr(self, text):
        self.__searchPopPopRet()

    def help_ppr(self):
        self.__printHelpText('ppr', 'shows all pop,pop,ret instructions')

    @safe_cmd
    def do_search(self, text):
        if len(text) == 0:
            self.help_search()
            return
        match = re.match('/\d+/', text)
        qual = None
        if match:
            qual = int(match.group(0)[1:-1])
            text = text[len(match.group(0)):].strip()
        self.__search(text, qual)

    def help_search(self):
        desc = 'search gadgets.\n\n'
        desc += '/quality/\tThe quality of the gadget (1 = best).'
        desc += 'The better the quality the less instructions are between the found intruction and ret\n'
        desc += '?\t\tany character\n%\t\tany string\n\n'
        desc += 'Example:\n'
        desc += 'search mov e?x\n\n'
        desc += '0x000067f1: mov edx, dword ptr [ebp + 0x14]; mov dword ptr [esp], edx; call eax;\n'
        desc += '0x00006d03: mov eax, esi; pop ebx; pop esi; pop edi; pop ebp; ret ;\n'
        desc += '0x00006d6f: mov ebx, esi; mov esi, dword ptr [esp + 0x18]; add esp, 0x1c; ret ;\n'
        desc += '0x000076f8: mov eax, dword ptr [eax]; mov byte ptr [eax + edx], 0; add esp, 0x18; pop ebx; ret ;\n\n\n'
        desc += 'search mov [%], edx\n\n'
        desc += '0x000067ed: mov dword ptr [esp + 4], edx; mov edx, dword ptr [ebp + 0x14]; mov dword ptr [esp], edx; call eax;\n'
        desc += '0x00006f4e: mov dword ptr [ecx + 0x14], edx; add esp, 0x2c; pop ebx; pop esi; pop edi; pop ebp; ret ;\n'
        desc += '0x000084b8: mov dword ptr [eax], edx; ret ;\n'
        desc += '0x00008d9b: mov dword ptr [eax], edx; add esp, 0x18; pop ebx; ret ;\n\n\n'
        desc += 'search /1/ mov [%], edx\n\n'
        desc += '0x000084b8: mov dword ptr [eax], edx; ret ;\n'

        self.__printHelpText('search [/<quality>/] <string>', desc)

    @safe_cmd
    def do_inst(self, text):
        if len(text) == 0:
            self.help_inst()
            return

        self.__searchInstructions(text)

    def help_inst(self):
        self.__printHelpText(
            'inst <instructions>', 'searchs instructions in executable sections')

    @safe_cmd
    def do_opcode(self, text):
        if len(text) == 0:
            self.help_opcode()
            return

        self.__searchOpcode(text)

    def help_opcode(self):
        self.__printHelpText(
            'opcode <opcode>', 'searchs opcode in executable sections\nExample:\nopcode ffe4\nopcode ff4?\nopcode ff??\n\nNot allowed:\nopcode ff?4')

    @safe_cmd
    def do_imagebase(self, text):
        if len(text) == 0:
            self.__rs.setImageBaseFor(self.currentFileName, None)
            self.__printInfo('Imagebase reseted')
        elif isHex(text):
            self.__rs.setImageBaseFor(self.currentFileName, int(text, 16))
            self.__printInfo('Imagebase set to %s' % text)
        else:
            self.help_imagebase()

    def help_imagebase(self):
        self.__printHelpText(
            'imagebase [<base>]', 'sets a new imagebase. An empty imagebase sets the imagebase to the original value.')

    @safe_cmd
    def do_type(self, text):
        if len(text) == 0:
            self.help_type()
            return

        self.do_settings('type %s' % text)

    def help_type(self):
        self.__printHelpText(
            'type <type>', 'sets the gadget type (rop, jop, sys, all, default:all)')

    @safe_cmd
    def do_jmp(self, text):
        if len(text) == 0:
            self.help_jmp()
            return

        self.__searchJmpReg(text)

    def help_jmp(self):
        self.__printHelpText(
            'jmp <reg[,reg...]>', 'searchs jmp reg instructions')

    @safe_cmd
    def do_detailed(self, text):
        self.do_settings('detailed %s' % text)

    def help_detailed(self):
        self.__printHelpText(
            'detailed [on|off]', 'sets detailed gadget output')

    def complete_detailed(self, text, line, begidx, endidx):
        return [i for i in ['on', 'off'] if i.startswith(text)]

    @safe_cmd
    def do_settings(self, text):
        if len(text):
            try:
                splits = text.strip().split(' ')
                if len(splits) == 1:
                    if splits[0] == 'color':
                        self.__rs.options[splits[0]] = True
                    else:
                        self.__rs.options[splits[0]] = None
                elif len(splits) == 2:
                    if splits[1] in ['on', 'off']:
                        self.__rs.options[splits[0]] = True if splits[1] == 'on' else False
                    elif splits[0] in ('inst_count', 'count_of_findings'):
                        self.__rs.options[splits[0]] = int(splits[1])
                    else:
                        self.__rs.options[splits[0]] = splits[1]

                else:
                    raise RopperError('Invalid setting')
            except TypeError as e:
                raise RopperError(e)
            except AttributeError as e:
                raise RopperError(e)
        else:
            data = []
            desc = {'cfg_only':'if on gadgets are filtered for use in CFG exploits (only PE)',
                    'all':'If on shows all found gadgets including double gadgets',
                    'color':'If on output is colored',
                    'badbytes':'Gadget addresses are not allowed to contain this bytes',
                    'type':'The file is scanned for this type of gadgets. (rop, jop, sys, all)',
                    'detailed':'If on the gadgets will be printed with more detailed information',
                    'inst_count':'The max count of instructions in a gadgets',
                    'count_of_findings':'The max count of findings which will be printed with semantic search (0 = undefined, default: 5'}
            for key, value in self.__rs.options.items():
                if isinstance(value, bool):
                    data.append((cstr(key), cstr('on' if value else 'off'), cstr(desc.get(key,''))))
                else:
                    data.append((cstr(key), cstr(value), cstr(desc[key])))

            printTable('Settings', (cstr('Name'), cstr('Value'), cstr('Description')), data)

    def help_settings(self):
        self.__printHelpText('settings', 'shows the current settings or set the settings\nHow to set:\nsettings badbytes 00 - sets badbytes to 00\nsettings badbytes - sets badbytes to default (empty)')

    @safe_cmd
    def do_badbytes(self, text):
        if len(text) == 0:
            self.__printInfo('badbytes cleared')

        self.do_settings('badbytes %s' % text)

        # for binary in self.__binaries:
        #     if binary.loaded:
        #         self.__gadgets[binary] = ropper.filterBadBytes(binary.gadgets, self.__options.badbytes)

        #         if not self.__options.all:
        #             self.__gadgets[binary] = ropper.deleteDuplicates(self.__gadgets[binary])

        self.__cprinter.printInfo('Filter gadgets')

    def help_badbytes(self):
        self.__printHelpText(
            'badbytes [bytes]', 'sets/clears bad bytes\n\n Example:\nbadbytes 000a0d  -- sets 0x00, 0x0a and 0x0d as badbytes')

    @safe_cmd
    def do_color(self, text):
        if self.__options.isWindows():
            self.__printInfo('No color support for windows')
            return
        self.do_settings('color %s' % text)

    def help_color(self):
        self.__printHelpText('color [on|off]', 'sets colorized output')

    def complete_color(self, text, line, begidx, endidx):
        return [i for i in ['on', 'off'] if i.startswith(text)]

    @safe_cmd
    def do_ropchain(self, text):
        if len(text) == 0:
            self.help_ropchain()
            return
        if not self.currentFile.loaded:
            self.do_load(text)

        gadgets = []
        for binary in self.__rs.files:
            gadgets.append(binary.gadgets)
        self.__generateChain(text)

    def help_ropchain(self):
        self.__printHelpText('ropchain <generator>[ argname=arg[ argname=arg...]]',
                             'uses the given generator and create a ropchain with args\n\nAvailable generators:\nexecve\nargs: cmd (optional)\navailable: x86, x86_64\nOS: linux\n\nmprotect\nargs: address, size\navailable: x86, x86_64\nOS: linux\n\nvirtualprotect\nargs: address (IAT)(optional)\navailable: x86\nOS: Windows\n\nExamples:\nropchain execve\nropchain mprotect address=0xbfff0000 size=0x21000')

    def do_quit(self, text):
        exit(0)

    def help_quit(self):
        self.__printHelpText('quit', 'quits the application')

    @safe_cmd
    def do_arch(self, text):
        if not text:
            self.help_arch()
            return
        self.__setarch(text)

    def help_arch(self):
        self.__printHelpText(
            'arch <arch>', 'sets the architecture <arch> for the loaded file')

    @safe_cmd
    def do_string(self, text):

        self.__printStrings(text)

    def help_string(self):
        self.__printHelpText(
            'string [<string>]', 'Looks for string <string> in section <section>. If no string is given all strings are printed.')

    @safe_cmd
    def do_hex(self, text):
        if not text:
            self.help_hex()
            return
        self.__printSectionInHex(text)

    def help_hex(self):
        self.__printHelpText(
            'hex <section>', 'Prints the section <section> in hex format')

    @safe_cmd
    def do_asm(self, text):
        if not text:
            self.help_asm()
            return

        text = text.strip()
        format = 'H'
        if text[-2:] in (' H', ' R', ' S'):
            format = text[-1:]
            text = text[:-2]

        arch = None
        if text.startswith('-a'):
            text = text[3:]
            index = text.index(' ')
            arch = text[:index]
            text = text[index:]
            arch = arch

        if not arch:
            if self.__currentFileName:
                arch = str(self.currentFile.arch)
            else:
                arch = 'x86'

        self.__asm(text, arch, format)

    def help_asm(self):
        self.__printHelpText('asm [-a <arch>] <code> [<format>]',
                             'assembles the given code. \n Format:\nR - Raw\nS - String\nH - Hex\nDefault: H')

    @safe_cmd
    def do_disasm(self, text):
        if not text:
            self.help_disasm()
            return
        arch = None
        if text.startswith('-a'):
            text = text[3:]
            index = text.index(' ')
            arch = text[:index]
            text = text[index:].strip()
            arch = getArchitecture(arch)
        if not arch:
            if self.__currentFileName:
                arch = str(self.currentFile.arch)
            else:
                arch = 'x86'

        self.__disasm(text, arch)

    def help_disasm(self):
        self.__printHelpText('disasm <bytes>', 'disassembles the given bytes.\nExample:\ndisasm ffe4')

    @safe_cmd
    def do_disasm_address(self, text):
        split = text.split(' ')
        length = 1
        if not isHex(split[0]):
            self.__cprinter.printError(
                'Number have to be in hex format 0x....')
            return
        if len(split) > 1:
            if split[1][1:].isdigit() or (len(split[1]) >= 3 and split[1][1] == '-' and split[1][2:].isdigit()):  # is L\d or L-\d
                length = int(split[1][1:])
            else:
                self.__cprinter.printError(
                    'Length have to be in the following format L + Number e.g. L3')
                return

        addr = int(split[0], 16)
        self.__disassembleAddress(addr, length)

    def help_disasm_address(self):
        self.__printHelpText(
            'disassembleAddress <address> [<length>]', 'Disassembles instruction at address <address>. The count of instructions to disassemble can be specified (0x....:L...)\nExample:\ndisasm_address 0x8048cd8\ndisasm_address 0x8048cd8 L2\ndisasm_address 0x8048cd8 L-2')

    @safe_cmd
    def do_stack_pivot(self, text):
        if self.currentFile.loaded:
            self.__printGadgets(self.currentFile.gadgets, Category.STACK_PIVOT)
        else:
            self.__printInfo('No gadgets loaded. Please load gadgets with \'load\'')

    def help_stack_pivot(self):
        self.__printHelpText('stack_pivot','Prints all stack pivot gadgets')

    def do_EOF(self, text):
        self.__cprinter.println('')
        self.do_quit(text)

    @safe_cmd
    def do_clearcache(self, text):
        self.__rs.clearCache()

    def help_clearcache(self):
        self.__printHelpText('clearcache','Clears the cache')

    @safe_cmd
    def do_semantic(self, text):
        if not text:
            self.help_semantic()
            return
        if not self.currentFile.analysed:
            self.__rs.analyseGadgets(self.currentFile)
        constraint = None
        constraints = text.split(';')

        split = constraints[-1].split(' ')
        stableRegs = []
        for s in split:
            if s.startswith('!'):
                stableRegs.append(s[1:])
            else:
                constraint = s.strip()
        constraints[-1] = constraint
        for c in range(len(constraints)):
            constraints[c] = constraints[c].strip()

        self.__printInfo('Searching for gadgets: ' + text)
        old = None
        found = False
        analysedCount = None
        count = 0
        for fc, gadget in self.__rs.semanticSearch(constraints, stableRegs=stableRegs):
            if fc != old:
                old = fc
                self.__cprinter.println()
                self.__printInfo('File: %s' % fc)
            found = True
            self.__printGadget(gadget, self.__options.detailed)
            count += 1

        self.__cprinter.printInfo('%d gadgets found' % count)

        self.__cprinter.println()

    def help_semantic(self):
        self.__printHelpText('semantic', 'Searchs gadgets\nsemantic <constraint>[; <constraint>][ !<stable reg>*]\n\nExample:\nsemantc eax==ebx; ecx==1 !edx !esi\n\nValid constraints:\nreg==reg\nreg==number\nreg==[reg]\nreg<+|-|*|/>=<reg|number|[reg]>')
Exemplo n.º 4
0
class GeneralTests(unittest.TestCase):
    def setUp(self):
        self.rs = RopperService()
        self.rs.addFile('test-binaries/ls-x86_64')
        self.rs.loadGadgetsFor()

    def test_search(self):

        found_gadgets = self.rs.searchdict('mov [rax]')[FILE]
        self.assertEqual(len(found_gadgets), 2)

        found_gadgets = self.rs.searchdict('mov [r?x]')[FILE]
        self.assertEqual(len(found_gadgets), 12)

        found_gadgets = self.rs.searchdict('mov [r?x%]')[FILE]
        self.assertGreater(len(found_gadgets), 12)

    def test_badbytes(self):

        self.rs.options.badbytes = 'adfd'

        badbytes = 'adfd'

        gadget = self.rs.files[0].gadgets[0]
        self.assertNotEqual(gadget.lines[0][0], 0x1adfd)

        self.rs.options.badbytes = '52f8'
        gadgets = self.rs.searchPopPopRet()
        self.assertNotEqual(int(gadgets[FILE][0].lines[0][0]), 0x52f8)

        self.rs.options.badbytes = 'b1c7'
        gadgets = self.rs.searchJmpReg(['rsp'])
        self.assertNotEqual(gadgets[FILE][0].lines[0][0], 0xb1c7)

        with self.assertRaises(AttributeError):
            self.rs.options.badbytes = 'b1c'

        with self.assertRaises(AttributeError):
            self.rs.options.badbytes = 'qwer'

    def test_opcode_failures(self):
        r = RopperService()

        if version_info.major == 3 and version_info.minor >= 2:
            # Wrong question mark position
            with self.assertRaisesRegex(
                    RopperError,
                    'A \? for the highest 4 bit of a byte is not supported.*'):
                self.rs.searchOpcode('ff?4')
            # Wrong lengh
            with self.assertRaisesRegex(
                    RopperError,
                    'The length of the opcode has to be a multiple of two'):
                self.rs.searchOpcode('ff4')
            # Unallowed character
            with self.assertRaisesRegex(RopperError,
                                        'Invalid characters in opcode string'):
                self.rs.searchOpcode('ff4r')
        else:
            # Wrong question mark position
            with self.assertRaisesRegexp(
                    RopperError,
                    'A \? for the highest 4 bit of a byte is not supported.*'):
                self.rs.searchOpcode('ff?4')
            # Wrong lengh
            with self.assertRaisesRegexp(
                    RopperError,
                    'The length of the opcode has to be a multiple of two'):
                self.rs.searchOpcode('ff4')
            # Unallowed character
            with self.assertRaisesRegexp(
                    RopperError, 'Invalid characters in opcode string'):
                self.rs.searchOpcode('ff4r')