Skip to content

Commit ee5f1a6

Browse files
committed
added S2E support
1 parent a0162e8 commit ee5f1a6

14 files changed

+672
-15
lines changed

CMakeLists.txt

+45-3
Original file line numberDiff line numberDiff line change
@@ -114,20 +114,62 @@ if (BUILD_LIBFUZZER)
114114
src/lib/Option.c
115115
src/lib/Stream.c
116116
)
117-
117+
118118
target_compile_options(${PROJECT_NAME}_LF PUBLIC -DLIBFUZZER -mno-avx -fsanitize=fuzzer-no-link,undefined)
119-
119+
120120
target_include_directories(${PROJECT_NAME}_LF
121121
PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include"
122122
)
123-
123+
124124
install(
125125
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_LF
126126
LIBRARY DESTINATION lib
127127
ARCHIVE DESTINATION lib
128128
)
129129
endif()
130130

131+
if (NOT DEFINED BUILD_S2E AND DEFINED ENV{BUILD_S2E})
132+
set(BUILD_S2E "$ENV{BUILD_S2E}")
133+
endif()
134+
135+
if (BUILD_S2E)
136+
if (NOT DEFINED S2E_ENV_PATH AND DEFINED ENV{S2EDIR})
137+
SET(S2E_ENV_PATH "$ENV{S2EDIR}")
138+
endif()
139+
140+
GET_FILENAME_COMPONENT(S2E_ENV_PATH ${S2E_ENV_PATH} ABSOLUTE)
141+
if ("${S2E_ENV_PATH}" STREQUAL "")
142+
message(FATAL_ERROR "S2E support enabled but S2E environment path not provided."
143+
" Please activate your S2E environment or set S2E_ENV_PATH")
144+
endif ()
145+
146+
set(S2E_INCLUDE "${S2E_ENV_PATH}/source/s2e/guest/common/include")
147+
if (EXISTS "${S2E_INCLUDE}/s2e/s2e.h")
148+
message(STATUS "Valid S2E environment found at ${S2E_ENV_PATH}")
149+
else ()
150+
message(FATAL_ERROR "s2e.h not found in ${S2E_INCLUDE}/s2e. Are you sure that this is a valid S2E environment?")
151+
endif ()
152+
153+
add_library(${PROJECT_NAME}_S2E STATIC
154+
src/lib/DeepState.c
155+
src/lib/Log.c
156+
src/lib/Option.c
157+
src/lib/Stream.c
158+
)
159+
160+
target_compile_options(${PROJECT_NAME}_S2E PUBLIC -DBUILD_S2E)
161+
162+
target_include_directories(${PROJECT_NAME}_S2E
163+
PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include"
164+
PUBLIC SYSTEM "${S2E_INCLUDE}"
165+
)
166+
167+
install(
168+
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_S2E
169+
LIBRARY DESTINATION lib
170+
ARCHIVE DESTINATION lib
171+
)
172+
endif()
131173

132174
set(SETUP_PY_IN "${CMAKE_SOURCE_DIR}/bin/setup.py.in")
133175
set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py")

