内容纲要

恶意代码分析实战 - 静态分析基础技术


ASCII 和 Unicode 编码

  1. ASCII 和 Unicode 两种编码类型格式 , 在存储字符序列时都以 NULL 结束符表示字符串已经终结 , 一个 NULL 结束符表示该字符串是完整的.

  2. ASCII字符串每个字符使用 1 个字节 , Unicode字符串每个字符使用 2 个字节.


加壳程序与混淆程序

  1. 混淆程序是恶意代码编写者尝试隐藏执行过程的代码 , 加壳程序属于混淆程序中的一类 , 加壳后的程序会被压缩 .

  2. 合法的程序通常包含大量字符串 , 如果对程序搜索字符串后发现字符串很少 , 则有可能是混淆或者加壳程序.

    加壳文件的字符串列表 , 导入表和其他信息都会被压缩 , 对大多数静态分析工具是不可见的.

  3. 加壳或混淆程序通常至少会包含 LoadLibraryGetProcAddress 函数 , 以便加载或使用其他函数的功能.

  4. 当执行加壳程序时 , 会先执行脱壳代码 , 来解压加壳文件 , 因此对加壳文件进行静态分析时 , 我们通常只能分析脱壳代码.

  5. 如何检测加壳文件 : PEID 工具


PE 文件头是有价值的

  1. PE( 可移植执行程序 )通常以一个文件头开始 , 其中包括代码信息 , 应用程序类型 , 所需的库函数 , 空间要求 .

链接库与函数

  1. 导入表中包含导入函数 , 导入函数是一个程序所使用的但存储在另一程序中的函数( 例如很多程序通用的代码函数库 ) , 这些代码函数库可以通过链接的方式 , 被链接到主程序中.

  2. 代码库的链接可分为 "静态链接" , "动态链接" , "运行时链接".

  3. 静态链接 : 当一个库被静态链接到可执行程序中时 , 所有这个库的代码都会被复制到可执行程序中 , 这会使得可执行程序增大很多 ; 并且当多个程序调用相同的函数时 , 内存中会存在这个函数的多个拷贝 , 比较浪费内存资源

    静态链接库是 ".lib" 后缀的文件 , 静态链接在 Unix / Linux 平台程序中比较常见 , 在 Windows 平台程序中不常见.

  4. 动态链接 : 不会将代码库拷贝到可执行程序中 , 而是在可执行程序中加入所调用函数的描述信息( 通常是一些重定位信息 ) , 只有当可执行程序被装入内存开始运行时 , 才在可执行程序与动态链接库( DLL )之间建立链接关系 , 当要执行被链接DLL中的函数时 , Windows才会根据链接产生的重定位信息去执行相应的函数.

    动态链接库是 ".dll"后缀的文件 , 程序在运行时可以动态加载和移除 DLL 的数据或者函数 , 节省内存空间.

  5. 运行时链接 : 当一个程序运行时 , 只有当需要使用函数时 , 才链接到代码库

    运行时链接在恶意代码中比较常见.

PostScript : "动态链接" 是在程序运行时加载所有的代码库 , 在需要用到其中函数或者数据时才加载它们 , 而 "运行时链接" 是在程序运行期间需要用到代码库中的函数或数据时 , 才会加载代码库 , 并加载对应函数或数据.

  1. 一些 Windows API 允许程序导入没有在程序的文件头中列出的链接函数 , 例如 "LoadLibrary" , "GetProcAddress" , "LdrGetProcAddress" , "LdrLoadDll" 等函数 .

    例如 LoadLibrary 和 "GetProcAddress" 函数允许一个程序访问系统上任何库的任何函数 . 这意味着当这些函数被使用时 , 无法静态分析出可疑样本中会调用哪些函数

  2. 在分析恶意代码时 , 动态链接是最常见的 , 也是最需要关注的 . PE文件头中存储了每个将被装载的代码库 , 以及每个会被程序使用的函数信息 !

  3. 如何查看被加载的动态链接库以被调用的函数 : Dependency Walker工具


DLL导出

  1. DLL文件中通常包含导出表( EXE文件一般不会包含导出表 ) , 该表包含 DLL 导出到其他可执行文件的每个函数的名称.

  2. 导出表中函数的名称是进入 DLL 的入口点 , 只有导出表中的函数才能被其他可执行程序访问 , 而任何其他函数都是DLL的私有函数 . 外界无法直接访问 .

  3. DLL导出函数的声明方式一共有两种 :

    • 根据函数名导出 : 通过 __declspec(dllexport) 关键字
      举例 : __declspec(dllexport) void __cdecl Function1(void)

    • 根据函数序号导出 : 通过模块定义( .def )文件声明
      举例 :

      ;LIBRARY 语句说明 .def 文件对应的 DLL
      ;EXPORTS 语句后面列出要导出函数的名称 , 并在导出函数名后添加 @n , 表示要到导出函数的序号为n
      ;代码中可以通过 GetProcAddress( hInstance , MAKEINTRESOURCE(1)) 来调用指定序号对应的函数.
      LIBRARY DllTestDef
      EXPORTS
      Add @1
      Sub @2
  4. 通过任一方法导出函数时 , 必须要使用 __stdcall 调用约定 .


