├── .ci └── ci.yaml ├── .dockerignore ├── .gitignore ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── README_Windows.md ├── build.sh ├── clippy.toml ├── examples ├── .gitignore ├── README.md ├── c-ares │ ├── custom.rule │ └── hopper.config ├── cjson │ └── hopper.config ├── lcms2 │ ├── custom.rule │ └── hopper.config ├── libaom │ ├── aom_all.h │ ├── av1_dev_fuzzer.dict │ ├── custom.rule │ └── hopper.config ├── libmagic │ ├── custom.rule │ └── hopper.config ├── libpcap │ ├── custom.rule │ ├── hopper.config │ └── seeds │ │ ├── 1.txt │ │ ├── 2.txt │ │ ├── 3.txt │ │ ├── 4.txt │ │ ├── 5.txt │ │ ├── 6.txt │ │ ├── 7.txt │ │ └── small_capture.pcap ├── libpng │ ├── custom.rule │ └── hopper.config ├── libvpx │ ├── custom.rule │ ├── hopper.config │ ├── vpx.dict │ └── vpx_all.h ├── pcre2 │ ├── custom.rule │ └── hopper.config ├── re2 │ ├── custom.rule │ └── hopper.config ├── sqlite3 │ ├── custom.rule │ └── hopper.config └── zlib │ ├── custom.rule │ └── hopper.config ├── hopper ├── hopper-compiler ├── Cargo.toml └── src │ ├── binary_info.rs │ ├── cargo.rs │ ├── check.rs │ ├── config.rs │ ├── dwarf │ ├── analyzer.rs │ ├── arg_type.rs │ ├── function.rs │ ├── mod.rs │ ├── position.rs │ ├── program.rs │ └── variable.rs │ ├── main.rs │ └── patch │ ├── e9.rs │ ├── mod.rs │ └── patchelf.rs ├── hopper-core ├── Cargo.toml ├── build.rs └── src │ ├── config.rs │ ├── depot │ ├── io.rs │ ├── mod.rs │ ├── priority.rs │ └── select.rs │ ├── error.rs │ ├── execute │ ├── executor.rs │ ├── forkcli.rs │ ├── forklib_win.rs │ ├── forksrv.rs │ ├── io_utils.rs │ ├── limit.rs │ ├── mod.rs │ └── signal.rs │ ├── feedback │ ├── branches.rs │ ├── cmp.rs │ ├── globl │ │ ├── e9-globl-win.S │ │ ├── e9-globl.S │ │ ├── llvm-globl.c │ │ ├── mod.rs │ │ └── variadic.c │ ├── hook.rs │ ├── instr.rs │ ├── mem.rs │ ├── mod.rs │ ├── observer.rs │ ├── ops.rs │ ├── path.rs │ ├── res.rs │ ├── review.rs │ ├── sanitize.rs │ ├── shm.rs │ └── shm_win.rs │ ├── fuzz │ ├── check.rs │ ├── constraints │ │ ├── constraint.rs │ │ ├── context.rs │ │ ├── entry.rs │ │ ├── internal.rule │ │ ├── literal.rs │ │ ├── mod.rs │ │ ├── parse.rs │ │ ├── ret.rs │ │ └── role.rs │ ├── det.rs │ ├── effective.rs │ ├── find.rs │ ├── flag.rs │ ├── generate.rs │ ├── infer │ │ ├── array.rs │ │ ├── cast.rs │ │ ├── context.rs │ │ ├── file.rs │ │ ├── length.rs │ │ ├── mod.rs │ │ ├── non_null.rs │ │ ├── opaque.rs │ │ ├── res.rs │ │ └── sigfpe.rs │ ├── minimize.rs │ ├── mod.rs │ ├── mutate.rs │ ├── object │ │ ├── bitfield.rs │ │ ├── buf.rs │ │ ├── corpus.rs │ │ ├── fn_pointer.rs │ │ ├── mod.rs │ │ ├── number.rs │ │ ├── option.rs │ │ ├── pointer.rs │ │ ├── seq.rs │ │ └── void.rs │ ├── operator.rs │ ├── pcg.rs │ ├── refine.rs │ ├── rng.rs │ ├── stmt │ │ ├── assert.rs │ │ ├── call.rs │ │ ├── file.rs │ │ ├── load.rs │ │ ├── mod.rs │ │ └── update.rs │ └── weight.rs │ ├── fuzzer.rs │ ├── lib.rs │ ├── runtime │ ├── func.rs │ ├── gadgets.rs │ ├── loc.rs │ ├── mod.rs │ ├── object │ │ ├── bitfield.rs │ │ ├── builder.rs │ │ ├── canary.rs │ │ ├── fn_pointer.rs │ │ ├── layout.rs │ │ ├── mod.rs │ │ ├── number.rs │ │ ├── option.rs │ │ ├── pointer.rs │ │ ├── seq.rs │ │ ├── state.rs │ │ └── void.rs │ ├── program.rs │ ├── serde.rs │ ├── stmt │ │ ├── assert.rs │ │ ├── call.rs │ │ ├── file.rs │ │ ├── index.rs │ │ ├── load.rs │ │ ├── mod.rs │ │ └── update.rs │ └── translate.rs │ ├── slices.rs │ ├── test.rs │ └── utils.rs ├── hopper-derive-impl ├── Cargo.toml └── src │ ├── field.rs │ ├── folder.rs │ ├── format.rs │ ├── func_hook.rs │ ├── func_sig.rs │ ├── lib.rs │ ├── object.rs │ ├── serde.rs │ └── visitor.rs ├── hopper-derive ├── Cargo.toml └── src │ └── lib.rs ├── hopper-harness ├── Cargo.toml ├── build.rs └── src │ ├── bin │ ├── hopper-bench.rs │ ├── hopper-fuzzer.rs │ ├── hopper-generator.rs │ ├── hopper-harness.rs │ ├── hopper-sanitizer.rs │ ├── hopper-slice.rs │ └── hopper-translate.rs │ └── lib.rs ├── hopper-instrument ├── e9-mode │ ├── README.md │ ├── build.sh │ ├── config.h │ ├── e9patch.diff │ ├── hopper-e9-plugin.cpp │ ├── hopper-e9-rt.c │ ├── hopper-instr-plugin.cpp │ ├── rt-linux.c │ └── rt-win.c └── llvm-mode │ ├── .gitignore │ ├── CMakeLists.txt │ ├── Readme.md │ ├── hopper-clang.c │ ├── hopper-pass.cc │ ├── hopper-pre-pass.cc │ └── include │ ├── alloc_inl.h │ ├── config.h │ └── debug.h ├── rustfmt.toml ├── testsuite ├── README.md ├── assert │ ├── assert.c │ ├── assert.h │ └── custom.rule ├── basic │ ├── basic.c │ ├── basic.h │ └── custom.rule ├── buf │ ├── buf.c │ ├── buf.h │ ├── custom.rule │ ├── dict │ └── seeds │ │ ├── @buf │ │ └── buf.txt │ │ └── seed1.txt ├── common.h ├── constraint │ ├── constraint.c │ ├── constraint.h │ └── custom.rule ├── pointer │ ├── custom.rule │ ├── pointer.c │ └── pointer.h ├── strcmp │ ├── custom.rule │ ├── dict │ ├── strcmp.c │ └── strcmp.h └── test.sh └── tools ├── core_affinity.sh └── style.sh /.ci/ci.yaml: -------------------------------------------------------------------------------- 1 | version: v2.0 2 | 3 | on: 4 | push: ["*"] 5 | mr: ["*"] 6 | 7 | stages: 8 | - name: build and test stage 9 | jobs: 10 | job1: 11 | name: build and test job 12 | runs-on: 13 | pool-name: docker 14 | container: 15 | image: mirrors.tencent.com/rust-ci/rust:latest 16 | steps: 17 | - checkout: self 18 | - run: | 19 | cargo build 20 | cargo test 21 | name: cargo build and test 22 | - run: | 23 | rustup component add clippy 24 | name: install clippy 25 | - run: | 26 | cargo clippy --all-targets -- -D warnings 27 | name: run clippy 28 | 29 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /install 2 | /target 3 | /.vscode 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .vscode/ 4 | *.so 5 | *.dSYM 6 | *.dylib 7 | output/ 8 | output_cov/ 9 | install/ 10 | core.* 11 | *.data 12 | *.log 13 | build/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | 'hopper-core', 5 | 'hopper-derive-impl', 6 | 'hopper-derive', 7 | 'hopper-compiler', 8 | 'hopper-harness', 9 | ] 10 | 11 | # [patch.crates-io] 12 | # bindgen = { path = "../rust-bindgen" } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | ENV HOPPER_BIN=/hopper/hopper \ 4 | RUSTUP_HOME=/usr/local/rustup \ 5 | CARGO_HOME=/usr/local/cargo \ 6 | PATH=/hopper:/usr/local/cargo/bin:/root/.cargo/bin:$PATH \ 7 | DEBIAN_FRONTEND=noninteractive 8 | 9 | # RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 10 | # RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 11 | 12 | RUN apt-get update \ 13 | && apt-get -y upgrade \ 14 | && apt-get -y install build-essential wget curl cmake git unzip xxd protobuf-compiler libprotobuf-dev \ 15 | && apt-get -y install llvm llvm-dev libclang-dev clang \ 16 | && apt-get clean 17 | 18 | # ENV RUSTUP_DIST_SERVER="https://mirrors.ustc.edu.cn/rust-static" 19 | # ENV RUSTUP_UPDATE_ROOT="https://mirrors.ustc.edu.cn/rust-static/rustup" 20 | 21 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable 22 | 23 | # RUN echo '[source.crates-io]' > ${CARGO_HOME}/config && \ 24 | # echo "replace-with = 'tencent'" >> ${CARGO_HOME}/config && \ 25 | # echo '[source.tencent]' >> ${CARGO_HOME}/config && \ 26 | # echo 'registry = "http://mirrors.tencent.com/rust/index"' >> ${CARGO_HOME}/config 27 | 28 | RUN mkdir -p /hopper 29 | COPY . /hopper 30 | WORKDIR /hopper 31 | 32 | RUN ./build.sh 33 | 34 | # RUN mkdir /llvm 35 | # ENV PATH=/llvm/bin:$PATH 36 | # ENV LD_LIBRARY_PATH=/llvm/lib:$LD_LIBRARY_PATH 37 | 38 | RUN mkdir /fuzz_lib 39 | RUN mkdir /fuzz 40 | WORKDIR /fuzz 41 | -------------------------------------------------------------------------------- /README_Windows.md: -------------------------------------------------------------------------------- 1 | # Use Hopper in Windows 2 | ATTN: The Windows feature is no longer being maintained. 3 | ATTN: Hopper was tested in Windows10 **19044.1645**, and **fork() will fail after 19044.1646**. 4 | 5 | Since e9patch can only works on Linux environment, Hopper should need both linux and Windows environment. 6 | Hopper uses e9patch to instrument libraries in Linux, and then copy the patched library to Windows environment. 7 | 8 | ## On Linux side 9 | - Build hopper 10 | ``` 11 | ./build.sh 12 | ``` 13 | 14 | - Compile libraries 15 | ``` 16 | ./hopper --header ./cJSON.h --library ./libcjson.dll 17 | ``` 18 | 19 | ## On windows side 20 | - Build hopper (toolchain: stable-x86_64-pc-windows-gnu) 21 | ``` 22 | cargo build --release 23 | ``` 24 | 25 | - Compile libraries 26 | ```sh 27 | # ./libcjson.dll is copied from linux side 28 | /path-to-release/hopper-compiler.exe --header ./cJSON.h --library ./libcjson.dll --output output 29 | ``` 30 | 31 | ## Fuzz library with Hopper 32 | ```sh 33 | ./path-to-output/bin/hopper-fuzzer.exe 34 | ``` 35 | 36 | ### Envionment variables 37 | - `HOPPER_TASK`: task name. default: `libname_fuzz`. 38 | - `HOPPER_E9_BLACK_LIST`: functions should not be patched. e.g `export HOPPER_E9_BLACK_LIST=xx` 39 | - `HOPPER_USE_THREAD`: `0` use `fork_loop`, `1` use `thread_loop`. 40 | - `HOPPER_USE_THREAD_NUM`: Child process will exit after executing `HOPPER_USE_THREAD_NUM` threads. The higher the number, the faster the speed and the worse the stability. default: `100`. 41 | 42 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | realpath() { 4 | [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" 5 | } 6 | BIN_PATH=$(realpath "$0") 7 | ROOT_DIR=$(dirname $BIN_PATH) 8 | INSTALL_DIR=$ROOT_DIR/install 9 | PATCHELF_VERSION=0.14.5 10 | 11 | source ${ROOT_DIR}/tools/style.sh 12 | 13 | mkdir -p $INSTALL_DIR 14 | if [[ "$OSTYPE" == "linux-gnu"* ]]; then 15 | info "start install e9path and hopper's e9 plugins ..." 16 | cd hopper-instrument/e9-mode 17 | PREFIX=$INSTALL_DIR ./build.sh 18 | cd ../../ 19 | 20 | if [ ! -x $INSTALL_DIR/patchelf ]; then 21 | info "download patchelf ..." 22 | cd $INSTALL_DIR 23 | mkdir -p tmp 24 | cd tmp 25 | wget https://github.com/NixOS/patchelf/releases/download/${PATCHELF_VERSION}/patchelf-${PATCHELF_VERSION}-x86_64.tar.gz 26 | tar -xvf patchelf-${PATCHELF_VERSION}-x86_64.tar.gz 27 | cp bin/patchelf ../. 28 | fi 29 | fi 30 | 31 | info "start install hopper's llvm plugins ..." 32 | cd $INSTALL_DIR 33 | rm -rf llvm_build 34 | mkdir llvm_build && cd llvm_build 35 | cmake -DHOPPER_BIN_DIR=$INSTALL_DIR $ROOT_DIR/hopper-instrument/llvm-mode 36 | make 37 | make install 38 | 39 | # BUILD_TYPE=${BUILD_TYPE:-debug} 40 | BUILD_TYPE=${BUILD_TYPE:-release} 41 | 42 | info "start build and install hopper fuzzer ..." 43 | cd $ROOT_DIR 44 | if [[ "$BUILD_TYPE" == "debug" ]]; then 45 | cargo build 46 | else 47 | cargo build --release 48 | fi 49 | 50 | ln -sf $ROOT_DIR/target/$BUILD_TYPE/hopper-compiler $INSTALL_DIR/ 51 | 52 | info "build and install hopper done!" 53 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | too-many-arguments-threshold=10 -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | *.h 3 | *.so 4 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples for Hopper 2 | 3 | We have set configurations in `hopper.config` files for the examples. You can install the libraries and change the path for the header and shared libraries in your environment. 4 | 5 | If `hopper` detects the configuration file in current directory, it loads the setting. Therefore, you can run `hopper` directly by following commands. 6 | 7 | ``` sh 8 | # compile fuzzer for library 9 | hopper compile 10 | # run fuzzer 11 | hopper fuzz 12 | # translate specific file 13 | hopper translate 14 | # clean files generated by fuzzing to restart from scratch 15 | hopper clean 16 | # sanitize crashes 17 | hopper sanitize 18 | ``` -------------------------------------------------------------------------------- /examples/c-ares/custom.rule: -------------------------------------------------------------------------------- 1 | func_include ares_* 2 | 3 | // func_exclude ares_expand_name,ares_getaddrinfo,ares_gethostbyaddr 4 | // func_exclude ares_fds,ares_dup 5 | // func_exclude ares_library_init_mem -------------------------------------------------------------------------------- /examples/c-ares/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | # Full path for header file 4 | TEST_HEADER=/data/workspace/fuzzing_bench/c-ares/src/include/ares.h 5 | 6 | # Full path to shared library 7 | TEST_LIBRARY=/data/workspace/fuzzing_bench/c-ares/build/hopper_build/install/lib/libcares.so 8 | 9 | # Output directory 10 | OUT_DIR=output 11 | 12 | # Set map size 13 | HOPPER_MAP_SIZE_POW2=18 14 | 15 | # Set the header include path 16 | HOPPER_INCLUDE_SEARCH_PATH=/data/workspace/fuzzing_bench/c-ares/src/include/ 17 | 18 | # Disable generate calls that failed to be invoked 19 | DISABLE_GEN_FAIL=1 20 | 21 | # Set seeds 22 | HOPPER_SEED_DIR=/data/workspace/fuzzing_bench/c-ares/src/test/fuzzinput 23 | 24 | -------------------------------------------------------------------------------- /examples/cjson/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | # Full path for header file 4 | TEST_HEADER=cJSON.h 5 | 6 | # Full path to shared library 7 | TEST_LIBRARY=libcjson.so 8 | 9 | # Output directory 10 | OUT_DIR=output 11 | 12 | # API functions that you are interesting 13 | FUNC_PATTERN=cJSON_* 14 | -------------------------------------------------------------------------------- /examples/lcms2/custom.rule: -------------------------------------------------------------------------------- 1 | func_include cms* 2 | // func_exclude cmsCreateBCHSWabstractProfile,cmsCreateBCHSWabstractProfileTHR 3 | // func_exclude cmsSetLogErrorHandler,cmsSetLogErrorHandlerTHR 4 | // func_exclude cmsPlugin,cmsPluginTHR,cmsUnregisterPlugins,cmsUnregisterPluginsTHR 5 | // func_include cmsOpenProfileFromMem,cmsCreate_sRGBProfile,cmsCreateTransform,cmsDoTransform 6 | 7 | // type _cmsContext_struct* = $null 8 | // func cmsCreateContext[$0] = $null 9 | // func cmsCreateContext[$1] = $null 10 | 11 | // alias hModel <- cmsCIECAM02Init[$ret],cmsCIECAM02Done[$0],cmsCIECAM02Forward[$0],cmsCIECAM02Reverse[$0] 12 | // alias hDict <- cmsDictAlloc[$ret],cmsDictFree[$0],cmsDictDup[$0],cmsDictAddEntry[$0],cmsDictGetEntryList[$0] 13 | // alias ITHandle <- cmsIT8LoadFromMem[$ret],cmsIT8Free[$0] 14 | // alias hGBD <- cmsGBDAlloc[$ret],cmsGBDFree[$0],cmsGDBAddPoint[$0],cmsGDBCompute[$0],cmsGDBCheckPoint[$0] 15 | -------------------------------------------------------------------------------- /examples/lcms2/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | LIB_DIR=/data/workspace/fuzzing_bench/lcms/build/hopper_build 4 | 5 | # Full path for header file 6 | TEST_HEADER=${LIB_DIR}/include/lcms2.h 7 | 8 | # Full path to shared library 9 | TEST_LIBRARY=${LIB_DIR}/src/.libs/liblcms2.so 10 | 11 | # Output directory 12 | OUT_DIR=output 13 | 14 | # Custom rule for invoking API functions 15 | CUSTOM_RULES=custom.rule 16 | 17 | # Set map size for branch counting 18 | HOPPER_MAP_SIZE_POW2=18 19 | 20 | # Disable API-sensitive 21 | HOPPER_API_INSENSITIVE_COV=1 22 | 23 | # Disable fast loop for execution 24 | HOPPER_FAST_EXECUTE_LOOP=1 25 | 26 | # Set seeds for hopper 27 | # HOPPER_SEED_DIR=seeds 28 | -------------------------------------------------------------------------------- /examples/libaom/aom_all.h: -------------------------------------------------------------------------------- 1 | #include "aom/aom_decoder.h" 2 | //#include "aom/aom_encoder.h" 3 | //#include "aom/aomcx.h" 4 | #include "aom/aomdx.h" 5 | //#include "aom_ports/mem_ops.h" 6 | // #include "common/tools_common.h" 7 | // #include "common/video_writer.h" 8 | // #include "common/video_reader.h" 9 | // #include "config/aom_config.h" 10 | 11 | -------------------------------------------------------------------------------- /examples/libaom/av1_dev_fuzzer.dict: -------------------------------------------------------------------------------- 1 | # from aom's source code. 2 | 3 | # IVF Signature + version (bytes 0-5) 4 | kw1="DKIF\x00\x00" 5 | 6 | # AV1 codec fourCC (bytes 8-11) 7 | kw2="AV01" 8 | 9 | -------------------------------------------------------------------------------- /examples/libaom/custom.rule: -------------------------------------------------------------------------------- 1 | func_include aom_* 2 | // func_include aom_codec_decode,aom_codec_dec_init_ver,aom_codec_av1_dx 3 | // func_key aom_codec_decode,aom_codec_get_frame 4 | func_exclude aom_img_metadata_free,aom_img_free,aom_codec_control,aom_img_alloc,aom_img_alloc_with_border 5 | -------------------------------------------------------------------------------- /examples/libaom/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | # Full path for header file 4 | TEST_HEADER=aom_all.h 5 | 6 | # Full path to shared library 7 | TEST_LIBRARY=/data/workspace/fuzzing_bench/libaom/build/hopper_build/libaom.so 8 | 9 | # Output directory 10 | OUT_DIR=output 11 | 12 | # Set map size for branch counting 13 | HOPPER_MAP_SIZE_POW2=18 14 | 15 | # Set dictionary for bytes 16 | HOPPER_DICT=./av1_dec_fuzzer.dict 17 | 18 | # Set the header include path 19 | HOPPER_INCLUDE_SEARCH_PATH=/data/workspace/fuzzing_bench/libaom/src 20 | -------------------------------------------------------------------------------- /examples/libmagic/custom.rule: -------------------------------------------------------------------------------- 1 | target_include magic_* 2 | # You need to set it the correct path 3 | func magic_load[$1] = "/data/workspace/fuzzing_bench/libmagic/build/hopper_build/magic/magic.mgc" 4 | -------------------------------------------------------------------------------- /examples/libmagic/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | # Full path for header file 4 | TEST_HEADER=/data/workspace/fuzzing_bench/libmagic/build/hopper_build/src/magic.h 5 | 6 | # Full path to shared library 7 | TEST_LIBRARY=/data/workspace/fuzzing_bench/libmagic/build/hopper_build/src/.libs/libmagic.so 8 | 9 | # Output directory 10 | OUT_DIR=output 11 | 12 | # set map size for branch counting 13 | HOPPER_MAP_SIZE_POW2=18 14 | 15 | # Set seeds 16 | # HOPPER_SEED_DIR=./seeds 17 | -------------------------------------------------------------------------------- /examples/libpcap/custom.rule: -------------------------------------------------------------------------------- 1 | func_include pcap_* 2 | # func_key pcap_compile 3 | # PCAP_DEPRECATED 4 | func_exclude pcap_freealldevs,pcap_geterr 5 | func_exclude pcap_dump_file,pcap_file,pcap_dump 6 | func_exclude pcap_open_live,pcap_activate 7 | 8 | # type pcap_pkthdr = $opaque 9 | # func pcap_open[$0] = $read_file 10 | # func pcap_dump_open_append[$1] = $write_file 11 | -------------------------------------------------------------------------------- /examples/libpcap/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | # Full path for header file 4 | TEST_HEADER=/data/workspace/fuzzing_bench/libpcap/src/pcap/pcap.h 5 | 6 | # Full path to shared library 7 | TEST_LIBRARY=/data/workspace/fuzzing_bench/libpcap/build/hopper_build/libpcap.so.1.10.1 8 | 9 | # Output directory 10 | OUT_DIR=output 11 | 12 | # Custom rule for invoking API functions 13 | CUSTOM_RULES=custom.rule 14 | 15 | # Set map size 16 | HOPPER_MAP_SIZE_POW2=18 17 | 18 | # Do not use API-senitive 19 | #HOPPER_API_INSENSITIVE_COV=1 20 | 21 | # Set seeds 22 | HOPPER_SEED_DIR=./seeds 23 | 24 | # Set the header include path 25 | HOPPER_INCLUDE_SEARCH_PATH=/data/workspace/fuzzing_bench/libpcap/src 26 | 27 | -------------------------------------------------------------------------------- /examples/libpcap/seeds/1.txt: -------------------------------------------------------------------------------- 1 | host 192.168.1.1 2 | -------------------------------------------------------------------------------- /examples/libpcap/seeds/2.txt: -------------------------------------------------------------------------------- 1 | port 80 2 | -------------------------------------------------------------------------------- /examples/libpcap/seeds/3.txt: -------------------------------------------------------------------------------- 1 | tcp[tcpflags]&tcp-syn != 0 or tcp[tcpflags]&tcp-fin != 0 or tcp[tcpflags]&tcp-rst != 0 2 | -------------------------------------------------------------------------------- /examples/libpcap/seeds/4.txt: -------------------------------------------------------------------------------- 1 | ether[12:2] = 0x800 or ether[12:2] = 0x8100 or ether[0] & 0x80 != 0 or ether[12:2] = 0x9100 2 | -------------------------------------------------------------------------------- /examples/libpcap/seeds/5.txt: -------------------------------------------------------------------------------- 1 | vlan 186 and ip 2 | -------------------------------------------------------------------------------- /examples/libpcap/seeds/6.txt: -------------------------------------------------------------------------------- 1 | ip and ((icmp and dst host 1.1.1.1 and not host 2.2.2.2) or (host 1.1.1.1 and src host 3.3.3.3)) 2 | -------------------------------------------------------------------------------- /examples/libpcap/seeds/7.txt: -------------------------------------------------------------------------------- 1 | not vlan and tcp port 80 2 | -------------------------------------------------------------------------------- /examples/libpcap/seeds/small_capture.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuzzAnything/Hopper/6dd62e84290c8567e5fbf063af1d7ec3c983add9/examples/libpcap/seeds/small_capture.pcap -------------------------------------------------------------------------------- /examples/libpng/custom.rule: -------------------------------------------------------------------------------- 1 | func_include png_* 2 | # func_include png_create_read_struct,png_create_write_struct,png_create_info_struct,png_init_io,png_set_sig_bytes,png_set_crc_action 3 | # func_target png_set_unknown_chunks 4 | 5 | # DEPRECATED 6 | func_exclude png_info_init_3,png_convert_to_rfc1123,png_malloc_default,png_free_default,png_get_io_chunk_name,png_reset_zstream 7 | # lead to errors 8 | func_exclude png_get_io_state,png_set_read_fn,png_set_write_fn,png_set_rows,png_set_user_transform_info,png_read_image,png_set_read_user_transform_fn,png_benign_error,png_set_error_fn,png_free_data 9 | 10 | # func png_create_read_struct[$0] = "1.6.37"; 11 | # func png_create_read_struct[$1] = $null; 12 | # func png_create_read_struct[$2] = $null; 13 | # func png_create_read_struct[$3] = $null; 14 | 15 | func png_image_write_to_file[$4] = 0 16 | func png_image_write_to_stdio[$4] = 0 17 | func png_image_write_to_memory[$5] = 0 -------------------------------------------------------------------------------- /examples/libpng/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | LIB_DIR=/data/workspace/fuzzing_bench/libpng/build/hopper_build 4 | 5 | # Full path for header file 6 | TEST_HEADER=${LIB_DIR}/include/png.h 7 | 8 | # Full path to shared library 9 | TEST_LIBRARY=${LIB_DIR}/.libs/libpng16.so 10 | 11 | # Output directory 12 | OUT_DIR=output 13 | 14 | # Custom rule for invoking API functions 15 | CUSTOM_RULES=custom.rule 16 | 17 | # set map size for branch counting 18 | HOPPER_MAP_SIZE_POW2=18 19 | 20 | # set seeds for hopper 21 | # HOPPER_SEED_DIR=seeds 22 | -------------------------------------------------------------------------------- /examples/libvpx/custom.rule: -------------------------------------------------------------------------------- 1 | func_include vpx_* 2 | // func_include vpx_codec_dec_init_ver,vpx_codec_vp9_dx,vpx_codec_decode 3 | // func_key vpx_codec_decode,vpx_codec_get_frame 4 | func_exclude vpx_img_free,vpx_codec_control_ -------------------------------------------------------------------------------- /examples/libvpx/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | # Full path for header file 4 | TEST_HEADER=vpx_all.h 5 | 6 | # Full path to shared library 7 | TEST_LIBRARY=/data/workspace/fuzzing_bench/libvpx/build/hopper_build/libvpx.so.7.0.0 8 | 9 | # Output directory 10 | OUT_DIR=output 11 | 12 | # Set map size for branch counting 13 | HOPPER_MAP_SIZE_POW2=18 14 | 15 | # Set dictionary for bytes 16 | HOPPER_DICT=./vpx.dict 17 | 18 | # Set the header include path 19 | HOPPER_INCLUDE_SEARCH_PATH=/data/workspace/fuzzing_bench/libvpx/build/hopper_build 20 | -------------------------------------------------------------------------------- /examples/libvpx/vpx.dict: -------------------------------------------------------------------------------- 1 | # IVF Signature + version (bytes 0-5) 2 | kw1="DKIF\x00\x00" 3 | 4 | # VP9 codec fourCC (bytes 8-11) 5 | kw2="VP90" 6 | 7 | # VP8 codec fourCC (bytes 8-11) 8 | kw3="VP80" -------------------------------------------------------------------------------- /examples/libvpx/vpx_all.h: -------------------------------------------------------------------------------- 1 | 2 | //#include "vpx/vp8cx.h" 3 | // #include "vpx/vpx_encoder.h" 4 | #include "vpx/vp8dx.h" 5 | #include "vpx/vpx_decoder.h" 6 | #include "vpx_ports/mem_ops.h" 7 | 8 | -------------------------------------------------------------------------------- /examples/pcre2/custom.rule: -------------------------------------------------------------------------------- 1 | // func_include * 2 | -------------------------------------------------------------------------------- /examples/pcre2/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | # Full path for header file 4 | #$(echo '#include "pcre2posix.h"' | cpp -H -o /dev/null 2>&1 | head -n1 | cut -d ' ' -f 2) 5 | TEST_HEADER= /usr/include/pcre2posix.h 6 | 7 | # Full path to shared library 8 | #$(ldconfig -p | grep -Po 'libpcre2-posix.so.*>\s*\K.+' | head -n 1) 9 | TEST_LIBRARY=/lib64/libpcre2-posix.so /usr/lib64/libpcre2-8.so 10 | 11 | # Output directory 12 | OUT_DIR=output 13 | 14 | # Custom rule for invoking API functions 15 | CUSTOM_RULES=custom.rule 16 | 17 | # set map size for branch counting 18 | HOPPER_MAP_SIZE_POW2=18 19 | 20 | # set seeds for hopper 21 | # HOPPER_SEED_DIR=seeds 22 | -------------------------------------------------------------------------------- /examples/re2/custom.rule: -------------------------------------------------------------------------------- 1 | func_include cre2_* 2 | func_exclude cre2_delete,cre2_set_delete,cre2_named_groups_iter_delete 3 | 4 | type cre2_string_t[length] = $len(data) -------------------------------------------------------------------------------- /examples/re2/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | # Full path for header file 4 | TEST_HEADER=/data/workspace/fuzzing_bench/re2/cre2.h 5 | 6 | # Full path to shared library 7 | TEST_LIB_DIR=/data/workspace/fuzzing_bench/re2/build/hopper_build/ 8 | TEST_LIBRARY=${TEST_LIB_DIR}/cre2/lib/libcre2.so ${TEST_LIB_DIR}/lib/libre2.so 9 | 10 | # Output directory 11 | OUT_DIR=output 12 | 13 | # Custom rule for invoking API functions 14 | CUSTOM_RULES=custom.rule 15 | 16 | # set map size for branch counting 17 | HOPPER_MAP_SIZE_POW2=20 18 | 19 | # set seeds for hopper 20 | # HOPPER_SEED_DIR=seeds 21 | 22 | # target library 23 | HOPPER_TEST_LIBRARY=cre2/lib/libre2.so 24 | 25 | HOPPER_CUSTOM_RULES=$SRC_DIR/../custom_rule 26 | -------------------------------------------------------------------------------- /examples/sqlite3/custom.rule: -------------------------------------------------------------------------------- 1 | func_include sqlite3_* 2 | func_exclude sqlite3_sleep,sqlite3_mprintf,sqlite3_vmprintf,sqlite3_snprintf,sqlite3_vsnprintf 3 | // func_key sqlite3_exec,sqlite3_get_table,sqlite3_step,sqlite3_complete 4 | // sqlite3_prepare, sqlite3_prepare_v2, sqlite3_step 5 | // our file name is not uft16 6 | func_exclude sqlite3_open16,sqlite3_realloc,sqlite3_realloc64 7 | func_exclude sqlite3_vtab_nochange,sqlite3_vtab_rhs_value,sqlite3_free_filename 8 | func_exclude sqlite3_overload_function,sqlite3_test_control,sqlite3_drop_modules 9 | func_exclude sqlite3_mutex_try,sqlite3_mutex_enter,sqlite3_mutex_free,sqlite3_mutex_leave,sqlite3_mutex_alloc 10 | func_exclude sqlite3_filename_wal,sqlite3_value_free,sqlite3_free_table 11 | func_exclude sqlite3_result_* 12 | func_exclude sqlite3_context_db_handle,sqlite3_aggregate_context,sqlite3_user_data,sqlite3_aggregate_count 13 | 14 | // func sqlite3_open[$0] = $write_file 15 | // func sqlite3_open_v2[$0] = $write_file 16 | // func sqlite3_open16[$0] = $write_file 17 | // func sqlite3_open[$1][&] = $non_null 18 | // func sqlite3_open_v2[$1] = $non_null 19 | // func sqlite3_open16[$1] = $non_null 20 | 21 | // type sqlite3* = $init_with(sqlite3_open, 1) 22 | // type sqlite3_stmt* = $init_with(sqlite3_prepare_v2, 3) -------------------------------------------------------------------------------- /examples/sqlite3/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | TEST_LIB_DIR=/data/workspace/fuzzing_bench/sqlite3/build/hopper_build 4 | 5 | # Full path for header file 6 | TEST_HEADER=${TEST_LIB_DIR}/sqlite3.h 7 | 8 | # Full path to shared library 9 | TEST_LIBRARY=${TEST_LIB_DIR}/.libs/libsqlite3.so 10 | 11 | # Output directory 12 | OUT_DIR=output 13 | 14 | # Custom rule for invoking API functions 15 | CUSTOM_RULES=custom.rule 16 | 17 | # set map size for branch counting 18 | HOPPER_MAP_SIZE_POW2=20 19 | 20 | # set seeds for hopper 21 | # HOPPER_SEED_DIR=seeds 22 | -------------------------------------------------------------------------------- /examples/zlib/custom.rule: -------------------------------------------------------------------------------- 1 | // func_include * 2 | func_exclude gzprintf 3 | // func_include gzfwrite,gzopen 4 | // func_include gzopen,gzread,gzdopen,gzbuffer,gzclose -------------------------------------------------------------------------------- /examples/zlib/hopper.config: -------------------------------------------------------------------------------- 1 | # Configurations for hopper fuzzer 2 | 3 | # Full path for header file 4 | TEST_HEADER=$(echo '#include "zlib.h"' | cpp -H -o /dev/null 2>&1 | head -n1 | cut -d ' ' -f 2) 5 | 6 | # Full path to shared library 7 | TEST_LIBRARY=$(ldconfig -p | grep -Po 'libz.so.*=>\s*\K.+' | head -n 1) 8 | 9 | # Output directory 10 | OUT_DIR=output 11 | 12 | # Custom rule for invoking API functions 13 | CUSTOM_RULES=custom.rule 14 | 15 | # set map size for branch counting 16 | HOPPER_MAP_SIZE_POW2=18 17 | 18 | # set seeds for hopper 19 | # HOPPER_SEED_DIR=seeds 20 | -------------------------------------------------------------------------------- /hopper-compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hopper-compiler" 3 | version = "1.0.0" 4 | edition = "2021" 5 | authors = ["Peng Chen "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rand = "0.8" 11 | log = "0.4" 12 | simplelog = "0.12" 13 | goblin = { version = "0.6", optional = true } 14 | clap = { version = "4.2", features = ["derive"] } 15 | memmap = "0.7" 16 | gimli = { version = "0.26", default-features = false, features = ["read"] } 17 | object = { version = "0.28", default-features = false, features = ["read"] } 18 | eyre = "0.6" 19 | twoway = "0.2" 20 | # NO GPL 21 | # patchelf = "0.2.1" 22 | 23 | [features] 24 | default = ["elf", "dwarf"] 25 | elf = ["goblin"] 26 | dwarf = [] -------------------------------------------------------------------------------- /hopper-compiler/src/check.rs: -------------------------------------------------------------------------------- 1 | use eyre::{ensure, ContextCompat, Result}; 2 | use std::{fs::File, path::Path}; 3 | 4 | #[cfg(target_os = "linux")] 5 | static DYNAMIC_LIB_SUFFIX: &str = ".so"; 6 | #[cfg(target_os = "macos")] 7 | static DYNAMIC_LIB_SUFFIX: &'static str = ".dylib"; 8 | #[cfg(target_os = "windows")] 9 | static DYNAMIC_LIB_SUFFIX: &str = ".dll"; 10 | static STATIC_LIB_SUFFIX: &str = ".a"; 11 | 12 | static DYNAMIC_LIB_SUFFIX_PE: &str = ".dll"; 13 | 14 | pub fn check_header(header: &Path) -> Result<()> { 15 | ensure!(header.is_file(), "Header is not a file"); 16 | ensure!( 17 | header.extension().context("Can't find header extension")? == "h", 18 | "Header is not end with .h" 19 | ); 20 | Ok(()) 21 | } 22 | 23 | pub fn check_library(library: &Path) -> Result<()> { 24 | ensure!(library.is_file(), "Library is not a file"); 25 | 26 | let file_name = library 27 | .file_name() 28 | .context("Can't find library file name")? 29 | .to_str() 30 | .context("fail to convert to str")?; 31 | ensure!( 32 | file_name.starts_with("lib"), 33 | "Library is not start with lib" 34 | ); 35 | ensure!( 36 | file_name.contains(DYNAMIC_LIB_SUFFIX) 37 | || file_name.ends_with(STATIC_LIB_SUFFIX) 38 | || file_name.ends_with(DYNAMIC_LIB_SUFFIX_PE), 39 | "Library does not contain `.so` or `.a` or `.dylib`" 40 | ); 41 | Ok(()) 42 | } 43 | 44 | pub fn output_lib_name(file: &str) -> String { 45 | if let Some(index) = file.find(DYNAMIC_LIB_SUFFIX) { 46 | let lib = &file[..index]; 47 | return format!("{lib}_fuzz{DYNAMIC_LIB_SUFFIX}"); 48 | } 49 | file.to_string() 50 | } 51 | 52 | pub fn check_llvm_runtime(libraries: &[String]) -> bool { 53 | libraries 54 | .iter() 55 | .any(|l| check_file_contains(l, "__hopper_area_ptr")) 56 | } 57 | 58 | pub fn check_file_contains(target: &str, s: &str) -> bool { 59 | let file = File::open(target).unwrap_or_else(|_| panic!("Unable to open file: {target}")); 60 | let f = unsafe { 61 | memmap::MmapOptions::new() 62 | .map(&file) 63 | .expect("unable to mmap file") 64 | }; 65 | twoway::find_bytes(&f[..], s.as_bytes()).is_some() 66 | } 67 | -------------------------------------------------------------------------------- /hopper-compiler/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use clap::{ValueEnum, Parser}; 4 | 5 | #[derive(Parser, Debug, Copy, Clone, ValueEnum)] 6 | pub enum InstrumentType { 7 | E9, 8 | Llvm, 9 | Cov, 10 | } 11 | 12 | /// Hopper - fuzz libraries fully automatically 13 | #[derive(Parser, Debug)] 14 | #[clap(name = "hopper-compiler")] 15 | #[clap(version = "1.0.0", author = "Tencent")] 16 | pub struct Config { 17 | /// Path of target dynamic library 18 | #[clap(long, value_parser, num_args(1..))] 19 | pub library: Vec, 20 | 21 | /// Path of header file of library 22 | #[clap(long, value_parser, num_args(1..))] 23 | pub header: Vec, 24 | 25 | /// Output directory of harness 26 | #[clap(long, value_parser, default_value = "./")] 27 | pub output: String, 28 | 29 | /// Intrument type 30 | #[clap(long, value_enum, value_parser, default_value = "e9")] 31 | pub instrument: InstrumentType, 32 | 33 | /// Show detailed compiling information or not 34 | #[clap(long, value_parser)] 35 | pub quiet: bool, 36 | } 37 | 38 | impl FromStr for InstrumentType { 39 | type Err = eyre::Error; 40 | fn from_str(input: &str) -> Result { 41 | match input { 42 | "e9" => Ok(Self::E9), 43 | "llvm" => Ok(Self::Llvm), 44 | "cov" => Ok(Self::Cov), 45 | _ => Err(eyre::eyre!("fail to parse instrument type")), 46 | } 47 | } 48 | } 49 | 50 | impl InstrumentType { 51 | pub fn as_str(&self) -> &str { 52 | match self { 53 | Self::E9 => "e9", 54 | Self::Llvm => "llvm", 55 | Self::Cov => "cov", 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /hopper-compiler/src/dwarf/arg_type.rs: -------------------------------------------------------------------------------- 1 | 2 | #[derive(Debug, Clone)] 3 | pub enum ArgType { 4 | Char(CharType), 5 | Integer(IntegerType), 6 | Void, 7 | TypeDef(DefType), 8 | Point(PointType), 9 | Const(ConstType), 10 | Struct(StructType), 11 | Array(ArrayType), 12 | Decla(DeclaType), 13 | Ref(RefType), 14 | Undefined, 15 | } 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct IntegerType { 19 | pub sign: bool, 20 | pub size: usize, 21 | } 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct CharType { 25 | pub sign: bool, 26 | } 27 | 28 | #[derive(Debug, Clone)] 29 | pub struct DefType { 30 | pub name: String, 31 | pub alias: Box, 32 | } 33 | 34 | #[derive(Debug, Clone)] 35 | pub struct PointType { 36 | pub dst_type: Box, 37 | pub size: usize, 38 | } 39 | 40 | #[derive(Debug, Clone)] 41 | pub struct ConstType { 42 | pub inner_type: Box, 43 | } 44 | 45 | #[derive(Debug, Clone)] 46 | pub struct StructType { 47 | pub name: String, 48 | pub fields: Vec, 49 | pub size: usize, 50 | // position 51 | } 52 | 53 | #[derive(Debug, Clone)] 54 | pub struct StructField { 55 | pub name: String, 56 | pub ty: Box, 57 | pub location: usize, 58 | } 59 | 60 | #[derive(Debug, Clone)] 61 | pub struct ArrayType { 62 | pub ele_type: Box, 63 | pub sub_range: Vec, 64 | } 65 | 66 | #[derive(Debug, Clone)] 67 | pub struct DeclaType { 68 | pub name: String, 69 | } 70 | 71 | #[derive(Debug, Clone)] 72 | pub struct RefType { 73 | pub offset: u64, 74 | } 75 | 76 | impl ArgType { 77 | pub fn from(name: &str, size: usize) -> Self { 78 | match name { 79 | "void" => ArgType::Void, 80 | "short int" | "int" | "long int" => ArgType::Integer(IntegerType { sign: true, size }), 81 | "short unsigned int" | "unsigned int" | "long unsigned int" => { 82 | ArgType::Integer(IntegerType { sign: false, size }) 83 | } 84 | // FIXME: should make sure char signed or unsigned 85 | "char" | "signed char" => ArgType::Char(CharType { sign: true }), 86 | "unsigned char" => ArgType::Char(CharType { sign: false }), 87 | _ => { 88 | println!("name: {name}, size: {size}"); 89 | ArgType::Undefined 90 | } 91 | } 92 | } 93 | 94 | pub fn alias(name: &str, alias_type: ArgType) -> Self { 95 | ArgType::TypeDef(DefType { 96 | name: name.to_string(), 97 | alias: Box::new(alias_type), 98 | }) 99 | } 100 | 101 | pub fn pointer(dst_type: ArgType, size: usize) -> Self { 102 | ArgType::Point(PointType { 103 | dst_type: Box::new(dst_type), 104 | size, 105 | }) 106 | } 107 | 108 | pub fn constt(inner_type: ArgType) -> Self { 109 | ArgType::Const(ConstType { 110 | inner_type: Box::new(inner_type), 111 | }) 112 | } 113 | 114 | pub fn structt(name: &str, size: usize) -> Self { 115 | ArgType::Struct(StructType { 116 | name: name.to_string(), 117 | size, 118 | fields: vec![], 119 | }) 120 | } 121 | 122 | pub fn array(ele_type: ArgType) -> Self { 123 | ArgType::Array(ArrayType { 124 | ele_type: Box::new(ele_type), 125 | sub_range: vec![], 126 | }) 127 | } 128 | 129 | pub fn ref_as(offset: u64) -> Self { 130 | ArgType::Ref(RefType { offset }) 131 | } 132 | 133 | /* 134 | use std::collections::BTreeMap; 135 | pub fn expand_ref<'a>(&'a self, type_table: &'a BTreeMap) -> &'a ArgType { 136 | match self { 137 | ArgType::Ref(t) => t.find(type_table).unwrap_or_else(|| &ArgType::Undefined), 138 | _ => self, 139 | } 140 | } 141 | */ 142 | } 143 | 144 | /* 145 | impl RefType { 146 | pub fn find<'a>(&self, type_table: &'a BTreeMap) -> Option<&'a ArgType> { 147 | if let Some(r) = type_table.get(&self.offset) { 148 | Some(r) 149 | } else { 150 | None 151 | } 152 | } 153 | } */ 154 | -------------------------------------------------------------------------------- /hopper-compiler/src/dwarf/function.rs: -------------------------------------------------------------------------------- 1 | use super::{ArgType, Position}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Function { 5 | pub name: String, 6 | pub ret_type: ArgType, 7 | pub arg_types: Vec, 8 | pub external: bool, 9 | pub position: Position, 10 | } 11 | 12 | impl Function {} 13 | -------------------------------------------------------------------------------- /hopper-compiler/src/dwarf/mod.rs: -------------------------------------------------------------------------------- 1 | //! TODO: Extract information from debugging information 2 | //! It needs to reverse function signatures and custom data structures. 3 | //! We just put some simple codes and do not implement it, 4 | //! so it is not used in Hopper, too. 5 | 6 | #![allow(dead_code)] 7 | 8 | mod analyzer; 9 | mod arg_type; 10 | mod function; 11 | mod position; 12 | mod program; 13 | mod variable; 14 | 15 | use object::{Object, ObjectSection}; 16 | use std::{borrow, fs::File, path::Path}; 17 | 18 | pub use analyzer::*; 19 | pub use arg_type::*; 20 | pub use function::*; 21 | pub use position::*; 22 | pub use program::*; 23 | pub use variable::*; 24 | 25 | pub fn analyze_dwarf(library: &Path) -> Result { 26 | let file = File::open(library).expect("fail to open library file"); 27 | let mmap = unsafe { memmap::Mmap::map(&file).unwrap() }; 28 | let object = &object::File::parse(&*mmap).expect("fail to parse object"); 29 | 30 | let endian = if object.is_little_endian() { 31 | gimli::RunTimeEndian::Little 32 | } else { 33 | gimli::RunTimeEndian::Big 34 | }; 35 | 36 | // Load a section and return as `Cow<[u8]>`. 37 | let load_section = |id: gimli::SectionId| -> Result, gimli::Error> { 38 | match object.section_by_name(id.name()) { 39 | Some(ref section) => Ok(section 40 | .uncompressed_data() 41 | .unwrap_or_else(|_| borrow::Cow::Borrowed(&[][..]))), 42 | None => Ok(borrow::Cow::Borrowed(&[][..])), 43 | } 44 | }; 45 | 46 | // Load all of the sections. 47 | let dwarf_cow = gimli::Dwarf::load(&load_section)?; 48 | 49 | // Borrow a `Cow<[u8]>` to create an `EndianSlice`. 50 | let borrow_section: &dyn for<'a> Fn( 51 | &'a borrow::Cow<[u8]>, 52 | ) -> gimli::EndianSlice<'a, gimli::RunTimeEndian> = 53 | &|section| gimli::EndianSlice::new(section, endian); 54 | 55 | // Create `EndianSlice`s for all of the sections. 56 | let dwarf = dwarf_cow.borrow(&borrow_section); 57 | let mut analyzer = DwarfAnalyzer::new(dwarf); 58 | 59 | analyzer.parse() 60 | } 61 | -------------------------------------------------------------------------------- /hopper-compiler/src/dwarf/position.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub struct Position { 3 | // TODO: support file name 4 | // https://github.com/eliben/pyelftools/issues/250 5 | pub file: usize, 6 | pub line: usize, 7 | pub column: usize, 8 | } 9 | -------------------------------------------------------------------------------- /hopper-compiler/src/dwarf/program.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use super::*; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Program { 7 | pub units: Vec, 8 | } 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct ProgramUnit { 12 | pub name: String, 13 | pub producer: String, 14 | pub type_table: BTreeMap, 15 | pub fn_list: Vec, 16 | pub var_list: Vec, 17 | } 18 | -------------------------------------------------------------------------------- /hopper-compiler/src/dwarf/variable.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Variable { 5 | pub name: String, 6 | pub ty: ArgType, 7 | pub external: bool, 8 | pub position: Position, 9 | } 10 | 11 | impl Variable {} 12 | -------------------------------------------------------------------------------- /hopper-compiler/src/main.rs: -------------------------------------------------------------------------------- 1 | use binary_info::BinaryInfo; 2 | use eyre::{Context, ContextCompat, Result}; 3 | use std::{ 4 | fs, 5 | io::Write, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | use clap::Parser; 10 | 11 | mod cargo; 12 | mod check; 13 | mod config; 14 | mod dwarf; 15 | #[cfg(target_os = "linux")] 16 | mod patch; 17 | mod binary_info; 18 | 19 | use config::*; 20 | 21 | pub fn compile(config: &Config) -> Result<()> { 22 | log::info!("config: {:?}", config); 23 | fs::create_dir_all(&config.output).expect("fail to create output directory"); 24 | let output = PathBuf::from(&config.output) 25 | .canonicalize() 26 | .context("cononicalize output path fail")?; 27 | eyre::ensure!(!config.header.is_empty(), "require at least one header"); 28 | eyre::ensure!(!config.library.is_empty(), "require at least one library"); 29 | let header = if config.header.len() == 1 { 30 | let header = PathBuf::from(&config.header[0]) 31 | .canonicalize() 32 | .context("cononicalize header path fail")?; 33 | check::check_header(&header)?; 34 | header 35 | } else { 36 | concat_headers(&output, &config.header)? 37 | }; 38 | 39 | let mut libraries = vec![]; 40 | let mut func_list = vec![]; 41 | for lib in &config.library { 42 | let lib = PathBuf::from(lib) 43 | .canonicalize() 44 | .context("cononicalize library path fail")?; 45 | check::check_library(&lib)?; 46 | let lib_info = crate::binary_info::BinaryInfo::parse(&lib)?; 47 | let instrumented_lib = instrument(&lib, &output, config, &lib_info)?; 48 | libraries.push(instrumented_lib); 49 | func_list.extend(lib_info.func_list); 50 | } 51 | binary_info::save_func_list(&func_list, &output)?; 52 | cargo::cargo_install(libraries, &header, &output, config, func_list)?; 53 | Ok(()) 54 | } 55 | 56 | fn instrument(library: &Path, output: &Path, config: &Config, lib_info: &BinaryInfo) -> Result { 57 | let lib_name = library.file_name().context("fail to parse library name")?; 58 | let lib_name = check::output_lib_name(lib_name.to_str().context("fail cast as str")?); 59 | let output_lib = output.join(&lib_name); 60 | match config.instrument { 61 | InstrumentType::E9 => { 62 | #[cfg(target_os = "windows")] 63 | { 64 | eyre::ensure!( 65 | check::check_file_contains(library, "E9PATCH"), 66 | "The library should be instrumented by E9 in linux" 67 | ); 68 | fs::copy(library, &output_lib).context("fail to copy library")?; 69 | } 70 | #[cfg(target_os = "linux")] 71 | patch::e9_instrument(library, &output_lib, lib_info)?; 72 | } 73 | InstrumentType::Llvm | InstrumentType::Cov => { 74 | fs::copy(library, &output_lib).context("fail to copy library")?; 75 | } 76 | } 77 | #[cfg(target_os = "linux")] 78 | { 79 | let output_lib_path = output_lib.to_str().context("fail to be str")?; 80 | patch::patchelf_set_so_name(&lib_name, output_lib_path)?; 81 | patch::remove_prev_needed(&config.library, output_lib_path, lib_info)?; 82 | } 83 | Ok(output_lib) 84 | } 85 | 86 | 87 | fn main() -> Result<()> { 88 | init_logger(); 89 | let mut config = Config::parse(); 90 | if check::check_llvm_runtime(&config.library) { 91 | config.instrument = InstrumentType::Llvm; 92 | } 93 | let ret = compile(&config); 94 | if let Err(e) = ret { 95 | log::error!("Meets error: {}", e); 96 | return Err(e); 97 | } 98 | 99 | Ok(()) 100 | } 101 | 102 | 103 | 104 | fn init_logger() { 105 | let mut config_builder = simplelog::ConfigBuilder::new(); 106 | config_builder.set_time_offset_to_local().unwrap(); 107 | simplelog::CombinedLogger::init(vec![simplelog::TermLogger::new( 108 | simplelog::LevelFilter::Info, 109 | config_builder.build(), 110 | simplelog::TerminalMode::Mixed, 111 | simplelog::ColorChoice::Auto, 112 | )]) 113 | .unwrap(); 114 | } 115 | 116 | fn concat_headers(output: &Path, headers: &Vec) -> Result { 117 | let tmp_header = output.join("tmp.h"); 118 | let mut content = String::new(); 119 | for header in headers { 120 | let header = PathBuf::from(header) 121 | .canonicalize() 122 | .expect("cononicalize header path fail"); 123 | check::check_header(&header)?; 124 | content.push_str(&format!("#include \"{}\"\n", header.to_str().unwrap())); 125 | } 126 | let mut f = std::fs::File::create(&tmp_header)?; 127 | f.write_all(content.as_bytes())?; 128 | 129 | Ok(tmp_header) 130 | } 131 | -------------------------------------------------------------------------------- /hopper-compiler/src/patch/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | mod patchelf; 4 | mod e9; 5 | 6 | pub use patchelf::*; 7 | pub use e9::*; 8 | -------------------------------------------------------------------------------- /hopper-compiler/src/patch/patchelf.rs: -------------------------------------------------------------------------------- 1 | //! Linux specification 2 | //! set shared libary's soname 3 | 4 | use std::{env, path::PathBuf, process::Command}; 5 | 6 | use eyre::{ensure, Context, Result}; 7 | 8 | use crate::binary_info::BinaryInfo; 9 | 10 | fn patchelf_path() -> Result { 11 | if let Ok(path) = env::var("HOPPER_PATH") { 12 | return Ok(format!("{}/patchelf", &path).into()); 13 | } 14 | let exe_dir = env::current_dir()?; 15 | Ok(exe_dir) 16 | } 17 | 18 | pub fn patchelf_set_so_name(lib_name: &str, path: &str) -> Result<()> { 19 | let patchelf = patchelf_path()?; 20 | log::info!( 21 | "patchelf cmd: {:?}, lib_name: {:?}, path: {:}", 22 | patchelf, 23 | lib_name, 24 | path 25 | ); 26 | let mut child = Command::new(&patchelf) 27 | .arg("--set-soname") 28 | .arg(lib_name) 29 | .arg(path) 30 | .spawn() 31 | .context("Fail to invoke patchelf")?; 32 | 33 | log::info!("start set soname .."); 34 | 35 | let status = child.wait()?; 36 | ensure!(status.success(), "patchelf set soname error"); 37 | log::info!("patchelf set soname done"); 38 | 39 | Ok(()) 40 | } 41 | 42 | pub fn remove_prev_needed(input_libs: &[String], path: &str, lib_info: &BinaryInfo) -> Result<()> { 43 | if input_libs.len() == 1 { 44 | return Ok(()); 45 | } 46 | let patchelf = patchelf_path()?; 47 | let needed_names: Vec = input_libs 48 | .iter() 49 | .map(|l| { 50 | format!( 51 | "{}", 52 | PathBuf::from(l).file_name().unwrap().to_string_lossy() 53 | ) 54 | }) 55 | .collect(); 56 | 57 | log::info!("lib needed: {:?}", lib_info.needed); 58 | 59 | for name in &needed_names { 60 | if let Some(exist) = lib_info.needed.iter().find(|l| l.contains(name)) { 61 | log::info!("try remove need {exist} in {path:?}.."); 62 | let mut child = Command::new(&patchelf) 63 | .arg("--remove-needed") 64 | .arg(exist) 65 | .arg(path) 66 | .spawn() 67 | .context("Fail to invoke patchelf")?; 68 | let status = child.wait()?; 69 | ensure!(status.success(), "patchelf remove needed serror"); 70 | } 71 | } 72 | 73 | Ok(()) 74 | } 75 | 76 | /* 77 | pub fn patchelf_set_so_name2(lib_name: &str, path: &str) -> eyre::Result<()> { 78 | let suc = patchelf::PatchElf::config() 79 | .input(path) 80 | .set_soname(lib_name) 81 | .patch(); 82 | 83 | eyre::ensure!(suc, "fail to set soname via patchelf"); 84 | Ok(()) 85 | } 86 | */ 87 | -------------------------------------------------------------------------------- /hopper-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hopper" 3 | version = "1.0.0" 4 | edition = "2021" 5 | authors = ["Peng Chen "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | hopper-derive = { path = "../hopper-derive", features = ["use_crate"] } 11 | rand = "0.8" 12 | rand_core = "0.6" 13 | log = "0.4" 14 | thiserror = "1.0" 15 | downcast-rs = "1.2" 16 | nix = "0.24" 17 | libc = "0.2" 18 | once_cell = "1.1" 19 | wait-timeout = "0.2" 20 | clap = { version = "4.2", features = ["derive"] } 21 | linkme = { version = "0.3", optional = true } 22 | ctrlc = { version = "3.2", features = ["termination"] } 23 | num-traits = "0.2" 24 | num = "0.4" 25 | dyn-clone = "1.0" 26 | paste = "1.0" 27 | findshlibs = "0.10" 28 | region = "3.0" 29 | eyre = "0.6" 30 | twoway = "0.2" 31 | base64 = "0.13" 32 | regex = "1" 33 | serde_json = "1.0" 34 | plthook = "0.2" 35 | 36 | #[cfg(target_os = "windows")] 37 | uds_windows = "1.0.1" 38 | #[cfg(target_os = "windows")] 39 | ntapi = "0.3.7" 40 | #[cfg(target_os = "windows")] 41 | winapi = { version = "0.3", features = [ 42 | "profileapi", 43 | "sysinfoapi", 44 | "errhandlingapi", 45 | "processthreadsapi", 46 | "libloaderapi", 47 | "consoleapi", 48 | "winbase", 49 | "processenv", 50 | "wow64apiset", 51 | "synchapi", 52 | "memoryapi" 53 | ] } 54 | 55 | [build-dependencies] 56 | cc = "1.0" 57 | 58 | [features] 59 | default = ["ctor_hook", "fat_bucket"] # "slices" 60 | ctor_hook = [] 61 | link_hook = ["linkme"] 62 | e9_mode = [] 63 | llvm_mode = [] 64 | select_timeout = [] 65 | fat_bucket = [] 66 | slices = [] 67 | verbose = [] 68 | testsuite = [] -------------------------------------------------------------------------------- /hopper-core/build.rs: -------------------------------------------------------------------------------- 1 | extern crate cc; 2 | 3 | fn main() { 4 | if cfg!(feature = "e9_mode") { 5 | #[cfg(target_family = "unix")] 6 | cc::Build::new().file("src/feedback/globl/e9-globl.S").compile("e9"); 7 | #[cfg(target_family = "windows")] 8 | cc::Build::new().file("src/feedback/globl/e9-globl-win.S").compile("e9"); 9 | } else if cfg!(feature = "llvm_mode") { 10 | cc::Build::new() 11 | .file("src/feedback/globl/llvm-globl.c") 12 | .compile("llvm-globl"); 13 | } 14 | cc::Build::new() 15 | .file("src/feedback/globl/variadic.c") 16 | .define("_LARGEFILE64_SOURCE", "1") 17 | .compile("variadic"); 18 | } 19 | -------------------------------------------------------------------------------- /hopper-core/src/depot/mod.rs: -------------------------------------------------------------------------------- 1 | mod io; 2 | mod priority; 3 | mod select; 4 | 5 | use std::{collections::BinaryHeap, fmt}; 6 | 7 | use self::select::*; 8 | use crate::{execute::StatusType, FuzzProgram, FeedbackSummary}; 9 | pub use io::*; 10 | use priority::PriorityWrap; 11 | 12 | /// Depot for saving all inputs, hangs, and crashes. 13 | pub struct Depot { 14 | pub queue: BinaryHeap>, 15 | pub inputs: DepotDir, 16 | pub hangs: DepotDir, 17 | pub crashes: DepotDir, 18 | pub selector: Box, 19 | } 20 | 21 | impl Depot { 22 | /// Create new depot. 23 | pub fn new() -> eyre::Result { 24 | let (inputs, hangs, crashes) = io::init_depot_dirs()?; 25 | Ok(Self { 26 | queue: BinaryHeap::new(), 27 | inputs, 28 | hangs, 29 | crashes, 30 | selector: init_selector(&crate::config::get_config().select), 31 | }) 32 | } 33 | 34 | /// Fetch new ID 35 | pub fn fetch_id(&mut self, status: StatusType) -> usize { 36 | match status { 37 | StatusType::Normal { .. } => self.inputs.inc_id(), 38 | StatusType::Timeout => self.hangs.inc_id(), 39 | StatusType::Crash { .. } => self.crashes.inc_id(), 40 | _ => 0, 41 | } 42 | } 43 | 44 | /// Save new interesting input into depot. 45 | pub fn save( 46 | &mut self, 47 | status: StatusType, 48 | program: &FuzzProgram, 49 | sync: bool, 50 | ) -> eyre::Result<()> { 51 | if sync { 52 | return Ok(()); 53 | } 54 | match status { 55 | StatusType::Normal { .. } => self.inputs.save_program(program, status), 56 | StatusType::Timeout => self.hangs.save_program(program, status), 57 | StatusType::Crash { .. } => self.crashes.save_program(program, status), 58 | _ => { 59 | eyre::bail!("unknown status type"); 60 | } 61 | } 62 | } 63 | 64 | pub fn add_appendix( 65 | &mut self, 66 | status: StatusType, 67 | id: usize, 68 | appendix: &str, 69 | ) -> eyre::Result<()> { 70 | match status { 71 | StatusType::Normal { .. } => self.inputs.add_appendix(id, appendix), 72 | StatusType::Timeout => self.hangs.add_appendix(id, appendix), 73 | StatusType::Crash { .. } => self.crashes.add_appendix(id, appendix), 74 | _ => { 75 | eyre::bail!("unknown status type"); 76 | } 77 | } 78 | } 79 | 80 | /// put program in the queue, 81 | pub fn push_queue(&mut self, program: FuzzProgram, feedback: &FeedbackSummary) -> eyre::Result<()> { 82 | let id = program.id; 83 | let score = self.selector.init_score(&program, feedback); 84 | self.queue.push(PriorityWrap::new(program, score)); 85 | crate::log!( 86 | debug, 87 | "put new program on queue, id: {id}, priority score: {score:?}" 88 | ); 89 | if self.queue.len() > crate::config::MAX_QUEUE_SIZE { 90 | // To make the memory usage to be low, 91 | // we find the seeds with small IDs, and kick out them 92 | let archived = id - self.queue.len() + 5; 93 | self.queue.retain(|item| item.data.id > archived); 94 | } 95 | Ok(()) 96 | } 97 | 98 | /// Get program in queue by id 99 | pub fn get_program_by_id(&self, id: usize) -> Option<&FuzzProgram> { 100 | self.queue 101 | .iter() 102 | .find(|p| p.data.id == id) 103 | .map(|qw| &qw.data) 104 | } 105 | } 106 | 107 | impl fmt::Display for Depot { 108 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 109 | write!( 110 | f, 111 | "#queue: {}, #crashes: {}, #hangs: {}", 112 | self.inputs.size(), 113 | self.crashes.size(), 114 | self.hangs.size(), 115 | ) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /hopper-core/src/depot/priority.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | fmt::{self, Display}, 4 | }; 5 | 6 | #[derive(Debug)] 7 | pub struct PriorityWrap { 8 | pub data: T, 9 | pub score: (u64, f64), 10 | } 11 | 12 | impl PriorityWrap { 13 | pub fn new(data: T, score: (u64, f64)) -> Self { 14 | Self { data, score } 15 | } 16 | } 17 | 18 | impl PartialEq for PriorityWrap { 19 | fn eq(&self, other: &PriorityWrap) -> bool { 20 | self.score.0 == other.score.0 && self.score.1 == other.score.1 21 | } 22 | } 23 | 24 | impl Eq for PriorityWrap {} 25 | 26 | // Make the queue get largestscore first. 27 | impl Ord for PriorityWrap { 28 | fn cmp(&self, other: &PriorityWrap) -> Ordering { 29 | // score.0 is more important than score.1 30 | // only if score.0 is useless, then we use score.1 31 | if self.score.0 == 0 && other.score.0 == 0 { 32 | // use score.1 33 | match self.score.1.partial_cmp(&other.score.1) { 34 | Some(o) => match o { 35 | Ordering::Greater => Ordering::Greater, 36 | Ordering::Less => Ordering::Less, 37 | Ordering::Equal => Ordering::Equal, 38 | }, 39 | None => { 40 | panic!("The priority cannot be NaN!"); 41 | } 42 | } 43 | } else { 44 | match self.score.0.partial_cmp(&other.score.0) { 45 | Some(o) => match o { 46 | Ordering::Greater => Ordering::Greater, 47 | Ordering::Less => Ordering::Less, 48 | Ordering::Equal => Ordering::Equal 49 | }, 50 | None => { 51 | panic!("The priority cannot be NaN!"); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | impl PartialOrd for PriorityWrap { 59 | fn partial_cmp(&self, other: &PriorityWrap) -> Option { 60 | Some(self.cmp(other)) 61 | } 62 | } 63 | 64 | impl fmt::Display for PriorityWrap { 65 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 66 | write!( 67 | f, 68 | "data: {}, priority: ({}, {})", 69 | self.data, self.score.0, self.score.1 70 | ) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /hopper-core/src/depot/select.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | 3 | use crate::{FuzzProgram, SelectType, FeedbackSummary}; 4 | 5 | use super::Depot; 6 | 7 | 8 | 9 | /// Select seed input form depot 10 | pub trait Selector { 11 | fn init_score(&self, program: &FuzzProgram, feedback: &FeedbackSummary) -> (u64, f64); 12 | fn next_score(&self, score: (u64, f64)) -> (u64, f64); 13 | } 14 | 15 | /// Round Robin 16 | pub struct RrSelector; 17 | 18 | /// Simulated annealing 19 | pub struct SaSelector; 20 | 21 | const RR_BASE: u64 = 100000; 22 | const RR_STEP: u64 = 1; 23 | 24 | impl Selector for RrSelector { 25 | fn init_score(&self, program: &FuzzProgram, _feedback: &FeedbackSummary) -> (u64, f64) { 26 | let bonus = get_bonus(program); 27 | (RR_BASE + bonus, 0_f64) 28 | } 29 | 30 | fn next_score(&self, score: (u64, f64)) -> (u64, f64) { 31 | if score.0 == 0 { 32 | return score; 33 | } 34 | (score.0 - RR_STEP, score.1) 35 | } 36 | } 37 | 38 | // base score 39 | const SA_BASE: f64 = 60_f64; 40 | // 20**(-N/500) 41 | const SA_COE: f64 = 0.9_f64; 42 | // for fresh seeds 43 | const SA_UNIQ_NEW: u64 = 3; 44 | // bonus for key function 45 | const KEY_BONUS: u64 = 2; 46 | 47 | thread_local! { 48 | static AVG_SCORE: Cell<(f64, u64)> = const { Cell::new((0_f64, 0_u64)) }; 49 | } 50 | 51 | impl Selector for SaSelector { 52 | fn init_score(&self, program: &FuzzProgram, feedback: &FeedbackSummary) -> (u64, f64) { 53 | let edge_num = feedback.path_len as u128; 54 | let t_used = feedback.time_used as f64; 55 | let call_num = program.stmts.iter().filter(|s| s.stmt.is_call()).count() as u128; 56 | // assert!(call_num > 0, "The number of call statements cannot be zero!"); 57 | let r = edge_num as f64 / t_used; 58 | // make the range of `r` to be [0, 5]; 59 | let avg = AVG_SCORE.with(|c| { 60 | let (mut avg, mut num) = c.get(); 61 | let sum = avg.mul_add(num as f64, r); 62 | num += 1; 63 | avg = sum / (num as f64); 64 | c.replace((avg, num)); 65 | avg 66 | }); 67 | let mut coef = r / avg; 68 | if coef > 5_f64 { 69 | coef = 5_f64; 70 | } 71 | let score = SA_BASE * (1_f64 + coef); 72 | let mut bonus = get_bonus(program); 73 | crate::log!( 74 | debug, 75 | "#edge: {edge_num}, #t: {t_used}, #call: {call_num}, score: {score}" 76 | ); 77 | if feedback.num_uniq_path > 5 { 78 | bonus += SA_UNIQ_NEW; 79 | } 80 | (bonus, score) 81 | } 82 | 83 | fn next_score(&self, score: (u64, f64)) -> (u64, f64) { 84 | if score.0 > 0 { 85 | (score.0 - 1, score.1) 86 | } else { 87 | (0, score.1 * SA_COE) 88 | } 89 | } 90 | } 91 | 92 | impl Depot { 93 | pub fn select_seed(&mut self) -> Option { 94 | if let Some(mut entry) = self.queue.peek_mut() { 95 | crate::log!(debug, "select program {} as seed, score: {:?}", entry.data.id, entry.score); 96 | entry.score = self.selector.next_score(entry.score); 97 | return Some(entry.data.clone()); 98 | } 99 | None 100 | } 101 | } 102 | 103 | pub fn init_selector(ty: &SelectType) -> Box { 104 | match ty { 105 | SelectType::Rr => Box::new(RrSelector), 106 | SelectType::Sa => Box::new(SaSelector), 107 | } 108 | } 109 | 110 | /// Bonus for specific programs 111 | fn get_bonus(program: &FuzzProgram) -> u64 { 112 | if let Some(call) = program.get_target_stmt() { 113 | // bonus for key function 114 | if crate::config::get_config().func_key.contains(&call.name) { 115 | return KEY_BONUS; 116 | } 117 | } 118 | 0 119 | } 120 | 121 | #[test] 122 | fn test_priority_in_queue() { 123 | use super::PriorityWrap; 124 | use std::collections::BinaryHeap; 125 | let selector = RrSelector; 126 | let mut heap = BinaryHeap::new(); 127 | heap.push(PriorityWrap::new(1, (200, 100_f64))); 128 | heap.push(PriorityWrap::new(2, (200, 100_f64))); 129 | for _ in 0..10 { 130 | let v1 = { 131 | let mut entry = heap.peek_mut().unwrap(); 132 | println!("v1: {}", *entry); 133 | entry.score = selector.next_score(entry.score); 134 | entry.data 135 | }; 136 | let v2 = { 137 | let mut entry = heap.peek_mut().unwrap(); 138 | println!("v2: {}", *entry); 139 | entry.score = selector.next_score(entry.score); 140 | entry.data 141 | }; 142 | assert_ne!(v1, v2); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /hopper-core/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::execute::{Pid, Signal}; 4 | 5 | use thiserror::Error; 6 | 7 | #[derive(Error, Debug)] 8 | pub enum HopperError { 9 | #[error("fail to fork, errno `{0}`")] 10 | ForkError(String), 11 | 12 | #[error("null function pointer")] 13 | NullFuncionPointer, 14 | 15 | #[error("OS Error: {errno}, caused by: {info}")] 16 | OSError { errno: std::io::Error, info: String }, 17 | 18 | #[error("Catch unwind panic")] 19 | UnwindPanic(Box), 20 | 21 | #[error("Crash in child process, pid: `{pid}`, signal: `{signal}`")] 22 | ProcessCrash { pid: Pid, signal: Signal }, 23 | 24 | #[error("Timeout in child process, pid: `{pid}`")] 25 | ProcessTimeout { pid: Pid }, 26 | 27 | #[error("Panic at rust runtime")] 28 | RuntimeError, 29 | 30 | #[error("Assert error: `{msg}`")] 31 | AssertError{ msg: String, silent: bool }, 32 | 33 | #[error("Use resource `{ptr:?}` after free")] 34 | UseAfterFree { ptr: *mut u8 }, 35 | 36 | #[error("Free resource `{ptr:?}` more than once")] 37 | DoubleFree { ptr: *mut u8 }, 38 | 39 | #[error("Test success")] 40 | TestSuccess, 41 | 42 | #[error("Fail to spawn thread")] 43 | SpawnThreadPanic(Box), 44 | 45 | #[error("Timeout in spawn thread")] 46 | SpawnTimeout, 47 | 48 | #[error("union field not found")] 49 | UnionErr, 50 | 51 | #[error("`{0}`")] 52 | FieldNotFound(String), 53 | 54 | #[error("Index does not exist in sequence")] 55 | IndexNotExist, 56 | 57 | #[error("Fail to read line: EOF")] 58 | ReadLineEOF, 59 | } 60 | 61 | unsafe impl Sync for HopperError {} 62 | unsafe impl Send for HopperError {} 63 | 64 | /// Get error number (errno) if return value less than zero, used for libc function calls. 65 | pub fn check_os_error(num: T, msg: &str) -> Result<(), HopperError> { 66 | if num < T::default() { 67 | return Err(HopperError::OSError { 68 | errno: std::io::Error::last_os_error(), 69 | info: msg.to_string(), 70 | }); 71 | } 72 | Ok(()) 73 | } 74 | -------------------------------------------------------------------------------- /hopper-core/src/execute/io_utils.rs: -------------------------------------------------------------------------------- 1 | use std::io::BufRead; 2 | 3 | use eyre::Context; 4 | 5 | use crate::{Deserialize, Deserializer}; 6 | 7 | pub fn read_line(reader: &mut R) -> eyre::Result { 8 | let mut buf = String::new(); 9 | let n = reader 10 | .read_line(&mut buf) 11 | .with_context(|| format!("fail to read line: {buf}"))?; 12 | if n == 0 { 13 | crate::log!(warn, "read EOF, the other side may be down..."); 14 | eyre::bail!(crate::HopperError::ReadLineEOF); 15 | } 16 | trim_newline(&mut buf); 17 | Ok(buf) 18 | } 19 | 20 | pub fn receive_line(reader: &mut R) -> eyre::Result { 21 | let buf = read_line(reader)?; 22 | let mut de = Deserializer::new(&buf, None); 23 | let ret = T::deserialize(&mut de); 24 | if ret.is_err() { 25 | crate::log!(warn, "fail to parse: {buf}"); 26 | } 27 | ret.with_context(|| format!("fail to parse : {}", &buf)) 28 | } 29 | 30 | /// Trim newline chars in lines. 31 | pub fn trim_newline(s: &mut String) { 32 | if s.ends_with('\n') { 33 | s.pop(); 34 | if s.ends_with('\r') { 35 | s.pop(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /hopper-core/src/execute/limit.rs: -------------------------------------------------------------------------------- 1 | //! Limitation of memory and time 2 | 3 | pub trait SetLimit { 4 | /// Limit memory 5 | fn mem_limit(&mut self, size: Option) -> &mut Self; 6 | /// Dumping cores is slow and can lead to anomalies if SIGKILL is delivered 7 | /// before the dump is complete 8 | fn core_limit(&mut self) -> &mut Self; 9 | /// Isolate the process and configure standard descriptors. 10 | fn setsid(&mut self) -> &mut Self; 11 | } 12 | 13 | #[cfg(target_family = "unix")] 14 | use std::{os::unix::process::CommandExt, process::Command}; 15 | #[cfg(target_family = "unix")] 16 | impl SetLimit for Command { 17 | fn mem_limit(&mut self, size: Option) -> &mut Self { 18 | if let Some(size) = size { 19 | let func = move || { 20 | if size > 0 { 21 | let size = size << 20; 22 | let mem_limit: libc::rlim_t = size; 23 | let r = libc::rlimit { 24 | rlim_cur: mem_limit, 25 | rlim_max: mem_limit, 26 | }; 27 | unsafe { 28 | #[cfg(any(target_os = "linux", target_os = "macos"))] 29 | libc::setrlimit(libc::RLIMIT_AS, &r); 30 | // This takes care of OpenBSD, which doesn't have RLIMIT_AS, but 31 | // according to reliable sources, RLIMIT_DATA covers anonymous 32 | // maps - so we should be getting good protection against OOM bugs 33 | #[cfg(target_os = "freebsd")] 34 | libc::setrlimit(libc::RLIMIT_DATA, &r); 35 | } 36 | } 37 | Ok(()) 38 | }; 39 | return unsafe { self.pre_exec(func) }; 40 | } 41 | self 42 | } 43 | 44 | fn setsid(&mut self) -> &mut Self { 45 | let func = move || { 46 | unsafe { 47 | libc::setsid(); 48 | }; 49 | Ok(()) 50 | }; 51 | unsafe { self.pre_exec(func) } 52 | } 53 | 54 | fn core_limit(&mut self) -> &mut Self { 55 | let func = move || { 56 | let r0 = libc::rlimit { 57 | rlim_cur: 0, 58 | rlim_max: 0, 59 | }; 60 | unsafe { 61 | libc::setrlimit(libc::RLIMIT_CORE, &r0); 62 | }; 63 | Ok(()) 64 | }; 65 | unsafe { self.pre_exec(func) } 66 | } 67 | } 68 | 69 | #[cfg(target_os = "windows")] 70 | use std::process::Command; 71 | #[cfg(target_os = "windows")] 72 | impl SetLimit for Command { 73 | fn mem_limit(&mut self, _size: Option) -> &mut Self { 74 | self 75 | } 76 | 77 | fn setsid(&mut self) -> &mut Self { 78 | self 79 | } 80 | 81 | fn core_limit(&mut self) -> &mut Self { 82 | self 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /hopper-core/src/execute/mod.rs: -------------------------------------------------------------------------------- 1 | mod executor; 2 | mod forkcli; 3 | mod forksrv; 4 | pub mod io_utils; 5 | mod limit; 6 | mod signal; 7 | 8 | pub use executor::*; 9 | pub use forkcli::*; 10 | pub use forksrv::*; 11 | pub use signal::*; 12 | 13 | use hopper_derive::Serde; 14 | 15 | #[cfg(target_family = "unix")] 16 | use std::os::unix::net::{UnixListener, UnixStream}; 17 | #[cfg(target_os = "windows")] 18 | use uds_windows::{UnixListener, UnixStream}; 19 | 20 | #[cfg(target_family = "unix")] 21 | pub use nix::sys::signal::Signal; 22 | #[cfg(target_os = "windows")] 23 | pub type Signal = u32; 24 | 25 | #[cfg(target_family = "unix")] 26 | pub use nix::unistd::Pid; 27 | #[cfg(target_os = "windows")] 28 | pub type Pid = u32; 29 | 30 | #[cfg(target_os = "windows")] 31 | pub mod forklib_win; 32 | #[cfg(target_os = "windows")] 33 | pub use forklib_win::*; 34 | 35 | /// Status type of program's executing result 36 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serde)] 37 | pub enum StatusType { 38 | /// program runs OK 39 | Normal, 40 | /// program runs timeout 41 | Timeout, 42 | /// program crash 43 | Crash { signal: Signal }, 44 | /// Ignored cases (error) during executing 45 | Ignore, 46 | /// Loop is endding 47 | LoopEnd 48 | } 49 | 50 | impl Default for StatusType { 51 | fn default() -> Self { 52 | Self::Normal 53 | } 54 | } 55 | 56 | impl StatusType { 57 | pub fn is_normal(&self) -> bool { 58 | matches!(self, Self::Normal) 59 | } 60 | pub fn is_ignore(&self) -> bool { 61 | matches!(self, Self::Ignore) 62 | } 63 | pub fn is_crash(&self) -> bool { 64 | matches!(self, Self::Crash { signal: _ }) 65 | } 66 | pub fn is_timeout(&self) -> bool { 67 | matches!(self, Self::Timeout) 68 | } 69 | pub fn is_loop_end(&self) -> bool { 70 | matches!(self, Self::LoopEnd) 71 | } 72 | pub fn is_abort(&self) -> bool { 73 | matches!( 74 | self, 75 | StatusType::Crash { 76 | signal: Signal::SIGABRT 77 | } 78 | ) 79 | } 80 | pub fn is_sigfpe(&self) -> bool { 81 | matches!( 82 | self, 83 | StatusType::Crash { 84 | signal: Signal::SIGFPE 85 | } 86 | ) 87 | } 88 | pub fn is_overflow(&self) -> bool { 89 | matches!( 90 | self, 91 | StatusType::Crash { 92 | signal: Signal::SIGSEGV 93 | } | StatusType::Crash { 94 | signal: Signal::SIGBUS 95 | } 96 | ) 97 | } 98 | } 99 | 100 | #[derive(Debug, Clone, PartialEq, Eq, Serde)] 101 | pub enum ForkCmd { 102 | Execute, 103 | Loop, 104 | Review, 105 | Sanitize, 106 | Config(String), 107 | Finish, 108 | } 109 | 110 | pub static OPAQUE_CONFIG_KEY: &str = "opaque"; 111 | -------------------------------------------------------------------------------- /hopper-core/src/feedback/globl/e9-globl-win.S: -------------------------------------------------------------------------------- 1 | .text 2 | .globl __hopper_inc_stmt_index 3 | .globl __hopper_reset_stmt_index 4 | .globl __hopper_last_stmt_index 5 | .globl __hopper_enable_cov 6 | .globl __hopper_disable_cov 7 | .globl __hopper_set_context 8 | __hopper_inc_stmt_index: 9 | incw 0x47ff1000 10 | incl %ds:0x47fb0008 11 | ret 12 | __hopper_reset_stmt_index: 13 | movw $0,0x47ff1000 14 | movl $0, %ds:0x47fb0008 15 | ret 16 | __hopper_last_stmt_index: 17 | movw $0xFFFF, 0x47ff1000 18 | ret 19 | __hopper_enable_cov: 20 | movl $0, 0x47ff2000 21 | ret 22 | __hopper_disable_cov: 23 | movl $0xFFFFFFFF, 0x47ff2000 24 | ret 25 | __hopper_set_context: 26 | ret -------------------------------------------------------------------------------- /hopper-core/src/feedback/globl/e9-globl.S: -------------------------------------------------------------------------------- 1 | .text 2 | .globl __hopper_inc_stmt_index 3 | .globl __hopper_reset_stmt_index 4 | .globl __hopper_last_stmt_index 5 | .globl __hopper_enable_cov 6 | .globl __hopper_disable_cov 7 | .globl __hopper_set_context 8 | __hopper_inc_stmt_index: 9 | incw %ds:0x3B0108 10 | incl %ds:0x3B0008 11 | ret 12 | __hopper_reset_stmt_index: 13 | movw $0, %ds:0x3B0108 14 | movl $0, %ds:0x3B0008 15 | ret 16 | __hopper_last_stmt_index: 17 | movw $0xFFFF, %ds:0x3B0108 18 | ret 19 | __hopper_enable_cov: 20 | movl $0, %ds:0x3B0100 21 | ret 22 | __hopper_disable_cov: 23 | movl $0xFFFFFFFF, %ds:0x3B0100 24 | ret 25 | __hopper_set_context: 26 | movq %rdi, %ds:0x3B0110 27 | ret -------------------------------------------------------------------------------- /hopper-core/src/feedback/globl/llvm-globl.c: -------------------------------------------------------------------------------- 1 | 2 | #include "stdint.h" 3 | 4 | extern __thread uint32_t __hopper_prev_loc; // = 0xFFFFFFFF; 5 | extern __thread uint32_t __hopper_context; // = 0; 6 | extern uint32_t* __hopper_stmt_index_ptr; 7 | 8 | void __hopper_disable_cov() { 9 | __hopper_prev_loc = 0xFFFFFFFF; 10 | } 11 | 12 | void __hopper_enable_cov() { 13 | __hopper_prev_loc = 0x0; 14 | } 15 | 16 | void __hopper_set_context(uint32_t context) { 17 | __hopper_context = context; 18 | } 19 | 20 | void __hopper_inc_stmt_index() { 21 | uint32_t index = *__hopper_stmt_index_ptr; 22 | *__hopper_stmt_index_ptr = index + 1; 23 | } 24 | 25 | void __hopper_reset_stmt_index() { 26 | *__hopper_stmt_index_ptr = 0; 27 | } 28 | 29 | void __hopper_last_stmt_index() { 30 | *__hopper_stmt_index_ptr = 0xFFFF; 31 | } 32 | 33 | uint32_t __hopper_get_stmt_index() { 34 | return *__hopper_stmt_index_ptr; 35 | } 36 | 37 | void __hopper_branch_stub(uint32_t stub) { 38 | stub += 1; // do sth 39 | } 40 | -------------------------------------------------------------------------------- /hopper-core/src/feedback/globl/mod.rs: -------------------------------------------------------------------------------- 1 | // FFI for E9/LLVM globls 2 | #[cfg(any(feature = "e9_mode", feature = "llvm_mode"))] 3 | extern "C" { 4 | fn __hopper_enable_cov(); 5 | fn __hopper_disable_cov(); 6 | fn __hopper_set_context(ctx: u32); 7 | fn __hopper_inc_stmt_index(); 8 | fn __hopper_reset_stmt_index(); 9 | fn __hopper_last_stmt_index(); 10 | fn __hopper_get_stmt_index() -> u32; 11 | } 12 | 13 | #[inline] 14 | pub fn disable_coverage_feedback() { 15 | #[cfg(any(feature = "e9_mode", feature = "llvm_mode"))] 16 | unsafe { 17 | __hopper_disable_cov(); 18 | } 19 | } 20 | 21 | #[inline] 22 | pub fn enable_coverage_feedback() { 23 | #[cfg(any(feature = "e9_mode", feature = "llvm_mode"))] 24 | unsafe { 25 | __hopper_enable_cov(); 26 | } 27 | } 28 | 29 | #[inline] 30 | pub fn set_coverage_context(_ctx: u32) { 31 | #[cfg(any(feature = "e9_mode", feature = "llvm_mode"))] 32 | unsafe { 33 | __hopper_set_context(_ctx); 34 | } 35 | } 36 | 37 | #[inline] 38 | pub fn inc_rt_stmt_index() { 39 | #[cfg(any(feature = "e9_mode", feature = "llvm_mode"))] 40 | unsafe { 41 | __hopper_inc_stmt_index() 42 | } 43 | } 44 | 45 | #[inline] 46 | pub fn reset_rt_stmt_index() { 47 | #[cfg(any(feature = "e9_mode", feature = "llvm_mode"))] 48 | unsafe { 49 | __hopper_reset_stmt_index() 50 | } 51 | } 52 | 53 | #[inline] 54 | pub fn set_rt_last_stmt_index() { 55 | #[cfg(any(feature = "e9_mode", feature = "llvm_mode"))] 56 | unsafe { 57 | __hopper_last_stmt_index() 58 | } 59 | } 60 | 61 | // only for non e9 mode 62 | #[inline] 63 | pub fn current_stmt_index() -> u32 { 64 | #[cfg(not(feature = "llvm_mode"))] 65 | return 0_u32; 66 | #[cfg(feature = "llvm_mode")] 67 | unsafe { __hopper_get_stmt_index() } 68 | } 69 | -------------------------------------------------------------------------------- /hopper-core/src/feedback/globl/variadic.c: -------------------------------------------------------------------------------- 1 | // Some hooks that can't implemented in Rust 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | extern void __hopper_open_hook(u_int32_t, const char*, int); 10 | 11 | // int open(const char *pathname, int flags); 12 | // int open(const char *pathname, int flags, mode_t mode); 13 | int __hopper_open(const char* pathname, int flags, ...) { 14 | va_list ap; 15 | va_start(ap, flags); 16 | // mot_t mode = va_arg (args, int); 17 | int fd = open(pathname, flags, ap); 18 | va_end(ap); 19 | // u_int32_t id = (uintptr_t)__builtin_return_address(0) % 0xFFFFFFFF; 20 | u_int32_t id = 0; 21 | __hopper_open_hook(id, pathname, flags); 22 | return fd; 23 | } 24 | 25 | void* __hopper_open_fn = __hopper_open; 26 | 27 | #ifdef _WIN64 28 | 29 | #elif _WIN32 30 | 31 | #elif __APPLE__ 32 | 33 | #elif __linux 34 | int __hopper_open64(const char* pathname, int flags, ...) { 35 | va_list ap; 36 | va_start(ap, flags); 37 | // mot_t mode = va_arg (args, int); 38 | int fd = open64(pathname, flags, ap); 39 | va_end(ap); 40 | // u_int32_t id = (uintptr_t)__builtin_return_address(0) % 0xFFFFFFFFF; 41 | u_int32_t id = 0; 42 | __hopper_open_hook(id, pathname, flags); 43 | return fd; 44 | } 45 | void* __hopper_open64_fn = __hopper_open64; 46 | #endif 47 | -------------------------------------------------------------------------------- /hopper-core/src/feedback/mod.rs: -------------------------------------------------------------------------------- 1 | mod branches; 2 | mod cmp; 3 | pub mod globl; 4 | mod instr; 5 | mod mem; 6 | mod observer; 7 | mod ops; 8 | mod path; 9 | mod res; 10 | mod review; 11 | mod sanitize; 12 | mod hook; 13 | 14 | pub use branches::*; 15 | pub use cmp::*; 16 | pub use instr::*; 17 | pub use mem::*; 18 | pub use observer::*; 19 | pub use ops::*; 20 | pub use path::*; 21 | pub use res::*; 22 | pub use review::*; 23 | pub use sanitize::*; 24 | pub use hook::add_hooks; 25 | 26 | #[cfg(target_family = "unix")] 27 | mod shm; 28 | #[cfg(target_os = "windows")] 29 | mod shm_win; 30 | #[cfg(target_family = "unix")] 31 | pub use shm::*; 32 | #[cfg(target_os = "windows")] 33 | pub use shm_win::*; 34 | 35 | /// Feedback of program execution, including branch/coverage feedback 36 | pub struct Feedback { 37 | // executed edges 38 | pub path: SharedMemory, 39 | // executed instructions, e.g. cmp, malloc.. 40 | pub instrs: SharedMemory, 41 | } 42 | 43 | #[derive(Default, Debug)] 44 | pub struct FeedbackSummary { 45 | // micro secs 46 | pub time_used: u128, 47 | // path's length 48 | pub path_len: usize, 49 | // is it reach uniq new path 50 | pub num_uniq_path: usize, 51 | } 52 | 53 | pub static mut INSTR_LIST: *mut InstrList = std::ptr::null_mut(); 54 | 55 | pub fn get_instr_list<'a>() -> &'a InstrList { 56 | unsafe { &*INSTR_LIST } 57 | } 58 | 59 | pub fn get_instr_list_mut<'a>() -> &'a mut InstrList { 60 | unsafe { &mut *INSTR_LIST } 61 | } 62 | 63 | impl Feedback { 64 | pub fn new() -> eyre::Result { 65 | let feedback = Self { 66 | path: setup_shm()?, 67 | instrs: setup_shm()?, 68 | }; 69 | unsafe { 70 | INSTR_LIST = feedback.instrs.ptr; 71 | } 72 | Ok(feedback) 73 | } 74 | 75 | pub fn clear(&mut self) { 76 | self.path.clear(); 77 | self.instrs.inner_clear(); 78 | // mark share memory works! 79 | self.path.buf[0] = 1; 80 | // set func addr 81 | self.instrs.set_mem_fn(); 82 | } 83 | 84 | /// Get last stmt index 85 | pub fn last_stmt_index(&self) -> usize { 86 | self.instrs.last_stmt_index() 87 | } 88 | 89 | // Our path tracking find nothing (none edge) 90 | // the program exit before invoking the target function that we want to track 91 | pub fn track_nothing(&self) -> bool { 92 | self.path.get_list().len() <= 1 93 | } 94 | } 95 | 96 | pub trait SHMable { 97 | fn name() -> &'static str; 98 | fn shmid_env_var() -> &'static str; 99 | fn ptr_base() -> *const libc::c_void; 100 | fn buf_size() -> usize; 101 | fn post_hander(_ptr: *const u8) {} 102 | } 103 | -------------------------------------------------------------------------------- /hopper-core/src/feedback/observer.rs: -------------------------------------------------------------------------------- 1 | //! Observer, used to check feedback collected from program 2 | //! inlucing branch coverage, compare instructions/functions 3 | 4 | use eyre::Context; 5 | 6 | use crate::{execute::StatusType, BucketType, FuzzProgram, TimeUsage}; 7 | 8 | use super::*; 9 | 10 | pub struct Observer { 11 | // Feedback for current execution 12 | pub feedback: Feedback, 13 | // All branches our testcases visited 14 | pub branches_state: GlobalBranches, 15 | // Stat for operation 16 | pub op_stat: OperationStat, 17 | // Time usage 18 | pub usage: TimeUsage, 19 | } 20 | 21 | impl Observer { 22 | pub fn new() -> eyre::Result { 23 | Ok(Self { 24 | feedback: Feedback::new()?, 25 | branches_state: GlobalBranches::default(), 26 | op_stat: OperationStat::default(), 27 | usage: TimeUsage::default(), 28 | }) 29 | } 30 | 31 | /// Check if current execution has trigger new feedback or not? 32 | pub fn has_new_path(&mut self, status: StatusType) -> eyre::Result> { 33 | let _counter = self.usage.count(); 34 | let trace = self.feedback.path.get_list(); 35 | // crate::log!(trace, "find cov: {trace:?}"); 36 | let ret = self.branches_state.has_new(&trace, status); 37 | Ok(ret) 38 | } 39 | 40 | /// Check if current execution has trigger new unique path or not? 41 | pub fn get_new_uniq_path(&mut self, status: StatusType) -> Vec<(usize, BucketType)> { 42 | let _counter = self.usage.count(); 43 | let trace = self.feedback.path.get_list(); 44 | self.branches_state.has_new_uniq(&trace, status) 45 | } 46 | 47 | 48 | /// Merge the update list to global coverage 49 | pub fn merge_coverage(&mut self, update_list: &[(usize, BucketType)], status: StatusType) { 50 | let _counter = self.usage.count(); 51 | self.branches_state.merge_coverage(update_list, status); 52 | crate::log!(trace, "merge cov: {:?}", update_list); 53 | } 54 | 55 | /// Update cmp state and infer relationship between mutation operator and cmps 56 | pub fn infer_cmp(&mut self, program: &FuzzProgram) -> eyre::Result<()> { 57 | let _counter = self.usage.count(); 58 | self.feedback 59 | .instrs 60 | .associate_loc_with_cmp_instructions(program) 61 | .with_context(|| { 62 | format!( 63 | "fail to asscociate cmp, program:\n {}", 64 | program.serialize_all().unwrap() 65 | ) 66 | }) 67 | } 68 | 69 | /// Check if the program using `val` as fd 70 | pub fn contain_fd(&self, val: i32) -> (bool, bool) { 71 | let fd_list = self.feedback.instrs.get_fd_list(); 72 | let mut is_fd = false; 73 | let mut is_fd_read = false; 74 | for (fd, read) in fd_list { 75 | if fd == val { 76 | is_fd = true; 77 | if read { 78 | is_fd_read = true; 79 | } 80 | } 81 | } 82 | (is_fd, is_fd_read) 83 | } 84 | 85 | pub fn summary_feedback(&self, status: StatusType) -> FeedbackSummary { 86 | let mut sf = FeedbackSummary::default(); 87 | self.update_summary(&mut sf, status); 88 | sf 89 | } 90 | 91 | pub fn update_summary(&self, feedback: &mut FeedbackSummary, status: StatusType) { 92 | let path = self.feedback.path.get_list(); 93 | feedback.path_len = path.len(); 94 | feedback.num_uniq_path = self.branches_state.has_new_uniq(&path, status).len(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /hopper-core/src/feedback/res.rs: -------------------------------------------------------------------------------- 1 | //! Current resource state 2 | //! used in harness 3 | 4 | use super::*; 5 | use crate::runtime::*; 6 | 7 | #[cfg(target_os = "windows")] 8 | use std::collections::{BTreeMap as PtrMap, BTreeSet as PtrSet}; 9 | #[cfg(target_family = "unix")] 10 | use std::collections::{BTreeSet as PtrSet, HashMap as PtrMap}; 11 | 12 | /// States of memory resource 13 | #[derive(Default)] 14 | pub struct ResourceStates { 15 | /// used for review or not 16 | review_mode: bool, 17 | /// list of freed ptrs 18 | freed_ptrs: PtrSet<*mut u8>, 19 | /// memory object's size 20 | ptr_size_map: PtrMap<*mut u8, usize>, 21 | } 22 | 23 | impl ResourceStates { 24 | /// It is used for review 25 | pub fn set_review(&mut self) { 26 | self.review_mode = true; 27 | } 28 | 29 | /// Get size of ptr 30 | pub fn get_ptr_size(&self, ptr: *mut u8) -> Option { 31 | self.ptr_size_map.get(&ptr).copied() 32 | } 33 | 34 | /// Insert size for specific ptr 35 | pub fn insert_ptr_size(&mut self, ptr: *mut u8, size: usize) { 36 | self.ptr_size_map.insert(ptr, size); 37 | } 38 | 39 | /// Check pointers before filling them 40 | /// - check if pointer is freed 41 | #[inline] 42 | pub fn check_pointer(&self, ptr: *mut u8) -> eyre::Result<()> { 43 | if !ptr.is_null() { 44 | eyre::ensure!( 45 | !self.freed_ptrs.contains(&ptr), 46 | crate::HopperError::UseAfterFree { ptr } 47 | ); 48 | } 49 | Ok(()) 50 | } 51 | 52 | /// Check arguments 53 | /// - Check if any argument is freed 54 | pub fn check_arguments( 55 | &self, 56 | arg_indices: &[StmtIndex], 57 | stmts: &[IndexedStmt], 58 | ) -> eyre::Result<()> { 59 | for arg_i in arg_indices { 60 | let is = &stmts[arg_i.get()]; 61 | if let Some(value) = is.stmt.get_value() { 62 | let layout = value.get_layout(false); 63 | layout.check_ptr(self, 0)?; 64 | } 65 | } 66 | Ok(()) 67 | } 68 | 69 | /// Update pointers after call functions 70 | /// - Record which pointers are freed 71 | pub fn update_pointers_after_call(&mut self) -> eyre::Result<()> { 72 | if cfg!(test) { 73 | return Ok(()); 74 | } 75 | let instrs = get_instr_list(); 76 | let stmt_index = instrs.last_stmt_index() as u16; 77 | for op in instrs.mem_iter() { 78 | if op.stmt_index == stmt_index { 79 | crate::log!(trace, "mem op: {op:?} "); 80 | let ty = op.get_type(); 81 | // find out freed pointer 82 | match ty { 83 | MemType::Free | MemType::ReallocFree => { 84 | if op.addr == 0 { 85 | continue; 86 | } 87 | let ptr = op.addr as *mut u8; 88 | if !self.freed_ptrs.insert(ptr) && canary::is_in_canary(ptr) { 89 | // double free 90 | eyre::bail!(crate::HopperError::DoubleFree { ptr }); 91 | } 92 | self.ptr_size_map.insert(ptr, 0); 93 | } 94 | _ => {} 95 | } 96 | 97 | match ty { 98 | MemType::Malloc 99 | | MemType::Calloc 100 | | MemType::ReallocMalloc 101 | | MemType::ReallocResize => { 102 | let size = op.size as usize; 103 | if size > 0 { 104 | let ptr = op.addr as *mut u8; 105 | // store memory size (only for review mode) 106 | // if self.review_mode { 107 | self.ptr_size_map.insert(ptr, size); 108 | if self.freed_ptrs.contains(&ptr) { 109 | self.freed_ptrs.remove(&ptr); 110 | } 111 | } else { 112 | crate::log!(debug, "zero size memory record!"); 113 | } 114 | } 115 | _ => {} 116 | } 117 | } 118 | } 119 | Ok(()) 120 | } 121 | } 122 | 123 | #[test] 124 | fn test_check_resource() { 125 | let mut resource_states = ResourceStates::default(); 126 | let ptr = 123 as *mut u8; 127 | resource_states.freed_ptrs.insert(ptr); 128 | assert!(resource_states.check_pointer(ptr).is_err()); 129 | } 130 | -------------------------------------------------------------------------------- /hopper-core/src/feedback/shm_win.rs: -------------------------------------------------------------------------------- 1 | use super::SHMable; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | /// Shared memory, used for IPC communication between the fuzzer and testing targets. 5 | pub struct SharedMemory { 6 | /// SHMID 7 | pub handle: crate::execute::Handle, 8 | /// size of shared memory 9 | pub size: usize, 10 | /// content of shared memory 11 | pub ptr: *mut T, 12 | } 13 | 14 | impl SharedMemory { 15 | /// Create a shared memory at a proper location 16 | #[cfg(test)] 17 | pub fn new() -> eyre::Result { 18 | let ptr_base = crate::execute::NULL; 19 | let lp_name = "TEST".to_string(); 20 | Self::new_at(ptr_base, &lp_name) 21 | } 22 | 23 | /// Create a shared memory at specific location of process memory 24 | pub fn new_at(ptr_base: *mut std::os::raw::c_void, lp_name: &str) -> eyre::Result { 25 | let handle = 26 | crate::execute::hopper_create_file_mapping(0, 0x100000, lp_name.as_ptr() as u32)?; 27 | Self::from_id_at(handle, ptr_base) 28 | } 29 | 30 | /// Load shared memory by its SHMID at specific location of process memory 31 | fn from_id_at( 32 | handle: *mut std::os::raw::c_void, 33 | lp_addr: *mut std::os::raw::c_void, 34 | ) -> eyre::Result { 35 | let size = std::mem::size_of::() as usize; 36 | let ptr = match crate::execute::hopper_map_view_of_file_ex(handle, 0, 0, 0, lp_addr) { 37 | Ok(ptr) => ptr as *mut T, 38 | Err(_) => { 39 | // crate::execute::hopper_unmap_view_of_file(ptr_base as crate::execute::PVOID); 40 | // crate::execute::hopper_map_view_of_file_ex(id,0,0,0,ptr_base as *mut std::os::raw::c_void).unwrap() as *mut T 41 | // eyre::bail!("fail to setup shared memory!"); 42 | lp_addr as *mut T 43 | } 44 | }; 45 | Ok(SharedMemory:: { handle, size, ptr }) 46 | } 47 | 48 | /// Clear content at shared memory 49 | pub fn clear(&mut self) { 50 | unsafe { libc::memset(self.ptr as *mut libc::c_void, 0, self.size) }; 51 | } 52 | 53 | /// Get unique key for environment 54 | pub fn get_env_var(&self) -> String { 55 | "".to_string() 56 | } 57 | } 58 | 59 | pub fn setup_shm() -> eyre::Result> { 60 | log::info!("setup {} shm for e9 runtime...", T::name()); 61 | // let area_base = format!("{}_AREA_BASE\x00", crate::config::TASK_NAME); 62 | let lp_name = format!("{}_{}\x00", T::shmid_env_var(),crate::config::TASK_NAME); 63 | let shm = SharedMemory::::new_at(T::ptr_base() as *mut std::os::raw::c_void, &lp_name)?; 64 | log::info!("setup {} shared memory success ! shm: {:?}, lp_name: {}", T::name(), shm, lp_name); 65 | Ok(shm) 66 | } 67 | 68 | impl Deref for SharedMemory { 69 | type Target = T; 70 | fn deref(&self) -> &Self::Target { 71 | unsafe { &*self.ptr } 72 | } 73 | } 74 | 75 | impl DerefMut for SharedMemory { 76 | fn deref_mut(&mut self) -> &mut Self::Target { 77 | unsafe { &mut *self.ptr } 78 | } 79 | } 80 | 81 | impl std::fmt::Debug for SharedMemory { 82 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 83 | write!(f, "{:?}, {}, {:p}", self.handle, self.size, self.ptr) 84 | } 85 | } 86 | 87 | impl Drop for SharedMemory { 88 | fn drop(&mut self) { 89 | crate::execute::hopper_unmap_view_of_file(self.ptr as crate::execute::Pvoid).unwrap(); 90 | crate::execute::hopper_close_handle(self.handle).unwrap(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/constraints/context.rs: -------------------------------------------------------------------------------- 1 | use hopper_derive::Serde; 2 | 3 | #[derive(Debug, Clone, Serde, PartialEq, Eq)] 4 | pub struct CallContext { 5 | pub f_name: String, 6 | pub related_arg_pos: Option, 7 | pub kind: ContextKind 8 | } 9 | 10 | #[derive(Debug, Clone, Serde, PartialEq, Eq)] 11 | pub enum ContextKind { 12 | Required, 13 | Prefered, 14 | Forbidden 15 | } 16 | 17 | impl CallContext { 18 | pub fn from_rule(de: &mut crate::Deserializer) -> eyre::Result{ 19 | let related_arg_pos = if de.strip_token("-") || de.strip_token("*") { 20 | None 21 | } else { 22 | de.eat_token("$")?; 23 | let arg_i: usize = de.parse_number()?; 24 | Some(arg_i) 25 | }; 26 | de.eat_token("]")?; 27 | de.eat_token("<-")?; 28 | let mut kind = ContextKind::Required; 29 | let mut f_name = de.buf.trim().trim_end_matches(';'); 30 | if let Some(f) = f_name.strip_prefix('!') { 31 | f_name = f.trim(); 32 | kind = ContextKind::Forbidden; 33 | } else if let Some(f) = f_name.strip_suffix('?') { 34 | f_name = f.trim(); 35 | kind = ContextKind::Prefered; 36 | } 37 | Ok(Self { 38 | f_name: f_name.to_string(), 39 | related_arg_pos, 40 | kind, 41 | }) 42 | } 43 | 44 | pub fn is_required(&self) -> bool { 45 | matches!(self.kind, ContextKind::Required) 46 | } 47 | 48 | pub fn is_preferred(&self) -> bool { 49 | matches!(self.kind, ContextKind::Prefered) 50 | } 51 | 52 | pub fn is_forbidden(&self) -> bool { 53 | matches!(self.kind, ContextKind::Forbidden) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/constraints/internal.rule: -------------------------------------------------------------------------------- 1 | // used for generate FILE* 2 | func_internal fopen 3 | func fopen[$0] = $read_file; 4 | func fopen[$1] = $non_null; 5 | func fopen[$1] = "rb+"; 6 | type _IO_FILE = $opaque -------------------------------------------------------------------------------- /hopper-core/src/fuzz/constraints/ret.rs: -------------------------------------------------------------------------------- 1 | use hopper_derive::Serde; 2 | 3 | use crate::{global_gadgets, utils}; 4 | 5 | #[derive(Debug, Default, Clone, Serde)] 6 | pub struct RetType { 7 | // return is opaque pointer 8 | pub is_opaque: bool, 9 | // return is malloc'ed statically 10 | pub is_static: bool, 11 | // return pointer is unwriteable 12 | pub is_unwriteable: bool, 13 | // return pointer is partial opaque 14 | pub is_partial_opaque: bool, 15 | // the API is both consumer and producer of certain pointers. e.g. A* = f(A*) 16 | pub both_cosumer_and_producer: bool, 17 | } 18 | 19 | impl RetType { 20 | /// Check if the function's return can be used as arguments or fields 21 | #[inline] 22 | pub fn can_used_as_arg(&self) -> bool { 23 | !self.is_unwriteable 24 | && ((self.is_opaque && !self.both_cosumer_and_producer) 25 | || (!self.is_opaque && !self.is_static)) 26 | } 27 | 28 | /// Infer function return's kind: opaque or recursion 29 | pub fn infer(&mut self, f_name: &str) -> eyre::Result<()> { 30 | let fg = global_gadgets::get_instance().get_func_gadget(f_name)?; 31 | if let Some(ret_type) = fg.ret_type { 32 | if let Some(ret_inner) = utils::get_pointer_inner(ret_type) { 33 | if utils::is_opaque_type(ret_inner) { 34 | self.is_opaque = true; 35 | } 36 | let mut alias_ret_inner = ret_inner; 37 | if let Some(alias_ret_type) = fg.alias_ret_type { 38 | if let Some(inner) = utils::get_pointer_inner(alias_ret_type) { 39 | alias_ret_inner = inner; 40 | } 41 | } 42 | for (t, at) in fg.arg_types.iter().zip(fg.alias_arg_types.iter()) { 43 | let mut t = t; 44 | let mut r = ret_inner; 45 | // avoid recursion 46 | if utils::is_void_pointer(t) { 47 | t = at; 48 | r = alias_ret_inner; 49 | } 50 | if let Some(arg_inner) = utils::get_pointer_inner(t) { 51 | if arg_inner == r { 52 | self.both_cosumer_and_producer = true; 53 | break; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | Ok(()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/constraints/role.rs: -------------------------------------------------------------------------------- 1 | use hopper_derive::Serde; 2 | 3 | #[derive(Debug, Default, Clone, Serde)] 4 | pub struct FuncRole { 5 | // the function will init argument 6 | pub init_arg: bool, 7 | // the function will free argument 8 | pub free_arg: bool, 9 | } 10 | 11 | impl FuncRole { 12 | /// Check if the function's return can be used as arguments or fields 13 | #[inline] 14 | pub fn can_used_as_arg(&self) -> bool { 15 | !self.free_arg 16 | } 17 | } 18 | 19 | #[inline] 20 | pub fn filter_init_func(f_name: &str) -> bool { 21 | super::filter_function_constraint_with(f_name, |fc| fc.is_success() && fc.role.init_arg && !fc.role.free_arg) 22 | } 23 | 24 | #[inline] 25 | pub fn filter_free_func(f_name: &str) -> bool { 26 | super::filter_function_constraint_with(f_name, |fc| fc.role.free_arg) 27 | } -------------------------------------------------------------------------------- /hopper-core/src/fuzz/infer/context.rs: -------------------------------------------------------------------------------- 1 | //! Infer context that must not been used 2 | //! e.g API A must not be called before API B. 3 | 4 | use eyre::ContextCompat; 5 | 6 | use crate::{fuzz::*, fuzzer::*, runtime::*}; 7 | 8 | impl Fuzzer { 9 | /// Infer that the crash is due to certain relative/implicit calls 10 | pub fn infer_broken_contexts( 11 | &mut self, 12 | program: &FuzzProgram, 13 | ) -> eyre::Result> { 14 | let fail_at = program 15 | .get_fail_stmt_index() 16 | .context("fail to get fail index")? 17 | .get(); 18 | let target_call = if let Some(crash_func) = program.get_call_stmt(fail_at) { 19 | crash_func 20 | } else { 21 | return Ok(None); 22 | }; 23 | crate::log!(trace, "start infer broken contexts"); 24 | for index in (0..fail_at).rev() { 25 | let is = &program.stmts[index]; 26 | let FuzzStmt::Call(call) = &is.stmt else { 27 | continue; 28 | }; 29 | // we only consider impicit contexts 30 | if !call.is_implicit() { 31 | // call.is_relative() 32 | continue; 33 | } 34 | let mut p = program.clone(); 35 | p.delete_stmt(index); 36 | p.eliminate_invalidatd_contexts(); 37 | crate::log!( 38 | trace, 39 | "remove {}, program is: {}", 40 | is.index.get(), 41 | p.serialize()? 42 | ); 43 | let status = self.executor.execute_program(&p)?; 44 | if !status.is_normal() { 45 | continue; 46 | } 47 | let target_f_name = target_call.fg.f_name; 48 | let call_f_name = call.fg.f_name; 49 | let context = CallContext { 50 | f_name: call_f_name.to_string(), 51 | related_arg_pos: target_call.has_overlop_arg(program, call), 52 | kind: ContextKind::Forbidden, 53 | }; 54 | crate::log!(trace, "function {call_f_name} is likely to broken context"); 55 | if self.observer.op_stat.count_func_infer(call_f_name, program) { 56 | crate::inspect_function_constraint_mut_with(target_f_name, |fc| { 57 | fc.contexts.push(context.clone()); 58 | log_new_constraint(&format!( 59 | "add context on function `{target_f_name}`: {context:?}" 60 | )); 61 | Ok(()) 62 | })?; 63 | } 64 | // just for hints 65 | let sig = ConstraintSig { 66 | f_name: target_f_name.to_string(), 67 | arg_pos: 0, 68 | fields: LocFields::default(), 69 | constraint: Constraint::Context { context }, 70 | }; 71 | return Ok(Some(sig)); 72 | } 73 | Ok(None) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/infer/sigfpe.rs: -------------------------------------------------------------------------------- 1 | //! Infer non zero constraint for SIGFPE 2 | 3 | use crate::{fuzz::*, fuzzer::*, runtime::*, utils}; 4 | 5 | impl Fuzzer { 6 | /// Infer SIGFPE, find division by zero errors. 7 | pub fn infer_sigfpe( 8 | &mut self, 9 | program: &FuzzProgram, 10 | _call_i: usize, 11 | call_stmt: &CallStmt, 12 | stmt_index: &StmtIndex, 13 | load_state: &ObjectState, 14 | arg_pos: usize, 15 | prefix: LocFields, 16 | last_cmps: &[u32], 17 | ) -> eyre::Result> { 18 | crate::log!( 19 | trace, 20 | "infer division by zero error for sigfpe, stmt: {}!", 21 | stmt_index.get() 22 | ); 23 | // hash? 24 | let num_fields = 25 | load_state.find_fields_with(|s| utils::is_index_or_length_number(s.ty), true); 26 | for f in num_fields { 27 | // if it is zero 28 | let num_loc = Location::new(stmt_index.use_index(), f.clone()); 29 | let num = program.find_number_by_loc(num_loc.clone())?; 30 | if num == 0 { 31 | crate::log!(trace, "find zero number: {num_loc:?}"); 32 | let op = MutateOperator::new(num_loc, MutateOperation::IntSet { val: 1.into() }); 33 | let status = self.execute_with_op(program, &op, false)?; 34 | if !status.is_sigfpe() 35 | && self.observer.feedback.instrs.contain_cmp_chunks(last_cmps) 36 | { 37 | let full_f = prefix.with_suffix(f.clone()); 38 | let sig = add_function_constraint( 39 | call_stmt.fg.f_name, 40 | arg_pos, 41 | full_f, 42 | Constraint::NonZero, 43 | &format!("infer non_zero for sigfpe in crash {} #bug", program.id), 44 | ); 45 | return sig; 46 | } 47 | } 48 | } 49 | Ok(None) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/mod.rs: -------------------------------------------------------------------------------- 1 | //! Mutating module 2 | //! Code for how to generate or mutating a program 3 | 4 | pub mod constraints; 5 | mod det; 6 | pub mod effective; 7 | mod flag; 8 | mod generate; 9 | mod infer; 10 | mod minimize; 11 | mod mutate; 12 | mod object; 13 | mod operator; 14 | mod pcg; 15 | pub mod refine; 16 | mod rng; 17 | pub mod stmt; 18 | mod weight; 19 | mod check; 20 | mod find; 21 | 22 | pub use constraints::*; 23 | pub use det::*; 24 | pub use flag::*; 25 | pub use generate::*; 26 | pub use object::*; 27 | pub use operator::*; 28 | pub use rng::*; 29 | pub use weight::*; 30 | 31 | pub trait EnumKind { 32 | fn kind(&self) -> &'static str; 33 | } 34 | 35 | #[test] 36 | fn test_generate_and_mutate() { 37 | use crate::FuzzProgram; 38 | use crate::Serialize; 39 | 40 | fn gen_and_mutate(target: &str) { 41 | for _ in 0..100 { 42 | let (mut p1, mut p2) = { 43 | let mut program = FuzzProgram::generate_program_for_func(target).unwrap(); 44 | // make it like a seed 45 | program.parent = Some(0); 46 | println!("**p0**\n{}", program.serialize().unwrap()); 47 | let p1 = program.clone(); 48 | let p2 = program.clone(); 49 | (p1, p2) 50 | }; 51 | p1.mutate_program().unwrap(); 52 | println!("**p1**\n{}", p1.serialize().unwrap()); 53 | println!("**ops**: {}", p1.ops.serialize().unwrap()); 54 | 55 | p2.mutate_program_by_ops(&p1.ops).unwrap(); 56 | p2.refine_program().unwrap(); 57 | println!("**p2**\n{}", p2.serialize().unwrap()); 58 | 59 | assert_eq!(p1.serialize().unwrap(), p2.serialize().unwrap()) 60 | } 61 | } 62 | 63 | gen_and_mutate("func_add"); 64 | gen_and_mutate("func_create"); 65 | gen_and_mutate("func_use"); 66 | gen_and_mutate("func_struct"); 67 | } 68 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/object/bitfield.rs: -------------------------------------------------------------------------------- 1 | use crate::{HopperBindgenBitfieldUnit, ObjFuzzable}; 2 | 3 | use super::*; 4 | 5 | impl ObjGenerate for HopperBindgenBitfieldUnit { 6 | fn generate_new(state: &mut ObjectState) -> eyre::Result { 7 | let sub_state = state 8 | .add_child( 9 | FieldKey::Field("storage".to_string()), 10 | std::any::type_name::(), 11 | ) 12 | .last_child_mut()?; 13 | Ok(Self{ storage: Storage::generate_new(sub_state)? }) 14 | } 15 | } 16 | 17 | impl ObjMutate for HopperBindgenBitfieldUnit { 18 | fn mutate(&mut self, state: &mut ObjectState) -> eyre::Result { 19 | self.storage.mutate(state.last_child_mut()?) 20 | } 21 | 22 | fn mutate_by_op( 23 | &mut self, 24 | state: &mut ObjectState, 25 | keys: &[FieldKey], 26 | op: &MutateOperation, 27 | ) -> eyre::Result<()> { 28 | self.storage.mutate_by_op(state.last_child_mut()?, keys, op) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/object/corpus.rs: -------------------------------------------------------------------------------- 1 | //! Corpus for types, lists of special values. 2 | //! Copy from lain 3 | use super::ObjCorpus; 4 | 5 | static DANGEROUS_NUMBERS_U8: &[u8] = &[ 6 | 1, 7 | 0x10, 8 | 0x40, 9 | u8::MIN, // 0x00 10 | u8::MAX, // 0xff 11 | i8::MAX as u8, // 0x7f 12 | i8::MIN as u8, // 0x80 13 | ]; 14 | 15 | static DANGEROUS_NUMBERS_U16: &[u16] = &[ 16 | 1, 17 | 0x40, 18 | 0x0400, 19 | u8::MAX as u16, // 0xff 20 | i8::MAX as u16, // 0x7f 21 | // big-endian variants 22 | u16::MIN, // 0x0000 23 | u16::MAX, // 0xffff 24 | i16::MAX as u16, // 0x7fff 25 | i16::MIN as u16, // 0x8000 26 | // little-endian variants 27 | (i16::MAX as u16).swap_bytes(), // 0xff7f 28 | (i16::MIN as u16).swap_bytes(), // 0x0080 29 | ]; 30 | 31 | static DANGEROUS_NUMBERS_U32: &[u32] = &[ 32 | 1, 33 | 0x40, 34 | 0x0400, 35 | u8::MAX as u32, // 0xff 36 | i8::MAX as u32, // 0x7f 37 | u16::MAX as u32, // 0xffff 38 | i16::MAX as u32, // 0x7fff 39 | i16::MIN as u32, // 0x8000 40 | // big-endian variants 41 | u32::MIN, // 0x0000_0000 42 | u32::MAX, // 0xffff_ffff 43 | i32::MAX as u32, // 0x7fff_ffff 44 | i32::MIN as u32, // 0x8000_0000 45 | // little-endian variants 46 | (i32::MAX as u32).swap_bytes(), // 0xffff_ff7f 47 | (i32::MIN as u32).swap_bytes(), // 0x0000_0080 48 | ]; 49 | 50 | static DANGEROUS_NUMBERS_U64: &[u64] = &[ 51 | 1, 52 | 0x40, 53 | 0x0400, 54 | u8::MAX as u64, // 0xff 55 | i8::MAX as u64, // 0x7f 56 | u16::MAX as u64, // 0xffff 57 | i16::MAX as u64, // 0x7fff 58 | i16::MIN as u64, // 0x8000 59 | u32::MAX as u64, // 0xffff_ffff 60 | i32::MAX as u64, // 0x7fff_ffff 61 | i32::MIN as u64, // 0x8000_0000 62 | // big-endian variants 63 | u64::MIN, 64 | u64::MAX, 65 | i64::MAX as u64, 66 | i64::MIN as u64, 67 | // little-endian variants 68 | (i64::MAX as u64).swap_bytes(), // 0xffff_ffff_ffff_ff7f 69 | (i64::MIN as u64).swap_bytes(), // 0x0000_0000_0000_0080 70 | ]; 71 | 72 | static DANGEROUS_NUMBERS_USIZE: &[usize] = &[ 73 | 1, 74 | 0x40, 75 | 0x0400, 76 | u8::MAX as usize, // 0xff 77 | i8::MAX as usize, // 0x7f 78 | u16::MAX as usize, // 0xffff 79 | i16::MAX as usize, // 0x7fff 80 | i16::MIN as usize, // 0x8000 81 | #[cfg(target_pointer_width = "64")] 82 | 0x7fff_ffff, 83 | #[cfg(target_pointer_width = "64")] 84 | 0x8000_0000, 85 | #[cfg(target_pointer_width = "64")] 86 | 0xffff_ffff, 87 | // big-endian variants 88 | usize::MIN, 89 | usize::MAX, 90 | isize::MAX as usize, 91 | isize::MIN as usize, 92 | // little-endian variants 93 | (isize::MAX as usize).swap_bytes(), 94 | (isize::MIN as usize).swap_bytes(), 95 | ]; 96 | 97 | static DANGEROUS_NUMBERS_F32: &[f32] = &[ 98 | f32::INFINITY, 99 | f32::MAX, 100 | f32::MIN, 101 | f32::MIN_POSITIVE, 102 | f32::NAN, 103 | f32::NEG_INFINITY, 104 | ]; 105 | 106 | static DANGEROUS_NUMBERS_F64: &[f64] = &[ 107 | f64::INFINITY, 108 | f64::MAX, 109 | f64::MIN, 110 | f64::MIN_POSITIVE, 111 | f64::NAN, 112 | f64::NEG_INFINITY, 113 | ]; 114 | 115 | macro_rules! impl_corpus { 116 | ( $ty:ident, $corpus:ident ) => { 117 | impl ObjCorpus for $ty { 118 | fn corpus_size() -> usize { 119 | $corpus.len() 120 | } 121 | 122 | fn get_interesting_value(index: usize) -> Option { 123 | Some($corpus[index] as $ty) 124 | } 125 | } 126 | }; 127 | } 128 | 129 | impl_corpus!(u8, DANGEROUS_NUMBERS_U8); 130 | impl_corpus!(i8, DANGEROUS_NUMBERS_U8); 131 | impl_corpus!(u16, DANGEROUS_NUMBERS_U16); 132 | impl_corpus!(i16, DANGEROUS_NUMBERS_U16); 133 | impl_corpus!(u32, DANGEROUS_NUMBERS_U32); 134 | impl_corpus!(i32, DANGEROUS_NUMBERS_U32); 135 | impl_corpus!(u64, DANGEROUS_NUMBERS_U64); 136 | impl_corpus!(i64, DANGEROUS_NUMBERS_U64); 137 | impl_corpus!(f32, DANGEROUS_NUMBERS_F32); 138 | impl_corpus!(f64, DANGEROUS_NUMBERS_F64); 139 | impl_corpus!(u128, DANGEROUS_NUMBERS_U64); 140 | impl_corpus!(i128, DANGEROUS_NUMBERS_U64); 141 | impl_corpus!(usize, DANGEROUS_NUMBERS_USIZE); 142 | impl_corpus!(isize, DANGEROUS_NUMBERS_USIZE); 143 | 144 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/object/fn_pointer.rs: -------------------------------------------------------------------------------- 1 | //! Generate and mutatte fn pointer 2 | 3 | use super::*; 4 | use crate::{global_gadgets, runtime::fn_pointer::cast_fn_pointer, FnFuzzable, FnSignature, fn_pointer::cast_canary_fn_pointer}; 5 | use std::fmt::Debug; 6 | 7 | impl ObjGenerate for T { 8 | /// Choose any function from gadgets 9 | fn generate_new(state: &mut ObjectState) -> eyre::Result { 10 | let (func_name, f) = choose_function_pointer::()?; 11 | cast_fn_pointer(func_name, f, state) 12 | } 13 | } 14 | 15 | impl ObjMutate for T { 16 | /// Re-Choose any function from gadgets 17 | fn mutate(&mut self, state: &mut ObjectState) -> eyre::Result { 18 | let op = if let Ok((func_name, f)) = choose_function_pointer::() { 19 | *self = cast_fn_pointer(func_name, f, state)?; 20 | MutateOperation::FnPointer { 21 | f_name: func_name.into(), 22 | } 23 | } else { 24 | MutateOperation::Nop 25 | }; 26 | Ok(state.as_mutate_operator(op)) 27 | } 28 | fn mutate_by_op( 29 | &mut self, 30 | state: &mut ObjectState, 31 | keys: &[FieldKey], 32 | op: &MutateOperation, 33 | ) -> eyre::Result<()> { 34 | match op { 35 | MutateOperation::FnPointer { f_name } => { 36 | let fg = global_gadgets::get_instance().get_func_gadget(f_name)?; 37 | if T::arg_type_names() != fg.arg_types || T::ret_type_name() != fg.ret_type { 38 | eyre::bail!("function pointer type mismatch"); 39 | } 40 | *self = cast_fn_pointer(fg.f_name, fg.f, state)?; 41 | } 42 | MutateOperation::PointerCanary => { 43 | *self = cast_canary_fn_pointer(state); 44 | } 45 | _ => { 46 | eyre::bail!("fail to mutate pointer, keys: {keys:?}, op: {op:?}"); 47 | } 48 | } 49 | Ok(()) 50 | } 51 | } 52 | 53 | /// Random choose function from existing gadgets, 54 | /// which should has the same signature with `T` 55 | fn choose_function_pointer( 56 | ) -> eyre::Result<(&'static String, &'static dyn FnFuzzable)> { 57 | let cands = global_gadgets::get_instance() 58 | .functions 59 | .iter() 60 | .filter(|(f_name, fg)| { 61 | crate::filter_fn_pointer(f_name) 62 | && T::arg_type_names() == fg.arg_types 63 | && T::ret_type_name() == fg.ret_type 64 | }); 65 | rng::choose_iter(cands) 66 | .map(|(name, fg)| (name, fg.f)) 67 | .ok_or_else(|| { 68 | // spefic error for telling the option warpper should be None 69 | crate::log!(trace, "fail to find a function pointer"); 70 | eyre::eyre!(crate::HopperError::NullFuncionPointer) 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/object/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{FieldKey, ObjType, ObjectState}; 3 | 4 | /// Trait for creating new object that generate form nothing 5 | pub trait ObjGenerate: Clone + ObjType { 6 | /// Generate a totally new object 7 | fn generate_new(state: &mut ObjectState) -> eyre::Result; 8 | } 9 | 10 | /// Trait for mutating object 11 | pub trait ObjMutate { 12 | /// Mutate object itself 13 | fn mutate( 14 | &mut self, 15 | state: &mut ObjectState, 16 | ) -> eyre::Result; 17 | /// Deterministic mutate 18 | fn det_mutate( 19 | &mut self, 20 | state: &mut ObjectState, 21 | ) -> eyre::Result { 22 | state.done_deterministic(); 23 | Ok(MutateOperator::nop()) 24 | } 25 | /// Mutate object by operator 26 | fn mutate_by_op( 27 | &mut self, 28 | state: &mut ObjectState, 29 | keys: &[FieldKey], 30 | op: &MutateOperation, 31 | ) -> eyre::Result<()>; 32 | } 33 | 34 | /// Corpus of interesting values 35 | pub trait ObjCorpus: Sized { 36 | /// Size of corpus 37 | fn corpus_size() -> usize { 38 | 0 39 | } 40 | /// Get interesting value in corpus 41 | fn get_interesting_value(_index: usize) -> Option { 42 | None 43 | } 44 | } 45 | 46 | mod corpus; 47 | mod fn_pointer; 48 | mod number; 49 | mod option; 50 | pub mod pointer; 51 | pub mod seq; 52 | mod void; 53 | pub mod buf; 54 | mod bitfield; -------------------------------------------------------------------------------- /hopper-core/src/fuzz/object/option.rs: -------------------------------------------------------------------------------- 1 | //! Generate and mutate option 2 | 3 | use crate::{config, FieldKey, HopperError, ObjFuzzable}; 4 | 5 | use super::*; 6 | 7 | /// Generate data inside option 8 | fn generate_inner(state: &mut ObjectState) -> eyre::Result> { 9 | match T::generate_new(state) { 10 | Ok(obj) => Ok(Some(obj)), 11 | Err(err) => { 12 | // For fn pointer, if we can't find any valid fn, it should be NONE 13 | if let Some(HopperError::NullFuncionPointer) = err.downcast_ref::() { 14 | return Ok(None); 15 | } 16 | Err(err) 17 | } 18 | } 19 | } 20 | 21 | impl ObjGenerate for Option { 22 | fn generate_new(state: &mut ObjectState) -> eyre::Result { 23 | state.done_deterministic_itself(); 24 | let sub_state = state 25 | .add_child(FieldKey::Option, std::any::type_name::>()) 26 | .last_child_mut()?; 27 | if !config::ENABLE_SET_FN_POINTER { 28 | let _ = state.replace_weight(0); 29 | return Ok(None); 30 | } 31 | if rng::mostly() && !flag::is_pilot_det() { 32 | return Ok(None); 33 | } 34 | generate_inner(sub_state) 35 | } 36 | } 37 | 38 | impl ObjMutate for Option { 39 | fn mutate(&mut self, state: &mut ObjectState) -> eyre::Result { 40 | if !config::ENABLE_SET_FN_POINTER { 41 | return Ok(MutateOperator::nop()); 42 | } 43 | let op = if let Some(val) = self { 44 | if rng::likely() { 45 | *self = None; 46 | state.last_child_mut()?.pointer.take(); 47 | MutateOperation::OptionNone 48 | } else { 49 | return val.mutate(state.last_child_mut()?); 50 | } 51 | } else { 52 | // None 53 | let rng_state = rng::save_rng_state(); 54 | *self = generate_inner(state.last_child_mut()?)?; 55 | MutateOperation::OptionNew { rng_state } 56 | }; 57 | Ok(state.as_mutate_operator(op)) 58 | } 59 | 60 | fn mutate_by_op( 61 | &mut self, 62 | state: &mut ObjectState, 63 | keys: &[FieldKey], 64 | op: &MutateOperation, 65 | ) -> eyre::Result<()> { 66 | // crate::log!(trace, "state: {:?}", state); 67 | match op { 68 | MutateOperation::PointerGen { rng_state } 69 | | MutateOperation::OptionNew { rng_state } => { 70 | let _tmp_rng = rng::TempRngGuard::temp_use(rng_state); 71 | *self = generate_inner(state.last_child_mut()?)?; 72 | } 73 | MutateOperation::OptionNone | MutateOperation::PointerNull => { 74 | if self.is_none() { 75 | flag::set_refine_suc(false); 76 | } else { 77 | *self = None; 78 | state.last_child_mut()?.pointer.take(); 79 | } 80 | } 81 | MutateOperation::FnPointer { f_name: _ } => { 82 | eyre::ensure!(keys[0] == FieldKey::Option, "key should be option!"); 83 | if self.is_none() { 84 | *self = generate_inner(state.last_child_mut()?)?; 85 | } 86 | if let Some(val) = self { 87 | val.mutate_by_op(state.last_child_mut()?, &keys[1..], op)?; 88 | } 89 | } 90 | MutateOperation::PointerCanary => { 91 | if self.is_none() { 92 | *self = generate_inner(state.last_child_mut()?)?; 93 | } 94 | if let Some(val) = self { 95 | val.mutate_by_op(state.last_child_mut()?, keys, op)?; 96 | } 97 | } 98 | _ => { 99 | eyre::bail!("keys: {:?}, op: {:?}", keys, op); 100 | } 101 | } 102 | Ok(()) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/object/void.rs: -------------------------------------------------------------------------------- 1 | //! Generate and mutate void 2 | //! void is uint, and we do nothing 3 | 4 | use crate::{FuzzVoid, RetVoid}; 5 | 6 | use super::*; 7 | 8 | macro_rules! impl_void { 9 | ($void:ident) => { 10 | impl ObjGenerate for $void { 11 | fn generate_new( state: &mut ObjectState) -> eyre::Result { 12 | let _ = state.replace_weight(0); 13 | state.done_deterministic_itself(); 14 | Ok(Self::default()) 15 | } 16 | } 17 | 18 | impl ObjMutate for $void { 19 | fn mutate( 20 | &mut self, 21 | state: &mut ObjectState, 22 | ) -> eyre::Result { 23 | if state.is_deterministic() { 24 | state.done_deterministic(); 25 | } 26 | Ok(state.as_mutate_operator(MutateOperation::Nop)) 27 | } 28 | fn mutate_by_op( 29 | &mut self, 30 | _state: &mut ObjectState, 31 | keys: &[FieldKey], 32 | op: &MutateOperation, 33 | ) -> eyre::Result<()> { 34 | if !keys.is_empty() { 35 | unimplemented!() 36 | } 37 | match op { 38 | MutateOperation::Nop => {} 39 | _ => { 40 | unimplemented!(); 41 | } 42 | } 43 | Ok(()) 44 | } 45 | } 46 | }; 47 | } 48 | 49 | impl_void!(FuzzVoid); 50 | impl_void!(RetVoid); 51 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/rng.rs: -------------------------------------------------------------------------------- 1 | use rand::{ 2 | distributions::uniform::{SampleRange, SampleUniform}, 3 | prelude::*, 4 | }; 5 | use std::cell::RefCell; 6 | 7 | use super::pcg::Pcg32; 8 | 9 | pub type RngState = Pcg32; 10 | 11 | thread_local! { 12 | pub static RNG: RefCell = RefCell::new(Pcg32::seed_from_u64(rand::random())); 13 | } 14 | 15 | #[inline] 16 | pub fn save_rng_state() -> RngState { 17 | RNG.with(|cell| cell.borrow().clone()) 18 | } 19 | 20 | #[inline] 21 | pub fn restore_rng_state(rng: RngState) { 22 | RNG.with(|cell| cell.replace(rng)); 23 | } 24 | 25 | #[inline] 26 | pub fn renew_rng_state() { 27 | RNG.with(|cell| cell.replace(Pcg32::seed_from_u64(rand::random()))); 28 | } 29 | 30 | pub fn gen_rng_state() -> RngState { 31 | Pcg32::seed_from_u64(rand::random()) 32 | } 33 | 34 | #[inline] 35 | pub fn gen_range(range: R) -> T 36 | where 37 | T: SampleUniform, 38 | R: SampleRange, 39 | { 40 | RNG.with(|cell| cell.borrow_mut().gen_range(range)) 41 | } 42 | 43 | #[inline] 44 | pub fn prob(p: f64) -> bool { 45 | RNG.with(|cell| cell.borrow_mut().gen_bool(p)) 46 | } 47 | 48 | #[inline] 49 | pub fn mostly() -> bool { 50 | prob(0.90) 51 | } 52 | 53 | #[inline] 54 | pub fn likely() -> bool { 55 | prob(0.65) 56 | } 57 | 58 | #[inline] 59 | pub fn coin() -> bool { 60 | prob(0.50) 61 | } 62 | 63 | #[inline] 64 | pub fn unlikely() -> bool { 65 | prob(0.35) 66 | } 67 | 68 | #[inline] 69 | pub fn rarely() -> bool { 70 | prob(0.10) 71 | } 72 | 73 | #[inline] 74 | pub fn gen() -> T 75 | where 76 | rand::distributions::Standard: Distribution, 77 | { 78 | RNG.with(|cell| cell.borrow_mut().gen()) 79 | } 80 | 81 | pub fn choose_slice(list: &[T]) -> Option<&T> { 82 | RNG.with(|cell| list.choose(&mut *cell.borrow_mut())) 83 | } 84 | 85 | pub fn choose_iter(list: T) -> Option { 86 | RNG.with(|cell| list.choose(&mut *cell.borrow_mut())) 87 | } 88 | 89 | pub fn choose_multiple(list: T, amount: usize) -> Vec { 90 | RNG.with(|cell| list.choose_multiple(&mut *cell.borrow_mut(), amount)) 91 | } 92 | 93 | pub fn shuffle(list: &mut [T]) { 94 | RNG.with(|cell| list.shuffle(&mut *cell.borrow_mut())) 95 | } 96 | 97 | pub fn cond_likely(cond: bool) -> bool { 98 | if cond { 99 | likely() 100 | } else { 101 | coin() 102 | } 103 | } 104 | pub struct TempRngGuard { 105 | cur: RngState, 106 | } 107 | 108 | impl TempRngGuard { 109 | pub fn temp_use(rng: &RngState) -> Self { 110 | RNG.with(|cell| { 111 | let cur = cell.borrow().clone(); 112 | cell.replace(rng.clone()); 113 | Self { cur } 114 | }) 115 | } 116 | } 117 | 118 | impl Drop for TempRngGuard { 119 | fn drop(&mut self) { 120 | restore_rng_state(self.cur.clone()); 121 | } 122 | } 123 | 124 | #[test] 125 | fn test_tmp_rng_guard() { 126 | let rng_cur = save_rng_state(); 127 | let new_rng = gen_rng_state(); 128 | { 129 | let _tmp_rng = TempRngGuard::temp_use(&new_rng); 130 | assert_eq!(new_rng, save_rng_state()); 131 | } 132 | assert_eq!(rng_cur, save_rng_state()); 133 | } 134 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/stmt/file.rs: -------------------------------------------------------------------------------- 1 | //! Mutate File statement 2 | 3 | use super::*; 4 | 5 | impl WeightedItem for FileStmt {} 6 | 7 | impl StmtMutate for FileStmt {} 8 | 9 | impl FileStmt { 10 | pub fn generate_new( 11 | program: &mut FuzzProgram, 12 | ident: &str, 13 | is_mut: bool, 14 | is_fd: bool, 15 | may_read: bool, 16 | depth: usize, 17 | ) -> eyre::Result { 18 | let mut file_stmt = FileStmt::new(ident, is_mut, is_fd); 19 | if may_read { 20 | let _tmp = flag::ReuseStmtGuard::temp_disable(); 21 | let load = LoadStmt::generate_vec( program, "u8", "file_buf", depth)?; 22 | let index = program.insert_or_append_stmt(load)?; 23 | file_stmt.set_buf_index(index); 24 | } 25 | Ok(file_stmt) 26 | } 27 | } 28 | 29 | /// Just some code to make FileFd to be Fuzzable 30 | impl ObjGenerate for FileFd { 31 | fn generate_new(_state: &mut ObjectState) -> eyre::Result { 32 | Ok(Default::default()) 33 | } 34 | } 35 | 36 | impl ObjMutate for FileFd { 37 | fn mutate(&mut self, _state: &mut ObjectState) -> eyre::Result { 38 | Ok(MutateOperator::nop()) 39 | } 40 | 41 | fn mutate_by_op( 42 | &mut self, 43 | _state: &mut ObjectState, 44 | _keys: &[FieldKey], 45 | _op: &MutateOperation, 46 | ) -> eyre::Result<()> { 47 | Ok(()) 48 | } 49 | } -------------------------------------------------------------------------------- /hopper-core/src/fuzz/stmt/load.rs: -------------------------------------------------------------------------------- 1 | //! Mutate Load statement 2 | 3 | use super::*; 4 | 5 | impl WeightedItem for LoadStmt { 6 | fn get_weight(&self) -> usize { 7 | self.state.mutate.borrow().get_weight() 8 | } 9 | } 10 | 11 | impl StmtMutate for LoadStmt { 12 | fn is_deterministic(&self) -> bool { 13 | self.state.is_deterministic() 14 | } 15 | 16 | fn is_incompatible(&self, op: &MutateOperator) -> bool { 17 | matches!( 18 | op.op, 19 | MutateOperation::PointerTodo 20 | | MutateOperation::PointerNull 21 | | MutateOperation::PointerGen { rng_state: _ } 22 | | MutateOperation::PointerUse { loc: _ } 23 | | MutateOperation::PointerRet { 24 | f_name: _, 25 | rng_state: _ 26 | } 27 | | MutateOperation::UnionNew { rng_state: _ } 28 | ) 29 | } 30 | 31 | fn mutate(&mut self, program: &mut FuzzProgram) -> eyre::Result { 32 | let mut op = self.value.mutate(&mut self.state)?; 33 | if !program.ops.is_empty() && self.is_incompatible(&op) { 34 | if op.op.is_pointer_todo() { 35 | return Ok(MutateOperator::nop()); 36 | } 37 | return Ok(op); 38 | } 39 | if op.op.is_pointer_todo() { 40 | op.op = pointer::mutate_pointer_location( 41 | program, 42 | &mut self.state, 43 | op.key.fields.as_slice(), 44 | )?; 45 | } 46 | Ok(op) 47 | } 48 | 49 | fn det_mutate(&mut self, _program: &mut FuzzProgram) -> eyre::Result { 50 | // crate::log!(trace, "det mutate: {:?}", self.value); 51 | self.value.det_mutate(&mut self.state) 52 | } 53 | 54 | fn mutate_by_op( 55 | &mut self, 56 | program: &mut FuzzProgram, 57 | keys: &[FieldKey], 58 | op: &MutateOperation, 59 | ) -> eyre::Result<()> { 60 | if op.is_nop() { 61 | return Ok(()); 62 | } 63 | flag::set_mutate_ptr(false); 64 | let state = &mut self.state; 65 | let keys = crate::check_fields(keys, state); 66 | self.value.mutate_by_op(state, keys, op)?; 67 | if flag::is_mutate_ptr() { 68 | pointer::mutate_pointer_location_by_op(program, state, keys, op)?; 69 | } 70 | Ok(()) 71 | } 72 | } 73 | 74 | impl LoadStmt { 75 | /// Generate variable for specific type 76 | pub fn generate_new( 77 | program: &mut FuzzProgram, 78 | type_name: &str, 79 | ident: &str, 80 | depth: usize, 81 | ) -> eyre::Result { 82 | let mut state = LoadStmt::new_state(ident, type_name); 83 | let value = global_gadgets::get_instance() 84 | .get_object_builder(type_name)? 85 | .generate_new(&mut state)?; 86 | pointer::generate_pointer_location(program, &mut state, depth)?; 87 | let load = LoadStmt::new(value, state); 88 | Ok(load) 89 | } 90 | 91 | /// Generate vector statement for specific type 92 | pub fn generate_vec( 93 | program: &mut FuzzProgram, 94 | type_name: &str, 95 | ident: &str, 96 | depth: usize, 97 | ) -> eyre::Result { 98 | let mut state = 99 | LoadStmt::new_state(ident, format!("alloc::vec::Vec<{type_name}>").as_str()); 100 | let value = global_gadgets::get_instance() 101 | .get_object_builder(type_name)? 102 | .generate_vec(&mut state)?; 103 | pointer::generate_pointer_location(program, &mut state, depth)?; 104 | let load = LoadStmt::new(value, state); 105 | Ok(load) 106 | } 107 | 108 | /// Generate load statement without filling pointer 109 | fn generate_new_without_filling_pointer(type_name: &str, ident: &str) -> eyre::Result { 110 | let mut state = LoadStmt::new_state(ident, type_name); 111 | let value = global_gadgets::get_instance() 112 | .get_object_builder(type_name)? 113 | .generate_new(&mut state)?; 114 | let load = LoadStmt::new(value, state); 115 | Ok(load) 116 | } 117 | 118 | /// Generate fixed load statement, and the pointers are null in default. 119 | pub fn generate_constant(type_name: &str, ident: &str) -> eyre::Result { 120 | let mut load_stmt = Self::generate_new_without_filling_pointer(type_name, ident)?; 121 | load_stmt.is_const = true; 122 | load_stmt.state.replace_weight(0); 123 | Ok(load_stmt) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /hopper-core/src/fuzz/stmt/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{impl_stmt_match, runtime::*}; 2 | use super::*; 3 | 4 | pub trait StmtMutate: WeightedItem { 5 | /// Is deterministic or not 6 | fn is_deterministic(&self) -> bool { 7 | false 8 | } 9 | // Is incompatible or not 10 | fn is_incompatible(&self, _op: &MutateOperator) -> bool { 11 | false 12 | } 13 | /// Mutate the statement 14 | fn mutate(&mut self, _program: &mut FuzzProgram) -> eyre::Result { 15 | unimplemented!() 16 | } 17 | /// Det mutate 18 | fn det_mutate(&mut self, _program: &mut FuzzProgram) -> eyre::Result { 19 | unimplemented!(); 20 | } 21 | /// Mutate by op 22 | fn mutate_by_op( 23 | &mut self, 24 | _program: &mut FuzzProgram, 25 | _keys: &[FieldKey], 26 | _op: &MutateOperation, 27 | ) -> eyre::Result<()> { 28 | unimplemented!() 29 | } 30 | } 31 | 32 | impl FuzzStmt { 33 | pub fn is_deterministic(&self) -> bool { 34 | impl_stmt_match!(self, is_deterministic) 35 | } 36 | pub fn is_incompatible(&self, op: &MutateOperator) -> bool { 37 | impl_stmt_match!(self, is_incompatible(op)) 38 | } 39 | pub fn mutate(&mut self, program: &mut FuzzProgram) -> eyre::Result { 40 | impl_stmt_match!(self, mutate(program)) 41 | } 42 | pub fn det_mutate(&mut self, program: &mut FuzzProgram) -> eyre::Result { 43 | impl_stmt_match!(self, det_mutate(program)) 44 | } 45 | pub fn mutate_by_op( 46 | &mut self, 47 | program: &mut FuzzProgram, 48 | keys: &[FieldKey], 49 | op: &MutateOperation, 50 | ) -> eyre::Result<()> { 51 | impl_stmt_match!(self, mutate_by_op(program, keys, op)) 52 | } 53 | } 54 | 55 | impl WeightedItem for FuzzStmt { 56 | fn get_weight(&self) -> usize { 57 | impl_stmt_match!(self, get_weight) 58 | } 59 | } 60 | 61 | impl WeightedItem for IndexedStmt { 62 | fn get_weight(&self) -> usize { 63 | self.stmt.get_weight() 64 | } 65 | } 66 | 67 | mod assert; 68 | mod call; 69 | mod file; 70 | mod load; 71 | mod update; 72 | 73 | pub use assert::*; -------------------------------------------------------------------------------- /hopper-core/src/runtime/mod.rs: -------------------------------------------------------------------------------- 1 | //! Runtime module 2 | //! includes IR of functions, calls, variables .. of programs, 3 | //! how they wil be executed(eval), 4 | //! their trais for serde, fuzz(new, mutating).., 5 | //! and stored them as gadgets 6 | mod func; 7 | #[macro_use] 8 | mod object; 9 | mod stmt; 10 | 11 | mod gadgets; 12 | mod loc; 13 | mod program; 14 | mod serde; 15 | mod translate; 16 | 17 | use std::marker::PhantomData; 18 | 19 | pub use func::*; 20 | pub use gadgets::*; 21 | pub use loc::*; 22 | pub use object::*; 23 | pub use program::*; 24 | pub use serde::*; 25 | pub use stmt::*; 26 | pub use translate::*; 27 | /// Custom void type 28 | #[repr(transparent)] 29 | #[derive(Debug, Default)] 30 | pub struct FuzzVoid(u8); // just hold something useless 31 | 32 | /// Mutatable pointer: *mut 33 | #[repr(transparent)] 34 | #[derive(Debug)] 35 | pub struct FuzzMutPointer(*mut T); 36 | 37 | /// Const pointer: *const 38 | /// 39 | /// We use *mut as inner data since it will be mutated by our fuzzer 40 | #[repr(transparent)] 41 | #[derive(Debug)] 42 | pub struct FuzzConstPointer(*mut T); 43 | 44 | /// FrozenPointer: Always be NULL 45 | /// 46 | /// We use this kind of pointer to hold filtered function pointer type 47 | #[repr(transparent)] 48 | pub struct FuzzFrozenPointer(*const u8, PhantomData); -------------------------------------------------------------------------------- /hopper-core/src/runtime/object/builder.rs: -------------------------------------------------------------------------------- 1 | //! FuzzObjectBuilder is used to generate objects at runtime by theit types, 2 | //! The builds will be stored at global gadgets for usage at anywhere. 3 | 4 | use std::{marker::PhantomData, collections::HashMap}; 5 | 6 | use crate::{fuzz::*, runtime::*}; 7 | 8 | /// Hold the type of objects 9 | pub struct FuzzTypeHolder(PhantomData); 10 | 11 | /// Use for build new objects by its type, it is a wrapper of holder 12 | pub type FuzzObjectBuilder = Box; 13 | 14 | unsafe impl Sync for FuzzTypeHolder {} 15 | unsafe impl Send for FuzzTypeHolder {} 16 | 17 | /// Trait for build new objects by its type at runtime 18 | pub trait DynBuildFuzzObject: Sync + Send { 19 | /// Desrialize the value the trait hold 20 | fn deserialize(&self, de: &mut Deserializer, state: &mut ObjectState) -> eyre::Result; 21 | /// Desrialize a vector of the value that the trait hold 22 | fn deserialize_vec(&self, de: &mut Deserializer, state: &mut ObjectState) 23 | -> eyre::Result; 24 | /// Generate the value the trait hold 25 | fn generate_new(&self, state: &mut ObjectState) -> eyre::Result; 26 | /// Generate a vector of the value that the trait hold 27 | fn generate_vec(&self, state: &mut ObjectState) -> eyre::Result; 28 | /// Get size of Object 29 | fn mem_size(&self) -> usize; 30 | /// Is opaque 31 | fn is_opaque(&self) -> bool; 32 | /// fields ty 33 | fn get_fields_ty(&self) -> HashMap; 34 | } 35 | 36 | impl FuzzTypeHolder { 37 | pub fn builder() -> FuzzObjectBuilder { 38 | Box::new(Self(PhantomData)) 39 | } 40 | pub fn as_builder(self) -> FuzzObjectBuilder { 41 | Box::new(self) 42 | } 43 | } 44 | 45 | impl DynBuildFuzzObject for FuzzTypeHolder { 46 | fn deserialize(&self, de: &mut Deserializer, state: &mut ObjectState) -> eyre::Result { 47 | let val = T::deserialize_obj(de, state)?; 48 | Ok(Box::new(val)) 49 | } 50 | fn deserialize_vec( 51 | &self, 52 | de: &mut Deserializer, 53 | state: &mut ObjectState, 54 | ) -> eyre::Result { 55 | if de.canary { 56 | Ok(Box::new(CanarySlice::::deserialize_obj(de, state)?)) 57 | } else { 58 | Ok(Box::new(Vec::::deserialize_obj(de, state)?)) 59 | } 60 | } 61 | 62 | fn generate_new(&self, state: &mut ObjectState) -> eyre::Result { 63 | Ok(Box::new(T::generate_new( state)?)) 64 | } 65 | fn generate_vec(&self, state: &mut ObjectState) -> eyre::Result { 66 | Ok(Box::new(Vec::::generate_new( state)?)) 67 | } 68 | fn mem_size(&self) -> usize { 69 | std::mem::size_of::() 70 | } 71 | fn is_opaque(&self) -> bool { 72 | T::is_opaque() 73 | } 74 | fn get_fields_ty(&self) -> HashMap { 75 | T::get_fields_ty() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /hopper-core/src/runtime/object/fn_pointer.rs: -------------------------------------------------------------------------------- 1 | //! Describe function pointer 's fuzzing trait 2 | //! function pointer are : Option xx> 3 | //! 4 | //! ATTN: we can only choose function from gadgets, 5 | //! and can't crate functions for pointer dynamicly. 6 | 7 | use eyre::ContextCompat; 8 | 9 | use super::*; 10 | 11 | impl ObjFuzzable for T {} 12 | 13 | /// Type Cast FnFuzzAble to its original type 14 | pub fn cast_fn_pointer( 15 | f_name: &'static str, 16 | f: &'static dyn FnFuzzable, 17 | state: &mut ObjectState, 18 | ) -> eyre::Result { 19 | // crate::log!(trace, "use {f_name} as function pointer"); 20 | state.pointer = Some(PointerState::new_fn_pointer(f_name, false)); 21 | state.done_deterministic_itself(); 22 | let f = f 23 | .downcast_ref::() 24 | .context("fail to cast function pointer")?; 25 | Ok(f.clone()) 26 | } 27 | 28 | pub fn cast_canary_fn_pointer(state: &mut ObjectState) -> T { 29 | // crate::log!(trace, "use canary as function pointer"); 30 | state.pointer = Some(PointerState::new_fn_pointer("__hopper_fn_canary", false)); 31 | state.done_deterministic_itself(); 32 | T::canary_fn_pointer() 33 | } 34 | 35 | impl ObjValue for T {} 36 | impl ObjType for T {} 37 | 38 | impl ObjectSerialize for T { 39 | fn serialize_obj(&self, state: &ObjectState) -> eyre::Result { 40 | let fn_name = &state 41 | .pointer 42 | .as_ref() 43 | .context("pointer state does not exists in fn pointer!")? 44 | .pointer_type; 45 | Ok(format!("fn* {fn_name}&")) 46 | } 47 | } 48 | 49 | impl ObjectTranslate for T { 50 | fn translate_obj_to_c( 51 | &self, 52 | state: &ObjectState, 53 | _program: &FuzzProgram, 54 | ) -> eyre::Result { 55 | let fn_name = &state 56 | .pointer 57 | .as_ref() 58 | .context("pointer state does not exists in fn pointer!")? 59 | .pointer_type; 60 | Ok(format!("&{fn_name}")) 61 | } 62 | } 63 | 64 | impl Serialize for T { 65 | fn serialize(&self) -> eyre::Result { 66 | // we simply return null here 67 | Ok(format!("fn* {}&", "__null_fp")) 68 | } 69 | } 70 | 71 | impl ObjectDeserialize for T { 72 | fn deserialize_obj(de: &mut Deserializer, state: &mut ObjectState) -> eyre::Result { 73 | de.eat_token("fn* ")?; 74 | let func_name = de.next_token_until("&")?; 75 | if func_name == "__null_fp" { 76 | eyre::bail!(HopperError::NullFuncionPointer); 77 | } 78 | if func_name == "__hopper_fn_canary" { 79 | return Ok(cast_canary_fn_pointer(state)); 80 | } 81 | let fg = global_gadgets::get_instance().get_func_gadget(func_name)?; 82 | cast_fn_pointer(fg.f_name, fg.f, state) 83 | } 84 | } 85 | 86 | impl Deserialize for T { 87 | fn deserialize(_de: &mut Deserializer) -> eyre::Result { 88 | unimplemented!(); 89 | } 90 | } 91 | 92 | #[test] 93 | fn test_fn_pointer_serde() { 94 | use crate::test; 95 | use crate::ObjectSerialize; 96 | let mut de = Deserializer::new("fn* func_add&", None); 97 | let mut state = ObjectState::root("test", "fn(u8, u8) -> u8"); 98 | let f = u8>::deserialize_obj(&mut de, &mut state).unwrap(); 99 | assert_eq!(f, test::func_add as fn(u8, u8) -> u8); 100 | let f_name = f.serialize_obj(&state).unwrap(); 101 | assert_eq!(f_name, "fn* func_add&"); 102 | } 103 | -------------------------------------------------------------------------------- /hopper-core/src/runtime/object/mod.rs: -------------------------------------------------------------------------------- 1 | //! Traits for object we are fuzzing 2 | 3 | mod builder; 4 | pub mod canary; 5 | pub mod fn_pointer; 6 | mod layout; 7 | mod number; 8 | mod option; 9 | mod pointer; 10 | pub mod seq; 11 | mod state; 12 | mod void; 13 | mod bitfield; 14 | 15 | pub use builder::*; 16 | pub use canary::*; 17 | pub use layout::*; 18 | pub use state::*; 19 | pub use void::*; 20 | pub use bitfield::*; 21 | 22 | use downcast_rs::Downcast; 23 | use dyn_clone::DynClone; 24 | use std::{fmt::Debug, collections::HashMap}; 25 | 26 | use crate::{runtime::*, HopperError, ObjMutate}; 27 | 28 | pub type FuzzObject = Box; 29 | 30 | /// Trait for fuzzing objects 31 | pub trait ObjFuzzable: 32 | ObjMutate 33 | + ObjValue 34 | + ObjectSerialize 35 | + Serialize 36 | + ObjectTranslate 37 | + Debug 38 | + Send 39 | + DynClone 40 | + Downcast 41 | { 42 | } 43 | 44 | /// Trait for get object's value information 45 | pub trait ObjValue: 'static + std::any::Any { 46 | /// Get type name of object 47 | fn type_name(&self) -> &'static str { 48 | std::any::type_name::() 49 | } 50 | /// Get layout of the object 51 | fn get_layout(&self, _fold_ptr: bool) -> ObjectLayout { 52 | ObjectLayout::root( 53 | std::any::type_name::(), 54 | self as *const Self as *mut u8, 55 | ) 56 | } 57 | // get raw pointer by key for mutating or fill pointer 58 | fn get_ptr_by_keys(&self, keys: &[FieldKey]) -> eyre::Result<*mut u8> { 59 | if !keys.is_empty() { 60 | unimplemented!() 61 | } 62 | Ok(self as *const Self as *mut u8) 63 | } 64 | // Is it zero or not 65 | fn is_zero(&self) -> bool { 66 | false 67 | } 68 | // get length 69 | fn get_length(&self) -> usize { 70 | 1 71 | } 72 | } 73 | 74 | /// Trait for get type information 75 | pub trait ObjType { 76 | /// If this type is void or not 77 | fn is_void() -> bool { 78 | false 79 | } 80 | /// If this type is primitive type or not 81 | fn is_primitive() -> bool { 82 | false 83 | } 84 | /// If it is opaque type 85 | /// e.g zero-size array, or type contains fields starts with '_' 86 | fn is_opaque() -> bool { 87 | false 88 | } 89 | 90 | /// add fields's types to gadgets 91 | fn add_fields_to_gadgets(_gadgets: &mut ProgramGadgets) {} 92 | 93 | /// try renew a object from antoher 94 | fn cast_from(_other: &FuzzObject) -> Box { 95 | unreachable!("the type is not support cast"); 96 | } 97 | 98 | /// Get all fields 99 | fn get_fields_ty() -> HashMap { 100 | HashMap::default() 101 | } 102 | } 103 | 104 | #[macro_export] 105 | macro_rules! impl_obj_fuzz { 106 | ( $($name:ident),* ) => { 107 | $( 108 | impl ObjFuzzable for $name {} 109 | )* 110 | } 111 | } 112 | 113 | dyn_clone::clone_trait_object!(ObjFuzzable); 114 | downcast_rs::impl_downcast!(ObjFuzzable); 115 | 116 | #[test] 117 | fn test_get_ptr() { 118 | let ptr = crate::test::create_test_ptr(); 119 | let keys = [FieldKey::Pointer, FieldKey::Field("p".to_string())]; 120 | let ptr = ptr.get_ptr_by_keys(&keys[..]).unwrap(); 121 | println!("ptr: {ptr:?}"); 122 | } 123 | 124 | #[test] 125 | fn test_get_layout() { 126 | let val: i32 = 1; 127 | let layout = val.get_layout(false); 128 | println!("layout: {layout:?}"); 129 | assert_eq!(layout.get_ir_fields().len(), 0); 130 | } 131 | -------------------------------------------------------------------------------- /hopper-core/src/runtime/object/option.rs: -------------------------------------------------------------------------------- 1 | //! Describe option type's fuzzing trait 2 | //! 3 | //! If T is an FFI-safe non-nullable pointer type, Option is guaranteed to have the same layout 4 | //! and ABI as T and is therefore also FFI-safe. As of this writing, this covers &, &mut, and 5 | //! function pointers, all of which can never be null. 6 | //! 7 | //! In FFI, Option is warpped with function pointer to bring `NULL` pointer in rust. 8 | //! 9 | 10 | use eyre::Context; 11 | 12 | use crate::{config, ObjGenerate}; 13 | 14 | use super::*; 15 | 16 | impl ObjFuzzable for Option {} 17 | 18 | impl ObjValue for Option {} 19 | 20 | impl ObjType for Option {} 21 | 22 | impl ObjectSerialize for Option { 23 | fn serialize_obj(&self, state: &ObjectState) -> eyre::Result { 24 | if let Some(v) = self { 25 | let sub_state = state 26 | .last_child() 27 | .with_context(|| format!("failed state: {state:?}"))?; 28 | if sub_state.pointer.is_some() { 29 | let s = v.serialize_obj(sub_state)?; 30 | return Ok(format!("option {s}")); 31 | } 32 | } 33 | // avoid data is "None" 34 | Ok("option_ None".to_string()) 35 | } 36 | } 37 | 38 | impl ObjectTranslate for Option { 39 | fn translate_obj_to_c( 40 | &self, 41 | state: &ObjectState, 42 | program: &FuzzProgram, 43 | ) -> eyre::Result { 44 | if let Some(v) = self { 45 | let s = v.translate_obj_to_c(state.last_child()?, program)?; 46 | Ok(s) 47 | } else { 48 | Ok("NULL".to_string()) 49 | } 50 | } 51 | } 52 | 53 | impl Serialize for Option { 54 | fn serialize(&self) -> eyre::Result { 55 | if let Some(v) = self { 56 | let s = v.serialize()?; 57 | Ok(format!("option {s}")) 58 | } else { 59 | // avoid data is "None" 60 | Ok("option_ None".to_string()) 61 | } 62 | } 63 | } 64 | 65 | impl ObjectDeserialize for Option { 66 | fn deserialize_obj(de: &mut Deserializer, state: &mut ObjectState) -> eyre::Result { 67 | if !config::ENABLE_SET_FN_POINTER { 68 | let _ = state.replace_weight(0); 69 | } 70 | state.done_deterministic_itself(); 71 | let sub_state = state 72 | .add_child(FieldKey::Option, std::any::type_name::>()) 73 | .last_child_mut()?; 74 | if de.strip_token("option_ None") { 75 | return Ok(None); 76 | } 77 | de.eat_token("option")?; 78 | let ret = T::deserialize_obj(de, sub_state); 79 | match ret { 80 | Ok(obj) => Ok(Some(obj)), 81 | Err(err) => { 82 | if let Some(HopperError::NullFuncionPointer) = err.downcast_ref::() { 83 | return Ok(None); 84 | } 85 | Err(err) 86 | } 87 | } 88 | } 89 | } 90 | 91 | impl Deserialize for Option { 92 | fn deserialize(de: &mut Deserializer) -> eyre::Result { 93 | if de.strip_token("option_ None") { 94 | return Ok(None); 95 | } 96 | de.eat_token("option")?; 97 | let ret = T::deserialize(de)?; 98 | Ok(Some(ret)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /hopper-core/src/runtime/object/void.rs: -------------------------------------------------------------------------------- 1 | //! Describe void's fuzzing trait, 2 | //! There are two kinds of void: 3 | //! 1) RetVoid: return void, which is similar to RUST's `()` 4 | //! 2) ArgVoid: void in arguments, or pointer, struct ..., we use cvoid directly. 5 | 6 | use super::*; 7 | 8 | /// Void for return 9 | pub type RetVoid = (); 10 | 11 | macro_rules! impl_void { 12 | ($void:ident) => { 13 | impl ObjFuzzable for $void {} 14 | 15 | impl ObjValue for $void {} 16 | 17 | impl ObjType for $void { 18 | fn is_void() -> bool { 19 | true 20 | } 21 | fn is_opaque() -> bool { 22 | true 23 | } 24 | } 25 | 26 | impl ObjectSerialize for $void { 27 | fn serialize_obj(&self, _state: &ObjectState) -> eyre::Result { 28 | Ok("void".to_string()) 29 | } 30 | } 31 | 32 | impl Serialize for $void { 33 | fn serialize(&self) -> eyre::Result { 34 | Ok("void".to_string()) 35 | } 36 | } 37 | 38 | impl ObjectTranslate for $void {} 39 | 40 | impl ObjectDeserialize for $void { 41 | fn deserialize_obj( 42 | de: &mut Deserializer, 43 | state: &mut ObjectState, 44 | ) -> eyre::Result { 45 | let _ = state.replace_weight(0); 46 | state.done_deterministic_itself(); 47 | de.eat_token("void")?; 48 | Ok(Self::default()) 49 | } 50 | } 51 | 52 | impl Deserialize for $void { 53 | fn deserialize(_de: &mut Deserializer) -> eyre::Result { 54 | unimplemented!(); 55 | } 56 | } 57 | }; 58 | } 59 | 60 | impl_void!(FuzzVoid); 61 | impl_void!(RetVoid); 62 | 63 | impl Clone for FuzzVoid { 64 | fn clone(&self) -> Self { 65 | // unreachable!("void can't be clone!") 66 | Self(0_u8) 67 | } 68 | } 69 | 70 | unsafe impl Sync for FuzzVoid {} 71 | unsafe impl Send for FuzzVoid {} 72 | -------------------------------------------------------------------------------- /hopper-derive-impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hopper-derive-impl" 3 | version = "1.0.0" 4 | edition = "2021" 5 | authors = ["Peng Chen "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | syn = {version = "1.0", features = ["full", "visit", "fold"]} 11 | quote = "1.0" 12 | proc-macro2 = "1" 13 | 14 | [features] 15 | default = ["ctor_hook"] 16 | ctor_hook = [] 17 | link_hook = [] 18 | use_crate = [] -------------------------------------------------------------------------------- /hopper-derive-impl/src/folder.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | fold::{self, Fold}, 3 | Type, TypeBareFn, 4 | }; 5 | 6 | use crate::my_quote; 7 | 8 | pub struct FuzzFolder { 9 | replace_ptr: bool, 10 | } 11 | 12 | impl Default for FuzzFolder { 13 | fn default() -> Self { 14 | Self { replace_ptr: true } 15 | } 16 | } 17 | 18 | impl Fold for FuzzFolder { 19 | fn fold_type(&mut self, node: Type) -> Type { 20 | if !self.replace_ptr { 21 | return fold::fold_type(self, node); 22 | } 23 | match node { 24 | Type::Ptr(ptr) => { 25 | let crate_path = super::get_crate_path(); 26 | let inner = self.fold_type(*ptr.elem); 27 | if ptr.mutability.is_some() { 28 | Type::Verbatim(my_quote!( 29 | #crate_path::FuzzMutPointer::<#inner> 30 | )) 31 | } else { 32 | Type::Verbatim(my_quote!( 33 | #crate_path::FuzzConstPointer::<#inner> 34 | )) 35 | } 36 | } 37 | Type::Path(ref path) => { 38 | if check_void_type(path) { 39 | let crate_path = super::get_crate_path(); 40 | Type::Verbatim(my_quote!( 41 | #crate_path::FuzzVoid 42 | )) 43 | } else if check_fn_ptr_path(path) { 44 | let crate_path = super::get_crate_path(); 45 | Type::Verbatim(my_quote!( 46 | #crate_path::FuzzFrozenPointer::<#path> 47 | )) 48 | } else if let Some(ty) = check_bitfield_type(path) { 49 | ty 50 | } else { 51 | fold::fold_type(self, node) 52 | } 53 | } 54 | _ => fold::fold_type(self, node), 55 | } 56 | } 57 | 58 | fn fold_item_impl(&mut self, i: syn::ItemImpl) -> syn::ItemImpl { 59 | let ty = &i.self_ty; 60 | let ty = my_quote!(#ty).to_string(); 61 | // ignore 62 | if ty.starts_with("__BindgenBitfieldUnit") { 63 | return i; 64 | } 65 | if ty.starts_with("__IncompleteArrayField") { 66 | println!("cargo:warning=item: {ty:?}, disable replace ptr"); 67 | self.replace_ptr = false; 68 | let ret = fold::fold_item_impl(self, i); 69 | self.replace_ptr = true; 70 | ret 71 | } else { 72 | fold::fold_item_impl(self, i) 73 | } 74 | } 75 | } 76 | 77 | const VOID_PATH: [&str; 4] = ["std", "os", "raw", "c_void"]; 78 | 79 | fn check_void_type(path: &syn::TypePath) -> bool { 80 | let it = path.path.segments.iter(); 81 | it.zip(VOID_PATH.iter()) 82 | .all(|(seg, ident)| seg.ident == ident) 83 | } 84 | 85 | fn check_bitfield_type(path: &syn::TypePath) -> Option { 86 | let mut it = path.path.segments.iter(); 87 | if let Some(seg) = it.next() { 88 | if seg.ident == "__BindgenBitfieldUnit" { 89 | let crate_path = super::get_crate_path(); 90 | let arg = &seg.arguments; 91 | return Some(Type::Verbatim(my_quote!( 92 | #crate_path::HopperBindgenBitfieldUnit::#arg 93 | ))); 94 | } 95 | } 96 | None 97 | } 98 | 99 | fn check_fn_ptr_path(path: &syn::TypePath) -> bool { 100 | if let Some(fn_ty) = get_fn_type_in_option(path) { 101 | // We don't want the number of arguments in a function pointer to exceed MAX_SIG_ARG_LEN either. 102 | if fn_ty.inputs.len() > crate::func_hook::MAX_SIG_ARG_LEN { 103 | return true; 104 | } 105 | } 106 | false 107 | } 108 | 109 | pub fn get_fn_type_in_option(path: &syn::TypePath) -> Option<&TypeBareFn> { 110 | let mut it = path.path.segments.iter(); 111 | let generic_args = it 112 | .next() 113 | .and_then(|seg| { 114 | if seg.ident == "std" || seg.ident == "core" { 115 | it.next() 116 | } else { 117 | None 118 | } 119 | }) 120 | .and_then(|seg| { 121 | if seg.ident == "option" { 122 | it.next() 123 | } else { 124 | None 125 | } 126 | }) 127 | .and_then(|seg| { 128 | if seg.ident == "Option" { 129 | Some(&seg.arguments) 130 | } else { 131 | None 132 | } 133 | }); 134 | if let Some(syn::PathArguments::AngleBracketed(params)) = generic_args { 135 | if let syn::GenericArgument::Type(syn::Type::BareFn(fn_ty)) = 136 | params.args.iter().next().unwrap() 137 | { 138 | return Some(fn_ty); 139 | } 140 | } 141 | None 142 | } 143 | -------------------------------------------------------------------------------- /hopper-derive-impl/src/format.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | env, 4 | io::{self, Write}, 5 | path::PathBuf, 6 | process::{Command, Stdio}, 7 | }; 8 | 9 | /// Gets the rustfmt path to rustfmt the generated bindings. 10 | fn rustfmt_path() -> io::Result { 11 | if let Ok(rustfmt) = env::var("RUSTFMT") { 12 | return Ok(rustfmt.into()); 13 | } 14 | // No rustfmt binary was specified, so assume that the binary is called 15 | // "rustfmt" and that it is in the user's PATH. 16 | Ok("rustfmt".into()) 17 | } 18 | 19 | pub fn rustfmt_generated_string(source: &str) -> io::Result> { 20 | let rustfmt = rustfmt_path()?; 21 | let mut cmd = Command::new(rustfmt); 22 | 23 | cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); 24 | 25 | let mut child = cmd.spawn()?; 26 | let mut child_stdin = child.stdin.take().unwrap(); 27 | let mut child_stdout = child.stdout.take().unwrap(); 28 | 29 | let source = source.to_owned(); 30 | 31 | // Write to stdin in a new thread, so that we can read from stdout on this 32 | // thread. This keeps the child from blocking on writing to its stdout which 33 | // might block us from writing to its stdin. 34 | let stdin_handle = ::std::thread::spawn(move || { 35 | let _ = child_stdin.write_all(source.as_bytes()); 36 | source 37 | }); 38 | 39 | let mut output = vec![]; 40 | io::copy(&mut child_stdout, &mut output)?; 41 | 42 | let status = child.wait()?; 43 | let source = stdin_handle.join().expect( 44 | "The thread writing to rustfmt's stdin doesn't do \ 45 | anything that could panic", 46 | ); 47 | 48 | match String::from_utf8(output) { 49 | Ok(bindings) => match status.code() { 50 | Some(0) => Ok(Cow::Owned(bindings)), 51 | Some(2) => Err(io::Error::new( 52 | io::ErrorKind::Other, 53 | "Rustfmt parsing errors.".to_string(), 54 | )), 55 | Some(3) => { 56 | // log::warn!("Rustfmt could not format some lines."); 57 | Ok(Cow::Owned(bindings)) 58 | } 59 | _ => Err(io::Error::new( 60 | io::ErrorKind::Other, 61 | "Internal rustfmt error".to_string(), 62 | )), 63 | }, 64 | _ => Ok(Cow::Owned(source)), 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /hopper-derive-impl/src/func_sig.rs: -------------------------------------------------------------------------------- 1 | ///! Implementation general function for both foreign and internal functions. 2 | ///! Ref: https://docs.rs/syn/1.0.76/src/syn/item.rs.html#611-622 3 | ///! WARN: Deprecated!! 4 | 5 | use std::{iter, slice}; 6 | 7 | use proc_macro2::TokenStream; 8 | use quote::{ToTokens, TokenStreamExt}; 9 | use syn::{ 10 | braced, bracketed, 11 | parse::{Parse, ParseStream, Result}, 12 | AttrStyle, Attribute, Block, Path, Signature, Token, Visibility, 13 | }; 14 | pub struct FnDecl { 15 | pub attrs: Vec, 16 | pub vis: Visibility, 17 | pub sig: Signature, 18 | pub block: Option>, 19 | pub semi_token: Option, 20 | } 21 | 22 | impl Parse for FnDecl { 23 | fn parse(input: ParseStream) -> Result { 24 | let mut attrs = input.call(Attribute::parse_outer)?; 25 | let vis: Visibility = input.parse()?; 26 | let sig: Signature = input.parse()?; 27 | let lookahead = input.lookahead1(); 28 | let mut block = None; 29 | let mut semi_token = None; 30 | if lookahead.peek(Token![;]) { 31 | // ForeignItemFn 32 | let semi: Token![;] = input.parse()?; 33 | semi_token = Some(semi); 34 | } else if lookahead.peek(syn::token::Brace) { 35 | // ItemFn 36 | let content; 37 | let brace_token = braced!(content in input); 38 | parse_inner(&content, &mut attrs)?; 39 | let stmts = content.call(Block::parse_within)?; 40 | block = Some(Box::new(Block { brace_token, stmts })); 41 | } else { 42 | return Err(lookahead.error()); 43 | }; 44 | 45 | Ok(FnDecl { 46 | attrs, 47 | vis, 48 | sig, 49 | block, 50 | semi_token 51 | }) 52 | } 53 | } 54 | 55 | impl ToTokens for FnDecl { 56 | fn to_tokens(&self, tokens: &mut TokenStream) { 57 | tokens.append_all(self.attrs.outer()); 58 | self.vis.to_tokens(tokens); 59 | self.sig.to_tokens(tokens); 60 | if let Some(blk) = &self.block { 61 | blk.brace_token.surround(tokens, |tokens| { 62 | tokens.append_all(self.attrs.inner()); 63 | tokens.append_all(&blk.stmts); 64 | }); 65 | } 66 | if let Some(semi) = &self.semi_token { 67 | semi.to_tokens(tokens); 68 | } 69 | } 70 | } 71 | 72 | pub fn parse_inner(input: ParseStream, attrs: &mut Vec) -> Result<()> { 73 | while input.peek(Token![#]) && input.peek2(Token![!]) { 74 | attrs.push(input.call(single_parse_inner)?); 75 | } 76 | Ok(()) 77 | } 78 | 79 | pub fn single_parse_inner(input: ParseStream) -> Result { 80 | let content; 81 | Ok(Attribute { 82 | pound_token: input.parse()?, 83 | style: AttrStyle::Inner(input.parse()?), 84 | bracket_token: bracketed!(content in input), 85 | path: content.call(Path::parse_mod_style)?, 86 | tokens: content.parse()?, 87 | }) 88 | } 89 | 90 | 91 | pub trait FilterAttrs<'a> { 92 | type Ret: Iterator; 93 | 94 | fn outer(self) -> Self::Ret; 95 | fn inner(self) -> Self::Ret; 96 | } 97 | 98 | impl<'a> FilterAttrs<'a> for &'a [Attribute] { 99 | type Ret = iter::Filter, fn(&&Attribute) -> bool>; 100 | 101 | fn outer(self) -> Self::Ret { 102 | fn is_outer(attr: &&Attribute) -> bool { 103 | match attr.style { 104 | AttrStyle::Outer => true, 105 | AttrStyle::Inner(_) => false, 106 | } 107 | } 108 | self.iter().filter(is_outer) 109 | } 110 | 111 | fn inner(self) -> Self::Ret { 112 | fn is_inner(attr: &&Attribute) -> bool { 113 | match attr.style { 114 | AttrStyle::Inner(_) => true, 115 | AttrStyle::Outer => false, 116 | } 117 | } 118 | self.iter().filter(is_inner) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /hopper-derive-impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod field; 2 | pub mod folder; 3 | pub mod format; 4 | pub mod func_hook; 5 | pub mod object; 6 | pub mod visitor; 7 | pub mod serde; 8 | 9 | #[macro_use] 10 | extern crate quote; 11 | use proc_macro2::TokenStream; 12 | use syn::{fold::Fold, visit::Visit}; 13 | 14 | pub const ENABLE_SET_FN_POINTER: bool = enable_fn_pointer(); 15 | pub const DEFAULT_FN_POINTER_PREFIX: &str = fn_pointer_name_prefix(); 16 | 17 | #[macro_export] 18 | macro_rules! my_quote { 19 | ($($t:tt)*) => (quote_spanned!(proc_macro2::Span::call_site() => $($t)*)) 20 | } 21 | 22 | pub fn derive_bindings(content: &str) -> TokenStream { 23 | let syntax = syn::parse_file(content).expect("Unable to parse file"); 24 | 25 | let mut folder = folder::FuzzFolder::default(); 26 | let replaced_syntax = folder.fold_file(syntax); 27 | 28 | let mut fuzz_visitor = visitor::FuzzVisitor::default(); 29 | fuzz_visitor.visit_file(&replaced_syntax); 30 | 31 | let callbacks = fuzz_visitor.generate_callbacks(); 32 | 33 | let result = my_quote!( 34 | #callbacks 35 | 36 | #replaced_syntax 37 | 38 | #fuzz_visitor 39 | ); 40 | 41 | result 42 | } 43 | 44 | static mut USE_IN_COMPILER: bool = false; 45 | 46 | pub fn set_compiler_env() { 47 | unsafe { 48 | USE_IN_COMPILER = true; 49 | } 50 | } 51 | 52 | pub fn get_crate_path() -> syn::Path { 53 | if cfg!(feature = "use_crate") && ! unsafe { USE_IN_COMPILER } { 54 | syn::parse_quote!(crate) 55 | } else { 56 | syn::parse_quote!(::hopper) 57 | } 58 | } 59 | 60 | const fn enable_fn_pointer() -> bool { 61 | option_env!("HOPPER_DISABLE_FN_POINTER").is_none() 62 | } 63 | 64 | const fn fn_pointer_name_prefix() -> &'static str { 65 | if let Some(v) = option_env!("HOPPER_FUNCTION_POINTER_PREFIX") { 66 | v 67 | } else { 68 | "GENERATED_hopper_callback_" 69 | } 70 | } 71 | 72 | fn is_packed_struct(attrs: &[syn::Attribute]) -> bool { 73 | let attrs_tokens = my_quote!(#(#attrs)*, ); 74 | let attrs = attrs_tokens.to_string(); 75 | attrs.contains("packed") 76 | } 77 | -------------------------------------------------------------------------------- /hopper-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hopper-derive" 3 | version = "1.0.0" 4 | edition = "2021" 5 | authors = ["Peng Chen "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | syn = {version = "1.0", features = ["full", "visit"]} 11 | quote = "1.0" 12 | proc-macro2 = "1" 13 | hopper-derive-impl = { path = "../hopper-derive-impl" } 14 | 15 | [dev-dependencies] 16 | hopper = { path = "../hopper-core" } 17 | 18 | [lib] 19 | proc-macro = true 20 | 21 | [features] 22 | use_crate = ["hopper-derive-impl/use_crate"] -------------------------------------------------------------------------------- /hopper-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //!# A custom dervie implementation for `#[derive(Hopper)]`, which is used in Hopper Fuzzer. 2 | //! 3 | //!blabla... 4 | 5 | #![crate_type = "proc-macro"] 6 | #![recursion_limit = "192"] 7 | extern crate proc_macro; 8 | extern crate proc_macro2; 9 | extern crate syn; 10 | #[macro_use] 11 | extern crate quote; 12 | use hopper_derive_impl::*; 13 | 14 | use proc_macro::TokenStream; 15 | use syn::visit::Visit; 16 | use syn::ItemFn; 17 | 18 | #[proc_macro_attribute] 19 | pub fn fuzz_all(_metadata: TokenStream, input: TokenStream) -> TokenStream { 20 | let input: syn::ItemMod = syn::parse(input).expect("Couldn't parse item mod"); 21 | let mut fuzz_vistor = visitor::FuzzVisitor::default().set_mod_ident(input.ident.clone()); 22 | fuzz_vistor.visit_item_mod(&input); 23 | 24 | let result = my_quote!( 25 | #input 26 | #fuzz_vistor 27 | ); 28 | result.into() 29 | } 30 | 31 | #[proc_macro_derive(Fuzz, attributes(Fuzz))] 32 | pub fn derive_fuzz(input: TokenStream) -> TokenStream { 33 | let ast: syn::DeriveInput = syn::parse(input).expect("Couldn't parse item"); 34 | let result = match ast.data { 35 | syn::Data::Enum(ref e) => object::object_trait_for_enum(&ast.ident, &ast.generics, &ast.attrs, &e.variants), 36 | syn::Data::Struct(ref s) => object::object_trait_for_struct(&ast.ident, &ast.generics, &ast.attrs, &s.fields), 37 | syn::Data::Union(ref u) => object::object_trait_for_union(&ast.ident, &ast.generics, &ast.attrs, &u.fields), 38 | }; 39 | result.into() 40 | } 41 | 42 | #[proc_macro_attribute] 43 | pub fn fuzz(_metadata: TokenStream, input: TokenStream) -> TokenStream { 44 | let input_fn: ItemFn = syn::parse(input).expect("Couldn't parse function signature"); 45 | let ctor_hook = func_hook::add_func_hook( 46 | &format!("fn_{}", &input_fn.sig.ident), 47 | &vec![input_fn.sig.clone()], 48 | &[], 49 | &[], 50 | ); 51 | 52 | let result = my_quote!( 53 | #input_fn 54 | #ctor_hook 55 | ); 56 | 57 | result.into() 58 | } 59 | 60 | #[proc_macro_derive(Serde, attributes(Serde))] 61 | pub fn derive_serde(input: TokenStream) -> TokenStream { 62 | let ast: syn::DeriveInput = syn::parse(input).expect("Couldn't parse item"); 63 | let result = match ast.data { 64 | syn::Data::Enum(ref e) => serde::serde_trait_for_enum(&ast.ident, &ast.generics, &ast.attrs, &e.variants), 65 | syn::Data::Struct(ref s) => serde::serde_trait_for_struct(&ast.ident, &ast.generics, &ast.attrs, &s.fields), 66 | syn::Data::Union(ref u) => serde::serde_trait_for_union(&ast.ident, &ast.generics, &ast.attrs, &u.fields), 67 | }; 68 | result.into() 69 | } 70 | 71 | #[proc_macro_derive(ObjectSerde, attributes(ObjectSerde))] 72 | pub fn derive_obj_serde(input: TokenStream) -> TokenStream { 73 | let ast: syn::DeriveInput = syn::parse(input).expect("Couldn't parse item"); 74 | let result = match ast.data { 75 | syn::Data::Enum(ref e) => serde::serde_trait_for_enum(&ast.ident, &ast.generics, &ast.attrs, &e.variants), 76 | syn::Data::Struct(ref s) => serde::serde_trait_for_struct(&ast.ident, &ast.generics, &ast.attrs, &s.fields), 77 | syn::Data::Union(ref u) => serde::serde_trait_for_union(&ast.ident, &ast.generics, &ast.attrs, &u.fields), 78 | }; 79 | result.into() 80 | } 81 | 82 | #[proc_macro_derive(EnumKind, attributes(EnumKind))] 83 | pub fn derive_enum_kind(input: TokenStream) -> TokenStream { 84 | let ast: syn::DeriveInput = syn::parse(input).expect("Couldn't parse item"); 85 | let result = match ast.data { 86 | syn::Data::Enum(ref e) => serde::kind_trait_for_enum(&ast.ident, &ast.generics, &ast.attrs, &e.variants), 87 | _ => unreachable!() 88 | }; 89 | result.into() 90 | } -------------------------------------------------------------------------------- /hopper-harness/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hopper-harness" 3 | version = "1.0.0" 4 | edition = "2021" 5 | authors = ["Peng Chen "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | hopper = { path = "../hopper-core" } 11 | log = "0.4" 12 | time = "0.3" 13 | flexi_logger = "0.22" 14 | clap = { version = "4.2", features = ["derive"] } 15 | linkme = { version = "0.3", optional = true } 16 | eyre = "0.6" 17 | color-eyre = { version = "0.6", default-features = false } 18 | rand = "0.8" 19 | regex = "1" 20 | 21 | [build-dependencies] 22 | bindgen = "0.65" 23 | hopper-derive-impl = { path = "../hopper-derive-impl" } 24 | 25 | [features] 26 | default = ["ctor_hook"] 27 | link_hook = ["linkme", "hopper/link_hook", "hopper-derive-impl/link_hook"] 28 | ctor_hook = ["hopper/ctor_hook", "hopper-derive-impl/ctor_hook"] 29 | llvm_mode = ["hopper/llvm_mode"] 30 | e9_mode = ["hopper/e9_mode"] 31 | cov_mode = [] 32 | verbose = ["hopper/verbose"] 33 | testsuite = ["hopper/testsuite"] -------------------------------------------------------------------------------- /hopper-harness/src/bin/hopper-bench.rs: -------------------------------------------------------------------------------- 1 | //! Used for benchmark hopper's runtime efficient 2 | //! Since we do not set core_limit, you should set: 3 | //! ulimit -c 0 4 | 5 | use std::{path::PathBuf, io::Read}; 6 | 7 | pub fn main() { 8 | hopper_harness::hopper_extend(); 9 | if let Some(dir) = std::env::args().nth(1) { 10 | let mut executor = hopper::Executor::default(); 11 | hopper::init_depot_dirs().unwrap(); 12 | // let mut feedback = hopper::Feedback::new().unwrap(); 13 | // feedback.clear(); 14 | // executor.set_timeout(1); 15 | let start_at = std::time::Instant::now(); 16 | let path = PathBuf::from(dir); 17 | let mut inputs = vec![]; 18 | for entry in path.read_dir().unwrap() { 19 | let file = entry.unwrap().path(); 20 | if !file.is_file() { 21 | continue; 22 | } 23 | let mut buffer = String::new(); 24 | let mut f = std::fs::File::open(file).unwrap(); 25 | f.read_to_string(&mut buffer).unwrap(); 26 | inputs.push(buffer); 27 | } 28 | let start_run_at = std::time::Instant::now(); 29 | 30 | for (i, input) in inputs.iter().enumerate() { 31 | let ret = executor.execute(|| { 32 | let mut program = hopper::read_program(input, false).unwrap(); 33 | program.eval() 34 | }); 35 | println!("{i}, {ret:?}"); 36 | } 37 | 38 | let t_run = start_run_at.elapsed(); 39 | let t_all = start_at.elapsed(); 40 | println!("num: {}, run: {}s, all: {}s", inputs.len(), t_run.as_secs_f32(), t_all.as_secs_f32()); 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /hopper-harness/src/bin/hopper-fuzzer.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | use hopper::HopperError; 3 | use std::io::prelude::*; 4 | 5 | fn init_logger() { 6 | use flexi_logger::*; 7 | 8 | let output_file = FileSpec::default() 9 | .directory(hopper::OUTPUT_DIR) 10 | .basename("fuzzer"); 11 | 12 | #[cfg(not(feature = "verbose"))] 13 | Logger::try_with_env_or_str("info") 14 | .unwrap() // Write all error, warn, and info messages 15 | .log_to_file(output_file) 16 | .duplicate_to_stdout(Duplicate::Debug) 17 | .format_for_files(opt_format) 18 | .adaptive_format_for_stdout(AdaptiveFormat::Opt) 19 | .rotate( 20 | // If the program runs long enough, 21 | Criterion::Size(1 << 30), 22 | Naming::Timestamps, 23 | Cleanup::KeepLogFiles(3), 24 | ) 25 | .start() 26 | .unwrap(); 27 | 28 | #[cfg(feature = "verbose")] 29 | { 30 | use flexi_logger::writers::FileLogWriter; 31 | let status_writer = Box::new( 32 | FileLogWriter::builder( 33 | FileSpec::default() 34 | .directory(hopper::OUTPUT_DIR) 35 | .suppress_timestamp() 36 | .basename("status"), 37 | ).rotate( 38 | Criterion::Size(1 << 30), 39 | Naming::Timestamps, 40 | Cleanup::KeepLogFiles(3)) 41 | .try_build() 42 | .unwrap(), 43 | ); 44 | 45 | let status_oneshot_writer = Box::new( 46 | FileLogWriter::builder( 47 | FileSpec::default() 48 | .directory(hopper::OUTPUT_DIR) 49 | .suppress_timestamp() 50 | .basename("status_oneshot"), 51 | ) 52 | .rotate( 53 | Criterion::Size(1), 54 | Naming::Numbers, 55 | Cleanup::KeepLogFiles(0), 56 | ) 57 | .try_build() 58 | .unwrap(), 59 | ); 60 | 61 | Logger::try_with_env_or_str("info") 62 | .unwrap() // Write all error, warn, and info messages 63 | .log_to_file(output_file) 64 | .add_writer("Status", status_writer) 65 | .add_writer("StatusOneShot", status_oneshot_writer) 66 | .duplicate_to_stdout(Duplicate::Debug) 67 | .format_for_files(opt_format) 68 | .adaptive_format_for_stdout(AdaptiveFormat::Opt) 69 | .rotate( 70 | // If the program runs long enough, 71 | Criterion::Size(1 << 30), 72 | Naming::Timestamps, 73 | Cleanup::KeepLogFiles(3), 74 | ) 75 | .start() 76 | .unwrap(); 77 | } 78 | 79 | 80 | } 81 | 82 | fn main() -> eyre::Result<()> { 83 | color_eyre::install()?; 84 | hopper::parse_config()?; 85 | init_logger(); 86 | log::info!("Hopper starting ..."); 87 | log::info!("config: {:?}", hopper::get_config()); 88 | hopper_harness::hopper_extend(); 89 | let res = hopper::run_fuzzer(); 90 | if let Err(err) = &res { 91 | if let Some(HopperError::TestSuccess) = err.downcast_ref::() { 92 | std::process::exit(hopper::TEST_SUCCESS_EXIT_CODE); 93 | } 94 | log::error!("fuzzer error is wrote into misc/fuzzer_error.log"); 95 | hopper::create_dir_in_output_if_not_exist(hopper::MISC_DIR)?; 96 | let path = hopper::output_file_path("misc/fuzzer_error.log"); 97 | let mut f = std::fs::File::create(path)?; 98 | writeln!(f, "{err:#?}")?; 99 | } 100 | log::info!("Hopper ending ..."); 101 | res?; 102 | Ok(()) 103 | } 104 | -------------------------------------------------------------------------------- /hopper-harness/src/bin/hopper-harness.rs: -------------------------------------------------------------------------------- 1 | //! Hopper Harness 2 | //! Create a fork server to receive control message and execute program 3 | //! 4 | //! Fork Server: harness --server 5 | //! Replay: harness [file_name] [options] 6 | //! options: 7 | //! --sanitize : use `sanitize` in program, the default one is `review` 8 | //! --execute : use `eval` in program, the default one is `review` 9 | //! --nofork : do not fork a process and then execute, it will run program with fork by default 10 | 11 | use std::path::Path; 12 | 13 | fn init_logger(name: &str) { 14 | use flexi_logger::*; 15 | let mut output_file = FileSpec::default().basename(name); 16 | output_file = output_file.directory(hopper::OUTPUT_DIR); 17 | Logger::try_with_env_or_str("info") 18 | .unwrap() // Write all error, warn, and info messages 19 | .log_to_file(output_file) 20 | .format_for_files(opt_format) 21 | .rotate( 22 | // If the program runs long enough, 23 | Criterion::Size(1 << 30), 24 | Naming::Timestamps, 25 | Cleanup::KeepLogFiles(3), 26 | ) 27 | .start() 28 | .unwrap(); 29 | } 30 | 31 | pub fn main() -> eyre::Result<()> { 32 | hopper_harness::hopper_extend(); 33 | let is_server = std::env::args().any(|f| f == "--server"); 34 | if is_server { 35 | let is_fast = std::env::args().any(|f| f == "--fast"); 36 | if is_fast { 37 | init_logger("harness_fast"); 38 | } else { 39 | init_logger("harness"); 40 | } 41 | let res = hopper::run_fork_server(); 42 | if let Err(err) = res { 43 | log::error!("error: {}", err); 44 | log::error!("root cause: {:?}", err.root_cause()); 45 | std::process::exit(hopper::FORK_ERROR_EXIT_CODE); 46 | } 47 | return Ok(()); 48 | } 49 | let query_gadgets = std::env::args().any(|f| f == "--gadgets"); 50 | if query_gadgets { 51 | hopper::create_dir_in_output_if_not_exist(hopper::MISC_DIR)?; 52 | hopper::global_gadgets::get_instance().save_gadgets_to_file()?; 53 | return Ok(()); 54 | } 55 | 56 | if let Some(file_name) = std::env::args().nth(1) { 57 | color_eyre::install()?; 58 | flexi_logger::Logger::try_with_env_or_str("trace") 59 | .unwrap() 60 | .start() 61 | .unwrap(); 62 | let infer_crash = std::env::args().any(|f| f == "--infer"); 63 | if infer_crash { 64 | hopper::global_gadgets::get_mut_instance().build_arg_and_ret_graph(); 65 | hopper::infer_crash(&file_name)?; 66 | let path = Path::new("found_constraints"); 67 | hopper::CONSTRAINTS.with(|c| c.borrow().save_to_file(path))?; 68 | return Ok(()); 69 | } 70 | let minimize_input = std::env::args().any(|f| f == "--minimize"); 71 | if minimize_input { 72 | hopper::minimize_input(&file_name)?; 73 | return Ok(()); 74 | } 75 | let mut cmd = hopper::ForkCmd::Review; 76 | if std::env::args().any(|f| f == "--sanitize") { 77 | cmd = hopper::ForkCmd::Sanitize; 78 | } else if std::env::args().any(|f| f == "--execute") { 79 | cmd = hopper::ForkCmd::Execute; 80 | } 81 | hopper::run_program(&file_name, cmd)?; 82 | } 83 | Ok(()) 84 | } 85 | -------------------------------------------------------------------------------- /hopper-harness/src/bin/hopper-slice.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use hopper::{effective::*, FuzzStmt, Serialize}; 3 | 4 | /// Hopper - fuzz libraries fully automatically 5 | /// hopper-slice is a tool for debugging slice issues 6 | #[derive(Parser, Debug)] 7 | #[clap(name = "hopper-slice")] 8 | #[clap(version = "1.0.0", author = "Tencent")] 9 | pub struct SliceConfig { 10 | /// Input program 11 | #[clap(long, value_parser)] 12 | pub input: String, 13 | 14 | /// Call index 15 | #[clap(long, value_parser)] 16 | pub index: usize, 17 | 18 | /// Argument position 19 | #[clap(long, value_parser)] 20 | pub arg: Option, 21 | } 22 | 23 | fn main() -> eyre::Result<()> { 24 | color_eyre::install()?; 25 | hopper_harness::hopper_extend(); 26 | flexi_logger::Logger::try_with_env_or_str("trace")?.start()?; 27 | let config = SliceConfig::parse(); 28 | log::info!("config: {:?}", config); 29 | // hopper::check_gadgets().unwrap(); 30 | let buf = std::fs::read_to_string(&config.input)?; 31 | let mut program = hopper::read_program(&buf, false)?; 32 | hopper::parse_program_extra(&buf, &mut program)?; 33 | let call_i = config.index; 34 | if let Some(arg_pos) = config.arg { 35 | if let FuzzStmt::Call(call) = &program.stmts[call_i].stmt { 36 | let p = slice_arg(&program, call, call_i, arg_pos)?; 37 | log::info!("sliced: {}", p.serialize().unwrap()); 38 | } 39 | } else { 40 | hopper::Fuzzer::collect_effective_args_in_call(&program, call_i)?; 41 | // log_effective_args(); 42 | } 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /hopper-harness/src/bin/hopper-translate.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use hopper::Translate; 3 | use std::io::Write; 4 | 5 | /// Hopper - fuzz libraries fully automatically 6 | #[derive(Parser, Debug)] 7 | #[clap(name = "hopper-translate")] 8 | #[clap(version = "1.0.0", author = "Tencent")] 9 | pub struct TranslateConfig { 10 | /// Path of header file of library 11 | #[clap(long, value_parser)] 12 | pub header: String, 13 | 14 | /// Output directory of harness 15 | #[clap(long, value_parser)] 16 | pub input: String, 17 | 18 | /// Output directory of harness 19 | #[clap(long, value_parser)] 20 | pub output: Option, 21 | } 22 | 23 | fn main() -> eyre::Result<()> { 24 | color_eyre::install()?; 25 | hopper_harness::hopper_extend(); 26 | flexi_logger::Logger::try_with_env_or_str("trace")?.start()?; 27 | let config = TranslateConfig::parse(); 28 | log::info!("config: {:?}", config); 29 | // hopper::check_gadgets().unwrap(); 30 | let buf = std::fs::read_to_string(&config.input)?; 31 | let program = hopper::read_program(&buf, false)?; 32 | let out = program.translate_to_c()?; 33 | let include_header = format!("#include \"{}\"\n", config.header); 34 | let mut out = include_header + &out; 35 | fix_error(&mut out, &config.header)?; 36 | log::info!("{}", out); 37 | let f_name = if let Some(out_f) = config.output { 38 | out_f 39 | } else { 40 | config.input + ".c" 41 | }; 42 | let mut f = std::fs::File::create(&f_name)?; 43 | f.write_all(out.as_bytes())?; 44 | log::info!("please run: gcc -g -I. -L. -lyourlib {}", f_name); 45 | Ok(()) 46 | } 47 | 48 | fn fix_error(code: &mut String, header: &str) -> eyre::Result<()> { 49 | use std::io::BufRead; 50 | static TMP_CODE_FILE: &str = "/tmp/hopper_tmp.c"; 51 | static TMP_OUT_FILE: &str = "/tmp/hopper_tmp.out"; 52 | log::info!("try to fix struct type error.."); 53 | let header_path = std::path::Path::new(header); 54 | std::fs::write(TMP_CODE_FILE, &code)?; 55 | let mut args = vec![ 56 | TMP_CODE_FILE, 57 | "-g", 58 | "-c", 59 | "-I.", 60 | "-o", 61 | TMP_OUT_FILE, 62 | ]; 63 | if let Some(header_dir) = header_path.parent() { 64 | args.push("-I"); 65 | args.push(header_dir.to_str().unwrap()); 66 | if let Some(hh_dir) = header_dir.parent() { 67 | args.push("-I"); 68 | args.push(hh_dir.to_str().unwrap()); 69 | } 70 | } 71 | if let Some(include_search_paths) = option_env!("HOPPER_INCLUDE_SEARCH_PATH") { 72 | let list = include_search_paths.split(':'); 73 | for item in list { 74 | args.push("-I"); 75 | args.push(item); 76 | } 77 | } 78 | let ret = std::process::Command::new("clang") 79 | .args(args) 80 | .output()?; 81 | let mut struct_list = vec![]; 82 | for line in ret.stdout.lines() { 83 | let line = line?; 84 | if line.contains(r"use \xe2\x80\x98struct\xe2\x80\x99 keyword to refer to the") || line.contains(r"unknown type name") { 85 | struct_list.push(get_struct_name(&line)?); 86 | } 87 | } 88 | if struct_list.is_empty() { 89 | return Ok(()); 90 | } 91 | for s in struct_list { 92 | *code = code.replace(&format!("{s} "), &format!("struct {s} ")); 93 | log::warn!("replace `{s}` to `struct {s}`"); 94 | } 95 | 96 | Ok(()) 97 | } 98 | 99 | 100 | fn get_struct_name(line: &str) -> eyre::Result { 101 | static LEFT_MARK: &str = r"\xe2\x80\x98"; 102 | static RIGHT_MAKR: &str = r"\xe2\x80\x99"; 103 | if let Some(l) = line.find(LEFT_MARK) { 104 | let rest = &line[l + LEFT_MARK.len() ..]; 105 | if let Some(r) = line.find(RIGHT_MAKR) { 106 | return Ok(rest[..r].to_string()); 107 | } 108 | } 109 | eyre::bail!("Fail to find struct name"); 110 | } -------------------------------------------------------------------------------- /hopper-harness/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | include!(concat!(env!("OUT_DIR"), "/fuzz_extend.rs")); 6 | 7 | pub fn hopper_extend() { 8 | log::debug!("hopper extend loaded"); 9 | } 10 | -------------------------------------------------------------------------------- /hopper-instrument/e9-mode/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## E9Patch 3 | - *E9* mode is using [e9pacth](https://github.com/GJDuck/e9patch) for instrumentation. You can read its [paper](https://comp.nus.edu.sg/~gregory/papers/e9patch.pdf) and [documentations](https://github.com/GJDuck/e9patch/tree/master/doc). 4 | 5 | 6 | ## Our plugins 7 | - `hopper-e9-plugin.cpp`: from [E9AFL](https://github.com/GJDuck/e9afl) for branch coverage collection. 8 | - `hopper-instr-plugin.cpp`: our plugin for tracing cmp instructions. 9 | 10 | ## Test e9 plugin 11 | 12 | - Print intermidiate content with JSON format. 13 | ``` 14 | /root/hopper/install/e9tool --format='json' -o /root/hopper/testsuite/ctest/libctest_instr.so -M 'plugin("/root/hopper/install/hopper-instr-plugin.so").match()' -P 'plugin("/root/hopper/install/hopper-instr-plugin.so").patch()' -- /root/hopper/testsuite/ctest/libctest.so 15 | ``` 16 | 17 | - Run pacth manually. 18 | ``` 19 | E9AFL_PATH=/root/hopper/install /root/hopper/install/e9tool -o /root/hopper/testsuite/ctest/output/libctest_cov.so -M 'plugin("/root/hopper/install/hopper-e9-plugin.so").match()' -P 'plugin("/root/hopper/install/hopper-e9-plugin.so").patch()' -M 'plugin("/root/hopper/install/hopper-instr-plugin.so").match()' -P 'plugin("/root/hopper/install/hopper-instr-plugin.so").patch()' -- /root/hopper/testsuite/ctest/libctest.so 20 | ``` 21 | -------------------------------------------------------------------------------- /hopper-instrument/e9-mode/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (C) 2021 National University of Singapore 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | if [ -t 1 ] 19 | then 20 | RED="\033[31m" 21 | GREEN="\033[32m" 22 | YELLOW="\033[33m" 23 | BOLD="\033[1m" 24 | OFF="\033[0m" 25 | else 26 | RED= 27 | GREEN= 28 | YELLOW= 29 | BOLD= 30 | OFF= 31 | fi 32 | 33 | set -e 34 | 35 | VERSION=811642cf744ba1726fc34851dc44f4c4df436ce7 36 | 37 | SOURCE_DIR=$(pwd) 38 | PREFIX=${PREFIX:-install} 39 | 40 | echo "PREFIX: $PREFIX" 41 | echo "PWD: $SOURCE_DIR" 42 | mkdir -p $PREFIX 43 | mkdir -p $PREFIX/tmp 44 | 45 | # STEP (1): install e9patch if necessary: 46 | if [ ! -x $PREFIX/tmp/e9patch-$VERSION/e9patch ] 47 | then 48 | if [ ! -f $PREFIX/tmp/e9patch-$VERSION.zip ] 49 | then 50 | echo -e "${GREEN}$0${OFF}: downloading e9patch-$VERSION.zip..." 51 | wget -O $PREFIX/tmp/e9patch-$VERSION.zip https://github.com/GJDuck/e9patch/archive/$VERSION.zip 52 | fi 53 | echo -e "${GREEN}$0${OFF}: extracting e9patch-$VERSION.zip..." 54 | cd $PREFIX/tmp 55 | unzip -q e9patch-$VERSION.zip 56 | echo -e "${GREEN}$0${OFF}: building e9patch..." 57 | cd e9patch-$VERSION 58 | echo -e "${GREEN}$0${OFF}: patch e9patch..." 59 | # only used for windows instrumentation 60 | patch -p1 <$SOURCE_DIR/e9patch.diff 61 | ./build.sh 62 | cp e9patch ../../ 63 | cp e9tool ../../ 64 | echo -e "${GREEN}$0${OFF}: e9patch has been built..." 65 | else 66 | echo -e "${GREEN}$0${OFF}: using existing e9patch..." 67 | fi 68 | 69 | # STEP (2): build the E9Tool plugin: 70 | # build the E9Tool plugin for ELF: 71 | cd $SOURCE_DIR 72 | echo -e "${GREEN}$0${OFF}: building the hopper plugin..." 73 | echo "g++ -std=c++11 -fPIC -shared -o hopper-e9-plugin.so -O2 hopper-e9-plugin.cpp -I ." 74 | g++ -std=c++11 -fPIC -shared -o $PREFIX/hopper-e9-plugin-elf.so -O2 hopper-e9-plugin.cpp \ 75 | -I $PREFIX/tmp/e9patch-$VERSION/src/e9tool/ 76 | strip $PREFIX/hopper-e9-plugin-elf.so 77 | chmod a-x $PREFIX/hopper-e9-plugin-elf.so 78 | # build the E9Tool plugin for PE: 79 | g++ -std=c++11 -fPIC -shared -o $PREFIX/hopper-e9-plugin-pe.so -O2 hopper-e9-plugin.cpp \ 80 | -I $PREFIX/tmp/e9patch-$VERSION/src/e9tool/ -DWINDOWS 81 | strip $PREFIX/hopper-e9-plugin-pe.so 82 | chmod a-x $PREFIX/hopper-e9-plugin-pe.so 83 | 84 | # build cmp plugin 85 | # build cmp plugin for ELF: 86 | g++ -std=c++11 -fPIC -shared -o $PREFIX/hopper-instr-plugin-elf.so -O2 hopper-instr-plugin.cpp \ 87 | -I $PREFIX/tmp/e9patch-$VERSION/src/e9tool/ 88 | strip $PREFIX/hopper-instr-plugin-elf.so 89 | chmod a-x $PREFIX/hopper-instr-plugin-elf.so 90 | # build cmp plugin for PE 91 | g++ -std=c++11 -fPIC -shared -o $PREFIX/hopper-instr-plugin-pe.so -O2 hopper-instr-plugin.cpp \ 92 | -I $PREFIX/tmp/e9patch-$VERSION/src/e9tool/ -DWINDOWS 93 | strip $PREFIX/hopper-instr-plugin-pe.so 94 | chmod a-x $PREFIX/hopper-instr-plugin-pe.so 95 | 96 | # STEP (3): build the runtime: 97 | # build the runtime for ELF 98 | echo -e "${GREEN}$0${OFF}: building the hopper runtime..." 99 | echo -e "${PREFIX}/tmp/e9patch-${VERSION}/e9compile.sh hopper-e9-rt.c -I ${PREFIX}/tmp/e9patch-${VERSION}/examples/ \ 100 | -I ${PREFIX}/tmp/e9patch-${VERSION}/src/e9patch/ -DNO_GLIBC=1" 101 | $PREFIX/tmp/e9patch-$VERSION/e9compile.sh hopper-e9-rt.c -I $PREFIX/tmp/e9patch-$VERSION/examples/ \ 102 | -I $PREFIX/tmp/e9patch-$VERSION/src/e9patch/ -DNO_GLIBC=1 103 | rm hopper-e9-rt.o 104 | chmod a-x hopper-e9-rt 105 | mv hopper-e9-rt $PREFIX/hopper-e9-rt-elf 106 | # build the runtime for PE: 107 | echo -e "${GREEN}$0${OFF}: remember to change HOPPER_PATH_SHMID and HOPPER_INSTR_SHMID in windows.c" 108 | $PREFIX/tmp/e9patch-$VERSION/e9compile.sh hopper-e9-rt.c -I $PREFIX/tmp/e9patch-$VERSION/examples/ \ 109 | -I $PREFIX/tmp/e9patch-$VERSION/src/e9patch/ -DWINDOWS -mabi=ms 110 | rm hopper-e9-rt.o 111 | chmod a-x hopper-e9-rt 112 | mv hopper-e9-rt $PREFIX/hopper-e9-rt-pe 113 | 114 | # STEP (4): build the driver: 115 | # g++ -std=c++11 -fPIC -pie -O2 -o e9hopper e9hopper.cpp 116 | # strip e9hopper 117 | 118 | echo -e "${GREEN}$0${OFF}: done!" 119 | echo 120 | -------------------------------------------------------------------------------- /hopper-instrument/e9-mode/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _HAVE_COMMON_CONFIG_H 2 | #define _HAVE_COMMON_CONFIG_H 3 | 4 | #ifndef MAP_SIZE_POW2 5 | #define MAP_SIZE_POW2 16 6 | #endif 7 | #define MAP_SIZE ((size_t)1 << MAP_SIZE_POW2) 8 | 9 | // coverage 10 | #ifndef WINDOWS 11 | #define AREA_BASE 0x200000 12 | #else 13 | #define AREA_BASE 0x47E00000 14 | #endif 15 | #define AREA_SIZE MAP_SIZE 16 | #define AREA_POINTER ((uint8_t *)AREA_BASE) 17 | 18 | // cmp and mem 19 | #define CMP_ITEM_SIZE 32 20 | #define INSTR_AREA (AREA_BASE + 0x100000) 21 | #define CMP_AREA INSTR_AREA 22 | #define CMP_AREA_SIZE 0x80000 23 | #define CMP_LIST_SIZE (CMP_AREA_SIZE / CMP_ITEM_SIZE) 24 | #define MEM_AREA (INSTR_AREA + CMP_AREA_SIZE) 25 | #define MEM_AREA_SIZE 0x30000 26 | #define MEM_ITEM_SIZE 24 27 | #define MEM_LIST_SIZE (MEM_AREA_SIZE / MEM_ITEM_SIZE) 28 | #define INSTR_AREA_SIZE (CMP_AREA_SIZE + MEM_AREA_SIZE) 29 | #define INFO_AREA (INSTR_AREA + INSTR_AREA_SIZE) 30 | #define INSTR_INFO_SIZE 64 31 | #define INSTR_ALL_SIZE (INSTR_AREA_SIZE + INSTR_INFO_SIZE) 32 | #define INSTR_AREA_POINTER ((uint64_t *)INSTR_AREA) 33 | 34 | // for canary 35 | #define CANARY_PTR (INSTR_AREA + 0x100000) 36 | #define CANARY_AREA_SIZE 0x100000 37 | 38 | #ifndef WINDOWS 39 | #define arg1 rdi 40 | #define arg2 rsi 41 | #else 42 | #define arg1 rcx 43 | #define arg2 rdx 44 | #endif 45 | 46 | #endif /* ! _HAVE_DEFS_H */ 47 | -------------------------------------------------------------------------------- /hopper-instrument/e9-mode/e9patch.diff: -------------------------------------------------------------------------------- 1 | diff -ruNa ./src/e9patch/e9loader_pe.cpp ./src/e9patch_patch/e9loader_pe.cpp 2 | --- ./src/e9patch/e9loader_pe.cpp 2022-07-06 17:04:31.091502513 +0800 3 | +++ ./src/e9patch2/e9loader_pe.cpp 2022-07-06 17:06:20.076026514 +0800 4 | @@ -486,7 +486,7 @@ 5 | kernel32 = (const uint8_t *)entry->DllBase; 6 | else if (e9wcscasecmp(name->Buffer, L"ntdll.dll") == 0) 7 | ntdll = (const uint8_t *)entry->DllBase; 8 | - else if (e9wcscasecmp(name->Buffer, L"user32.dll") == 0) 9 | + else if (e9wcscasecmp(name->Buffer, L"msvcrt.dll") == 0) 10 | user32 = (const uint8_t *)entry->DllBase; 11 | curr = curr->Flink; 12 | } 13 | @@ -516,8 +516,8 @@ 14 | config->magic[4] != 'T' || config->magic[5] != 'C' || 15 | config->magic[6] != 'H' || config->magic[7] != '\0') 16 | e9error("missing \"E9PATCH\" magic number"); 17 | - if (config->inits != 0x0) 18 | - e9error("custom initialization functions are not-yet-implemented"); 19 | + // if (config->inits != 0x0) 20 | + // e9error("custom initialization functions are not-yet-implemented"); 21 | if (config->finis != 0x0) 22 | e9error("custom finalization functions are not-yet-implemented"); 23 | if (config->mmap != 0x0) 24 | @@ -641,7 +641,18 @@ 25 | uint32_t old_prot; 26 | (void)VirtualProtect(base, config->size, PAGE_EXECUTE_READ, &old_prot); 27 | } 28 | - 29 | + 30 | + if (config->inits != 0x0) 31 | + { 32 | + const intptr_t *inits = (const intptr_t *)(loader_base + config->inits); 33 | + typedef void (*init_t)(const struct e9_config_s *config); 34 | + for (uint32_t i = 0; i < config->num_inits; i++) 35 | + { 36 | + init_t init = (init_t)(inits[i]+image_base); 37 | + init(config); 38 | + } 39 | + } 40 | + 41 | return entry; 42 | } 43 | 44 | diff -ruNa ./src/e9patch/e9pe.cpp ./src/e9patch2/e9pe.cpp 45 | --- ./src/e9patch/e9pe.cpp 2022-07-06 17:04:31.091502513 +0800 46 | +++ ./src/e9patch2/e9pe.cpp 2022-07-06 17:06:20.076026514 +0800 47 | @@ -384,6 +384,16 @@ 48 | memcpy(data + size, e9loader_pe_bin, sizeof(e9loader_pe_bin)); 49 | size += sizeof(e9loader_pe_bin); 50 | 51 | + config->inits = (B->inits.size() > 0? (uint32_t)(size - config_offset): 0); 52 | + for (auto init: B->inits) 53 | + { 54 | + intptr_t addr = BASE_ADDRESS(init); 55 | + addr |= (IS_ABSOLUTE(init)? E9_ABS_ADDR: 0); 56 | + memcpy(data + size, &addr, sizeof(addr)); 57 | + size += sizeof(addr); 58 | + config->num_inits++; 59 | + } 60 | + 61 | uint32_t loader_virtual_size = (uint32_t)(size - config_offset); 62 | size = ALIGN(size, file_align); 63 | uint32_t loader_disk_size = (uint32_t)(size - config_offset); 64 | @@ -506,9 +516,9 @@ 65 | warning("ignoring `--loader-phdr' option for Windows PE binary"); 66 | if (option_loader_static_set) 67 | warning("ignoring `--loader-static' option for Windows PE binary"); 68 | - if (B->inits.size() > 0) 69 | - error("initialization routines are non-yet-implemented for " 70 | - "Windows PE binaries"); 71 | + // if (B->inits.size() > 0) 72 | + // error("initialization routines are non-yet-implemented for " 73 | + // "Windows PE binaries"); 74 | if (B->finis.size() > 0) 75 | error("finalization routines are non-yet-implemented for " 76 | "Windows PE binaries"); 77 | -------------------------------------------------------------------------------- /hopper-instrument/llvm-mode/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | CMakeLists.txt.user 3 | CMakeCache.txt 4 | CMakeFiles 5 | CMakeScripts 6 | Testing 7 | Makefile 8 | cmake_install.cmake 9 | install_manifest.txt 10 | compile_commands.json 11 | CTestTestfile.cmake 12 | _deps 13 | -------------------------------------------------------------------------------- /hopper-instrument/llvm-mode/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4) 2 | 3 | project(llvm_mode VERSION 1.3.0 LANGUAGES C CXX) 4 | 5 | include_directories(include) 6 | 7 | ##### Check LLVM 8 | find_package(LLVM REQUIRED CONFIG) 9 | 10 | if (LLVM_FOUND) 11 | message(STATUS "LLVM_VERSION_MAJOR: ${LLVM_VERSION_MAJOR}") 12 | message(STATUS "LLVM_VERSION_MINOR: ${LLVM_VERSION_MINOR}") 13 | if (NOT (${LLVM_VERSION_MAJOR} GREATER 9)) 14 | message(STATUS ${CMAKE_VERSION}) 15 | message(FATAL_ERROR "LLVM version must be at least 10!") 16 | endif() 17 | else() 18 | message(FATAL_ERROR "You haven't install LLVM !") 19 | endif() 20 | 21 | add_compile_definitions(LLVM_VERSION_MAJOR=${LLVM_VERSION_MAJOR}) 22 | 23 | ##### Compiler 24 | add_executable(HopperClang hopper-clang.c) 25 | set_target_properties(HopperClang PROPERTIES OUTPUT_NAME "hopper-clang") 26 | 27 | add_custom_command(TARGET HopperClang POST_BUILD 28 | COMMAND ln -sf "hopper-clang" "hopper-clang++") 29 | if (HOPPER_BIN_DIR) 30 | install (TARGETS HopperClang DESTINATION ${HOPPER_BIN_DIR}) 31 | install (FILES ${CMAKE_CURRENT_BINARY_DIR}/hopper-clang++ DESTINATION ${HOPPER_BIN_DIR}) 32 | endif() 33 | 34 | ## LLVM Pass 35 | 36 | if (NOT TARGET LLVMPassConfig) 37 | add_library(LLVMPassConfig INTERFACE IMPORTED) 38 | set_target_properties(LLVMPassConfig PROPERTIES 39 | INTERFACE_COMPILE_OPTIONS "-fno-rtti" #-fpic 40 | INTERFACE_INCLUDE_DIRECTORIES "${LLVM_INCLUDE_DIRS}" 41 | INTERFACE_LINK_DIRECTORIES "${LLVM_LIBRARY_DIRS}" 42 | # INTERFACE_LINK_OPTIONS "-Wl,-znodelete" 43 | ) 44 | endif() 45 | 46 | set (CMAKE_CXX_STANDARD 14) 47 | if(APPLE) 48 | # User teor2345 reports that this is required to make things work on MacOS X. 49 | set (CMAKE_MODULE_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -Wl,-flat_namespace -Wl,-undefined,suppress") 50 | else() 51 | # fix pass bug: https://github.com/sampsyo/llvm-pass-skeleton/issues/7#issuecomment-401834287 52 | set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_CXX_LINK_FLAGS} -Wl,-znodelete") 53 | endif() 54 | 55 | # add_library(HopperPrePass MODULE hopper-pre-pass.cc) 56 | # target_link_libraries(HopperPrePass LLVMPassConfig) 57 | # if (HOPPER_BIN_DIR) 58 | # install (TARGETS HopperPrePass DESTINATION ${HOPPER_BIN_DIR}) 59 | # endif() 60 | 61 | add_library(HopperPass MODULE hopper-pass.cc) 62 | target_link_libraries(HopperPass LLVMPassConfig) 63 | if (HOPPER_BIN_DIR) 64 | install (TARGETS HopperPass DESTINATION ${HOPPER_BIN_DIR}) 65 | endif() -------------------------------------------------------------------------------- /hopper-instrument/llvm-mode/Readme.md: -------------------------------------------------------------------------------- 1 | ## LLVM Mode 2 | - LLVM mode is still under development. Though it works currently, some features are not yet implemented. 3 | - our `llvm-mode` instrumentation needs llvm dependencies (>= LLVM 10.0) and only tested in LLVM 10.0 and LLVM 14.0 currently. 4 | 5 | ## Plan 6 | - [x] API-sensitive branch counting 7 | - [x] Cmp hooking 8 | - [x] Resource related hooking 9 | 10 | ## How to use 11 | 12 | - Compile your libraries with `hopper-clang`. 13 | ``` 14 | CC=hopper-clang 15 | CXX=hopper-clang++ 16 | ``` 17 | 18 | - Use hopper to compile the libraries. Hopper can identify the libraries are compiled by the `hopper-clang` and uses the `llvm-mode` automatically. 19 | ``` 20 | hopper compile --header ./cJSON.h --library ./libcjson.so --output output 21 | ``` -------------------------------------------------------------------------------- /hopper-instrument/llvm-mode/include/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _HAVE_LLVM_CONFIG_H 2 | #define _HAVE_LLVM_CONFIG_H 3 | 4 | #ifndef MAP_SIZE_POW2 5 | #define MAP_SIZE_POW2 16 6 | #endif 7 | #ifdef HOPPER_MAP_SIZE_POW2 8 | #define MAP_SIZE_POW2 HOPPER_MAP_SIZE_POW2 9 | #endif 10 | #define MAP_SIZE ((size_t)1 << MAP_SIZE_POW2) 11 | 12 | 13 | #include 14 | #include 15 | typedef uint8_t u8; 16 | typedef uint16_t u16; 17 | typedef uint32_t u32; 18 | typedef int8_t s8; 19 | typedef int16_t s16; 20 | typedef int32_t s32; 21 | typedef int64_t s64; 22 | 23 | #endif /* ! _HAVE_DEFS_H */ -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md 2 | tab_spaces = 4 3 | edition = "2021" 4 | # max_width = 100 5 | use_small_heuristics = "Default" 6 | # indent_style = "Block" 7 | # combine_control_expr = false 8 | # brace_style = "SameLineWhere" 9 | # control_brace_style = "AlwaysSameLine" 10 | # merge_imports = true 11 | # match_block_trailing_comma = true 12 | -------------------------------------------------------------------------------- /testsuite/README.md: -------------------------------------------------------------------------------- 1 | # Testsuite for Hopper 2 | 3 | Testsuite includes some simple functions that wrote by developers artificially for testing. 4 | 5 | ## Build and run testsuite 6 | 7 | - build and run all tests 8 | ``` 9 | ./test.sh build_all 10 | ./test.sh test_all 11 | ``` 12 | 13 | - build and run specific test case 14 | ```sh 15 | # build library under `basic` directory 16 | ./test.sh build basic 17 | # test specific function under `basic`` directory 18 | ./test.sh test basic test_cmp_var 19 | ``` 20 | 21 | If test success (find any crash in N rounds, N is defined in test.sh), the script will print `test success`. 22 | otherwise, it will print `test fail`. 23 | 24 | ## How to write testcase 25 | 26 | If you want to define a new library, you should create a directory (e.g. test), and it should has following files: 27 | - test.c 28 | - test.h 29 | - custom.rule 30 | 31 | 32 | If you just want to add test case in an exsited library. 33 | - Define the *Entry* function by adding a function whose name is starts with `test_`. The *Entry* function could be *crash* by specific inputs. 34 | - Define the *TOOL* function if needed by adding a function whose name is starts with `util_`. The *Tool* functions is used for providing or mutating arguments for *Entry* functions. 35 | - Define the dependencies between functions. Just add a comment starts with `// depend: ` in the header file. 36 | 37 | ```c 38 | void util_set_gval(); 39 | // depend: util_set_gval 40 | void test_use_gval(int num); 41 | ``` 42 | 43 | - If you want to ignore a `test_*` function in `test` command. Just add a comment with *ignore* above its declaration. 44 | ```c 45 | // ignore 46 | void test_variadic_function_ptr(void (*)(int, ...), int); 47 | ``` 48 | 49 | - If you want to test whether the tool can infer some constraints succesfully or not, e.g. the first argument should be non-null and the second argument is the length of first one, you can define the constraints should be infered by following way. 50 | ```c 51 | // infer: @[$0] = $non_null; @[$1] = $len($0) 52 | void test_buf(char* ptr, int len); 53 | ``` 54 | 55 | - If the API function is expected to crash with *abort* signal, you can add `abort` for add checkings. `abort` is checked in default. 56 | ```c 57 | // abort 58 | void test_sth(int maigic); 59 | ``` 60 | -------------------------------------------------------------------------------- /testsuite/assert/assert.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int test_assert_eq(int magic) { 5 | if (magic == 23334) { 6 | return 1; 7 | } 8 | return 0; 9 | } 10 | 11 | int test_assert_neq(int magic) { 12 | if (magic == 23334) { 13 | return 1; 14 | } 15 | return 0; 16 | } -------------------------------------------------------------------------------- /testsuite/assert/assert.h: -------------------------------------------------------------------------------- 1 | /* 2 | Testing for assertion 3 | */ 4 | 5 | int test_assert_eq(int magic); 6 | 7 | int test_assert_neq(int magic); -------------------------------------------------------------------------------- /testsuite/assert/custom.rule: -------------------------------------------------------------------------------- 1 | assert test_assert_eq == 0 2 | assert test_assert_neq != 1 3 | -------------------------------------------------------------------------------- /testsuite/basic/basic.h: -------------------------------------------------------------------------------- 1 | /* 2 | Basic testing for C APIs 3 | */ 4 | 5 | #include "../common.h" 6 | 7 | /* Test value compare */ 8 | void test_cmp_var(int a, long b, char c); 9 | void test_cmp_struct(struct CmpStruct p); 10 | void test_cmp_struct2(struct CmpStruct* p); 11 | void test_switch(int a, int b); 12 | void test_switch2(int a); 13 | // ignore 14 | // float compare is not support now, it use ucomiss instruction 15 | void test_cmp_float(float a, float b); 16 | 17 | /* Implicitly Related calls */ 18 | void util_set_gval(); 19 | // depend: util_set_gval 20 | void test_use_gval(int num); 21 | 22 | /* utils */ 23 | char *util_static_ret(); 24 | 25 | /* Test for complicated strctures */ 26 | enum TestEnum { 27 | Mon, 28 | Tue, 29 | Wed, 30 | Thu, 31 | Fri, 32 | Sut, 33 | Sun, 34 | }; 35 | 36 | typedef union TestUnion { 37 | int i; 38 | float f; 39 | char str[20]; 40 | } TestUnion; 41 | 42 | 43 | typedef union TestUnion2 { 44 | int num; 45 | TestCustom *member1; 46 | int num2; 47 | } TestUnion2; 48 | 49 | typedef struct ComplicatedStruct { 50 | int ty; 51 | union { 52 | int num; 53 | TestCustom *member1; 54 | TestCustom2 member2; 55 | ListNode *member3; 56 | ListNodeWrapper *member4; 57 | ListNode *member5; 58 | ArrayWrap *member6; 59 | } inner_union; 60 | TestUnion2 *inner_union2; 61 | } ComplicatedStruct; 62 | void test_enum(enum TestEnum v1, enum TestEnum v2); 63 | void test_union(TestUnion2); 64 | void test_complicated_struct(ComplicatedStruct *); 65 | void test_complicated_struct2(ComplicatedStruct *); 66 | 67 | /* Test private fields for structure */ 68 | typedef struct ValWithPrivateField { 69 | int val; 70 | int __unused[16]; 71 | } ValWithPrivateField; 72 | 73 | void test_private_field(ValWithPrivateField obj); 74 | 75 | /* Test long and variadic arguments and function pointer */ 76 | void util_long_args_function(int, int, int, int, char, char, char, char, long, 77 | long, long, long, long); 78 | int util_variadic_function1(int, ...); 79 | int util_variadic_function2(int, ...); 80 | int util_variadic_function3(int, ...); 81 | void test_long_args_one_level(LONG_FN_PTR, int); 82 | void test_long_args_two_level(LONG_FN_PTR *, int); 83 | // ignore 84 | void test_variadic_function_ptr(int (*)(int, ...), int); 85 | 86 | -------------------------------------------------------------------------------- /testsuite/basic/custom.rule: -------------------------------------------------------------------------------- 1 | func_exclude util_static_ret 2 | 3 | func test_union[$0] = $use(member1) 4 | // type ComplicatedStruct["inner_union"] = $use("member2") <- test_complicated_struct[$0] 5 | // type ComplicatedStruct["ty"] = 2 <- test_complicated_struct[$0] 6 | func test_complicated_struct[$0][&.$0.inner_union] = $use(member2) 7 | func test_complicated_struct[$0][&.$0.ty] = 2 8 | // type ComplicatedStruct["inner_union2"] = $use("num") <- test_complicated_struct[$0] 9 | // type ComplicatedStruct["inner_union"] = $use("member3") <- test_complicated_struct2[$0] 10 | // type ComplicatedStruct["ty"] = 3 <- test_complicated_struct2[$0] 11 | func test_complicated_struct2[$0][&.$0.inner_union] = $use(member3) 12 | func test_complicated_struct2[$0][&.$0.ty] = 3 13 | -------------------------------------------------------------------------------- /testsuite/buf/buf.c: -------------------------------------------------------------------------------- 1 | #include "buf.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void test_load_buf(char *buf, int len) { 11 | if (len < 20) { 12 | return; 13 | } 14 | 15 | uint16_t x = 0; 16 | int32_t y = 0; 17 | int32_t z = 0; 18 | uint32_t a = 0; 19 | 20 | memcpy(&x, buf + 1, 2); // x 1 - 2 21 | memcpy(&y, buf + 4, 4); // y 4 - 7 22 | memcpy(&z, buf + 10, 4); // 10 - 13 23 | memcpy(&a, buf + 14, 4); // 14 - 17 24 | printf("x: %d, y:%d, z: %d, a: %d\n", x, y, z, a); 25 | if (x > 12300 && x < 12350 && z < -100000000 && z > -100000005 && 26 | z != -100000003 && y >= 987654321 && y <= 987654325 && a == 123456789) { 27 | printf("hey, you hit it \n"); 28 | abort(); 29 | } 30 | } 31 | 32 | void test_load_fp(FILE *fp) { 33 | char buf[255]; 34 | if (!fp) { 35 | printf("st err\n"); 36 | return; 37 | } 38 | int len = 20; 39 | size_t ret = fread(buf, sizeof *buf, len, fp); 40 | fclose(fp); 41 | printf("len: %ld\n", ret); 42 | if (ret < len) { 43 | printf("input fail \n"); 44 | return; 45 | } 46 | test_load_buf(buf, len); 47 | } 48 | 49 | 50 | void test_load_file(char *file_name) { 51 | FILE *fp = fopen(file_name, "rb"); 52 | test_load_fp(fp); 53 | } 54 | 55 | void test_load_file2(char *arg1) { 56 | FILE *fp = fopen(arg1, "rb"); 57 | test_load_fp(fp); 58 | } 59 | 60 | void test_load_file3(char *arg1) { 61 | printf("filename: %s\n", arg1); 62 | int fd = open(arg1, O_RDONLY); 63 | printf("fd: %d\n", fd); 64 | if (fd > 0) { 65 | char buf[50]; 66 | int n = read(fd, buf, 20); 67 | printf("read %d byte\n", n); 68 | if (n >= 20) { 69 | test_load_buf(buf, n); 70 | } 71 | } 72 | } 73 | 74 | void test_load_file4(ArrayWrap wrap) { 75 | FILE *fp = fopen(wrap.name, "rb"); 76 | test_load_fp(fp); 77 | } 78 | 79 | void test_load_fd(int fd) { 80 | FILE* fp = fdopen(fd, "rb"); 81 | test_load_fp(fp); 82 | } 83 | 84 | void test_load_fd2(FdWrap wrap) { 85 | FILE* fp = fdopen(wrap.fd, "rb"); 86 | test_load_fp(fp); 87 | } 88 | 89 | void test_long_buffer(char *buffer, int a) { 90 | if (buffer[550]) { 91 | if (a == 123456) { 92 | abort(); 93 | } 94 | } 95 | } 96 | 97 | void test_long_buffer2(ArrayWrap a, int b) { 98 | if (a.name != NULL && a.name[550]) { 99 | if (b == 123456) { 100 | abort(); 101 | } 102 | } 103 | } 104 | 105 | void test_long_buffer3(ArrayWrap *a, int b) { 106 | if (a != NULL && a->name[550]) { 107 | if (b == 123456) { 108 | abort(); 109 | } 110 | } 111 | } 112 | 113 | char *util_get_buf() { 114 | char *buf = malloc(50); 115 | memset(buf, 0, 50); 116 | strcpy(buf, "{ password 123456 }"); 117 | return buf; 118 | } 119 | 120 | char *util_get_buf2() { 121 | char *buf = malloc(50); 122 | memset(buf, 0, 50); 123 | strcpy(buf, "{ PASSWORD 666123 }"); 124 | return buf; 125 | } 126 | 127 | void test_buf_splice(int magic, char *buf) { 128 | if (buf == NULL) { 129 | return; 130 | } 131 | if (magic != 66666) return; 132 | 133 | char key[100] = "empty"; 134 | int value = 0; 135 | int ret = sscanf(buf, "{ %s %d }", key, &value); 136 | printf("key: %s, value: %d, ret: %d\n", key, value, ret); 137 | 138 | if (strncmp(key, "password", 10) == 0) { 139 | if (value == 66612) { 140 | abort(); 141 | } 142 | } 143 | printf("key: %s, value: %d, ret: %d\n", key, value, ret); 144 | if (strncmp(key, "PASSWORD", 10) == 0) { 145 | if (value == 123456) { 146 | abort(); 147 | } 148 | } 149 | } 150 | 151 | void test_buf_seed(char *buf, int len) { 152 | if (buf != NULL && len >= 9) { 153 | int val = atoi(buf); 154 | printf("buf: %s, val: %d\n", buf, val); 155 | if (val == 12345678) { 156 | abort(); 157 | } 158 | } 159 | } 160 | 161 | void test_buffer_len_and_non_null(int sw, ArrayWrap *array_list, int n) { 162 | if (n < 10) return; 163 | for (int i = 0; i < 10; i++) { 164 | ArrayWrap a = array_list[i]; 165 | for (int j = 0; j < a.len; j++) { 166 | printf("%c\n", a.name[j]); 167 | } 168 | } 169 | if (sw == 123456) { 170 | abort(); 171 | } 172 | } 173 | 174 | void test_dict(char *buf, int len) { 175 | if (len < 12) { 176 | return; 177 | } 178 | printf("last: %d\n", buf[len - 1]); 179 | if (buf[0] != 'h') { 180 | return; 181 | } 182 | for (int i = 0; i < 6; i++) { 183 | buf[i] = toupper(buf[0]); 184 | } 185 | if (strcmp(buf, "HOPPER") != 0) { 186 | printf("hopper\n"); 187 | if ((buf[6] - buf[7] == 0) && buf[6] == 0x66) { 188 | printf("66 \n"); 189 | if (buf[8] - buf[9] + buf[10] - buf[11] == 2) { 190 | abort(); 191 | } 192 | } 193 | } 194 | } -------------------------------------------------------------------------------- /testsuite/buf/buf.h: -------------------------------------------------------------------------------- 1 | #include "../common.h" 2 | 3 | /* Input buffer */ 4 | void test_load_buf(char *buf, int len); 5 | void test_load_fp(FILE *fp); 6 | 7 | /* File input */ 8 | // abort 9 | // infer: @[$0] = $read_file 10 | void test_load_file(char *file_name); 11 | // abort 12 | // infer: @[$0] = $read_file 13 | void test_load_file2(char *arg1); 14 | // abort 15 | // infer: @[$0] = $read_file 16 | void test_load_file3(char *arg1); 17 | // abort 18 | // infer: @[$0][name] = $read_file 19 | void test_load_file4(ArrayWrap warp); 20 | // abort 21 | // infer: @[$0] = $read_fd 22 | void test_load_fd(int fd); 23 | 24 | typedef struct FdWrap { 25 | char *name; 26 | int fd; 27 | } FdWrap; 28 | 29 | // abort 30 | // infer: @[$0][fd] = $read_fd 31 | void test_load_fd2(FdWrap wrap); 32 | 33 | void test_long_buffer(char *, int); 34 | void test_long_buffer2(ArrayWrap, int); 35 | void test_long_buffer3(ArrayWrap *, int); 36 | 37 | void test_dict(char *buf, int len); 38 | 39 | char *util_get_buf(); 40 | char *util_get_buf2(); 41 | 42 | // depend: util_get_buf,util_get_buf2 43 | // ignore 44 | void test_buf_splice(int magic, char *buf); 45 | 46 | void test_buf_seed(char *buf, int len); 47 | 48 | void test_buffer_len_and_non_null(int sw, ArrayWrap *array_list, int n); 49 | -------------------------------------------------------------------------------- /testsuite/buf/custom.rule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuzzAnything/Hopper/6dd62e84290c8567e5fbf063af1d7ec3c983add9/testsuite/buf/custom.rule -------------------------------------------------------------------------------- /testsuite/buf/dict: -------------------------------------------------------------------------------- 1 | # test dict 2 | kw1="hopper\x66\x66" 3 | # key word 2 4 | "2022" -------------------------------------------------------------------------------- /testsuite/buf/seeds/@buf/buf.txt: -------------------------------------------------------------------------------- 1 | 1234545555 -------------------------------------------------------------------------------- /testsuite/buf/seeds/seed1.txt: -------------------------------------------------------------------------------- 1 | 12345678 -------------------------------------------------------------------------------- /testsuite/common.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* Types and Structs */ 4 | typedef unsigned int uint32_t; 5 | typedef void (*LONG_FN_PTR)(int, int, int, int, char, char, char, char, long, 6 | long, long, long, long); 7 | typedef void *HANDLE; 8 | 9 | typedef struct TestCustom { 10 | char title[10]; 11 | int book_id; 12 | int cat_id; 13 | float price; 14 | } TestCustom; 15 | 16 | typedef struct CmpStruct { 17 | int x; 18 | int y; 19 | } CmpStruct; 20 | 21 | typedef struct TestCustom2 { 22 | int id; 23 | char content[10]; 24 | } TestCustom2; 25 | 26 | typedef struct ArrayWrap { 27 | char *name; 28 | int len; 29 | } ArrayWrap; 30 | 31 | typedef struct ListNode { 32 | int val; 33 | struct ListNode *next; 34 | struct ListNode *next2; 35 | struct ListNode *next3; 36 | } ListNode; 37 | 38 | typedef struct ListNodeWrapper { 39 | ListNode *inner; 40 | } ListNodeWrapper; 41 | -------------------------------------------------------------------------------- /testsuite/constraint/constraint.h: -------------------------------------------------------------------------------- 1 | /* 2 | Test for constraint inference 3 | */ 4 | #include "../common.h" 5 | 6 | // abort 7 | // infer: @[$1] = $non_zero 8 | void test_div_zero(int a, int b ); 9 | 10 | // abort 11 | // infer: @[$1][&.$0.name] = $non_null 12 | void test_null_ptr(int, ArrayWrap *, int); 13 | 14 | // abort 15 | // infer: @[$0][name] = $non_null 16 | void test_null_field(ArrayWrap arr); 17 | 18 | // abort 19 | // infer: @[$3][&.$0] = $len($2) 20 | void test_buffer_len(int, int, unsigned char*, int*); 21 | 22 | // infer: @[$0][len] = $len([$0][name]) 23 | void test_buffer_len_in_struct(ArrayWrap arr); 24 | 25 | // abort 26 | // infer: @[$2] = $len_factors($3, $4) 27 | void test_buffer_combined_len(int sw, int sw2, unsigned char *buffer, unsigned int a, unsigned int b); 28 | 29 | // abort 30 | // infer: @[$2] = $len_factors(2, $3) 31 | void test_buffer_len_with_constant(int sw, int sw2, unsigned char *buffer, unsigned int len); 32 | 33 | // abort 34 | void test_buffer_len_with_pos(int sw, int sw2, unsigned char *buffer, 35 | unsigned int n, unsigned int spos, unsigned int epos) ; 36 | 37 | // abort 38 | // infer: @[$2][&.$0.len] = $len([$2][&.$0.name]) 39 | void test_buffer_len2(int, int, ArrayWrap*); 40 | 41 | // abort 42 | // infer: @[$1] = $len($0) 43 | void test_buffer_len3(char *arg1, unsigned int arg2); 44 | 45 | // abort 46 | // infer: @[$2] = $len($0); @[$1] = $arr_len($len($0)) 47 | void test_two_buffer_len(char* buf1, char* buf2, int len, int sw); 48 | 49 | // abort 50 | // infer: @[$2] = $len($0); @[$1][&.$0] = $len([$0][&.$0]) 51 | // @[$1] = $arr_len($len($0)); 52 | void test_two_buffer_len2(char** bufs, int* sizes, int nbufs, int sw); 53 | 54 | // abort 55 | // infer: @[$1] = $range(0, $len($0)) 56 | void test_buffer_index(char *buf, unsigned int index, int magic); 57 | 58 | // abort 59 | // infer: @[$0] = $len_factors(3, 0..$len($1)) 60 | void test_buffer_index2(char *buf, unsigned int index, int magic); 61 | 62 | // infer: @[$1] = $range(0, $len(0)) 63 | void test_buffer_index3(char *name, int index); 64 | 65 | // abort 66 | // infer: @[$0] = $range(1, 4096); @[$1] = $range(1, 4096) 67 | void test_underflow(int val, int val2, int val3); 68 | 69 | // infer: @[$0] = $range(0, 4096); 70 | void test_oom(unsigned int num); 71 | 72 | // ignore 73 | // infer: @[$0] = $range(0, 4096); 74 | void test_timeout(unsigned int num); 75 | 76 | // infer: @[$0][&] = $arr_len(4) 77 | uint32_t test_get_uint_32(char* buf); -------------------------------------------------------------------------------- /testsuite/constraint/custom.rule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuzzAnything/Hopper/6dd62e84290c8567e5fbf063af1d7ec3c983add9/testsuite/constraint/custom.rule -------------------------------------------------------------------------------- /testsuite/pointer/custom.rule: -------------------------------------------------------------------------------- 1 | func test_custom_cast[$0] = $cast_from(*mut u32) 2 | func test_custom_cast2[$1] = $cast_from(*mut ListNode) 3 | // type Partial = $opaque 4 | -------------------------------------------------------------------------------- /testsuite/pointer/pointer.h: -------------------------------------------------------------------------------- 1 | /* 2 | Testing for pointers 3 | */ 4 | 5 | #include "../common.h" 6 | 7 | /* Test opaque pointer */ 8 | struct OpaqueType; 9 | struct OpaqueType *util_create_opaque(); 10 | void util_opaque_init(struct OpaqueType *ptr); 11 | // depend: util_create_opaque,util_opaque_init 12 | void test_opaque_arg(struct OpaqueType *ptr, int magic); 13 | 14 | // depend: util_create_opaque 15 | // infer: @[$0] = $need_init 16 | void test_opaque_arg2(struct OpaqueType *ptr); 17 | 18 | /* Test for type alias for opaque pointer */ 19 | typedef struct HandleWrap { 20 | HANDLE handle; 21 | } HandleWrap; 22 | HANDLE util_handle(); 23 | // depend: util_handle 24 | void test_handle(HANDLE handle, int magic); 25 | // depend: util_handle 26 | void test_handle_wrap(HandleWrap handle, int magic); 27 | 28 | /* Test opeauqe that partial exported */ 29 | struct Partial { 30 | int a; 31 | }; 32 | 33 | typedef struct Partial SemiOpaque; 34 | 35 | SemiOpaque *util_get_partial_pointer(); 36 | // depend: util_get_partial_pointer 37 | // abort 38 | // infer: Partial = $opaque 39 | void test_partial_pointer(SemiOpaque *ptr, int magic); 40 | 41 | /* Test opaque type with warpper */ 42 | typedef struct OpaqueWrapper { 43 | struct OpaqueType *opaque; 44 | } OpaqueWrapper; 45 | 46 | void util_init_opaque_type(struct OpaqueType **); 47 | // depend: util_init_opaque_type 48 | void test_init_opaque(OpaqueWrapper *, int); 49 | 50 | /* Test function pointers */ 51 | void util_fn_pointer(void (*f)(TestCustom *p), TestCustom *p); 52 | 53 | // depend: GENERATED_hopper_callback_* 54 | void test_function_pointer_ret(int a, TestCustom (*)(int, int)); 55 | 56 | // depend: GENERATED_hopper_callback_* 57 | void test_multi_func_pointer(TestCustom (*)(int, int), TestCustom (*)(int, int), TestCustom (*)(int)); 58 | 59 | /* Test pointers that makes a reference circle */ 60 | 61 | ListNode *util_reference_circle(); 62 | // depend: util_reference_circle 63 | // ignore 64 | void test_visit_list_node(ListNode *); 65 | // depend: util_reference_circle 66 | // ignore 67 | void test_visit_list_node2(ListNode **, int); 68 | // depend: util_reference_circle 69 | // ignore 70 | void test_visit_list_node3(ListNodeWrapper *); 71 | 72 | /* Test checking for pointer frees */ 73 | TestCustom2 *util_create_TestCustom2(); 74 | char *util_get_content(TestCustom2 *); 75 | // depend: util_get_content,util_create_TestCustom2 76 | // ignore 77 | void test_illegal_free(char *); 78 | 79 | typedef struct PtrFnWarp { 80 | void (*f)(void *f); 81 | } PtrFnWarp; 82 | void util_set_free_fn(PtrFnWarp* f_wrap); 83 | // depend: util_set_free_fn 84 | // ignore 85 | void test_indirect_free_ptr(PtrFnWarp* f_wrap); 86 | 87 | /* Explicitly Related calls */ 88 | TestCustom *util_create_pointer(char *title, int n); 89 | void util_free_pointer(TestCustom *b); 90 | // depend: util_create_pointer 91 | void test_with_update(TestCustom *b); 92 | 93 | /* Type Casting */ 94 | void test_custom_cast(void *p); 95 | // void is cast to a type that contains pointer 96 | void test_custom_cast2(int magic, void* arg); 97 | 98 | // abort 99 | // infer: @[$0] = $cast_from(*mut i8) 100 | void test_infer_cast(void *p); 101 | 102 | // abort 103 | // infer: @[$0][&.$0] = $cast_from(*mut i8) 104 | void test_infer_cast2(void **p); 105 | 106 | typedef struct PtrWrap { 107 | void* ptr; 108 | } PtrWrap; 109 | 110 | // abort 111 | // infer: @[$0][ptr] = $cast_from(*mut i8) 112 | void test_infer_cast3(PtrWrap p); -------------------------------------------------------------------------------- /testsuite/strcmp/custom.rule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuzzAnything/Hopper/6dd62e84290c8567e5fbf063af1d7ec3c983add9/testsuite/strcmp/custom.rule -------------------------------------------------------------------------------- /testsuite/strcmp/dict: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuzzAnything/Hopper/6dd62e84290c8567e5fbf063af1d7ec3c983add9/testsuite/strcmp/dict -------------------------------------------------------------------------------- /testsuite/strcmp/strcmp.c: -------------------------------------------------------------------------------- 1 | #include "strcmp.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void test_strcmp(char *s) { 8 | printf("addr: %p\n", s); 9 | if (s != NULL && strcmp(s, "test") == 0) { 10 | abort(); 11 | } 12 | } 13 | 14 | void test_strncmp(char *s) { 15 | printf("addr: %p\n", s); 16 | if (s != NULL && strncmp(s, "test445566", 10) == 0) { 17 | abort(); 18 | } 19 | } 20 | 21 | static char *TEST_STR = "test112233"; 22 | 23 | void test_strcmp2(char *s) { 24 | printf("addr1: %p, addr2: %p\n", s, TEST_STR); 25 | if (s != NULL && strcmp(s, TEST_STR) == 0) { 26 | abort(); 27 | } 28 | } 29 | 30 | void test_strcmp_indirect(char *s) { 31 | printf("addr: %p\n", s); 32 | if (s != NULL && strlen(s) >= 8) { 33 | printf("s: %d %d %d %d\n", s[4], s[5], s[6], s[7]); 34 | char buf[10]; 35 | strncpy(buf, &s[4], 4); 36 | buf[4] = 0; 37 | printf("buf: %p: %d %d %d %d\n", buf, buf[0], buf[1], buf[2], buf[3]); 38 | if (s != NULL && strcmp(buf, "test") == 0) { 39 | abort(); 40 | } 41 | } 42 | } 43 | 44 | void test_strcmp_in_struct(TestCustom *b) { 45 | if (b != NULL && b->book_id == 20000) { 46 | if (b->cat_id > 12345 && b->cat_id < 22222) { 47 | if (strcmp(b->title, "test") == 0) { 48 | printf("boom at targetp! id: %d, cat: %d\n", b->book_id, b->cat_id); 49 | abort(); 50 | } 51 | } 52 | } 53 | } 54 | 55 | uint32_t TRST_ARR[] = {1, 2, 3, 4, 5, 6, 7, 8}; 56 | void test_memcmp(uint32_t *s, int n) { 57 | if (n > 8) n = 8; 58 | if (s != NULL && n > 0 && memcmp(s, TRST_ARR, n * 4) == 0) { 59 | abort(); 60 | } 61 | } 62 | 63 | #define VERSION "1.6.37" 64 | void test_match_version(char* ver) { 65 | int match = 1; 66 | int i = -1; 67 | if (ver != 0) 68 | { 69 | int found_dots = 0; 70 | 71 | do 72 | { 73 | i++; 74 | printf("%d vs %d \n", ver[i], VERSION[i]); 75 | if (ver[i] != VERSION[i]) { 76 | // printf("bingo\n"); 77 | match = 0; 78 | } 79 | if (ver[i] == '.') { 80 | found_dots++; 81 | } 82 | } while (found_dots < 2 && ver[i] != 0 && 83 | VERSION[i] != 0); 84 | } else { 85 | match = 0; 86 | } 87 | 88 | if (match != 0) { 89 | abort(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /testsuite/strcmp/strcmp.h: -------------------------------------------------------------------------------- 1 | /* 2 | Testing for string comparison 3 | */ 4 | #include 5 | typedef unsigned int uint32_t; 6 | typedef struct TestCustom { 7 | char title[10]; 8 | int book_id; 9 | int cat_id; 10 | float price; 11 | } TestCustom; 12 | 13 | 14 | void test_strcmp(char *s); 15 | void test_strcmp2(char *s); 16 | void test_strncmp(char *s); 17 | void test_strcmp_indirect(char *s); 18 | void test_strcmp_in_struct(TestCustom *b); 19 | 20 | /* compare in a loop */ 21 | void test_match_version(char* ver); 22 | 23 | /* Mem Related */ 24 | void test_memcmp(uint32_t *s, int n); 25 | -------------------------------------------------------------------------------- /tools/core_affinity.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CORE_NUM=$(grep -c ^processor /proc/cpuinfo) 4 | USED_CORES=() 5 | TASK_SET_CMD="" 6 | 7 | for i in $(seq 0 $CORE_NUM); do 8 | USED_CORES[i]=0 9 | done 10 | 11 | find_free_cores() { 12 | for FILE in /proc/[0-9]*/status; do 13 | # echo $FILE 14 | has_vm=$(cat $FILE | grep 'VmSize:') 15 | if [[ -z $has_vm ]]; then 16 | continue 17 | fi 18 | allow_list=$(cat $FILE | grep '^Cpus_allowed_list:' | awk '{print $NF}') 19 | # echo $allow_list 20 | if [[ $allow_list == *,* ]]; then 21 | #echo "has ," 22 | continue 23 | fi 24 | if [[ $allow_list == *-* ]]; then 25 | # echo "has -" 26 | continue 27 | fi 28 | # FREE_CORES+=($allow_list) 29 | # echo "$allow_list is used" 30 | USED_CORES[$allow_list]=1 31 | done 32 | } 33 | 34 | find_core_for_task_set() { 35 | if [[ ! -z "${DOCKER_RUNNING:-}" ]]; then 36 | return 37 | fi 38 | find_free_cores 39 | echo "free cores: ${USED_CORES[@]}" 40 | 41 | for i in $(seq 0 $CORE_NUM); do 42 | if [ "${USED_CORES[$i]}" -eq "0" ]; then 43 | echo "core $i is free, set task to it" 44 | TASK_SET_CMD="taskset -c $i" 45 | break 46 | fi 47 | done 48 | } 49 | -------------------------------------------------------------------------------- /tools/style.sh: -------------------------------------------------------------------------------- 1 | RED='\033[1;31m' 2 | YELLOW='\033[1;33m' 3 | GREEN="\033[1;32m" 4 | NC='\033[0m' # No Color 5 | 6 | # determine if the output is on a terminal 7 | # if so, output colored text 8 | # otherwise output plain text 9 | output_is_terminal() { 10 | if [ -t 1 ]; then 11 | return 0 12 | else 13 | return -1 14 | fi 15 | } 16 | 17 | info() { 18 | if output_is_terminal; then 19 | printf "${GREEN}[+] $@${NC}\n" 20 | else 21 | printf $@ 22 | fi; 23 | } 24 | 25 | warn() { 26 | if output_is_terminal; then 27 | printf "${YELLOW}[-] $@${NC}\n" 28 | else 29 | printf $@ 30 | fi; 31 | } 32 | 33 | error() { 34 | if output_is_terminal; then 35 | printf "${RED}[x] $@${NC}\n" 36 | else 37 | printf $@ 38 | fi; 39 | } 40 | --------------------------------------------------------------------------------