首页
社区
课程
招聘
[原创]IDA静态逆向分析模型原理的补充
2021-3-24 16:41 11316

[原创]IDA静态逆向分析模型原理的补充

2021-3-24 16:41
11316

上一篇IDA静态逆向分析模型原理透析比较受到大家的欢迎。所以这一篇笔记,是对上一篇的补充。

 

IDA是用来做手工分析的辅助工具。它的反汇编时间,与程序代码的大小,有直接的关系。大体,我们可以将这个过程分为两个阶段。

  1. 代码与数据的分开,标记各个函数和符号并分析参数调用、参数栈、局部变量栈、跳转等指令的关系,并分析数据结构,自动生成流程图和模块调用关系。
  2. 识别出文件变异类型信息并加载对应特征库。这里用到了FLIRT技术实现,该技术可以通过对比特征码自动找出库函数调用

一、查看PE文件

去除程序保护以后,也就是去壳,会有两个操作。一个是了解文件的类型,另一个是分析文件中使用的API调用或者导出符号,这部分可以通过函数导入表和导出表得到。
了解文件的类型,包括两个方面。

  1. 文件的编程语言和编译器类型,比如C++
  2. 文件的用途,IDA自动根据PE格式获取文件类型是可执行程序、动态链接库、静态链接库、驱动程序等。同时用户可以根据经验通过查看入口函数部分指令序列判断程序类型。

二、分析程序结构

分离各个PE节,分离用户代码和库函数代码,IDA提供PE文件布局图,对于PE文件各个段数据,以及库函数代码和用户代码都做出了区分。红色部分是未识别出的函数,需要使用者手工确定该数据类型是函数,还是数据。

三、识别库函数

IDA使用FLIRT技术识别库函数,加载并标记特征库,并加载相关的数据结构模板

四、识别数据结构

一般来说,IDA可以根据API函数和库函数的相关信息推导出与之关联的变量类型,但是对于用户自定义函数则无能为力,因此IDA提供了自定义数据结构的方法,包括简单数据类型、浮点类型、结构体、枚举类型等。用户可以通过直接修改数据类型、自定义结构体、从C语言头文件导入结构体、从选择的结构体操作代码区域推导结构体。对于代码区隐藏的数据和数据区的结构体,经过上面的操作,可以以与源代码定义数据结构形式最接近的方式呈现给用户。
用户可以手动加载头文件添加数据结构,同时也可以自定义数据结构。

五、代码分析

IDA可以反汇编指令、16进制数据、C语言伪代码、函数关系结构图四种形式展示代码分析结果。由于代码分析过程会产生错误,因此IDA允许用户手动调整,自定义指令序列或数据部分开始的位置。

六、函数识别

加载PE文件后,IDA会自动分析其中的库函数和API函数,并将识别出的函数名替换到函数列表中以便查阅。对于未能识别或识别错误的代码,需要进行手工分析其参数类型、参数个数、起始位置、结束位置、调用方式等,并对IDA的相应参数进行修改,必要时还需要做堆栈平衡分析和修改。在了解函数功能后,最好进行注释,并取符合功能的名称作为函数名。
—————————————————————————————————
下面我将使用该模型,对C++程序进行一次完整的分析
重点,我要放大字体,这里请仔细看
—————————————————————————————————

七、对C++程序的完整分析

实验环境:
操作系统平台:Windows 10 x64
使用的工具:PEID、exeinfope、IDA
测试对象:AltStreamDump v1.05
(如有不当之处,请指正,勿谩骂)

 

介绍一下该软件,AltStreamDump v1.05,主要用来查找指定目录下的NTFS文件数据流。网上的下载地址是:AltStreamDump下载
图片描述

1. 去除软件保护
 

使用PEID和exeinfope来查看文件信息,因为这个工具,可以相互补充。
通过PEID,查看的结果如下:
图片描述
并没有查出编译语言和环境,猜测这里应该是没有相应的特征值。
通过exeinfope,查看结果如下:
图片描述
这里没有加壳,确定软件编写的语言是C++

2. 分析类型
 

查看输入表,结果如下:
图片描述
发现这里使用的是msvcrt.dll。从后面分析的入口可知,它为C运行时库的命令行应用层程序。

3. 识别库函数
 

由于使用了运行时库,因此IDA自动加载的库函数为Microsoft Visual C 2-10/net runtime。使用的类型库为mssdk和vc6win。

4.查找程序的入口
 

