├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── app.cpp ├── app.h ├── cbr.cpp ├── cbr.h ├── cfg.cpp ├── cfg_impl.cpp ├── cfg_impl.h ├── cti.cpp ├── cti.h ├── json.hpp └── test ├── call.asm ├── call.out ├── cbr.asm ├── cbr.out ├── jmp.asm └── jmp.out /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | project (cfg) 3 | 4 | add_library(cfg SHARED cfg.cpp cfg_impl.cpp cbr.cpp cti.cpp app.cpp) 5 | find_package(DynamoRIO REQUIRED) 6 | configure_DynamoRIO_client(cfg) 7 | use_DynamoRIO_extension(cfg "drmgr") 8 | use_DynamoRIO_extension(cfg "droption") 9 | set(CMAKE_CXX_FLAGS "-std=c++14 -flto") 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Toshi Piazza 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | drcfg 2 | ===== 3 | 4 | Constructs a CFG of the target program, without source. For motivation, see this [blog 5 | post](https://tpiazza.me/posts/2016-11-04-dynamorio_cfg.html). This plugin implements the 6 | following: 7 | 8 | - intercepts conditional branch instructions (`cbr's`) 9 | - intercepts control transfer instructions (`cti's`) 10 | - dumps output in json format 11 | 12 | # Usage 13 | 14 | ``` 15 | $ drrun -c ./libcfg.so -- ../test/call.out 16 | { 17 | "branches": [ 18 | 19 | { 20 | "address": 4194497, 21 | "targets": [ 22 | 4194500 23 | ] 24 | }, 25 | 26 | ] 27 | } 28 | ``` 29 | 30 | Options for `drcfg` are shown below: 31 | 32 | ``` 33 | -only_from_app [ false] Only count app, not lib, instructions 34 | -instrument_ret [ false] Count return instructions as control flow instructions 35 | -racy [ false] Perform racy hashtable insertion 36 | -no_cbr [ false] Don't count conditional branch instructions 37 | -no_cti [ false] Don't count control transfer instructions 38 | -output [ ""] Output results to file 39 | ``` 40 | 41 | # How to Build 42 | 43 | ``` 44 | $ mkdir -p build && cd build 45 | $ cmake .. -DDynamoRIO_DIR= 46 | $ make -j4 47 | ``` 48 | 49 | # TODO 50 | 51 | Listed in relative order of importance 52 | 53 | - [x] Implement cache flushing as per `cbr.c`, to remove instrumentation once a branch has 54 | or has not been taken. This will hopefully speed up applications considerably. 55 | - [x] Optionally intercept only branches in main module (i.e. `-only_from_app`) 56 | - [x] Dump json to a file 57 | - [ ] Optionally dump YAML 58 | - [x] Optionally instrument return cti's 59 | -------------------------------------------------------------------------------- /app.cpp: -------------------------------------------------------------------------------- 1 | #include "dr_api.h" 2 | #include "droption.h" 3 | #include "app.h" 4 | 5 | static droption_t only_from_app 6 | (DROPTION_SCOPE_CLIENT, "only_from_app", false, 7 | "Only count app, not lib, instructions", ""); 8 | static app_pc exe_start; 9 | 10 | void 11 | app_init(void) 12 | { 13 | /* Get main module address */ 14 | if (only_from_app.get_value()) { 15 | module_data_t *exe = dr_get_main_module(); 16 | if (exe != NULL) 17 | exe_start = exe->start; 18 | dr_free_module_data(exe); 19 | } 20 | } 21 | 22 | bool 23 | app_should_ignore_tag(void *tag) 24 | { 25 | /* Only count in app BBs */ 26 | if (only_from_app.get_value()) { 27 | module_data_t *mod = dr_lookup_module(dr_fragment_app_pc(tag)); 28 | if (mod != NULL) { 29 | bool from_exe = (mod->start == exe_start); 30 | dr_free_module_data(mod); 31 | return !from_exe; 32 | } 33 | } 34 | return false; 35 | } 36 | -------------------------------------------------------------------------------- /app.h: -------------------------------------------------------------------------------- 1 | #ifndef APP_H_ 2 | #define APP_H_ 3 | 4 | void app_init(void); 5 | bool app_should_ignore_tag(void *tag); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /cbr.cpp: -------------------------------------------------------------------------------- 1 | #include "dr_api.h" 2 | #include "cbr.h" 3 | #include "cfg_impl.h" 4 | #include "app.h" 5 | 6 | #include 7 | 8 | static void 9 | taken_or_not(uintptr_t src, uintptr_t targ) 10 | { 11 | dr_mcontext_t mcontext = {sizeof(mcontext),DR_MC_ALL,}; 12 | void *drcontext = dr_get_current_drcontext(); 13 | 14 | safe_insert(src, targ); 15 | 16 | /* Remove the bb from the cache so it will be re-built the next 17 | * time it executes. 18 | */ 19 | /* Since the flush will remove the fragment we're already in, 20 | * redirect execution to the fallthrough address. 21 | */ 22 | dr_flush_region((app_pc)src, 1); 23 | dr_get_mcontext(drcontext, &mcontext); 24 | mcontext.pc = (app_pc)targ; 25 | dr_redirect_execution(&mcontext); 26 | } 27 | 28 | dr_emit_flags_t 29 | cbr_event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, 30 | bool for_trace, bool translating, void *user_data) 31 | { 32 | if (!instr_is_cbr(instr)) 33 | return DR_EMIT_DEFAULT; 34 | if (app_should_ignore_tag(tag)) 35 | return DR_EMIT_DEFAULT; 36 | 37 | app_pc src = instr_get_app_pc(instr); 38 | app_pc fall = (app_pc)decode_next_pc(drcontext, (byte *)src); 39 | app_pc targ = instr_get_branch_target_pc(instr); 40 | 41 | bool insert_targ = !branch_present((uintptr_t)src, (uintptr_t)targ); 42 | bool insert_fall = !branch_present((uintptr_t)src, (uintptr_t)fall); 43 | 44 | if (insert_targ || insert_fall) { 45 | instr_t *label = INSTR_CREATE_label(drcontext); 46 | instr_set_meta_no_translation(instr); 47 | if (instr_is_cti_short(instr)) 48 | instr = instr_convert_short_meta_jmp_to_long(drcontext, bb, instr); 49 | instr_set_target(instr, opnd_create_instr(label)); 50 | 51 | if (insert_fall) { 52 | dr_insert_clean_call(drcontext, bb, NULL, (void *)taken_or_not, false, 2, 53 | OPND_CREATE_INTPTR(src), OPND_CREATE_INTPTR(fall)); 54 | } 55 | instrlist_preinsert(bb, NULL, INSTR_XL8 56 | (XINST_CREATE_jump 57 | (drcontext, 58 | opnd_create_pc(fall)), fall)); 59 | instrlist_meta_preinsert(bb, NULL, label); 60 | 61 | if (insert_targ) { 62 | dr_insert_clean_call(drcontext, bb, NULL, (void *)taken_or_not, false, 2, 63 | OPND_CREATE_INTPTR(src), OPND_CREATE_INTPTR(targ)); 64 | } 65 | instrlist_preinsert(bb, NULL, INSTR_XL8 66 | (XINST_CREATE_jump 67 | (drcontext, 68 | opnd_create_pc(targ)), targ)); 69 | } 70 | 71 | return DR_EMIT_STORE_TRANSLATIONS; 72 | } 73 | -------------------------------------------------------------------------------- /cbr.h: -------------------------------------------------------------------------------- 1 | #ifndef CBR_H_ 2 | #define CBR_H_ 3 | 4 | dr_emit_flags_t 5 | cbr_event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, 6 | bool for_trace, bool translating, void *user_data); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /cfg.cpp: -------------------------------------------------------------------------------- 1 | #include "dr_api.h" 2 | #include "drmgr.h" 3 | #include "droption.h" 4 | #include "cfg_impl.h" 5 | #include "cti.h" 6 | #include "cbr.h" 7 | #include "app.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | static droption_t no_cbr 14 | (DROPTION_SCOPE_CLIENT, "no_cbr", false, 15 | "Don't count conditional branch instructions", ""); 16 | 17 | static droption_t no_cti 18 | (DROPTION_SCOPE_CLIENT, "no_cti", false, 19 | "Don't count control transfer instructions", ""); 20 | 21 | static droption_t output 22 | (DROPTION_SCOPE_CLIENT, "output", "", 23 | "Output results to file", ""); 24 | 25 | void 26 | dr_exit(void) 27 | { 28 | if (output.get_value() == "") 29 | std::cout << std::setw(2) << construct_json() << std::endl; 30 | else { 31 | std::ofstream ofs(output.get_value()); 32 | ofs << std::setw(2) << construct_json() << std::endl; 33 | } 34 | drmgr_exit(); 35 | } 36 | 37 | DR_EXPORT 38 | void 39 | dr_client_main(client_id_t id, int argc, const char *argv[]) 40 | { 41 | dr_set_client_name("Dynamic CFG-generator", "toshi.piazza@gmail.com"); 42 | if (!droption_parser_t::parse_argv(DROPTION_SCOPE_CLIENT, argc, argv, NULL, NULL)) { 43 | std::cout << droption_parser_t::usage_short(DROPTION_SCOPE_CLIENT) << std::endl; 44 | exit(1); 45 | } 46 | 47 | app_init(); 48 | drmgr_init(); 49 | if (!no_cbr.get_value()) 50 | drmgr_register_bb_instrumentation_event(NULL, cbr_event_app_instruction, NULL); 51 | if (!no_cti.get_value()) 52 | drmgr_register_bb_instrumentation_event(NULL, cti_event_app_instruction, NULL); 53 | dr_register_exit_event(dr_exit); 54 | } 55 | -------------------------------------------------------------------------------- /cfg_impl.cpp: -------------------------------------------------------------------------------- 1 | #include "cfg_impl.h" 2 | #include "droption.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using nlohmann::json; 9 | 10 | static droption_t racy 11 | (DROPTION_SCOPE_CLIENT, "racy", false, 12 | "Perform racy hashtable insertion", ""); 13 | 14 | static std::unordered_map> cbr; 15 | static std::mutex mtx; 16 | 17 | void 18 | safe_insert(uintptr_t src, uintptr_t trg) 19 | { 20 | if (!racy.get_value()) { 21 | std::lock_guard g(mtx); 22 | cbr[src].insert(trg); 23 | } else 24 | cbr[src].insert(trg); 25 | } 26 | 27 | static json 28 | construct_json_impl() 29 | { 30 | json j; 31 | std::transform(std::begin(cbr), std::end(cbr), 32 | std::back_inserter(j["branches"]), 33 | [] (auto i) -> json { 34 | return { 35 | { "address", i.first }, 36 | { "targets", i.second } 37 | }; 38 | }); 39 | return j; 40 | } 41 | 42 | json 43 | construct_json() 44 | { 45 | if (!racy.get_value()) { 46 | std::lock_guard g(mtx); 47 | return construct_json_impl(); 48 | } else 49 | return construct_json_impl(); 50 | } 51 | 52 | bool 53 | branch_present(uintptr_t src, uintptr_t trg) 54 | { 55 | if (!racy.get_value()) { 56 | std::lock_guard g(mtx); 57 | return cbr[src].find(trg) != cbr[src].end(); 58 | } else 59 | return cbr[src].find(trg) != cbr[src].end(); 60 | } 61 | -------------------------------------------------------------------------------- /cfg_impl.h: -------------------------------------------------------------------------------- 1 | #ifndef CFG_IMPL_H_ 2 | #define CFG_IMPL_H_ 3 | 4 | #include "json.hpp" 5 | void safe_insert(uintptr_t src, uintptr_t trg); 6 | nlohmann::json construct_json(); 7 | bool branch_present(uintptr_t src, uintptr_t trg); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /cti.cpp: -------------------------------------------------------------------------------- 1 | #include "dr_api.h" 2 | #include "cti.h" 3 | #include "cfg_impl.h" 4 | #include "app.h" 5 | #include "droption.h" 6 | 7 | #include 8 | 9 | static droption_t instrument_ret 10 | (DROPTION_SCOPE_CLIENT, "instrument_ret", false, 11 | "Count return instructions as control flow instructions", ""); 12 | 13 | static void 14 | at_cti(uintptr_t src, uintptr_t targ) 15 | { 16 | safe_insert(src, targ); 17 | } 18 | 19 | dr_emit_flags_t 20 | cti_event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, 21 | bool for_trace, bool translating, void *user_data) 22 | { 23 | if (!instr_is_cti(instr)) 24 | return DR_EMIT_DEFAULT; 25 | if (instr_is_cbr(instr)) { 26 | // we already handle cbr's, more efficiently 27 | return DR_EMIT_DEFAULT; 28 | } 29 | if (app_should_ignore_tag(tag)) 30 | return DR_EMIT_DEFAULT; 31 | 32 | app_pc src = instr_get_app_pc(instr); 33 | if (instr_is_return(instr)) { 34 | // checking returns could help construct a more complete CFG in the case that 35 | // we see obfuscated control flow, i.e. returning to a different place than to 36 | // the original caller. 37 | if (instrument_ret.get_value()) { 38 | dr_insert_clean_call(drcontext, bb, instr, (void *)at_cti, false, 2, 39 | OPND_CREATE_INTPTR(src), OPND_CREATE_MEMPTR(DR_REG_XSP, 0)); 40 | } 41 | return DR_EMIT_DEFAULT; 42 | } 43 | 44 | opnd_t target_opnd = instr_get_target(instr); 45 | if (opnd_is_reg(target_opnd) || opnd_is_memory_reference(target_opnd)) { 46 | dr_insert_clean_call(drcontext, bb, instr, (void *)at_cti, false, 2, 47 | OPND_CREATE_INTPTR(src), target_opnd); 48 | } 49 | return DR_EMIT_DEFAULT; 50 | } 51 | -------------------------------------------------------------------------------- /cti.h: -------------------------------------------------------------------------------- 1 | #ifndef CTI_H_ 2 | #define CTI_H_ 3 | 4 | dr_emit_flags_t 5 | cti_event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, 6 | bool for_trace, bool translating, void *user_data); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /test/call.asm: -------------------------------------------------------------------------------- 1 | ; compile with nasm -felf64 foo.asm; ld foo.o -o foo.out 2 | bits 64 3 | 4 | section .data 5 | 6 | test2_ind dq test2 7 | 8 | section .start 9 | global _start 10 | _start: 11 | call test1 ; should not trigger 12 | 13 | test1: 14 | call [test2_ind] ; should trigger (?) 15 | 16 | test2: 17 | push test3 ; should trigger 18 | call [rsp] 19 | 20 | test3: 21 | mov rax, test4 ; should trigger 22 | call rax 23 | 24 | test4: 25 | mov rax, 60 26 | syscall 27 | -------------------------------------------------------------------------------- /test/call.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toshipiazza/drcfg/cd5d19a19889c61a63b803e49f895ccf3bec1aad/test/call.out -------------------------------------------------------------------------------- /test/cbr.asm: -------------------------------------------------------------------------------- 1 | bits 64 2 | 3 | section .start 4 | global _start 5 | _start: 6 | xor rax, rax 7 | jz always 8 | mov rax, [rax] ; crash 9 | always: 10 | add rax, 1 11 | jz never 12 | jmp should_not_trigger 13 | should_not_trigger: 14 | mov rax, 60 15 | syscall 16 | never: 17 | mov rax, [rax] ; crash 18 | -------------------------------------------------------------------------------- /test/cbr.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toshipiazza/drcfg/cd5d19a19889c61a63b803e49f895ccf3bec1aad/test/cbr.out -------------------------------------------------------------------------------- /test/jmp.asm: -------------------------------------------------------------------------------- 1 | ; compile with nasm -felf64 foo.asm; ld foo.o -o foo.out 2 | bits 64 3 | 4 | section .start 5 | global _start 6 | _start: 7 | jmp static_label 8 | 9 | static_label: 10 | mov rax, dynamic_label 11 | jmp rax 12 | 13 | dynamic_label: 14 | 15 | ; exit 16 | mov rax, 60 17 | syscall 18 | 19 | -------------------------------------------------------------------------------- /test/jmp.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toshipiazza/drcfg/cd5d19a19889c61a63b803e49f895ccf3bec1aad/test/jmp.out --------------------------------------------------------------------------------