freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

使用PoolTag识别主机指纹
2018-09-19 13:00:31
所属地 浙江省

写在前面的话

通常情况下,恶意软件会对它所在的主机进行指纹识别,以便发现更多的信息。这个过程会分析一些特定的数据,用来判断恶意软件是否在VM中运行,除此之外,还会检测其他软件的存在。例如,恶意软件经常试图找出系统监控工具是否在运行(procmon, sysmon等)和安装了哪些AV软件。在本文中,我们将介绍另一种被恶意软件滥用的主机进行指纹识别的方法。

指纹识别的常用方法

首先,我们介绍几种恶意软件检测VM环境的常见方法:

1.枚举进程

2.枚举加载的模块

3.枚举文件

4.从Windows注册表中提取的数据(硬盘,BIOS等)

5.枚举加载的驱动程序

6.打开特定设备对象的句柄

7.枚举系统资源(CPU内核,RAM,屏幕分辨率等)

PoolTag了解一下

如果您对Windows内核驱动程序的开发和分析有一定的经验,那么我想您应该熟悉ExAllocatePoolWithTag函数,该函数用于在内核级别上分配内存块。这里的关键部分是Tag'参数,用于为特定的分配提供某种标识。如果出现错误,例如由于内存损坏这种问题,我们可以使用指定的标记(最多4个字符)来将缓冲区与分配内存块的内核驱动程序中的代码路径关联起来。这种方法可以检测内核驱动程序的存在,因此,在内核中加载模块的软件可能会绕过上面提到的指纹方法,这些方法依赖于驱动程序可能更改的信息。换句话说,从恶意软件作者的角度来看,它是用来检测某些真正重要的东西很好选择。例如,安全或监控软件可能试图通过在内核级别注册回调过滤器来隐藏其进程和文件。分析师可能会试图通过从注册表中删除恶意软件通常搜索的东西来强化虚拟机环境。但是,安全软件供应商或分析人员可能并不会修改他们自己的程序或系统中VM环境使用的特定内核驱动程序,从而修改内核池分配的标记。

获取PoolTag信息

可以通过调用NtQuerySystemInformation函数并为SysteminformationClass参数选择SystemPoolTagInformation (0x16 )来获取此信息  。MSDN上记录了部分上述功能和相关的SysteminformationClass可能值,幸运的是,通过一些研究,我们找到研究人员完成的一些文档。Alex Ionescu在他的NDK项目中记录了许多关于Windows内容。为了证明这个,我们编写了一个可以自己的获取和解析PoolTag信息的小工具,但是如果你想用GUI方式,推荐使用PoolMonEx这个工具。源代码如下:
1.png您可以将其与Nbtk标记的PoolMonEx分配结果进行比较,如下所示。
2.png

QueryPoolTagInfo.cpp

#include "Defs.h"
#include <iostream>
 
using namespace std;
 
int main()
{
    NTSTATUS NtStatus = STATUS_SUCCESS;
    BYTE * InfoBuf = nullptr;
    ULONG ReturnLength = 0;
    
    _ZwQuerySystemInformation ZwQuerySystemInformation = (_ZwQuerySystemInformation)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwQuerySystemInformation");
 
    do{
        NtStatus = ZwQuerySystemInformation(SystemPoolTagInformation, InfoBuf, ReturnLength, &ReturnLength);
 
        if (NtStatus == STATUS_INFO_LENGTH_MISMATCH)
        { 
            if (InfoBuf != nullptr)
            {
                delete[] InfoBuf;
                InfoBuf = nullptr;
            }
 
            InfoBuf = new (nothrow) BYTE[ReturnLength];
 
            if (InfoBuf != nullptr)
                memset(InfoBuf, 0, ReturnLength);
            else
                goto Exit;
        }
 
    } while (NtStatus != STATUS_SUCCESS);
 
 
    PSYSTEM_POOLTAG_INFORMATION pSysPoolTagInfo = (PSYSTEM_POOLTAG_INFORMATION)InfoBuf;
    PSYSTEM_POOLTAG psysPoolTag = (PSYSTEM_POOLTAG)&pSysPoolTagInfo->TagInfo->Tag;
 
    ULONG count = pSysPoolTagInfo->Count;
    cout << "Count: " << count << endl << endl;
 
    for (ULONG i = 0; i < count; i++)
    {
        cout << "PoolTag: ";
 
        for (int k = 0; k < sizeof(ULONG); k++)
            cout << psysPoolTag->Tag[k];
 
        cout << endl;
 
        if (psysPoolTag->NonPagedAllocs != 0)
        {
            cout << "NonPaged Allocs: " << psysPoolTag->NonPagedAllocs << endl;
            cout << "NonPaged Frees: " << psysPoolTag->NonPagedFrees << endl;
            cout << "NonPaged Pool Bytes Used: " << psysPoolTag->NonPagedUsed << endl;
        }
        else
        {
            cout << "Paged Allocs: " << psysPoolTag->PagedAllocs << endl;
            cout << "Paged Frees: " << psysPoolTag->PagedFrees << endl;
            cout << "Paged Pool Bytes Used: " << psysPoolTag->PagedUsed << endl;
        }
 
        psysPoolTag++;
        cout << endl << "-------------------------------" << endl;
        cout << endl << "-------------------------------" << endl << endl;
 
    }
 
    if (InfoBuf != nullptr)
        delete[] InfoBuf;
 
Exit:
    cin.get();
    return 0;
}

