首页
社区
课程
招聘
[原创]从0到1实现一个IDA插件之TryCatch(x32)分析插件
发表于: 3天前 2815

[原创]从0到1实现一个IDA插件之TryCatch(x32)分析插件

3天前
2815

本文将从0到1教大家实现一个简单的IDA TryCatch(x32) 插件。
掌握IDA插件开发,可以实现一些自动化逆向工作,以提高我们的逆向效率。
效果图:

IDA插件

概述

IDA功能拓展有两种方式:

◆ 可扩展模块:运行速度快,调用方便,能实现更底层的功能。
C++开发的DLL
◆ 脚本:开发方便,不用重启IDA。
IDC或Python开发

​ IDC是IDA自带的脚本语言。本质由idaPython插件负责支持Python脚本。

对于可扩展模块开发:

IDA提供的SDK(库、头文件)支持我们二次开发四类可扩展模块,我们主要讲插件。

模块类型 作用
处理器 增加对不同处理器架构的支持,也被称做IDP ( IDAProcessor)模块,负责解析硬编码生成反汇编。ida\procs\目录下。可应对虚拟壳。
插件 扩展IDA功能
文件加载器 增加对不同的可执行文件格式的支持,本质是一个导出loader_t结构的dll
调试器 在不同平台下,增强与其他调试器( 或远程调试)交互的调试能力。ida\dbg\dbg目录下,包括IDA原生调试器,也是该方式实现。

eg:可以在ida反编译的基础上二次开发反编译插件:

反编译工具(F5)文件,以hex开头

hexrays负责x86指令集反编译

hexppc负责PowerPC指令集反编译

hexmips负责mips指令集反编译

插件的安装

复制到Plugins文件夹下。分析32位程序的ida.exe和分析64位程序的 ida64.exe都在这里找各自的插件。

插件的配置

ida\plugins\plugins.cfg对插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
配置按行组织,每行指定一个插件的
插件名 、插件文件(dll)名 、快捷键、参数、插件标签
menu_name  filename    hotkey  arg  flags
 
插件标签解释:表示啥类型的插件
       0:通常设为0
       DEBUG: Debugger plugin
       WIN: Enable plugin for MS Windows
       MAC: Enable plugin for Mac
       LINUX: Enable plugin for Linux
        
参数:允许给同一个插件编写多行配置项以传入不同的参数
eg:
menu_name1  xxx.dll    alt-1  1  0
menu_name2  xxx.dll    alt-2  65500  0

版本问题

IDA主程序、IDA_SDK&ida.lib、ida.dll、VS这几个个版本要一致,以保证其函数名称、数量及顺序一致。

1
2
3
4
5
6
7
8
9
10
IDA主程序:
    通过Detect it easy可以查看到其是MSVC编译器编译的,因此其生成的ida.dll有些语法细节 存在版本问题。
ida.dll:
    IDA目录下的,实现了SDK的内部功能
IDA.lib:
    即dll的基本使用,通过ida.lib定位到具体ida.dll里面的函数实现。
IDA_SDK:
    一堆头文件,其使用的C++语法不能用老的MSVC编译器,会不支持。
         
32/64位插件 SDK头文件一致,但所使用的lib不同。即分析32/64 PE 的函数名一致,但实现不同。

IDA规定:

分析32位程序的32位插件名为XXX.DLL

分析64位程序的64位插件名为XXX64.DLL

分析32、64程序的插件均为x64.DLL

SDK目录结构

SDK一般为IDA自带,如果没有可以去看雪工具区下载。

目录 内容
/ 不同平台下的makefile 编译配置文件,以及您应该首先阅读的readme.txt,特别是当版本有变化的时候。
include/ 以功能分类的头文件,重点
libbor.wXX/ 用于Borland C编译时,要用到的IDA库文件。
libgccXX.Inx/ Linux下的GCC编译时,要用到的IDA库文件。
libgcc.wXX/ Windows下,GCC编译时,要用到的IDA库文件。
libvc.wXX/ Windows下,Visual C++编译时,要用到的IDA库文件。
plugins/ 插件例子代码。
dbg/ ida调试器插件源码
ldr/ ida各平台可执行文件加载器的源码
module 各处理器架构解析源码

XX表示32位或者64位,这要看您所运行的平台架构。

头文件介绍

文件 内容
area.hpp area_t和aareacbb_t类,他们表示代码中的"域(areas)"
bytes. hpp 反汇编文件中,处理字节的函数和一些定义。
dbg.hpp & idd.hpp 调试器类和函数
diskio. hpp & fpro.h IDA自己的fopen(), open()等文件操作函数
entry. hpp 获取和操作执行文件的入口点(entry point)信息的相关函数。
frame.hpp 处理堆栈、函数帧、局部变量以及地址标志的函数
funcs.hpp funcs_t类和许多与函数相关的东西
ida.hpp idainfo结构,它包含被反汇编的文件的许多重要信息
kernwin.hpp 用于跟IDA界面进行交互的函数和类。
lines.hpp 相关的函数和定义,用来处理反汇编文本、代码着色,等等。
loader.hpp 加载或操作IDB文件的一些函数。
name.hpp 获取或者设置名称的函数和定义(例如局部变量,函数名,等等)。
pro.hpp 所有其他的函数的定义。
search. hpp 各种函数和定义,用来搜索反汇编文件中的文本,数据代码等等。
segment.hpp segment_t 类和所有处理二进制文件中的段(区块)的函数。
strlist.hpp string_ info_t 结构和用来获取IDA的字符串列表的一些函数。
ua.hpp insn_ t, op_ t和optype_ t类分别表示指令、操作数与操作数类型,以及与IDA分析器一同运作的函数。
xref.hpp 处理交叉参考引用代码,和数据参考引用的函数。

我们可以使用这些文件里带有前缀ida_export、ida_export_data 的函数或者全局变量,以及所有类和结构体、枚举。

其它的函数、变量是IDA自己内部使用的。

编写插件项目

剃刀法则

复制配置好的解决方案,编辑即可

如需编写x64ida的插件或调试版,更换项目配置即可。

插件项目配置详解

1、VS2019创建DLL项目,项目为\textcolor{Red}{X64}X64Dll

2、创建myIdaPlugin.cpp,复制测试代码。DllMain代码可以删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//myIdaPlugin.cpp
#include <ida.hpp>
#include <idp.hpp>
#include <loader.hpp>
#include <kernwin.hpp>
 
plugmod_t* idaapi init(void)
{
    return PLUGIN_OK;
}
 
void idaapi term(void)
{
    return;
}
 
bool idaapi run(size_t)
{
    warning("Hello, world!");
    return true;
}
 
static char comment[] = "It's a plugin to show Hello world!";
 
extern "C" plugin_t PLUGIN =
{
  IDP_INTERFACE_VERSION,
  0,                  
  init,                
  term,          
  run,                 
  comment,            
  "",
  "Hello, world",      
  "Alt-F1"             
};

3、C/C++ -> 常规 -> 附加包含目录:添加 IDA SDK include 目录

$(ProjectDir)idasdk77\include

4、C/C++ -> 预编译头 ->不使用预编译头,删除pch.h

4、C/C++ -> 常规 -> 预处理器:添加 __NT__ __IDP__

如果是x64插件,还需额外添加__EA64__

5、C/C++ -> 代码生成 -> 安全检查:修改为 禁用安全检查(/GS-)

6、C/C++->高级->调用约定:修改为__stdcall

7、链接器 -> 常规 -> 附加包含目录:

  • 针对 ida32.exe ,则添加 idasdk\lib\x64_win_vc_32

    • $(ProjectDir)idasdk77\lib\x64_win_vc_32
  • 针对 ida64.exe ,则添加 idasdk\lib\x64_win_vc_64

    • $(ProjectDir)idasdk77\lib\x64_win_vc_64

注:这里32、64指ida操作的程序为32位或64位。

ida32.exe依旧是x64程序,只是它处理的目标文件为32位程序。

如果目标文件是x64,则链接器->常规->输出文件改为 $(OutDir)My$(TargetName)64$(TargetExt)

