更新时间:2008-07-28, 21:57

翻译这篇文档时,kanxue版主和arhat组长给予了很多指导和帮助,
非常感谢kanxue兄、arhat兄、绿盟的四哥、安焦的funnywei兄、
killer兄,长期以来的热心指导和帮助,衷心感谢你们。
也十分感谢作者Steve Micallef给我们分享他的编程经验。
还要感谢父母、老师多年来的教养,同学朋友们的帮助,看雪论坛
的同道朋友的顶贴支持,谢谢你们。
 
完成的翻译文档,分别做成了pdf和doc两种格式,因为论坛附件
大小的限制原因,所以给doc文件压缩成了rar文件。而为了方便查看
文档,给pdf加了每一章的标签,制作pdf是用的Adobe Acrobat 7.0。
希望大家都能正常查看文档,而没有兼容性的问题。
 
希望大家能在论坛内,提出宝贵意见和批评,谢谢……

C/C++编写IDA插件

[1.0]

 

 

版权® 2005 Steve Micallef

steve@binarypool.com

 

内容目录

1. 入门........................................................6

1.1 为什么会有这本手册?.......................................6

1.2 涵盖的内容................................................6

1.3 不包括的内容..............................................6

1.4 前置知识..................................................6

1.5 居家旅行必备良药..........................................6

1.6 C/C++之外的选择...........................................7

1.7 关于这本手册..............................................7

1.8 致谢......................................................7

1.9 其他资料..................................................7

2. IDA SDK全局组织.............................................8

2.1 安装方法..................................................9

2.2 目录结构..................................................9

2.3 头文件介绍................................................9

2.4 使用SDK..................................................10

3. 配置一个编译环境............................................11

3.1 Windows下使用Visual Studio.............................. 11

3.2 Windows下使用GCCDev-C++MinGW.......................12

3.3 Linux下使用GCC........................................... 12

3.4 一份插件模板............................................. 13

3.5 配置及运行插件........................................... 14

4.IDA插件原理.................................................15

  4.1重要的数据类型............................................15

4.2 核心结构以及类........................................... 16

4.2.1 元数据信息(Meta Information) ........................ 16

4.2.2 域的概念............................................. 17

      4.2.2.1 area_t结构....................................... 17

      4.2.2.2 areacb_t....................................... 18

4.2.3 段和函数............................................... 18

      4.2.3.1 ............................................... 18

      4.2.3.2 函数............................................. 19

  4.2.4 代码的表示............................................. 20

      4.2.4.1 操作数类型....................................... 21

      4.2.4.2 操作数........................................... 21

      4.2.4.3 助记符........................................... 22

      4.2.4.4 指令............................................. 22

  4.2.5 交叉引用参考........................................... 23

      4.2.5.1 xrefblk_t结构....................................23

      4.2.5.2 代码............................................. 24

      4.2.5.3 数据............................................. 25

4.3 字节标志.................................................26

4.4 调试器...................................................27

4.4.1 debugger_t 结构......................................27

4.4.2 寄存器...............................................28

4.4.3 断点.................................................29

4.4.4 跟踪.................................................30

4.4.5 进程和线程...........................................32

4.5 事件通知.................................................32

    4.5.1 接收通知.............................................33

    4.5.2 UI事件通知.......................................... 34

    4.5.3 调试器事件通知.......................................35

      4.5.3.1 底层型事件.......................................35

      4.5.3.2 高层型事件通知...................................37

      4.5.3.3 函数返回型通知...................................37

  4.6 字符串...................................................38

5.函数........................................................40

  5.1 常用函数的替代...........................................40

  5.2 消息框...................................................40

5.2.1 msg.................................................. 41

5.2.2 info................................................. 41

5.2.3 warning.............................................. 41

5.2.3 error................................................ 41

5.3 UI浏览...................................................41

5.3.1 get_screen_ea........................................ 41

5.3.2 jumpto............................................... 42

5.3.3 get_cursor........................................... 42

5.3.4 get_curline.......................................... 42

5.3.5 read_selection....................................... 42

5.3.6 callui............................................... 43

5.3.7 askaddr............................................. .43

5.3.8 AskUsingForm_c....................................... 44

5.4 入口点...................................................44

5.4.1 get_entry_qty........................................ 44

5.4.2 get_entry_ordinal.................................... 44

5.4.3 get_entry............................................45

5.4.4 get_entry_name.......................................45

5.5 .......................................................45

5.5.1 get_area............................................. 46

5.5.2 get_area_qty......................................... 46

5.5.3 getn_area............................................ 46

5.5.4 get_next_area........................................ 47

5.5.5 get_prev_area........................................ 47

5.6 ...................................................... 48

5.6.1 get_segm_qty........................................ 48

5.6.2 getnseg............................................. 48

5.6.3 get_segm_by_name.................................... 48

5.6.4 getseg.............................................. 49

5.6.5 get_segm_name(IDA 4.8) ............................. 49

5.6.6 get_segm_name(IDA 4.9) ............................. 50

5.7 函数.....................................................50

5.7.1 get_func_qty........................................ 50

5.7.2 get_func............................................ 50

5.7.3 getn_func........................................... 51

5.7.4 get_func_name....................................... 51

5.7.5 get_next_func....................................... 52

5.7.6 get_prev_func....................................... 52

5.7.7 get_func_comment.................................... 52

5.8 指令.....................................................53

5.8.1 generate_disasm_line................................ 53

5.8.2 ua_ana0............................................. 53

5.8.3 ua_code............................................. 54

5.8.4 ua_outop............................................ 54

5.8.5 ua_mnem............................................. 55

5.9 交叉引用.................................................56

5.9.1 first_from.......................................... 56

5.9.2 first_to............................................ 57

5.9.3 next_from........................................... 57

5.9.4 next_to............................................. 58

5.10 名称....................................................58

5.10.1 get_name........................................... 58

5.10.2 get_name_ea........................................ 59

5.10.3 get_name_value..................................... 59

5.11 搜索....................................................60

5.11.1 find_text(仅支持IDA 4.9) ...........................60

5.11.2 find_binary........................................ 61

5.12 IDB.....................................................62

5.12.1 open_linput........................................ 62

5.12.2 close_linput....................................... 62

5.12.3 load_loader_module................................. 62

5.12.4 load_binary_file................................... 63

5.12.5 gen_file........................................... 64

5.12.6 save_database...................................... 65

5.13 标志....................................................65

5.13.1 getFlags........................................... 65

5.13.2 isEnabled.......................................... 65

5.13.3 isHead............................................. 66

5.13.4 isCode............................................. 66

5.13.5 isData............................................. 67

5.13.6 isUnknown..........................................67

5.14 数据................................................... 68

5.14.1 get_byte...........................................68

5.14.2 get_many_bytes.....................................68

5.14.3 patch_byte.........................................69

5.14.4 patch_many_bytes...................................69

5.15 I/O.................................................... 70

5.15.1 fopenWT............................................70

5.15.2 openR..............................................70

5.15.3 ecreate............................................70

5.15.4 eclose.............................................70

5.15.5 eread..............................................71

5.15.6 ewrite.............................................71

5.16 调试函数............................................... 72

5.16.0 请求(Request)中的注意事项........................72

5.16.1 run_requests.......................................72

5.16.2 get_process_state..................................72

5.16.3 get_process_qty....................................73

5.16.4 get_process_info...................................73

5.16.5 start_process *....................................74

5.16.6 continue_process*..................................74

5.16.7 suspend_process*...................................74

5.16.8 attach_process*....................................74

5.16.9 detach_process*....................................75

5.16.10 exit_process*.....................................75

5.16.11 get_thread_qty....................................76

5.16.12 get_reg_val.......................................76

5.16.13 set_reg_val*......................................76

5.16.14 invalidate_dbgmem_contents........................77

5.16.15 invalidate_dbgmem_config..........................77

5.16.16 run_to *..........................................78

5.16.17 step_into*........................................78

5.16.18 step_over*........................................78

5.16.19 step_until_ret*...................................78

5.17 断点................................................... 79

5.17.1 get_bpt_qty........................................79

5.17.2 getn_bpt...........................................79

5.17.3 get_bpt............................................80

5.17.4 add_bpt*...........................................80

5.17.5 del_bpt*...........................................80

5.17.6 update_bpt.........................................81

5.17.7 enable_bpt*........................................81

5.18 跟踪................................................... 82

5.18.1 set_trace_size.....................................82

5.18.2 clear_trace*......................................82

5.18.3 is_step_trace_enabled.............................82

5.18.4 enable_step_trace*................................82

5.18.5 is_insn_trace_enabled.............................83

5.18.6 enable_insn_trace*................................83

5.18.7 is_func_trace_enalbed.............................83

5.18.8 enable_func_trace*................................83

5.18.9 get_tev_qty.......................................84

5.18.10 get_tev_info.....................................84

5.18.11 get_insn_tev_reg_val.............................84

5.18.12 get_insn_tev_reg_result..........................85

5.18.13 get_call_tev_callee..............................85

5.18.14 get_ret_tev_return...............................86

5.18.15 get_bpt_tev_ea...................................86

5.19 字符串................................................ 87

5.19.1 refresh_strlist...................................87

5.19.2 get_strlist_qty...................................87

5.19.3 get_strlist_item..................................87

5.20 其它.................................................. 88

5.20.1 tag_remove........................................88

5.20.2 open_url..........................................88

5.20.3 call_system.......................................89

5.20.4 idadir............................................89

5.20.5 getdspace.........................................89

5.20.6 str2ea............................................89

5.20.7 ea2str............................................90

5.20.8 get_nice_colored_name.............................90

6. 示例.....................................................91

6.1 搜索sprintf,strcpy,sscanf的调用....................91

6.2 输出含有MOVS指令的函数...............................93

6.3 自动加载动态链接库到IDA数据库........................95

6.4 断点设置器,记录器...................................97

6.5 可选式跟踪(方法一) .................................100

6.6 可选式跟踪(方法二)................................101

6.7 二进制代码拷贝&粘贴.................................103

 

 

 

 

 

 

 

 

 

第一章 入门

1.1 为什么会有这本手册?

花了大量时间在IDA SDK中,来阅读那些头文件,以及学习别人的插件源代码后,我觉得应该有一个更简单的方法来开始IDA插件编写。尽管这些头文件中的注释十分翔实,但我发现这样浏览和搜索这些注释有点困难,因为我需要它们时,并不想通过大劳动量的搜索。我想我该写这样一本手册,来帮助那些希望开始学习插件开发的朋友。因此,我决定用一个篇章来介绍如何配置开发环境,让您能更快速地入门。

1.2 涵盖的内容

这本手册将引导您开始编写IDA插件,首先将介绍SDK,然后是介绍在多个平台下,配置插件开发环境。您将得到如何使用各种类和结构的经验,接下来是介绍一些作用广泛的SDK导出函数。最后,我将介绍如何使用IDA API来完成基本的任务,例如,用循环来分析函数,钩挂调试器和操作IDA数据库文件(IDB文件)。当您读完后,您应该能运用自己的知识来编写您自己的插件,希望您能通过社区把您的插件公布出来。

1.3 不包括的内容

尽管IDA的标准版和高级版支持许多其他的平台,但我主要关注于x86平台,因为我在这平台上面有最多的经验。因此,如果您需要全面掌握所有的IDA函数,我建议您去看看其他的那些头文件。

