From 8936832c280a403d4d93f835ab71fd6eb0dcb599 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 06:07:08 +0300 Subject: [PATCH 01/12] Fix _Reg in emulator: op_t.reg can be long --- ida_kernelcache/collect_classes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ida_kernelcache/collect_classes.py b/ida_kernelcache/collect_classes.py index c486bf9..a76a1e6 100644 --- a/ida_kernelcache/collect_classes.py +++ b/ida_kernelcache/collect_classes.py @@ -52,7 +52,7 @@ def clear(self, reg): pass def _reg(self, reg): - if type(reg) is int: + if isinstance(reg, (int, long)): reg = _Regs._reg_names[reg] return reg @@ -64,7 +64,7 @@ def __getitem__(self, reg): def __setitem__(self, reg, value): if value is None or value is _Regs.Unknown: - self.clear(reg) + self.clear(self._reg(reg)) else: self._regs[self._reg(reg)] = value & 0xffffffffffffffff From 5d567ab217640cdce5708e5b01cf93193dd9ee34 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 06:13:36 +0300 Subject: [PATCH 02/12] idaapi.is_ident_char expects wchar32_t and not str --- ida_kernelcache/symbol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ida_kernelcache/symbol.py b/ida_kernelcache/symbol.py index a05332b..fba3773 100644 --- a/ida_kernelcache/symbol.py +++ b/ida_kernelcache/symbol.py @@ -91,7 +91,7 @@ def make_ident(name): """Convert a name into a valid identifier, substituting any invalid characters.""" ident = '' for c in name: - if idaapi.is_ident_char(c): + if idaapi.is_ident_char(ord(c)): ident += c else: ident += '_' From 1585e894f5a23bd8fb015030d7f5539d93438ae0 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 06:33:35 +0300 Subject: [PATCH 03/12] OpStroffEx expects first arg to be insn and not ea --- ida_kernelcache/class_struct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ida_kernelcache/class_struct.py b/ida_kernelcache/class_struct.py index 6d1caff..730a0a8 100644 --- a/ida_kernelcache/class_struct.py +++ b/ida_kernelcache/class_struct.py @@ -446,7 +446,7 @@ def _convert_operands_to_struct_offsets(access_addresses): if insn: for op in insn.Operands: if op.type == idaapi.o_displ: - if not idc.OpStroffEx(ea, op.n, sid, delta): + if not idc.OpStroffEx(insn, op.n, sid, delta): _log(1, 'Could not convert {:#x} to struct offset for class {} ' 'delta {}', ea, classname, delta) From a51e8c44e11fc62dfea1b8645b11bc1b6c59863e Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 06:34:36 +0300 Subject: [PATCH 04/12] force_function: RemoveFchunk in cycle --- ida_kernelcache/ida_utilities.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ida_kernelcache/ida_utilities.py b/ida_kernelcache/ida_utilities.py index d572723..1ca9f1b 100644 --- a/ida_kernelcache/ida_utilities.py +++ b/ida_kernelcache/ida_utilities.py @@ -431,7 +431,11 @@ def _convert_address_to_function(func): idc.AnalyseArea(item, itemend) else: # Just try removing the chunk from its current function. - idc.RemoveFchunk(func, func) + # IDA can add it to another function automatically, so make sure + # it's removed from all functions by doing it in loop until it + # fails + while idc.RemoveFchunk(func, func): + pass # Now try making a function. if idc.MakeFunction(func) != 0: return True From c733f252b1f2da676ef4f35bde483d7457184b98 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 16:15:37 +0300 Subject: [PATCH 05/12] stubs.py: 32bit --- ida_kernelcache/stub.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/ida_kernelcache/stub.py b/ida_kernelcache/stub.py index c793dd5..3bd2378 100644 --- a/ida_kernelcache/stub.py +++ b/ida_kernelcache/stub.py @@ -43,6 +43,9 @@ def _process_stub_template_1(stub): LDR X, [X, #@PAGEOFF] BR X """ + if idau.WORD_SIZE != 8: + return None + adrp, ldr, br = idau.Instructions(stub, count=3) if (adrp.itype == idaapi.ARM_adrp and adrp.Op1.type == idaapi.o_reg and adrp.Op2.type == idaapi.o_imm @@ -55,8 +58,35 @@ def _process_stub_template_1(stub): if target and idau.is_mapped(target): return target +def _process_stub_template_2(stub): + """A template to match the following stub pattern: + + MOV R, #( - PC + 2) + ADD R, PC + LDR.W R, [] + BX R + """ + if idau.WORD_SIZE != 4: + return None + + movl, add, ldr, bx = idau.Instructions(stub, count=4) + if (movl.itype == idaapi.ARM_movl and movl.Op1.type == idaapi.o_reg + and movl.Op2.type == idaapi.o_imm + and add.itype == idaapi.ARM_add and add.Op1.type == idaapi.o_reg + and add.Op2.type == idaapi.o_reg and add.Op2.reg == 15 # PC + and ldr.itype == idaapi.ARM_ldr and ldr.Op2.type == idaapi.o_displ + and bx.itype == idaapi.ARM_bx and bx.Op1.type == idaapi.o_reg + and movl.Op1.reg == add.Op1.reg == ldr.Op2.reg + and ldr.Op1.reg == bx.Op1.reg): + offset = movl.Op2.value + add.ea + 4 # +4 because fuck arm 32 + target = idau.read_word(offset) + if target and idau.is_mapped(target): + return target & ~1 + + _stub_processors = ( _process_stub_template_1, + _process_stub_template_2, ) def stub_target(stub_func): @@ -142,6 +172,8 @@ def _process_stubs_section(segstart, make_thunk, next_stub): if idc.isRef(idc.GetFlags(ea)) and not stub_name_target(idau.get_ea_name(ea)): _process_possible_stub(ea, make_thunk, next_stub) +STUB_SECT_POSTFIX = '__stubs' if idau.WORD_SIZE == 8 else '__stub' + def initialize_stub_symbols(make_thunk=True): """Populate IDA with information about the stubs in an iOS kernelcache. @@ -156,7 +188,7 @@ def initialize_stub_symbols(make_thunk=True): next_stub = internal.make_name_generator(kernelcache_stub_suffix) for ea in idautils.Segments(): segname = idc.SegName(ea) - if not segname.endswith('__stubs'): + if not segname.endswith(STUB_SECT_POSTFIX): continue _log(3, 'Processing segment {}', segname) _process_stubs_section(ea, make_thunk, next_stub) From e092e27c76f537d6957dcef0331c36ead9d64517 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 16:32:56 +0300 Subject: [PATCH 06/12] Handle "pseudo FAT" kernelcaches --- ida_kernelcache/kernel.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ida_kernelcache/kernel.py b/ida_kernelcache/kernel.py index 9102a70..b2e3f22 100644 --- a/ida_kernelcache/kernel.py +++ b/ida_kernelcache/kernel.py @@ -17,7 +17,14 @@ def find_kernel_base(): """Find the kernel base.""" - return idaapi.get_fileregion_ea(0) + kbase = idaapi.get_fileregion_ea(0) + + if kbase == idc.BADADDR: + # sometimes kernelcache is a FAT Mach-O with one arch + # sizeof(fat_header) + 1 * sizeof(fat_arch) = 28 + kbase = idaapi.get_fileregion_ea(28) + + return kbase base = find_kernel_base() """The kernel base address (the address of the main kernel Mach-O header).""" From 547e63c9a6477cbf2d205aa4e7c0f0584afc0bc4 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 16:35:24 +0300 Subject: [PATCH 07/12] Segment: 32bit Mach-O handling --- ida_kernelcache/segment.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/ida_kernelcache/segment.py b/ida_kernelcache/segment.py index 74550cc..4b712b5 100644 --- a/ida_kernelcache/segment.py +++ b/ida_kernelcache/segment.py @@ -13,12 +13,24 @@ _log = idau.make_log(0, __name__) -idc.Til2Idb(-1, 'mach_header_64') -idc.Til2Idb(-1, 'load_command') -idc.Til2Idb(-1, 'segment_command_64') -idc.Til2Idb(-1, 'section_64') -_LC_SEGMENT_64 = 0x19 +_LOAD_COMMAND = 'load_command' + +if idau.WORD_SIZE == 4: + _MACH_HEADER = 'mach_header' + _SEGMENT_COMMAND = 'segment_command' + _SECTION = 'section' + _LC_SEGMENT = 0x1 +else: # idau.WORD_SIZE == 8 + _MACH_HEADER = 'mach_header_64' + _SEGMENT_COMMAND = 'segment_command_64' + _SECTION = 'section_64' + _LC_SEGMENT = 0x19 + +idc.Til2Idb(-1, _MACH_HEADER) +idc.Til2Idb(-1, _LOAD_COMMAND) +idc.Til2Idb(-1, _SEGMENT_COMMAND) +idc.Til2Idb(-1, _SECTION) def _macho_segments_and_sections(ea): """A generator to iterate through a Mach-O file's segments and sections. @@ -26,21 +38,21 @@ def _macho_segments_and_sections(ea): Each iteration yields a tuple: (segname, segstart, segend, [(sectname, sectstart, sectend), ...]) """ - hdr = idau.read_struct(ea, 'mach_header_64', asobject=True) + hdr = idau.read_struct(ea, _MACH_HEADER, asobject=True) nlc = hdr.ncmds lc = int(hdr) + len(hdr) lcend = lc + hdr.sizeofcmds while lc < lcend and nlc > 0: - loadcmd = idau.read_struct(lc, 'load_command', asobject=True) - if loadcmd.cmd == _LC_SEGMENT_64: - segcmd = idau.read_struct(lc, 'segment_command_64', asobject=True) + loadcmd = idau.read_struct(lc, _LOAD_COMMAND, asobject=True) + if loadcmd.cmd == _LC_SEGMENT: + segcmd = idau.read_struct(lc, _SEGMENT_COMMAND, asobject=True) segname = idau.null_terminated(segcmd.segname) segstart = segcmd.vmaddr segend = segstart + segcmd.vmsize sects = [] sc = int(segcmd) + len(segcmd) for i in range(segcmd.nsects): - sect = idau.read_struct(sc, 'section_64', asobject=True) + sect = idau.read_struct(sc, _SECTION, asobject=True) sectname = idau.null_terminated(sect.sectname) sectstart = sect.addr sectend = sectstart + sect.size From 4735a84a988a7474930d5e0a750d4d207513f823 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 16:41:12 +0300 Subject: [PATCH 08/12] ida_utilities: unset last bit in force_function's param --- ida_kernelcache/ida_utilities.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ida_kernelcache/ida_utilities.py b/ida_kernelcache/ida_utilities.py index 1ca9f1b..780adc6 100644 --- a/ida_kernelcache/ida_utilities.py +++ b/ida_kernelcache/ida_utilities.py @@ -467,6 +467,9 @@ def is_function_start(ea): def force_function(addr): """Ensure that the given address is a function type, converting it if necessary.""" + # Unset last bin -- so it works with THUMB functions too + # TODO: Consider setting THUMB/ARM mode too + addr &= ~1 if is_function_start(addr): return True return _convert_address_to_function(addr) From 6a47d48d0893af223d71067b2fdce45a4f271a52 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 16:49:12 +0300 Subject: [PATCH 09/12] Check that function addresses in vtable refer to code --- ida_kernelcache/vtable.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/ida_kernelcache/vtable.py b/ida_kernelcache/vtable.py index d454e54..93c7efb 100644 --- a/ida_kernelcache/vtable.py +++ b/ida_kernelcache/vtable.py @@ -93,13 +93,32 @@ def return_value(possible, length): return return_value(False, zeros) # We can skip all but the last VTABLE_OFFSET zeros. return return_value(False, zeros - VTABLE_OFFSET) - # TODO: We should verify that all vtable entries refer to code. # Now we know that we have at least one nonzero value, our job is easier. Get the full length # of the vtable, including the first VTABLE_OFFSET entries and the subsequent nonzero entries, # until either we find a zero word (not included) or run out of words in the stream. - length = VTABLE_OFFSET + 1 + idau.iterlen(takewhile(lambda word: word != 0, words)) - # Now it's simple: We are valid if the length is long enough, invalid if it's too short. - return return_value(length >= MIN_VTABLE_LENGTH, length) + funcs = list(takewhile(lambda word: word != 0, words)) + length = VTABLE_OFFSET + 1 + len(funcs) + + # There's no need to check if found funcs refer to code if they're + # too short + + if length < MIN_VTABLE_LENGTH: + return return_value(False, length) + + # We need to fill funcs with nonzero values, and only then check all + # values and fail if they don't refer to code -- so we can skip + # sequences like "code data zero", which are obviously invalid + for f in [first] + funcs: + # For some reason checking for perms on segment didn't work + # properly for me (it always returned 0), so I decieded to check + # segment names instead and see if they contain "text" or "stub" + s = idc.SegName(f).lower() + if 'text' not in s and 'stub' not in s: + _log(5, "element in vtable at {:#x} isn't from __text or __stub but rather from {}", ea, s) + return return_value(False, length) + + # If we're still there then vtable is valid + return return_value(True, length) def convert_vtable_to_offsets(vtable, length=None): """Convert a vtable into a sequence of offsets. From 06b1ff81558d3d1f874eb226fba02ebf9231869d Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 17:35:08 +0300 Subject: [PATCH 10/12] collect_classes: arm32 --- ida_kernelcache/collect_classes.py | 195 +++++++++++++++++++++++++++-- 1 file changed, 184 insertions(+), 11 deletions(-) diff --git a/ida_kernelcache/collect_classes.py b/ida_kernelcache/collect_classes.py index a76a1e6..fbb5eb9 100644 --- a/ida_kernelcache/collect_classes.py +++ b/ida_kernelcache/collect_classes.py @@ -24,8 +24,15 @@ _MEMOP_WBINDEX = _MEMOP_PREINDEX | _MEMOP_POSTINDEX +# on 64bit devices __DATA_CONST segment is used for constant data +# instead of __DATA (eg __DATA_CONST.__const instead of __DATA.__const) +if idau.WORD_SIZE == 4: + _CONST_SEGNAME = '__DATA' +else: + _CONST_SEGNAME = '__DATA_CONST' + class _Regs(object): - """A set of registers for _emulate_arm64.""" + """A set of registers for _emulate_arm64/32.""" class _Unknown: """A wrapper class indicating that the value is unknown.""" @@ -54,6 +61,11 @@ def clear(self, reg): def _reg(self, reg): if isinstance(reg, (int, long)): reg = _Regs._reg_names[reg] + + # Automatically map Rn to Xn + if reg[0] == 'R' and reg[1:].isdigit(): + reg = 'X' + reg[1:] + return reg def __getitem__(self, reg): @@ -68,7 +80,7 @@ def __setitem__(self, reg, value): else: self._regs[self._reg(reg)] = value & 0xffffffffffffffff -def _emulate_arm64(start, end, on_BL=None, on_RET=None): +def _emulate_arm64(start, end=None, count=None, on_BL=None, on_RET=None): """A very basic partial Arm64 emulator that does just enough to find OSMetaClass information.""" # Super basic emulation. @@ -86,8 +98,7 @@ def load(addr, dtyp): def cleartemps(): for t in ['X{}'.format(i) for i in range(0, 19)]: reg.clear(t) - for insn in idau.Instructions(start, end): - _log(11, 'Processing instruction {:#x}', insn.ea) + for insn in idau.Instructions(start, end=end, count=count): mnem = insn.get_canon_mnem() if mnem == 'ADRP' or mnem == 'ADR': reg[insn.Op1.reg] = insn.Op2.value @@ -123,6 +134,167 @@ def cleartemps(): _log(10, 'Unrecognized instruction at address {:#x}', insn.ea) reg.clearall() +def _emulate_arm32(start, end=None, count=None, on_BL=None, on_RET=None): + """A very basic partial Arm32 emulator that does just enough to find OSMetaClass + information.""" + # Super basic emulation. + reg = _Regs() + def load(addr, dtyp): + if not addr: + return None + if dtyp == idaapi.dt_dword: + size = 4 + else: + return None + return idau.read_word(addr, size) + def cleartemps(): + for t in ['R{}'.format(i) for i in range(0, 12)]: + reg.clear(t) + + # Handle thumb stuff + start = start & ~1 + if end is not None: + end = (end + 1) & ~1 + + # if bl is found, lr is replaced, and marked dirty + # if pop {... lr ...} is found, lr is assumed to be restored to + # original, "clean" state + lr_dirty = False + + # Special registers have special handling + _SP_REG = 13 + _LR_REG = 14 + _PC_REG = 15 + + for insn in idau.Instructions(start, end=end, count=count): + mnem = insn.get_canon_mnem() + _log(12, 'Regs: {}', reg._regs) + _log(11, 'Processing instruction {} at {:#x}', mnem, insn.ea) + if mnem == 'ADR': + reg[insn.Op1.reg] = insn.Op2.value + elif ((mnem == 'ADD' or mnem == 'SUB') + and insn.Op1.type == insn.Op2.type == idc.o_reg + and insn.Op1.reg == insn.Op2.reg == _SP_REG): + # ignore add/sub on on SP + pass + elif mnem in ('ADD', 'ORR', 'SUB') and insn.Op2.type == idc.o_reg and insn.Op3.type == idc.o_imm: + # There might be more operations, but in practice + # add/sub/orr are enough + + # Don't bother checking if src register is unknown and + # just mark dst register as unknown too + if isinstance(reg[insn.Op2.reg], _Regs._Unknown): + reg.clear(insn.Op1.reg) + else: + tmp = reg[insn.Op2.reg] + if mnem == 'ADD': + tmp += insn.Op3.value + elif mnem == 'SUB': + tmp -= insn.Op3.value + elif mnem == 'ORR': + tmp |= insn.Op3.value + else: + pass + reg[insn.Op1.reg] = tmp + elif mnem == 'ADD' and insn.Op3.type == idaapi.o_void: + # Don't bother checking if it's unknown + if not isinstance(reg[insn.Op1.reg], _Regs._Unknown): + if insn.Op2.type == idc.o_imm: + # ADD Rx, + reg[insn.Op1.reg] = reg[insn.Op1.reg] + insn.Op2.value + elif insn.Op2.type == idc.o_reg and insn.Op2.reg == _PC_REG: + # ADD Rx, PC -- special handling + # On ARM PC is "address of current instruction + 4" + # for historical reasons + reg[insn.Op1.reg] = reg[insn.Op1.reg] + insn.ea + 4 + elif mnem == 'NOP': + pass + elif mnem == 'MOV' and insn.Op2.type == idc.o_imm: + reg[insn.Op1.reg] = insn.Op2.value + elif mnem == 'MOV' and insn.Op2.type == idc.o_reg: + reg[insn.Op1.reg] = reg[insn.Op2.reg] + elif mnem == 'BX' and insn.Op1.type == idc.o_reg and insn.Op1.reg == _LR_REG: + # bx lr is often used for ret + if on_RET: + on_RET(reg) + break + elif mnem == 'POP' and insn.Op1.type in (idc.o_idpspec1, idc.o_reg): + poped = [] + + # Either it's one register pop'ped + if insn.Op1.type == idc.o_reg: + poped.append(insn.Op1.reg) + + # Or whole set of them, identified by specval bits + if insn.Op1.type == idc.o_idpspec1: + for i in range(0, 16): + if insn.Op1.specval & (1< Date: Fri, 16 Mar 2018 18:05:52 +0300 Subject: [PATCH 11/12] offset.py: arm32 --- ida_kernelcache/ida_utilities.py | 5 +++++ ida_kernelcache/offset.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ida_kernelcache/ida_utilities.py b/ida_kernelcache/ida_utilities.py index 780adc6..345156b 100644 --- a/ida_kernelcache/ida_utilities.py +++ b/ida_kernelcache/ida_utilities.py @@ -125,6 +125,11 @@ def get_ea_name(ea, fromaddr=idc.BADADDR, true=False, user=False): Returns: The name of the address or "". """ + if WORD_SIZE == 4: + s = idc.SegName(ea).lower() + if 'text' in s or 'stub' in s: + ea &= ~1 + if user and not idc.hasUserName(idc.GetFlags(ea)): return "" if true: diff --git a/ida_kernelcache/offset.py b/ida_kernelcache/offset.py index de40170..13a42d7 100644 --- a/ida_kernelcache/offset.py +++ b/ida_kernelcache/offset.py @@ -28,7 +28,7 @@ def initialize_data_offsets(): for seg in idautils.Segments(): name = idc.SegName(seg) if not (name.endswith('__DATA_CONST.__const') or name.endswith('__got') - or name.endswith('__DATA.__data')): + or name.endswith('__DATA.__data') or name.endswith('__nl_symbol_ptr')): continue for word, ea in idau.ReadWords(seg, idc.SegEnd(seg), addresses=True): if idau.is_mapped(word, value=False): @@ -95,7 +95,7 @@ def initialize_offset_symbols(): next_offset = internal.make_name_generator(kernelcache_offset_suffix) for ea in idautils.Segments(): segname = idc.SegName(ea) - if not segname.endswith('__got'): + if not segname.endswith('__got') and not segname.endswith('__nl_symbol_ptr'): continue _log(2, 'Processing segment {}', segname) _process_offsets_section(ea, next_offset) From d88dec0ee9e39bdbf4c57d146f908996f909da72 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Fri, 16 Mar 2018 18:15:56 +0300 Subject: [PATCH 12/12] Typo fixes --- ida_kernelcache/collect_classes.py | 2 +- ida_kernelcache/ida_utilities.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ida_kernelcache/collect_classes.py b/ida_kernelcache/collect_classes.py index fbb5eb9..c65c542 100644 --- a/ida_kernelcache/collect_classes.py +++ b/ida_kernelcache/collect_classes.py @@ -364,7 +364,7 @@ def found_metaclass(metaclass, classname, class_size, meta_superclass): metaclass_to_meta_superclass[metaclass] = meta_superclass for ea in idautils.Segments(): segname = idc.SegName(ea) - if not segname.endswith(_CONST_SEGNAME + '__mod_init_func'): + if not segname.endswith(_CONST_SEGNAME + '.__mod_init_func'): continue _log(2, 'Processing segment {}', segname) _process_mod_init_func_section_for_metaclasses(ea, found_metaclass) diff --git a/ida_kernelcache/ida_utilities.py b/ida_kernelcache/ida_utilities.py index 345156b..eb94b44 100644 --- a/ida_kernelcache/ida_utilities.py +++ b/ida_kernelcache/ida_utilities.py @@ -153,6 +153,8 @@ def set_ea_name(ea, name, rename=False, auto=False): Returns: True if the address was successfully named (or renamed). """ + if WORD_SIZE == 4: + ea &= ~1 if not rename and idc.hasUserName(idc.GetFlags(ea)): return get_ea_name(ea) == name flags = idc.SN_CHECK