Skip to content

Commit

Permalink
commands/new_project: added bios project support
Browse files Browse the repository at this point in the history
Signed-off-by: Vitaly Chipounov <[email protected]>
  • Loading branch information
vitalych committed Oct 1, 2023
1 parent 46039c3 commit 274e9ec
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 50 deletions.
64 changes: 42 additions & 22 deletions s2e_env/commands/new_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from s2e_env.command import EnvCommand, CommandError
from s2e_env.commands.project_creation import CGCProject, LinuxProject, AbstractProject
from s2e_env.commands.project_creation import WindowsExeProject, \
WindowsDLLProject, WindowsDriverProject
WindowsDLLProject, WindowsDriverProject, BIOSProject
from s2e_env.commands.project_creation import Target
from s2e_env.commands.project_creation.abstract_project import validate_arguments, SUPPORTED_TOOLS
from s2e_env.infparser.driver import Driver
Expand All @@ -45,6 +45,7 @@
'windows': WindowsExeProject,
'windows_dll': WindowsDLLProject,
'windows_driver': WindowsDriverProject,
'bios': BIOSProject
}

# Paths
Expand All @@ -63,6 +64,9 @@
PE64_REGEX = re.compile(r'^PE32\+ executable')
MSDOS_REGEX = re.compile(r'^MS-DOS executable')

ARCH_I386 = 'i386'
ARCH_X86_64 = 'x86_64'
SUPPORTED_ARCHS=[ARCH_I386, ARCH_X86_64]

def _determine_arch_and_proj(target_path):
"""
Expand All @@ -79,16 +83,16 @@ def _determine_arch_and_proj(target_path):
"""
default_magic = Magic()
magic_checks = (
(Magic(magic_file=CGC_MAGIC), CGC_REGEX, CGCProject, 'i386', 'decree'),
(default_magic, ELF32_REGEX, LinuxProject, 'i386', 'linux'),
(default_magic, ELF64_REGEX, LinuxProject, 'x86_64', 'linux'),
(default_magic, DLL32_REGEX, WindowsDLLProject, 'i386', 'windows'),
(default_magic, DLL64_REGEX, WindowsDLLProject, 'x86_64', 'windows'),
(default_magic, WIN32_DRIVER_REGEX, WindowsDriverProject, 'i386', 'windows'),
(default_magic, WIN64_DRIVER_REGEX, WindowsDriverProject, 'x86_64', 'windows'),
(default_magic, PE32_REGEX, WindowsExeProject, 'i386', 'windows'),
(default_magic, PE64_REGEX, WindowsExeProject, 'x86_64', 'windows'),
(default_magic, MSDOS_REGEX, WindowsExeProject, 'i386', 'windows'),
(Magic(magic_file=CGC_MAGIC), CGC_REGEX, CGCProject, ARCH_I386, 'decree'),
(default_magic, ELF32_REGEX, LinuxProject, ARCH_I386, 'linux'),
(default_magic, ELF64_REGEX, LinuxProject, ARCH_X86_64, 'linux'),
(default_magic, DLL32_REGEX, WindowsDLLProject, ARCH_I386, 'windows'),
(default_magic, DLL64_REGEX, WindowsDLLProject, ARCH_X86_64, 'windows'),
(default_magic, WIN32_DRIVER_REGEX, WindowsDriverProject, ARCH_I386, 'windows'),
(default_magic, WIN64_DRIVER_REGEX, WindowsDriverProject, ARCH_X86_64, 'windows'),
(default_magic, PE32_REGEX, WindowsExeProject, ARCH_I386, 'windows'),
(default_magic, PE64_REGEX, WindowsExeProject, ARCH_X86_64, 'windows'),
(default_magic, MSDOS_REGEX, WindowsExeProject, ARCH_I386, 'windows'),
)

# Need to resolve symbolic links, otherwise magic will report the file type
Expand Down Expand Up @@ -189,12 +193,15 @@ def _parse_sym_args(sym_args_str):
return sym_args