这本手册主要介绍的是“只读(read only)”函数,而不大介绍其他的函数,如添加注释,错误校验,定义结构等等函数。SDK资料中的种类很庞大,不介绍这些函数,是想让手册体积适中。

我开始想介绍netnodes的概念,可是因为IDA SDK的结构和类的成员很复杂,而且还有很多特殊原因,您知道的,手册不会包含一切。如果您确实需要这些知识,请您写信告诉我,可能会在下一个手册版本中来介绍这些,如果没别的特殊原因的话。

1.4 前置知识

首先最重要的是,您应该掌握如何使用IDA,这样您就能够舒服地浏览反汇编代码,以及配置调试器。还有,您应该准备C/C++语言的知识,最好还有x86汇编语言。在这里,C++是非常重要的,因为SDK有相当多的C++代码。如果您对C++不熟悉,但很精通C,您应该至少理解OOP的概念,如类,对象,方法以及继承。

1.5 居家旅行必备良药

编写、运行IDA插件,您需要IDA pro反汇编器4.8版或4.9版,还有IDA SDK(您可以从 http://www.datarescue.com处获得,但需要IDA授权许可),以及一个C/C++编译器,象Visual Studio,GCC平台,Borland系列,或其他的。

请注意,4.9版中所做的一些改变,已经被写进手册了。而且,对于4.9版,SDK是稳定的,4.9版的一些函数将不会再改变,也就是说,给4.9版写的插件(通常是二进制形式)也可以在以后的版本中正常工作。

1.6 C/C++之外的选择

如果您对C也不感冒,那么可以看看IDAPython,它是一个函数集,用高级语言Python封装了所有C++ API。要获取更多详细资料,请去 http://d-home.net/idapython。还有一份使用IDApython的手册在 http://dkbza.org/idapython_intro.html,里面有很多详尽的介绍,作者是Ero Carrera。

还有一份介绍使用VB6和C#编写IDA插件的文章,请登陆:

http://www.openrce.org/articles/full_view/13

1.7 关于这本手册

如果您有任何问题、建议或您发现一些错误,请您告诉我,Steve Micallef,邮箱是 steve@binarypool.com。如果您真的从手册中读到对您有帮助的内容,我仍然会写信感谢您,这么做是非常值得的。

因为SDK会不断“长胖”,所以,这本手册也会适时的升级。您将从这里 http://www.binarypool.com/idapluginwriting/ 处获得最新版本的手册拷贝。

1.8 致谢

我必须感谢下面列出的朋友,他们对本手册提供了,审校、鼓励以及反馈,下列牛人排名不分顺序:

Iifak Guilfanov,Pierre Vandevenne,Eric Landuyt,Vitaly Osipov,Sccott Madison,Andrew Griffiths,Thorsten Schneider和Pedram Amini。

1.9 其他资料

在手册的编写过程中,参考了一份关于IDA插件的文档,该文档介绍了如何使用4.9版的通用脱壳插件,其中包括如何编写这种插件,以及如何运行插件。它可以在:                  

http://www.datarescue.com/idabase/unpack_pe/unpacking.pdf 处被找到。如果您对编写插件很积极,您可以去Dataresuce的论坛去寻求帮助(http://www.dataresuce.com/cig-local/ultimatebb.cgi),当没有官方支持的时候,您可以向Datarescue的人(或者IDA老手)求助,他们会乐意帮助您。

另一个非常棒的地方是 http://www.openrce.org, 在那儿,您将不仅仅找到很多逆向工程方面的好文章,还有工具,插件以及文档。那儿还有牛人在论坛里,他们将尽可能帮您解决IDA或者一般的逆向工程问题。

 

 

第二章 IDA SDK全局组织

IDA是一款非常好的反汇编器,而且最近还发布了一个调试器。IDA单独实现了很多了不起的功能,可是有时候,您可能需要实现一些IDA并没提供的功能,比如自动化或者做一些特殊的任务。另人欣慰的是,发布IDA SDK的Dataresuce公司的那些哥们儿,提供了一些接口,供您自己扩展IDA的功能。

下面是您能够用IDA SDK编写的四种类型模块,插件模块将是本手册的主题:

模块类型

作用

处理器

增加对不同处理器架构的支持,也被称做IDPIDA Processor)模块。

插件

扩展IDA功能。

文件加载器

增加对不同的可执行文件格式的支持。

调试器

 

在不同平台下,增强与其他调试器(或远程调试)交互的调试能力。

 

上面的“插件(plug-in)”术语将代替“插件模块(plug-in module)”,除非有特别说明。

IDA SDK包含了您需要编写IDA插件的所有头文件和库文件。还支持Linux和Win平台下很多的编译器,而且还给出了一个插件例子的源代码,用来示范一些简单的功能。

不管您是一位逆向工程师、漏洞研究员、病毒分析员,或是身兼以上数职,SDK都提供了一个格外强大灵活的平台。您差不多都能用它编写您自己的调试器/反汇编器,这当然是另人满意的。下面保守地列出了一些您能够用SDK做的事情:

 

Ø  自动分析和脱壳。

Ø  自动搜索被使用的函数(比如, LoadLibrary(),strcpy(),和您想到的其他函数)。

Ø  分析程序数据流,寻找您感兴趣的东西。

Ø  二进制文件比对。

Ø  编写一个反编译器。

Ø  还有其他的功能……

 

查看别的朋友用IDA SDK编写的插件代码,请登陆IDA Palace网站,地址是 http://home.arcor.de/idapalace/

 

 

 

2.1 安装方法

很简单的。当您得到了SDK(一般都应该是.zip格式的文件),解压缩它到您选择的目录。我通常是在IDA的安装目录下,建立一个sdk目录,把所有的玩意儿都放那儿,但这不是很要紧的,您也可以按您的想法办。

2.2 目录结构

我将探讨一些关于插件编写的内容,以及讨论它们的实质,但不涉及所有SDK的内容。

目录

内容

/

 

此根目录下有一些不同平台下的makefile编译配置文件,以及您应该首先阅读的readme.txt,特别是当版本有变化的时候。

include/

 

此目录下包括:以功能分类的头文件。我建议仔细查看这些头文件,并且纪录一下这些函数,当您看完本手册后,便可以应用这些函数。

libbor.wXX/

用于Borland C编译时,所使用的IDA库文件。

libgccXX.lnx/

Linux下的GCC编译时,要用到的IDA库文件。

libgcc.wXX/

Windows下,GCC编译时,所使用的IDA库文件。

libvc.wXX/

Windows下,Visual C++编译时,要用到的IDA库文件。

plugins/

插件例子代码。

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

2.3 头文件介绍

在include目录中的五十多个头文件中,我发现很多常用于编写插件的头文件。如果您需要所有头文件中的说明信息,可以看看SDK根目录下的readme.txt说明文档,或者是头文件中自带的说明。下面这个列表只是简单描述了它们的大致功能,更详细的介绍请参阅以后的章节。

文件

内容

area.hpp

文件包括:area_tareacb_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_top_toptype_t类分别表示指令、操作数与操作数类型,以及与IDA分析器一同运作的函数。

xref.hpp

处理交叉参考引用代码,和数据参考引用的函数。

 

 

2.4 使用SDK

大致来说,头文件中的任何您能使用的函数都有一个前缀:ida_export,而全局变量则冠以ida_export_data前缀。这点规矩是为了与底层函数(在头文件中也有定义)保持安全距离,并且提供了一些封装好的高层函数。您还可以使用任何定义好的类、结构和枚举。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第三章 配置一个编译环境

Borland用户注意事项:IDA SDK所支持的编译器中,本节将不讨论Borland公司的编译器。您应该阅读SDK根目录下install_cb.txtmakeenv_br.mak以决定需要用的编译器和连接标志。

 

开始编程之路前,最好是有一个合适的环境,以使得开发过程简单容易。一般流行的环境都会被介绍,如果您不是一般流行环境,比如您是ENIAC的忠实用户,那么我只能说抱歉了。如果您已经配置好了开发环境,请从容地跳到下一章。

3.1 Windows下使用Visual Studio

这里将用Visual Studio .NET 2003做示范,一般也适用于这以后的版本,

可能也适应以前的版本。

一旦您的Visual Studio开始运行,请您关闭您可能打开的任何一个工程或方案,我们需要一个完全干净的状态。

1

到菜单,文件->新建->项目...(Ctrl-Shift-N)

2

展开“Visual C++项目”,在“Win32”子目录下,选择“Win32项目”图标,给项目取一个您喜欢的名称,点击确定。

3

“Win32应用程序向导”应该会出现,单击“应用程序设置”标签,而且确保“Windows应用程序”被选定,再单击“空项目”复选框,点击“完成”。

4

在右侧的“解决方案浏览器”中,右键单击“源文件”目录,再依次 添加->添加新项...

5

选择C++文件(.cpp)图标,给文件一个恰当的名称。单击打开。您要添加其他文件到此项目的话,就重复这个步骤。

6

到菜单“项目”-> “项目名”(刚才您输入的名称) 属性...

7

改变如下设置(一些是减小插件尺寸,因为VS的输出文件有些大):

 

配置属性->常规:更改“配置类型”为动态链接库(.dll)

C/C++->常规:设置“检测64位可移植性问题”为否

C/C++->常规:设置“调试信息格式”为禁用

C/C++->常规:添加SDK的include路径到“附加包含目录”一栏。比如C:\IDA\SDK\Include

C/C++->预处理器:添加__NT__;__IDP__到“预处理器定义”

C/C++->代码生成:关掉“缓冲区安全检查”和“基本运行时检查”,设置“运行时库”为单线程

C/C++->高级:“调用约定”为__stdcall

连接器->常规:把“输出文件”的.exe后缀改为.plw,并让它生成到IDA的插件目录中。

连接器->常规:添加您的libvc.w32路径到“附加库目录”。比如C:\IDA\SDK\libvc.w32

连接器->输入:添加ida.lib到“附加依赖项”

连接器->调试:禁用“生成调试信息”

连机器->命令行:添加/EXPORT:PLUGIN

生成事件->生成后事件:设置“命令行”为您的idag.exe,这样每次编译成功都会自动启动IDA(可选)

 

单击 确定

8

回到第6步,但在第7步前,把“配置”从“活动(Debug)”改为Release,然后再重复更改第7步的设置。单击 确定

9

跳到3.4章节

3.2 Windows下使用GCCDev-C++MinGW

您可以从http://www.bloodshed.net/dev/devcpp.html获取Dev-C++GCCMinGW的一个集合软件包。先安装并设置好,现在假设一切都能正常工作。

 

同上,启动Dev-C++,并确保没有其他的工程文件被打开,我们需要一个纯净的状态。

 

1

到菜单的 文件->新建->工程,选择Empty Project,确保 C++工程被选定,然后指定一个合适的名称,单击确定。

2

选择一个目录保存工程文件,任何地方都可以。

3

到菜单的 工程->新建单元,这样就会保存源代码到您的插件。如果您需要添加其他的文件到工程,就重复这个步骤。

4

到菜单的 工程->工程属性,单击“参数”标签

5

C++编译器下,添加:

-DWIN32 -D__NT__ -D__IDP__ -v -mrtd