x64插件dll名以64结尾(自己额外加个My前缀以便区分其它插件)

8、链接器 -> 输入 -> 附加依赖项:添加 ida.lib

9、编译生成,将生成dll拷至ida\plugins下,运行IDA,Edit->Plugins->MyPlugin即可

插件的调试

同DLL调试,VS ctrl alt p 附加到IDA.exe。

代码框架解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
IDA插件是通过PLUGIN类来实现的,唯一需要被导出的一个对象
//基本SDK头文件
#include <ida.hpp>
#include <idp.hpp>
#include <loader.hpp>
#include <kernwin.hpp>
int IDAP_init(void) //在IDA反汇编处理第一个文件时执行
{
    //初始化、版本环境检查
    //IDA在启动的时候会调用每个插件的init函数。
    //返回值有三种选项:
    //PLUGIN_OK
    IDA启动时仅尝试加载,要运行插件时重新加载,之后一直驻留内存。
    即执行一遍IDAP_init、IDAP_term,当点击插件时再执行IDAP_init、IDAP_run,不再卸载。
    适合那些执行一次性功能的插件
    //PLUGIN_KEEP
        IDA启动时加载插件后,一直处于内存中。
        即执行一遍IDAP_init,当点击插件时再执行IDAP_run
        适合那些需要一直保持功能的插件
    //PLUGIN_SKIP
        检查发现当前环境不支持该插件 运行及使用
        IDA将不会加载该插件
    return PLUGIN_OK;
}
void IDAP_term(void)
{
    //插件退出代码
    //当结束插件时,一般您可以在此添加一点任务清理的代码。
    return;
}
// arg:
// 源自plugins.cfg文件中,被传进的一个整型参数。
// 当按下不同的热键或者菜单时,您需要一个插件做不同的事情时,这非常有用。
bool IDAP_run(int arg) //热键或Edit->Plugins时执行
{
    //插件干活
    //当按下热键时候,执行功能的入口函数
    msg("Hello world!"); //消息栏显示字符串
    return PLUGIN_OK;
}
 
//*****插件设置*****
// 在Edit->Plugins 菜单中,插件的现实名称, 它能被用户的plugins.cfg文件改写
char IDAP_name[] = "My plugin";
// 启动插件的热键
char IDAP_hotkey[] = "Alt-X";
// 这些不太重要,但我还是设置了。
char IDAP_comment[] = "This is my test plug-in";
char IDAP_help[] = "My plugin";
// 所有PLUGIN对象导出的重要属性。
plugin_t PLUGIN = //IDA通过该导出对象 与 插件交互
{
    IDP_INTERFACE_VERSION, // IDA version plug-in is written for
    0, // Flags (see below) 参考 loader.hpp 一般为0
    IDAP_init, // Initialisation function
    IDAP_term, // Clean-up function
    IDAP_run, // Main plug-in body
    IDAP_comment, //插件的说明,会显示在IDA下方的状态栏中
    IDAP_help, // multiline help about the plugin
    IDAP_name, // 插件在列表中显示的名称 Edit->Plugins menu
    IDAP_hotkey /插件想要注册的功能快捷键
};
 
//注意点
run(self,arg)
{
    ref=idc.get_screen_ea()
    string =d.encode( 'utf-8')
    idc.MakeComm(ref,d);//makeComm添加注释需要转utf-8编码,py代码不要出现中文,可能ida会加载失败
}

数据类型

下面我会快速地介绍一些SDK中常用的数据结构概念,以供参考。
PS:更多细节以及函数细节在需要时请自行查阅IDA官方手册及阅读SDK源码。

pro.h 定义了一些数据类型和宏

x64项目要配置好宏__EA64__则对应数据类型变为64位,不再赘述。

1
2
3
4
5
6
7
eg:
#ifdef __EA64__
  typedef uint64 ea_t;
...
#else
  typedef uint32 ea_t;
...
类型 本质 描述
ea_t uint32 有效地址(Effective Address),表示IDA中很多不同类型的地址(如内存,文件, limits 等等)
sel_t uint32 段选择子,如 代码,栈和数据段选择子
uval_t uint32 被用来表示无符号值
asize_t uint32 通常用来表示某些东西的尺寸,例如一块内存。
sval_t int32 用来表示有符号值
adiff_t int32 表示两个地址间的不同处
BADADDR -1 表示一个无效或不存在的地址,用来检测
MAXSTR 1024 表示最大字符串长度

plugin_t

ida与插件交互的中介 对象

1
2
3
4
5
6
7
8
9
10
11
12
struct plugin_t
{
    int version;//插件适用IDA的版本,通常为IDP_INTERFACE_VERSION(SDK内部版本宏)
    int flags;//特性位图,各位表不同特性。参考SDK注释。eg:是否会修改idb内容、是否为调试插件等等
    void (idaapi* init)(void);//加载插件进内存时被调用,通常进行兼容性检查工作
    void (idaapi* term)(void);//从内存卸载插件时被调用,通常进行清理类工作
    void (idaapi* run)(int arg);//在IDA中运行插件时被调用,通常进行核心任务
    char* comment;//注释字符串,当鼠标移到插件名上,IDA状态栏中会显示该串。
    char* help;//帮助信息
    char* wanted_name;//插件名,但优先级低于plugins.cfg
    char* wanted_hotkey;//快捷键,但优先级低于plugins.cfg ""空串表示无快捷键
}

元数据信息

元数据信息用于检查插件环境,是一些文件、IDA环境的一些基本信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
元数据信息idainfo存储在 IDA 数据库中(即 IDB 文件)
元数据信息idainfo在第一个文件被 IDA 加载反汇编后,以后无论再多的文件被加载,元数据信息都不会改变。
struct idainfo
{
...
char procName[8]; // IDA所运行的架构芯片  (比如"metapc" = x86)
ushort filetype; // 被反汇编的文件类型. 参看 filetype_t 枚举 – 可以是 f_ELF,  f_PE, 等等.
ea_t startSP; // 程序开始运行时, [E]SP 寄存器的值
ea_t startIP; // 程序开始运行之初, [E]IP 寄存器的值
ea_t beginEA; // 程序入口点的线性地址,  一般和 startIP 相同
ea_t minEA; // 程序的最小线性地址
ea_t maxEA; // 程序的最大线性地址 不包含 maxEA
...
}inf; //inf即 idainfo 全局对象
 
如果您要编写的插件,仅处理x86平台下的PE和ELF二进制格式文件,则代码如下:
int IDAP_init(void)
{
    // "metapc" 表示 x86 平台
    if(strncmp(inf.procName, "metapc", 8) != 0 || inf.filetype != f_ELF && inf.filetype != f_PE))
    {
        error("Only PE and ELF binary type compiled for the x86 platform is supported, sorry.");
        return PLUGIN_SKIP; // 返回 PLUGIN_SKIP 意味着插件不会被载入
    }
    return PLUGIN_KEEP; // 继续此插件的加载
}

地址范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//area.hpp
“域”由很多area_t实例构成。
    域是一个连续的非空地址范围(由它的起始和结束地址指定,但不包括结束地址在内),还有地址范围的属性,也是
域的内容。
段和函数都是域的派生类,这就意味着,域可以包含另外的域。一组段就是一组域。
struct area_t
{
...
ea_t startEA; //起始地址
ea_t endEA; // 结束地址,但域不包括结束地址
bool contains(ea_t ea) const { return startEA <= ea && endEA > ea; } //判断一个域是否包括某一个地址
bool empty(void) const { return startEA >= endEA; } //某一个域是否为空
asize_t size(void) const { return endEA - startEA; } //返回一个域的尺寸
...
};
域的一些派生类:
struct func_t:public area_t //函数类
struct segment_t:public area_t //段类
struct hidden_area_t :public area_t(bytes. hpp)
   代码或数据被替换成的隐藏域,它以一个描述作为摘要,并能被展开查看隐藏的信息。
struct regvar_t:public area_t(frame. hpp)
    被用户自定义所替换的寄存器名称(寄存器变量)
struct memory_info_t:public area_t (idd. hpp)
    一块内存的相关信息(当使用调试器时)