def target_from_file(path, args=None, project_class=None):
def target_from_file(path, args=None, project_class=None, custom_arch=None):
files = _translate_target_to_files(path)
path_to_analyze = files[0]
aux_files = files[1:]

arch, op_sys, proj_class = _determine_arch_and_proj(path_to_analyze)
if not arch:
arch = custom_arch

if not arch:
raise Exception(f'Could not determine architecture for {path_to_analyze}')

Expand All @@ -208,13 +215,28 @@ def target_from_file(path, args=None, project_class=None):


def _handle_with_file(target_path, target_args, proj_class, *args, **options):
target, proj_class = target_from_file(target_path, target_args, proj_class)
arch = options.get('arch', None)
if arch and arch not in SUPPORTED_ARCHS:
raise Exception(f'Architecture {arch} is not supported')

target, proj_class = target_from_file(target_path, target_args, proj_class, arch)
options['target'] = target

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


def _get_project_class(**options):
project_types = list(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)
return PROJECT_TYPES[options['type']]


def _handle_empty_project(proj_class, *args, **options):
if not proj_class:
raise CommandError('Please specify a project type')

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

# 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 = list(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']]

options['target'] = Target.empty()

return call_command(proj_class(), *args, **options)
Expand Down Expand Up @@ -287,6 +300,10 @@ def add_arguments(self, parser):
'seeds themselves and place them in the '
'project\'s ``seeds`` directory')

parser.add_argument('--arch', required=False, default=None,
help='Architecture for binaries whose architecture cannot be autodetected. '
f'Supported architectures: {SUPPORTED_ARCHS}')

