Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

last refactor PR of 2018 🎉🎉🎉 #227

Merged
merged 10 commits into from
Jan 1, 2019
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ Currently, custom projects can only be used programmatically as follows:
import os

from s2e_env.commands.new_project import Command as NewProjectCommand
from s2e_env.commands.project_creation.abstract_project import AbstractProject
from s2e_env.commands.project_creation import AbstractProject
from s2e_env.manage import call_command


Expand Down
29 changes: 8 additions & 21 deletions s2e_env/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"""


from abc import ABCMeta, abstractmethod
from argparse import ArgumentParser
import json
import logging
Expand All @@ -38,8 +39,6 @@

import yaml

from s2e_env.utils import log


class CommandError(Exception):
"""
Expand Down Expand Up @@ -104,16 +103,15 @@ class BaseCommand(object):
``help`` class attribute should be specified.
"""

# Abstract class
__metaclass__ = ABCMeta

# Metadata about this command
help = ''

# Configuration shortcuts that alter various logic.
called_from_command_line = False

def __init__(self):
# Initialize the default logger
log.configure_logging()

def create_parser(self, prog_name, subcommand):
"""
Create and return the ``CommandParser`` which will be used to parse
Expand Down Expand Up @@ -189,6 +187,7 @@ def execute(self, *args, **options):
def name(self):
return self.__module__.split('.')[-1]

@abstractmethod
def handle(self, *args, **options):
"""
The actual logic of the command. Subclasses must implement this method.
Expand All @@ -211,17 +210,6 @@ def __init__(self):
self._env_dir = None
self._config = None

def _init_logging(self):
config_lvl = self.config.get('logging', {}).get('level', 'info')
color = self.config.get('logging', {}).get('color', True)

level = logging.getLevelName(config_lvl.upper())
if not isinstance(level, int):
raise CommandError('Invalid logging level \'%s\' in s2e.yaml' %
config_lvl)

log.configure_logging(level, color)

def handle_common_args(self, **options):
"""
Adds the environment directory as a class member.
Expand All @@ -242,10 +230,9 @@ def handle_common_args(self, **options):
self._config = yaml.load(f)
except IOError:
raise CommandError('This does not look like an S2E environment - '
'it does not contain an s2e.yaml configuration file (%s does not exist)' % path)

# Reinitialize logging with settings from the environment's config
self._init_logging()
'it does not contain an s2e.yaml configuration '
'file (%s does not exist). Source %s in your '
'environment' % (path, self.env_path('s2e_activate')))

def add_arguments(self, parser):
super(EnvCommand, self).add_arguments(parser)
Expand Down
2 changes: 2 additions & 0 deletions s2e_env/commands/code_coverage/basic_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from __future__ import division

from abc import abstractmethod
from collections import defaultdict
import json
import itertools
Expand Down Expand Up @@ -276,6 +277,7 @@ def _initialize_disassembler(self):
"""
pass

@abstractmethod
def _get_disassembly_info(self, module_path):
"""
Disassemble the give module using on the of the supported backends (IDA
Expand Down
3 changes: 1 addition & 2 deletions s2e_env/commands/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@


from s2e_env.command import ProjectCommand, CommandError
from s2e_env.commands.code_coverage.basic_block import BasicBlockCoverage
from s2e_env.commands.code_coverage.lcov import LineCoverage
from s2e_env.manage import call_command

Expand Down Expand Up @@ -62,7 +61,7 @@ def add_arguments(self, parser):
help='Where to store the LCOV report (by default into s2e-last)',
required=False)