struct segreg_t:public area_t(srarea. hpp)
    段寄存器(在x86平台是,CS,SS,等等)信息

域控制块

1
2
3
4
5
6
7
8
9
10
11
12
13
//area.hpp
struct areacb_t
{   ...提供了一些控制域的通用方法,但并不常用。通常采用域派生类自带的方法
    iget_area_qty()
    get_next_area()
}
有两个areacb_t类的全局实例,名为segs(segment.hpp中定义)和funcs(funcs.hpp中定义),
    很明显,在当前的反汇编文件中,它们表示所有的段和函数。
     
您可以使用下面的代码获取段和函数的数量:
#include <segment.hpp>
#include <funcs.hpp>
msg("Segments: %d, Functions: %d\n",segs.get_area_qty(),funcs.get_area_qty());

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//segment.hpp
class segment_t : public area_t
{
  public:
    uchar perm;//段访问权限(0表示没有内容)。可以是下面的宏的组合。
        #define SEGPERM_EXEC 1//可执行
        #define SEGPERM_WRITE 2//可写
        #define SEGPERM_READ 4//可读
    uchar type;// 段的类型。 可以是下面的一个值。
        #define SEG_NORM 0//未知类型,没有使用
        #define SEG_XTRN 1//定义为"extern"的段,仅被允许包含非指令的内容
        #define SEG_CODE 2//代码段 
        #define SEG_DATA 3//数据段
        #define SEG_NULL 7//零长度的段
        #define SEG_BSS 9//未初始化的段
    ...
}
SEG_XTRN是一个特殊的(即非实际存在内容)段类型,由IDA建立,但是其他的类型,表示实际存在部分。
    对于一个被IDA载入的执行文件,例如.text区块的类型值为SEG_CODE,
    则perm成员的值为SEGPERM_EXEC|SEGPERM_READ。
在一个二进制文件中,要遍历所有的段,并在IDA的日志窗口中,打印段名和地址,您可以使用下面的代码。
#include <segment.hpp>
//此代码只能在IDA 4.8中正常运行,因为get_segm_name ()在4.9中已经改变
//欲知详情,请阅读第五章
// get_segm_qty() 返回加载文件中,段的总数量
for (int s = 0; s < get_segm_qty(); s++)
{
    // getnseg() 返回一个对应于提供的段序号的segment_t结构
    segment_t *curSeg = getnseg(s);
     
    // get_segm_name() 返回段名称
    // msg()把信息打印到IDA的日志窗口
    msg("%s @ %a\n",get_segm_name (curSeg),curSeg->startEA) ;
}

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//funcs.hpp
func_t
    先理解下函数块(chunk),函数源( parents),以及函数尾(tail)的概念。
它们都是func_t类型,通过func_t.flags区分。只是由于编译器优化可能把一个函数代码体打散了的不同称呼。
class func_t : public area_t
{
    public:
        ...
    ushort flags;//表示函数类型的标志
        //下面是一些常用的标志:
        #define FUNC_NORET 0x00000001L//函数并不返回
        #define FUNC_LIB 0x00000004L //库函数
        #define FUNC_HIDDEN 0x00000040L//一段隐藏函数块
        #define FUNC_THUNK 0x00000080L//块(jump)函数
        #define FUNC_TAIL 0x00008000L// 一段函数尾
        //其他标志都好理解(除了FUNC_HIDDEN)
    union // func_t要么表示整个函数块,要么表示一块函数尾
    {
           struct //一个函数的整个块的属性
           {
                asize_ t argsize;//返回之前,堆栈中要清除的字节数量
                ushort pntqty;//整个函数过程中,ESP寄存器被改变的次数 (与PUSH等指令相关)
                int tailqty; //该函数自身含有的函数尾数量
                area_t *tails;//函数尾的数组,以ea排序
           }
 