6

在连接器下,添加:

../path/to/your/sdk/libgcc.w32/ida.a –Wl,--dll –shared

注意,在路径的开始,最好添加../,因为msys好像是拒绝单独的 / 斜杠,再设法从msys目录的根路径引用它。

7

单击“文件/目录”tab,以及sub-tab中的“包含文件目录”,添加您的IDA SDK include目录的路径到该列表中。

8

单击“Build选项”tab,设置“可执行文件输出目录”为您的IDA plugins目录,然后“覆盖同名的文件”一栏中改为.plw文件。单击确定。

9

跳到3.4章节

3.3 Linux下使用GCC

Windows下的插件是.plw扩展名,Linux插件有所有不同,是以.plx为扩展名。因此,在示例中,没有GUI IDE,所以,不再是一步步的配置过程,我将只用Makefile来做示范,下面的例子中,可能并不是一个最纯净的Makefile,但它应该能工作。

在这个示例中,IDA安装于/usr/local/idaadvSDK位于sdk子目录。把下面的Makefile放到您插件的源代码目录。

设置SRC为您的插件包含的源代码,以及OBJS为将要被编译的目标文件(相同文件名,仅仅是换了一个扩展名为.o

 

SRC=file1.cpp file2.cpp

OBJS=file1.o file2.o

CC=g++

LD=g++

CFLAGS=-D__IDP__ -D__PLUGIN__ -c -D__LINUX__ \

-I/usr/local/idaadv/sdk/include $(SRC)

LDFLAGS=--shared $(OBJS) -L/usr/local/idaadv -lida \

--no-undefined -Wl,--version-script=./plugin.script

all:

$(CC) $(CFLAGS)

$(LD) $(LDFLAGS) -o myplugin.plx

cp myplugin.plx /usr/local/idaadv/plugins

 

要编译您的插件,make程序将完成这项任务,再为您复制插件到IDA plugins目录。

3.4 一份插件模板

IDA“钩挂(hooks in)”到您的插件是通过PLUGIN类来实现的,而且一般来说,是唯一需要被导出的一个对象(因而能被IDA使用)。还有,您应该通过#include预编译指令,来包含一些基本的头文件,例如,ida.hpp,idp.hpploader.hpp

下面的模板,可以作为您开始学习插件的一个开始。如果您将它粘贴到您的开发环境的一个文件里,它应该能编译,而且当在IDA里运行它的时候(Edit->Plugins->pluginname,或者直接按下定义的热键),它将在IDA的日志窗口中插入“Hello World”文本。

 

#include <ida.hpp>

#include <idp.hpp>

#include <loader.hpp>

int IDAP_init(void)

{

//在这里做一些校验,以确保您的插件是被用在合适的环境里。

}

void IDAP_term(void)

{

//当结束插件时,一般您可以在此添加一点任务清理的代码。

return;

}

// 插件可以从plugins.cfg文件中,被传进一个整型参数。

// 当按下不同的热键或者菜单时,您需要一个插件做不同

// 的事情时,这非常有用。

void IDAP_run(int arg)

{

// 插件的实体

msg("Hello world!");

return;

}

// 这些不太重要,但我还是设置了。

char IDAP_comment[] = "This is my test plug-in";

char IDAP_help[] = "My plugin";

// Edit->Plugins 菜单中,插件的现实名称,

// 它能被用户的plugins.cfg文件改写

char IDAP_name[] = "My plugin";

// 启动插件的热键

char IDAP_hotkey[] = "Alt-X";

// 所有PLUGIN对象导出的重要属性。

plugin_t PLUGIN =

{

IDP_INTERFACE_VERSION,    // IDA version plug-in is written for

0,                        // Flags (see below)

IDAP_init,                // Initialisation function

IDAP_term,                // Clean-up function

IDAP_run,                 // Main plug-in body

IDAP_comment,             // Comment unused

IDAP_help,                // As above unused

IDAP_name,                // Plug-in name shown in

// Edit->Plugins menu

IDAP_hotkey               // Hot key to run the plug-in

};

PLUGIN结构中,您通常并不能逃避设置这些标志属性(从上数第二个标志),除非它是一个调试器模块,或者您想在Edit->Plugins菜单中隐藏点什么。您可能需要设置别的标志,请参看loader.hpp中的更多的有用信息。

上面的插件模板也可以从如下地址下载,

http://www.binarypool.com/idapluginwriting/template.cpp.

3.5 配置及运行插件

这是所有步骤中最简单的 -- 复制编译好的插件(确定它在Windows下的扩展名为.plwLinux下为.plx)到IDAplugins目录,然后IDA将自动加载它。

开始的时候,检查正确无误地检查您的编译环境(比如Linux下的LD_LIBRARY_PATH),确保您的插件能加载所有应该加载的DLL和共享库。您可以用-z20参数来启动IDA,这表示允许插件来进行调试。如果在加载的过程中,有什么错误的话,通常会显示出来。

     如果您在IDAP_init()函数中添加了代码,这些代码将在IDA反汇编处理第一个文件时,得以执行,另外,如果您在IDAP_run()函数中添加了代码,它们将在用户按下热键组合,或通过选择Edit->Plugins菜单时,得以运行。

     用户可以改写plugins.cfg文件中的一些PLUGIN的设置,比如插件名和热键,但是这对于您来说也没什么要紧的。在插件开始运行时,plugins.cfg文件也可以被用来传递参数到您的插件里面。

第四章 IDA插件原理

IDA SDK中有各式各样的类,数据结构和类型,其中有一些用得更广泛。这一章的目的就是向您介绍它们,因为它们提供了很棒的视角,来分析在一个反汇编文件中,IDA到底知道多少信息,而且,也可以让您思考,能用SDK做哪些可能的事情。

一些类和结构都非常大,都有很多的成员变量和方法或函数。在这一章,大多是介绍这些变量,而函数将在《第五章-函数》中介绍。有一些代码的注释是从SDK中获得的,还有一些是我自己写的注释,另外一些是兼有前两者。在一些情况下,#define预编译指令已经定义了一些成员,同样是在SDK中实现的。我囊括了它们,因为这样能更好的演示,成员变量可以设置什么样的有效值。

 

关于例子代码的重要事情:本章中的所有例子代码,都应该放到3.4章节中的插件模板的IDAP_run()函数中,除非有特殊的情况。

4.1重要的数据类型

贯穿SDK和这本手册,下面的数据类型都在被使用,因此,您能够非常清楚地明白,这些数据类型表示的是什么就非常重要了。

下面所有的类型,都是无符号长整型(unsigned long intergers),和64位系统下的,无符号超长整型(unsigned long long integers)。它们被定义在pro.h头文件中。

 

类型

描述

ea_t

‘有效地址(Effective Address)’的意思,表示IDA中很多不同类型的地址(如内存,文件,limits等等)

sel_t

段选择子,如,代码,栈和数据段选择子

uval_t

被用来表示无符号值

asize_t

通常用来表示某些东西的尺寸,例如一块内存。

 

下面这些是有符号长整型,和64为系统下的有符号超长整型。它们同样都被定义在pro.h头文件中。

类型

描述

sval_t

用来表示有符号值

adiff_t

表示两个地址间的不同处

 

最后,还有一些有意义的预定义值;其中之一是BADADDR,它表示一个无效或不存在的地址,比如您会看到它被用来检测,一个可读地址区域或结构的末尾。同样,在字符缓冲区定义中的MAXSTR宏,它的值是1024.

 

 

4.2 核心结构以及类

4.2.1 元数据信息(Meta Information)

idainfo结构,事实上是存储在IDA数据库中(即IDB文件),注意,我提到的元数据信息是指,第一个文件被IDA加载反汇编后,以后无论再多的文件被加载,元数据信息都不会改变。下面是在ida.hpp头文件里,其中一些比较有意思的部分:

 

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.procName检查,以明确插件是为何种系统平台所编写。

    比方说,如果您要编写的插件,仅处理x86平台下的PEELF二进制格式文件,您可以添加如下代码到您的插件的初始化函数(即3.4章节插件模板中的IDAP_init函数)。

// "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;     // 继续此插件的加载

 

4.2.2 域的概念

在探究“高层”类的技术细节前,如段(segment),函数和指令的处理,我们应该了解一下两个关键概念:名为areas()area control blocks(域控制块)

4.2.2.1 area_t结构

域以area_t结构表示,在area.hpp头文件中定义。基于这个头文件中的注释,严格来讲:

“域”由很多单独的area_t实例构成。域是一个连续的非空地址范围(由它的起始和结束地址指定,但不包括结束地址在内),还有地址范围的属性,也是域的内容。比如,一组段是一些域的集合。

请看下面摘录的一部分area_t结构的定义,它包括一个起始地址(startEA)和结束地址(endEA)成员。还有很多成员函数,比如判断一个域是否包括某一个地址,某一个域是否为空,以及返回一个域的尺寸。段是一个域,而函数也是,这就意味着,域可以包含另外的域。

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; }

...

};

 

一般来说,函数和段都是域,这也表明,func_tsegment_t类继承了area_t结构。这就意味着,area_t结构中的成员变量和函数都能应用到func_tsegment_t(比如,segment_t.startEAfunc_t.contains()都是合理的)func_tsegment_t也扩展了area_t结构,还增加了自身的特殊成员变量和函数。稍后仍将介绍它们。

还有一些其他的基于area_t的继承,如下

类型(和所在文件)

描述

hidden_area_t

(bytes.hpp)

代码或数据被替换成的隐藏域,它以一个描述作为摘要,并能被展开查看隐藏的信息。

regvar_t

(frame.hpp)

被用户自定义所替换的寄存器名称(寄存器变量)

memory_info_t

(idd.hpp)

一块内存的相关信息(当使用调试器时)

segreg_t

(srarea.hpp)

段寄存器(x86平台是,CS,SS,等等)信息

 

4.2.2.2 areacb_t

域控制块(Area Control Block)areacb_t类表示,也定义于area.hpp头文件中。如下是对它的注释,稍微描述得简单,但事实上并不是非常需要。

 

“areacb_t”是一个基类,在IDA的很多地方都被用到。

 

域控制块的类很简单,由一些函数构成一个集合,这些函数可以被用来操作域。函数包括get_area_qty()get_next_area()等等。您可能发觉自己可能并不需要直接使用这些函数,比如您反汇编分析二进制文件中的一个函数时,您可能更喜欢使用func_t的成员函数,或使用继承于area_t的其他的类。

有两个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());

 

4.2.3 段和函数

前面提到过,segment_tfunc_t类都是继承了或扩展了area_t结构,这意味着area_t的成员变量和函数都能在这些类中被使用,另外它们自身还实现了更多于area_t结构的功能。

4.2.3.1

segment_t类在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);

}

4.2.3.2 函数

函数由func_t类来表示,该类被定义在funcs.hpp中,开始讨论func_t类的技术细节前,可能有必要弄清楚函数块(chunk),函数源(parents),以及函数尾(tail)的概念。

函数,是正在被分析的二进制文件中的,一些连续的代码块,通常被表示成一个单独的函数块。然而,很多时候,当优化性编译器移除一些冗余代码时,函数被分割成了很多含有代码的函数块,这是因为其他函数的隔离。这些被隔离的松散的函数块被叫做“函数尾(tail)”,还有一些函数块引用这些函数尾代码(由JMP或类似的指令引用),被称作“函数源(parents)”。有些容易混淆的是,所有的这些函数块,函数源,函数尾都是同一func_t类型,因此您需要检测func_t的成员flags,以确定该func_t实例到底是函数尾还是函数源。

 

