0%

IDA Pro阅读笔记

《IDA Pro权威指南(第二版)》阅读笔记

趁着上班没有样本分析的时间把这本书看完吧!记录一些自己印象深刻的知识点。有些自己已经比较熟悉的点不会列出来了。

第一部分 IDA简介

第1章 反汇编简介
  1. 模糊测试:模糊测试是一种发现漏洞的技术,它为程序输入大量不常见的输入,希望其中某些输入能对程序造成可被检测、分许,最终被利用的错误。
  2. x86汇编语言的两种语法:Intel、AT&T,gdb使用AT&T语法,MASM、TASM、NASM使用Intel语法。
  3. 反汇编的算法有线性扫描、递归下降两种。
第2章 逆向与反汇编

这一工具主要介绍了一些小工具。

  1. nm:摘要工具,用来列举目标文件的符号。

  2. ldd:摘要工具,用来列举可执行文件所需的动态库,这才想起来之前做Pwn的时候,看到有别人用这个命令查看可执行文件的依赖库。

第3章 IDA Pro背景知识

这一章主要讲了IDA的开发历史及其反盗版的一些措施,大致介绍了本书的一些关键性内容,其中讲插件的章节是在第17章,这是后面我要详细看的地方。

第二部分 IDA基本用法

第4章 IDA入门

介绍了IDA使用的一些小技巧,并没有值得我记录的,要说我积累出来的就是EscCtrl+W的使用了,还有Space,Tab,N,Y,A这些快捷键的使用,得益于参加的之前的实验室的项目。

  1. 当你不小心拖动了一些窗口后,发现想移动却移动不回去了,使用Windows > Reset Desktop可以恢复原始的布局。
第5章 IDA数据显示窗口

介绍了IDA的一些常见的显示窗口,我算是比较熟悉了,这里就不记录了。
经常用到的有反汇编窗口、16进制窗口、字符串窗口、导入窗口、导出窗口、伪代码窗口。

第6章 反汇编导航
  1. 对栈帧有了比较新的认识:

    编译器通过栈帧(也叫做激活记录)使得对函数参数和局部变量进行分配和释放的过程对程
    序员透明。在将控制权转交给函数之前,编译器会插入代码,将函数参数放入栈帧内,并分配足
    够的内存,以保存函数的局部变量。鉴于栈帧的结构,该函数的返回地址也存储在新的栈帧内。
    使用栈帧使得递归成为可能,因为每个递归函数调用都有它自己的栈帧,这恰好将当前调用与前
    一次调用分隔开来。

  2. 对fastcall的新认识:

    fastcall约定是stdcall约定的一个变体,它向CPU寄存器(而非程序栈)最多传递两个参数。
    Microsoft Visual C/C++ 和GNU gcc/g++(3.4及更低版本)编译器能够识别函数声明中的fastcall
    修饰符。如果指定使用fastcall约定,则传递给函数的前两个参数将分别位于ECX和EDX寄存器
    中。剩余的其他参数则以类似于stdcall约定的方式从右到左放入栈上。同样与stdcall约定类似
    的是,在返回其调用方时,fastcall函数负责从栈中删除参数。下面的声明中即使用了fastcall
    修饰符:

    1
    2
    3
    4
    5
    6
    7
    void fastcall demo_fastcall(int w, int x, int y, int z);
    ; demo_fastcall(1,2,3,4); //programmmer calls demo_fastcall
    push 4
    push 3
    mov edx, 2
    mov ecx, 1
    call demo_fastcall
第7章 反汇编操作

也是自己比较熟悉的地方吧,数据的转换,数组的设置,函数的操作,类型的设置,在自己实习的时候做样本分析时都体验过了,那个远控木马至今印象深刻。

