├── .clang-format ├── .flake8 ├── .gitignore ├── .gitlab-ci.yml ├── .lisp-format ├── .pre-commit-config.yaml ├── .stack-stamp.svg ├── .stack-stamp.tex ├── CHANGELOG.md ├── CMakeLists.googletest ├── CMakeLists.txt ├── Dockerfile ├── LICENSE.txt ├── README.md ├── doc └── README.md ├── gtirb-stack-stamp.asd ├── gtirb-stack-stamp.lisp ├── gtirb_stack_stamp ├── __init__.py ├── __main__.py ├── stack_stamp.py └── version.py ├── include └── gtirb_stack_stamp │ └── gtirb_stack_stamp.hpp ├── setup.py ├── src ├── CMakeLists.txt ├── driver │ ├── CMakeLists.txt │ └── driver.cpp └── gtirb_stack_stamp.cpp └── tests ├── CMakeLists.txt ├── cl-nop-rewrite ├── cl-stack-stamp-rewrite ├── factorial.c ├── hello.v1.gtirb ├── stack-overwrite.c ├── test_stack_stamp.cpp └── test_stack_stamp.py /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Attach 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | BreakAfterJavaFieldAnnotations: false 40 | BreakStringLiterals: true 41 | ColumnLimit: 80 42 | CommentPragmas: '^ IWYU pragma:' 43 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 44 | ConstructorInitializerIndentWidth: 4 45 | ContinuationIndentWidth: 4 46 | Cpp11BracedListStyle: true 47 | DerivePointerAlignment: false 48 | DisableFormat: false 49 | ExperimentalAutoDetectBinPacking: false 50 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 51 | # IncludeBlocks: Regroup 52 | IncludeCategories: 53 | - Regex: '^".*"' 54 | Priority: 1 55 | - Regex: '^)\n" package) 35 | ;; After printing the messages require again to trigger the error. 36 | (require package))) 37 | (verbose-require 'slime) 38 | (verbose-require 'paredit) 39 | 40 | (set-default 'indent-tabs-mode nil) 41 | (pushnew 'untabify *lisp-format-fixers*) 42 | 43 | (defun fix-trailing-parens (start end &optional _arg) 44 | "Use `paredit-close-parenthesis' to fix trailing parens." 45 | (interactive (if current-prefix-arg 46 | (list (point-min) (point-max) current-prefix-arg) 47 | (list (region-beginning) (region-end) nil))) 48 | (let ((c (current-column))) 49 | (save-excursion 50 | (save-restriction 51 | (narrow-to-region (point-min) end) 52 | (goto-char start) 53 | (while (re-search-forward "^ *)" nil t) 54 | (forward-char -1) 55 | (paredit-close-parenthesis)))) 56 | (move-to-column c))) 57 | (pushnew 'fix-trailing-parens *lisp-format-fixers*) 58 | 59 | ;;; Syntax table extension for curry-compose-reader-macros 60 | (modify-syntax-entry ?\[ "(]" lisp-mode-syntax-table) 61 | (modify-syntax-entry ?\] ")[" lisp-mode-syntax-table) 62 | (modify-syntax-entry ?\{ "(}" lisp-mode-syntax-table) 63 | (modify-syntax-entry ?\} "){" lisp-mode-syntax-table) 64 | (modify-syntax-entry ?\« "(»" lisp-mode-syntax-table) 65 | (modify-syntax-entry ?\» ")«" lisp-mode-syntax-table) 66 | 67 | ;;; Specify indentation levels for specific functions. 68 | (mapc (lambda (pair) (put (first pair) 'lisp-indent-function (second pair))) 69 | '((make-instance 1) 70 | (if-let 1) 71 | (if-let* 1) 72 | (when-let 1) 73 | (when-let* 1) 74 | (defixture 1) 75 | (lambda-bind 1) 76 | (signals 1) 77 | (match 1) 78 | (start-case 1) 79 | (define-proto-backed-class 4) 80 | (register-groups-bind 2))) 81 | 82 | (defun define-feature-lisp-indent 83 | (path state indent-point sexp-column normal-indent) 84 | "Indentation function called by `lisp-indent-function' for define-feature." 85 | ;; (message "CALLED: %S" 86 | ;; (list 'define-feature-lisp-indent 87 | ;; path state indent-point sexp-column normal-indent)) 88 | (cond 89 | ((equalp path '(2)) 2) ; Doc string for enclosing define-feature. 90 | ((equalp path '(3)) 2) ; Extractor function definition. 91 | ((equalp path '(3 2)) 4) ; Doc string for extractor. 92 | ((equalp path '(4)) 2) ; Merge function definition. 93 | (t nil))) ; Otherwise do the default. 94 | (put 'define-feature 'lisp-indent-function 'define-feature-lisp-indent) 95 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 22.3.0 4 | hooks: 5 | - id: black 6 | args: ["--line-length", "79"] 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v2.4.0 9 | hooks: 10 | - id: flake8 11 | - id: end-of-file-fixer 12 | - id: trailing-whitespace 13 | args: ["--chars"," \t"] 14 | - id: check-merge-conflict 15 | - repo: https://github.com/eschulte/lisp-format 16 | rev: master 17 | hooks: 18 | - id: lisp-format 19 | name: lisp-format 20 | args: [-style=file] 21 | - repo: local 22 | hooks: 23 | - id: clang-format 24 | name: clang-format 25 | language: system 26 | files: \.(c|h|cpp|hpp)$ 27 | entry: clang-format -i 28 | -------------------------------------------------------------------------------- /.stack-stamp.svg: -------------------------------------------------------------------------------- 1 | A: xorl $0xSECRET,(%rsp)BCDEGFHI: xorl $0xSECRET,(%rsp); retFunctionRet. Addr.   ...StackLockUnlock 2 | -------------------------------------------------------------------------------- /.stack-stamp.tex: -------------------------------------------------------------------------------- 1 | \tikzstyle{stack} = [draw, fill=black!10, text centered, text=black, text width=12em] 2 | \tikzstyle{label} = [text=black,font=\tt] 3 | 4 | \tikzstyle{block} = [draw, minimum height=2em, minimum width=2em, rounded corners] 5 | \tikzstyle{datablock} = [block, fill=gt@gray!20] 6 | \tikzstyle{codeblock} = [block, fill=gt@red!20] 7 | \tikzstyle{asmblock} = [codeblock, text width=16em] 8 | 9 | \begin{tikzpicture}[node distance=2em] 10 | %% Code Blocks 11 | \node[asmblock] (c1) {A: xorl \$0xSECRET,(\%rsp)}; 12 | \node[codeblock, below=of c1] (c2) {B}; 13 | \node[codeblock, below left=of c2] (c3) {C}; 14 | \node[codeblock, below right=of c2] (c4) {D}; 15 | \node[codeblock, below=of c4] (c5) {E}; 16 | \node[codeblock, right=of c4] (c6) {G}; 17 | \node[codeblock, below=of c3] (c7) {F}; 18 | \node[codeblock, below right=of c7] (c8) {H}; 19 | \node[asmblock, below=of c8] (c9) {I: xorl \$0xSECRET,(\%rsp); ret}; 20 | 21 | %% Proxy Blocks 22 | \node[above left=of c1] (p1) {}; 23 | \node[below left=of c9] (p2) {}; 24 | 25 | %% Control Flow 26 | \draw[->, very thick] (c1) to (c2); 27 | \draw[->, crossed, bend right] (c2) to (c3); 28 | \draw[->, crossed, bend left] (c2) to (c4); 29 | \draw[->, crossed, bend left] (c4) to (c6); 30 | \draw[->, crossed, bend left] (c6) to (c4); 31 | \draw[->, crossed] (c4) to (c5); 32 | \draw[->, crossed] (c3) to (c7); 33 | \draw[->, crossed, bend left] (c5) to (c8); 34 | \draw[->, very thick, bend right] (c7) to (c8); 35 | \draw[->, very thick] (c8) to (c9); 36 | \draw[->, very thick, bend right, dashed] (p1.west) to (c1.west); 37 | \draw[->, very thick, bend right, dashed] (c9.west) to (p2.west); 38 | 39 | %% Function 40 | \node[draw, dotted, very thick, fit=(c1) (c3) (c6) (c9)] (func) {}; 41 | \node[label, above=0.25em of func] () {Function}; 42 | 43 | %% Stack 44 | \node[stack, right=6em of c1] (s1) {Ret. Addr.}; 45 | \node[stack, below=0em of s1] (s2) {~\\~\\~\\~}; 46 | \node[stack, below=0em of s2] (s3) {~\\~\\~}; 47 | \node[stack, below=0em of s3] (s4) {~\\~\\~\\~\\~\\~\\~}; 48 | \node[stack, below=0em of s4] (s5) {...\\~\\~}; 49 | \node[fit=(s1) (s5)] (stack) {}; 50 | \node[label, above=0.25em of stack] () {Stack}; 51 | 52 | %% Lock and unlock 53 | \draw[->, very thick, dotted, draw=gt@red] (c1.north east) |- node[auto, near end, text=gt@red, above] {Lock} (s1.north west); 54 | \node[right=3em of c9] (anchor) {}; 55 | \draw[very thick, dotted, draw=gt@red] (c9.east) |- node[auto, very near end, text=gt@red, below] {Unlock} (anchor.center); 56 | \draw[->, very thick, dotted, draw=gt@red] (anchor.center) |- (s1.south west) ; 57 | 58 | \end{tikzpicture} 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.1.1: (unreleased) 2 | 3 | - TBD 4 | 5 | 0.1.0: 6 | 7 | - Initial release 8 | -------------------------------------------------------------------------------- /CMakeLists.googletest: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.2) 2 | 3 | project(googletest-download NONE) 4 | 5 | include(ExternalProject) 6 | externalproject_add( 7 | googletest 8 | GIT_REPOSITORY https://github.com/google/googletest.git 9 | GIT_TAG release-1.10.0 10 | SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" 11 | BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" 12 | CONFIGURE_COMMAND "" 13 | BUILD_COMMAND "" 14 | INSTALL_COMMAND "" 15 | TEST_COMMAND "" 16 | ) 17 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Need 3.10 to support CXX_STANDARD=17 2 | cmake_minimum_required(VERSION 3.10.0) 3 | project(gtirb-stack-stamp) 4 | 5 | include(CheckFunctionExists) 6 | include(CheckCXXSourceCompiles) 7 | include(CheckIncludeFile) 8 | 9 | # --------------------------------------------------------------------------- 10 | # Build options 11 | # --------------------------------------------------------------------------- 12 | 13 | option(GTIRB_STACK_STAMP_ENABLE_TESTS "Enable building and running unit tests." ON) 14 | 15 | # This just sets the builtin BUILD_SHARED_LIBS, but if defaults to ON instead of 16 | # OFF. 17 | option(GTIRB_STACK_STAMP_BUILD_SHARED_LIBS "Build shared libraries." ON) 18 | if(GTIRB_STACK_STAMP_BUILD_SHARED_LIBS) 19 | set(BUILD_SHARED_LIBS ON) 20 | else() 21 | set(BUILD_SHARED_LIBS OFF) 22 | endif() 23 | if(UNIX AND NOT BUILD_SHARED_LIBS) 24 | # Find only static libraries 25 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") 26 | add_compile_options(-static) 27 | endif() 28 | 29 | if(GTIRB_STACK_STAMP_ENABLE_TESTS) 30 | enable_testing() 31 | endif() 32 | 33 | # --------------------------------------------------------------------------- 34 | # Global settings 35 | # --------------------------------------------------------------------------- 36 | 37 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 38 | set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src) 39 | if(WIN32) 40 | set(CMAKE_DEBUG_POSTFIX 41 | "d" 42 | CACHE STRING "add a postfix, usually d on windows" 43 | ) 44 | endif() 45 | set(CMAKE_RELEASE_POSTFIX 46 | "" 47 | CACHE STRING "add a postfix, usually empty on windows" 48 | ) 49 | set(CMAKE_RELWITHDEBINFO_POSTFIX 50 | "" 51 | CACHE STRING "add a postfix, usually empty on windows" 52 | ) 53 | set(CMAKE_MINSIZEREL_POSTFIX 54 | "" 55 | CACHE STRING "add a postfix, usually empty on windows" 56 | ) 57 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 58 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 59 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 60 | 61 | # Use C++17 62 | set(CMAKE_CXX_STANDARD 17) 63 | # Error if it's not available 64 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 65 | 66 | # Specifically check for gcc-7 or later. gcc-5 is installed on many systems 67 | # and will accept -std=c++17, but does not fully support the standard. 68 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 69 | if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "7.0.0") 70 | message(FATAL_ERROR "gcc 7 or later is required to build gtirb") 71 | endif() 72 | endif() 73 | 74 | # 75 | # Global Options (Compile / Link) 76 | # 77 | if((${CMAKE_CXX_COMPILER_ID} STREQUAL GNU) OR (${CMAKE_CXX_COMPILER_ID} 78 | STREQUAL Clang)) 79 | add_compile_options(-Wall -Wextra -Wpointer-arith -Wshadow -Werror) 80 | add_compile_options(-fPIC) 81 | endif() 82 | 83 | # --------------------------------------------------------------------------- 84 | # Boost 85 | # --------------------------------------------------------------------------- 86 | set(BOOST_COMPONENTS filesystem program_options system) 87 | find_package(Boost 1.67 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) 88 | 89 | add_definitions(-DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION) 90 | add_definitions(-DBOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE) 91 | add_definitions(-DBOOST_SYSTEM_NO_DEPRECATED) 92 | 93 | # Boost versions 1.70.0+ may use Boost's provided CMake support rather than 94 | # CMake's internal Boost support. The former uses "Boost::boost" and so on, 95 | # while the latter uses "Boost_BOOST" and so on. This normalizes the two cases 96 | # to use Boost_INCLUDE_DIRS and Boost_LIBRARIES. 97 | if(TARGET Boost::headers) 98 | get_target_property(Boost_INCLUDE_DIRS Boost::headers 99 | INTERFACE_INCLUDE_DIRECTORIES) 100 | foreach(BOOST_COMPONENT ${BOOST_COMPONENTS}) 101 | list(APPEND Boost_LIBRARIES Boost::${BOOST_COMPONENT}) 102 | endforeach() 103 | endif() 104 | 105 | include_directories(${Boost_INCLUDE_DIRS}) 106 | 107 | # --------------------------------------------------------------------------- 108 | # Google Test Application 109 | # --------------------------------------------------------------------------- 110 | if(GTIRB_STACK_STAMP_ENABLE_TESTS) 111 | # Pull in Google Test 112 | # https://github.com/google/googletest/tree/master/googletest#incorporating- 113 | # into-an-existing-cmake-project 114 | 115 | # Download and unpack googletest at configure time 116 | configure_file(CMakeLists.googletest googletest-download/CMakeLists.txt) 117 | 118 | execute_process( 119 | COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . 120 | RESULT_VARIABLE result 121 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" 122 | ) 123 | 124 | if(result) 125 | message(WARNING "CMake step for googletest failed: ${result}") 126 | endif() 127 | 128 | execute_process( 129 | COMMAND "${CMAKE_COMMAND}" --build . 130 | RESULT_VARIABLE result 131 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" 132 | ) 133 | 134 | if(result) 135 | message(WARNING "Build step for googletest failed: ${result}") 136 | endif() 137 | 138 | # Prevent overriding the parent project's compiler/linker settings on Windows 139 | set(gtest_force_shared_crt 140 | ON 141 | CACHE BOOL "" FORCE 142 | ) 143 | 144 | # Add googletest directly to our build. This defines the gtest and gtest_main 145 | # targets. 146 | add_subdirectory( 147 | "${CMAKE_BINARY_DIR}/googletest-src" "${CMAKE_BINARY_DIR}/googletest-build" 148 | EXCLUDE_FROM_ALL 149 | ) 150 | 151 | include_directories("${gtest_SOURCE_DIR}/include") 152 | endif() 153 | 154 | # --------------------------------------------------------------------------- 155 | # Capstone and Keystone 156 | # --------------------------------------------------------------------------- 157 | if(BUILD_SHARED_LIBS) 158 | find_library(CAPSTONE NAMES capstone) 159 | find_library(KEYSTONE NAMES keystone) 160 | if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC) 161 | include_directories(SYSTEM ${CAPSTONE}/../include ${KEYSTONE}/../include) 162 | endif() 163 | else() 164 | find_library(CAPSTONE NAMES libcapstone.a) 165 | find_library(KEYSTONE NAMES libkeystone.a) 166 | if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC) 167 | include_directories(SYSTEM ${CAPSTONE}/../include ${KEYSTONE}/../include) 168 | endif() 169 | endif() 170 | 171 | # --------------------------------------------------------------------------- 172 | # gtirb 173 | # --------------------------------------------------------------------------- 174 | find_package(gtirb REQUIRED) 175 | 176 | # --------------------------------------------------------------------------- 177 | # Sources 178 | # --------------------------------------------------------------------------- 179 | add_subdirectory(src) 180 | 181 | if(GTIRB_STACK_STAMP_ENABLE_TESTS) 182 | add_subdirectory(tests) 183 | endif() 184 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | ENV LC_ALL=C.UTF-8 4 | ENV LANG=C.UTF-8 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | 7 | # Setup formatting 8 | RUN apt-get update -y && apt-get install -y clang-format curl elpa-paredit \ 9 | emacs-nox git sbcl slime python3-pip python3-protobuf 10 | RUN pip3 install "virtualenv<20.0.0" 11 | RUN pip3 install pre-commit 12 | # Install the lisp-format pre-commit format checker. 13 | RUN curl https://raw.githubusercontent.com/eschulte/lisp-format/master/lisp-format > /usr/bin/lisp-format 14 | RUN chmod +x /usr/bin/lisp-format 15 | RUN echo "(add-to-list 'load-path \"/usr/share/emacs/site-lisp/\")" > /root/.lisp-formatrc 16 | 17 | COPY ./.pre-commit-config.yaml /gt/gtirb-stack-stamp/.pre-commit-config.yaml 18 | WORKDIR /gt/gtirb-stack-stamp 19 | RUN git init 20 | RUN pre-commit install-hooks 21 | WORKDIR / 22 | RUN rm -rf /gt/ 23 | 24 | # Setup build 25 | RUN apt-get install -y build-essential cmake \ 26 | libprotobuf-dev libboost-program-options1.71-dev \ 27 | libboost-filesystem1.71-dev libboost-system1.71-dev make pkg-config \ 28 | software-properties-common unzip wget 29 | RUN python3 -m pip install --upgrade setuptools wheel 30 | 31 | # Setup apt repositories 32 | RUN wget -O - https://download.grammatech.com/gtirb/files/apt-repo/conf/apt.gpg.key | apt-key add - 33 | # We have to use the stable repository because pypi can only contain stable 34 | # releases of gtirb. 35 | RUN echo "deb https://download.grammatech.com/gtirb/files/apt-repo $(lsb_release -sc) stable" | tee -a /etc/apt/sources.list 36 | RUN apt-get update -y 37 | 38 | # Install Keystone 39 | RUN git clone https://github.com/keystone-engine/keystone.git 40 | RUN cd keystone && \ 41 | mkdir build && \ 42 | cd build && \ 43 | ../make-share.sh && \ 44 | make install && \ 45 | cd .. && \ 46 | rm -rf keystone 47 | 48 | RUN ldconfig /usr/local/lib 49 | 50 | # Install Python packages 51 | RUN python3 -m pip install --upgrade pip 52 | 53 | # Common Lisp Setup 54 | RUN apt-get install -y sbcl 55 | RUN curl -O https://beta.quicklisp.org/quicklisp.lisp 56 | RUN sbcl --load quicklisp.lisp \ 57 | --eval '(quicklisp-quickstart:install)' \ 58 | --eval '(let ((ql-util::*do-not-prompt* t)) (ql:add-to-init-file))' 59 | RUN mkdir -p $HOME/quicklisp/local-projects 60 | WORKDIR /root/quicklisp/local-projects 61 | RUN git clone https://github.com/GrammaTech/cl-utils.git gt/ 62 | RUN git clone https://github.com/rpav/cl-interval.git 63 | RUN git clone https://github.com/GrammaTech/cl-capstone.git 64 | RUN git clone --branch quicklisp https://git.grammatech.com/rewriting/gtirb.git 65 | RUN git clone https://github.com/GrammaTech/gtirb-functions.git 66 | RUN git clone https://git.grammatech.com/rewriting/gtirb-capstone.git 67 | RUN git clone https://github.com/GrammaTech/keystone.git 68 | RUN sbcl --eval '(ql:register-local-projects)' 69 | RUN sbcl --eval '(ql:quickload :gtirb-capstone)' 70 | RUN sbcl --eval '(ql:quickload :gt/full)' 71 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 GrammaTech, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GTIRB Stack Stamp 2 | 3 | This repository holds example implementations of binary 4 | transformations implemented over top of 5 | [GTIRB](https://github.com/grammatech/gtirb). See the accompanying 6 | [GTIRB 7 | Tutorial](https://grammatech.github.io/gtirb/md_stack-stamp.html) for 8 | more information. 9 | 10 | Specifically, this example repository implements a transform to apply 11 | 'stack stamping' protections to a binary. 12 | 13 | ![stack-stamp signature graphic.](.stack-stamp.svg) 14 | 15 | ## Abstract 16 | 17 | Stack stamping is a technique to help mitigate 18 | ROP style attacks. 19 | This is done by 'stamping' the return address on the stack, thus 20 | encrypting it. Before it is popped off the stack and used, it is 21 | decrypted by 'un-stamping' it. This can be an efficient protection, 22 | as no registers are needed, and while flags are affected, they are 23 | only affected at function entry/exits where they do not need to be 24 | preserved. By encoding and decoding this return address, an attacker 25 | has a more difficult task, since the replacement data would need to be 26 | properly encoded, such that when it is un-stamped, it results in the 27 | desired address. 28 | 29 | ## Building 30 | 31 | This repository contains three implementations of stack-stamping in three 32 | different languages: 33 | 34 | 1. [Python](#python) 35 | 2. [C++](#c) 36 | 3. [Common Lisp](#common-lisp) 37 | 38 | ### Python 39 | 40 | The Python transform requires some dependencies to be installed: 41 | 42 | ```sh 43 | pip3 install gtirb-capstone gtirb-functions capstone keystone-engine 44 | ``` 45 | 46 | To starting using it, run: 47 | 48 | ```sh 49 | python3 setup.py develop 50 | ``` 51 | 52 | To invoke the command line utility thus generated: 53 | 54 | ```sh 55 | python3 -m gtirb_stack_stamp 56 | ``` 57 | 58 | ### C++ 59 | 60 | This transform depends on the following libraries: 61 | 62 | * [Boost](https://www.boost.org/) (version 1.67 or later) 63 | * [GTIRB](https://github.com/grammatech/gtirb) 64 | * [Capstone](https://github.com/aquynh/capstone) 65 | * [Keystone](https://github.com/keystone-engine/keystone) 66 | 67 | Ensure they are installed before compiling the C++ version of the transform. 68 | Building from source also depends on [CMake](https://cmake.org) being installed. 69 | 70 | #### Options 71 | 72 | We add the filling CMake options during building: 73 | 74 | * `GTIRB_STACK_STAMP_ENABLE_TESTS`: Set to `OFF` to disable the downloading of 75 | Google Test and the building of the test executable. `ON` by default. 76 | * `GTIRB_STACK_STAMP_BUILD_SHARED_LIBS`: Set to `OFF` to build static libraries 77 | instead of dybnamic ones. `ON` by default. 78 | 79 | #### On Linux 80 | 81 | ```sh 82 | cmake -Bbuild ./ 83 | cd build 84 | make 85 | ``` 86 | 87 | The generated command-line utility will then be available in `build/bin`. 88 | 89 | #### On Windows 90 | 91 | Currently, [some](https://github.com/keystone-engine/keystone/issues/471) 92 | [issues](https://github.com/keystone-engine/keystone/issues/472) are preventing 93 | Keystone from being built on Windows, so the C++ version of gtirb-stack-stamp is 94 | buildable on Linux only for the time being. 95 | 96 | #### Tests 97 | 98 | Our CMake automatically downloads a local copy of [Google Test](https://github.com/google/googletest) 99 | and produces a test executable. To run it: 100 | 101 | ```sh 102 | build/bin/test-gtirb-stack-stamp 103 | ``` 104 | 105 | You will need [gtirb-pprinter](https://github.com/grammatech/gtirb-pprinter) 106 | and [ddisasm](https://github.com/grammatech/ddisasm) on your PATH. 107 | 108 | ### Common Lisp 109 | 110 | The Common Lisp transform requires the following external libraries: 111 | - [Capstone](https://github.com/aquynh/capstone) 112 | - [Keystone](https://github.com/keystone-engine/keystone) 113 | 114 | and the following common lisp packages 115 | ([gtirb](https://github.com/grammatech/gtirb), 116 | [gtirb-functions](https://github.com/grammatech/gtirb-functions), 117 | [gtirb-capstone](https://github.com/grammatech/gtirb-capstone)) 118 | which may be installed via QuickLisp: 119 | 120 | 1. Clone this repository into your `~/quicklisp/local-projects` directory 121 | ```sh 122 | git clone https://github.com/grammatech/gtirb-stack-stamp 123 | ``` 124 | 125 | 2. Load `gtirb-stack-stamp` and all dependencies. 126 | ```lisp 127 | (ql:quickload :gtirb-stack-stamp) 128 | ``` 129 | 130 | To run the transform at the REPL: 131 | ```lisp 132 | (write-gtirb (stack-stamp (drop-cfi (read-gtirb "in.gtirb"))) "out.gtirb") 133 | ``` 134 | 135 | To build the command line executable: 136 | ```sh 137 | sbcl --eval '(ql:quickload :gtirb-stack-stamp)' \ 138 | --eval '(asdf:make :gtirb-stack-stamp :type :program :monolithic t)' 139 | ``` 140 | 141 | To invoke the command line utility thus generated: 142 | ```sh 143 | ./stack-stamp --help 144 | ``` 145 | 146 | ## Copyright and Acknowledgments 147 | 148 | Copyright (C) 2020 GrammaTech, Inc. 149 | 150 | This code is licensed under the MIT license. See the LICENSE file in 151 | the project root for license terms. 152 | 153 | This project is sponsored by the Office of Naval Research, One Liberty 154 | Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 155 | N68335-17-C-0700. The content of the information does not necessarily 156 | reflect the position or policy of the Government and no official 157 | endorsement should be inferred. 158 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Stack Stamp 2 | 3 | Transform to apply 'stack stamping' protections to a binary. 4 | 5 | # Abstract 6 | 7 | The goal of the stack stamp transform is to encode the return address on the 8 | stack and decode it just before it is used. This technique can mitigate some 9 | ROP style attacks. 10 | 11 | To accomplish this, for each function, instructions are added on entry and at 12 | each exit, that encode the return address. The location of the return address 13 | is known relative to the current stack pointer at entry and exit. We will use 14 | a simple XOR operation to do this, meaning the encoding and decoding 15 | instructions will be the same and just need to XOR the return address with some 16 | value. We will select a random value for each function, making this harder to 17 | defeat than if we selected the same value for the entire binary. 18 | 19 | On function entry, rsp points to the return address, so the indirect access 20 | [rsp] will access the return address. We want to encode all 8 bytes of the 21 | return address, but there is no 64-bit immediate version of xor, so we will do 22 | it with two instructions, 4 bytes each. Since XOR is symmetric, the same 23 | instructions will decode the return address on exit. 24 | 25 | xorl 00112233, [rsp-8] 26 | xorl 44556677, [rsp-4] 27 | 28 | When changing instructions (including adding or removing) one must be careful to 29 | consider if flags or registers are live and if they will be affected by your 30 | changes. In our case flags will be affected, but the ABI (application binary 31 | interface) states that flags are not preserved across function calls so this 32 | should not be a problem. We are not using any registers so that should be fine 33 | as well. 34 | 35 | In addition to considering that functions may have multiple exits, there is 36 | another case that need to be considered. Tail calls are cases where a function 37 | exits by jumping to another function, rather than returning or calling. This 38 | needs special attention, since a jmp instruction does not always indicate an 39 | function boundary, but if it does, we would need to decode the return address 40 | before the jump. We will opt to skip functions that do not have clear 'return' 41 | exits to avoid this case. 42 | 43 | 1. Identify basic blocks that encompass a function 44 | 2. Identify entry and exit basic blocks for each function 45 | 3. Insert encoding instructions at the start of each entry block 46 | 4. Insert decoding instructions in each exit block, just before the return 47 | 48 | ## Identify function blocks (1) 49 | 50 | This first part is done for us. GTIRB contains an auxdata table that lists the 51 | basic blocks that encompass each function, along with the entry block for each. 52 | 53 | ## Identify entry/exit blocks (2) 54 | 55 | As mentioned in the previous section, the entry block for each function is 56 | identified for us already in the GTIRB auxdata table. 57 | 58 | The exit blocks can be identified by looking at the cfg edges. Any block that 59 | has an edge with a target block that is not within the set of blocks 60 | encompassing this function, is likely an exit block. We further confirm the 61 | exit edge type is a 'return' type edge. 62 | 63 | ## Insert entry instructions (3) 64 | 65 | Entry instructions should be placed immediately on entry, i.e. before all 66 | instructions in the entry block for the function. 67 | 68 | GTIRB only includes the raw bytes for each basic block with no instruction 69 | semantics or mnemonics. To insert new instructions, we modify the byte sequence 70 | of the basic block. For entry we don't care and can just insert the new 71 | instruction bytes before the existing bytes. 72 | 73 | We do this using the Keystone assembler package. Given instructions in string 74 | form, it will give us a sequence of assembled bytes we can insert in the block. 75 | 76 | ## Insert exit instructions (4) 77 | 78 | Exit instructions need to be placed immediately before the last instruction of 79 | each exit block. To identify the last instruction we can use the Capstone 80 | disassembler package. Given the bytes in a basic block we can disassemble the 81 | block, identify the last instruction to confirm it is what we expect, and see 82 | how many bytes it is so we know where to insert our new bytes. Again we'll do 83 | so using the Keystone assembler. 84 | 85 | # Getting started 86 | 87 | To start, make sure gtirb, keystone, and capstone are installed in python. 88 | 89 | ``` 90 | pip3 install keystone-engine capstone TBD-GTIRB 91 | 92 | git clone https://github.com/keystone-engine/keystone.git 93 | cd keystone 94 | mkdir build 95 | cd build 96 | ../make-share.sh 97 | make install 98 | ``` 99 | 100 | ## Rewriting steps 101 | 102 | gtirb-pprinter:jrobbins/use-symbol-type 103 | ddisasm:jrobbins/use-symbol-type 104 | 105 | 1. Build IR 106 | `ddisasm factorial -i factorial.gtirb` 107 | 2. Break up byte intervals so each code block has its own byte interval 108 | 3. Remove CFI information (ONLY for stack stamp, or other transformations that 109 | modify stack unwinding.) 110 | 4. Stack stamp it 111 | `python3 stack_stamp.py factorial.gtirb -o factorial.stamp.gtirb` 112 | 5. Rebuild it 113 | `gtirb-binary-printer factorial.stamp.gtirb -b factorial.stamp -c -no-pie` 114 | 115 | 116 | # Notes 117 | 118 | An ROP (Return Oriented Programming) attack is one that replaces an existing 119 | return address on the stack with a different address, causing the process to be 120 | redirected to code that was not intended. 121 | 122 | Any symetrical combination of encoding instructions, may be used (add/sub, bit 123 | rotation, xor, etc). We chose xor for it's simplicity. The value to xor can be 124 | anything, but the more variation, the better. We chose to seed a PRNG with the 125 | function name (a hash of), and use that to generate a value per function. 126 | 127 | The goal of the stack stamp transform is to encode the return address on the 128 | stack immediatly on function entry, and decode it just before it is used to 129 | return to the caller. This technique can mitigate some ROP style attacks. 130 | -------------------------------------------------------------------------------- /gtirb-stack-stamp.asd: -------------------------------------------------------------------------------- 1 | ;;; Copyright (C) 2020 GrammaTech, Inc. 2 | ;;; 3 | ;;; This code is licensed under the MIT license. See the LICENSE file in 4 | ;;; the project root for license terms. 5 | ;;; 6 | ;;; This project is sponsored by the Office of Naval Research, One Liberty 7 | ;;; Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 8 | ;;; N68335-17-C-0700. The content of the information does not necessarily 9 | ;;; reflect the position or policy of the Government and no official 10 | ;;; endorsement should be inferred. 11 | (defsystem "gtirb-stack-stamp" 12 | :name "gtirb-stack-stamp" 13 | :author "GrammaTech" 14 | :licence "MIT" 15 | :description "Stack-stamp binary executable transformation over GTIRB" 16 | :long-description "Stack stamping is a binary hardening 17 | transformation in which a random key is xor'd on the return address on 18 | the top of the stack upon entry to any function. This same key is 19 | again xor'd against the top of the stack just before the function 20 | returns. This can help defeat return oriented programming (ROP) 21 | attacks--especially when used as part of a moving target defense 22 | system." 23 | :depends-on (:gtirb-stack-stamp/gtirb-stack-stamp) 24 | :class :package-inferred-system 25 | :defsystem-depends-on (:asdf-package-system) 26 | :build-operation "asdf:program-op" 27 | :build-pathname "stack-stamp" 28 | :entry-point "gtirb-stack-stamp::run-ss" 29 | :perform 30 | (test-op (o c) (symbol-call :gtirb-stack-stamp '#:test))) 31 | -------------------------------------------------------------------------------- /gtirb-stack-stamp.lisp: -------------------------------------------------------------------------------- 1 | ;;; Copyright (C) 2020 GrammaTech, Inc. 2 | ;;; 3 | ;;; This code is licensed under the MIT license. See the LICENSE file in 4 | ;;; the project root for license terms. 5 | ;;; 6 | ;;; This project is sponsored by the Office of Naval Research, One Liberty 7 | ;;; Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 8 | ;;; N68335-17-C-0700. The content of the information does not necessarily 9 | ;;; reflect the position or policy of the Government and no official 10 | ;;; endorsement should be inferred. 11 | (defpackage :gtirb-stack-stamp/gtirb-stack-stamp 12 | (:nicknames :gtirb-stack-stamp) 13 | (:use :gt/full :gtirb :gtirb-functions :gtirb-capstone 14 | :stefil :command-line-arguments) 15 | (:shadowing-import-from :gt :size) 16 | (:import-from :cl-intbytes :int->octets :octets->uint) 17 | (:import-from :asdf/system :system-relative-pathname) 18 | (:shadow :version :architecture :mode :symbol) 19 | (:export :gtirb-stack-stamp)) 20 | (in-package :gtirb-stack-stamp/gtirb-stack-stamp) 21 | (in-readtable :curry-compose-reader-macros) 22 | (defmethod size ((obj gtirb-node)) (gtirb:size obj)) 23 | 24 | ;;; Implementation 25 | (defgeneric stack-stamp (object) 26 | (:documentation "Apply the stack-stamp transformation.") 27 | (:method ((obj gtirb)) 28 | (set-syntax obj :syntax-att) 29 | (mapc #'stack-stamp (modules obj))) 30 | (:method ((obj module)) (mapc #'stack-stamp (functions obj))) 31 | (:method ((obj func)) 32 | (unless (entries obj) (return-from stack-stamp)) 33 | (let ((exits (append (returns obj) (tail-calls obj)))) 34 | (unless exits (return-from stack-stamp)) 35 | (let* ((key (int->octets (sxhash obj) 8)) 36 | (stamp-bytes (asm obj (format nil "xorl $0x~x,(%rsp); ~ 37 | xorl $0x~x,4(%rsp);" 38 | (octets->uint (subseq key 0 4) 4) 39 | (octets->uint (subseq key 4) 4))))) 40 | #+debug (format t "ENTRANCE: 0x~x~%" (address (first (entries obj)))) 41 | (mapc (lambda (entry-block) 42 | (setf (bytes entry-block 0 0) stamp-bytes)) 43 | (entries obj)) 44 | (mapc (lambda (exit-block) 45 | (if-let* ((ins (instructions exit-block)) 46 | ;; Ret instruction *or* last instruction (tail call). 47 | (ins-pos (or (position-if [{eql :ret} #'mnemonic] ins) 48 | (1- (length ins)))) 49 | ;; Convert instruction index into byte index. 50 | (byte-pos (reduce #'+ (subseq ins 0 ins-pos) 51 | :key #'size))) 52 | (progn 53 | #+debug (format t "EXIT: 0x~x~%" (address exit-block)) 54 | (setf (bytes exit-block byte-pos byte-pos) stamp-bytes)) 55 | (error "Unable to find stamp return block for ~s" obj))) 56 | exits))))) 57 | 58 | (defmethod stack-stamp :around ((obj gtirb-node)) (call-next-method) obj) 59 | 60 | 61 | ;;;; Main test suite. 62 | (defsuite test) 63 | (in-suite test) 64 | 65 | (defvar *hello*) 66 | 67 | (defixture hello 68 | (:setup (setf *hello* (read-gtirb 69 | (system-relative-pathname "gtirb-stack-stamp" 70 | "tests/hello.v1.gtirb")))) 71 | (:teardown (setf *hello* nil))) 72 | 73 | (defun drop-cfi (ir) 74 | (mapc (lambda (module) 75 | (setf (aux-data module) 76 | (remove-if [{string= "cfiDirectives"} #'car] 77 | (aux-data module)))) 78 | (modules ir)) 79 | ir) 80 | 81 | (deftest stack-stamp-hello () 82 | (nest 83 | (with-fixture hello) 84 | (flet ((symbolic-expressions (it) 85 | (nest (mappend [#'hash-table-values #'symbolic-expressions]) 86 | (mappend #'byte-intervals) (mappend #'sections) 87 | (modules it))) 88 | (interval-bytes (it) 89 | (nest (apply #'concatenate 'vector) 90 | (mapcar #'bytes) 91 | (mappend #'byte-intervals) (mappend #'sections) 92 | (modules it))) 93 | (block-bytes (it) 94 | (nest (apply #'concatenate 'vector) 95 | (mapcar #'bytes) (mappend #'blocks) 96 | (mappend #'byte-intervals) (mappend #'sections) 97 | (modules it))))) 98 | (let ((original-symbolic-expressions (symbolic-expressions *hello*)) 99 | (original-interval-bytes (interval-bytes *hello*)) 100 | (original-bytes (block-bytes *hello*))) 101 | (is (typep (stack-stamp *hello*) 'gtirb)) 102 | (let ((new-symbolic-expressions (symbolic-expressions *hello*))) 103 | ;; Should not have fewer symbolic expressions when we're only 104 | ;; inserting (not removing) instructions (and therefore bytes). 105 | (is (= (length original-symbolic-expressions) 106 | (length new-symbolic-expressions))) 107 | ;; In fact we should have exactly the same instructions. 108 | (is (= (length original-symbolic-expressions) 109 | (length new-symbolic-expressions)))) 110 | (let ((new-bytes (block-bytes *hello*))) 111 | ;; We should have more bytes (with the new stamping instructions). 112 | (is (< (length original-interval-bytes) 113 | (length (interval-bytes *hello*)))) 114 | (is (< (length original-bytes) (length new-bytes))))))) 115 | 116 | 117 | ;;;; External command-line driver. 118 | ;;; 119 | ;;; Compile with the following command: 120 | ;;; sbcl --eval '(ql:quickload :gtirb-stack-stamp)' \ 121 | ;;; --eval '(asdf:make :gtirb-stack-stamp :type :program :monolithic t)' 122 | ;;; 123 | (define-command ss 124 | (input output 125 | &spec 126 | '((("help" #\h #\?) :type boolean :optional t 127 | :documentation "display help output") 128 | (("gtirb" #\g) :type boolean :optional t :initial-value nil 129 | :documentation "output binary gtirb (default)") 130 | (("asm" #\a) :type boolean :optional t :initial-value nil 131 | :documentation "output assembly text") 132 | (("binary" #\b) :type boolean :optional t :initial-value nil 133 | :documentation "output a binary executable")) 134 | &aux ir) 135 | "Apply \"stack stamp\" protections to a binary executable." "" 136 | (when help (show-help-for-ss)) 137 | (unless (or gtirb asm binary) 138 | (error "Must specify at least one output type: gtirb, asm, or binary.")) 139 | (setf ir 140 | ;; If INPUT is a path that doesn't end in "gtirb" call ddisasm. 141 | (if (and (or (pathnamep input) (stringp input)) 142 | (not (string= "gtirb" (pathname-type input)))) 143 | (with-temporary-file (:pathname temp :type "gtirb" :direction input) 144 | (wait-process 145 | (launch-program 146 | (list "ddisasm" (namestring input) "--ir" (namestring temp)) 147 | :output *standard-output* 148 | :error-output *error-output*)) 149 | (read-gtirb temp)) 150 | ;; Otherwise we assume our input is already a GTIRB file. 151 | (read-gtirb input))) 152 | (setf ir (stack-stamp (drop-cfi ir))) 153 | (labels ((normalize-path (others extension) 154 | (let ((new (if others 155 | (make-pathname :type extension :defaults output) 156 | output))) 157 | (when (and (not (equalp new output)) (probe-file new)) 158 | (error "Output ~a already exists" (namestring new))) 159 | (namestring new))) 160 | (gtirb-path () (normalize-path (or asm binary) "gtirb")) 161 | (binary-path () (normalize-path (or asm gtirb) nil)) 162 | (asm-path () (normalize-path (or gtirb binary) "s"))) 163 | (with-temporary-file (:pathname temp :type "gtirb" :direction output) 164 | (write-gtirb ir temp) 165 | (when gtirb 166 | (wait-process 167 | (launch-program (list "cp" (namestring temp) (gtirb-path))))) 168 | (when (or asm binary) 169 | (wait-process 170 | (launch-program 171 | `("gtirb-pprinter" ,(namestring temp) 172 | "--skip-section"".eh_frame" 173 | ,@(when binary (list "--binary" (binary-path))) 174 | ,@(when asm (list "--asm" (asm-path)))) 175 | :output *standard-output* 176 | :error-output *error-output*)))))) 177 | -------------------------------------------------------------------------------- /gtirb_stack_stamp/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 GrammaTech, Inc. 3 | # 4 | # This code is licensed under the MIT license. See the LICENSE file in 5 | # the project root for license terms. 6 | # 7 | # This project is sponsored by the Office of Naval Research, One Liberty 8 | # Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 9 | # N68335-17-C-0700. The content of the information does not necessarily 10 | # reflect the position or policy of the Government and no official 11 | # endorsement should be inferred. 12 | # 13 | __all__ = ["apply_stack_stamp"] 14 | 15 | from .stack_stamp import apply_stack_stamp 16 | -------------------------------------------------------------------------------- /gtirb_stack_stamp/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # Copyright (C) 2020 GrammaTech, Inc. 4 | # 5 | # This code is licensed under the MIT license. See the LICENSE file in 6 | # the project root for license terms. 7 | # 8 | # This project is sponsored by the Office of Naval Research, One Liberty 9 | # Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 10 | # N68335-17-C-0700. The content of the information does not necessarily 11 | # reflect the position or policy of the Government and no official 12 | # endorsement should be inferred. 13 | # 14 | import argparse 15 | import logging 16 | from gtirb import IR 17 | from .stack_stamp import apply_stack_stamp 18 | import subprocess 19 | 20 | 21 | def main(): 22 | ap = argparse.ArgumentParser( 23 | description="Show (un)reachable code in GTIRB" 24 | ) 25 | ap.add_argument("infile") 26 | ap.add_argument( 27 | "-o", "--outfile", default=None, help="GTIRB output filename" 28 | ) 29 | ap.add_argument( 30 | "--rebuild", 31 | metavar="FILENAME", 32 | default=None, 33 | help="Rebuild binary as FILENAME", 34 | ) 35 | ap.add_argument( 36 | "-v", "--verbose", action="store_true", help="Verbose output" 37 | ) 38 | ap.add_argument("-q", "--quiet", action="store_true", help="No output") 39 | args = ap.parse_args() 40 | 41 | logging.basicConfig(format="%(message)s") 42 | logger = logging.getLogger("gtirb.stackstamp") 43 | if args.verbose: 44 | logger.setLevel(logging.DEBUG) 45 | elif not args.quiet: 46 | logger.setLevel(logging.INFO) 47 | 48 | if args.rebuild is not None and args.outfile is None: 49 | logger.error("Error: with --rebuild, --outfile is required") 50 | exit(1) 51 | 52 | logger.info("Loading IR... " + args.infile) 53 | ir = IR.load_protobuf(args.infile) 54 | 55 | logger.info("Stamping functions...") 56 | apply_stack_stamp(ir, logger=logger) 57 | 58 | if args.outfile is not None: 59 | logger.info("Saving new IR...") 60 | ir.save_protobuf(args.outfile) 61 | 62 | logger.info("Done.") 63 | 64 | if args.rebuild is not None: 65 | logger.info("Pretty printing...") 66 | args_pp = [ 67 | "gtirb-pprinter", 68 | args.outfile, 69 | "--policy", 70 | "complete", 71 | "-b", 72 | args.rebuild, 73 | ] 74 | ec = subprocess.call(args_pp) 75 | return ec 76 | return 0 77 | 78 | 79 | if __name__ == "__main__": 80 | exit(main()) 81 | -------------------------------------------------------------------------------- /gtirb_stack_stamp/stack_stamp.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020 GrammaTech, Inc. 3 | # 4 | # This code is licensed under the MIT license. See the LICENSE file in 5 | # the project root for license terms. 6 | # 7 | # This project is sponsored by the Office of Naval Research, One Liberty 8 | # Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 9 | # N68335-17-C-0700. The content of the information does not necessarily 10 | # reflect the position or policy of the Government and no official 11 | # endorsement should be inferred. 12 | # 13 | import random 14 | import logging 15 | 16 | from gtirb_rewriting import ( 17 | SingleBlockScope, 18 | BlockPosition, 19 | Pass, 20 | PassManager, 21 | Patch, 22 | patch_constraints, 23 | ) 24 | 25 | 26 | class StampPass(Pass): 27 | """Add stack stamping instructions to every function.""" 28 | 29 | def begin_module(self, module, functions, context): 30 | """Register insertions and replacements for the given functions.""" 31 | for function in functions: 32 | # When the entry point function runs, there is no return address 33 | # on the stack, so we shouldn't stamp it. 34 | if module.entry_point not in function.get_all_blocks(): 35 | for block in function.get_entry_blocks(): 36 | context.register_insert( 37 | SingleBlockScope(block, BlockPosition.ENTRY), 38 | Patch.from_function(self.get_function_stamp_value), 39 | ) 40 | for block in function.get_exit_blocks(): 41 | context.register_insert( 42 | SingleBlockScope(block, BlockPosition.EXIT), 43 | Patch.from_function(self.get_function_stamp_value), 44 | ) 45 | 46 | @patch_constraints(clobbers_flags=True) 47 | def get_function_stamp_value(self, context): 48 | # Use the same seed every time this is called for the same function so 49 | # that each entrance and exit uses the same stamp. 50 | random.seed(context.function.uuid.int) 51 | w1 = random.randint(0, 2**32) 52 | w2 = random.randint(0, 2**32) 53 | return f""" 54 | xorl $0x{w1:X},{context.stack_adjustment}(%rsp) 55 | xorl $0x{w2:X},{context.stack_adjustment+4}(%rsp) 56 | """ 57 | 58 | 59 | def apply_stack_stamp(ir, logger=logging.Logger("null")): 60 | manager = PassManager(logger=logger) 61 | manager.add(StampPass()) 62 | manager.run(ir) 63 | -------------------------------------------------------------------------------- /gtirb_stack_stamp/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.1.dev0" 2 | -------------------------------------------------------------------------------- /include/gtirb_stack_stamp/gtirb_stack_stamp.hpp: -------------------------------------------------------------------------------- 1 | //===- gtirb_stack_stamp.hpp ------------------------------------*- C++ -*-===// 2 | // 3 | // Copyright (C) 2020 GrammaTech, Inc. 4 | // 5 | // This code is licensed under the MIT license. See the LICENSE file in the 6 | // project root for license terms. 7 | // 8 | // This project is sponsored by the Office of Naval Research, One Liberty 9 | // Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 10 | // N68335-17-C-0700. The content of the information does not necessarily 11 | // reflect the position or policy of the Government and no official 12 | // endorsement should be inferred. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #ifndef GTIRB_STACK_STAMP_H 17 | #define GTIRB_STACK_STAMP_H 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | namespace gtirb_stack_stamp { 27 | 28 | /// \class StackStamper 29 | /// 30 | /// \brief This class handles the insertion of stack-stamps in the entry and 31 | /// exit blocks of functions, as a simple example of control-flow integrity. 32 | class StackStamper { 33 | public: 34 | explicit StackStamper(gtirb::Context& Ctx_) : Ctx{Ctx_} { 35 | [[maybe_unused]] cs_err CSRet = cs_open(CS_ARCH_X86, CS_MODE_64, &Capstone); 36 | assert(CSRet == CS_ERR_OK); 37 | cs_option(Capstone, CS_OPT_DETAIL, CS_OPT_ON); 38 | 39 | [[maybe_unused]] ks_err KSRet = 40 | ks_open(KS_ARCH_X86, KS_MODE_LITTLE_ENDIAN | KS_MODE_64, &Keystone); 41 | assert(KSRet == KS_ERR_OK); 42 | ks_option(Keystone, KS_OPT_SYNTAX, KS_OPT_SYNTAX_ATT); 43 | } 44 | 45 | ~StackStamper() { 46 | cs_close(&Capstone); 47 | ks_close(Keystone); 48 | } 49 | 50 | /// \brief Insert a sequence of assembly instructions into a byte interval. 51 | /// Unlike ByteInterval::insertBytes, it automatically updates the offsets 52 | /// of anything that occurs after the insertion. 53 | /// 54 | /// \param BI The byte interval to insert into. This interval must 55 | /// have a section and module it belongs to. 56 | /// \param Offset The offset into the byte interval to insert code at. 57 | /// \param InsnsStr A string representing a sequence of assembly instructions. 58 | void insertInstructions(gtirb::ByteInterval& BI, uint64_t Offset, 59 | const std::string& InsnsStr) const; 60 | 61 | /// \brief Insert stack-stamping instructions into the entrance block of a 62 | /// function. 63 | /// 64 | /// \param FunctionId The UUID of the function we are stack-stamping. Used 65 | /// to calculate hash values. 66 | /// \param Block The code block to insert instructions into. This block must 67 | /// have a byte interval, section, and module it belongs to. 68 | void stampEntranceBlock(const gtirb::UUID& FunctionId, 69 | gtirb::CodeBlock& Block) const; 70 | 71 | /// \brief Insert stack-stamping instructions into the exit block of a 72 | /// function, that is, right before a return statement. 73 | /// 74 | /// \param FunctionId The UUID of the function we are stack-stamping. Used 75 | /// to calculate hash values. 76 | /// \param Block The code block to insert instructions into. This block must 77 | /// have a byte interval, section, and module it belongs to. 78 | void stampExitBlock(const gtirb::UUID& FunctionId, 79 | gtirb::CodeBlock& Block) const; 80 | 81 | /// \brief Stack-stamp a function. 82 | /// 83 | /// \param M The module that contains the function. 84 | /// \param FunctionId The UUID of the function to stack-stamp. 85 | void stampFunction(gtirb::Module& M, const gtirb::UUID& FunctionId) const; 86 | 87 | /// \brief Is this code block an exit block; that is, does it end in a 88 | /// return instruction? 89 | /// 90 | /// \param FunctionID The function to check. 91 | /// \param Block The code block in the given function to check. This block 92 | /// must belong to an IR. 93 | bool isExitBlock(const gtirb::UUID& FunctionId, 94 | const gtirb::CodeBlock& Block) const; 95 | 96 | private: 97 | gtirb::Context& Ctx; 98 | csh Capstone; 99 | ks_engine* Keystone; 100 | }; 101 | 102 | /// \class CapstoneExecution 103 | /// 104 | /// Construct this to get the disassembly for a block. 105 | /// Destroying this automatically frees memory allocated by Capstone. 106 | class CapstoneExecution { 107 | public: 108 | CapstoneExecution(csh Capstone, const gtirb::CodeBlock& Block); 109 | ~CapstoneExecution(); 110 | 111 | /// \brief Get the instructions contained in the disassembled block. 112 | /// This is an array with a length determined by getNumInstructions. 113 | const cs_insn* getInstructions() const { return Instructions; } 114 | 115 | /// \brief Get the number of instructions contained in the disassembled block. 116 | size_t getNumInstructions() const { return NumInstructions; } 117 | 118 | private: 119 | cs_insn* Instructions = nullptr; 120 | size_t NumInstructions = 0; 121 | }; 122 | 123 | /// \class KeystoneExecution 124 | /// 125 | /// Construct this to get the assembly for a string. 126 | /// Destroying this automatically frees memory allocated by Keystone. 127 | class KeystoneExecution { 128 | public: 129 | KeystoneExecution(ks_engine* Keystone, const std::string& Asm, 130 | gtirb::Addr Addr); 131 | ~KeystoneExecution(); 132 | 133 | /// \brief Get the bytes contained in the generated assembly. 134 | /// This is an array with a length determined by getNumBytes. 135 | const unsigned char* getBytes() const { return Bytes; } 136 | 137 | /// \brief Get the number of bytes contained in the generated assembly. 138 | size_t getNumBytes() const { return NumBytes; } 139 | 140 | private: 141 | unsigned char* Bytes = nullptr; 142 | size_t NumBytes = 0; 143 | }; 144 | 145 | /// \brief Stack-stamps all functions in a module. 146 | /// 147 | /// \param Ctx the context that the module was created from. 148 | /// \param M The module to stack-stamp. 149 | void stamp(gtirb::Context& Ctx, gtirb::Module& M); 150 | 151 | /// \brief Registers all auxillary data schema needed by this file. 152 | /// Call this function before any usages of the GTIRB API. 153 | void registerAuxDataSchema(); 154 | 155 | } // namespace gtirb_stack_stamp 156 | 157 | #endif // GTIRB_STACK_STAMP_H 158 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from imp import load_source 3 | import unittest 4 | 5 | pkginfo = load_source("pkginfo.version", "gtirb_stack_stamp/version.py") 6 | __version__ = pkginfo.__version__ 7 | 8 | 9 | def gtirb_stack_stamp_test_suite(): 10 | test_loader = unittest.TestLoader() 11 | test_suite = test_loader.discover("tests", pattern="test_*.py") 12 | return test_suite 13 | 14 | 15 | if __name__ == "__main__": 16 | setup( 17 | name="gtirb-stack-stamp", 18 | version=__version__, 19 | author="Grammatech", 20 | author_email="gtirb@grammatech.com", 21 | description="Apply a stack-stamp transform to GTIRB", 22 | package_data={"gtirb_stack_stamp": ["gtirb_stack_stamp/*.py"]}, 23 | packages=find_packages(), 24 | test_suite="setup.gtirb_stack_stamp_test_suite", 25 | install_requires=["gtirb", "gtirb-rewriting"], 26 | classifiers=["Programming Language :: Python :: 3"], 27 | entry_points={ 28 | "console_scripts": [ 29 | "gtirb-stack-stamp = gtirb_stack_stamp.__main__:main" 30 | ], 31 | "gtirb_rewriting": [ 32 | "stack-stamp = gtirb_stack_stamp.stack_stamp:StampPass", 33 | ], 34 | }, 35 | ) 36 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # specify source files 2 | file(GLOB ${PROJECT_NAME}_H ${CMAKE_SOURCE_DIR}/include/${PROJECT_NAME}/*.hpp) 3 | file(GLOB ${PROJECT_NAME}_SRC *.cpp) 4 | 5 | # specify libraries generated 6 | add_library(${PROJECT_NAME} ${${PROJECT_NAME}_H} ${${PROJECT_NAME}_SRC}) 7 | 8 | # specify header direcotries 9 | target_include_directories( 10 | ${PROJECT_NAME} PUBLIC $ 11 | ) 12 | 13 | # add dependencies 14 | target_link_libraries( 15 | ${PROJECT_NAME} 16 | PUBLIC ${Boost_LIBRARIES} 17 | ${CAPSTONE} 18 | ${KEYSTONE} 19 | gtirb 20 | ) 21 | 22 | # build driver 23 | add_subdirectory(driver) 24 | -------------------------------------------------------------------------------- /src/driver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(BINARY_NAME gtirb-stack-stamp-driver) 2 | 3 | add_executable(${BINARY_NAME} driver.cpp) 4 | 5 | target_link_libraries(${BINARY_NAME} ${PROJECT_NAME}) 6 | 7 | set_target_properties(${BINARY_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) 8 | -------------------------------------------------------------------------------- /src/driver/driver.cpp: -------------------------------------------------------------------------------- 1 | //===- driver.cpp -----------------------------------------------*- C++ -*-===// 2 | // 3 | // Copyright (C) 2020 GrammaTech, Inc. 4 | // 5 | // This code is licensed under the MIT license. See the LICENSE file in the 6 | // project root for license terms. 7 | // 8 | // This project is sponsored by the Office of Naval Research, One Liberty 9 | // Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 10 | // N68335-17-C-0700. The content of the information does not necessarily 11 | // reflect the position or policy of the Government and no official 12 | // endorsement should be inferred. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #include "gtirb_stack_stamp/gtirb_stack_stamp.hpp" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace po = boost::program_options; 24 | 25 | int main(int ArgC, char** ArgV) { 26 | po::options_description Desc("gtirb-stack-stamp - modifies binaries with a " 27 | "simple ROP-protection tool.\n\n" 28 | "Allowed options"); 29 | Desc.add_options()("help,h", "Produce this help message."); 30 | Desc.add_options()("in,i", po::value()->required(), 31 | "Input GTIRB file."); 32 | Desc.add_options()("out,o", po::value()->required(), 33 | "Output GTIRB file."); 34 | 35 | po::positional_options_description PD; 36 | PD.add("in", 1); 37 | PD.add("out", 1); 38 | 39 | po::variables_map VM; 40 | try { 41 | po::store( 42 | po::command_line_parser(ArgC, ArgV).options(Desc).positional(PD).run(), 43 | VM); 44 | if (VM.count("help") != 0) { 45 | std::cerr << Desc << std::endl; 46 | return EXIT_FAILURE; 47 | } 48 | po::notify(VM); 49 | } catch (std::exception& E) { 50 | std::cerr << "error: " << E.what() << ". Try '" << ArgV[0] 51 | << " --help' for more information." << std::endl; 52 | return EXIT_FAILURE; 53 | } 54 | 55 | gtirb_stack_stamp::registerAuxDataSchema(); 56 | gtirb::Context Ctx; 57 | gtirb::IR* Ir; 58 | 59 | boost::filesystem::path InputPath = VM["in"].as(); 60 | std::cerr << "Reading GTIRB file: " << InputPath << std::endl; 61 | std::ifstream InputStream(InputPath.string(), 62 | std::ios::in | std::ios::binary); 63 | if (auto ErrorOrIR = gtirb::IR::load(Ctx, InputStream)) { 64 | Ir = *ErrorOrIR; 65 | } else { 66 | std::cerr << "error: " << ErrorOrIR.getError().message() << std::endl; 67 | return EXIT_FAILURE; 68 | } 69 | 70 | for (auto& M : Ir->modules()) { 71 | std::cerr << "Stack stamping module '" << M.getBinaryPath() << "'..." 72 | << std::endl; 73 | gtirb_stack_stamp::stamp(Ctx, M); 74 | } 75 | 76 | boost::filesystem::path OutputPath = VM["out"].as(); 77 | std::cerr << "Writing to GTIRB file: " << OutputPath << std::endl; 78 | std::ofstream OutputStream(OutputPath.string(), 79 | std::ios::out | std::ios::binary); 80 | Ir->save(OutputStream); 81 | 82 | std::cerr << "Output written successfully. " << std::endl; 83 | return EXIT_SUCCESS; 84 | } 85 | -------------------------------------------------------------------------------- /src/gtirb_stack_stamp.cpp: -------------------------------------------------------------------------------- 1 | //===- gtirb_stack_stamp.cpp ------------------------------------*- C++ -*-===// 2 | // 3 | // Copyright (C) 2020 GrammaTech, Inc. 4 | // 5 | // This code is licensed under the MIT license. See the LICENSE file in the 6 | // project root for license terms. 7 | // 8 | // This project is sponsored by the Office of Naval Research, One Liberty 9 | // Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 10 | // N68335-17-C-0700. The content of the information does not necessarily 11 | // reflect the position or policy of the Government and no official 12 | // endorsement should be inferred. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #include "gtirb_stack_stamp/gtirb_stack_stamp.hpp" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | namespace gtirb { 23 | namespace schema { 24 | 25 | struct CfiDirectives { 26 | static constexpr const char* Name = "cfiDirectives"; 27 | typedef std::map< 28 | gtirb::Offset, 29 | std::vector, gtirb::UUID>>> 30 | Type; 31 | }; 32 | 33 | struct SymbolicExpressionSizes { 34 | static constexpr const char* Name = "symbolicExpressionSizes"; 35 | typedef std::map Type; 36 | }; 37 | 38 | } // namespace schema 39 | } // namespace gtirb 40 | 41 | template 42 | static void modifyBlock(BlockType& Block, uint64_t Offset, uint64_t Size) { 43 | uint64_t BlockOff = Block.getOffset(), BlockSize = Block.getSize(); 44 | 45 | if (BlockOff <= Offset && BlockOff + BlockSize > Offset) { 46 | // Increase in size any blocks that intersect with the new bytes. 47 | Block.setSize(BlockSize + Size); 48 | } else if (BlockOff >= Offset) { 49 | // Move any blocks over that occur after the inserted bytes. 50 | Block.getByteInterval()->addBlock(BlockOff + Size, &Block); 51 | } 52 | } 53 | 54 | static std::string getStampAssembly(const gtirb::UUID& FunctionId) { 55 | // All that matters for these two numbers is that they are the same given the 56 | // same function UUID. Thus, we just take the UUID's 128-bit contents and 57 | // convert it into two 32-bit numbers, selected from the beginning and the end 58 | // of the region (as to avoid the fixed bits in the middle set to "4"). 59 | uint32_t Num1 = *reinterpret_cast(FunctionId.data), 60 | Num2 = *(reinterpret_cast(FunctionId.data) + 3); 61 | 62 | std::ostringstream ss; 63 | ss << "xorl $0x" << std::hex << Num1 << ",(%rsp); xorl $0x" << std::hex 64 | << Num2 << ",4(%rsp);"; 65 | return ss.str(); 66 | } 67 | 68 | gtirb_stack_stamp::CapstoneExecution::CapstoneExecution( 69 | csh Capstone, const gtirb::CodeBlock& Block) { 70 | assert(Block.getByteInterval() && "Block must belong to a byte interval"); 71 | 72 | gtirb::Addr A{0}; 73 | if (auto BA = Block.getAddress()) { 74 | A = *BA; 75 | } 76 | 77 | const uint8_t* RawBytes; 78 | std::vector RawBytesVector; 79 | if (auto* BI = Block.getByteInterval(); 80 | Block.getOffset() + Block.getSize() <= BI->getInitializedSize()) { 81 | RawBytes = Block.rawBytes(); 82 | } else { 83 | std::copy(Block.bytes_begin(), Block.bytes_end(), 84 | std::back_inserter(RawBytesVector)); 85 | RawBytes = RawBytesVector.data(); 86 | } 87 | 88 | NumInstructions = cs_disasm(Capstone, RawBytes, Block.getSize(), 89 | static_cast(A), 0, &Instructions); 90 | } 91 | 92 | gtirb_stack_stamp::CapstoneExecution::~CapstoneExecution() { 93 | if (Instructions) { 94 | cs_free(Instructions, NumInstructions); 95 | } 96 | } 97 | 98 | gtirb_stack_stamp::KeystoneExecution::KeystoneExecution(ks_engine* Keystone, 99 | const std::string& Asm, 100 | gtirb::Addr Addr) { 101 | size_t StatCount; 102 | [[maybe_unused]] int KSRes = 103 | ks_asm(Keystone, Asm.c_str(), static_cast(Addr), &Bytes, 104 | &NumBytes, &StatCount); 105 | assert(KSRes == KS_ERR_OK); 106 | } 107 | 108 | gtirb_stack_stamp::KeystoneExecution::~KeystoneExecution() { 109 | if (Bytes) { 110 | ks_free(Bytes); 111 | } 112 | } 113 | 114 | void gtirb_stack_stamp::StackStamper::insertInstructions( 115 | gtirb::ByteInterval& BI, uint64_t Offset, 116 | const std::string& InsnsStr) const { 117 | assert(BI.getSection() && BI.getSection()->getModule() && 118 | "BI must belong to a section and a module"); 119 | 120 | gtirb::Addr Addr{0}; 121 | if (auto BiAddr = BI.getAddress()) { 122 | Addr = *BiAddr + Offset; 123 | } 124 | 125 | KeystoneExecution Asm{Keystone, InsnsStr, Addr}; 126 | const unsigned char* Bytes = Asm.getBytes(); 127 | size_t BytesLen = Asm.getNumBytes(); 128 | 129 | // Modify contents. 130 | BI.insertBytes(BI.bytes_begin() + Offset, Bytes, 131 | Bytes + BytesLen); 132 | 133 | // Modify blocks. 134 | std::vector CodeBlocks; 135 | for (auto& Block : BI.code_blocks()) { 136 | CodeBlocks.push_back(&Block); 137 | } 138 | for (auto* Block : CodeBlocks) { 139 | modifyBlock(*Block, Offset, BytesLen); 140 | } 141 | 142 | std::vector DataBlocks; 143 | for (auto& Block : BI.data_blocks()) { 144 | DataBlocks.push_back(&Block); 145 | } 146 | for (auto* Block : DataBlocks) { 147 | modifyBlock(*Block, Offset, BytesLen); 148 | } 149 | 150 | // Modify symbolic expressions. 151 | std::vector> SEEs; 152 | for (const auto SEE : BI.symbolic_expressions()) { 153 | if (SEE.getOffset() >= Offset) { 154 | SEEs.emplace_back(SEE.getOffset(), SEE.getSymbolicExpression()); 155 | } 156 | } 157 | for (const auto& SEE : SEEs) { 158 | BI.removeSymbolicExpression(std::get<0>(SEE)); 159 | } 160 | for (const auto& SEE : SEEs) { 161 | BI.addSymbolicExpression(std::get<0>(SEE) + BytesLen, std::get<1>(SEE)); 162 | } 163 | 164 | // Modify any affected aux data. 165 | if (const auto* CFIs = BI.getSection() 166 | ->getModule() 167 | ->getAuxData()) { 168 | gtirb::schema::CfiDirectives::Type NewCFIs; 169 | for (const auto& [BlockOffset, Directive] : *CFIs) { 170 | const auto* CB = dyn_cast_or_null( 171 | gtirb::Node::getByUUID(Ctx, BlockOffset.ElementId)); 172 | if (!CB || CB->getByteInterval() != &BI || 173 | CB->getOffset() + BlockOffset.Displacement < Offset || 174 | CB->getOffset() > Offset || 175 | CB->getOffset() + CB->getSize() <= Offset) { 176 | NewCFIs[BlockOffset] = Directive; 177 | } else { 178 | gtirb::Offset NewOffset = BlockOffset; 179 | NewOffset.Displacement += BytesLen; 180 | NewCFIs[NewOffset] = Directive; 181 | } 182 | } 183 | BI.getSection()->getModule()->addAuxData( 184 | std::move(NewCFIs)); 185 | } 186 | 187 | if (const auto* SymExprSizes = 188 | BI.getSection() 189 | ->getModule() 190 | ->getAuxData()) { 191 | gtirb::schema::SymbolicExpressionSizes::Type NewSES; 192 | for (const auto& [BIOffset, Size] : *SymExprSizes) { 193 | if (BIOffset.ElementId != BI.getUUID() || 194 | BIOffset.Displacement < Offset) { 195 | NewSES[BIOffset] = Size; 196 | } else { 197 | gtirb::Offset NewOffset = BIOffset; 198 | NewOffset.Displacement += BytesLen; 199 | NewSES[NewOffset] = Size; 200 | } 201 | } 202 | BI.getSection() 203 | ->getModule() 204 | ->addAuxData(std::move(NewSES)); 205 | } 206 | } 207 | 208 | void gtirb_stack_stamp::StackStamper::stampEntranceBlock( 209 | const gtirb::UUID& FunctionId, gtirb::CodeBlock& Block) const { 210 | assert(Block.getByteInterval() && "Block must belong to a byte interval"); 211 | insertInstructions(*Block.getByteInterval(), Block.getOffset(), 212 | getStampAssembly(FunctionId)); 213 | } 214 | 215 | void gtirb_stack_stamp::StackStamper::stampExitBlock( 216 | const gtirb::UUID& FunctionId, gtirb::CodeBlock& Block) const { 217 | CapstoneExecution Disasm{Capstone, Block}; 218 | 219 | uint64_t Offset = Block.getOffset(); 220 | if (size_t NumInsn = Disasm.getNumInstructions()) { 221 | for (size_t I = 0; I < NumInsn - 1; I++) { 222 | const cs_insn& Insn = Disasm.getInstructions()[I]; 223 | Offset += Insn.size; 224 | } 225 | insertInstructions(*Block.getByteInterval(), Offset, 226 | getStampAssembly(FunctionId)); 227 | } 228 | } 229 | 230 | bool gtirb_stack_stamp::StackStamper::isExitBlock( 231 | const gtirb::UUID& FunctionId, const gtirb::CodeBlock& Block) const { 232 | assert(Block.getByteInterval() && Block.getByteInterval()->getSection() && 233 | Block.getByteInterval()->getSection()->getModule() && 234 | Block.getByteInterval()->getSection()->getModule()->getIR() && 235 | "Block must belong to an IR."); 236 | 237 | // If the block ends in a return, then it is an exit block. 238 | CapstoneExecution Disasm{Capstone, Block}; 239 | size_t NumInstructions = Disasm.getNumInstructions(); 240 | if (NumInstructions == 0) { 241 | return false; 242 | } 243 | if (Disasm.getInstructions()[NumInstructions - 1].id == X86_INS_RET) { 244 | return true; 245 | } 246 | 247 | // If the block exits this function via a tail call, then it is an exit block. 248 | const auto* M = Block.getByteInterval()->getSection()->getModule(); 249 | if (const auto* AllBlocks = M->getAuxData()) { 250 | if (auto AllBlocksIt = AllBlocks->find(FunctionId); 251 | AllBlocksIt != AllBlocks->end()) { 252 | const auto& BlockIds = AllBlocksIt->second; 253 | const auto& Cfg = M->getIR()->getCFG(); 254 | // A tail call can be seen as a single, unconditional branch edge going 255 | // from inside a function to outside a function. 256 | if (auto Vert = gtirb::getVertex(&Block, Cfg)) { 257 | // If there isn't exactly one outgoing edge, then it is not a tail call. 258 | auto [Begin, End] = boost::out_edges(*Vert, Cfg); 259 | if (Begin == End || std::next(Begin) != End) { 260 | return false; 261 | } 262 | 263 | const auto& Edge = *Begin; 264 | auto* Target = Cfg[boost::target(Edge, Cfg)]; 265 | if (!Target || BlockIds.count(Target->getUUID())) { 266 | // Target of edge is in this function, therefore not a tail call. 267 | return false; 268 | } 269 | 270 | const auto& EdgeLabel = Cfg[Edge]; 271 | if (!EdgeLabel) { 272 | // Has no edge label, therefore we can't tell what this edge is. 273 | return false; 274 | } 275 | 276 | if (std::get(*EdgeLabel) != gtirb::EdgeType::Branch || 277 | std::get(*EdgeLabel) != 278 | gtirb::ConditionalEdge::OnFalse) { 279 | // Not an unconditional branch, therefore not a tail call. 280 | return false; 281 | } 282 | 283 | // This is a tail call! 284 | return true; 285 | } 286 | } 287 | } 288 | // It is not an exit block. 289 | return false; 290 | } 291 | 292 | void gtirb_stack_stamp::StackStamper::stampFunction( 293 | gtirb::Module& M, const gtirb::UUID& FunctionId) const { 294 | // Get the aux data. 295 | const auto* AllBlocks = M.getAuxData(); 296 | const auto* AllEntries = M.getAuxData(); 297 | 298 | if (!AllBlocks || !AllEntries) { 299 | return; 300 | } 301 | 302 | // If there are no entrance or exit blocks, don't add either. 303 | if (AllEntries->at(FunctionId).empty()) { 304 | return; 305 | } 306 | 307 | std::vector ExitBlocks; 308 | for (const auto& BlockId : AllBlocks->at(FunctionId)) { 309 | if (auto* Block = dyn_cast_or_null( 310 | gtirb::Node::getByUUID(Ctx, BlockId)); 311 | Block && isExitBlock(FunctionId, *Block)) { 312 | ExitBlocks.push_back(Block); 313 | } 314 | } 315 | if (ExitBlocks.empty()) { 316 | return; 317 | } 318 | 319 | // Handle entrance blocks. 320 | for (const auto& BlockId : AllEntries->at(FunctionId)) { 321 | if (auto* Block = dyn_cast_or_null( 322 | gtirb::Node::getByUUID(Ctx, BlockId))) { 323 | stampEntranceBlock(FunctionId, *Block); 324 | } 325 | } 326 | 327 | // Handle exit blocks. 328 | for (auto* Block : ExitBlocks) { 329 | stampExitBlock(FunctionId, *Block); 330 | } 331 | } 332 | 333 | void gtirb_stack_stamp::stamp(gtirb::Context& Ctx, gtirb::Module& M) { 334 | gtirb_stack_stamp::StackStamper SS{Ctx}; 335 | if (const auto* Functions = M.getAuxData()) { 336 | for (const auto& [FnId, _] : *Functions) { 337 | (void)_; // This line is necesary so GCC compilers <= 7 don't complain 338 | // about unused variables. 339 | SS.stampFunction(M, FnId); 340 | } 341 | } 342 | } 343 | 344 | void gtirb_stack_stamp::registerAuxDataSchema() { 345 | gtirb::AuxDataContainer::registerAuxDataType(); 346 | gtirb::AuxDataContainer::registerAuxDataType< 347 | gtirb::schema::FunctionEntries>(); 348 | gtirb::AuxDataContainer::registerAuxDataType(); 349 | gtirb::AuxDataContainer::registerAuxDataType< 350 | gtirb::schema::SymbolicExpressionSizes>(); 351 | } 352 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJECT_NAME test-gtirb-stack-stamp) 2 | 3 | include_directories(${GTEST_INCLUDE_DIRS}) 4 | 5 | add_executable(${PROJECT_NAME} test_stack_stamp.cpp) 6 | 7 | target_link_libraries( 8 | ${PROJECT_NAME} gtest gtest_main gtirb-stack-stamp 9 | ) 10 | 11 | add_test(NAME ${PROJECT_NAME} COMMAND $) 12 | -------------------------------------------------------------------------------- /tests/cl-nop-rewrite: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Test that a nop transformation works using the CL API. 4 | # 5 | set -ex 6 | echo 'main(){puts("hello world");}'|gcc -x c - -o /tmp/hello 7 | ddisasm /tmp/hello --ir /tmp/hello.gtirb 8 | sbcl --noinform --disable-debugger \ 9 | --eval '(ql:quickload :gtirb-stack-stamp)' \ 10 | --eval '(in-package :gtirb-stack-stamp)' \ 11 | --eval '(write-gtirb (drop-cfi (read-gtirb "/tmp/hello.gtirb")) 12 | "/tmp/hello.nop.gtirb")' \ 13 | --eval '(uiop:quit)' 14 | gtirb-pprinter /tmp/hello.nop.gtirb --skip-section .eh_frame \ 15 | --asm /tmp/hello.nop.s \ 16 | --binary /tmp/hello.nop 17 | /tmp/hello.nop|grep -q "hello world" 18 | -------------------------------------------------------------------------------- /tests/cl-stack-stamp-rewrite: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Test that a stack stamp transformation works using the CL API. 4 | # 5 | set -ex 6 | echo 'main(){puts("hello world");}'|gcc -x c - -o /tmp/hello 7 | ddisasm /tmp/hello --ir /tmp/hello.gtirb 8 | sbcl --noinform --disable-debugger \ 9 | --eval '(ql:quickload :gtirb-stack-stamp)' \ 10 | --eval '(in-package :gtirb-stack-stamp)' \ 11 | --eval '(write-gtirb (drop-cfi (stack-stamp (read-gtirb "/tmp/hello.gtirb"))) 12 | "/tmp/hello.ss.gtirb")' \ 13 | --eval '(uiop:quit)' 14 | gtirb-layout --in /tmp/hello.ss.gtirb --out /tmp/hello.ss.layout.gtirb 15 | gtirb-pprinter /tmp/hello.ss.layout.gtirb --skip-section .eh_frame \ 16 | --asm /tmp/hello.ss.s \ 17 | --binary /tmp/hello.ss 18 | /tmp/hello.ss|grep -q "hello world" 19 | -------------------------------------------------------------------------------- /tests/factorial.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | unsigned long long factorial(unsigned long long n) { 5 | if (n == 0) { 6 | return 1; 7 | } else { 8 | return n * factorial(n - 1); 9 | } 10 | } 11 | 12 | int main(int argc, char** argv) { 13 | unsigned int n; 14 | if (argc != 2) { 15 | printf("USAGE: factorial [NUM]\n"); 16 | return 1; 17 | } else { 18 | n = atoi(argv[1]); 19 | printf("Factorial(%u)=%llu\n", n, factorial(n)); 20 | return 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/hello.v1.gtirb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrammaTech/gtirb-stack-stamp/acf03c738ca040cd7e9a370b175768c83360f77a/tests/hello.v1.gtirb -------------------------------------------------------------------------------- /tests/stack-overwrite.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void B() asm("function_B"); 4 | 5 | void A(int flag) { 6 | printf("Function A\n"); 7 | fflush(NULL); 8 | if (flag) { 9 | ((void**)__builtin_frame_address(0))[1] = &B; 10 | } 11 | } 12 | 13 | void B() { 14 | printf("Function B\n"); 15 | fflush(NULL); 16 | } 17 | 18 | int main(int argc, char** argv) { A(argc > 1 ? 0 : 1); } 19 | -------------------------------------------------------------------------------- /tests/test_stack_stamp.cpp: -------------------------------------------------------------------------------- 1 | //===- test_stack_stamp.cpp -------------------------------------*- C++ -*-===// 2 | // 3 | // Copyright (C) 2020 GrammaTech, Inc. 4 | // 5 | // This code is licensed under the MIT license. See the LICENSE file in the 6 | // project root for license terms. 7 | // 8 | // This project is sponsored by the Office of Naval Research, One Liberty 9 | // Center, 875 N. Randolph Street, Arlington, VA 22203 under contract # 10 | // N68335-17-C-0700. The content of the information does not necessarily 11 | // reflect the position or policy of the Government and no official 12 | // endorsement should be inferred. 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | #include "gtirb_stack_stamp/gtirb_stack_stamp.hpp" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | static bool alreadySetUp = false; 23 | 24 | class GtirbStackStampFixture : public ::testing::Test { 25 | protected: 26 | virtual void SetUp() override { 27 | if (!alreadySetUp) { 28 | alreadySetUp = true; 29 | gtirb_stack_stamp::registerAuxDataSchema(); 30 | } 31 | } 32 | }; 33 | 34 | TEST_F(GtirbStackStampFixture, TestInsertInstructions) { 35 | ks_engine* Keystone; 36 | ks_err KSRet = 37 | ks_open(KS_ARCH_X86, KS_MODE_LITTLE_ENDIAN | KS_MODE_64, &Keystone); 38 | ASSERT_EQ(KSRet, KS_ERR_OK); 39 | ks_option(Keystone, KS_OPT_SYNTAX, KS_OPT_SYNTAX_ATT); 40 | 41 | std::string InstructionsToInsert = "ret;"; 42 | 43 | unsigned char* Bytes; 44 | size_t BytesLen, StatCount; 45 | [[maybe_unused]] int KSRes = ks_asm(Keystone, InstructionsToInsert.c_str(), 0, 46 | &Bytes, &BytesLen, &StatCount); 47 | ASSERT_EQ(KSRes, KS_ERR_OK); 48 | 49 | std::string BIContents = "\x01\x02\x03\x04\x05\x06\x07\x08"; 50 | gtirb::Context Ctx; 51 | auto* IR = gtirb::IR::Create(Ctx); 52 | auto* M = IR->addModule(Ctx, "test"); 53 | auto* S = M->addSection(Ctx, ".text"); 54 | auto* BI = S->addByteInterval(Ctx, BIContents.begin(), BIContents.end()); 55 | auto* B1 = BI->addBlock(Ctx, 0, 3); 56 | auto* B2 = BI->addBlock(Ctx, 2, 4); 57 | auto* B3 = BI->addBlock(Ctx, 4, 2); 58 | auto* B4 = BI->addBlock(Ctx, 6, 1); 59 | BI->addSymbolicExpression(2, 0, nullptr); 60 | BI->addSymbolicExpression(4, 0, nullptr); 61 | BI->addSymbolicExpression(6, 0, nullptr); 62 | 63 | gtirb_stack_stamp::StackStamper SS{Ctx}; 64 | SS.insertInstructions(*BI, 4, InstructionsToInsert); 65 | 66 | EXPECT_EQ(std::string(BI->bytes_begin(), BI->bytes_end()), 67 | BIContents.substr(0, 4) + std::string(Bytes, Bytes + BytesLen) + 68 | BIContents.substr(4, 4)); 69 | EXPECT_EQ(std::distance(BI->blocks_begin(), BI->blocks_end()), 4); 70 | 71 | EXPECT_EQ(B1->getOffset(), 0); 72 | EXPECT_EQ(B1->getSize(), 3); 73 | EXPECT_EQ(B2->getOffset(), 2); 74 | EXPECT_EQ(B2->getSize(), 4 + BytesLen); 75 | EXPECT_EQ(B3->getOffset(), 4); 76 | EXPECT_EQ(B3->getSize(), 2 + BytesLen); 77 | EXPECT_EQ(B4->getOffset(), 6 + BytesLen); 78 | EXPECT_EQ(B4->getSize(), 1); 79 | 80 | EXPECT_EQ(std::distance(BI->symbolic_expressions_begin(), 81 | BI->symbolic_expressions_end()), 82 | 3); 83 | std::set Offsets{{2, 4 + BytesLen, 6 + BytesLen}}; 84 | const auto Pred = [&Offsets](uint64_t Off) { return Offsets.count(Off); }; 85 | 86 | for (auto SEE : BI->symbolic_expressions()) { 87 | EXPECT_PRED1(Pred, SEE.getOffset()); 88 | Offsets.erase(SEE.getOffset()); 89 | } 90 | } 91 | 92 | TEST_F(GtirbStackStampFixture, TestStackStamp) { 93 | boost::filesystem::current_path("tests"); 94 | std::remove("factorial.gtirb.stamp"); 95 | std::remove("factorial.stamp"); 96 | 97 | ASSERT_EQ(std::system("make factorial -B"), EXIT_SUCCESS); 98 | ASSERT_EQ(std::system("ddisasm factorial --ir factorial.gtirb"), 99 | EXIT_SUCCESS); 100 | 101 | gtirb::Context Ctx; 102 | gtirb::IR* Ir; 103 | { 104 | std::ifstream File{"factorial.gtirb"}; 105 | auto ErrorOrIr = gtirb::IR::load(Ctx, File); 106 | ASSERT_TRUE(ErrorOrIr); 107 | Ir = *ErrorOrIr; 108 | } 109 | 110 | for (auto& M : Ir->modules()) { 111 | gtirb_stack_stamp::stamp(Ctx, M); 112 | } 113 | 114 | { 115 | std::ofstream File{"factorial.gtirb.stamp"}; 116 | Ir->save(File); 117 | ASSERT_TRUE(File); 118 | } 119 | 120 | ASSERT_EQ(std::system("gtirb-pprinter factorial.gtirb.stamp --binary " 121 | "factorial.stamp"), 122 | EXIT_SUCCESS); 123 | ASSERT_TRUE(boost::filesystem::exists("factorial.stamp")); 124 | 125 | auto* TempFile = std::tmpnam(nullptr); 126 | int ReturnCode = 127 | std::system(("./factorial.stamp 10 > " + std::string{TempFile}).c_str()); 128 | std::string Output; 129 | { 130 | std::ifstream File{TempFile}; 131 | std::noskipws(File); 132 | File >> Output; 133 | } 134 | std::remove(TempFile); 135 | 136 | ASSERT_EQ(ReturnCode, EXIT_SUCCESS); 137 | EXPECT_EQ(Output, "Factorial(10)=3628800"); 138 | } 139 | -------------------------------------------------------------------------------- /tests/test_stack_stamp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import subprocess 3 | import os 4 | import platform 5 | import contextlib 6 | import tempfile 7 | import shutil 8 | 9 | # You can use the KEEP_TEMP_FILES environment variable to tell the tests not to 10 | # clean up after themselves, which can help with debugging. 11 | KEEP_TEMP_FILES = os.getenv("KEEP_TEMP_FILES", "") != "" 12 | 13 | 14 | class StackStampTest(unittest.TestCase): 15 | @classmethod 16 | def setUpClass(cls): 17 | os.chdir("tests") 18 | 19 | def setUp(self): 20 | if platform.system() == "Linux": 21 | self._ddisasm = "ddisasm" 22 | elif platform.system() == "Windows": 23 | self._ddisasm = "ddisasm.exe" 24 | 25 | @contextlib.contextmanager 26 | def do_stamp(self, source): 27 | base = os.path.basename(source) 28 | binary = os.path.splitext(base)[0] 29 | gtirb = f"{binary}.gtirb" 30 | stamped_gtirb = f"{binary}.gtirb.stamp" 31 | stamped = f"{binary}.stamp" 32 | 33 | class TempDir: 34 | def __init__(self, prefix): 35 | self.prefix = prefix 36 | 37 | def __enter__(self): 38 | self.dir = tempfile.mkdtemp( 39 | prefix=self.prefix + "-tmp-", dir=os.getcwd() 40 | ) 41 | return self.dir 42 | 43 | def __exit__(self, type, value, traceback): 44 | shutil.rmtree(self.dir) 45 | 46 | with contextlib.ExitStack() as es: 47 | tempdir = es.enter_context(TempDir(binary)) 48 | if KEEP_TEMP_FILES: 49 | es.pop_all() 50 | msg = "KEEP_TEMP_FILES is preserving test directory: " 51 | es.callback(lambda: print(msg, tempdir)) 52 | 53 | if platform.system() == "Linux": 54 | shutil.copy(source, os.path.join(tempdir, source)) 55 | args = ["make", binary, "-B"] 56 | ec = subprocess.call(args, cwd=tempdir) 57 | self.assertEqual(ec, 0) 58 | elif platform.system() == "Windows": 59 | shutil.copy(binary, os.path.join(tempdir, binary)) 60 | 61 | args = [self._ddisasm, binary, "--ir", gtirb] 62 | ec = subprocess.call(args, cwd=tempdir) 63 | self.assertEqual(ec, 0) 64 | 65 | if platform.system() == "Linux": 66 | python = "python3" 67 | elif platform.system() == "Windows": 68 | python = "python" 69 | 70 | args = [ 71 | python, 72 | "-m", 73 | "gtirb_stack_stamp", 74 | gtirb, 75 | "--outfile", 76 | stamped_gtirb, 77 | ] 78 | # On windows, gtirb-pprinter can't binary print an ELF file, so 79 | # only do that part of the test on linux 80 | if platform.system() == "Linux": 81 | args += ["--rebuild", stamped] 82 | ec = subprocess.call(args, cwd=tempdir) 83 | self.assertEqual(ec, 0) 84 | yield (os.path.join(tempdir, file) for file in (binary, stamped)) 85 | 86 | def test_1_invocation(self): 87 | with self.do_stamp("factorial.c") as (binary, stamped): 88 | if platform.system() == "Linux": 89 | args = [stamped, "10"] 90 | output = subprocess.check_output(args) 91 | self.assertEqual(output, b"Factorial(10)=3628800\n") 92 | return True 93 | 94 | @unittest.skipUnless( 95 | platform.system() == "Linux", 96 | ("The test binary can't run on Windows."), 97 | ) 98 | def test_stamp(self): 99 | with self.do_stamp("stack-overwrite.c") as (binary, stamped): 100 | # Check that the stamped program's behavior is unchanged when we 101 | # don't overwrite the return address 102 | self.assertEqual( 103 | subprocess.check_output([binary, "don't overwrite"]), 104 | subprocess.check_output([stamped, "don't overwrite"]), 105 | ) 106 | # Check that stamping blocks return address overwrites 107 | unstamped_output = subprocess.run( 108 | [binary], stdout=subprocess.PIPE 109 | ).stdout 110 | stamped_output = subprocess.run( 111 | [stamped], stdout=subprocess.PIPE 112 | ).stdout 113 | self.assertTrue(unstamped_output.startswith(stamped_output)) 114 | self.assertNotEqual(unstamped_output, stamped_output) 115 | --------------------------------------------------------------------------------