下面是func_t类的超级剪切版本,并附上一些funcs.hpp中的注释。

 

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函数的地址

}

...

}

因为函数同段一样,都是域,处理函数差不多和处理段一样。下面这段实例代码列出了所有函数名和它们在反汇编文件中的地址,并在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);

}

4.2.4 代码的表示

通常,汇编语言指令由助记符(PUSHSHRCALL等),以及操作数(EAX[EBP+0xAh]0x0Fh等)组成。一些操作数可以有多种形式,而有一些指令则没有操作数。所有这些都在IDA SDK中清楚的写明了。

您可以从insn_t类型入手,它表示一整条指令,像“MOV EAX, 0x0A”这样的一整条指令。insn_t由一些成员变量,6op_t变量(每一个对应指令中的一个操作数),而且每一个操作数,可以是一个特定的optype_t值(比如,通用寄存器,立即数,等等)。

现在,我们开始仔细地,探索其中的每一个部分,它们都定义在ua.hpp

4.2.4.1 操作数类型

optype_t表示一条指令中的操作数类型。下面列举了一些常用的操作数类型。附带的一些描述取自ua.hppoptype_t的定义。

 

操作数

 

描述

反汇编举例(操作数以粗体标出)

 

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

 

4.2.4.2 操作数

op_t表示一条指令中,某一个的操作数的相关信息。下面是op_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)

...

}

 

因此,举个例子来说,[esp+8]这样的操作数将返回o_displ这样的类型(即type成员的值为o_displ),reg成员的值为4(正是ESP寄存器的序号)以及,addr成员的值为8,因为您正在访问堆栈指针指向的8字节,因此正是一个内存引用。您可以用下面的一段代码获取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);

4.2.4.3 助记符

指令中的助记符(PUSHMOV,等)由insn_t类(参考下一节)的itype成员表示。然而,itype是一个整数,就目前而言,并不能在用户定义的数据结构中,直接显示指令的文本样式。但有一个ua_mnem()函数可以实现上述功能,以后将在《第五章 函数》中介绍。

一个名为instruc_tallins.hpp)的枚举保存了所有的助记符(前缀为NN_)。如果您知道您要寻找或测试的指令,您可以使用它,而不是使用文本表示的指令。比如,测试二进制文件中,某条指令的助记符是否为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;

4.2.4.4 指令

insn_t表示一整条指令。包括一个名叫Operandsop_t类型数组,表示指令中的所有操作数。当然,也有指令没有操作数的(象PUSHACDQ,等指令),这种情况下,Operands[0]变量则为optype_t类型的o_void值(无操作数)。

 

class insn_t

{

public:

ea_t cs;        // 代码段基址(in paragraphs)

ea_t ip;        // 段中的偏移

ea_t ea;        // 指令起始地址

ushort itype;   // 助记符ID

ushort size;    //  指令大小(字节)

#define UA_MAXOP 6

op_t Operands[UA_MAXOP];

#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()函数填充。稍后将详细讨论,但目前,给出一个示例代码,它获取入口点的指令序号,地址和大小, 并在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);

4.2.5 交叉引用参考

IDA的一个很方便的功能就是交叉引用参考功能,它有助于让您知道反汇编文件中,所有部分引用其他地址的情况。举例来说,在IDA里,您可以在反汇编窗口中高亮选中一个函数,然后按下‘x’键,然后就会在弹出窗口里,显示所有引用该函数的其他地址(比如,调用此函数的地址)。对于数据和局部变量也可以用同样的方式进行引用参考。

SDK提供了一个简单接口,来访问这些交叉引用参考的信息,这些信息以B-tree数据结构存储,并可以通过xrefblk_t结构来访问。此外,也有更手动化的方式,来获取这些信息,但和下面列举的方法比起来,实在是太慢了。

应该记住的重要事情是,当一条指令后续又有一条指令,IDA会潜在地看作第一条指令引用第二条指令,但这项功能可以用xrefblk_t结构的一些方法关掉,以后将在《第五章 函数》中介绍。

4.2.5.1 xrefblk_t结构

交叉引用参考功能的核心是xrefblk_t结构,它被定义在xref.hpp中。此结构首先需要使用first_from()first_to()成员函数来填充(这看您是要寻找一个地址的引用到‘reference to’,或引用于‘reference from’,然后您遍历引用的时候,就用next_from()next_to()成员函数来填充。

下面列出的这个结构的成员变量和大部分注释来自xref.hpp头文件。成员函数(first_fromfirst_tonext_fromnext_to)省略了,但是会在《第五章-函数》里讨论。

 

struct xrefblk_t

{

ea_t from;      // 被引用地址(referencing address)

ea_t to;        // 引用的地址(referenced address)

uchar iscode;   // 1表示代码参考引用,0表示数据参考引用

uchar type;     // cref_t 或者dref_t 类型中之一(参看

//  4.2.5.2章节、和 4.2.5.3章节)

...

};

 

iscode成员变量表示的一样,xrefblk_t能获取代码参考引用或者数据参考引用的信息,其中的每一个都有可能的参考引用类型,且以type成员变量表示其类型。这些代码和数据参考引用类型在接下来两节里被阐述。

 

下面的代码片段,将给您当前光标所在的位置,相关的交叉参考引用信息:

 

#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);

}

 

4.2.5.2 代码

下面是cref_t枚举类型,剪去了一些不相关的内容。对于参考引用的类型,如果xrefblk_tiscode成员变量为1的话,那么type成员变量将会是下面列出来的枚举值。下面的注释取自xref.hpp头文件。

 

enum cref_t

{

...

fl_CF = 16,     // 远调用(Call Far)

// xref在引用的地方创建一个函数

fl_CN,          // 近调用(Call Near)

// xref在引用的地方创建一个函数

fl_JF,          // 远跳转(Call Far)

fl_JN,          // 近跳转(Call Near)

fl_F,           // 选择跳转:用来表示下一条指令的执行流程。

...

};

下面的一个代码参考引用取自一个简单的二进制文件,712D9BFE712D9BF6引用,即它是一个近跳转引用类型。

 

.text:712D9BF6 jz short loc_712D9BFE    //近跳转引用类型

...

.text:712D9BFE loc_712D9BFE:

.text:712D9BFE lea ecx, [ebp+var_14]

 

4.2.5.3 数据

如果xrefblk_tiscode成员被置为0,说明它是一个数据参考引用。下面是当您在处理数据参考引用时,一些可能的type成员变量的值。此枚举类型的注释取自xref.hpp头文件。

 

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_ttype成员将是典型的dr_R值,因为是简单地读取了ds:wsprintfA这一行的地址。另外一种数据参考引用如下,在712EABE2处的PUSH指令引用了位于712C255C的一串字符。

 

.text:712C255C aVersion:

.text:712C255C unicode 0, <Version>,0

...

.text:712EABE2 push offset aVersion

 

这种情况,xrefblk_ttype成员变量将会是dr_O,因为它以偏移访问该数据。

 

4.3 字节标志

对于反汇编文件的每一个字节,IDA录入了一个相应的四字节(32位)值,并保存在id1文件中。这些个四字节中,每半字节(四位)是一个标志,表示反汇编文件的某个地址中,该字节的一条信息。反汇编文件的地址中,四字节的最后一个字节才是真正内容。

 

比方,下面这个被反汇编的文件中,一条指令占据一个单独字节(0x55)

 

.text:010060FA push ebp

 

文件中的上述地址被反汇编为IDA标志就是,0x000107550001007是标志成分,而55则是文件中该地址的字节标志。请记住,地址在标志中并没有实际意义,也不可能从一个地址或字节本身得到标志,您应该使用getFlags()来获取一个地址的标志(详见下)。

 

很明显,不是所有的指令都是一个字节的尺寸;拿下面的指令做为例子,它有三个字节(0x83 0xEC 0x14)。因此,该指令分成三个地址;0x010011DE,0x010011DF0x010011E0

 

.text:010011DE sub esp, 14h

.text:010011E1 ...

 

下面是该指令的每个字节对应的标志:

010011DE: 41010783

010011DF: 001003EC

010011E0: 00100314

 

因为这三个字节同属一条指令,故该指令的第一个字节被称作“指令头(head)”,然后剩下两个字节叫做“指令尾(tail)”。再说下,每个标志的最后一个字节对应于指令(0x83,0xEC,0x14)。

 

所有标志定义在bytes.hpp头文件,然后您可以检验,getFlags(ea_t ea)的返回的标志与适当的标志检测函数的测试结果,来确定该标志到底是哪个标志。下面是一些常用的标志及其封装函数。一些函数将在《第五章-函数》中讨论,剩下的你可以在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()

 

回到开始的“push ebp”例子,如果我们用上面的两个标志来手动检测getFlags(0x010060FA)的返回值,我们会得到下面的结果:

 

0x00010755 & 0x00000600 (FF_CODE) = 0x00000600. 由此,我们知道这是一条指令。

0x00010755 & 0x00000800 (FF_COMM) = 0x00000000. 我们知道这没有被注释。

 

上面的例子是纯粹的演示目的,请别再您的插件中用这样的方法。上面提到过,您可能经常要使用助手函数(helper function),来检测一个标志到底是哪个。下面的代码将返回您的光标所在行,指定的头地址(head address)标志。

 

#include <bytes.hpp>

#include <kernwin.hpp>

msg("%08x\n", getFlags(get_screen_ea()));

 

4.4 调试器

IDA SDK的一个很强大的新特性是,可以与IDA的调试器交互,而且除非您已经安装了您自己的调试器插件,那么,IDA会使用一个自带的调试器插件。下面是IDA自带的调试器插件,也可以在您的IDA plugins目录中被找到:

插件文件名

描述

win32_user.plw

Windows本机调试器

win32_stub.plw

Windows远程调试器

linux_user.plw

Linux本机调试器

linux_stub.plw

Linux远程调试器

 

这些调试器被IDA自动加载,而且会在Debugger->Run菜单显示。打今儿起,“调试器”一词,将表示,您正在使用的调试器(IDA将自动选择一个最适合您的)。

先前提到,为IDA写一个调试器模块是可以的,但这也不是说,就拒绝使用编写的插件模块来与调试器交互。下面描述的是插件的第二种类型。

此外,所有与调试器交互的接口函数,将在《第五章-函数》中被讨论,在深入讨论前,还有一些关键的数据结构和类需要去认识。

 

4.4.1 debugger_t 结构

定义在idd.hpp头文件中的debugger_t结构,导出了一个dbg指针,表示当前激活的调试器插件,并且,当调试器被加载时,该指针就是有效的了。(比如,在IDA启动时,而不仅仅是您抄起调试器的时候)。

 

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成员变量获取一些可用寄存器的时候,也很有用处(请看下集……)。

 

4.4.2 寄存器

当使用调试器的时候,通常的任务就是访问及操作寄存器值。IDA SDK里,寄存器以register_info_t结构来描述,保存寄存器的值由regval_t结构表示。下面是部分摘取自idd.hpp头文件中的register_info_t结构定义。

 

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     // 寄存器可以包含地址

...

}

 

