首页
社区
课程
招聘
[原创]从0到1实现一个IDA插件之TryCatch(x32)分析插件
发表于: 2024-10-14 18:02 7801

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

2024-10-14 18:02
7801

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

IDA功能拓展有两种方式:

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

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

对于可扩展模块开发:

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

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

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

hexrays负责x86指令集反编译

hexppc负责PowerPC指令集反编译

hexmips负责mips指令集反编译

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

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

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

IDA规定:

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

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

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

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

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

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

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

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

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

$(ProjectDir)idasdk77\include

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

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

针对 ida64.exe ,则添加 idasdk\lib\x64_win_vc_64

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

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

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

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

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

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

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

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

ida与插件交互的中介 对象

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

地址范围

Debugger->Run

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

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

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

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

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

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

模块类型 作用
处理器 增加对不同处理器架构的支持,也被称做IDP ( IDAProcessor)模块,负责解析硬编码生成反汇编。ida\procs\目录下。可应对虚拟壳。
插件 扩展IDA功能
文件加载器 增加对不同的可执行文件格式的支持,本质是一个导出loader_t结构的dll
调试器 在不同平台下,增强与其他调试器( 或远程调试)交互的调试能力。ida\dbg\dbg目录下,包括IDA原生调试器,也是该方式实现。
配置按行组织,每行指定一个插件的
插件名 、插件文件(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
配置按行组织,每行指定一个插件的
插件名 、插件文件(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主程序:
    通过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主程序:
    通过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 的函数名一致,但实现不同。
目录 内容
/ 不同平台下的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 各处理器架构解析源码
文件 内容
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 处理交叉参考引用代码,和数据参考引用的函数。
//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"             
};
//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"             
};
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会加载失败
}
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会加载失败
}
eg:
#ifdef __EA64__
  typedef uint64 ea_t;
...
#else
  typedef uint32 ea_t;
...
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 表示最大字符串长度
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 ""空串表示无快捷键
}
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 ""空串表示无快捷键
}
元数据信息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; // 继续此插件的加载
}
元数据信息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; // 继续此插件的加载
}
//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,等等)信息
//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,等等)信息
//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());
//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());
//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) ;
}
//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) ;
}
//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) ;
}
//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) ;
}
//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);
//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 用于描述 对应指令中的一个操作数信息,可以是一个特定的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,
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
因此,举个例子来说,对于[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);
因此,举个例子来说,对于[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);
即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;
即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;
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);
}
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);
}
对于反汇编文件的每一个字节,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
对于反汇编文件的每一个字节,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()
... ... ... ...
通过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()));
通过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()));
插件文件名 描述
win32_user.plw Windows 本机调试器
win32_stub.plw Windows 远程调试器
linux_user.plw Linux 本机调试器
linux_stub.plw Linux 远程调试器
//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 用于获取一些可用寄存器。
//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 用于获取一些可用寄存器。
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);
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);
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);
}
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命令。

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 4
支持
分享
最新回复 (11)
雪    币: 91
活跃值: (321)
能力值: ( 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;

}


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

这边运行一切正常

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


2024-10-15 08:57
0
雪    币: 91
活跃值: (321)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5






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

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

最后于 2024-10-15 10:15 被mb_wdzhnevo编辑 ,原因:
2024-10-15 10:15
0
雪    币: 308
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
mb_wdzhnevo 应该是我哪里没设置对,,有劳,,,https://pan.baidu.com/s/15tFI5LWaNRdjCho7SjiKpw?pwd=8f35&nbsp;
经检查,预处理器未定义相关宏,请参考插件项目配置详解。
2024-10-15 10:56
0
雪    币: 91
活跃值: (321)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
坏坏光 经检查,预处理器未定义相关宏,请参考插件项目配置详解。
还真是,谢谢啦
2024-10-15 17:06
0
雪    币: 91
活跃值: (321)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
坏坏光 经检查,预处理器未定义相关宏,请参考插件项目配置详解。
我也不知道怎么帮你推荐这帖,可能你需要充值一下雪币然后转正?
2024-10-16 17:33
0
雪    币: 119
活跃值: (621)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
牛逼
2024-10-16 17:44
0
雪    币: 8
活跃值: (647)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
感觉兄弟可以直接搞成一个插件
2024-10-18 10:11
0
雪    币: 381
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
谢大佬分享,小白学习学习
2024-10-23 14:37
0
雪    币: 1821
活跃值: (4035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
厉害,支持
2024-11-6 20:17
0
游客
登录 | 注册 方可回帖
返回
//