├── Example ├── addvmp │ ├── obfuscated │ │ └── addvm.vmp │ ├── source │ │ └── addvm │ └── traces │ │ ├── ida_trace_add.txt │ │ ├── immunity_trace_add.txt │ │ └── olly_trace_add.txt ├── div │ ├── deobfuscated │ │ └── divi.vmp.exe │ ├── source │ │ └── divi.exe │ └── traces │ │ └── ida_trace.txt ├── ida_trace_clear_fib32.txt ├── ida_trace_fib32.txt ├── mulvmp │ ├── obfuscated │ │ └── mul.vmp.exe │ ├── source │ │ └── mul.exe │ └── traces │ │ └── ida_trace_mul.txt └── sub │ ├── deobfuscated │ └── sub.vmp.exe │ ├── source │ └── sub.exe │ └── traces │ └── ida_trace.txt ├── LICENSE ├── README.md ├── VMAttack.py ├── VMAttack_plugin_stub.py ├── dynamic ├── Debugger.py ├── DebuggerHandler.py ├── IDADebugger.py ├── ImmunityDebugger.py ├── OllyDebugger.py ├── TraceRepresentation.py ├── __init__.py └── dynamic_deobfuscate.py ├── lib ├── Instruction.py ├── Logging.py ├── Optimize.py ├── PseudoInstruction.py ├── Register.py ├── StartVal.py ├── TraceAnalysis.py ├── TraceOptimizations.py ├── Util.py ├── VMRepresentation.py ├── VmInstruction.py └── __init__.py ├── screenshots ├── InputOutput1.png ├── InputOutput2.png ├── InputOutput3.png ├── StackChanges.png ├── ab_vm_graph.png ├── clustering1.png ├── dynamic.png ├── generate_trace.png ├── grading.png ├── grading1.png ├── grading2.png ├── grading3.png ├── grading4.png ├── load_trace.png ├── manual_dynamic.png ├── manual_static.png ├── manual_vm_context.png ├── optimizations1.png ├── optimizations2.png ├── optimizations_success.png ├── overview.png ├── settings.png ├── static.png ├── static1.png ├── static2.png ├── stub.png ├── stub2.png └── switch.png ├── setup.py ├── static ├── __init__.py └── static_deobfuscate.py └── ui ├── AboutWindow.py ├── BBGraphViewer.py ├── ClusterViewer.py ├── GradingViewer.py ├── NotifyProgress.py ├── OptimizationViewer.py ├── PluginViewer.py ├── SettingsWindow.py ├── StackChangeViewer.py ├── UIManager.py ├── VMInputOutputViewer.py ├── __init__.py └── legacyUI ├── ClusterViewer.py ├── GradingViewer.py ├── OptimizationViewer.py ├── StackChangeViewer.py ├── VMInputOutputViewer.py └── __init__.py /Example/addvmp/obfuscated/addvm.vmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/Example/addvmp/obfuscated/addvm.vmp -------------------------------------------------------------------------------- /Example/addvmp/source/addvm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/Example/addvmp/source/addvm -------------------------------------------------------------------------------- /Example/div/deobfuscated/divi.vmp.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/Example/div/deobfuscated/divi.vmp.exe -------------------------------------------------------------------------------- /Example/div/source/divi.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/Example/div/source/divi.exe -------------------------------------------------------------------------------- /Example/ida_trace_clear_fib32.txt: -------------------------------------------------------------------------------- 1 | Thread Address Instruction Result 2 | 00000A44 .text:_foo Memory layout changed: 45 segments Memory layout changed: 45 segments 3 | 00000A44 ST0=FFFFFFFFFFFFFFFF ST1=FFFFFFFFFFFFFFFF ST2=FFFFFFFFFFFFFFFF ST3=FFFFFFFFFFFFFFFF ST4=FFFFFFFFFFFFFFFF ST5=FFFFFFFFFFFFFFFF ST6=FFFFFFFFFFFFFFFF ST7=FFFFFFFFFFFFFFFF CTRL=FFFF CS=001B DS=0023 ES=0023 FS=003B GS=0000 SS=0023 EAX=76D71142 EBX=7FFD6000 ECX=00000000 EDX=00401047 ESI=00000000 EDI=00000000 EBP=0022FF94 ESP=0022FF8C EFL=00000246 XMM0= XMM1= XMM2= XMM3= XMM4= XMM5= XMM6= XMM7= MXCSR=FFFFFFFF MM0= MM1= MM2= MM3= MM4= MM5= MM6= MM7= 4 | 00000A44 .text:_foo push ebp ESP=0022FF88 5 | 00000A44 .text:_foo+1 mov ebp, esp EBP=0022FF88 6 | 00000A44 .text:_foo+3 sub esp, 28h ESP=0022FF60 ZF=0 7 | 00000A44 .text:_foo+6 mov [esp+28h+Str], offset __data_end__; \"Hello Pointer\" 8 | 00000A44 .text:_foo+D call _puts ESP=0022FF5C 9 | 00000A44 .text:_puts jmp ds:__imp__puts EAX=00000000 ECX=75E28E8A EDX=77336194 ESP=0022FF60 ZF=1 10 | 00000A44 .text:_foo+12 mov [esp+28h+Str], 3 11 | 00000A44 .text:_foo+19 call _fib ESP=0022FF5C 12 | 00000A44 .text:_fib push ebp ESP=0022FF58 13 | 00000A44 .text:_fib+1 mov ebp, esp EBP=0022FF58 14 | 00000A44 .text:_fib+3 push ebx ESP=0022FF54 15 | 00000A44 .text:_fib+4 sub esp, 14h ESP=0022FF40 PF=0 ZF=0 16 | 00000A44 .text:_fib+7 cmp [ebp+arg_0], 1 17 | 00000A44 .text:_fib+B jnz short loc_401014 18 | 00000A44 .text:_fib:loc_401014 cmp [ebp+arg_0], 2 19 | 00000A44 .text:_fib+18 jnz short loc_401021 20 | 00000A44 .text:_fib:loc_401021 mov eax, [ebp+arg_0] EAX=00000003 21 | 00000A44 .text:_fib+24 sub eax, 1 EAX=00000002 22 | 00000A44 .text:_fib+27 mov [esp+18h+var_18], eax 23 | 00000A44 .text:_fib+2A call _fib ESP=0022FF3C 24 | 00000A44 .text:_fib push ebp ESP=0022FF38 25 | 00000A44 .text:_fib+1 mov ebp, esp EBP=0022FF38 26 | 00000A44 .text:_fib+3 push ebx ESP=0022FF34 27 | 00000A44 .text:_fib+4 sub esp, 14h ESP=0022FF20 28 | 00000A44 .text:_fib+7 cmp [ebp+arg_0], 1 29 | 00000A44 .text:_fib+B jnz short loc_401014 30 | 00000A44 .text:_fib:loc_401014 cmp [ebp+arg_0], 2 PF=1 ZF=1 31 | 00000A44 .text:_fib+18 jnz short loc_401021 32 | 00000A44 .text:_fib+1A mov eax, 1 EAX=00000001 33 | 00000A44 .text:_fib+1F jmp short loc_401041 34 | 00000A44 .text:_fib:loc_401041 add esp, 14h ESP=0022FF34 PF=0 ZF=0 35 | 00000A44 .text:_fib+44 pop ebx ESP=0022FF38 36 | 00000A44 .text:_fib+45 pop ebp EBP=0022FF58 ESP=0022FF3C 37 | 00000A44 .text:_fib+46 retn ESP=0022FF40 38 | 00000A44 .text:_fib+2F mov ebx, eax EBX=00000001 39 | 00000A44 .text:_fib+31 mov eax, [ebp+arg_0] EAX=00000003 40 | 00000A44 .text:_fib+34 sub eax, 2 EAX=00000001 41 | 00000A44 .text:_fib+37 mov [esp+18h+var_18], eax 42 | 00000A44 .text:_fib+3A call _fib ESP=0022FF3C 43 | 00000A44 .text:_fib push ebp ESP=0022FF38 44 | 00000A44 .text:_fib+1 mov ebp, esp EBP=0022FF38 45 | 00000A44 .text:_fib+3 push ebx ESP=0022FF34 46 | 00000A44 .text:_fib+4 sub esp, 14h ESP=0022FF20 47 | 00000A44 .text:_fib+7 cmp [ebp+arg_0], 1 PF=1 ZF=1 48 | 00000A44 .text:_fib+B jnz short loc_401014 49 | 00000A44 .text:_fib+D mov eax, 1 50 | 00000A44 .text:_fib+12 jmp short loc_401041 51 | 00000A44 .text:_fib:loc_401041 add esp, 14h ESP=0022FF34 PF=0 ZF=0 52 | 00000A44 .text:_fib+44 pop ebx ESP=0022FF38 53 | 00000A44 .text:_fib+45 pop ebp EBP=0022FF58 ESP=0022FF3C 54 | 00000A44 .text:_fib+46 retn ESP=0022FF40 55 | 00000A44 .text:_fib+3F add eax, ebx EAX=00000002 56 | 00000A44 .text:_fib:loc_401041 add esp, 14h ESP=0022FF54 57 | 00000A44 .text:_fib+44 pop ebx EBX=7FFD6000 ESP=0022FF58 58 | 00000A44 .text:_fib+45 pop ebp EBP=0022FF88 ESP=0022FF5C 59 | 00000A44 .text:_fib+46 retn ESP=0022FF60 60 | 00000A44 .text:_foo+1E mov [ebp+var_C], eax 61 | 00000A44 .text:_foo+21 mov eax, [ebp+var_C] 62 | 00000A44 .text:_foo+24 mov [esp+28h+var_24], eax 63 | 00000A44 .text:_foo+28 mov [esp+28h+Str], offset Format; \"erg = %i\\n\" 64 | 00000A44 .text:_foo+2F call _printf ESP=0022FF5C 65 | 00000A44 .text:_printf jmp ds:__imp__printf EAX=00000008 ECX=75DCC620 ESP=0022FF60 PF=1 ZF=1 66 | 00000A44 .text:_foo+34 mov [esp+28h+Str], 0; Code 67 | 00000A44 .text:_foo+3B call _exit ESP=0022FF5C -------------------------------------------------------------------------------- /Example/mulvmp/obfuscated/mul.vmp.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/Example/mulvmp/obfuscated/mul.vmp.exe -------------------------------------------------------------------------------- /Example/mulvmp/source/mul.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/Example/mulvmp/source/mul.exe -------------------------------------------------------------------------------- /Example/sub/deobfuscated/sub.vmp.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/Example/sub/deobfuscated/sub.vmp.exe -------------------------------------------------------------------------------- /Example/sub/source/sub.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/Example/sub/source/sub.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 anatolikalysch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /VMAttack.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from lib.Logging import get_log 3 | 4 | __author__ = 'Anatoli Kalysch' 5 | 6 | import ui.SettingsWindow as SettingsWindow 7 | from dynamic.dynamic_deobfuscate import * 8 | from lib.VMRepresentation import * 9 | from static.static_deobfuscate import * 10 | from ui.AboutWindow import AboutWindow 11 | from ui.UIManager import UIManager 12 | 13 | 14 | class VMAttack_Manager(object): 15 | def __init__(self): 16 | self.choice = None 17 | self._vmr = get_vmr() 18 | 19 | # UI Management 20 | self.ui_mgr = UIManager() 21 | self.ui_mgr.get_init_menu() 22 | self.menu_name = "VMAttack" 23 | self.menu_extensions = [] 24 | 25 | ### EVIRONMENT AND INIT ### 26 | @property 27 | def trace(self): 28 | return self.vmr._trace 29 | 30 | @trace.setter 31 | def trace(self, value): 32 | self.vmr._trace = value 33 | 34 | @property 35 | def vmr(self): 36 | self.update_vmr() 37 | return self._vmr 38 | 39 | @property 40 | def dbg_handl(self): 41 | return get_dh(self.choice) 42 | 43 | @dbg_handl.setter 44 | def dbg_handl(self, value): 45 | self.vmr._dbg_handl = value 46 | 47 | @property 48 | def vm_operands(self): 49 | return self.vmr._vm_operands 50 | 51 | @vm_operands.setter 52 | def vm_operands(self, value): 53 | self.vmr._vm_operands = value 54 | 55 | @property 56 | def vm_returns(self): 57 | return self.vmr._vm_returns 58 | 59 | @vm_returns.setter 60 | def vm_returns(self, value): 61 | self.vmr._vm_returns = value 62 | 63 | @property 64 | def vm_ctx(self): 65 | return self.vmr._vm_ctx 66 | 67 | @vm_ctx.setter 68 | def vm_ctx(self, value): 69 | self.vmr._vm_ctx = value 70 | 71 | def select_debugger(self): 72 | c = Choose([], "Choose your preferred debugger:", 1) 73 | c.list = ["Currently selected IDA Debugger", "Bochs Dbg", "Win32 Dbg"] # TODO , "OllyDbg", "Immunity Dbg"] 74 | c.width = 33 75 | # choose() starts counting at 1, not 0 76 | self.choice = c.choose() - 1 77 | if self.choice == 1: 78 | LoadDebugger('Bochs', 0) 79 | elif self.choice == 2: 80 | LoadDebugger('Win32', 0) 81 | 82 | def update_vmr(self): 83 | self._vmr = get_vmr() 84 | 85 | ### UI MANAGEMENT ### 86 | @staticmethod 87 | def show_about(): 88 | AboutWindow().exec_() 89 | 90 | @staticmethod 91 | def show_settings(): 92 | SettingsWindow.Show() 93 | 94 | def show_trace(self): 95 | self.update_vmr() 96 | if self._vmr.trace is not None: 97 | for line in self._vmr.trace: 98 | print line.to_str_line() 99 | 100 | def remove_colors(self): 101 | # reset color 102 | heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA())) 103 | for i in heads: 104 | SetColor(i, CIC_ITEM, 0xFFFFFF) 105 | 106 | def extend_menu(self): 107 | """ 108 | Extends the menu. 109 | """ 110 | try: 111 | self.revert_menu() 112 | menu_path = self.menu_name + "/" 113 | self.ui_mgr.get_init_menu() 114 | self.ui_mgr.add_menu('&'+self.menu_name) 115 | 116 | # debugger selection - will be added after plugin interaction with ollydbg and immunitydbg will be enabled - as of now no additional value is generated compared to the debugger selection in IDA itself so it is commented out 117 | # An alternative to the chooser would be to hook IDAs Debugger selection? 118 | #select_debugger_menu_item = add_menu_item(menu_path, "Select VMAttack Debugger", "", 0, self.select_debugger, None) 119 | # credits & settings 120 | settings_menu_item = add_menu_item(menu_path, "Settings", "", 0, self.show_settings, None) 121 | about_menu_item = add_menu_item(menu_path, "About ...", "", 0, self.show_about, None) 122 | # instruction trace generation and handling 123 | remove_colors_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Remove Colors from Graph", "", 0, self.remove_colors, None) 124 | load_trace_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Load Trace", "", 0, load_trace, None) 125 | save_trace_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Save Trace", "", 0, save_trace, None) 126 | gen_trace_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Generate Trace", "", 0, gen_instruction_trace, (self.choice,)) 127 | show_trace_menu_item = add_menu_item(menu_path + "Instruction Trace/", "Show Trace", "", 0, self.show_trace, None) 128 | 129 | 130 | 131 | ### automation ### 132 | grading_menu_item = add_menu_item(menu_path + 'Automated Analysis/', "Grading System Analysis", "", 0, grading_automaton, None) 133 | automaton_menu_item = add_menu_item(menu_path + 'Automated Analysis/', "Run all analysis capabilities", "", 0, self.automaton, None) 134 | 135 | show_opti_menu_item = add_menu_item(menu_path + "Automated Analysis/Semi Automated (dynamic)/", "Dynamic Trace Optimization", "", 0, optimization_analysis, None) 136 | analyze_addr_trace_menu_item = add_menu_item(menu_path + 'Automated Analysis/Semi Automated (dynamic)/', "Clustering Analysis", "", 0, clustering_analysis, None) 137 | show_input_output = add_menu_item(menu_path + "Automated Analysis/Semi Automated (dynamic)/", "VM Input<=>Ouput Analysis", "", 0, input_output_analysis, None) 138 | 139 | deobfuscate_from_menu_item = add_menu_item(menu_path + "Automated Analysis/Semi Automated (static)/", "Static deobfuscate", "", 0, static_deobfuscate, None) 140 | show_abstract_graph_menu_item = add_menu_item(menu_path + "Automated Analysis/Semi Automated (static)/", "Create Abstract VM-Graph", "", 0, static_deobfuscate, (2,)) 141 | ### manual analysis ### 142 | # vm context related 143 | static_start_search_menu_item = add_menu_item(menu_path + "Manual Analysis/VM Context/", "Find VM Context (static)", "", 0, static_vmctx, (True,)) 144 | find_vm_values_menu_item = add_menu_item(menu_path + "Manual Analysis/VM Context/", "Find VM Context (dynamic)", "", 0, dynamic_vmctx, (True,)) 145 | # static analysis menu items 146 | manual_static_menu_item = add_menu_item(menu_path + "Manual Analysis/Static/", "Deobfuscate from ...", "", 0, static_deobfuscate, (0,True)) 147 | # dynamic analysis menu items 148 | follow_virt_register = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Follow Virtual Register", "", 0, manual_analysis, (3,)) 149 | find_reg_mapping = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Find Virtual Reg to Reg mapping", "", 0, manual_analysis, (2,)) 150 | find_vmfunc_input = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Find VM Function Input Parameter", "", 0, manual_analysis, (1,)) 151 | find_vmfunc_output = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Find VM Function Output Parameter", "", 0, manual_analysis, (0,)) 152 | analyze_count_menu_item = add_menu_item(menu_path + "Manual Analysis/Dynamic/", "Address Count", "", 0, address_heuristic, None) 153 | #manual_input_output = add_menu_item(menu_path + "Manual Analysis/Dynamic/", " Run Input<=>Ouput Analysis on Function", "", 0, input_output_analysis, (True,)) 154 | 155 | 156 | self.menu_extensions.append(deobfuscate_from_menu_item) 157 | self.menu_extensions.append(settings_menu_item) 158 | #self.menu_extensions.append(select_debugger_menu_item) 159 | self.menu_extensions.append(load_trace_menu_item) 160 | self.menu_extensions.append(save_trace_menu_item) 161 | self.menu_extensions.append(gen_trace_menu_item) 162 | self.menu_extensions.append(analyze_count_menu_item) 163 | self.menu_extensions.append(analyze_addr_trace_menu_item) 164 | self.menu_extensions.append(static_start_search_menu_item) 165 | self.menu_extensions.append(find_vm_values_menu_item) 166 | self.menu_extensions.append(automaton_menu_item) 167 | self.menu_extensions.append(show_abstract_graph_menu_item) 168 | self.menu_extensions.append(find_vmfunc_input) 169 | self.menu_extensions.append(find_vmfunc_output) 170 | self.menu_extensions.append(manual_static_menu_item) 171 | self.menu_extensions.append(find_reg_mapping) 172 | self.menu_extensions.append(follow_virt_register) 173 | self.menu_extensions.append(show_input_output) 174 | self.menu_extensions.append(show_trace_menu_item) 175 | self.menu_extensions.append(about_menu_item) 176 | self.menu_extensions.append(show_opti_menu_item) 177 | self.menu_extensions.append(grading_menu_item) 178 | #self.menu_extensions.append(manual_input_output) 179 | self.menu_extensions.append(remove_colors_menu_item) 180 | 181 | 182 | except Exception, e: 183 | print "[*] Menu could not be added! Following Error occurred:\n %s" % e.message 184 | 185 | def revert_menu(self): 186 | for i in self.menu_extensions: 187 | del_menu_item(i) 188 | self.ui_mgr.clear() 189 | 190 | def welcome(self): 191 | msg("\n\ 192 | ..........llllllllllllllllll..llllllll......llllll.......llllll..........\n\ 193 | ..........llllllllllllllllll..lllllllll.....llllll.......llllll..........\n\ 194 | ..........llllll.............lllll.lllll....llllll.......llllll..........\n\ 195 | ..........llllll............,lllll.lllll,...llllll.......llllll..........\n\ 196 | ..........llllll............llllll.llllll...llllll.......llllll..........\n\ 197 | ..........lllllllllllllllllllllll...lllll...llllll.......llllll..........\n\ 198 | ..........lllllllllllllllllllllll...llllll..llllll.......llllll..........\n\ 199 | ..........llllllllllllllllllllll.....lllll..llllll.......llllll..........\n\ 200 | ..........llllll..........lllllllllllllllll.llllll.......llllll..........\n\ 201 | ..........llllll.........llllllllllllllllll.llllll.......llllll..........\n\ 202 | ..........llllll........lllllllllllllllllllllllllll......llllll..........\n\ 203 | ..........llllll........llllll.........lllllllllllllllllllllll...........\n\ 204 | ..........llllll.......lllllll.........lllllll.lllllllllllll.............\n\ 205 | ..........llllll......lllllll...........lllllll.lllllllllll..............\n\ 206 | ............Friedrich-Alexander University Erlangen-Nuremberg............\n\ 207 | ") 208 | 209 | def reset_grade(self, trace): 210 | for line in trace: 211 | line.grade = 0 212 | 213 | return trace 214 | 215 | def grade(self, trace, excerpt): 216 | for line in excerpt: 217 | trace[trace.index(line)].raise_grade() 218 | 219 | return trace 220 | 221 | # automaton 222 | def automaton(self): 223 | trace = prepare_trace() 224 | self.reset_grade(trace) 225 | # load current IDA-Debugger 226 | if self.dbg_handl.dbg.module_name is "NoDbg": 227 | self.dbg_handl = self.select_debugger() 228 | 229 | # instruction trace 230 | if trace is None: 231 | try: 232 | trace = self.dbg_handl.gen_instruction_trace() 233 | except: 234 | self._vmr.trace = prepare_trace() 235 | # run all trace analysis functions and present the results 236 | dynamic_vmctx() 237 | 238 | # afterwards if the VM context was found run the static analysis automatically since it depends on the VM context 239 | try: 240 | self.update_vmr() 241 | deobfuscate(self._vmr.code_start, self._vmr.base_addr, self._vmr.code_end, self._vmr.vm_addr) 242 | except Exception, e: 243 | try: 244 | static_vmctx() 245 | self.update_vmr() 246 | deobfuscate(self._vmr.code_start, self._vmr.base_addr, self._vmr.code_end, self._vmr.vm_addr) 247 | except Exception, ex: 248 | msg("[*] Could not provide static deobfuscation analysis! The following errors occured:\n %s \n %s" % ( 249 | e.message, ex.message)) 250 | 251 | 252 | # run the dynamic analysis capabilities of the plugin -> each analysis increases a special trace lines grade which will be evaluated at the end of the analysis 253 | # input / output 254 | try: 255 | input_output_analysis() 256 | except Exception, e: 257 | print '[*] Exception occured while running Input/Output analysis!\n %s' % e.message 258 | # clustering 259 | try: 260 | clustering_analysis() 261 | except Exception, e: 262 | print '[*] Exception occured while running Clustering analysis!\n %s' % e.message 263 | # optimizations 264 | try: 265 | optimization_analysis() 266 | except Exception, e: 267 | print '[*] Exception occured while running optimization analysis!\n %s' % e.message 268 | # grade the trace line 269 | try: 270 | grading_automaton() 271 | except Exception, e: 272 | print '[*] Exception occured while running grading analysis!\n %s' % e.message 273 | 274 | 275 | # Virtualization obfuscated interpretation 276 | class VMAttack(plugin_t): 277 | flags = PLUGIN_PROC 278 | comment = "This Framework is supposed to help with the analysis of virtualization obfuscated binaries." 279 | help = "HELP!" 280 | wanted_name = "VMAttack" 281 | wanted_hotkey = "" 282 | 283 | def init(self): 284 | self.vma_mgr = None 285 | try: 286 | self.vma_mgr = get_mgr() 287 | self.vma_mgr.extend_menu() 288 | #self.vma_mgr.welcome() 289 | msg('[*] Starting VMAttack plugin...\n') 290 | get_log().log('[VMA] Starting VMAttack and initiating variables ...\n') 291 | return PLUGIN_KEEP 292 | 293 | except Exception, e: 294 | msg("[*] Failed to initialize VMAttack.\n %s\n" % e.message) 295 | if self.vma_mgr is not None: 296 | self.vma_mgr.revert_menu() 297 | del self.vma_mgr 298 | return PLUGIN_SKIP 299 | 300 | def run(self, arg): 301 | try: 302 | self.vma_mgr = get_mgr() 303 | self.vma_mgr.extend_menu() 304 | #self.vma_mgr.welcome() 305 | msg('[*] Reloading VMAttack plugin...\n') 306 | add_menu_item('Edit/Plugins/', 'Load VMAttack', None, 0, self.vma_mgr.extend_menu, ()) 307 | except Exception,e: 308 | msg("[*] Failed to initialize VMAttack.\n %s\n" % e.message) 309 | msg(e.args) 310 | 311 | def term(self): 312 | if self.vma_mgr is not None: 313 | get_log().finalize() 314 | self.vma_mgr.revert_menu() 315 | del_vmr() 316 | del self 317 | 318 | def PLUGIN_ENTRY(): 319 | return VMAttack() 320 | 321 | 322 | # Singelton VMA MGR 323 | vma_mgr = None 324 | 325 | def get_mgr(): 326 | global vma_mgr 327 | if vma_mgr is None: 328 | vma_mgr = VMAttack_Manager() 329 | return vma_mgr 330 | -------------------------------------------------------------------------------- /VMAttack_plugin_stub.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | import imp 5 | import sys 6 | import os 7 | 8 | 9 | F_DIR = os.environ["VMAttack"] 10 | F_NAME = "VMAttack.py" 11 | sys.path.append(F_DIR) 12 | 13 | plugin_path = os.path.join(F_DIR, F_NAME) 14 | plugin = imp.load_source(__name__, plugin_path) 15 | PLUGIN_ENTRY = plugin.PLUGIN_ENTRY -------------------------------------------------------------------------------- /dynamic/Debugger.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | 5 | class Debugger(object): 6 | def __init__(self): 7 | self.registers = [] 8 | self.hooked = False 9 | self.stack = [] 10 | self.binary = None 11 | self._module_name = "NoDBG" 12 | self.error_msg = "Please specify a Debugger!" 13 | 14 | @property 15 | def module_name(self): 16 | return self._module_name 17 | 18 | def set_breakpoint(self, address): 19 | return self.error_msg 20 | 21 | def remove_breakpoint(self, address): 22 | return self.error_msg 23 | 24 | def single_step(self): 25 | return self.error_msg 26 | 27 | def hook_dbg(self): 28 | return self.error_msg 29 | 30 | def unhook_dbg(self): 31 | return self.error_msg 32 | 33 | def gen_trace(self, trace_start, trace_end): 34 | return self.error_msg 35 | 36 | def get_env_context(self): 37 | return self.error_msg 38 | 39 | def set_env_context(self, ctx): 40 | return self.error_msg 41 | -------------------------------------------------------------------------------- /dynamic/DebuggerHandler.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from dynamic.TraceRepresentation import Trace, Traceline 3 | from lib.Logging import get_log 4 | 5 | __author__ = 'Anatoli Kalysch' 6 | 7 | 8 | import json 9 | 10 | from collections import defaultdict 11 | from copy import deepcopy 12 | from lib.Util import remove_all_colors 13 | from ui.UIManager import QtGui 14 | from IDADebugger import IDADebugger 15 | 16 | from idautils import * 17 | from idaapi import * 18 | from idc import * 19 | 20 | class DebuggerHandler(object): 21 | def __init__(self, func=None): 22 | self.dbg = None 23 | # if function for loading the Debugger is given execute it 24 | if func is not None: 25 | self.load_dbg = types.MethodType(func, self) 26 | self.dbg = self.load_dbg() 27 | else: 28 | self.dbg = IDADebugger() 29 | 30 | if dbg_get_name() is None: 31 | get_log().log('[DBG] Debugger name was none so loaded default Windows32 debugger\n') 32 | LoadDebugger('Win32', 0) 33 | 34 | 35 | @property 36 | def check(self): 37 | return self.dbg is not None 38 | 39 | @property 40 | def hooked(self): 41 | return self.dbg.hooked 42 | 43 | @property 44 | def trace(self): 45 | return self._trace 46 | 47 | @trace.setter 48 | def trace(self, value): 49 | assert isinstance(value, Trace) 50 | self._trace = value 51 | 52 | def switch_debugger(self, func): 53 | if func is None: 54 | get_log().log('[DBG] Instantiation function was empty so no debugger chosen\n') 55 | raise Exception('[*] empty function! Cannot instantiate Debugger!') 56 | 57 | self.load_dbg = types.MethodType(func, self) 58 | self.dbg = self.load_dbg() 59 | self.dbg.hook_dbg() 60 | 61 | def gen_instruction_trace(self, start=BeginEA(), end=BADADDR): 62 | self._trace = Trace() 63 | if not self.check: 64 | self.dbg = self.load_dbg() 65 | self.dbg.hook_dbg() 66 | remove_all_colors() 67 | trace = self.dbg.gen_trace(start, end) 68 | self.dbg.unhook_dbg() 69 | return trace 70 | 71 | 72 | def ida_offset(string): 73 | """ 74 | Converts non-IDA conforming offset representation to a more IDAesk form. 75 | :param string: a non IDA conform string 76 | :return: IDA conform string 77 | """ 78 | segment, rest = string.split(':', 2) 79 | offset_start = rest.rfind('+') 80 | offset = rest[offset_start + 1:-1] 81 | operands = rest[1:offset_start] 82 | 83 | # ds:off_40439c[eax * 4] 84 | return '%s:off_%s[%s]' % (segment, offset, operands) 85 | 86 | 87 | def load(): 88 | """ 89 | Load a trace from file. Supported are IDAs txt trace files and VMAttacks json files. Further OllyDBG and ImmunityDBG traces are supported but have slightly limited analysis capabilities. 90 | :param path: system path to trace file 91 | :return: trace object 92 | """ 93 | path = '' 94 | try: 95 | fd = QtGui.QFileDialog() 96 | fd.setFileMode(QtGui.QFileDialog.AnyFile) 97 | fd.setFilters(["Text files (*.txt)", "JSON files (*.json)"]) 98 | fd.setWindowTitle('Load Trace ...') 99 | if fd.exec_(): 100 | path = fd.selectedFiles()[0] 101 | else: 102 | path = None 103 | except: 104 | msg('A Problem occured with the file selector dialog, first *.txt file in the current working directory was choosen!') 105 | for f in os.listdir(os.getcwd()): 106 | if f.endswith('txt'): 107 | path = f 108 | if path == '': 109 | path = asktext(40, '', 'Please provide the full path to the trace file: ') 110 | 111 | if path is not None: 112 | get_log().log('[TRC] Loaded the trace at %s\n' % path) 113 | if path.endswith('.txt'): 114 | with open(path, 'r') as f: 115 | lines = f.readlines() 116 | elif path.endswith('.json'): 117 | with open(path) as f: 118 | lines = json.load(f) 119 | else: 120 | return None 121 | trace = Trace() 122 | 123 | functions = {SegName(addr): {GetFunctionName(ea): ea for ea in Functions(SegStart(addr), SegEnd(addr))} for addr in Segments()} 124 | 125 | try: 126 | context = defaultdict(lambda: False) 127 | 128 | # framework json trace 129 | if isinstance(lines, dict) or path.endswith('.json'): 130 | get_log().log('[TRC] The trace seems to be a VMAttack trace\n') 131 | for index in range(len(lines.keys())): 132 | line = lines[str(index)] 133 | t = Traceline(thread_id=line[0], addr=line[1], disasm=line[2], ctx=line[3], comment=line[4]) 134 | t.grade = line[5] 135 | trace.append(t) 136 | 137 | # ida trace via Win32Dbg 138 | elif lines[0].startswith('Thread '): 139 | for i in lines[3:]: 140 | if i.startswith('Thread'): 141 | break 142 | values = i.split('\t') 143 | # thread id 144 | thread_id = int(values[0], 16) 145 | 146 | # addr 147 | addr = BADADDR 148 | func_name = values[1].strip(' ').split(':') 149 | if len(func_name) == 2: 150 | try: # .segment:addr 151 | addr = int(func_name[1], 16) 152 | except: 153 | try: # .segment:func_name+offset 154 | offset = int(func_name[1].split('+')[1], 16) 155 | name = func_name[1].split('+')[0] 156 | addr = functions[func_name[0]][name] + offset 157 | except: 158 | try: # .segment:func_name-offset 159 | offset = int(i.split('-')[1].split(' ')[0], 16) 160 | name = func_name[1].split('-')[0] 161 | addr = functions[func_name[0]][name] - offset 162 | except: 163 | if not func_name[1].startswith('loc_'): # .segment:func_name 164 | addr = functions[func_name[0]][func_name[1]] 165 | else: # .segment:jmp_location 166 | addr = int(func_name[1][4:], 16) 167 | elif len(func_name) == 3: 168 | addr = int(func_name[2][4:], 16) 169 | 170 | # disasm 171 | disasm = values[2].strip(' ').lower() 172 | disasm = disasm.split(' ') 173 | disasm = [x.lstrip() for x in disasm] 174 | disasm = filter(None, disasm) 175 | if len(disasm) > 1 and disasm[1].__contains__(', '): 176 | temp = disasm.pop(1) 177 | for elem in temp.split(', '): 178 | disasm.append(elem.lstrip().lstrip('0').rstrip('h')) 179 | 180 | # remove [ebp+0] 181 | for dis in disasm: 182 | if dis.__contains__('[ebp+0]'): 183 | dis.replace('[ebp+0]', '[ebp]') 184 | 185 | # context 186 | ida_ctx = values[3].strip(' ').split(' ') 187 | for value in ida_ctx: 188 | try: 189 | a, b = value.split('=') 190 | if len(b) > 1: 191 | b = ''.join(c.rstrip('\r\n') for c in b.lstrip('0')) 192 | if b == '': 193 | b = '0' 194 | context[a.lower()] = b 195 | except: 196 | pass 197 | 198 | trace.append(Traceline(thread_id=thread_id, addr=addr, disasm=disasm, ctx=deepcopy(context))) 199 | # immunity trace 200 | elif lines[0].startswith('Address '): 201 | for i in lines[1:]: 202 | if i.__contains__('Run trace closed') or i.__contains__('Process terminated'): 203 | break 204 | values = i.split('\t') 205 | try: 206 | # thread_id 207 | thread_id = sum(ord(c) for c in values[1]) # immunity uses names, e.g. main 208 | # addr 209 | try: 210 | addr = int(values[0], 16) 211 | except: 212 | addr = BADADDR 213 | # disasm 214 | disasm = values[2].lower().rstrip('\r\n') 215 | disasm = disasm.split(' ', 1) 216 | if len(disasm) > 1 and disasm[1].__contains__(','): 217 | temp = disasm.pop(1) 218 | for elem in temp.split(','): 219 | disasm.append(elem.lstrip('0')) 220 | disasm = [x.split('dword ptr ')[1] if x.__contains__('dword ptr ') else x for x in disasm] 221 | if len(disasm) == 2 and len(re.findall(r'.*\[.*[\+\-\*].*[\+\-\*].*\].*', disasm[1])) > 0: 222 | disasm[1] = ida_offset(disasm[1]) 223 | # context 224 | if len(values) > 3: 225 | olly_ctx = values[3].lstrip(' ').rstrip('\r\n').split(',') 226 | for value in olly_ctx: 227 | try: 228 | a, b = value.split('=') 229 | if len(b) > 1: 230 | b = ''.join(c for c in b.lstrip('0') if c not in '\n\r\t') 231 | if b == '': 232 | b = '0' 233 | context[a.lower()] = b 234 | except: 235 | pass 236 | trace.append(Traceline(thread_id=thread_id, addr=addr, disasm=disasm, ctx=deepcopy(context))) 237 | except: 238 | if i.__contains__('terminated') or i.__contains__('entry point'): 239 | pass 240 | 241 | # olly trace 242 | elif lines[1].startswith('main '): 243 | for i in lines[1:]: 244 | if i.__contains__('Logging stopped'): 245 | break 246 | values = i.split('\t') 247 | # thread_id 248 | thread_id = sum(ord(c) for c in values[0]) # olly uses names, e.g. main 249 | # addr 250 | try: 251 | addr = int(values[1], 16) 252 | except: 253 | addr = BADADDR 254 | # disasm 255 | disasm = values[2].lower().rstrip('\r\n') 256 | disasm = disasm.split(' ', 1) 257 | if len(disasm) > 1 and disasm[1].__contains__(','): 258 | temp = disasm.pop(1) 259 | for elem in temp.split(','): 260 | disasm.append(elem.lstrip('0')) 261 | 262 | disasm = [x.split('dword ptr ')[1] if x.__contains__('dword ptr ') else x for x in disasm] 263 | if len(disasm) == 2 and len(re.findall(r'.*\[.*[\+\-\*].*[\+\-\*].*\].*', disasm[1])) > 0: 264 | disasm[1] = ida_offset(disasm[1]) 265 | # context 266 | if len(values) > 3: 267 | olly_ctx = values[3].lstrip(' ').rstrip('\r\n').split(',') 268 | for value in olly_ctx: 269 | try: 270 | a, b = value.split('=') 271 | if len(b) > 1: 272 | b = ''.join(c for c in b.lstrip('0') if c not in '\n\r\t') 273 | if b == '': 274 | b = '0' 275 | context[a.lower()] = b 276 | except: 277 | pass 278 | trace.append(Traceline(thread_id=thread_id, addr=addr, disasm=disasm, ctx=deepcopy(context))) 279 | 280 | 281 | if 'rax' in trace[-1].ctx.keys(): 282 | trace.ctx_reg_size = 64 283 | elif 'eax' in trace[-1].ctx.keys() and 'rax' not in trace[-1].ctx.keys(): 284 | trace.ctx_reg_size = 32 285 | msg("[*] Trace Loaded!\n") 286 | return trace 287 | except Exception, e: 288 | raise Exception('[*] Exception occured: \n%s\n' % (e.message)) 289 | else: 290 | return None 291 | 292 | 293 | def save(trace): 294 | try: 295 | fd = QtGui.QFileDialog() 296 | fd.setFileMode(QtGui.QFileDialog.AnyFile) 297 | fd.setFilter('JSON Files (*.json)') 298 | fd.setWindowTitle('Save Trace ...') 299 | if fd.exec_(): 300 | path = fd.selectedFiles()[0] 301 | else: 302 | path = None 303 | except: 304 | path = os.getcwd() + get_root_filename() + '_trace_%s.json' % time.time() 305 | 306 | if path is not None: 307 | if path.endswith('.json'): 308 | path = path[:-5] 309 | with open(path + '.json', 'w') as f: 310 | if trace: 311 | obj = {i:['%x' % trace[i].thread_id, 312 | '%x' % trace[i].addr, 313 | trace[i].disasm, 314 | trace[i].ctx, 315 | trace[i].comment, 316 | trace[i].grade] for i in range(len(trace))} 317 | f.write(json.dumps(obj)) 318 | msg('[*] Trace saved!\n') 319 | else: 320 | raise Exception("[*] Trace seems to be None:\n %s" % trace) 321 | 322 | # Singelton DebuggerHandler 323 | dbg_handl = None 324 | 325 | def get_dh(choice=None): 326 | global dbg_handl 327 | if dbg_handl is None: 328 | dbg_handl = DebuggerHandler(choice) 329 | return dbg_handl -------------------------------------------------------------------------------- /dynamic/IDADebugger.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from copy import deepcopy 3 | 4 | from lib.VMRepresentation import get_vmr 5 | 6 | __author__ = 'Anatoli Kalysch' 7 | 8 | from Debugger import Debugger 9 | from dynamic.TraceRepresentation import Trace, Traceline 10 | from idaapi import * 11 | from lib.Util import * 12 | from _collections import defaultdict 13 | from lib.Util import get_reg_class, get_reg 14 | 15 | 16 | class IDADebugger(DBG_Hooks, Debugger): 17 | def __init__(self, *args): 18 | super(IDADebugger, self).__init__(*args) 19 | self.hooked = False 20 | self.trace = Trace() 21 | self._module_name = 'IDADbg' 22 | self.arch = get_arch_dynamic() 23 | # init the cpu context with 0 24 | if self.arch == 32: 25 | self.ctx = {c: '0' for c in ['eax', 'ebx', 'edx', 'ecx', 'ebp', 'esp', 'eip', 'edi', 'esi', 'cf', 'zf', 'sf', 'of', 'pf', 26 | 'af', 'tf', 'df']} 27 | elif self.arch == 64: 28 | self.ctx = {c: '0' for c in ['rax', 'rbx', 'rdx', 'rcx', 'rbp', 'rsp', 'rip', 'edi', 'rsi', 'r8', 'r9', 'r10', 'r11', 'r12', 29 | 'r13', 'r14', 'r15', 'cf', 'zf', 'sf', 'of', 'pf', 'af', 'tf', 'df']} 30 | 31 | self.IAT = [] 32 | self.func_args = defaultdict(lambda: set()) 33 | 34 | @property 35 | def module_name(self): 36 | return self._module_name 37 | 38 | def convert(self, value): 39 | """ 40 | Convert a value into its hex representation. 41 | :param value: 42 | :return: 43 | """ 44 | result = '%x' % int(value) 45 | return result.upper() 46 | 47 | def disconv(self, value): 48 | """ 49 | Convert the DISASM to a standardized representation. This enables the equivalence between generated traces and loaded traces. 50 | :param value: a disasm str 51 | :return: standardized str 52 | """ 53 | # disregard comments 54 | if value.__contains__(';'): 55 | value = value.split(';')[0] 56 | disasm = value.lower().split(' ') 57 | 58 | 59 | disasm = [x.lstrip() for x in disasm] 60 | disasm = filter(None, disasm) 61 | if len(disasm) > 1 and disasm[1].__contains__(', '): 62 | temp = disasm.pop(1) 63 | for elem in temp.split(', '): 64 | disasm.append(elem.lstrip().lstrip('0').rstrip('h')) 65 | 66 | return disasm 67 | 68 | def trace_init(self): 69 | """ 70 | Init the trace. 71 | """ 72 | if self.arch is None: 73 | self.arch == get_arch_dynamic() 74 | # reset trace 75 | self.trace = Trace(reg_size=self.arch) 76 | 77 | def hook_dbg(self): 78 | if self.hooked: # Release any current hooks 79 | self.unhook() 80 | 81 | try: 82 | # check if ida dbg is present and ready 83 | if not dbg_can_query(): 84 | return 85 | 86 | # hook IDADebugger 87 | self.hook() 88 | self.hooked = True 89 | self.arch = get_arch_dynamic() 90 | 91 | except Exception as ex: 92 | print "An Exception was encountered: %s" % ex.message 93 | 94 | def get_new_color(self, current_color): 95 | """ 96 | Redistribute a new color to a line. 97 | :param current_color: the current color of a line 98 | :return: the next or the max color 99 | """ 100 | colors = [0xffe699, 0xffcc33, 0xe6ac00, 0xb38600] 101 | try: 102 | index = colors.index(current_color) 103 | if index == len(colors) - 1: 104 | return colors[-1] 105 | else: 106 | return colors[index + 1] 107 | except ValueError: 108 | return colors[0] 109 | 110 | # TODO IAT checks 111 | def gen_trace(self, trace_start=BeginEA(), trace_end=BADADDR): 112 | """ 113 | Generate trace for the loaded binary. 114 | :param trace_start: 115 | :param trace_end: 116 | :return: 117 | """ 118 | vmr = get_vmr() 119 | self.trace_init() 120 | # reset color 121 | heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA())) 122 | for i in heads: 123 | SetColor(i, CIC_ITEM, 0xFFFFFF) 124 | # start exec 125 | RunTo(BeginEA()) 126 | event = GetDebuggerEvent(WFNE_SUSP, -1) 127 | # enable tracing 128 | EnableTracing(TRACE_STEP, 1) 129 | if vmr.sys_libs: 130 | pass 131 | event = GetDebuggerEvent(WFNE_ANY | WFNE_CONT, -1) 132 | while True: 133 | event = GetDebuggerEvent(WFNE_ANY, -1) 134 | addr = GetEventEa() 135 | 136 | # change color of executed line 137 | current_color = GetColor(addr, CIC_ITEM) 138 | new_color = self.get_new_color(current_color) 139 | SetColor(addr, CIC_ITEM, new_color) 140 | # break by exception 141 | if event <= 1: 142 | break 143 | 144 | # standardize the difference between ida_trace.txt files and generated trace files by debugger hook: 145 | # since dbg_trace returns the cpu context before the instruction execution and trace files the ctx after 146 | for line in self.trace: 147 | try: 148 | line.ctx = self.trace[self.trace.index(line) + 1].ctx 149 | except IndexError: 150 | line.ctx = defaultdict(lambda: '0') 151 | # return the trace, for population see dbg_trace() below 152 | msg('[*] Trace generated!\n') 153 | if vmr.extract_param: 154 | vmr.func_args = self.func_args 155 | for key in self.func_args.keys(): 156 | print 'Function %s call args:' % key, ''.join('%s, ' % arg for arg in self.func_args[key]).rstrip(', ') 157 | return self.trace 158 | 159 | def unhook_dbg(self): 160 | if self.hooked: 161 | # unhook IDADebugger 162 | self.unhook() 163 | self.hooked = False 164 | else: 165 | pass 166 | 167 | def dbg_process_start(self, pid, tid, ea, name, base, size): 168 | # print("Process started, pid=%d tid=%d name=%s" % (pid, tid, name)) 169 | pass 170 | 171 | def dbg_process_exit(self, pid, tid, ea, code): 172 | # print("Process exited pid=%d tid=%d ea=0x%x code=%d" % (pid, tid, ea, code)) 173 | pass 174 | 175 | def dbg_library_unload(self, pid, tid, ea, info): 176 | print("Library unloaded: pid=%d tid=%d ea=0x%x info=%s" % (pid, tid, ea, info)) 177 | return 0 178 | 179 | def dbg_process_attach(self, pid, tid, ea, name, base, size): 180 | # print("Process attach pid=%d tid=%d ea=0x%x name=%s base=%x size=%x" % (pid, tid, ea, name, base, size)) 181 | pass 182 | 183 | def dbg_process_detach(self, pid, tid, ea): 184 | # print("Process detached, pid=%d tid=%d ea=0x%x" % (pid, tid, ea)) 185 | return 0 186 | 187 | def dbg_library_load(self, pid, tid, ea, name, base, size): 188 | print "Library loaded: pid=%d tid=%d name=%s base=%x" % (pid, tid, name, base) 189 | 190 | 191 | def dbg_bpt(self, tid, ea): 192 | # print "Break point at 0x%x pid=%d" % (ea, tid) 193 | # self.tid = tid 194 | # return values: 195 | # -1 - to display a breakpoint warning dialog 196 | # if the process is suspended. 197 | # 0 - to never display a breakpoint warning dialog. 198 | # 1 - to always display a breakpoint warning dialog. 199 | return 0 200 | 201 | def dbg_suspend_process(self): 202 | # print "Process suspended" 203 | pass 204 | 205 | def dbg_exception(self, pid, tid, ea, exc_code, exc_can_cont, exc_ea, exc_info): 206 | print("Exception: pid=%d tid=%d ea=0x%x exc_code=0x%x can_continue=%d exc_ea=0x%x exc_info=%s" % (pid, tid, ea, exc_code & BADADDR, exc_can_cont, exc_ea, exc_info)) 207 | # return values: 208 | # -1 - to display an exception warning dialog 209 | # if the process is suspended. 210 | # 0 - to never display an exception warning dialog. 211 | # 1 - to always display an exception warning dialog. 212 | return 0 213 | 214 | def dbg_trace(self, tid, ea): 215 | """ 216 | 217 | :param tid: 218 | :param ea: 219 | :return: 220 | """ 221 | vmr = get_vmr() 222 | try: 223 | if vmr.extract_param and GetDisasm(ea).__contains__('call'): 224 | run_var = 0 225 | key = GetDisasm(ea).split('call')[1].strip() 226 | while True: 227 | # traverse trace backwards and get sequential push and mov params 228 | line = self.trace[-(run_var + 1)] 229 | if line.is_push and line.disasm_len == 2: 230 | try: 231 | self.func_args[key].add(line.ctx[get_reg(line.disasm[1], self.arch)]) 232 | except: 233 | self.func_args[key].add(line.disasm[1]) 234 | elif line.is_mov: 235 | try: 236 | self.func_args[key].add(line.ctx[get_reg(line.disasm[2], self.arch)]) 237 | except: 238 | self.func_args[key].add(line.disasm[2]) 239 | else: 240 | break 241 | run_var += 1 242 | # TODO mmx xmmx ymmx 243 | # compute next ctx 244 | if self.arch == 32: 245 | self.ctx = defaultdict(lambda: '0', {'eax': self.convert(cpu.eax), 'ebx': self.convert(cpu.ebx), 'edx': self.convert(cpu.edx), 'ecx': self.convert(cpu.ecx), 246 | 'ebp': self.convert(cpu.ebp), 'esp': self.convert(cpu.esp), 'eip': self.convert(cpu.eip), 'edi': self.convert(cpu.edi), 247 | 'esi': self.convert(cpu.esi), 'cf': self.convert(cpu.cf), 'zf': self.convert(cpu.zf), 'sf': self.convert(cpu.sf), 248 | 'of': self.convert(cpu.of), 'pf': self.convert(cpu.pf), 'af': self.convert(cpu.af), 'tf': self.convert(cpu.tf), 249 | 'df': self.convert(cpu.df)}) 250 | elif self.arch == 64: 251 | self.ctx = defaultdict(lambda: '0', {'rax': self.convert(cpu.eax), 'rbx': self.convert(cpu.ebx), 'rdx': self.convert(cpu.edx), 'rcx': self.convert(cpu.ecx), 252 | 'rbp': self.convert(cpu.ebp), 'rsp': self.convert(cpu.esp), 'rip': self.convert(cpu.eip), 'edi': self.convert(cpu.edi), 253 | 'rsi': self.convert(cpu.rsi), 'r8': self.convert(cpu.r8), 'r9': self.convert(cpu.r9), 'r10': self.convert(cpu.r10), 254 | 'r11': self.convert(cpu.r11), 'r12': self.convert(cpu.r12), 'r13': self.convert(cpu.r13), 'r14': self.convert(cpu.r14), 255 | 'r15': self.convert(cpu.r15), 'cf': self.convert(cpu.cf), 'zf': self.convert(cpu.zf), 'sf': self.convert(cpu.sf), 256 | 'of': self.convert(cpu.of), 'pf': self.convert(cpu.pf), 'af': self.convert(cpu.af), 'tf': self.convert(cpu.tf), 257 | 'df': self.convert(cpu.df)}) 258 | 259 | self.trace.append(Traceline(thread_id=tid, addr=ea, disasm=self.disconv(GetDisasm(ea)), ctx=deepcopy(self.ctx))) 260 | except Exception, e: 261 | print e.message 262 | # return values: 263 | # 1 - do not log this trace event; 264 | # 0 - log it 265 | return 0 266 | -------------------------------------------------------------------------------- /dynamic/ImmunityDebugger.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | 5 | from Debugger import Debugger 6 | import sys 7 | 8 | # TODO this is a stub and will be completed later 9 | class OllyDebugger(Debugger): 10 | def __init__(self, *args): 11 | super(Debugger, self).__init__() 12 | self.steps = 0 13 | self.hooked = False 14 | self.bp = None 15 | self.callstack = {} 16 | self.prev_bp_ea = None 17 | self._module_name = 'ImmunityDbg' 18 | self.start_time = 0 19 | self.end_time = 0 20 | 21 | self.error_msg = "TODO!" 22 | 23 | def set_breakpoint(self, address): 24 | return self.error_msg 25 | 26 | def remove_breakpoint(self, address): 27 | return self.error_msg 28 | 29 | def single_step(self): 30 | return self.error_msg 31 | 32 | def gen_trace(self): 33 | return self.error_msg 34 | 35 | def part_exec(self, start=None, end=None, reg_ctx=None): 36 | return self.error_msg 37 | 38 | def get_env_context(self): 39 | return self.error_msg -------------------------------------------------------------------------------- /dynamic/OllyDebugger.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from Debugger import Debugger 5 | import sys 6 | from ollyapi import * 7 | 8 | # TODO this is a stub and will be completed later 9 | class OllyDebugger(Debugger): 10 | def __init__(self, *args): 11 | super(Debugger, self).__init__() 12 | self.steps = 0 13 | self.hooked = False 14 | self.bp = None 15 | self.callstack = {} 16 | self.prev_bp_ea = None 17 | self._module_name = 'OllyDbg' 18 | self.start_time = 0 19 | self.end_time = 0 20 | 21 | self.error_msg = "TODO!" 22 | 23 | def set_breakpoint(self, address): 24 | return self.error_msg 25 | 26 | def remove_breakpoint(self, address): 27 | return self.error_msg 28 | 29 | def single_step(self): 30 | return self.error_msg 31 | 32 | def gen_trace(self): 33 | return self.error_msg 34 | 35 | def part_exec(self, start=None, end=None, reg_ctx=None): 36 | return self.error_msg 37 | 38 | def get_env_context(self): 39 | return self.error_msg -------------------------------------------------------------------------------- /dynamic/TraceRepresentation.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from lib.Register import get_reg_class 3 | 4 | __author__ = 'Anatoli Kalysch' 5 | 6 | 7 | class Trace(list): 8 | def __init__(self, reg_size=64, tr=None): 9 | super(Trace, self).__init__() 10 | self.peephole = False 11 | self.constant_propagation = False 12 | self.standardization = False 13 | self.operand_folding = False 14 | self.stack_addr_propagation = False 15 | self.ctx_reg_size = reg_size 16 | if tr is not None: 17 | assert isinstance(tr, list) 18 | for line in tr: 19 | assert isinstance(line, Traceline) 20 | self.append(line) 21 | 22 | 23 | class Traceline(object): 24 | def __init__(self, **kwargs): 25 | self._line = [kwargs.get('thread_id'), 26 | kwargs.get('addr'), 27 | kwargs.get('disasm'), 28 | kwargs.get('ctx', ''), 29 | kwargs.get('comment', '')] 30 | self.grade = 0 31 | 32 | def __eq__(self, other): 33 | if isinstance(other, Traceline): 34 | # grade is IGNORED, while things like comments and ctx are taken into account! 35 | return self._line == other._line 36 | else: 37 | return False 38 | 39 | def __ne__(self, other): 40 | return not self.__eq__(other) 41 | 42 | def raise_grade(self, value=1): 43 | self.grade += value 44 | 45 | def lower_grade(self, value=1): 46 | self.grade -= value 47 | self.grade = max(0, self.grade) # do not lower below zero to make zero the common denominator for trace line grades 48 | 49 | @property 50 | def thread_id(self): 51 | return self._line[0] 52 | 53 | @thread_id.setter 54 | def thread_id(self, value): 55 | self._line[0] = value 56 | 57 | @property 58 | def addr(self): 59 | return self._line[1] 60 | 61 | @addr.setter 62 | def addr(self, value): 63 | self._line[1] = value 64 | 65 | @property 66 | def disasm(self): 67 | return self._line[2] 68 | 69 | @disasm.setter 70 | def disasm(self, value): 71 | self._line[2] = value 72 | 73 | @property 74 | def disasm_len(self): 75 | return len(self.disasm) 76 | 77 | @property 78 | def ctx(self): 79 | return self._line[3] 80 | 81 | @ctx.setter 82 | def ctx(self, value): 83 | self._line[3] = value 84 | 85 | @property 86 | def comment(self): 87 | return self._line[4] 88 | 89 | @comment.setter 90 | def comment(self, value): 91 | self._line[4] = value 92 | 93 | def disasm_str(self): 94 | try: 95 | return '%s\t%s, %s' % (self.disasm[0], self.disasm[1], self.disasm[2]) 96 | except: 97 | if self.disasm_len == 2: 98 | return '%s\t%s' % (self.disasm[0], self.disasm[1]) 99 | else: 100 | return self.disasm[0] 101 | 102 | def to_str_line(self): 103 | return "%x %x %s\t\t%s\t\t%s" % (self.thread_id, 104 | self.addr, 105 | self.disasm_str(), 106 | ''.join(c for c in self.comment if self.comment is not None), 107 | ''.join('%s:%s ' % (c, self.ctx[c]) for c in self.ctx.keys() if isinstance(self.ctx, dict))) 108 | @property 109 | def is_mov(self): 110 | return self._line[2][0].__contains__('mov') 111 | 112 | @property 113 | def is_pop(self): 114 | return self._line[2][0].startswith('pop') 115 | 116 | @property 117 | def is_push(self): 118 | return self._line[2][0].startswith('push') 119 | 120 | @property 121 | def is_jmp(self): 122 | # returns true for conditional AND non-cond jumps 123 | return self._line[2][0].startswith('j') 124 | 125 | @property 126 | def is_op1_reg(self): 127 | try: 128 | return get_reg_class(self._line[2][1]) is not None 129 | except: 130 | return False 131 | 132 | @property 133 | def is_op2_reg(self): 134 | try: 135 | return get_reg_class(self._line[2][2]) is not None 136 | except: 137 | return False 138 | 139 | @property 140 | def is_comparison(self): 141 | return self.disasm[0].__contains__('cmp') or self.disasm[0].__contains__('test') 142 | 143 | @property 144 | def is_op1_mem(self): 145 | if self.disasm_len > 1: 146 | if self.disasm[1].startswith('[') and self.disasm[1].endswith(']'): 147 | return True 148 | elif self.disasm[1].__contains__('ptr'): 149 | return True 150 | else: 151 | return False 152 | else: 153 | return False 154 | 155 | @property 156 | def is_op2_mem(self): 157 | if self.disasm_len > 2: 158 | if self.disasm[2].startswith('[') and self.disasm[2].endswith(']'): 159 | return True 160 | elif self.disasm[2].__contains__('ptr'): 161 | return True 162 | else: 163 | return False 164 | else: 165 | return False 166 | -------------------------------------------------------------------------------- /dynamic/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Anatoli Kalysch' 2 | -------------------------------------------------------------------------------- /dynamic/dynamic_deobfuscate.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | __author__ = 'Anatoli Kalysch' 4 | 5 | from threading import Thread 6 | 7 | from ui.UIManager import GradingViewer 8 | from ui.UIManager import OptimizationViewer 9 | from ui.UIManager import StackChangeViewer 10 | from ui.UIManager import VMInputOuputViewer 11 | 12 | from DebuggerHandler import load, save, get_dh 13 | from lib.TraceAnalysis import * 14 | from lib.VMRepresentation import get_vmr 15 | from ui.NotifyProgress import NotifyProgress 16 | from ui.UIManager import ClusterViewer 17 | 18 | 19 | ### DEBUGGER LOADING STRATEGIES ### 20 | # IDA Debugger 21 | def load_idadbg(self): 22 | from IDADebugger import IDADebugger 23 | return IDADebugger() 24 | 25 | # OllyDbg 26 | def load_olly(self): 27 | from OllyDebugger import OllyDebugger 28 | return OllyDebugger() 29 | 30 | # Bochs Dbg 31 | def load_bochsdbg(self): 32 | from IDADebugger import IDADebugger 33 | LoadDebugger('Bochs', 0) 34 | return IDADebugger() 35 | 36 | # Win32 Dbg 37 | def load_win32dbg(self): 38 | from IDADebugger import IDADebugger 39 | LoadDebugger('win32', 0) 40 | return IDADebugger() 41 | 42 | # Immunity Dbg 43 | def load_immunitydbg(self): 44 | from IDADebugger import IDADebugger 45 | return IDADebugger() 46 | 47 | 48 | # Working with Win32Dbg, BochsDbg, OllyDbg 49 | available_debuggers = [load_idadbg, load_olly, load_bochsdbg, load_win32dbg, load_immunitydbg] 50 | 51 | 52 | ### INIT AND LOAD CONTEXT ### 53 | 54 | def prepare_trace(): 55 | vmr = get_vmr() 56 | if vmr.trace is None: 57 | vmr.trace = load() 58 | return deepcopy(vmr.trace) 59 | 60 | def prepare_vm_ctx(): 61 | vmr = get_vmr() 62 | return deepcopy(vmr.vm_ctx) 63 | 64 | def prepare_vm_operands(): 65 | vmr = get_vmr() 66 | return deepcopy(vmr.vm_operands) 67 | 68 | def load_dbg(choice): 69 | dbg_handl = get_dh(available_debuggers[choice]) 70 | if dbg_handl.check: 71 | return dbg_handl 72 | else: 73 | raise Exception("[*] Could not load debugger! Please check if the selected debugger is available.") 74 | 75 | def load_trace(): 76 | vmr = get_vmr() 77 | trace = load() 78 | vmr.trace = trace 79 | 80 | def save_trace(): 81 | trace = prepare_trace() 82 | save(trace) 83 | 84 | def gen_instruction_trace(choice): 85 | """ 86 | Generate instruction trace 87 | :param choice: which debugger to use 88 | """ 89 | dbg_handl = get_dh(choice) 90 | vmr = get_vmr() 91 | trace = dbg_handl.gen_instruction_trace() 92 | if trace is not None: 93 | vmr.trace = trace 94 | else: 95 | raise Exception('[*] Trace seems to be None, so it was disregarded!') 96 | 97 | ### ANALYSIS FUNCTIONALITY### 98 | # TODO multithreading !!! 99 | class DynamicAnalyzer(Thread): 100 | def __init__(self, func, trace, **kwargs): 101 | super(DynamicAnalyzer, self).__init__() 102 | self.analysis = func 103 | self.trace = deepcopy(trace) 104 | self.kwargs = kwargs 105 | self.result = None 106 | 107 | def run(self): 108 | self.result = self.analysis(self.trace, self.kwargs) 109 | 110 | def get_result(self): 111 | return self.result 112 | 113 | 114 | def address_heuristic(): 115 | """ 116 | Compute the occurrence of every address in the instruction trace. 117 | """ 118 | w = NotifyProgress('Address count') 119 | w.show() 120 | try: 121 | trace = prepare_trace() 122 | w.pbar_update(40) 123 | ac = address_count(deepcopy(trace)) 124 | w.pbar_update(60) 125 | w.close() 126 | 127 | for addr, count in ac: 128 | print 'Address %x (Disasm: %s) was encountered %s times.' % (addr, GetDisasm(addr), count) 129 | except: 130 | print '[*] An exception occurred! Quitting! ' 131 | w.close() 132 | 133 | # analysis functions supporting manual flag 134 | manual_func = [find_output, find_input, find_virtual_regs, follow_virt_reg] 135 | def manual_analysis(choice): 136 | """ 137 | Allows the execution of analysis functions with the manual flag. Output will mainly be in the Output window and some instances require user interaction. 138 | :param choice: the manual function to be executed 139 | """ 140 | w = NotifyProgress('Address count') 141 | w.show() 142 | trace = prepare_trace() 143 | func = manual_func[choice] 144 | w.pbar_update(10) 145 | func(deepcopy(trace), manual=True, update=w) 146 | w.close() 147 | 148 | def input_output_analysis(manual=False): 149 | """ 150 | Input / Output analysis wrapper which computes the components of the output values of the VM function and allows for comparing these with the input arguments to the VM function. 151 | Afterwards the results are presented in the VMInputOutputViewer. 152 | :param manual: let user choose Function for input output analysis 153 | """ 154 | func_addr = None 155 | if manual: 156 | func_addr = ChooseFunction('Please select the function for black box analysis') 157 | w = NotifyProgress('In/Out') 158 | w.show() 159 | 160 | trace = prepare_trace() 161 | vmr = get_vmr() 162 | # find relevant regs and operands 163 | ctx = {} 164 | try: 165 | if func_addr is not None: # TODO enable input / output analysis of all functions 166 | input = find_input(deepcopy(trace)) 167 | output = find_output(deepcopy(trace)) 168 | w.close() 169 | else: 170 | vr = DynamicAnalyzer(find_virtual_regs, trace) 171 | w.pbar_update(10) 172 | vr.start() 173 | input = DynamicAnalyzer(find_input, trace) 174 | w.pbar_update(10) 175 | input.start() 176 | output = DynamicAnalyzer(find_output, trace) 177 | w.pbar_update(10) 178 | output.start() 179 | vr.join() 180 | w.pbar_update(20) 181 | vr = vr.get_result() 182 | # create the trace excerpt for every relevant reg 183 | for key in vr.keys(): 184 | if get_reg_class(key) is not None: 185 | ctx[key] = follow_virt_reg(deepcopy(trace), virt_reg_addr=vr[key], real_reg_name=key) 186 | vmr.vm_stack_reg_mapping = ctx 187 | w.pbar_update(20) 188 | input.join() 189 | w.pbar_update(10) 190 | output.join() 191 | w.pbar_update(10) 192 | 193 | w.close() 194 | v = VMInputOuputViewer(input.get_result(), output.get_result(), ctx) 195 | v.Show() 196 | except: 197 | w.close() 198 | 199 | def clustering_analysis(visualization=0, grade=False, trace=None): 200 | """ 201 | Clustering analysis wrapper which clusters the trace into repeating instructions and presents the results in the Clustering Viewer. 202 | :param visualization: output via Clustering Viewer or output window 203 | :param grade: grading 204 | :param trace: instruction trace 205 | """ 206 | if trace is None: 207 | trace = prepare_trace() 208 | 209 | w = NotifyProgress('Clustering') 210 | w.show() 211 | 212 | try: 213 | try: 214 | if not trace.constant_propagation: 215 | trace = optimization_const_propagation(trace) 216 | if not trace.stack_addr_propagation: 217 | trace = optimization_stack_addr_propagation(trace) 218 | except: 219 | pass 220 | w.pbar_update(30) 221 | # cluster 222 | vr = find_virtual_regs(deepcopy(trace)) 223 | w.pbar_update(20) 224 | cluster = repetition_clustering(deepcopy(trace)) 225 | w.pbar_update(25) 226 | if visualization == 0: 227 | 228 | v0 = ClusterViewer(cluster, create_bb_diff, trace.ctx_reg_size, save_func=save) 229 | w.pbar_update(24) 230 | v0.Show() 231 | 232 | prev_ctx = defaultdict(lambda: 0) 233 | stack_changes = defaultdict(lambda: 0) 234 | for line in cluster: 235 | if isinstance(line, Traceline): 236 | prev_ctx = line.ctx 237 | else: 238 | stack_changes = create_cluster_gist(line, trace.ctx_reg_size, prev_ctx, stack_changes) 239 | prev_ctx = line[-1].ctx 240 | # sort the stack_changes by address 241 | sorted_result = sorted(stack_changes.keys()) 242 | sorted_result.reverse() 243 | w.close() 244 | v1 = StackChangeViewer(vr, sorted_result, stack_changes) 245 | v1.Show() 246 | else: 247 | w.close() 248 | visualize_cli(cluster) 249 | except: 250 | w.close() 251 | 252 | def optimization_analysis(): 253 | """ 254 | Opens the Optimization Viewer to let the user dynamically interact with the trace. 255 | """ 256 | trace = prepare_trace() 257 | v = OptimizationViewer(trace, save=save) 258 | v.Show() 259 | 260 | def dynamic_vmctx(manual=False): 261 | """ 262 | Compute the VM context values based on the trace. 263 | :param manual: output in output window 264 | """ 265 | trace = prepare_trace() 266 | vm_ctx = dynamic_vm_values(trace) 267 | vmr = get_vmr() 268 | vmr.vm_ctx = vm_ctx 269 | if manual: 270 | print 'Code Start: %x; Code End: %x; Base Addr: %x; VM Addr: %x' % (vm_ctx.code_start, vm_ctx.code_end, vm_ctx.base_addr, vm_ctx.vm_addr) 271 | 272 | def init_grading(trace): 273 | """ 274 | Grading System initialization. This function is part of the automaton grading system. 275 | High grade equals importance. The higher the better. This is the initialization routine for the grading automaton. It assigns the initial grade according to the uniqueness of a line. 276 | :param trace: instruction trace 277 | """ 278 | addr_count = address_count(deepcopy(trace)) 279 | grade = len(set(i[1] for i in addr_count)) 280 | addr_count.reverse() # now the addrs with single occurence are at the beginning of the list 281 | ctr = 1 282 | for tupel in addr_count: # tupel[0] == address, tupel[1] == address occurence 283 | if ctr != tupel[1]: 284 | ctr = tupel[1] 285 | grade -= 1 286 | 287 | for line in trace: 288 | if line.addr == tupel[0]: 289 | line.grade = grade 290 | return trace 291 | 292 | 293 | def grading_automaton(visualization=0): 294 | """ 295 | Grading System Analysis computes a grade for every trace line. It is basically a combination of all available analysis capabilities and runs them one after another, increases the grade 296 | for those trace lines which are in the analysis result and then runs the next trace analysis. In between the analysis runs a pattern matching run is started, to increase / decrease cer- 297 | tain trace lines grades based on known patterns. The patterns are modelled after known short comings of the analysis capabilities. 298 | :param trace: instruction trace 299 | :return: graded instruction trace 300 | """ 301 | vmr = get_vmr() 302 | 303 | w = NotifyProgress('Grading') 304 | w.show() 305 | 306 | trace = prepare_trace() 307 | orig_trace = deepcopy(trace) 308 | try: 309 | ### INIT THE TRACE GRADES ### 310 | trace = init_grading(deepcopy(trace)) 311 | w.pbar_update(10) # 10% 312 | 313 | ### REGISTER USAGE BASED: this must be done before optimization 314 | reg_dict = defaultdict(lambda: 0) 315 | 316 | # find the register infrastructure and vm addressing scheme -> this tells us which registers are used for addressing and are not important for grading_automaton 317 | try: 318 | for line in trace: 319 | assert isinstance(line, Traceline) 320 | if line.is_op2_reg and get_reg_class(line.disasm[2]) is not None: # get reg class will only return != None for the 8-16 standard cpu regs 321 | reg_dict[get_reg_class(line.disasm[2])] += 1 322 | 323 | # get the sorted list of regs highest occurence first 324 | sorted_keys = sorted(reg_dict.items(), key=operator.itemgetter(1), reverse=True) # sorted_list = list of (reg_name, frequency) 325 | length = len(sorted_keys) 326 | w.pbar_update(10) # 20% 327 | # classify the important and less important registers 328 | if length % 2 == 0: 329 | important_regs = set(reg[0] for reg in sorted_keys[:(length / 2)]) 330 | disregard_regs = set(reg[0] for reg in sorted_keys[(length / 2):]) 331 | else: 332 | # if this is the case, one more register gets declared unimportant, since it is better to be more careful about raising grades 333 | important_regs = set(reg[0] for reg in sorted_keys[:(length - 1) / 2]) 334 | disregard_regs = set(reg[0] for reg in sorted_keys[(length - 1) / 2:]) 335 | except: 336 | pass 337 | 338 | 339 | ### OPTIMIZE TRACE ### 340 | try: 341 | if not trace.constant_propagation: 342 | trace = optimization_const_propagation(trace) 343 | except: 344 | pass 345 | w.pbar_update(10) #30% 346 | try: 347 | if not trace.stack_addr_propagation: 348 | trace = optimization_stack_addr_propagation(trace) 349 | except: 350 | pass 351 | 352 | ### REGISTER USAGE AND INPUT OUTPUT BASED ### 353 | # raise the grade of line containing input and output values 354 | try: 355 | values = find_input(deepcopy(trace)).union(find_output(deepcopy(trace))) 356 | for line in trace: 357 | for val in values: 358 | if val in line.to_str_line(): 359 | line.raise_grade(vmr.in_out) 360 | 361 | w.pbar_update(10) #40% 362 | 363 | # backtrace regs and raise grade 364 | virt_regs = find_virtual_regs(deepcopy(trace)) 365 | for key in virt_regs: 366 | if get_reg_class(key) in important_regs: 367 | for line in follow_virt_reg(deepcopy(trace), virt_reg_addr=virt_regs[key]): 368 | try: 369 | for other in trace: 370 | if line == other: 371 | other.raise_grade(vmr.in_out) 372 | except ValueError: 373 | print 'The line %s was not found in the trace, hence the grade could not be raised properly!' % line.to_str_line() 374 | elif get_reg_class(key) in disregard_regs: 375 | for line in follow_virt_reg(deepcopy(trace), virt_reg_addr=virt_regs[key]): 376 | try: 377 | for other in trace: 378 | if line == other: 379 | other.lower_grade(vmr.in_out) 380 | except ValueError: 381 | print 'The line %s was not found in the trace, hence the grade could not be lowered properly!' % line.to_str_line() 382 | except: 383 | pass 384 | w.pbar_update(5) #45% 385 | 386 | ### REGISTER USAGE FREQUENCY BASED ### 387 | try: 388 | # lower the grades for the most commonly used registers 389 | for line in trace: 390 | assert isinstance(line, Traceline) 391 | if line.is_op1_reg and get_reg_class(line.disasm[1]) is not None: # get reg class will only return != None for the 8-16 standard cpu regs 392 | reg_dict[get_reg_class(line.disasm[1])] += 1 393 | 394 | # get the sorted list of regs highest occurrence first 395 | sorted_keys = sorted(reg_dict.items(), key=operator.itemgetter(1), reverse=True) # sorted_list = list of (reg_name, frequency) 396 | length = len(sorted_keys) 397 | w.pbar_update(5) #50% 398 | # classify the less important registers 399 | if length % 2 == 0: 400 | disregard_regs = set(reg[0] for reg in sorted_keys[:(length / 2)]) 401 | else: 402 | disregard_regs = set(reg[0] for reg in sorted_keys[:(length - 1) / 2]) 403 | 404 | 405 | for line in trace: 406 | assert isinstance(line, Traceline) 407 | if line.is_jmp or line.is_mov or line.is_pop or line.is_push or line.disasm[0].startswith('ret') or line.disasm[ 408 | 0].startswith('inc') or line.disasm[0].startswith('lea'): 409 | line.lower_grade(vmr.pa_ma) 410 | elif len(line.disasm) > 1 and get_reg_class(line.disasm[1]) in disregard_regs: 411 | line.lower_grade(vmr.pa_ma) 412 | except: 413 | pass 414 | w.pbar_update(10) #60% 415 | 416 | ### CLUSTERING BASED ### 417 | try: 418 | # raise the grades of the unique lines after clustering 419 | cluster_result = repetition_clustering(deepcopy(trace)) 420 | for line in cluster_result: 421 | if isinstance(line, Traceline): 422 | trace[trace.index(line)].raise_grade(vmr.clu) 423 | except: 424 | pass 425 | w.pbar_update(10) #70% 426 | 427 | ### PEEPHOLE GRADING ### 428 | try: 429 | # peephole grading 430 | for line in trace: 431 | assert isinstance(line, Traceline) 432 | if line.disasm[0] in ['pop', 'push', 'inc', 'dec', 'lea', 'test'] or line.disasm[0].startswith('c') or line.is_jmp or line.is_mov or line.disasm[0].startswith('r'): 433 | line.lower_grade(vmr.pa_ma) 434 | elif len(line.disasm) > 1 and get_reg_class(line.disasm[1]) > 4: 435 | continue 436 | else: 437 | line.raise_grade(vmr.pa_ma) 438 | except: 439 | pass 440 | w.pbar_update(10) #80% 441 | 442 | ### OPTIMIZATION BASED ### 443 | try: 444 | opti_trace = optimize(deepcopy(trace)) 445 | w.pbar_update(10) #90% 446 | for line in opti_trace: 447 | assert isinstance(line, Traceline) 448 | try: # trace is heavily changed after optimization, might not find the trace line in the pre_op_trace 449 | trace[trace.index(line)].raise_grade(vmr.pa_ma) 450 | except: 451 | pass 452 | # additionally raise grade for every line that uses the memory and is not a mov 453 | if line.disasm_len == 3 and line.is_op1_mem and not line.is_mov: 454 | try: 455 | trace[trace.index(line)].raise_grade(vmr.mem_use) 456 | except: 457 | pass 458 | else: 459 | trace[trace.index(line)].lower_grade(vmr.pa_ma) 460 | except: 461 | pass 462 | w.pbar_update(5) 463 | 464 | ### STATIC OPTIMIZATION BASED ### 465 | # TODO atm this is a little workaround to include the static analysis results 466 | try: 467 | comments = set(v_inst.split(' ')[0] for v_inst in [Comment(ea) for ea in range(vmr.code_start, vmr.code_end)] if v_inst is not None) 468 | print comments 469 | ins = [c.lstrip('v').split('_')[0] for c in comments] 470 | for line in trace: 471 | if line.disasm[0] in ins: 472 | line.raise_grade(vmr.static) 473 | 474 | except: 475 | pass 476 | w.pbar_update(5) 477 | 478 | ### RECURSION ### 479 | try: 480 | recursion = 0 481 | vm_func = find_vm_addr(orig_trace) 482 | for line in orig_trace: 483 | if line.disasm[0].startswith('call') and line.disasm[1].__contains__(vm_func): 484 | recursion = recursion + 1 485 | except: 486 | pass 487 | w.close() 488 | 489 | grades = set([line.grade for line in trace]) 490 | max_grade = max(grades) 491 | # raise the trace lines grade containing calls to maximum grade 492 | try: 493 | # such nach call und vm_addr 494 | for line in trace: 495 | if line.disasm[0].startswith('call') and line.disasm[1].__contains__(vm_func): 496 | line.grade = max_grade 497 | elif line.disasm[1].__contains__('ss:') or line.disasm[2].__contains('ss:'): 498 | line.grade = max_grade 499 | except: 500 | pass 501 | 502 | 503 | if visualization == 0: 504 | v = GradingViewer(trace, save=save) 505 | v.Show() 506 | else: 507 | threshold = AskLong(1, 'There are a total of %s grades: %s. Specify a threshold which lines to display:' % (len(grades), ''.join('%s ' % c for c in grades))) 508 | if threshold > -1: 509 | for line in trace: 510 | if line.grade >= threshold: 511 | print line.grade, line.to_str_line() 512 | 513 | except Exception, e: 514 | w.close() 515 | msg(e.message + '\n') -------------------------------------------------------------------------------- /lib/Instruction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @author: Tobias 4 | """ 5 | 6 | import distorm3 7 | 8 | from lib import StartVal as SV 9 | 10 | 11 | class Instruction(object): 12 | """ 13 | @brief Implements the interface to distorm3 Instructions 14 | """ 15 | 16 | def __init__(self, offset, code, type = distorm3.Decode32Bits, feature = 0): 17 | """ 18 | @param offset Address of the instruction 19 | @param code Opcode bytes of the instruction 20 | @param type Dissassemble 32 or 64 bit code 21 | @param feature Possible settings for distrom3 22 | not used at the moment 23 | """ 24 | self.valid = False 25 | if SV.dissassm_type == 64: 26 | type = distorm3.Decode64Bits 27 | else: 28 | type = distorm3.Decode32Bits 29 | inst = distorm3.Decompose(offset, code, type, feature) 30 | if len(inst) == 1: 31 | self.Instruction = inst[0] 32 | if self.Instruction.valid: 33 | self.valid = True 34 | self.opcode_len = len(code) 35 | self.opcode_bytes = [] 36 | self.addr = offset 37 | for x in code: 38 | self.opcode_bytes.append(ord(x)) 39 | self._len = len(self.Instruction.operands) + 1 40 | 41 | 42 | def __str__(self): 43 | return str(self.Instruction).lower() 44 | 45 | 46 | def __len__(self): 47 | return self._len 48 | 49 | 50 | def is_catch_instr(self): 51 | """ 52 | @brief Tests if the instruction fetches 53 | more bytes form the obfuscated code 54 | @return True/False 55 | """ 56 | if len(self.Instruction.operands) != 2: 57 | return False 58 | if (self.is_mov() and 59 | self.Instruction.operands[1].type == distorm3.OPERAND_MEMORY and 60 | self.Instruction.operands[0].type == distorm3.OPERAND_REGISTER): 61 | reg_index = self.Instruction.operands[1].index 62 | if reg_index != None: 63 | reg_name = distorm3.Registers[reg_index] 64 | #change to reverserers input 65 | if('ESI' in reg_name or 'RSI' in reg_name): 66 | return True 67 | else: 68 | return False 69 | else: 70 | return False 71 | else: 72 | return False 73 | 74 | 75 | 76 | def is_mov(self): 77 | """ 78 | @brief Test if the instruction is a mov 79 | """ 80 | mnem = distorm3.Mnemonics[self.Instruction.opcode] 81 | return ('MOV' in mnem) and (self.Instruction.instructionClass == 'ISC_INTEGER') 82 | 83 | 84 | def is_byte_mov(self): 85 | """ 86 | @brief Tests if a mov moves one byte 87 | """ 88 | #both operands must exist 89 | if len(self.Instruction.operands) != 2: 90 | return False 91 | return (self.Instruction.operands[0].size == 8 or 92 | self.Instruction.operands[1].size == 8) 93 | 94 | 95 | def is_word_mov(self): 96 | """ 97 | @brief Tests if a mov moves two byte 98 | """ 99 | #both operands must exist 100 | if len(self.Instruction.operands) != 2: 101 | return False 102 | sizeOp1 = self.Instruction.operands[0].size 103 | sizeOp2 = self.Instruction.operands[1].size 104 | if (sizeOp1 == 16 and sizeOp2 >= 16): 105 | return True 106 | elif (sizeOp1 >= 16 and sizeOp2 == 16): 107 | return True 108 | else: 109 | return False 110 | 111 | 112 | 113 | def is_double_mov(self): 114 | """ 115 | @brief Tests if a mov moves four byte 116 | """ 117 | #both operands must exist 118 | if len(self.Instruction.operands) != 2: 119 | return False 120 | sizeOp1 = self.Instruction.operands[0].size 121 | sizeOp2 = self.Instruction.operands[1].size 122 | if (sizeOp1 == 32 and sizeOp2 >= 32): 123 | return True 124 | elif (sizeOp1 >= 32 and sizeOp2 == 32): 125 | return True 126 | else: 127 | return False 128 | 129 | 130 | def is_quad_mov(self): 131 | """ 132 | @brief Tests if a mov moves eight byte 133 | """ 134 | #both operands must exist 135 | if len(self.Instruction.operands) != 2: 136 | return False 137 | sizeOp1 = self.Instruction.operands[0].size 138 | sizeOp2 = self.Instruction.operands[1].size 139 | if (sizeOp1 == 64 and sizeOp2 >= 64): 140 | return True 141 | elif (sizeOp1 >= 64 and sizeOp2 == 64): 142 | return True 143 | else: 144 | return False 145 | 146 | 147 | def get_mov_size(self): 148 | """ 149 | @brief Determines how many bytes are moved 150 | @return size in bytes 151 | """ 152 | if self.is_quad_mov(): 153 | return 8 154 | elif self.is_double_mov(): 155 | return 4 156 | elif self.is_word_mov(): 157 | return 2 158 | elif self.is_byte_mov(): 159 | return 1 160 | else: 161 | return None 162 | 163 | 164 | def get_size(self): 165 | """ 166 | @brief Easy access to size of distorm3 instruction 167 | """ 168 | return self.Instruction.size 169 | 170 | 171 | def is_mov_basep_stackp(self): 172 | """ 173 | @brief Tests if the instruction is 'mov ebp, esp' or 174 | 'mov rbp, rsp' 175 | """ 176 | if len(self.Instruction.operands) != 2: 177 | return False 178 | Op0 = self.Instruction.operands[0] 179 | Op1 = self.Instruction.operands[1] 180 | if (Op0.type == distorm3.OPERAND_REGISTER and 181 | Op1.type == distorm3.OPERAND_REGISTER and 182 | (Op0.name == 'EBP' or Op0.name == 'RBP') and 183 | (Op1.name == 'ESP' or Op1.name == 'RSP')): 184 | return True 185 | else: 186 | return False 187 | 188 | def is_write_stack(self): 189 | """ 190 | @brief Tests if the instruction writes to 191 | the stack 192 | """ 193 | if len(self.Instruction.operands) != 2: 194 | return False 195 | op0 = self.Instruction.operands[0] 196 | if op0.index == None or op0.disp != 0: 197 | return False 198 | if (self.is_mov() and 199 | op0.type == distorm3.OPERAND_MEMORY and 200 | (distorm3.Registers[op0.index] == 'EBP' or 201 | distorm3.Registers[op0.index] == 'RBP')): 202 | return True 203 | else: 204 | return False 205 | 206 | 207 | def is_read_stack(self): 208 | """ 209 | @brief Tests if the instruction reads from 210 | the stack 211 | """ 212 | if len(self.Instruction.operands) != 2: 213 | return False 214 | op1 = self.Instruction.operands[1] 215 | if op1.index == None or op1.disp != 0: 216 | return False 217 | if (self.is_mov() and 218 | op1.type == distorm3.OPERAND_MEMORY and 219 | (distorm3.Registers[op1.index] == 'EBP' or 220 | distorm3.Registers[op1.index] == 'RBP')): 221 | return True 222 | else: 223 | return False 224 | 225 | 226 | def is_isp_mov(self): 227 | """ 228 | @brief Tests if the instructionpoiter of the vm 229 | gets a new value 230 | """ 231 | if len(self.Instruction.operands) != 2: 232 | return False 233 | op0 = self.Instruction.operands[0] 234 | if op0.index == None: 235 | return False 236 | if (self.is_mov() and 237 | op0.type == distorm3.OPERAND_REGISTER and 238 | (distorm3.Registers[op0.index] == 'ESI' or 239 | distorm3.Registers[op0.index] == 'RSI')): 240 | return True 241 | else: 242 | return False 243 | 244 | 245 | #first op is 1 secend 2 and so on 246 | def op_is_reg(self, op): 247 | """ 248 | @brief Tests if a operand of a instruction is a 249 | register 250 | @param op Access to operand; for first operand: op = 1 251 | """ 252 | if op < 1 or op > len(self.Instruction.operands): 253 | return False 254 | return self.Instruction.operands[op-1].type == distorm3.OPERAND_REGISTER 255 | 256 | 257 | def op_is_imm(self, op): 258 | """ 259 | @brief Tests if a operand of a instruction is a 260 | immediate 261 | @param op Access to operand; for first operand: op = 1 262 | """ 263 | if op < 1 or op > len(self.Instruction.operands): 264 | return False 265 | return self.Instruction.operands[op-1].type == distorm3.OPERAND_IMMEDIATE 266 | 267 | 268 | def op_is_mem(self, op): 269 | """ 270 | @brief Tests if a operand of a instruction is a 271 | memory access 272 | @param op Access to operand; for first operand: op = 1 273 | """ 274 | if op < 1 or op > len(self.Instruction.operands): 275 | return False 276 | return self.Instruction.operands[op-1].type == distorm3.OPERAND_MEMORY 277 | 278 | 279 | def op_is_mem_abs(self, op): 280 | """ 281 | @brief Tests if a operand of a instruction is a 282 | is a memory access through an absolute address 283 | @param op Access to operand; for first operand: op = 1 284 | """ 285 | if op < 1 or op > len(self.Instruction.operands): 286 | return False 287 | return self.Instruction.operands[op-1].type == distorm3.OPERAND_ABSOLUTE_ADDRESS 288 | 289 | 290 | def is_vinst(self): 291 | """ 292 | @brief Tests if one of the operands of the instruction is 293 | the 'esi' or 'rsi' register 294 | """ 295 | for op in self.Instruction.operands: 296 | if op.type == distorm3.OPERAND_REGISTER: 297 | if op.name == 'ESI' or op.name == 'RSI': 298 | return True 299 | elif op.type == distorm3.OPERAND_MEMORY: 300 | if op.index != None: 301 | if (distorm3.Registers[op.index] == 'ESI' or 302 | distorm3.Registers[op.index] == 'RSI'): 303 | return True 304 | return False 305 | 306 | 307 | def is_ret(self): 308 | """ 309 | @brief Tests if the instruction is a 'ret' 310 | """ 311 | return self.Instruction.flowControl == 'FC_RET' 312 | 313 | 314 | def is_call(self): 315 | """ 316 | @brief Tests if the instruction is a 'call' 317 | """ 318 | return (self.Instruction.mnemonic.startswith('CALL') and 319 | self.Instruction.instructionClass == 'ISC_INTEGER') 320 | 321 | 322 | def is_and(self): 323 | """ 324 | @brief Tests if the instruction is a 'and' 325 | """ 326 | return (self.Instruction.mnemonic.startswith('AND') and 327 | self.Instruction.instructionClass == 'ISC_INTEGER') 328 | 329 | 330 | def is_shr(self): 331 | """ 332 | @brief Tests if the instruction is a 'shr' 333 | """ 334 | return (self.Instruction.mnemonic == 'SHR' and 335 | self.Instruction.instructionClass == 'ISC_INTEGER') 336 | 337 | 338 | def is_shl(self): 339 | """ 340 | @brief Tests if the instruction is a 'shl' 341 | """ 342 | return (self.Instruction.mnemonic == 'SHL' and 343 | self.Instruction.instructionClass == 'ISC_INTEGER') 344 | 345 | 346 | def is_shld(self): 347 | """ 348 | @brief Tests if the instruction is a 'shld' 349 | """ 350 | return (self.Instruction.mnemonic == 'SHLD' and 351 | self.Instruction.instructionClass == 'ISC_INTEGER') 352 | 353 | 354 | def is_shrd(self): 355 | """ 356 | @brief Tests if the instruction is a 'shrd' 357 | """ 358 | return (self.Instruction.mnemonic == 'SHRD' and 359 | self.Instruction.instructionClass == 'ISC_INTEGER') 360 | 361 | 362 | def is_cwde(self): 363 | """ 364 | @brief Tests if the instruction is a 'cwde' 365 | """ 366 | return self.Instruction.mnemonic == 'CWDE' 367 | 368 | 369 | def is_cbw(self): 370 | """ 371 | @brief Tests if the instruction is a 'cbw' 372 | """ 373 | return self.Instruction.mnemonic == 'CBW' 374 | 375 | 376 | def is_cdqe(self): 377 | """ 378 | @brief Tests if the instruction is a 'cbw' 379 | """ 380 | return self.Instruction.mnemonic == 'CDQE' 381 | 382 | def is_imul(self): 383 | """ 384 | @brief Tests if the instruction is a 'imul' 385 | """ 386 | return self.Instruction.mnemonic == 'IMUL' 387 | 388 | 389 | def is_idiv(self): 390 | """ 391 | @brief Tests if the instruction is a 'idiv' 392 | """ 393 | return self.Instruction.mnemonic == 'IDIV' 394 | 395 | 396 | def is_add(self): 397 | """ 398 | @brief Tests if the instruction is a 'add' 399 | """ 400 | return (self.Instruction.mnemonic.startswith('ADD') and 401 | self.Instruction.instructionClass == 'ISC_INTEGER') 402 | 403 | 404 | def is_not(self): 405 | """ 406 | @brief Tests if the instruction is a 'not' 407 | """ 408 | return (self.Instruction.mnemonic.startswith('NOT') and 409 | self.Instruction.instructionClass == 'ISC_INTEGER') 410 | 411 | 412 | def is_pop(self): 413 | """ 414 | @brief Tests if the instruction is a 'pop' 415 | """ 416 | return (self.Instruction.mnemonic == 'POP' or 417 | self.Instruction.mnemonic == 'POPF') 418 | 419 | 420 | def is_push(self): 421 | """ 422 | @brief Tests if the instruction is a 'push' 423 | """ 424 | return (self.Instruction.mnemonic == 'PUSH' or 425 | self.Instruction.mnemonic == 'PUSHF') 426 | 427 | 428 | def is_uncnd_jmp(self): 429 | """ 430 | @brief Tests if the instruction is an unconditional jump 431 | """ 432 | return self.Instruction.flowControl == 'FC_UNC_BRANCH' 433 | 434 | 435 | def is_sub_basepointer(self): 436 | """ 437 | @brief Tests if the instruction subtracts something from 438 | the basepointer 439 | """ 440 | return (('SUB' in self.Instruction.mnemonic) and 441 | (self.Instruction.instructionClass == 'ISC_INTEGER') and 442 | (self.Instruction.operands[0].name == 'EBP' or 443 | self.Instruction.operands[0].name == 'RBP')) 444 | 445 | 446 | def is_add_basepointer(self): 447 | """ 448 | @brief Tests if the instruction adds something to the 449 | basepointer 450 | """ 451 | return (('ADD' in self.Instruction.mnemonic) and 452 | (self.Instruction.instructionClass == 'ISC_INTEGER') and 453 | (self.Instruction.operands[0].name == 'EBP' or 454 | self.Instruction.operands[0].name == 'RBP')) 455 | 456 | 457 | def get_op_str(self, op): 458 | """ 459 | @param op Access to operand; for first operand: op = 1 460 | @return Returns the string represtentation of op 461 | """ 462 | if op < 1 or op > len(self.Instruction.operands): 463 | return None 464 | return str(self.Instruction.operands[op-1]).lower() 465 | 466 | 467 | def get_op_size(self, op): 468 | """ 469 | @param op Access to operand; for first operand: op = 1 470 | @return Returns the size of op 471 | """ 472 | if op < 1 or op > len(self.Instruction.operands): 473 | return None 474 | return self.Instruction.operands[op-1].size 475 | 476 | 477 | def get_reg_name(self, op): 478 | """ 479 | @param op Access to operand; for first operand: op = 1 480 | @return Returns the name of the register from op 481 | """ 482 | if op < 1 or op > len(self.Instruction.operands): 483 | return None 484 | if self.op_is_reg(op): 485 | return self.Instruction.operands[op-1].name.lower() 486 | elif self.op_is_mem(op): 487 | #abfrage 488 | return distorm3.Registers[self.Instruction.operands[op-1].index] 489 | else: 490 | return None 491 | 492 | 493 | def get_op_value(self, op): 494 | """ 495 | @param op Access to operand; for first operand: op = 1 496 | @return Returns the value of op 497 | """ 498 | if op < 1 or op > len(self.Instruction.operands): 499 | return None 500 | if self.op_is_imm(op): 501 | return self.Instruction.operands[op-1].value 502 | else: 503 | return None 504 | 505 | 506 | def get_op_disp(self, op): 507 | """ 508 | @param op Access to operand; for first operand: op = 1 509 | @return Returns the displacement of op 510 | """ 511 | if op < 1 or op > len(self.Instruction.operands): 512 | return None 513 | if self.op_is_mem_abs(op) or self.op_is_mem(op): 514 | return self.Instruction.operands[op-1].disp 515 | else: 516 | return None 517 | 518 | 519 | def get_op(self, op): 520 | """ 521 | @param op Access to operand; for first operand: op = 1 522 | @return Returns the distorm3 op 523 | """ 524 | if op < 1 or op > len(self.Instruction.operands): 525 | return None 526 | return self.Instruction.operands[op-1] 527 | 528 | 529 | def is_rip_rel(self): 530 | """ 531 | @brief tests if the address is relativ to 'rip' 532 | """ 533 | return 'FLAG_RIP_RELATIVE' in self.Instruction.flags 534 | 535 | -------------------------------------------------------------------------------- /lib/Logging.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | 5 | class LoggingEngine(object): 6 | logger = None 7 | # private class 8 | class __Logger(object): 9 | def __init__(self): 10 | self._log = open('VMAttack.log', 'a') 11 | 12 | def log(self, message): 13 | assert(isinstance(message, str)) 14 | message_lines = message.splitlines() 15 | for line in message_lines: 16 | self._log.write(line + '\n') 17 | 18 | def rm(self): 19 | try: 20 | self._log.close() 21 | except: 22 | pass 23 | self._log = open('VMAttack.log', 'w') 24 | 25 | def finalize(self): 26 | self._log.close() 27 | 28 | 29 | def __init__(self): 30 | # init singleton 31 | if not LoggingEngine.logger: 32 | LoggingEngine.logger = LoggingEngine.__Logger() 33 | 34 | def log(self, message): 35 | self.logger.log(message) 36 | 37 | def rm(self): 38 | self.logger.rm() 39 | LoggingEngine.logger = LoggingEngine.__Logger() 40 | 41 | 42 | def finalize(self): 43 | self.logger.finalize() 44 | 45 | logEng = None 46 | 47 | def get_log(): 48 | global logEng 49 | if not logEng: 50 | logEng = LoggingEngine() 51 | 52 | return logEng 53 | 54 | def rm_log(): 55 | global logEng 56 | if not logEng: 57 | logEng = LoggingEngine() 58 | 59 | logEng.rm() -------------------------------------------------------------------------------- /lib/Register.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | @author: Tobias 5 | """ 6 | 7 | """@brief List of register classes""" 8 | _registerClasses = [ 9 | ['al', 'ah', 'ax', 'eax', 'rax'], 10 | ['bl', 'bh', 'bx', 'ebx', 'rbx'], 11 | ['cl', 'ch', 'cx', 'ecx', 'rcx'], 12 | ['dl', 'dh', 'dx', 'edx', 'rdx'], 13 | ['bpl', 'bp', 'ebp', 'rbp'], 14 | ['dil', 'di', 'edi', 'rdi'], 15 | ['sil', 'si', 'esi', 'rsi'], 16 | ['spl', 'sp', 'esp', 'rsp'], 17 | ['r8l', 'r8w', 'r8d', 'r8'], 18 | ['r9l', 'r9w', 'r9d', 'r9'], 19 | ['r10l', 'r10w', 'r10d', 'r10'], 20 | ['r11l', 'r11w', 'r11d', 'r11'], 21 | ['r12l', 'r12w', 'r12d', 'r12'], 22 | ['r13l', 'r13w', 'r13d', 'r13'], 23 | ['r14l', 'r14w', 'r14d', 'r14'], 24 | ['r15l', 'r15w', 'r15d', 'r15'] 25 | ] 26 | 27 | 28 | def get_reg_class(reg): 29 | """ 30 | @brief Determines the register class of a given reg. 31 | All different register names that address the same register 32 | belong to the same register class e.g.: 'ax' and 'eax' 33 | @param reg name of register 34 | @return register class 35 | """ 36 | lreg = reg.lower() 37 | ret_value = None 38 | for pos, reg_list in enumerate(_registerClasses): 39 | for reg in reg_list: 40 | found = False 41 | if reg == lreg: 42 | found = True 43 | ret_value = pos 44 | break 45 | if found: 46 | break 47 | return ret_value 48 | 49 | 50 | def get_reg_by_size(reg_class, reg_size): 51 | """ 52 | @brief Determines the register by its size and class 53 | @param reg_class The register class of the register 54 | @param reg_size The size of the register 55 | @return Name of the register 56 | """ 57 | if reg_class >= len(_registerClasses): 58 | return None 59 | num_regs = len(_registerClasses[reg_class]) 60 | if num_regs < 4: 61 | return None 62 | reg_index = -1 63 | if reg_size > 32: # 64-bit regs 64 | reg_index = num_regs - 1 65 | elif reg_size > 16: # 32-bit regs 66 | reg_index = num_regs - 2 67 | elif reg_size > 8: # 16-bit regs 68 | reg_index = num_regs - 3 69 | elif reg_size > 0: # 8-bit regs 70 | reg_index = 0 71 | else: 72 | return None 73 | return _registerClasses[reg_class][reg_index] 74 | 75 | 76 | def get_size_by_reg(reg): 77 | """ 78 | @brief Determines the size of the given register 79 | @param reg Register 80 | @return Size of register 81 | """ 82 | reg_class = get_reg_class(reg) 83 | num_regs = len(_registerClasses[reg_class]) 84 | for index, test_reg in enumerate(_registerClasses[reg_class]): 85 | if test_reg == reg: 86 | break 87 | else: # no break 88 | return None 89 | if index == (num_regs-1): 90 | return 64 91 | elif index == (num_regs-2): 92 | return 32 93 | elif index == (num_regs-3): 94 | return 16 95 | else: 96 | return 8 97 | 98 | 99 | 100 | def get_reg_class_lst(reg_class): 101 | """ 102 | @return Returns the whole list of a given register class 103 | """ 104 | return _registerClasses[reg_class] 105 | -------------------------------------------------------------------------------- /lib/StartVal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ASSEMBLER_64 = 64 4 | ASSEMBLER_32 = 32 5 | 6 | dissassm_type = 0 7 | -------------------------------------------------------------------------------- /lib/Util.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | import idaapi 5 | 6 | from idc import * 7 | from idautils import * 8 | from lib.Register import get_reg_class, get_reg_by_size 9 | 10 | ### ARCHITECTURE AND REGISTER FUNCTIONALITY ### 11 | # IDA PRO execution context layouts 64 12 | ctx_layout_64 = {'st':['ST0', 'ST1', 'ST2', 'ST3', 'ST4', 'ST5', 'ST6', 'ST7'], 13 | 'ctrl':'CTRL', 14 | 'segments':['CS', 'DS', 'ES', 'FS', 'GS', 'SS'], 15 | 'registers':['RAX', 'RBX', 'RCX', 'RDX', 'RSI', 'RDI', 'RBP', 'RSP', 'R8', 'R9', 'R10', 'R11', 'R12', 'R13', 'R14', 'R15'], 16 | 'flags':'EFL', 17 | 'multimedia':['XMM0', 'XMM1', 'XMM2', 'XMM3', 'XMM4', 'XMM5', 'XMM6', 'XMM7', 'XMM8', 'XMM9', 'XMM10', 'XMM11', 'XMM12', 'XMM13', 'XMM14', 'XMM15', 'MXCSR', 'MM0', 'MM1', 'MM2', 'MM3', 'MM4', 'MM5', 'MM6', 'MM7']} 18 | 19 | # IDA PRO execution context layouts 86 20 | ctx_layout_86 = {'st':['ST0', 'ST1', 'ST2', 'ST3', 'ST4', 'ST5', 'ST6', 'ST7'], 21 | 'ctrl':'CTRL', 22 | 'segments':['CS', 'DS', 'ES', 'FS', 'GS', 'SS'], 23 | 'registers':['EAX', 'EBX', 'ECX', 'EDX', 'ESI', 'EDI', 'EBP', 'ESP'], 24 | 'flags':'EFL', 25 | 'multimedia':['MXCSR', 'MM0', 'MM1', 'MM2', 'MM3', 'MM4', 'MM5', 'MM6', 'MM7']} 26 | 27 | def get_arch_dynamic(): 28 | """ 29 | Determine the execution environments architecture. 30 | :return: 'x64' or 'x86' if arch could be determined, else None 31 | """ 32 | info = idaapi.get_inf_structure() 33 | if info.is_64bit(): 34 | return 64 35 | elif info.is_32bit(): 36 | return 32 37 | else: 38 | env = idaapi.dbg_get_registers() 39 | if env[17][0] == 'RAX': 40 | return 64 41 | elif env[17][0] == 'EAX': 42 | return 32 43 | else: 44 | return None 45 | 46 | 47 | ############################### 48 | # LIB DETECTION FUNCTIONALITY # 49 | ############################### 50 | def is_import_or_lib_func(ea): 51 | """ 52 | Is ea part of an imported function or a known library? 53 | @param ea: any ea within the function scope 54 | @return: True if function is either imported or a known library function. 55 | """ 56 | 57 | return Functions(ea).flags & (idaapi.FUNC_LIB | idaapi.FUNC_THUNK) 58 | 59 | def is_system_lib(ea): 60 | """ 61 | Returns true if a segment belongs to a system library, in which case we don't want to recursively hook calls. 62 | Covers Windows, Linux, Mac, Android, iOS 63 | @param ea: an effective address within a function 64 | """ 65 | name = idc.SegName(ea) 66 | 67 | if not name: 68 | return False 69 | 70 | # the below is for Windows kernel debugging 71 | if name == 'nt': 72 | return True 73 | 74 | sysfolders = [re.compile("\\\\windows\\\\", re.I), re.compile("\\\\Program Files ", re.I), re.compile("/usr/", re.I), \ 75 | re.compile("/system/", re.I), re.compile("/lib/", re.I)] 76 | 77 | m = idc.GetFirstModule() 78 | while m: 79 | path = idc.GetModuleName(m) 80 | if re.search(name, path): 81 | if any(regex.search(path) for regex in sysfolders): 82 | return True 83 | else: 84 | return False 85 | m = idc.GetNextModule(m) 86 | 87 | return False 88 | 89 | ############################ 90 | ### COLORS FUNCTIONALITY ### 91 | ############################ 92 | # current palette are mainly shades of red, green, blue 93 | palette = [0xccccff, 0xb3b3ff, 0x9999ff, 0x8080ff, 0x6666ff, 0x4d4dff, 0x3333ff, 0x1a1aff, 0x0000ff, 0x00ff00, 0xff0000] 94 | 95 | 96 | def remove_all_colors(): 97 | heads = Heads(BeginEA(), BADADDR) 98 | for head in heads: 99 | SetColor(head, CIC_ITEM, 0xFFFFFF) 100 | 101 | def set_new_color(addr): 102 | # get current color of addr_line and set the next escalation color 103 | current_color = GetColor(addr, CIC_ITEM) 104 | SetColor(addr, CIC_ITEM, set_new_color(current_color)) 105 | current_color = 0xFFFFFF 106 | # processing 107 | if current_color == 0xFFFFFF: 108 | return palette[0] 109 | if current_color in palette: 110 | pos = palette.index(current_color) 111 | if pos == len(palette) - 1: 112 | return palette[pos] 113 | else: 114 | return palette[pos + 1] 115 | 116 | return 0xFFFFFF 117 | 118 | 119 | class CPU(object): 120 | def __init__(self): 121 | self.registers = {} 122 | self.st = {'ST0':0, 'ST1':0, 'ST2':0, 'ST3':0, 'ST4':0, 'ST5':0, 'ST6':0, 'ST7':0} 123 | self.ctrl = {'CTRL':0} 124 | self.segments = {'CS', 'DS', 'ES', 'FS', 'GS', 'SS'} 125 | self.registers = {'RAX', 'RBX', 'RCX', 'RDX', 'RSI', 'RDI', 'RBP', 'RSP', 'R8', 'R9', 'R10', 'R11', 'R12', 'R13', 126 | 'R14', 'R15'} 127 | self.flags = {} 128 | self.multimedia = {'XMM0':0, 'XMM1':0, 'XMM2':0, 'XMM3':0, 'XMM4':0, 'XMM5':0, 'XMM6':0, 'XMM7':0, 'XMM8':0, 'XMM9':0, 'XMM10':0, 'XMM11':0, 129 | 'XMM12':0, 'XMM13':0, 'XMM14':0, 'XMM15':0, 'MXCSR':0, 'MM0':0, 'MM1':0, 'MM2':0, 'MM3':0, 'MM4':0, 'MM5':0, 'MM6':0, 'MM7':0} 130 | 131 | 132 | def get_reg(reg_string, reg_size): 133 | """ 134 | returns the register name to be used as key with a Traceline.ctx object 135 | :param reg_string: any string representing a reg, e.g. rax, RAX, eax, ah, al, etc. 136 | :param reg_size: size in bit of the registers in Traceline.ctx, e.g. 64, 32, 16 137 | :return: reg_string of the ctx keys, e.g. rax 138 | """ 139 | return get_reg_by_size(get_reg_class(reg_string), reg_size) 140 | 141 | def sanitize_hex(hex_string): 142 | """ 143 | Sanitize input to uppercase hex string 144 | :param hex_string: the input hex string, e.g. 0xabc, 0xABC, abc, 28h 145 | :return: sanitized hex string, e.g. ABC or 28 146 | """ 147 | return ''.join(c for c in hex_string.upper() if c in '0123456789ABCDEF') 148 | 149 | def interprete_math_expr(operands, expr): 150 | """ 151 | 152 | :param operands: list of operands 153 | :param expr: the expression to use: + or - or * 154 | :return: mathematical result 155 | """ 156 | result = operands[0] 157 | for operand in operands[1:]: 158 | if expr == '+': 159 | result += operand 160 | elif expr == '-': 161 | result -= operand 162 | elif expr == '*': 163 | result *= operand 164 | else: 165 | raise Exception('[*] Exception parsing math expression: Unknown Value \'%s\'!' % expr) 166 | return result 167 | 168 | -------------------------------------------------------------------------------- /lib/VMRepresentation.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from idaapi import BADADDR 5 | 6 | class VMRepresentation(object): 7 | # private scriptor class contains the necessary info for analysis 8 | class __Scriptor: 9 | def __init__(self): 10 | # dbg handler and trace 11 | self._trace = None 12 | 13 | # VM init values 14 | self._vm_operands = set() # set of operands the vm_function gets 15 | self._vm_returns = dict() # dictionary of reg:value mappings the vm_function returns 16 | self._vm_ctx = VMContext() 17 | self._vm_stack_reg_mapping = dict() 18 | 19 | # Env values 20 | self._sys_libs = False 21 | self._extract_param = True 22 | self._func_args = {} 23 | self._greedy = True 24 | self._bb = True 25 | self._cluster_magic = 2 26 | 27 | # grading automaton 28 | self._in_out = 2 29 | self._pa_ma = 2 30 | self._clu = 1 31 | self._mem_use = 3 32 | self._static = 3 33 | 34 | 35 | scriptor = None 36 | def __init__(self): 37 | # init the singleton instance 38 | if not VMRepresentation.scriptor: 39 | VMRepresentation.scriptor = VMRepresentation.__Scriptor() 40 | 41 | def __getattr__(self, item): 42 | return getattr(self.scriptor, item) 43 | 44 | ### trace ### 45 | @property 46 | def trace(self): 47 | return self.scriptor._trace 48 | 49 | @trace.setter 50 | def trace(self, value): 51 | self.scriptor._trace = value 52 | 53 | ### VM init val ### 54 | @property 55 | def code_start(self): 56 | return self.scriptor._vm_ctx.code_start 57 | 58 | @property 59 | def code_end(self): 60 | return self.scriptor._vm_ctx.code_end 61 | 62 | @property 63 | def base_addr(self): 64 | return self.scriptor._vm_ctx.base_addr 65 | 66 | @property 67 | def vm_addr(self): 68 | return self.scriptor._vm_ctx.vm_addr 69 | 70 | @property 71 | def vm_ctx(self): 72 | return self.scriptor._vm_ctx 73 | 74 | @vm_ctx.setter 75 | def vm_ctx(self, value): 76 | assert isinstance(value, VMContext) 77 | self.scriptor._vm_ctx = value 78 | 79 | @property 80 | def vm_operands(self): 81 | return self.scriptor._vm_operands 82 | 83 | @vm_operands.setter 84 | def vm_operands(self, value): 85 | assert isinstance(value, set) 86 | self.scriptor._vm_operands = value 87 | 88 | @property 89 | def vm_stack_reg_mapping(self): 90 | return self.scriptor._vm_stack_reg_mapping 91 | 92 | @vm_stack_reg_mapping.setter 93 | def vm_stack_reg_mapping(self, value): 94 | self.scriptor._vm_stack_reg_mapping = value 95 | 96 | @property 97 | def vm_returns(self): 98 | return self.scriptor._vm_returns 99 | 100 | @vm_returns.setter 101 | def vm_returns(self, value): 102 | assert isinstance(value, dict) 103 | self.scriptor._vm_returns = value 104 | 105 | ### grading automaton ### 106 | @property 107 | def in_out(self): 108 | return self.scriptor._in_out 109 | 110 | @property 111 | def pa_ma(self): 112 | return self.scriptor._pa_ma 113 | 114 | @property 115 | def clu(self): 116 | return self.scriptor._clu 117 | 118 | @property 119 | def mem_use(self): 120 | return self.scriptor._mem_use 121 | 122 | @property 123 | def static(self): 124 | return self.scriptor._static 125 | 126 | @in_out.setter 127 | def in_out(self, value): 128 | self.scriptor._in_out = value 129 | 130 | @pa_ma.setter 131 | def pa_ma(self, value): 132 | self.scriptor._pa_ma = value 133 | 134 | @clu.setter 135 | def clu(self, value): 136 | self.scriptor._clu = value 137 | 138 | @mem_use.setter 139 | def mem_use(self, value): 140 | self.scriptor._mem_use = value 141 | 142 | @static.setter 143 | def static(self, value): 144 | self.scriptor._static = value 145 | 146 | ### env ### 147 | @property 148 | def greedy(self): 149 | return self.scriptor._greedy 150 | 151 | @greedy.setter 152 | def greedy(self, value): 153 | self.scriptor._greedy = value 154 | 155 | @property 156 | def sys_libs(self): 157 | return self.scriptor._sys_libs 158 | 159 | @sys_libs.setter 160 | def sys_libs(self, value): 161 | self.scriptor._sys_libs = value 162 | 163 | @property 164 | def extract_param(self): 165 | return self.scriptor._extract_param 166 | 167 | @extract_param.setter 168 | def extract_param(self, value): 169 | self.scriptor._extract_param = value 170 | 171 | @property 172 | def func_args(self): 173 | return self.scriptor._func_args 174 | 175 | @func_args.setter 176 | def func_args(self, value): 177 | self.scriptor._func_args = value 178 | 179 | @property 180 | def bb(self): 181 | return self.scriptor._bb 182 | 183 | @bb.setter 184 | def bb(self, value): 185 | self.scriptor._bb = value 186 | 187 | @property 188 | def cluster_magic(self): 189 | return self.scriptor._cluster_magic 190 | 191 | @cluster_magic.setter 192 | def cluster_magic(self, value): 193 | self.scriptor._cluster_magic = value 194 | 195 | 196 | class VMContext(object): 197 | def __init__(self): 198 | self.code_start = BADADDR 199 | self.code_end = BADADDR 200 | self.base_addr = BADADDR 201 | self.vm_addr = BADADDR 202 | 203 | # Singelton VMR 204 | vmr = None 205 | 206 | def get_vmr(): 207 | """ 208 | Get the VMR instance. 209 | :return: vmr 210 | """ 211 | global vmr 212 | if vmr is None: 213 | vmr = VMRepresentation() 214 | return vmr 215 | 216 | def del_vmr(): 217 | """ 218 | Delete the VMR instance 219 | """ 220 | global vmr 221 | vmr = None 222 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Anatoli Kalysch' 2 | -------------------------------------------------------------------------------- /screenshots/InputOutput1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/InputOutput1.png -------------------------------------------------------------------------------- /screenshots/InputOutput2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/InputOutput2.png -------------------------------------------------------------------------------- /screenshots/InputOutput3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/InputOutput3.png -------------------------------------------------------------------------------- /screenshots/StackChanges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/StackChanges.png -------------------------------------------------------------------------------- /screenshots/ab_vm_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/ab_vm_graph.png -------------------------------------------------------------------------------- /screenshots/clustering1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/clustering1.png -------------------------------------------------------------------------------- /screenshots/dynamic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/dynamic.png -------------------------------------------------------------------------------- /screenshots/generate_trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/generate_trace.png -------------------------------------------------------------------------------- /screenshots/grading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/grading.png -------------------------------------------------------------------------------- /screenshots/grading1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/grading1.png -------------------------------------------------------------------------------- /screenshots/grading2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/grading2.png -------------------------------------------------------------------------------- /screenshots/grading3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/grading3.png -------------------------------------------------------------------------------- /screenshots/grading4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/grading4.png -------------------------------------------------------------------------------- /screenshots/load_trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/load_trace.png -------------------------------------------------------------------------------- /screenshots/manual_dynamic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/manual_dynamic.png -------------------------------------------------------------------------------- /screenshots/manual_static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/manual_static.png -------------------------------------------------------------------------------- /screenshots/manual_vm_context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/manual_vm_context.png -------------------------------------------------------------------------------- /screenshots/optimizations1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/optimizations1.png -------------------------------------------------------------------------------- /screenshots/optimizations2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/optimizations2.png -------------------------------------------------------------------------------- /screenshots/optimizations_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/optimizations_success.png -------------------------------------------------------------------------------- /screenshots/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/overview.png -------------------------------------------------------------------------------- /screenshots/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/settings.png -------------------------------------------------------------------------------- /screenshots/static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/static.png -------------------------------------------------------------------------------- /screenshots/static1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/static1.png -------------------------------------------------------------------------------- /screenshots/static2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/static2.png -------------------------------------------------------------------------------- /screenshots/stub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/stub.png -------------------------------------------------------------------------------- /screenshots/stub2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/stub2.png -------------------------------------------------------------------------------- /screenshots/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anatolikalysch/VMAttack/67dcce6087163d85bbe7780e3f6e6e9e72e2212a/screenshots/switch.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | import pip 5 | import sys 6 | from os import getcwd, system, remove 7 | from shutil import copyfile 8 | 9 | 10 | def do(action, dependency): 11 | return pip.main([action, dependency]) 12 | 13 | def usage(): 14 | print "Usage: python setup.py " 15 | 16 | 17 | dependencies = ["distorm3", 'idacute'] 18 | 19 | 20 | if __name__ == '__main__': 21 | print '[*] Starting dependency handling!' 22 | stub_name = 'VMAttack_plugin_stub.py' 23 | for dependency in dependencies: 24 | try: 25 | if sys.argv[1] in ["install", "uninstall"]: 26 | retval = do(sys.argv[1], dependency) 27 | else: 28 | retval = do("install", dependency) 29 | if retval == 0: 30 | continue 31 | else: 32 | print '[!] An error occured! Please resolve issues with dependencies and try again.' 33 | 34 | except IndexError: 35 | usage() 36 | sys.exit(1) 37 | 38 | try: 39 | if sys.argv[1] == 'uninstall': 40 | with open('install_dir') as f: 41 | ida_dir = f.read() 42 | if ida_dir: 43 | remove(ida_dir + stub_name) 44 | sys.exit(0) 45 | except: 46 | pass 47 | 48 | print '[*] Setting up environment and installing Plugin.' 49 | # set up environment variable on Windows: setx Framework C:\path\to\Framework\ 50 | plugin_dir = getcwd() 51 | system('setx VMAttack %s' % plugin_dir) 52 | # copy stub into the IDA PRO Plugin directory 53 | ida_dir = raw_input('Please input full path to the IDA *plugin* folder (e.g. X:\IDA\plugins\): ') 54 | if not ida_dir.endswith(r'\\'): 55 | ida_dir += r'\\' 56 | with open('install_dir', 'w') as f: 57 | f.write(ida_dir) 58 | copyfile(stub_name, ida_dir+stub_name) 59 | print '[*] Install complete. All Done!' 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /static/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Anatoli Kalysch' 2 | -------------------------------------------------------------------------------- /ui/AboutWindow.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from UIManager import QtCore, QtWidgets 5 | 6 | class AboutWindow(QtWidgets.QDialog): 7 | def __init__(self, *args, **kwargs): 8 | super(AboutWindow, self).__init__(*args, **kwargs) 9 | self.setFixedSize(600, 250) 10 | self.setWindowTitle("About ...") 11 | self.title = "VMAttack IDA PRO Plugin" 12 | self.subtitle = "IDA Pro Plugin for static and dynamic virtualization-obfuscation analysis and deobfuscation" 13 | self.author = u"Anatoli Kalysch and Tobias Krauß" 14 | self.thanks = u"Special thanks to Johannes Götzfried for conceptual help along the way!" 15 | self.version = "Version 0.2" 16 | self.address = "Friedrich-Alexander University Erlangen-Nuremberg\n i1 Software Security Research Group \n" 17 | 18 | try: 19 | title = self.config_label(self.title, 16, True) 20 | subtitle = self.config_label(self.subtitle, 14) 21 | subtitle.move(0, title.height() + title.y() + 10) 22 | version = self.config_label(self.version, 12) 23 | version.move(0, subtitle.height() + subtitle.y() + 30) 24 | author = self.config_label(self.author, 12) 25 | author.move(0, version.height() + version.y()) 26 | thanks = self.config_label(self.thanks, 12) 27 | thanks.move(0, author.height() + author.y()) 28 | except Exception, e: 29 | print e.message 30 | 31 | self.show() 32 | 33 | def config_label(self, name, size, bold=False, alignment="center"): 34 | label = QtWidgets.QLabel(name, self) 35 | label.setWordWrap(True) 36 | font = label.font() 37 | font.setPointSize(size) 38 | font.setBold(bold) 39 | label.setFont(font) 40 | if alignment == "center": 41 | label.setAlignment(QtCore.Qt.AlignCenter) 42 | elif alignment == "right": 43 | label.setAlignment(QtCore.Qt.AlignRight) 44 | elif alignment == "left": 45 | label.setAlignment(QtCore.Qt.AlignLeft) 46 | label.setFixedWidth(600) 47 | 48 | return label 49 | -------------------------------------------------------------------------------- /ui/BBGraphViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from idaapi import * 5 | from idc import * 6 | from lib.Instruction import Instruction 7 | 8 | 9 | class GraphCloser(action_handler_t): 10 | def __init__(self, graph): 11 | action_handler_t.__init__(self) 12 | self.graph = graph 13 | 14 | 15 | def activate(self, ctx): 16 | self.graph.Close() 17 | 18 | 19 | def update(self, ctx): 20 | return AST_ENABLE_ALWAYS 21 | 22 | 23 | # class ColorChanger(action_handler_t): 24 | # def __init__(self, graph): 25 | # action_handler_t.__init__(self) 26 | # self.graph = graph 27 | # 28 | # 29 | # def activate(self, ctx): 30 | # self.graph.color = self.graph.color ^ 0xfffff0 31 | # self.graph.Refresh() 32 | # return 1 33 | # 34 | # 35 | # def update(self, ctx): 36 | # return AST_ENABLE_ALWAYS 37 | 38 | 39 | def get_jmp_addr(bb): 40 | """ 41 | @param bb List of PseudoInstructions of one basic block 42 | @return Address of jump instruction in this basic block 43 | """ 44 | 45 | for inst in bb: 46 | if inst.inst_type == 'jmp_T': 47 | return inst.addr 48 | return None 49 | 50 | 51 | class CallbackExtension(action_handler_t): 52 | def __init__(self, graph, bb_lst, jmp_addrs, basic_blocks, func_addr): 53 | action_handler_t.__init__(self) 54 | self.graph = graph 55 | self.bb_lst = bb_lst 56 | self.jmp_addrs = jmp_addrs 57 | self.basic_blocks = basic_blocks 58 | self.func_addr = func_addr 59 | 60 | def write_dot_rep(self, bb_lst, jmp_addrs, basic_blocks, func_addr): 61 | """ 62 | @brief Write dot graph representation of obfuscated function 63 | @param bb_lst List of basic block lists 64 | @param jmp_addrs List of Tuples (jump address, address of jmp instruction) 65 | @param basic_blocks List of Tuples: (address start basic block, 66 | address end basic block) 67 | @param func_addr Addresse of the deobfuscated function 68 | """ 69 | file_name = GetInputFile() 70 | char_num = file_name.find('.') 71 | file1 = open(file_name[:char_num] + '{0:#x}_dot'.format(func_addr), 'w') 72 | file1.write('digraph G {\n') 73 | file1.write('node [shape=box fontname=Courier];\n') 74 | depend = '' 75 | for node, bb in enumerate(bb_lst): 76 | if bb == []: 77 | continue 78 | label = '' 79 | for item in bb: 80 | label += ('{0:#x} '.format(item.addr) + 81 | str(item)[:len(str(item)) - 1] + 82 | ' ' + item.comment + '\l') 83 | file1.write('bb' + str(node) + 84 | ' [label=\"' + label + '\", style=filled, color=\"#' + 85 | '{0:#x}'.format([0xddddff, 0xffdddd, 0xddffdd, 0xffddff, 0xffffdd, 0xddffff][node % 3])[2:] + '\"]\n') 86 | # check whether basic block has a ret instruction 87 | if (lambda bb: True if 'ret_T' in map(lambda inst: inst.inst_type, bb) else False)(bb): 88 | continue 89 | jmp_addr = get_jmp_addr(bb) 90 | if jmp_addr == None: 91 | depend += 'bb' + str(node) + '-> bb' + str(node + 1) + ';\n' 92 | else: 93 | jmp_locs = [jmp_to for jmp_to, j_addr in jmp_addrs if j_addr == jmp_addr] 94 | for loc in jmp_locs: 95 | for pos, (saddr, eaddr) in enumerate(basic_blocks): 96 | if loc >= saddr and loc < eaddr: 97 | depend += ('bb' + str(node) + 98 | '-> bb' + str(pos) + ';\n') 99 | file1.write(depend) 100 | file1.write('}') 101 | file1.close() 102 | 103 | def activate(self, ctx): 104 | self.write_dot_rep(self.bb_lst, self.jmp_addrs, self.basic_blocks, self.func_addr) 105 | return 1 106 | 107 | 108 | def update(self, ctx): 109 | return AST_ENABLE_ALWAYS 110 | 111 | 112 | class BBGraph(GraphViewer): 113 | def __init__(self, n, e, bb_lst, jmp_addrs, basic_blocks, func_addr): 114 | self.title = "Absract VMFunctions Structure" 115 | GraphViewer.__init__(self, self.title) 116 | self.n = n 117 | self.e = e 118 | self.n_dict = {} 119 | self.bb_lst = bb_lst 120 | self.jmp_addrs = jmp_addrs 121 | self.basic_blocks = basic_blocks 122 | self.func_addr = func_addr 123 | for inst in bb_lst: 124 | if isinstance(inst, Instruction): 125 | print inst.__str__() 126 | 127 | def OnRefresh(self): 128 | self.Clear() 129 | run_var = 0 130 | for start, end in self.basic_blocks: 131 | self.n_dict[run_var] = self.AddNode(self.CreateNode(start, end)) 132 | run_var += 1 133 | for tupel in self.e: 134 | self.AddEdge(self.n_dict[tupel[0]], self.n_dict[tupel[1]]) 135 | 136 | return True 137 | 138 | 139 | def OnGetText(self, node_id): 140 | return str(self[node_id]) 141 | 142 | 143 | def Show(self): 144 | GraphViewer.Show(self) 145 | # graph closer 146 | actname = "graph_closer:%s" % self.title 147 | register_action(action_desc_t(actname, "Close: %s" % self.title, GraphCloser(self))) 148 | attach_action_to_popup(self.GetTCustomControl(), None, actname) 149 | 150 | # # color changer 151 | # actname = "color_changer:%s" % self.title 152 | # register_action(action_desc_t(actname, "Change colors: %s" % self.title, ColorChanger(self))) 153 | # attach_action_to_popup(self.GetTCustomControl(), None, actname) 154 | 155 | # export graph to dot file 156 | actname = "export_dot_file:%s" % self.title 157 | register_action(action_desc_t(actname, "Export as dot file: %s" % self.title, CallbackExtension(self, self.bb_lst, self.jmp_addrs, self.basic_blocks, self.func_addr))) 158 | attach_action_to_popup(self.GetTCustomControl(), None, actname) 159 | 160 | return True 161 | 162 | def CreateNode(self, start, end): 163 | return ''.join('%s\n' % comment.rstrip('\t\n') for comment in filter(None, [Comment(addr) for addr in [ea for ea in xrange(start, end, 1)]])) 164 | 165 | 166 | def show_graph(n, e, bb_lst, jmp_addrs, basic_blocks, func_addr): 167 | g = BBGraph(n, e, bb_lst, jmp_addrs, basic_blocks, func_addr) 168 | if g.Show(): 169 | return g 170 | else: 171 | return None -------------------------------------------------------------------------------- /ui/ClusterViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | import re 5 | 6 | 7 | from copy import deepcopy 8 | from ui.NotifyProgress import NotifyProgress 9 | from _collections import defaultdict, deque 10 | from lib.VMRepresentation import get_vmr 11 | from dynamic.TraceRepresentation import Traceline 12 | from ui.PluginViewer import PluginViewer 13 | from ui.UIManager import QtGui, QtCore, QtWidgets 14 | # from PyQt5 import QtGui, QtCore, QtWidgets 15 | 16 | from lib.TraceAnalysis import cluster_removal 17 | 18 | from idaapi import is_basic_block_end 19 | from idc import AskLong 20 | 21 | 22 | ########################### 23 | ### CLUSTERING ANALYSIS ### 24 | ########################### 25 | class ClusterViewer(PluginViewer): 26 | def __init__(self, clustered_trace, bb_func, ctx_reg_size, title='Clustering Analysis Result', save_func=None): 27 | # context should be a dictionary containing the backward traced result of each relevant register 28 | super(ClusterViewer, self).__init__(title) 29 | self.orig_trace = clustered_trace 30 | self.trace = deepcopy(self.orig_trace) 31 | self.bb_func = bb_func 32 | self.ctx_reg_size = ctx_reg_size 33 | self.save = save_func 34 | self.undo_stack = deque([deepcopy(self.trace)], maxlen=3) 35 | 36 | def PopulateModel(self): 37 | self.Clean() 38 | vmr = get_vmr() 39 | w = NotifyProgress() 40 | w.show() 41 | ctr = 0 42 | max = len(self.trace) 43 | 44 | # present clustering analysis in viewer 45 | prev_ctx = defaultdict(lambda: 0) 46 | for line in self.trace: 47 | 48 | ctr += 1 49 | w.pbar_set(int(float(ctr) / float(max) * 100)) 50 | 51 | if isinstance(line, Traceline): 52 | tid = QtGui.QStandardItem('%s' % line.thread_id) 53 | addr = QtGui.QStandardItem('%x' % line.addr) 54 | disasm = QtGui.QStandardItem(line.disasm_str()) 55 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 56 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx if line.ctx is not None)) 57 | prev_ctx = line.ctx 58 | self.sim.appendRow([tid, addr, disasm, comment, context]) 59 | else: 60 | cluster_node = QtGui.QStandardItem('Cluster %x-%x' % (line[0].addr, line[-1].addr)) 61 | self.sim.appendRow(cluster_node) 62 | if vmr.bb: 63 | cluster = line 64 | bbs = [] 65 | bb = [] 66 | # subdivide the clusters by basic blocks 67 | for line in cluster: 68 | assert isinstance(line, Traceline) 69 | if line.disasm[0].startswith('j'): 70 | bb.append(line) 71 | bbs.append(bb) 72 | bb = [] 73 | else: 74 | bb.append(line) 75 | 76 | for bb in bbs: 77 | 78 | bb_sum = self.bb_func(bb, self.ctx_reg_size, prev_ctx) 79 | bb_node = QtGui.QStandardItem( 80 | 'BB%s Summary %x-%x: %s\t%s\t%s' % (bbs.index(bb), bb[0].addr, bb[-1].addr, 81 | ''.join('%s ; ' % (''.join('%s, ' % c for c in line)) for line in bb_sum.disasm), 82 | ''.join('%s, ' % c for c in filter(None, bb_sum.comment) if bb_sum.comment is not None), 83 | ''.join('%s:%s ' % (c, bb_sum.ctx[c]) for c in bb_sum.ctx if bb_sum.ctx is not None))) 84 | for line in bb: 85 | tid = QtGui.QStandardItem('%s' % line.thread_id) 86 | addr = QtGui.QStandardItem('%x' % line.addr) 87 | disasm = QtGui.QStandardItem(line.disasm_str()) 88 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 89 | context = QtGui.QStandardItem( 90 | ''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx if line.ctx is not None)) 91 | bb_node.appendRow([tid, addr, disasm, comment, context]) 92 | cluster_node.appendRow(bb_node) 93 | self.treeView.setFirstColumnSpanned(bbs.index(bb), cluster_node.index(), True) 94 | 95 | prev_ctx = bb[-1].ctx 96 | else: 97 | for l in line: 98 | tid = QtGui.QStandardItem('%s' % l.thread_id) 99 | addr = QtGui.QStandardItem('%x' % l.addr) 100 | disasm = QtGui.QStandardItem(l.disasm_str()) 101 | comment = QtGui.QStandardItem(''.join(c for c in l.comment if l.comment is not None)) 102 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, l.ctx[c]) for c in l.ctx if l.ctx is not None)) 103 | cluster_node.appendRow([tid, addr, disasm, comment, context]) 104 | 105 | w.close() 106 | 107 | self.treeView.resizeColumnToContents(0) 108 | self.treeView.resizeColumnToContents(1) 109 | self.treeView.resizeColumnToContents(2) 110 | self.treeView.resizeColumnToContents(3) 111 | self.treeView.resizeColumnToContents(4) 112 | 113 | def Clean(self): 114 | self.sim.clear() 115 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 116 | 117 | def PopulateForm(self): 118 | ### init widgets 119 | # model 120 | self.sim = QtGui.QStandardItemModel() 121 | 122 | # tree view 123 | self.treeView = QtWidgets.QTreeView() 124 | self.treeView.setExpandsOnDoubleClick(True) 125 | self.treeView.setSortingEnabled(False) 126 | self.treeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) 127 | self.treeView.setToolTip('Filter instructions/clusters/basic blocks from trace by double clicking on them.') 128 | # Context menus 129 | self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 130 | self.treeView.customContextMenuRequested.connect(self.OnCustomContextMenu) 131 | 132 | self.treeView.doubleClicked.connect(self.ItemDoubleClickSlot) 133 | self.treeView.setModel(self.sim) 134 | 135 | ### populate widgets 136 | # fill model with data 137 | self.PopulateModel() 138 | 139 | # self.treeView.setFirstColumnSpanned(0, self.treeView.rootIndex(), True) 140 | # finalize layout 141 | layout = QtWidgets.QGridLayout() 142 | layout.addWidget(self.treeView) 143 | 144 | self.parent.setLayout(layout) 145 | 146 | def isVisible(self): 147 | try: 148 | return self.treeView.isVisible() 149 | except: 150 | return False 151 | 152 | @QtCore.pyqtSlot(QtCore.QModelIndex) 153 | def ItemDoubleClickSlot(self, index): 154 | """ 155 | TreeView DoubleClicked Slot. 156 | @param index: QModelIndex object of the clicked tree index item. 157 | @return: 158 | """ 159 | 160 | # fetch the clicked string 161 | s = index.data(0) 162 | print s 163 | line_index = [] 164 | inner_cluster_index = [] 165 | if s.startswith('Cluster'): 166 | addrs = re.findall(r'Cluster (.*-.*)', s)[0] 167 | addrs = addrs.split('-') 168 | start = int(addrs[0], 16) 169 | end = int(addrs[1], 16) 170 | for line in self.trace: 171 | if isinstance(line, Traceline): 172 | continue 173 | elif isinstance(line, list): 174 | if start == line[0].addr and end == line[-1].addr: 175 | line_index.append(line) 176 | 177 | elif s.startswith('BB'): 178 | addrs = re.findall(r'BB.*Summary (.*-.*): .*', s)[0] 179 | addrs = addrs.split('-') 180 | bad_range = range(int(addrs[0], 16), int(addrs[1], 16)) 181 | for line in self.trace: 182 | if isinstance(line, Traceline): 183 | continue 184 | elif isinstance(line, list): 185 | for l in line: 186 | if l.addr in bad_range: 187 | inner_cluster_index.append(l) 188 | else: # assume trace line 189 | for line in self.trace: 190 | if isinstance(line, Traceline) and line.to_str_line().__contains__(s): 191 | line_index.append(line) 192 | elif isinstance(line, list): 193 | for l in line: 194 | if l.to_str_line().__contains__(s): 195 | line_index.append(line) 196 | 197 | self.undo_stack.append(deepcopy(self.trace)) 198 | 199 | for line in line_index: 200 | self.trace.remove(line) 201 | 202 | for line in self.trace: 203 | if isinstance(line, list): 204 | for l in line: 205 | if l in inner_cluster_index: 206 | line.remove(l) 207 | 208 | self.PopulateModel() 209 | 210 | @QtCore.pyqtSlot(QtCore.QPoint) 211 | def OnCustomContextMenu(self, point): 212 | menu = QtWidgets.QMenu() 213 | init_index = self.treeView.indexAt(point) 214 | index = self.treeView.indexAt(point) 215 | level = 0 216 | while index.parent().isValid(): 217 | index = index.parent() 218 | level += 1 219 | 220 | text = 'Remove Line' 221 | 222 | if level == 0: 223 | text = "Remove Cluster / Line" 224 | elif level == 1 and get_vmr().bb: 225 | text = "Remove Basic Block" 226 | elif level == 2: 227 | text = "Remove Line" 228 | try: 229 | action_remove = QtWidgets.QAction(text, self.treeView) 230 | action_remove.triggered.connect(lambda: self.ItemDoubleClickSlot(init_index)) 231 | menu.addAction(action_remove) 232 | menu.addSeparator() 233 | except: 234 | print '[*] An Exception occured, remove action could not be added to the menu!' 235 | # Actions 236 | action_remove_threshold = QtWidgets.QAction('Remove several clusters...', self.treeView) 237 | action_remove_threshold.triggered.connect(self.ClusterRemoval) 238 | action_undo = QtWidgets.QAction('Undo', self.treeView) 239 | action_undo.triggered.connect(self.Undo) 240 | action_restore = QtWidgets.QAction('Restore original trace', self.treeView) 241 | action_restore.triggered.connect(self.Restore) 242 | action_export_trace = QtWidgets.QAction('Export this trace ...', self.treeView) 243 | action_export_trace.triggered.connect(self.SaveTrace) 244 | action_close_viewer = QtWidgets.QAction('Close Viewer', self.treeView) 245 | action_close_viewer.triggered.connect(lambda: self.Close(4)) 246 | 247 | # add actions to menu 248 | menu.addAction(action_remove_threshold) 249 | menu.addAction(action_undo) 250 | menu.addAction(action_restore) 251 | menu.addAction(action_export_trace) 252 | menu.addSeparator() 253 | menu.addAction(action_close_viewer) 254 | 255 | menu.exec_(self.treeView.viewport().mapToGlobal(point)) 256 | 257 | @QtCore.pyqtSlot(str) 258 | def ClusterRemoval(self): 259 | threshold = AskLong(1, 'How many most common clusters do you want removed?') 260 | self.undo_stack.append(deepcopy(self.trace)) 261 | self.trace = cluster_removal(deepcopy(self.trace), threshold=threshold) 262 | self.PopulateModel() 263 | 264 | @QtCore.pyqtSlot(str) 265 | def SaveTrace(self): 266 | if self.save is not None: 267 | self.save(self.trace) 268 | 269 | @QtCore.pyqtSlot(str) 270 | def Undo(self): 271 | self.trace = self.undo_stack[-1] 272 | self.PopulateModel() 273 | 274 | @QtCore.pyqtSlot(str) 275 | def Restore(self): 276 | self.undo_stack = deque([deepcopy(self.trace)], maxlen=3) 277 | self.trace = deepcopy(self.orig_trace) 278 | self.PopulateModel() 279 | -------------------------------------------------------------------------------- /ui/GradingViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from ui.NotifyProgress import NotifyProgress 3 | 4 | __author__ = 'Anatoli Kalysch' 5 | 6 | from dynamic.TraceRepresentation import Traceline 7 | from idc import AskLong 8 | from ui.PluginViewer import PluginViewer 9 | from ui.UIManager import QtGui, QtCore, QtWidgets 10 | # from PyQt5 import QtGui, QtCore, QtWidgets 11 | 12 | 13 | ########################### 14 | ### GradingSys Analysis ### 15 | ########################### 16 | class GradingViewer(PluginViewer): 17 | def __init__(self, trace, title='Grading System Analysis', **kwargs): 18 | # context should be a dictionary containing the backward traced result of each relevant register 19 | super(GradingViewer, self).__init__(title) 20 | self.trace = trace 21 | self.save = kwargs.get('save', None) 22 | self.grades = kwargs.get('grades', None) 23 | if self.grades is None: 24 | self.grades = self.GetGrades() 25 | 26 | def GetGrades(self): 27 | return set([line.grade for line in self.trace]) 28 | 29 | def PopulateModel(self, threshold): 30 | self.CleanModel() 31 | 32 | w = NotifyProgress() 33 | ctr = 0 34 | max = len(self.trace) 35 | prev = None 36 | for line in self.trace: 37 | assert isinstance(line, Traceline) 38 | 39 | if prev is not None and threshold > 2: 40 | if prev is not None: 41 | grade = QtGui.QStandardItem(' ') 42 | tid = QtGui.QStandardItem(' ') 43 | addr = QtGui.QStandardItem(' ') 44 | disasm = QtGui.QStandardItem('previous CPU context:') 45 | comment = QtGui.QStandardItem(' ') 46 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, prev.ctx[c]) for c in prev.ctx.keys() if prev.ctx is not None)) 47 | self.sim.appendRow([grade, tid, addr, disasm, comment, context]) 48 | grade = QtGui.QStandardItem('%s' % line.grade) 49 | tid = QtGui.QStandardItem('%s' % line.thread_id) 50 | addr = QtGui.QStandardItem('%x' % line.addr) 51 | disasm = QtGui.QStandardItem(line.disasm_str()) 52 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 53 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx.keys() if line.ctx is not None)) 54 | 55 | self.sim.appendRow([grade, tid, addr, disasm, comment, context]) 56 | 57 | ctr += 1 58 | w.pbar_set(int(float(ctr) / float(max) * 100)) 59 | prev = line 60 | w.close() 61 | 62 | self.treeView.resizeColumnToContents(0) 63 | self.treeView.resizeColumnToContents(1) 64 | self.treeView.resizeColumnToContents(2) 65 | self.treeView.resizeColumnToContents(3) 66 | self.treeView.resizeColumnToContents(4) 67 | self.treeView.resizeColumnToContents(5) 68 | 69 | def CleanModel(self): 70 | self.sim.clear() 71 | self.sim.setHorizontalHeaderLabels(['Grade','ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 72 | 73 | 74 | def PopulateForm(self): 75 | ### init widgets 76 | # model 77 | self.sim = QtGui.QStandardItemModel() 78 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 79 | 80 | # toolbar 81 | self.ftb = QtWidgets.QToolBar() 82 | self.stb = QtWidgets.QToolBar() 83 | 84 | # tree view 85 | self.treeView = QtWidgets.QTreeView() 86 | self.treeView.setToolTip('Double click a grade to filter') 87 | self.treeView.setExpandsOnDoubleClick(True) 88 | self.treeView.setSortingEnabled(False) 89 | self.treeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) 90 | # Context menus 91 | self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 92 | self.treeView.customContextMenuRequested.connect(self.OnCustomContextMenu) 93 | 94 | self.treeView.doubleClicked.connect(self.ItemDoubleClickSlot) 95 | self.treeView.setModel(self.sim) 96 | 97 | ### populate widgets 98 | # fill model with data 99 | self.PopulateModel(0) 100 | # finalize layout 101 | layout = QtWidgets.QGridLayout() 102 | layout.addWidget(self.treeView) 103 | 104 | 105 | self.parent.setLayout(layout) 106 | 107 | 108 | def IsVisible(self): 109 | try: 110 | return self.treeView.isVisible() 111 | except: 112 | return False 113 | 114 | @QtCore.pyqtSlot(QtCore.QModelIndex) 115 | def ItemDoubleClickSlot(self, index): 116 | """ 117 | TreeView DoubleClicked Slot. 118 | @param index: QModelIndex object of the clicked tree index item. 119 | @return: 120 | """ 121 | # fetch the clicked string 122 | try: 123 | instr = int(index.data(0)) 124 | except: 125 | instr = None 126 | if instr in self.grades: 127 | # if instr is an instruction, remove trace lines with said instruction 128 | self.PopulateModel(instr) 129 | 130 | 131 | @QtCore.pyqtSlot(QtCore.QPoint) 132 | def OnCustomContextMenu(self, point): 133 | menu = QtWidgets.QMenu() 134 | 135 | # Actions 136 | action_set_t = QtWidgets.QAction('Set grade threshold...', self.treeView) 137 | action_set_t.triggered.connect(self.SetThreshold) 138 | action_restore = QtWidgets.QAction('Show All', self.treeView) 139 | action_restore.triggered.connect(self.Restore) 140 | action_export_trace = QtWidgets.QAction('Export this trace...', self.treeView) 141 | action_export_trace.triggered.connect(self.SaveTrace) 142 | action_close_viewer = QtWidgets.QAction('Close Viewer', self.treeView) 143 | action_close_viewer.triggered.connect(lambda: self.Close(4)) 144 | # add actions to menu 145 | menu.addAction(action_set_t) 146 | menu.addAction(action_restore) 147 | menu.addAction(action_export_trace) 148 | menu.addSeparator() 149 | menu.addAction(action_close_viewer) 150 | 151 | menu.exec_(self.treeView.viewport().mapToGlobal(point)) 152 | 153 | @QtCore.pyqtSlot(str) 154 | def SetThreshold(self): 155 | threshold = AskLong(-1, 'There are a total of %s grades: %s. Specify a threshold which lines to display:' % (len(self.grades), ''.join('%s ' % c for c in self.grades))) 156 | if threshold in self.grades: 157 | self.PopulateModel(threshold) 158 | 159 | 160 | @QtCore.pyqtSlot(str) 161 | def SaveTrace(self): 162 | if self.save is not None: 163 | self.save(self.trace) 164 | 165 | @QtCore.pyqtSlot(str) 166 | def Restore(self): 167 | self.PopulateModel(0) -------------------------------------------------------------------------------- /ui/NotifyProgress.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from UIManager import QtWidgets 5 | 6 | class NotifyProgress(QtWidgets.QWidget): 7 | def __init__(self, name='current', *args, **kwargs): 8 | super(NotifyProgress, self).__init__(*args, **kwargs) 9 | self.analysis = name 10 | self.pbar = QtWidgets.QProgressBar(self) 11 | self.pbar.setGeometry(30, 40, 370, 25) 12 | self.value = 0 13 | self.setFixedSize(400, 100) 14 | self.setWindowTitle('Running %s Analysis...' % self.analysis) 15 | 16 | def pbar_update(self, value): 17 | self.value += value 18 | if self.value > 100: 19 | self.value = 100 20 | self.close() 21 | self.pbar.setValue(self.value) 22 | 23 | def pbar_set(self, value): 24 | self.pbar.setValue(value) 25 | 26 | -------------------------------------------------------------------------------- /ui/OptimizationViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from _collections import deque 5 | from copy import deepcopy 6 | 7 | from dynamic.TraceRepresentation import Traceline, Trace 8 | from lib.Register import get_reg_by_size 9 | from lib.TraceAnalysis import repetition_clustering, find_virtual_regs, create_bb_diff 10 | from lib.TraceOptimizations import optimizations, optimization_names, optimization_selective_register_folding 11 | from ui.NotifyProgress import NotifyProgress 12 | from ui.PluginViewer import PluginViewer 13 | from ui.UIManager import QtGui, QtCore, QtWidgets 14 | from ui.UIManager import ClusterViewer 15 | # from PyQt5 import QtGui, QtCore, QtWidgets 16 | 17 | 18 | ########################### 19 | ### Trace Optimizations ### 20 | ########################### 21 | class OptimizationViewer(PluginViewer): 22 | def __init__(self, trace, title='Optimizations', **kwargs): 23 | # context should be a dictionary containing the backward traced result of each relevant register 24 | super(OptimizationViewer, self).__init__(title) 25 | self.orig_trace = trace 26 | self.trace = deepcopy(trace) 27 | self.undo_stack = deque([deepcopy(trace), deepcopy(trace), deepcopy(trace)], maxlen=3) 28 | self.opti_map = dict(zip(optimization_names, optimizations)) 29 | self.order = [] 30 | self.foldable_regs = [] 31 | self.save = kwargs.get('save', None) 32 | 33 | 34 | def PopulateModel(self, trace): 35 | self.CleanModel() 36 | 37 | w = NotifyProgress() 38 | w.show() 39 | ctr = 0 40 | max = len(trace) 41 | 42 | for line in trace: 43 | 44 | assert isinstance(line, Traceline) 45 | tid = QtGui.QStandardItem('%s' % line.thread_id) 46 | addr = QtGui.QStandardItem('%x' % line.addr) 47 | disasm = QtGui.QStandardItem(line.disasm_str()) 48 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 49 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx.keys() if line.ctx is not None)) 50 | 51 | ctr += 1 52 | w.pbar_set(int(float(ctr) / float(max) * 100)) 53 | 54 | self.sim.appendRow([tid, addr, disasm, comment, context]) 55 | 56 | w.close() 57 | 58 | self.treeView.resizeColumnToContents(0) 59 | self.treeView.resizeColumnToContents(1) 60 | self.treeView.resizeColumnToContents(2) 61 | self.treeView.resizeColumnToContents(3) 62 | self.treeView.resizeColumnToContents(4) 63 | 64 | def CleanModel(self): 65 | self.sim.clear() 66 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 67 | 68 | def PopulateOptimizationsToolbar(self): 69 | self.ftb.addWidget(QtWidgets.QLabel('Available Optimizations (check to run on trace): ')) 70 | self.cpcb = QtWidgets.QCheckBox(optimization_names[0]) 71 | self.cpcb.stateChanged.connect(lambda: self.OptimizeTrace(self.cpcb)) 72 | self.ftb.addWidget(self.cpcb) 73 | self.ftb.addSeparator() 74 | 75 | self.sacb = QtWidgets.QCheckBox(optimization_names[1]) 76 | self.sacb.stateChanged.connect(lambda: self.OptimizeTrace(self.sacb)) 77 | self.ftb.addWidget(self.sacb) 78 | self.ftb.addSeparator() 79 | 80 | self.oscb = QtWidgets.QCheckBox(optimization_names[2]) 81 | self.oscb.stateChanged.connect(lambda: self.OptimizeTrace(self.oscb)) 82 | self.ftb.addWidget(self.oscb) 83 | self.ftb.addSeparator() 84 | 85 | self.uocb = QtWidgets.QCheckBox(optimization_names[3]) 86 | self.uocb.stateChanged.connect(lambda: self.OptimizeTrace(self.uocb)) 87 | self.ftb.addWidget(self.uocb) 88 | self.ftb.addSeparator() 89 | 90 | self.pcb = QtWidgets.QCheckBox(optimization_names[4]) 91 | self.pcb.stateChanged.connect(lambda: self.OptimizeTrace(self.pcb)) 92 | self.ftb.addWidget(self.pcb) 93 | self.ftb.addSeparator() 94 | 95 | def PopulateSelectiveRegsToolbar(self): 96 | self.stb.addWidget(QtWidgets.QLabel('Selective Register Folding: ')) 97 | assert isinstance(self.trace, Trace) 98 | if self.trace.ctx_reg_size == 32: 99 | for i in range(8): 100 | self.foldable_regs.append(QtWidgets.QCheckBox(get_reg_by_size(i, self.trace.ctx_reg_size))) 101 | self.foldable_regs[-1].stateChanged.connect(lambda: self.FoldRegs()) 102 | self.stb.addWidget(self.foldable_regs[-1]) 103 | self.stb.addSeparator() 104 | elif self.trace.ctx_reg_size == 64: 105 | for i in range(16): 106 | self.foldable_regs.append(QtWidgets.QCheckBox(get_reg_by_size(i, self.trace.ctx_reg_size))) 107 | self.foldable_regs[-1].stateChanged.connect(lambda: self.FoldRegs()) 108 | self.stb.addWidget(self.foldable_regs[-1]) 109 | self.stb.addSeparator() 110 | 111 | 112 | def PopulateForm(self): 113 | ### init widgets 114 | # model 115 | self.sim = QtGui.QStandardItemModel() 116 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 117 | 118 | # toolbar 119 | self.ftb = QtWidgets.QToolBar() 120 | self.stb = QtWidgets.QToolBar() 121 | 122 | # tree view 123 | self.treeView = QtWidgets.QTreeView() 124 | self.treeView.setToolTip('Filter instructions from trace by double clicking on them.') 125 | self.treeView.setExpandsOnDoubleClick(True) 126 | self.treeView.setSortingEnabled(False) 127 | self.treeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) 128 | # Context menus 129 | self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 130 | self.treeView.customContextMenuRequested.connect(self.OnCustomContextMenu) 131 | 132 | self.treeView.doubleClicked.connect(self.ItemDoubleClickSlot) 133 | self.treeView.setModel(self.sim) 134 | 135 | ### populate widgets 136 | # fill model with data 137 | self.PopulateModel(self.orig_trace) 138 | # fill toolbars with data 139 | self.PopulateOptimizationsToolbar() 140 | self.PopulateSelectiveRegsToolbar() 141 | # finalize layout 142 | layout = QtWidgets.QGridLayout() 143 | layout.addWidget(self.ftb) 144 | layout.addWidget(self.stb) 145 | layout.addWidget(self.treeView) 146 | 147 | 148 | self.parent.setLayout(layout) 149 | 150 | def OptimizeTrace(self, check_box): 151 | self.undo_stack.append(deepcopy(self.trace)) 152 | self.last_cb = check_box 153 | optimization = self.opti_map[check_box.text()] 154 | if check_box.isChecked(): 155 | self.order.append(optimization) 156 | self.trace = optimization(self.trace) 157 | else: 158 | try: 159 | self.order.remove(optimization) 160 | except: 161 | pass 162 | self.trace = deepcopy(self.orig_trace) 163 | for optimization in self.order: 164 | self.trace = optimization(self.trace) 165 | self.FoldRegs() 166 | 167 | def FoldRegs(self): 168 | self.undo_stack.append(deepcopy(self.trace)) 169 | folded_regs = [] 170 | for check_box in self.foldable_regs: 171 | if check_box.isChecked(): 172 | folded_regs.append(check_box.text()) 173 | self.trace = optimization_selective_register_folding(self.trace, folded_regs) 174 | 175 | self.PopulateModel(self.trace) 176 | 177 | def IsVisible(self): 178 | try: 179 | return self.treeView.isVisible() 180 | except: 181 | return False 182 | 183 | @QtCore.pyqtSlot(QtCore.QModelIndex) 184 | def ItemDoubleClickSlot(self, index): 185 | """ 186 | TreeView DoubleClicked Slot. 187 | @param index: QModelIndex object of the clicked tree index item. 188 | @return: 189 | """ 190 | # fetch the clicked string 191 | instr = index.data(0) 192 | # if instr is an instruction, remove trace lines with said instruction 193 | self.trace = Trace(tr=[line for line in self.trace if line.disasm_str() != instr]) 194 | 195 | self.PopulateModel(self.trace) 196 | 197 | @QtCore.pyqtSlot(QtCore.QPoint) 198 | def OnCustomContextMenu(self, point): 199 | menu = QtWidgets.QMenu() 200 | # Actions 201 | action_undo = QtWidgets.QAction('Undo', self.treeView) 202 | action_undo.triggered.connect(self.Undo) 203 | action_restore = QtWidgets.QAction('Restore original trace', self.treeView) 204 | action_restore.triggered.connect(self.Restore) 205 | action_forward_to_clustering = QtWidgets.QAction("Open in Clustering Analysis", self.treeView) 206 | action_forward_to_clustering.triggered.connect(self.ClusterForward) 207 | action_export_trace = QtWidgets.QAction('Export this trace...', self.treeView) 208 | action_export_trace.triggered.connect(self.SaveTrace) 209 | action_close_viewer = QtWidgets.QAction('Close Viewer', self.treeView) 210 | action_close_viewer.triggered.connect(lambda: self.Close(4)) 211 | # add actions to menu 212 | menu.addAction(action_undo) 213 | menu.addAction(action_restore) 214 | menu.addAction(action_forward_to_clustering) 215 | menu.addAction(action_export_trace) 216 | menu.addSeparator() 217 | menu.addAction(action_close_viewer) 218 | 219 | menu.exec_(self.treeView.viewport().mapToGlobal(point)) 220 | 221 | @QtCore.pyqtSlot(str) 222 | def ClusterForward(self): 223 | # cluster 224 | vr = find_virtual_regs(deepcopy(self.trace)) 225 | cluster = repetition_clustering(deepcopy(self.trace)) 226 | v0 = ClusterViewer(cluster, create_bb_diff, self.trace.ctx_reg_size) 227 | v0.Show() 228 | # Do not display StackChangeViewer. After the user worked on the trace it will be heavily malformed and missing crutial information for a stack change analysis, so the stack change view will do more harm than good. 229 | 230 | @QtCore.pyqtSlot(str) 231 | def SaveTrace(self): 232 | if self.save is not None: 233 | self.save(self.trace) 234 | 235 | @QtCore.pyqtSlot(str) 236 | def Undo(self): 237 | self.trace = self.undo_stack[-1] 238 | self.last_cb.setCheckState(QtCore.Qt.Unchecked) 239 | self.PopulateModel(self.trace) 240 | 241 | @QtCore.pyqtSlot(str) 242 | def Restore(self): 243 | self.undo_stack = [self.orig_trace] 244 | self.order = [] 245 | self.cpcb.setCheckState(QtCore.Qt.Unchecked) 246 | self.sacb.setCheckState(QtCore.Qt.Unchecked) 247 | self.oscb.setCheckState(QtCore.Qt.Unchecked) 248 | self.uocb.setCheckState(QtCore.Qt.Unchecked) 249 | self.pcb.setCheckState(QtCore.Qt.Unchecked) 250 | try: 251 | for check_box in self.foldable_regs: 252 | check_box.setCheckState(QtCore.Qt.Unchecked) 253 | except: 254 | pass 255 | self.trace = deepcopy(self.orig_trace) 256 | self.PopulateModel(self.trace) 257 | -------------------------------------------------------------------------------- /ui/PluginViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from idaapi import PluginForm, msg 5 | from ui.UIManager import form_to_widget 6 | 7 | 8 | class PluginViewer(PluginForm): 9 | def __init__(self, title): 10 | super(PluginViewer, self).__init__() 11 | self.title = title 12 | 13 | def Show(self, **kwargs): 14 | return PluginForm.Show(self, self.title, options=PluginForm.FORM_PERSIST) 15 | 16 | def OnCreate(self, form): 17 | # Get parent widget 18 | self.parent = form_to_widget(form) 19 | self.PopulateForm() 20 | 21 | def PopulateForm(self): 22 | ### do stuff 23 | pass 24 | 25 | def OnClose(self, form): 26 | msg("Closed %s.\n" % self.title) -------------------------------------------------------------------------------- /ui/SettingsWindow.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from idaapi import Form, BADADDR 5 | from lib.VMRepresentation import get_vmr, VMContext 6 | 7 | 8 | class SettingsView(Form): 9 | def __init__(self): 10 | Form.__init__(self, ("STARTITEM 0\n" 11 | "BUTTON YES* Confirm\n" 12 | "BUTTON CANCEL Cancel\n" 13 | "VMAttack Settings\n" 14 | "\n" 15 | "VM Values:\n" 16 | "\n" 17 | "\n" 18 | "\n" 19 | "\n" 20 | "\n" 21 | "Clustering:\n" 22 | "\n" 23 | "{cClusterValues}>\n" 24 | "\n" 25 | "\n" 26 | "Grading Automation:\n" 27 | "\n" 28 | "\n" 29 | "\n" 30 | "\n" 31 | "\n" 32 | "\n" 33 | "\n" 34 | "Dynamic Analysis:\n" 35 | "\n" 36 | '{cDynamicValues}>\n' 37 | ), { 38 | 'cClusterValues': Form.ChkGroupControl(("rShowBB", "rGreedyCluster")), 39 | 'cDynamicValues': Form.ChkGroupControl(('rStepInSysLibs', 'rFuncParams')), 40 | 'iClusterHeu': Form.NumericInput(tp=Form.FT_DEC), 41 | 'iInOut': Form.NumericInput(tp=Form.FT_DEC), 42 | 'iClu': Form.NumericInput(tp=Form.FT_DEC), 43 | 'iPaMa': Form.NumericInput(tp=Form.FT_DEC), 44 | 'iMeUs': Form.NumericInput(tp=Form.FT_DEC), 45 | 'iSta': Form.NumericInput(tp=Form.FT_DEC), 46 | 'iVMAddr': Form.NumericInput(tp=Form.FT_DEC), 47 | 'iBaseAddr': Form.NumericInput(tp=Form.FT_DEC), 48 | 'iCodeEnd': Form.NumericInput(tp=Form.FT_DEC), 49 | 'iCodeStart': Form.NumericInput(tp=Form.FT_DEC), 50 | }) 51 | 52 | def OnButtonNop(self, code=0): 53 | pass 54 | 55 | def Show(): 56 | settings = SettingsView() 57 | settings.Compile() 58 | vmr = get_vmr() 59 | vm_ctx = vmr.vm_ctx 60 | 61 | settings.iCodeStart.value = vm_ctx.code_start 62 | settings.iCodeEnd.value = vm_ctx.code_end 63 | settings.iBaseAddr.value = vm_ctx.base_addr 64 | settings.iVMAddr.value = vm_ctx.vm_addr 65 | 66 | settings.rGreedyCluster.checked = vmr.greedy 67 | settings.rShowBB.checked = vmr.bb 68 | settings.iClusterHeu.value = vmr.cluster_magic 69 | 70 | settings.iInOut.value = vmr.in_out 71 | settings.iClu.value = vmr.clu 72 | settings.iPaMa.value = vmr.pa_ma 73 | settings.iMeUs.value = vmr.mem_use 74 | settings.iSta.value = vmr.static 75 | 76 | settings.rStepInSysLibs.checked = vmr.sys_libs 77 | settings.rFuncParams.checked = vmr.extract_param 78 | 79 | if settings.Execute() == 0: # Cancel 80 | settings.Free() 81 | else: # Confirm 82 | vmr = get_vmr() 83 | # VM values 84 | vm_ctx = VMContext() 85 | vm_ctx.code_start = settings.iCodeStart.value 86 | vm_ctx.code_end = settings.iCodeEnd.value 87 | vm_ctx.base_addr = settings.iBaseAddr.value 88 | vm_ctx.vm_addr = settings.iVMAddr.value 89 | 90 | vmr.vm_ctx = vm_ctx 91 | 92 | vmr.in_out = settings.iInOut.value 93 | vmr.clu = settings.iClu.value 94 | vmr.pa_ma = settings.iPaMa.value 95 | vmr.mem_use = settings.iMeUs.value 96 | vmr.static = settings.iSta.value 97 | 98 | # Env values 99 | vmr.sys_libs = settings.rStepInSysLibs.checked 100 | vmr.extract_param = settings.rFuncParams.checked 101 | vmr.greedy = settings.rGreedyCluster.checked 102 | vmr.bb = settings.rShowBB.checked 103 | vmr.cluster_magic = settings.iClusterHeu.value 104 | 105 | settings.Free() 106 | -------------------------------------------------------------------------------- /ui/StackChangeViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from ui.PluginViewer import PluginViewer 5 | from ui.UIManager import QtGui, QtCore, QtWidgets 6 | # from PyQt5 import QtGui, QtCore, QtWidgets 7 | 8 | 9 | #################### 10 | ### STACK CHANGE ### 11 | #################### 12 | class StackChangeViewer(PluginViewer): 13 | def __init__(self, vr, sorted, stack_changes, title='Stack Changes Analysis'): 14 | # context should be a dictionary containing the backward traced result of each relevant register 15 | super(StackChangeViewer, self).__init__(title) 16 | self.vr = vr 17 | self.sorted = sorted 18 | self.stack_changes = stack_changes 19 | 20 | 21 | def PopulateModel(self): 22 | for key in self.sorted: 23 | sa = QtGui.QStandardItem('%s' % key) 24 | chg = QtGui.QStandardItem('%s' % self.stack_changes[key]) 25 | 26 | if key in self.vr.values(): 27 | reg = QtGui.QStandardItem('%s' % [k for k in self.vr.keys() if self.vr[k] == key][0]) 28 | else: 29 | reg = QtGui.QStandardItem(' ') 30 | self.sim.appendRow([sa, reg, chg]) 31 | 32 | 33 | self.treeView.resizeColumnToContents(0) 34 | self.treeView.resizeColumnToContents(1) 35 | self.treeView.resizeColumnToContents(2) 36 | 37 | 38 | def PopulateForm(self): 39 | ### init widgets 40 | # model 41 | self.sim = QtGui.QStandardItemModel() 42 | self.sim.setHorizontalHeaderLabels(['Stack Address', 'Address Mapped to CPU Reg', 'Value Changes during Execution']) 43 | 44 | # tree view 45 | self.treeView = QtWidgets.QTreeView() 46 | self.treeView.setExpandsOnDoubleClick(True) 47 | self.treeView.setSortingEnabled(False) 48 | self.treeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) 49 | 50 | ### populate widgets 51 | # fill model with data 52 | self.PopulateModel() 53 | 54 | self.treeView.setModel(self.sim) 55 | # finalize layout 56 | layout = QtWidgets.QGridLayout() 57 | layout.addWidget(self.treeView) 58 | 59 | self.parent.setLayout(layout) 60 | 61 | 62 | def isVisible(self): 63 | try: 64 | return self.treeView.isVisible() 65 | except: 66 | return False 67 | -------------------------------------------------------------------------------- /ui/UIManager.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | 5 | import idaapi 6 | 7 | # to mitigate the migration from PySide(IDA SDK <= 6.8) to PyQt5(IDA SDK >= 6.9) this class will handle UI viewer element imports 8 | 9 | from cute import QtGui, QtCore, QtWidgets, form_to_widget, use_qt5 10 | # import dependent version dependent UI elements 11 | if not use_qt5: 12 | from legacyUI.ClusterViewer import ClusterViewer 13 | from legacyUI.GradingViewer import GradingViewer 14 | from legacyUI.OptimizationViewer import OptimizationViewer 15 | from legacyUI.VMInputOutputViewer import VMInputOuputViewer 16 | from legacyUI.StackChangeViewer import StackChangeViewer 17 | else: 18 | from ClusterViewer import ClusterViewer 19 | from GradingViewer import GradingViewer 20 | from OptimizationViewer import OptimizationViewer 21 | from VMInputOutputViewer import VMInputOuputViewer 22 | from StackChangeViewer import StackChangeViewer 23 | 24 | 25 | class UIManager(object): 26 | def __init__(self): 27 | 28 | self.window = None 29 | self.widget = None 30 | self.menu = None 31 | self.menu_dict = {} 32 | self.get_init_menu() 33 | 34 | # initial menu grab 35 | def get_init_menu(self): 36 | try: 37 | self.widget = form_to_widget(idaapi.get_current_tform()) 38 | if self.widget is None: 39 | raise Exception() 40 | except: 41 | self.widget = form_to_widget(idaapi.find_tform('Output window')) 42 | self.window = self.widget.window() 43 | self.menu = self.window.findChild(QtWidgets.QMenuBar) 44 | 45 | # add top level menu 46 | def add_menu(self, name): 47 | if name in self.menu_dict: 48 | raise Exception("Menu name %s already exists." % name) 49 | menu = self.menu.addMenu(name) 50 | self.menu_dict[name] = menu 51 | # remove top level menu 52 | def remove_menu(self, name): 53 | if name not in self.menu_dict: 54 | raise Exception("Menu %s was not found. It might be deleted, or belong to another menu manager." % name) 55 | 56 | self.menu.removeAction(self.menu_dict[name].menuAction()) 57 | del self.menu_dict[name] 58 | 59 | # remove all menus currently in dict 60 | def clear(self): 61 | for menu in self.menu_dict.itervalues(): 62 | self.menu.removeAction(menu.menuAction()) 63 | self.menu_dict = {} 64 | 65 | def add_view(self, view): 66 | pass 67 | -------------------------------------------------------------------------------- /ui/VMInputOutputViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | from copy import deepcopy 4 | 5 | from dynamic.TraceRepresentation import Traceline 6 | from lib.Register import get_reg_class 7 | from ui.PluginViewer import PluginViewer 8 | from ui.UIManager import QtGui, QtCore, QtWidgets 9 | # from PyQt5 import QtGui, QtCore, QtWidgets 10 | 11 | 12 | ############################# 13 | ### INPUT OUTPUT ANALYSIS ### 14 | ############################# 15 | class VMInputOuputViewer(PluginViewer): 16 | def __init__(self, input_set, output_set, output_ctx, title='Input/Output Analysis'): 17 | # context should be a dictionary containing the backward traced result of each relevant register 18 | super(VMInputOuputViewer, self).__init__(title) 19 | self.input = input_set 20 | self.output = output_set 21 | self.ctx = output_ctx 22 | self.selection = {'upper':[], 'lower':[]} 23 | self.ucb_map = [] 24 | self.lcb_map = [] 25 | # brush map 26 | self.brush_map = {0:QtGui.QBrush(QtCore.Qt.white), # unselected values 27 | 1:QtGui.QBrush(QtGui.QColor(228,153,105)), # input values color 28 | 2:QtGui.QBrush(QtGui.QColor(183,166,173)), # output values color 29 | 3:QtGui.QBrush(QtGui.QColor(157,151,84))} # BOTH values, mix of both colors 30 | 31 | def PopulateModel(self): 32 | self.CleanModel() 33 | assert isinstance(self.ctx, dict) 34 | for key in self.ctx.keys(): 35 | if get_reg_class(key) is not None: 36 | node = QtGui.QStandardItem('Register %s' % key) 37 | node_brush = set() 38 | for line in self.ctx[key]: 39 | assert isinstance(line, Traceline) 40 | tid = QtGui.QStandardItem('%s' % line.thread_id) 41 | addr = QtGui.QStandardItem('%x' % line.addr) 42 | disasm = QtGui.QStandardItem(line.disasm_str()) 43 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 44 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx.keys() if line.ctx is not None)) 45 | ci = 0 46 | co = 0 47 | for selector in self.selection['upper']: # check input values 48 | if line.to_str_line().__contains__(selector) or line.to_str_line().__contains__(selector.lower()): 49 | ci = 1 50 | 51 | for selector in self.selection['lower']: # check output values 52 | if line.to_str_line().__contains__(selector) or line.to_str_line().__contains__(selector.lower()): 53 | co = 2 54 | 55 | node_brush.add(ci+co) 56 | tid.setBackground(self.brush_map[ci+co]) 57 | addr.setBackground(self.brush_map[ci+co]) 58 | disasm.setBackground(self.brush_map[ci+co]) 59 | comment.setBackground(self.brush_map[ci+co]) 60 | context.setBackground(self.brush_map[ci+co]) 61 | 62 | node.appendRow([tid, addr, disasm, comment, context]) 63 | try: 64 | if len(node_brush) == 3: 65 | color = 3 66 | else: 67 | color = max(node_brush) 68 | node.setBackground(self.brush_map[color]) 69 | except: 70 | pass 71 | self.sim.appendRow(node) 72 | 73 | self.treeView.resizeColumnToContents(0) 74 | self.treeView.resizeColumnToContents(1) 75 | self.treeView.resizeColumnToContents(2) 76 | self.treeView.resizeColumnToContents(3) 77 | self.treeView.resizeColumnToContents(4) 78 | 79 | def PopulateUpperToolbar(self): 80 | assert isinstance(self.input, set) 81 | self.utb.addWidget(QtWidgets.QLabel('Input values found (check to highlight in trace): ')) 82 | for value in self.input: 83 | self.ucb_map.append(QtWidgets.QCheckBox(value)) 84 | self.ucb_map[-1].stateChanged.connect(self.OnValueChecked) 85 | self.utb.addWidget(self.ucb_map[-1]) 86 | self.utb.addSeparator() 87 | 88 | def PopulateLowerToolbar(self): 89 | assert isinstance(self.input, set) 90 | self.ltb.addWidget(QtWidgets.QLabel('Output values found (check to highlight in trace): ')) 91 | for value in self.output: 92 | self.lcb_map.append(QtWidgets.QCheckBox(value)) 93 | self.lcb_map[-1].stateChanged.connect(self.OnValueChecked) 94 | self.ltb.addWidget(self.lcb_map[-1]) 95 | self.ltb.addSeparator() 96 | 97 | def PopulateForm(self): 98 | ### init widgets 99 | # model 100 | self.sim = QtGui.QStandardItemModel() 101 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 102 | 103 | # toolbar 104 | self.utb = QtWidgets.QToolBar() 105 | self.ltb = QtWidgets.QToolBar() 106 | # tree view 107 | self.treeView = QtWidgets.QTreeView() 108 | self.treeView.setExpandsOnDoubleClick(True) 109 | self.treeView.setSortingEnabled(False) 110 | self.treeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) 111 | self.treeView.setToolTip('Highlights:\n Rust red - Input\n Violet - Output\n Olive - Both') 112 | 113 | ### populate widgets 114 | # fill model with data 115 | self.PopulateModel() 116 | # fill toolbar with data 117 | self.PopulateUpperToolbar() 118 | self.PopulateLowerToolbar() 119 | self.treeView.setModel(self.sim) 120 | # finalize layout 121 | layout = QtWidgets.QGridLayout() 122 | layout.addWidget(self.utb) 123 | layout.addWidget(self.treeView) 124 | layout.addWidget(self.ltb) 125 | 126 | self.parent.setLayout(layout) 127 | 128 | def CleanModel(self): 129 | self.sim.clear() 130 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 131 | 132 | def OnValueChecked(self): 133 | for check_box in self.ucb_map: 134 | if check_box.isChecked() and check_box.text() not in self.selection['upper']: 135 | self.selection['upper'].append(check_box.text()) 136 | elif not check_box.isChecked() and check_box.text() in self.selection['upper']: 137 | self.selection['upper'].remove(check_box.text()) 138 | 139 | for check_box in self.lcb_map: 140 | if check_box.isChecked() and check_box.text() not in self.selection['lower']: 141 | self.selection['lower'].append(check_box.text()) 142 | elif not check_box.isChecked() and check_box.text() in self.selection['lower']: 143 | self.selection['lower'].remove(check_box.text()) 144 | self.PopulateModel() 145 | 146 | def isVisible(self): 147 | try: 148 | return self.treeView.isVisible() 149 | except: 150 | return False 151 | -------------------------------------------------------------------------------- /ui/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Anatoli Kalysch' 2 | -------------------------------------------------------------------------------- /ui/legacyUI/ClusterViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | import re 5 | 6 | 7 | from copy import deepcopy 8 | from ui.NotifyProgress import NotifyProgress 9 | from _collections import defaultdict, deque 10 | from lib.VMRepresentation import get_vmr 11 | from dynamic.TraceRepresentation import Traceline 12 | from ui.PluginViewer import PluginViewer 13 | from ui.UIManager import QtGui, QtCore 14 | from lib.TraceAnalysis import cluster_removal 15 | 16 | from idaapi import is_basic_block_end 17 | from idc import AskLong 18 | 19 | 20 | ########################### 21 | ### CLUSTERING ANALYSIS ### 22 | ########################### 23 | class ClusterViewer(PluginViewer): 24 | def __init__(self, clustered_trace, bb_func, ctx_reg_size, title='Clustering Analysis Result (legacy)', save_func=None): 25 | # context should be a dictionary containing the backward traced result of each relevant register 26 | super(ClusterViewer, self).__init__(title) 27 | self.orig_trace = clustered_trace 28 | self.trace = deepcopy(self.orig_trace) 29 | self.bb_func = bb_func 30 | self.ctx_reg_size = ctx_reg_size 31 | self.save = save_func 32 | self.undo_stack = deque([deepcopy(self.trace)], maxlen=3) 33 | 34 | def PopulateModel(self): 35 | self.Clean() 36 | vmr = get_vmr() 37 | w = NotifyProgress() 38 | w.show() 39 | ctr = 0 40 | max = len(self.trace) 41 | 42 | # present clustering analysis in viewer 43 | prev_ctx = defaultdict(lambda: 0) 44 | for line in self.trace: 45 | 46 | ctr += 1 47 | w.pbar_set(int(float(ctr) / float(max) * 100)) 48 | 49 | if isinstance(line, Traceline): 50 | tid = QtGui.QStandardItem('%s' % line.thread_id) 51 | addr = QtGui.QStandardItem('%x' % line.addr) 52 | disasm = QtGui.QStandardItem(line.disasm_str()) 53 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 54 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx if line.ctx is not None)) 55 | prev_ctx = line.ctx 56 | self.sim.appendRow([tid, addr, disasm, comment, context]) 57 | else: 58 | cluster_node = QtGui.QStandardItem('Cluster %x-%x' % (line[0].addr, line[-1].addr)) 59 | self.sim.appendRow(cluster_node) 60 | if vmr.bb: 61 | cluster = line 62 | bbs = [] 63 | bb = [] 64 | # subdivide the clusters by basic blocks 65 | for line in cluster: 66 | assert isinstance(line, Traceline) 67 | if line.disasm[0].startswith('j'): 68 | bb.append(line) 69 | bbs.append(bb) 70 | bb = [] 71 | else: 72 | bb.append(line) 73 | 74 | for bb in bbs: 75 | 76 | bb_sum = self.bb_func(bb, self.ctx_reg_size, prev_ctx) 77 | bb_node = QtGui.QStandardItem( 78 | 'BB%s Summary %x-%x: %s\t%s\t%s' % (bbs.index(bb), bb[0].addr, bb[-1].addr, 79 | ''.join('%s ; ' % (''.join('%s, ' % c for c in line)) for line in bb_sum.disasm), 80 | ''.join('%s, ' % c for c in filter(None, bb_sum.comment) if bb_sum.comment is not None), 81 | ''.join('%s:%s ' % (c, bb_sum.ctx[c]) for c in bb_sum.ctx if bb_sum.ctx is not None))) 82 | for line in bb: 83 | tid = QtGui.QStandardItem('%s' % line.thread_id) 84 | addr = QtGui.QStandardItem('%x' % line.addr) 85 | disasm = QtGui.QStandardItem(line.disasm_str()) 86 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 87 | context = QtGui.QStandardItem( 88 | ''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx if line.ctx is not None)) 89 | bb_node.appendRow([tid, addr, disasm, comment, context]) 90 | cluster_node.appendRow(bb_node) 91 | self.treeView.setFirstColumnSpanned(bbs.index(bb), cluster_node.index(), True) 92 | 93 | prev_ctx = bb[-1].ctx 94 | else: 95 | for l in line: 96 | tid = QtGui.QStandardItem('%s' % l.thread_id) 97 | addr = QtGui.QStandardItem('%x' % l.addr) 98 | disasm = QtGui.QStandardItem(l.disasm_str()) 99 | comment = QtGui.QStandardItem(''.join(c for c in l.comment if l.comment is not None)) 100 | context = QtGui.QStandardItem( 101 | ''.join('%s:%s ' % (c, l.ctx[c]) for c in l.ctx if l.ctx is not None)) 102 | cluster_node.appendRow([tid, addr, disasm, comment, context]) 103 | 104 | w.close() 105 | 106 | self.treeView.resizeColumnToContents(0) 107 | self.treeView.resizeColumnToContents(1) 108 | self.treeView.resizeColumnToContents(2) 109 | self.treeView.resizeColumnToContents(3) 110 | self.treeView.resizeColumnToContents(4) 111 | 112 | def Clean(self): 113 | self.sim.clear() 114 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 115 | 116 | def PopulateForm(self): 117 | ### init widgets 118 | # model 119 | self.sim = QtGui.QStandardItemModel() 120 | 121 | # tree view 122 | self.treeView = QtGui.QTreeView() 123 | self.treeView.setExpandsOnDoubleClick(True) 124 | self.treeView.setSortingEnabled(False) 125 | self.treeView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) 126 | self.treeView.setToolTip('Filter instructions/clusters/basic blocks from trace by double clicking on them.') 127 | # Context menus 128 | self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 129 | self.treeView.customContextMenuRequested.connect(self.OnCustomContextMenu) 130 | 131 | self.treeView.doubleClicked.connect(self.ItemDoubleClickSlot) 132 | self.treeView.setModel(self.sim) 133 | 134 | ### populate widgets 135 | # fill model with data 136 | self.PopulateModel() 137 | 138 | # self.treeView.setFirstColumnSpanned(0, self.treeView.rootIndex(), True) 139 | # finalize layout 140 | layout = QtGui.QGridLayout() 141 | layout.addWidget(self.treeView) 142 | 143 | self.parent.setLayout(layout) 144 | 145 | def isVisible(self): 146 | try: 147 | return self.treeView.isVisible() 148 | except: 149 | return False 150 | 151 | @QtCore.Slot(QtCore.QModelIndex) 152 | def ItemDoubleClickSlot(self, index): 153 | """ 154 | TreeView DoubleClicked Slot. 155 | @param index: QModelIndex object of the clicked tree index item. 156 | @return: 157 | """ 158 | 159 | # fetch the clicked string 160 | s = index.data(0) 161 | print s 162 | line_index = [] 163 | inner_cluster_index = [] 164 | if s.startswith('Cluster'): 165 | addrs = re.findall(r'Cluster (.*-.*)', s)[0] 166 | addrs = addrs.split('-') 167 | start = int(addrs[0], 16) 168 | end = int(addrs[1], 16) 169 | for line in self.trace: 170 | if isinstance(line, Traceline): 171 | continue 172 | elif isinstance(line, list): 173 | if start == line[0].addr and end == line[-1].addr: 174 | line_index.append(line) 175 | 176 | elif s.startswith('BB'): 177 | addrs = re.findall(r'BB.*Summary (.*-.*): .*', s)[0] 178 | addrs = addrs.split('-') 179 | bad_range = range(int(addrs[0], 16), int(addrs[1], 16)) 180 | for line in self.trace: 181 | if isinstance(line, Traceline): 182 | continue 183 | elif isinstance(line, list): 184 | for l in line: 185 | if l.addr in bad_range: 186 | inner_cluster_index.append(l) 187 | else: # assume trace line 188 | for line in self.trace: 189 | if isinstance(line, Traceline) and line.to_str_line().__contains__(s): 190 | line_index.append(line) 191 | elif isinstance(line, list): 192 | for l in line: 193 | if l.to_str_line().__contains__(s): 194 | line_index.append(line) 195 | 196 | self.undo_stack.append(deepcopy(self.trace)) 197 | 198 | for line in line_index: 199 | self.trace.remove(line) 200 | 201 | for line in self.trace: 202 | if isinstance(line, list): 203 | for l in line: 204 | if l in inner_cluster_index: 205 | line.remove(l) 206 | 207 | self.PopulateModel() 208 | 209 | @QtCore.Slot(QtCore.QPoint) 210 | def OnCustomContextMenu(self, point): 211 | menu = QtGui.QMenu() 212 | init_index = self.treeView.indexAt(point) 213 | index = self.treeView.indexAt(point) 214 | level = 0 215 | while index.parent().isValid(): 216 | index = index.parent() 217 | level += 1 218 | 219 | text = 'Remove Line' 220 | 221 | if level == 0: 222 | text = "Remove Cluster / Line" 223 | elif level == 1 and get_vmr().bb: 224 | text = "Remove Basic Block" 225 | elif level == 2: 226 | text = "Remove Line" 227 | try: 228 | action_remove = QtGui.QAction(text, self.treeView, triggered=lambda: self.ItemDoubleClickSlot(init_index)) 229 | menu.addAction(action_remove) 230 | menu.addSeparator() 231 | except: 232 | print '[*] An Exception occured, remove action could not be added to the menu!' 233 | # Actions 234 | action_remove_threshold = QtGui.QAction('Remove several clusters...', self.treeView, triggered=lambda: self.ClusterRemoval()) 235 | 236 | action_undo = QtGui.QAction('Undo', self.treeView, triggered=lambda: self.Undo()) 237 | action_restore = QtGui.QAction('Restore original trace', self.treeView, triggered=lambda: self.Restore()) 238 | action_export_trace = QtGui.QAction('Export this trace ...', self.treeView, 239 | triggered=lambda: self.SaveTrace()) 240 | action_close_viewer = QtGui.QAction('Close Viewer', self.treeView, triggered=lambda: self.Close(4)) 241 | 242 | # add actions to menu 243 | menu.addAction(action_remove_threshold) 244 | menu.addAction(action_undo) 245 | menu.addAction(action_restore) 246 | menu.addAction(action_export_trace) 247 | menu.addSeparator() 248 | menu.addAction(action_close_viewer) 249 | 250 | menu.exec_(self.treeView.viewport().mapToGlobal(point)) 251 | 252 | @QtCore.Slot(str) 253 | def ClusterRemoval(self): 254 | threshold = AskLong(1, 'How many most common clusters do you want removed?') 255 | self.undo_stack.append(deepcopy(self.trace)) 256 | self.trace = cluster_removal(deepcopy(self.trace), threshold=threshold) 257 | self.PopulateModel() 258 | 259 | @QtCore.Slot(str) 260 | def SaveTrace(self): 261 | if self.save is not None: 262 | self.save(self.trace) 263 | 264 | @QtCore.Slot(str) 265 | def Undo(self): 266 | self.trace = self.undo_stack[-1] 267 | self.PopulateModel() 268 | 269 | @QtCore.Slot(str) 270 | def Restore(self): 271 | self.undo_stack = deque([deepcopy(self.trace)], maxlen=3) 272 | self.trace = deepcopy(self.orig_trace) 273 | self.PopulateModel() 274 | -------------------------------------------------------------------------------- /ui/legacyUI/GradingViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from ui.NotifyProgress import NotifyProgress 3 | 4 | __author__ = 'Anatoli Kalysch' 5 | 6 | 7 | import os 8 | import json 9 | import time 10 | 11 | from idaapi import get_root_filename 12 | 13 | from dynamic.TraceRepresentation import Traceline 14 | from idc import AskLong 15 | from ui.PluginViewer import PluginViewer 16 | from ui.UIManager import QtGui, QtCore 17 | 18 | 19 | ########################### 20 | ### GradingSys Analysis ### 21 | ########################### 22 | class GradingViewer(PluginViewer): 23 | def __init__(self, trace, title='Grading System Analysis (legacy)', **kwargs): 24 | # context should be a dictionary containing the backward traced result of each relevant register 25 | super(GradingViewer, self).__init__(title) 26 | self.trace = trace 27 | self.save = kwargs.get('save', None) 28 | self.grades = kwargs.get('grades', None) 29 | if self.grades is None: 30 | self.grades = self.GetGrades() 31 | 32 | def GetGrades(self): 33 | return set([line.grade for line in self.trace]) 34 | 35 | def PopulateModel(self, threshold): 36 | self.CleanModel() 37 | 38 | w = NotifyProgress() 39 | ctr = 0 40 | max = len(self.trace) 41 | prev = None 42 | for line in self.trace: 43 | assert isinstance(line, Traceline) 44 | if line.grade >= threshold: 45 | if prev is not None and threshold > 2: 46 | grade = QtGui.QStandardItem(' ') 47 | tid = QtGui.QStandardItem(' ') 48 | addr = QtGui.QStandardItem(' ') 49 | disasm = QtGui.QStandardItem('previous CPU context:') 50 | comment = QtGui.QStandardItem(' ') 51 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, prev.ctx[c]) for c in prev.ctx.keys() if prev.ctx is not None)) 52 | self.sim.appendRow([grade, tid, addr, disasm, comment, context]) 53 | grade = QtGui.QStandardItem('%s' % line.grade) 54 | tid = QtGui.QStandardItem('%s' % line.thread_id) 55 | addr = QtGui.QStandardItem('%x' % line.addr) 56 | disasm = QtGui.QStandardItem(line.disasm_str()) 57 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 58 | context = QtGui.QStandardItem( 59 | ''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx.keys() if line.ctx is not None)) 60 | 61 | self.sim.appendRow([grade, tid, addr, disasm, comment, context]) 62 | 63 | ctr += 1 64 | w.pbar_set(int(float(ctr) / float(max) * 100)) 65 | prev = line 66 | w.close() 67 | 68 | self.treeView.resizeColumnToContents(0) 69 | self.treeView.resizeColumnToContents(1) 70 | self.treeView.resizeColumnToContents(2) 71 | self.treeView.resizeColumnToContents(3) 72 | self.treeView.resizeColumnToContents(4) 73 | self.treeView.resizeColumnToContents(5) 74 | 75 | def CleanModel(self): 76 | self.sim.clear() 77 | self.sim.setHorizontalHeaderLabels(['Grade','ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 78 | 79 | 80 | def PopulateForm(self): 81 | ### init widgets 82 | # model 83 | self.sim = QtGui.QStandardItemModel() 84 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 85 | 86 | # toolbar 87 | self.ftb = QtGui.QToolBar() 88 | self.stb = QtGui.QToolBar() 89 | 90 | # tree view 91 | self.treeView = QtGui.QTreeView() 92 | self.treeView.setToolTip('Double click a grade to filter') 93 | self.treeView.setExpandsOnDoubleClick(True) 94 | self.treeView.setSortingEnabled(False) 95 | self.treeView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) 96 | # Context menus 97 | self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 98 | self.treeView.customContextMenuRequested.connect(self.OnCustomContextMenu) 99 | 100 | self.treeView.doubleClicked.connect(self.ItemDoubleClickSlot) 101 | self.treeView.setModel(self.sim) 102 | 103 | ### populate widgets 104 | # fill model with data 105 | self.PopulateModel(0) 106 | # finalize layout 107 | layout = QtGui.QGridLayout() 108 | layout.addWidget(self.treeView) 109 | 110 | 111 | self.parent.setLayout(layout) 112 | 113 | 114 | def IsVisible(self): 115 | try: 116 | return self.treeView.isVisible() 117 | except: 118 | return False 119 | 120 | @QtCore.Slot(QtCore.QModelIndex) 121 | def ItemDoubleClickSlot(self, index): 122 | """ 123 | TreeView DoubleClicked Slot. 124 | @param index: QModelIndex object of the clicked tree index item. 125 | @return: 126 | """ 127 | # fetch the clicked string 128 | try: 129 | instr = int(index.data(0)) 130 | except: 131 | instr = None 132 | if instr in self.grades: 133 | # if instr is an instruction, remove trace lines with said instruction 134 | self.PopulateModel(instr) 135 | 136 | 137 | @QtCore.Slot(QtCore.QPoint) 138 | def OnCustomContextMenu(self, point): 139 | menu = QtGui.QMenu() 140 | 141 | # Actions 142 | action_set_t = QtGui.QAction('Set grade threshold...', self.treeView, triggered=lambda: self.SetThreshold()) 143 | action_restore = QtGui.QAction('Show All', self.treeView, triggered=lambda: self.Restore()) 144 | action_export_trace = QtGui.QAction('Export this trace...', self.treeView, triggered=lambda: self.SaveTrace()) 145 | action_close_viewer = QtGui.QAction('Close Viewer', self.treeView, triggered=lambda: self.Close(4)) 146 | 147 | # add actions to menu 148 | menu.addAction(action_set_t) 149 | menu.addAction(action_restore) 150 | menu.addAction(action_export_trace) 151 | menu.addSeparator() 152 | menu.addAction(action_close_viewer) 153 | 154 | menu.exec_(self.treeView.viewport().mapToGlobal(point)) 155 | 156 | @QtCore.Slot(str) 157 | def SetThreshold(self): 158 | threshold = AskLong(-1, 'There are a total of %s grades: %s. Specify a threshold which lines to display:' % (len(self.grades), ''.join('%s ' % c for c in self.grades))) 159 | if threshold in self.grades: 160 | self.PopulateModel(threshold) 161 | 162 | 163 | @QtCore.Slot(str) 164 | def SaveTrace(self): #TODO 165 | if self.save is not None: 166 | self.save(self.trace) 167 | 168 | @QtCore.Slot(str) 169 | def Restore(self): 170 | self.PopulateModel(0) -------------------------------------------------------------------------------- /ui/legacyUI/OptimizationViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from _collections import deque 5 | from copy import deepcopy 6 | 7 | from dynamic.TraceRepresentation import Traceline, Trace 8 | from lib.Register import get_reg_by_size 9 | from lib.TraceAnalysis import repetition_clustering, find_virtual_regs, create_bb_diff 10 | from lib.TraceOptimizations import optimizations, optimization_names, optimization_selective_register_folding 11 | from ui.NotifyProgress import NotifyProgress 12 | from ui.PluginViewer import PluginViewer 13 | from ui.UIManager import QtGui, QtCore 14 | from ui.UIManager import ClusterViewer 15 | 16 | 17 | ########################### 18 | ### Trace Optimizations ### 19 | ########################### 20 | class OptimizationViewer(PluginViewer): 21 | def __init__(self, trace, title='Optimizations (legacy)', **kwargs): 22 | # context should be a dictionary containing the backward traced result of each relevant register 23 | super(OptimizationViewer, self).__init__(title) 24 | self.orig_trace = trace 25 | self.trace = deepcopy(trace) 26 | self.undo_stack = deque([deepcopy(trace), deepcopy(trace), deepcopy(trace)], maxlen=3) 27 | self.opti_map = dict(zip(optimization_names, optimizations)) 28 | self.order = [] 29 | self.foldable_regs = [] 30 | self.save = kwargs.get('save', None) 31 | 32 | 33 | def PopulateModel(self, trace): 34 | self.CleanModel() 35 | 36 | w = NotifyProgress() 37 | w.show() 38 | ctr = 0 39 | max = len(trace) 40 | 41 | for line in trace: 42 | 43 | assert isinstance(line, Traceline) 44 | tid = QtGui.QStandardItem('%s' % line.thread_id) 45 | addr = QtGui.QStandardItem('%x' % line.addr) 46 | disasm = QtGui.QStandardItem(line.disasm_str()) 47 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 48 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx.keys() if line.ctx is not None)) 49 | 50 | ctr += 1 51 | w.pbar_set(int(float(ctr) / float(max) * 100)) 52 | 53 | self.sim.appendRow([tid, addr, disasm, comment, context]) 54 | 55 | w.close() 56 | 57 | self.treeView.resizeColumnToContents(0) 58 | self.treeView.resizeColumnToContents(1) 59 | self.treeView.resizeColumnToContents(2) 60 | self.treeView.resizeColumnToContents(3) 61 | self.treeView.resizeColumnToContents(4) 62 | 63 | def CleanModel(self): 64 | self.sim.clear() 65 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 66 | 67 | def PopulateOptimizationsToolbar(self): 68 | self.ftb.addWidget(QtGui.QLabel('Available Optimizations (check to run on trace): ')) 69 | self.cpcb = QtGui.QCheckBox(optimization_names[0]) 70 | self.cpcb.stateChanged.connect(lambda: self.OptimizeTrace(self.cpcb)) 71 | self.ftb.addWidget(self.cpcb) 72 | self.ftb.addSeparator() 73 | 74 | self.sacb = QtGui.QCheckBox(optimization_names[1]) 75 | self.sacb.stateChanged.connect(lambda: self.OptimizeTrace(self.sacb)) 76 | self.ftb.addWidget(self.sacb) 77 | self.ftb.addSeparator() 78 | 79 | self.oscb = QtGui.QCheckBox(optimization_names[2]) 80 | self.oscb.stateChanged.connect(lambda: self.OptimizeTrace(self.oscb)) 81 | self.ftb.addWidget(self.oscb) 82 | self.ftb.addSeparator() 83 | 84 | self.uocb = QtGui.QCheckBox(optimization_names[3]) 85 | self.uocb.stateChanged.connect(lambda: self.OptimizeTrace(self.uocb)) 86 | self.ftb.addWidget(self.uocb) 87 | self.ftb.addSeparator() 88 | 89 | self.pcb = QtGui.QCheckBox(optimization_names[4]) 90 | self.pcb.stateChanged.connect(lambda: self.OptimizeTrace(self.pcb)) 91 | self.ftb.addWidget(self.pcb) 92 | self.ftb.addSeparator() 93 | 94 | def PopulateSelectiveRegsToolbar(self): 95 | self.stb.addWidget(QtGui.QLabel('Selective Register Folding: ')) 96 | assert isinstance(self.trace, Trace) 97 | if self.trace.ctx_reg_size == 32: 98 | for i in range(8): 99 | self.foldable_regs.append(QtGui.QCheckBox(get_reg_by_size(i, self.trace.ctx_reg_size))) 100 | self.foldable_regs[-1].stateChanged.connect(lambda: self.FoldRegs()) 101 | self.stb.addWidget(self.foldable_regs[-1]) 102 | self.stb.addSeparator() 103 | elif self.trace.ctx_reg_size == 64: 104 | for i in range(16): 105 | self.foldable_regs.append(QtGui.QCheckBox(get_reg_by_size(i, self.trace.ctx_reg_size))) 106 | self.foldable_regs[-1].stateChanged.connect(lambda: self.FoldRegs()) 107 | self.stb.addWidget(self.foldable_regs[-1]) 108 | self.stb.addSeparator() 109 | 110 | 111 | def PopulateForm(self): 112 | ### init widgets 113 | # model 114 | self.sim = QtGui.QStandardItemModel() 115 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 116 | 117 | # toolbar 118 | self.ftb = QtGui.QToolBar() 119 | self.stb = QtGui.QToolBar() 120 | 121 | # tree view 122 | self.treeView = QtGui.QTreeView() 123 | self.treeView.setToolTip('Filter instructions from trace by double clicking on them.') 124 | self.treeView.setExpandsOnDoubleClick(True) 125 | self.treeView.setSortingEnabled(False) 126 | self.treeView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) 127 | # Context menus 128 | self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 129 | self.treeView.customContextMenuRequested.connect(self.OnCustomContextMenu) 130 | 131 | self.treeView.doubleClicked.connect(self.ItemDoubleClickSlot) 132 | self.treeView.setModel(self.sim) 133 | 134 | ### populate widgets 135 | # fill model with data 136 | self.PopulateModel(self.orig_trace) 137 | # fill toolbars with data 138 | self.PopulateOptimizationsToolbar() 139 | self.PopulateSelectiveRegsToolbar() 140 | # finalize layout 141 | layout = QtGui.QGridLayout() 142 | layout.addWidget(self.ftb) 143 | layout.addWidget(self.stb) 144 | layout.addWidget(self.treeView) 145 | 146 | 147 | self.parent.setLayout(layout) 148 | 149 | def OptimizeTrace(self, check_box): 150 | self.undo_stack.append(deepcopy(self.trace)) 151 | self.last_cb = check_box 152 | optimization = self.opti_map[check_box.text()] 153 | if check_box.isChecked(): 154 | self.order.append(optimization) 155 | self.trace = optimization(self.trace) 156 | else: 157 | try: 158 | self.order.remove(optimization) 159 | except: 160 | pass 161 | self.trace = deepcopy(self.orig_trace) 162 | for optimization in self.order: 163 | self.trace = optimization(self.trace) 164 | self.FoldRegs() 165 | 166 | def FoldRegs(self): 167 | self.undo_stack.append(deepcopy(self.trace)) 168 | folded_regs = [] 169 | for check_box in self.foldable_regs: 170 | if check_box.isChecked(): 171 | folded_regs.append(check_box.text()) 172 | self.trace = optimization_selective_register_folding(self.trace, folded_regs) 173 | 174 | self.PopulateModel(self.trace) 175 | 176 | def IsVisible(self): 177 | try: 178 | return self.treeView.isVisible() 179 | except: 180 | return False 181 | 182 | @QtCore.Slot(QtCore.QModelIndex) 183 | def ItemDoubleClickSlot(self, index): 184 | """ 185 | TreeView DoubleClicked Slot. 186 | @param index: QModelIndex object of the clicked tree index item. 187 | @return: 188 | """ 189 | # fetch the clicked string 190 | instr = index.data(0) 191 | # if instr is an instruction, remove trace lines with said instruction 192 | self.trace = Trace(tr=[line for line in self.trace if line.disasm_str() != instr]) 193 | 194 | self.PopulateModel(self.trace) 195 | 196 | @QtCore.Slot(QtCore.QPoint) 197 | def OnCustomContextMenu(self, point): 198 | menu = QtGui.QMenu() 199 | # Actions 200 | action_undo = QtGui.QAction('Undo', self.treeView, triggered=lambda: self.Undo()) 201 | action_restore = QtGui.QAction('Restore original trace', self.treeView, triggered=lambda: self.Restore()) 202 | action_forward_to_clustering = QtGui.QAction("Open in Clustering Analysis", self.treeView, triggered=lambda: self.ClusterForward()) 203 | action_export_trace = QtGui.QAction('Export this trace...', self.treeView, triggered=lambda: self.SaveTrace()) 204 | action_close_viewer = QtGui.QAction('Close Viewer', self.treeView, triggered=lambda: self.Close(4)) 205 | 206 | # add actions to menu 207 | menu.addAction(action_undo) 208 | menu.addAction(action_restore) 209 | menu.addAction(action_forward_to_clustering) 210 | menu.addAction(action_export_trace) 211 | menu.addSeparator() 212 | menu.addAction(action_close_viewer) 213 | 214 | menu.exec_(self.treeView.viewport().mapToGlobal(point)) 215 | 216 | @QtCore.Slot(str) 217 | def ClusterForward(self): 218 | # cluster 219 | vr = find_virtual_regs(deepcopy(self.trace)) 220 | cluster = repetition_clustering(deepcopy(self.trace)) 221 | v0 = ClusterViewer(cluster, create_bb_diff, self.trace.ctx_reg_size) 222 | v0.Show() 223 | # Do not display StackChangeViewer. After the user worked on the trace it will be heavily malformed and missing crutial information for a stack change analysis, so the stack change view will do more harm than good. 224 | 225 | @QtCore.Slot(str) 226 | def SaveTrace(self): 227 | if self.save is not None: 228 | self.save(self.trace) 229 | 230 | @QtCore.Slot(str) 231 | def Undo(self): 232 | self.trace = self.undo_stack[-1] 233 | self.last_cb.setCheckState(QtCore.Qt.Unchecked) 234 | self.PopulateModel(self.trace) 235 | 236 | @QtCore.Slot(str) 237 | def Restore(self): 238 | self.undo_stack = [self.orig_trace] 239 | self.order = [] 240 | self.cpcb.setCheckState(QtCore.Qt.Unchecked) 241 | self.sacb.setCheckState(QtCore.Qt.Unchecked) 242 | self.oscb.setCheckState(QtCore.Qt.Unchecked) 243 | self.uocb.setCheckState(QtCore.Qt.Unchecked) 244 | self.pcb.setCheckState(QtCore.Qt.Unchecked) 245 | try: 246 | for check_box in self.foldable_regs: 247 | check_box.setCheckState(QtCore.Qt.Unchecked) 248 | except: 249 | pass 250 | self.trace = deepcopy(self.orig_trace) 251 | self.PopulateModel(self.trace) 252 | -------------------------------------------------------------------------------- /ui/legacyUI/StackChangeViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | 4 | from ui.PluginViewer import PluginViewer 5 | from ui.UIManager import QtGui 6 | 7 | 8 | #################### 9 | ### STACK CHANGE ### 10 | #################### 11 | class StackChangeViewer(PluginViewer): 12 | def __init__(self, vr, sorted, stack_changes, title='Stack Changes Analysis (legacy)'): 13 | # context should be a dictionary containing the backward traced result of each relevant register 14 | super(StackChangeViewer, self).__init__(title) 15 | self.vr = vr 16 | self.sorted = sorted 17 | self.stack_changes = stack_changes 18 | 19 | 20 | def PopulateModel(self): 21 | for key in self.sorted: 22 | sa = QtGui.QStandardItem('%s' % key) 23 | chg = QtGui.QStandardItem('%s' % self.stack_changes[key]) 24 | 25 | if key in self.vr.values(): 26 | reg = QtGui.QStandardItem('%s' % [k for k in self.vr.keys() if self.vr[k] == key][0]) 27 | else: 28 | reg = QtGui.QStandardItem(' ') 29 | self.sim.appendRow([sa, reg, chg]) 30 | 31 | 32 | self.treeView.resizeColumnToContents(0) 33 | self.treeView.resizeColumnToContents(1) 34 | self.treeView.resizeColumnToContents(2) 35 | 36 | 37 | def PopulateForm(self): 38 | ### init widgets 39 | # model 40 | self.sim = QtGui.QStandardItemModel() 41 | self.sim.setHorizontalHeaderLabels(['Stack Address', 'Address Mapped to CPU Reg', 'Value Changes during Execution']) 42 | 43 | # tree view 44 | self.treeView = QtGui.QTreeView() 45 | self.treeView.setExpandsOnDoubleClick(True) 46 | self.treeView.setSortingEnabled(False) 47 | self.treeView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) 48 | 49 | ### populate widgets 50 | # fill model with data 51 | self.PopulateModel() 52 | 53 | self.treeView.setModel(self.sim) 54 | # finalize layout 55 | layout = QtGui.QGridLayout() 56 | layout.addWidget(self.treeView) 57 | 58 | self.parent.setLayout(layout) 59 | 60 | 61 | def isVisible(self): 62 | try: 63 | return self.treeView.isVisible() 64 | except: 65 | return False 66 | -------------------------------------------------------------------------------- /ui/legacyUI/VMInputOutputViewer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | from copy import deepcopy 4 | 5 | from dynamic.TraceRepresentation import Traceline 6 | from lib.Register import get_reg_class 7 | from ui.PluginViewer import PluginViewer 8 | from ui.UIManager import QtGui, QtCore 9 | 10 | 11 | ############################# 12 | ### INPUT OUTPUT ANALYSIS ### 13 | ############################# 14 | class VMInputOuputViewer(PluginViewer): 15 | def __init__(self, input_set, output_set, output_ctx, title='Input/Output Analysis (legacy)'): 16 | # context should be a dictionary containing the backward traced result of each relevant register 17 | super(VMInputOuputViewer, self).__init__(title) 18 | self.input = input_set 19 | self.output = output_set 20 | self.ctx = output_ctx 21 | self.selection = {'upper':[], 'lower':[]} 22 | self.ucb_map = [] 23 | self.lcb_map = [] 24 | # brush map 25 | self.brush_map = {0:QtGui.QBrush(QtCore.Qt.white), # unselected values 26 | 1:QtGui.QBrush(QtGui.QColor(228,153,105)), # input values color 27 | 2:QtGui.QBrush(QtGui.QColor(183,166,173)), # output values color 28 | 3:QtGui.QBrush(QtGui.QColor(157,151,84))} # BOTH values, mix of both colors 29 | 30 | def PopulateModel(self): 31 | assert isinstance(self.ctx, dict) 32 | for key in self.ctx.keys(): 33 | if get_reg_class(key) is not None: 34 | node = QtGui.QStandardItem('Register %s' % key) 35 | node_brush = set() 36 | for line in self.ctx[key]: 37 | assert isinstance(line, Traceline) 38 | tid = QtGui.QStandardItem('%s' % line.thread_id) 39 | addr = QtGui.QStandardItem('%x' % line.addr) 40 | disasm = QtGui.QStandardItem(line.disasm_str()) 41 | comment = QtGui.QStandardItem(''.join(c for c in line.comment if line.comment is not None)) 42 | context = QtGui.QStandardItem(''.join('%s:%s ' % (c, line.ctx[c]) for c in line.ctx.keys() if line.ctx is not None)) 43 | ci = 0 44 | co = 0 45 | for selector in self.selection['upper']: # check input values 46 | if line.to_str_line().__contains__(selector) or line.to_str_line().__contains__(selector.lower()): 47 | ci = 1 48 | 49 | for selector in self.selection['lower']: # check output values 50 | if line.to_str_line().__contains__(selector) or line.to_str_line().__contains__(selector.lower()): 51 | co = 2 52 | 53 | node_brush.add(ci+co) 54 | tid.setBackground(self.brush_map[ci+co]) 55 | addr.setBackground(self.brush_map[ci+co]) 56 | disasm.setBackground(self.brush_map[ci+co]) 57 | comment.setBackground(self.brush_map[ci+co]) 58 | context.setBackground(self.brush_map[ci+co]) 59 | 60 | node.appendRow([tid, addr, disasm, comment, context]) 61 | try: 62 | if len(node_brush) == 3: 63 | color = 3 64 | else: 65 | color = max(node_brush) 66 | node.setBackground(self.brush_map[color]) 67 | except: 68 | pass 69 | self.sim.appendRow(node) 70 | 71 | self.treeView.resizeColumnToContents(0) 72 | self.treeView.resizeColumnToContents(1) 73 | self.treeView.resizeColumnToContents(2) 74 | self.treeView.resizeColumnToContents(3) 75 | self.treeView.resizeColumnToContents(4) 76 | 77 | 78 | def PopulateUpperToolbar(self): 79 | assert isinstance(self.input, set) 80 | self.utb.addWidget(QtGui.QLabel('Input values found (check to highlight in trace): ')) 81 | for value in self.input: 82 | self.ucb_map.append(QtGui.QCheckBox(value)) 83 | self.ucb_map[-1].stateChanged.connect(lambda: self.OnValueChecked()) 84 | self.utb.addWidget(self.ucb_map[-1]) 85 | self.utb.addSeparator() 86 | 87 | def PopulateLowerToolbar(self): 88 | assert isinstance(self.input, set) 89 | self.ltb.addWidget(QtGui.QLabel('Output values found (check to highlight in trace): ')) 90 | for value in self.output: 91 | self.lcb_map.append(QtGui.QCheckBox(value)) 92 | self.lcb_map[-1].stateChanged.connect(lambda: self.OnValueChecked()) 93 | self.ltb.addWidget(self.lcb_map[-1]) 94 | self.ltb.addSeparator() 95 | 96 | def PopulateForm(self): 97 | ### init widgets 98 | # model 99 | self.sim = QtGui.QStandardItemModel() 100 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 101 | 102 | # toolbar 103 | self.utb = QtGui.QToolBar() 104 | self.ltb = QtGui.QToolBar() 105 | # tree view 106 | self.treeView = QtGui.QTreeView() 107 | self.treeView.setExpandsOnDoubleClick(True) 108 | self.treeView.setSortingEnabled(False) 109 | self.treeView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) 110 | self.treeView.setToolTip('Highlights:\n Rust red - Input\n Violet - Output\n Olive - Both') 111 | 112 | ### populate widgets 113 | # fill model with data 114 | self.PopulateModel() 115 | # fill toolbar with data 116 | self.PopulateUpperToolbar() 117 | self.PopulateLowerToolbar() 118 | self.treeView.setModel(self.sim) 119 | # finalize layout 120 | layout = QtGui.QGridLayout() 121 | layout.addWidget(self.utb) 122 | layout.addWidget(self.treeView) 123 | layout.addWidget(self.ltb) 124 | 125 | self.parent.setLayout(layout) 126 | 127 | def CleanModel(self): 128 | self.sim.clear() 129 | self.sim.setHorizontalHeaderLabels(['ThreadId', 'Address', 'Disasm', 'Stack Comment', 'CPU Context']) 130 | 131 | def OnValueChecked(self): 132 | for check_box in self.ucb_map: 133 | if check_box.isChecked() and check_box.text() not in self.selection['upper']: 134 | self.selection['upper'].append(check_box.text()) 135 | elif not check_box.isChecked() and check_box.text() in self.selection['upper']: 136 | self.selection['upper'].remove(check_box.text()) 137 | 138 | for check_box in self.lcb_map: 139 | if check_box.isChecked() and check_box.text() not in self.selection['lower']: 140 | self.selection['lower'].append(check_box.text()) 141 | elif not check_box.isChecked() and check_box.text() in self.selection['lower']: 142 | self.selection['lower'].remove(check_box.text()) 143 | self.CleanModel() 144 | self.PopulateModel() 145 | 146 | def isVisible(self): 147 | try: 148 | return self.treeView.isVisible() 149 | except: 150 | return False 151 | -------------------------------------------------------------------------------- /ui/legacyUI/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Anatoli Kalysch' 3 | --------------------------------------------------------------------------------