├── .gitignore ├── README.md ├── angr_antievasion ├── __init__.py ├── cpu_instr_patches.py ├── paranoid_plugin.py ├── stdcall_simproc.py ├── utils.py └── win32_patches.py ├── testing ├── __init__.py ├── malware │ ├── M4LW4R3 │ └── malware_test.py ├── pafish │ ├── generate_check_table.py │ ├── pafish.exe │ ├── pafish.exe_checks.json │ ├── pafish_checks.txt │ └── pafish_test.py └── utilities │ ├── __init__.py │ └── aux_simprocs.py └── thesis ├── msc_slides.pdf └── msc_thesis.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | playground 2 | windows_dlls 3 | .idea 4 | *.pyc 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angr-antievasion 2 | 3 | An extension for the [angr](http://angr.io/) open source binary analysis and symbolic execution framework. 4 | 5 | The extension mainly consists of a set of Win32 API patches for the angr framework that constrain the symbolic exploration in order to automatically pass common _**evasive**_ checks. 6 | 7 | This tool was originally developed as part of the **Master's Thesis** of the author. 8 | The original release is available under the *thesis* tag. 9 | 10 | The main ideas behind this project are described in the thesis [Symbolic Execution of Malicious Software: Countering Sandbox Evasion Techniques](https://github.com/fabros/angr-antievasion/blob/master/thesis/msc_thesis.pdf). 11 | -------------------------------------------------------------------------------- /angr_antievasion/__init__.py: -------------------------------------------------------------------------------- 1 | from stdcall_simproc import * 2 | from paranoid_plugin import * 3 | from win32_patches import * 4 | from cpu_instr_patches import * 5 | from utils import * 6 | -------------------------------------------------------------------------------- /angr_antievasion/cpu_instr_patches.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | l = logging.getLogger('angr_antievasion.cpu_instr_patches') 4 | 5 | 6 | # Custom VEX dirty helpers for anti-evasion 7 | 8 | def rdtsc_patch(state): 9 | state.paranoid.tsc += 500 # magic number (pafish detects if consecutive runs diff is between 0 and 750) 10 | return state.solver.BVV(state.paranoid.tsc, 64), [] 11 | 12 | # CPUID based detection tricks don't need any handling (angr dirty helper emulates a real cpu info) 13 | -------------------------------------------------------------------------------- /angr_antievasion/paranoid_plugin.py: -------------------------------------------------------------------------------- 1 | from angr.state_plugins.plugin import SimStatePlugin 2 | from win32_patches import TICKS_PER_MS 3 | 4 | 5 | class ParanoidPlugin(SimStatePlugin): 6 | """ 7 | This state plugin keeps track of various paranoid stuff that may be checked during malware evasion 8 | """ 9 | 10 | def __init__(self): 11 | SimStatePlugin.__init__(self) 12 | self.tsc = 50 * 1000 * 60 * TICKS_PER_MS # init tick count ~= 50 minutes 13 | self.last_error = 0 # should be thread-local, but angr does NOT currently support threads 14 | self.open_regkeys = {} # handle -> string id 15 | 16 | def copy(self): 17 | c = ParanoidPlugin() 18 | c.tsc = self.tsc 19 | c.last_error = self.last_error 20 | c.open_regkeys = self.open_regkeys.copy() # shallow copy should be enough (handles don't change target) 21 | return c 22 | 23 | SimStatePlugin.register_default('paranoid', ParanoidPlugin) -------------------------------------------------------------------------------- /angr_antievasion/stdcall_simproc.py: -------------------------------------------------------------------------------- 1 | import angr 2 | 3 | 4 | class StdcallSimProcedure(angr.SimProcedure): 5 | # class tag to identify SimProcedures that should be executed with the Stdcall calling convention 6 | pass 7 | -------------------------------------------------------------------------------- /angr_antievasion/utils.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import angr.engines.vex.dirty as vex_dirtyhelpers 3 | import inspect 4 | import cpu_instr_patches 5 | import win32_patches 6 | from stdcall_simproc import StdcallSimProcedure 7 | from angr.calling_conventions import SimCCStdcall 8 | 9 | default_rdtsc_helper = vex_dirtyhelpers.amd64g_dirtyhelper_RDTSC 10 | 11 | 12 | # Auxiliary functions # 13 | 14 | def rdtsc_monkey_patch(): 15 | vex_dirtyhelpers.amd64g_dirtyhelper_RDTSC = cpu_instr_patches.rdtsc_patch 16 | vex_dirtyhelpers.x86g_dirtyhelper_RDTSC = cpu_instr_patches.rdtsc_patch 17 | 18 | 19 | def rdtsc_monkey_unpatch(): 20 | vex_dirtyhelpers.amd64g_dirtyhelper_RDTSC = default_rdtsc_helper 21 | vex_dirtyhelpers.x86g_dirtyhelper_RDTSC = default_rdtsc_helper 22 | 23 | 24 | def hook_all(proj): 25 | sim_procs = [x for x in win32_patches.__dict__.values() if inspect.isclass(x) and issubclass(x, angr.SimProcedure)] 26 | 27 | for sp in sim_procs: 28 | if issubclass(sp, StdcallSimProcedure): 29 | proj.hook_symbol(sp.__name__, sp(cc=SimCCStdcall(proj.arch))) 30 | else: 31 | # use default cc for the arch (for x86 it's Cdecl) 32 | proj.hook_symbol(sp.__name__, sp()) 33 | 34 | rdtsc_monkey_patch() -------------------------------------------------------------------------------- /angr_antievasion/win32_patches.py: -------------------------------------------------------------------------------- 1 | import angr 2 | from stdcall_simproc import StdcallSimProcedure 3 | from random import randint, getrandbits 4 | import logging 5 | 6 | l = logging.getLogger('angr_antievasion.win32_patches') 7 | 8 | TICKS_PER_MS = 10000 # Windows TicksPerMillisecond = 10000 9 | MALWARE_STRS = ['malware', 'sample', 'virus'] 10 | ANALYSIS_STRS = ['vm', 'hgfs', 'vbox', 'virtualbox', 'sandboxie', 'sboxie', 'wine', 'qemu', 'bochs'] 11 | # WHITELISTED_MODULES = ['advapi32.dll', 'msvcrt.dll', 'kernel32.dll'] 12 | # BLACKLISTED_MODULES = ['sbiedll.dll', 'dbghelp.dll', 'api_log.dll', 'dir_watch.dll', 'pstorec.dll', 'vmcheck.dll', 13 | # 'wpespy.dll'] 14 | # BLACKLISTED_SYMBOLS = ['IsWow64Process', 'IsNativeVhdBoot', 'wine_get_unix_file_name'] 15 | SENSITIVE_KEYS = { 16 | 'HARDWARE\DESCRIPTION\SYSTEM': {'SYSTEMBIOSVERSION': '1', 17 | 'VIDEOBIOSVERSION': '1', 18 | 'SYSTEMBIOSDATE': '01/01/2015'}, 19 | 'HARDWARE\DEVICEMAP\SCSI\SCSI PORT 0\SCSI BUS 0\TARGET ID 0\LOGICAL UNIT ID 0': {'IDENTIFIER': 'INTEL'}, 20 | 'HARDWARE\DEVICEMAP\SCSI\SCSI PORT 1\SCSI BUS 0\TARGET ID 0\LOGICAL UNIT ID 0': {'IDENTIFIER': 'INTEL'}, 21 | 'HARDWARE\DEVICEMAP\SCSI\SCSI PORT 2\SCSI BUS 0\TARGET ID 0\LOGICAL UNIT ID 0': {'IDENTIFIER': 'INTEL'}, 22 | } 23 | 24 | 25 | class SetLastError(StdcallSimProcedure): 26 | def run(self, dwErrCode): 27 | self.argument_types = { 28 | 0: angr.sim_type.SimTypeInt(), 29 | } 30 | 31 | self.state.paranoid.last_error = dwErrCode.args[0] 32 | 33 | self.return_type = angr.sim_type.SimTypeInt() 34 | 35 | l.info('{} @ {}: {} => void'.format( 36 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 37 | str(dwErrCode))) 38 | return 39 | 40 | 41 | class GetLastError(StdcallSimProcedure): 42 | def run(self): 43 | self.argument_types = {} 44 | 45 | self.return_type = angr.sim_type.SimTypeInt() 46 | 47 | ret_expr = self.state.paranoid.last_error 48 | l.info('{} @ {}: => {}'.format( 49 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 50 | str(ret_expr))) 51 | return ret_expr 52 | 53 | 54 | class GetModuleHandleA(StdcallSimProcedure): 55 | def run(self, pointer): 56 | if self.state.se.is_true(pointer == 0): 57 | return self.handle(None) 58 | else: 59 | return self.handle(self.state.mem[pointer].string.concrete) 60 | 61 | def handle(self, module_name): 62 | if module_name is None: 63 | obj = self.project.loader.main_object 64 | else: 65 | obj = self.project.loader.find_object(module_name) 66 | if obj is None: 67 | l.info('GetModuleHandle: No loaded object named "%s"', module_name) 68 | return 0 69 | return obj.mapped_base 70 | 71 | 72 | class GetModuleHandleW(GetModuleHandleA): 73 | def run(self, pointer): 74 | if self.state.se.is_true(pointer == 0): 75 | return self.handle(None) 76 | else: 77 | return self.handle(self.state.mem[pointer].wstring.concrete) 78 | 79 | 80 | # Alternative, symbolic GetModuleHandle and GetProcAddress implementations 81 | # 82 | # class GetModuleHandleA(StdcallSimProcedure): 83 | # def extract_string(self, addr): 84 | # return self.state.mem[addr].string.concrete 85 | # 86 | # def run(self, lpModuleName): 87 | # self.argument_types = { 88 | # 0: self.ty_ptr(angr.sim_type.SimTypeString()), 89 | # } 90 | # 91 | # self.return_type = self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)) 92 | # 93 | # assert not self.state.solver.symbolic(lpModuleName) 94 | # module_name = self.extract_string(lpModuleName) 95 | # 96 | # if module_name.lower() in BLACKLISTED_MODULES: 97 | # ret_expr = 0 # NULL, i.e. module not found 98 | # else: 99 | # ret_expr = self.state.solver.BVS("retval_{}_{}".format(self.display_name, module_name), 32) 100 | # if module_name.lower() in WHITELISTED_MODULES: 101 | # self.state.solver.add(ret_expr != 0) 102 | # 103 | # l.info("{} @ {}: {} ({}) => {}".format( 104 | # self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 105 | # str(lpModuleName), module_name, str(ret_expr))) 106 | # return ret_expr 107 | # 108 | # 109 | # class GetModuleHandleW(GetModuleHandleA): 110 | # def extract_string(self, addr): 111 | # return self.state.mem[addr].wstring.concrete 112 | # 113 | # 114 | # class GetProcAddress(StdcallSimProcedure): 115 | # def run(self, hModule, lpProcName): 116 | # self.argument_types = { 117 | # 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 118 | # 1: self.ty_ptr(angr.sim_type.SimTypeString()) 119 | # } 120 | # 121 | # self.return_type = self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)) 122 | # 123 | # assert not self.state.solver.symbolic(lpProcName) 124 | # 125 | # lpProcName_int_high = self.state.solver.eval(lpProcName) & 0xFFFF0000 126 | # 127 | # if lpProcName_int_high == 0: # ordinal import 128 | # assert False # TODO: add support ordinal value 129 | # else: 130 | # sym_name = self.state.mem[lpProcName].string.concrete 131 | # if sym_name in BLACKLISTED_SYMBOLS: 132 | # ret_expr = 0 # NULL, i.e. symbol not found 133 | # else: 134 | # ret_expr = self.state.solver.BVS("retval_{}_{}".format(self.display_name, sym_name), 32) 135 | # 136 | # l.info("{} @ {}: {}, {} ({}) => {}".format( 137 | # self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 138 | # str(hModule), str(lpProcName), sym_name, str(ret_expr))) 139 | # return ret_expr 140 | 141 | 142 | class IsWow64Process(StdcallSimProcedure): 143 | def run(self, hProcess, Wow64Process): 144 | self.argument_types = { 145 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 146 | 1: self.ty_ptr(angr.sim_type.SimTypeInt()), 147 | } 148 | 149 | self.return_type = angr.sim_type.SimTypeInt() 150 | 151 | assert not self.state.solver.symbolic(Wow64Process) 152 | self.state.memory.store(Wow64Process, self.state.solver.BVV(0, 32)) # always return FALSE 153 | ret_expr = 1 # success 154 | l.info("{} @ {}: {}, {} => {}".format( 155 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 156 | str(hProcess), str(Wow64Process), str(ret_expr))) 157 | return ret_expr 158 | 159 | 160 | class GetFileAttributesA(StdcallSimProcedure): 161 | def extract_string(self, addr): 162 | return self.state.mem[addr].string.concrete 163 | 164 | def run(self, lpFileName): 165 | self.argument_types = { 166 | 0: self.ty_ptr(angr.sim_type.SimTypeString()), 167 | } 168 | 169 | self.return_type = angr.sim_type.SimTypeInt() 170 | 171 | assert not self.state.solver.symbolic(lpFileName) 172 | file_name = self.extract_string(lpFileName) 173 | 174 | malware_related = any(mal_str in file_name.lower() for mal_str in MALWARE_STRS) 175 | analysis_related = any(vm_str in file_name.lower() for vm_str in ANALYSIS_STRS) 176 | 177 | if malware_related or analysis_related: 178 | ret_expr = -1 # INVALID_FILE_ATTRIBUTES, i.e. file not found 179 | self.state.paranoid.last_error = 0x2 # ERROR_FILE_NOT_FOUND 180 | else: 181 | ret_expr = self.state.solver.BVS("retval_{}_{}".format(self.display_name, file_name), 32) 182 | 183 | l.info("{} @ {}: {} () => {}".format( 184 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 185 | str(lpFileName), file_name, str(ret_expr))) 186 | return ret_expr 187 | 188 | 189 | class GetFileAttributesW(GetFileAttributesA): 190 | def extract_string(self, addr): 191 | return self.state.mem[addr].wstring.concrete 192 | 193 | 194 | class RegOpenKeyExA(StdcallSimProcedure): 195 | def extract_string(self, addr): 196 | return self.state.mem[addr].string.concrete 197 | 198 | def run(self, hKey, lpSubKey, ulOptions, samDesired, phkResult): 199 | self.argument_types = { 200 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 201 | 1: self.ty_ptr(angr.sim_type.SimTypeString()), 202 | 2: angr.sim_type.SimTypeInt(), 203 | 3: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 204 | 4: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 205 | } 206 | 207 | self.return_type = angr.sim_type.SimTypeLong() 208 | 209 | assert not self.state.solver.symbolic(lpSubKey) 210 | assert not self.state.solver.symbolic(phkResult) 211 | 212 | regkey_name = self.extract_string(lpSubKey).upper() 213 | 214 | analysis_related = any(vm_str in regkey_name.lower() for vm_str in ANALYSIS_STRS) 215 | 216 | if analysis_related: 217 | ret_expr = 2 # ERROR_FILE_NOT_FOUND 218 | else: 219 | handle = self.state.solver.BVS("handle_{}_{}".format(self.display_name, regkey_name), 32) 220 | if regkey_name in SENSITIVE_KEYS: 221 | # common key, so we always succeed in opening it 222 | self.state.memory.store(phkResult, handle, endness=self.arch.memory_endness) 223 | self.state.paranoid.open_regkeys[handle] = regkey_name 224 | ret_expr = 0 # ERROR_SUCCESS 225 | else: 226 | # state forking (hackish way of doing it): we either... 227 | old_state = self.state 228 | 229 | # succeed in opening the key 230 | success_state = old_state.copy() 231 | self.state = success_state 232 | self.state.paranoid.open_regkeys[handle] = regkey_name 233 | self.state.memory.store(phkResult, handle, endness=self.arch.memory_endness) 234 | l.info("{} @ {}: {}, {} ({}), {}, {}, {} => {}".format( 235 | self.display_name, 236 | self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 237 | str(hKey), str(lpSubKey), regkey_name, str(ulOptions), str(samDesired), str(phkResult), 0)) 238 | self.ret(0) # ERROR_SUCCESS 239 | 240 | # or fail 241 | fail_state = old_state.copy() 242 | self.state = fail_state 243 | ret_expr = self.state.solver.BVS("retval_{}_{}".format(self.display_name, regkey_name), 32) 244 | self.state.solver.add(ret_expr != 0) 245 | l.info("{} @ {}: {}, {} ({}), {}, {}, {} => {}".format( 246 | self.display_name, 247 | self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 248 | str(hKey), str(lpSubKey), regkey_name, str(ulOptions), str(samDesired), str(phkResult), 0)) 249 | self.ret(ret_expr) 250 | 251 | # N.B. we can't just return the alternative ret_expr because of the way ret is implemented, 252 | # do NOT "optimize", we need to invoke ret explicitly for both states 253 | return 254 | 255 | l.info("{} @ {}: {}, {} ({}), {}, {}, {} => {}".format( 256 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 257 | str(hKey), str(lpSubKey), regkey_name, str(ulOptions), str(samDesired), str(phkResult), str(ret_expr))) 258 | return ret_expr 259 | 260 | 261 | class RegOpenKeyExW(RegOpenKeyExA): 262 | def extract_string(self, addr): 263 | return self.state.mem[addr].wstring.concrete 264 | 265 | 266 | class RegCloseKey(StdcallSimProcedure): 267 | def run(self, hKey): 268 | 269 | self.argument_types = { 270 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 271 | } 272 | 273 | self.return_type = angr.sim_type.SimTypeLong() 274 | 275 | assert hKey in self.state.paranoid.open_regkeys 276 | 277 | self.state.paranoid.open_regkeys.pop(hKey, None) 278 | ret_expr = 1 # success 279 | 280 | l.info('{} @ {}: {} => {}'.format( 281 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 282 | str(hKey), ret_expr)) 283 | return ret_expr 284 | 285 | 286 | class RegQueryValueExA(StdcallSimProcedure): 287 | def extract_string(self, addr): 288 | return self.state.mem[addr].string.concrete 289 | 290 | def get_key_value(self, regkey_name, value_name, buffer_size): 291 | return SENSITIVE_KEYS[regkey_name][value_name][:buffer_size - 1] + '\0' 292 | 293 | def run(self, hKey, lpValueName, lpReserved, lpType, lpData, lpcbData): 294 | self.argument_types = { 295 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 296 | 1: self.ty_ptr(angr.sim_type.SimTypeString()), 297 | 2: self.ty_ptr(angr.sim_type.SimTypeInt()), 298 | 3: self.ty_ptr(angr.sim_type.SimTypeInt()), 299 | 4: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 300 | 5: self.ty_ptr(angr.sim_type.SimTypeInt()), 301 | } 302 | 303 | self.return_type = angr.sim_type.SimTypeLong() 304 | 305 | assert not self.state.solver.symbolic(lpValueName) 306 | assert not self.state.solver.symbolic(lpData) 307 | assert not self.state.solver.symbolic(lpcbData) 308 | assert hKey in self.state.paranoid.open_regkeys 309 | 310 | regkey_name = self.state.paranoid.open_regkeys[hKey] 311 | value_name = self.extract_string(lpValueName).upper() 312 | buffer_size = self.state.mem[self.state.solver.eval(lpcbData)].int.concrete 313 | data_str = None 314 | 315 | if regkey_name in SENSITIVE_KEYS and value_name in SENSITIVE_KEYS[regkey_name]: 316 | if self.state.solver.eval(lpData) != 0: # i.e. not NULL 317 | data_str = self.get_key_value(regkey_name, value_name, buffer_size) 318 | data = self.state.solver.BVV(data_str) 319 | self.state.memory.store(lpData, data) 320 | self.state.memory.store(lpcbData, self.state.solver.BVV(len(data_str), 32), 321 | endness=self.arch.memory_endness) 322 | ret_expr = 1 323 | else: 324 | if self.state.solver.eval(lpData) != 0: # i.e. not NULL 325 | data = self.state.solver.BVS('value_{}_{}_{}'.format( 326 | self.display_name, regkey_name, value_name), buffer_size*8) 327 | self.state.memory.store(lpData, data) 328 | size = self.state.solver.BVS('size_{}_{}_{}'.format( 329 | self.display_name, regkey_name, value_name), 32) 330 | self.state.memory.store(lpcbData, size, endness=self.arch.memory_endness) 331 | ret_expr = self.state.solver.BVS('retval_{}_{}_{}'.format( 332 | self.display_name, regkey_name, value_name), 32) 333 | l.info("{} @ {}: {}, {}, {}, {}, {}, {} => {}".format( 334 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 335 | str(hKey), str(lpValueName), str(lpReserved), str(lpType), 336 | str(lpData) + (" ({})".format(data_str) if data_str else ""), str(lpcbData), str(ret_expr))) 337 | return ret_expr 338 | 339 | 340 | class RegQueryValueExW(StdcallSimProcedure): 341 | def extract_string(self, addr): 342 | return self.state.mem[addr].wstring.concrete 343 | 344 | def get_key_value(self, regkey_name, value_name, buffer_size): 345 | return (SENSITIVE_KEYS[regkey_name][value_name][:buffer_size - 1] + '\0').encode('utf-16-le') # wchar string 346 | 347 | 348 | class GetCurrentProcess(StdcallSimProcedure): 349 | def run(self, ): 350 | self.argument_types = { 351 | } 352 | 353 | self.return_type = self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)) 354 | 355 | ret_expr = -1 # special constant that is interpreted as the current process handle 356 | l.info("{} @ {}: => {}".format( 357 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 358 | str(ret_expr))) 359 | return ret_expr 360 | 361 | 362 | # Debuggers detection 363 | 364 | class IsDebuggerPresent(StdcallSimProcedure): 365 | def run(self): 366 | self.argument_types = {} 367 | 368 | self.return_type = angr.sim_type.SimTypeInt() 369 | 370 | ret_expr = 0 # always return false 371 | l.info("{} @ {}: => {}".format( 372 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 373 | str(ret_expr))) 374 | return ret_expr 375 | 376 | 377 | class OutputDebugStringA(StdcallSimProcedure): 378 | def run(self, lpOutputString): 379 | self.argument_types = { 380 | 0: self.ty_ptr(angr.sim_type.SimTypeString()), 381 | } 382 | 383 | self.state.paranoid.last_error = 1284 # Update last error since debugger is not present 384 | 385 | self.return_type = angr.sim_type.SimTypeInt() 386 | 387 | l.info("{} @ {}: {} => void".format( 388 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 389 | str(lpOutputString))) 390 | return 391 | 392 | 393 | class OutputDebugStringW(OutputDebugStringA): 394 | pass 395 | 396 | 397 | class CheckRemoteDebuggerPresent(StdcallSimProcedure): 398 | def run(self, hProcess, pbDebuggerPresent): 399 | self.argument_types = { 400 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 401 | 1: self.ty_ptr(angr.sim_type.SimTypeInt()), 402 | } 403 | 404 | self.return_type = angr.sim_type.SimTypeInt() 405 | assert not self.state.solver.symbolic(pbDebuggerPresent) 406 | self.state.memory.store(pbDebuggerPresent, self.state.solver.BVV(0, 32)) # always return FALSE 407 | ret_expr = 1 # success 408 | l.info("{} @ {}: {}, {} => {}".format( 409 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 410 | str(hProcess), str(pbDebuggerPresent), str(ret_expr))) 411 | return ret_expr 412 | 413 | 414 | # Generic sandbox detection 415 | 416 | class GetCursorPos(StdcallSimProcedure): 417 | def run(self, lpPoint): 418 | self.argument_types = { 419 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 420 | } 421 | 422 | x = self.state.solver.BVV(randint(0, 300), 32) 423 | y = self.state.solver.BVV(randint(0, 300), 32) 424 | 425 | self.state.memory.store(lpPoint, x, endness=self.arch.memory_endness) 426 | self.state.memory.store(lpPoint + 4, y, endness=self.arch.memory_endness) 427 | 428 | self.return_type = angr.sim_type.SimTypeInt() 429 | ret_expr = 1 430 | l.info("{} @ {}: {} ({}, {}) => {}".format( 431 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 432 | str(lpPoint), x, y, str(ret_expr))) 433 | return ret_expr 434 | 435 | 436 | class GetUserNameA(StdcallSimProcedure): 437 | def get_username_string(self, size): 438 | return "John"[:size - 1] + '\0' 439 | 440 | def run(self, lpBuffer, lpnSize): 441 | self.argument_types = { 442 | 0: self.ty_ptr(angr.sim_type.SimTypeString()), 443 | 1: self.ty_ptr(angr.sim_type.SimTypeInt()), 444 | } 445 | 446 | self.return_type = angr.sim_type.SimTypeInt() 447 | 448 | assert not self.state.solver.symbolic(lpBuffer) 449 | if self.state.solver.eval(lpBuffer) != 0: # not NULL 450 | assert not self.state.solver.symbolic(lpnSize) 451 | size = self.state.mem[lpnSize].int.concrete # assuming lpcbData is not null 452 | user_str = self.get_username_string(size) 453 | user = self.state.solver.BVV(user_str) 454 | self.state.memory.store(lpBuffer, user) 455 | self.state.memory.store(lpnSize, self.state.solver.BVV(len(user_str), 32), endness=self.arch.memory_endness) 456 | 457 | ret_expr = 1 458 | l.info("{} @ {}: {} ({}), {} => {}".format( 459 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 460 | str(lpBuffer), user_str, str(lpnSize), str(ret_expr))) 461 | return ret_expr 462 | 463 | 464 | class GetUserNameW(GetUserNameA): 465 | def get_username_string(self, size): 466 | return ("Johnny"[:size - 1] + '\0').encode('utf-16-le') 467 | 468 | 469 | class GetModuleFileNameA(StdcallSimProcedure): 470 | def get_modulefilename_string(self, size): 471 | return "C:\\installer.exe"[:size - 1] + '\0' 472 | 473 | def run(self, hModule, lpFilename, nSize): 474 | self.argument_types = { 475 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 476 | 1: self.ty_ptr(angr.sim_type.SimTypeString()), 477 | 2: angr.sim_type.SimTypeInt(), 478 | } 479 | 480 | self.return_type = angr.sim_type.SimTypeInt() 481 | 482 | assert not self.state.solver.symbolic(hModule) 483 | assert not self.state.solver.symbolic(lpFilename) 484 | assert not self.state.solver.symbolic(nSize) 485 | size = self.state.solver.eval(nSize) 486 | path_str = None 487 | if self.state.solver.eval(hModule) == 0: # NULL, retrieve path of the exe of the current process 488 | path_str = self.get_modulefilename_string(size) 489 | path = self.state.solver.BVV(path_str) 490 | self.state.memory.store(lpFilename, path) 491 | ret_expr = len(path_str) - 1 # not including terminating null 492 | else: 493 | self.state.memory.store(lpFilename, 494 | self.state.solver.BVS("filename_{}".format(self.display_name), size * 8)) 495 | ret_expr = self.state.solver.BVS("retval_{}".format(self.display_name), 32) 496 | 497 | l.info("{} @ {}: {}, {}, {} => {}".format( 498 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 499 | str(hModule), str(lpFilename) + (" ({})".format(path_str) if path_str else ""), 500 | str(nSize), str(ret_expr))) 501 | return ret_expr 502 | 503 | 504 | class GetModuleFileNameW(GetModuleFileNameA): 505 | def get_modulefilename_string(self, size): 506 | return ("C:\\installer.exe"[:size - 1] + '\0').encode('utf-16-le') 507 | 508 | 509 | class GetLogicalDriveStringsA(StdcallSimProcedure): 510 | def get_drive_string(self): 511 | return "C:\\" 512 | 513 | def run(self, nBufferLength, lpBuffer): 514 | self.argument_types = { 515 | 0: angr.sim_type.SimTypeInt(), 516 | 1: self.ty_ptr(angr.sim_type.SimTypeString()), 517 | } 518 | 519 | self.return_type = angr.sim_type.SimTypeInt() 520 | 521 | assert not self.state.solver.symbolic(lpBuffer) 522 | assert not self.state.solver.symbolic(nBufferLength) 523 | drives_str = self.get_drive_string() 524 | 525 | data = None 526 | if self.state.solver.is_true(nBufferLength >= len(drives_str)): # nBufferLength does NOT include terminating null 527 | data = self.state.solver.BVV(drives_str + '\0\0') # additional null indicates end of list 528 | self.state.memory.store(lpBuffer, data) 529 | else: 530 | pass # return the required buffer size to store it all 531 | 532 | ret_expr = len(drives_str) - 1 # not including terminating null 533 | l.info("{} @ {}: {}, {} => {}".format( 534 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 535 | str(nBufferLength), str(lpBuffer) + (" ({})".format(drives_str) if data is not None else ""), str(ret_expr))) 536 | return ret_expr 537 | 538 | 539 | class GetLogicalDriveStringsW(GetLogicalDriveStringsA): 540 | def get_drive_string(self): 541 | return "C:\\".encode('utf-16-le') 542 | 543 | 544 | class GetDriveTypeA(StdcallSimProcedure): 545 | def extract_string(self, addr): 546 | return self.state.mem[addr].string.concrete 547 | 548 | def run(self, lpRootPathName): 549 | 550 | self.argument_types = { 551 | 0: self.ty_ptr(angr.sim_type.SimTypeString()), 552 | } 553 | 554 | self.return_type = angr.sim_type.SimTypeInt() 555 | 556 | assert not self.state.solver.symbolic(lpRootPathName) 557 | 558 | root_str = self.state.mem[lpRootPathName].string.concrete.upper() 559 | if root_str == 'C:\\': 560 | ret_expr = 3 # i.e. DRIVE_FIXED 561 | else: 562 | ret_expr = self.state.solver.BVS("retval_{}".format(self.display_name), 32) 563 | self.state.add_constraints(ret_expr >= 0) 564 | self.state.add_constraints(ret_expr <= 6) 565 | l.info("{} @ {}: {} ({}) => {}".format( 566 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 567 | str(lpRootPathName), root_str, str(ret_expr))) 568 | return ret_expr 569 | 570 | 571 | class GetDriveTypeW(StdcallSimProcedure): 572 | def extract_string(self, addr): 573 | return self.state.mem[addr].wstring.concrete 574 | 575 | 576 | class CreateFileA(StdcallSimProcedure): 577 | def extract_string(self, addr): 578 | return self.state.mem[addr].string.concrete 579 | 580 | def run(self, lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, 581 | dwFlagsAndAttributes, hTemplateFile): 582 | self.argument_types = { 583 | 0: self.ty_ptr(angr.sim_type.SimTypeString()), 584 | 1: angr.sim_type.SimTypeInt(), 585 | 2: angr.sim_type.SimTypeInt(), 586 | 3: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 587 | 4: angr.sim_type.SimTypeInt(), 588 | 5: angr.sim_type.SimTypeInt(), 589 | 6: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 590 | } 591 | 592 | self.return_type = self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)) 593 | 594 | assert not self.state.solver.symbolic(lpFileName) 595 | assert not self.state.solver.symbolic(dwDesiredAccess) 596 | file_name = self.extract_string(lpFileName) 597 | ret_expr = self.state.solver.BVS("retval_{}_{}".format(self.display_name, file_name), 32) 598 | 599 | access = self.state.solver.eval(dwDesiredAccess) 600 | if access & 0x80000000: # GENERIC_READ 601 | analysis_related = any(vm_str in file_name.lower() for vm_str in ANALYSIS_STRS) 602 | if analysis_related: 603 | ret_expr = -1 # INVALID_HANDLE_VALUE 604 | self.state.paranoid.last_error = 0x2 # ERROR_FILE_NOT_FOUND 605 | elif file_name == '\\\\.\\PhysicalDrive0': 606 | self.state.solver.add(ret_expr != -1) # valid handle 607 | 608 | l.info("{} @ {}: {} ({}), {}, {}, {}, {}, {}, {} => {}".format( 609 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 610 | str(lpFileName), file_name, str(dwDesiredAccess), str(dwShareMode), str(lpSecurityAttributes), 611 | str(dwCreationDisposition), str(dwFlagsAndAttributes), str(hTemplateFile), str(ret_expr))) 612 | return ret_expr 613 | 614 | 615 | class CreateFileW(CreateFileA): 616 | def extract_string(self, addr): 617 | return self.state.mem[addr].wstring.concrete 618 | 619 | 620 | class DeviceIoControl(StdcallSimProcedure): 621 | def run(self, hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, 622 | lpOverlapped): 623 | 624 | self.argument_types = { 625 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 626 | 1: angr.sim_type.SimTypeInt(), 627 | 2: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 628 | 3: angr.sim_type.SimTypeInt(), 629 | 4: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 630 | 5: angr.sim_type.SimTypeInt(), 631 | 6: self.ty_ptr(angr.sim_type.SimTypeInt()), 632 | 7: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 633 | } 634 | 635 | self.return_type = angr.sim_type.SimTypeInt() 636 | 637 | assert not self.state.solver.symbolic(dwIoControlCode) 638 | ret_expr = self.state.solver.BVS("retval_{}".format(self.display_name), 32) 639 | control_code = self.state.solver.eval(dwIoControlCode) 640 | if self.state.solver.symbolic(hDevice) and 'PhysicalDrive0' in hDevice.args[0] and control_code == 0x7405c: 641 | # IOCTL_DISK_GET_LENGTH_INFO on PhysicalDrive0: return properly configured data 642 | assert not self.state.solver.symbolic(lpOutBuffer) 643 | assert not self.state.solver.symbolic(nOutBufferSize) 644 | assert self.state.solver.eval(nOutBufferSize) >= 8 # the buffer can fit the GET_LENGTH_INFO struct 645 | getlengthinfo_struct = self.state.solver.BVV(128 * 2 ** 30, 8 * 8) # drive size = 128 GB 646 | self.state.memory.store(lpOutBuffer, getlengthinfo_struct, endness=self.arch.memory_endness) 647 | ret_expr = 1 # success 648 | 649 | l.info("{} @ {}: {}, {}, {}, {}, {}, {}, {}, {} => {}".format( 650 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 651 | str(hDevice), str(dwIoControlCode), str(lpInBuffer), str(nInBufferSize), str(lpOutBuffer), 652 | str(nOutBufferSize), str(lpBytesReturned), str(lpOverlapped), str(ret_expr))) 653 | return ret_expr 654 | 655 | 656 | class GetDiskFreeSpaceExA(StdcallSimProcedure): 657 | def run(self, lpDirectoryName, lpFreeBytesAvailable, lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes): 658 | self.argument_types = { 659 | 0: self.ty_ptr(angr.sim_type.SimTypeString()), 660 | 1: self.ty_ptr(angr.sim_type.SimTypeInt()), 661 | 2: self.ty_ptr(angr.sim_type.SimTypeInt()), 662 | 3: self.ty_ptr(angr.sim_type.SimTypeInt()), 663 | } 664 | 665 | self.return_type = angr.sim_type.SimTypeInt() 666 | 667 | assert not self.state.solver.symbolic(lpTotalNumberOfBytes) 668 | ret_expr = self.state.solver.BVS("retval_{}".format(self.display_name), 32) 669 | if self.state.solver.eval(lpTotalNumberOfBytes) != 0: # not NULL 670 | getlengthinfo_struct = self.state.solver.BVV(128 * 2 ** 30, 8 * 8) # drive size = 128 GB 671 | self.state.memory.store(lpTotalNumberOfBytes, getlengthinfo_struct, endness=self.arch.memory_endness) 672 | ret_expr = 1 # success 673 | 674 | l.info("{} @ {}: {}, {}, {}, {} => {}".format( 675 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 676 | str(lpDirectoryName), str(lpFreeBytesAvailable), str(lpTotalNumberOfBytes), 677 | str(lpTotalNumberOfFreeBytes), str(ret_expr))) 678 | return ret_expr 679 | 680 | 681 | class GetDiskFreeSpaceExW(GetDiskFreeSpaceExA): 682 | pass 683 | 684 | 685 | class Sleep(StdcallSimProcedure): 686 | def run(self, dwMilliseconds): 687 | self.argument_types = { 688 | 0: angr.sim_type.SimTypeInt(), 689 | } 690 | 691 | self.return_type = angr.sim_type.SimTypeInt() 692 | 693 | self.state.paranoid.tsc += self.state.solver.eval(dwMilliseconds) * TICKS_PER_MS 694 | 695 | l.info("{} @ {}: {} => void".format( 696 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 697 | str(dwMilliseconds))) 698 | return 699 | 700 | 701 | class GetTickCount(StdcallSimProcedure): 702 | def run(self, ): 703 | self.argument_types = {} 704 | 705 | self.return_type = angr.sim_type.SimTypeInt() 706 | 707 | ret_expr = self.state.paranoid.tsc // TICKS_PER_MS 708 | 709 | # additionally increase the tick counter to handle repeated GetTickCount calls check 710 | self.state.paranoid.tsc += TICKS_PER_MS 711 | 712 | l.info("{} @ {}: => {}".format( 713 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 714 | str(ret_expr))) 715 | return ret_expr 716 | 717 | 718 | class GetSystemInfo(StdcallSimProcedure): 719 | def run(self, lpSystemInfo): 720 | self.argument_types = { 721 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 722 | } 723 | 724 | self.return_type = angr.sim_type.SimTypeInt() 725 | 726 | sysinfo_struct = self.state.solver.BVS('SYSTEM_INFO', 36 * 8) 727 | self.state.memory.store(lpSystemInfo, sysinfo_struct) 728 | # dwNumberOfProcessors = self.state.solver.BVS('dwNumberOfProcessors', 4 * 8) 729 | # self.state.solver.add(self.state.solver.UGE(dwNumberOfProcessors, 2)) # dwNumberOfProcessors >= 2 730 | # Note: the value is correctly constrained, still angr doesn't seem to be aware of it. 731 | # This is because angr only checks satisfiability for branches that affect control flow. 732 | # Because of the particular structure of the gensandbox_one_cpu_GetSystemInfo check, 733 | # i.e. return siSysInfo.dwNumberOfProcessors < 2 ? TRUE : FALSE; 734 | # the branch does not affect control flow and thus the return value remains conditional. 735 | dwNumberOfProcessors = self.state.solver.BVV(4, 4 * 8) 736 | self.state.memory.store(lpSystemInfo + 20, dwNumberOfProcessors) 737 | 738 | l.info("{} @ {}: {} => void".format( 739 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 740 | str(lpSystemInfo))) 741 | return 742 | 743 | 744 | class GlobalMemoryStatusEx(StdcallSimProcedure): 745 | def run(self, lpBuffer): 746 | self.argument_types = { 747 | 0: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 748 | } 749 | 750 | self.return_type = angr.sim_type.SimTypeInt() 751 | 752 | memstatus_struct = self.state.solver.BVS('MEMORYSTATUSEX', 68 * 8) # dwLength is concrete 753 | self.state.memory.store(lpBuffer + 4, memstatus_struct) # ignore dwLength field 754 | ullTotalPhys = self.state.solver.BVV(8 * 2 ** 30, 8 * 8) 755 | self.state.memory.store(lpBuffer + 8, ullTotalPhys, endness=self.arch.memory_endness) 756 | 757 | ret_expr = 1 758 | l.info("{} @ {}: {} => {}".format( 759 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 760 | str(lpBuffer), str(ret_expr))) 761 | return ret_expr 762 | 763 | 764 | # Sandboxie detection tricks 765 | 766 | 767 | # Wine detection tricks 768 | 769 | 770 | # VirtualBox detection tricks 771 | 772 | class GetAdaptersAddresses(StdcallSimProcedure): 773 | def run(self, Family, Flags, Reserved, AdapterAddresses, SizePointer): 774 | 775 | self.argument_types = { 776 | 0: angr.sim_type.SimTypeLong(), 777 | 1: angr.sim_type.SimTypeLong(), 778 | 2: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 779 | 3: self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)), 780 | 4: self.ty_ptr(angr.sim_type.SimTypeLong()), 781 | } 782 | 783 | self.return_type = angr.sim_type.SimTypeLong() 784 | 785 | assert not self.state.solver.symbolic(AdapterAddresses) and not self.state.solver.symbolic(SizePointer) 786 | if self.state.solver.eval(AdapterAddresses) == 0: # NULL, insert required buffer size in SizePointer 787 | # insert 0x90 (i.e. sizeof(IP_ADAPTER_ADDRESSES)) in SizePointer 788 | self.state.memory.store(SizePointer, self.state.solver.BVV(0x90, 32), endness=self.arch.memory_endness) 789 | ret_expr = 0x6F # return 0x6F (i.e. ERROR_BUFFER_OVERFLOW) 790 | else: 791 | # return a single random address (assuming function has been correctly invoked and space allocated) 792 | adapter_struct = self.state.solver.BVS('IP_ADAPTER_ADDRESSES', 0x90 * 8) 793 | self.state.memory.store(AdapterAddresses, adapter_struct) 794 | # concretize relevant fields 795 | PhysicalAddressLength = AdapterAddresses + 52 796 | self.state.memory.store(PhysicalAddressLength, self.state.solver.BVV(6, 32), 797 | endness=self.arch.memory_endness) 798 | PhysicalAddress = AdapterAddresses + 44 799 | for i in range(6): # generate random mac 800 | self.state.memory.store(PhysicalAddress + i, self.state.solver.BVV(getrandbits(8), 8)) 801 | Next = AdapterAddresses + 8 802 | self.state.memory.store(Next, self.state.solver.BVV(0, 32)) 803 | Description = AdapterAddresses + 36 804 | global_alloc = angr.SIM_PROCEDURES['win32']['GlobalAlloc'] 805 | lp_description = self.inline_call(global_alloc, 0x0040, 128).ret_expr # allocate space for the wstring 806 | description = self.state.solver.BVV('Intel(R) Gigabit Network Connection\0'.encode('utf-16-le')) # wchar string 807 | self.state.memory.store(lp_description, description) 808 | self.state.memory.store(Description, lp_description, endness=self.arch.memory_endness) 809 | ret_expr = 0 # NO_ERROR 810 | 811 | l.info("{} @ {}: {}, {}, {}, {}, {} => {}".format( 812 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 813 | str(Family), str(Flags), str(Reserved), str(AdapterAddresses), str(SizePointer), str(ret_expr))) 814 | return ret_expr 815 | 816 | 817 | class FindWindowA(StdcallSimProcedure): 818 | def extract_string(self, addr): 819 | return self.state.mem[addr].string.concrete 820 | 821 | def run(self, lpClassName, lpWindowName): 822 | self.argument_types = { 823 | 0: self.ty_ptr(angr.sim_type.SimTypeString()), 824 | 1: self.ty_ptr(angr.sim_type.SimTypeString()), 825 | } 826 | 827 | self.return_type = self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)) 828 | 829 | assert not self.state.solver.symbolic(lpClassName) 830 | assert not self.state.solver.symbolic(lpWindowName) 831 | class_name = '' 832 | if self.state.solver.is_true(lpClassName != 0): 833 | class_name = self.extract_string(lpClassName) 834 | win_name = '' 835 | if self.state.solver.is_true(lpWindowName != 0): 836 | win_name = self.extract_string(lpWindowName) 837 | analysis_related_class_name = any(vm_str in class_name.lower() for vm_str in ANALYSIS_STRS) 838 | analysis_related_win_name = any(vm_str in win_name.lower() for vm_str in ANALYSIS_STRS) 839 | if analysis_related_class_name or analysis_related_win_name: 840 | ret_expr = 0 # NULL, i.e. not found 841 | else: 842 | ret_expr = self.state.solver.BVS("retval_{}_{}_{}".format(self.display_name, class_name, win_name), 843 | 32) 844 | 845 | l.info("{} @ {}: {} ({}), {} ({}) => {}".format( 846 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 847 | str(lpClassName), class_name, str(lpWindowName), win_name, str(ret_expr))) 848 | return ret_expr 849 | 850 | 851 | class FindWindowW(FindWindowA): 852 | def extract_string(self, addr): 853 | return self.state.mem[addr].wstring.concrete 854 | 855 | 856 | class WNetGetProviderNameA(StdcallSimProcedure): 857 | def get_provider_string(self, size): 858 | return "Microsoft"[:size - 1] + '\0' 859 | 860 | def run(self, dwNetType, lpProviderName, lpBufferSize): 861 | self.argument_types = { 862 | 0: angr.sim_type.SimTypeInt(), 863 | 1: self.ty_ptr(angr.sim_type.SimTypeString()), 864 | 2: self.ty_ptr(angr.sim_type.SimTypeInt()), 865 | } 866 | 867 | self.return_type = angr.sim_type.SimTypeInt() 868 | 869 | assert not self.state.solver.symbolic(dwNetType) 870 | assert not self.state.solver.symbolic(lpProviderName) 871 | assert not self.state.solver.symbolic(lpBufferSize) 872 | size = self.state.solver.eval(lpBufferSize) 873 | if self.state.solver.eval(dwNetType) == 0x00250000: # WNNC_NET_RDR2SAMPLE, for vbox shared folders 874 | name_str = self.get_provider_string(size) 875 | name = self.state.solver.BVV(name_str) 876 | self.state.memory.store(lpProviderName, name) 877 | ret_expr = 0 # NO_ERROR 878 | else: 879 | self.state.memory.store(lpProviderName, 880 | self.state.solver.BVS("provider_name_{}".format(self.display_name), size * 8)) 881 | ret_expr = self.state.solver.BVS("retval_{}".format(self.display_name), 32) 882 | 883 | l.info("{} @ {}: {}, {}, {} => {}".format( 884 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 885 | str(dwNetType), str(lpProviderName), str(lpBufferSize), str(ret_expr))) 886 | return ret_expr 887 | 888 | 889 | class WNetGetProviderNameA(WNetGetProviderNameA): 890 | def get_provider_string(self, size): 891 | return ("Microsoft"[:size - 1] + '\0').encode('utf-16-le') 892 | 893 | 894 | class CreateToolhelp32Snapshot(StdcallSimProcedure): 895 | def run(self, dwFlags, th32ProcessID): 896 | self.argument_types = { 897 | 0: angr.sim_type.SimTypeInt(), 898 | 1: angr.sim_type.SimTypeInt(), 899 | } 900 | 901 | self.return_type = self.ty_ptr(angr.sim_type.SimTypeTop(self.state.arch)) 902 | 903 | assert not self.state.solver.symbolic(dwFlags) 904 | flags = self.state.solver.eval(dwFlags) 905 | if flags & 0x00000002: # TH32CS_SNAPPROCESS, to enumerate processes 906 | ret_expr = -1 # INVALID_HANDLE_VALUE 907 | else: 908 | ret_expr = self.state.solver.BVS("retval_{}".format(self.display_name), 32) 909 | 910 | l.info("{} @ {}: {}, {} => {}".format( 911 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 912 | str(dwFlags), str(th32ProcessID), str(ret_expr))) 913 | return ret_expr -------------------------------------------------------------------------------- /testing/__init__.py: -------------------------------------------------------------------------------- 1 | from utilities import setup 2 | -------------------------------------------------------------------------------- /testing/malware/M4LW4R3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farosato/angr-antievasion/c16e96d4015cb1799ba852d7bffa78401d651584/testing/malware/M4LW4R3 -------------------------------------------------------------------------------- /testing/malware/malware_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import angr 4 | # python path hack to import package angr_antievasion in sibling directory 5 | import os 6 | rootdir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 7 | os.sys.path.insert(0, rootdir) 8 | import angr_antievasion 9 | import testing.utilities 10 | import logging 11 | 12 | CHECK_TABLE = [ 13 | ('evasive_checks', 0x4020F4), 14 | ] 15 | 16 | 17 | def test(): 18 | logging.getLogger('angr_antievasion').setLevel(logging.INFO) 19 | logging.getLogger('testing.utilities').setLevel(logging.INFO) 20 | logging.getLogger('angr.procedures').setLevel(logging.DEBUG) 21 | # logging.getLogger('angr.procedures.win32').setLevel(logging.INFO) 22 | # logging.getLogger().setLevel(logging.WARNING) 23 | # logging.getLogger('angr.project').setLevel(logging.DEBUG) 24 | # logging.getLogger('angr.analyses.callee_cleanup_finder').setLevel(logging.INFO) 25 | # logging.getLogger("cle.loader").setLevel(logging.DEBUG) 26 | # logging.getLogger("angr.procedures.libc.memcmp").setLevel(logging.DEBUG) 27 | 28 | proj = angr.Project('./M4LW4R3', load_options={ 29 | 'auto_load_libs': True, 30 | 'use_system_libs': False, 31 | 'case_insensitive': True, 32 | 'custom_ld_path': '../../windows_dlls', 33 | 'except_missing_libs': True, 34 | }) 35 | 36 | # stub out imports 37 | proj.analyses.CalleeCleanupFinder(hook_all=True) 38 | 39 | # Alternative, expensive but exhaustive way to stub out all imports 40 | # for obj in proj.loader.all_pe_objects: 41 | # # stub out all imports (by stubbing out each module exports) 42 | # export_addrs = [x.rebased_addr for x in obj._exports.values() if x.forwarder is None] 43 | # proj.analyses.CalleeCleanupFinder(starts=export_addrs, hook_all=True) 44 | 45 | # setup testing utilities 46 | # symbols for which no SimProcedure is available and/or is better to use the actual implementation 47 | no_sim_syms = ['_vsnprintf', 'mbstowcs', 'wcsstr', 'lstrcmpiA', 'lstrcmpiW'] 48 | # snprintf is (ab)used by pafish: angr stub returns an empty string so it's useless 49 | testing.setup(proj, aux_hooks=True, unhook=no_sim_syms) 50 | 51 | # anti-evasion hooks 52 | angr_antievasion.hook_all(proj) 53 | 54 | # return address for the check call state configuration 55 | ret_addr = proj.loader.extern_object.allocate() 56 | 57 | # import IPython; IPython.embed() 58 | 59 | for check_name, check_addr in CHECK_TABLE: 60 | print '\n### {} check @ {} ###'.format(check_name, hex(check_addr)) 61 | 62 | call_state_extended = proj.factory.call_state(check_addr, ret_addr=ret_addr) 63 | 64 | simgr = proj.factory.simulation_manager(call_state_extended, save_unconstrained=True) 65 | 66 | print '\n! Instrumented exploration !' 67 | 68 | while len(simgr.active) > 0: 69 | simgr.explore(find=ret_addr) 70 | 71 | print simgr 72 | 73 | import IPython; IPython.embed() 74 | 75 | 76 | if __name__ == '__main__': 77 | test() 78 | -------------------------------------------------------------------------------- /testing/pafish/generate_check_table.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from subprocess import check_output 3 | import json 4 | 5 | 6 | if __name__ == '__main__': 7 | # create dict symbols -> addr 8 | bin_name = sys.argv[1] 9 | check_file_name = sys.argv[2] 10 | 11 | symbols = {} 12 | nm_output = check_output("nm --demangle {}".format(bin_name), shell=True).split('\n') 13 | for line in nm_output: 14 | line = line.split() 15 | try: 16 | addr, symtype, sym = line 17 | if symtype == 'T': 18 | symbols[sym] = int(addr, 16) 19 | except ValueError: 20 | pass # bad line 21 | 22 | with open(check_file_name, 'r') as cf: 23 | lines = [line.strip() for line in cf] 24 | checks = [] 25 | for line in lines: 26 | try: 27 | line = line[:line.index('#')].strip() 28 | except ValueError: 29 | pass 30 | if line: 31 | checks.append(line) 32 | 33 | check_table = [] 34 | for c in checks: 35 | check_table.append((c, symbols[c])) 36 | 37 | # dump to json file 38 | with open(bin_name + '_checks.json', 'w') as jdump: 39 | jdump.write(json.dumps(check_table, indent=4)) 40 | -------------------------------------------------------------------------------- /testing/pafish/pafish.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farosato/angr-antievasion/c16e96d4015cb1799ba852d7bffa78401d651584/testing/pafish/pafish.exe -------------------------------------------------------------------------------- /testing/pafish/pafish.exe_checks.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "debug_isdebuggerpresent", 4 | 4206088 5 | ], 6 | [ 7 | "debug_outputdebugstring", 8 | 4206119 9 | ], 10 | [ 11 | "cpu_rdtsc", 12 | 4212523 13 | ], 14 | [ 15 | "cpu_rdtsc_force_vmexit", 16 | 4212675 17 | ], 18 | [ 19 | "cpu_hv", 20 | 4212827 21 | ], 22 | [ 23 | "cpu_known_vm_vendors", 24 | 4212992 25 | ], 26 | [ 27 | "gensandbox_mouse_act", 28 | 4208639 29 | ], 30 | [ 31 | "gensandbox_username", 32 | 4208728 33 | ], 34 | [ 35 | "gensandbox_path", 36 | 4208956 37 | ], 38 | [ 39 | "gensandbox_common_names", 40 | 4209186 41 | ], 42 | [ 43 | "gensandbox_drive_size", 44 | 4209509 45 | ], 46 | [ 47 | "gensandbox_drive_size2", 48 | 4209747 49 | ], 50 | [ 51 | "gensandbox_sleep_patched", 52 | 4209834 53 | ], 54 | [ 55 | "gensandbox_one_cpu", 56 | 4209898 57 | ], 58 | [ 59 | "gensandbox_one_cpu_GetSystemInfo", 60 | 4209938 61 | ], 62 | [ 63 | "gensandbox_less_than_onegb", 64 | 4209974 65 | ], 66 | [ 67 | "gensandbox_uptime", 68 | 4210041 69 | ], 70 | [ 71 | "gensandbox_IsNativeVhdBoot", 72 | 4210067 73 | ], 74 | [ 75 | "check_hook_DeleteFileW_m1", 76 | 4211389 77 | ], 78 | [ 79 | "check_hook_ShellExecuteExW_m1", 80 | 4211410 81 | ], 82 | [ 83 | "check_hook_CreateProcessA_m1", 84 | 4211431 85 | ], 86 | [ 87 | "vbox_reg_key1", 88 | 4206291 89 | ], 90 | [ 91 | "vbox_reg_key2", 92 | 4206335 93 | ], 94 | [ 95 | "vbox_reg_key3", 96 | 4206379 97 | ], 98 | [ 99 | "vbox_reg_key4", 100 | 4206407 101 | ], 102 | [ 103 | "vbox_reg_key5", 104 | 4206451 105 | ], 106 | [ 107 | "vbox_reg_key7", 108 | 4206479 109 | ], 110 | [ 111 | "vbox_reg_key8", 112 | 4206507 113 | ], 114 | [ 115 | "vbox_reg_key9", 116 | 4206535 117 | ], 118 | [ 119 | "vbox_reg_key10", 120 | 4206808 121 | ], 122 | [ 123 | "vbox_sysfile1", 124 | 4206852 125 | ], 126 | [ 127 | "vbox_sysfile2", 128 | 4207114 129 | ], 130 | [ 131 | "vbox_mac", 132 | 4207476 133 | ], 134 | [ 135 | "vbox_devices", 136 | 4207496 137 | ], 138 | [ 139 | "vbox_traywindow", 140 | 4207823 141 | ], 142 | [ 143 | "vbox_network_share", 144 | 4207911 145 | ], 146 | [ 147 | "vbox_processes", 148 | 4208074 149 | ], 150 | [ 151 | "vbox_wmi_devices", 152 | 4208486 153 | ], 154 | [ 155 | "vmware_reg_key1", 156 | 4210323 157 | ], 158 | [ 159 | "vmware_reg_key2", 160 | 4210463 161 | ], 162 | [ 163 | "vmware_sysfile1", 164 | 4210491 165 | ], 166 | [ 167 | "vmware_sysfile2", 168 | 4210511 169 | ], 170 | [ 171 | "vmware_mac", 172 | 4210531 173 | ], 174 | [ 175 | "vmware_adapter_name", 176 | 4210636 177 | ], 178 | [ 179 | "vmware_devices", 180 | 4210656 181 | ], 182 | [ 183 | "vmware_wmi_serial", 184 | 4211096 185 | ], 186 | [ 187 | "qemu_reg_key1", 188 | 4211196 189 | ], 190 | [ 191 | "qemu_reg_key2", 192 | 4211240 193 | ], 194 | [ 195 | "qemu_cpu_name", 196 | 4211284 197 | ], 198 | [ 199 | "bochs_reg_key1", 200 | 4213344 201 | ], 202 | [ 203 | "bochs_cpu_amd1", 204 | 4213388 205 | ], 206 | [ 207 | "bochs_cpu_amd2", 208 | 4213442 209 | ], 210 | [ 211 | "bochs_cpu_intel1", 212 | 4213494 213 | ], 214 | [ 215 | "sboxie_detect_sbiedll", 216 | 4206192 217 | ], 218 | [ 219 | "cuckoo_check_tls", 220 | 4213251 221 | ], 222 | [ 223 | "wine_detect_get_unix_file_name", 224 | 4210156 225 | ], 226 | [ 227 | "wine_reg_key1", 228 | 4210237 229 | ] 230 | ] -------------------------------------------------------------------------------- /testing/pafish/pafish_checks.txt: -------------------------------------------------------------------------------- 1 | # Debuggers detection 2 | debug_isdebuggerpresent 3 | debug_outputdebugstring 4 | 5 | # CPU information based detection 6 | cpu_rdtsc 7 | cpu_rdtsc_force_vmexit 8 | cpu_hv # simuvex dirty helper for CPUID is enough 9 | cpu_known_vm_vendors # simuvex dirty helper for CPUID is enough 10 | 11 | # Generic sandbox detection 12 | gensandbox_mouse_act 13 | gensandbox_username 14 | gensandbox_path 15 | gensandbox_common_names # need vsnprintf SimProcedure (currently a stub) 16 | gensandbox_drive_size # hacked 17 | gensandbox_drive_size2 # hacked 18 | gensandbox_sleep_patched 19 | gensandbox_one_cpu # don't think it's possible to patch it (symbolic indirection) 20 | gensandbox_one_cpu_GetSystemInfo # correctly constrained num processors, return value might still be conditional bc it doesn't affect control flow 21 | gensandbox_less_than_onegb # correctly constrained available ram, return value might still be conditional bc it doesn't affect control flow 22 | gensandbox_uptime 23 | gensandbox_IsNativeVhdBoot 24 | 25 | # Hooks detection 26 | check_hook_DeleteFileW_m1 27 | check_hook_ShellExecuteExW_m1 28 | check_hook_CreateProcessA_m1 29 | 30 | # VirtualBox detection tricks 31 | vbox_reg_key1 32 | vbox_reg_key2 33 | vbox_reg_key3 34 | vbox_reg_key4 35 | vbox_reg_key5 36 | vbox_reg_key7 37 | vbox_reg_key8 38 | vbox_reg_key9 39 | vbox_reg_key10 40 | vbox_sysfile1 41 | vbox_sysfile2 42 | vbox_mac 43 | vbox_devices 44 | vbox_traywindow 45 | vbox_network_share 46 | vbox_processes # hacked 47 | vbox_wmi_devices # huge overkill to implement, actually fails by itself 48 | 49 | # VMWare detection tricks 50 | vmware_reg_key1 51 | vmware_reg_key2 52 | vmware_sysfile1 53 | vmware_sysfile2 54 | vmware_mac 55 | vmware_adapter_name # needs auxiliary string conversion functions 56 | vmware_devices 57 | vmware_wmi_serial # huge overkill to implement, actually fails by itself 58 | 59 | # Qemu detection tricks 60 | qemu_reg_key1 61 | qemu_reg_key2 62 | qemu_cpu_name 63 | 64 | # Bochs detection tricks 65 | bochs_reg_key1 66 | bochs_cpu_amd1 # simuvex dirty helper for CPUID is enough 67 | bochs_cpu_amd2 # simuvex dirty helper for CPUID is enough 68 | bochs_cpu_intel1 # simuvex dirty helper for CPUID is enough 69 | 70 | # Sandboxie detection tricks 71 | sboxie_detect_sbiedll 72 | 73 | # Cuckoo detection tricks 74 | cuckoo_check_tls # not worth it 75 | 76 | # Wine detection tricks 77 | wine_detect_get_unix_file_name 78 | wine_reg_key1 -------------------------------------------------------------------------------- /testing/pafish/pafish_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import angr 4 | # python path hack to import package angr_antievasion in sibling directory 5 | import os 6 | rootdir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 7 | os.sys.path.insert(0, rootdir) 8 | import angr_antievasion 9 | import testing.utilities 10 | import logging 11 | import json 12 | from termcolor import colored 13 | 14 | CHECK_TABLE = [ 15 | ('vbox_regkey9', 4209898), 16 | ] 17 | 18 | UNAIDED_SKIP = ['vbox_mac', 'vbox_processes', 'vmware_mac', 'vmware_adapter_name', 19 | 'vbox_reg_key9', 'vbox_sysfile2', 'vmware_reg_key1'] 20 | 21 | 22 | def test(): 23 | logging.getLogger('angr_antievasion').setLevel(logging.INFO) 24 | logging.getLogger('testing.utilities').setLevel(logging.INFO) 25 | logging.getLogger('angr.procedures').setLevel(logging.DEBUG) 26 | 27 | proj_unaided = angr.Project('./pafish.exe', load_options={ 28 | 'auto_load_libs': True, 29 | 'use_system_libs': False, 30 | 'case_insensitive': True, 31 | 'custom_ld_path': '../../windows_dlls', 32 | 'except_missing_libs': True, 33 | }) 34 | 35 | proj_extended = angr.Project('./pafish.exe', load_options={ 36 | 'auto_load_libs': True, 37 | 'use_system_libs': False, 38 | 'case_insensitive': True, 39 | 'custom_ld_path': '../../windows_dlls', 40 | 'except_missing_libs': True, 41 | }) 42 | 43 | # stub out imports 44 | proj_unaided.analyses.CalleeCleanupFinder(hook_all=True) 45 | proj_extended.analyses.CalleeCleanupFinder(hook_all=True) 46 | 47 | # Alternative, expensive but exhaustive way to stub out all imports 48 | # for obj in proj.loader.all_pe_objects: 49 | # # stub out all imports (by stubbing out each module exports) 50 | # export_addrs = [x.rebased_addr for x in obj._exports.values() if x.forwarder is None] 51 | # proj.analyses.CalleeCleanupFinder(starts=export_addrs, hook_all=True) 52 | 53 | # setup testing utilities 54 | # symbols for which no SimProcedure is available and/or is better to use the actual implementation 55 | no_sim_syms = ['_vsnprintf', 'mbstowcs', 'wcsstr', 'toupper', 'tolower', 'lstrcmpiA', 'lstrcmpiW'] 56 | # snprintf is (ab)used by pafish: angr stub returns an empty string so it's useless 57 | # we use the concrete implementation for the extended, and an unconstrained stub for the unaided 58 | testing.setup(proj_unaided, cdecl_stub=['_vsnprintf'], stdcall_stub=['IsWow64Process']) 59 | testing.setup(proj_extended, unhook=no_sim_syms) 60 | 61 | # anti-evasion hooks 62 | angr_antievasion.hook_all(proj_extended) 63 | 64 | # return addresses for the check call state configuration 65 | ret_addr_unaided = proj_unaided.loader.extern_object.allocate() 66 | ret_addr_extended = proj_extended.loader.extern_object.allocate() 67 | 68 | # import IPython; IPython.embed() 69 | 70 | if 'CHECK_TABLE' not in globals(): 71 | # load it from json file 72 | with open('pafish.exe_checks.json', 'r') as jfile: 73 | global CHECK_TABLE 74 | CHECK_TABLE = json.load(jfile) 75 | 76 | latex_table = [] 77 | 78 | for check_name, check_addr in CHECK_TABLE: 79 | print '\n### {} check @ {} ###'.format(check_name, hex(check_addr)) 80 | 81 | call_state_unaided = proj_unaided.factory.call_state(check_addr, ret_addr=ret_addr_unaided) 82 | call_state_extended = proj_extended.factory.call_state(check_addr, ret_addr=ret_addr_extended) 83 | 84 | simgr_unaided = proj_unaided.factory.simulation_manager(call_state_unaided, save_unconstrained=True) 85 | simgr_extended = proj_extended.factory.simulation_manager(call_state_extended, save_unconstrained=True) 86 | 87 | print '! Unaided exploration !' 88 | unaided_total = 0 89 | unaided_false = 0 90 | unaided_true = 0 91 | 92 | if check_name in UNAIDED_SKIP: 93 | print 'SKIPPED' 94 | else: 95 | angr_antievasion.rdtsc_monkey_unpatch() # monkey patch is global so we need to patch and unpatch for each check 96 | while len(simgr_unaided.active) > 0: 97 | if check_name in UNAIDED_SKIP: 98 | break 99 | simgr_unaided.explore(find=ret_addr_unaided) 100 | 101 | print simgr_unaided 102 | 103 | for sim in simgr_unaided.found: 104 | ret = sim.state.regs.eax 105 | ret_str = colored(ret, 'red') 106 | if not sim.state.solver.symbolic(ret) and sim.state.solver.eval(ret) == 0: 107 | ret_str = colored(ret, 'cyan') 108 | unaided_false += 1 109 | else: 110 | if sim.state.solver.symbolic(ret): 111 | unaided_false += 1 # symbolic means undetermined so add to false too 112 | unaided_total += 1 113 | unaided_true += 1 114 | print sim, "returned {}".format(ret_str) 115 | # import IPython; IPython.embed() 116 | unaided_total += len(simgr_unaided.found) 117 | 118 | print '\n! Instrumented exploration !' 119 | extended_total = 0 120 | extended_false = 0 121 | extended_true = 0 122 | angr_antievasion.rdtsc_monkey_patch() # monkey patch is global so we need to patch and unpatch for each check 123 | 124 | while len(simgr_extended.active) > 0: 125 | simgr_extended.explore(find=ret_addr_extended) 126 | 127 | print simgr_extended 128 | 129 | for sim in simgr_extended.found: 130 | ret = sim.state.regs.eax 131 | ret_str = colored(ret, 'red') 132 | if not sim.state.solver.symbolic(ret) and sim.state.solver.eval(ret) == 0: 133 | ret_str = colored(ret, 'cyan') 134 | extended_false += 1 135 | else: 136 | if sim.state.solver.symbolic(ret): 137 | extended_false += 1 # symbolic means undetermined so add to false too 138 | extended_total += 1 139 | extended_true += 1 140 | print sim, "returned {}".format(ret_str) 141 | # import IPython; IPython.embed() 142 | extended_total += len(simgr_extended.found) 143 | 144 | import IPython; IPython.embed() 145 | 146 | latex_table.append("{} & {} & {} & {} && {} & {} & {}".format( 147 | check_name, 148 | unaided_total, unaided_true, unaided_false, 149 | extended_total, extended_true, extended_false 150 | )) 151 | 152 | for line in latex_table: 153 | print line 154 | 155 | 156 | if __name__ == '__main__': 157 | test() 158 | -------------------------------------------------------------------------------- /testing/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | from aux_simprocs import * 2 | from angr.calling_conventions import SimCCCdecl, SimCCStdcall 3 | from angr.procedures.stubs.ReturnUnconstrained import ReturnUnconstrained 4 | import inspect 5 | 6 | 7 | def _hook_all_aux(proj): 8 | sim_procs = [x for x in globals().values() if inspect.isclass(x) and issubclass(x, angr.SimProcedure)] 9 | 10 | for sp in sim_procs: 11 | if issubclass(sp, StdcallSimProcedure): 12 | proj.hook_symbol(sp.__name__, sp(cc=SimCCStdcall(proj.arch))) 13 | else: 14 | # use default cc for the arch (for x86 it's Cdecl) 15 | proj.hook_symbol(sp.__name__, sp()) 16 | 17 | 18 | def setup(proj, aux_hooks=False, unhook=[], cdecl_stub=[], stdcall_stub=[]): 19 | if aux_hooks: 20 | _hook_all_aux(proj) 21 | 22 | for sym in unhook: 23 | proj.unhook_symbol(sym) 24 | 25 | for sym in cdecl_stub: 26 | proj.hook_symbol(sym, ReturnUnconstrained(cc=SimCCCdecl(proj.arch), is_stub=True)) 27 | 28 | for sym in stdcall_stub: 29 | proj.hook_symbol(sym, ReturnUnconstrained(cc=SimCCStdcall(proj.arch), is_stub=True)) 30 | -------------------------------------------------------------------------------- /testing/utilities/aux_simprocs.py: -------------------------------------------------------------------------------- 1 | import angr 2 | from angr_antievasion import StdcallSimProcedure 3 | import logging 4 | 5 | l = logging.getLogger("testing.utilities") 6 | 7 | 8 | # Auxiliary SimProcedures to perform tests 9 | # (without them checks could fail for "accessory" reasons, e.g. string handling functions not working or not "present") 10 | # Practically all of them assert we are handling concrete input, so they are not suitable for purely symbolic tests. 11 | 12 | class toupper(angr.SimProcedure): 13 | def run(self, c): 14 | self.argument_types = {0: angr.sim_type.SimTypeInt(self.state.arch, True)} 15 | self.return_type = angr.sim_type.SimTypeInt(self.state.arch, True) 16 | 17 | ret_expr = self.state.solver.If( 18 | self.state.solver.And(c >= 97, c <= 122), # a - z 19 | c - 32, c) 20 | l.info('{} @ {}: {} => {}'.format( 21 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 22 | c, ret_expr)) 23 | return ret_expr 24 | 25 | 26 | class tolower(angr.SimProcedure): 27 | def run(self, c): 28 | self.argument_types = {0: angr.sim_type.SimTypeInt(self.state.arch, True)} 29 | self.return_type = angr.sim_type.SimTypeInt(self.state.arch, True) 30 | 31 | ret_expr = self.state.solver.If( 32 | self.state.solver.And(c >= 65, c <= 90), # A - Z 33 | c + 32, c) 34 | l.info('{} @ {}: {} => {}'.format( 35 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 36 | c, ret_expr)) 37 | return ret_expr 38 | 39 | 40 | class lstrcmpiA(StdcallSimProcedure): 41 | def extract_string(self, addr): 42 | return self.state.mem[addr].string.concrete 43 | 44 | def run(self, lpString1, lpString2): 45 | self.argument_types = {0: self.ty_ptr(angr.sim_type.SimTypeString()), 46 | 1: self.ty_ptr(angr.sim_type.SimTypeString())} 47 | self.return_type = angr.sim_type.SimTypeInt(32, True) 48 | 49 | assert not self.state.solver.symbolic(lpString1) 50 | assert not self.state.solver.symbolic(lpString2) 51 | 52 | str1 = self.extract_string(lpString1) 53 | str_l1 = str1.lower() 54 | str2 = self.extract_string(lpString2) 55 | str_l2 = str2.lower() 56 | ret_expr = -1 if str_l1 < str_l2 else 1 if str_l1 > str_l2 else 0 57 | 58 | l.info('{} @ {}: {} ({}), {} ({}) => {}'.format( 59 | self.display_name, self.state.memory.load(self.state.regs.esp, 4, endness=self.arch.memory_endness), 60 | lpString1, str1, lpString2, str2, ret_expr)) 61 | return ret_expr 62 | 63 | 64 | class lstrcmpiW(lstrcmpiA): 65 | def extract_string(self, addr): 66 | return self.state.mem[addr].wstring.concrete -------------------------------------------------------------------------------- /thesis/msc_slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farosato/angr-antievasion/c16e96d4015cb1799ba852d7bffa78401d651584/thesis/msc_slides.pdf -------------------------------------------------------------------------------- /thesis/msc_thesis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farosato/angr-antievasion/c16e96d4015cb1799ba852d7bffa78401d651584/thesis/msc_thesis.pdf --------------------------------------------------------------------------------