README.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The [2018 IEEE Cybersecurity Development Conference](https://secdev.ieee.org/201
1212

1313
* Tests look like Google Test, but can use symbolic execution/fuzzing to generate data (parameterized unit testing)
1414
* Easier to learn than binary analysis tools/fuzzers, but provides similar functionality
15-
* Already supports Manticore, Angr, libFuzzer, file-based fuzzing with e.g., AFL; more back-ends likely in future
15+
* Already supports Manticore, Angr, libFuzzer, S2E, file-based fuzzing with e.g., AFL; more back-ends likely in future
1616
* Switch test generation tool without re-writing test harness
1717
* Work around show-stopper bugs
1818
* Find out which tool works best for your code under test
@@ -296,6 +296,21 @@ Because AFL and other file-based fuzzers only rely on the DeepState
296296
native test executable, they should (like DeepState's built-in simple
297297
fuzzer) work fine on macOS and other Unix-like OSes.
298298

299+
## Test-case Generation with S2E
300+
301+
To enable S2E support, run CMake with the following options:
302+
303+
```shell
304+
cmake -DBUILD_S2E=On -DS2E_ENV_PATH=/path/to/s2e/environment ../
305+
```
306+
307+
Where `S2E_ENV_PATH` is a path to an S2E environment created with [s2e-env](https://github.com/S2E/s2e-env).
308+
309+
The `deepstate-s2e` command will create analysis projects in your S2E
310+
environment. You can then start the S2E analysis by running `launch-s2e.sh`.
311+
Once the analysis completes tests will be available in the `s2e-last/tests/`
312+
directory.
313+
299314
## Contributing
300315

301316
All accepted PRs are awarded bounties by Trail of Bits. Join the #deepstate channel on the [Empire Hacking Slack](https://empireslacking.herokuapp.com/) to discuss ongoing development and claim bounties. Check the [good first issue](https://github.com/trailofbits/deepstate/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label for suggested contributions.

bin/deepstate/main_s2e.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env python
2+
# Copyright (c) 2018 Adrian Herrera
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
import logging
17+
import os
18+
19+
from s2e_env.manage import call_command
20+
from s2e_env.commands.new_project import Command as NewProjectCommand
21+
from s2e_env.utils import log as s2e_log
22+
23+
from deepstate.common import LOG_LEVEL_DEBUG, LOG_LEVEL_TRACE, LOG_LEVEL_INFO, \
24+
LOG_LEVEL_WARNING, LOG_LEVEL_ERROR, LOG_LEVEL_FATAL
25+
from .common import DeepState
26+
from .s2e.project import DeepStateProject
27+
28+
29+
L = logging.getLogger("deepstate.s2e")
30+
L.setLevel(logging.INFO)
31+
32+
LOG_LEVEL_TO_LOGGING_LEVEL = {
33+
LOG_LEVEL_DEBUG: logging.DEBUG,
34+
LOG_LEVEL_TRACE: 15,
35+
LOG_LEVEL_INFO: logging.INFO,
36+
LOG_LEVEL_WARNING: logging.WARNING,
37+
LOG_LEVEL_ERROR: logging.ERROR,
38+
LOG_LEVEL_FATAL: logging.CRITICAL,
39+
}
40+
41+
42+
def get_s2e_env():
43+
s2e_env_dir = os.getenv("S2EDIR")
44+
if not s2e_env_dir:
45+
raise Exception("S2EDIR environment variable not specified. Ensure "
46+
"that s2e_activate.sh has been sourced")
47+
if not os.path.isdir(s2e_env_dir):
48+
raise Exception("S2EDIR {} is invalid".format(s2e_env_dir))
49+
50+
return s2e_env_dir
51+
52+
53+
def main():
54+
"""
55+
Create an s2e-env project that is suitable for analyzing a DeepState test.
56+
"""
57+
args = DeepState.parse_args()
58+
59+
# Sync S2E and DeepState logging levels
60+
s2e_log.configure_logging(level=LOG_LEVEL_TO_LOGGING_LEVEL[args.verbosity])
61+
62+
try:
63+
s2e_env_path = get_s2e_env()
64+
proj_name = "{}-deepstate".format(os.path.basename(args.binary))
65+
66+
call_command(NewProjectCommand(), args.binary, env=s2e_env_path,
67+
name=proj_name, project_class=DeepStateProject,
68+
**vars(args))
69+
except Exception as e:
70+
L.critical("Cannot create an S2E project for %s: %s", args.binary, e)
71+
return 1
72+
73+
return 0
74+
75+
76+
if __name__ == '__main__':
77+
exit(main())

bin/deepstate/s2e/__init__.py

Whitespace-only changes.

bin/deepstate/s2e/project.py

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Copyright (c) 2018 Adrian Herrera
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
import datetime
17+
import logging
18+
import os
19+
import shutil
20+
21+
from s2e_env.command import CommandError
22+
from s2e_env.commands.project_creation.abstract_project import AbstractProject
23+
from s2e_env.utils.templates import render_template
24+
25+
26+
L = logging.getLogger('deepstate.s2e')
27+
L.setLevel(logging.INFO)
28+
29+
# Only Linux targets are supported
30+
ARCH_TO_IMAGE = {
31+
'i386': 'debian-9.2.1-i386',
32+
'x86_64': 'debian-9.2.1-x86_64',
33+
}
34+
35+
DEEPSTATE_TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), 'templates')
36+
37+
INSTRUCTIONS = """
38+
Your DeepState project is available in {project_dir}.
39+
40+
This is a simplified version of a regular S2E analysis project. To start the
41+
analysis:
42+
43+
* cd {project_dir} && ./launch-s2e.sh
44+
45+
This will run the DeepState-enabled program in an S2E guest VM. The generated
46+
tests will appear in the s2e-last/tests directory. The tests can be run using
47+
the `--input_test_files_dir` option on a **NON** S2E-compiled program (running
48+
an S2E-compiled program **outside** of S2E will result in an illegal
49+
instruction error).
50+
51+
Customization
52+
=============
53+
54+
* By default, your analysis will run with {num_workers} worker(s). This can be
55+
changed by modifying the S2E_MAX_PROCESSES variable in launch-s2e.sh
56+
* If your target program depends on other files (e.g., shared libraries, etc.),
57+
then these should be retrieved in bootstrap.sh by adding a call to ${{S2EGET}}
58+
* Only the minimum plugins required to generate tests have been enabled. To
59+
enable more, edit s2e-config.lua
60+
"""
61+
62+
63+
class DeepStateProject(AbstractProject):
64+
"""A simplified S2E analysis project for DeepState-compiled programs."""
65+
66+
def _configure(self, target, *args, **options):
67+
"""
68+
Generate the S2E analysis project configuration.
69+
"""
70+
if target.is_empty():
71+
raise CommandError('Cannot use an empty target for a DeepState '
72+
'project')
73+
74+
# Decide on the image to use
75+
image = ARCH_TO_IMAGE.get(target.arch)
76+
if not image:
77+
raise CommandError('Unable to find a suitable image for %s' %
78+
target.path)
79+
img_desc = self._select_image(target, image, download_image=False)
80+
L.info('Using %s', img_desc['name'])
81+
82+
# Determine if guestfs is available for this image
83+
guestfs_path = self._select_guestfs(img_desc)
84+
if not guestfs_path:
85+
L.warn('No guestfs available. The VMI plugin may not run optimally')
86+
87+
# Return the project config dict
88+
return {
89+
'creation_time': str(datetime.datetime.now()),
90+
'project_dir': self.env_path('projects', options['name']),
91+
'image': img_desc,
92+
'has_guestfs': guestfs_path is not None,
93+
'guestfs_path': guestfs_path,
94+
'target_path': target.path,
95+
'target_arch': target.arch,
96+
'num_workers': options['num_workers'],
97+
}
98+
99+
def _create(self, config, force=False):
100+
"""
101+
Create the S2E analysis project, based on the given configuration.
102+
"""
103+
project_dir = config['project_dir']
104+
105+
# The force option is not exposed on the command-line for a DeepState
106+
# project, so fail if the project directory already exists
107+
if os.path.isdir(project_dir):
108+
raise CommandError('Project directory %s already exists' %
109+
project_dir)
110+
111+
os.mkdir(project_dir)
112+
113+
try:
114+
# Create a symlink to the analysis target
115+
self._symlink_project_files(project_dir, config['target_path'])
116+
117+
# Create a symlink to the guest tools directory
118+
self._symlink_guest_tools(project_dir, config['image'])
119+
120+
# Create a symlink to the guestfs (if it exists)
121+
if config['guestfs_path']:
122+
self._symlink_guestfs(project_dir, config['guestfs_path'])
123+
124+
# Render the templates
125+
self._create_launch_script(project_dir, config)
126+
self._create_lua_config(project_dir, config)
127+
self._create_bootstrap(project_dir, config)
128+
except Exception:
129+
# If anything goes wrong during project creation, remove anything
130+
# incomplete
131+
shutil.rmtree(project_dir)
132+
raise
133+
134+
def _get_instructions(self, config):
135+
"""
136+
Generate instructions.
137+
"""
138+
return INSTRUCTIONS.format(**config)
139+
140+
def _create_launch_script(self, project_dir, config):
141+
"""
142+
Create the S2E launch script.
143+
"""
144+
L.info('Creating launch script')
145+
146+
img_desc = config['image']
147+
context = {
148+
'creation_time': config['creation_time'],
149+
'env_dir': self.env_path(),
150+
'rel_image_path': os.path.relpath(img_desc['path'], self.env_path()),
151+
'max_processes': config['num_workers'],
152+
'qemu_arch': img_desc['qemu_build'],
153+
'qemu_memory': img_desc['memory'],
154+
'qemu_snapshot': img_desc['snapshot'],
155+
'qemu_extra_flags': img_desc['qemu_extra_flags'],
156+
}
157+
158+
output_file = 'launch-s2e.sh'
159+
output_path = os.path.join(project_dir, output_file)
160+
161+
render_template(context, '%s.j2' % output_file, output_path,
162+
templates_dir=DEEPSTATE_TEMPLATES_DIR, executable=True)
163+
164+
def _create_lua_config(self, project_dir, config):
165+
"""
166+
Create the S2E Lua config.
167+
"""
168+
L.info('Creating S2E configuration')
169+
170+
self._copy_lua_library(project_dir)
171+
172+
context = {
173+
'creation_time': config['creation_time'],
174+
'project_dir': config['project_dir'],
175+
'target_process': os.path.basename(config['target_path']),
176+
'has_guestfs': config['has_guestfs'],
177+
'guestfs_path': config['guestfs_path'],
178+
}
179+
180+
output_file = 's2e-config.lua'
181+
output_path = os.path.join(project_dir, output_file)
182+
183+
render_template(context, '%s.j2' % output_file, output_path,
184+
templates_dir=DEEPSTATE_TEMPLATES_DIR)
185+
186+
def _create_bootstrap(self, project_dir, config):
187+
"""
188+
Create the S2E bootstrap script.
189+
"""
190+
L.info('Creating S2E bootstrap script')
191+
192+
context = {
193+
'creation_time': config['creation_time'],
194+
'target': os.path.basename(config['target_path']),
195+
}
196+
197+
output_file = 'bootstrap.sh'
198+
output_path = os.path.join(project_dir, output_file)
199+
200+
render_template(context, '%s.j2' % output_file, output_path,
201+
templates_dir=DEEPSTATE_TEMPLATES_DIR)

bin/deepstate/s2e/templates/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)