逆向基础03


使用IDA分析程序

IDA 的快速启动界面(打开一个可执行文件)

image-20191218083159935

打开一个新的应用程序时,IDA的自动识别功能

image-20191218083418367

关闭IDA的时候,会自动的提示是否需要保存当前的数据文件(.idb),文件中存储了分析的过程中存在的函数名称等数据,以及 EXE 程序的本体,以后再次进行分析就不需要源文件。

image-20191218084225743

认识IDA的界面

主界面的默认布局如下,其中保存了多个不同的窗口,如果不小心关闭了窗口,可以通过菜单中的 View -> Open Subview 再次打开指定的窗口,每一个窗口都可以同时存在多个

image-20191218091339936

IDA 代码显示界面

image-20191218091926595

IDA 的内存窗口使用,可以通过右键修改内存的显示方式

image-20191218092906818

IDA 导出窗口,查看导出函数,找到 OEP

image-20191218093159151

IDA 导入窗口,查看导入函数

image-20191218093351847

IDA 名称窗口,可以通过 F1 查看具体的类型

image-20191218093732115

IDA 的函数窗口,可以查看函数的信息

image-20191218094253260

IDA 字符串窗口,配合交叉引用有很大的作用

image-20191218094629768

IDA 区段窗口:使用的比较少,主要是查看区段属性

image-20191218094748186

IDA 签名窗口(Shift + F5 ),合理的使用签名可以让 IDA识别更多的函数,尽量打开一个程序的第一件事情就是加载签名,主要会识别一些库函数,防止不小心分析系统模块

image-20191218095156793

IDA 的类型窗口,可以添加内置类型

image-20191218095355001

本地类型窗口,当前已经可以使用的一些内置结构体

image-20191218095444018

如何查找 main 函数

image-20191218101816256

image-20191218101905909

image-20191218102039613

使用 OD 查找 main 函数: 1 + 2 + 3(三个间隔的函数中间一个) + 4

image-20191218102350963

image-20191218102439791

image-20191218102528138

image-20191218102627002

image-20191218102803453

image-20191218102937009

image-20191218103057238

image-20191218103124110

使用IDA分析程序

局部变量

1
2
3
4
5
6
7
8
9
10
int _tmain(int argc, _TCHAR* argv[])
{
// 局部变量
int nNum = 1;
float fNum = 2.5;
char ch = 'A';

printf("int %d, float %f, char %c", nNum, fNum, ch);
return 0;
}

使用快捷键 n 可以给函数或者变量或者代码修改名称

  • 如果不能确定一个函数的实际名称,尽量不要使用内置的名称

  • 对于 IDA 来说,所有的操作都是不可逆的,可以使用 ESC 返回上一层,使用 ctrl+enter 进入函数

IDA中可以使用冒号(:)和分号(;)对代码进行注释,合理的注释可以更方便的分析

image-20191218104404469

可以使用 y 或者菜单项修改变量的类型

image-20191218110133693

全局变量

1
2
3
4
5
6
7
8
9
10
11
// 全局变量,静态变量
int g_nNum = 1;
static int g_nCount = 2;
float g_fNum = 2.5;
char g_ch = 'A';
int _tmain(int argc, _TCHAR* argv[])
{
printf("int %d, float %f, char %c",
g_nNum, g_fNum, g_ch);
return 0;
}

交叉引用:

  • 函数的引用: 查找一个函数被哪一些地方调用
  • 数据的引用: 查找一个数据被哪一些地方使用

image-20191218111329371

对于已经初始化的数据,实际没有代码进行赋值,初始化操作是编译器完成的

image-20191218111504596

数组类型

1
2
3
4
5
6
7
int _tmain(int argc, _TCHAR* argv[])
{
int nArr[5] = { 1, 2, 3, 4, 5 };
int n = 2;
nArr[n] = 20;
return 0;
}

函数的模拟栈帧

image-20191218112120979

将类型转换成数组的操作

image-20191218112548932

如果不小心修改了代码,并且无法还原,可以执行下面的操作

  • u : 表示取消这个函数的定义,取消后就是字节
  • c : 将目标看作是一段代码进行解释
  • p : 将目标看作是一个函数解释,解释函数的过程中会重新构建模拟栈帧

结构体类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct MyStruct{
int nNum;
float fNum;
char chA;
};
void Print(MyStruct stc)
{
printf("int %d, y %f, z %c", stc.nNum, stc.fNum, stc.chA);
}
int _tmain(int argc, _TCHAR* argv[])
{
MyStruct stc = { 1, 2.2, ‘A’ };
stc.fNum = 5.5;
Print(stc);
return 0;
}

添加一个新的结构体

image-20191218113959148

为结构体添加字段

image-20191218114135872

应用结构体

image-20191218114259750

一些内置函数的特征

CheckEsp 函数: 用于检查堆栈是否平衡,通常在函数调用后使用

1
2
cmp  xxx, esp
call CheckEsp

CheckStackVars 函数: 用于检查数组是否越界,原理是判断数组后的 cc 有没有被覆盖

1
2
lea  edx, dword_411420(全局变量)
call CheckStackVars