-
-
[原创]ida 笔记
-
发表于: 2025-6-14 09:43 1657
-
前言
记录了我遇到的/遇到过的一些操作和指令, 难免有不全或者错的地方, 我保证会积极改正(狗头保命)
快捷键
| 功能 | 按键 |
|---|---|
| (伪代码窗口)重命名 | n |
| (伪代码窗口)改变类型 | y |
| (伪代码窗口)交叉引用 | x |
| (伪代码窗口)移除返回值 | v |
| (反汇编窗口)地址跳转 | g |
| (反汇编窗口)按数据识别、调整大小 | d |
| (反汇编窗口)将地址标记为代码并反汇编 | c |
| (反汇编窗口)识别成字符串 | a |
| (反汇编窗口)取消 ida 对当前地址的解释 | u |
| (反汇编窗口)创建函数 | p |
| (反汇编窗口)注释 | ; |
| 反汇编 | f5/tab |
| 图形/汇编 | space |
| 十进制与十六进制互相转换 | h |
常见内置类型
_BYTE、_WORD(16位)、_DWORD(32位)和 _QWORD(64位)
常见函数
- __stack_chk_fail: GCC 栈溢出保护机制(Stack Smashing Protector, SSP)的关键函数, 在检测到栈溢出时触发程序终止。其核心原理是通过 Canary(栈金丝雀) 值验证栈的完整性
技巧(只进行了罗列, 如何操作需自行查询)
- 自定义结构体
- 动态调试
- 重新分析文件: Options > General > Analysis → 点击 “Reanalyze program”
idapython
常用 api
用于静态脚本
idaapi
- func = idaapi.get_func(addr): 查询地址 addr 是否位于某个已识别函数的边界内, 并返回该函数的信息
- func.start_ea: 函数起始地址(包含)
- func.end_ea: 函数结束地址(函数最后一条指令的下一条地址)
- blocks = idaapi.FlowChart(func): 生成函数的控制流图, 将函数分解为多个基本块, 并建立块间的跳转关系, 参数 func 是 idaapi.func_t 对象, 通过 idaapi.get_func(ea) 获取, 返回值 blocks 是函数内所有基本块(Basic Block) 的集合, 每个基本块 block 具有一下属性:
- block.start_ea: 块起始地址(包含)
- block.end_ea: 块结束地址(块最后一条指令的下一条指令地址)
- block.preds(): 前驱块列表
- block.succs(): 后继块列表
- start_ea = idaapi.find_text(ustr=text, x=0, y=0, sflag=idaapi.SEARCH_DOWN, start_ea=start_ea): 此函数在指定地址范围内搜索文本字符串 text, 返回匹配项的起始地址(若未找到则返回 idc.BADADDR)各参数作用如下:
- ustr: 待搜索的文本字符串, 搜索区分大小写, 不支持通配符或正则表达式。
- x 和 y(通常设为 0): y 是行号偏移(0 表示从当前行开始), x 是行内字符偏移(0 表示从行首开始)
- sflag: 搜索方向控制
- start_ea: 指定搜索的起始地址, 函数执行后会被更新为下一个待搜索地址(便于循环迭代)。若搜索失败则更新为 idc.BADADDR。
- idaapi.SEARCH_DOWN: 从当前地址向高地址方向(正向)搜索, 通常与搜索函数(如 idaapi.find_binary()、idaapi.find_text())结合使用。其值为 0x01
- idaapi.SEARCH_UP: 向低地址搜索(逆向)。其值为 0x00
- idaapi.SEARCH_NEXT: 从上一次匹配位置后继续搜索, 用于遍历所有结果。其值为 0x02
- idaapi.SEARCH_NOSHOW: 禁止显示搜索进度(提升脚本性能)。其值为 0x20
idc
- idc.BADADDR: 表示无效的内存地址, 通常作为错误返回值(例如搜索失败、符号不存在)。其值为无符号整数形式的 -1(即 0xFFFFFFFFFFFFFFFF)。
- idc.prev_head(ea: int) -> int: 返回 ea 地址之前的最近一条指令的起始地址, 若 ea 是函数的第一条指令, 则返回 idaapi.BADADDR(通常为 0xFFFFFFFF)
- idc.next_head(ea: int) -> int: 返回 ea 之后下一条有效指令的起始地址, 若 ea 后无有效指令(如函数末尾), 返回 idaapi.BADADDR
- idc.GetDisasm(ea: int) -> str: 用于获取指定地址(ea)的反汇编指令文本
- idc.print_insn_mnem(ea: int) -> str: 获取指定地址处指令助记符(例如 "B", "MOV", "RET" 等)
- idc.get_operand_value(ea: int, index: int) -> int: 用于获取指令操作数值, ea 为指令起始地址, index 是操作数索引
- idc.print_operand(ea: int, index: int) -> str: 用于提取指令操作数字符串, ea 为指令起始地址, index 是操作数索引
- op_type = idc.get_operand_type(ea: int, index: int): 获取指令操作数的类型
类型常量 值 描述 示例指令 o_void0 无操作数(如 ret、nop)retno_reg1 寄存器操作数(直接使用寄存器值) mov eax, ebx→eaxo_mem2 直接内存引用(绝对地址指向数据段) cmp dword_406C80, 0→dword_406C80o_phrase3 基址+索引寄存器寻址(无位移) mov [eax+ebx], ecx→[eax+ebx]o_displ4 基址寄存器+位移偏移(或基址+索引+位移) mov eax, [ebp-0x10]→[ebp-0x10]o_imm5 立即数(指令中直接嵌入的常量值) add esp, 0Ch→0Cho_far6 远地址(段选择子+偏移, 用于跨段跳转) jmp 0x1000:0x2000o_near7 近地址(仅偏移, 用于同段内跳转) call sub_401000→sub_401000o_idpspec0-58-13 处理器特定类型(如 ARM 的移位操作、MIPS 的 HI/LO 寄存器) ARM: LDR R0, [R1, R2 LSL #2] - idc.patch_byte(addr: int, value: int): 将 addr 处的 1 个字节修改为 value, 并立即更新 IDA 数据库(但不修改原始二进制文件)
- idc.create_insn(addr: int) -> int: 尝试从 addr 地址开始解析二进制数据, 将其识别为有效的汇编指令, 成功时返回指令长度, 如 4 表示 4 字节指令, 失败时返回 0(常见于数据无法解析为合法指令)
- idc.del_items(ea: int, flags: int = 0, count: int = 1) -> bool: 取消 ida 对当前地址的解释, 成功返回 True, 失败返回 False, 参数说明:
- ea: 目标起始地址
- flags: 控制清除行为的标志位, 常用值:
- 0(默认): 仅清除 ea 处的单字节属性
- idc.DELIT_EXPAND: 清除从 ea 开始的连续 count 字节的属性(最常用)
- idc.DELIT_DELNAMES: 同时清除关联的符号名(如标签、变量名)
- count: 清除的字节数(需与 idc.DELIT_EXPAND 联用)
- idc.set_color(ea: int, what, color): 设置反汇编代码中不同元素的颜色, 参数:
- ea: 目标地址, 指定需设置颜色的代码位置
- what: 指定作用对象类型, 必须是以下常量之一
- idc.CIC_SEGM: 设置整个内存段的背景色(如代码段、数据段)
- idc.CIC_FUNC: 设置函数的背景色
- idc.CIC_ITEM: 设置单条指令或数据的背景色
- color: RGB 颜色编码, 格式为十六进制 0xBBGGRR
- idc.get_wide_byte(ea: int) -> int: 从指定的有效地址 ea 读取连续 1 字节数据, 按小端序(Little-Endian) 解析为 8 位无符号整数
- idc.get_wide_word(ea: int) -> int: 从指定的有效地址 ea 读取连续 2 字节数据, 按小端序(Little-Endian) 解析为 16 位无符号整数
- idc.get_wide_dword(ea: int) -> int: 从指定的有效地址 ea 读取连续 4 字节数据, 按小端序(Little-Endian) 解析为 32 位无符号整数
idautils
- idautils.Heads(start_ea: int = None, end_ea: int = None) -> generator: 用于高效遍历指令
- start_ea: 遍历起始地址(包含)。若为 None, 默认从程序最小地址(MinEA)开始
- end_ea: 遍历结束地址(不包含)。若为 None, 默认到程序最大地址(MaxEA)结束
- 返回值 generator: 逐个生成指令的起始地址, 例:
for ea in idautils.Heads(block_start_ea, block_end_ea):
- xrefs = idautils.XrefsTo(ea: int, flags=0): 用于获取指向特定地址的所有交叉引用, 枚举所有引用目标地址 ea 的位置。
- 参数 flags: 可以选择的值
- ida_xref.XREF_ALL(默认, 值为 0): 返回所有类型的引用
- ida_xref.XREF_DATA: 仅数据引用(如 LDR X0, [X1] 访问内存)
- ......
- 返回值 xrefs: xrefs 是一个可迭代对象, 每个 xref 对象包含以下关键字段:
属性 类型 说明 xref.frm 整数 引用来源地址(发起引用的指令地址) xref.to 整数 引用目标地址(即传入的 ea) xref.type 枚举值 引用类型 xref.iscode 布尔值 True 表示代码引用;False 表示数据引用
- 参数 flags: 可以选择的值
ida_hexrays
- cfunc = ida_hexrays.decompile(ea: int) -> Optional[ida_hexrays.cfuncptr_t]: 用于将汇编代码反编译为高级语言伪代码(通常是 C 语言风格), 并返回结构化数据供脚本分析, ea 指定待反编译函数的起始地址, 地址必须指向函数的入口点, 否则可能返回不完整结果。返回值 cfunc:
- body: cinsn_t 对象, 表示函数的根语句(如循环、条件分支)
- get_func(): 返回当前函数的 func_t 对象(包含地址范围、属性等)
- build_c_tree(): 构建 C 语法树(CTree)
- mba: mba_t 对象(Microcode Block Array), 存储微码中间表示
- print(): 生成格式化伪代码字符串, 标准输出
- str(cfunc): python 字符串, 完整伪代码字符串
ida_bytes
- ida_bytes.create_strlit(ea: int, length: int = 0, strtype: int = 0) -> bool: 用于将二进制数据标记为字符串, 成功返回 True, 失败返回 False, 参数说明:
- ea: 目标字符串的起始地址
- length: 字符串长度(以字节为单位)
- strtype: 字符串类型, 常用值
- ida_bytes.STRTYPE_C: C 风格 null 终止字符串(默认)
- ida_bytes.STRTYPE_LEN2/ida_bytes.STRTYPE_LEN4: 带 2/4 字节长度前缀的字符串
- ida_bytes.STRTYPE_PASCAL: Pascal 风格(长度前缀 + 字符数据)。
- flags = ida_bytes.get_full_flags(ea): 返回一个整数值(flags_t), 包含地址 ea 的所有类型标记信息。这些标记以位掩码(bitmask)形式存储, 需通过特定宏(如 is_code()、is_data())解析
- ida_bytes.is_code(flags) → 是否为指令
- ida_bytes.is_data(flags) → 是否为独立数据项
- ida_bytes.is_strlit(flags) → 是否为字符串
- ......
ida_funcs
- ida_funcs.add_func(ea: int, end_ea: int, flags=0) -> bool: 用于强制创建函数定义, 成功返回 True, 失败返回 False, 参数说明:
- ea: 函数起始地址
- end_ea: 函数结束地址(可选)。若为 BADADDR(默认值), IDA 自动分析函数边界
- flags: 控制函数创建行为的标志位(通常保留为 0)
用于动态调试
1 2 3 4 5 6 7 8 | # 读取寄存器的值idc.get_reg_value("rax")# 设置寄存器的值idaapi.set_reg_val("rax", 1234)# 读取内存idc.read_dbg_memory(addr, size)# 修改内存idc.patch_dbg_byte(addr, val) |
零散
1 2 | ## 获取光标所在地址here() |
还算通用的函数
文本/指令搜索
1 2 3 4 5 6 7 8 9 10 11 | def search_text(text): '''全局搜索文本字符串''' start_ea = 0 result = [] while True: start_ea = idaapi.find_text(ustr = text, x = 0, y = 0, sflag = idaapi.SEARCH_DOWN, start_ea = start_ea) if start_ea == idc.BADADDR: break result.append(start_ea) start_ea = idc.next_head(start_ea) return result |
控制流中可以用来识别分发器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def find_dispatchers_by_predecessor_count(addr: int, expect_min_count=10) -> List[idaapi.BasicBlock]: '''根据前驱数确定分发器''' # 获取函数对象 func = idaapi.get_func(addr) # 获取函数的流图对象, 用于分析函数的控制流 blocks = idaapi.FlowChart(func) # 用于存储识别出的分发器块 dispatchers = [] # 遍历函数的每个基本块 for block in blocks: # 获取当前基本块的所有前驱块 preds = block.preds() if len(list(preds)) > expect_min_count: dispatchers.append(block) |
根据地址获取基本块
1 2 3 4 5 6 7 8 9 10 11 12 13 | def get_block_by_address(addr): '''根据地址获取基本块''' func = idaapi.get_func(addr) if not func: print(f"地址 {hex(addr)} 不在任何函数中") return None blocks = idaapi.FlowChart(func) for block in blocks: if block.start_ea <= addr < block.end_ea: return block |
获取 ida 识别的类型
1 2 3 4 5 6 7 8 9 10 | def typeof(ea: int): '''code | data | other''' flags = ida_bytes.get_full_flags(ea) if ida_bytes.is_data(flags): return "data" elif ida_bytes.is_code(flags): return "code" else: return "other" |
解混淆时, 往往需要筛选出特殊情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def expect_mnem(insn_addr, expect_mnem: str | list[str]): ''' 判断输入地址是不是期待的助记符, 不是就报错 insn_addr: 指令地址 expect_mnem: 预期助记符 ''' insn_mnem = idc.print_insn_mnem(insn_addr) if isinstance(expect_mnem, str): expect_mnem = [expect_mnem] if not isinstance(expect_mnem, list): raise TypeError(f"参数 expect_mnem 传入类型错误") if insn_mnem not in expect_mnem: raise Exception(f"期待 {'|'.join(expect_mnem)}, 但在 {hex(insn_addr)} 获得的是 {insn_mnem}") |
强迫症必备, 判断 LDR 指令中的地址是否直接可算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | def is_relative_ldr(ea): '''判断 LDR 指令中的地址是否直接可算''' if idc.print_insn_mnem(ea) != "LDR": raise Exception(f"{hex(ea)} 并不是一条 LDR指令") op_type = idc.get_operand_type(ea, 1) op_str = idc.print_operand(ea, 1) # 情况1: 操作数含 PC 寄存器 if "PC" in op_str: return True # 情况2: 操作数为一个内存地址 if op_type == idc.o_mem: target_addr = idc.get_operand_value(ea, 1) current_pc = ea + 4 # ARM 流水线 PC 值 if 0 < abs(target_addr - current_pc) < 0x1000: # 排除 0 偏移且相对偏移不能超过 0x1000 return True return False |
最后于 2025-6-27 19:36
被nothing233编辑
,原因:
赞赏记录
参与人
雪币
留言
时间
道友请留步
感谢你的积极参与,期待更多精彩内容!
2025-6-23 12:05
马来
这个讨论对我很有帮助,谢谢!
2025-6-19 20:30
咖啡_741298
为你点赞!
2025-6-17 17:45
wusha
+1
为你点赞!
2025-6-16 19:06
孤独的街
为你点赞!
2025-6-14 12:28
赞赏
他的文章
- [原创]LLVM: ADCEPass 862
- [原创]llvm: InstSimplifyPass 728
- [原创]一个 ELF 文件的运行 28121
- [原创]capstone学习: 反汇编器如何实现的 11370
- [原创]chromium 的启动 2185
赞赏
雪币:
留言: