前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >规避检测(共五章):第二章

规避检测(共五章):第二章

作者头像
Creaper
发布2023-11-20 12:43:09
2400
发布2023-11-20 12:43:09
举报
文章被收录于专栏:锦鲤安全锦鲤安全

一、全局对象检测

1.1 检查特定的全局互斥锁

此方法检查虚拟环境中存在但不在常规主机系统中的特定互斥锁。

代码语言:javascript
复制
// 使用示例:
supMutexExist(L"Sandboxie_SingleInstanceMutex_Control"); // 示例值


BOOL supMutexExist(_In_ LPWSTR lpMutexName)
{
    DWORD dwError;
    HANDLE hObject = NULL;
    if (lpMutexName == NULL) {
        return FALSE;
    }

    SetLastError(0);
    hObject = CreateMutex(NULL, FALSE, lpMutexName);
    dwError = GetLastError();

    if (hObject) {
        CloseHandle(hObject);
    }

    return (dwError == ERROR_ALREADY_EXISTS);
}

1.2 检查特定的虚拟设备

此方法检查虚拟环境中存在但不在常规主机系统中的特定虚拟设备。

代码语言:javascript
复制
HANDLE hDummy = NULL;
supOpenDevice(L"\\Device\\Null", GENERIC_READ, &hDummy);

BOOL supOpenDevice(
    _In_ LPWSTR lpDeviceName,
    _In_ ACCESS_MASK DesiredAccess,
    _Out_opt_ PHANDLE phDevice)
{
    OBJECT_ATTRIBUTES attr;
    IO_STATUS_BLOCK iost;
    UNICODE_STRING uDevName;
    HANDLE hDevice;
    NTSTATUS Status;

    if (phDevice) {
        *phDevice = NULL;
    }
    if (lpDeviceName == NULL) {
        return FALSE;
    }

    hDevice = NULL;
    RtlSecureZeroMemory(&uDevName, sizeof(uDevName));
    RtlInitUnicodeString(&uDevName, lpDeviceName);
    InitializeObjectAttributes(&attr, &uDevName, OBJ_CASE_INSENSITIVE, 0, NULL);

    Status = NtCreateFile(&hDevice, DesiredAccess, &attr, &iost, NULL, 0,
        0, FILE_OPEN, 0, NULL, 0);
    if (NT_SUCCESS(Status)) {
        if (phDevice != NULL) {
            *phDevice = hDevice;
        }
    }

    return NT_SUCCESS(Status);
}

1.3 检查全局对象

此方法检查虚拟环境中存在但不在常规主机系统中的特定全局对象。

代码语言:javascript
复制
supIsObjectExists(L"\\Driver", L"SbieDrv");


typedef struct _OBJECT_DIRECTORY_INFORMATION {
    UNICODE_STRING Name;
    UNICODE_STRING TypeName;
} OBJECT_DIRECTORY_INFORMATION, * POBJECT_DIRECTORY_INFORMATION;

BOOL supIsObjectExists(
    _In_ LPWSTR RootDirectory,
    _In_ LPWSTR ObjectName)
{
    OBJSCANPARAM Param;
    if (ObjectName == NULL) {
        return FALSE;
    }

    Param.Buffer = ObjectName;
    Param.BufferSize = (ULONG)_strlen_w(ObjectName);

    return NT_SUCCESS(supEnumSystemObjects(RootDirectory, NULL, supDetectObjectCallback, &Param));
}

NTSTATUS NTAPI supDetectObjectCallback(
    _In_ POBJECT_DIRECTORY_INFORMATION Entry,
    _In_ PVOID CallbackParam)
{
    POBJSCANPARAM Param = (POBJSCANPARAM)CallbackParam;
    if (Entry == NULL) {
        return STATUS_INVALID_PARAMETER_1;
    }
    if (CallbackParam == NULL) {
        return STATUS_INVALID_PARAMETER_2;
    }
    if (Param->Buffer == NULL || Param->BufferSize == 0) {
        return STATUS_MEMORY_NOT_ALLOCATED;
    }
    if (Entry->Name.Buffer) {
        if (_strcmpi_w(Entry->Name.Buffer, Param->Buffer) == 0) {
            return STATUS_SUCCESS;
        }
    }

    return STATUS_UNSUCCESSFUL;
}