第8章 数据类型与数据结构
  1. 关于数组的成员的访问,本章中列出了三种类型的数组:全局变量的数组、栈生成的数组以及堆生成的数组。前两种数组的访问方式基本一致,都是直接获取数组变量的地址来进行赋值,而堆生成的数组由于是由malloc或者new等方法动态得到的,在IDA中静态分析时并不能得到其真正的地址,所以需要通过寄存器先获取其基址,再根据偏移访问其成员,这和结构体的访问类似。
  2. 同样也有对结构体的访问,也包括三中类型:全局的、栈分配的、堆分配的。如果一个结构体在堆中分配,那么对编译器来说,引用该结构体的唯一线索就是指向该结构体起始地址的指针。而另外两种在编译时是能够计算出其固定地址的。
  3. 解析头文件:要解析头文件,可以使用File>Load File>Parse C Header File(文件>加载文件>解析C 头
    文件)选择你想要解析的头文件。
  4. 遇到虚函数的问题:虚函数是用于实现多态行为的,虚函数的地址在编译时不能确定,只能在即将调用进行时确定(通过虚函数表)。

    且C++中程序会以ecx作为this指针的载体传递给虚成员函数。

第9章 交叉引用与绘图功能
  1. 三种代码交叉引用:读取交叉引用、写入交叉引用、偏移交叉引用。

    一般而言,以一个程序指令字节为目标的写入交叉引用表示这是一段自修改代码,这种代码通常被视为无效代码,在恶意软件使用的“去模糊例程”(de-obfuscation routine)中经常可以发现这类代码。
    偏移量交叉引用表示的是引用的是某个位置的地址,并非内容。

  2. 关于基本块

    在计算机程序中,基本块是一条或数条指令的组合,它拥有唯一一个指向块起始位置的入口点和唯一一个指向块结束位置的退出点。
    一般来说,除最后一条指令外,基本块中的每条指令都将控制权转交给它后面的“继任”指令。同样,除第一条指令外,基本块中的每条指令都从它“前任”指令那里接收控制权。
    通常,为判定基本块,应忽略函数调用指令并未将控制权转交到当前函数这一事实,除非已知被调用的函数无法正常返回。基本块在行为方面有一个重要的特点,即一旦基本块中的第一条指令开始执行,块中的其他指令都会执行,直到最后一条指令。这个特点会对程序的运行时检测产生重大影响,因为这时不再需要为程序中的每一条指令设置一个断点,或者逐步执行程序,以记录程序执行的每一条指令。相反,你可以为每个基本块的第一条指令设置断点,当这些断点被触发时,相关块中的每一条指令都被标记为“已执行”。Pedram Amini 的PaiMei③框架中的Process Stalker 组件就是以这种方式执行的。

  3. F12可以生成一个函数的外部流程图,不过很难看!Ctrl+F12可以生成整个二进制文件函数调用图,也很难看!

  4. 选定一个函数可以生成以其为源头或者目标的交叉引用图,View->Graphs->Xrefs from/to.

  5. 图形化的其他工具:

    自IDA 引入集成化反汇编图形模式后,这些困难有一部分得到了解决。但是,IDA 主要是一个反汇编器,生成图形并不是它的主要用途。对专用的图形分析工具感兴趣的读者可以研究专门用于此类目的的应用程序,如BinNavi,这款工具是Halvar Flake 的公司Zynamics开发的。

第10章 IDA的多种面孔

介绍了IDA的控制台模式,在各种平台上的不同面孔,目前我只需关注Windows上的IDA控制台版本(idaw.exe),其实没什么好说的,书中的实际应用也还没有接触过。
另外还提了一下IDA的批量模式,批量模式是为了自动处理任务,这似乎和我后面打算做的插件有点关系,先在这儿提一下吧,有具体的参数说明。

使用批量模式的主要目的是启动IDA,使它运行一段特定的IDC 脚本,并在该脚本完成后立即终止。

第三部分 IDA高级应用

第11章 定制IDA

ida目录下的cfg目录中的ida.cfg, idagui.cfg, idatui.cfg是IDA的设置文件,另外也可以新建一个idauser.cfg来重写其中的设置,这样便于在不同的ida中使用。
idagui.cfg是图形界面的配置,idatui.cfg是控制台模式的配置。

第12章 使用FLIRT签名来识别库