这个结构的唯一实例,可以用*dbgSDK导出的一个debugger_t实例)的数组成员*register来访问,因此知道您使用调试器时,在您的系统上,这些寄存器才是有效的。

要获取寄存器的值,最起码调试器得运行起来。读取或操作调试器值的函数将在《第五章-函数》中详细讨论,现今,您需要知道的事,就是要获取这些值,可以通过regval_t的成员ival,或者,如果您正在处理浮点数,那么您可以使用fval成员。

    下面是定义在idd.hpp头文件中的regval_t结构。

struct regval_t

{

    ulonglong ival;     // 整数值

    ushort    fval[6];  // 浮点数值

// 表示方法参看 ieee.h头文件

};

    ival/fval将直接对应于一个寄存器里存储的东西,因此,如果EBX寄存器为0xDEADBEEFival成员(一旦用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);

}

 

4.4.3 断点

调试的一个最核心的部分就是断点,而且IDAdbg.hpp中定义的bpt_t结构(见下),来表示不同的硬件和软件断点。硬件调试器是用CPU的调试寄存器(x86DR0-DR3,但是软件调试器则是在需要中断的地址处,插入INT 3指令来达到目的。尽管这些都由IDA来为您处理好了,但知道其中的不同是有帮助的。在x86平台,您最多能设置4个硬件断点。

struct bpt_t

{

  // 只读属性:

  ea_t ea;                  // 断点的起始地址

  asize_t size;             // 断点的尺寸

                            // (若是软件断点,则是未定义的)

  bpttype_t type;           // 断点的类型:

// 摘自idd.hppbpttype_t常量定义:

// BPT_EXEC  =  0,             // 可执行的指令

// BPT_WRITE =  1,             // 可写

// BPT_RDWR  =  3,             // 可读写

// BPT_SOFT  =  4;             // 软件断点

  // 可修改属性 (使用update_bpt()函数修改):

  int pass_count;               // 执行流达到此断点消耗的时间

                                // (如果未定义则为-1)

  int flags;

#define BPT_BRK   0x01          // 调试器停在这个断点吗?

#define BPT_TRACE 0x02          // 当到达这个断点时,

// 调试器添加了跟踪信息吗?

  char condition[MAXSTR];       // 一个IDC表达式,

// 它将被用来表示一个中断条件,

                                // 或者当这个断点被激活时,要执行

                                // IDC命令。

};

 

因此,如果bpt_ttype成员是01或者3,则表示硬件断点,但是4表示软件断点。

有许多的函数可以创建,操作,读取该结构,但目前,我给出一个很简单的例子,它遍历所有的断点,并且在IDALog窗口显示到底是一个软件或硬件断点。这些函数将在以后详细解释。

#include <dbg.hpp>

// get_bpt_qty() 获取断点数目

for (int i = 0; i < get_bpt_qty(); i++) {

    bpt_t brkpnt;

    // getn_bpt基于给定的断点数目,

// 并用断点信息填充bpt_t结构

    getn_bpt(i, &brkpnt);

    // BPT_SOFT就表示软件断点

    if (brkpnt.type == BPT_SOFT)

        msg("Software breakpoint found at %a\n", brkpnt.ea);

    else

        msg("Hardware breakpoint found at %a\n", brkpnt.ea);

}

 

4.4.4 跟踪

    IDA中,有三种类型的跟踪可供您打开;函数跟踪,指令跟踪和断点(又被称作可读/可写/可执行)跟踪。编写插件时,还有另一个形式的跟踪是有用的;单步跟踪。单步跟踪是跟踪的一个底层形式,允许您在该形式之上建立自己的跟踪机制,再利用事件通知(参看4.5章节)驱动您的插件,这样,每一条指令可以被单步执行。这是基于CPU的跟踪能力,而非用断点。

    跟踪时,一个“跟踪事件(trace evnet)”就会产生,并保存到一个缓冲区,而且,您允许的跟踪类型,决定了引发产生什么样的跟踪事件。但是,单步跟踪不产生跟踪事件,这样就有些麻烦,但也可以用事件通知(参见4.5章节)替代。下面的表格列出了所有不同的跟踪事件类型,和在dbg.hpp,对应的tev_type_t枚举值定义。

   

跟踪类型

事件类型

(tev_type_t)

描述

函数调用和返回

tev_calltev_ret

函数已经被调用或者已经返回

指令

tev_insn

指令已经被执行(在IDA内核中,这建立于单步跟踪之上)

断点

tev_bpt

设置的断点被激活。也被称作可读/可写/可执行跟踪

    所有跟踪事件都被保存到一个循环缓冲区,所以它不会填满,但如果是这个缓冲区太小,那么原来的跟踪事件可能会被覆盖掉。每个跟踪事件被表示为tev_info_t结构,它被定义在dbg.hpp头文件中。

 

struct tev_info_t

{

  tev_type_t  type; // 跟踪事件类型(上述表格中之一,或者tev_none

  thread_id_t tid;  // 被记录的事件所在线程。

  ea_t        ea;   // 发生事件的地址。

};

    4.4.3章节基于bpt_t结构的描述,一个断点跟踪和普通的断点差不多,不过断点跟踪在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");

}

}

    目前,无需讨论的是,给插件增加入口是不必要的,甚至是修改跟踪事件日志。

    所有这些函数将在《第五章-函数》中详细讨论。

 

4.4.5 进程和线程

IDA给运行在调试器中的进程和线程,保存了一些它们的信息。进程和线程ID分别用process_id_tthread_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");

}

    使用这些结构的函数将在《第五章-函数》中讨论。

 

4.5 事件通知

 

一般来说,插件是同步运行的,即由用户来决定执行,要么通过按下热键,要么通过Edit->Plugins菜单。但是,插件也可以异步运行,就是响应IDA或者用户产生的事件通知来达到这个目的。

IDA下工作的过程中,您可能经常点击按钮,执行搜索,等等。所有这些动作都是“事件(events)”,所以,IDA做的事情就是当这些动作发生时,产生“事件通知(event notifications)”。如果您的插件被配置成接收这些通知(稍后解释),您就可以编程实现做一些动作。打个比方,这样的程序可以保存一些宏,然后插件就能产生事件,驱使IDA执行各种功能。

 

4.5.1 接收通知

IDA中接收事件通知,插件必须用hook_to_notification_point()函数,注册一个回调函数(call-back)。要产生事件通知,可以使用callui()函数,这些将在《第五章-函数》中详细讨论。

hook_to_notification_point()函数注册了一个回调函数后,您可以使用三种事件类型其中之一,这要看您需要接收什么样的事件通知。这些类型在loader.hpp中定义为hook_type_t枚举:

 

类型

从模块中接收事件通知

事件通知类型的枚举

HT_IDP

处理器模块

idp_notify(不会讨论)

HT_UI

IDA用户界面

ui_notification_t

HT_DBG

正在运行的IDA调试器

dbg_notification_t

 

因此,要接收所有适合调试器的事件通知,并发送它们到您的dbg_callback(打个比方)回调函数,您可以将如下代码放到IDAP_init()函数中:

 

hook_to_notification_point(HT_DBG, dbg_callback, NULL);

   

    第三个参数一般设置为NULL,除非在收到一个通知时,您需要传递数据到回调函数(可以是任何您选择的数据结构)。

    提供给hook_to_notification_point()的回调函数,必须符合下面的形式:

 

int idaapi mycallback (void *user_data, int notif_code, va_list va)

{

    ...

    return 0;

}

   

    在处理事件通知时,mycallback实际上由IDA负责调用,user_data将指向您传递给回调函数的数据结构(在调用hook_to_notification_point()时定义)。notif_code将会是接收到的事件标志(接下来两章会列出),va则是由IDA提供的,与事件相关的数据,可能是附加的信息。

    如果回调函数允许事件通知被后来的处理者操作,那么它应该返回0,或者该回调函数本身即最终的处理者,则应该返回任何其他值。

    有一点值得注意的是,如果您在插件中使用hook_to_notification_point()

一旦您不需要接收通知了,或已经运行到IDAP_term()函数了,那么您还应该使用unhook_from_notification_point()函数。这将在退出IDA时避免发生不可预料的段访问错误。回到上面的例子代码,要卸载钩挂的事件通知,应该像下面一样:

unhook_from_notification_point(HT_DBG, dbg_callback, NULL);

 

4.5.2 UI事件通知

    ui_notification_t是定义在kernwin.hpp中的一个枚举,并包括了所有的,可以由IDA或者插件产生的用户界面事件通知。要注册获取这些事件通知,您必须将hook_to_notification_point()的第一个函数设置为HT_UI

    下面的两个列表给出了一些可以由插件接收或产生的事件通知。这些只是全部事件通知的一个子集;列出来的这些比较通用一点。

    尽管这些通知可以由插件调用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

显示有YesNo选项的消息框

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_savingui_saved

分别表示,IDA正在保存或者已经保存完数据库

ui_term

IDA已经关闭数据库

比如,下面的代码将产生一个ui_screenea事件通知,并用ui_mbox事件通知在IDA对话框中显示结果。

void IDAP_run(int arg)

{

    ea_t addr;

    va_list va;

    char buf[MAXSTR];

 

    // 获取当前光标的虚拟地址,并保存到addr变量

    callui(ui_screenea, &addr);

    qsnprintf(buf, sizeof(buf)-1, "Currently at: %a\n", addr);

    // 在消息框中显示相关信息

callui(ui_mbox, mbox_inf buf va);

return;

}

    上面这种情况,您一般应该使用助手函数,这里使用cullui()只不过是演示的目的。

 

4.5.3 调试器事件通知

    调试器事件通知分为三种类型,底层型,高层型和函数返回事件通知;它们之间的不同点,将在接下来的章节解释清楚。上面说的的这些事件通知,都属于dbg_notification_t枚举,它定义在dbg.hpp头文件中。如果您传递给hook_to_notification_point()一个HT_DBG参数,那么,在IDA调试进程的时候,下面这些事件通知将被传给您的插件。

   

4.5.3.1 底层型事件

    下面这些事件摘自dbg_notification_t,都表示底层型事件。底层事件通知都由调试器生成。

 

 

事件通知

描述

dbg_process_start

进程启动

dbg_process_exit

进程终止

dbg_library_load

加载了库文件

dbg_library_unload

卸载了库文件

dbg_exception

产生异常

dbg_breakpoint

非用户定义的断点被激活

您能够使用debug_event_t结构(idd.hpp)获取更多关于调试器事件通知的信息,通常是提供给va参数到您的回调函数(仅支持底层函数事件)。下面是debug_evnet_t结构的全貌。

 

struct debug_event_t

{

event_id_t eid;           // 事件代码(常用于解释 ‘info’联合)

process_id_t pid;         // 事件发生的进程

thread_id_t tid;          // 事件发生的线程

ea_t ea;                  // 事件发生的地址

bool handled;             // 事件是否由调试器处理?

// (从系统的角度看)

// 右边的注释说明了eid

// 与联合成员的设置有对应关系。

union

{

module_info_t modinfo;  // PROCESS_START, PROCESS_ATTACH,

// LIBRARY_LOAD

int exit_code;          // PROCESS_EXIT, THREAD_EXIT

char info[MAXSTR];     // LIBRARY_UNLOAD (卸载的库文件名称)

// INFORMATION (若有信息,将被显示在消息窗口)

e_breakpoint_t bpt;     // BREAKPOINT (非用户定义)

e_exception_t exc;     // EXCEPTION

};

};

 

