文章总结: Hex-Rays发布IDAPro9.3Beta1,集成Teams协作界面并移除HVUI;新增V850反编译器及微码操作,增强Obj-C与Golang类型支持;架构方面支持ARM64SVE/SME/MTE及Android14-17原生调试;优化了交叉引用视图与UI性能。文末附带Keygen脚本与Keypatch插件源码供技术测试。
综合评分: 91
文章分类: 逆向分析,安全工具,二进制安全
【逆向分析】Hex.Rays.IDA.Pro.v9.3.251224-HEXRAYS(IDA 9.3 Beta 1)
原创
利刃信安
利刃信安
2026年1月2日 12:49
北京
Hex.Rays.IDA.Pro.v9.3.251224-HEXRAYS(IDA 9.3 Beta 1)
时光荏苒,岁月如梭,白驹过隙之间2025年结束而2026年开启,值此辞旧纳新之际,Hex-Rays官方发布了全新版本,Hex.Rays.IDA.Pro.v9.3.251224-HEXRAYS(IDA 9.3 Beta 1)。
以下是此Beta版本引入的关键亮点与更新内容。
Beta 1 版本的新功能
版本亮点
反编译器
- 新增 V850 架构反编译器
- 支持微码操作
- 查看器功能增强及其他改进
类型系统
- Objective-C 解析器支持
- 更多 Golang 类型支持
团队协作插件
- 优化的 Teams 用户界面现已完全集成至 IDA,无需再使用 HVUI
架构支持
- Apple SVE、SME、MTE ARM64 扩展
- Andes Andestar V3 NDS32(Beta 1 暂未包含)
- Tricore、ARC、x86/x64、PowerPC、RISC-V、ARM64 架构改进
调试器
- 支持 Android 14–17 原生调试
- 堆栈视图支持解引用操作
用户界面
- 改进的交叉引用树与交叉引用图
- 其他性能优化
FLIRT 2.0
- 新版签名库暂未包含于 Beta 1,计划在后续 Beta 版本中提供
安装程序
- 新增 Linux ARM64 安装包
团队协作插件
IDA Teams 现已原生集成至标准版IDA:您无需启动任何外部应用程序,即可与团队协作进行逆向工程项目。
- 全新增的顶级“团队协作”菜单:所有已知的主要功能和小部件,现均可通过此菜单访问。
- 便捷的远程文件访问:可通过“快速启动”对话框直接打开 Hex-Rays Vault 服务器上的文件。
- 停用 HVUI 应用程序:独立的 HVUI 应用程序已不再包含。其核心功能现已完全集成至 IDA 主界面中。
集成式Teams功能快速入门
自 IDA 9.3 起,Teams 功能现已完全集成至 IDA 主程序中。这使得用户能够直接在 IDA 内开展逆向工程项目的协作,无需借助任何外部应用程序(例如 HVUI 图形界面)。无论是普通团队用户还是管理员,均可享受这一统一、集成的操作体验。如果您曾使用过 HVUI,会发现大多数小部件名称、命令及工作流程均保持熟悉,便于快速上手。
集成式 Teams 功能的主要变化
- 新增的 “Teams” 顶级菜单汇集了以往在 HVUI 中的所有主要功能与小部件。这些部件在 IDA 中现以典型的 IDA 视图形式运行,可停靠、分组或浮动。
- 您现可在 IDA 内部直接进行文件或数据库的差异对比,无需再切换到其他外部工具。
- 现在可以通过 “文件” → “快速启动” 对话框,直接访问并打开存储在 Hex-Rays Vault 服务器上的文件,简化了工作流程。
- 自 9.3 版本起,不再附带独立的 HVUI 应用程序,因其所有核心功能均已完全集成到 IDA 主程序中。
- Vault 服务器的连接状态现显示于 IDA 窗口的右下角,便于您实时查看网络连接情况。
使用集成式 Teams 功能的前提条件
- IDA Pro 9.3 及含 Teams 附加功能的有效 IDA 许可证
- 由管理员安装/部署的 Teams 服务器(也称为 “Hex-Rays Vault 服务器”)
管理员集成式Teams功能快速入门
- 启动 IDA 9.3,在“快速启动”对话框中点击 Go to work locally。
- 然后在主菜单导航至 Teams → Connect,输入您的 Hex-Rays Vault 服务器地址和管理员凭据。
- 请注意:管理员可无需有效许可证直接连接至 Hex-Rays Vault 服务器进行管理。
- 通过菜单 Teams → Connect → Users 打开用户视图。
- 在此视图中,您可以更新和管理允许访问该 Hex-Rays Vault 服务器的用户列表(如添加、移除用户或调整权限)
团队成员集成式Teams功能快速入门
首次使用集成式Teams功能且未关联任何项目仓库时,请按以下步骤操作:
- 启动 IDA 9.3。在出现的“快速启动”对话框中,点击 Go to work locally。
- 随后,在主菜单中导航至 Teams → Connect。
- 在“连接到服务器”对话框中,输入您的服务器地址和用户凭据。
- 首次成功连接服务器时,系统会提示您选择一个本地工作根目录,用于存放从服务器检出的仓库文件。请按需选择或创建目录。
- 连接成功后,Vault 文件视图 窗口会自动打开,其中会列出服务器上您有权限访问的所有文件。
- 在此窗口中双击任意文件,IDA 会将其下载到您刚设置的工作目录,并在一个新的 IDA 实例中打开它进行分析。
- 若希望将整个仓库的最新版本完整下载到本地工作空间,只需在 Vault 文件视图 窗口中右键点击,并选择“获取最新版本…” 即可。
提交更改
操作步骤
- 扫描并提交:在IDA的结构列表、函数窗口或反汇编视图等界面中右键点击,从上下文菜单中选择 Scan and Commit。
- 检查更改列表:系统将弹出对话框,自动扫描并列出所有检测到的更改。请仔细核对列表中的修改项。
- 输入提交信息:在 Commit message 文本框中,简要描述本次修改的内容和目的(如“修复函数X的调用逻辑”、“添加数据结构Y的定义”)。
- 选择提交方式:
- 点击 Commit:将更改立即推送至远程仓库,供团队其他成员同步。
- 点击 Save:将更改仅保存至本地工作清单(Worklist)中暂不推送,便于后续累积或分批提交。
重要说明
- 此右键菜单操作与通过 Teams → Commit 主菜单功能完全等效,可根据操作习惯选择。
- 使用 Save 功能可灵活暂存阶段性修改,待完成一个完整任务后再统一提交。
- 提交后,其他团队成员可通过 Teams → Update 获取您的更改。
类型系统
Objective-C 解析器
示例 – 输入 Objective-C 头文件:
@interface NSObject- (id)init;+ (id)alloc;@end
@protocol MyDelegate- (void)didFinish;@end
@interface MyClass : NSObject <MyDelegate>@property (nonatomic) int value;@property (nonatomic, getter=isEnabled) BOOL enabled;- (void)doAction:(int)action withObject:(id)obj;+ (instancetype)sharedInstance;@end
@interface MyClass (Extensions)- (void)extendedMethod;@end
生成的类型库输出示例:
// Typestypedef char BOOL;__objc_interface NSObject {};__objc_interface __cppobj MyClass : NSObject {int _value; BOOL _enabled;};
// NSObject methodsid __cdecl +[NSObject alloc](id, SEL);id __cdecl -[NSObject init](NSObject *self, SEL);
// MyClass methods - including auto-generated property accessorsMyClass *__cdecl +[MyClass sharedInstance](id, SEL);void __cdecl -[MyClass didFinish](MyClass *self, SEL); // from protocolvoid __cdecl -[MyClass doAction:withObject:](MyClass *self, SEL, int action, id obj);BOOL __cdecl -[MyClass isEnabled](MyClass *self, SEL); // custom gettervoid __cdecl -[MyClass setEnabled:](MyClass *self, SEL, BOOL enabled);void __cdecl -[MyClass setValue:](MyClass *self, SEL, int value);int __cdecl -[MyClass value](MyClass *self, SEL);void __cdecl -[MyClass extendedMethod](MyClass *self, SEL); // from category
生成 C 头文件:内联匿名类型
在将类型导出至 C 头文件时,IDA 会创建基于哈希命名的中间匿名类型。新的 “忽略 IDA 匿名类型” 选项可将这些类型直接内联到其父结构中,从而生成更清晰、更便于移植的输出。
之前(匿名类型被单独导出):
union test::$161DE73CEC70B46D2147F48043C58578{ a a; b b;};
struct test{ test::$161DE73CEC70B46D2147F48043C58578;};
struct with_inline_struct_t::$42129BFC740492F1999D6296462C8FF7{ int a;};
struct with_inline_struct_t{ with_inline_struct_t::$42129BFC740492F1999D6296462C8FF7 b;};
之后(匿名类型已内联):
struct test{ union { a a; b b; };};
struct with_inline_struct_t{ struct { int a; } b;};
该功能同样能正确处理嵌套的内联结构:
struct with_inline_struct_2_t{ struct { struct { int a; } b; } b; struct { int a; } c;};
固定结构体支持:完整的导入/导出循环
IDA 9.3 引入了 __fixed 和 __at 属性,用于支持具有显式字段偏移量的结构体,从而实现了类型导出与导入的完整闭环。以往,包含显式偏移(如函数栈帧)的类型虽然可导出至 C 头文件,但无法重新导入——偏移信息会丢失。现在,IDA 生成的头文件已完全支持重新解析。
新语法:
__fixed(size) – 声明一个具有显式总大小的结构体
__at(offset) – 为字段指定精确的字节偏移量
示例 – 函数栈帧:
struct __fixed(0x48) x64_prologue_frame{ __at(0x0000) void *return_addr; __at(0x0008) void *saved_rbp; __at(0x0010) void *saved_rbx; __at(0x0018) void *saved_r12; __at(0x0020) void *saved_r13; __at(0x0028) void *saved_r14; __at(0x0030) void *saved_r15; __at(0x0038) int local_var1; __at(0x003C) int local_var2; __at(0x0040) void *local_ptr;};
示例 – 压缩嵌入式结构(非对齐字段):
struct __fixed(0x20) packed_frame{ __at(0x0000) char header; __at(0x0001) __int16 flags; /* unaligned short */ __at(0x0003) int sequence; /* unaligned int */ __at(0x0007) char payload[16]; __at(0x0017) char checksum; __at(0x0018) void *next;};
更多 Golang 类型支持
类型名称包含完整包路径:类型现在包含完整的包路径,消除了不同包中间名类型的歧义问题:
分层式类型库组织 类型现按 Go 包路径对应的文件夹结构进行组织:
/golang/runtime/internal/atomic/runtime_internal_atomic_Uint32/golang/runtime/internal/atomic/_ptr_runtime_internal_atomic_Uint32/golang/syscall/syscall_Iovec/golang/internal/poll/internal_poll_FD/golang/fmt/fmt_Formatter/golang/reflect/reflect_Value
其他改进
- 更优的 FUNCINFO 及 Go 版本检测
- 将内部 BUILTIN_STRING 替换为标准 Go 字符串类型
- 现能正确识别许多标准 Go 类型,无需添加 struct 前缀
- 在解析 DWARF 信息时,改进了对匿名 Go 函数返回类型的处理
架构支持
Tricore
改进了寄存器查找器,实现更优的交叉引用检测,并增强了 switch 语句的识别能力。
ARC:压栈/弹栈惯用模式识别
现在,带自动递增的内存存储/加载操作可被识别为压栈和弹栈指令,这使得函数的序言和尾声更易于理解。
之前(9.2版):
.start:01C001A6 FC 1C 08 B4 st.a r16, [sp,0xC+var_10].start:01C001AA FC 1C 48 B4 st.a r17, [sp,0x10+var_14].start:01C001AE FC 1C 88 B4 st.a r18, [sp,0x14+var_18].start:01C001B2 FC 1C C8 B4 st.a r19, [sp,0x18+var_1C].start:01C001B6 FC 1C 08 B5 st.a r20, [sp,0x1C+var_20].start:01C001BA FC 1C 48 B5 st.a r21, [sp,0x20+var_24].start:01C001BE FC 1C C8 B6 st.a fp, [sp,0x24+var_28]
之后(9.3版):
.start:01C001A6 FC 1C 08 B4 push r16.start:01C001AA FC 1C 48 B4 push r17.start:01C001AE FC 1C 88 B4 push r18.start:01C001B2 FC 1C C8 B4 push r19.start:01C001B6 FC 1C 08 B5 push r20.start:01C001BA FC 1C 48 B5 push r21.start:01C001BE FC 1C C8 B6 push fp
x86/x64
- 支持跟踪在分析时目标已知的间接跳转
- 修复了 MPX 可提示 NOP 指令的处理问题
PowerPC
修正了 addi 指令的错误偏移量显示问题
RISC-V
修复了无限循环的自动分析问题
枚举类型成员现在能生成正确的交叉引用
ARM64
SVE/SME 指令支持(用于 iPhone 17 内核)
IDA 9.3 现已能够解码苹果 iPhone 17 内核中使用的可扩展向量扩展(SVE)和可扩展矩阵扩展(SME)指令:
__text:FFFFFE00082434E4 STR Z0, [X2]__text:FFFFFE00082434E8 STR Z1, [X2,#1,MUL VL]__text:FFFFFE00082434EC STR Z2, [X2,#2,MUL VL]__text:FFFFFE00082434F0 STR Z3, [X2,#3,MUL VL]...__text:FFFFFE00082AFF48 UZP1 V0.4S, V0.4S, V1.4S__text:FFFFFE0008240118 ZIP1 V2.4S, V2.4S, V2.4S
IDA 9.3 现已能够正确解码苹果 iPhone 17 内核中使用的可扩展向量扩展(SVE)和可扩展矩阵扩展(SME)指令。
此前,这些指令仅显示为原始字节或未定义的操作码。
内存标签扩展(MTE)内联函数
反编译器现在能够为 ARM 内存标签扩展指令生成清晰的内联函数调用,替代了以往的内联汇编代码块。
MTE 技术用于 iOS 18+ 和 Android 系统以提升内存安全性。
之前(9.2 版本) – 内联汇编:
__int64 __fastcall create_tag(__int64 _X0, unsigned int a2){ __int64 result; // x0 _X8 = a2; __asm { IRG X0, X0, X8 } return result;}
__int64 __fastcall increment_tag1(__int64 _X0){ __int64 result; // x0 __asm { ADDG X0, X0, #0, #7 } return result;}
__int64 __fastcall get_tag1(__int64 _X0){ __int64 result; // x0 __asm { LDG X0, [X0] } return result;}
__int64 __fastcall set_tag1(__int64 result){ __asm { STG X0, [X1] } return result;}
__int64 __fastcall subtract_pointers(__int64 _X0, __int64 _X1){ __int64 result; // x0 __asm { SUBP X0, X0, X1 } return result;}
之后(9.3 版本) – 清晰的内联函数调用:
void *__fastcall create_tag(void *a1, unsigned int a2){ return __arm_mte_create_random_tag(a1, a2);}
void *__fastcall increment_tag1(void *a1){ return __arm_mte_increment_tag(a1, 7u);}
void *__fastcall get_tag1(void *a1){ return __arm_mte_get_tag(a1);}
void *__fastcall set_tag1(void *result, void *a2){ __arm_mte_set_tag(a2, result); return result;}
unsigned __int64 __fastcall subtract_pointers(void *a1, void *a2){ return __arm_mte_ptrdiff(a1, a2);}
支持的 MTE 内联函数列表:
ARM64:CSSC 指令解码
现已支持解码 ARM64 CSSC 扩展中的比较及最值指令,包括 SMAX、SMIN、UMAX、UMIN、ABS、CNT、CTZ。
MOV/MOVK 地址构造优化
反汇编器与反编译器现可正确优化 MOV/MOVK/MOVW/MOVT 指令序列构成的 ARM64 地址构造模式,即使这些指令分散在代码中,也能生成准确的交叉引用。
之前(9.2 版本):
.text:00000000000005FC MOV X8, #0.text:0000000000000600 MOV W9, #0x64AE.text:0000000000000604 MOVK X8, #0,LSL#16.text:0000000000000608 MOVK W9, #0x299E,LSL#16.text:000000000000060C MOVK X8, #0,LSL#32.text:0000000000000610 MOVK X8, #0,LSL#48
之后(9.3 版本):
.text:00000000000005FC MOV X8, #(WORD0(unk_16120)).text:0000000000000600 MOV W9, #(WORD0(0x299E64AE)).text:0000000000000604 MOVK X8, #(WORD1(unk_16120)),LSL#16.text:0000000000000608 MOVK W9, #(WORD1(0x299E64AE)),LSL#16.text:000000000000060C MOVK X8, #(WORD2(unk_16120)),LSL#32.text:0000000000000610 MOVK X8, #(WORD3(unk_16120)),LSL#48
其他更新
AARCH64 重定位
新增对 MOVW_UABS_G[0-3] 重定位类型的支持,并配备了相应的自定义引用处理程序。
iOS PAC 桩函数
改进了对 __auth_stubs 区段的识别能力,以更好地支持带有指针认证(PAC)的iOS二进制文件。
支持 BTI 的 PLT
新增对启用了分支目标识别(BTI) 的 ELF PLT(过程链接表)桩函数的识别。
架构支持:Andes Andestar V3 NDS32
IDA 现已提供对 Andestar V3 NDS32 架构的处理器模块支持。
架构简介:
Andes Andestar V3 NDS32 是一种获得专利的32位RISC风格指令集架构(ISA),其特点是混合使用16位与32位指令,以实现功耗与代码密度效率的最优化。该架构主要广泛应用于各类深度嵌入式系统和低功耗应用场景,例如物联网设备、微控制器、可穿戴设备、存储设备以及无人机等。
反编译器更新
V850 反编译器
IDA 9.3 引入了对 瑞萨 V850(亦称 NEC850 或 RH850)架构的反编译器支持。该架构是一款广泛应用于汽车电子控制单元(ECU)和工业应用领域的流行 32 位 RISC 处理器。反编译器同时支持现代的 RH850 ABI 和传统的 GCC V850 ABI。
void __fastcall matgen(float *a1, int a2, int a3, _DWORD *a4, int a5, int a6, int a7, int a8, float *a9){ int v9; // r27 float *v10; // r28 int v11; // r29 float v12; // r10 float v13; // r11 _DWORD *v14; // r11 int i; // r10 int j; // r24 float *v17; // r27 float *v18; // r29 int k; // r28 float *v20; // [sp+18h] [-10h] float *v21; // [sp+1Ch] [-Ch] int v22; // [sp+20h] [-8h] int v23; // [sp+24h] [-4h]
*a9 = 0.0; if ( a3 > 0 ) { v20 = a1; v23 = 4 * a2; v21 = a1; v22 = 0; v9 = 1325; do { v10 = v21; v11 = 0; do { ++v11; v9 = (int)(3125 * ((3125 * v9) & 0x8000FFFF)) % 0x10000; v12 = ((double)v9 - 32768.0) * 0.00006103515625; *v10 = v12; v13 = v12; if ( v12 <= *a9 ) v13 = *a9; *a9 = v13; ++v10; } while ( v11 != a3 ); ++v22; v21 = (float *)((char *)v21 + v23); } while ( v22 != a3 ); v14 = a4; for ( i = 0; i != a3; ++i ) *v14++ = 0; for ( j = 0; j != a3; ++j ) { v17 = v20; v18 = (float *)a4; for ( k = 0; k != a3; ++k ) { *v18 = *v18 + *v17++; ++v18; } v20 = (float *)((char *)v20 + v23); } }}
微码操作
微码查看器现支持删除指令,并可为寄存器和局部变量指定特定值,以影响反编译过程的方向与结果。
赋值传播的精细控制
新增用户可控的 “禁止赋值传播” 选项(可通过右键上下文菜单访问)。
值范围优化
条件语句的分析现已包含增强的值范围跟踪。例如,在形如 (unsigned int)(a2 – 1) <= 5 的条件判断中,我们可以确定 a2 的取值范围是 [1, 6]。
实际示例:
if ( (unsigned int)v7 >= 0xA000 ) { ... if ( (unsigned int)(v7 - 0x1000) > 0x1000 ) {
第二个 if 语句现已被移除,因为其判断条件恒为真。
“全部反编译” 功能现已加入 IDA Home 与 IDA Pro Essential 版本
该功能现已在 IDA Home 和 IDA Pro Essential 版本中提供。
调试器更新
Android 14+ 原生调试
Android 调试器现已能正确支持 Android 14(及更高版本) 的应用程序调试。鉴于指针认证(PAC)在 Android 领域的应用日益广泛,我们还新增了对受保护的间接控制流转移指令(如 RETAA 等)的正确步过(stepping over) 支持。
调试器堆栈视图解引用功能
调试器中的堆栈视图现已支持自动解引用,其功能与之前在寄存器部件中提供的特性保持一致。调试会话期间,您可直接在堆栈视图中查看指针所指向的实际值。
用户界面改进
Dirtree 部件性能优化
完成了首轮速度优化。与 IDA 9.2 有时响应极慢(尤其是在进行大规模选择操作时)的情况相比,IDA 9.3 的工作体验将更加流畅愉悦。
处理大型数据集时,展示表格数据的部件性能应得到显著改善。
对于包含海量条目(10万以上)的部件,搜索操作的响应速度将大为提升。
在此过程中,排序与过滤功能(如使用 Ctrl+F)也同步得到了优化。
交叉引用树
IDA 9.2 中引入的 交叉引用树部件 现已得到显著增强。该树状视图现在能够利用寄存器追踪器和反编译器的信息,在函数调用处显示已知的函数参数值。这使得用户无需逐一跳转到每个调用位置,即可立即了解传递给函数的具体参数,极大提升了分析效率。
其他改进包括:
- 多选支持:支持使用标准的 Shift 和 Ctrl 键修饰符进行多选,以便执行批量操作。
- 搜索功能:按下 / 键打开搜索框,然后使用 F3 键在匹配项之间跳转(此功能仅适用于树中已展开的数据)。
- 列管理:列可以重新排序和调整大小,在列标题上使用右键菜单可以控制显示哪些列。
- 操作系统集成:交叉引用树操作现在可以像任何其他 IDA GUI 操作一样进行映射和自定义,允许设置个性化的键盘快捷键并集成到工作流中。
- 改进的深色主题支持:增强了在 macOS 系统上的深色主题体验。
交叉引用图
此版本主要改进了图的交互体验。
- 节点稳定:现在,当光标悬停在节点上时,节点不再移动,您无需再费力追踪它们。此外,您现在可以将节点固定在原位,从而始终精确掌握其位置——只需按下 P 键即可。
- 容量警告:为了保持图的响应能力,我们添加了一个警告功能,当图的容量超出限制时会自动弹出。这样,您就不会意外地使图过载并导致其变慢。容量限制可以在配置文件中进行调整。
- 图管理优化:图管理功能现在位于一个更符合逻辑的位置:视图 → 图 → 管理…,并且进行了许多细微的改进。
- 深色主题支持:交叉引用图现在支持深色主题。
全局跳转功能改进
- 针对大型数据库实现了更快的并行搜索(提升搜索响应速度)
- 新增对修饰名的搜索支持
- 优化了搜索排名算法
- 与撤销机制的集成更为完善
平台特定优化
- Linux Wayland:修复了应用图标显示问题,改进了停靠行为
- macOS:工具栏新增深色模式切换按钮、支持 .i64 捆绑文件类型扩展、Dock 菜单加入最近文件列表
通用改进
- 支持使用系统快捷键进行列表起始/结束导航(Ctrl+Home/End)
- 许可证借用功能增加日历选择器
- 本地类型窗口中移除了冗余的“解析声明”命令
细微调整
arm 架构:识别 Objective-C 桩函数的填充指令(brk#1)
- ui/输出窗口:新增控制自动滚动的选项(注册表设置);支持 Shift+单击进行选择
- ui/全局跳转:采用更高效的索引器搜索算法
SDK 与构建
简化开发者构建流程:
C++ SDK 现支持使用 CMake 进行构建
IDAPython 构建流程已简化,可与默认包管理器提供的 SWIG 4.2 和 4.3 配合使用(Linux 的 apt、Windows 的 winget、macOS 的 brew),无需再手动构建 SWIG
新增直接访问 Lumina API 的途径:通过 lumina.hpp(C++)和 ida_lumina(Python),开发者可在 IDA 内部以编程方式提取和应用 Lumina 元数据
其他增强:
新增公共函数 get_import_entry()(仅适用于 PE 文件!)
Hex-Rays SDK:将 hexrays.hpp 拆分为 4 个文件以提高模块化:
hexrays.hpp(主头文件,体积减小)
hexrays_ctree.hpp(C 树定义)
hexrays_defs.hpp(定义和常量)
hexrays_micro.hpp(微码定义)
idalib:
idat 命令新增 -v 选项用于输出详细信息
防止 idat 删除已有的 IDB 文件
允许为临时数据库启用批处理模式
命令行开关 -S 已在 IDA Home 版本中启用
安全与第三方依赖更新
llvm:更新至 21.1.5
z3:更新至 4.15.3
pcre:更新至 10.47(修复 CVE-2025-58050)
clp:更新至 clp 1.17.10 / CoinUtils 2.11.12
libdwarf:更新至 2.2.0
已知问题
PySide6 在 Python 3.14 环境下会出现崩溃
软件获取
如有需要,请前往官网购买正版软件。
如需学习,公众号后台回复【IDA】获取技术交流版本,仅供学习使用。请24小时内删除即可!
pro_93_x64win.exe
FEA92387D60FAAD562343286F1E0E0111ED7FC9665ED8B3519D001BEADC9EE55
hexvault93_x64linux.run
E845B50C72E0DBEA34D3CA854796DCB342C06A0C606E3FE9AB2C7D67BF43A78D
lumina93_x64linux.run
A3E2F06DFB8705ACA6942170A2DF0E41DDDEEBD684BF879D44CC0A3FAAA1D6D3
脚本文件
All_Platform_93idakeygen.py
#!/usr/bin/env python3# -*- coding: utf-8 -*-import hashlibimport jsonimport osimport platformlicense = { "header": {"version": 1}, "payload": { "name": "HuanmengX", "email": "[email protected]", "licenses": [ { "id": "48-2137-ACAB-99", "edition_id": "ida-pro", "description": "license", "license_type": "named", "product": "IDA", "product_id": "IDAPRO", "product_version": "9.3", "seats": 1, "start_date": "2024-08-10 00:00:00", "end_date": "2083-12-31 23:59:59", "issued_on": "2024-08-10 00:00:00", "owner": "Creaked By [email protected]", "add_ons": [], "features": [], } ], },}def add_every_addon(license): platforms = [ "W", # Windows "L", # Linux "M", # macOS ] addons = [ "HEXX86", "HEXX64", "HEXARM", "HEXARM64", "HEXMIPS", "HEXMIPS64", "HEXPPC", "HEXPPC64", "HEXRV64", "HEXARC", "HEXARC64", ] i = 0 for addon in addons: i += 1 license["payload"]["licenses"][0]["add_ons"].append( { "id": f"48-1337-0000-{i:02}", "code": addon, "owner": license["payload"]["licenses"][0]["id"], "start_date": "2024-08-10 00:00:00", "end_date": "2083-12-31 23:59:59", } )add_every_addon(license)def json_stringify_alphabetical(obj): return json.dumps(obj, sort_keys=True, separators=(",", ":"))def buf_to_bigint(buf): return int.from_bytes(buf, byteorder="little")def bigint_to_buf(i): return i.to_bytes((i.bit_length() + 7) // 8, byteorder="little")# Yup, you only have to patch 5c -> cb in libida64.sopub_modulus_hexrays = buf_to_bigint( bytes.fromhex( "edfd425cf978546e8911225884436c57140525650bcf6ebfe80edbc5fb1de68f4c66c29cb22eb668788afcb0abbb718044584b810f8970cddf227385f75d5dddd91d4f18937a08aa83b28c49d12dc92e7505bb38809e91bd0fbd2f2e6ab1d2e33c0c55d5bddd478ee8bf845fcef3c82b9d2929ecb71f4d1b3db96e3a8e7aaf93" ))pub_modulus_patched = buf_to_bigint( bytes.fromhex( "edfd42cbf978546e8911225884436c57140525650bcf6ebfe80edbc5fb1de68f4c66c29cb22eb668788afcb0abbb718044584b810f8970cddf227385f75d5dddd91d4f18937a08aa83b28c49d12dc92e7505bb38809e91bd0fbd2f2e6ab1d2e33c0c55d5bddd478ee8bf845fcef3c82b9d2929ecb71f4d1b3db96e3a8e7aaf93" ))private_key = buf_to_bigint( bytes.fromhex( "77c86abbb7f3bb134436797b68ff47beb1a5457816608dbfb72641814dd464dd640d711d5732d3017a1c4e63d835822f00a4eab619a2c4791cf33f9f57f9c2ae4d9eed9981e79ac9b8f8a411f68f25b9f0c05d04d11e22a3a0d8d4672b56a61f1532282ff4e4e74759e832b70e98b9d102d07e9fb9ba8d15810b144970029874" ))def decrypt(message): decrypted = pow(buf_to_bigint(message), exponent, pub_modulus_patched) decrypted = bigint_to_buf(decrypted) return decrypted[::-1]def encrypt(message): encrypted = pow(buf_to_bigint(message[::-1]), private_key, pub_modulus_patched) encrypted = bigint_to_buf(encrypted) return encryptedexponent = 0x13def sign_hexlic(payload: dict) -> str: data = {"payload": payload} data_str = json_stringify_alphabetical(data) buffer = bytearray(128) # first 33 bytes are random for i in range(33): buffer[i] = 0x42 # compute sha256 of the data sha256 = hashlib.sha256() sha256.update(data_str.encode()) digest = sha256.digest() # copy the sha256 digest to the buffer for i in range(32): buffer[33 + i] = digest[i] # encrypt the buffer encrypted = encrypt(buffer) return encrypted.hex().upper()def patch(filename): if not os.path.exists(filename): print(f"Skip: {filename} - didn't find") return with open(filename, "rb") as f: data = f.read() if data.find(bytes.fromhex("EDFD42CBF978")) != -1: print(f"Patch: {filename} - looks to be already patched :)") return if data.find(bytes.fromhex("EDFD425CF978")) == -1: print(f"Patch: {filename} - doesn't contain the original modulus.") return data = data.replace( bytes.fromhex("EDFD425CF978"), bytes.fromhex("EDFD42CBF978") ) with open(filename, "wb") as f: f.write(data) print(f"Patch: {filename} - OK")license["signature"] = sign_hexlic(license["payload"])serialized = json_stringify_alphabetical(license)filename = "idapro.hexlic"with open(filename, "w") as f: f.write(serialized)print(f"\nSaved new license to {filename}!\n")os_name = platform.system().lower()if os_name == 'windows': patch("ida.dll") patch("ida32.dll")elif os_name == 'linux': patch("libida.so") patch("libida32.so")elif os_name == 'darwin': patch("libida.dylib") patch("libida32.dylib")
keypatch-2.2.py
#!/usr/bin/env python3# -*- coding: utf-8 -*-# Keypatch IDA Plugin, powered by Keystone Engine (http://www.keystone-engine.org).# By Nguyen Anh Quynh & Thanh Nguyen, 2018.# Keypatch is released under the GPL v2. See COPYING for more information.# Find docs & latest version at http://keystone-engine.org/keypatch# This IDA plugin includes 3 tools inside: Patcher, Fill Range & Search.# Access to these tools via menu "Edit | Keypatch", or via right-click popup menu "Keypatch".# Hotkey Ctrl-Alt-K opens either Patcher or "Fill Range" window, depending on context.# - If there is no code selection, hotkey opens Patcher dialog# - If a range of code is selected, hotkey opens "Fill Range" dialog# To revert (undo) the last patching, choose menu "Edit | Keypatch | Undo last patching".# To check for update version, choose menu "Edit | Keypatch | Check for update".''' Do not use shortcut(Ctrk + Alt + K) for debugging, otherwise debugging features will not be available.'''#########################################################################################################is_debug = Falseif is_debug: ''' Install pydevd: 1. sudo pip install pydevd or 2. Install pycharm-debug.egg, Ensure to use pycharm pro https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html # import site # site.addsitedir("/usr/local/lib/python2.7/site-packages") ''' try: import pydevd pydevd.settrace(host='localhost', port=51234, stdoutToServer=True, stderrToServer=True ) except Exception as e: print(e)#########################################################################################################import jsonimport osimport reimport ida_idaimport idaapiimport idcimport sixfrom keystone import *################################ IDA 6/7 Compatibility import ###########################################if idaapi.IDA_SDK_VERSION >= 700: import ida_search from idc import ( get_operand_type, print_operand, get_item_end, get_item_head, get_item_size, get_sreg, has_value, get_full_flags, get_wide_byte, get_func_attr, set_func_end, warning, get_screen_ea ) from idaapi import ( get_bytes, plan_and_wait, is_mapped, Choose, jumpto )else: from idc import ( GetOpType as get_operand_type, GetOpnd as print_operand, ItemEnd as get_item_end, ItemHead as get_item_head, ItemSize as get_item_size, GetReg as get_sreg, hasValue as has_value, GetFlags as get_full_flags, Byte as get_wide_byte, GetFunctionAttr as get_func_attr, Jump as jumpto, Warning as warning, ScreenEA as get_screen_ea ) from idaapi import ( get_many_bytes as get_bytes, analyze_area as plan_and_wait, isEnabled as is_mapped, func_setend as set_func_end, Choose2 as Choose )########################################################################################################## bleeding-edge version# on a new release, this should be sync with VERSION_STABLE fileVERSION = "2.2"MAX_INSTRUCTION_STRLEN = 256MAX_ENCODING_LEN = 40MAX_ADDRESS_LEN = 40ENCODING_ERR_OUTPUT = "..."KP_GITHUB_VERSION = "https://raw.githubusercontent.com/keystone-engine/keypatch/master/VERSION_STABLE"KP_HOMEPAGE = "http://keystone-engine.org/keypatch"X86_NOP = "\x90"# Configuration fileKP_CFGFILE = os.path.join(idaapi.get_user_idadir(), "keypatch.cfg")# save all the info on patchingpatch_info = []################################ Python2/3 Compatibility function ######################################## Convert unicode/string/bytes to stringdef to_string(s): # python3 bytes if six.PY3 and isinstance(s, bytes): return s.decode('latin-1') # python2 unicode elif six.PY2 and isinstance(s, six.text_type): return s.encode('utf-8') return str(s)def to_hexstr(buf, sep=' '): # for python3 bytes if six.PY3 and isinstance(buf, bytes): return sep.join("{0:02x}".format(c) for c in buf).upper() return sep.join("{0:02x}".format(ord(c)) for c in buf).upper()######################################################################################################################################### IDA 6/7 Compatibility function #########################################def get_dtype(ea, op_idx): if idaapi.IDA_SDK_VERSION >= 700: insn = idaapi.insn_t() idaapi.decode_insn(insn, ea) dtype = insn.ops[op_idx].dtype dtyp_size = idaapi.get_dtype_size(dtype) else: dtype = idaapi.cmd.Operands[op_idx].dtyp dtyp_size = idaapi.get_dtyp_size(dtype) return dtype, dtyp_sizedef set_comment(ea, comment): if idaapi.IDA_SDK_VERSION >= 700: idc.set_cmt(ea, comment, 0) else: idc.MakeComm(ea, comment)def get_comment(ea): if idaapi.IDA_SDK_VERSION >= 700: return idc.get_cmt(ea, 0) return idc.Comment(ea)def read_range_selection(): if idaapi.IDA_SDK_VERSION >= 700: return idaapi.read_range_selection(None) return idaapi.read_selection()########################################################################################################## return a normalized code, or None if input is invaliddef convert_hexstr(code): # normalize code code = code.lower() code = code.replace(' ', '') # remove space code = code.replace('h', '') # remove trailing 'h' in 90h code = code.replace('0x', '') # remove 0x code = code.replace('\\x', '') # remove \x code = code.replace(',', '') # remove , code = code.replace(';', '') # remove ; code = code.replace('"', '') # remove " code = code.replace("'", '') # remove ' code = code.replace("+", '') # remove + # single-digit hexcode? if len(code) == 1 and ((code >= '0' and code <= '9') or (code >= 'a' and code <= 'f')): # stick 0 in front (so 'a' --> '0a') code = '0' + code # odd-length is invalid if len(code) % 2 != 0: return None try: hex_data = code.decode('hex') # we want a list of int return [ord(i) for i in hex_data] except: # invalid hex return None# download a file from @url, then return (result, file-content)# return (0, content) on success, or ({1|2}, None) on download failuredef url_download(url): # Import for python2 try: from urllib2 import Request, urlopen, URLError, HTTPError except: from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError # create the url and the request req = Request(url) # Open the url try: # download this URL f = urlopen(req) content = f.read() return (0, content) # handle errors except HTTPError as e: # print "HTTP Error:", e.code , url # fail to download this file return (1, None) except URLError as e: # print "URL Error:", e.reason , url # fail to download this file return (1, None) except Exception as e: # fail to save the downloaded file # print("Error:", e) return (2, None)def get_name_value(_from, name): """ Fixed: the return value truncated(32 bit) of get_name_value function that analyzed 64 bit binary file about ida64 for win. eg: type == idaapi.NT_BYTE (type, value) = idaapi.get_name_value(idc.BADADDR, "wcschr") # ida64 for win value = 0x14003d3f0L is correct ida64 > 7.x for macOS value = 0x4003d3f0L is truncated ida64 >= 6.x for win, ida64 == 6.x for macOS :param _from: ea :param name: name string :return: tuple """ name = to_string(name) (typ, value) = idaapi.get_name_value(_from, name) if typ == idaapi.NT_BYTE: # typ is byte name (regular name) value = idaapi.get_name_ea(_from, name) return (typ, value)## Main Keypatch classclass Keypatch_Asm: # supported architectures arch_lists = { "X86 16-bit": (KS_ARCH_X86, KS_MODE_16), # X86 16-bit "X86 32-bit": (KS_ARCH_X86, KS_MODE_32), # X86 32-bit "X86 64-bit": (KS_ARCH_X86, KS_MODE_64), # X86 64-bit "ARM": (KS_ARCH_ARM, KS_MODE_ARM), # ARM "ARM Thumb": (KS_ARCH_ARM, KS_MODE_THUMB), # ARM Thumb "ARM64 (ARMV8)": (KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN), # ARM64 "Hexagon": (KS_ARCH_HEXAGON, KS_MODE_BIG_ENDIAN), # Hexagon "Mips32": (KS_ARCH_MIPS, KS_MODE_MIPS32), # Mips32 "Mips64": (KS_ARCH_MIPS, KS_MODE_MIPS64), # Mips64 "PowerPC 32": (KS_ARCH_PPC, KS_MODE_PPC32), # PPC32 "PowerPC 64": (KS_ARCH_PPC, KS_MODE_PPC64), # PPC64 "Sparc 32": (KS_ARCH_SPARC, KS_MODE_SPARC32), # Sparc32 "Sparc 64": (KS_ARCH_SPARC, KS_MODE_SPARC64), # Sparc64 "SystemZ": (KS_ARCH_SYSTEMZ, KS_MODE_BIG_ENDIAN), # SystemZ } endian_lists = { "Little Endian": KS_MODE_LITTLE_ENDIAN, # little endian "Big Endian": KS_MODE_BIG_ENDIAN, # big endian } syntax_lists = { "Intel": KS_OPT_SYNTAX_INTEL, "Nasm": KS_OPT_SYNTAX_NASM, "AT&T": KS_OPT_SYNTAX_ATT } def __init__(self, arch=None, mode=None): # update current arch and mode self.update_hardware_mode() # override arch & mode if provided if arch is not None: self.arch = arch if mode is not None: self.mode = mode # IDA uses Intel syntax by default self.syntax = KS_OPT_SYNTAX_INTEL # return Keystone arch & mode (with endianess included) @staticmethod def get_hardware_mode(): (arch, mode) = (None, None) try: # since IDA 9 is_64bit = ida_ida.idainfo_is_64bit() is_32bit = ida_ida.idainfo_is_32bit() except: # heuristically detect hardware setup info = idaapi.get_inf_structure() is_64bit = info.is_64bit() is_32bit = info.is_32bit() try: # since IDA 9 cpuname = ida_ida.inf_get_procname().lower() except: try: cpuname = info.procname.lower() except: cpuname = info.procName.lower() try: # since IDA 9 is_be = ida_ida.inf_is_be() except: try: # since IDA7 beta 3 (170724) renamed inf.mf -> is_be()/set_be() is_be = idaapi.cvar.inf.is_be() except: # older IDA versions is_be = idaapi.cvar.inf.mf # print("Keypatch BIG_ENDIAN = %s" %is_be) if cpuname == "metapc": arch = KS_ARCH_X86 if is_64bit: mode = KS_MODE_64 elif is_32bit: mode = KS_MODE_32 else: mode = KS_MODE_16 elif cpuname.startswith("arm"): # ARM or ARM64 if is_64bit: arch = KS_ARCH_ARM64 if is_be: mode = KS_MODE_BIG_ENDIAN else: mode = KS_MODE_LITTLE_ENDIAN else: arch = KS_ARCH_ARM # either big-endian or little-endian if is_be: mode = KS_MODE_ARM | KS_MODE_BIG_ENDIAN else: mode = KS_MODE_ARM | KS_MODE_LITTLE_ENDIAN elif cpuname.startswith("sparc"): arch = KS_ARCH_SPARC if is_64bit: mode = KS_MODE_SPARC64 else: mode = KS_MODE_SPARC32 if is_be: mode |= KS_MODE_BIG_ENDIAN else: mode |= KS_MODE_LITTLE_ENDIAN elif cpuname.startswith("ppc"): arch = KS_ARCH_PPC if is_64bit: mode = KS_MODE_PPC64 else: mode = KS_MODE_PPC32 if cpuname == "ppc": # do not support Little Endian mode for PPC mode += KS_MODE_BIG_ENDIAN elif cpuname.startswith("mips"): arch = KS_ARCH_MIPS if is_64bit: mode = KS_MODE_MIPS64 else: mode = KS_MODE_MIPS32 if is_be: mode |= KS_MODE_BIG_ENDIAN else: mode |= KS_MODE_LITTLE_ENDIAN elif cpuname.startswith("systemz") or cpuname.startswith("s390x"): arch = KS_ARCH_SYSTEMZ mode = KS_MODE_BIG_ENDIAN return (arch, mode) def update_hardware_mode(self): (self.arch, self.mode) = self.get_hardware_mode() # normalize assembly code # remove comment at the end of assembly code @staticmethod def asm_normalize(text): text = ' '.join(text.split()) if text.rfind(';') != -1: return text[:text.rfind(';')].strip() return text.strip() @staticmethod # check if input address is valid # return # -1 invalid address at target binary # 0 type mismatch of input address # 1 valid address at target binary def check_address(address): try: if is_mapped(address): return 1 else: return -1 except: # invalid type return 0 ### resolve IDA names from input asm code # todo: a better syntax parser for all archs def ida_resolve(self, assembly, address=idc.BADADDR): def _resolve(_op, ignore_kw=True): names = re.findall(r"[\$a-z0-9_:\.]+", _op, re.I) # try to resolve all names for name in names: # ignore known keywords if ignore_kw and name in ('byte', 'near', 'short', 'word', 'dword', 'ptr', 'offset'): continue sym = name # split segment reg parts = name.partition(':') if parts[2] != '': sym = parts[2] (typ, value) = get_name_value(address, sym) # skip if name doesn't exist or segment / segment registers if typ in (idaapi.NT_SEG, idaapi.NT_NONE): continue _op = _op.replace(sym, '0x{0:X}'.format(value)) return _op if self.check_address(address) == 0: print("Keypatch: WARNING: invalid input address {0}".format(address)) return assembly # for now, we only support IDA name resolve for X86, ARM, ARM64, MIPS, PPC, SPARC if not (self.arch in (KS_ARCH_X86, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_PPC, KS_ARCH_SPARC)): return assembly _asm = assembly.partition(' ') mnem = _asm[0] opers = _asm[2].split(',') for idx, op in enumerate(opers): _op = list(op.partition('[')) ignore_kw = True if _op[1] == '': _op[2] = _op[0] _op[0] = '' else: _op[0] = _resolve(_op[0], ignore_kw=True) ignore_kw = False _op[2] = _resolve(_op[2], ignore_kw=ignore_kw) opers[idx] = ''.join(_op) asm = "{0} {1}".format(mnem, ','.join(opers)) return asm # return bytes of instruction or data # return None on failure def ida_get_item(self, address, hex_output=False): if self.check_address(address) != 1: # not a valid address return (None, 0) # return None if address is in the middle of instruction / data if address != get_item_head(address): return (None, 0) size = get_item_size(address) item = get_bytes(address, size) if item is None: return (None, 0) if hex_output: item = to_hexstr(item) return (item, size) @staticmethod def get_op_dtype_name(ea, op_idx): dtyp_lists = { idaapi.dt_byte: 'byte', # 8 bit idaapi.dt_word: 'word', # 16 bit idaapi.dt_dword: 'dword', # 32 bit idaapi.dt_float: 'dword', # 4 byte idaapi.dt_double: 'dword', # 8 byte # idaapi.dt_tbyte = 5 # variable size (ph.tbyte_size) # idaapi.dt_packreal = 6 # packed real format for mc68040 idaapi.dt_qword: 'qword', # 64 bit idaapi.dt_byte16: 'xmmword', # 128 bit # idaapi.dt_code = 9 # ptr to code (not used?) # idaapi.dt_void = 10 # none # idaapi.dt_fword = 11 # 48 bit # idaapi.dt_bitfild = 12 # bit field (mc680x0) # idaapi.dt_string = 13 # pointer to asciiz string # idaapi.dt_unicode = 14 # pointer to unicode string # idaapi.dt_3byte = 15 # 3-byte data # idaapi.dt_ldbl = 16 # long double (which may be different from tbyte) idaapi.dt_byte32: 'ymmword', # 256 bit } dtype, dtyp_size = get_dtype(ea, op_idx) if dtype == idaapi.dt_tbyte and dtyp_size == 10: return 'xword' dtyp_name = dtyp_lists.get(dtype, None) return dtyp_name # return asm instructions from start to end def ida_get_disasm_range(self, start, end): codes = [] while start < end: asm = self.asm_normalize(idc.GetDisasm(start)) if asm == None: asm = '' codes.append(asm) start = start + get_item_size(start) return codes # get disasm from IDA # return '' on invalid address def ida_get_disasm(self, address, fixup=False): def GetMnem(asm): sp = asm.find(' ') if sp == -1: return asm return asm[:sp] if self.check_address(address) != 1: # not a valid address return '' # return if address is in the middle of instruction / data if address != get_item_head(address): return '' asm = self.asm_normalize(idc.GetDisasm(address)) # for now, only support IDA syntax fixup for Intel CPU if not fixup or self.arch != KS_ARCH_X86: return asm # KS_ARCH_X86 mode # rebuild disasm code from IDA i = 0 mnem = GetMnem(asm) if mnem == '' or mnem in ('rep', 'repne', 'repe'): return asm opers = [] while get_operand_type(address, i) > 0 and i < 6: t = get_operand_type(address, i) o = print_operand(address, i) if t in (idc.o_mem, idc.o_displ): parts = list(o.partition(':')) if parts[2] == '': parts[2] = parts[0] parts[0] = '' if '[' not in parts[2]: parts[2] = '[{0}]'.format(parts[2]) o = ''.join(parts) if 'ptr ' not in o: dtyp_name = self.get_op_dtype_name(address, i) if dtyp_name != None: o = "{0} ptr {1}".format(dtyp_name, o) opers.append(o) i += 1 asm = mnem for o in opers: if o != '': asm = "{0} {1},".format(asm, o) asm = asm.strip(',') return asm # assemble code with Keystone # return (encoding, count), or (None, 0) on failure def assemble(self, assembly, address, arch=None, mode=None, syntax=None): # return assembly with arithmetic equation evaluated def eval_operand(assembly, start, stop, prefix=''): imm = assembly[start + 1:stop] try: eval_imm = eval(imm) if eval_imm > 0x80000000: eval_imm = 0xffffffff - eval_imm eval_imm += 1 eval_imm = -eval_imm return assembly.replace(prefix + imm, prefix + hex(eval_imm)) except: return assembly # IDA uses different syntax from Keystone # sometimes, we can convert code to be consumable by Keystone def fix_ida_syntax(assembly): # return True if this insn needs to be fixed def check_arm_arm64_insn(arch, mnem): if arch == KS_ARCH_ARM: if mnem.startswith("ldr") or mnem.startswith("str"): return True return False elif arch == KS_ARCH_ARM64: if mnem.startswith("ldr") or mnem.startswith("str"): return True return mnem in ("stp") return False # return True if this insn needs to be fixed def check_ppc_insn(mnem): return mnem in ("stw") # replace the right most string occurred def rreplace(s, old, new): li = s.rsplit(old, 1) return new.join(li) # convert some ARM pre-UAL assembly to UAL, so Keystone can handle it # example: streqb --> strbeq def fix_arm_ual(mnem, assembly): # TODO: this is not an exhaustive list yet if len(mnem) != 6: return assembly if (mnem[-1] in ('s', 'b', 'h', 'd')): # print(">> 222", mnem[3:5]) if mnem[3:5] in ("cc", "eq", "ne", "hs", "lo", "mi", "pl", "vs", "vc", "hi", "ls", "ge", "lt", "gt", "le", "al"): return assembly.replace(mnem, mnem[:3] + mnem[-1] + mnem[3:5], 1) return assembly if self.arch != KS_ARCH_X86: assembly = assembly.lower() else: # Keystone does not support immediate 0bh, but only 0Bh assembly = assembly.upper() # however, 0X must be converted to 0x # Keystone should fix this limitation in the future assembly = assembly.replace("0X", " 0x") _asm = assembly.partition(' ') mnem = _asm[0] if mnem == '': return assembly # for PPC, Keystone does not accept registers with 'r' prefix, # but only the number behind. lets try to fix that here by # removing the prefix 'r'. if self.arch == KS_ARCH_PPC: for n in range(32): r = " r%u," % n if r in assembly: assembly = assembly.replace(r, " %u," % n) for n in range(32): r = "(r%u)" % n if r in assembly: assembly = assembly.replace(r, "(%u)" % n) for n in range(32): r = ", r%u" % n if assembly.endswith(r): assembly = rreplace(assembly, r, ", %u" % n) if self.arch == KS_ARCH_X86: if mnem == "RETN": # replace retn with ret return assembly.replace('RETN', 'RET', 1) if 'OFFSET ' in assembly: return assembly.replace('OFFSET ', ' ') if mnem in ('CALL', 'JMP') or mnem.startswith('LOOP'): # remove 'NEAR PTR' if ' NEAR PTR ' in assembly: return assembly.replace(' NEAR PTR ', ' ') elif mnem[0] == 'J': # JMP instruction if ' SHORT ' in assembly: # remove ' short ' return assembly.replace(' SHORT ', ' ') elif self.arch in (KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_PPC): # *** ARM # LDR R1, [SP+rtld_fini],#4 # STR R2, [SP,#-4+rtld_fini]! # STR R0, [SP,#fini]! # STR R12, [SP,#4+var_8]! # *** ARM64 # STP X29, X30, [SP,#-0x10+var_150]! # STR W0, [X29,#0x150+var_8] # LDR X0, [X0,#(qword_4D6678 - 0x4D6660)] # TODO: # ADRP X19, #interactive@PAGE # *** PPC # stw r5, 0x120+var_108(r1) if self.arch == KS_ARCH_ARM and mode == KS_MODE_THUMB: assembly = assembly.replace('movt.w', 'movt') if self.arch == KS_ARCH_ARM: # print(">> before UAL fix: ", assembly) assembly = fix_arm_ual(mnem, assembly) # print(">> after UAL fix: ", assembly) if check_arm_arm64_insn(self.arch, mnem) or (("[" in assembly) and ("]" in assembly)): bang = assembly.find('#') bracket = assembly.find(']') if bang != -1 and bracket != -1 and bang < bracket: return eval_operand(assembly, bang, bracket, '#') elif '+0x0]' in assembly: return assembly.replace('+0x0]', ']') elif check_ppc_insn(mnem): start = assembly.find(', ') stop = assembly.find('(') if start != -1 and stop != -1 and start < stop: return eval_operand(assembly, start, stop) return assembly def is_thumb(address): return get_sreg(address, 'T') == 1 if self.check_address(address) == 0: return (None, 0) # use default syntax, arch and mode if not provided if syntax is None: syntax = self.syntax if arch is None: arch = self.arch if mode is None: mode = self.mode if arch == KS_ARCH_ARM and is_thumb(address): mode = KS_MODE_THUMB try: ks = Ks(arch, mode) if arch == KS_ARCH_X86: ks.syntax = syntax encoding, count = ks.asm(fix_ida_syntax(assembly), address) except KsError as e: # keep the below code for debugging # print("Keypatch Error: {0}".format(e)) # print("Original asm: {0}".format(assembly)) # print("Fixed up asm: {0}".format(fix_ida_syntax(assembly))) encoding, count = None, 0 return (encoding, count) # patch at address, return the number of written bytes & original data # this process can fail in some cases @staticmethod def patch_raw(address, patch_data, size): ea = address orig_data = '' while ea < (address + size): if not has_value(get_full_flags(ea)): print("Keypatch: FAILED to read data at 0x{0:X}".format(ea)) break orig_byte = get_wide_byte(ea) orig_data += chr(orig_byte) patch_byte = ord(patch_data[ea - address]) if patch_byte != orig_byte: # patch one byte if idaapi.patch_byte(ea, patch_byte) != 1: print("Keypatch: FAILED to patch byte at 0x{0:X} [0x{1:X}]".format(ea, patch_byte)) break ea += 1 return (ea - address, orig_data) # patch at address, return the number of written bytes & original data # on patch failure, we revert to the original code, then return (None, None) def patch(self, address, patch_data, size): # save original function end to fix IDA re-analyze issue after patching orig_func_end = get_func_attr(address, idc.FUNCATTR_END) (patched_len, orig_data) = self.patch_raw(address, patch_data, size) if size != patched_len: # patch failure if patched_len > 0: # revert the changes (rlen, _) = self.patch_raw(address, orig_data, patched_len) if rlen == patched_len: print("Keypatch: successfully reverted changes of {0:d} byte(s) at 0x{1:X} [{2}]".format( patched_len, address, to_hexstr(orig_data))) else: print("Keypatch: FAILED to revert changes of {0:d} byte(s) at 0x{1:X} [{2}]".format( patched_len, address, to_hexstr(orig_data))) return (None, None) # ask IDA to re-analyze the patched area if orig_func_end == idc.BADADDR: # only analyze patched bytes, otherwise it would take a lot of time to re-analyze the whole binary plan_and_wait(address, address + patched_len + 1) else: plan_and_wait(address, orig_func_end) # try to fix IDA function re-analyze issue after patching set_func_end(address, orig_func_end) return (patched_len, orig_data) # return number of bytes patched # return # 0 Invalid assembly # -1 PatchByte failure # -2 Can't read original data # -3 Invalid address def patch_code(self, address, assembly, syntax, padding, save_origcode, orig_asm=None, patch_data=None, patch_comment=None, undo=False): global patch_info if self.check_address(address) != 1: # not a valid address return -3 orig_comment = get_comment(address) if orig_comment is None: orig_comment = '' nop_comment = "" padding_len = 0 if not undo: # we are patching via Patcher (orig_encoding, orig_len) = self.ida_get_item(address) if (orig_encoding, orig_len) == (None, 0): return -2 (encoding, count) = self.assemble(assembly, address, syntax=syntax) if encoding is None: return 0 patch_len = len(encoding) patch_data = ''.join(chr(c) for c in encoding) if patch_data == orig_encoding: # print("Keypatch: no need to patch, same encoding data [{0}] at 0x{1:X}".format(to_hexstr(orig_encoding), address)) return orig_len # for now, only support NOP padding on Intel CPU if padding and self.arch == KS_ARCH_X86: if patch_len < orig_len: padding_len = orig_len - patch_len patch_len = orig_len patch_data = patch_data.ljust(patch_len, X86_NOP) elif patch_len > orig_len: patch_end = address + patch_len - 1 ins_end = get_item_end(patch_end) padding_len = ins_end - patch_end - 1 if padding_len > 0: patch_len = ins_end - address patch_data = patch_data.ljust(patch_len, X86_NOP) if padding_len > 0: nop_comment = "\nKeypatch padded NOP to next boundary: {0} bytes".format(padding_len) orig_asm = self.ida_get_disasm_range(address, address + patch_len) else: # we are reverting the change via "Undo" menu patch_len = len(patch_data) (plen, p_orig_data) = self.patch(address, patch_data, patch_len) if plen is None: # failed to patch return -1 if not undo: # we are patching new_patch_comment = None if save_origcode is True: # append original instruction to comments if orig_comment == '': new_patch_comment = "Keypatch modified this from:\n {0}{1}".format('\n '.join(orig_asm), nop_comment) else: new_patch_comment = "\nKeypatch modified this from:\n {0}{1}".format('\n '.join(orig_asm), nop_comment) new_comment = "{0}{1}".format(orig_comment, new_patch_comment) set_comment(address, new_comment) if padding_len == 0: print("Keypatch: successfully patched {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}]".format(plen, address, to_hexstr( p_orig_data), to_hexstr( patch_data))) else: print( "Keypatch: successfully patched {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}], with {4} byte(s) NOP padded".format( plen, address, to_hexstr(p_orig_data), to_hexstr(patch_data), padding_len)) # save this patching for future "undo" patch_info.append((address, assembly, p_orig_data, new_patch_comment)) else: # we are reverting if patch_comment: # clean previous IDA comment by replacing it with '' new_comment = orig_comment.replace(patch_comment, '') set_comment(address, new_comment) print("Keypatch: successfully reverted {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}]".format(plen, address, to_hexstr( p_orig_data), to_hexstr( patch_data))) return plen # fill a range of code [addr_begin, addr_end]. # return the length of patched area # on failure, return 0 = wrong input, -1 = failed to patch def fill_code(self, addr_begin, addr_end, assembly, syntax, padding, save_origcode, orig_asm=None): # treat input as assembly code first (encoding, _) = self.assemble(assembly, addr_begin, syntax=syntax) if encoding is None: # input might be a hexcode string. try to convert it to raw bytes encoding = convert_hexstr(assembly) if encoding is None: # invalid input: this is neither assembly nor hexcode string return 0 # save original assembly code before overwritting them orig_asm = self.ida_get_disasm_range(addr_begin, addr_end) # save original comment at addr_begin # TODO: save comments in this range, but how to interleave them? orig_comment = get_comment(addr_begin) if orig_comment is None: orig_comment = '' patch_data = "" assembly_new = [] size = addr_end - addr_begin # calculate filling data encode_chr = ''.join(chr(c) for c in encoding) while True: if len(patch_data) + len(encode_chr) <= size: patch_data = patch_data + encode_chr assembly_new += [assembly.strip()] else: break # for now, only support NOP padding on Intel CPU if padding and self.arch == KS_ARCH_X86: for i in range(size - len(patch_data)): assembly_new += ["nop"] patch_data = patch_data.ljust(size, X86_NOP) (plen, p_orig_data) = self.patch(addr_begin, patch_data, len(patch_data)) if plen is None: # failed to patch return -1 new_patch_comment = '' # append original instruction to comments if save_origcode is True: if orig_comment == '': new_patch_comment = "Keypatch filled range [0x{0:X}:0x{1:X}] ({2} bytes), replaced:\n {3}".format( addr_begin, addr_end - 1, addr_end - addr_begin, '\n '.join(orig_asm)) else: new_patch_comment = "\nKeypatch filled range [0x{0:X}:0x{1:X}] ({2} bytes), replaced:\n {3}".format( addr_begin, addr_end - 1, addr_end - addr_begin, '\n '.join(orig_asm)) new_comment = "{0}{1}".format(orig_comment, new_patch_comment) set_comment(addr_begin, new_comment) print("Keypatch: successfully filled range [0x{0:X}:0x{1:X}] ({2} bytes) with \"{3}\", replaced \"{4}\"".format( addr_begin, addr_end - 1, addr_end - addr_begin, assembly, '; '.join(orig_asm))) # save this modification for future "undo" patch_info.append((addr_begin, '\n '.join(assembly_new), p_orig_data, new_patch_comment)) return plen ### Form helper functions @staticmethod def dict_to_ordered_list(dictionary): l = sorted(list(dictionary.items()), key=lambda t: t[0], reverse=False) keys = [i[0] for i in l] values = [i[1] for i in l] return (keys, values) def get_value_by_idx(self, dictionary, idx, default=None): (keys, values) = self.dict_to_ordered_list(dictionary) try: val = values[idx] except IndexError: val = default return val def find_idx_by_value(self, dictionary, value, default=None): (keys, values) = self.dict_to_ordered_list(dictionary) try: idx = values.index(value) except: idx = default return idx def get_arch_by_idx(self, idx): return self.get_value_by_idx(self.arch_lists, idx) def find_arch_idx(self, arch, mode): return self.find_idx_by_value(self.arch_lists, (arch, mode)) def get_syntax_by_idx(self, idx): return self.get_value_by_idx(self.syntax_lists, idx, self.syntax) def find_syntax_idx(self, syntax): return self.find_idx_by_value(self.syntax_lists, syntax) ### /Form helper functions# Common ancestor form to be derived by Patcher, FillRange & Searchclass Keypatch_Form(idaapi.Form): # prepare for form initializing def setup(self, kp_asm, address, assembly=None): self.kp_asm = kp_asm self.address = address # update ordered list of arch and syntax self.syntax_keys = self.kp_asm.dict_to_ordered_list(self.kp_asm.syntax_lists)[0] self.arch_keys = self.kp_asm.dict_to_ordered_list(self.kp_asm.arch_lists)[0] # update current arch & mode self.kp_asm.update_hardware_mode() # find right value for c_arch & c_endian controls mode = self.kp_asm.mode self.endian_id = 0 # little endian if self.kp_asm.mode & KS_MODE_BIG_ENDIAN: self.endian_id = 1 # big endian mode = self.kp_asm.mode - KS_MODE_BIG_ENDIAN self.arch_id = self.kp_asm.find_arch_idx(self.kp_asm.arch, mode) self.syntax_id = 0 # to make non-X86 arch happy if self.kp_asm.arch == KS_ARCH_X86: self.syntax_id = self.kp_asm.find_syntax_idx(self.kp_asm.syntax) # get original instruction and bytes self.orig_asm = kp_asm.ida_get_disasm(address) (self.orig_encoding, self.orig_len) = kp_asm.ida_get_item(address, hex_output=True) if self.orig_encoding is None: self.orig_encoding = '' if assembly is None: self.asm = self.kp_asm.ida_get_disasm(self.address, fixup=True) else: self.asm = assembly # update Encoding control # return True on success, False on failure def _update_encoding(self, arch, mode): try: syntax = None if arch == KS_ARCH_X86: syntax_id = self.GetControlValue(self.c_syntax) syntax = self.kp_asm.get_syntax_by_idx(syntax_id) address = self.GetControlValue(self.c_addr) try: is_mapped(address) except: # invalid address value address = 0 assembly = self.GetControlValue(self.c_assembly) raw_assembly = self.kp_asm.ida_resolve(assembly, address) self.SetControlValue(self.c_raw_assembly, raw_assembly) (encoding, count) = self.kp_asm.assemble(raw_assembly, address, arch=arch, mode=mode, syntax=syntax) if encoding is None: self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT) self.SetControlValue(self.c_encoding_len, 0) return False else: text = "" for byte in encoding: text += "%02X " % byte text.strip() if text == "": # error? self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT) return False else: self.SetControlValue(self.c_encoding, text.strip()) self.SetControlValue(self.c_encoding_len, len(encoding)) return True except Exception as e: print(str(e)) import traceback traceback.print_exc() self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT) return False # callback to be executed when any form control changed def OnFormChange(self, fid): return 1 # update Patcher & Fillrange controls def update_patchform(self, fid): self.EnableField(self.c_endian, False) self.EnableField(self.c_addr, False) (arch, mode) = (self.kp_asm.arch, self.kp_asm.mode) # assembly is focused self.SetFocusedField(self.c_assembly) if arch == KS_ARCH_X86: # do not show Endian control self.ShowField(self.c_endian, False) # allow to choose Syntax self.ShowField(self.c_syntax, True) self.ShowField(self.c_opt_padding, True) else: # do not show Syntax control for non-X86 mode self.ShowField(self.c_syntax, False) # for now, we do not support padding for non-X86 archs self.ShowField(self.c_opt_padding, False) # self.EnableField(self.c_opt_padding, False) # update other controls & Encoding with live assembling self.update_controls(arch, mode) return 1 # update some controls - including Encoding control def update_controls(self, arch, mode): # Fixup & Encoding-len are read-only controls self.EnableField(self.c_raw_assembly, False) self.EnableField(self.c_encoding_len, False) # Encoding is enable to allow user to select & copy self.EnableField(self.c_encoding, True) if self.GetControlValue(self.c_endian) == 1: endian = KS_MODE_BIG_ENDIAN else: endian = KS_MODE_LITTLE_ENDIAN # update encoding with live assembling self._update_encoding(arch, mode | endian) return 1 # get Patcher/FillRange options def get_opts(self, name=None): names = self.c_opt_chk.children_names val = self.c_opt_chk.value opts = {} for i in range(len(names)): opts[names[i]] = val & (2 ** i) if name != None: opts[name] = val return opts# Fill Range formclass Keypatch_FillRange(Keypatch_Form): def __init__(self, kp_asm, addr_begin, addr_end, assembly=None, opts=None): self.setup(kp_asm, addr_begin, assembly) self.addr_end = addr_end # create FillRange form super(Keypatch_FillRange, self).__init__( r"""STARTITEM {id:c_assembly}BUTTON YES* PatchKEYPATCH:: Fill Range {FormChangeCb} <Endian :{c_endian}> <~S~yntax :{c_syntax}> <Start :{c_addr}> <End :{c_addr_end}> <Size :{c_size}> <~A~ssembly :{c_assembly}> <##- Fixup :{c_raw_assembly}> <##- Encode:{c_encoding}> <##- Size :{c_encoding_len}> <~N~OPs padding until next instruction boundary:{c_opt_padding}> <Save ~o~riginal instructions in IDA comment:{c_opt_comment}>{c_opt_chk}> """, { 'c_endian': self.DropdownListControl( items=list(self.kp_asm.endian_lists.keys()), readonly=True, selval=self.endian_id), 'c_addr': self.NumericInput(value=addr_begin, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR), 'c_addr_end': self.NumericInput(value=addr_end - 1, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR), 'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN), 'c_size': self.NumericInput(value=addr_end - addr_begin, swidth=8, tp=self.FT_DEC), 'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN), 'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN), 'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC), 'c_syntax': self.DropdownListControl( items=self.syntax_keys, readonly=True, selval=self.syntax_id), 'c_opt_chk': idaapi.Form.ChkGroupControl(('c_opt_padding', 'c_opt_comment', ''), value=opts['c_opt_chk']), 'FormChangeCb': self.FormChangeCb(self.OnFormChange), }) self.Compile() # callback to be executed when any form control changed def OnFormChange(self, fid): # make some controls read-only in FillRange mode self.EnableField(self.c_size, False) self.EnableField(self.c_addr_end, False) return self.update_patchform(fid)# Patcher formclass Keypatch_Patcher(Keypatch_Form): def __init__(self, kp_asm, address, assembly=None, opts=None): self.setup(kp_asm, address, assembly) # create Patcher form super(Keypatch_Patcher, self).__init__( r"""STARTITEM {id:c_assembly}BUTTON YES* PatchKEYPATCH:: Patcher {FormChangeCb} <Endian :{c_endian}> <~S~yntax :{c_syntax}> <Address :{c_addr}> <Original :{c_orig_assembly}> <##- Encode:{c_orig_encoding}> <##- Size :{c_orig_len}> <~A~ssembly :{c_assembly}> <##- Fixup :{c_raw_assembly}> <##- Encode:{c_encoding}> <##- Size :{c_encoding_len}> <~N~OPs padding until next instruction boundary:{c_opt_padding}> <Save ~o~riginal instructions in IDA comment:{c_opt_comment}>{c_opt_chk}> """, { 'c_endian': self.DropdownListControl( items=list(self.kp_asm.endian_lists.keys()), readonly=True, selval=self.endian_id), 'c_addr': self.NumericInput(value=address, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR), 'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN), 'c_orig_assembly': self.StringInput(value=self.orig_asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN), 'c_orig_encoding': self.StringInput(value=self.orig_encoding[:MAX_ENCODING_LEN], width=MAX_ENCODING_LEN), 'c_orig_len': self.NumericInput(value=self.orig_len, swidth=8, tp=self.FT_DEC), 'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN), 'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN), 'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC), 'c_syntax': self.DropdownListControl( items=self.syntax_keys, readonly=True, selval=self.syntax_id), 'c_opt_chk': self.ChkGroupControl(('c_opt_padding', 'c_opt_comment', ''), value=opts['c_opt_chk']), 'FormChangeCb': self.FormChangeCb(self.OnFormChange), }) self.Compile() # callback to be executed when any form control changed def OnFormChange(self, fid): # make some fields read-only in Patch mode self.EnableField(self.c_orig_assembly, False) self.EnableField(self.c_orig_encoding, False) self.EnableField(self.c_orig_len, False) return self.update_patchform(fid)# Search position chooserclass SearchResultChooser(Choose): def __init__(self, title, items, flags=0, width=None, height=None, embedded=False, modal=False): super(SearchResultChooser, self).__init__( title, [["Address", Choose.CHCOL_HEX | 40]], flags=flags, width=width, height=height, embedded=embedded) self.n = 0 self.items = items self.selcount = 0 self.modal = modal def OnClose(self): return def OnSelectLine(self, n): self.selcount += 1 jumpto(self.items[n][0]) def OnGetLine(self, n): res = self.items[n] res = [idc.atoa(res[0])] return res def OnGetSize(self): n = len(self.items) return n def show(self): return self.Show(self.modal) >= 0# Search formclass Keypatch_Search(Keypatch_Form): def __init__(self, kp_asm, address, assembly=None): self.setup(kp_asm, address, assembly) # create Search form super(Keypatch_Search, self).__init__( r"""STARTITEM {id:c_assembly}BUTTON YES* SearchKEYPATCH:: Search {FormChangeCb} <A~r~ch :{c_arch}> <E~n~dian :{c_endian}> <~S~yntax :{c_syntax}> <A~d~dress :{c_addr}> <~A~ssembly :{c_assembly}> <##- Fixup :{c_raw_assembly}> <##- Encode:{c_encoding}> <##- Size :{c_encoding_len}> """, { 'c_addr': self.NumericInput(value=address, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR), 'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN), 'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN), 'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN), 'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC), 'c_arch': self.DropdownListControl( items=self.arch_keys, readonly=True, selval=self.arch_id, width=32), 'c_endian': self.DropdownListControl( items=list(self.kp_asm.endian_lists.keys()), readonly=True, selval=self.endian_id), 'c_syntax': self.DropdownListControl( items=self.syntax_keys, readonly=True, selval=self.syntax_id), 'FormChangeCb': self.FormChangeCb(self.OnFormChange), }) self.Compile() ################################ IDA 6/7 Compatibility method ####################################### def find_binary(self, ea): if idaapi.IDA_SDK_VERSION >= 700: return ida_search.find_binary(ea, idaapi.cvar.inf.max_ea, self.GetControlValue(self.c_encoding), 16, idc.SEARCH_DOWN) return idc.FindBinary(ea, idc.SEARCH_DOWN, self.GetControlValue(self.c_encoding)) ##################################################################################################### # callback to be executed when any form control changed def OnFormChange(self, fid): # handle the search button if fid == -2: address = 0 addresses = [] while address != idc.BADADDR: address = self.find_binary(address) if address == idc.BADADDR: break addresses.append([address]) address = address + 1 c = SearchResultChooser("Searching for [{0}]".format(self.GetControlValue(self.c_raw_assembly)), addresses) c.show() return 1 # only Search mode allows to select arch+mode arch_id = self.GetControlValue(self.c_arch) (arch, mode) = self.kp_asm.get_arch_by_idx(arch_id) # assembly is focused self.SetFocusedField(self.c_assembly) if arch == KS_ARCH_X86: # enable Syntax and disable Endian for x86 self.ShowField(self.c_syntax, True) self.EnableField(self.c_syntax, True) self.syntax_id = self.GetControlValue(self.c_syntax) self.EnableField(self.c_endian, False) # set Endian index properly self.SetControlValue(self.c_endian, 0) elif arch in (KS_ARCH_ARM64, KS_ARCH_HEXAGON, KS_ARCH_SYSTEMZ): # no Syntax & Endian option for these archs self.ShowField(self.c_syntax, False) self.EnableField(self.c_syntax, False) self.EnableField(self.c_endian, False) # set Endian index properly self.SetControlValue(self.c_endian, (mode & KS_MODE_BIG_ENDIAN != 0)) elif (arch == KS_ARCH_PPC) and (mode & KS_MODE_PPC32 != 0): # no Syntax & Endian option for these archs self.ShowField(self.c_syntax, False) self.EnableField(self.c_syntax, False) self.EnableField(self.c_endian, False) # set Endian index properly self.SetControlValue(self.c_endian, (mode & KS_MODE_BIG_ENDIAN != 0)) else: # no Syntax & Endian option self.ShowField(self.c_syntax, False) self.EnableField(self.c_syntax, False) self.EnableField(self.c_endian, True) if self.GetControlValue(self.c_endian) == 1: endian = KS_MODE_BIG_ENDIAN else: endian = KS_MODE_LITTLE_ENDIAN # update other controls & Encoding with live assembling self.update_controls(arch, mode) return 1# About formclass About_Form(idaapi.Form): def __init__(self, version): # create About form super(About_Form, self).__init__( r"""STARTITEM 0BUTTON YES* Open Keypatch WebsiteKEYPATCH:: About {FormChangeCb} Keypatch IDA plugin v%s, using Keystone Engine v%s. (c) Nguyen Anh Quynh + Thanh Nguyen, 2018. Keypatch is released under the GPL v2. Find more info at http://www.keystone-engine.org/keypatch """ % (version, keystone.__version__), { 'FormChangeCb': self.FormChangeCb(self.OnFormChange), }) self.Compile() # callback to be executed when any form control changed def OnFormChange(self, fid): if fid == -2: # Goto homepage import webbrowser # open Keypatch homepage in a new tab, if possible webbrowser.open(KP_HOMEPAGE, new=2) return 1# Check-for-update formclass Update_Form(idaapi.Form): def __init__(self, version, message): # create Update form super(Update_Form, self).__init__( r"""STARTITEM 0BUTTON YES* Open Keypatch WebsiteKEYPATCH:: Check for update {FormChangeCb} Your Keypatch is v%s %s """ % (version, message), { 'FormChangeCb': self.FormChangeCb(self.OnFormChange), }) self.Compile() # callback to be executed when any form control changed def OnFormChange(self, fid): if fid == -2: # Goto homepage import webbrowser # open Keypatch homepage in a new tab, if possible webbrowser.open(KP_HOMEPAGE, new=2) return 1try: # adapted from pull request #7 by @quangnh89 class Kp_Menu_Context(idaapi.action_handler_t): @classmethod def get_name(self): return self.__name__ @classmethod def get_label(self): return self.label @classmethod def register(self, plugin, label): self.plugin = plugin self.label = label instance = self() return idaapi.register_action(idaapi.action_desc_t( self.get_name(), # Name. Acts as an ID. Must be unique. instance.get_label(), # Label. That's what users see. instance # Handler. Called when activated, and for updating )) @classmethod def unregister(self): """Unregister the action. After unregistering the class cannot be used. """ idaapi.unregister_action(self.get_name()) @classmethod def activate(self, ctx): # dummy method return 1 @classmethod def update(self, ctx): try: if ctx.widget_type == idaapi.BWN_DISASM: return idaapi.AST_ENABLE_FOR_FORM else: return idaapi.AST_DISABLE_FOR_FORM except Exception as e: # Add exception for main menu on >= IDA 7.0 return idaapi.AST_ENABLE_ALWAYS # context menu for Patcher class Kp_MC_Patcher(Kp_Menu_Context): def activate(self, ctx): self.plugin.patcher() return 1 # context menu for Fill Range class Kp_MC_Fill_Range(Kp_Menu_Context): def activate(self, ctx): self.plugin.fill_range() return 1 # context menu for Undo class Kp_MC_Undo(Kp_Menu_Context): def activate(self, ctx): self.plugin.undo() return 1 # context menu for Search class Kp_MC_Search(Kp_Menu_Context): def activate(self, ctx): self.plugin.search() return 1 # context menu for Check Update class Kp_MC_Updater(Kp_Menu_Context): def activate(self, ctx): self.plugin.updater() return 1 # context menu for About class Kp_MC_About(Kp_Menu_Context): def activate(self, ctx): self.plugin.about() return 1except: pass# hooks for popup menuclass Hooks(idaapi.UI_Hooks): if idaapi.IDA_SDK_VERSION >= 700: # IDA >= 700 right click widget popup def finish_populating_widget_popup(self, form, popup): if idaapi.get_widget_type(form) == idaapi.BWN_DISASM: try: idaapi.attach_action_to_popup(form, popup, Kp_MC_Patcher.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_Fill_Range.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_Undo.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_Search.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_Updater.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_About.get_name(), 'Keypatch/') except: pass else: # IDA < 700 right click popup def finish_populating_tform_popup(self, form, popup): # We'll add our action to all "IDA View-*"s. # If we wanted to add it only to "IDA View-A", we could # also discriminate on the widget's title: # # if idaapi.get_tform_title(form) == "IDA View-A": # ... # if idaapi.get_tform_type(form) == idaapi.BWN_DISASM: try: idaapi.attach_action_to_popup(form, popup, Kp_MC_Patcher.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_Fill_Range.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_Undo.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_Search.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_Updater.get_name(), 'Keypatch/') idaapi.attach_action_to_popup(form, popup, Kp_MC_About.get_name(), 'Keypatch/') except: pass# check if we already initialized Keypatchkp_initialized = False# --------------------------------------------------------------------------# Plugin# --------------------------------------------------------------------------class Keypatch_Plugin_t(idaapi.plugin_t): comment = "Keypatch plugin for IDA Pro (using Keystone framework)" help = "Find more information on Keypatch at http://keystone-engine.org/keypatch" wanted_name = "Keypatch Patcher" wanted_hotkey = "Ctrl-Alt-K" flags = idaapi.PLUGIN_KEEP def load_configuration(self): # default self.opts = {} # load configuration from file try: f = open(KP_CFGFILE, "rt") self.opts = json.load(f) f.close() except IOError: print("Keypatch: FAILED to load config file. Use default setup now.") except Exception as e: print("Keypatch: FAILED to load config file, with exception: {0}".format(str(e))) # use default values if not defined in config file if 'c_opt_padding' not in self.opts: self.opts['c_opt_padding'] = 1 if 'c_opt_comment' not in self.opts: self.opts['c_opt_comment'] = 2 self.opts['c_opt_chk'] = self.opts['c_opt_padding'] | self.opts['c_opt_comment'] def init(self): global kp_initialized # register popup menu handlers try: Kp_MC_Patcher.register(self, "Patcher (Ctrl-Alt-K)") Kp_MC_Fill_Range.register(self, "Fill Range") Kp_MC_Undo.register(self, "Undo last patching") Kp_MC_Search.register(self, "Search") Kp_MC_Updater.register(self, "Check for update") Kp_MC_About.register(self, "About") except: pass # setup popup menu self.hooks = Hooks() self.hooks.hook() self.opts = None if kp_initialized == False: kp_initialized = True if idaapi.IDA_SDK_VERSION >= 700: # Add menu IDA >= 7.0 idaapi.attach_action_to_menu("Edit/Keypatch/Patcher", Kp_MC_Patcher.get_name(), idaapi.SETMENU_APP) idaapi.attach_action_to_menu("Edit/Keypatch/About", Kp_MC_About.get_name(), idaapi.SETMENU_APP) idaapi.attach_action_to_menu("Edit/Keypatch/Check for update", Kp_MC_Updater.get_name(), idaapi.SETMENU_APP) idaapi.attach_action_to_menu("Edit/Keypatch/Search", Kp_MC_Search.get_name(), idaapi.SETMENU_APP) idaapi.attach_action_to_menu("Edit/Keypatch/Undo last patching", Kp_MC_Undo.get_name(), idaapi.SETMENU_APP) idaapi.attach_action_to_menu("Edit/Keypatch/Fill Range", Kp_MC_Fill_Range.get_name(), idaapi.SETMENU_APP) else: # add Keypatch menu menu = idaapi.add_menu_item("Edit/Keypatch/", "Patcher (Ctrl-Alt-K)", "", 1, self.patcher, None) if menu is not None: idaapi.add_menu_item("Edit/Keypatch/", "About", "", 1, self.about, None) idaapi.add_menu_item("Edit/Keypatch/", "Check for update", "", 1, self.updater, None) idaapi.add_menu_item("Edit/Keypatch/", "-", "", 1, self.menu_null, None) idaapi.add_menu_item("Edit/Keypatch/", "Search", "", 1, self.search, None) idaapi.add_menu_item("Edit/Keypatch/", "-", "", 1, self.menu_null, None) idaapi.add_menu_item("Edit/Keypatch/", "Undo last patching", "", 1, self.undo, None) idaapi.add_menu_item("Edit/Keypatch/", "Fill Range", "", 1, self.fill_range, None) elif idaapi.IDA_SDK_VERSION < 680: # older IDAPython (such as in IDAPro 6.6) does add new submenu. # in this case, put Keypatch menu in menu Edit \ Patch program # not sure about v6.7, so to be safe we just check against v6.8 idaapi.add_menu_item("Edit/Patch program/", "-", "", 0, self.menu_null, None) idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: About", "", 0, self.about, None) idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Check for update", "", 0, self.updater, None) idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Search", "", 0, self.search, None) idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Undo last patching", "", 0, self.undo, None) idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Fill Range", "", 0, self.fill_range, None) idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Patcher (Ctrl-Alt-K)", "", 0, self.patcher, None) print("=" * 80) print("Keypatch v{0} (c) Nguyen Anh Quynh & Thanh Nguyen, 2018".format(VERSION)) print("Keypatch is using Keystone v{0}".format(keystone.__version__)) print("Keypatch Patcher's shortcut key is Ctrl-Alt-K") print("Use the same hotkey Ctrl-Alt-K to open 'Fill Range' window on a selected range of code") print("To revert (undo) the last patching, choose menu Edit | Keypatch | Undo last patching") print("Keypatch Search is available from menu Edit | Keypatch | Search") print("Find more information about Keypatch at http://keystone-engine.org/keypatch") self.load_configuration() print("=" * 80) self.kp_asm = Keypatch_Asm() return idaapi.PLUGIN_KEEP def term(self): if self.hooks is not None: self.hooks.unhook() self.hooks = None if self.opts is None: return # save configuration to file try: json.dump(self.opts, open(KP_CFGFILE, "wt")) except Exception as e: print("Keypatch: FAILED to save config file, with exception: {0}".format(str(e))) else: print("Keypatch: configuration is saved to {0}".format(KP_CFGFILE)) # null handler def menu_null(self): pass # handler for About menu def about(self): f = About_Form(VERSION) f.Execute() f.Free() # handler for Check-for-Update menu def updater(self): (r, content) = url_download(KP_GITHUB_VERSION) content = to_string(content) if r == 0: # find stable version sig = 'VERSION_STABLE = "' tmp = content[content.find(sig) + len(sig):] version_stable = tmp[:tmp.find('"')] # compare with the current version if version_stable == VERSION: f = Update_Form(VERSION, "Good, you are already on the latest stable version!") f.Execute() # free this form f.Free() else: f = Update_Form(VERSION, "Download latest stable version {0} from http://keystone-engine.org/keypatch".format( version_stable)) f.Execute() # free this form f.Free() else: # fail to download warning("ERROR: Keypatch failed to connect to internet (Github). Try again later.") print("Keypatch: FAILED to connect to Github to check for latest update. Try again later.") # handler for Undo menu def undo(self): global patch_info if len(patch_info) == 0: # TODO: disable Undo menu? warning("ERROR: Keypatch already got to the last undo patching!") else: (address, assembly, p_orig_data, patch_comment) = patch_info[-1] # undo the patch self.kp_asm.patch_code(address, None, None, None, None, orig_asm=[assembly], patch_data=p_orig_data, patch_comment=patch_comment, undo=True) del (patch_info[-1]) # handler for Search menu def search(self): address = get_screen_ea() f = Keypatch_Search(self.kp_asm, address) f.Execute() f.Free() # handler for Patcher menu def patcher(self): # be sure that this arch is supported by Keystone if self.kp_asm.arch is None: warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!") return selection, addr_begin, addr_end = read_range_selection() if selection: # call Fill Range function on this selected code return self.fill_range() address = get_screen_ea() if self.opts is None: self.load_configuration() init_assembly = None while True: f = Keypatch_Patcher(self.kp_asm, address, assembly=init_assembly, opts=self.opts) ok = f.Execute() if ok == 1: try: syntax = None if f.kp_asm.arch == KS_ARCH_X86: syntax_id = f.c_syntax.value syntax = self.kp_asm.get_syntax_by_idx(syntax_id) assembly = f.c_assembly.value self.opts = f.get_opts('c_opt_chk') padding = (self.opts.get("c_opt_padding", 0) != 0) comment = (self.opts.get("c_opt_comment", 0) != 0) raw_assembly = self.kp_asm.ida_resolve(assembly, address) print("Keypatch: attempting to modify \"{0}\" at 0x{1:X} to \"{2}\"".format( self.kp_asm.ida_get_disasm(address), address, assembly)) length = self.kp_asm.patch_code(address, raw_assembly, syntax, padding, comment, None) if length > 0: # update start address pointing to the next instruction init_assembly = None address += length else: init_assembly = f.c_assembly.value if length == 0: warning("ERROR: Keypatch found invalid assembly [{0}]".format(assembly)) elif length == -1: warning("ERROR: Keypatch failed to patch binary at 0x{0:X}!".format(address)) elif length == -2: warning("ERROR: Keypatch can't read original data at 0x{0:X}, try again".format(address)) except KsError as e: print("Keypatch Error: {0}".format(e)) else: # Cancel break f.Free() # handler for Fill Range menu def fill_range(self): # be sure that this arch is supported by Keystone if self.kp_asm.arch is None: warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!") return selection, addr_begin, addr_end = read_range_selection() if not selection: warning("ERROR: Keypatch requires a range to be selected for fill in, try again") return if self.opts is None: self.load_configuration() init_assembly = None f = Keypatch_FillRange(self.kp_asm, addr_begin, addr_end, assembly=init_assembly, opts=self.opts) ok = f.Execute() if ok == 1: try: syntax = None if f.kp_asm.arch == KS_ARCH_X86: syntax_id = f.c_syntax.value syntax = self.kp_asm.get_syntax_by_idx(syntax_id) assembly = f.c_assembly.value self.opts = f.get_opts('c_opt_chk') padding = (self.opts.get("c_opt_padding", 0) != 0) comment = (self.opts.get("c_opt_comment", 0) != 0) raw_assembly = self.kp_asm.ida_resolve(assembly, addr_begin) print("Keypatch: attempting to fill range [0x{0:X}:0x{1:X}] with \"{2}\"".format( addr_begin, addr_end - 1, assembly)) length = self.kp_asm.fill_code(addr_begin, addr_end, raw_assembly, syntax, padding, comment, None) if length == 0: warning("ERROR: Keypatch failed to process this input.") print("Keypatch: FAILED to process this input '{0}'".format(assembly)) elif length == -1: warning("ERROR: Keypatch failed to patch binary at 0x{0:X}!".format(addr_begin)) except KsError as e: print("Keypatch Error: {0}".format(e)) # free this form f.Free() def run(self, arg): self.patcher()# register IDA plugindef PLUGIN_ENTRY(): return Keypatch_Plugin_t()
IDA Pro MCP
首先,安装最新版本IDA Pro MCP包
pip uninstall ida-pro-mcppip install https://github.com/mrexodia/ida-pro-mcp/archive/refs/heads/main.zip
然后,配置MCP服务安装IDA插件
ida-pro-mcp --install
获取MCP配置
ida-pro-mcp --config
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:利刃信安 《【逆向分析】Hex.Rays.IDA.Pro.v9.3.251224-HEXRAYS(IDA 9.3 Beta 1)》