            struct//函数尾的属性
            {
                ea_t owner; //拥有该函数尾的main函数的地址
            }
   ...
}
     
eg代码:
    列出了所有函数名和它们在反汇编文件中的地址,并在IDA的日志窗口中显示结果。
#include <funcs.hpp>
// get_func_qty() 返回加载的文件中,函数的总数量。
for (int f = 0; f < get_func_qty(); f++)
{
    //getn_func()返回由函数序号指定的func_t结构
    func_t *curFunc = getn_func(f) ;
    char funcName [MAXSTR] ;
    // get_func_name ()获取函数名,并存储到funcName
    get_func_name (curFunc->startEA,funcName,sizeof (funcName)-1) ;
    msg ("%s: \t %a\n",funcName, curFunc->startEA) ;
}

代码表示

insn_t指令结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//ua.hpp
从insn_t结构研究,其表示一整条指令,如"MOV EAX, 0x0A"
class insn_t
{
    public:
    ea_t cs;  // 代码段基址(in paragraphs)
    ea_t ip;   // 段中的偏移
    ea_t ea;   // 指令起始地址
    ushort itype;   // 助记符ID
    op_t ops[8]; //表示指令中的所有操作数。没有操作数的指令(PUSHA,CDQ)ops[0]=o_void
    ushort size;  //  指令大小(字节)
    #define Op1 Operands[0] // 第一个操作数
    #define Op2 Operands[1] // 第二个操作数
    #define Op3 Operands[2] // ...
    #define Op4 Operands[3]
    #define Op5 Operands[4]
    #define Op6 Operands[5]
};
insn_t cmd;//全局变量由ua_ana0()和 ua_code()函数填充。
代码eg:
获取入口点的指令序号,地址和大小, 并在IDA的日志窗口显示出来。
// ua_ana0()函数在指定的地址,填充cmd结构
ua_ana0(inf.beginEA); // or inf.startIP
msg("instruction number: %d, at %a is %d bytes in size.\n", cmd.itype, cmd.ea, cmd.size);

op_t操作数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
op_t 用于描述 对应指令中的一个操作数信息,可以是一个特定的optype_t值(比如,通用寄存器,立即数,等等)。
class op_t
{
public:
char n;   // 操作数序号或位置,(比如0,1,2)
optype_t type;  // 操作数类型 (请看上一节的描述)
ushort reg;  // 寄存器序号 (如果操作数类型是o_reg)
uval_t value;  // 操作数的具体数值 (如果操作数类型是o_imm)
ea_t addr;   // 指向操作数或被其使用的虚拟地址 (如果操作数类型是o_mem)
...
}
optype_t  本质是typedef uchar optype_t; 表示一条指令中的操作数类型,有一些全局常量(宏)表示如下。
const optype_t
  o_void     =  0,
  o_reg      =  1,
  o_mem      =  2,
操作数 描述 反汇编举例(操作数以 粗体标出)
o_void 不含操作数 pusha
o_reg 通用寄存器 dec eax
o_mem 直接内存引用 mov eax, ds:1001h
o_phrase 间接内存引用[基址寄存器+偏移寄存器] push dword ptr [eax]
o_displ 间接偏移内存引用[基址寄存器+偏移寄存 器+偏移量] push [esp+8]
o_imm 立即数 add ebx, 10h
o_near 立即近地址 call _initterm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
因此,举个例子来说,对于[esp+8]这样的操作数op_t结构有:
    op_t.type =o_displ
    op_t.reg=4//正是ESP寄存器的序号
    op_t.addr =8
代码eg:
获取IDA光标所处指令的第一个操作数的op_t值:
    #include <kernwin.hpp>
    #include <ua.hpp>
// 反汇编当前光标所在位置的指令,
// 并使其存储到‘cmd’全局结构中。
ua_ana0(get_screen_ea());
// 显示第一个操作数的相关信息
msg("n = %d type = %d reg = %d value = %a addr = %a\n",
cmd.Operands[0].n,
cmd.Operands[0].type,
cmd.Operands[0].reg,
cmd.Operands[0].value,
cmd.Operands[0].addr);

insn_t.itype助记符、操作码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
即PUSH,MOV等
insn_t{
    uint16 itype
}
Ps:
ua_mnem()可将itype转为对应助记符文本。instruc_t(allins.hpp)枚举保存了所有的助记符由NN打头
enum
{
NN_null = 0,         
NN_aaa,                
NN_aad,                
NN_aam,            
     
如果您知道您要寻找或测试的指令,您可以使用它,而不是使用文本表示的指令。
比如,测试二进制文件中,某条指令的助记符是否为PUSH,您可以这么做:
#include <ua.hpp>
#include <allins.hpp>
// 在入口点填充‘cmd’结构
ua_ana0(inf.startIP);
// 测试这条指令是否为PUSH指令
if (cmd.itype == NN_push)
msg("First instruction is a PUSH");
else
msg("First instruction isn't a PUSH");
return;

交叉引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
IDA通过B-tree结构来存储这些交叉引用参考的信息,并通过 xrefblk_t 结构来访问。
当一条指令后续又有一条指令,IDA会潜在地看作第一条指令引用第二条指令,可以用 xrefblk_t 的成员方法来关掉。
//xref.hpp
 首先需要使用first_from()或first_to()成员函数来填充xrefblk_t
(用哪个取决于引用到‘reference to’或引用于‘reference from’)
    然后遍历引用的时候,就用next_from()或next_to()成员函数来填充。
 
struct xrefblk_t
{
    ea_t from;   // 被引用地址(referencing address)
    ea_t to;   // 引用的地址(referenced address)
    uchar iscode;  // 1表示代码参考引用,0表示数据参考引用
    uchar type;  // cref_t 或者dref_t 类型中之一
    ...
};
如果xrefblk_t 的iscode 成员变量为1的话,那么type成员变量将会是下面列出来的枚举值。
enum cref_t
{
...
fl_CF = 16,  // 远调用(Call Far) 该xref在引用的地方创建一个函数
 
fl_CN,    //近调用(Call Near) 该xref在引用的地方创建一个函数
fl_JF,    // 远跳转(Call Far)
fl_JN,    // 近跳转(Call Near)
fl_F,    // 选择跳转:用来表示下一条指令的执行流程。
...
};
 
//近跳转引用类型  示例:
.text:712D9BF6 jz short loc_712D9BFE
...
.text:712D9BFE loc_712D9BFE:
.text:712D9BFE lea ecx, [ebp+var_14]
     
如果xrefblk_t 的iscode 成员变量为0的话,说明它是一个数据参考引用。
    那么type成员变量将会是下面列出来的枚举值。
enum dref_t
{
...
dr_O,   // 参考是一个偏移(Offset) 参考引用使用数据的‘偏移’而不是它的值
        或者
        // 参考引用的出现是因为指令的“OFFSET”标志被设置。这时,就意味着此类型基于IDP(IDA Processor)。
dr_W,   // 写访问(Write acess)
dr_R,   // 读访问(Read acess)
...
};
  
请记住,当您看见如下代码时,您实际上看到的是数据引用,因此712D9BD9在引用712C119C:
.idata:712C119C extrn wsprintfA:dword //数据区
...
.text:712D9BD9 call ds:wsprintfA
这种情况下,xrefblk_t的type成员将是典型的dr_R值,因为是简单地读取了ds:wsprintfA这一行的地址。
     
另外一种数据参考引用如下,在712EABE2处的PUSH指令引用了位于712C255C的一串字符。
.text:712C255C aVersion:
.text:712C255C unicode 0, <Version>,0
...
.text:712EABE2 push offset aVersion
这种情况,xrefblk_t的type成员变量将会是dr_O,因为它以偏移访问该数据。
     
代码eg:
显示当前光标所在的位置,相关的交叉参考引用信息:
#include <kernwin.hpp>
#include <xref.hpp>
xrefblk_t xb;
// 获取当前光标所在地址
ea_t addr = get_screen_ea();
// 循环遍历所有交叉引用
for (bool res = xb.first_to(addr, XREF_FAR); res; res = xb.next_to()) {
    msg("From: %a, To: %a\n", xb.from, xb.to);
    msg("Type: %d, IsCode: %d\n", xb.type, xb.iscode);
}

字节标志

1
2
3
4
5
6
7
8
9
10
11
对于反汇编文件的每一个字节,IDA录入了一个相应的四字节(32位)值Flag,并保存在id1文件中。
这些个四字节中,前3字节描述该字节信息。
.text:010060FA 0x55 push ebp 对应一个四字节信息:0x00010755
    同理:
.text:010011DE 0x83 0xEC 0x14   sub esp, 14h
    0x010011DE:0x83      Flag:41010783
    0x010011DF:0xEC      Flag:001003EC
    0x010011E0:0x14      Flag:00100314
         
关于标志值的含义:
// bytes.hpp
标志名 标志值 含义 封装函数
FF_CODE 0x00000600L 该字节是代码吗? isCode()
FF_DATA 0x00000400L 该字节是数据吗? isData()
FF_TAIL 0x00000200L 该字节是一条指令的一部分(非指令头)吗? isTail()
FF_UNK 0x00000000L IDA 能分辨该字节吗? isUnknown()
FF_COMM 0x00000800L 该字节被注释了吗? has_cmt()
FF_REF 0x00001000L 该字节被别的地方引用吗? hasRef()
FF_NAME 0x00004000L 该字节有名称吗? has_name()
FF_FLOW 0x00010000L 上条指令是否流过这里? isFlow()
... ... ... ...
1
2
3
4
5
6
7
8
9
10
11
12
通过getFlags(ea_t ea)获取标志,并通过一些函数检测字节的信息。
函数底层即与运算:
Ps:
0x00010755 & 0x00000600 (FF_CODE) = 0x00000600. 由此,我们知道这是一条指令。
0x00010755 & 0x00000800 (FF_COMM) = 0x00000000. 我们知道这没有被注释。
     
代码eg:
返回光标所在行地址的标志。
#include <bytes.hpp>
     
    msg("Chick Add:%p\n", get_screen_ea());
    msg("Flags:    %08x\n\n", get_flags(get_screen_ea()));

调试器

Debugger->Run

IDA 插件可以与IDA的调试器交互。(虽然IDA调试模块也行)

ida自带的调试器插件:(IDA plugins目录中)

插件文件名 描述
win32_user.plw Windows 本机调试器
win32_stub.plw Windows 远程调试器
linux_user.plw Linux 本机调试器
linux_stub.plw Linux 远程调试器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//idd.hpp
debugger_t 结构,导出了一个dbg 指针,表示当前激活的调试器插件。当调试器模块被加载时,该指针就是有效的了。
struct debugger_t
{
    ...
    char *name; // 类似‘win32’或‘linux’的调试器短名称
    #define DEBUGGER_ID_X86_IA32_WIN32_USER 0 // win32用户态进程
    #define DEBUGGER_ID_X86_IA32_LINUX_USER 1 // linux用户态进程
    register_info_t *registers; // 寄存器数组
    int registers_size; // 寄存器个数
    ...
}
作为插件模块,可能您会需要访问name指针成员变量,来测试您的插件与哪个调试器交互。
registers 和 registers_size 用于获取一些可用寄存器。

寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
IDA SDK 里,寄存器以register_info_t 结构来描述,保存寄存器的值由regval_t结构表示。
//idd.hpp
struct register_info_t
{
const char *name;  // 寄存器全名(EBX,等)
ulong flags; // 寄存器特性
    // 可以是下面值的组合
    #define REGISTER_READONLY 0x0001  // 用户不能修改该寄存器的当前值
    #define REGISTER_IP 0x0002    // 指令指针
    #define REGISTER_SP 0x0004    // 栈顶指针
    #define REGISTER_FP 0x0008    // 栈帧指针
    #define REGISTER_ADDRESS 0x0010  // 寄存器可以包含地址
    ...
}
这个结构的唯一实例,可以用*dbg(SDK导出的一个debugger_t实例)的数组成员*register 来访问。
要获取寄存器的值,最起码调试器得运行起来。
通过regval_t.ival、regval_t.fval(浮点数)获取寄存器的值。
//idd.hpp
struct regval_t
    ulonglong ival;     // 整数值
    ushort    fval[6];  // 浮点数值 ,表示方法参看 ieee.h头文件
};
 ival/fval将直接对应于一个寄存器里存储的东西,
     因此,如果EBX寄存器为0xDEADBEEF,ival成员(一旦用get_reg_val()函数填充后),同样将是 0xDEADBEEF。
  