bb_parser = subparsers.add_parser('basic_block', cmd=BasicBlockCoverage(),
bb_parser = subparsers.add_parser('basic_block', cmd=IDABasicBlockCoverage(),
help='Generate a basic block report')
bb_parser.add_argument('-d', '--disassembler', choices=('ida', 'r2', 'binaryninja'),
default='ida', help='Disassembler backend to use')
Expand Down
205 changes: 31 additions & 174 deletions s2e_env/commands/new_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,40 +23,24 @@

import argparse
import logging
import os
import re

from magic import Magic

from s2e_env.command import EnvCommand, CommandError
from s2e_env.commands.project_creation.abstract_project import AbstractProject
from s2e_env.commands.project_creation.cgc_project import CGCProject
from s2e_env.commands.project_creation.linux_project import LinuxProject
from s2e_env.commands.project_creation.windows_project import WindowsProject, WindowsDLLProject, WindowsDriverProject
from s2e_env.infparser.driver import Driver
from s2e_env.commands.project_creation import CGCProject
from s2e_env.commands.project_creation import LinuxProject
from s2e_env.commands.project_creation import WindowsProject, \
WindowsDLLProject, WindowsDriverProject
from s2e_env.commands.project_creation import Target
from s2e_env.manage import call_command


logger = logging.getLogger('new_project')

# Paths
FILE_DIR = os.path.dirname(__file__)
CGC_MAGIC = os.path.join(FILE_DIR, '..', 'dat', 'cgc.magic')

# Magic regexs
CGC_REGEX = re.compile(r'^CGC 32-bit')
ELF32_REGEX = re.compile(r'^ELF 32-bit')
ELF64_REGEX = re.compile(r'^ELF 64-bit')
PE32_REGEX = re.compile(r'^PE32 executable')
PE64_REGEX = re.compile(r'^PE32\+ executable')
MSDOS_REGEX = re.compile(r'^MS-DOS executable')
DLL32_REGEX = re.compile(r'^PE32 executable \(DLL\)')
DLL64_REGEX = re.compile(r'^PE32\+ executable \(DLL\)')

PROJECT_CLASSES = {
PROJECT_TYPES = {
'cgc': CGCProject,
'linux': LinuxProject,
'windows': WindowsProject,
'windows_dll': WindowsDLLProject,
'windows_driver': WindowsDriverProject,
}


Expand All @@ -83,135 +67,14 @@ def _parse_sym_args(sym_args_str):
return sym_args


def _get_arch(target_path):
"""
Check that the given target is supported by S2E.

The target's magic is checked to see if it is a supported file type (e.g.
ELF, PE, etc.). The architecture that the target was compiled for (e.g.
i386, x64, etc.) is also checked.

Returns:
A tuple containing the target's architecture and a project class is
returned.
"""
default_magic = Magic()
magic_checks = [
(Magic(magic_file=CGC_MAGIC), CGC_REGEX, CGCProject, 'i386'),
(default_magic, ELF32_REGEX, LinuxProject, 'i386'),
(default_magic, ELF64_REGEX, LinuxProject, 'x86_64'),
(default_magic, DLL32_REGEX, WindowsDLLProject, 'i386'),
(default_magic, DLL64_REGEX, WindowsDLLProject, 'x86_64'),
(default_magic, PE32_REGEX, WindowsProject, 'i386'),
(default_magic, PE64_REGEX, WindowsProject, 'x86_64'),
(default_magic, MSDOS_REGEX, WindowsProject, 'i386')
]

# Check the target program against the valid file types
for magic_check, regex, proj_class, arch in magic_checks:
magic = magic_check.from_file(target_path)
matches = regex.match(magic)

# If we find a match, create that project
if matches:
return arch, proj_class

return None, None


def _handle_win_driver_project(target_path, driver_files, *args, **options):
first_sys_file = None
for f in driver_files:
if f.endswith('.sys'):
first_sys_file = f

# TODO: prompt the user to select the right driver
if not first_sys_file:
raise CommandError('Could not find any *.sys file in the INF file. '
'Make sure the INF file is valid and belongs to a '
'Windows driver')

# Determine the architecture of the first sys file
first_sys_file = os.path.realpath(first_sys_file)
arch, _ = _get_arch(first_sys_file)
if not arch:
raise CommandError('Could not determine architecture for %s' %
first_sys_file)

options['target_files'] = [target_path] + driver_files
options['target_arch'] = arch

# TODO: support multiple kernel drivers
options['modules'] = [(os.path.basename(first_sys_file), True)]

call_command(WindowsDriverProject(), *args, **options)


def _extract_inf_files(target_path):
driver = Driver(target_path)
driver.analyze()
driver_files = driver.get_files()
if not driver_files:
raise CommandError('Driver has no files')
def _handle_with_file(target_path, proj_class, *args, **options):
target = Target.from_file(target_path, proj_class)
options['target'] = target

base_dir = os.path.dirname(target_path)
return call_command(target.initialize_project(), *args, **options)

logger.info(' Driver files:')
file_paths = []
for f in driver_files:
full_path = os.path.join(base_dir, f)
if not os.path.exists(full_path):
if full_path.endswith('.cat'):
logger.warn('Catalog file %s is missing', full_path)
continue
else:
raise CommandError('%s does not exist' % full_path)

logger.info(' %s', full_path)
file_paths.append(full_path)

return list(set(file_paths))


def _handle_generic_project(target_path, *args, **options):
arch, proj_class = _get_arch(target_path)
if not arch:
raise CommandError('%s is not a valid target for S2E analysis' % target_path)

options['target_files'] = [target_path]
options['target_arch'] = arch

# The module list is a list of tuples where the first element is the module
# name and the second element is True if the module is a kernel module
options['modules'] = [(os.path.basename(target_path), False)]

call_command(proj_class(), *args, **options)


def _handle_with_file(target_path, *args, **options):
# Check that the target is a valid file
if not os.path.isfile(target_path):
raise CommandError('Target %s is not valid' % target_path)

if target_path.endswith('.inf'):
# Don't call realpath on an inf file. Doing so will force
# lookup of binary files in the same directory as the actual inf file.
logger.info('Detected Windows INF file, attempting to create a driver project...')
driver_files = _extract_inf_files(target_path)

_handle_win_driver_project(target_path, driver_files, *args, **options)
elif target_path.endswith('.sys'):
logger.info('Detected Windows SYS file, attempting to create a driver project...')
target_path = os.path.realpath(target_path)

_handle_win_driver_project(target_path, [], *args, **options)
else:
target_path = os.path.realpath(target_path)

_handle_generic_project(target_path, *args, **options)


def _handle_empty_project(*args, **options):
def _handle_empty_project(proj_class, *args, **options):
if not options['no_target']:
raise CommandError('No target binary specified. Use the -m option to '
'create an empty project')
Expand All @@ -224,21 +87,19 @@ def _handle_empty_project(*args, **options):
raise CommandError('An empty project requires a name. Use the -n '
'option to specify one')

project_types = PROJECT_CLASSES.keys()
if options['type'] not in project_types:
raise CommandError('An empty project requires a type. Use the -t '
'option and specify one from %s' % project_types)

options['target_files'] = []
options['target_arch'] = None
# If the project class wasn't explicitly overridden programmatically, get
# one of the default project classes from the command line
if not proj_class:
project_types = PROJECT_TYPES.keys()
if options['type'] not in project_types:
raise CommandError('An empty project requires a type. Use the -t '
'option and specify one from %s' % project_types)
proj_class = PROJECT_TYPES[options['type']]

# The module list is a list of tuples where the first element is
# the module name and the second element is True if the module is
# a kernel module
options['modules'] = []
target = Target.empty(proj_class)
options['target'] = target

project = PROJECT_CLASSES[options['type']]
call_command(project(), *args, **options)
return call_command(target.initialize_project(), *args, **options)


class Command(EnvCommand):
Expand Down Expand Up @@ -274,11 +135,12 @@ def add_arguments(self, parser):

parser.add_argument('-m', '--no-target', required=False, default=False,
action='store_true',
help='Create an empty, target-less project. Used when no binary is needed')
help='Create an empty, target-less project. Used '
'when no binary is needed')

parser.add_argument('-t', '--type', required=False, default=None,
help='Project type (%s), valid only when creating empty projects' %
','.join(PROJECT_CLASSES.keys()))
','.join(PROJECT_TYPES.keys()))

parser.add_argument('-s', '--use-seeds', action='store_true',
help='Use this option to use seeds for creating '
Expand All @@ -302,13 +164,8 @@ def handle(self, *args, **options):
# it is typically used when creating a custom project programatically.
# It provides a class that is instantiated with the current
# command-line arguments and options
proj_class = options.get('project_class')
if proj_class:
if not issubclass(proj_class, AbstractProject):
raise CommandError('Custom projects must be a subclass of '
'AbstractProject')
call_command(proj_class(), *args, **options)
elif options['target']:
_handle_with_file(options.pop('target'), *args, **options)
proj_class = options.pop('project_class', None)
if options['target']:
_handle_with_file(options.pop('target'), proj_class, *args, **options)
else:
_handle_empty_project(*args, **options)
_handle_empty_project(proj_class, *args, **options)
Loading