首页
社区
课程
招聘
[原创]ida 笔记
发表于: 2025-6-14 09:43 1657

[原创]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_void 0 无操作数(如 retnop) retn
    o_reg 1 寄存器操作数(直接使用寄存器值) mov eax, ebxeax
    o_mem 2 直接内存引用(绝对地址指向数据段) cmp dword_406C80, 0dword_406C80
    o_phrase 3 基址+索引寄存器寻址(无位移) mov [eax+ebx], ecx[eax+ebx]
    o_displ 4 基址寄存器+位移偏移(或基址+索引+位移) mov eax, [ebp-0x10][ebp-0x10]
    o_imm 5 立即数(指令中直接嵌入的常量值) add esp, 0Ch0Ch
    o_far 6 远地址(段选择子+偏移, 用于跨段跳转) jmp 0x1000:0x2000
    o_near 7 近地址(仅偏移, 用于同段内跳转) call sub_401000sub_401000
    o_idpspec0-5 8-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 表示数据引用

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编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回