NTSTATUS NTAPI supEnumSystemObjects(
    _In_opt_ LPWSTR pwszRootDirectory,
    _In_opt_ HANDLE hRootDirectory,
    _In_ PENUMOBJECTSCALLBACK CallbackProc,
    _In_opt_ PVOID CallbackParam)
{
    BOOL cond = TRUE;
    ULONG ctx, rlen;
    HANDLE hDirectory = NULL;
    NTSTATUS status;
    NTSTATUS CallbackStatus;
    OBJECT_ATTRIBUTES attr;
    UNICODE_STRING sname;
    POBJECT_DIRECTORY_INFORMATION objinf;

    if (CallbackProc == NULL) {
        return STATUS_INVALID_PARAMETER_4;
    }
    status = STATUS_UNSUCCESSFUL;

    __try {
        // We can use root directory.
        if (pwszRootDirectory != NULL) {
            RtlSecureZeroMemory(&sname, sizeof(sname));
            RtlInitUnicodeString(&sname, pwszRootDirectory);
            InitializeObjectAttributes(&attr, &sname, OBJ_CASE_INSENSITIVE, NULL, NULL);

            status = NtOpenDirectoryObject(&hDirectory, DIRECTORY_QUERY, &attr);
            if (!NT_SUCCESS(status)) {
                return status;
            }
        }
        else {
            if (hRootDirectory == NULL) {
                return STATUS_INVALID_PARAMETER_2;
            }
            hDirectory = hRootDirectory;
        }

        // Enumerate objects in directory.
        ctx = 0;
        do {
            rlen = 0;
            status = NtQueryDirectoryObject(hDirectory, NULL, 0, TRUE, FALSE, &ctx, &rlen);
            if (status != STATUS_BUFFER_TOO_SMALL)
                break;
            objinf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, rlen);
            if (objinf == NULL)
                break;

            status = NtQueryDirectoryObject(hDirectory, objinf, rlen, TRUE, FALSE, &ctx, &rlen);
            if (!NT_SUCCESS(status)) {
                HeapFree(GetProcessHeap(), 0, objinf);
                break;
            }

            CallbackStatus = CallbackProc(objinf, CallbackParam);
            HeapFree(GetProcessHeap(), 0, objinf);
            if (NT_SUCCESS(CallbackStatus)) {
                status = STATUS_SUCCESS;
                break;
            }
        } while (cond);

        if (hDirectory != NULL) {
            NtClose(hDirectory);
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        status = STATUS_ACCESS_VIOLATION;
    }

    return status;
}

1.4 检查对象目录(仅限沙箱)

此方法检查特定对象目录,该目录存在于 沙箱虚拟环境中,但不存在于通常的主机系统中。

代码语言:javascript
复制
#define DIRECTORY_QUERY (0x0001)
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define DIRECTORY_SANDBOXIE L"\\Sandbox"

int check_if_obj_dir_present() {
    OBJECT_ATTRIBUTES attr;
    UNICODE_STRING ustrName;
    HANDLE hObject = NULL;

    RtlSecureZeroMemory(&ustrName, sizeof(ustrName));
    RtlInitUnicodeString(&ustrName, DIRECTORY_SANDBOXIE);
    InitializeObjectAttributes(&attr, &ustrName, OBJ_CASE_INSENSITIVE, NULL, NULL);

    if (NT_SUCCESS(NtOpenDirectoryObject(&hObject, DIRECTORY_QUERY, &attr))) {
        NtClose(hObject);
        return TRUE;
    }

    return FALSE;
}

二、UI 项目检测方法

某些窗口的名称仅存在于虚拟环境中,而不是通常的主机操作系统。更重要的是,主机操作系统包含大量窗口,而虚拟机和沙盒更喜欢将打开的窗口保持在最低限度。检查它们的数量并得出结论,它是否是 VM。

2.1 检查操作系统中是否存在具有某些类名的窗口

代码语言:javascript
复制
BOOL vbox_window_class()
{
    HWND hClass = FindWindow(_T("VBoxTrayToolWndClass"), NULL);
    HWND hWindow = FindWindow(NULL, _T("VBoxTrayToolWnd"));

    if (hClass || hWindow)
        return TRUE;
    else
        return FALSE;
}

2.2 检查顶层窗口的数量是否太少

如上所述,主机操作系统包含大量窗口,而虚拟机和沙盒则努力将打开的窗口保持在可能的最小值。测量窗口计数,并得出它是否是 VM 的结论。如果操作系统中的窗口太少,则可能表示虚拟环境。

典型的主机有很多(>10)顶层窗口。

代码语言:javascript
复制
BOOL CALLBACK enumProc(HWND, LPARAM lParam)
{
    if (LPDWORD pCnt = reinterpret_cast<LPDWORD>(lParam))
        *pCnt++;
    return TRUE;
}

bool enumWindowsCheck(bool& detected)
{
    DWORD winCnt = 0;

    if (!EnumWindows(enumProc, LPARAM(&winCnt))) {
        std::cerr << "EnumWindows() failed\n";
        return false;
    }

    return winCnt < 10;
}

三、操作系统功能检测

规避使用操作系统工作方式的特殊性。

3.1 检查调试权限

如果恶意软件在调试器下或在 Cuckoo 等沙箱中运行,则其进程令牌将具有启用状态下的调试权限。发生这种情况是因为此权限在父进程中启用并由恶意软件进程继承。

该恶意软件试图以访问权限打开关键的系统进程,如 csrss.exe、smss.exe、lsass.exe PROCESS_ALL_ACCESS然后尝试终止它们。在正常情况下,当从资源管理器或命令行执行恶意软件时,此操作将失败,因为即使是管理员用户也无法终止这些进程。但是,如果进程令牌在启用状态下具有调试权限,则此操作将成功。关键系统进程的终止会导致操作系统崩溃到BSOD并出现错误0x000000F4因此仿真过程将被中止。

代码语言:javascript
复制
DWORD GetCsrssProcessId()
{
    if (API::IsAvailable(API_IDENTIFIER::API_CsrGetProcessId))
    {
        auto CsrGetProcessId = static_cast<pCsrGetId>(API::GetAPI(API_IDENTIFIER::API_CsrGetProcessId));

        return CsrGetProcessId();
    }
    else
        return GetProcessIdFromName(_T("csrss.exe"));
}


BOOL CanOpenCsrss()
{
    HANDLE hCsrss = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, GetCsrssProcessId());
    if (hCsrss != NULL)
    {
        CloseHandle(hCsrss);
        return TRUE;
    }
    else
        return FALSE;
}

3.2 使用不平衡堆栈

为了跟踪进程行为,CuckooMon/Cuckoo Monitor 模块会挂钩相关功能。在这种类型的体系结构中,钩子在原始函数之前调用。挂钩函数除了原始函数使用的空间外,还可以使用堆栈上的一些空间。因此,挂钩函数使用的堆栈上的总空间可能大于仅原始函数使用的空间。

问题:恶意软件包含有关被调用函数在堆栈上使用多少空间的信息。因此,它可以将堆栈指针移动到较低的地址,其偏移量足以存储函数参数、局部变量和返回地址,以便为它们保留空间。恶意软件用一些相关数据填充堆栈指针下方的空间。然后,它将堆栈指针移动到原始位置并调用库函数。如果函数未挂钩,则恶意软件会在相关数据之前填充保留空间(请参阅图 1)。

如果函数被钩住,恶意软件会重叠相关数据,因为为原始函数的局部变量保留的空间小于钩子和原始函数的局部变量所占用的空间之和。因此,相关数据已损坏(请参阅图 2)。

如果它存储指向稍后在执行过程中使用的某些函数的指针,则恶意软件会跳转到任意代码,偶尔会使应用程序崩溃。

为了避免这种行为,Cuckoo Monitor/CuckooMon模块可以使用两阶段挂钩过程。在第一个阶段,它可以将堆栈指针移动到特定大小的较低地址,而不是钩子的代码执行,该地址足以容纳恶意软件的相关数据。然后,函数的参数被复制到新的堆栈指针下。只有在这些准备操作完成后,才会调用第二阶段钩子(执行真正的钩子)。恶意软件输入的相关数据驻留在上层堆栈地址上,因此它不受被调用函数的任何影响。

代码语言:javascript
复制
bool Cuckoo::CheckUnbalancedStack() const {
    usf_t f = {
      { lib_name_t(L"ntdll"), {
        {sizeof(void*), NULL, "ZwDelayExecution", ARG_ITEM(kZwDelayExecutionArgs) }
      } }
    };
    const uint8_t canary[8] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF };

    uint32_t args_size;
    const void* args_buff;
    uint32_t reserved_size;
    uint32_t reserved_size_after_call;
    uint32_t canary_size;
    FARPROC func;
    bool us_detected;
    void* canary_addr = (void*)&canary[0];

    static_assert((sizeof(canary) % sizeof(void*)) == 0, "Invalid canary alignement");

    for (auto it = f.begin(), end = f.end(); it != end; ++it) {
        for (auto& vi : it->second) {
            vi.func_addr = GetProcAddress(GetModuleHandleW(it->first.c_str()), vi.func_name.c_str());

            // call to Unbalanced Stack
            args_size = vi.args_size;
            args_buff = vi.args_buff;
            canary_size = sizeof(canary);
            reserved_size = sizeof(void*) + vi.local_vars_size + canary_size;
            reserved_size_after_call = reserved_size + args_size;
            func = vi.func_addr;
            us_detected = false;

            __asm {
                pusha
                mov ecx, args_size
                sub esp, ecx
                mov esi, args_buff
                mov edi, esp
                cld
                rep movsb
                sub esp, reserved_size
                mov ecx, canary_size
                mov esi, canary_addr
                mov edi, esp
                rep movsb
                add esp, reserved_size
                mov eax, func
                call eax
                sub esp, reserved_size_after_call
                mov ecx, canary_size
                mov esi, canary_addr
                mov edi, esp
                repz cmpsb
                cmp ecx, 0
                setnz us_detected
                add esp, reserved_size_after_call
                popa
            }

            if (us_detected)
                return true;
        }
    }

    return false;
}

锦鲤安全

一个安全技术学习与工具分享平台

点分享

点收藏

点点赞

点在看

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-06-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 锦鲤安全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、全局对象检测
  • 1.2 检查特定的虚拟设备
  • 1.3 检查全局对象
  • 1.4 检查对象目录(仅限沙箱)
  • 二、UI 项目检测方法
  • 2.1 检查操作系统中是否存在具有某些类名的窗口
  • 2.2 检查顶层窗口的数量是否太少
  • 三、操作系统功能检测
  • 3.1 检查调试权限
  • 3.2 使用不平衡堆栈
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档