parser.add_argument('--tools', type=lambda s: s.split(','),
default=[],
help='Comma-separated list of tools to enable '
Expand All @@ -312,6 +329,9 @@ def handle(self, *args, **options):
# It provides a class that is instantiated with the current
# command-line arguments and options
proj_class = options.pop('project_class', None)
if not proj_class:
proj_class = _get_project_class(**options)

if options['target']:
_handle_with_file(options.pop('target'), options.pop('target_args'), proj_class, *args, **options)
else:
Expand Down
1 change: 1 addition & 0 deletions s2e_env/commands/project_creation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@
from .target import Target
from .windows_project import WindowsExeProject, WindowsDLLProject, \
WindowsDriverProject
from .bios_project import BIOSProject
5 changes: 4 additions & 1 deletion s2e_env/commands/project_creation/abstract_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,10 @@ def _save_json_description(self, project_dir, config):
# when images are rebuilt. Instead, always use latest images.json
# in guest-images repo. This avoids forcing users to create new projects
# everytime guest image parameters change.
config_copy['image'] = os.path.dirname(config['image']['path'])
if config['image'].get('path', None):
config_copy['image'] = os.path.dirname(config['image']['path'])
else:
config_copy['image'] = None

project_desc_path = os.path.join(project_dir, 'project.json')
with open(project_desc_path, 'w', encoding='utf-8') as f:
Expand Down
53 changes: 35 additions & 18 deletions s2e_env/commands/project_creation/base_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,31 +79,38 @@ class BaseProject(AbstractProject):

supported_tools = []

def __init__(self, bootstrap_template, lua_template):
def __init__(self, bootstrap_template, lua_template, image_override=None):
super().__init__()

self._bootstrap_template = bootstrap_template
self._lua_template = lua_template
self._image_override = image_override

def _configure(self, target, *args, **options):
if target.is_empty():
logger.warning('Creating a project without a target file. You must manually edit bootstrap.sh')

# Decide on the image to be used
img_desc = self._select_image(target, options.get('image'),
options.get('download_image', False))
img_desc = None
guestfs_paths = []

# Check architecture consistency (if the target has been specified)
if target.path and not is_valid_arch(target.arch, img_desc['os']):
raise CommandError(f'Binary is {target.arch} while VM image is {img_desc["os"]["arch"]}. Please '
'choose another image')
if self._image_override:
img_desc = self._image_override
else:
img_desc = self._select_image(target, options.get('image'),
options.get('download_image', False))

# Check architecture consistency (if the target has been specified)
if target.path and not is_valid_arch(target.arch, img_desc['os']):
raise CommandError(f'Binary is {target.arch} while VM image is {img_desc["os"]["arch"]}. Please '
'choose another image')

# Determine if guestfs is available for this image
guestfs_paths = self._select_guestfs(img_desc)
if not guestfs_paths:
logger.warning('No guestfs available. The VMI plugin may not run optimally')
# Determine if guestfs is available for this image
guestfs_paths = self._select_guestfs(img_desc)
if not guestfs_paths:
logger.warning('No guestfs available. The VMI plugin may not run optimally')

target.translated_path = self._translate_target_path_to_guestfs(target.path, guestfs_paths)
target.translated_path = self._translate_target_path_to_guestfs(target.path, guestfs_paths)

# Generate the name of the project directory. The default project name
# is the target program name without any file extension
Expand Down Expand Up @@ -293,15 +300,21 @@ def _create_launch_script(self, project_dir, config):
"""
logger.info('Creating launch script')

image = config.get('image')
rel_image_path = None
if image.get('path', None):
rel_image_path = os.path.relpath(config['image']['path'], self.env_path())

context = {
'creation_time': config['creation_time'],
'env_dir': self.env_path(),
'rel_image_path': os.path.relpath(config['image']['path'], self.env_path()),
'qemu_arch': config['image']['qemu_build'],
'qemu_memory': config['image']['memory'],
'qemu_snapshot': config['image']['snapshot'],
'qemu_extra_flags': config['image']['qemu_extra_flags'],
'single_path': config['single_path'],
'rel_image_path': rel_image_path,
'qemu_arch': image['qemu_build'],
'qemu_memory': image['memory'],
'qemu_snapshot': image['snapshot'],
'qemu_extra_flags': image['qemu_extra_flags'],
'qemu_bios': config.get('bios', ''),
'single_path': config['single_path']
}

template = 'launch-s2e.sh'
Expand All @@ -328,6 +341,10 @@ def _create_bootstrap(self, project_dir, config):
"""
Create the S2E bootstrap script.
"""
if not self._bootstrap_template:
logger.info('Project does not support bootstrap script')
return

logger.info('Creating S2E bootstrap script')

context = config.copy()
Expand Down
62 changes: 62 additions & 0 deletions s2e_env/commands/project_creation/bios_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Copyright (c) 2023 Vitaly Chipounov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""


import logging

from s2e_env.command import CommandError

from .base_project import BaseProject


logger = logging.getLogger('new_project')


class BIOSProject(BaseProject):
supported_tools = []
image = {
"image_group": "bios",
"memory": "4M",
"name": "bios",
"os": None,
"qemu_build": "x86_64",
"qemu_extra_flags": "-net none -net nic,model=e1000 ",
"snapshot": None,
"version": 3
}

def __init__(self):
super().__init__(None, 's2e-config.bios.lua', BIOSProject.image)

def _is_valid_image(self, target, os_desc):
# Any image is ok for BIOS, they will just be ignored.
return True

def _finalize_config(self, config):
config['project_type'] = 'bios'

args = config.get('target').args.raw_args
if args:
raise CommandError('Command line arguments for BIOS binaries '
'not supported')

config['bios'] = config['target'].path
43 changes: 34 additions & 9 deletions s2e_env/templates/launch-s2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ if [ "x$1" = "xdebug" ]; then
shift
fi

{% if rel_image_path %}

IMAGE_PATH="$ENV_DIR/{{ rel_image_path }}"
IMAGE_JSON="$(dirname $IMAGE_PATH)/image.json"

Expand All @@ -31,10 +33,24 @@ if [ ! -f "$IMAGE_PATH" -o ! -f "$IMAGE_JSON" ]; then
exit 1
fi

QEMU_DRIVE="-drive file=$IMAGE_PATH,format=s2e,cache=writeback"
QEMU_EXTRA_FLAGS=$(jq -r '.qemu_extra_flags' "$IMAGE_JSON")
QEMU_MEMORY=$(jq -r '.memory' "$IMAGE_JSON")
QEMU_SNAPSHOT=$(jq -r '.snapshot' "$IMAGE_JSON")
QEMU_DRIVE="-drive file=$IMAGE_PATH,format=s2e,cache=writeback"

{% else %}

QEMU_EXTRA_FLAGS="{{qemu_extra_flags}}"
QEMU_MEMORY="{{qemu_memory}}"
{% if qemu_snapshot %}
QEMU_SNAPSHOT="{{qemu_snapshot}}"
{% endif %}
{% if qemu_bios %}
QEMU_BIOS="-bios \"{{qemu_bios}}\""
{% endif %}

{% endif %}


export S2E_CONFIG=s2e-config.lua
export S2E_SHARED_DIR=$INSTALL_DIR/share/libs2e
Expand All @@ -47,6 +63,21 @@ if [ $S2E_MAX_PROCESSES -gt 1 ]; then
export GRAPHICS=-nographic
fi

QEMU_ARGS="-k en-us -monitor null -enable-kvm -serial file:serial.txt $GRAPHICS -m $QEMU_MEMORY $QEMU_EXTRA_FLAGS"

if [ "x$QEMU_DRIVE" != "x" ]; then
QEMU_ARGS="$QEMU_ARGS $QEMU_DRIVE"
fi

if [ "x$QEMU_SNAPSHOT" != "x" ]; then
QEMU_ARGS="$QEMU_ARGS -loadvm $QEMU_SNAPSHOT"
fi

if [ "x$QEMU_BIOS" != "x" ]; then
QEMU_ARGS="$QEMU_ARGS $QEMU_BIOS"
QEMU_ARGS="$QEMU_ARGS -chardev stdio,id=seabios -device isa-debugcon,iobase=0x402,chardev=seabios"
fi

if [ "x$DEBUG" != "x" ]; then

if [ ! -d "$BUILD_DIR/qemu-$BUILD" ]; then
Expand Down Expand Up @@ -78,19 +109,13 @@ if [ "x$DEBUG" != "x" ]; then
# - Display debug output from the BIOS:
# -chardev stdio,id=seabios -device isa-debugcon,iobase=0x402,chardev=seabios

$GDB $QEMU $QEMU_DRIVE \
-k en-us $GRAPHICS -monitor null -m $QEMU_MEMORY -enable-kvm \
-serial file:serial.txt $QEMU_EXTRA_FLAGS \
-loadvm $QEMU_SNAPSHOT $*
$GDB $QEMU $QEMU_ARGS $*

else
QEMU="$INSTALL_DIR/bin/qemu-system-{{ qemu_arch }}"
LIBS2E="$INSTALL_DIR/share/libs2e/libs2e-{{ qemu_arch }}-$S2E_MODE.so"

LD_PRELOAD=$LIBS2E $QEMU $QEMU_DRIVE \
-k en-us $GRAPHICS -monitor null -m $QEMU_MEMORY -enable-kvm \
-serial file:serial.txt $QEMU_EXTRA_FLAGS \
-loadvm $QEMU_SNAPSHOT $* &
LD_PRELOAD=$LIBS2E $QEMU $QEMU_ARGS $* &

CHILD_PID=$!
trap "kill $CHILD_PID" SIGINT
Expand Down
11 changes: 11 additions & 0 deletions s2e_env/templates/s2e-config.bios.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
add_plugin("RawMonitor")
pluginsConfig.RawMonitor = {
kernelStart=0xd0000,
bios={
name="bios",
size=0x20000,
start=0xd0000,
nativebase=0xd0000,
kernelmode=true
}
}

0 comments on commit 274e9ec

Please sign in to comment.