Defs.h

#include <Windows.h>
 
#define SystemPoolTagInformation (DWORD)0x16
 
#define STATUS_SUCCESS 0
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004
 
typedef DWORD SYSTEM_INFORMATION_CLASS;
 
typedef struct _SYSTEM_POOLTAG
{
    union
    {
        UCHAR Tag[4];
        ULONG TagUlong;
    };
    ULONG PagedAllocs;
    ULONG PagedFrees;
    SIZE_T PagedUsed;
    ULONG NonPagedAllocs;
    ULONG NonPagedFrees;
    SIZE_T NonPagedUsed;
 
}SYSTEM_POOLTAG, *PSYSTEM_POOLTAG;
 
typedef struct _SYSTEM_POOLTAG_INFORMATION
{
    ULONG Count;
    SYSTEM_POOLTAG TagInfo[ANYSIZE_ARRAY];
 
}SYSTEM_POOLTAG_INFORMATION, *PSYSTEM_POOLTAG_INFORMATION;
 
 
typedef NTSTATUS(WINAPI *_ZwQuerySystemInformation)(
    _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
    _Inout_   PVOID                    SystemInformation,
    _In_      ULONG                    SystemInformationLength,
    _Out_opt_ PULONG                   ReturnLength
    );

目标PoolTag信息

为了更好的了解获取的PoolTag信息,有必要进行分析。通过搜索对locatepoolwithtag函数的调用,我们可以记录这些驱动程序使用的特定标签,并将它们保留在我们的列表中。我们知道任何驱动程序都可以随意使用任何标记,那么,尝试找到一些不太常见的、标准Windows内核驱动程序或对象没有使用的标记是有非常意义的。话虽如此,但这种检测特定驱动因素的方法可能会产生误报。

PoolTag示例列表

>为了进一步证明,我们从特定的驱动程序中收集了一些PoolTag信息。

VMWare

>vm3dmp.sys(标签:VM3D)vmci.sys(标签:CTGC,CTMM,QPMM等......)vmhgfs.sys(标签:HGCC,HGAC,HGVS,HGCD等......)vmmemctl.sys(标签:VMBL)vsock.sys(标签:vskg,vskd,vsks等...)

Process Explorer

>procexp152.sys(标签:PEOT,PrcX等...)

进程监视器

>procmon23.sys(标签:Pmn)

SYSMON

>sysmondrv.sys(标签:Sys1,Sys2,Sys3,SysA,SysD,SysE等...)

Avast Internet Security

> aswsnx.sys(标签:'Snx',Aw ++)(我们在第一个中使用单引号,因为它以空格字符结尾)aswsp.sys(标签:pSsA,AsDr)

结论

就像其他方法一样,这个方法也有优缺点。比如这种方法不容易被规避,特别是在64位Windows中,内核补丁保护(Patch Guard)不允许我们修改内核函数等,因此可以直接挂钩NtQuerySystemInformation。此外,此方法不受来自用户空间进程的特定进程,文件和注册表项的访问的驱动程序的影响。所以该方法可以用于识别指纹主机。通过搜索操作系统中引入的Windows对象的特定标记,我们可以确定其主要版本。例如,通过比较不同版本的Windbg附带的poolTag信息(pooltag.txt),在本例中为Windows 8.1 x64Windows 10 x64(Build 10.0.15063),我们能够找到 Windows 10中使用的PoolTag通过 netio.sys内核驱动程序,如 NrsdNrtrNrtw,但不在Windows 8.1中我们后来使用两个虚拟机进行了验证,我们确实可以在Windows 10中找到至少有两个上述标签的池分配,而我们的Windows 8.1虚拟机中没有这些。话虽这么说,内核驱动程序开发使用基于分配它们的模块及其用途有意义的标记是一种常见且良好的做法。另一方面,如前所述, PoolTag可以随便用,因此我们必须小心使用。最后要提到的是 PoolTag信息一直在变化,换句话说,内存块经常被分配和解除分配,因此我们在选择要搜索的PoolTag时应该记住这一点 。

*参考来源labs,由周大涛编译,转载请注明来自FreeBuf.COM

# 指纹识别 # 恶意软件
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者