比如,如果您的回调函数接收到dbg_library_load事件通知,您就能检查debug_event_tmodinfo成员,以确定加载了什么文件:

 

...

// 我们的回调函数要处理HT_DBG事件通知

static int idaapi dbg_callback(void *udata, int event_id, va_list va)

{

// va包含了一个debug_event_t指针

debug_event_t *evt = va_arg(va, debug_event_t *);

// 若事件是dbg_library_load,我们就知道modinfo将被填充,

// 而且,包含了加载的库文件名称

if (event_id == dbg_library_load)

msg("Loaded library, %s\n", evt->modinfo.name);

 

return 0;

}

 

// 我们的init函数

int IDAP_init(void)

{

// 注册通知点为我们的回调函数

hook_to_notification_point(HT_DBG, dbg_callback, NULL);

...

 

4.5.3.2 高层型事件通知

    下面的这些事件摘自dbg_notification_t,它们都是高层型事件通知,由IDA内核产生。

事件通知

描述

dbg_bpt

用户定义的断点被激活

dbg_trace

一条指令被执行(需要允许单步跟踪)

dbg_suspend_process

进程已经被暂停

dbg_request_error

请求的时候,产生一个错误(参看5.14章节)

 

    每个这样的事件通知都有不同的参数,并复制给va参数,供您的回调函数使用。但没有提供debug_event_t,如底层型事件通知那样。

    dbg_bpt事件通知与受影响的线程的线程ID(thread_id_t),和被激活断点的地址,一起复制到va参数。下面的例子在用户断点被激活的时候,显示一些信息到IDA的日志窗口。

...

int idaapi dbg_callback(void *udata, int event_id, va_list va)

{

// Only for the dbg_bpt event notification

if (event_id == dbg_bpt)

// 获取线程ID

thread_id_t tid = va_arg(va, thread_id_t);

// 获取被激活断点的地址

ea_t addr = va_arg(va, ea_t);

msg("Breakpoint hit at: %a, in Thread: %d\n", addr, tid);

return 0;

}

int IDAP_init(void)

{

hook_to_notification_point(HT_DBG, dbg_callback, NULL);

...

 

4.5.3.3 函数返回型通知

    最后一节里,将详细讨论同步和异步调试器函数;到目前为止,所有您需要知道的同步调试器函数只是一些普通函数 您调用它们,它们则完成一些事情,然后返回。而异步函数呢,在调用后就立即返回,高效地把请求放进队列,然后再后台运行。当任务完成,一个表示原来请求完成的事件通知就产生了。

    下面都是函数返回型通知。

事件通知

描述

dbg_attach_process

调试器附加到进程(IDA 4.8)

dbg_detach_process

调试器离开于进程(IDA 4.9)

dbg_process_attach

调试器附加了一个进程(IDA 4.9)

dbg_process_detach

调试器离开了一个进程(IDA 4.9)

dbg_step_info

调试器步入一个函数

dbg_step_over

调试器步过一个函数

dbg_run_to

调试器已经运行到用户的光标位置

dbg_step_until_ret

调试器已经返回到调用者的位置

    下面的例子在IDAP_run()中让IDA附加到一个进程。一旦附加成功,IDA产生一个事件通知,dbg_attach_process,并由dbg_callback回调函数所处理。

 

...

int idaapi dbg_callback(void *udata, int event_id, va_list va)

{

// 获取被附加进程的ID

process_id_t pid = va_arg(va, process_id_t);

// 若您使用IDA 4.9,更改dbg_attach_process

// dbg_process_attach

if (event_id == dbg_attach_process)

msg("Successfully attached to PID %d\n", pid);

return 0;

}

void IDAP_run(int arg)

{

int res;

// 附加到一个进程,用法参看第五章

attach_process(NO_PROCESS, res);

return;

}

int IDAP_init(void) {

hook_to_notification_point(HT_DBG, dbg_callback, NULL);

...

 

4.6 字符串

    我们可以用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.)

...

};

 

    请注意,上面的结构并不含字符串。要获取字符串,您需要使用get_bytes()或者get_many_bytes()来从二进制文件中提取。要得到所有有效的字符串,可以使用如下代码:

 

// 遍历所有字符串

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);

}

}

 

上面的一些函数将在第五章《函数》中,详细讨论。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第五章 函数

    这一章,将以导出的IDA SDK函数的不同作用,来分类进行描述。从最简单的开始,然后使用更复杂一些的函数。还将提供使用这些函数的简单例子,而且在第六章《实例代码》中,将提供更多的内容。显然,这里并不是一个完整的介绍(更完整的请参阅SDK中的头文件),但总的来看,仍然是一些很有用的函数。

    例子的重要事项:下面所有的函数,都可以在IDAP_run()IDAP_init()IDAP_term()函数中使用,除非有特殊情况。下面的任何一个例子,都可以粘贴到3.4章节中插件模板的IDAP_run()函数中,而且应该能正常运行。每个函数或例子需要的头文件都会写明。

 

5.1 常用函数的替代

    IDA提供了很多常用C标准库函数的替代调用。推荐您用下面这些替代函数,而不是C标准库函数。在IDA 4.9中,许多C标准库函数都不再有效,您应该使用IDA的替代函数。

C库函数

IDA替代函数

定义在

fopen, fread, fwrite,

fseek, fclose

qfopen, qfread, qfwrite,

qfseek, qfclose

fpro.h

fputc, fgetc, fputs,fgets

qfputc, qfgetc, qfputs,

qfgets

fpro.h

vfprintf, vfscanf, vprintf

qfprintf, qfscanf, qvprintf

fpro.h

strcpy, strncpy, strcat,

strncat

qstrncpy, qstrncat

pro.h

sprintf, snprintf,

wsprintf

qsnprintf

pro.h

open, close, read, write,

seek

qopen, qclose, qread, qwrite,

qseek

pro.h

mkdir, isdir, filesize

qmkdir, qisdir, qfilesize

pro.h

exit, atexit

qexit, qatexit

pro.h

malloc, calloc, realloc,

strdup, free

qalloc, qcalloc, qrealloc,

qstrdup, qfree

pro.h

 

强烈推荐您使用上面这些替代函数,但是如果您在写一个老版本上面的插件,而且因为一些原因要用到C标准库函数,您可以用-DUSE_DANGEROUS_FUNCTIONS -DUSE_STANDARD_FILE_FUNCTIONS预定义来进行编译。

 

5.2 消息框

您可能会在写插件的时候,用一些常见的函数;但并不是因为它们是最有用的,只是因为它们的用法简单,而且在调试插件的时候很有帮助。象你从定义中知道的一样,这些函数都是内联型,而且和printf的参数格式一致。它们定义在kernwin.hpp

5.2.1 msg

定义

inline int

msg(const char *format,...)

含义

IDA的日志窗口中显示一段文本(静态反汇编模式下,屏幕的底端,或者动态调试模式下,屏幕的顶端)

示例

msg(“Starting analysis at: %a\n”, inf.startIP);

 

5.2.2 info

定义

inline int

info(const char *format,...)

含义

“info”形式图标,在一个弹出的对话框中,显示一段文本。

示例

info(“My plug-in v1.202 loaded.”);

 

5.2.3 warning

定义

inline int

warning(const char *format,...)

含义

”warning”形式图标,在一个弹出对话框中,显示文本。

示例

warning(“Please beware this could crash IDA!\n”);

 

5.2.3 error

定义

inline int

error(const char *format,...)

含义

“error”图表形式,在一个弹出对话框中,显示文本。当用户单击OK后,关闭IDA(非正常)。

示例

error(“There was a critical error, exiting IDA.\n”);

 

5.3 UI浏览

 

下面这些函数专门用来与IDA GUI进行交互。其中一些用callui()产生事件通知给IDA。所有这些函数定义在kernwin.hpp

 

5.3.1 get_screen_ea

定义

inline ea_t

get_screen_ea(void)

含义

返回用户的光标所在地址。

示例

#include <kernwein.hpp>

msg(“Cursor position is %a\n”,get_screen_ea());

 

5.3.2 jumpto

定义

inline bool

jumpto(ea_t ea, int opnum=-1)

含义

移动用户的光标到由ea指定的地址。opnum表示光标要移动到的横坐标位置,opnum-1则表示不改变横坐标原来的位置。返回true表示成功,false为失败。

示例

#include <kernwin.hpp>

 

// 跳到入口点+8字节偏移的位置,但不改变

// 光标以前的横坐标

jumpto(inf.startIP+8);

 

5.3.3 get_cursor

定义

inline bool

get_cursor(int *x, int *y)

含义

获取光标的横纵坐标到x,y

示例

#include <kernwin.hpp>

 

int x, y;

// 保存光标横纵坐标到x, y

// 显示结果到日志窗口

get_cursor(&x, &y);

msg(“X: %d, Y: %d\n”, x, y);

 

5.3.4 get_curline

定义

inline char *

get_curline(void)

含义

返回指针,它指向光标位置的一行文本。该函数将返回在那一行中的所有东西:地址,代码和注释。而且也是着色代码,您可以使用tag_remove()清除着色。(参阅5.20.1节)

示例

#include <kernwin.hpp>

 

// 在日志窗口显示光标所在的一行的文本。

msg(“%s\n”, get_curline());

 

5.3.5 read_selection

定义

inline bool

read_selection(ea_t *ea1, ea_t *ea2)

含义

把用户选定的区域的开始和结束地址,分别填充到*ea1*ea2

示例

#include <kernwin.hpp>

 

ea_t saddr, eaddr;

// 获取选定的地址范围,或者没有选定,

// 则返回false

int selected = read_selection(&saddr, &eaddr);