常用的 DLL 程序

  1. Kernel32.dll : 包含系统核心功能 , 例如访问和操作内存 , 文件 , 硬件.

  2. Advapi32.dll : 提供了对核心 Windows 组件的访问 , 例如服务管理器与注册表.

  3. User32.dll : 包含了所有用户界面组件 , 例如按钮 , 滚动条 , 以及控制和响应用户操作的组件.

  4. Gdi32.dll : 包含了图形显示和操作的函数.

  5. Ntdll.dll : 该 DLL 为Windows内核接口 , 可执行文件通常不直接导入这个DLL , 而是通过 Kernel32.dll 间接导入 .

    如果一个可执行程序导入了这个DLL , 则意味着程序作者企图使用那些不是正常提供给Windows程序所使用的函数 . 一些隐藏功能或者操作进程的任务会调用这个接口.

  6. Wsock32.dll / Ws2_32.dll : 这两个都是用于联网的 DLL , 导入该DLL的程序通常用于连接网络 .

  7. Wininet.dll : 包含了更高层次的网络函数 , 例如实现 FTP , HTTP , NTP 等协议 .


函数命名约定

  1. EX 为后缀 : 当微软更新一个函数 , 并且该函数与原先函数不兼容时 , 此时微软会保留原先的函数 , 在原函数名后面加上 EX , 作为新函数的函数名.

    例如 : CreateWindowCreateWindowEx

  2. A 为后缀 : 以字符串作为参数的函数在接收参数时只接收 ASCII 字符串 .

  3. W 为后缀 : 以字符串作为参数的函数在接收参数时只接收 宽字符字符串 .


导入函数与导出函数

  1. PE头中包含了可执行文件使用的特定函数信息 , 这些函数被称为导入函数 , 微软通过 MSDN 库对 Windows API 进行文档化.

  2. DLL 和 EXE 的导出函数是用来与其他程序的和代码进行交互时使用的 . 通常情况下 , 一个 DLL 会实现一个或者多个功能函数 , 然后将他们导出 , 使得其他程序可以导入并使用这些函数.


  1. EXE 文件不是用于为其他 EXE 程序提供功能的 , 因此一般不含有导出函数 ; 如果一个 EXE 文件包含了导出表函数, 那么很有可能提供有价值的信息

PE 文件头与分节

  1. PE文件格式包括一个 PE 文件头和一系列分节 , 常见的分节如下 :

    .text : 该节包含了 CPU 执行指令与所有其他节存储数据和支持性的信息 . 一般来说 , 这是唯一可执行的节 , 也是唯一包含代码的节.

    .rdata : 该节包含导入与导出的函数信息 , 以及程序使用的只读数据 . 有些文件也会使用 .idata.edata 来存储导入导出信息

    .data : 该节包含程序的全局数据 , 可以从程序任何地方访问到 .

    .rsrc : 该节包含可执行程序使用的资源 , 这些内容是不可执行的 , 例如图标 , 图片 , 菜单项 , 字符串等等. 字符串存储在 .rsrc 节中通常是为了提供多语种支持.

    .reloc : 该接包含用来重定位库文件的信息.

  2. 节名称在同一个编译器编译出来的可执行文件中都是一样的 , 但是对于不同的编译器 , 可能会不相同.

    例如在 VS 中使用 .text 节作为可执行代码节的名称 , 但是在 Delphi 中使用 CODE 节来作为可执行代码节的名称.

  3. 通过 PETools 分析PE文件时 , 需要注意 : 字符串是正常顺序存储 , 而数据是小端存储

  4. 通过 PEViewer 分析 PE 文件( notepad.exe )

    1. IMAGE_DOS_HEADER

      5A4D ( MZ ) 代表 PE 文件的开头.

    2. MS-DOS_Stub Program( MS-DOS 存根程序 )

      This Program cannot be run in DOS mode , 看到这句话也能证明这是一个 PE 程序.

    3. IMAGE_NT_HEADERS.Signature

      NT文件头的特征签名始终是相同的 , 即 4550( PE )

    4. IMAGE_NT_HEADERS.IMAGE_FILE_HEADER

      该项包含了文件的基本信息 , 例如运行平台 , 时间戳等.

      有时候该字段还会显示文件编译的时间戳 , 时间戳说明了文件是什么时候编译的 . 但是某些编译器的时间戳是固定的 , 例如 Delphi 程序的编译时间统一为 1992年6月19日 ; 并且时间戳是可以伪造的.

    5. IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER

      Subsystem 字段会标注该 PE 程序是一个控制台程序( CUI ) 还是一个图形界面程序( GUI )

    6. IMAGE_SECTION_HEADER

      该 PE 头描述了 PE 文件的各个分节 , 编译器通常会创建和命名一个可执行程序的各个分节 , 用户几乎无法控制这些分节的名称 ; 因此可执行文件的分节名称通常都是一致的 , 任何偏差都是可疑的 .

      在上面的分节信息中 , 需要重点关注 Virtual SizeSize Of Raw Data 两个字段

      • Virtual Size : 内存在程序加载过程中需要分配多少空间给一个分节
      • Size Of Raw Data : 磁盘上这个分节的大小规模

      一般情况下 , Virtual SizeSize Of Raw Data 的值应该是接近的 ,

      如果 PE 程序的 "虚拟大小" 比 "原始数据" 大很多 , 则说明这个节在内存中占用了比磁盘上更多的空间 . 如果这个节又恰好是 ".text" 分节 , 那么该程序很可能是一个加壳程序 , 加壳器可能会将脱出的可执行代码放到 .text 分节中.

  5. 查看 .rsrc 分节中的数据 : Resource Hacker

    一些恶意程序会将一个嵌入的程序或者驱动放在 .rsrc 分节中 , 在程序运行之前再将这些文件提取出来 , Resource Hacker 可以很方便的提取这些文件单独提取出来分析.


最后修改日期:2020年11月14日

作者

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。