恶意软件通常由许多组件组成,比如宏和脚本就是比较常见的恶意下载器。一些功能是通过shellcode实现的。还有更复杂的元素和核心模块,即原生Windows可执行文件格式——PE文件。
原因很简单,在PE文件中实现复杂的功能比在shellcode中要容易得多。PE格式有定义好的结构,也有更多的灵活性。有一些头文件就定义了应该加载哪些导入、何时应该加载,以及重定位的应用等。PE格式也是编译Windows应用的默认生成格式,其结构之后被Windows加载器用于加载和执行应用。当恶意软件作者开发定制的加载器时,大多会选择PE格式。
但也会有一些例外的情况,比如与Hidden Bee相关的payload。研究人员在分析Hidden Bee的payload时发现有两个释放的payload并不是PE格式。其结构组织良好、并且比常见的shellcode要复杂得多。下面对其格式进行分析。
第一个payload(扩展名为.wasm)是一个加载器,负责下载和解压Cabinet文件:
第二个payload是从Cabinet文件中解压的:
与大多数shellcode相比,其并不是从代码开始的,而是从头文件。比较两个模块,可以看出两个头的结构相同。
下面解释header中不同域的意义:
两个payload的第一个域都是DWORD: 0x10000301,研究人员未发现对应的模块号,因此,研究人员猜测这是格式识别号。
接着是与加载导入相关的元素的偏移量。第一个0x18指向DLL列表,第二个0x60看起来更加神秘,可以理解未在IDA中加载该模块。下图可以看到这些域的交叉引用:
可以看出被用作IAT,即用导入函数的地址来填充:
下一个值是DWORD (0x2A62),如果在IDA中追踪,就可以看到指向一个新函数的开始:
该函数没有被任何函数引用,因此研究人员猜测这是程序的入口点。
下一个值(0x509C)与整个模块的大小相同。
倒数第二个DWORD (0x4D78)指向的结构与PE的重定位非常相似。研究人员猜测这应该是模块的重定位表,最后一个是的DWORD是其大小。
然后就重构了全部的header:
typedef struct {
DWORD magic;
WORD dll_list;
WORD iat;
DWORD ep;
DWORD mod_size;
DWORD relocs_size;
DWORD relocs;
} t_bee_hdr;
从header的分析中可以得知,DLL列表的开始点是在0x18,可以看出每个DLL名中都加入了一个数字:
该数字并不与DLL名相对应:因为在两个不同的模块同,相同的DLL被分配的数字不相同。但如果把所有数字都加起来,那么和与IAT中DWORD的数是相同的。因此,可以推测这些数说明了有多少函数从某个特定的DLL中导入的次数。
typedef struct {
WORD func_count;
char name;
} t_dll_name;
根据上面的结构,IAT中的DWORD列表:
在恶意软件中,一般函数名都不是明确的字符串,而是通过校验和导入的。这里也是一样,猜测用于计算校验和的函数很难。但还是在加载器组件中找到了:
DWORD checksum(char *func_name)
{
DWORD result = 0x1505;
while ( *func_name )
result = *func_name++ + 33 * result;
return result;
}
知道了函数后,就可以与函数名的校验和进行配对了:
一旦提取出函数地址,就保存在IAT中校验和的位置。
创建一个重定位表很难,因为其中包含了模块加载时可以识别加入到base中的偏移量的DWORD。如果不适用重定位,模块就会奔溃。
虽然PE格式很复杂,有不同的header,但只包含必须项。大多数保存在PE header中的信息这里都略去了。
下面是Ange Albertini可视化的PE格式效果图:
(https://raw.githubusercontent.com/corkami/pics/master/binary/PE101.png)
比较可视化效果图:
可以在IDA中以原始代码的形式加载代码,但可能会错失很多重要信息。因为该文件并不是PE格式的,所以其导入表也不是标准的,所以很难理解API调用的位置。为了解决这个问题,研究人员开发了一个工具将哈希值解析为函数名,并生成一个TAG文件来标记每个函数地址的偏移量。
这些标签可以用IFL插件加载到IDA中:
标记了所有的API函数后,理解该模块执行哪些动作就容易多了。比如,可以看出下图是与C2服务器建立连接:
该格式是定制的,所以通用的分析工具是不支持的。但在理解了其原理后,就可以开发响应的工具,比如开发header分析器和加载器来帮助分析动态过程。
与PE格式相比,该模式没有分区(section)。所以需要加载到一个有RWX(读写和执行)权限的连续内存区。经过重定位列表,可以将base值加入到加载到列表地址的模块中。然后,通过哈希值解析出导入的函数,并将地址填入thunk中。然后,只需要跳转到模块的入口点即可。
上面描述的元素非常简单,是恶意软件包的第一阶段,会下载其他代码并注入到进程中。但恶意软件作者有许多的创新性,并开发出一个比PE格式简单的定制的格式,比shellcode要先进一步。与独立的shellcode相比,这样的模块是不能以一种简单的方式加载的,必须首先进行语法和语义分析。不过,完全定制的格式在恶意软件界并不多见,一般来说,恶意软件作者会根据现有格式进行修改,比如破坏或定制PE头中的指定部分。