Added Interrupt structure to State structure for x86. It's not an ideal way of configuring interrupts, but it currently seems like the only way to handle conditional interrupts, e.g. via BOUND or INTO. I might need something similar for SYSCALL vs. SYSENTER. Split the build script out into a build script and a build system. Made it so that the IDA script splits interrupts off into their own blocks. This is so that we can restart interrupts by jumping back to the blocks associated with the address of the instruction. Moving the tools stuff out into its own repo as it complicates things to have it in the source.

This commit is contained in:
Peter Goodman
2016-01-27 14:25:52 -05:00
parent 09bf511d1a
commit ecb1f39266
16 changed files with 431 additions and 806 deletions
+2
View File
@@ -36,3 +36,5 @@ generated/*
*.exe
*.out
*.app
*.pyc
+5
View File
@@ -46,6 +46,11 @@
ALWAYS_INLINE static void name ( \
State &state, const addr_t next_pc, ##__VA_ARGS__) noexcept
// Define a semantics implementing function that is also an instruction.
#define DEF_ISEL_SEM(name, ...) \
extern "C" ALWAYS_INLINE void name ( \
State &state, const addr_t next_pc, ##__VA_ARGS__) noexcept
// An instruction where the implementation is the same for all operand sizes.
#define DEF_ISEL_ALL(name, func) \
DEF_ISEL(name ## _8) = func ; \
+9 -8
View File
@@ -246,22 +246,22 @@ bool Instr::CheckArgumentTypes(const llvm::Function *F,
llvm::Function *Instr::GetInstructionFunction(void) {
auto func_name = InstructionFunctionName(xedd);
llvm::Function *F = FindFunction(M, func_name);
llvm::Function *IF = FindFunction(M, func_name);
llvm::GlobalVariable *FP = FindGlobaVariable(M, func_name);
if (!F && FP) {
if (!IF && FP) {
CHECK(FP->isConstant() && FP->hasInitializer())
<< "Expected a `constexpr` variable as the function pointer.";
llvm::Constant *FC = FP->getInitializer()->stripPointerCasts();
F = llvm::dyn_cast<llvm::Function>(FC);
IF = llvm::dyn_cast<llvm::Function>(FC);
}
// TODO(pag): Memory leak of `args`, `prepend_instrs`, and `append_instrs`.
if (F && !CheckArgumentTypes(F, func_name)) {
if (!IF || !CheckArgumentTypes(IF, func_name)) {
LOG(WARNING) << "Missing instruction semantics for " << func_name;
}
return F;
return IF;
}
// Lift a generic instruction.
@@ -282,10 +282,10 @@ void Instr::LiftGeneric(const Translator &lifter) {
LiftOperand(lifter, i);
}
if (auto F = GetInstructionFunction()) {
if (auto IF = GetInstructionFunction()) {
auto &IList = B->getInstList();
IList.insert(IList.end(), prepend_instrs.rbegin(), prepend_instrs.rend());
IList.push_back(llvm::CallInst::Create(F, args));
IList.push_back(llvm::CallInst::Create(IF, args));
IList.insert(IList.end(), append_instrs.begin(), append_instrs.end());
}
}
@@ -607,7 +607,8 @@ bool Instr::IsSystemReturn(void) const {
}
bool Instr::IsInterruptCall(void) const {
return XED_ICLASS_INT <= iclass && XED_ICLASS_INTO >= iclass;
return (XED_ICLASS_INT <= iclass && XED_ICLASS_INTO >= iclass) ||
XED_ICLASS_BOUND == iclass;
}
bool Instr::IsInterruptReturn(void) const {
+1
View File
@@ -12,6 +12,7 @@
#include "mcsema/Arch/X86/Semantics/CMOV.h"
#include "mcsema/Arch/X86/Semantics/COND_BR.h"
#include "mcsema/Arch/X86/Semantics/DATAXFER.h"
#include "mcsema/Arch/X86/Semantics/INTERRUPT.h"
#include "mcsema/Arch/X86/Semantics/FMA.h"
#include "mcsema/Arch/X86/Semantics/LOGICAL.h"
#include "mcsema/Arch/X86/Semantics/MISC.h"
+16 -1
View File
@@ -258,6 +258,18 @@ enum : size_t {
kNumVecRegisters = 32
};
// Representing the state of an in-progress interrupt request.
//
// TODO(pag): This is kind of an ugly solution to there not being a particularly
// nice way of "configuring" interrupts for the
// `__mcsema_interrupt_call` intrinsic, especially in the case of
// conditional interrupts.
struct alignas(sizeof(addr_t)) Interrupt {
addr_t vector;
addr_t next_pc;
bool trigger;
};
struct alignas(64) State final {
// Native `FXSAVE64` representation of the FPU, plus a semi-duplicate
// representation of all vector regs (XMM, YMM, ZMM).
@@ -275,6 +287,8 @@ struct alignas(64) State final {
Segments seg; // 12 bytes, padded to 16 bytes.
GPR gpr; // 136 bytes.
Interrupt interrupt; // 2 * sizeof(addr_t) bytes.
// Total: 2728 bytes, padded to 2752 bytes.
} __attribute__((packed));
@@ -296,6 +310,7 @@ static_assert(2576 == __builtin_offsetof(State, seg),
static_assert(2592 == __builtin_offsetof(State, gpr),
"Invalid packing of `State::seg`.");
static_assert(2752 == sizeof(State), "Invalid packing of `State`.");
static_assert(2728 == __builtin_offsetof(State, interrupt),
"Invalid packing of `State::interrupt`.");
#endif // MCSEMA_ARCH_X86_RUNTIME_STATE_H_
+57
View File
@@ -0,0 +1,57 @@
/* Copyright 2016 Peter Goodman (peter@trailofbits.com), all rights reserved. */
#ifndef MCSEMA_ARCH_X86_SEMANTICS_INTERRUPT_H_
#define MCSEMA_ARCH_X86_SEMANTICS_INTERRUPT_H_
namespace {
#if 32 == ADDRESS_SIZE_BITS
template <typename S1, typename S2>
DEF_SEM(BOUND, S1 idx_, S2 bounds) {
const auto idx = R(idx_);
const auto lb = R(bounds);
const auto ub = R(S2{A(bounds) + sizeof(idx)});
if (idx < lb || ub < idx) {
state.interrupt.next_pc = next_pc;
state.interrupt.vector = 5;
state.interrupt.trigger = true;
} else {
state.interrupt.next_pc = next_pc;
state.interrupt.trigger = false;
}
}
#endif
} // namespace
DEF_ISEL_SEM(INT_IMMb, I8 num) {
state.interrupt.next_pc = next_pc;
state.interrupt.vector = R(num);
state.interrupt.trigger = true;
}
DEF_ISEL_SEM(INT1) {
INT_IMMb(state, next_pc, {1});
state.interrupt.trigger = true;
}
DEF_ISEL_SEM(INT3) {
INT_IMMb(state, next_pc, {3});
state.interrupt.trigger = true;
}
#if 32 == ADDRESS_SIZE_BITS
DEF_ISEL_SEM(INTO) {
if (state.aflag.of) {
INT_IMMb(state, next_pc, {4});
} else {
state.interrupt.next_pc = next_pc;
state.interrupt.trigger = false;
}
}
DEF_ISEL(BOUND_GPRv_MEMa16_16) = BOUND<R16, M16>;
DEF_ISEL(BOUND_GPRv_MEMa32_32) = BOUND<R32, M32>;
#endif // 32 == ADDRESS_SIZE_BITS
#endif // MCSEMA_ARCH_X86_SEMANTICS_INTERRUPT_H_
View File
+2 -2
View File
@@ -29,8 +29,8 @@ if [[ "$OSTYPE" == "linux-gnu" ]]; then
elif [[ "$OSTYPE" == "darwin"* ]]; then
XED_VERSION=xed-install-base-2015-09-10-mac-x86-64
LLVM_RELEASE_DIR=releases/3.7.1
LLVM_RELEASE=3.7.1
LLVM_RELEASE_DIR=releases/3.7.0
LLVM_RELEASE=3.7.0
LLVM_VERSION=clang+llvm-${LLVM_RELEASE}-x86_64-apple-darwin
MCSEMA_OS_NAME="mac"
LIB_EXT=dylib
+24 -336
View File
@@ -1,326 +1,29 @@
#!/usr/bin/env python
import glob
import hashlib
import os
import subprocess
import sys
MCSEMA_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
CC = os.path.join(MCSEMA_DIR, "third_party", "bin", "clang")
CXX = os.path.join(MCSEMA_DIR, "third_party", "bin", "clang++")
OS = {
"darwin": "mac",
"linux": "linux",
"linux2": "linux",
"win32": "win",
}[sys.platform]
SHARED_LIB_EXT = {
"linux": "so",
"mac": "dylib",
"win": "dll",
}[OS]
SRC_DIR = os.path.join(MCSEMA_DIR, "mcsema")
BUILD_DIR = os.path.join(MCSEMA_DIR, "build")
TEST_DIR = os.path.join(MCSEMA_DIR, "tests")
GEN_DIR = os.path.join(MCSEMA_DIR, "generated")
INCLUDE_DIR = os.path.join(MCSEMA_DIR, "third_party", "include")
BIN_DIR = os.path.join(MCSEMA_DIR, "third_party", "bin")
LIB_DIR = os.path.join(MCSEMA_DIR, "third_party", "lib")
TOOLS_DIR = os.path.join(MCSEMA_DIR, "tools")
try:
import concurrent.futures
POOL = concurrent.futures.ThreadPoolExecutor(max_workers=32)
TASKS = []
def Task(func, *args, **kargs):
future = POOL.submit(func, *args, **kargs)
TASKS.append(future)
return future
def FinishAllTasks():
for task in TASKS:
task.result()
POOL.shutdown()
# Don't have a thread pool available.
except:
def Task(func, *args, **kargs):
return func(*args, **kargs)
def FinishAllTasks():
pass
CXX_FLAGS = [
# Enable warnings.
"-Wall",
"-Werror",
"-pedantic",
# Disable specific warnings.
"-Wno-nested-anon-types",
"-Wno-extended-offsetof",
"-Wno-gnu-anonymous-struct",
"-Wno-variadic-macros",
"-Wno-gnu-zero-variadic-macro-arguments",
"-Wno-error=unused-command-line-argument",
"-Wno-override-module",
# Features.
"-fno-omit-frame-pointer",
"-fno-rtti",
"-fno-exceptions",
"-fvisibility-inlines-hidden",
"-std=gnu++11",
# Macros.
'-DMCSEMA_DIR="{}"'.format(MCSEMA_DIR),
'-DMCSEMA_OS="{}"'.format(OS),
"-D__STDC_LIMIT_MACROS",
"-D__STDC_CONSTANT_MACROS",
"-DGOOGLE_PROTOBUF_NO_RTTI",
"-DNDEBUG",
# Includes.
"-isystem", INCLUDE_DIR,
"-I{}".format(MCSEMA_DIR),
# Output info.
"-fPIC",
"-fpie",
"-g3",
"-m64",
]
# Dictionary for memoizing the compilation of source files.
SOURCE_FILES = {}
# Execute a command.
def Command(*args):
args = [str(a) for a in args]
try:
return subprocess.check_output(args)
except:
print "{}\n\n".format(" ".join(args))
pass
# Recursively make directors.
def MakeDirsForFile(file_name):
dir_name = os.path.dirname(file_name)
while not os.path.exists(dir_name):
try:
os.makedirs(dir_name)
except:
pass
class FileName(object):
"""File name wrapper. A file name is either a string or a
Future returning a string."""
def __init__(self, path):
self.path = path
def __str__(self):
if isinstance(self.path, str) or isinstance(self.path, unicode):
return os.path.abspath(self.path)
elif hasattr(self.path, 'result'):
return os.path.abspath(str(self.path.result()))
assert False
class _File(object):
"""Generic file abstraction with a method of extracting the
file location."""
def __init__(self, path):
self.path = path
def Paths(self):
return [self.path]
def __str__(self):
return str(self.path)
class _SourceFile(_File):
"""Source file that will be compiled."""
def __init__(self, source_path, target_path, extra_args):
super(_SourceFile, self).__init__(FileName(Task(
self._Build,
source_path,
target_path,
extra_args)))
def _Build(self, source_path, target_path, extra_args):
MakeDirsForFile(target_path)
args = [CXX]
if "mac" == OS:
args.append("-stdlib=libc++")
args.extend(CXX_FLAGS)
args.extend(extra_args)
args.extend([
"-c", source_path,
"-o", target_path])
Command(*args)
return target_path
# Memoized source file compiler. Names compiled object files
# in terms of the extra args and the path to the source file.
def SourceFile(path, extra_args=[]):
path = os.path.abspath(str(path))
key = hashlib.md5("{}{}".format(path, "".join(extra_args))).hexdigest()
target_path = os.path.join(BUILD_DIR, "{}.o".format(key))
if target_path not in SOURCE_FILES:
SOURCE_FILES[target_path] = _SourceFile(
path, target_path, extra_args)
return SOURCE_FILES[target_path]
class StaticLibrary(_File):
"""Pre-compiled library within the source/library dirs."""
def __init__(self, name):
super(StaticLibrary, self).__init__(self._FindLib(name))
def _FindLib(self, name):
abs_path = os.path.abspath(name)
if os.path.exists(abs_path):
return abs_path
for where in (LIB_DIR, BUILD_DIR):
for ext in ("o", "bc", "so", "dylib", "a"):
for prefix in ("lib", ""):
path = os.path.join(where, "{}{}.{}".format(prefix, name, ext))
if os.path.exists(path):
return path
print "Warning: cannot find object file: {}".format(name)
return name
class ConfigLibraries(object):
"""Set of libraries returned from a configuration command."""
def __init__(self, *args):
self.paths = subprocess.check_output(args).strip().split(" ")
def Paths(self):
return self.paths
class LinkerLibrary(object):
"""A library that the linker will figure out how to find."""
def __init__(self, name, os=None):
global OS
self._name = name
self._include = True
if os and OS != os:
self._include = False
def Paths(self):
paths = []
if self._include:
paths.append("-l{}".format(self._name))
return paths
class _Target(_File):
"""Generic target that must be compiled."""
def __init__(self, path, source_files=[], object_files=[], libraries=[]):
global POOL
path = os.path.abspath(path)
MakeDirsForFile(path)
super(_Target, self).__init__(FileName(Task(
self._Build,
path,
source_files,
object_files,
libraries)))
def _Build(self, path, source_files, object_files, libraries):
args = [CXX]
args.extend(CXX_FLAGS)
if "mac" == OS:
args.append("-stdlib=libc++")
args.extend(self.extra_args)
args.extend([
"-o",
path,
"-L{}".format(LIB_DIR)])
if "linux" == OS:
args.extend([
"-Wl,-z,now",
"-Wl,-rpath={}".format(LIB_DIR),
"-Wl,-gc-sections",
"-Wl,-E"])
elif "mac" == OS:
args.extend([
"-Xlinker", "-rpath", "-Xlinker", LIB_DIR,
"-Wl,-dead_strip",])
for src in source_files:
args.extend(src.Paths())
for obj in object_files:
args.extend(obj.Paths())
for lib in libraries:
args.extend(lib.Paths())
Command(*args)
return path
def Wait(self):
[str(p) for p in self.Paths()]
class TargetExecutable(_Target):
"""Represents an individual binary executable file."""
def __init__(self, *args, **kargs):
self.extra_args = []
super(TargetExecutable, self).__init__(*args, **kargs)
def Execute(self, *args):
Command(self.path, *args)
class TargetLibrary(_Target):
"""Shared library that must be compiled by the system."""
def __init__(self, *args, **kargs):
if "linux" == OS:
self.extra_args = ["-shared"]
elif "mac" == OS:
self.extra_args = [
"-Wl,-flat_namespace",
"-Wl,-undefined,suppress",
"-dynamiclib"]
super(TargetLibrary, self).__init__(*args, **kargs)
# Copyright 2016 Peter Goodman (peter@trailofbits.com), all rights reserved.
if "__main__" != __name__:
exit(0)
from buildsystem import *
if OS not in ("mac", "linux"):
print "Unsupported platform: {}".format(OS)
exit(1)
# Find all source code.
source_paths = [os.path.join(SRC_DIR, "Translate.cpp")]
source_paths = []
source_paths.append(
os.path.join(MCSEMA_SRC_DIR, "Translate.cpp"))
source_paths.extend(list(
glob.glob(os.path.join(SRC_DIR, "Arch", "*.cpp"))))
glob.glob(os.path.join(MCSEMA_SRC_DIR, "Arch", "*.cpp"))))
source_paths.extend(list(
glob.glob(os.path.join(SRC_DIR, "Arch", "X86", "*.cpp"))))
glob.glob(os.path.join(MCSEMA_SRC_DIR, "Arch", "X86", "*.cpp"))))
source_paths.extend(list(
glob.glob(os.path.join(SRC_DIR, "CFG", "*.cpp"))))
glob.glob(os.path.join(MCSEMA_SRC_DIR, "CFG", "*.cpp"))))
source_paths.extend(list(
glob.glob(os.path.join(SRC_DIR, "BC", "*.cpp"))))
glob.glob(os.path.join(MCSEMA_SRC_DIR, "BC", "*.cpp"))))
source_paths.extend(list(
glob.glob(os.path.join(SRC_DIR, "OS", "*.cpp"))))
glob.glob(os.path.join(MCSEMA_SRC_DIR, "OS", "*.cpp"))))
# Find the pre-existing static libraries to link in.
object_files = [
@@ -339,19 +42,19 @@ system_libraries = [
# Find the LLVM libraries to link in.
libraries = [ConfigLibraries(
os.path.join(BIN_DIR, "llvm-config"), "--libs")]
os.path.join(MCSEMA_BIN_DIR, "llvm-config"), "--libs")]
libraries.extend(system_libraries)
# Create a program that lifts a CFG protobuf file into
cfg_to_bc = TargetExecutable(
os.path.join(BUILD_DIR, "cfg_to_bc"),
os.path.join(MCSEMA_BUILD_DIR, "cfg_to_bc"),
source_files=[SourceFile(f) for f in source_paths],
object_files=object_files,
libraries=libraries)
# Create an LLVM plugin for optimizing lifted bitcode files.
libOptimize = TargetLibrary(
os.path.join(BUILD_DIR, "libOptimize.{}".format(SHARED_LIB_EXT)),
os.path.join(MCSEMA_BUILD_DIR, "libOptimize.{}".format(SHARED_LIB_EXT)),
source_files=[
SourceFile(os.path.join(MCSEMA_DIR, "mcsema", "Optimize.cpp"))])
@@ -372,32 +75,32 @@ def BuildTests(arch, bits, suffix, has_avx, has_avx512):
# Create an executable that will create a CFG file describing all of
# the testcases for this arch/config.
gen_cfg = TargetExecutable(
os.path.join(BUILD_DIR, "gen_cfg_{}{}".format(arch, suffix)),
os.path.join(MCSEMA_BUILD_DIR, "gen_cfg_{}{}".format(arch, suffix)),
source_files=[
SourceFile(
os.path.join(TEST_DIR, "X86", "Generate.cpp"),
os.path.join(MCSEMA_TEST_DIR, "X86", "Generate.cpp"),
extra_args=macro_args),
# The `Tests.S` file needs to be compiled with extra arguments
# that enable Clang to assemble code with extra features.
SourceFile(
os.path.join(TEST_DIR, "X86", "Tests.S"),
os.path.join(MCSEMA_TEST_DIR, "X86", "Tests.S"),
extra_args=macro_args+target_args+["-DIN_TEST_GENERATOR"]),
SourceFile(os.path.join(SRC_DIR, "CFG", "CFG.cpp"))],
SourceFile(os.path.join(MCSEMA_SRC_DIR, "CFG", "CFG.cpp"))],
object_files=object_files,
libraries=libraries)
# CFG protobuf that will describe the test cases.
cfg_file = os.path.join(
GEN_DIR, "tests", "cfg_{}{}".format(arch, suffix))
MCSEMA_GEN_DIR, "tests", "cfg_{}{}".format(arch, suffix))
# Lifted bitcode of the test cases.
bc_file = os.path.join(
GEN_DIR, "tests", "bc_{}{}".format(arch, suffix))
MCSEMA_GEN_DIR, "tests", "bc_{}{}".format(arch, suffix))
sem_file = os.path.join(
GEN_DIR, "sem_{}{}.bc".format(arch, suffix))
MCSEMA_GEN_DIR, "sem_{}{}.bc".format(arch, suffix))
MakeDirsForFile(cfg_file)
@@ -416,16 +119,16 @@ def BuildTests(arch, bits, suffix, has_avx, has_avx512):
# Build the test runner.
run_tests = TargetExecutable(
os.path.join(BUILD_DIR, "run_tests_{}{}".format(arch, suffix)),
os.path.join(MCSEMA_BUILD_DIR, "run_tests_{}{}".format(arch, suffix)),
source_files=[
SourceFile(
"{}.bc".format(bc_file),
extra_args=["-O3", "-mno-avx", "-mno-sse"]),
SourceFile(
os.path.join(TEST_DIR, "X86", "Tests.S"),
os.path.join(MCSEMA_TEST_DIR, "X86", "Tests.S"),
extra_args=macro_args+target_args),
SourceFile(
os.path.join(TEST_DIR, "X86", "Run.cpp"),
os.path.join(MCSEMA_TEST_DIR, "X86", "Run.cpp"),
extra_args=macro_args)],
object_files=[
StaticLibrary("gflags"),
@@ -442,20 +145,5 @@ for target in [("x86", 32), ("amd64", 64)]:
suffix, has_avx, has_avx512 = avx
Task(BuildTests, arch, bits, suffix, has_avx, has_avx512)
decree = TargetExecutable(
os.path.join(BUILD_DIR, "decree"),
source_files=[
SourceFile(os.path.join(TOOLS_DIR, "CGC", "Process.cpp")),
SourceFile(os.path.join(TOOLS_DIR, "CGC", "Snapshot.cpp")),
SourceFile(os.path.join(TOOLS_DIR, "CGC", "Runtime.cpp"))],
object_files=[
StaticLibrary("gflags"),
StaticLibrary("glog")],
libraries=[
LinkerLibrary("dl"),
LinkerLibrary("pthread"),
LinkerLibrary("c++", os="mac"),
LinkerLibrary("c++abi", os="mac")])
# Wait for all executors to finish.
FinishAllTasks()
+314
View File
@@ -0,0 +1,314 @@
#!/usr/bin/env python
# Copyright 2016 Peter Goodman (peter@trailofbits.com), all rights reserved.
import glob
import hashlib
import os
import subprocess
import sys
MCSEMA_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
CC = os.path.join(MCSEMA_DIR, "third_party", "bin", "clang")
CXX = os.path.join(MCSEMA_DIR, "third_party", "bin", "clang++")
OS = {
"darwin": "mac",
"linux": "linux",
"linux2": "linux",
"win32": "win",
}[sys.platform]
SHARED_LIB_EXT = {
"linux": "so",
"mac": "dylib",
"win": "dll",
}[OS]
MCSEMA_SRC_DIR = os.path.join(MCSEMA_DIR, "mcsema")
MCSEMA_BUILD_DIR = os.path.join(MCSEMA_DIR, "build")
MCSEMA_TEST_DIR = os.path.join(MCSEMA_DIR, "tests")
MCSEMA_GEN_DIR = os.path.join(MCSEMA_DIR, "generated")
MCSEMA_INCLUDE_DIR = os.path.join(MCSEMA_DIR, "third_party", "include")
MCSEMA_BIN_DIR = os.path.join(MCSEMA_DIR, "third_party", "bin")
MCSEMA_LIB_DIR = os.path.join(MCSEMA_DIR, "third_party", "lib")
try:
import concurrent.futures
POOL = concurrent.futures.ThreadPoolExecutor(max_workers=32)
TASKS = []
def Task(func, *args, **kargs):
global POOL, TASKS
future = POOL.submit(func, *args, **kargs)
TASKS.append(future)
return future
def FinishAllTasks():
global TASKS
for task in TASKS:
task.result()
POOL.shutdown()
# Don't have a thread pool available.
except:
def Task(func, *args, **kargs):
return func(*args, **kargs)
def FinishAllTasks():
pass
CXX_FLAGS = [
# Enable warnings.
"-Wall",
"-Werror",
"-pedantic",
# Disable specific warnings.
"-Wno-nested-anon-types",
"-Wno-extended-offsetof",
"-Wno-gnu-anonymous-struct",
"-Wno-variadic-macros",
"-Wno-gnu-zero-variadic-macro-arguments",
"-Wno-error=unused-command-line-argument",
"-Wno-override-module",
# Features.
"-fno-omit-frame-pointer",
"-fno-rtti",
"-fno-exceptions",
"-fvisibility-inlines-hidden",
"-std=gnu++11",
# Macros.
'-DMCSEMA_DIR="{}"'.format(MCSEMA_DIR),
'-DMCSEMA_OS="{}"'.format(OS),
"-D__STDC_LIMIT_MACROS",
"-D__STDC_CONSTANT_MACROS",
"-DGOOGLE_PROTOBUF_NO_RTTI",
"-DNDEBUG",
# Includes.
"-isystem", MCSEMA_INCLUDE_DIR,
"-I{}".format(MCSEMA_DIR),
# Output info.
"-fPIC",
"-fpie",
"-g3",
"-m64",
]
def Command(*args):
"""Executes a command and waits for it to finish. If it fails
then the command itself is printed out."""
args = [str(a) for a in args]
try:
return subprocess.check_output(args)
except:
print "{}\n\n".format(" ".join(args))
pass
def MakeDirsForFile(file_name):
"""Recursively make directories that lead to a particular
file name."""
dir_name = os.path.dirname(file_name)
while not os.path.exists(dir_name):
try:
os.makedirs(dir_name)
except:
pass
class FileName(object):
"""File name wrapper. A file name is either a string or a
Future returning a string."""
def __init__(self, path):
self.path = path
def __str__(self):
if isinstance(self.path, str) or isinstance(self.path, unicode):
return os.path.abspath(self.path)
elif hasattr(self.path, 'result'):
return os.path.abspath(str(self.path.result()))
assert False
class _File(object):
"""Generic file abstraction with a method of extracting the
file location."""
def __init__(self, path):
self.path = path
def Paths(self):
return [self.path]
def __str__(self):
return str(self.path)
class _SourceFile(_File):
"""Source file that will be compiled."""
# Dictionary for memoizing the compilation of source files.
CACHE = {}
def __init__(self, source_path, target_path, extra_args):
super(_SourceFile, self).__init__(FileName(Task(
self._Build,
source_path,
target_path,
extra_args)))
def _Build(self, source_path, target_path, extra_args):
MakeDirsForFile(target_path)
args = [CXX]
if "mac" == OS:
args.append("-stdlib=libc++")
args.extend(CXX_FLAGS)
args.extend(extra_args)
args.extend([
"-c", source_path,
"-o", target_path])
Command(*args)
return target_path
def SourceFile(path, extra_args=[]):
"""Memoized source file compiler. Names compiled object files
in terms of the extra args and the path to the source file."""
path = os.path.abspath(str(path))
key = hashlib.md5("{}{}".format(path, "".join(extra_args))).hexdigest()
target_path = os.path.join(os.sep, "tmp", "build", "{}.o".format(key))
if target_path not in _SourceFile.CACHE:
_SourceFile.CACHE[target_path] = _SourceFile(
path, target_path, extra_args)
return _SourceFile.CACHE[target_path]
class StaticLibrary(_File):
"""Pre-compiled library within the source/library dirs."""
SEARCH_PATHS = [MCSEMA_LIB_DIR, MCSEMA_BUILD_DIR]
def __init__(self, name):
super(StaticLibrary, self).__init__(self._FindLib(name))
def _FindLib(self, name):
abs_path = os.path.abspath(name)
if os.path.exists(abs_path):
return abs_path
for where in self.SEARCH_PATHS:
for ext in ("o", "bc", "so", "dylib", "a"):
for prefix in ("lib", ""):
path = os.path.join(where, "{}{}.{}".format(prefix, name, ext))
if os.path.exists(path):
return path
print "Warning: cannot find object file: {}".format(name)
return name
class ConfigLibraries(object):
"""Set of libraries returned from a configuration command."""
def __init__(self, *args):
self.paths = subprocess.check_output(args).strip().split(" ")
def Paths(self):
return self.paths
class LinkerLibrary(object):
"""A library that the linker will figure out how to find."""
def __init__(self, name, os=None):
global OS
self._name = name
self._include = True
if os and OS != os:
self._include = False
def Paths(self):
paths = []
if self._include:
paths.append("-l{}".format(self._name))
return paths
class _Target(_File):
"""Generic target that must be compiled."""
def __init__(self, path, source_files=[], object_files=[], libraries=[]):
path = os.path.abspath(path)
MakeDirsForFile(path)
super(_Target, self).__init__(FileName(Task(
self._Build,
path,
source_files,
object_files,
libraries)))
def _Build(self, path, source_files, object_files, libraries):
args = [CXX]
args.extend(CXX_FLAGS)
if "mac" == OS:
args.append("-stdlib=libc++")
args.extend(self.extra_args)
args.extend([
"-o",
path,
"-L{}".format(MCSEMA_LIB_DIR)])
if "linux" == OS:
args.extend([
"-Wl,-z,now",
"-Wl,-rpath={}".format(MCSEMA_LIB_DIR),
"-Wl,-gc-sections",
"-Wl,-E"])
elif "mac" == OS:
args.extend([
"-Xlinker", "-rpath", "-Xlinker", MCSEMA_LIB_DIR,
"-Wl,-dead_strip",])
for src in source_files:
args.extend(src.Paths())
for obj in object_files:
args.extend(obj.Paths())
for lib in libraries:
args.extend(lib.Paths())
Command(*args)
return path
def Wait(self):
[str(p) for p in self.Paths()]
class TargetExecutable(_Target):
"""Represents an individual binary executable file."""
def __init__(self, *args, **kargs):
self.extra_args = []
super(TargetExecutable, self).__init__(*args, **kargs)
def Execute(self, *args):
Command(self.path, *args)
class TargetLibrary(_Target):
"""Shared library that must be compiled by the system."""
def __init__(self, *args, **kargs):
if "linux" == OS:
self.extra_args = ["-shared"]
elif "mac" == OS:
self.extra_args = [
"-Wl,-flat_namespace",
"-Wl,-undefined,suppress",
"-dynamiclib"]
super(TargetLibrary, self).__init__(*args, **kargs)
+1 -1
View File
@@ -21,7 +21,7 @@ from generated.CFG import CFG_pb2
CALL_ITYPES = frozenset([idaapi.NN_call, idaapi.NN_callfi, idaapi.NN_callni])
JMP_ITYPES = frozenset([idaapi.NN_jmp, idaapi.NN_jmpfi, idaapi.NN_jmpni])
SYSCALL_ITYPES = frozenset([idaapi.NN_int, idaapi.NN_syscall,
idaapi.NN_sysenter])
idaapi.NN_sysenter, idaapi.NN_bound])
PREFIX_ITYPES = frozenset([idaapi.NN_lock, idaapi.NN_rep, idaapi.NN_repe,
idaapi.NN_repne])
-101
View File
@@ -1,101 +0,0 @@
/* Copyright 2016 Peter Goodman (peter@trailofbits.com), all rights reserved. */
#include <dlfcn.h>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <sstream>
#include <sys/mman.h>
#include "mcsema/Arch/Runtime/Intrinsics.h"
#include "tools/CGC/Process.h"
#include "tools/CGC/Snapshot.h"
DECLARE_string(lifted_dir);
namespace cgc {
namespace {
// Open up a shared library that represents the lifted code from a
// CGC binary.
static void *OpenLiftedCode(int process_id) {
std::stringstream file_name;
const char *begin = "";
if ('/' != FLAGS_lifted_dir[0]) {
begin = ".";
}
file_name << FLAGS_lifted_dir << begin << "/mcsema.lifted." << process_id;
LOG_IF(FATAL, access(file_name.str().c_str(), R_OK))
<< "Unable to locate lifted code: " << file_name.str();
dlerror(); // Clear out any errors.
// Go find the symbol.
auto code = dlopen(file_name.str().c_str(), RTLD_NOW | RTLD_LOCAL);
LOG_IF(FATAL, !code)
<< "Unable to load lifted code from " << file_name.str()
<< ": " << dlerror();
return code;
}
// Allocate a new State structure. These can't be allocated directly because
// they have deleted constructors.
static State *CreateMachineState(void) {
typedef std::aligned_storage<sizeof(State), alignof(State)>::type T;
auto state = reinterpret_cast<State *>(new T);
memset(state, 0, sizeof(State));
return state;
}
} // namespace
// Find the address of a lifted block. Once found, cache the address in a
// hash table.
LiftedBlock *Process::Find(addr_t addr) {
auto &block = blocks[addr];
if (!block) {
block = &__mcsema_undefined_block;
// We have to find the block by its function name, which will be
// `__lifted_block_<id>_<address>`, where `id` will be `1` (because the
// code we lifted will be the "first" binary in the bitcode file, and we
// won't add in more).
std::stringstream func;
func << "__lifted_block_1_0x" << std::hex << addr;
if (auto sym = dlsym(code, func.str().c_str())) {
block = reinterpret_cast<LiftedBlock *>(sym);
}
}
return block;
}
// Execute some lifted code.
void Process::Execute(void) {
Find(this->state->gpr.rip.dword)(*state);
}
// Initialize the process data structures. This chains the processes into
// a circularly linked list.
Process *CreateProcesses(int num_processes) {
Process *first_process = nullptr;
Process **next_process = nullptr;
for (auto i = 0; i < num_processes; ++i) {
auto process = new Process;
if (next_process) {
*next_process = process;
} else {
next_process = &(process->next);
first_process = process;
}
process->state = CreateMachineState();
process->code = OpenLiftedCode(i + 1);
process->is_running = true;
LoadMemoryFromSnapshot(process, i + 1);
}
*next_process = first_process;
return first_process;
}
} // namespace cgc
-36
View File
@@ -1,36 +0,0 @@
/* Copyright 2016 Peter Goodman (peter@trailofbits.com), all rights reserved. */
#ifndef TOOLS_CGC_PROCESS_H_
#define TOOLS_CGC_PROCESS_H_
#define ADDRESS_WIDTH_BITS 32
#define HAS_FEATURE_AVX 0
#define HAS_FEATURE_AVX512 0
#include <unordered_map>
#include "mcsema/Arch/X86/Runtime/State.h"
namespace cgc {
typedef void (LiftedBlock)(State &);
struct Process final {
Process *next;
bool is_running;
void *code;
uintptr_t base;
uintptr_t limit;
std::unordered_map<addr_t, LiftedBlock *> blocks;
State *state;
void Execute(void);
LiftedBlock *Find(addr_t addr);
};
Process *CreateProcesses(int num_processes);
} // namespace cgc
#endif // TOOLS_CGC_PROCESS_H_
-154
View File
@@ -1,154 +0,0 @@
/* Copyright 2016 Peter Goodman (peter@trailofbits.com), all rights reserved. */
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "tools/CGC/Process.h"
DEFINE_string(snapshot_dir, "", "Directory where snapshots are stored.");
DEFINE_string(lifted_dir, "", "Directory where lifted binaries are stored.");
DEFINE_int32(num_exe, 1, "Number of executables to run.");
namespace cgc {
namespace {
// Currently running process.
Process *gProcess = nullptr;
// Schedule the next process.
void Schedule(void) {
gProcess = gProcess->next;
}
// Run some code from the current process. If the process was running then
// this will return `true`, even if the process hit an error, because it
// still made progress by going from a running to error state.
bool Run(void) {
auto was_running = gProcess->is_running;
if (was_running) gProcess->Execute();
return was_running;
}
// Access memory within the current process.
//
// TODO(pag): Implement a try read/write operations instead of using this.
template <typename T>
ALWAYS_INLINE static T &AccessMemory(addr_t addr) {
return *reinterpret_cast<T *>(gProcess->base + addr);
}
} // namespace
} // namespace cgc
extern "C" {
#define MAKE_RW_MEMORY(size) \
uint ## size ## _t __mcsema_read_memory_ ## size(addr_t addr) {\
return cgc::AccessMemory<uint ## size ## _t>(addr); \
} \
void __mcsema_write_memory_ ## size ( \
addr_t addr, const uint ## size ## _t in) { \
cgc::AccessMemory<uint ## size ## _t>(addr) = in; \
}
#define MAKE_RW_VEC_MEMORY(size) \
void __mcsema_read_memory_v ## size(\
addr_t addr, vec ## size ## _t &out) { \
out = cgc::AccessMemory<vec ## size ## _t>(addr); \
} \
void __mcsema_write_memory_v ## size (\
addr_t addr, const vec ## size ## _t &in) { \
cgc::AccessMemory<vec ## size ## _t>(addr) = in; \
}
MAKE_RW_MEMORY(8)
MAKE_RW_MEMORY(16)
MAKE_RW_MEMORY(32)
MAKE_RW_MEMORY(64)
MAKE_RW_VEC_MEMORY(8)
MAKE_RW_VEC_MEMORY(16)
MAKE_RW_VEC_MEMORY(32)
MAKE_RW_VEC_MEMORY(64)
MAKE_RW_VEC_MEMORY(128)
MAKE_RW_VEC_MEMORY(256)
MAKE_RW_VEC_MEMORY(512)
// Address computation intrinsic. This is only used for non-zero
// `address_space`d memory accesses.
addr_t __mcsema_compute_address(const State &, addr_t addr, int) {
LOG(FATAL) << "Computing address of FS- or GS-segmented memory.";
return addr;
}
void __mcsema_barrier_load_load(void) {}
void __mcsema_barrier_load_store(void) {}
void __mcsema_barrier_store_load(void) {}
void __mcsema_barrier_store_store(void) {}
void __mcsema_atomic_begin(addr_t, uint32_t) {}
void __mcsema_atomic_end(addr_t, uint32_t) {}
void __mcsema_defer_inlining(void) {}
void __mcsema_error(State &state) {
LOG(ERROR) << "Error: " << std::hex << state.gpr.rip.dword;
cgc::gProcess->is_running = false;
}
void __mcsema_undefined_block(State &state) {
LOG(ERROR) << "Undefined block: " << std::hex << state.gpr.rip.dword;
cgc::gProcess->is_running = false;
}
void __mcsema_function_call(State &) {}
void __mcsema_function_return(State &) {}
void __mcsema_jump(State &) {}
addr_t __mcsema_conditional_branch(bool cond, addr_t addr_true,
addr_t addr_false) {
return cond ? addr_true : addr_false;
}
void __mcsema_system_call(State &state) {
LOG(FATAL) << "System call: " << std::hex << state.gpr.rip.dword;
}
void __mcsema_system_return(State &state) {
LOG(FATAL) << "System return: " << std::hex << state.gpr.rip.dword;
}
void __mcsema_interrupt_call(State &state) {
LOG(FATAL) << "Interrupt call: " << std::hex << state.gpr.rip.dword;
}
void __mcsema_interrupt_return(State &state) {
LOG(FATAL) << "Interrupt return: " << std::hex << state.gpr.rip.dword;
}
int main(int argc, char **argv, char **) {
google::SetUsageMessage(std::string(argv[0]) + " [options]");
google::ParseCommandLineFlags(&argc, &argv, false);
LOG_IF(FATAL, 0 >= FLAGS_num_exe)
<< "One or more executables must be available.";
LOG_IF(FATAL, FLAGS_snapshot_dir.empty())
<< "Must provide a unique path to a directory where the "
<< "snapshots are located persisted.";
LOG_IF(FATAL, FLAGS_lifted_dir.empty())
<< "Must provide a unique path to a directory where the "
<< "lifted code for each process is located.";
cgc::gProcess = cgc::CreateProcesses(FLAGS_num_exe);
for (bool made_progress = true; made_progress; cgc::Schedule()) {
for (made_progress = false; cgc::Run(); cgc::Schedule()) {
made_progress = true;
}
}
return EXIT_SUCCESS;
}
} // extern C
-153
View File
@@ -1,153 +0,0 @@
/* Copyright 2016 Peter Goodman (peter@trailofbits.com), all rights reserved. */
#include <cstring>
#include <fcntl.h>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <sstream>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/user.h>
#include <unistd.h>
#include "tools/CGC/Snapshot.h"
#include "tools/CGC/Process.h"
DECLARE_string(snapshot_dir);
namespace granary {
enum : uintptr_t {
kPageSize = 4096,
k1GiB = 1ULL << 30ULL,
kProcessSize = k1GiB * 4ULL,
k1MiB = 1048576,
kStackSize = 8 * k1MiB,
kStackLimitPage = 0xbaaaa000U,
kStackEnd = kStackLimitPage + kPageSize,
kStackBegin = kStackEnd - kStackSize,
kMaxAddress = 0xB8000000U,
kReserveNumRanges = 32UL,
kMagicPageBegin = 0x4347c000U,
kMagicPageEnd = kMagicPageBegin + kPageSize,
kTaskSize = 0xFFFFe000U
};
// A range of mapped memory.
struct MappedRange32 final {
public:
inline size_t Size(void) const {
return end - begin;
}
off_t fd_offs;
uint32_t begin;
uint32_t end;
bool is_r;
bool is_w;
bool is_x;
};
// On-disk layout of a snapshot file.
struct alignas(kPageSize) Snapshot32File final {
public:
struct Meta {
struct {
char magic[4];
int exe_num;
} __attribute__((packed));
struct user_regs_struct gregs;
struct user_fpregs_struct fpregs;
};
Meta meta;
enum {
kNumPages = 4,
kNumBytes = kNumPages * kPageSize,
kMaxNumMappedRanges = (kNumBytes - sizeof(Meta)) / sizeof(MappedRange32)
};
MappedRange32 ranges[kMaxNumMappedRanges];
};
static_assert(
sizeof(FPU) == sizeof(struct user_fpregs_struct),
"Invalid structure packing of `FPU` or `struct user_fpregs_struct`.");
} // namespace granary
namespace cgc {
void LoadMemoryFromSnapshot(Process *process, int pid) {
using namespace granary;
auto base = mmap64(
nullptr,
kProcessSize,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE,
-1,
0);
process->base = reinterpret_cast<uintptr_t>(base);
process->limit = process->base + kProcessSize;
const char *begin = "";
if ('/' != FLAGS_snapshot_dir[0]) {
begin = ".";
}
std::stringstream file_name;
file_name << FLAGS_snapshot_dir << begin
<< "/grr.snapshot." << pid << ".persist";
// Open the snapshot file.
auto fd = open(file_name.str().c_str(), O_RDONLY | O_LARGEFILE);
LOG_IF(FATAL, -1 == fd)
<< "Unable to open snapshot file: " << file_name.str();
auto file = reinterpret_cast<Snapshot32File *>(
mmap64(nullptr, sizeof(Snapshot32File),
PROT_READ, MAP_POPULATE | MAP_PRIVATE, fd, 0));
// Map each page range into the process memory.
for (const auto &range : file->ranges) {
auto range_base = reinterpret_cast<void *>(process->base + range.begin);
mmap64(
range_base,
range.end - range.begin,
(range.is_w ? PROT_READ | PROT_WRITE : PROT_READ),
MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE,
fd,
range.fd_offs);
}
// Initialize the XMM registers.
memcpy(&(process->state->fpu), &(file->meta.fpregs), sizeof(FPU));
for (auto i = 0; i < 16; ++i) {
process->state->vec[i].xmm = process->state->fpu.xmm[i];
}
// Initialize the flags.
process->state->rflag.flat = file->meta.gregs.eflags;
process->state->aflag.af = process->state->rflag.af;
process->state->aflag.cf = process->state->rflag.cf;
process->state->aflag.df = process->state->rflag.df;
process->state->aflag.of = process->state->rflag.of;
process->state->aflag.pf = process->state->rflag.pf;
process->state->aflag.sf = process->state->rflag.sf;
process->state->aflag.zf = process->state->rflag.zf;
// Initialize the registers.
process->state->gpr.rax.qword = file->meta.gregs.rax;
process->state->gpr.rbx.qword = file->meta.gregs.rbx;
process->state->gpr.rcx.qword = file->meta.gregs.rcx;
process->state->gpr.rdi.qword = file->meta.gregs.rdi;
process->state->gpr.rdx.qword = file->meta.gregs.rdx;
process->state->gpr.rip.qword = file->meta.gregs.rip;
process->state->gpr.rsi.qword = file->meta.gregs.rsi;
process->state->gpr.rsp.qword = file->meta.gregs.rsp;
}
} // namespace cgc
-14
View File
@@ -1,14 +0,0 @@
/* Copyright 2016 Peter Goodman (peter@trailofbits.com), all rights reserved. */
#ifndef TOOLS_CGC_SNAPSHOT_H_
#define TOOLS_CGC_SNAPSHOT_H_
namespace cgc {
struct Process;
void LoadMemoryFromSnapshot(Process *process, int pid);
} // namespace cgc
#endif // TOOLS_CGC_SNAPSHOT_H_