使用IDA加载,找到IDA输出表中的系统入口函数start,接着找到用户入口,该函数出现在环境变量初始化之后,退出在函数之前。
这里发现text:0x401914 call sub_401914符合要求,并且该函数调用之前使用了三个压栈指令。
图片描述
说明这里需要三个参数,再根据前面_wgetmainargs获取这三个参数,可以推测,这是宽字符版本的main函数,其符号名和参数类型为:
int wmain(int argc,wchar_t argv[],wchar_t envp[])

这三个参数,我来说明一下。分别是命令行参数,命令行个数,环境变量。

5. 分析函数属性
 

以wmain函数为例(将sub_401914重新标注符号为wmain)。父函数调用该函数前,进行了三次压栈操作。同时再调用后,有一个堆栈平衡。很明显,这里的函数约定采用了——cdecl调用方式。
由于太多,不方便截屏,故代码呈上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
int __cdecl wmain(signed int a1, int a2)
{
  signed int v2; // edi@1
  wchar_t *v3; // eax@2
  HRSRC v4; // eax@9
  HGLOBAL v5; // eax@10
  const char *v6; // eax@11
  HMODULE hLibModule; // [sp+Ch] [bp-454h]@1
  wchar_t *Str; // [sp+10h] [bp-450h]@2
  wchar_t *Str1; // [sp+14h] [bp-44Ch]@2
  int (__stdcall **v11)(int); // [sp+18h] [bp-448h]@1
  int v12; // [sp+1Ch] [bp-444h]@1
  int v13; // [sp+20h] [bp-440h]@14
  wchar_t v14; // [sp+24h] [bp-43Ch]@1
  int v15; // [sp+230h] [bp-230h]@1
  int v16; // [sp+234h] [bp-22Ch]@1
  int v17; // [sp+238h] [bp-228h]@14
  int v18; // [sp+23Ch] [bp-224h]@14
  int v19; // [sp+240h] [bp-220h]@14
  int v20; // [sp+244h] [bp-21Ch]@14
  int v21; // [sp+248h] [bp-218h]@14
  int v22; // [sp+24Ch] [bp-214h]@14
  WCHAR Buffer; // [sp+250h] [bp-210h]@1
  char Dst; // [sp+252h] [bp-20Eh]@1
 
  hConsoleOutput = GetStdHandle(0xFFFFFFF5);
  v14 = 0;
  v16 = 0;
  v12 = 0;
  v11 = &off_4022D8;
  Buffer = 0;
  memset(&Dst, 0, 0x208u);
  GetCurrentDirectoryW(0x104u, &Buffer);
  v15 = 0;
  sub_4013C4(&Buffer);
  v2 = a1;
  for ( hLibModule = (HMODULE)1; (signed int)hLibModule < v2 - 1; hLibModule = (HMODULE)((char *)hLibModule + 1) )
  {
    v3 = *(wchar_t **)(4 * (_DWORD)hLibModule + a2 + 4);
    Str1 = *(wchar_t **)(4 * (_DWORD)hLibModule + a2);
    Str = v3;
    if ( !wcscmp(Str1, L"-d") )
    {
      v15 = 1;
      v16 = wtoi(Str);
    }
    if ( !wcscmp(Str1, L"-f") )
    {
      sub_4013C4(Str);
      v2 = a1;
    }
  }
  if ( v2 > 1 && !wcscmp(*(const wchar_t **)(a2 + 4), L"-h") )
  {
    v4 = FindResourceW(0, (LPCWSTR)0x65, L"BIN");
    if ( v4 && (v5 = LoadResource(0, v4)) != 0 )
      v6 = (const char *)LockResource(v5);
    else
      v6 = 0;
    printf(v6);
  }
  else
  {
    hLibModule = 0;
    sub_401B0D();
    v13 = 0;
    v17 = 0;
    v18 = 0;
    v19 = 0;
    v20 = 0;
    v21 = 0;
    v22 = 0;
    sub_401004(&v14, 0);
    sub_401848();
    wprintf(L"\r\n\r\nType AltStreamDump -h to get more information about this tool.\r\n");
    if ( hLibModule )
      FreeLibrary(hLibModule);
  }
  return 0;
}

由于IDA已经识别出该处代码,函数结尾之后的指令段已经处于其他函数范围,因此终止位置正确。

6. 分析局部变量
 