FLIRT签名技术是用来识别可执行文件中的一些静态库的函数的,这样可以使逆向分析人员专注于程序本身的代码的分析。

  1. 关于Main_start:

    程序的入口点是即将执行的第一条指令的地址。因此,许多熟练的C 程序员错误地认为这就是main 函数的地址,但事实并非如此。程序的文件类型,而不是创建程序所使用的语言,决定了向这个程序提交命令行参数的方式。为了使加载器加载命令行参数的方式与程序预期接收参数的方式(例如,通过向main 提交参数)保持一致,程序必须在将控制权转交给main 之前执行一段初始化代码。IDA 将这段初始化代码作为程序的入口点,并将其标记为_start。
    这段初始化代码还负责必须在main 运行之前完成的初始化任务。在C++程序中,这段代码负责确保在执行main 之前调用全局声明对象的构造函数。同样,为了在程序真正终止前调用所有全局对象的析构函数,必须插入在main 之后执行的清理代码(cleanup code)。

  2. 如何生成签名文件:

    创建签名文件的基本过程听起来并不复杂,可以归结为4 个看似简单的步骤。
    a. 获得一个你希望为其创建签名文件的静态库。
    b. 利用其中一个FLAIR 解析器为该库创建一个模式文件。
    c. 运行sigmake.exe 来处理生成的模式文件,并生成一个签名文件。
    d. 将新的签名文件复制到<IDADIR>/sig 目录中,安装这个文件。

在之前的CTF经历中遇到过需要自己制作sig文件的题目,但是当时自己并没有仔细看,现在大概应该懂了。

第13章 扩展IDA的知识

这一章主要是介绍til和ids的扩展功能,其主要作用就是让用户可以自定义设置自己的函数声明相关配置文件,这样在IDA自动分析的过程中就会识别出对应的函数的调用约定、参数类型、数量等。以后遇到具体的应用的时候可以再来重新仔细的看一遍。

  1. 库函数中函数序号:

    序号是与每个导出函数有关的整数索引。使用序号可通过整数查询表迅速定位一个函数。若通过将函数名称与字符串进行比较来定位函数,则很缓慢。

  2. 创建ids文件:

    IDA 的idsutils 实用工具用于创建.ids 文件。这些实用工具包括两个库解析器:从Windows DLL 中提取信息的dll2idt 和从ar 库中提取信息的ar2idt.exe。无论使用哪一个解析器,其输出都是一个.idt 文本文件,它每行显示一个导出函数,并将导出函数的序号与函数名称对应起来。

20191102094833
3. 修改注释,使用loadint实用工具,后续需要花点时间来研究一下怎么创建一个ids文件和til文件。

第14章 修补二进制文件及其他IDA的限制

IDA 并不是一个二进制文件编辑器。任何时候,如果你想要使用IDA 修补一个二进制文件,请记住这个事实。但是,它是一款特别有用的工具,可帮助你输入并显示潜在的更改。掌握IDA的全部功能,并结合IDA 通过适当的脚本或外部程序生成的信息,修补二进制文件也会变得简单可行。

IDA 对二进制文件的修改这一方面感觉确实不行,如果要修改二进制文件,还是用专用的二进制编辑器吧,例如010 Editor、Winhex,扯一句,最近发现Winhex的功能真的挺强大的。

第四部分 扩展IDA的功能

第15章 编写IDA脚本
IDC脚本的特性

IDC语言和C类似,但还是有很多区别的。

IDC使用关键字auto来声明一个局部变量,使用关键字extern来声明一个全局变量,但声明全局变量时不能为其提供初始值;

IDC并不支持C风格的数组,对于字符串这类的变量,支持和python一样的分片操作;

IDC并不严格限制变量的作用域,你可以在变量声明的花括号外面访问变量;

IDC中的Message函数类似于C中的printf函数;

IDC仅支持在独立的.idc文件中声明函数,static关键字用于声明用户自定义的函数,在IDC命令对话框中不能定义函数,因为这相当于在函数中定义函数,而IDC不支持嵌套声明;

IDC也支持类对象,但是不使用public、private类似的访问说明符,所有的成员都是有效公共类,且如果要在类中创建数据成员,只需要一个给类的数据成员的赋值语句就行;

IDC程序文件的基本结构:

