├── WechatIMG18960.png ├── WechatIMG209.png ├── arm_unwind_plugin.py └── readme.md /WechatIMG18960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeadroyaL/IDA_ARM_Unwind/9a44a7da0ea1bee79c028107e0a72d031ecfd2ed/WechatIMG18960.png -------------------------------------------------------------------------------- /WechatIMG209.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeadroyaL/IDA_ARM_Unwind/9a44a7da0ea1bee79c028107e0a72d031ecfd2ed/WechatIMG209.png -------------------------------------------------------------------------------- /arm_unwind_plugin.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from idc import * 3 | from idaapi import * 4 | from PyQt5 import QtWidgets, QtGui 5 | 6 | from io import BytesIO 7 | from elftools.elf.elffile import ELFFile 8 | from elftools.elf.structs import ELFStructs 9 | from elftools.ehabi.ehabiinfo import EHABIInfo 10 | 11 | 12 | class ArmUnwinder: 13 | def __init__(self): 14 | self.unwind_caches = list() 15 | self.pid = get_event_pid() 16 | 17 | def check_pid(self, pid): 18 | if pid == self.pid: 19 | return 20 | print "Pid changed, refresh arm unwind caches." 21 | self.unwind_caches = list() 22 | self.pid = pid 23 | 24 | def unwind_all(self): 25 | self.check_pid(get_event_pid()) 26 | _status = VRSStatus() 27 | _status.load_ida_registers() 28 | pc_list = list() 29 | pc_list.append(GetRegValue("PC")) 30 | while self.unwind_single_frame(_status): 31 | pc_value = _status.getRx(_status.PC_INDEX) 32 | pc_list.append(pc_value) 33 | return pc_list 34 | 35 | def unwind_single_frame(self, _status): 36 | # 1. 拿PC 37 | pc = _status.getRx(_status.PC_INDEX) 38 | # 2. 查cache 39 | unwind_item = None 40 | for unwind_cache in self.unwind_caches: 41 | if unwind_cache.startEA <= pc < unwind_cache.endEA: 42 | unwind_item = unwind_cache 43 | print "found cache", unwind_cache.name 44 | break 45 | if unwind_item is None: 46 | # 2.5 查不到cache就去解析内存 47 | cache = self.create_unwind_cache(pc) 48 | if cache is None: 49 | print "cannot unwind this pc", '%0.8x' % pc 50 | else: 51 | print "add", cache.name, "into cache" 52 | self.unwind_caches.append(cache) 53 | unwind_item = cache 54 | 55 | # 3. 使用cache 56 | if unwind_item is None: 57 | return False 58 | else: 59 | entries = unwind_item.entries 60 | relative_pc = pc - unwind_item.startEA 61 | for i in range(len(entries) - 1): 62 | if entries[i].function_offset <= relative_pc < entries[i + 1].function_offset: 63 | print 'find entry', entries[i] 64 | # 4. 如果entry不包含bytecode 65 | if entries[i].bytecode_array is None: 66 | print "cannot unwind", entries[i] 67 | return False 68 | # 4.5 进行unwind 69 | if _interp(entries[i].bytecode_array, _status) == URC_FAILURE: 70 | raise RuntimeError("interpret fail") 71 | lr_value = _status.getRx(_status.LR_INDEX) 72 | if lr_value == 0: 73 | return False 74 | # 5. lr -> pc 75 | _status.setRx(_status.PC_INDEX, _lr2pc(lr_value)) 76 | print 'pc', '%0.8x' % _status.getRx(_status.PC_INDEX) 77 | return True 78 | print "Cannot find entry for pc", '%0.8x' % pc 79 | return False 80 | 81 | @staticmethod 82 | def create_unwind_cache(_pc): 83 | """根据当前 pc,解析对应的 elf,拿到 exception handler entry 表""" 84 | # 找到 ELF 头 85 | _header_seg, _seg_name = _getELFHeader(_pc) 86 | if not _checkELFHeader(_header_seg): 87 | print "cannot determine ELF header of this module", _seg_name 88 | return None 89 | # 稍微加载一小段,用于获得 program headers,没必要加载完 90 | _header_data = DbgRead(_header_seg.start_ea, min(0x1000, _header_seg.size())) 91 | _stream = BytesIO(_header_data) 92 | 93 | _elf = MemoryELFFile(_stream) 94 | _exidx_segment = _elf.get_exidx_segment() 95 | if _exidx_segment is None: 96 | print 'cannot get PT_ARM_EXIDX of this module', _seg_name 97 | return None 98 | _exidx_offset = _exidx_segment['p_offset'] 99 | _exidx_size = _exidx_segment['p_memsz'] 100 | _load_size = _elf.get_load_segment()['p_memsz'] 101 | _stream.close() 102 | 103 | # 加载整个内存,需要获得 .arm.exidx 和 .arm.extab 的数据 104 | _load_data = DbgRead(_header_seg.start_ea, _load_size) 105 | _stream = BytesIO(_load_data) 106 | _ehabi_info = EHABIInfo(FakeSection(_stream, _exidx_offset, _exidx_size), _elf.little_endian) 107 | _unwind_entries = [_ehabi_info.get_entry(i) for i in range(_ehabi_info.num_entry())] 108 | _stream.close() 109 | return UnwindItem(_header_seg.start_ea, _header_seg.start_ea + _load_size, _seg_name, _unwind_entries) 110 | 111 | @staticmethod 112 | def pc2frame(_pc): 113 | name = get_segm_name(getseg(_pc)) 114 | if name == '.text': 115 | name = get_root_filename() 116 | base = get_imagebase() 117 | else: 118 | base = get_segm_start(_pc) 119 | relative_pc = _pc - base 120 | fullpath = get_module_name(base) 121 | if fullpath == 0: 122 | fullpath = "" 123 | return Frame(relative_pc, _pc, name, fullpath) 124 | 125 | @staticmethod 126 | def pclist2framelist(_pclist): 127 | return [ArmUnwinder.pc2frame(_pc) for _pc in _pclist] 128 | 129 | 130 | def pc2func(_pc): 131 | _func = get_func(_pc) 132 | if _func is None: 133 | return "" 134 | return "%s + %d" % (get_func_name(_pc), _pc - _func.startEA) 135 | 136 | 137 | class Frame: 138 | def __init__(self, relative_pc, absolute_pc, name, fullpath): 139 | self.relative_pc = relative_pc 140 | self.func_with_offset = pc2func(absolute_pc) 141 | self.absolute_pc = absolute_pc 142 | self.name = name 143 | self.fullpath = fullpath 144 | 145 | def __repr__(self): 146 | return "%s-%s-%0.8x-%0.8x" % (self.name, self.fullpath, self.relative_pc, self.absolute_pc) 147 | 148 | 149 | class MemoryELFFile(ELFFile): 150 | """精简过的 ELFFile,去除里面多余的功能""" 151 | 152 | def __init__(self, stream): 153 | self.stream = stream 154 | self._identify_file() 155 | self.structs = ELFStructs( 156 | little_endian=self.little_endian, 157 | elfclass=self.elfclass) 158 | 159 | self.structs.create_basic_structs() 160 | self.header = self._parse_elf_header() 161 | self.structs.e_type = self['e_type'] 162 | self.structs.e_machine = self['e_machine'] 163 | self.structs.e_ident_osabi = self['e_ident']['EI_OSABI'] 164 | self.structs._create_phdr() 165 | 166 | def get_load_segment(self): 167 | return self._get_first_segment_by_type('PT_LOAD') 168 | 169 | def get_exidx_segment(self): 170 | return self._get_first_segment_by_type('PT_ARM_EXIDX') 171 | 172 | def _get_first_segment_by_type(self, _type): 173 | for i in range(self.num_segments()): 174 | seg_header = self._get_segment_header(i) 175 | seg_type = seg_header['p_type'] 176 | if seg_type == _type: 177 | return seg_header 178 | 179 | 180 | class FakeSection: 181 | """因为无法从内存中恢复完整的 Section,只能手动创建虚假的 Section,填充关键数据""" 182 | 183 | def __init__(self, stream, offset, size): 184 | self.stream = stream 185 | self.offset = offset 186 | self.size = size 187 | 188 | def __getitem__(self, item): 189 | if item == 'sh_offset': 190 | return self.offset 191 | elif item == 'sh_size': 192 | return self.size 193 | 194 | 195 | class UnwindItem: 196 | def __init__(self, _startEA, _endEA, _seg_name, _unwind_entries): 197 | self.startEA = _startEA 198 | self.endEA = _endEA 199 | self.name = _seg_name 200 | self.entries = _unwind_entries 201 | 202 | 203 | def _getELFHeader(_pc): 204 | """ 205 | parameter pc 206 | return (segment, module_name) 207 | """ 208 | name = get_segm_name(getseg(_pc)) 209 | if name == '.text': 210 | # 如果是.text,说明就是正在被分析的文件 211 | return getseg(get_imagebase()), get_root_filename() 212 | else: 213 | # 寻找同名module,通常第一个就是 R+X而且 7FELF 214 | return get_segm_by_name(name), name 215 | 216 | 217 | def _checkELFHeader(_seg): 218 | DWORD_MAGIC = 0x464c457f 219 | return DWORD_MAGIC == Dword(_seg.startEA) 220 | 221 | 222 | def _lr2pc(lr_value): 223 | """lr是下一条指令,arm是-4的位置,thumb可能是-2和-4,需要都尝试一遍""" 224 | if lr_value & 1 == 0: 225 | # ARM, 32byte 226 | return lr_value - 4 227 | else: 228 | # thumb, 16byte or 32byte 229 | test1 = lr_value - 1 - 4 230 | test2 = lr_value - 1 - 2 231 | hit1 = False 232 | hit2 = False 233 | pc = None 234 | if Word(test1) & 0b11110 << 11 == 0b11110 << 11 and Word(test1 + 2) & 0b11 << 14 == 0b11 << 14: 235 | hit1 = True 236 | if Word(test2) & 0b010001111 << 7 == 0b010001111 << 7: 237 | hit2 = True 238 | if hit1 and hit2: 239 | print "cannot determine -2 or -4" 240 | elif hit1: 241 | pc = test1 + 1 242 | elif hit2: 243 | pc = test2 + 1 244 | else: 245 | raise RuntimeError("disasm result is not BL/BLX", lr_value) 246 | return pc 247 | 248 | 249 | class VRSStatus: 250 | """解释执行用于存放寄存器状态""" 251 | 252 | def __init__(self): 253 | self._regs = [0] * 16 254 | self._double_regs = [0] * 32 255 | 256 | VSP_INDEX = 13 257 | FP_INDEX = 11 258 | IP_INDEX = 12 259 | SP_INDEX = 12 260 | LR_INDEX = 14 261 | PC_INDEX = 15 262 | 263 | def load_ida_registers(self): 264 | """从 IDA 读取初始状态""" 265 | for i in range(16): 266 | self._regs[i] = GetRegValue("R%d" % i) 267 | 268 | def vsp_mov(self, data): 269 | self._regs[self.VSP_INDEX] = data 270 | 271 | def vsp_add(self, data): 272 | self._regs[self.VSP_INDEX] += data 273 | 274 | def vsp_sub(self, data): 275 | self._regs[self.VSP_INDEX] -= data 276 | 277 | def pop(self, registers): 278 | # pop uint32 register 279 | # pop {R0,R1,R2} -> pop R0;pop R1;pop R2 280 | for i in range(0, 16): 281 | if registers & (1 << i) != 0: 282 | self._regs[i] = Dword(self._regs[self.VSP_INDEX]) 283 | self._regs[self.VSP_INDEX] += 4 284 | 285 | def popD(self, registers): 286 | # pop uint32 register 287 | # pop {D0,D1,D2} -> pop D0;pop D1;pop D2 288 | for i in range(0, 32): 289 | if registers & (1 << i) != 0: 290 | self._double_regs[i] = Dword(self._regs[self.VSP_INDEX]) 291 | self._double_regs[self.VSP_INDEX] += 4 292 | 293 | def getRx(self, n): 294 | return self._regs[n] 295 | 296 | def setRx(self, n, value): 297 | self._regs[n] = value 298 | 299 | def __repr__(self): 300 | part1 = """R0: %0.8x, R1: %0.8x, R2: %0.8x, R3: %0.8x 301 | R4: %0.8x, R5: %0.8x, R6: %0.8x, R7: %0.8x 302 | R8: %0.8x, R9: %0.8x, R10:%0.8x, FP: %0.8x 303 | IP: %0.8x, SP: %0.8x, LR: %0.8x, PC: %0.8x""" % ( 304 | self._regs[0], self._regs[1], self._regs[2], self._regs[3], 305 | self._regs[4], self._regs[5], self._regs[6], self._regs[7], 306 | self._regs[8], self._regs[9], self._regs[10], self._regs[11], 307 | self._regs[12], self._regs[13], self._regs[14], self._regs[15],) 308 | if any(s != 0 for s in self._double_regs): 309 | part2 = """D0: %0.8x, D1: %0.8x, D2: %0.8x, D3: %0.8x 310 | D4: %0.8x, D5: %0.8x, D6: %0.8x, D7: %0.8x 311 | D8: %0.8x, D9: %0.8x, D10: %0.8x, D11: %0.8x 312 | D12: %0.8x, D13: %0.8x, D14: %0.8x, D15: %0.8x 313 | D16: %0.8x, D17: %0.8x, D18: %0.8x, D19: %0.8x 314 | D20: %0.8x, D21: %0.8x, D22: %0.8x, D23: %0.8x 315 | D24: %0.8x, D25: %0.8x, D26: %0.8x, D27: %0.8x 316 | D28: %0.8x, D29: %0.8x, D30: %0.8x, D31: %0.8x""" % ( 317 | self._double_regs[0], self._double_regs[1], self._double_regs[2], self._double_regs[3], 318 | self._double_regs[4], self._double_regs[5], self._double_regs[6], self._double_regs[7], 319 | self._double_regs[8], self._double_regs[9], self._double_regs[10], self._double_regs[11], 320 | self._double_regs[12], self._double_regs[13], self._double_regs[14], self._double_regs[15], 321 | self._double_regs[16], self._double_regs[17], self._double_regs[18], self._double_regs[19], 322 | self._double_regs[20], self._double_regs[21], self._double_regs[22], self._double_regs[23], 323 | self._double_regs[24], self._double_regs[25], self._double_regs[26], self._double_regs[27], 324 | self._double_regs[28], self._double_regs[29], self._double_regs[30], self._double_regs[31], 325 | ) 326 | return "%s\n%s" % (part1, part2) 327 | else: 328 | return part1 329 | 330 | 331 | def _register_mask(start, count_minus_one): 332 | return ((1 << (count_minus_one + 1)) - 1) << start 333 | 334 | 335 | URC_SUCCESS = 0 336 | URC_FAILURE = 1 337 | 338 | 339 | def _interp(bytecode_array, status): 340 | """ 341 | reference: https://github.com/llvm/llvm-project/blob/master/libunwind/src/Unwind-EHABI.cpp 342 | _LIBUNWIND_EXPORT _Unwind_Reason_Code 343 | _Unwind_VRS_Interpret(_Unwind_Context *context, const uint32_t *data, size_t offset, size_t len) 344 | """ 345 | offset = 0 346 | finish = False 347 | wrote_pc = False 348 | bc_len = len(bytecode_array) 349 | while offset < bc_len and not finish: 350 | b = int(bytecode_array[offset]) 351 | offset += 1 352 | if b & 0x80 == 0: 353 | if b & 0x40 != 0: 354 | status.vsp_sub(((b & 0x3f) << 2) + 4) 355 | else: 356 | status.vsp_add((b << 2) + 4) 357 | else: 358 | if b & 0xf0 == 0x80: 359 | registers = ((b & 0xf) << 12) | (bytecode_array[offset] << 4) 360 | offset += 1 361 | if registers == 0: 362 | return URC_FAILURE 363 | if registers & (1 << 15) != 0: 364 | wrote_pc = True 365 | status.pop(registers) 366 | elif b & 0xf0 == 0x90: 367 | reg = b & 0x0f 368 | if reg == 13 or reg == 15: 369 | return URC_FAILURE 370 | status.vsp_mov(status.getRx(reg)) 371 | elif b & 0xf0 == 0xa0: 372 | registers = _register_mask(4, b & 0x07) 373 | if b & 0x08 != 0: 374 | registers |= 1 << 14 375 | status.pop(registers) 376 | elif b & 0xf0 == 0xb0: 377 | if b == 0xb0: 378 | finish = True 379 | elif b == 0xb1: 380 | registers = bytecode_array[offset] 381 | offset += 1 382 | if registers & (1 << 15) != 0 or registers == 0: 383 | return URC_FAILURE 384 | status.pop(registers) 385 | elif b == 0xb2: 386 | addend = 0 387 | shift = 0 388 | while True: 389 | b = bytecode_array[offset] 390 | offset += 1 391 | addend |= (b & 0x7f) << shift 392 | if b & 0x80 == 0: 393 | break 394 | shift += 7 395 | status.vsp_add(0x204 + (addend << 2)) 396 | elif b == 0xb3: 397 | b = bytecode_array[offset] 398 | offset += 1 399 | start = ((b & 0xf0) >> 4) 400 | count = ((b & 0x0f) >> 0) 401 | status.popD(_register_mask(start, count)) 402 | elif b == 0xb4 or b == 0xb5 or b == 0xb6 or b == 0xb7: 403 | return URC_FAILURE 404 | else: 405 | b = bytecode_array[offset] 406 | offset += 1 407 | status.popD(_register_mask(8, b & 0x07)) 408 | elif b & 0xf0 == 0xc0: 409 | if b == 0xc0 or b == 0xc1 or b == 0xc2 or b == 0xc3 or b == 0xc4 or b == 0xc5: 410 | raise RuntimeError("unsupport wmmxd") 411 | elif b == 0xc6: 412 | raise RuntimeError("unsupport wmmxd") 413 | elif b == 0xc7: 414 | raise RuntimeError("unsupport wmmxd") 415 | elif b == 0xc8 or b == 0xc9: 416 | b = bytecode_array[offset] 417 | offset += 1 418 | start = 16 + ((b & 0xf0) >> 4) 419 | count = ((b & 0x0f) >> 0) 420 | status.popD(_register_mask(start, count)) 421 | else: 422 | return URC_FAILURE 423 | elif b & 0xf0 == 0xd0: 424 | if b & 0x80 != 0: 425 | return URC_FAILURE 426 | status.popD(_register_mask(8, b & 0x07)) 427 | else: 428 | return URC_FAILURE 429 | if not wrote_pc: 430 | status.setRx(status.IP_INDEX, status.getRx(status.LR_INDEX)) 431 | print "interp done, now list registers" 432 | print status 433 | return URC_SUCCESS 434 | 435 | 436 | class ArmUnwindView(PluginForm): 437 | """GUI container""" 438 | cols = ['Module', 'Path', 'Position', 'Address'] 439 | 440 | def __init__(self, frames): 441 | super(ArmUnwindView, self).__init__() 442 | self.tree = None 443 | self.frames = frames 444 | 445 | def dblclick(self, item): 446 | """Handle double click event.""" 447 | try: 448 | if get_imagebase() == 0: 449 | jumpto(int(item.text(2).split('(')[0], 16)) 450 | else: 451 | jumpto(int(item.text(3), 16)) 452 | except: 453 | pass 454 | 455 | def load_data(self): 456 | x = lambda ea: '0x%X' % ea 457 | 458 | for frame in self.frames: 459 | item = QtWidgets.QTreeWidgetItem(self.tree) 460 | item.setText(0, frame.name) 461 | item.setText(1, frame.fullpath) 462 | item.setText(2, x(frame.relative_pc) + '(' + frame.func_with_offset + ')') 463 | item.setText(3, x(frame.absolute_pc)) 464 | 465 | def OnCreate(self, form): 466 | """Called when the plugin form is created""" 467 | 468 | self.parent = self.FormToPyQtWidget(form) 469 | 470 | self.tree = QtWidgets.QTreeWidget() 471 | self.tree.setColumnCount(len(self.cols)) 472 | self.tree.setHeaderLabels(self.cols) 473 | self.tree.itemDoubleClicked.connect(self.dblclick) 474 | 475 | layout = QtWidgets.QVBoxLayout() 476 | layout.addWidget(self.tree) 477 | layout.setSpacing(0) 478 | layout.setContentsMargins(0, 0, 0, 0) 479 | 480 | self.load_data() 481 | 482 | self.tree.setColumnWidth(0, 256) 483 | self.tree.setColumnWidth(1, 256) 484 | self.tree.setColumnWidth(2, 256) 485 | self.tree.setColumnWidth(3, 256) 486 | self.parent.setLayout(layout) 487 | 488 | def OnClose(self, form): 489 | """Called when the plugin form is closed.""" 490 | del self 491 | 492 | def Show(self): 493 | """Creates the form is not created or focuses it if it was.""" 494 | return PluginForm.Show(self, 'ArmUnwind 0x%0.8x' % GetRegValue('PC')) 495 | 496 | 497 | g_arm_unwinder = ArmUnwinder() 498 | 499 | 500 | def get_unwinder(): 501 | """exported api""" 502 | return g_arm_unwinder 503 | 504 | 505 | def unwind_now(show_gui): 506 | """exported api""" 507 | if not is_debugger_on(): 508 | warning("Please run script after attach target process.") 509 | return None 510 | if get_inf_structure().procName != "ARM": 511 | warning("This plugin only supports ARM32 CPU.") 512 | return None 513 | _pcs = get_unwinder().unwind_all() 514 | _frames = ArmUnwinder.pclist2framelist(_pcs) 515 | if show_gui: 516 | _view = ArmUnwindView(_frames) 517 | _view.Show() 518 | return _frames 519 | 520 | 521 | def unwind_with_gui(): 522 | """exported api""" 523 | return unwind_now(True) 524 | 525 | 526 | def unwind_without_gui(): 527 | """exported api""" 528 | return unwind_now(False) 529 | 530 | 531 | ARM_UNWIND_HOTKEY = "Ctrl-Shift-U" 532 | 533 | 534 | def unwind_add_hotkey(hotkey=ARM_UNWIND_HOTKEY): 535 | """exported api""" 536 | add_hotkey(hotkey, unwind_with_gui) 537 | 538 | 539 | class ArmUnwindPlugin(plugin_t): 540 | """Class that is required for the code to be recognized as 541 | a plugin by IDA.""" 542 | flags = 0 543 | comment = "arm unwind plugin" 544 | help = comment 545 | wanted_name = "arm unwind plugin" 546 | wanted_hotkey = ARM_UNWIND_HOTKEY 547 | 548 | def init(self): 549 | print "ArmUnwindPlugin init success." 550 | return PLUGIN_OK 551 | 552 | def run(self, arg): 553 | unwind_with_gui() 554 | 555 | def term(self): 556 | pass 557 | 558 | 559 | def PLUGIN_ENTRY(): 560 | return ArmUnwindPlugin() 561 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # IDA ARM Unwind Plugin 2 | 3 | [English Version](readme_en.md) 4 | 5 | # 功能 6 | 7 | 使用 IDA 调试 arm 调试时,打印实时的栈回溯。 8 | 9 | IDA 的菜单栏有一处功能:`Debugger` -> `Debugger windows` -> `Stack trace (Ctrl + Alt + S)`,在调试 x86、x64、arm64 的程序时它的功能是正常的,在调试 arm 程序时它的功能是不正常的,表现出来栈回溯缺失和栈回溯错误。本质上因为 arm 使用的是自己搞的一套 `arm exception handler abi`,由 `.arm.exidx` 和 `.arm.extab` 描述;而其他 cpu 使用的是 `dwarf`,由 `.eh_frame` 描述,IDA 没有针对它做处理。本文使用 idapython 实现这个缺失的功能。 10 | 11 | # 用法 12 | 13 | ### 1. 安装 pyelftools >= 0.27 14 | 15 | 当前最新版是0.26,该版本暂不提供 arm ehabi 的解析功能,我完善该功能后已提交 [pull request](https://github.com/eliben/pyelftools/pull/328) ,需要官方repo自行安装 [https://github.com/eliben/pyelftools](https://github.com/eliben/pyelftools) 。 16 | 17 | ``` 18 | git clone https://github.com/eliben/pyelftools.git --depth=1 19 | cd pyelftools 20 | python setup.py install --old-and-unmanageable 21 | ``` 22 | 23 | ### 2. 加载 arm unwind plugin 24 | 25 | ### 2.1. ida script 26 | 27 | IDA 菜单栏:`File` -> `Script File (Alt + F7)`。 28 | 29 | IDA 菜单栏:`File` -> `Script Command (Shift + F12)` -> `python` -> `run`。 30 | 31 | ### 2.2 ida plugin 32 | 33 | Windows: `C:\Program Files\IDA 7.x\plugins` 34 | 35 | Mac: `/Applications/IDA Pro 7.0/idabin/plugins` 36 | 37 | Linux: `~/ida-7.x/plugins/` 38 | 39 | 打开 ELF 文件后会在 console 里输出 `ArmUnwindPlugin init success.` 40 | 41 | ### script & plugin 区别 42 | 43 | plugin 在 IDA 打开时就会被加载,自动绑定了快捷键 `Ctrl + Shift + U`,稍微方便一点。 44 | 45 | script 需要手动加载,之后调用 `unwind_add_hotkey(hotkey=HOTKEY)` 来绑定快捷键。 46 | 47 | ### 3. 进行栈回溯 48 | 49 | 调试 arm32 程序,下断点,触发断点。 **【请不要下断点在函数入口】** 50 | 51 | 使用快捷键 `Ctrl + Shift + U` 或者调用 python 函数 `unwind_with_gui()`、`unwind_without_gui()`、`unwind_now(True/False)`。 52 | 53 | 输出结果在 GUI 里或者 Console 里有显示,并且函数会返回 `List[Frame]` 结构体。 54 | 55 | 效果图:GUI 56 | 57 | ![](WechatIMG18960.png) 58 | 59 | 60 | 效果图:console 61 | 62 | ![](WechatIMG209.png) 63 | 64 | # 提供的 API 列表 65 | 66 | ### `get_unwinder()` 67 | 68 | return `g_arm_unwinder` 69 | 70 | ### `unwind_now(show_gui)` 71 | 72 | 根据当前状态进行 unwind。 73 | 74 | 参数:show_gui。类型:bool。意义:是否绘制 gui。 75 | 76 | 返回值:`List[Frame]`。 77 | 78 | ### `unwind_with_gui()` 79 | 80 | 根据当前状态进行 unwind,并且绘制 gui。 81 | 82 | 返回值:`List[Frame]`。 83 | 84 | ### `unwind_without_gui()` 85 | 86 | 根据当前状态进行 unwind,但不绘制 gui。 87 | 88 | 返回值:`List[Frame]`。 89 | 90 | ### `unwind_add_hotkey(hotkey=ARM_UNWIND_HOTKEY)` 91 | 92 | 为 `unwind_with_gui` 绑定快捷键。 93 | 94 | 参数:hotkey。类型:str。ida 约定的快捷键字符串,例如 `Ctrl-Shift-U`。 95 | --------------------------------------------------------------------------------