if (selected) {

    msg(“Selected range: %a -> %a\n”, saddr, eaddr);

} else {

    msg(“No selection.\n”

}

 

5.3.6 callui

定义

idaman callui_t ida_export_data

(idaapi*callui)(ui_notification_t what,...)

含义

用户界面转发函数。允许您调用4.5.2章节列出的事件,以及其它一些ui_notification_t枚举值。callui()的第一个参数,通常是传一个ui_notification_t类型(ui_jumpto,ui_banner,等等),后面跟一些各个事件的参数。

示例

#include <windows.hpp> // 需要HWND的定义

#include <kernwin.hpp>

 

// 对于ui_get_hwnd,callui_t*vptr指针指向结果

// 我们需要转换该结果,因为vptr是一个void指针

HWND hwnd = (HWND)callui(ui_get_hwnd).vptr;

 

// hwndNULL,表示我们工作在IDA控制台模式之下。

 

5.3.7 askaddr

定义

inline int

askaddr(ea_t *addr,const char *format,...)

含义

显示一个对话框,询问用户提供一个地址。开始以*addr作为默认值,然后当点击OK后,填充addr为用户提供的地址。*format为显示在对话框中的printf输出格式。

示例

#include <kernwin.hpp>

 

// 设置默认值为文件的入口点

ea_t addr = inf.startIP;

// 当用户输入一个地址

askaddr(&addr, “Please supply an address to jump to.”);

// 移动光标到用户输入的那个地址(参看5.3.2章节)

jumpto(addr);

 

5.3.8 AskUsingForm_c

定义

inline int

AskUsingForm_c(const char *form,...)

含义

为用户显示一个界面,在这里讨论有些麻烦,但是在kernwin.hpp里有更详细的解释。该接口允许您设计自己的用户界面,包括按钮,文本区域,单选按钮和格式化文本。

示例

#include <kernwin.hpp>

 

// 第一个 \n 之前的文本为标题,接下来的为首个输入字段,// (用<>表示),以及后来的第二个输入字段。

// 输入字段的格式为:

// <label:field type:maximum chars:field length:help

// identifier>

// 返回结果分别保存到result1result2

// 要获取输入字段的信息,参看kernwin.hpp

// AskUsingForm_c函数解释。

 

char form[] = “My Title\n<Please enter some text “

              “here:A:20:30::>\n<And here:A:20:30::>\n”;

char result1[MAXSTR] = “”;

char result2[MAXSTR] = “”;

AskUsingForm_c(form, result1, result2);

msg(“User entered text: %s and %s\n”, result1, result2);

 

5.4 入口点

下面的函数可以分析二进制文件的入口点(执行开始之处)。可以在entry.hpp中找到它们。

 

5.4.1 get_entry_qty

定义

idaman size_t

ida_export get_entry_qty(void)

含义

返回当前反汇编文件的入口点数目。通常是返回1,除非是DLL,就会返回更多。

示例

#include <entry.hpp>

msg(“Number of entry points: %d\n”, get_entry_qty());

5.4.2 get_entry_ordinal

定义

idaman uval_t

ida_export get_entry_ordinal(size_t idx)

含义

返回入口点的序数,由idx提供索引参数。您需要这个序数是因为get_entry()get_entry_name()要使用它。

示例

#include <entry.hpp>

 

// 显示所有入口点的序数

for (int e = 0; e < get_entry_qty(); e++)

    msg(“Ord # for %d is %d\n”, e, get_entry_ordinal(e));

5.4.3 get_entry

定义

idaman ea_t

ida_export get_entry(uval_t ord);

含义

返回ord参数对应入口点的地址,使用get_entry_ordinal()获取入口点的序数,参看5.4.2章节

示例

#include <entry.hpp>

 

// 循环搜索每个入口点

for (int e = 0; e < get_entry_qty(); e++)

    msg(“Entry point found at: %a\n”,get_entry(get_entry_ordinal(e)));

5.4.4 get_entry_name

定义

idaman char *

ida_export get_entry_name(uval_t ord)

含义

返回指向入口点地址名称的指针(如,start

示例

#include <entry.hpp>

 

// 循环遍历每个入口点

for (int e = 0; e < get_entry_qty(); e++) {

    int ord = get_entry_ordinal(e);

    // 显示入口点地址和名称

    msg(“Entry point %a: %s\n”,

         get_entry(ord),

         get_entry_name(ord));

}

5.5

 

下面这些函数可以分析域和域控制块,分别在4.2.24.2.3章节描述过。与以前描述的函数有所不同的是,这些函数是areacb_t类的成员函数,所以只能在这个类的实例中使用这些函数。areacb_t的两个实例是funcssegs,表示当前反汇编文件中,所有的函数和段。

尽管您应该使用“段处理”(segment-specific)函数来处理段,还有“函数处理”(function-specific)函数来处理函数,使用域会直接给您更多的处理函数和段的手段。

下面这些都定义在area.hpp

 

5.5.1 get_area

定义

area_t *

get_area(ea_t ea)

含义

返回指向属于eaarea_t结构的指针。

示例

#inlcude <kernwin.hpp>

#include <funcs.hpp>

#include <area.hpp>

 

ea_t addr;

 

// 询问用户输入一个地址

askaddr(&addr, “Find the function owner of address:”);

 

// 获取含有该地址的函数

// 您应该使用segs.get_area(addr)

// 来获取包含该地址的段

areat *area = funcs.get_area(addr);

msg(“Area holding %a starts at %a, ends at %a\n”,

         addr,

         area->startEA,

         area->endEA);

 

5.5.2 get_area_qty

定义

uint

get_area_qty(void)

含义

获取当前域控制块的域数目。

示例

#include <funcs.hpp>

#include <setment.hpp>

#include <area.hpp>

 

msg(“%d Functions, and %d Segments”,

         funcs.get_area_qty(),

         segs.get_area_qty());

5.5.3 getn_area

定义

area_t *

getn_area(unsigned int n)

含义

返回由域序数n指定的,指向area_t结构的指针。

示例

#include <funcs.hpp>

#include <setments.hpp>

#include <area.hpp>

 

// funcs表示所有的函数,因此获取第一个

// 函数域,使用0作为序数

 

area_t *firstFunc = funcs.getn_area(0);

msg(“First func starts: %a, ends: %a\n”,

         firstFunc->startEA,

         firstFunc->endEA);

 

// segs表示所有的段,因此获取第一个

// 段域,也使用0作为序数

area_t *firstSeg = segs.getn_area(0);

msg(“First seg starts: %a, ends: %a\n”,

         firstSeg->startEA,

         firstSeg->endEA);

5.5.4 get_next_area

定义

int

get_next_area(ea_t ea)

含义

返回包含地址ea的域的下一个域的序数。

示例

#include <funcs.hpp>

#include <area.hpp>

 

// 循环遍历所有函数的域

int i = 0;

for (area_t *func = funcs.getn_area(0);

        i < funcs.get_area_qty();

        i++)

{

        msg (“Area start: %a, end: %a\n”,

                 func->startEA,

                 func->endEA);

    int funcNo = funcs.get_next_area(func->startEA);

    funcs = funcs.getn_area(funcNo);

}

5.5.5 get_prev_area

定义

int

get_prev_area(ea_t ea)

含义

返回包含地址ea的域的上一个域的序数

示例

#include <setment.hpp>

#include <area.hpp>

 

// 循环遍历所有的段

int i = segs.get_area_qty();

for (area_t *seg = segs.getn_area(0); i > 0; i--) {

    msg (“Area start: %a, end: %a\n”,

             seg->startEA,

             seg->endEA);

    int segNo = segs.get_next_area(seg->startEA);

    seg = segs.getn_area(segNo);

}

 

5.6

   

    下面的函数用于分析段(.text.idata,等),定义在segment.hpp。大部分都是简单封装segs变量的areacb_t成员函数。

 

5.6.1 get_segm_qty

定义

inline int

get_segm_qty(void)

含义

返回当前反汇编文件中,段的总数。可以见调用segs.get_area_qty()

示例

#include <segment.hpp>

msg(“%d segments in disassemble file(s).\n”

        get_segm_qty());

 

5.6.2 getnseg

定义

inline segment_t *

getnseg(int n)

含义

返回由n参数指定的segment_t结构指针。

示例

#include <segment.hpp>

 

// 获取第一个段的地址

segment_t *firstSeg = getnseg(0);

msg(“Addres of the first segment is %a\n”,

         firstSeg->startEA);

5.6.3 get_segm_by_name

定义

idaman segment_t *ida_export

get_segm_by_name(const char *name)

含义

返回一个指向segment_t结构的指针,该结构对应于*name参数所指定的段。如果不存在这个段,则返回NULL。如果多个段有相同的名称,将返回第一个。

示例

#include <segment.hpp>

 

// 获取对应于.text段的segment_t结构

segment_t *textSeg = get_segm_by_name(“.text”);

msg(“Text segment is at %a\n”,textSeg->startEA);

5.6.4 getseg

定义

inline segment_t *

getseg(ea_t ea)

含义

返回指向对应于段的segment_t结构的指针,该段包含地址ea。这个韩素是segs.get_area()的一个封装形式。

示例

#include <kernwin.hpp>

#include <segment.hpp>

 

// 获取用户光标位置的地址

// 参见5.2.1章节的get_screen_ea()函数

ea_t addr = get_screen_ea();

 

// 获取包含该地址的段

area_t *area = segs.get_area(addr);

msg(“Segment holding %a starts at %a, ends at %a\n”,

         addr,

         area->startEA,

         area->endEA);

 

 

5.6.5 get_segm_name(IDA 4.8)

定义

idaman char *ida_export

get_segm_name(const segment_t *s)

含义

返回区段*s的名称(“_text,”,“_idata”,)

示例

#include <segment.hpp>

 

// 循环遍历所有的区段,并显示它们的名称

for (int i = 0; i < get_segm_qty(); i++) {

    segment_t *seg = getnseg(i);

    msg(“Segment %d at %a is named %s\n”,

             i,

             seg->startEA,

             get_segm_name(seg));

}

5.6.6 get_segm_name(IDA 4.9)

定义

idaman ssize_t ida_export

get_segm_name(const segment_t *s, char *buf, size_t

bufsize)

含义

用区段*s的名称(“_text”,“_idata”,etc)填充*buf,名称长度限定为bufsize。返回区段名称长度,sNULL则返回-1

示例

#include <segment.hpp>

 

// 循环遍历所有区段,并显示它们的名称

for (int i = 0; i < get_segm_qty(); i++) {

    char segName[MAXSTR];

    segment_t *seg = getnseg(i);

    get_segm_name(seg, segName, sizeof(segName)-1);

    msg(“Segment %d at %a is named %s\n”,

             i,

             seg->startEA,

             segName);

}

 

5.7 函数

 

下面介绍处理IDA反汇编文件中的函数的方法。和区段一样,函数也是域。下面这些函数简单地封装了areacb_t的成员方法,它们都定义在funcs.hpp

 

5.7.1 get_func_qty

定义

idaman size_t ida_export

get_func_qty(void)

含义

返回当前反汇编文件中的函数个数。

示例

#include <funcs.hpp>

 

msg(“%d functions in disassembled file(s).\n”,

         get_func_qty());

5.7.2 get_func

定义

idaman func_t *ida_export

get_func(ea_t ea)

含义

返回指向func_t结构的指针,func_t结构表示包含地址ea的函数.如果ea并没有在该函数内,返回NULL。只返回函数的入口块(参看4.2.3.2章节关于函数块和函数尾的信息)。

示例

#include <kernwin.hpp>

#include <funcs.hpp>

 

// 获取用户光标所在位置的地址

ea_t addr = get_screen_ea();

func_t *func = get_func(addr);

if (func != NULL) {

    msg(“Current function starts at %a\n”, func->startEA);

} else {

    msg(“Not inside a function!\n”);

}

5.7.3 getn_func

定义

idaman func_t *ida_export

getn_func(size_t n)

含义

返回一个指针,它指向以func_t结构表示的函数,参数n表示函数序号。如果n是不存在的函数序号,将返回NULL。同样,它也只返回函数的入口块。

示例

#include <funcs.hpp>

 

// 循环遍历所有函数

for (int i = 0; i < get_func_qty(); i++) {

    func_t *curFunc = getn_func(i);

    msg(“Function at: %a\n”, curFunc->startEA);

}

5.7.4 get_func_name

定义

idaman char *char_export

get_func_name(ea_t ea, char *buf, size_t bufsize)

含义

获取拥有地址ea的函数的名称,并把名称保存到*buf,长度限定为bufsize。返回*buf指针,函数没有名称就返回NULL

示例

#include <kernwin.hpp>

#include <funcs.hpp>

 

// 获取用户的光标所在位置的地址

ea_t addr = get_screen_ea();

func_t *func = get_func(addr);

if (func != NULL) {

    // 保存函数名的缓冲区

char funcName[MAXSTR];

if (get_func_name(func->startEA, funcName, MAXSTR)

       != NULL) {

    msg(“Current function %a, named %s\n”,

         func->startEA,

         funcName);

}

}

5.7.5 get_next_func

定义

idaman func_t *

ida_export get_next_func(ea_t ea)

含义

返回一个指针,它指向以func_t结构表示的,拥有地址ea的函数的下一个函数。若没有下一个函数,则返回NULL

示例

#include <kernwin.hpp>

#include <funcs.hpp>

 

ea_t addr = get_screen_ea();

// 获取在包含地址的函数的后一个函数。

func_t *nextFunc = get_next_func(addr);

 

if (nextFunc != NULL)

    msg(“Next function starts at %a\n”,

             nextFunc->startEA);

5.7.6 get_prev_func

定义

idaman func_t *

ida_export get_prev_func(ea_t ea)

含义

返回一个指针,它指向以func_t结构表示的,拥有地址ea的函数的上一个函数。若没有上一个函数,则返回NULL

示例

#include <kernwin.hpp>

#include <funcs.hpp>

 

ea_t addr = get_screen_ea();

// 获取在包含地址的函数的后一个函数。

 

func_t *prevFunc = get_prev_func(addr);

if (prevFunc != NULL)

    msg(“Previous function starts at %a\n”,

             prevFunc->startEA);

5.7.7 get_func_comment

定义

inline char *

get_func_comment(func_t *fn, bool repeatable)

含义

返回用户添加到由*fn表示的函数的注释。如果repeatabletrue,将包括重复性注释。如果该函数没有注释,则返回NULL

示例

#include <funcs.hpp>

 

// 循环遍历所有函数,显示它们的注释,

// 包括重复性注释

for (int i = 0; i < get_func_qty(); i++) {

    func_t *curFunc = get_func(i);

    msg(“%a: %s\n”,

         curFunc->startEA,

         get_func_comment(curFunc, false));

}

5.8 指令

   

    下面这些函数用来分析反汇编文件中的指令。它们定义在ua.hpp,但除了定义在lines.hpp中的generate_disasm_line()

 

5.8.1 generate_disasm_line

定义

idaman bool ida_export

generate_disasm_line(ea_t ea, char *buf, size_t bufsize,

int flags=0)

含义

把地址ea的反汇编代码填充到*buf,长度限定为bufsize。这些反汇编代码是着过色的,所以您需要使用tag_remove()来得到可以正常打印的文本(参看5.20.1章节)

示例

#include <kernwin.hpp>

#include <lines.hpp>

 

ea_t ea = get_screen_ea();

// 将保存反汇编文本的缓冲区

char buf[MAXSTR];

 

// 保存反汇编文本

generate_disasm_line(ea, buf, sizeof(buf)-1);

 

// 显示着色文本的反汇编代码(在IDA的日志

// 窗口可能不太好读)

msg(“Current line: %s\n”, buf);

 

5.8.2 ua_ana0

定义

idaman int

ida_export ua_ana0(ea_t ea)

含义

反汇编地址ea。返回指令的字节数长度,并且把该指令的信息填充到全局cmd结构。如果地址ea处没有指令,返回0。这是只读函数,也不能修改IDA数据库。

示例

#include <kernwin.hpp>

#include <ua.hpp>

 

ea_t ea = get_screen_ea();

 

if (ua_ana0(ea) > 0)

         msg(“Instruction size: %d bytes\n”, cmd.size);

else

         msg(“Not at an instruction.\n”);

 

5.8.3 ua_code

定义

idaman int

ida export ua_code(ea_t ea)

含义

反汇编地址ea。返回指令的字节数长度,并用指令的信息填充全局cmd结构,而且将最后的结果更新IDA的数据库。如果ea地址处没有指令,返回0

示例

#include <kernwin.hpp>

#include <ua.hpp>

 

ea_t saddr, eaddr;

ea_t addr;

 

// 获取用户的选择范围

int selected = read_selection(&saddr, %eaddr);

if (selected) {

    // 重新分析选择的地址区域

    for (addr = saddr; addr <= eaddr; addr++) {

        ua_code(addr);

    }

} else {

    msg(“No selection.\n”);

}

5.8.4 ua_outop

定义

idaman bool ida_export

ua_outop(ea_t ea, char *buf, size_t bufsize, int n)

含义

ea处指令的操作数序号n位置的文本,来填充*buf,长度限定为bufsize,而且如果指令没有被定义,则更新IDA数据库。如果操作数n不存在,则返回false

 

返回到*buf中的文本是着过色的,所以您需要使用tag_remove()来获得可正常打印的文本(参看5.20.1章节)

示例

#include <ua.hpp>

 

// 获取入口点地址

ea_t addr = inf.startIP;

 

// 把入口点的指令信息填充到cmd

 

ua_ana0(addr);

// 循环遍历每个操作数(直到碰到一个o_void类型),

// 并显示操作数文本

for (int i = 0; cmd.Operands[i].type != o_void; i++) {

    char op[MAXSTR];

    ua_outop(addr, op, sizeof(op)-1, i);

    msg(“Operand %d: %s\n”, i, op);

}

5.8.5 ua_mnem

定义

idaman const char *ida_export

ua_mnem(ea_t ea, char *buf, size_t bufsize)

含义

ea处指令的助记符填充到*buf,长度限定为bufsize,如果指令没有被定义好,就更新IDA数据库。返回*buf指针,或者ea处没有指令则返回NULL

示例

#include <segment.hpp>

#include <ua.hpp>

 

// 循环遍历每个可执行区段,并显示

// 每条指令的助记符

for (int s = 0; s < get_segm_qty(); s++) {

    segment_t *seg = getnseg(s);

    is (seg->type == SEG_CODE) {

        int bytes = 0;

 

        // a should always be the address of an

        // instruction, which is why bytes is dynamic

        // depending on the result of ua_mnem()

        for (ea_t a = seg->startEA;

                a < seg->endEA; a += bytes) {

             char mnem[MAXSTR];

             const char *res;

            

        // 获取地址a的指令助记符,并保存到mnem

        res = ua_mnem(a, mnem, sizeof(mnem)-1);

       

        // 如果是一条指令,则显示助记符,

        // 并设置字节数到cmd.size

        // 因此由ua_mnem()处理的下一地址,

        // 即为下一条指令。

        if (res != NULL) {

            msg (“Mnemonic at %a: %s\n:”, a, mnem);

            bytes = cmd.size;

        } else {

            msg (“No code\n”);

            // 如果该地址处没有指令代码,

            // 就把字节数加一,所有ua_mnem()

            // 不会继续处理下一个地址。

            bytes = 1;

       }

    }

  }

}

 

5.9 交叉引用

   

    下面四个函数是xrefblk_t结构的一部分,定义在xref.hpp。它们用来填充和枚举一个地址的交叉引用。所有这些函数以标志作为一个参数,标志可以是如下的一个,同样取自在xref.hpp

 

#define XREF_ALL         0x00       // 返回所有引用

#define XREF_FAR         0x01       // 不返回普通流程引用

#define XREF_DATA        0x02       // 只返回数据引用

 

普通流程(ordinary flow)指从一条指令直接执行下一条指令,并不通过CALLJMP(或等效跳转指令)。如果您只对代码交叉引用(忽略普通流程)感兴趣,那么您应该使用XREF_ALL,并在任何情况下都检查xrefblk_tisCode成员是否为true。如果您只对数据引用感兴趣,就使用XREF_DATA

 

5.9.1 first_from

定义

bool

first_from(ea_t from, int flags)

含义

用来自from地址的首个交叉引用,来填充xrefblk_t结构。flags参数表示您感兴趣的交叉引用。

示例

#include <kernwin.hpp>

#include <xref.hpp>

 

ea_t addr = get_screen_ea();

xrefblk_t xb;

if (xb.first_from(addr, XREF_ALL)) {

    // xb开始被填充

    msg(“First reference FROM %a is %a\n”, xb.from,

             xb.to);

}

5.9.2 first_to

定义

bool

first_to(ea_t to,int flags)

含义

用引用到to地址的首个交叉引用,来填充xrefblk_t结构。flags表示您感兴趣的交叉引用。如果没有to没有引用,则返回NULL

示例

#include <kernwin.hpp> // For get_screen_ea() definition

#include <xref.hpp>

ea_t addr = get_screen_ea();

xrefblk_t xb;

if (xb.first_to(addr, XREF_ALL)) {

// xb is now populated

msg("First reference TO %a is %a\n", xb.to,

xb.from);

}

 

 

5.9.3 next_from

定义

bool

next_from(void)

含义

from地址的下一个参考引用,填充xrefblk_t结构。如果没有其它的参考引用,则返回false

示例

#include <kernwin.hpp>  // For get_screen_ea() definition

#include <lines.hpp>    // For tag_remove() and

// generate_disasm_line()

#include <xref.hpp>

xrefblk_t xb;

ea_t addr = get_screen_ea();

// Replicate IDA 'x' keyword functionality

for (bool res = xb.first_to(addr, XREF_FAR); res;

res = xb.next_to()) {

char buf[MAXSTR];

char clean_buf[MAXSTR];

 

// Get the disassembly text for the referencing addr

generate_disasm_line(xb.from, buf, sizeof(buf)-1);

 

// Clean out any format or colour codes

tag_remove(buf, clean_buf, sizeof(clean_buf)-1);

msg("%a: %s\n", xb.from, clean_buf);

}

5.9.4 next_to

定义

bool

next_to(void)

含义

用引用到to地址的交叉引用信息,填充xrefblk_t结构。如果没有其它的交叉引用,则返回false

示例

#include <kernwin.hpp> // For get_screen_ea() definition

#include <xref.hpp>

xrefblk_t xb;

ea_t addr = get_screen_ea();

// Get the first cross reference to addr

if (xb.first_to(addr, XREF_FAR)) {

if (xb.next_to())

msg("There are multiple references to %a\n",

addr);

else

msg("The only reference to %a is at %a\n",

addr, xb.from);

}

 

5.10 名称

    下面的函数处理有IDA或用户所设置的函数(sub_*),位置(loc_*)和变量(arg_*,var_*)的名称。定义在name.hpp。而寄存器名称不能被这些函数识别。

 

5.10.1 get_name

定义

idaman char *ida_export

get_name(ea_t fromea_t eachar *bufsize_t bufsize)

含义

ea的非着色名称填充*buf,长度限定为bufsize。如果ea有一个名称,就返回*buf指针,否则返回NULL。如果您在一个名称后面是一个函数的位置,from也应该位于同样的函数内,或者它不可见。如果您不是在一个位置名称后面,from应该为BADADDR

示例

#include <name.hpp>

char name[MAXSTR];

// Get the name of the entry point, should be start

// in most cases.

char *res = get_name(BADADDR,

inf.startIP, // Entry point

name,

sizeof(name)-1);

if (res != NULL)

msg("Name: %s\n", name);

else

msg("No name for %a\n", inf.startIP);