依然是以wmain函数为例,函数序言部分有指令sub esp,454h,
可见局部栈变量使用了不少于454h字节的空间,因此需要从函数头部开始分析栈使用情况判断使用了哪些变量及这些变量的作用域。
图片描述

7. 分析函数功能
 

以分析text:00401004 sub_401004为例,如下图,使用的API调用的序列为:
图片描述
当我们对程序中的调用函数,一一分析以后,可以得到下面的结论:
GetCurrentProcess、LoadLibraryW“advapi32.dll”、OpenProcessToken 、LookupPrivilegeValueW、AdjustTokenPrivileges、Closehandle
通过在Windows官网API查询,可以知道,这是系统的提权。

8.分析函数算法
 

以分析出来的类成员函数RecurseFind为例。该函数采用回溯法遍历当前目录及其所有子目录,并分析和显示其中文文件的数据流信息。具体的流程分析结果如图。
图片描述
为了容易理解,我用了两个开始和结束。

9. 分析类
 

以wmain函数中的类为例。在分析多个函数中的代码时,发现调用很多函数之前都会设置esi寄存器,同时在被调用函数中,也在未初始化状态下直接使用该寄存器,因此可以假设该寄存器存放的是this指针。
我们可以发现该函数申请了很大的局部变量空间,对于类成员函数调用的线索,可以看到该函数最后在text:00401AC0处有一条指令为:
lea esi,[esp+460h+var_448]
然后又调用子函数sub_401848,而且在该子函数中,直接使用了esi,因此认定这个函数为成员函数(所有使用了this指针的函数,都可以看成是成员函数)。
同时关注之前esi和var_448的操作情况,将var_448作为栈上存储类的对象空间开始处,对于其结束位置,需要我们从两个方面推测,一方面是wmain函数在该栈上该类空间起始位置后的第一个其他变量所在位置,该对象在栈上的结束位置不可能超过这个值。另一方面,esi是this指针这个信息从函数列表中所有的子函数中进行搜索,查看对于this指针的最大偏移数,由于对象并没有赋予虚表指针的情况,因此最大的偏移数就可以假定是临近最后的成员变量位置。
根据这种方法综合分析,得到程序中的两个自命名类,一个是用于控制显示部分的MainClass,另一个用于文件查找的FindFileClass。这样就根据this指针确定所有成员函数和类成员。另外调用第一个成员函数之前存在对对象成员变量域的赋值操作,这种操作极有可能是类构造函数采用了优化内联的形式存在于wmain函数中。对于是否为构造函数,可以通过该类每次出现于内存中是否都执行了构造函数这个本质进行验证,如果不是则认为它是一般的成员函数,对于析构函数同理。
分析得到的两个简单的类结构,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class FindFileClass
{
public:
    FindFileClass();
    void GenSearchString(LPCNSIN Path);
    Bool EnumNextFile();
    void ReleaseFile();
Public:
    HANDLE   hFindFile;
    WCHAR    LastFileName[324];
    WCHAR    CurrentFileName[324];
    WCHAR    CurrentFolderPath[324];
}
1
2
3
4
5
6
7
class MainClass{
public:
    MainClass();
    void GoSearch();
private:
    void ShowStaticinfo();
}
10. 修正函数的属性
 

将所有手工分析处的函数,对于IDA分析其参数类型、参数个数、起始位置、终止位置等不对的地方,在函数列表窗口选择函数并右键选择菜单中的修改函数选项。

11.分析异常处理
 

这里没有在用户函数发现于异常处理相关的函数

12. 分析RTTI信息
 

一般经过编译器编译后,都会对代码进行一些优化,所以这里也不做考虑。

八、重建开发文档

AltStreamDump v1.05 Copyright@2011-2012NirSofer
系统配置:不支持Wind10.支持windows2000到Windows7系统
使用说明:不需要任何安装过程或附加dll文件,可以直接打开命令提示符就可以运行该程序。程序默认显示当前目录的文件数据流。
可以通过-f 和-d 命令行参数查看其他文件夹的文件数据流
命令行参数:
-h:显示命令行帮助
-f:指定要搜索的目录
-d:用于指定要搜索的父目录深度(0=不搜索子目录,1=搜索1级子目录。)


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞5
打赏
分享
最新回复 (1)
雪    币: 9667
活跃值: (3830)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Willarcap 2021-7-9 17:29
2
0
https://zhuanlan.zhihu.com/p/341448835
这篇文章应该能帮到楼主
游客
登录 | 注册 方可回帖
返回