]> xenbits.xensource.com Git - people/tklengyel/memory-replay.git/commitdiff
Initial commit of Memory Replay PoC for Xen Project
authorTamas K Lengyel <tamas.lengyel@intel.com>
Thu, 11 Jun 2020 15:16:19 +0000 (09:16 -0600)
committerTamas K Lengyel <tamas.lengyel@intel.com>
Thu, 11 Jun 2020 15:16:19 +0000 (09:16 -0600)
23 files changed:
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
INSTALL [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
configure.ac [new file with mode: 0644]
src/code_tracer.c [new file with mode: 0644]
src/code_tracer.h [new file with mode: 0644]
src/forkvm.c [new file with mode: 0644]
src/forkvm.h [new file with mode: 0644]
src/fuzz.c [new file with mode: 0755]
src/fuzz.h [new file with mode: 0755]
src/main.c [new file with mode: 0644]
src/mem_tracer.c [new file with mode: 0644]
src/mem_tracer.h [new file with mode: 0644]
src/private.h [new file with mode: 0644]
src/shredder.c [new file with mode: 0644]
src/shredder.h [new file with mode: 0644]
src/signal.c [new file with mode: 0644]
src/signal.h [new file with mode: 0644]
src/vmi.c [new file with mode: 0644]
src/vmi.h [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..20db47e
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Tamas K Lengyel, tamas.lengyel@intel.com
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..f1eb755
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,20 @@
+Copyright (c) 2020 Intel Corporation
+
+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.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..8865734
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,368 @@
+Installation Instructions
+*************************
+
+   Copyright (C) 1994-1996, 1999-2002, 2004-2016 Free Software
+Foundation, Inc.
+
+   Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved.  This file is offered as-is,
+without warranty of any kind.
+
+Basic Installation
+==================
+
+   Briefly, the shell command './configure && make && make install'
+should configure, build, and install this package.  The following
+more-detailed instructions are generic; see the 'README' file for
+instructions specific to this package.  Some packages provide this
+'INSTALL' file but do not implement all of the features documented
+below.  The lack of an optional feature in a given package is not
+necessarily a bug.  More recommendations for GNU packages can be found
+in *note Makefile Conventions: (standards)Makefile Conventions.
+
+   The 'configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a 'Makefile' in each directory of the package.
+It may also create one or more '.h' files containing system-dependent
+definitions.  Finally, it creates a shell script 'config.status' that
+you can run in the future to recreate the current configuration, and a
+file 'config.log' containing compiler output (useful mainly for
+debugging 'configure').
+
+   It can also use an optional file (typically called 'config.cache' and
+enabled with '--cache-file=config.cache' or simply '-C') that saves the
+results of its tests to speed up reconfiguring.  Caching is disabled by
+default to prevent problems with accidental use of stale cache files.
+
+   If you need to do unusual things to compile the package, please try
+to figure out how 'configure' could check whether to do them, and mail
+diffs or instructions to the address given in the 'README' so they can
+be considered for the next release.  If you are using the cache, and at
+some point 'config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+   The file 'configure.ac' (or 'configure.in') is used to create
+'configure' by a program called 'autoconf'.  You need 'configure.ac' if
+you want to change it or regenerate 'configure' using a newer version of
+'autoconf'.
+
+   The simplest way to compile this package is:
+
+  1. 'cd' to the directory containing the package's source code and type
+     './configure' to configure the package for your system.
+
+     Running 'configure' might take a while.  While running, it prints
+     some messages telling which features it is checking for.
+
+  2. Type 'make' to compile the package.
+
+  3. Optionally, type 'make check' to run any self-tests that come with
+     the package, generally using the just-built uninstalled binaries.
+
+  4. Type 'make install' to install the programs and any data files and
+     documentation.  When installing into a prefix owned by root, it is
+     recommended that the package be configured and built as a regular
+     user, and only the 'make install' phase executed with root
+     privileges.
+
+  5. Optionally, type 'make installcheck' to repeat any self-tests, but
+     this time using the binaries in their final installed location.
+     This target does not install anything.  Running this target as a
+     regular user, particularly if the prior 'make install' required
+     root privileges, verifies that the installation completed
+     correctly.
+
+  6. You can remove the program binaries and object files from the
+     source code directory by typing 'make clean'.  To also remove the
+     files that 'configure' created (so you can compile the package for
+     a different kind of computer), type 'make distclean'.  There is
+     also a 'make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+  7. Often, you can also type 'make uninstall' to remove the installed
+     files again.  In practice, not all packages have tested that
+     uninstallation works correctly, even though it is required by the
+     GNU Coding Standards.
+
+  8. Some packages, particularly those that use Automake, provide 'make
+     distcheck', which can by used by developers to test that all other
+     targets like 'make install' and 'make uninstall' work correctly.
+     This target is generally not run by end users.
+
+Compilers and Options
+=====================
+
+   Some systems require unusual options for compilation or linking that
+the 'configure' script does not know about.  Run './configure --help'
+for details on some of the pertinent environment variables.
+
+   You can give 'configure' initial values for configuration parameters
+by setting variables in the command line or in the environment.  Here is
+an example:
+
+     ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+   *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+   You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you can use GNU 'make'.  'cd' to the
+directory where you want the object files and executables to go and run
+the 'configure' script.  'configure' automatically checks for the source
+code in the directory that 'configure' is in and in '..'.  This is known
+as a "VPATH" build.
+
+   With a non-GNU 'make', it is safer to compile the package for one
+architecture at a time in the source code directory.  After you have
+installed the package for one architecture, use 'make distclean' before
+reconfiguring for another architecture.
+
+   On MacOS X 10.5 and later systems, you can create libraries and
+executables that work on multiple system types--known as "fat" or
+"universal" binaries--by specifying multiple '-arch' options to the
+compiler but only a single '-arch' option to the preprocessor.  Like
+this:
+
+     ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+                 CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+                 CPP="gcc -E" CXXCPP="g++ -E"
+
+   This is not guaranteed to produce working output in all cases, you
+may have to build one architecture at a time and combine the results
+using the 'lipo' tool if you have problems.
+
+Installation Names
+==================
+
+   By default, 'make install' installs the package's commands under
+'/usr/local/bin', include files under '/usr/local/include', etc.  You
+can specify an installation prefix other than '/usr/local' by giving
+'configure' the option '--prefix=PREFIX', where PREFIX must be an
+absolute file name.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+pass the option '--exec-prefix=PREFIX' to 'configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like '--bindir=DIR' to specify different values for particular
+kinds of files.  Run 'configure --help' for a list of the directories
+you can set and what kinds of files go in them.  In general, the default
+for these options is expressed in terms of '${prefix}', so that
+specifying just '--prefix' will affect all of the other directory
+specifications that were not explicitly provided.
+
+   The most portable way to affect installation locations is to pass the
+correct locations to 'configure'; however, many packages provide one or
+both of the following shortcuts of passing variable assignments to the
+'make install' command line to change installation locations without
+having to reconfigure or recompile.
+
+   The first method involves providing an override variable for each
+affected directory.  For example, 'make install
+prefix=/alternate/directory' will choose an alternate location for all
+directory configuration variables that were expressed in terms of
+'${prefix}'.  Any directories that were specified during 'configure',
+but not in terms of '${prefix}', must each be overridden at install time
+for the entire installation to be relocated.  The approach of makefile
+variable overrides for each directory variable is required by the GNU
+Coding Standards, and ideally causes no recompilation.  However, some
+platforms have known limitations with the semantics of shared libraries
+that end up requiring recompilation when using this method, particularly
+noticeable in packages that use GNU Libtool.
+
+   The second method involves providing the 'DESTDIR' variable.  For
+example, 'make install DESTDIR=/alternate/directory' will prepend
+'/alternate/directory' before all installation names.  The approach of
+'DESTDIR' overrides is not required by the GNU Coding Standards, and
+does not work on platforms that have drive letters.  On the other hand,
+it does better at avoiding recompilation issues, and works well even
+when some directory options were not specified in terms of '${prefix}'
+at 'configure' time.
+
+Optional Features
+=================
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving 'configure' the
+option '--program-prefix=PREFIX' or '--program-suffix=SUFFIX'.
+
+   Some packages pay attention to '--enable-FEATURE' options to
+'configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to '--with-PACKAGE' options, where PACKAGE
+is something like 'gnu-as' or 'x' (for the X Window System).  The
+'README' should mention any '--enable-' and '--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, 'configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the 'configure' options '--x-includes=DIR' and
+'--x-libraries=DIR' to specify their locations.
+
+   Some packages offer the ability to configure how verbose the
+execution of 'make' will be.  For these packages, running './configure
+--enable-silent-rules' sets the default to minimal output, which can be
+overridden with 'make V=1'; while running './configure
+--disable-silent-rules' sets the default to verbose, which can be
+overridden with 'make V=0'.
+
+Particular systems
+==================
+
+   On HP-UX, the default C compiler is not ANSI C compatible.  If GNU CC
+is not installed, it is recommended to use the following options in
+order to use an ANSI C compiler:
+
+     ./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
+
+and if that doesn't work, install pre-built binaries of GCC for HP-UX.
+
+   HP-UX 'make' updates targets which have the same time stamps as their
+prerequisites, which makes it generally unusable when shipped generated
+files such as 'configure' are involved.  Use GNU 'make' instead.
+
+   On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
+parse its '<wchar.h>' header file.  The option '-nodtk' can be used as a
+workaround.  If GNU CC is not installed, it is therefore recommended to
+try
+
+     ./configure CC="cc"
+
+and if that doesn't work, try
+
+     ./configure CC="cc -nodtk"
+
+   On Solaris, don't put '/usr/ucb' early in your 'PATH'.  This
+directory contains several dysfunctional programs; working variants of
+these programs are available in '/usr/bin'.  So, if you need '/usr/ucb'
+in your 'PATH', put it _after_ '/usr/bin'.
+
+   On Haiku, software installed for all users goes in '/boot/common',
+not '/usr/local'.  It is recommended to use the following options:
+
+     ./configure --prefix=/boot/common
+
+Specifying the System Type
+==========================
+
+   There may be some features 'configure' cannot figure out
+automatically, but needs to determine by the type of machine the package
+will run on.  Usually, assuming the package is built to be run on the
+_same_ architectures, 'configure' can figure that out, but if it prints
+a message saying it cannot guess the machine type, give it the
+'--build=TYPE' option.  TYPE can either be a short name for the system
+type, such as 'sun4', or a canonical name which has the form:
+
+     CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+     OS
+     KERNEL-OS
+
+   See the file 'config.sub' for the possible values of each field.  If
+'config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+   If you are _building_ compiler tools for cross-compiling, you should
+use the option '--target=TYPE' to select the type of system they will
+produce code for.
+
+   If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with '--host=TYPE'.
+
+Sharing Defaults
+================
+
+   If you want to set default values for 'configure' scripts to share,
+you can create a site shell script called 'config.site' that gives
+default values for variables like 'CC', 'cache_file', and 'prefix'.
+'configure' looks for 'PREFIX/share/config.site' if it exists, then
+'PREFIX/etc/config.site' if it exists.  Or, you can set the
+'CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all 'configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+   Variables not defined in a site shell script can be set in the
+environment passed to 'configure'.  However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost.  In order to avoid this problem, you should set
+them in the 'configure' command line, using 'VAR=value'.  For example:
+
+     ./configure CC=/usr/local2/bin/gcc
+
+causes the specified 'gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for 'CONFIG_SHELL' due to an
+Autoconf limitation.  Until the limitation is lifted, you can use this
+workaround:
+
+     CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+'configure' Invocation
+======================
+
+   'configure' recognizes the following options to control how it
+operates.
+
+'--help'
+'-h'
+     Print a summary of all of the options to 'configure', and exit.
+
+'--help=short'
+'--help=recursive'
+     Print a summary of the options unique to this package's
+     'configure', and exit.  The 'short' variant lists options used only
+     in the top level, while the 'recursive' variant lists options also
+     present in any nested packages.
+
+'--version'
+'-V'
+     Print the version of Autoconf used to generate the 'configure'
+     script, and exit.
+
+'--cache-file=FILE'
+     Enable the cache: use and save the results of the tests in FILE,
+     traditionally 'config.cache'.  FILE defaults to '/dev/null' to
+     disable caching.
+
+'--config-cache'
+'-C'
+     Alias for '--cache-file=config.cache'.
+
+'--quiet'
+'--silent'
+'-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to '/dev/null' (any error
+     messages will still be shown).
+
+'--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     'configure' can determine that directory automatically.
+
+'--prefix=DIR'
+     Use DIR as the installation prefix.  *note Installation Names:: for
+     more details, including other options available for fine-tuning the
+     installation locations.
+
+'--no-create'
+'-n'
+     Run the configure checks, but stop before creating any output
+     files.
+
+'configure' also accepts some other, not widely useful, options.  Run
+'configure --help' for more details.
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..ebf0029
--- /dev/null
@@ -0,0 +1,19 @@
+AC_INIT([shredder], [1.0])
+AM_INIT_AUTOMAKE([1.14 subdir-objects])
+
+AC_CONFIG_SRCDIR(src/main.c)
+AC_CONFIG_HEADERS(config.h)
+
+AC_CANONICAL_HOST
+AC_PROG_CC
+AM_SANITY_CHECK
+
+PKG_CHECK_MODULES([GLIB], [glib-2.0],[],[AC_MSG_ERROR(GLib not found. Install missing package and re-run)])
+PKG_CHECK_MODULES([VMI], [libvmi],[],[AC_MSG_ERROR(LibVMI not found. Install missing package and re-run)])
+PKG_CHECK_MODULES([CAPSTONE], [capstone],[],[AC_MSG_ERROR(Capstone not found. Install missing package and re-run)])
+AC_CHECK_HEADERS([xenctrl.h], [], [AC_ERROR([libxc headers are not usable. Install missing package and re-run])])
+AC_CHECK_HEADERS([libxl.h], [], [AC_ERROR([libxl headers are not usable. Install missing package and re-run])])
+AC_CHECK_LIB(xenctrl, xc_interface_open,[],[AC_MSG_ERROR(libxc not found. Install missing package and re-run)])
+
+AC_CONFIG_FILES(Makefile)
+AC_OUTPUT
diff --git a/src/code_tracer.c b/src/code_tracer.c
new file mode 100644 (file)
index 0000000..bbb5162
--- /dev/null
@@ -0,0 +1,232 @@
+#include "private.h"
+
+static uint8_t bp = 0xCC;
+
+static void track_coverage(code_tracer_t *code_tracer, uint64_t cur_loc, uint64_t va)
+{
+    cur_loc = (cur_loc >> 4) ^ (cur_loc << 8);
+    cur_loc &= CODE_TRACER_MAP_SIZE - 1;
+    addr_t coverage_point = cur_loc ^ code_tracer->prev_loc;
+
+    code_tracer->map[coverage_point]++;
+    code_tracer->prev_loc = cur_loc >> 1;
+
+    if ( code_tracer->map[coverage_point] == 1 )
+    {
+        printf("\t\tNew code hit at 0x%lx (prev_loc: 0x%lx)\n", va, code_tracer->prev_loc);
+
+        code_tracer->points++;
+        code_tracer->counter = 0; // reset cf counter to allow exploration of the new path
+
+        if ( code_tracer->cb )
+            code_tracer->cb(code_tracer);
+    }
+}
+
+static bool is_cf(unsigned int id)
+{
+    switch ( id )
+    {
+        case X86_INS_JA:
+        case X86_INS_JBE:
+        case X86_INS_JB:
+        case X86_INS_JCXZ:
+        case X86_INS_JECXZ:
+        case X86_INS_JE:
+        case X86_INS_JGE:
+        case X86_INS_JG:
+        case X86_INS_JLE:
+        case X86_INS_JL:
+        case X86_INS_JMP:
+        case X86_INS_LJMP:
+        case X86_INS_JNE:
+        case X86_INS_JNO:
+        case X86_INS_JNP:
+        case X86_INS_JNS:
+        case X86_INS_JO:
+        case X86_INS_JP:
+        case X86_INS_JRCXZ:
+        case X86_INS_JS:
+        case X86_INS_CALL:
+        case X86_INS_RET:
+        case X86_INS_INT3:
+            return true;
+        default:
+            break;
+    }
+
+    return false;
+}
+
+static addr_t next_cf_insn(vmi_instance_t vmi, addr_t cr3, addr_t start, bool *int3)
+{
+    cs_insn *insn;
+
+    size_t count;
+    size_t read;
+
+    unsigned char buff[CODE_TRACER_BUFFER_SIZE] = { 0 };
+    addr_t next_cf = 0;
+
+    access_context_t ctx = {
+        .translate_mechanism = VMI_TM_PROCESS_DTB,
+        .dtb = cr3,
+        .addr = start
+    };
+
+    if ( VMI_FAILURE == vmi_read(vmi, &ctx, CODE_TRACER_BUFFER_SIZE, buff, &read) )
+    {
+        //printf("Failed to grab memory from 0x%lx\n", start);
+        return 0;
+    }
+
+    count = cs_disasm(cs_handle, buff, read, start, 0, &insn);
+    if ( count ) {
+        size_t j;
+        for ( j=0; j<count; j++) {
+             //printf("Next instruction @ 0x%lx: %s!\n", insn[j].address, insn[j].mnemonic);
+
+             if ( is_cf(insn[j].id) )
+             {
+                next_cf = insn[j].address;
+
+                *int3 = (insn[j].id == X86_INS_INT3);
+
+                //printf("\tFound next control flow instruction @ 0x%lx: %s!\n", next_cf, insn[j].mnemonic);
+                break;
+             }
+        }
+        cs_free(insn, count);
+    }
+
+    return next_cf;
+}
+
+static event_response_t singlestep_cb(vmi_instance_t vmi, vmi_event_t *event)
+{
+    addr_t pa = (event->ss_event.gfn << 12) + event->ss_event.offset;
+    code_tracer_t *code_tracer = (code_tracer_t *)event->data;
+
+    track_coverage(code_tracer, pa, event->x86_regs->rip);
+
+    access_context_t ctx = {
+        .translate_mechanism = VMI_TM_PROCESS_DTB,
+        .dtb = event->x86_regs->cr3,
+    };
+
+    bool int3;
+    code_tracer->next_cf = next_cf_insn(vmi, event->x86_regs->cr3, event->x86_regs->rip, &int3);
+    vmi_pagetable_lookup(vmi, event->x86_regs->cr3, code_tracer->next_cf, &code_tracer->next_cf_pa);
+
+    if ( int3 || !code_tracer->next_cf || code_tracer->next_cf == event->interrupt_event.gla )
+    {
+        vmi_pause_vm(vmi);
+        *code_tracer->stop = true;
+        return VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP;
+    }
+
+    ctx.addr = code_tracer->next_cf;
+    vmi_read_8(vmi, &ctx, &code_tracer->backup);
+    vmi_write_8(vmi, &ctx, &bp);
+
+    return VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP;
+}
+
+static event_response_t int3_cb(vmi_instance_t vmi, vmi_event_t *event)
+{
+    addr_t pa = (event->interrupt_event.gfn << 12) + event->interrupt_event.offset;
+    code_tracer_t *code_tracer = (code_tracer_t *)event->data;
+
+    code_tracer->counter++;
+    track_coverage(code_tracer, pa, event->x86_regs->rip);
+
+    event->interrupt_event.reinject = 0;
+
+    if ( event->interrupt_event.gla != code_tracer->next_cf )
+    {
+        event->interrupt_event.reinject = 1;
+        vmi_pause_vm(vmi);
+        *code_tracer->stop = true;
+        return 0;
+    }
+
+    if ( code_tracer->counter >= CODE_TRACER_CF_LIMIT )
+    {
+        printf("[CODE_TRACER] Stopping after exceeding CF limit\n");
+        vmi_pause_vm(vmi);
+        *code_tracer->stop = true;
+        return 0;
+    }
+
+    access_context_t ctx = {
+        .translate_mechanism = VMI_TM_PROCESS_DTB,
+        .dtb = event->x86_regs->cr3,
+    };
+
+    ctx.addr = event->interrupt_event.gla;
+    vmi_write_8(vmi, &ctx, &code_tracer->backup);
+
+    return VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP;
+}
+
+code_tracer_t *code_tracer_setup(vmi_t *vmi, uint8_t *map, bool *stop)
+{
+    code_tracer_t *code_tracer = g_try_malloc0(sizeof(code_tracer_t));
+    if ( !code_tracer )
+        return NULL;
+
+    code_tracer->vmi = vmi;
+    code_tracer->map = map;
+    code_tracer->stop = stop;
+
+    if ( !enable_callback(vmi, BREAKPOINT, int3_cb, code_tracer) )
+        goto err;
+    if ( !enable_callback(vmi, SINGLESTEP, singlestep_cb, code_tracer) )
+        goto err;
+
+    return code_tracer;
+
+err:
+    g_free(code_tracer);
+    disable_callback(vmi, BREAKPOINT, int3_cb);
+    disable_callback(vmi, SINGLESTEP, singlestep_cb);
+    return NULL;
+}
+
+bool code_tracer_start(code_tracer_t *code_tracer, addr_t pagetable, addr_t start)
+{
+    vmi_instance_t vmi = code_tracer->vmi->libvmi;
+    bool int3 = false;
+
+    code_tracer->prev_loc = 0;
+    code_tracer->counter = 0;
+
+    if ( !(code_tracer->next_cf = next_cf_insn(vmi, pagetable, start, &int3)) || int3 )
+    {
+        printf("[CODE_TRACER] Skipping memory location because no more CF left\n");
+        return false;
+    }
+
+    if ( VMI_FAILURE == vmi_pagetable_lookup(vmi, pagetable, code_tracer->next_cf, &code_tracer->next_cf_pa) )
+        return false;
+    if ( VMI_FAILURE == vmi_read_8_pa(vmi, code_tracer->next_cf_pa, &code_tracer->backup) )
+        return false;
+    if ( VMI_FAILURE == vmi_write_8_pa(vmi, code_tracer->next_cf_pa, &bp) )
+        return false;
+
+    return true;
+}
+
+void code_tracer_close(code_tracer_t *code_tracer)
+{
+    if ( !code_tracer )
+        return;
+
+    disable_callback(code_tracer->vmi, BREAKPOINT, int3_cb);
+    disable_callback(code_tracer->vmi, SINGLESTEP, singlestep_cb);
+
+    if ( code_tracer->next_cf_pa )
+        vmi_write_8_pa(code_tracer->vmi->libvmi, code_tracer->next_cf_pa, &code_tracer->backup);
+
+    g_free(code_tracer);
+}
diff --git a/src/code_tracer.h b/src/code_tracer.h
new file mode 100644 (file)
index 0000000..d92eb07
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef CODE_TRACER_H
+#define CODE_TRACER_H
+
+#define CODE_TRACER_BUFFER_SIZE 128ul
+#define CODE_TRACER_MAP_SIZE (1ul << 16) //64kb
+#define CODE_TRACER_CF_LIMIT 20ul
+
+typedef struct code_tracer code_tracer_t;
+typedef void (*code_tracer_callback_t)(code_tracer_t *code_tracer);
+
+typedef struct code_tracer {
+    // private
+    vmi_t *vmi;
+    code_tracer_callback_t cb; // optional callback to issue when a new path is triggered
+
+    bool *stop;
+
+    addr_t prev_loc;
+    addr_t next_cf;
+    addr_t next_cf_pa;
+    addr_t points;
+
+    uint8_t backup;
+
+    // public
+    uint8_t *map;
+
+    unsigned long counter;
+    int error;
+} code_tracer_t;
+
+code_tracer_t *code_tracer_setup(vmi_t *vmi, uint8_t *map, bool *stop);
+bool code_tracer_start(code_tracer_t *code_tracer, addr_t pagetable, addr_t start);
+void code_tracer_close(code_tracer_t *code_tracer);
+
+#endif
diff --git a/src/forkvm.c b/src/forkvm.c
new file mode 100644 (file)
index 0000000..9d8036e
--- /dev/null
@@ -0,0 +1,32 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "forkvm.h"
+
+extern xc_interface *xc;
+
+bool fork_vm(uint32_t domid, uint32_t vcpus, uint32_t *forkdomid)
+{
+    int rc;
+    struct xen_domctl_createdomain create = {0};
+    create.flags |= XEN_DOMCTL_CDF_hvm;
+    create.flags |= XEN_DOMCTL_CDF_hap;
+    create.flags |= XEN_DOMCTL_CDF_oos_off;
+    create.arch.emulation_flags = (XEN_X86_EMU_ALL & ~XEN_X86_EMU_VPCI);
+    create.ssidref = 11; // SECINITSID_DOMU
+    create.max_vcpus = vcpus;
+    create.max_evtchn_port = 1023;
+    create.max_grant_frames = LIBXL_MAX_GRANT_FRAMES_DEFAULT;
+    create.max_maptrack_frames = LIBXL_MAX_MAPTRACK_FRAMES_DEFAULT;
+
+    if ( (rc = xc_domain_create(xc, forkdomid, &create)) )
+        return false;
+
+    if ( (rc = xc_memshr_fork(xc, domid, *forkdomid, true, true)) )
+    {
+        xc_domain_destroy(xc, *forkdomid);
+        return false;
+    }
+
+    return true;
+}
diff --git a/src/forkvm.h b/src/forkvm.h
new file mode 100644 (file)
index 0000000..9e1a42c
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef FORKVM_H
+#define FORKVM_H
+
+#define XC_WANT_COMPAT_EVTCHN_API 1
+#define XC_WANT_COMPAT_MAP_FOREIGN_API 1
+#include <xenctrl.h>
+#define LIBXL_API_VERSION 0x041300
+#include <libxl.h>
+
+bool fork_vm(uint32_t domid, uint32_t vcpus, uint32_t *fork_domid);
+
+#endif
diff --git a/src/fuzz.c b/src/fuzz.c
new file mode 100755 (executable)
index 0000000..fb448bf
--- /dev/null
@@ -0,0 +1,117 @@
+#include "private.h"
+
+static const char* exit_reason(int interrupt)
+{
+    if ( interrupt < 0 )
+        return "signal";
+    if ( interrupt == 0 )
+        return "timeout";
+
+    return "deadend";
+}
+
+static bool fuzz_iterate(vmi_t *vmi, uint32_t fork_domid, uint64_t pa, uint64_t *mem, bool *stop)
+{
+    if ( mem )
+    {
+        //uint8_t test[9] = "notbeef";
+        uint8_t test[9] = { [8] = '\0' };
+        memcpy(&test[0], mem, 8);
+        //printf("\t Starting test with '%s'\n ", test);
+
+        uint8_t *v = (uint8_t*)mem;
+        printf("\t Starting fuzz iteration with: %x%x %x%x %x%x %x%x",
+               v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
+
+        if ( g_str_is_ascii((const gchar*)test) )
+            printf(": %s", test);
+        printf("\n");
+
+        if ( VMI_FAILURE == vmi_write_64_pa(vmi->libvmi, pa, (uint64_t*)mem) )
+            return false;
+    }
+
+    loop_exit_condition_t exit_cond = loop_vmi(vmi, 50, stop);
+
+    xc_dominfo_t info = { 0 };
+    xc_domain_getinfo(xc, fork_domid, 1, &info);
+
+    vmi_pagecache_flush(vmi->libvmi);
+
+    GTimer *timer = g_timer_new();
+    g_timer_start(timer);
+    xc_memshr_fork_reset(xc, fork_domid);
+    g_timer_stop(timer);
+
+    printf("\t - VM Fork memory used: %lu kbyte\n", info.nr_pages * 4096 / 1024);
+    printf("\t - Reset time: %f\n", g_timer_elapsed(timer, NULL));
+    printf("\t - Exit condition: %s\n", exit_cond_str[exit_cond]);
+    printf("\t -------------------\n");
+
+    g_timer_destroy(timer);
+    return true;
+}
+
+uint64_t create_fork_and_fuzz(uint64_t domid, uint32_t vcpus, uint8_t *map, GHashTable *values, addr_t pagetable, addr_t rip, addr_t pa, addr_t va)
+{
+    bool stop = false, fork_success;
+    uint32_t fork_domid = 0;
+    vmi_t *vmi = NULL;
+    code_tracer_t *code_tracer = NULL;
+
+    guint size = g_hash_table_size(values);
+
+    GTimer *timer = g_timer_new();
+    g_timer_start(timer);
+
+    fork_success = fork_vm(domid, vcpus, &fork_domid);
+
+    g_timer_stop(timer);
+    gdouble time = g_timer_elapsed(timer, NULL);
+    g_timer_destroy(timer);
+
+    if ( !fork_success )
+        goto done;
+
+    printf("[FUZZ] Fork: %u. Creation time: %f. Memory @ 0x%lx -> 0x%lx. CF: 0x%lx. Values: %u\n", fork_domid, time, va, pa, rip, size);
+
+    vmi = setup_vmi(NULL, fork_domid, NULL, true, false);
+    if ( !vmi )
+        goto done;
+
+    code_tracer = code_tracer_setup(vmi, map, &stop);
+    if ( !code_tracer )
+        goto done;
+
+    /* Record the baseline execution first without touching anything */
+    if ( !size )
+    {
+        printf("[FUZZ] Starting baseline execution to setup coverage map\n");
+
+        if ( code_tracer_start(code_tracer, pagetable, rip) )
+            fuzz_iterate(vmi, fork_domid, 0, NULL, &stop);
+
+        goto done;
+    }
+
+    GHashTableIter iter;
+    gpointer key, value;
+    g_hash_table_iter_init (&iter, values);
+
+    while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+        if ( !code_tracer_start(code_tracer, pagetable, rip) )
+            break;
+        if ( !fuzz_iterate(vmi, fork_domid, pa, key, &stop) )
+            break;
+    }
+
+done:
+    code_tracer_close(code_tracer);
+    close_vmi(vmi);
+
+    if ( fork_domid )
+        xc_domain_destroy(xc, fork_domid);
+
+    return 0;
+}
diff --git a/src/fuzz.h b/src/fuzz.h
new file mode 100755 (executable)
index 0000000..8b1ba47
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef FUZZ_H
+#define FUZZ_H
+
+uint64_t create_fork_and_fuzz(uint64_t domid, uint32_t vcpus, uint8_t *map, GHashTable *values, addr_t cr3, addr_t rip, addr_t pa, addr_t va);
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..d917c82
--- /dev/null
@@ -0,0 +1,81 @@
+#include <stdio.h>
+
+#include "private.h"
+
+os_t os;
+bool manual, refresh;
+
+int interrupted;
+xc_interface *xc;
+csh cs_handle;
+int vcpus;
+
+static inline void usage(void)
+{
+    fprintf(stderr,
+            "-d/--domain            VM name\n"
+            "-j/--json-path         Path to VM's kernel json config file\n"
+            "-f/--fuzz              Turn on memory fuzzing (off by default)\n"
+            "-m/--manual            Perform manual memory fuzz\n"
+    );
+}
+
+int main(int argc, char** argv)
+{
+    int ret = -1, c, long_index = 0;
+    const struct option long_opts[] =
+    {
+        {"domain", required_argument, NULL, 'd'},
+        {"json-path", required_argument, NULL, 'j'},
+        {"fuzz", optional_argument, NULL, 'f'},
+        {"manual", optional_argument, NULL, 'm'},
+        {NULL, 0, NULL, 0}
+    };
+    const char* opts = "d:j:fm";
+
+    const char* domain = NULL, *json_path = NULL;
+    bool fuzz = false;
+    manual = false;
+
+    while ((c = getopt_long (argc, argv, opts, long_opts, &long_index)) != -1)
+    {
+        switch(c)
+        {
+        case 'd':
+            domain = optarg;
+            break;
+        case 'j':
+            json_path = optarg;
+            break;
+        case 'f':
+            fuzz = true;
+            break;
+        case 'm':
+            manual = true;
+            break;
+        default:
+            break;
+        };
+    }
+
+    if ( !domain || !json_path )
+    {
+        usage();
+        return ret;
+    }
+
+    if ( !(xc = xc_interface_open(0,0,0)) )
+        goto done;
+
+    if ( cs_open(CS_ARCH_X86, CS_MODE_64, &cs_handle) )
+        goto done;
+
+    shredder(domain, json_path, fuzz, manual);
+    ret = 0;
+
+done:
+    cs_close(&cs_handle);
+    xc_interface_close(xc);
+
+    return ret;
+}
diff --git a/src/mem_tracer.c b/src/mem_tracer.c
new file mode 100644 (file)
index 0000000..b387966
--- /dev/null
@@ -0,0 +1,237 @@
+#include "private.h"
+
+void mem_tracer_print_stats(mem_tracer_t *mem_tracer)
+{
+    GList *offsets = g_hash_table_get_keys(mem_tracer->pages);
+    offsets = g_list_sort(offsets, gint64_compare);
+    unsigned long r_total = 0, rw_total = 0;
+
+    GList *offset = offsets;
+    while ( offset )
+    {
+        addr_t offset_value = *(addr_t*)offset->data;
+        offset = offset->next;
+
+        page_access_log_t *page = g_hash_table_lookup(mem_tracer->pages, &offset_value);
+
+        printf("[0x%lx %03lx] R: %5lu, R/W: %5lu\n", offset_value >> 12, offset_value & 0xfff, page->r_count, page->rw_count);
+
+        page->log = g_list_reverse(page->log);
+
+        GList *loop = page->log;
+        while ( loop )
+        {
+            uint64_t *mem = (uint64_t*)loop->data;
+
+            uint8_t *before = (uint8_t*)&mem[0];
+            uint8_t *after = (uint8_t*)&mem[1];
+
+            uint8_t test[9] = { [8] = '\0' };
+            memcpy(&test[0], after, 8);
+
+            printf("\t%02x%02x %02x%02x %02x%02x %02x%02x\n", before[0], before[1], before[2], before[3], before[4], before[5], before[6], before[7]);
+            printf("\t%02x%02x %02x%02x %02x%02x %02x%02x", after[0], after[1], after[2], after[3], after[4], after[5], after[6], after[7]);
+
+
+            if ( g_str_is_ascii((const char*)&test) )
+                printf(" -> '%s'\n", test);
+
+            printf("\n\n");
+
+            g_free(loop->data);
+            loop = loop->next;
+        }
+
+        g_list_free(page->log);
+
+        r_total += page->r_count;
+        rw_total += page->rw_count;
+    }
+
+    g_list_free(offsets);
+
+    printf("--------------------------\n");
+    printf("Total R: %5lu    Total R/W: %5lu\n", r_total, rw_total);
+    printf("--------------------------\n\n");
+
+    printf("Unique memaccess values: %u\n", g_hash_table_size(mem_tracer->values));
+    GHashTableIter iter;
+    gpointer key, value;
+
+    g_hash_table_iter_init (&iter, mem_tracer->values);
+    while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+        uint8_t test[9] = { [8] = '\0' };
+        memcpy(&test[0], key, 8);
+
+        uint8_t *v = (uint8_t*)key;
+        printf("\t[%3d] %02x%02x %02x%02x %02x%02x %02x%02x", GPOINTER_TO_UINT(value), v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
+
+        if ( g_str_is_ascii((const char*)&test) )
+            printf(" -> '%s'", test);
+
+        printf("\n");
+    }
+}
+
+static event_response_t mem_cb(vmi_instance_t vmi, vmi_event_t *event)
+{
+    mem_tracer_t *mem_tracer = event->data;
+    addr_t pa = (event->mem_event.gfn << 12) + event->mem_event.offset;
+    page_access_log_t *page;
+
+    if ( !(page = g_hash_table_lookup(mem_tracer->pages, &pa)) )
+    {
+        if ( !(page = g_try_malloc0(sizeof(page_access_log_t))) )
+        {
+            mem_tracer->error = -ENOMEM;
+            goto done;
+        }
+
+        page->pa = pa;
+        g_hash_table_insert(mem_tracer->pages, &page->pa, page);
+    }
+
+    mem_tracer->reset_pages = g_slist_prepend(mem_tracer->reset_pages, page);
+
+    if ( event->mem_event.out_access & VMI_MEMACCESS_W )
+        page->rw_count++;
+    else if ( event->mem_event.out_access & VMI_MEMACCESS_R )
+        page->r_count++;
+
+    uint64_t *mem = g_try_malloc0(2*sizeof(uint64_t));
+    if ( !mem )
+    {
+        mem_tracer->error = -ENOMEM;
+        goto done;
+    }
+
+    vmi_read_64_pa(vmi, page->pa, mem);
+    page->log = g_list_prepend(page->log, mem);
+
+    if ( mem_tracer->cb )
+    {
+        mem_tracer->event = event;
+        mem_tracer->cb(mem_tracer, page, event->mem_event.gla);
+    }
+
+    vmi_set_mem_event(vmi, pa >> 12, VMI_MEMACCESS_N, 0);
+
+done:
+    return VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP;
+}
+
+static event_response_t singlestep_cb(vmi_instance_t vmi, vmi_event_t *event)
+{
+    mem_tracer_t *mem_tracer = event->data;
+    GSList *loop = mem_tracer->reset_pages;
+    while (loop)
+    {
+        page_access_log_t *page = loop->data;
+
+        uint64_t *mem = page->log->data;
+        vmi_read_64_pa(vmi, page->pa, ++mem);
+
+        guint count = 0;
+        gpointer v = g_hash_table_lookup(mem_tracer->values, mem);
+        if ( v )
+            count = GPOINTER_TO_UINT(v);
+        count++;
+
+        g_hash_table_insert(mem_tracer->values, mem, GUINT_TO_POINTER(count));
+        vmi_set_mem_event(vmi, page->pa >> 12, VMI_MEMACCESS_RW, 0);
+        loop=loop->next;
+    }
+
+    g_slist_free(mem_tracer->reset_pages);
+    mem_tracer->reset_pages = NULL;
+
+    return VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP;
+}
+
+mem_tracer_t *mem_tracer_setup(vmi_t *vmi, mem_tracer_callback_t cb, void *data)
+{
+    mem_tracer_t *mem_tracer = g_try_malloc0(sizeof(mem_tracer_t));
+    if ( !mem_tracer )
+        return NULL;
+
+    mem_tracer->pages = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, g_free);
+    if ( !mem_tracer->pages )
+        goto err;
+
+    mem_tracer->values = g_hash_table_new(g_int64_hash, g_int64_equal);
+    if ( !mem_tracer->values )
+        goto err;
+
+    mem_tracer->vmi = vmi;
+    mem_tracer->cb = cb;
+    mem_tracer->data = data;
+
+    if ( !enable_callback(vmi, MEMACCESS, mem_cb, mem_tracer) )
+        goto err;
+    if ( !enable_callback(vmi, SINGLESTEP, singlestep_cb, mem_tracer) )
+        goto err;
+
+    return mem_tracer;
+
+err:
+    mem_tracer_close(mem_tracer, false);
+    return NULL;
+}
+
+void mem_tracer_trap_user_pages(mem_tracer_t *mem_tracer, addr_t pagetable)
+{
+    /*
+     * Set memory permissions on process and start memory listener
+     */
+    vmi_instance_t vmi = mem_tracer->vmi->libvmi;
+    GSList *page_list = vmi_get_va_pages(vmi, pagetable);
+    printf("Got %u pages in target process' memory at 0x%lx\n", g_slist_length(page_list), pagetable);
+
+    GSList *loop = page_list;
+    while ( loop )
+    {
+        page_info_t *info = (page_info_t*)loop->data;
+        loop = loop->next;
+
+        if ( !USER_SUPERVISOR(info->x86_ia32e.pte_value) )
+            continue;
+
+        printf("%lx -> %lx\n", info->vaddr, info->paddr);
+
+        //if ( READ_WRITE(info->x86_ia32e.pte_value) )
+        {
+            vmi_set_mem_event(vmi, info->paddr >> 12, VMI_MEMACCESS_RW, 0);
+        }
+    }
+
+    g_slist_free_full(page_list, (GDestroyNotify)g_free);
+}
+
+void mem_tracer_close(mem_tracer_t *mem_tracer, bool remove_traps)
+{
+    if ( !mem_tracer )
+        return;
+
+    disable_callback(mem_tracer->vmi, MEMACCESS, mem_cb);
+    disable_callback(mem_tracer->vmi, SINGLESTEP, singlestep_cb);
+
+    if ( remove_traps && mem_tracer->pages )
+    {
+        GHashTableIter iter;
+        gpointer key, value;
+
+        g_hash_table_iter_init (&iter, mem_tracer->pages);
+        while (g_hash_table_iter_next (&iter, &key, &value))
+        {
+            page_access_log_t *page = value;
+            vmi_set_mem_event(mem_tracer->vmi->libvmi, page->pa >> 12, VMI_MEMACCESS_N, 0);
+        }
+    }
+
+    if ( mem_tracer->pages )
+        g_hash_table_destroy(mem_tracer->pages);
+    if ( mem_tracer->values )
+        g_hash_table_destroy(mem_tracer->values);
+    g_free(mem_tracer);
+}
diff --git a/src/mem_tracer.h b/src/mem_tracer.h
new file mode 100644 (file)
index 0000000..ffb2321
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef MEM_TRACER_H
+#define MEM_TRACER_H
+
+typedef struct page_access_log page_access_log_t;
+typedef struct mem_tracer mem_tracer_t;
+typedef void (*mem_tracer_callback_t)(mem_tracer_t *mem_tracer, page_access_log_t *page, addr_t va);
+
+typedef struct page_access_log {
+    addr_t pa;
+    unsigned long r_count;
+    unsigned long rw_count;
+    GList *log; // list of two-dim arrays, with pre/post access values
+} page_access_log_t;
+
+typedef struct mem_tracer {
+    // private
+    event_callback_t mem_cb;
+    GSList *reset_pages; // list of page_access_log_t
+
+    vmi_t *vmi;
+    mem_tracer_callback_t cb; // optional callback to issue when a page is accessed
+    vmi_event_t *event; // the currently active event
+    void *data;
+    bool *stop;
+
+    // public
+    GHashTable *pages; // table of page_access_log_t, key: pa
+    GHashTable *values; // table of access counts, key: value
+    int error;
+} mem_tracer_t;
+
+/*
+ * Setup the mem_tracer infrastructure, initialize and register events.
+ * Callback is optional, issued before a memory page is accessed
+ */
+mem_tracer_t *mem_tracer_setup(vmi_t *vmi, mem_tracer_callback_t cb, void* data);
+
+/*
+ * Change EPT access permissions on user pages in target pagetable
+ */
+void mem_tracer_trap_user_pages(mem_tracer_t *mem_tracer, addr_t pagetable);
+
+/*
+ * Print out collected memaccess values and location information
+ */
+void mem_tracer_print_stats(mem_tracer_t *mem_tracer);
+
+/*
+ * Free mem_tracer infrastructure, optionally reset EPT access permissions
+ */
+void mem_tracer_close(mem_tracer_t *mem_tracer, bool remove_traps);
+
+#endif
diff --git a/src/private.h b/src/private.h
new file mode 100644 (file)
index 0000000..9ff19b6
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef PRIVATE_H
+#define PRIVATE_H
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#define LIBVMI_EXTRA_GLIB
+#define LIBVMI_EXTRA_JSON
+#include <libvmi/libvmi.h>
+#include <libvmi/events.h>
+#include <libvmi/libvmi_extra.h>
+#include <libvmi/x86.h>
+#include <libvmi/slat.h>
+
+#include <glib.h>
+
+#define XC_WANT_COMPAT_EVTCHN_API 1
+#define XC_WANT_COMPAT_MAP_FOREIGN_API 1
+#include <xenctrl.h>
+#define LIBXL_API_VERSION 0x041300
+#include <libxl.h>
+
+#include <capstone.h>
+
+#include "signal.h"
+#include "vmi.h"
+#include "shredder.h"
+#include "fuzz.h"
+#include "mem_tracer.h"
+#include "code_tracer.h"
+#include "forkvm.h"
+
+extern os_t os;
+extern xc_interface *xc;
+extern csh cs_handle;
+extern int interrupted;
+extern bool manual, refresh;
+
+gint gint64_compare(gconstpointer ptr_a, gconstpointer ptr_b);
+
+#endif
diff --git a/src/shredder.c b/src/shredder.c
new file mode 100644 (file)
index 0000000..9381b6a
--- /dev/null
@@ -0,0 +1,209 @@
+#include "private.h"
+
+typedef struct shredder {
+    GHashTable *processes;
+    vmi_pid_t target_pid;
+    addr_t target_cr3;
+    addr_t magic_string;
+
+    bool manual;
+    bool harness_bp;
+    bool stop;
+
+    vmi_t *vmi;
+    mem_tracer_t *mem_tracer;
+
+    uint8_t *map;
+} shredder_t;
+
+gint gint64_compare(gconstpointer ptr_a, gconstpointer ptr_b)
+{
+    gint64 a = *(gint64*)ptr_a;
+    gint64 b = *(gint64*)ptr_b;
+
+    if ( a > b )
+        return 1;
+    if ( a == b )
+        return 0;
+    return -1;
+}
+
+static void find_existing_processes(shredder_t *shredder)
+{
+    vmi_instance_t vmi = shredder->vmi->libvmi;
+    GHashTable *processes = shredder->processes;
+
+    addr_t current_list_entry = 0, next_list_entry = 0, start_entry = 0;
+    addr_t linkedlist_offset, name_offset, pid_offset;
+    bool looped = 0;
+
+    if ( VMI_OS_LINUX == os )
+    {
+        if ( VMI_FAILURE == vmi_get_kernel_struct_offset(vmi, "task_struct", "tasks", &linkedlist_offset) )
+            return;
+        if ( VMI_FAILURE == vmi_get_kernel_struct_offset(vmi, "task_struct", "comm", &name_offset) )
+            return;
+        if ( VMI_FAILURE == vmi_get_kernel_struct_offset(vmi, "task_struct", "pid", &pid_offset) )
+            return;
+        if ( VMI_FAILURE == vmi_translate_ksym2v(vmi, "init_task", &next_list_entry) )
+            return;
+
+        next_list_entry += linkedlist_offset;
+        start_entry = next_list_entry;
+    }
+
+    do {
+
+        if ( !next_list_entry )
+            return;
+
+        if ( next_list_entry == start_entry && looped )
+            return;
+
+        current_list_entry = next_list_entry;
+        looped = true;
+
+        addr_t process = current_list_entry - linkedlist_offset;
+
+        if (VMI_FAILURE == vmi_read_addr_va(vmi, current_list_entry, 0, &next_list_entry))
+            return;
+
+        uint16_t pid;
+        if ( VMI_FAILURE == vmi_read_16_va(vmi, process + pid_offset, 0, &pid) )
+            continue;
+
+        gint *_pid = g_try_malloc0(sizeof(gint));
+        *_pid = pid;
+
+        if ( !g_hash_table_insert(processes, _pid, GSIZE_TO_POINTER(1)) )
+            return;
+
+#if 0
+        char *name = vmi_read_str_va(vmi, process + name_offset, 0);
+        printf("Found process [%u] %s\n", pid, name);
+        free(name);
+#endif
+    } while ( 1 );
+}
+
+static event_response_t int3_cb(vmi_instance_t vmi, vmi_event_t *event)
+{
+    shredder_t *shredder = event->data;
+
+    gint pid;
+    event->interrupt_event.reinject = 1;
+
+    if ( VMI_FAILURE == vmi_dtb_to_pid(vmi, event->x86_regs->cr3, (vmi_pid_t*)&pid) )
+    {
+        printf("Can't find PID for CR3: 0x%lx\n", event->x86_regs->cr3);
+        return 0;
+    }
+
+    if ( !shredder->target_pid )
+    {
+        shredder->target_pid = pid;
+        shredder->target_cr3 = event->x86_regs->cr3;
+    }
+
+    printf("INT3 in target pid! [%u] CR3: 0x%lx. SLAT: %u\n", pid, event->x86_regs->cr3, event->slat_id);
+
+    event_response_t rsp = VMI_EVENT_RESPONSE_SET_REGISTERS;
+    event->interrupt_event.reinject = 0;
+    event->x86_regs->rip += event->interrupt_event.insn_length;
+
+    vmi_pause_vm(vmi);
+    shredder->stop = true;
+
+    if ( !shredder->harness_bp && shredder->manual )
+    {
+        printf("Enter magic string location: \n");
+        char word[256];
+        char *w = fgets(word, sizeof(word), stdin);
+        shredder->magic_string = strtoull(w, NULL, 0);
+        shredder->harness_bp = true;
+    }
+
+    return rsp;
+}
+
+void mem_tracer_cb(mem_tracer_t *mem_tracer, page_access_log_t *page, addr_t va)
+{
+    shredder_t *shredder = mem_tracer->data;
+
+    vmi_instance_t vmi = mem_tracer->vmi->libvmi;
+    vmi_event_t *event = mem_tracer->event;
+
+    if ( shredder->manual )
+    {
+        if ( shredder->magic_string != va )
+            return;
+
+        printf("Enter magic string: \n");
+        char word[256], magic_string[8];
+        char *w = fgets(word, sizeof(word), stdin);
+
+        memcpy(&magic_string, w, 8);
+
+        GHashTable *values = g_hash_table_new(g_int64_hash, g_int64_equal);
+        g_hash_table_insert(values, &magic_string, GUINT_TO_POINTER(1));
+        create_fork_and_fuzz(vmi_get_vmid(vmi), vmi_get_num_vcpus(vmi), shredder->map, values, event->x86_regs->cr3, event->x86_regs->rip, page->pa, va);
+        g_hash_table_destroy(values);
+        return;
+    }
+
+    create_fork_and_fuzz(vmi_get_vmid(vmi), vmi_get_num_vcpus(vmi), shredder->map, mem_tracer->values, event->x86_regs->cr3, event->x86_regs->rip, page->pa, va);
+    return;
+}
+
+bool shredder(const char *domain, const char* json_path, bool fuzz, bool manual)
+{
+    shredder_t shredder = {0};
+    vmi_t *vmi = NULL;
+    vmi_instance_t libvmi = NULL;
+
+    if ( !(shredder.map = g_try_malloc0(CODE_TRACER_MAP_SIZE)) )
+        goto exit;
+
+    if ( !(shredder.vmi = setup_vmi(domain, 0, json_path, true, true)) )
+        goto exit;
+
+    shredder.manual = manual;
+    vmi = shredder.vmi;
+    libvmi = vmi->libvmi;
+
+    setup_signals();
+    vmi_pause_vm(libvmi);
+
+    printf("Waiting for harness int3\n");
+
+    if ( !enable_callback(vmi, BREAKPOINT, int3_cb, &shredder) )
+        goto exit;
+
+    if ( loop_vmi(vmi, ~0, &shredder.stop) != STOP )
+        goto exit;
+
+    shredder.mem_tracer = mem_tracer_setup(vmi, fuzz ? mem_tracer_cb : NULL, &shredder);
+    if ( !shredder.mem_tracer )
+        goto exit;
+
+    mem_tracer_trap_user_pages(shredder.mem_tracer, shredder.target_cr3);
+
+    if ( loop_vmi(vmi, ~0, &shredder.stop) != STOP )
+        goto exit;
+
+    if ( !fuzz )
+        mem_tracer_print_stats(shredder.mem_tracer);
+
+    vmi_resume_vm(libvmi);
+
+exit:
+    if ( shredder.processes )
+        g_hash_table_destroy(shredder.processes);
+    if ( shredder.map )
+        g_free(shredder.map);
+
+    mem_tracer_close(shredder.mem_tracer, true);
+    disable_callback(vmi, BREAKPOINT, int3_cb);
+    close_vmi(vmi);
+    return true;
+}
diff --git a/src/shredder.h b/src/shredder.h
new file mode 100644 (file)
index 0000000..5d221d8
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef SHREDDER_H
+#define SHREDDER_H
+
+bool shredder(const char *domain, const char *json_path, bool fuzz, bool manual);
+
+#endif
diff --git a/src/signal.c b/src/signal.c
new file mode 100644 (file)
index 0000000..76bc37f
--- /dev/null
@@ -0,0 +1,19 @@
+#include "private.h"
+
+static struct sigaction act;
+
+static void close_handler(int sig)
+{
+    printf("Interrupted: %i\n", -sig);
+    interrupted = -sig;
+}
+
+void setup_signals(void)
+{
+    act.sa_handler = close_handler;
+    sigemptyset(&act.sa_mask);
+    sigaction(SIGHUP,  &act, NULL);
+    sigaction(SIGTERM, &act, NULL);
+    sigaction(SIGINT,  &act, NULL);
+    sigaction(SIGALRM, &act, NULL);
+}
diff --git a/src/signal.h b/src/signal.h
new file mode 100644 (file)
index 0000000..c01f114
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef SIGNAL_H
+#define SIGNAL_H
+
+void setup_signals();
+
+#endif
diff --git a/src/vmi.c b/src/vmi.c
new file mode 100644 (file)
index 0000000..a2a42d5
--- /dev/null
+++ b/src/vmi.c
@@ -0,0 +1,261 @@
+#include "private.h"
+
+static event_response_t mem_cb(vmi_instance_t libvmi, vmi_event_t *event)
+{
+    event_response_t rsp = 0;
+    vmi_t *vmi = event->data;
+    GSList *loop = vmi->event[MEMACCESS].cb;
+
+    while (loop)
+    {
+        vmi_callback_t *cb = loop->data;
+        event->data = cb->data;
+        rsp |= cb->cb(libvmi, event);
+        loop = loop->next;
+    }
+
+    if ( rsp & VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP )
+    {
+        /* If we already have singlestep on, don't disable it */
+        if ( vmi->singlestep )
+            rsp &= ~VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP;
+        else
+            vmi->singlestep = true;
+    }
+
+    event->data = vmi;
+    return rsp;
+}
+
+static event_response_t int3_cb(vmi_instance_t libvmi, vmi_event_t *event)
+{
+    event_response_t rsp = 0;
+    vmi_t *vmi = event->data;
+    GSList *loop = vmi->event[BREAKPOINT].cb;
+
+    while (loop)
+    {
+        vmi_callback_t *cb = loop->data;
+        event->data = cb->data;
+        rsp |= cb->cb(libvmi, event);
+        loop = loop->next;
+    }
+
+    if ( rsp & VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP )
+    {
+        /* If we already have singlestep on, don't disable it */
+        if ( vmi->singlestep )
+            rsp &= ~VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP;
+        else
+            vmi->singlestep = true;
+    }
+
+    event->data = vmi;
+    return rsp;
+}
+
+static event_response_t cr3_cb(vmi_instance_t libvmi, vmi_event_t *event)
+{
+    event_response_t rsp = 0;
+    vmi_t *vmi = event->data;
+    GSList *loop = vmi->event[CR3WRITE].cb;
+
+    while (loop)
+    {
+        vmi_callback_t *scb = loop->data;
+        event->data = scb->data;
+        rsp |= scb->cb(libvmi, event);
+        loop = loop->next;
+    }
+
+    event->data = vmi;
+    return rsp;
+}
+
+static event_response_t singlestep_cb(vmi_instance_t libvmi, vmi_event_t *event)
+{
+    event_response_t rsp = 0;
+    vmi_t *vmi = event->data;
+    GSList *loop = vmi->event[SINGLESTEP].cb;
+
+    while (loop)
+    {
+        vmi_callback_t *cb = loop->data;
+        event->data = cb->data;
+        rsp |= cb->cb(libvmi, event);
+        loop = loop->next;
+    }
+
+    if ( rsp & VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP )
+        vmi->singlestep = false;
+
+    event->data = vmi;
+    return rsp;
+}
+
+vmi_t *setup_vmi(const char* domain, uint64_t domid, const char* json_path, bool init_events, bool init_os)
+{
+    //fprintf(stderr, "Init vmi, events: %i domain %s domid %lu\n", init_events, domain, domid);
+    vmi_t *vmi = g_try_malloc0(sizeof(vmi_t));
+    if ( !vmi )
+        return NULL;
+
+    vmi_mode_t mode = (init_events ? VMI_INIT_EVENTS : 0) | (domain ? VMI_INIT_DOMAINNAME : VMI_INIT_DOMAINID);
+    const void *d = domain?:(void*)&domid;
+
+    if ( VMI_FAILURE == vmi_init(&vmi->libvmi, VMI_XEN, d, mode, NULL, NULL) )
+        goto err;
+
+    vmi_instance_t libvmi = vmi->libvmi;
+
+    if ( init_os )
+    {
+        os = vmi_init_os(libvmi, VMI_CONFIG_JSON_PATH, (void*)json_path, NULL);
+
+        if ( VMI_OS_UNKNOWN == os )
+            goto err;
+    }
+    else
+    {
+        if ( VMI_PM_UNKNOWN == vmi_init_paging(libvmi, os == VMI_OS_WINDOWS ? VMI_PM_INITFLAG_TRANSITION_PAGES : 0) )
+            goto err;
+    }
+
+    if ( init_events )
+    {
+        SETUP_MEM_EVENT(&vmi->event[MEMACCESS].handler, ~0ULL, VMI_MEMACCESS_RWX, mem_cb, true);
+        SETUP_SINGLESTEP_EVENT(&vmi->event[SINGLESTEP].handler, ~0U, singlestep_cb, false);
+        SETUP_REG_EVENT(&vmi->event[CR3WRITE].handler, CR3, VMI_REGACCESS_W, 0, cr3_cb);
+        SETUP_INTERRUPT_EVENT(&vmi->event[BREAKPOINT].handler, int3_cb);
+
+        vmi->event[MEMACCESS].handler.data = vmi;
+        vmi->event[SINGLESTEP].handler.data = vmi;
+        vmi->event[CR3WRITE].handler.data = vmi;
+        vmi->event[BREAKPOINT].handler.data = vmi;
+    }
+
+    return vmi;
+
+err:
+    close_vmi(vmi);
+    return NULL;
+}
+
+void close_vmi(vmi_t *vmi)
+{
+    if ( !vmi )
+        return;
+
+    int i;
+    for ( i = 0; i < __EVENT_TYPE_MAX; i++)
+    {
+        GSList *cb = vmi->event[i].cb;
+        if ( cb )
+            g_slist_free_full(cb, (GDestroyNotify)g_free);
+    }
+
+    vmi_destroy(vmi->libvmi);
+    g_free(vmi);
+}
+
+loop_exit_condition_t loop_vmi(vmi_t *vmi, unsigned int timeout, bool *stop)
+{
+    loop_exit_condition_t ret = ERROR;
+    if ( !vmi )
+        return ret;
+
+    bool _s;
+    bool *_stop = stop ?: &_s;
+    *_stop = false;
+
+    vmi_instance_t libvmi = vmi->libvmi;
+    vmi_resume_vm(libvmi);
+
+    while (!interrupted && timeout-- && !*_stop)
+    {
+        if ( vmi_events_listen(libvmi, 100) == VMI_FAILURE )
+        {
+            fprintf(stderr, "Error in vmi_events_listen!\n");
+            break;
+        }
+    }
+
+    if ( !stop )
+        vmi_pause_vm(libvmi);
+    vmi_events_listen(libvmi, 0);
+
+    if ( interrupted < 0 )
+        ret = SIGNAL;
+    else if ( !timeout )
+        ret = TIMEOUT;
+    else if ( stop )
+        ret = STOP;
+    else
+        ret = ERROR;
+
+    interrupted = 0;
+
+    return ret;
+}
+
+static bool toggle_event(vmi_t *vmi, enum event_types type)
+{
+    bool ret;
+    if ( vmi->event[type].enabled )
+        ret = VMI_SUCCESS == vmi_clear_event(vmi->libvmi, &vmi->event[type].handler, NULL);
+    else
+        ret = VMI_SUCCESS == vmi_register_event(vmi->libvmi, &vmi->event[type].handler);
+
+    if ( ret )
+        vmi->event[type].enabled = !vmi->event[type].enabled;
+
+    return ret;
+}
+
+bool enable_callback(vmi_t *vmi, enum event_types type, event_callback_t cb, void *data)
+{
+    if ( !vmi )
+        return false;
+
+    vmi_callback_t *scb = g_try_malloc0(sizeof(vmi_callback_t));
+    if ( !scb )
+        return false;
+
+    scb->cb = cb;
+    scb->data = data;
+
+    if ( !vmi->event[type].enabled && !toggle_event(vmi, type) )
+    {
+        g_free(scb);
+        return false;
+    }
+
+    vmi->event[type].cb = g_slist_prepend(vmi->event[type].cb, scb);
+    return true;
+}
+
+bool disable_callback(vmi_t *vmi, enum event_types type, event_callback_t cb)
+{
+    if ( !vmi )
+        return false;
+
+    if ( !vmi->event[type].enabled )
+        return true;
+
+    GSList *loop = vmi->event[type].cb;
+    while (loop)
+    {
+        vmi_callback_t *scb = loop->data;
+        if ( scb->cb == cb )
+            break;
+        loop = loop->next;
+    }
+
+    if ( loop )
+        vmi->event[type].cb = g_slist_remove(vmi->event[type].cb, loop->data);
+
+    if ( !vmi->event[type].cb )
+        return toggle_event(vmi, type);
+
+    return true;
+}
diff --git a/src/vmi.h b/src/vmi.h
new file mode 100644 (file)
index 0000000..dde7d2c
--- /dev/null
+++ b/src/vmi.h
@@ -0,0 +1,53 @@
+#ifndef VMI_H
+#define VMI_H
+
+typedef enum event_types {
+    MEMACCESS,
+    SINGLESTEP,
+    BREAKPOINT,
+    CR3WRITE,
+    __EVENT_TYPE_MAX
+} event_types_t;
+
+typedef struct {
+    void *data;
+    event_callback_t cb;
+} vmi_callback_t;
+
+typedef struct {
+    vmi_instance_t libvmi;
+
+    struct {
+        bool enabled;
+        vmi_event_t handler;
+        GSList *cb; // list of vmi_callback_t
+    } event[__EVENT_TYPE_MAX];
+
+    bool singlestep;
+    int interrupted;
+    int error;
+} vmi_t;
+
+vmi_t *setup_vmi(const char* domain, uint64_t domid, const char* json_path, bool init_events, bool init_os);
+void close_vmi(vmi_t *vmi);
+
+typedef enum loop_exit_condition {
+    SIGNAL,
+    ERROR,
+    TIMEOUT,
+    STOP
+} loop_exit_condition_t;
+
+static const char *exit_cond_str[] = {
+    [SIGNAL] = "Signal",
+    [ERROR] = "Error",
+    [TIMEOUT] = "Timeout",
+    [STOP] = "Stop"
+};
+
+loop_exit_condition_t loop_vmi(vmi_t *vmi, unsigned int timeout, bool *stop);
+
+bool enable_callback(vmi_t *vmi, event_types_t type, event_callback_t cb, void *data);
+bool disable_callback(vmi_t *vmi, event_types_t type, event_callback_t cb);
+
+#endif