 下面的例子将循环遍历所有有效寄存器,并显示每个寄存器的值。但如果您没祭起调试器,而运行这段代码,那么值将会是0xFFFFFFFF:
  
#include <dbg.hpp>
// 循环遍历所有寄存器
for (int i = 0; i < dbg->registers_size; i++) {
    regval_t val;
    // 获取寄存器中存储的值
    get_reg_val((dbg->registers+i)->name, &val);
    msg("%s: %08a\n", (dbg->registers+i)->name, val.ival);

断点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
IDA用bpt_t来表示不同的硬件和软件断点。
//dbg.hpp
struct bpt_t
{
  // 只读属性:
  ea_t ea;                 // 断点的起始地址
  asize_t size;            // 断点的尺寸 ,若是软件断点,则是未定义的
  bpttype_t type;          // 断点的类型:
// 摘自idd.hpp中bpttype_t常量定义:
// BPT_EXEC  =  0,             // 可执行的指令
// BPT_WRITE =  1,             // 可写
// BPT_RDWR  =  3,             // 可读写
// BPT_SOFT  =  4;             // 软件断点
    //0、1、3是硬件断点,但是4表示软件断点。
   
    // 可修改属性 (使用update_bpt()函数修改):
  int pass_count;           // 执行流达到此断点消耗的时间 (如果未定义则为-1)
  int flags;
#define BPT_BRK   0x01      // 调试器停在这个断点吗?
#define BPT_TRACE 0x02      // 当到达这个断点时,调试器添加了跟踪信息吗?
  char condition[MAXSTR]; //一个IDC表达式,它将被用来表示一个中断条件,
                        //或者当这个断点被激活时,要执行的IDC命令。
};
有许多的函数可以创建,操作,读取该结构,
但目前,我给出一个很简单的例子,它遍历所有的断点,
    并且在IDA的Log窗口显示到底是一个软件或硬件断点。
    这些函数将在以后详细解释。
     
#include <dbg.hpp>
for (int i = 0; i < get_bpt_qty(); i++) { // get_bpt_qty() 获取断点数目
    bpt_t brkpnt; // getn_bpt 基于给定的断点数目,并用断点信息填充bpt_t结构
    getn_bpt(i, &brkpnt);
     if (brkpnt.type == BPT_SOFT)   // BPT_SOFT 就表示软件断点
         msg("Software breakpoint found at %a\n", brkpnt.ea);
     else
         msg("Hardware breakpoint found at %a\n", brkpnt.ea);
}

跟踪

1
2
3
4
5
6
7
8
9
IDA 中,有三种类型的跟踪可供您打开;
    函数跟踪,指令跟踪和断点(又被称作可读/可写/可执行)跟踪。
    编写插件时,还有另一个形式的跟踪是有用的:单步跟踪。
    单步跟踪是跟踪的一个底层形式,允许您在该形式之上建立自己的跟踪机制,再利用事件通知 驱动您的插件,
    这样,每一条指令可以被单步执行。这是基于CPU的跟踪能力,而非用断点。
跟踪时,一个“跟踪事件(trace evnet)”就会产生,并保存到一个缓冲区,
而且,您允许的跟踪类型,决定了引发产生什么样的跟踪事件。
    但是,单步跟踪不产生跟踪事件,这样就有些麻烦,但也可以用事件通知 替代。
下面的表格列出了所有不同的跟踪事件类型,和在 dbg.hpp 中,对应的tev_type_t 枚举值定义。
跟踪类型 事件类型 (tev_type_t) 描述
函数调用和返回 tev_call 和 tev_ret 函数已经被调用或者已经返回
指令 tev_insn 指令已经被执行(在IDA内核中,这建立于单步跟踪之上)
断点 tev_bpt 设置的断点被激活。也被称作可读/可写/可执行 跟踪
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
所有跟踪事件都被保存到一个循环缓冲区,所以它不会填满,
但如果是这个缓冲区太小,那么原来的跟踪事件可能会被覆盖掉。
每个跟踪事件被表示为tev_info_t 结构,它被定义在dbg.hpp头文件中。
struct tev_info_t
{
  tev_type_t  type; // 跟踪事件类型(上述表格中之一,或者tev_none)
  thread_id_t tid;  // 被记录的事件所在线程。
  ea_t     ea;   // 发生事件的地址。  
};
  
一个断点跟踪和普通的断点差不多,不过断点跟踪在flags成员中有一个BPT_TRACE的标志。
还有一点,condition缓冲区成员可以有一个IDC命令,在断点被激活时,就可以执行IDC命令。
跟踪信息在进程运行时被填充,但仍然能在进程刚终止的时候被访问,
而且可以是您返回到静态反汇编模式的时候(除非在退出的时候,您使用的插件已经明显地清除了那块缓冲区)。
您可以使用如下代码,枚举所有跟踪事件(供您在执行调试的时候枚举):
#include <dbg.hpp>
//遍历所有跟踪事件
for (int i = 0; i < get_tev_qty(); i++) {
    regval_t esp;
    tev_info_t tev;
    // 获取跟踪事件信息
    get_tev_info(i, &tev);
    switch (tev.type) {
           case tev_ret:
                msg("Function return at %a\n", tev.ea);
                break;
           case tev_call:
                msg("Function called at %a\n", tev.ea);
                break;
           case tev_insn:
                msg("Instruction executed at %a\n", tev.ea);
                break;
           case tev_bpt:
                msg("Breakpoint with tracing hit at %a\n", tev.ea);
                 break;
           default:
                  msg("Unknown trace type..\n");
    }
}
目前,无需讨论的是,给插件增加入口是不必要的,甚至是修改跟踪事件日志。
所有这些函数将在《第五章-函数》中详细讨论。

进程和线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
IDA 给运行在调试器中的进程和线程,保存了一些它们的信息。
进程和线程ID 分别用process_id_t 和thread_id_t 类型标识,这两个类型都是有符号整数型。
所有这些类型在 idd.hpp 中定义。还有另一个关于进程的类型,即process_info_t 类型,
    如下:
struct process_info_t
{
  process_id_t pid;    // 进程 ID
  char name[MAXSTR];   // 进程名称 (执行文件名)
};
它们只有当二进制文件在IDA下面被调试执行的时候,才有用(比方说,您不能在静态反汇编模式下使用它们)。
下面的例子演示了process_info_t结构的使用方法。
     
#include <dbg.hpp>
// 获取调试中的有效进程的数量
// get_process_qty() 也可以初始化 IDA 的“进程快照(process sanpshot)”
if (get_process_qty() > 0) {
    process_info_t pif;
    get_process_info(0, &pif);
    msg("ID: %d, Name: %s\n", pif.pid, pif.name);
} else {
    msg("No process running!\n");
}
使用这些结构的函数将在《第五章-函数》中讨论。

事件通知

1
2
3
4
5
6
一般来说,插件是同步运行的,即由用户来决定执行,要么通过按下热键,要么通过Edit->Plugins菜单。
    但是,插件也可以异步运行,就是响应IDA或者用户产生的事件通知来达到这个目的。
在IDA下工作的过程中,您可能经常点击按钮,执行搜索,等等。所有这些动作都是“事件(events)”,
    所以,IDA做的事情就是当这些动作发生时,产生“事件通知(event notifications)”。
    如果您的插件被配置成接收这些通知,您就可以编程实现做一些动作。
    打个比方,这样的程序可以保存一些宏,然后插件就能产生事件,驱使IDA执行各种功能。

接收通知

1
2
3
4
插件要用hook_to_notification_point()注册事件响应回调,用callui()产生事件通知,
unhook_from_notification_point()卸载回调
//loader.hpp
事件类型hook_type_t
类型 从模块中接收事件通知 事件通知类型的枚举
HT_IDP 处理器模块 idp_notify(不会讨论)
HT_UI IDA 用户界面 ui_notification_t
HT_DBG 正在运行的IDA调试器 dbg_notification_t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
eg:接收所有调试器的事件通知
IDAP_init()
{
    //第三个参数一般为NULL,仅在收到通知时,需要传递数据给回调时才指定。
    hook_to_notification_point(HT_DBG, dbg_callback, NULL);
}
 
hook_to_notification_point(HT_???, mycallback, NULL);
回调格式
    //user_data即hook_to_notification_point第三个参数
    //notif_code 是接收到的事件标志
    //va 是IDA封装好的事件相关数据
int idaapi mycallback (void *user_data, int notif_code, va_list va)
{
    ...
    return 0;
    //返回0:不处理,由其他人处理
    //返回非0值:该回调即最终的处理者
}
IDAP_term()
{
    unhook_from_notification_point(HT_DBG, dbg_callback, NULL);
}

UI事件通知

1
2
3
4
5
6
7
8
9
10
11
//kernwin.hpp
enum ui_notification_t
{
    //IDA或者插件产生的用户界面事件通知 类型
}
下面的两个列表给出了一些常用的 可以由插件接收或产生的事件通知。
  
尽管这些通知可以由插件调用 callui()来产生,但还有一些助手函数(helper fuction)也有同样的功能,这意味着您无需使用 callui(),而调用助手函数也可以达到相同目的。
事件通知 描述 助手函数
ui_jumpto 移动光标到某地址 jumpto
ui_screenea 返回光标位置的当前地址 get_screen_ea
ui_refresh 刷新所有反汇编界面 refresh_idaview_anyway
ui_mbox 给用户显示一个消息框 vwarning, vinfo等等
ui_msg 在IDA的日志窗口显示一些文本 deb, vmsg
ui_askyn 显示有Yes 和No 选项的消息框 askbuttons_cv
ui_askfile 提示用户输入文件名 askfile_cv
ui_askstr 提示用户输入一个单行 的字符串 vaskstr
ui_asktext 提示用户输入一些文本 vasktext
ui_form 显示一个表格(很灵活) AskUsingForm_cv
ui_open_url 在web浏览器中打开一个 指定的URL open_url
ui_load_plugin 加载插件 load_plugin
ui_run_plugin 运行插件 run_plugin
ui_get_hwnd 获取IDA窗口的HWND(窗 口句柄) 暂无
ui_get_curline 获取着色的反汇编信息 get_curline
ui_get_cursor 获取当前光标位置的横纵坐标 get_cursor

下面的事件通知由插件接收,并交付给您的回调函数处理。

事件通知 描述
ui_saving 和 ui_saved 分别表示,IDA正在保存或者已经保存完数据库
ui_term IDA 已经关闭数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
比如,下面的代码将产生一个ui_screenea事件通知,并用ui_mbox事件通知在IDA 对话框中显示结果。
void IDAP_run(int arg)
{
    ea_t addr;
    va_list va;
    char buf[MAXSTR];
 
    callui(ui_screenea, &addr); // 获取当前光标的虚拟地址,并保存到addr变量
    qsnprintf(buf, sizeof(buf)-1, "Currently at: %a\n", addr);
 
    callui(ui_mbox, mbox_inf, buf, va); // 在消息框中显示相关信息
    return;
}
上面这种情况,您一般应该使用助手函数,这里使用cullui()只不过是演示的目的。

字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
可以用SDK来访问IDA中的字符串窗口,
字符串信息由string_info_t结构表示
//strlist.hpp
struct string_info_t
{
ea_t ea;   // 字符串的地址
int length;  // 字符串长度
int type;   // 字符串类型 (0=C语言, 1=Pascal, 2=Pascal 2 字节  3=Unicode, etc.)
...
};
string_info_t,需要使用get_bytes()或者 get_many_bytes()来从二进制文件中获取字符串。
     
代码eg:
// 遍历所有字符串
for (int i = 0; i < get_strlist_qty(); i++) {
    char string[MAXSTR];
    string_info_t si;
    // 获取字符串项目
    get_strlist_item(i, &si);
    if (si.length < sizeof(string)) {
        // 从二进制文件中得到字符串
        get_many_bytes(si.ea, string, si.length);
        if (si.type == 0) // C 字符串
            msg("String %d: %s\n", i, string);
        if (si.type == 3) // Unicode
            msg("String %d: %S\n", i, string);
    }
}

TryCatch(x32)分析插件

有了前面的知识,编写一个TryCatch(x32)分析插件就很简单了。前提是你需要了解TryCatch的底层结构,x64TryCatch及其它静态分析自动化都可以用类似流程解决。
TryCatch的实现可以参考MSDN以及ehdata.h相关文件
这里给出结构图

插件源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#include <ida.hpp>
#include <idp.hpp>
#include <loader.hpp>
#include <kernwin.hpp>
#include <bytes.hpp>
#include <funcs.hpp>
#include <ua.hpp>
#include <name.hpp>
#include <allins.hpp>//NN_push
#include <ehdata.h>//TryCatch相关结构体
#include <typeinfo>//rtti
 
#include <iostream>//正则
#include <string>
#include <regex>
#include "Rtti.cpp"
 
char comment[] = "TryCatch辅助分析插件";
char help[] = u8"暂无帮助信息";
char wanted_name[] = u8"TryCatch分析插件";
char wanted_hotkey[] = "Alt-1";
 
 
ea_t 光标地址;
Rtti沟通* RttiAnaObj;
char 读缓冲[0x1000];
 
 
/*
*
输入
.rdata:005326D4 D8 26 53 00                   dd offset __CT??_R0H@8                  ; catchable type 'int'
 
输出
int
*/
std::string 提取分析IDA_Rtti注释(const std::string& input) {
    // 定义正则表达式模式
    std::regex pattern(R"(\bcatchable type '([^']+)')");
 
    // 匹配字符串
    std::smatch match;
    if (std::regex_search(input, match, pattern)) {
        // 提取匹配的类型名称
        return match[1].str();
    }
 
    // 如果没有匹配,返回空字符串
    return "";
}
plugmod_t* idaapi init(void) {
    
    RttiAnaObj = new Rtti沟通();
    return PLUGIN_KEEP;
}
 
/*
注意:
是否为ThrowInfo()及 是否为_s_FuncInfo()
识别判定做的很粗糙,仅保证正确点击ThrowInfo和_s_FuncInfo时,可以正常运行,乱点崩溃不管。
*/
bool 是否为ThrowInfo(ea_t& OUT pThrowInfo)//通过push + offset判定
{
    qstring disasm;
    if (!generate_disasm_line(&disasm, 光标地址, 0))// 获取该地址的反汇编指令
        throw (u8"无法获取反汇编指令\n");
    if (disasm.find("offset") == qstring::npos)// 判断反汇编指令是否包含 "offset"
        return 0;
    insn_t insn;// 解析当前地址的指令
    if (!decode_insn(&insn, 光标地址))
        throw(u8"无法解析当前地址的指令\n");
    op_t op = insn.ops[0];// 获取指令的操作数
    if (op.type != o_imm) // 检查操作数是否是立即数
    return 0;
 
    pThrowInfo = op.value;// 立即数的值即 pThrowInfo
    return  insn.itype == NN_push?1:0;
};
bool 是否为_s_FuncInfo(ea_t& OUT p_s_FuncInfo)//通过mov  + reg,mem判定
{
    qstring disasm;
    if (!generate_disasm_line(&disasm, 光标地址, 0))// 获取该地址的反汇编指令
        throw (u8"无法获取反汇编指令\n");
  
    insn_t insn;// 解析当前地址的指令
    if (!decode_insn(&insn, 光标地址))
        throw(u8"无法解析当前地址的指令\n");
 
    if (insn.itype == NN_mov && insn.Op1.type == o_reg  &&insn.Op2.type == o_imm) {
        p_s_FuncInfo = insn.Op2.value;
        return 1;
    }
    return  0;
};
 
bool 解析ThrowInfo(ea_t& IN pThrowInfo)
{
    //获取_s_ThrowInfo结构
    _s_ThrowInfo* p_s_ThrowInfo = new _s_ThrowInfo;
    get_bytes(p_s_ThrowInfo, sizeof(_s_ThrowInfo), pThrowInfo);
  
    int nCount;//pCatchableTypeArray包含变长数组
    get_bytes(&nCount, 4, (ea_t)p_s_ThrowInfo->pCatchableTypeArray);
 
    for (int i = 0; i < nCount; i++)
    {
        jumpto(p_s_ThrowInfo->pCatchableTypeArray + 4);
        std::string str(get_curline());
        msg(提取分析IDA_Rtti注释(str).c_str());
        jumpto(光标地址);
 
    };
    return 1;
};
bool 解析_s_FuncInfo(ea_t& IN pFuncInfo)
{
    _s_FuncInfo* p_s_FuncInfo = new _s_FuncInfo;
    get_bytes(p_s_FuncInfo, sizeof(_s_FuncInfo), pFuncInfo);
 
    int try数量 = p_s_FuncInfo->nTryBlocks;
    msg(u8"try数量:%d\n", try数量);
    msg(u8"最大try等级:%d\n", p_s_FuncInfo->maxState);
    //msg(u8"try 处理地址:%a", p_s_FuncInfo->dispTryBlockMap);
 
    int nSize = try数量 * sizeof(_s_TryBlockMapEntry);
    _s_TryBlockMapEntry* arrTBME = (_s_TryBlockMapEntry*)malloc(nSize);
    get_bytes(arrTBME, nSize, p_s_FuncInfo->dispTryBlockMap);
 
    for (int i = 0; i < try数量; i++)//遍历TryBlockMapEntry数组
    {
        msg("try%d{", i);
        int catch数量 = arrTBME[i].nCatches;
        msg("tryLow: %d,try high:%d ,catch high:%d\n}\n", arrTBME[i].tryLow, arrTBME[i].tryHigh, arrTBME[i].catchHigh);
        //msg(u8"catch描述块地址:%a\n", arrTBME[i].dispHandlerArray);
        int nSize2 = catch数量 * 0x10;//sizeof(_s_HandlerType32)=0x10 但这里是x64程序头文件大小不对
        _s_HandlerType* arrHT = (_s_HandlerType*)malloc(nSize2);
        get_bytes(arrHT, nSize2, arrTBME[i].dispHandlerArray);
        for (int j = 0; j < catch数量; j++)
        {
            _s_HandlerType* p_s_HandlerType = ((_s_HandlerType*)((char*)arrHT + 0x10 * j));
            
            if (p_s_HandlerType->dispType)
            {
                void* ptype_info = malloc(sizeof(std::type_info));
                get_bytes(ptype_info, sizeof(std::type_info), (ea_t)p_s_HandlerType->dispType);
 
                RttiAnaObj->提交请求(ptype_info, sizeof(std::type_info), 读缓冲);
                //printf("%s\n", 读缓冲);
            }
            else {
                strcpy_s(读缓冲,100,"...");
            }
             
            msg("catch%d(%s){\nCodeAdd:%a\n}\n",j, 读缓冲, p_s_HandlerType->dispOfHandler);
        };
    }
    return 1;
};
bool idaapi run(size_t) {
    光标地址 = get_screen_ea();// 获取当前光标所处行的地址
    try {
        ea_t pData;
        if (是否为ThrowInfo(pData))
        {
            解析ThrowInfo(pData);
        }
        else if (是否为_s_FuncInfo(pData))
        {
            解析_s_FuncInfo(pData);
        }
        else
        {
            msg("false");
        };
    }
    catch (const char* str)
    {
        msg(str);
        return 0;
    };
    return PLUGIN_KEEP;
}
 
 
void idaapi term(void) {
  
    delete RttiAnaObj;
}
 
plugin_t PLUGIN = {
    IDP_INTERFACE_VERSION,
    PLUGIN_UNL,            
    init,                  
    term,                  
    run,                   
    comment,               
    help,                  
    wanted_name,           
    wanted_hotkey          
};

关于RTTI

值得指出的是,在TryCatch机制底层需要处理Rtti类型
在TryCatch源码中,简单提供了分析Rtti类型的几种解决方法:
◆ 正则处理ida自动分析的Rtti结果
◆ 利用Windows的<typeinfo>自带的机制分析
◆ 自行解析Rtti机制(暂未研究)
考虑我们需要在x64位插件中调用x32的<typeinfo>机制,这里采用进程通讯处理,(亦可采用天堂之门,核心业务不变。)参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
对于一个Rtti数据,可直接采用API转换为其类型name
unsigned char datas[128] = {//class Apple的type_info信息
0x90, 0xEE, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x3F, 0x41, 0x56, 0x41, 0x70, 0x70, 0x6C,
0x65, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xEE, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2E, 0x3F, 0x41, 0x56, 0x62, 0x61, 0x64, 0x5F, 0x61, 0x6C, 0x6C, 0x6F, 0x63, 0x40, 0x73, 0x74,
0x64, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xEE, 0x50, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2E, 0x3F, 0x41, 0x56, 0x62, 0x61, 0x64, 0x5F, 0x65, 0x78, 0x63, 0x65,
0x70, 0x74, 0x69, 0x6F, 0x6E, 0x40, 0x73, 0x74, 0x64, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x90, 0xEE, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x3F, 0x41, 0x56,
0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x40, 0x73, 0x74, 0x64, 0x40, 0x40, 0x00
};
//基本方法
const std::type_info* pxxx = (const std::type_info*)datas;
const char* c_stxxxr=pxxx->name();
 
 
//底层方法,逆向发现底层是这个API,还原难度有点大,科学放弃法。
HINSTANCE   hModule = LoadLibraryA("VCRUNTIME140.dll");
typedef DWORD(__stdcall* pFunc)(void*, void*);
pFunc func1 = (pFunc)GetProcAddress(hModule, "__std_type_info_name");
DWORD x = 0;
char* p = (char*)datas + 4;
const char* c_str = (const char*)func1(p, &x);
printf("%s", c_str);
 
//RTTI_32服务子进程
#include <iostream>
#include <Windows.h>
using namespace std;
 
#include <typeinfo>
 
char 读缓冲[0x1000];
DWORD WINAPI 线程函数(LPVOID 参数)//负责监听并退出该Rtti服务程序
{
    HANDLE g_hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, L"Stop_RTTI_Server32");
    WaitForSingleObject(g_hEvent, INFINITE);
    exit(0);
    return 0;  
}
void 子进程() {
 
    HANDLE hRead = GetStdHandle(STD_INPUT_HANDLE);
    HANDLE hWrite = GetStdHandle(STD_OUTPUT_HANDLE);
 
    DWORD dw实际读写 = 0;
    DWORD dw当前管道数据量 = 0;
    BOOL bRet = 0;
    HANDLE g_hE子进程写完 = CreateEvent(NULL, FALSE, FALSE, L"SonWriteOver");
    HANDLE g_hE父进程写完 = OpenEvent(EVENT_ALL_ACCESS, FALSE, L"DadWriteOver");
 
    while (1)
    {
        WaitForSingleObject(g_hE父进程写完, INFINITE);
        bRet = PeekNamedPipe(hRead, NULL, 0, NULL, &dw当前管道数据量, NULL);
        if (bRet)
        {
            bRet = ReadFile(hRead, 读缓冲, dw当前管道数据量, &dw实际读写, NULL);
            //解析数据
            const std::type_info* data = (const std::type_info*)读缓冲;
            const char* cstr_RTTI类型 = data->name();
            int size = strlen(cstr_RTTI类型) + 1;
            //写回
            bRet = WriteFile(hWrite, cstr_RTTI类型, size, &dw实际读写, NULL);
            SetEvent(g_hE子进程写完);
        }
    }
};
int main(int argc) {
    HANDLE 线程句柄 = ::CreateThread(NULL, 0, 线程函数, NULL, 0, NULL);
    ::CloseHandle(线程句柄);
    子进程();
    return 0;
}
 
 
 
//父进程 不要求 32、64位,但传递给子进程的数据是32位的
 
#include <iostream>
#include <Windows.h>
using namespace std;
 
#include <typeinfo>
#define 子进程路径 R"(C:\RootFolder\05_VS_Projects\IDAPlugin\MyIdaPlugin\Rtti32Server.exe)"
unsigned char datas[128] = {
0x90, 0xEE, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x3F, 0x41, 0x56, 0x41, 0x70, 0x70, 0x6C,
0x65, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xEE, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2E, 0x3F, 0x41, 0x56, 0x62, 0x61, 0x64, 0x5F, 0x61, 0x6C, 0x6C, 0x6F, 0x63, 0x40, 0x73, 0x74,
0x64, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xEE, 0x50, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2E, 0x3F, 0x41, 0x56, 0x62, 0x61, 0x64, 0x5F, 0x65, 0x78, 0x63, 0x65,
0x70, 0x74, 0x69, 0x6F, 0x6E, 0x40, 0x73, 0x74, 0x64, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x90, 0xEE, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x3F, 0x41, 0x56,
0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x40, 0x73, 0x74, 0x64, 0x40, 0x40, 0x00
};
 
char 读缓冲[0x1000];
 
class Rtti沟通
{
public:
    Rtti沟通()
    {
        //控制子进程退出
         g_hEStop = CreateEvent(NULL, FALSE, FALSE, L"Stop_RTTI_Server32");
        //进程通讯
         子读 = NULL;
         父写 = NULL;
         父读 = NULL;
         子写 = NULL;
         g_hE父进程写完 = CreateEvent(NULL, FALSE, FALSE, L"DadWriteOver");
 
        SECURITY_ATTRIBUTES sa;
        sa.nLength = sizeof(sa);
        sa.lpSecurityDescriptor = NULL;
        sa.bInheritHandle = TRUE;
 
        bRet = CreatePipe(&子读, &父写, &sa, 0);
        bRet = CreatePipe(&父读, &子写, &sa, 0);
 
        //创建子进程,利用si的三个句柄传递
        STARTUPINFO si = { 0 };
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESTDHANDLES;//表示需要使用句柄
        si.hStdInput = 子读;//设置子进程的输入设备为管道
        si.hStdOutput = 子写;
        //si.hStdError = 子写;//错误也可以用同一管道
        PROCESS_INFORMATION pi = {};
        TCHAR sz程序路径名[] = TEXT(子进程路径);
        BOOL res = CreateProcess(NULL, sz程序路径名, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
 
        //Sleep(1000);//保证子进程初始化再开始通讯
    };
    void 提交请求(LPCVOID Rtti结构地址,DWORD Rtti结构大小,OUT LPVOID 解析字符串存放地址)
    {
        //写管道
        DWORD dw实际读写 = 0;
        bRet = WriteFile(父写, Rtti结构地址, Rtti结构大小, &dw实际读写, NULL);
        SetEvent(g_hE父进程写完);
 
        //读
        static HANDLE g_hE子进程写完 = OpenEvent(EVENT_ALL_ACCESS, FALSE, L"SonWriteOver");
        WaitForSingleObject(g_hE子进程写完, INFINITE);
        DWORD dw当前管道数据量 = 0;
        bRet = PeekNamedPipe(父读, NULL, 0, NULL, &dw当前管道数据量, NULL);
        if (bRet)
        {
            bRet = ReadFile(父读, 解析字符串存放地址, dw当前管道数据量, &dw实际读写, NULL);
        }
    };
    ~Rtti沟通() {
        //控制Rtti32服务程序 结束
        SetEvent(g_hEStop);
    }
private:
    HANDLE g_hEStop;
     
    //进程通讯
    HANDLE 子读 = NULL;
    HANDLE 父写 = NULL;
    HANDLE 父读 = NULL;
    HANDLE 子写 = NULL;
    HANDLE g_hE父进程写完;
    BOOL bRet;
};
void 父进程()
{
    Rtti沟通* obj = new Rtti沟通();
    Sleep(1000);
    obj->提交请求(datas,sizeof(datas), 读缓冲);
    printf("%s\n", 读缓冲);
    obj->提交请求(datas,sizeof(datas), (char*)读缓冲+100);
    printf("%s\n", (char*)读缓冲 + 100);
}
int main(int argc) {
    父进程();
    return 0;
}

文卒,创作不易,共同进步!


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 3
支持
分享
最新回复 (9)
雪    币: 10
活跃值: (131)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2

我前些天试了下写个插件打印调试程序的段名起始和结束地址

总是不正确不知为什么

#include "pch.h"

#include <Windows.h>

#include <ida.hpp>

#include <idp.hpp>

#include <loader.hpp>

#include <kernwin.hpp>


// 插件的初始化函数,返回 plugmod_t* 类型

plugmod_t* idaapi init() {

    msg("Plugin initialized and will stay in memory.\n");

    return PLUGIN_KEEP;  // 告诉 IDA 插件保持加载

}


void idaapi term() {

    msg("Plugin terminated.\n");

}


void get_all_comments() {

    for (int s = 0; s < get_segm_qty();s++) //get_segm_qty() 获取段数量

    {


        qstring segmName;


        segment_t* curSeg = getnseg(s); //遍历获取段信息


        get_segm_name(&segmName, curSeg);    //获取段名,不能直接使用curSeg->name,ida会崩溃


        msg("%s @ %llx     %llx\n", segmName.c_str(), curSeg->start_ea,curSeg->end_ea);   //打印输出段名和起始地址

    }

}




// 插件运行函数

bool idaapi run(size_t arg) {

    msg("Plugin is running!\n");

get_all_comments();


    return true;

}


// 插件描述

plugin_t PLUGIN = {

    IDP_INTERFACE_VERSION,

    0,          // 标志位

    init,       // 插件初始化函数

    term,       // 插件终止函数

    run,        // 插件运行函数

    "My Plugin",  // 插件名称

    "This is a test plugin for IDA",  // 插件帮助

    "My Plugin",  // 插件注释

    "Ctrl-Alt-M"  // 快捷键

};

// DLLMain 函数

BOOL APIENTRY DllMain(HMODULE hModule,

    DWORD  ul_reason_for_call,

    LPVOID lpReserved)

{

    switch (ul_reason_for_call)

    {

    case DLL_PROCESS_ATTACH:

    case DLL_THREAD_ATTACH:

    case DLL_THREAD_DETACH:

    case DLL_PROCESS_DETACH:

        break;

    }

    return TRUE;

}


3天前
0
雪    币: 45
活跃值: (2410)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
mark
3天前
0
雪    币: 78
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
mb_wdzhnevo 我前些天试了下写个插件打印调试程序的段名起始和结束地址总是不正确不知为什么#include &quot;pch.h&quot;#include &lt;Windows.h&am ...

这边运行一切正常

请考虑项目配置(x64dll)或提供完整问题细节。


3天前
0
雪    币: 10
活跃值: (131)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5






应该是我哪里没设置对,,有劳,,,

https://pan.baidu.com/s/15tFI5LWaNRdjCho7SjiKpw?pwd=8f35 

最后于 3天前 被mb_wdzhnevo编辑 ,原因:
3天前
0
雪    币: 78
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
mb_wdzhnevo 应该是我哪里没设置对,,有劳,,,https://pan.baidu.com/s/15tFI5LWaNRdjCho7SjiKpw?pwd=8f35&nbsp;
经检查,预处理器未定义相关宏,请参考插件项目配置详解。
3天前
0
雪    币: 10
活跃值: (131)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
坏坏光 经检查,预处理器未定义相关宏,请参考插件项目配置详解。
还真是,谢谢啦
2天前
0
雪    币: 10
活跃值: (131)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
坏坏光 经检查,预处理器未定义相关宏,请参考插件项目配置详解。
我也不知道怎么帮你推荐这帖,可能你需要充值一下雪币然后转正?
1天前
0
雪    币: 76
活跃值: (491)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
牛逼
1天前
0
雪    币: 8
活跃值: (592)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
感觉兄弟可以直接搞成一个插件
3小时前
0
游客
登录 | 注册 方可回帖
返回
//