1
2
3
4
5
6
#include <idc.idc> //useful include directive
//declare addtional functions as required
static main(){
//do something fun here
AddHotkey("z", "func"); //Now 'z' invokes
}
IDC常用的函数
  1. 读取和修改数据的函数

    long Byte(long addr),从虚拟地址addr 处读取一个字节值。

    long Word(long addr),从虚拟地址addr 处读取一个字(2 字节)值。

    long Dword(long addr),从虚拟地址addr 处读取一个双字(4 字节)值。

    void PatchByte(long addr, long val),设置虚拟地址addr 处的一个字节值。

    void PatchWord(long addr, long val),设置虚拟地址addr 处的一个字值。

    void PatchDword(long addr, long val),设置虚拟地址addr 处的一个双字值。

    bool isLoaded(long addr),如果addr 包含有效数据,则返回1,否则返回0。

  2. 用户交互函数

    void Message(string format, ...),在输出窗口打印一条格式化消息。这个函数类似于C 语言的printf 函数,并接受printf 风格的格式化字符串。

    void print(...),在输出窗口中打印每个参数的字符串表示形式。

    void Warning(string format, ...),在对话框中显示一条格式化消息。

    string AskStr(string default, string prompt),显示一个输入框,要求用户输入一个字符串值。如果操作成功,则返回用户的字符串;如果对话框被取消,则返回0。

    string AskFile(long doSave, string mask, string prompt),显示一个文件选择对话框,以简化选择文件的任务。你可以创建新文件保存数据(doSave=1),或选择现有的文件读取数据(doSave=0)。你可以根据mask(如*..idc)过滤显示的文件列表。如果操作成功,则返回选定文件的名称;如果对话框被取消,则返回0。

    long AskYN(long default, string prompt),用一个答案为“是”或“否”的问题提示用户,突出一个默认的答案(1 为是,0 为否,-1 为取消)。返回值是一个表示选定答案的整数。

    long ScreenEA(),返回当前光标所在位置的虚拟地址。

    bool Jump(long addr),跳转到反汇编窗口的指定地址。

  3. 字符串操纵函数

    • string form(string format, …)//preIDA5.6,返回一个新字符串,该字符串根据所提供的格式化字符串和值进行格式化。这个函数基本上等同于C 语言的sprintf 函数。
    • string sprintf(string format,…)//IDA5.6+,在IDA5.6 中,sprintf 用于替代form(参见上面)。
    • long atol(string val),将十进制值val 转换成对应的整数值。
    • long xtol(string val),将十六进制值val(可选择以0x 开头)转换成对应的整数值。
    • string ltoa(long val, long radix),以指定的radix(2、8、10 或16)返回val 的字符串值。
    • long ord(string ch),返回单字符字符串ch 的ASCII 值。
    • long strlen(string str),返回所提供字符串的长度。
    • long strstr(string str, string substr),返回str 中substr 的索引。如果没有发现子字符串,则返回-1。
    • string substr(string str, long start, long end),返回包含str 中由start 到end-1位置的字符的子字符串。如果使用分片( IDA5.6 及更高版本), 此函数等同于str[start:end]。
  4. 文件输入输出函数

    • …这部分有点多了,单独写一篇出来吧,还有后面的IDAPython的相关函数,以便之后写插件的时候用到。
IDAPython编写插件
  • 编写python脚本需要导入三个核心的模块:idaapi、idautils、idc。
  • 需要定义一个plugin类作为插件的入口函数,参数idaapi.plugin_t是IDA的插件类,后续会讲到,例如:
1
2
classs PluginName_Plugin_t(idaapi.plugin_t):
...
  • 然后需要注册plugin:
1
2
3
# register IDA plugin
def PLUGIN_ENTRY():
return PluginName_Plugin_t()
第16章 IDA软件开发工具包

这一章讲的是IDA的SDK的使用方法,涉及到SDK的安装配置、应用编程接口,总的来说是让你如何系统的去使用IDC编写IDA的插件的这样一个体系结构。

