├── .gitignore ├── BUILDs ├── gs-04162014.zip └── readme.txt ├── CMakeLists.txt ├── DOCs ├── BBMatch-GraphSlick.pptx ├── Shattering4.pptx ├── readme.txt └── usage.docx ├── GraphSlick.sln ├── Plugin.sln ├── Plugin.vcxproj ├── Plugin.vcxproj.filters ├── algo.cpp ├── algo.hpp ├── bbgroup ├── 3rdparty │ └── ordered-set-1.1.tar.gz ├── bb_ida.py ├── bb_match.py ├── bb_types.py ├── bb_utils.py ├── build_cache.py ├── deploy.bat ├── ida_stats.py ├── init.py ├── readme.txt ├── t1.py ├── t2.py └── t4.py ├── bin ├── plugins │ ├── GraphSlick │ │ ├── bb_ida.py │ │ ├── bb_match.py │ │ ├── bb_types.py │ │ ├── bb_utils.py │ │ └── init.py │ └── graphslick.plw └── sample_exe │ ├── code1.exe │ └── code1.idb ├── colorgen.cpp ├── colorgen.h ├── doc ├── .~lock.usage.docx# └── usage.docx ├── groupman.cpp ├── groupman.h ├── makefile ├── plugin.cpp ├── presentation ├── Shattering4.pdf └── Shattering4.pptx ├── pybbmatcher.cpp ├── pybbmatcher.h ├── pywraps.hpp ├── readme.txt ├── sample_code ├── sample_asm │ └── v1 │ │ ├── makeit.bat │ │ └── v1.asm └── sample_c │ ├── bin │ ├── v1 │ │ └── code1.cpp │ └── v2 │ │ └── code1.cpp │ ├── code1.cpp │ ├── code1.sln │ ├── code1.vcxproj │ ├── code1.vcxproj.filters │ ├── stdafx.cpp │ ├── stdafx.h │ └── targetver.h ├── stdalone.cpp ├── stdalone.sln ├── stdalone.vcxproj ├── types.hpp ├── util.cpp └── util.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.sdf 3 | *.opensdf 4 | *.obj 5 | *.user 6 | Debug/ 7 | Release/ 8 | ipch/ 9 | SR/ 10 | SemiDebug/ 11 | Debug64/ 12 | Release64/ 13 | -------------------------------------------------------------------------------- /BUILDs/gs-04162014.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/BUILDs/gs-04162014.zip -------------------------------------------------------------------------------- /BUILDs/readme.txt: -------------------------------------------------------------------------------- 1 | Installation 2 | ============= 3 | 4 | Python 5 | ------- 6 | Download and install https://pypi.python.org/pypi/ordered-set/1.1 7 | You may need to install the setuptools first 8 | 9 | 10 | IDA 11 | -------- 12 | 13 | Unzip the GS package in %IDADIR%. It will extract: 14 | 15 | plugins\GraphSlick.plw 16 | and 17 | plugins\GraphSlick\*.py <- python support files 18 | 19 | 20 | 21 | Usage 22 | ======== 23 | 24 | Just go to a big function and press Ctrl-4 -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) 2 | 3 | ################### Variables. #################### 4 | # Change if you want modify path or other values. # 5 | ################################################### 6 | 7 | set(PROJECT_NAME asdf) 8 | # Output Variables 9 | set(OUTPUT_DEBUG Debug/bin) 10 | set(OUTPUT_RELEASE Release/bin) 11 | # Folders files 12 | set(CPP_DIR_1 ./) 13 | set(CPP_DIR_2 ./) 14 | set(HEADER_DIR_1 ) 15 | 16 | ############## CMake Project ################ 17 | # The main options of project # 18 | ############################################# 19 | 20 | project(${PROJECT_NAME} CXX) 21 | 22 | # Define Release by default. 23 | if(NOT CMAKE_BUILD_TYPE) 24 | set(CMAKE_BUILD_TYPE "Release") 25 | message(STATUS "Build type not specified: Use Release by default.") 26 | endif(NOT CMAKE_BUILD_TYPE) 27 | 28 | # Definition of Macros 29 | add_definitions( 30 | -D__NT__ 31 | -D__NT__ 32 | -D_DEBUG 33 | -D_CONSOLE 34 | ) 35 | 36 | ############## Artefacts Output ################# 37 | # Defines outputs , depending Debug or Release. # 38 | ################################################# 39 | 40 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 41 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${OUTPUT_DEBUG}") 42 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${OUTPUT_DEBUG}") 43 | set(CMAKE_EXECUTABLE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${OUTPUT_DEBUG}") 44 | else() 45 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${OUTPUT_REL}") 46 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${OUTPUT_REL}") 47 | set(CMAKE_EXECUTABLE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${OUTPUT_REL}") 48 | endif() 49 | 50 | ################# Flags ################ 51 | # Defines Flags for Windows and Linux. # 52 | ######################################## 53 | 54 | if(MSVC) 55 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /W3 /EHsc") 56 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /W3 /EHsc") 57 | endif(MSVC) 58 | if(NOT MSVC) 59 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 60 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 61 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 62 | endif() 63 | endif(NOT MSVC) 64 | 65 | ################ Files ################ 66 | # -- Add files to project. -- # 67 | ####################################### 68 | 69 | file(GLOB SRC_FILES 70 | ${CPP_DIR_1}/*.cpp 71 | ${CPP_DIR_2}/*.cpp 72 | ${HEADER_DIR_1}/*.h 73 | ) 74 | 75 | # Add executable to build. 76 | add_executable(${PROJECT_NAME} 77 | ${SRC_FILES} 78 | ) 79 | 80 | if(MSVC) 81 | target_link_libraries(${PROJECT_NAME} kernel32.lib user32.lib gdi32.lib advapi32.lib shell32.lib uuid.lib pro.lib ) 82 | endif(MSVC) 83 | -------------------------------------------------------------------------------- /DOCs/BBMatch-GraphSlick.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/DOCs/BBMatch-GraphSlick.pptx -------------------------------------------------------------------------------- /DOCs/Shattering4.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/DOCs/Shattering4.pptx -------------------------------------------------------------------------------- /DOCs/readme.txt: -------------------------------------------------------------------------------- 1 | Installation 2 | ============= 3 | 4 | Python 5 | ------- 6 | 1) 7 | 8 | Install SetupTools from: 9 | 10 | https://pypi.python.org/pypi/setuptools 11 | 12 | Unpack and open an administrator prompt 13 | 14 | Run "python.exe ez_setup.py" and let it finish 15 | 16 | 2) 17 | 18 | Download Ordered Set package from: 19 | 20 | https://pypi.python.org/pypi/ordered-set/1.1 21 | 22 | Unpack the package and then run: 23 | 24 | "python.exe setup.py install" 25 | 26 | 27 | IDA 28 | -------- 29 | 30 | Copy the contents of "bin\*" to %IDADIR%. It will deploy the following: 31 | 32 | plugins\GraphSlick.plw 33 | and 34 | plugins\GraphSlick\*.py <- python support files 35 | 36 | 37 | Files 38 | ======== 39 | 40 | bin\ <- contains the binaries 41 | doc\ <- usage document 42 | src\ <- sources 43 | presentation\ <- presentation about how the algorithm works 44 | 45 | Usage 46 | ======== 47 | 48 | Please check doc\usage.doc 49 | 50 | 51 | Contact Info 52 | ============== 53 | Ali Rahbar 54 | Ali Pezeshk 55 | Elias Bachaalany -------------------------------------------------------------------------------- /DOCs/usage.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/DOCs/usage.docx -------------------------------------------------------------------------------- /GraphSlick.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2010 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin", "Plugin.vcxproj", "{50A58E54-616A-4BC4-A677-D3D73EF9103C}" 5 | EndProject 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stdalone", "stdalone.vcxproj", "{75680022-7B8C-4C67-A0C1-D51DB40B52B1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM = Debug|ARM 11 | Debug|Win32 = Debug|Win32 12 | Debug64|ARM = Debug64|ARM 13 | Debug64|Win32 = Debug64|Win32 14 | InlineTest|ARM = InlineTest|ARM 15 | InlineTest|Win32 = InlineTest|Win32 16 | Release|ARM = Release|ARM 17 | Release|Win32 = Release|Win32 18 | Release64|ARM = Release64|ARM 19 | Release64|Win32 = Release64|Win32 20 | SemiDebug|ARM = SemiDebug|ARM 21 | SemiDebug|Win32 = SemiDebug|Win32 22 | SR|ARM = SR|ARM 23 | SR|Win32 = SR|Win32 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug|ARM.ActiveCfg = Debug|Win32 27 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug|Win32.ActiveCfg = Debug|Win32 28 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug|Win32.Build.0 = Debug|Win32 29 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug64|ARM.ActiveCfg = Debug64|Win32 30 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug64|Win32.ActiveCfg = Debug64|Win32 31 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug64|Win32.Build.0 = Debug64|Win32 32 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.InlineTest|ARM.ActiveCfg = Release64|Win32 33 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.InlineTest|Win32.ActiveCfg = Release64|Win32 34 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.InlineTest|Win32.Build.0 = Release64|Win32 35 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release|ARM.ActiveCfg = Release|Win32 36 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release|Win32.ActiveCfg = Release|Win32 37 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release|Win32.Build.0 = Release|Win32 38 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release64|ARM.ActiveCfg = Release64|Win32 39 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release64|Win32.ActiveCfg = Release64|Win32 40 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release64|Win32.Build.0 = Release64|Win32 41 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.SemiDebug|ARM.ActiveCfg = Debug|Win32 42 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.SemiDebug|Win32.ActiveCfg = SemiRelease|Win32 43 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.SemiDebug|Win32.Build.0 = SemiRelease|Win32 44 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.SR|ARM.ActiveCfg = Release64|Win32 45 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.SR|Win32.ActiveCfg = Release64|Win32 46 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.SR|Win32.Build.0 = Release64|Win32 47 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug|ARM.ActiveCfg = Debug|ARM 48 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug|ARM.Build.0 = Debug|ARM 49 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug|Win32.ActiveCfg = Debug|Win32 50 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug64|ARM.ActiveCfg = Debug|ARM 51 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug64|ARM.Build.0 = Debug|ARM 52 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug64|Win32.ActiveCfg = Debug|Win32 53 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug64|Win32.Build.0 = Debug|Win32 54 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.InlineTest|ARM.ActiveCfg = InlineTest|ARM 55 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.InlineTest|ARM.Build.0 = InlineTest|ARM 56 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.InlineTest|Win32.ActiveCfg = InlineTest|Win32 57 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.InlineTest|Win32.Build.0 = InlineTest|Win32 58 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release|ARM.ActiveCfg = Release|ARM 59 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release|ARM.Build.0 = Release|ARM 60 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release|Win32.ActiveCfg = Release|Win32 61 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release64|ARM.ActiveCfg = Release|ARM 62 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release64|ARM.Build.0 = Release|ARM 63 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release64|Win32.ActiveCfg = Release|Win32 64 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release64|Win32.Build.0 = Release|Win32 65 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SemiDebug|ARM.ActiveCfg = SemiDebug|ARM 66 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SemiDebug|ARM.Build.0 = SemiDebug|ARM 67 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SemiDebug|Win32.ActiveCfg = SemiDebug|Win32 68 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SR|ARM.ActiveCfg = SR|ARM 69 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SR|ARM.Build.0 = SR|ARM 70 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SR|Win32.ActiveCfg = SR|Win32 71 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SR|Win32.Build.0 = SR|Win32 72 | EndGlobalSection 73 | GlobalSection(SolutionProperties) = preSolution 74 | HideSolutionNode = FALSE 75 | EndGlobalSection 76 | EndGlobal 77 | -------------------------------------------------------------------------------- /Plugin.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin", "Plugin.vcxproj", "{50A58E54-616A-4BC4-A677-D3D73EF9103C}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Debug64|Win32 = Debug64|Win32 10 | Release|Win32 = Release|Win32 11 | Release64|Win32 = Release64|Win32 12 | SemiRelease|Win32 = SemiRelease|Win32 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug|Win32.ActiveCfg = Debug|Win32 16 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug|Win32.Build.0 = Debug|Win32 17 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug64|Win32.ActiveCfg = Debug64|Win32 18 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Debug64|Win32.Build.0 = Debug64|Win32 19 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release|Win32.ActiveCfg = Release|Win32 20 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release|Win32.Build.0 = Release|Win32 21 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release64|Win32.ActiveCfg = Release64|Win32 22 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.Release64|Win32.Build.0 = Release64|Win32 23 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.SemiRelease|Win32.ActiveCfg = SemiRelease|Win32 24 | {50A58E54-616A-4BC4-A677-D3D73EF9103C}.SemiRelease|Win32.Build.0 = SemiRelease|Win32 25 | EndGlobalSection 26 | GlobalSection(SolutionProperties) = preSolution 27 | HideSolutionNode = FALSE 28 | EndGlobalSection 29 | EndGlobal 30 | -------------------------------------------------------------------------------- /Plugin.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | sdk 14 | 15 | 16 | sdk 17 | 18 | 19 | sdk 20 | 21 | 22 | sdk 23 | 24 | 25 | sdk 26 | 27 | 28 | sdk 29 | 30 | 31 | sdk 32 | 33 | 34 | sdk 35 | 36 | 37 | sdk 38 | 39 | 40 | sdk 41 | 42 | 43 | sdk 44 | 45 | 46 | sdk 47 | 48 | 49 | sdk 50 | 51 | 52 | sdk 53 | 54 | 55 | sdk 56 | 57 | 58 | sdk 59 | 60 | 61 | sdk 62 | 63 | 64 | sdk 65 | 66 | 67 | sdk 68 | 69 | 70 | sdk 71 | 72 | 73 | sdk 74 | 75 | 76 | sdk 77 | 78 | 79 | sdk 80 | 81 | 82 | sdk 83 | 84 | 85 | sdk 86 | 87 | 88 | sdk 89 | 90 | 91 | sdk 92 | 93 | 94 | sdk 95 | 96 | 97 | sdk 98 | 99 | 100 | sdk 101 | 102 | 103 | sdk 104 | 105 | 106 | sdk 107 | 108 | 109 | sdk 110 | 111 | 112 | sdk 113 | 114 | 115 | sdk 116 | 117 | 118 | sdk 119 | 120 | 121 | sdk 122 | 123 | 124 | sdk 125 | 126 | 127 | sdk 128 | 129 | 130 | sdk 131 | 132 | 133 | sdk 134 | 135 | 136 | sdk 137 | 138 | 139 | sdk 140 | 141 | 142 | sdk 143 | 144 | 145 | sdk 146 | 147 | 148 | sdk 149 | 150 | 151 | sdk 152 | 153 | 154 | sdk 155 | 156 | 157 | sdk 158 | 159 | 160 | sdk 161 | 162 | 163 | sdk 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | {9bf4ff27-a0ab-4e06-aa40-ecaa123bed4c} 176 | 177 | 178 | -------------------------------------------------------------------------------- /algo.cpp: -------------------------------------------------------------------------------- 1 | #include "algo.hpp" 2 | 3 | //-------------------------------------------------------------------------- 4 | bool func_to_mgraph( 5 | ea_t func_ea, 6 | mutable_graph_t *mg, 7 | gnodemap_t &node_map, 8 | qflow_chart_t *fc, 9 | bool append_node_id) 10 | { 11 | // Build function's flowchart (if needed) 12 | qflow_chart_t _fc; 13 | if (fc == NULL) 14 | { 15 | fc = &_fc; 16 | if (!get_func_flowchart(func_ea, *fc)) 17 | return false; 18 | } 19 | 20 | // Resize the graph 21 | int nodes_count = fc->size(); 22 | mg->resize(nodes_count); 23 | 24 | // Build the node cache and edges 25 | for (int nid=0; nid < nodes_count; nid++) 26 | { 27 | qbasic_block_t &block = fc->blocks[nid]; 28 | gnode_t *nc = node_map.add(nid); 29 | 30 | // Append node ID to the output 31 | if (append_node_id) 32 | nc->text.sprnt("ID(%d)\n", nid); 33 | 34 | // Generate disassembly text 35 | get_disasm_text( 36 | block.startEA, 37 | block.endEA, 38 | &nc->text); 39 | 40 | // Build edges 41 | for (int nid_succ=0, succ_sz=fc->nsucc(nid); nid_succ < succ_sz; nid_succ++) 42 | { 43 | int nsucc = fc->succ(nid, nid_succ); 44 | mg->add_edge(nid, nsucc, NULL); 45 | } 46 | } 47 | return true; 48 | } 49 | 50 | //-------------------------------------------------------------------------- 51 | void build_groupman_from_fc( 52 | qflow_chart_t *fc, 53 | groupman_t *gm, 54 | bool sanitize) 55 | { 56 | // Clear previous groupman contents 57 | gm->clear(); 58 | 59 | gm->src_filename = "noname.bbgroup"; 60 | 61 | // Resize the graph 62 | int nodes_count = fc->size(); 63 | 64 | // Build groupman 65 | for (int nid=0; nid < nodes_count; nid++) 66 | { 67 | qbasic_block_t &block = fc->blocks[nid]; 68 | 69 | psupergroup_t sg = gm->add_supergroup(); 70 | sg->id.sprnt("ID_%d", nid); 71 | sg->name.sprnt("SG_%d", nid); 72 | sg->is_synthetic = false; 73 | 74 | pnodegroup_t ng = sg->add_nodegroup(); 75 | pnodedef_t nd = ng->add_node(); 76 | 77 | nd->nid = nid; 78 | nd->start = block.startEA; 79 | nd->end = block.endEA; 80 | 81 | gm->map_nodedef(nid, nd); 82 | } 83 | 84 | if (sanitize) 85 | { 86 | if (sanitize_groupman(BADADDR, gm, fc)) 87 | gm->initialize_lookups(); 88 | } 89 | } 90 | 91 | //-------------------------------------------------------------------------- 92 | void build_groupman_from_3dvec( 93 | qflow_chart_t *fc, 94 | int_3dvec_t &path, 95 | groupman_t *gm, 96 | bool sanitize) 97 | { 98 | // Clear previous groupman contents 99 | gm->clear(); 100 | 101 | gm->src_filename = "noname.bbgroup"; 102 | 103 | // Build groupman 104 | int sg_id = 0; 105 | for (int_3dvec_t::iterator it_sg=path.begin(); 106 | it_sg != path.end(); 107 | ++it_sg, ++sg_id) 108 | { 109 | // Build super group 110 | psupergroup_t sg = gm->add_supergroup(); 111 | 112 | sg->id.sprnt("ID_%d", sg_id); 113 | sg->name.sprnt("SG_%d", sg_id); 114 | sg->is_synthetic = false; 115 | 116 | // Build SG 117 | int_2dvec_t &ng_vec = *it_sg; 118 | for (int_2dvec_t::iterator it_ng= ng_vec.begin(); 119 | it_ng != ng_vec.end(); 120 | ++it_ng) 121 | { 122 | // Build NG 123 | pnodegroup_t ng = sg->add_nodegroup(); 124 | intvec_t &nodes_vec = *it_ng; 125 | 126 | // Build nodes 127 | for (intvec_t::iterator it_nd = nodes_vec.begin(); 128 | it_nd != nodes_vec.end(); 129 | ++it_nd) 130 | { 131 | int nid = *it_nd; 132 | qbasic_block_t &block = fc->blocks[nid]; 133 | 134 | pnodedef_t nd = ng->add_node(); 135 | nd->nid = nid; 136 | nd->start = block.startEA; 137 | nd->end = block.endEA; 138 | 139 | gm->map_nodedef(nid, nd); 140 | } 141 | } 142 | } 143 | 144 | if (sanitize) 145 | { 146 | if (sanitize_groupman(BADADDR, gm, fc)) 147 | gm->initialize_lookups(); 148 | } 149 | } 150 | 151 | //-------------------------------------------------------------------------- 152 | bool sanitize_groupman( 153 | ea_t func_ea, 154 | groupman_t *gm, 155 | qflow_chart_t *fc) 156 | { 157 | // Build function's flowchart (if needed) 158 | qflow_chart_t _fc; 159 | if (fc == NULL) 160 | { 161 | fc = &_fc; 162 | if (!get_func_flowchart(func_ea, *fc)) 163 | return false; 164 | } 165 | 166 | // Create a group for all potentially missing nodes 167 | psupergroup_t missing_sg = new supergroup_t(); 168 | 169 | int nodes_count = fc->size(); 170 | 171 | nid2ndef_t *nds = gm->get_nds(); 172 | 173 | // Verify that all nodes are present 174 | for (int n=0; n < nodes_count; n++) 175 | { 176 | nid2ndef_t::iterator it = nds->find(n); 177 | if (it != nds->end()) 178 | continue; 179 | 180 | // Convert basic block to an ND 181 | qbasic_block_t &block = fc->blocks[n]; 182 | pnodedef_t nd = new nodedef_t(); 183 | nd->nid = n; 184 | nd->start = block.startEA; 185 | nd->end = block.endEA; 186 | 187 | // Add the node to its own group 188 | pnodegroup_t ng = missing_sg->add_nodegroup(); 189 | ng->add_node(nd); 190 | } 191 | 192 | if (missing_sg->gcount() == 0) 193 | { 194 | // No orphan nodes where found, get rid of the group 195 | delete missing_sg; 196 | } 197 | else 198 | { 199 | // Found at least one orphan node, add it to the groupman 200 | missing_sg->name = missing_sg->id = "orphan_nodes"; 201 | 202 | // This is a synthetic group 203 | missing_sg->is_synthetic = true; 204 | 205 | gm->add_supergroup( 206 | gm->get_path_sgl(), 207 | missing_sg); 208 | } 209 | 210 | return true; 211 | } -------------------------------------------------------------------------------- /algo.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __ALGO__ 2 | #define __ALGO__ 3 | 4 | /*-------------------------------------------------------------------------- 5 | GraphSlick (c) Elias Bachaalany 6 | ------------------------------------- 7 | 8 | Algorithms module 9 | 10 | This module implements various algorithms used by the plugin 11 | 12 | History 13 | -------- 14 | 15 | 10/23/2013 - eliasb - First version, it comes from refactored code from the plugin module 16 | 10/24/2013 - eliasb - Renamed class to a function like name and dropped the () operator 17 | 10/25/2013 - eliasb - Return the ndl2id map to the caller 18 | - added 'append_node_id' param to func_to_mgraph() 19 | 10/28/2013 - eliasb - add 'hint' value to the combined blocks 20 | - show the 'groupname' or 'id' as text for combined nodes 21 | 10/29/2013 - eliasb - added sanity checks to fc_to_combined_mg() 22 | - functions don't compute flowchart each time now. user can pass a flowchart 23 | - added sanitize_groupman() to allow loaded incomplete bbgroup file 24 | 11/01/2013 - eliasb - Now sanitize_groupman()' sanitized the path SGL only 25 | - Added build_groupman_from_fc and build_groupman_from_3dvec functions 26 | 04/10/2014 - eliasb - fix: Auto increment SG number when building the info from BBMatch!Analyze() 27 | --------------------------------------------------------------------------*/ 28 | 29 | 30 | //-------------------------------------------------------------------------- 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "groupman.h" 37 | #include "util.h" 38 | 39 | //-------------------------------------------------------------------------- 40 | /** 41 | * @brief Creates a mutable graph that have the combined nodes per the groupmanager 42 | * A class was used to simulate nested functions needed by the combine algo 43 | */ 44 | class fc_to_combined_mg 45 | { 46 | // Create a mapping between single node ids and the nodedef list they belong to 47 | ng2nid_t *group2id; 48 | 49 | gnodemap_t *node_map; 50 | groupman_t *gm; 51 | qflow_chart_t *fc; 52 | bool show_nids_only; 53 | 54 | /** 55 | * @brief Create and return a groupped node ID 56 | */ 57 | int get_groupid(int n) 58 | { 59 | int group_id; 60 | 61 | // Find how this single node is defined in the group manager 62 | nodeloc_t *loc = gm->find_nodeid_loc(n); 63 | if (loc == NULL) 64 | return -1; 65 | 66 | // Does this node have a group yet? (ndl) 67 | ng2nid_t::iterator it = group2id->find(loc->ng); 68 | if (it == group2id->end()) 69 | { 70 | // Assign an auto-increment id 71 | group_id = group2id->size(); 72 | (*group2id)[loc->ng] = group_id; 73 | 74 | // Initialize this group's node id 75 | gnode_t gn; 76 | gn.id = group_id; 77 | size_t t = loc->ng->size(); 78 | for (nodegroup_t::iterator it=loc->ng->begin(); 79 | it != loc->ng->end(); 80 | ++it) 81 | { 82 | if (show_nids_only) 83 | { 84 | gn.text.cat_sprnt("%d", (*it)->nid); 85 | if (--t > 0) 86 | gn.text.append(", "); 87 | } 88 | 89 | qbasic_block_t &block = fc->blocks[(*it)->nid]; 90 | qstring s; 91 | get_disasm_text( 92 | block.startEA, 93 | block.endEA, 94 | &s); 95 | gn.hint.append(s); 96 | } 97 | 98 | if (!show_nids_only) 99 | { 100 | // Are there any groupped nodes? 101 | if (loc->ng->size() > 1) 102 | { 103 | //TODO: OPTION: enlarge groupped label 104 | gn.text.append("\n\n\n"); 105 | 106 | // Display the group name or the group id 107 | gn.text.append(loc->sg->get_display_name()); 108 | 109 | gn.text.append("\n\n\n"); 110 | } 111 | else 112 | { 113 | gn.text = gn.hint; 114 | } 115 | } 116 | 117 | // Cache the node data 118 | (*node_map)[group_id] = gn; 119 | } 120 | else 121 | { 122 | // Grab the group id 123 | group_id = it->second; 124 | } 125 | 126 | return group_id; 127 | } 128 | 129 | /** 130 | * @brief Build the combined mutable graph from the groupman and a flowchart 131 | */ 132 | bool build( 133 | qflow_chart_t *fc, 134 | groupman_t *gm, 135 | gnodemap_t &node_map, 136 | ng2nid_t &group2id, 137 | mutable_graph_t *mg) 138 | { 139 | // Take a reference to the local variables so they are used 140 | // in the other helper functions 141 | this->gm = gm; 142 | this->node_map = &node_map; 143 | this->fc = fc; 144 | this->group2id = &group2id; 145 | 146 | // Compute the total size of nodes needed for the combined graph 147 | // The size is the total count of node def lists in each group def 148 | size_t node_count = 0; 149 | for (supergroup_listp_t::iterator it=gm->get_path_sgl()->begin(); 150 | it != gm->get_path_sgl()->end(); 151 | ++it) 152 | { 153 | psupergroup_t sg = *it; 154 | node_count += sg->gcount(); 155 | } 156 | 157 | // Resize the graph 158 | mg->resize(node_count); 159 | 160 | // Build the combined graph 161 | int snodes_count = fc->size(); 162 | for (int nid=0; nid < snodes_count; nid++) 163 | { 164 | // Figure out the combined node ID 165 | int group_id = get_groupid(nid); 166 | if (group_id == -1) 167 | return false; 168 | 169 | // Build the edges 170 | for (int isucc=0, succ_sz=fc->nsucc(nid); 171 | isucc < succ_sz; 172 | isucc++) 173 | { 174 | // Get the successor node 175 | int nid_succ = fc->succ(nid, isucc); 176 | 177 | // This node belongs to the same group? 178 | int succ_grid = get_groupid(nid_succ); 179 | if (succ_grid == -1) 180 | return false; 181 | 182 | if (succ_grid == group_id) 183 | { 184 | // Do nothing, consider as one node 185 | continue; 186 | } 187 | // Add an edge 188 | mg->add_edge(group_id, succ_grid, NULL); 189 | } 190 | } 191 | return true; 192 | } 193 | 194 | public: 195 | /** 196 | * @brief Operator to call the class as a function 197 | */ 198 | fc_to_combined_mg( 199 | ea_t func_ea, 200 | groupman_t *gm, 201 | gnodemap_t &node_map, 202 | ng2nid_t &group2id, 203 | mutable_graph_t *mg, 204 | qflow_chart_t *fc = NULL): show_nids_only(false) 205 | { 206 | // Build function's flowchart (if needed) 207 | qflow_chart_t _fc; 208 | if (fc == NULL) 209 | { 210 | fc = &_fc; 211 | if (!get_func_flowchart(func_ea, *fc)) 212 | return; 213 | } 214 | 215 | build(fc, gm, node_map, group2id, mg); 216 | } 217 | }; 218 | 219 | //-------------------------------------------------------------------------- 220 | /** 221 | * @brief Build a mutable graph from a function address 222 | */ 223 | bool func_to_mgraph( 224 | ea_t func_ea, 225 | mutable_graph_t *mg, 226 | gnodemap_t &node_map, 227 | qflow_chart_t *fc = NULL, 228 | bool append_node_id = false); 229 | 230 | //-------------------------------------------------------------------------- 231 | /** 232 | * @brief Build the groupman with each node in its node group and super group 233 | */ 234 | void build_groupman_from_fc( 235 | qflow_chart_t *fc, 236 | groupman_t *gm, 237 | bool sanitize); 238 | 239 | //-------------------------------------------------------------------------- 240 | /** 241 | * @brief Build the group manager from another groupman defined in a 3d int vec 242 | */ 243 | void build_groupman_from_3dvec( 244 | qflow_chart_t *fc, 245 | int_3dvec_t &path, 246 | groupman_t *gm, 247 | bool sanitize); 248 | 249 | //-------------------------------------------------------------------------- 250 | /** 251 | * @brief Sanitize the contents of the groupman path SGL versus the flowchart 252 | of the function 253 | */ 254 | bool sanitize_groupman( 255 | ea_t func_ea, 256 | groupman_t *gm, 257 | qflow_chart_t *fc = NULL); 258 | 259 | #endif -------------------------------------------------------------------------------- /bbgroup/3rdparty/ordered-set-1.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/bbgroup/3rdparty/ordered-set-1.1.tar.gz -------------------------------------------------------------------------------- /bbgroup/bb_ida.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains IDA specific basic block datatypes and helper functions 3 | 4 | 5 | 09/12/2013 - eliasb - Initial version 6 | 09/16/2013 - eliasb - Added IDABBman subclass of BBMan 7 | - Added hash_itype1() and IDABBContext() class 8 | 10/07/2013 - eliasb - Added hash2 algorithm 9 | 10/08/2013 - eliasb - Refactored code and fixed some bugs 10 | - Fixed a bug in hashing bounds 11 | - Added operand type into consideration in hash_itype2() 12 | - Added utility HashNode() 13 | - Added InstructionCount to IDABBContext structure 14 | 10/15/2013 - eliasb - Added get_cmd_prime_characteristics() and use it in hash_itype2() 15 | - Added get_block_frequency() 16 | 17 | 10/16/2013 - eliasb - Finished match_block_frequencies() 18 | - get_block_frequency() now returns the total instruction count as well 19 | - Block frequency table keys are now rekeyed with RekeyDictionary() 20 | 10/25/2013 - eliasb - Integrated Ali's changes: 21 | - Made Rekeying optional and off by default (since it will mess up relative comparison) 22 | - Enforce division by floats where needed 23 | - Avoid division by zero 24 | 25 | TODO: 26 | ------ 27 | 28 | """ 29 | 30 | stdalone = False 31 | """Desginates whether this module is running inside IDA or in stand alone mode""" 32 | 33 | # ------------------------------------------------------------------------------ 34 | try: 35 | import idaapi 36 | import idautils 37 | import hashlib 38 | 39 | from idaapi import UA_MAXOP, o_last, o_void 40 | except: 41 | stdalone = True 42 | 43 | UA_MAXOP = 6 44 | o_last = 14 45 | o_void = 0 46 | 47 | from bb_types import * 48 | import bb_utils 49 | 50 | # ------------------------------------------------------------------------------ 51 | def _get_cache_filename(addr): 52 | # Form the cache file name 53 | return "%016X.cache" % addr 54 | 55 | 56 | # ------------------------------------------------------------------------------ 57 | def InstructionCount(start, end): 58 | """Count the number of instructions""" 59 | icount = 0 60 | while start < end: 61 | cmd = idautils.DecodeInstruction(start) 62 | if cmd is None: 63 | break 64 | icount += 1 65 | start += cmd.size 66 | 67 | return icount 68 | 69 | 70 | # ------------------------------------------------------------------------------ 71 | def get_cmd_prime_characteristics(cmd): 72 | """ 73 | Compute the characteristics of an instruction and return a prime number 74 | """ 75 | 76 | # Compute the itype's prime number 77 | r = _CachedPrimes[cmd.itype] 78 | 79 | # Compute operands prime number 80 | ro = 1 81 | for op in cmd.Operands: 82 | if op.type == o_void: 83 | break 84 | 85 | # Take a unique prime from the operands prime offset for the operand type and index 86 | ro = ro * _CachedPrimes[_OP_P_OFFS + ((op.n * o_last) + op.type)] 87 | 88 | return r * ro 89 | 90 | 91 | # ------------------------------------------------------------------------------ 92 | def hash_itype1(start, end): 93 | """Hash a block based on the instruction sequence""" 94 | sh = hashlib.sha1() 95 | buf = [] 96 | while start < end: 97 | cmd = idautils.DecodeInstruction(start) 98 | if cmd is None: 99 | break 100 | 101 | buf.append(str(cmd.itype)) 102 | start += cmd.size 103 | 104 | sh.update("".join(buf)) 105 | 106 | return sh.hexdigest() 107 | 108 | 109 | # ------------------------------------------------------------------------------ 110 | def hash_itype2(start, end): 111 | """ 112 | Hash a block based on the instruction sequence. 113 | Take into consideration the operands 114 | """ 115 | sh = hashlib.sha1() 116 | r = 1 117 | while start < end: 118 | cmd = idautils.DecodeInstruction(start) 119 | if cmd is None: 120 | break 121 | 122 | r = r * get_cmd_prime_characteristics(cmd) 123 | 124 | # Advance decoder 125 | start += cmd.size 126 | 127 | sh.update(str(r)) 128 | return sh.hexdigest() 129 | 130 | 131 | # ------------------------------------------------------------------------------ 132 | def get_block_frequency(start, end, rekey=False): 133 | """ 134 | Compute a table of instruction characteristic freqency 135 | Returns a tuple containing the total instruction count and freqency table 136 | """ 137 | d = {} 138 | t = 0 139 | while start < end: 140 | # Decode the instruction 141 | cmd = idautils.DecodeInstruction(start) 142 | if cmd is None: 143 | break 144 | 145 | # Get the prime characteristics 146 | r = get_cmd_prime_characteristics(cmd) 147 | 148 | # See if this characteristic has been seen already 149 | try: 150 | # Yes, take its frequency count 151 | v = d[r] 152 | except: 153 | # No, zero frequency 154 | v = 0 155 | 156 | # Add up the frequency count 157 | d[r] = v + 1 158 | 159 | # Add the instruction count 160 | t += 1 161 | 162 | # Advance decoder 163 | start += cmd.size 164 | 165 | # Return a simpler dictionary 166 | if rekey: 167 | d = bb_utils.RekeyDictionary(d) 168 | 169 | return (t, d) 170 | 171 | 172 | # ------------------------------------------------------------------------------ 173 | def match_block_frequencies(ft1, ft2, p1, p2): 174 | """ 175 | Compute the percentage of match between two frequency tables 176 | @param p1: ... 177 | @param p2: ... 178 | """ 179 | 180 | # Extract the total and frequency tables from the parameters 181 | t1, f1 = ft1 182 | t2, f2 = ft2 183 | 184 | # Identify big and small frequency tables 185 | if len(f1) > len(f2): 186 | fs = f2 187 | fb = f1 188 | else: 189 | fs = f1 190 | fb = f2 191 | 192 | # Clear common percentage 193 | tp = 0 194 | comm_count = 0 195 | total_count = 0 196 | ct1 = ct2 = 0 197 | 198 | # Walk the dictionary looking for common characteristics 199 | for k in fs: 200 | # Is this key common to both tables? 201 | if k not in fb: 202 | continue 203 | 204 | comm_count += 1 205 | 206 | # Get the values 207 | v1, v2 = fs[k], fb[k] 208 | 209 | # Add the common instructions that have different frequencies 210 | # in f1 and f2 211 | ct1 += v1 212 | ct2 += v2 213 | 214 | # Get the percentage 215 | v = float((min(v1, v2)) * 100) / float(max(v1, v2)) 216 | 217 | # Accumulate the result to the total percentage 218 | tp = tp + v 219 | 220 | # Compute how much the common match in each frequency table 221 | cp1 = (100 * ct1) / float(t1) 222 | cp2 = (100 * ct2) / float(t2) 223 | 224 | ok1 = (cp1 > p1) and (cp2 > p1) 225 | 226 | # Compute the percent of the common match 227 | if comm_count == 0: 228 | ok2 = False 229 | else: 230 | mp2 = tp / comm_count 231 | ok2 = mp2 > p2 232 | 233 | return (ok1, ok2) 234 | 235 | 236 | # ------------------------------------------------------------------------------ 237 | class IdaBBContext(object): 238 | """IDA Basic block context class""" 239 | 240 | def __init__(self): 241 | 242 | self.bytes = None 243 | """The bytes of the block""" 244 | 245 | self.hash_itype1 = None 246 | """Hash on itype v1""" 247 | 248 | self.hash_itype2 = None 249 | """Hash on itype v2""" 250 | 251 | self.inst_count = 0 252 | """Instruction count""" 253 | 254 | 255 | def get_context( 256 | self, 257 | bb, 258 | bytes=True, 259 | itype1=True, 260 | itype2=False, 261 | icount=True): 262 | """Compute the context of a basic block""" 263 | # Get the bytes 264 | if bytes: 265 | self.bytes = idaapi.get_many_bytes(bb.start, bb.end - bb.start) 266 | 267 | # Count instructions 268 | if icount: 269 | self.inst_count = InstructionCount(bb.start, bb.end) 270 | 271 | # Get the itype1 hash 272 | if itype1: 273 | self.hash_itype1 = hash_itype1(bb.start, bb.end) 274 | 275 | # Get the itype2 hash 276 | if itype2: 277 | self.hash_itype2 = hash_itype2(bb.start, bb.end) 278 | 279 | 280 | # ------------------------------------------------------------------------------ 281 | class IDABBMan(BBMan): 282 | def __init__(self): 283 | BBMan.__init__(self) 284 | 285 | def add_bb_ctx( 286 | self, 287 | bb, 288 | get_bytes, 289 | get_hash_itype1, 290 | get_hash_itype2): 291 | """Add a basic block to the manager with its context computed""" 292 | 293 | # Create the context object 294 | ctx = IdaBBContext() 295 | 296 | # Compute context 297 | ctx.get_context( 298 | bb, 299 | get_bytes, 300 | get_hash_itype1, 301 | get_hash_itype2) 302 | 303 | # Assign context to the basic block object 304 | bb.ctx = ctx 305 | 306 | # Remember this basic block 307 | self.add(bb) 308 | 309 | 310 | def FromFlowchart( 311 | self, 312 | func_addr, 313 | use_cache = False, 314 | get_bytes = False, 315 | get_hash_itype1 = False, 316 | get_hash_itype2 = False): 317 | """ 318 | Build a BasicBlock manager object from a function address 319 | """ 320 | 321 | # Use cache? 322 | if use_cache: 323 | # Try to load cached items 324 | if self.load(_get_cache_filename(func_addr)): 325 | # Loaded successfully, return to caller... 326 | return (True, self) 327 | 328 | # Is it possible to operate in standalone mode? 329 | if stdalone: 330 | return (False, "Cannot compute IDA basic blocks in standalone mode!") 331 | 332 | # Get the IDA function object 333 | fnc = idaapi.get_func(func_addr) 334 | if fnc is None: 335 | return (False, "No function at %x" % func_addr) 336 | 337 | # Update function address to point to the start of the function 338 | func_addr = fnc.startEA 339 | 340 | # Main IDA BB loop 341 | fc = idaapi.FlowChart(fnc) 342 | for block in fc: 343 | # Try to get BB reference 344 | bb = self[block.id] 345 | if bb is None: 346 | bb = BBDef(id=block.id, 347 | start=block.startEA, 348 | end=block.endEA) 349 | 350 | # Add the current block 351 | self.add_bb_ctx( 352 | bb, 353 | get_bytes, 354 | get_hash_itype1, 355 | get_hash_itype2) 356 | 357 | # Add all successors 358 | for succ_block in block.succs(): 359 | # Is the successor block already present? 360 | b0 = self[succ_block.id] 361 | if b0 is None: 362 | b0 = BBDef(id=succ_block.id, 363 | start=succ_block.startEA, 364 | end=succ_block.endEA) 365 | 366 | # Add successor 367 | self.add_bb_ctx( 368 | b0, 369 | get_bytes, 370 | get_hash_itype1, 371 | get_hash_itype2) 372 | 373 | # Link successor 374 | bb.add_succ(b0, link_pred = True) 375 | 376 | # Add all predecessors 377 | for pred_block in block.preds(): 378 | # Is the successor block already present? 379 | b0 = self[pred_block.id] 380 | if b0 is None: 381 | b0 = BBDef(id=pred_block.id, 382 | start=pred_block.startEA, 383 | end=pred_block.endEA) 384 | # Add predecessor 385 | self.add_bb_ctx( 386 | b0, 387 | get_bytes, 388 | get_hash_itype1, 389 | get_hash_itype2) 390 | 391 | # Link predecessor 392 | bb.add_pred(b0, link_succ = True) 393 | 394 | # Save nodes if cache is enabled 395 | if use_cache: 396 | self.save(_get_cache_filename(func_addr)) 397 | 398 | return (True, self) 399 | 400 | # ------------------------------------------------------------------------------ 401 | def BuildBBFromIdaFlowchart(func_addr, use_cache=True): 402 | bm = IDABBMan() 403 | ok = bm.FromFlowchart( 404 | func_addr, 405 | use_cache = use_cache, 406 | get_bytes = not stdalone, 407 | get_hash_itype1 = not stdalone, 408 | get_hash_itype2 = not stdalone) 409 | 410 | 411 | # ------------------------------------------------------------------------------ 412 | def HashNode(bm, node_id, hash_id=1): 413 | """ 414 | Hash a node 415 | """ 416 | bb = bm[node_id] 417 | if hash_id == 1: 418 | r = hash_itype1(bb.start, bb.end) 419 | elif hash_id == 2: 420 | r = hash_itype2(bb.start, bb.end) 421 | else: 422 | r = "unknown hash_id %d" % hash_id 423 | print "->%s" % r 424 | 425 | # ------------------------------------------------------------------------------ 426 | # Precompute lots of prime numbers. This should be more than the count of 427 | # instructions for most processor modules in IDA 428 | 429 | # Total prime numbers 430 | _MAX_PRIMES = 8117 431 | # Prime number offsets offset in the primes pool: 432 | # We have UA_MAXOP operands, each may have 'o_last' operand types. 433 | # For each operand type we have to assure a unique prime number 434 | _OP_P_OFFS = _MAX_PRIMES - (UA_MAXOP * (o_last+1)) 435 | 436 | # Precompute primes 437 | _CachedPrimes = bb_utils.CachedPrimes(_MAX_PRIMES) 438 | 439 | # ------------------------------------------------------------------------------ 440 | if __name__ == '__main__': 441 | ft1 = (7, {21614129: 5, 4790013691321L: 1, 722682555311L: 1}) 442 | ft2 = (3, {21614129: 1, 4790013691321L: 1, 722682555311L: 1}) 443 | 444 | match_block_frequencies(ft1, ft2, 90, 90) 445 | -------------------------------------------------------------------------------- /bbgroup/bb_match.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains IDA specific basic block datatypes and helper functions. 3 | 4 | It is written by Ali Rahbar and Ali Pezeshk 5 | 6 | 7 | 10/10/2013 - alirah - Initial version 8 | 10/11/2013 - alipezes - Added IDABBman subclass of BBMan 9 | - Added hash_itype1() and IDABBContext() class 10 | 11/07/2013 - eliasb - Renamed some functions to work with the C adapter 11 | 11/08/2013 - alipezes - Fixed serialization issue 12 | 08/27/2014 - alirah - Cleaned up the script and readied it for public release 13 | """ 14 | 15 | import idaapi 16 | 17 | 18 | # ------------------------------------------------------------------------------ 19 | import pickle 20 | import cStringIO 21 | from bb_ida import * 22 | import Queue 23 | from collections import defaultdict 24 | from ordered_set import OrderedSet 25 | 26 | # ------------------------------------------------------------------------------ 27 | class bbMatcherClass: 28 | 29 | MagicHeader = "--CONTEXT--" 30 | PathPerNodeHashMarker = "PathPerNodeHash\n" 31 | PathPerNodeHashFullMarker = "PathPerNodeHashFullMarker\n" 32 | SizeDicMarker = "Size_Dic\n" 33 | NodeHashesMarker = "Node_Hashes\n" 34 | NodeHashMatchesMarker = "Node_Hash_Matches\n" 35 | 36 | def __init__(self,func_addr=None): 37 | self.M={} 38 | # this one contains paths matched, that have entries only to the head node 39 | self.pathPerNodeHash=defaultdict(dict) 40 | # this one contains paths matched, regardless of entries 41 | self.pathPerNodeHashFull = defaultdict(dict) 42 | self.normalizedPathPerNodeHash = {} 43 | self.size_dic={} 44 | self.sorted_keys=None 45 | self.G=None 46 | self.address=None 47 | self.nodeHashes = defaultdict(dict) 48 | self.bm=None 49 | if func_addr!=None: 50 | self.buildGRaphFromFunc(func_addr) 51 | 52 | 53 | def buildGRaphFromFunc(self,func_addr): 54 | """Return a graph object from the function with the hash type 1""" 55 | self.bm = IDABBMan() 56 | ok,self.G=self.bm.FromFlowchart( 57 | func_addr, 58 | use_cache=True, 59 | get_bytes=True, 60 | get_hash_itype1 =True, 61 | get_hash_itype2 =True) 62 | self.address = func_addr 63 | 64 | def match(self,N1,N2, hashType): 65 | """Matches two nodes based on their type1(ordered instruction type hash) hash""" 66 | if (hashType == 'freq'): 67 | f1 = get_block_frequency(N1.start, N1.end) 68 | f2 = get_block_frequency(N2.start, N2.end) 69 | a, d1 = f1 70 | b, d2 = f2 71 | 72 | if ( a <= 4 or b <= 4 ): 73 | coveragePercentage = 50 74 | elif ( a <= 6 or b <= 6 ): 75 | coveragePercentage = 60 76 | elif ( a <= 8 or b <= 8 ): 77 | coveragePercentage = 75 78 | else: 79 | coveragePercentage = 85 80 | 81 | b1, b2 = match_block_frequencies(f1, f2, coveragePercentage, 95) 82 | if (b1 and b2): 83 | intersection = set.intersection(set(d1.keys()), set(d2.keys())) 84 | freqHash = hashlib.sha1() 85 | freqHash.update(intersection.__str__()) 86 | hash = freqHash.hexdigest() 87 | N1['freq'] = hash 88 | N2['freq'] = hash 89 | return b1 and b2 90 | if ((N1[hashType]==N2[hashType])) : 91 | return True 92 | return False 93 | 94 | def hashBBMatch(self, hashType): 95 | """Creates a dictionary of basic blocks with the hash as the key and matching block numbers as items of a list for that entry""" 96 | for i in range(0,len(self.G.items())): 97 | for j in range (i+1,len(self.G.items())): 98 | if self.match(self.G[i],self.G[j],hashType): 99 | x=self.G[i][hashType] 100 | if self.M.has_key(x): 101 | if self.M[x].count(j)==0: 102 | self.M[x]+=[j] 103 | 104 | else: 105 | self.M[x]=[i,j] 106 | 107 | def makeSubgraphSingleEntryPoint(self,path1, path2): 108 | if (len(path1) != len(path2)): 109 | quit() 110 | 111 | tmp_path1 = list(path1) 112 | tmp_path2 = list(path2) 113 | headNode = path1[0] 114 | nodeRemoved = True 115 | while nodeRemoved: 116 | nodeRemoved = False 117 | for node in tmp_path1: 118 | for pred in self.G[node].preds: 119 | if not (pred in tmp_path1) and node != headNode: 120 | nodeRemoved = True 121 | break 122 | if (nodeRemoved): 123 | try: 124 | nodeIndex = tmp_path1.index(node) 125 | tmp_path1.remove(node) 126 | tmp_path2.remove( tmp_path2[nodeIndex] ) 127 | except: 128 | quit() 129 | 130 | return OrderedSet(tmp_path1), OrderedSet(tmp_path2) 131 | 132 | def findMatchInSuccs(self, node1, Parent2, hashType, visitedNodes2, tmpVisitedNodes2, path2): 133 | matchedbyHash = False 134 | for m in self.G[Parent2].succs: 135 | if (m not in visitedNodes2) and (m !=Parent2) and (m not in path2): 136 | tmpVisitedNodes2.add(m) 137 | if (self.match(self.G[node1], self.G[m], hashType)): 138 | if node1==m: 139 | continue 140 | matchedbyHash = True 141 | break 142 | return matchedbyHash, m, tmpVisitedNodes2 143 | 144 | 145 | def findSubGraphs(self): 146 | """Find equivalent path from two equivalent nodes 147 | For each node hash it gets all of the BB and try to build path from each pair of them 148 | The result is put in a dual dictionary that has the starting node hash as the first key, the path hash as the second key and the equivalent pathS as a list of sets(containing nodes) 149 | """ 150 | matchedPathsWithDifferentLengths = 0 151 | for i in self.M.keys(): 152 | for z in range(0,len(self.M[i])-1): 153 | for j in self.M[i][z+1:]: #pick one from the second node onward 154 | visited1=set() 155 | visited2=set() 156 | q1=Queue.Queue() #add the first and n node to tmp 157 | q1.put((self.M[i][z],j)) 158 | path1=OrderedSet() 159 | path2=OrderedSet() 160 | path1_bis=OrderedSet() 161 | path2_bis=OrderedSet() 162 | path1NodeHashes = {} 163 | path1.add(self.M[i][z]) 164 | path2.add(j) 165 | path1Str='' 166 | path2Str='' 167 | path1NodeHashes[self.M[i][z]]=self.G[(self.M[i][z])].ctx.hash_itype2 168 | pathHash1= hashlib.sha1() 169 | while not q1.empty(): # for each matching pair from tmp 170 | x,y = q1.get(block = False) 171 | tmp_visited2=set() 172 | for l in self.G[x].succs : 173 | matchedbyHash = False 174 | if (l not in visited1) and (l !=x) and (l not in path1): 175 | visited1.add(l) 176 | tmp_visited2Backup=tmp_visited2 177 | hashType = 'hash_itype1' 178 | matchedbyHash, m, tmp_visited2 = self.findMatchInSuccs( l, y, hashType, visited2, tmp_visited2, path2) 179 | 180 | if not matchedbyHash: 181 | hashType = 'hash_itype2' 182 | tmp_visited2= tmp_visited2Backup 183 | matchedbyHash, m, tmp_visited2 = self.findMatchInSuccs( l, y, hashType, visited2, tmp_visited2, path2) 184 | 185 | if not matchedbyHash: 186 | hashType = 'freq' 187 | tmp_visited2= tmp_visited2Backup 188 | matchedbyHash, m, tmp_visited2 = self.findMatchInSuccs( l, y, hashType, visited2, tmp_visited2, path2) 189 | 190 | if matchedbyHash: 191 | path1NodeHashes[l] = self.G[l][hashType] 192 | path1.add(l) 193 | path2.add(m) 194 | q1.put((l,m)) 195 | visited2.add(m) 196 | 197 | visited2.update(tmp_visited2) 198 | if (len(path1) != len(path2)): 199 | matchedPathsWithDifferentLengths += 1 200 | else: 201 | path1_bis, path2_bis = self.makeSubgraphSingleEntryPoint(path1, path2) 202 | 203 | if len(path1) >1: 204 | for kk in path1: 205 | path1Str+=path1NodeHashes[kk] 206 | 207 | pathHash1.update(path1Str) 208 | a=pathHash1.hexdigest() 209 | if not(self.pathPerNodeHashFull.has_key(i)) or (not( self.pathPerNodeHashFull[i].has_key(a))): 210 | self.pathPerNodeHashFull[i][a]=[] 211 | 212 | duplicate1 = False 213 | duplicate2 = False 214 | 215 | listPath1 = list(path1) 216 | listPath2 = list(path2) 217 | 218 | for zz in self.pathPerNodeHashFull[i][a]: 219 | if listPath1 == zz: 220 | duplicate1 = True 221 | if listPath2 == zz: 222 | duplicate2 = True 223 | 224 | if not duplicate1: 225 | self.pathPerNodeHashFull[i][a].append(list(listPath1)) 226 | if not duplicate2: 227 | self.pathPerNodeHashFull[i][a].append(list(listPath2)) 228 | 229 | if len(path1_bis) >1: 230 | path1Str = '' 231 | for kk in path1_bis: 232 | path1Str+=path1NodeHashes[kk] 233 | 234 | pathHash1.update(path1Str) 235 | a=pathHash1.hexdigest() 236 | if not(self.pathPerNodeHash.has_key(i)) or (not( self.pathPerNodeHash[i].has_key(a))): 237 | self.pathPerNodeHash[i][a]=[] 238 | 239 | duplicate1 = False 240 | duplicate2 = False 241 | 242 | listPath1 = list(path1_bis) 243 | listPath2 = list(path2_bis) 244 | 245 | for zz in self.pathPerNodeHash[i][a]: 246 | if listPath1 == zz: 247 | duplicate1 = True 248 | if listPath2 == zz: 249 | duplicate2 = True 250 | 251 | if not duplicate1: 252 | self.pathPerNodeHash[i][a].append(list(listPath1)) 253 | if not duplicate2: 254 | self.pathPerNodeHash[i][a].append(list(listPath2)) 255 | 256 | def sortByPathLen(self): 257 | """It gets the structure created by findSubGraph and creates a dictionary with the path len as the key and the tupple of node hash and path hash as the entry""" 258 | 259 | for i in self.pathPerNodeHash: 260 | for k in self.pathPerNodeHash[i]: 261 | a=len(self.pathPerNodeHash[i][k][0]) 262 | if not(self.size_dic.has_key(a)): 263 | self.size_dic[a]=[] 264 | self.size_dic[a].append((i,k)) 265 | 266 | self.sorted_keys=sorted(self.size_dic.keys()) 267 | 268 | def subgraphHasExternalJumpsIntoIt(self,subgraph): 269 | for node in list(subgraph)[1:] : 270 | if not set(self.G[node].preds).issubset(set(subgraph)): 271 | return True 272 | return False 273 | 274 | def GetMatchedWellFormedFunctions(self, minFunctionSizeInBlocks = 4, minFunctionHeadSize = 0): 275 | MovedSubgraph = [] 276 | for i in reversed(sorted(self.size_dic.keys())): 277 | if i < minFunctionSizeInBlocks : 278 | break 279 | 280 | for item in self.size_dic[i]: 281 | x,y=item 282 | if (not self.normalizedPathPerNodeHash.has_key(x)): 283 | self.normalizedPathPerNodeHash[x] = {} 284 | if (not self.normalizedPathPerNodeHash[x].has_key(y)): 285 | self.normalizedPathPerNodeHash[x][y] = [] 286 | 287 | if self.subgraphHasExternalJumpsIntoIt( self.pathPerNodeHash[x][y][0]): 288 | continue 289 | for j in self.pathPerNodeHash[x][y]: 290 | skip=False 291 | for k in MovedSubgraph: 292 | if set(j).issubset(set(k)): 293 | skip=True 294 | break 295 | if not skip: 296 | self.normalizedPathPerNodeHash[x][y].append(j) 297 | 298 | if len(self.normalizedPathPerNodeHash[x][y]) < 2 : 299 | self.normalizedPathPerNodeHash[x][y] = [] 300 | else: 301 | if ( minFunctionHeadSize > 0 ): 302 | subgraphStartAddress = self.G[ self.normalizedPathPerNodeHash[x][y][0][0] ].start 303 | functionHeadBigEnough = True 304 | for address in range ( subgraphStartAddress, subgraphStartAddress + 8, 2 ): 305 | if not AddressIsInSubgraph( address, self.normalizedPathPerNodeHash[x][y][0] ): 306 | functionHeadBigEnough = False 307 | break 308 | if not functionHeadBigEnough: 309 | self.normalizedPathPerNodeHash[x][y] = [] 310 | 311 | MovedSubgraph.extend( self.normalizedPathPerNodeHash[x][y] ) 312 | 313 | def AddressIsInSubgraph(self, address, subgraph) : 314 | for i in subgraph : 315 | if (self.G[i].start <= address < self.G[i].end) : 316 | return True 317 | return False 318 | 319 | def FindSimilar(self, nodeList, hashType = 'hash_itype2' ): 320 | size = len(nodeList) 321 | headNode = nodeList[0] 322 | setNodeList = set( nodeList ) 323 | 324 | result = [] 325 | 326 | if ( size == 1 ): 327 | return self.M[ self.nodeHashes[headNode][hashType] ] 328 | 329 | for headNode in nodeList: 330 | headNodeHash = self.nodeHashes[headNode][hashType] 331 | for subgraphHash in self.pathPerNodeHashFull[headNodeHash]: 332 | if size <= len(self.pathPerNodeHashFull[headNodeHash][subgraphHash][0]): 333 | for match in self.pathPerNodeHashFull[headNodeHash][subgraphHash]: 334 | if headNode == match[0] and setNodeList.issubset(set(match)): 335 | # get the subsets from each path that matches the input node list 336 | matchIndex = {} 337 | for node in nodeList: 338 | matchIndex[node] = match.index(node) 339 | for matchedSubgraph in self.pathPerNodeHashFull[headNodeHash][subgraphHash]: 340 | subset = [] 341 | for node in nodeList: 342 | subset.append( matchedSubgraph[matchIndex[node]] ) 343 | if ( not result.__contains__( subset ) ) : 344 | result.append( subset ) 345 | break 346 | if len(result) > 0: 347 | return result 348 | return [] 349 | 350 | def SerializeMatchedInlineFunctions(self, fileName=None ): 351 | reducedPathPerNodeHash = {} 352 | if fileName!=None: 353 | f = open(fileName, 'w') 354 | else: 355 | f= StringIO.StringIO() 356 | for x in self.normalizedPathPerNodeHash: 357 | reducedPathPerNodeHash[x] = {} 358 | for y in self.normalizedPathPerNodeHash[x]: 359 | reducedPathPerNodeHash[x][y] = [] 360 | for i in range(0, len(self.normalizedPathPerNodeHash[x][y])): 361 | duplicate = False 362 | for j in reducedPathPerNodeHash[x][y]: 363 | if self.normalizedPathPerNodeHash[x][y][i].__str__() == j.__str__(): 364 | duplicate = True 365 | break 366 | if not duplicate: 367 | reducedPathPerNodeHash[x][y].append( self.normalizedPathPerNodeHash[x][y][i] ) 368 | 369 | 370 | 371 | c = 0 372 | f.write(bbMatcherClass.MagicHeader + "PATH_INFO\n") 373 | 374 | for x in reducedPathPerNodeHash: 375 | if reducedPathPerNodeHash[x] == {}: 376 | continue 377 | for y in reducedPathPerNodeHash[x]: 378 | if reducedPathPerNodeHash[x][y] == []: 379 | continue 380 | f.write ("ID:%s;NODESET:"%(y)), 381 | count =0 382 | for i in reducedPathPerNodeHash[x][y]: 383 | c += 1 384 | for j in i: # each set 385 | if j == i[0]: 386 | f.write ("("), 387 | f.write ("%d : %x : %x"%(j,self.G[j].start,self.G[j].end)), 388 | if j != i[len(i)-1]: # last item 389 | f.write (", "), 390 | else : 391 | f.write (")"), 392 | count +=1 393 | if count != len(reducedPathPerNodeHash[x][y]): # last one: 394 | f.write (", "), 395 | else: 396 | f.write (";\n") 397 | 398 | if fileName!=None: 399 | result =None 400 | else: 401 | result= f.value() 402 | f.close() 403 | return result 404 | 405 | 406 | def SaveState(self,fileName=None): 407 | if fileName!=None: 408 | f = open(fileName, 'a') 409 | else: 410 | f= StringIO.StringIO() 411 | result = self.SerializeMatchedInlineFunctions(fileName) 412 | 413 | serializedPathPerNodeHash = pickle.dumps( self.pathPerNodeHash ) 414 | serializedPathPerNodeHashFull = pickle.dumps( self.pathPerNodeHash ) 415 | serializedSizeDic = pickle.dumps( self.size_dic ) 416 | serializedNodeHashes = pickle.dumps( self.nodeHashes ) 417 | serializedNodeHashMatches = pickle.dumps (self.M) 418 | 419 | f.write(bbMatcherClass.MagicHeader + bbMatcherClass.PathPerNodeHashMarker) 420 | f.write(serializedPathPerNodeHash) 421 | f.write(bbMatcherClass.MagicHeader + bbMatcherClass.PathPerNodeHashFullMarker) 422 | f.write(serializedPathPerNodeHashFull) 423 | f.write("\n" + bbMatcherClass.MagicHeader + bbMatcherClass.SizeDicMarker) 424 | f.write(serializedSizeDic) 425 | f.write("\n" + bbMatcherClass.MagicHeader + bbMatcherClass.NodeHashesMarker) 426 | f.write(serializedNodeHashes) 427 | f.write("\n" + bbMatcherClass.MagicHeader + bbMatcherClass.NodeHashMatchesMarker) 428 | f.write(serializedNodeHashMatches) 429 | 430 | if fileName!=None: 431 | result =None 432 | else: 433 | result += f.value() 434 | f.close() 435 | return result 436 | 437 | def LoadState(self,fileName=None,input=None): 438 | if fileName!=None: 439 | f = open(fileName, 'r') 440 | if input!=None: 441 | f= StringIO.StringIO(input) 442 | 443 | fileContents = f.read() 444 | 445 | fileSegments = fileContents.split(bbMatcherClass.MagicHeader) 446 | 447 | for segment in fileSegments[1:] : 448 | if segment.startswith( bbMatcherClass.PathPerNodeHashMarker ): 449 | self.pathPerNodeHash = pickle.loads(segment[len(bbMatcherClass.PathPerNodeHashMarker):]) 450 | elif segment.startswith( bbMatcherClass.PathPerNodeHashFullMarker ): 451 | self.pathPerNodeHashFull = pickle.loads(segment[len( bbMatcherClass.PathPerNodeHashFullMarker):] ) 452 | elif segment.startswith( bbMatcherClass.SizeDicMarker ): 453 | self.size_dic = pickle.loads(segment[len(bbMatcherClass.SizeDicMarker):]) 454 | elif segment.startswith( bbMatcherClass.NodeHashesMarker ): 455 | self.nodeHashes = pickle.loads(segment[len( bbMatcherClass.NodeHashesMarker):]) 456 | elif segment.startswith( bbMatcherClass.NodeHashMatchesMarker ): 457 | self.M = pickle.loads(segment[len( bbMatcherClass.NodeHashMatchesMarker):] ) 458 | 459 | f.close() 460 | 461 | def Analyze(self,func_addr=None): 462 | result = [] 463 | if func_addr!=None: 464 | self.buildGRaphFromFunc(func_addr) 465 | if self.G !=None: 466 | # todo: refactor this to get the list from one place 467 | for hashName in ['hash_itype1', 'hash_itype2']: 468 | for i in self.G.items(): 469 | self.nodeHashes[i.id][hashName] = self.G[i.id][hashName] 470 | self.hashBBMatch('hash_itype2') 471 | self.findSubGraphs() 472 | self.sortByPathLen() 473 | self.GetMatchedWellFormedFunctions() 474 | 475 | 476 | for x in self.normalizedPathPerNodeHash: 477 | for y in self.normalizedPathPerNodeHash[x]: 478 | if self.normalizedPathPerNodeHash[x][y] != []: 479 | result.append( self.normalizedPathPerNodeHash[x][y] ) 480 | 481 | return result 482 | 483 | # ------------------------------------------------------------------------------ 484 | bbMatcher = bbMatcherClass() 485 | -------------------------------------------------------------------------------- /bbgroup/bb_types.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic Block base types module 3 | 4 | 5 | This module contains the basic types and data structures 6 | * 09/12/2013 - eliasb - Initial version 7 | * 09/16/2013 - eliasb - Renamed RawBB class to BBDef class 8 | - Added proper serialization 9 | - Added BBDef.size() and context attribute 10 | - Added Context attribute and size() to the BBDef class 11 | - Added BBMan.clear(), .get_cache() 12 | * 09/17/2013 - eliasb - Added method BBMan.is_using_cache() to see if the cache was used 13 | * 10/07/2013 - eliasb - Use the Caching class from bb_util 14 | - Added setitem/getitem helper methods to access the ctx object members of BBDef 15 | * 10/08/2013 - eliasb - bugfix: import from Caching 16 | - added BBMan.find_by_addr() 17 | * 10/09/2013 - eliasb - bugfix: find_by_addr() 18 | 19 | """ 20 | 21 | import pickle 22 | import sys 23 | from bb_utils import Caching 24 | 25 | # ------------------------------------------------------------------------------ 26 | class BBDef(object): 27 | """Class to define a raw basic block""" 28 | 29 | def __init__(self, start=0, end=0, id=-1, label="", ctx=None): 30 | self.start = start 31 | """Start address of basic block""" 32 | 33 | self.end = end 34 | """End address of basic block""" 35 | 36 | self.id = id 37 | """The ID of the basic block""" 38 | 39 | self.label = label 40 | """The label of the basic block""" 41 | 42 | self.preds = list() 43 | """List of predecessor node IDs""" 44 | 45 | self.succs = list() 46 | """List of successor node IDs""" 47 | 48 | self.ctx = ctx 49 | """Context associated with the BB""" 50 | 51 | 52 | def size(self): 53 | """Return the size of the BB""" 54 | return self.end - self.start 55 | 56 | 57 | def matchp(self, bb): 58 | """Match two BBs and return percentage of equality""" 59 | return 0.0 60 | 61 | 62 | def add_succ(self, bb, link_pred = False): 63 | """Add a successor basic block""" 64 | self.succs.append(bb.id) 65 | 66 | if link_pred: 67 | bb.add_pred(self, False) 68 | 69 | return self 70 | 71 | 72 | def add_pred(self, bb, link_succ = False): 73 | """Add predecessor basic block""" 74 | 75 | self.preds.append(bb.id) 76 | 77 | if link_succ: 78 | bb.add_succ(self, False) 79 | 80 | return self 81 | 82 | 83 | def __getitem__(self, key): 84 | """Shortcut method to access items in the context class""" 85 | return self.ctx.__dict__[key] 86 | 87 | 88 | def __setitem__(self, key, value): 89 | """Shortcut method to set item value in the context class""" 90 | self.ctx.__dict__[key] = value 91 | 92 | 93 | # ------------------------------------------------------------------------------ 94 | class BBMan(object): 95 | """Class to manage basic blocks""" 96 | def __init__(self): 97 | self.__idcache = {} 98 | self.__lasterr = None 99 | self.__is_using_cache = False 100 | 101 | 102 | def is_using_cache(): 103 | """Was the cache file used""" 104 | return self.__is_using_cache 105 | 106 | 107 | def clear(self): 108 | """Clear the basic blocks""" 109 | self.__idcache = {} 110 | self.__is_using_cache = False 111 | 112 | 113 | def last_error(self): 114 | """Return the last error string""" 115 | return self.__lasterr 116 | 117 | 118 | def save(self, filename): 119 | """Saves the basic blocks to a file (serialize)""" 120 | ok, self.__lasterr = Caching.Save(filename, self.__idcache) 121 | return ok 122 | 123 | 124 | def load(self, filename): 125 | """Load the basic blocks from a file (deserialize)""" 126 | ok, r = Caching.Load(filename) 127 | if not ok: 128 | self.__lasterr = r 129 | self.__is_using_cache = False 130 | return False 131 | 132 | self.__is_using_cache = True 133 | self.__idcache = r 134 | return True 135 | 136 | 137 | def add(self, bb): 138 | """Add a basic block""" 139 | 140 | # Cache the basic block by ID 141 | self.__idcache[bb.id] = bb 142 | 143 | 144 | def items(self): 145 | """Return all the raw basic block objects""" 146 | return self.__idcache.values() 147 | 148 | 149 | def __getitem__(self, index): 150 | try: 151 | return self.__idcache[index] 152 | except: 153 | return None 154 | 155 | 156 | def get_cache(self): 157 | return self.__idcache 158 | 159 | 160 | def gen_dot(self, filename): 161 | """Generate a DOT file""" 162 | 163 | # TODO 164 | pass 165 | 166 | 167 | def find_by_addr(self, addr): 168 | """Return the basic block that contains the given address""" 169 | for bb in self.items(): 170 | if bb.start <= addr < bb.end: 171 | return bb 172 | 173 | return None 174 | 175 | def build_from_id_string(self, conn): 176 | # Failed to load or no cache was to be used? 177 | for couples in conn.split(';'): 178 | n0, n1 = couples.split(':') 179 | n0 = int(n0) 180 | 181 | bb0 = self[n0] 182 | if bb0 is None: 183 | bb0 = BBDef(id=n0) 184 | self.add(bb0) 185 | 186 | for child in n1.split(','): 187 | ni = int(child) 188 | bbi = self[ni] 189 | if bbi is None: 190 | bbi = BBDef(id=ni) 191 | self.add(bbi) 192 | bb0.add_succ(bbi) 193 | 194 | # ------------------------------------------------------------------------------ 195 | def __test(): 196 | bm = BBMan() 197 | fn = 'test.bin' 198 | if not bm.load(fn): 199 | conn = "0:1,2,3,4,5;1:3;3:4;2:4" 200 | bm.build_from_id_string(conn) 201 | if not bm.save(fn): 202 | print "Could not save!" 203 | return 204 | 205 | # Process ..... 206 | bb0 = bm[0] 207 | for x in bb0.succs: 208 | x = bm[x] 209 | print x.id, ",", 210 | 211 | # ------------------------------------------------------------------------------ 212 | def __test2(): 213 | class X(object): 214 | def __init__(self): 215 | self.a = 0 216 | self.b = 0 217 | 218 | b = BBDef(ctx = X()) 219 | 220 | print "a=%d" % b['a'] 221 | b['a'] = 1 222 | b['c'] = 3 223 | print "a=%d c=%d" % (b['a'], b['c']) 224 | 225 | 226 | # ------------------------------------------------------------------------------ 227 | if __name__ == '__main__': 228 | __test2() 229 | -------------------------------------------------------------------------------- /bbgroup/bb_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility class 3 | 4 | This module contains the basic types and data structures 5 | 6 | * 09/17/2013 - eliasb - Added the GenPrimes() utility function 7 | * 10/07/2013 - eliasb - Added the Caching class 8 | - Added Cached prime generator 9 | * 10/15/2013 - eliasb - Added RekeyDictionary() 10 | 11 | """ 12 | 13 | import pickle 14 | import sys 15 | 16 | # ------------------------------------------------------------------------------ 17 | class Caching(object): 18 | @staticmethod 19 | def Save(filename, obj): 20 | """Serializes an object to a file""" 21 | try: 22 | f = open(filename, 'wb') 23 | pickle.dump(obj, f, protocol=pickle.HIGHEST_PROTOCOL) 24 | f.close() 25 | return (True, None) 26 | except: 27 | return (False, sys.exc_info()[0]) 28 | 29 | @staticmethod 30 | def Load(filename): 31 | """Deserializes an object from a file""" 32 | try: 33 | f = open(filename, 'rb') 34 | ret = pickle.load(f) 35 | f.close() 36 | return (True, ret) 37 | except: 38 | return (False, sys.exc_info()[0]) 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | def RekeyDictionary(d): 43 | """ 44 | Reassigns simple numeric keys to a dictionary 45 | """ 46 | 47 | # New mapping 48 | rkd = {} 49 | # New key numbering 50 | nk = 0 51 | for k in d: 52 | rkd[nk] = d[k] 53 | nk += 1 54 | 55 | return rkd 56 | 57 | 58 | # ------------------------------------------------------------------------------ 59 | def GenPrimes(): 60 | """ 61 | Generate an infinite sequence of prime numbers. 62 | http://stackoverflow.com/questions/567222/simple-prime-generator-in-python 63 | """ 64 | # Maps composites to primes witnessing their compositeness. 65 | # This is memory efficient, as the sieve is not "run forward" 66 | # indefinitely, but only as long as required by the current 67 | # number being tested. 68 | # 69 | D = {} 70 | 71 | # The running integer that's checked for primeness 72 | q = 2 73 | 74 | while True: 75 | if q not in D: 76 | # q is a new prime. 77 | # Yield it and mark its first multiple that isn't 78 | # already marked in previous iterations 79 | # 80 | yield q 81 | D[q * q] = [q] 82 | else: 83 | # q is composite. D[q] is the list of primes that 84 | # divide it. Since we've reached q, we no longer 85 | # need it in the map, but we'll mark the next 86 | # multiples of its witnesses to prepare for larger 87 | # numbers 88 | # 89 | for p in D[q]: 90 | D.setdefault(p + q, []).append(p) 91 | del D[q] 92 | 93 | q += 1 94 | 95 | 96 | # ------------------------------------------------------------------------------ 97 | class CachedPrimes(object): 98 | def __init__(self, count, fn_template = 'Primes%08d.cache'): 99 | # Format the file name 100 | fn = fn_template % count 101 | 102 | # Try to load the cache file 103 | ok, self.__primes = Caching.Load(fn) 104 | 105 | # No cache file? 106 | if not ok: 107 | # Pre-generate numbers 108 | L = [] 109 | for p in GenPrimes(): 110 | L.append(p) 111 | if count <= 1: 112 | break 113 | # Decrement count 114 | count = count - 1 115 | 116 | # Save the pregenerated primes 117 | self.__primes = L 118 | 119 | # Cache them as well 120 | Caching.Save(fn, L) 121 | 122 | def __getitem__(self, key): 123 | return self.__primes[key] 124 | 125 | def __iter__(self): 126 | return iter(self.__primes) 127 | 128 | def __len__(self): 129 | return len(self.__primes) 130 | 131 | 132 | # ------------------------------------------------------------------------------ 133 | if __name__ == '__main__': 134 | cp = CachedPrimes(5001) 135 | print "L=%d x2=%d" % (len(cp), cp[2]) 136 | for x in xrange(5, 10): 137 | print "\t->", cp[x] 138 | 139 | for x in cp: 140 | print x 141 | 142 | pass -------------------------------------------------------------------------------- /bbgroup/build_cache.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to build a cache of the current function 3 | """ 4 | try: 5 | import idaapi 6 | except: 7 | print "Standalone mode" 8 | 9 | import bb_ida 10 | from bb_ida import * 11 | 12 | # ------------------------------------------------------------------------------ 13 | def main(): 14 | global bm 15 | 16 | bm = IDABBMan() 17 | if bb_ida.stdalone: 18 | addr = 0x40A730 19 | addr = 0xFFFFF803CC152F78 20 | addr = 0x40A494 21 | addr = 0xFFFFF803CC152F78 22 | else: 23 | addr = here() 24 | f = idaapi.get_func(addr) 25 | addr = f.startEA 26 | 27 | ok, _ = bm.FromFlowchart(addr, use_cache = True, get_bytes = True, get_hash_itype1 = True) 28 | 29 | if not ok: 30 | print "Error: %r" % (bm) 31 | return False 32 | 33 | for bb in bm.items(): 34 | if bb_ida.stdalone: 35 | print "[%d] %x (%d); ctx=(bytes_len=%d, hash_itype1=%s)" % (bb.id, bb.start, bb.end - bb.start, len(bb.ctx.bytes), bb.ctx.hash_itype1) 36 | for sid in bb.succs: 37 | bb0 = bm[sid] 38 | if bb_ida.stdalone: 39 | print " SUCC: [%d] %x (%d)" % (bb0.id, bb0.start, bb0.end - bb0.start) 40 | 41 | for pid in bb.preds: 42 | bb1 = bm[pid] 43 | if bb_ida.stdalone: 44 | print " PREDS: [%d] %x (%d)" % (bb1.id, bb1.start, bb1.end - bb1.start) 45 | 46 | # ------------------------------------------------------------------------------ 47 | main() -------------------------------------------------------------------------------- /bbgroup/deploy.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set DEST=p:\tools\idadev\plugins\GraphSlick 4 | 5 | FOR %%a in (bb_utils bb_ida bb_types bb_match bb_match_final) DO copy %%a.py %DEST% 6 | 7 | copy bb_match_final.py p:\tools\idadev\plugins\GraphSlick\bb_match.py -------------------------------------------------------------------------------- /bbgroup/ida_stats.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains IDA specific basic block datatypes and helper functions 3 | 4 | 5 | 10/08/2013 - eliasb - Initial version 6 | - Added hash_node utility function 7 | - Added read_analysis() and addr_to_bb() 8 | 9 | 10/15/2013 - eliasb - Changed output format so it is compatible with the visualization plugin 10 | 11 | 12 | TODO: 13 | - sort the stats by min_match and min_icount 14 | """ 15 | try: 16 | import idaapi 17 | except: 18 | print "Standalone mode" 19 | 20 | import bb_ida 21 | from bb_ida import * 22 | 23 | # ------------------------------------------------------------------------------ 24 | # Define global variables 25 | print_out = False 26 | bm = None 27 | 28 | # ------------------------------------------------------------------------------ 29 | def print_hash_stats(bm, hashes, caption='', min_match=2, min_icount=2): 30 | print "%sStats for %d matched block(s) out of %d total; min_match=%d, min_icount=%d" % ( 31 | caption, 32 | len(hashes), 33 | len(bm.items()), 34 | min_match, 35 | min_icount) 36 | 37 | crit_match = 0 38 | for hash in hashes: 39 | v = hashes[hash] 40 | 41 | # Get count of matches 42 | match_count = len(v) 43 | if match_count < min_match: 44 | continue 45 | 46 | # Get instruction count 47 | inst_count = bm[v[0][0]].ctx.inst_count 48 | if inst_count < min_icount: 49 | continue 50 | 51 | crit_match = crit_match + 1 52 | print "ID:%s;IC:%d;MC:%d;NODESET:%s;" % ( 53 | hash, 54 | inst_count, 55 | match_count, 56 | ", ".join(["(%s : %s : %s)" % (nid, start, end) for nid, start, end in v]) 57 | ) 58 | 59 | print "Found %d results" % crit_match 60 | 61 | return 62 | 63 | # ------------------------------------------------------------------------------ 64 | def hash_node(id, hid=1): 65 | if bm is None: 66 | print "main() was not called!" 67 | else: 68 | return bb_ida.HashNode(bm, id, hid) 69 | 70 | 71 | # ------------------------------------------------------------------------------ 72 | def read_analysis(fn): 73 | try: 74 | f = open(fn); 75 | print "".join(f.readlines()); 76 | f.close() 77 | except: 78 | pass 79 | 80 | # ------------------------------------------------------------------------------ 81 | def addr_to_bb(addr): 82 | return bm.find_by_addr(addr) 83 | 84 | # ------------------------------------------------------------------------------ 85 | def main(min_match=1, min_icount=1): 86 | global bm 87 | global same_hashes 88 | global print_out 89 | global same_hashes1 90 | global same_hashes2 91 | 92 | same_hashes1 = {} 93 | same_hashes2 = {} 94 | 95 | bm = IDABBMan() 96 | if bb_ida.stdalone: 97 | addr = 0x40A730 98 | addr = 0xFFFFF803CC152F78 99 | addr = 0x40A494 100 | addr = 0xFFFFF803CC152F78 101 | else: 102 | addr = here() 103 | f = idaapi.get_func(addr) 104 | addr = f.startEA 105 | 106 | ok, err = bm.FromFlowchart( 107 | addr, 108 | use_cache = True, 109 | get_bytes = True, 110 | get_hash_itype1 = True, 111 | get_hash_itype2 = True) 112 | 113 | if not ok: 114 | print "Error: %r" % (err) 115 | return False 116 | 117 | for bb in bm.items(): 118 | # Stats for H1 119 | h = bb.ctx.hash_itype1 120 | try: 121 | v = (bb.id, "%x" % bb.start, "%x" % bb.end) 122 | L = same_hashes1[h] 123 | L.append(v) 124 | except: 125 | L = [v] 126 | same_hashes1[h] = L 127 | 128 | # Stats for H2 129 | h = bb.ctx.hash_itype2 130 | try: 131 | v = (bb.id, "%x" % bb.start, "%x" % bb.end) 132 | L = same_hashes2[h] 133 | L.append(v) 134 | except: 135 | L = [v] 136 | same_hashes2[h] = L 137 | 138 | if print_out: 139 | print "[%d] %x (%d); ctx=(bytes_len=%d, hash_itype1=%s hash_itype2=%s)" % ( 140 | bb.id, 141 | bb.start, 142 | bb.end - bb.start, 143 | len(bb.ctx.bytes), 144 | bb.ctx.hash_itype1, 145 | bb.ctx.hash_itype2) 146 | 147 | for sid in bb.succs: 148 | bb0 = bm[sid] 149 | if print_out: 150 | print " SUCC: [%d] %x (%d)" % (bb0.id, bb0.start, bb0.end - bb0.start) 151 | 152 | for pid in bb.preds: 153 | bb1 = bm[pid] 154 | if print_out: 155 | print " PREDS: [%d] %x (%d)" % (bb1.id, bb1.start, bb1.end - bb1.start) 156 | 157 | # Display stats 158 | #print_hash_stats(bm, same_hashes1, 'H1) ') 159 | print_hash_stats(bm, same_hashes2, 'H2) ', min_match=0, min_icount=0) 160 | 161 | # ------------------------------------------------------------------------------ 162 | main() -------------------------------------------------------------------------------- /bbgroup/init.py: -------------------------------------------------------------------------------- 1 | """ 2 | BB matcher initialization script. 3 | 4 | This script ensures that it runs once per Python runtime. 5 | 6 | 7 | 11/07/2013 - eliasb - Initial version 8 | """ 9 | 10 | import os 11 | import sys 12 | 13 | # Get the script path 14 | script_path = os.path.dirname(__file__) 15 | 16 | # Run this code once by checking if the required scripts 17 | # are in the path 18 | if script_path not in sys.path: 19 | sys.path.append(script_path) 20 | 21 | # Import the matcher module 22 | import bb_match 23 | 24 | #print "Imported" 25 | else: 26 | #print "Already imported" 27 | pass -------------------------------------------------------------------------------- /bbgroup/readme.txt: -------------------------------------------------------------------------------- 1 | Basic Block grouper (c) Elias Bachaalany and Ali Rahbar 2 | 3 | 4 | -------------------------------------------------------------------------------- /bbgroup/t1.py: -------------------------------------------------------------------------------- 1 | from bb_types import * 2 | 3 | bbs = BBMan() 4 | 5 | bbs.load('test.bin') 6 | 7 | for x in bbs[0].succs: 8 | print x.id 9 | -------------------------------------------------------------------------------- /bbgroup/t2.py: -------------------------------------------------------------------------------- 1 | try: 2 | import idaapi 3 | except: 4 | print "Standalone mode" 5 | 6 | import bb_ida 7 | from bb_ida import * 8 | 9 | # ------------------------------------------------------------------------------ 10 | def main(): 11 | global bm 12 | global same_hashes 13 | 14 | same_hashes1 = {} 15 | same_hashes2 = {} 16 | 17 | bm = IDABBMan() 18 | if bb_ida.stdalone: 19 | addr = 0x40A730 20 | addr = 0xFFFFF803CC152F78 21 | addr = 0x40A494 22 | addr = 0xFFFFF803CC152F78 23 | else: 24 | addr = here() 25 | f = idaapi.get_func(addr) 26 | addr = f.startEA 27 | 28 | ok, _ = bm.FromFlowchart( 29 | addr, 30 | use_cache = True, 31 | get_bytes = True, 32 | get_hash_itype1 = True, 33 | get_hash_itype2 = True) 34 | 35 | if not ok: 36 | print "Error: %r" % (bm) 37 | return False 38 | 39 | for bb in bm.items(): 40 | h1 = bb.ctx.hash_itype1 41 | try: 42 | L = same_hashes1[h1] 43 | L.append(bb.id) 44 | except: 45 | L = [bb.id] 46 | same_hashes1[h1] = L 47 | continue 48 | 49 | if bb_ida.stdalone: 50 | print "[%d] %x (%d); ctx=(bytes_len=%d, hash_itype1=%s hash_itype2=%s)" % (bb.id, bb.start, bb.end - bb.start, len(bb.ctx.bytes), bb.ctx.hash_itype1, bb.ctx.hash_itype2) 51 | for sid in bb.succs: 52 | bb0 = bm[sid] 53 | if bb_ida.stdalone: 54 | print " SUCC: [%d] %x (%d)" % (bb0.id, bb0.start, bb0.end - bb0.start) 55 | 56 | for pid in bb.preds: 57 | bb1 = bm[pid] 58 | if bb_ida.stdalone: 59 | print " PREDS: [%d] %x (%d)" % (bb1.id, bb1.start, bb1.end - bb1.start) 60 | 61 | print "Stats for %d blocks" % len(same_hashes1) 62 | L = [] 63 | for h1 in same_hashes1: 64 | v = same_hashes1[h1] 65 | c = len(v) 66 | if c < 2: 67 | continue 68 | L.append([c, v[0]]) 69 | print "%s -> (%d)" % (h1, c) 70 | 71 | L = sorted(L, key = lambda x: x[0]) 72 | 73 | for c, nid in L: 74 | bb = bm[nid] 75 | print "%x: count=%d" % (bb.start, c) 76 | 77 | # ------------------------------------------------------------------------------ 78 | main() -------------------------------------------------------------------------------- /bbgroup/t4.py: -------------------------------------------------------------------------------- 1 | from bb_types import * 2 | import pickle 3 | 4 | d = {} 5 | for i in xrange(1, 10000): 6 | b = RawBB(id=i, start=i, end=i*2) 7 | d[i] = b 8 | 9 | 10 | f = open('t.bin', 'wb') 11 | pickle.dump(d, f) 12 | f.close() 13 | 14 | 15 | f = open('t.bin', 'rb') 16 | t = pickle.load(f) 17 | f.close() 18 | 19 | print t 20 | print d -------------------------------------------------------------------------------- /bin/plugins/GraphSlick/bb_ida.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains IDA specific basic block datatypes and helper functions 3 | 4 | 5 | 09/12/2013 - eliasb - Initial version 6 | 09/16/2013 - eliasb - Added IDABBman subclass of BBMan 7 | - Added hash_itype1() and IDABBContext() class 8 | 10/07/2013 - eliasb - Added hash2 algorithm 9 | 10/08/2013 - eliasb - Refactored code and fixed some bugs 10 | - Fixed a bug in hashing bounds 11 | - Added operand type into consideration in hash_itype2() 12 | - Added utility HashNode() 13 | - Added InstructionCount to IDABBContext structure 14 | 10/15/2013 - eliasb - Added get_cmd_prime_characteristics() and use it in hash_itype2() 15 | - Added get_block_frequency() 16 | 17 | 10/16/2013 - eliasb - Finished match_block_frequencies() 18 | - get_block_frequency() now returns the total instruction count as well 19 | - Block frequency table keys are now rekeyed with RekeyDictionary() 20 | 10/25/2013 - eliasb - Integrated Ali's changes: 21 | - Made Rekeying optional and off by default (since it will mess up relative comparison) 22 | - Enforce division by floats where needed 23 | - Avoid division by zero 24 | 25 | TODO: 26 | ------ 27 | 28 | """ 29 | 30 | stdalone = False 31 | """Desginates whether this module is running inside IDA or in stand alone mode""" 32 | 33 | # ------------------------------------------------------------------------------ 34 | try: 35 | import idaapi 36 | import idautils 37 | import hashlib 38 | 39 | from idaapi import UA_MAXOP, o_last, o_void 40 | except: 41 | stdalone = True 42 | 43 | UA_MAXOP = 6 44 | o_last = 14 45 | o_void = 0 46 | 47 | from bb_types import * 48 | import bb_utils 49 | 50 | # ------------------------------------------------------------------------------ 51 | def _get_cache_filename(addr): 52 | # Form the cache file name 53 | return "%016X.cache" % addr 54 | 55 | 56 | # ------------------------------------------------------------------------------ 57 | def InstructionCount(start, end): 58 | """Count the number of instructions""" 59 | icount = 0 60 | while start < end: 61 | cmd = idautils.DecodeInstruction(start) 62 | if cmd is None: 63 | break 64 | icount += 1 65 | start += cmd.size 66 | 67 | return icount 68 | 69 | 70 | # ------------------------------------------------------------------------------ 71 | def get_cmd_prime_characteristics(cmd): 72 | """ 73 | Compute the characteristics of an instruction and return a prime number 74 | """ 75 | 76 | # Compute the itype's prime number 77 | r = _CachedPrimes[cmd.itype] 78 | 79 | # Compute operands prime number 80 | ro = 1 81 | for op in cmd.Operands: 82 | if op.type == o_void: 83 | break 84 | 85 | # Take a unique prime from the operands prime offset for the operand type and index 86 | ro = ro * _CachedPrimes[_OP_P_OFFS + ((op.n * o_last) + op.type)] 87 | 88 | return r * ro 89 | 90 | 91 | # ------------------------------------------------------------------------------ 92 | def hash_itype1(start, end): 93 | """Hash a block based on the instruction sequence""" 94 | sh = hashlib.sha1() 95 | buf = [] 96 | while start < end: 97 | cmd = idautils.DecodeInstruction(start) 98 | if cmd is None: 99 | break 100 | 101 | buf.append(str(cmd.itype)) 102 | start += cmd.size 103 | 104 | sh.update("".join(buf)) 105 | 106 | return sh.hexdigest() 107 | 108 | 109 | # ------------------------------------------------------------------------------ 110 | def hash_itype2(start, end): 111 | """ 112 | Hash a block based on the instruction sequence. 113 | Take into consideration the operands 114 | """ 115 | sh = hashlib.sha1() 116 | r = 1 117 | while start < end: 118 | cmd = idautils.DecodeInstruction(start) 119 | if cmd is None: 120 | break 121 | 122 | r = r * get_cmd_prime_characteristics(cmd) 123 | 124 | # Advance decoder 125 | start += cmd.size 126 | 127 | sh.update(str(r)) 128 | return sh.hexdigest() 129 | 130 | 131 | # ------------------------------------------------------------------------------ 132 | def get_block_frequency(start, end, rekey=False): 133 | """ 134 | Compute a table of instruction characteristic freqency 135 | Returns a tuple containing the total instruction count and freqency table 136 | """ 137 | d = {} 138 | t = 0 139 | while start < end: 140 | # Decode the instruction 141 | cmd = idautils.DecodeInstruction(start) 142 | if cmd is None: 143 | break 144 | 145 | # Get the prime characteristics 146 | r = get_cmd_prime_characteristics(cmd) 147 | 148 | # See if this characteristic has been seen already 149 | try: 150 | # Yes, take its frequency count 151 | v = d[r] 152 | except: 153 | # No, zero frequency 154 | v = 0 155 | 156 | # Add up the frequency count 157 | d[r] = v + 1 158 | 159 | # Add the instruction count 160 | t += 1 161 | 162 | # Advance decoder 163 | start += cmd.size 164 | 165 | # Return a simpler dictionary 166 | if rekey: 167 | d = bb_utils.RekeyDictionary(d) 168 | 169 | return (t, d) 170 | 171 | 172 | # ------------------------------------------------------------------------------ 173 | def match_block_frequencies(ft1, ft2, p1, p2): 174 | """ 175 | Compute the percentage of match between two frequency tables 176 | @param p1: ... 177 | @param p2: ... 178 | """ 179 | 180 | # Extract the total and frequency tables from the parameters 181 | t1, f1 = ft1 182 | t2, f2 = ft2 183 | 184 | # Identify big and small frequency tables 185 | if len(f1) > len(f2): 186 | fs = f2 187 | fb = f1 188 | else: 189 | fs = f1 190 | fb = f2 191 | 192 | # Clear common percentage 193 | tp = 0 194 | comm_count = 0 195 | total_count = 0 196 | ct1 = ct2 = 0 197 | 198 | # Walk the dictionary looking for common characteristics 199 | for k in fs: 200 | # Is this key common to both tables? 201 | if k not in fb: 202 | continue 203 | 204 | comm_count += 1 205 | 206 | # Get the values 207 | v1, v2 = fs[k], fb[k] 208 | 209 | # Add the common instructions that have different frequencies 210 | # in f1 and f2 211 | ct1 += v1 212 | ct2 += v2 213 | 214 | # Get the percentage 215 | v = float((min(v1, v2)) * 100) / float(max(v1, v2)) 216 | 217 | # Accumulate the result to the total percentage 218 | tp = tp + v 219 | 220 | # Compute how much the common match in each frequency table 221 | cp1 = (100 * ct1) / float(t1) 222 | cp2 = (100 * ct2) / float(t2) 223 | 224 | ok1 = (cp1 > p1) and (cp2 > p1) 225 | 226 | # Compute the percent of the common match 227 | if comm_count == 0: 228 | ok2 = False 229 | else: 230 | mp2 = tp / comm_count 231 | ok2 = mp2 > p2 232 | 233 | return (ok1, ok2) 234 | 235 | 236 | # ------------------------------------------------------------------------------ 237 | class IdaBBContext(object): 238 | """IDA Basic block context class""" 239 | 240 | def __init__(self): 241 | 242 | self.bytes = None 243 | """The bytes of the block""" 244 | 245 | self.hash_itype1 = None 246 | """Hash on itype v1""" 247 | 248 | self.hash_itype2 = None 249 | """Hash on itype v2""" 250 | 251 | self.inst_count = 0 252 | """Instruction count""" 253 | 254 | 255 | def get_context( 256 | self, 257 | bb, 258 | bytes=True, 259 | itype1=True, 260 | itype2=False, 261 | icount=True): 262 | """Compute the context of a basic block""" 263 | # Get the bytes 264 | if bytes: 265 | self.bytes = idaapi.get_many_bytes(bb.start, bb.end - bb.start) 266 | 267 | # Count instructions 268 | if icount: 269 | self.inst_count = InstructionCount(bb.start, bb.end) 270 | 271 | # Get the itype1 hash 272 | if itype1: 273 | self.hash_itype1 = hash_itype1(bb.start, bb.end) 274 | 275 | # Get the itype2 hash 276 | if itype2: 277 | self.hash_itype2 = hash_itype2(bb.start, bb.end) 278 | 279 | 280 | # ------------------------------------------------------------------------------ 281 | class IDABBMan(BBMan): 282 | def __init__(self): 283 | BBMan.__init__(self) 284 | 285 | def add_bb_ctx( 286 | self, 287 | bb, 288 | get_bytes, 289 | get_hash_itype1, 290 | get_hash_itype2): 291 | """Add a basic block to the manager with its context computed""" 292 | 293 | # Create the context object 294 | ctx = IdaBBContext() 295 | 296 | # Compute context 297 | ctx.get_context( 298 | bb, 299 | get_bytes, 300 | get_hash_itype1, 301 | get_hash_itype2) 302 | 303 | # Assign context to the basic block object 304 | bb.ctx = ctx 305 | 306 | # Remember this basic block 307 | self.add(bb) 308 | 309 | 310 | def FromFlowchart( 311 | self, 312 | func_addr, 313 | use_cache = False, 314 | get_bytes = False, 315 | get_hash_itype1 = False, 316 | get_hash_itype2 = False): 317 | """ 318 | Build a BasicBlock manager object from a function address 319 | """ 320 | 321 | # Use cache? 322 | if use_cache: 323 | # Try to load cached items 324 | if self.load(_get_cache_filename(func_addr)): 325 | # Loaded successfully, return to caller... 326 | return (True, self) 327 | 328 | # Is it possible to operate in standalone mode? 329 | if stdalone: 330 | return (False, "Cannot compute IDA basic blocks in standalone mode!") 331 | 332 | # Get the IDA function object 333 | fnc = idaapi.get_func(func_addr) 334 | if fnc is None: 335 | return (False, "No function at %x" % func_addr) 336 | 337 | # Update function address to point to the start of the function 338 | func_addr = fnc.startEA 339 | 340 | # Main IDA BB loop 341 | fc = idaapi.FlowChart(fnc) 342 | for block in fc: 343 | # Try to get BB reference 344 | bb = self[block.id] 345 | if bb is None: 346 | bb = BBDef(id=block.id, 347 | start=block.startEA, 348 | end=block.endEA) 349 | 350 | # Add the current block 351 | self.add_bb_ctx( 352 | bb, 353 | get_bytes, 354 | get_hash_itype1, 355 | get_hash_itype2) 356 | 357 | # Add all successors 358 | for succ_block in block.succs(): 359 | # Is the successor block already present? 360 | b0 = self[succ_block.id] 361 | if b0 is None: 362 | b0 = BBDef(id=succ_block.id, 363 | start=succ_block.startEA, 364 | end=succ_block.endEA) 365 | 366 | # Add successor 367 | self.add_bb_ctx( 368 | b0, 369 | get_bytes, 370 | get_hash_itype1, 371 | get_hash_itype2) 372 | 373 | # Link successor 374 | bb.add_succ(b0, link_pred = True) 375 | 376 | # Add all predecessors 377 | for pred_block in block.preds(): 378 | # Is the successor block already present? 379 | b0 = self[pred_block.id] 380 | if b0 is None: 381 | b0 = BBDef(id=pred_block.id, 382 | start=pred_block.startEA, 383 | end=pred_block.endEA) 384 | # Add predecessor 385 | self.add_bb_ctx( 386 | b0, 387 | get_bytes, 388 | get_hash_itype1, 389 | get_hash_itype2) 390 | 391 | # Link predecessor 392 | bb.add_pred(b0, link_succ = True) 393 | 394 | # Save nodes if cache is enabled 395 | if use_cache: 396 | self.save(_get_cache_filename(func_addr)) 397 | 398 | return (True, self) 399 | 400 | # ------------------------------------------------------------------------------ 401 | def BuildBBFromIdaFlowchart(func_addr, use_cache=True): 402 | bm = IDABBMan() 403 | ok = bm.FromFlowchart( 404 | func_addr, 405 | use_cache = use_cache, 406 | get_bytes = not stdalone, 407 | get_hash_itype1 = not stdalone, 408 | get_hash_itype2 = not stdalone) 409 | 410 | 411 | # ------------------------------------------------------------------------------ 412 | def HashNode(bm, node_id, hash_id=1): 413 | """ 414 | Hash a node 415 | """ 416 | bb = bm[node_id] 417 | if hash_id == 1: 418 | r = hash_itype1(bb.start, bb.end) 419 | elif hash_id == 2: 420 | r = hash_itype2(bb.start, bb.end) 421 | else: 422 | r = "unknown hash_id %d" % hash_id 423 | print "->%s" % r 424 | 425 | # ------------------------------------------------------------------------------ 426 | # Precompute lots of prime numbers. This should be more than the count of 427 | # instructions for most processor modules in IDA 428 | 429 | # Total prime numbers 430 | _MAX_PRIMES = 8117 431 | # Prime number offsets offset in the primes pool: 432 | # We have UA_MAXOP operands, each may have 'o_last' operand types. 433 | # For each operand type we have to assure a unique prime number 434 | _OP_P_OFFS = _MAX_PRIMES - (UA_MAXOP * (o_last+1)) 435 | 436 | # Precompute primes 437 | _CachedPrimes = bb_utils.CachedPrimes(_MAX_PRIMES) 438 | 439 | # ------------------------------------------------------------------------------ 440 | if __name__ == '__main__': 441 | ft1 = (7, {21614129: 5, 4790013691321L: 1, 722682555311L: 1}) 442 | ft2 = (3, {21614129: 1, 4790013691321L: 1, 722682555311L: 1}) 443 | 444 | match_block_frequencies(ft1, ft2, 90, 90) 445 | -------------------------------------------------------------------------------- /bin/plugins/GraphSlick/bb_match.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains IDA specific basic block datatypes and helper functions. 3 | 4 | It is written by Ali Rahbar and Ali Pezeshk 5 | 6 | 7 | 10/10/2013 - alirah - Initial version 8 | 10/11/2013 - alipezes - Added IDABBman subclass of BBMan 9 | - Added hash_itype1() and IDABBContext() class 10 | 11/07/2013 - eliasb - Renamed some functions to work with the C adapter 11 | 11/08/2013 - alipezes - Fixed serialization issue 12 | 08/27/2014 - alirah - Cleaned up the script and readied it for public release 13 | """ 14 | 15 | import idaapi 16 | 17 | 18 | # ------------------------------------------------------------------------------ 19 | import pickle 20 | import cStringIO 21 | from bb_ida import * 22 | import Queue 23 | from collections import defaultdict 24 | from ordered_set import OrderedSet 25 | 26 | # ------------------------------------------------------------------------------ 27 | class bbMatcherClass: 28 | 29 | MagicHeader = "--CONTEXT--" 30 | PathPerNodeHashMarker = "PathPerNodeHash\n" 31 | PathPerNodeHashFullMarker = "PathPerNodeHashFullMarker\n" 32 | SizeDicMarker = "Size_Dic\n" 33 | NodeHashesMarker = "Node_Hashes\n" 34 | NodeHashMatchesMarker = "Node_Hash_Matches\n" 35 | 36 | def __init__(self,func_addr=None): 37 | self.M={} 38 | # this one contains paths matched, that have entries only to the head node 39 | self.pathPerNodeHash=defaultdict(dict) 40 | # this one contains paths matched, regardless of entries 41 | self.pathPerNodeHashFull = defaultdict(dict) 42 | self.normalizedPathPerNodeHash = {} 43 | self.size_dic={} 44 | self.sorted_keys=None 45 | self.G=None 46 | self.address=None 47 | self.nodeHashes = defaultdict(dict) 48 | self.bm=None 49 | if func_addr!=None: 50 | self.buildGRaphFromFunc(func_addr) 51 | 52 | 53 | def buildGRaphFromFunc(self,func_addr): 54 | """Return a graph object from the function with the hash type 1""" 55 | self.bm = IDABBMan() 56 | ok,self.G=self.bm.FromFlowchart( 57 | func_addr, 58 | use_cache=True, 59 | get_bytes=True, 60 | get_hash_itype1 =True, 61 | get_hash_itype2 =True) 62 | self.address = func_addr 63 | 64 | def match(self,N1,N2, hashType): 65 | """Matches two nodes based on their type1(ordered instruction type hash) hash""" 66 | if (hashType == 'freq'): 67 | f1 = get_block_frequency(N1.start, N1.end) 68 | f2 = get_block_frequency(N2.start, N2.end) 69 | a, d1 = f1 70 | b, d2 = f2 71 | 72 | if ( a <= 4 or b <= 4 ): 73 | coveragePercentage = 50 74 | elif ( a <= 6 or b <= 6 ): 75 | coveragePercentage = 60 76 | elif ( a <= 8 or b <= 8 ): 77 | coveragePercentage = 75 78 | else: 79 | coveragePercentage = 85 80 | 81 | b1, b2 = match_block_frequencies(f1, f2, coveragePercentage, 95) 82 | if (b1 and b2): 83 | intersection = set.intersection(set(d1.keys()), set(d2.keys())) 84 | freqHash = hashlib.sha1() 85 | freqHash.update(intersection.__str__()) 86 | hash = freqHash.hexdigest() 87 | N1['freq'] = hash 88 | N2['freq'] = hash 89 | return b1 and b2 90 | if ((N1[hashType]==N2[hashType])) : 91 | return True 92 | return False 93 | 94 | def hashBBMatch(self, hashType): 95 | """Creates a dictionary of basic blocks with the hash as the key and matching block numbers as items of a list for that entry""" 96 | for i in range(0,len(self.G.items())): 97 | for j in range (i+1,len(self.G.items())): 98 | if self.match(self.G[i],self.G[j],hashType): 99 | x=self.G[i][hashType] 100 | if self.M.has_key(x): 101 | if self.M[x].count(j)==0: 102 | self.M[x]+=[j] 103 | 104 | else: 105 | self.M[x]=[i,j] 106 | 107 | def makeSubgraphSingleEntryPoint(self,path1, path2): 108 | if (len(path1) != len(path2)): 109 | quit() 110 | 111 | tmp_path1 = list(path1) 112 | tmp_path2 = list(path2) 113 | headNode = path1[0] 114 | nodeRemoved = True 115 | while nodeRemoved: 116 | nodeRemoved = False 117 | for node in tmp_path1: 118 | for pred in self.G[node].preds: 119 | if not (pred in tmp_path1) and node != headNode: 120 | nodeRemoved = True 121 | break 122 | if (nodeRemoved): 123 | try: 124 | nodeIndex = tmp_path1.index(node) 125 | tmp_path1.remove(node) 126 | tmp_path2.remove( tmp_path2[nodeIndex] ) 127 | except: 128 | quit() 129 | 130 | return OrderedSet(tmp_path1), OrderedSet(tmp_path2) 131 | 132 | def findMatchInSuccs(self, node1, Parent2, hashType, visitedNodes2, tmpVisitedNodes2, path2): 133 | matchedbyHash = False 134 | for m in self.G[Parent2].succs: 135 | if (m not in visitedNodes2) and (m !=Parent2) and (m not in path2): 136 | tmpVisitedNodes2.add(m) 137 | if (self.match(self.G[node1], self.G[m], hashType)): 138 | if node1==m: 139 | continue 140 | matchedbyHash = True 141 | break 142 | return matchedbyHash, m, tmpVisitedNodes2 143 | 144 | 145 | def findSubGraphs(self): 146 | """Find equivalent path from two equivalent nodes 147 | For each node hash it gets all of the BB and try to build path from each pair of them 148 | The result is put in a dual dictionary that has the starting node hash as the first key, the path hash as the second key and the equivalent pathS as a list of sets(containing nodes) 149 | """ 150 | matchedPathsWithDifferentLengths = 0 151 | for i in self.M.keys(): 152 | for z in range(0,len(self.M[i])-1): 153 | for j in self.M[i][z+1:]: #pick one from the second node onward 154 | visited1=set() 155 | visited2=set() 156 | q1=Queue.Queue() #add the first and n node to tmp 157 | q1.put((self.M[i][z],j)) 158 | path1=OrderedSet() 159 | path2=OrderedSet() 160 | path1_bis=OrderedSet() 161 | path2_bis=OrderedSet() 162 | path1NodeHashes = {} 163 | path1.add(self.M[i][z]) 164 | path2.add(j) 165 | path1Str='' 166 | path2Str='' 167 | path1NodeHashes[self.M[i][z]]=self.G[(self.M[i][z])].ctx.hash_itype2 168 | pathHash1= hashlib.sha1() 169 | while not q1.empty(): # for each matching pair from tmp 170 | x,y = q1.get(block = False) 171 | tmp_visited2=set() 172 | for l in self.G[x].succs : 173 | matchedbyHash = False 174 | if (l not in visited1) and (l !=x) and (l not in path1): 175 | visited1.add(l) 176 | tmp_visited2Backup=tmp_visited2 177 | hashType = 'hash_itype1' 178 | matchedbyHash, m, tmp_visited2 = self.findMatchInSuccs( l, y, hashType, visited2, tmp_visited2, path2) 179 | 180 | if not matchedbyHash: 181 | hashType = 'hash_itype2' 182 | tmp_visited2= tmp_visited2Backup 183 | matchedbyHash, m, tmp_visited2 = self.findMatchInSuccs( l, y, hashType, visited2, tmp_visited2, path2) 184 | 185 | if not matchedbyHash: 186 | hashType = 'freq' 187 | tmp_visited2= tmp_visited2Backup 188 | matchedbyHash, m, tmp_visited2 = self.findMatchInSuccs( l, y, hashType, visited2, tmp_visited2, path2) 189 | 190 | if matchedbyHash: 191 | path1NodeHashes[l] = self.G[l][hashType] 192 | path1.add(l) 193 | path2.add(m) 194 | q1.put((l,m)) 195 | visited2.add(m) 196 | 197 | visited2.update(tmp_visited2) 198 | if (len(path1) != len(path2)): 199 | matchedPathsWithDifferentLengths += 1 200 | else: 201 | path1_bis, path2_bis = self.makeSubgraphSingleEntryPoint(path1, path2) 202 | 203 | if len(path1) >1: 204 | for kk in path1: 205 | path1Str+=path1NodeHashes[kk] 206 | 207 | pathHash1.update(path1Str) 208 | a=pathHash1.hexdigest() 209 | if not(self.pathPerNodeHashFull.has_key(i)) or (not( self.pathPerNodeHashFull[i].has_key(a))): 210 | self.pathPerNodeHashFull[i][a]=[] 211 | 212 | duplicate1 = False 213 | duplicate2 = False 214 | 215 | listPath1 = list(path1) 216 | listPath2 = list(path2) 217 | 218 | for zz in self.pathPerNodeHashFull[i][a]: 219 | if listPath1 == zz: 220 | duplicate1 = True 221 | if listPath2 == zz: 222 | duplicate2 = True 223 | 224 | if not duplicate1: 225 | self.pathPerNodeHashFull[i][a].append(list(listPath1)) 226 | if not duplicate2: 227 | self.pathPerNodeHashFull[i][a].append(list(listPath2)) 228 | 229 | if len(path1_bis) >1: 230 | path1Str = '' 231 | for kk in path1_bis: 232 | path1Str+=path1NodeHashes[kk] 233 | 234 | pathHash1.update(path1Str) 235 | a=pathHash1.hexdigest() 236 | if not(self.pathPerNodeHash.has_key(i)) or (not( self.pathPerNodeHash[i].has_key(a))): 237 | self.pathPerNodeHash[i][a]=[] 238 | 239 | duplicate1 = False 240 | duplicate2 = False 241 | 242 | listPath1 = list(path1_bis) 243 | listPath2 = list(path2_bis) 244 | 245 | for zz in self.pathPerNodeHash[i][a]: 246 | if listPath1 == zz: 247 | duplicate1 = True 248 | if listPath2 == zz: 249 | duplicate2 = True 250 | 251 | if not duplicate1: 252 | self.pathPerNodeHash[i][a].append(list(listPath1)) 253 | if not duplicate2: 254 | self.pathPerNodeHash[i][a].append(list(listPath2)) 255 | 256 | def sortByPathLen(self): 257 | """It gets the structure created by findSubGraph and creates a dictionary with the path len as the key and the tupple of node hash and path hash as the entry""" 258 | 259 | for i in self.pathPerNodeHash: 260 | for k in self.pathPerNodeHash[i]: 261 | a=len(self.pathPerNodeHash[i][k][0]) 262 | if not(self.size_dic.has_key(a)): 263 | self.size_dic[a]=[] 264 | self.size_dic[a].append((i,k)) 265 | 266 | self.sorted_keys=sorted(self.size_dic.keys()) 267 | 268 | def subgraphHasExternalJumpsIntoIt(self,subgraph): 269 | for node in list(subgraph)[1:] : 270 | if not set(self.G[node].preds).issubset(set(subgraph)): 271 | return True 272 | return False 273 | 274 | def GetMatchedWellFormedFunctions(self, minFunctionSizeInBlocks = 4, minFunctionHeadSize = 0): 275 | MovedSubgraph = [] 276 | for i in reversed(sorted(self.size_dic.keys())): 277 | if i < minFunctionSizeInBlocks : 278 | break 279 | 280 | for item in self.size_dic[i]: 281 | x,y=item 282 | if (not self.normalizedPathPerNodeHash.has_key(x)): 283 | self.normalizedPathPerNodeHash[x] = {} 284 | if (not self.normalizedPathPerNodeHash[x].has_key(y)): 285 | self.normalizedPathPerNodeHash[x][y] = [] 286 | 287 | if self.subgraphHasExternalJumpsIntoIt( self.pathPerNodeHash[x][y][0]): 288 | continue 289 | for j in self.pathPerNodeHash[x][y]: 290 | skip=False 291 | for k in MovedSubgraph: 292 | if set(j).issubset(set(k)): 293 | skip=True 294 | break 295 | if not skip: 296 | self.normalizedPathPerNodeHash[x][y].append(j) 297 | 298 | if len(self.normalizedPathPerNodeHash[x][y]) < 2 : 299 | self.normalizedPathPerNodeHash[x][y] = [] 300 | else: 301 | if ( minFunctionHeadSize > 0 ): 302 | subgraphStartAddress = self.G[ self.normalizedPathPerNodeHash[x][y][0][0] ].start 303 | functionHeadBigEnough = True 304 | for address in range ( subgraphStartAddress, subgraphStartAddress + 8, 2 ): 305 | if not AddressIsInSubgraph( address, self.normalizedPathPerNodeHash[x][y][0] ): 306 | functionHeadBigEnough = False 307 | break 308 | if not functionHeadBigEnough: 309 | self.normalizedPathPerNodeHash[x][y] = [] 310 | 311 | MovedSubgraph.extend( self.normalizedPathPerNodeHash[x][y] ) 312 | 313 | def AddressIsInSubgraph(self, address, subgraph) : 314 | for i in subgraph : 315 | if (self.G[i].start <= address < self.G[i].end) : 316 | return True 317 | return False 318 | 319 | def FindSimilar(self, nodeList, hashType = 'hash_itype2' ): 320 | size = len(nodeList) 321 | headNode = nodeList[0] 322 | setNodeList = set( nodeList ) 323 | 324 | result = [] 325 | 326 | if ( size == 1 ): 327 | return self.M[ self.nodeHashes[headNode][hashType] ] 328 | 329 | for headNode in nodeList: 330 | headNodeHash = self.nodeHashes[headNode][hashType] 331 | for subgraphHash in self.pathPerNodeHashFull[headNodeHash]: 332 | if size <= len(self.pathPerNodeHashFull[headNodeHash][subgraphHash][0]): 333 | for match in self.pathPerNodeHashFull[headNodeHash][subgraphHash]: 334 | if headNode == match[0] and setNodeList.issubset(set(match)): 335 | # get the subsets from each path that matches the input node list 336 | matchIndex = {} 337 | for node in nodeList: 338 | matchIndex[node] = match.index(node) 339 | for matchedSubgraph in self.pathPerNodeHashFull[headNodeHash][subgraphHash]: 340 | subset = [] 341 | for node in nodeList: 342 | subset.append( matchedSubgraph[matchIndex[node]] ) 343 | if ( not result.__contains__( subset ) ) : 344 | result.append( subset ) 345 | break 346 | if len(result) > 0: 347 | return result 348 | return [] 349 | 350 | def SerializeMatchedInlineFunctions(self, fileName=None ): 351 | reducedPathPerNodeHash = {} 352 | if fileName!=None: 353 | f = open(fileName, 'w') 354 | else: 355 | f= StringIO.StringIO() 356 | for x in self.normalizedPathPerNodeHash: 357 | reducedPathPerNodeHash[x] = {} 358 | for y in self.normalizedPathPerNodeHash[x]: 359 | reducedPathPerNodeHash[x][y] = [] 360 | for i in range(0, len(self.normalizedPathPerNodeHash[x][y])): 361 | duplicate = False 362 | for j in reducedPathPerNodeHash[x][y]: 363 | if self.normalizedPathPerNodeHash[x][y][i].__str__() == j.__str__(): 364 | duplicate = True 365 | break 366 | if not duplicate: 367 | reducedPathPerNodeHash[x][y].append( self.normalizedPathPerNodeHash[x][y][i] ) 368 | 369 | 370 | 371 | c = 0 372 | f.write(bbMatcherClass.MagicHeader + "PATH_INFO\n") 373 | 374 | for x in reducedPathPerNodeHash: 375 | if reducedPathPerNodeHash[x] == {}: 376 | continue 377 | for y in reducedPathPerNodeHash[x]: 378 | if reducedPathPerNodeHash[x][y] == []: 379 | continue 380 | f.write ("ID:%s;NODESET:"%(y)), 381 | count =0 382 | for i in reducedPathPerNodeHash[x][y]: 383 | c += 1 384 | for j in i: # each set 385 | if j == i[0]: 386 | f.write ("("), 387 | f.write ("%d : %x : %x"%(j,self.G[j].start,self.G[j].end)), 388 | if j != i[len(i)-1]: # last item 389 | f.write (", "), 390 | else : 391 | f.write (")"), 392 | count +=1 393 | if count != len(reducedPathPerNodeHash[x][y]): # last one: 394 | f.write (", "), 395 | else: 396 | f.write (";\n") 397 | 398 | if fileName!=None: 399 | result =None 400 | else: 401 | result= f.value() 402 | f.close() 403 | return result 404 | 405 | 406 | def SaveState(self,fileName=None): 407 | if fileName!=None: 408 | f = open(fileName, 'a') 409 | else: 410 | f= StringIO.StringIO() 411 | result = self.SerializeMatchedInlineFunctions(fileName) 412 | 413 | serializedPathPerNodeHash = pickle.dumps( self.pathPerNodeHash ) 414 | serializedPathPerNodeHashFull = pickle.dumps( self.pathPerNodeHash ) 415 | serializedSizeDic = pickle.dumps( self.size_dic ) 416 | serializedNodeHashes = pickle.dumps( self.nodeHashes ) 417 | serializedNodeHashMatches = pickle.dumps (self.M) 418 | 419 | f.write(bbMatcherClass.MagicHeader + bbMatcherClass.PathPerNodeHashMarker) 420 | f.write(serializedPathPerNodeHash) 421 | f.write(bbMatcherClass.MagicHeader + bbMatcherClass.PathPerNodeHashFullMarker) 422 | f.write(serializedPathPerNodeHashFull) 423 | f.write("\n" + bbMatcherClass.MagicHeader + bbMatcherClass.SizeDicMarker) 424 | f.write(serializedSizeDic) 425 | f.write("\n" + bbMatcherClass.MagicHeader + bbMatcherClass.NodeHashesMarker) 426 | f.write(serializedNodeHashes) 427 | f.write("\n" + bbMatcherClass.MagicHeader + bbMatcherClass.NodeHashMatchesMarker) 428 | f.write(serializedNodeHashMatches) 429 | 430 | if fileName!=None: 431 | result =None 432 | else: 433 | result += f.value() 434 | f.close() 435 | return result 436 | 437 | def LoadState(self,fileName=None,input=None): 438 | if fileName!=None: 439 | f = open(fileName, 'r') 440 | if input!=None: 441 | f= StringIO.StringIO(input) 442 | 443 | fileContents = f.read() 444 | 445 | fileSegments = fileContents.split(bbMatcherClass.MagicHeader) 446 | 447 | for segment in fileSegments[1:] : 448 | if segment.startswith( bbMatcherClass.PathPerNodeHashMarker ): 449 | self.pathPerNodeHash = pickle.loads(segment[len(bbMatcherClass.PathPerNodeHashMarker):]) 450 | elif segment.startswith( bbMatcherClass.PathPerNodeHashFullMarker ): 451 | self.pathPerNodeHashFull = pickle.loads(segment[len( bbMatcherClass.PathPerNodeHashFullMarker):] ) 452 | elif segment.startswith( bbMatcherClass.SizeDicMarker ): 453 | self.size_dic = pickle.loads(segment[len(bbMatcherClass.SizeDicMarker):]) 454 | elif segment.startswith( bbMatcherClass.NodeHashesMarker ): 455 | self.nodeHashes = pickle.loads(segment[len( bbMatcherClass.NodeHashesMarker):]) 456 | elif segment.startswith( bbMatcherClass.NodeHashMatchesMarker ): 457 | self.M = pickle.loads(segment[len( bbMatcherClass.NodeHashMatchesMarker):] ) 458 | 459 | f.close() 460 | 461 | def Analyze(self,func_addr=None): 462 | result = [] 463 | if func_addr!=None: 464 | self.buildGRaphFromFunc(func_addr) 465 | if self.G !=None: 466 | # todo: refactor this to get the list from one place 467 | for hashName in ['hash_itype1', 'hash_itype2']: 468 | for i in self.G.items(): 469 | self.nodeHashes[i.id][hashName] = self.G[i.id][hashName] 470 | self.hashBBMatch('hash_itype2') 471 | self.findSubGraphs() 472 | self.sortByPathLen() 473 | self.GetMatchedWellFormedFunctions() 474 | 475 | 476 | for x in self.normalizedPathPerNodeHash: 477 | for y in self.normalizedPathPerNodeHash[x]: 478 | if self.normalizedPathPerNodeHash[x][y] != []: 479 | result.append( self.normalizedPathPerNodeHash[x][y] ) 480 | 481 | return result 482 | 483 | # ------------------------------------------------------------------------------ 484 | bbMatcher = bbMatcherClass() 485 | -------------------------------------------------------------------------------- /bin/plugins/GraphSlick/bb_types.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic Block base types module 3 | 4 | 5 | This module contains the basic types and data structures 6 | * 09/12/2013 - eliasb - Initial version 7 | * 09/16/2013 - eliasb - Renamed RawBB class to BBDef class 8 | - Added proper serialization 9 | - Added BBDef.size() and context attribute 10 | - Added Context attribute and size() to the BBDef class 11 | - Added BBMan.clear(), .get_cache() 12 | * 09/17/2013 - eliasb - Added method BBMan.is_using_cache() to see if the cache was used 13 | * 10/07/2013 - eliasb - Use the Caching class from bb_util 14 | - Added setitem/getitem helper methods to access the ctx object members of BBDef 15 | * 10/08/2013 - eliasb - bugfix: import from Caching 16 | - added BBMan.find_by_addr() 17 | * 10/09/2013 - eliasb - bugfix: find_by_addr() 18 | 19 | """ 20 | 21 | import pickle 22 | import sys 23 | from bb_utils import Caching 24 | 25 | # ------------------------------------------------------------------------------ 26 | class BBDef(object): 27 | """Class to define a raw basic block""" 28 | 29 | def __init__(self, start=0, end=0, id=-1, label="", ctx=None): 30 | self.start = start 31 | """Start address of basic block""" 32 | 33 | self.end = end 34 | """End address of basic block""" 35 | 36 | self.id = id 37 | """The ID of the basic block""" 38 | 39 | self.label = label 40 | """The label of the basic block""" 41 | 42 | self.preds = list() 43 | """List of predecessor node IDs""" 44 | 45 | self.succs = list() 46 | """List of successor node IDs""" 47 | 48 | self.ctx = ctx 49 | """Context associated with the BB""" 50 | 51 | 52 | def size(self): 53 | """Return the size of the BB""" 54 | return self.end - self.start 55 | 56 | 57 | def matchp(self, bb): 58 | """Match two BBs and return percentage of equality""" 59 | return 0.0 60 | 61 | 62 | def add_succ(self, bb, link_pred = False): 63 | """Add a successor basic block""" 64 | self.succs.append(bb.id) 65 | 66 | if link_pred: 67 | bb.add_pred(self, False) 68 | 69 | return self 70 | 71 | 72 | def add_pred(self, bb, link_succ = False): 73 | """Add predecessor basic block""" 74 | 75 | self.preds.append(bb.id) 76 | 77 | if link_succ: 78 | bb.add_succ(self, False) 79 | 80 | return self 81 | 82 | 83 | def __getitem__(self, key): 84 | """Shortcut method to access items in the context class""" 85 | return self.ctx.__dict__[key] 86 | 87 | 88 | def __setitem__(self, key, value): 89 | """Shortcut method to set item value in the context class""" 90 | self.ctx.__dict__[key] = value 91 | 92 | 93 | # ------------------------------------------------------------------------------ 94 | class BBMan(object): 95 | """Class to manage basic blocks""" 96 | def __init__(self): 97 | self.__idcache = {} 98 | self.__lasterr = None 99 | self.__is_using_cache = False 100 | 101 | 102 | def is_using_cache(): 103 | """Was the cache file used""" 104 | return self.__is_using_cache 105 | 106 | 107 | def clear(self): 108 | """Clear the basic blocks""" 109 | self.__idcache = {} 110 | self.__is_using_cache = False 111 | 112 | 113 | def last_error(self): 114 | """Return the last error string""" 115 | return self.__lasterr 116 | 117 | 118 | def save(self, filename): 119 | """Saves the basic blocks to a file (serialize)""" 120 | ok, self.__lasterr = Caching.Save(filename, self.__idcache) 121 | return ok 122 | 123 | 124 | def load(self, filename): 125 | """Load the basic blocks from a file (deserialize)""" 126 | ok, r = Caching.Load(filename) 127 | if not ok: 128 | self.__lasterr = r 129 | self.__is_using_cache = False 130 | return False 131 | 132 | self.__is_using_cache = True 133 | self.__idcache = r 134 | return True 135 | 136 | 137 | def add(self, bb): 138 | """Add a basic block""" 139 | 140 | # Cache the basic block by ID 141 | self.__idcache[bb.id] = bb 142 | 143 | 144 | def items(self): 145 | """Return all the raw basic block objects""" 146 | return self.__idcache.values() 147 | 148 | 149 | def __getitem__(self, index): 150 | try: 151 | return self.__idcache[index] 152 | except: 153 | return None 154 | 155 | 156 | def get_cache(self): 157 | return self.__idcache 158 | 159 | 160 | def gen_dot(self, filename): 161 | """Generate a DOT file""" 162 | 163 | # TODO 164 | pass 165 | 166 | 167 | def find_by_addr(self, addr): 168 | """Return the basic block that contains the given address""" 169 | for bb in self.items(): 170 | if bb.start <= addr < bb.end: 171 | return bb 172 | 173 | return None 174 | 175 | def build_from_id_string(self, conn): 176 | # Failed to load or no cache was to be used? 177 | for couples in conn.split(';'): 178 | n0, n1 = couples.split(':') 179 | n0 = int(n0) 180 | 181 | bb0 = self[n0] 182 | if bb0 is None: 183 | bb0 = BBDef(id=n0) 184 | self.add(bb0) 185 | 186 | for child in n1.split(','): 187 | ni = int(child) 188 | bbi = self[ni] 189 | if bbi is None: 190 | bbi = BBDef(id=ni) 191 | self.add(bbi) 192 | bb0.add_succ(bbi) 193 | 194 | # ------------------------------------------------------------------------------ 195 | def __test(): 196 | bm = BBMan() 197 | fn = 'test.bin' 198 | if not bm.load(fn): 199 | conn = "0:1,2,3,4,5;1:3;3:4;2:4" 200 | bm.build_from_id_string(conn) 201 | if not bm.save(fn): 202 | print "Could not save!" 203 | return 204 | 205 | # Process ..... 206 | bb0 = bm[0] 207 | for x in bb0.succs: 208 | x = bm[x] 209 | print x.id, ",", 210 | 211 | # ------------------------------------------------------------------------------ 212 | def __test2(): 213 | class X(object): 214 | def __init__(self): 215 | self.a = 0 216 | self.b = 0 217 | 218 | b = BBDef(ctx = X()) 219 | 220 | print "a=%d" % b['a'] 221 | b['a'] = 1 222 | b['c'] = 3 223 | print "a=%d c=%d" % (b['a'], b['c']) 224 | 225 | 226 | # ------------------------------------------------------------------------------ 227 | if __name__ == '__main__': 228 | __test2() 229 | -------------------------------------------------------------------------------- /bin/plugins/GraphSlick/bb_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility class 3 | 4 | This module contains the basic types and data structures 5 | 6 | * 09/17/2013 - eliasb - Added the GenPrimes() utility function 7 | * 10/07/2013 - eliasb - Added the Caching class 8 | - Added Cached prime generator 9 | * 10/15/2013 - eliasb - Added RekeyDictionary() 10 | 11 | """ 12 | 13 | import pickle 14 | import sys 15 | 16 | # ------------------------------------------------------------------------------ 17 | class Caching(object): 18 | @staticmethod 19 | def Save(filename, obj): 20 | """Serializes an object to a file""" 21 | try: 22 | f = open(filename, 'wb') 23 | pickle.dump(obj, f, protocol=pickle.HIGHEST_PROTOCOL) 24 | f.close() 25 | return (True, None) 26 | except: 27 | return (False, sys.exc_info()[0]) 28 | 29 | @staticmethod 30 | def Load(filename): 31 | """Deserializes an object from a file""" 32 | try: 33 | f = open(filename, 'rb') 34 | ret = pickle.load(f) 35 | f.close() 36 | return (True, ret) 37 | except: 38 | return (False, sys.exc_info()[0]) 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | def RekeyDictionary(d): 43 | """ 44 | Reassigns simple numeric keys to a dictionary 45 | """ 46 | 47 | # New mapping 48 | rkd = {} 49 | # New key numbering 50 | nk = 0 51 | for k in d: 52 | rkd[nk] = d[k] 53 | nk += 1 54 | 55 | return rkd 56 | 57 | 58 | # ------------------------------------------------------------------------------ 59 | def GenPrimes(): 60 | """ 61 | Generate an infinite sequence of prime numbers. 62 | http://stackoverflow.com/questions/567222/simple-prime-generator-in-python 63 | """ 64 | # Maps composites to primes witnessing their compositeness. 65 | # This is memory efficient, as the sieve is not "run forward" 66 | # indefinitely, but only as long as required by the current 67 | # number being tested. 68 | # 69 | D = {} 70 | 71 | # The running integer that's checked for primeness 72 | q = 2 73 | 74 | while True: 75 | if q not in D: 76 | # q is a new prime. 77 | # Yield it and mark its first multiple that isn't 78 | # already marked in previous iterations 79 | # 80 | yield q 81 | D[q * q] = [q] 82 | else: 83 | # q is composite. D[q] is the list of primes that 84 | # divide it. Since we've reached q, we no longer 85 | # need it in the map, but we'll mark the next 86 | # multiples of its witnesses to prepare for larger 87 | # numbers 88 | # 89 | for p in D[q]: 90 | D.setdefault(p + q, []).append(p) 91 | del D[q] 92 | 93 | q += 1 94 | 95 | 96 | # ------------------------------------------------------------------------------ 97 | class CachedPrimes(object): 98 | def __init__(self, count, fn_template = 'Primes%08d.cache'): 99 | # Format the file name 100 | fn = fn_template % count 101 | 102 | # Try to load the cache file 103 | ok, self.__primes = Caching.Load(fn) 104 | 105 | # No cache file? 106 | if not ok: 107 | # Pre-generate numbers 108 | L = [] 109 | for p in GenPrimes(): 110 | L.append(p) 111 | if count <= 1: 112 | break 113 | # Decrement count 114 | count = count - 1 115 | 116 | # Save the pregenerated primes 117 | self.__primes = L 118 | 119 | # Cache them as well 120 | Caching.Save(fn, L) 121 | 122 | def __getitem__(self, key): 123 | return self.__primes[key] 124 | 125 | def __iter__(self): 126 | return iter(self.__primes) 127 | 128 | def __len__(self): 129 | return len(self.__primes) 130 | 131 | 132 | # ------------------------------------------------------------------------------ 133 | if __name__ == '__main__': 134 | cp = CachedPrimes(5001) 135 | print "L=%d x2=%d" % (len(cp), cp[2]) 136 | for x in xrange(5, 10): 137 | print "\t->", cp[x] 138 | 139 | for x in cp: 140 | print x 141 | 142 | pass -------------------------------------------------------------------------------- /bin/plugins/GraphSlick/init.py: -------------------------------------------------------------------------------- 1 | """ 2 | BB matcher initialization script. 3 | 4 | This script ensures that it runs once per Python runtime. 5 | 6 | 7 | 11/07/2013 - eliasb - Initial version 8 | """ 9 | 10 | import os 11 | import sys 12 | import idaapi 13 | 14 | # Get the script path 15 | lib_path = os.path.join(idaapi.idadir("plugins"), "GraphSlick") 16 | 17 | # Run this code once by checking if the required scripts 18 | # are in the path 19 | if lib_path not in sys.path: 20 | sys.path.append(lib_path) 21 | print sys.path 22 | 23 | # Import the matcher module 24 | import bb_match 25 | 26 | #print "Imported" 27 | else: 28 | #print "Already imported" 29 | pass 30 | -------------------------------------------------------------------------------- /bin/plugins/graphslick.plw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/bin/plugins/graphslick.plw -------------------------------------------------------------------------------- /bin/sample_exe/code1.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/bin/sample_exe/code1.exe -------------------------------------------------------------------------------- /bin/sample_exe/code1.idb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/bin/sample_exe/code1.idb -------------------------------------------------------------------------------- /colorgen.cpp: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------------------- 2 | GraphSlick (c) Elias Bachaalany 3 | ------------------------------------- 4 | 5 | Color generation module 6 | 7 | This module implements the color generator 8 | 9 | History 10 | -------- 11 | 12 | 10/24/2013 - eliasb - First version 13 | 10/24/2013 - eliasb - Added Rewind() method 14 | 10/25/2013 - eliasb - Added get_color_anyway() method 15 | 16 | --------------------------------------------------------------------------*/ 17 | 18 | #include "colorgen.h" 19 | 20 | //---------------------------------------------------------------------- 21 | // Conversion between the HSL(Hue, Saturation, and Luminosity) 22 | // and RBG color model. 23 | //---------------------------------------------------------------------- 24 | // The conversion algorithms presented here come from the book by 25 | // Fundamentals of Interactive Computer Graphics by Foley and van Dam. 26 | // In the example code, HSL values are represented as floating point 27 | // number in the range 0 to 1. RGB tridrants use the Windows convention 28 | // of 0 to 255 of each element. 29 | //---------------------------------------------------------------------- 30 | 31 | //-------------------------------------------------------------------------- 32 | /** 33 | * @brief 34 | */ 35 | inline unsigned int make_rgb( 36 | bool bRealRgb, 37 | unsigned char r, 38 | unsigned char g, 39 | unsigned char b) 40 | { 41 | if (bRealRgb) 42 | { 43 | return (r << 16) | (g << 8) | b; 44 | } 45 | else 46 | { 47 | return (b << 16) | (g << 8) | r; 48 | } 49 | } 50 | 51 | // ------------------------------------------------------------------------------ 52 | /** 53 | * @brief 54 | */ 55 | static unsigned char to_rgb(double rm1, double rm2, double rh) 56 | { 57 | if (rh > 360.0) 58 | rh -= 360.0; 59 | else if (rh < 0.0) 60 | rh += 360.0; 61 | 62 | if (rh < 60.0) 63 | rm1 = rm1 + (rm2 - rm1) * rh / 60.0; 64 | else if (rh < 180.0) 65 | rm1 = rm2; 66 | else if (rh < 240.0) 67 | rm1 = rm1 + (rm2 - rm1) * (240.0 - rh) / 60.0; 68 | 69 | return (unsigned char)(rm1 * 255.0); 70 | } 71 | 72 | //-------------------------------------------------------------------------- 73 | static unsigned int HSLtoRGB( 74 | bool bRealRgb, 75 | unsigned int H, 76 | unsigned int S, 77 | unsigned int L) 78 | { 79 | if (S == 0) 80 | { 81 | // achromatic 82 | return make_rgb(bRealRgb, L, L, L); 83 | } 84 | 85 | double h = (double)H*360/255; 86 | double s = (double)S / 255; 87 | double l = (double)L / 255; 88 | double rm1, rm2; 89 | 90 | if (l <= 0.5) 91 | rm2 = l + l * s; 92 | else 93 | rm2 = l + s - l * s; 94 | rm1 = 2.0 * l - rm2; 95 | 96 | return make_rgb(bRealRgb, 97 | to_rgb(rm1, rm2, h + 120.0), 98 | to_rgb(rm1, rm2, h), 99 | to_rgb(rm1, rm2, h - 120.0) ); 100 | } 101 | 102 | //-------------------------------------------------------------------------- 103 | unsigned int colorvargen_t::get_color() 104 | { 105 | if (l <= L_END) 106 | return 0; 107 | 108 | unsigned int old_l = l; 109 | l += L_INT; 110 | 111 | return HSLtoRGB(bRealRgb, h, s, old_l); 112 | } 113 | 114 | //-------------------------------------------------------------------------- 115 | bool colorgen_t::get_colorvar(colorvargen_t &cv) 116 | { 117 | unsigned int old_h = h, old_s = s; 118 | 119 | // Done with the Hue? 120 | if (h > H_END) 121 | { 122 | // Done with the Saturation? 123 | if (s < S_END) 124 | { 125 | // Nothing else to do 126 | return false; 127 | } 128 | else 129 | { 130 | // Advance Saturation so we get a whole new set of Hue 131 | s += S_INT; 132 | old_s = s; 133 | } 134 | // Start Hue all over again 135 | old_h = h = H_START; 136 | } 137 | else 138 | { 139 | // Advance to next Hue 140 | h += H_INT; 141 | } 142 | 143 | cv.bRealRgb = bRealRgb; 144 | cv.h = old_h; 145 | cv.s = old_s; 146 | cv.l = L_START; 147 | cv.L_END = L_END; 148 | cv.L_INT = L_INT; 149 | 150 | return true; 151 | } 152 | 153 | //-------------------------------------------------------------------------- 154 | colorgen_t::colorgen_t( 155 | bool bRealRgb, 156 | unsigned int h_start, unsigned int h_end, unsigned int h_int, 157 | unsigned int s_start, unsigned int s_end, unsigned int s_int, 158 | unsigned int l_start, unsigned int l_end, unsigned int l_int) : 159 | bRealRgb(bRealRgb), 160 | H_START(h_start), H_END(h_end), H_INT(h_int), 161 | S_START(s_start), S_END(s_end), S_INT(s_int), 162 | L_START(l_start), L_END(l_end), L_INT(l_int) 163 | { 164 | h = H_START; 165 | s = S_START; 166 | } 167 | 168 | //-------------------------------------------------------------------------- 169 | void colorgen_t::rewind() 170 | { 171 | h = H_START; 172 | s = S_START; 173 | } 174 | 175 | //-------------------------------------------------------------------------- 176 | unsigned int colorgen_t::get_color_anyway(colorvargen_t &cv) 177 | { 178 | unsigned int clr; 179 | 180 | while (true) 181 | { 182 | // Get a color variant 183 | clr = cv.get_color(); 184 | if (clr != 0) 185 | break; 186 | 187 | // No variant? Pick a new color 188 | if (!get_colorvar(cv)) 189 | { 190 | // No more colors, just rewind 191 | rewind(); 192 | get_colorvar(cv); 193 | } 194 | } 195 | return clr; 196 | } 197 | -------------------------------------------------------------------------------- /colorgen.h: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------------------- 2 | GraphSlick (c) Elias Bachaalany 3 | ------------------------------------- 4 | 5 | Color generation module 6 | 7 | This module implements the color generator 8 | --------------------------------------------------------------------------*/ 9 | 10 | //-------------------------------------------------------------------------- 11 | class colorgen_t; 12 | class colorvargen_t 13 | { 14 | friend class colorgen_t; 15 | 16 | unsigned int l, L_END, L_INT; 17 | unsigned int h, s; 18 | bool bRealRgb; 19 | public: 20 | unsigned int get_color(); 21 | }; 22 | 23 | //-------------------------------------------------------------------------- 24 | class colorgen_t 25 | { 26 | private: 27 | unsigned int h, s; 28 | bool bRealRgb; 29 | public: 30 | unsigned int S_START, S_END, S_INT; 31 | unsigned int H_START, H_END, H_INT; 32 | unsigned int L_START, L_END, L_INT; 33 | 34 | colorgen_t(bool bRealRgb = false, 35 | unsigned int h_start=0, unsigned int h_end=255, unsigned int h_int=14, 36 | unsigned int s_start=255, unsigned int s_end=60, unsigned int s_int=-8, 37 | unsigned int l_start=190, unsigned int l_end=100, unsigned int l_int=-3); 38 | 39 | bool get_colorvar(colorvargen_t &cv); 40 | 41 | /** 42 | * @brief Generates a color. Prefers first a color variant 43 | */ 44 | unsigned int get_color_anyway(colorvargen_t &cv); 45 | 46 | void rewind(); 47 | }; 48 | -------------------------------------------------------------------------------- /doc/.~lock.usage.docx#: -------------------------------------------------------------------------------- 1 | ,aundro,flatiron.hex-rays.com,19.09.2014 09:52,file:///home/aundro/.config/libreoffice/3; -------------------------------------------------------------------------------- /doc/usage.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/doc/usage.docx -------------------------------------------------------------------------------- /groupman.h: -------------------------------------------------------------------------------- 1 | #ifndef __GROUPMAN__ 2 | #define __GROUPMAN__ 3 | 4 | /*-------------------------------------------------------------------------- 5 | GraphSlick (c) Elias Bachaalany 6 | ------------------------------------- 7 | 8 | GroupManager class 9 | 10 | This module define groups, nodes and related data structures. 11 | It also provides the group management class. 12 | 13 | --------------------------------------------------------------------------*/ 14 | 15 | 16 | //-------------------------------------------------------------------------- 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | //-------------------------------------------------------------------------- 23 | struct nodedef_t 24 | { 25 | int nid; 26 | ea_t start; 27 | ea_t end; 28 | 29 | nodedef_t(): nid(0), start(0), end(0) 30 | { 31 | } 32 | }; 33 | typedef nodedef_t *pnodedef_t; 34 | 35 | //-------------------------------------------------------------------------- 36 | /** 37 | * @brief A list of nodes making up a group 38 | */ 39 | class nodegroup_t: public std::list 40 | { 41 | public: 42 | void free_nodes(); 43 | pnodedef_t add_node(pnodedef_t nd = NULL); 44 | /** 45 | * @brief Return the first node definition from this group 46 | */ 47 | pnodedef_t get_first_node(); 48 | }; 49 | typedef nodegroup_t *pnodegroup_t; 50 | 51 | //-------------------------------------------------------------------------- 52 | /** 53 | * @brief Maps a node group to a single node id 54 | */ 55 | class ng2nid_t: public std::map 56 | { 57 | public: 58 | inline int get_ng_id(pnodegroup_t ng) 59 | { 60 | iterator it = find(ng); 61 | return it == end() ? -1 : it->second; 62 | } 63 | }; 64 | 65 | //-------------------------------------------------------------------------- 66 | /** 67 | * @brief Maps a node id to node definitions 68 | */ 69 | typedef std::map nid2ndef_t; 70 | 71 | //-------------------------------------------------------------------------- 72 | /** 73 | * @brief nodegroups type is a list of nodegroup type 74 | */ 75 | class nodegroup_list_t: public std::list 76 | { 77 | public: 78 | void free_nodegroup(bool free_nodes); 79 | /** 80 | * @brief Return the first node definition from the first group in the group list 81 | */ 82 | pnodedef_t get_first_node(); 83 | 84 | /** 85 | * @brief Return the first nodegroup 86 | */ 87 | pnodegroup_t get_first_ng(); 88 | 89 | /** 90 | * @brief Find the biggest node group (i.e: with the highest ND count) 91 | */ 92 | pnodegroup_t find_biggest(); 93 | 94 | /** 95 | * @brief Add a nodegroup to the list 96 | */ 97 | pnodegroup_t add_nodegroup(pnodegroup_t ng = NULL); 98 | }; 99 | typedef nodegroup_list_t *pnodegroup_list_t; 100 | 101 | //-------------------------------------------------------------------------- 102 | /** 103 | * @brief A super group is a groups container 104 | */ 105 | struct supergroup_t 106 | { 107 | public: 108 | /** 109 | * @brief Super group ID 110 | */ 111 | qstring id; 112 | 113 | /** 114 | * @brief Super group name 115 | */ 116 | qstring name; 117 | 118 | /** 119 | * @brief A synthetic group that was not loaded but generated on the fly 120 | */ 121 | bool is_synthetic; 122 | 123 | /** 124 | * @brief List of groups in the super group 125 | */ 126 | nodegroup_list_t groups; 127 | 128 | supergroup_t(); 129 | ~supergroup_t(); 130 | 131 | /** 132 | * @brief Properly clear out all the contained groups 133 | */ 134 | void clear(); 135 | 136 | /** 137 | * @brief Add a new node group 138 | * @return Node group 139 | */ 140 | pnodegroup_t add_nodegroup(pnodegroup_t ng = NULL); 141 | 142 | /** 143 | * @brief Removes a nodegroup from the SG list 144 | */ 145 | void remove_nodegroup(pnodegroup_t ng, bool free_ng); 146 | 147 | /** 148 | * @brief Copy attributes from the SG to this SG 149 | * The synthetic attribute is removed from both SGs 150 | */ 151 | void copy_attr_from(supergroup_t *sg); 152 | 153 | /** 154 | * @brief Return the count of defined groups 155 | */ 156 | inline size_t gcount() { return groups.size(); } 157 | 158 | /** 159 | * @brief Checks whether the SG has no more groups 160 | */ 161 | inline bool empty() { return groups.empty(); } 162 | 163 | /** 164 | * @brief Return the first node definition from the first group in the group list 165 | */ 166 | pnodedef_t get_first_node(); 167 | 168 | /** 169 | * @brief Return the first nodegroup 170 | */ 171 | pnodegroup_t get_first_ng(); 172 | 173 | /** 174 | * @brief Return a descriptive name for the super group 175 | */ 176 | const char *get_display_name(const char *defval = NULL); 177 | }; 178 | 179 | //-------------------------------------------------------------------------- 180 | typedef supergroup_t *psupergroup_t; 181 | 182 | //-------------------------------------------------------------------------- 183 | class supergroup_listp_t: public std::list 184 | { 185 | public: 186 | /** 187 | * @brief Copy this SGL to the desired one 188 | */ 189 | void copy_to(psupergroup_t dest); 190 | 191 | /** 192 | * @brief Remove a super group and frees it if needed 193 | */ 194 | void remove_sg(psupergroup_t sg, bool free_sg); 195 | }; 196 | 197 | typedef supergroup_listp_t *psupergroup_listp_t; 198 | 199 | //-------------------------------------------------------------------------- 200 | /** 201 | * @brief Node location class 202 | */ 203 | struct nodeloc_t 204 | { 205 | psupergroup_t sg; 206 | pnodegroup_t ng; 207 | pnodedef_t nd; 208 | 209 | nodeloc_t(): sg(NULL), ng(NULL), nd(NULL) 210 | { 211 | } 212 | nodeloc_t(psupergroup_t sg, 213 | pnodegroup_t ng, 214 | pnodedef_t nd): sg(sg), ng(ng), nd(nd) 215 | { 216 | } 217 | }; 218 | 219 | //-------------------------------------------------------------------------- 220 | /** 221 | * @brief Group management class 222 | */ 223 | class groupman_t 224 | { 225 | private: 226 | /** 227 | * @brief NodeId node location lookup map 228 | */ 229 | typedef std::map nid2nloc_map_t; 230 | nid2nloc_map_t nid2loc; 231 | 232 | /** 233 | * @brief Path super groups definition 234 | */ 235 | supergroup_listp_t path_sgl; 236 | 237 | /** 238 | * @brief Similar nodes super groups 239 | */ 240 | supergroup_listp_t similar_sgl; 241 | 242 | /** 243 | * @brief A lookup table for all node definitions 244 | */ 245 | nid2ndef_t all_nodes; 246 | 247 | /** 248 | * @brief Private copy constructor 249 | */ 250 | groupman_t(const groupman_t &) { } 251 | 252 | /** 253 | * @brief Parse a nodeset string 254 | */ 255 | bool parse_nodeset( 256 | psupergroup_t sg, 257 | char *grpstr); 258 | 259 | /** 260 | * @brief Parse a line 261 | */ 262 | bool parse_line( 263 | psupergroup_t sg, 264 | char *line); 265 | 266 | /** 267 | * @brief Free and clear a super group list 268 | */ 269 | void clear_sgl(psupergroup_listp_t sgl); 270 | 271 | public: 272 | 273 | /** 274 | * @brief File name that was last loaded 275 | */ 276 | qstring src_filename; 277 | 278 | /** 279 | * @brief Method to initialize lookups 280 | */ 281 | void initialize_lookups(); 282 | 283 | /** 284 | * @brief Return the path super groups 285 | */ 286 | inline psupergroup_listp_t get_path_sgl() { return &path_sgl; } 287 | 288 | /** 289 | * @brief All the node defs 290 | */ 291 | inline nid2ndef_t *get_nds() 292 | { 293 | return &all_nodes; 294 | } 295 | 296 | /** 297 | * @ctor Default constructor 298 | */ 299 | groupman_t() { } 300 | 301 | /** 302 | * @dtor Destructor 303 | */ 304 | ~groupman_t(); 305 | 306 | /** 307 | * @brief Clears the defined groups 308 | */ 309 | void clear(); 310 | 311 | /** 312 | * @brief Remember the node definition 313 | */ 314 | inline void map_nodedef(int nid, pnodedef_t nd) 315 | { 316 | all_nodes[nid] = nd; 317 | } 318 | 319 | /** 320 | * @brief Add a new super group 321 | */ 322 | psupergroup_t add_supergroup( 323 | psupergroup_listp_t sgl = NULL, 324 | psupergroup_t sg = NULL); 325 | 326 | /** 327 | * @brief Remove a super group 328 | */ 329 | void remove_supergroup( 330 | psupergroup_listp_t sgl, 331 | psupergroup_t sg); 332 | 333 | /** 334 | * @brief Rewrites the structure from memory back to a file 335 | * @param filename - the output file name 336 | */ 337 | bool emit( 338 | const char *filename, 339 | const char *additional_sections = NULL); 340 | 341 | /** 342 | * @brief Parse groups definition file 343 | */ 344 | bool parse( 345 | const char *filename, 346 | bool init_cache = true); 347 | 348 | 349 | /** 350 | * @brief A group manager is considered empty if it has no path information 351 | */ 352 | inline bool empty() { return path_sgl.empty(); } 353 | 354 | /** 355 | * @brief Combine the list of NGL into a single NG 356 | */ 357 | pnodegroup_t combine_ngl(pnodegroup_list_t ngl); 358 | 359 | /** 360 | * @brief Move nodes coming from various NGs to a single NG 361 | * The new NG will reside in the first node's SG 362 | */ 363 | pnodegroup_t move_nodes_to_ng(pnodegroup_t ng); 364 | 365 | /** 366 | * @brief Move all nodes to their own SG/NG 367 | */ 368 | void reset_groupping(); 369 | 370 | /** 371 | * @brief Find a node location by ID 372 | */ 373 | nodeloc_t *find_nodeid_loc(int nid); 374 | 375 | /** 376 | * @brief Find a node by an address 377 | */ 378 | nodeloc_t *find_node_loc(ea_t ea); 379 | 380 | /** 381 | * @brief Returns one node definition from the data structure 382 | */ 383 | pnodedef_t get_first_nd(); 384 | 385 | void emit_sgl( 386 | FILE *fp, 387 | supergroup_listp_t* path_sgl); 388 | }; 389 | #endif -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | PROC=plugin 2 | O1=algo 3 | O2=colorgen 4 | O3=util 5 | 6 | include ../plugin.mak 7 | include ../pyplg.mak 8 | _CFLAGS += ${PYTHON_CFLAGS} 9 | 10 | # MAKEDEP dependency list ------------------ 11 | $(F)algo$(O): $(I)bitrange.hpp $(I)bytes.hpp $(I)config.hpp $(I)fpro.h \ 12 | $(I)funcs.hpp $(I)ida.hpp $(I)idp.hpp $(I)kernwin.hpp \ 13 | $(I)lines.hpp $(I)llong.hpp $(I)loader.hpp $(I)nalt.hpp \ 14 | $(I)netnode.hpp $(I)pro.h $(I)range.hpp $(I)segment.hpp \ 15 | $(I)ua.hpp $(I)xref.hpp algo.hpp algo.cpp 16 | 17 | $(F)colorgen$(O): $(I)bitrange.hpp $(I)bytes.hpp $(I)config.hpp $(I)fpro.h \ 18 | $(I)funcs.hpp $(I)ida.hpp $(I)idp.hpp $(I)kernwin.hpp \ 19 | $(I)lines.hpp $(I)llong.hpp $(I)loader.hpp $(I)nalt.hpp \ 20 | $(I)netnode.hpp $(I)pro.h $(I)range.hpp $(I)segment.hpp \ 21 | $(I)ua.hpp $(I)xref.hpp colorgen.cpp 22 | 23 | $(F)util$(O): $(I)bitrange.hpp $(I)bytes.hpp $(I)config.hpp $(I)fpro.h \ 24 | $(I)funcs.hpp $(I)ida.hpp $(I)idp.hpp $(I)kernwin.hpp \ 25 | $(I)lines.hpp $(I)llong.hpp $(I)loader.hpp $(I)nalt.hpp \ 26 | $(I)netnode.hpp $(I)pro.h $(I)range.hpp $(I)segment.hpp \ 27 | $(I)ua.hpp $(I)xref.hpp util.cpp 28 | 29 | $(F)plugin$(O): $(I)bitrange.hpp $(I)bytes.hpp $(I)config.hpp $(I)fpro.h \ 30 | $(I)funcs.hpp $(I)ida.hpp $(I)idp.hpp $(I)kernwin.hpp \ 31 | $(I)lines.hpp $(I)llong.hpp $(I)loader.hpp $(I)nalt.hpp \ 32 | $(I)netnode.hpp $(I)pro.h $(I)range.hpp $(I)segment.hpp \ 33 | $(I)ua.hpp $(I)xref.hpp plugin.cpp 34 | -------------------------------------------------------------------------------- /presentation/Shattering4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/presentation/Shattering4.pdf -------------------------------------------------------------------------------- /presentation/Shattering4.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nihilus/GraphSlick/138a432d446ac6eab5b87cbdd3ecfa3ebf4bda6b/presentation/Shattering4.pptx -------------------------------------------------------------------------------- /pybbmatcher.cpp: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------------------- 2 | History 3 | -------- 4 | 5 | 11/07/2013 - eliasb - Initial version 6 | 04/15/2014 - eliasb - Check the result of PyAnalyze() before converting the result to C structs 7 | --------------------------------------------------------------------------*/ 8 | 9 | #include "pybbmatcher.h" 10 | #include "pywraps.hpp" 11 | 12 | //-------------------------------------------------------------------------- 13 | // Consts 14 | const char STR_PY_MATCH_MODULE[] = "bb_match"; 15 | 16 | //------------------------------------------------------------------------ 17 | // Helper function to get globals for the __main__ module 18 | // Note: The references are borrowed. No need to free them. 19 | static PyObject *GetMainGlobals() 20 | { 21 | PyObject *module = PyImport_AddModule("__main__"); 22 | return module == NULL ? NULL : PyModule_GetDict(module); 23 | } 24 | 25 | //-------------------------------------------------------------------------- 26 | const char *PyBBMatcher::call_init_file() 27 | { 28 | PYW_GIL_GET; 29 | if (!Py_IsInitialized()) 30 | return "Python is required to run this plugin"; 31 | 32 | static bool init_file_executed = false; 33 | 34 | if (init_file_executed) 35 | return NULL; 36 | 37 | PyW_RunPyFile(init_script); 38 | if (PyErr_Occurred()) 39 | return "Could not run the init script!"; 40 | 41 | init_file_executed = true; 42 | return NULL; 43 | } 44 | 45 | //-------------------------------------------------------------------------- 46 | const char *PyBBMatcher::init() 47 | { 48 | PYW_GIL_GET; 49 | const char *err = call_init_file(); 50 | if (err != NULL) 51 | return err; 52 | 53 | py_matcher_module = PyW_TryImportModule(STR_PY_MATCH_MODULE); 54 | if (py_matcher_module == NULL) 55 | return "BBMatch module is not present"; 56 | 57 | //NOTE: To create an object instance, get a reference to the class 58 | // then call PyObject_CallFunctionObjArgs(py_cls, NULL) 59 | py_instref = PyW_TryGetAttrString(py_matcher_module, "bbMatcher"); 60 | if (py_instref == NULL) 61 | return "BBMatcher instance not present"; 62 | 63 | py_meth_find_similar = PyW_TryGetAttrString(py_instref, "FindSimilar"); 64 | py_meth_save_state = PyW_TryGetAttrString(py_instref, "SaveState"); 65 | py_meth_load_state = PyW_TryGetAttrString(py_instref, "LoadState"); 66 | py_meth_analyze = PyW_TryGetAttrString(py_instref, "Analyze"); 67 | 68 | if ( py_meth_find_similar == NULL 69 | || py_meth_save_state == NULL 70 | || py_meth_load_state == NULL 71 | || py_meth_analyze == NULL) 72 | { 73 | return "Failed to find one or more needed methods"; 74 | } 75 | 76 | return NULL; 77 | } 78 | 79 | //-------------------------------------------------------------------------- 80 | void PyBBMatcher::deinit() 81 | { 82 | if (py_matcher_module != NULL) 83 | { 84 | Py_DECREF(py_matcher_module); 85 | py_matcher_module = NULL; 86 | } 87 | 88 | if (py_instref != NULL) 89 | { 90 | Py_DECREF(py_instref); 91 | py_instref = NULL; 92 | } 93 | 94 | if (py_meth_find_similar != NULL) 95 | { 96 | Py_DECREF(py_meth_find_similar); 97 | py_meth_find_similar = NULL; 98 | } 99 | 100 | if (py_meth_save_state != NULL) 101 | { 102 | Py_DECREF(py_meth_save_state); 103 | py_meth_save_state = NULL; 104 | } 105 | 106 | if (py_meth_load_state != NULL) 107 | { 108 | Py_DECREF(py_meth_load_state); 109 | py_meth_load_state = NULL; 110 | } 111 | 112 | if (py_meth_analyze != NULL) 113 | { 114 | Py_DECREF(py_meth_analyze); 115 | py_meth_analyze = NULL; 116 | } 117 | } 118 | 119 | //-------------------------------------------------------------------------- 120 | void PyBBMatcher::Analyze(ea_t func_addr, int_3dvec_t &result) 121 | { 122 | PYW_GIL_GET; 123 | PyObject *py_func_addr = Py_BuildValue(PY_FMT64, func_addr); 124 | PyObject *py_ret = PyObject_CallFunctionObjArgs(py_meth_analyze, py_func_addr, NULL); 125 | Py_DECREF(py_func_addr); 126 | 127 | if (py_ret != NULL) 128 | PyW_PyListListToIntVecVecVec(py_ret, result); 129 | 130 | Py_XDECREF(py_ret); 131 | } 132 | 133 | //-------------------------------------------------------------------------- 134 | bool PyBBMatcher::FindSimilar(intvec_t &node_list, int_2dvec_t &similar) 135 | { 136 | PYW_GIL_GET; 137 | PyObject *py_nodelist = PyW_IntVecToPyList(node_list); 138 | PyObject *py_ret = PyObject_CallFunctionObjArgs(py_meth_find_similar, py_nodelist, NULL); 139 | Py_DECREF(py_nodelist); 140 | 141 | if (py_ret == NULL) 142 | return false; 143 | 144 | bool bOk = PyW_PyListListToIntVecVec(py_ret, similar) == CIP_OK; 145 | 146 | Py_DECREF(py_ret); 147 | 148 | return bOk; 149 | } 150 | 151 | //-------------------------------------------------------------------------- 152 | bool PyBBMatcher::SaveState(qstring &out) 153 | { 154 | PYW_GIL_GET; 155 | PyObject *py_ret = PyObject_CallFunctionObjArgs(py_meth_save_state, NULL); 156 | if (py_ret == NULL || !PyString_Check(py_ret)) 157 | { 158 | Py_XDECREF(py_ret); 159 | return false; 160 | } 161 | 162 | out = PyString_AsString(py_ret); 163 | 164 | Py_DECREF(py_ret); 165 | 166 | return true; 167 | } 168 | 169 | //-------------------------------------------------------------------------- 170 | bool PyBBMatcher::LoadState(const char *filename) 171 | { 172 | PYW_GIL_GET; 173 | PyObject *py_filename = PyString_FromString(filename); 174 | PyObject *py_ret = PyObject_CallFunctionObjArgs(py_meth_load_state, py_filename, NULL); 175 | Py_DECREF(py_filename); 176 | Py_DECREF(py_ret); 177 | 178 | bool bOk = py_ret == Py_True; 179 | 180 | return bOk; 181 | } -------------------------------------------------------------------------------- /pybbmatcher.h: -------------------------------------------------------------------------------- 1 | #ifndef __PY_BBMATCHER_INC__ 2 | #define __PY_BBMATCHER_INC__ 3 | 4 | /*-------------------------------------------------------------------------- 5 | GraphSlick (c) Elias Bachaalany 6 | ------------------------------------- 7 | 8 | BBMatcher Python to C wrapper class 9 | 10 | --------------------------------------------------------------------------*/ 11 | 12 | #include 13 | #ifdef snprintf 14 | #undef snprintf 15 | #endif 16 | 17 | #include 18 | #include "types.hpp" 19 | 20 | //-------------------------------------------------------------------------- 21 | class PyBBMatcher 22 | { 23 | PyObject *py_matcher_module; 24 | PyObject *py_instref; 25 | PyObject *py_meth_save_state, *py_meth_load_state, *py_meth_analyze, *py_meth_find_similar; 26 | 27 | const char *init_script; 28 | 29 | /** 30 | * @brief Call the supporting init file once 31 | */ 32 | const char *call_init_file(); 33 | 34 | public: 35 | /** 36 | * @brief ctor 37 | */ 38 | PyBBMatcher(const char *init_script): py_matcher_module(NULL), py_instref(NULL), 39 | py_meth_find_similar (NULL), py_meth_save_state(NULL), 40 | py_meth_load_state (NULL), py_meth_analyze (NULL), init_script(init_script) 41 | { 42 | } 43 | 44 | ~PyBBMatcher() 45 | { 46 | deinit(); 47 | } 48 | 49 | /** 50 | * @brief Initialize the needed python references 51 | */ 52 | const char *init(); 53 | 54 | /** 55 | * @brief Release references 56 | */ 57 | void deinit(); 58 | 59 | /** 60 | * @brief Analyze and return the non-overlapping wellformed function instances 61 | */ 62 | void Analyze(ea_t func_addr, int_3dvec_t &result); 63 | 64 | /** 65 | * @brief Load state 66 | */ 67 | bool LoadState(const char *filename); 68 | 69 | /** 70 | * @brief Save state and return it as a string 71 | */ 72 | bool SaveState(qstring &out); 73 | 74 | /** 75 | * @brief Analyze and set the internal state 76 | */ 77 | bool FindSimilar(intvec_t &node_list, int_2dvec_t &similar); 78 | }; 79 | 80 | #endif -------------------------------------------------------------------------------- /pywraps.hpp: -------------------------------------------------------------------------------- 1 | //-------------------------------------------------------------------------- 2 | // Functions taken from code written by Elias Bachaalany for the IDAPython project 3 | // 4 | //-------------------------------------------------------------------------- 5 | 6 | //--------------------------------------------------------------------------- 7 | class gil_lock_t 8 | { 9 | private: 10 | PyGILState_STATE state; 11 | public: 12 | gil_lock_t() 13 | { 14 | state = PyGILState_Ensure(); 15 | } 16 | 17 | ~gil_lock_t() 18 | { 19 | PyGILState_Release(state); 20 | } 21 | }; 22 | 23 | //-------------------------------------------------------------------------- 24 | // Declare a variable to acquire/release the GIL 25 | #define PYW_GIL_GET gil_lock_t _lock; 26 | 27 | //-------------------------------------------------------------------------- 28 | #ifdef __EA64__ 29 | #define PY_FMT64 "K" 30 | #define PY_SFMT64 "L" 31 | #else 32 | #define PY_FMT64 "k" 33 | #define PY_SFMT64 "l" 34 | #endif 35 | 36 | //------------------------------------------------------------------------ 37 | // Constants used by the pyvar_to_idcvar and idcvar_to_pyvar functions 38 | #define CIP_FAILED -1 // Conversion error 39 | #define CIP_IMMUTABLE 0 // Immutable object passed. Will not update the object but no error occured 40 | #define CIP_OK 1 // Success 41 | #define CIP_OK_NODECREF 2 // Success but do not decrement its reference 42 | 43 | //------------------------------------------------------------------------- 44 | // Parses a Python object as a long or long long 45 | bool PyW_GetNumber(PyObject *py_var, uint64 *num, bool *is_64 = NULL) 46 | { 47 | if ( !(PyInt_CheckExact(py_var) || PyLong_CheckExact(py_var)) ) 48 | return false; 49 | 50 | // Can we convert to C long? 51 | long l = PyInt_AsLong(py_var); 52 | if ( !PyErr_Occurred() ) 53 | { 54 | if ( num != NULL ) 55 | *num = uint64(l); 56 | if ( is_64 != NULL ) 57 | *is_64 = false; 58 | return true; 59 | } 60 | 61 | // Clear last error 62 | PyErr_Clear(); 63 | 64 | // Can be fit into a C unsigned long? 65 | unsigned long ul = PyLong_AsUnsignedLong(py_var); 66 | if ( !PyErr_Occurred() ) 67 | { 68 | if ( num != NULL ) 69 | *num = uint64(ul); 70 | if ( is_64 != NULL ) 71 | *is_64 = false; 72 | return true; 73 | } 74 | PyErr_Clear(); 75 | 76 | // Try to parse as int64 77 | PY_LONG_LONG ll = PyLong_AsLongLong(py_var); 78 | if ( !PyErr_Occurred() ) 79 | { 80 | if ( num != NULL ) 81 | *num = uint64(ll); 82 | if ( is_64 != NULL ) 83 | *is_64 = true; 84 | return true; 85 | } 86 | PyErr_Clear(); 87 | 88 | // Try to parse as uint64 89 | unsigned PY_LONG_LONG ull = PyLong_AsUnsignedLongLong(py_var); 90 | PyObject *err = PyErr_Occurred(); 91 | if ( err == NULL ) 92 | { 93 | if ( num != NULL ) 94 | *num = uint64(ull); 95 | if ( is_64 != NULL ) 96 | *is_64 = true; 97 | return true; 98 | } 99 | // Negative number? _And_ it with uint64(-1) 100 | bool ok = false; 101 | if ( err == PyExc_TypeError ) 102 | { 103 | PyObject *py_mask = Py_BuildValue("K", 0xFFFFFFFFFFFFFFFFull); 104 | PyObject *py_num = PyNumber_And(py_var, py_mask); 105 | if ( py_num != NULL && py_mask != NULL ) 106 | { 107 | PyErr_Clear(); 108 | ull = PyLong_AsUnsignedLongLong(py_num); 109 | if ( !PyErr_Occurred() ) 110 | { 111 | if ( num != NULL ) 112 | *num = uint64(ull); 113 | if ( is_64 != NULL ) 114 | *is_64 = true; 115 | ok = true; 116 | } 117 | } 118 | Py_XDECREF(py_num); 119 | Py_XDECREF(py_mask); 120 | } 121 | PyErr_Clear(); 122 | return ok; 123 | } 124 | 125 | //------------------------------------------------------------------------- 126 | // Checks if a given object is of sequence type 127 | bool PyW_IsSequenceType(PyObject *obj) 128 | { 129 | if ( !PySequence_Check(obj) ) 130 | return false; 131 | 132 | Py_ssize_t sz = PySequence_Size(obj); 133 | if ( sz == -1 || PyErr_Occurred() != NULL ) 134 | { 135 | PyErr_Clear(); 136 | return false; 137 | } 138 | return true; 139 | } 140 | 141 | //-------------------------------------------------------------------------- 142 | void PyW_RunPyFile(const char *fn) 143 | { 144 | char *v_fn = qstrdup(fn); 145 | PyObject *py_fp = PyFile_FromString(v_fn, "r"); 146 | FILE *fp = PyFile_AsFile(py_fp); 147 | PyRun_SimpleFile(fp, v_fn); 148 | qfree(v_fn); 149 | } 150 | 151 | //------------------------------------------------------------------------ 152 | // Tries to import a module and clears the exception on failure 153 | PyObject *PyW_TryImportModule(const char *name) 154 | { 155 | PyObject *result = PyImport_ImportModule(name); 156 | if (result != NULL) 157 | return result; 158 | 159 | if (PyErr_Occurred()) 160 | PyErr_Clear(); 161 | 162 | return NULL; 163 | } 164 | 165 | //------------------------------------------------------------------------ 166 | // Returns an attribute or NULL 167 | // No errors will be set if the attribute did not exist 168 | PyObject *PyW_TryGetAttrString(PyObject *py_obj, const char *attr) 169 | { 170 | if ( !PyObject_HasAttrString(py_obj, attr) ) 171 | return NULL; 172 | else 173 | return PyObject_GetAttrString(py_obj, attr); 174 | } 175 | 176 | //------------------------------------------------------------------------- 177 | Py_ssize_t pyvar_walk_list( 178 | PyObject *py_list, 179 | int (*cb)(PyObject *py_item, Py_ssize_t index, void *ud), 180 | void *ud) 181 | { 182 | if ( !PyList_CheckExact(py_list) && !PyW_IsSequenceType(py_list) ) 183 | return CIP_FAILED; 184 | 185 | bool is_seq = !PyList_CheckExact(py_list); 186 | Py_ssize_t size = is_seq ? PySequence_Size(py_list) : PyList_Size(py_list); 187 | 188 | if ( cb == NULL ) 189 | return size; 190 | 191 | Py_ssize_t i; 192 | for ( i=0; isimilar.push_back(lst); 265 | 266 | // Now convert the inner list directly into the vector 267 | PyW_PyListToIntVec(py_item, _this->similar.back()); 268 | 269 | return CIP_OK; 270 | } 271 | }; 272 | cvt_t cvt(result); 273 | return pyvar_walk_list(py_list, cvt_t::cb, &cvt) != CIP_FAILED; 274 | } 275 | 276 | //-------------------------------------------------------------------------- 277 | bool PyW_PyListListToIntVecVecVec(PyObject *py_list, int_3dvec_t &result) 278 | { 279 | class cvt_t 280 | { 281 | private: 282 | int_3dvec_t &v; 283 | public: 284 | cvt_t(int_3dvec_t &v): v(v) 285 | { 286 | } 287 | 288 | static int cb( 289 | PyObject *py_item, 290 | Py_ssize_t index, 291 | void *ud) 292 | { 293 | cvt_t *_this = (cvt_t *)ud; 294 | 295 | // Declare an empty 2D list 296 | int_2dvec_t lst; 297 | 298 | // Push it immediately to the vector (to avoid double copies) 299 | _this->v.push_back(lst); 300 | 301 | // Now convert the inner list directly into the vector 302 | PyW_PyListListToIntVecVec(py_item, _this->v.back()); 303 | 304 | return CIP_OK; 305 | } 306 | }; 307 | cvt_t cvt(result); 308 | return pyvar_walk_list(py_list, cvt_t::cb, &cvt) != CIP_FAILED; 309 | } -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Installation 2 | ============= 3 | 4 | Python 5 | ------- 6 | 1) 7 | 8 | Install SetupTools from: 9 | 10 | https://pypi.python.org/pypi/setuptools 11 | 12 | Unpack and open an administrator prompt 13 | 14 | Run "python.exe ez_setup.py" and let it finish 15 | 16 | 2) 17 | 18 | Download Ordered Set package from: 19 | 20 | https://pypi.python.org/pypi/ordered-set/1.1 21 | 22 | Unpack the package and then run: 23 | 24 | "python.exe setup.py install" 25 | 26 | 27 | IDA 28 | -------- 29 | 30 | Copy the contents of "bin\*" to %IDADIR%. It will deploy the following: 31 | 32 | plugins\GraphSlick.plw 33 | and 34 | plugins\GraphSlick\*.py <- python support files 35 | 36 | 37 | Files 38 | ======== 39 | 40 | bin\ <- contains the binaries 41 | doc\ <- usage document 42 | src\ <- sources 43 | presentation\ <- presentation about how the algorithm works 44 | 45 | Usage 46 | ======== 47 | 48 | Please check doc\usage.doc 49 | 50 | 51 | Contact Info 52 | ============== 53 | Ali Rahbar 54 | Ali Pezeshk 55 | Elias Bachaalany -------------------------------------------------------------------------------- /sample_code/sample_asm/v1/makeit.bat: -------------------------------------------------------------------------------- 1 | rem @echo off 2 | 3 | set FN=v1 4 | 5 | if exist %FN%.obj del %FN%.obj 6 | if exist %FN%.exe del %FN%.exe 7 | 8 | \masm32\bin\ml /c /coff /nologo %FN%.asm 9 | 10 | \masm32\bin\Link /SUBSYSTEM:WINDOWS /BASE:0x401000 /MERGE:.rdata=.text /SECTION:.text,RWX %FN%.obj 11 | 12 | dir %FN%.* 13 | 14 | pause 15 | -------------------------------------------------------------------------------- /sample_code/sample_asm/v1/v1.asm: -------------------------------------------------------------------------------- 1 | ; ######################################################################### 2 | 3 | .386 4 | .model flat, stdcall 5 | option casemap :none ; case sensitive 6 | 7 | ; ######################################################################### 8 | 9 | include \masm32\include\windows.inc 10 | include \masm32\include\user32.inc 11 | include \masm32\include\kernel32.inc 12 | 13 | includelib \masm32\lib\user32.lib 14 | includelib \masm32\lib\kernel32.lib 15 | 16 | ; ######################################################################### 17 | 18 | .code 19 | 20 | 21 | ; ######################################################################### 22 | simple_if_1 proc 23 | 24 | .if eax == 1 25 | mov ebx, 1 26 | .else 27 | mov ebx, 0 28 | .endif 29 | ret 30 | simple_if_1 endp 31 | 32 | 33 | ; ######################################################################### 34 | conseq_block_1 proc 35 | mov eax, 1 36 | jmp L1 37 | L1: 38 | mov eax, 2 39 | jmp L2 40 | L2: 41 | ret 42 | conseq_block_1 endp 43 | 44 | start: 45 | nop 46 | call simple_if_1 47 | nop 48 | call conseq_block_1 49 | nop 50 | invoke ExitProcess, 0 51 | end start -------------------------------------------------------------------------------- /sample_code/sample_c/bin/v1/code1.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | using std::string; 3 | 4 | //-------------------------------------------------------------------------- 5 | int simple_loop1(int a) 6 | { 7 | int sigma=0; 8 | for (int i=0;i 2 | 3 | 4 | 5 | Debug 6 | ARM 7 | 8 | 9 | Debug 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | InlineTest 18 | ARM 19 | 20 | 21 | InlineTest 22 | Win32 23 | 24 | 25 | InlineTest 26 | x64 27 | 28 | 29 | Release 30 | ARM 31 | 32 | 33 | Release 34 | Win32 35 | 36 | 37 | Release 38 | x64 39 | 40 | 41 | 42 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1} 43 | Win32Proj 44 | code1 45 | 46 | 47 | 48 | Application 49 | true 50 | MultiByte 51 | v120 52 | 53 | 54 | Application 55 | true 56 | MultiByte 57 | v120 58 | 59 | 60 | Application 61 | true 62 | MultiByte 63 | v120 64 | 65 | 66 | Application 67 | false 68 | true 69 | MultiByte 70 | v120 71 | 72 | 73 | Application 74 | false 75 | true 76 | MultiByte 77 | v120 78 | 79 | 80 | Application 81 | false 82 | true 83 | MultiByte 84 | v120 85 | 86 | 87 | Application 88 | false 89 | true 90 | MultiByte 91 | v120 92 | 93 | 94 | Application 95 | false 96 | true 97 | MultiByte 98 | v120 99 | 100 | 101 | Application 102 | false 103 | true 104 | MultiByte 105 | v110 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | true 140 | 141 | 142 | true 143 | 144 | 145 | true 146 | 147 | 148 | false 149 | 150 | 151 | false 152 | 153 | 154 | false 155 | 156 | 157 | false 158 | 159 | 160 | false 161 | 162 | 163 | false 164 | 165 | 166 | 167 | Use 168 | Level3 169 | Disabled 170 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 171 | 172 | 173 | Console 174 | true 175 | false 176 | 177 | 178 | 179 | 180 | Use 181 | Level3 182 | Disabled 183 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 184 | 185 | 186 | Console 187 | true 188 | false 189 | 190 | 191 | 192 | 193 | Use 194 | Level3 195 | Disabled 196 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 197 | 198 | 199 | Console 200 | true 201 | false 202 | 203 | 204 | 205 | 206 | Level3 207 | Use 208 | MaxSpeed 209 | true 210 | true 211 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 212 | OnlyExplicitInline 213 | MultiThreaded 214 | 215 | 216 | Console 217 | true 218 | true 219 | true 220 | false 221 | false 222 | 223 | 224 | 225 | 226 | Level3 227 | Use 228 | MaxSpeed 229 | true 230 | true 231 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 232 | OnlyExplicitInline 233 | MultiThreaded 234 | 235 | 236 | Console 237 | true 238 | true 239 | true 240 | false 241 | false 242 | 243 | 244 | 245 | 246 | Level3 247 | Use 248 | MaxSpeed 249 | true 250 | true 251 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 252 | OnlyExplicitInline 253 | MultiThreaded 254 | 255 | 256 | Console 257 | true 258 | true 259 | true 260 | false 261 | false 262 | 263 | 264 | 265 | 266 | Level3 267 | Use 268 | MaxSpeed 269 | false 270 | true 271 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 272 | OnlyExplicitInline 273 | MultiThreaded 274 | false 275 | 276 | 277 | Console 278 | true 279 | true 280 | true 281 | false 282 | false 283 | 284 | 285 | 286 | 287 | Level3 288 | Use 289 | MaxSpeed 290 | false 291 | true 292 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 293 | OnlyExplicitInline 294 | MultiThreaded 295 | false 296 | 297 | 298 | Console 299 | true 300 | true 301 | true 302 | false 303 | false 304 | 305 | 306 | 307 | 308 | Level3 309 | Use 310 | MaxSpeed 311 | false 312 | true 313 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 314 | OnlyExplicitInline 315 | MultiThreaded 316 | false 317 | 318 | 319 | Console 320 | true 321 | true 322 | true 323 | true 324 | false 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | Create 335 | Create 336 | Create 337 | Create 338 | Create 339 | Create 340 | Create 341 | Create 342 | Create 343 | 344 | 345 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /sample_code/sample_c/code1.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | 26 | 27 | Source Files 28 | 29 | 30 | Source Files 31 | 32 | 33 | -------------------------------------------------------------------------------- /sample_code/sample_c/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // asdf.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /sample_code/sample_c/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #pragma warning(disable:4996) 24 | 25 | // TODO: reference additional headers your program requires here 26 | -------------------------------------------------------------------------------- /sample_code/sample_c/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /stdalone.cpp: -------------------------------------------------------------------------------- 1 | #include "groupman.h" 2 | 3 | //-------------------------------------------------------------------------- 4 | int main() 5 | { 6 | groupman_t gm; 7 | 8 | gm.parse("f1.txt"); 9 | gm.emit("f2.txt"); 10 | 11 | nodeloc_t *nl = gm.find_node_loc(0x4012DE); 12 | if (nl != NULL) 13 | { 14 | printf("nid=%d\n", nl->nd->nid); 15 | } 16 | 17 | return 0; 18 | } -------------------------------------------------------------------------------- /stdalone.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stdalone", "stdalone.vcxproj", "{75680022-7B8C-4C67-A0C1-D51DB40B52B1}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|ARM = Debug|ARM 9 | Debug|Win32 = Debug|Win32 10 | Release|ARM = Release|ARM 11 | Release|Win32 = Release|Win32 12 | SR|ARM = SR|ARM 13 | SR|Win32 = SR|Win32 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug|ARM.ActiveCfg = Debug|ARM 17 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug|ARM.Build.0 = Debug|ARM 18 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug|Win32.ActiveCfg = Debug|Win32 19 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Debug|Win32.Build.0 = Debug|Win32 20 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release|ARM.ActiveCfg = Release|ARM 21 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release|ARM.Build.0 = Release|ARM 22 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release|Win32.ActiveCfg = Release|Win32 23 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.Release|Win32.Build.0 = Release|Win32 24 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SR|ARM.ActiveCfg = SR|ARM 25 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SR|ARM.Build.0 = SR|ARM 26 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SR|Win32.ActiveCfg = SR|Win32 27 | {75680022-7B8C-4C67-A0C1-D51DB40B52B1}.SR|Win32.Build.0 = SR|Win32 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | EndGlobal 33 | -------------------------------------------------------------------------------- /types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __TYPES_INC__ 2 | #define __TYPES_INC__ 3 | 4 | /* 5 | GraphSlick (c) Elias Bachaalany 6 | ------------------------------------- 7 | 8 | Plugin module 9 | 10 | This header file contain some type definitions used by various modules 11 | */ 12 | 13 | //-------------------------------------------------------------------------- 14 | #include 15 | 16 | //-------------------------------------------------------------------------- 17 | /** 18 | * @brief Node data class. It will be served from the graph callback 19 | */ 20 | struct gnode_t 21 | { 22 | int id; 23 | qstring text; 24 | qstring hint; 25 | }; 26 | 27 | //-------------------------------------------------------------------------- 28 | typedef qvector int_2dvec_t; 29 | 30 | //-------------------------------------------------------------------------- 31 | typedef qvector int_3dvec_t; 32 | 33 | //-------------------------------------------------------------------------- 34 | #endif -------------------------------------------------------------------------------- /util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include 3 | #include 4 | 5 | /*-------------------------------------------------------------------------- 6 | 7 | History 8 | -------- 9 | 10 | 10/23/2013 - eliasb - First version, it comes from refactored 11 | code from the plugin module 12 | 10/25/2013 - eliasb - Added jump_to_node() 13 | 10/30/2013 - eliasb - moved str2asizet() and skip_spaces() from other modules 14 | 10/31/2013 - eliasb - added 'is_ida_gui()' 15 | --------------------------------------------------------------------------*/ 16 | 17 | //-------------------------------------------------------------------------- 18 | char *skip_spaces(char *p) 19 | { 20 | return skipSpaces(p); 21 | } 22 | 23 | //-------------------------------------------------------------------------- 24 | asize_t str2asizet(const char *str) 25 | { 26 | ea_t v; 27 | qsscanf(str, "%a", &v); 28 | return (asize_t)v; 29 | } 30 | 31 | //-------------------------------------------------------------------------- 32 | const char *get_screen_function_fn(const char *ext) 33 | { 34 | func_t *fnc = get_func(get_screen_ea()); 35 | if (fnc == NULL) 36 | return NULL; 37 | 38 | static char buf[QMAXPATH] = { 0 }; 39 | 40 | // Copy database path global var 41 | set_file_ext(buf, qnumber(buf), database_idb, ""); 42 | size_t t = qstrlen(buf); 43 | if (t > 0 && buf[t - 1] == '.') 44 | buf[t - 1] = '\0'; 45 | 46 | // format as: dir/file/func->startEA . ext 47 | static qstring s; 48 | 49 | s = buf; 50 | s.cat_sprnt("-%08a.%s", fnc->startEA, ext); 51 | 52 | return s.c_str(); 53 | } 54 | 55 | //-------------------------------------------------------------------------- 56 | /** 57 | * @brief Get the disassembly text into a qstring 58 | */ 59 | void get_disasm_text( 60 | ea_t start, 61 | ea_t end, 62 | qstring *out) 63 | { 64 | // Generate disassembly text 65 | text_t txt; 66 | gen_disasm_text( 67 | start, 68 | end, 69 | txt, 70 | false); 71 | 72 | // Append all disasm lines 73 | for (text_t::iterator it=txt.begin(); it != txt.end(); ++it) 74 | { 75 | out->append(it->line); 76 | out->append("\n"); 77 | } 78 | } 79 | 80 | //-------------------------------------------------------------------------- 81 | /** 82 | * @brief Build a function flowchart 83 | */ 84 | bool get_func_flowchart( 85 | ea_t ea, 86 | qflow_chart_t &qf) 87 | { 88 | func_t *f = get_func(ea); 89 | if (f == NULL) 90 | return false; 91 | 92 | qstring s; 93 | s.sprnt("$ flowchart of %a()", f->startEA); 94 | qf.create( 95 | s.c_str(), 96 | f, 97 | BADADDR, 98 | BADADDR, 99 | FC_PREDS); 100 | 101 | return true; 102 | } 103 | 104 | //-------------------------------------------------------------------------- 105 | void jump_to_node(graph_viewer_t *gv, int nid) 106 | { 107 | viewer_center_on(gv, nid); 108 | 109 | int x, y; 110 | 111 | // will return a place only when a node was previously selected 112 | place_t *old_pl = get_custom_viewer_place(gv, false, &x, &y); 113 | if (old_pl == NULL) 114 | return; 115 | 116 | user_graph_place_t *new_pl = (user_graph_place_t *) old_pl->clone(); 117 | new_pl->node = nid; 118 | jumpto(gv, new_pl, x, y); 119 | delete new_pl; 120 | } 121 | 122 | //-------------------------------------------------------------------------- 123 | bool is_ida_gui() 124 | { 125 | return callui(ui_get_hwnd).vptr != NULL || is_idaq(); 126 | } 127 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTIL__ 2 | #define __UTIL__ 3 | 4 | /*-------------------------------------------------------------------------- 5 | GraphSlick (c) Elias Bachaalany 6 | ------------------------------------- 7 | 8 | Util module 9 | 10 | This module implements various utility functions 11 | 12 | --------------------------------------------------------------------------*/ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "types.hpp" 20 | 21 | //-------------------------------------------------------------------------- 22 | /** 23 | * @brief Utility map class to store gnode_t types 24 | */ 25 | class gnodemap_t: public std::map 26 | { 27 | public: 28 | /** 29 | * @brief Add a node to the map 30 | */ 31 | gnode_t *add(int nid) 32 | { 33 | gnode_t *node = &(insert(std::make_pair(nid, gnode_t())).first->second); 34 | return node; 35 | } 36 | 37 | /** 38 | * @brief Return node data 39 | */ 40 | gnode_t *get(int nid) 41 | { 42 | gnodemap_t::iterator it = find(nid); 43 | if ( it == end() ) 44 | return NULL; 45 | else 46 | return &it->second; 47 | } 48 | }; 49 | 50 | //-------------------------------------------------------------------------- 51 | void get_disasm_text( 52 | ea_t start, 53 | ea_t end, 54 | qstring *out); 55 | 56 | bool get_func_flowchart( 57 | ea_t ea, 58 | qflow_chart_t &qf); 59 | 60 | //-------------------------------------------------------------------------- 61 | /** 62 | * @brief Focuses and jumps to the given node id in the graph viewer 63 | */ 64 | void jump_to_node(graph_viewer_t *gv, int nid); 65 | 66 | //-------------------------------------------------------------------------- 67 | /** 68 | * @brief Utility function to convert a string to the 'asize_t' type 69 | It works based on the EA64 define 70 | */ 71 | asize_t str2asizet(const char *str); 72 | 73 | //-------------------------------------------------------------------------- 74 | /** 75 | * @brief Skips white spaces 76 | */ 77 | char *skip_spaces(char *p); 78 | 79 | //-------------------------------------------------------------------------- 80 | /** 81 | * @brief Returns whether a graphical version of IDA is being used 82 | */ 83 | bool is_ida_gui(); 84 | 85 | 86 | //-------------------------------------------------------------------------- 87 | /** 88 | * @brief Returns a file name containing the idbpath and function start EA 89 | */ 90 | const char *get_screen_function_fn(const char *ext = ".bin"); 91 | 92 | #endif --------------------------------------------------------------------------------