有用的SDK数据类型:area_t, func_t, segment_t, idc_value_t, idainfo, struc_t, member_t, op_t, insn_t. 这些类型标明了IDA中的各个功能展现的数据形式,也就是可以通过编程将其进行操纵。

这一章也有一些常用的SDK函数,同上一章一样,我会单独将其列出来,方便以后查找,这一章的这些函数与IDC脚本相比,能够进行更加细化的控制。

第17章 IDA插件体系结构

插件是经过编译的、功能更加强大的IDC脚本。

  1. 插件的安装:我先挑最简单的来说,安装插件只需要将编译好的插件模块复制到<IDADIR>/plugins目录中即可,不过需要注意的是尽量在完全关闭IDA的情况下进行此操作,因为Windows系统不能覆写一个正在使用的插件,而在IDA打开的时候,你想要的覆写的插件可能设置了PLUGIN_FIX标志且证在运行。

  2. 编写插件:这里把插件上升为一个模块来解释,IDA模块都是以适用于执行插件的平台的共享库组件实现,而每个模块必须导出某个特定类的一个变量,对插件来说,就是plugin_t,这个类在SDK的loader.hpp文件中定义。【稍微查了一下:hpp = h + cpp, 先简单这么理解吧】
    plugin_t类中的字段包括:version, flags, init, term, run, comment, help, wanted_name, wanted_hotkey.
    以上的成员就涵盖了编写一个插件需要做的大致事务,根据名称能简单推断出各个字段的函数,其中term字段是一个函数指针,当插件卸载时,调用相关函数。【term即为terminate的缩写,好像我挺习惯这样记忆的】

  3. 构建插件:在Windows上,插件是有效的DLL文件(.plw/.p64),在Linux/Mac上,插件则是有效的共享文件对象(.plx/plx64, .pmc/pmc64)。构建插件需要简化版本的生成文件(makefile)和GNU工具,在Windows上使用MinGW。

  4. 可以在已编译的插件中扩展IDC,但现在还没有办法扩展IDAPython。

  5. 脚本化插件:创建Python插件的过程非常简单,只需要定义一个名为PLUGIN_ENTRY的函数,该函数返回plugin_t(在模块idaapi中定义)的一个实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    from idaapi import *

    class idabook_plugin_t(plugin_t):
    flags = 0
    wanted_name = "IdaBook Python Plugin"
    wanted_hotkey = "Alt-8"
    comment = "IdaBook Python Plugin"
    help = "Something helpful"

    def init(self):
    msg("IdaBook Python Plugin init called.\n")
    return PLUGIN_OK

    def term(self):
    msg("IdaBook Plugin term called.\n")

    def run(self, arg):
    warning("IdaBook plugin run(%d) called" % arg)

    def PLUGIN_ENTRY():
    return idabook_plugin_t()

上面是使用IDAPython写的一个小型插件,下面是使用IDC写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <idc.idc>

class idabook_plugin_t(){
idabook_plugin_t(){
this.flags = 0;
this.wanted_name = "IdaBook IDC Plugin";
this.wanted_hotkey = "Alt-9";
this.comment = "IdaBook IDC Plugin";
this.help = "Something helpful";
}

init(){
Message("IdaBook plugin init called.\n");
return PLUGIN_OK;
}

term(){
Message("IdaBook plugin term called.\n");
}

run(arg){
Warning("IdaBook plugin run(%d) called.\n", arg);
}
}

static PLUGIN_ENTRY(){
return idabook_plugin_t();
}
```
第18章 二进制文件与IDA加载器模块

粗略看了一下,感觉未来一段时间暂时不会接触到,主要是讲如何去构建一个简单的加载器来识别未知的文件类型什么的,想到这,要是以后自己写出某个文件的IDA加载器放在github上,应该能体现我的编码能力吧。

第19章 IDA处理器模块

本章介绍IDA处理器模块的构建流程,是IDA模块化中最复杂的,我感觉比较硬核了,未来一段时间自己也不会接触吧,这也是我想买一本这个实体书的原因,万一后面有需要用到可以再来仔细看看。在本章中介绍的是针对Python的编译文件pyc的逆向处理器的构建过程,同上一章有类似之处。

第五部分

后面的这两部分打算略过看吧,有些技术深度确实现在看看不懂,就只细看我要去学习的相关章节吧。

第20章 编译器变体

源代码相同,但是编译器不同,也就是构建选项不同所带来的二进制代码是截然不同的,这是因为编译器选择了不停的算法来实现各种高级结构。

这一章中提到了switch代码在Borland编译器和Visual C++下不同的的汇编代码结构,前者将跳转表嵌入进了函数当中,后者将跳转表放在邻近函数位置处,更好的区分了数据和代码,不会给程序的行为造成影响。

第21章 模糊代码分析

模糊代码,反逆向分析。模糊是一种是某物变得模糊、混乱,令人困惑、迷惑,以防止他人理解被模糊的项目的行为。这一章介绍了主流的模糊代码技术,是我要阅读的重点。

1.反静态分析技巧
  • call/jmp loc_XXXX + 1:形如这样的汇编代码,执行了一次调用,调用对象就在下一个指令的中间,由于IDA认为这个函数会返回,则继续反汇编接着的地址,然后真正的被调用指令却没有被反汇编。
  • xor eax, eax jz loc_XXXX :这两句汇编等于无条件跳转jmp,其与loc_XXXX之间的代码从未执行,仅起到迷惑分析人员的作用。

a. 动态计算目标地址

这里的动态计算指的是接下来的执行地址在运行时计算得出。

  • 通过操作寄存器运算,得到真正的执行地址,然后将其赋值给栈顶处,再执行返回执行,就能跳转到执行地址,也就是(push,ret)结合的一种形式,在之前练习pwn的入门题目时遇到过。
  • 通过FS段存放的异常函数指针链表,设置异常,调用异常处理函数,根据操作系统提供的CONTEXT结构体的参数信息恢复程序执行流程。
  • 加壳处理,UPX,ASPack,ASProtect,tElock,Burneye,Shiva,VMProtect。

b. 导入的函数模糊

这类技巧可以令dumpbin,ldd,objdump等工具失效,无法列出库依赖关系。

  • 使用Loadlibrary和GetProcAddress动态获取函数地址,使用函数。

  • 由于每一个Windows程序都会加载kernel32.dll,而上一种方法中的两个函数都处于这个dll中,所以可以通过获取kernel32.dll的导入表,查找相关函数,对比hash值,定位具体的函数入口地址,实现函数功能。【这里参见Skape的论文“Understanding Windows Shellcode”,这个方法也是在之前我遇到的一个样本中出现的方法,现在看起来真是醍醐灌顶啊。】

2.反动态分析技巧

这部分同反调试的技术差不多,检测虚拟化、检测调试器、反调试标志的设置,此处略过。

3. 使用IDA对二进制文件进行“静态去模糊”

面向脚本的去模糊

基于脚本的去模糊方案无法构建“万能式”的解决方案,现有包装器的任何细微变化都会使得脚本失效。脚本的针对性比较强。【这也导致我突然意识到我做的毕设似乎没有那么简单】

面向模拟的去模糊

基于模拟执行来实现去模糊,介绍了一个ida的插件x86emu,作为一个IDA的嵌入式x86指令的模拟器,后续对我的帮助也该会挺大的吧。

基于虚拟机的模糊

这主要是说VMProtect,感觉到达自己的知识瓶颈,我已经完全无法理解了。

当前,恶意软件大多是模糊程序。因此,如果你希望研究一个恶意软件样本的内部运行机制,几乎可以肯定,你需要完成某种类型的去模糊任务。无论你是采用调试器辅助的动态去模糊方法,还是不想运行可能恶意的代码,而选择使用脚本或模拟对二进制文件进行去模糊处理,你的最终目标都是生成一个可以被完全反汇编、正确分析的去模糊二进制文件。多数情况下,最后的分析都要由IDA 之类的工具来完成。鉴于此(即使用IDA 进行分析),尝试从头至尾使用IDA 似乎有一定的道理。


注:暂时写到这儿吧,后面的部分我感觉我看了收获也不是太多吧,以后接触到再来看吧,觉得入手一本实体书,闲来无事翻一翻!