75-反虚拟环境 - API 锤击
简介
API hammering(API 遍历)是一种沙盒绕过技术,其中随机调用 WinAPI 来延迟程序执行。它还可以用于混淆实现中正在运行的线程的调用堆栈。这意味着实现逻辑中的恶意函数调用将被随机的良性 WinAPI 调用隐藏。
此模块将通过两种方式演示 API hammering。第一种方法在一个后台线程中执行 API hammering,该线程从执行恶意代码的主线程调用不同的 WinAPI。第二种方法使用 API hammering 通过耗时的操作来延迟执行。
I/O 函数
API 攻击可以使用任何 WinAPI,不过,本模块将使用以下三个 WinAPI。
CreateFileW - 用于创建并打开一个文件。
WriteFile - 用于将数据写入文件。
ReadFile - 用于从文件读取数据。
之所以选择这些 WinAPI,是因为它们在处理大量数据时能够消耗大量处理时间,因此适合用于 API 攻击。
API 压力测试流程
CreateFileW
将用于在 Windows 临时文件夹中创建一个临时文件。此文件夹通常存储由 Windows 操作系统或第三方应用程序创建的 .tmp
文件。这些临时文件通常用于在安装应用程序或从互联网下载文件等计算过程中存储临时数据。任务完成后,这些文件通常会被删除。
创建 .tmp
文件后,将使用 WriteFile
WinAPI 调用向其中写入一个随机生成的、固定大小的缓冲区。完成后,使用 CreateFileW
关闭文件句柄,然后重新打开文件句柄。不过,这一次会使用一个特殊标记来标记文件,以便在文件句柄关闭后将其删除。
在再次关闭句柄之前,将使用 ReadFile
将之前写入的数据读入本地缓冲区。然后,清理并释放该缓冲区。最后,关闭文件句柄,删除文件。
可以清楚地看出,上述任务还没有意义,但很耗时。此外,为了增加时间浪费,所有这些都将在循环内进行。
下面的 ApiHammering
函数执行上面概述的步骤。函数需要的唯一参数是 dwStress
,表示重复整个过程的次数。
除 GetTempPathW WinAPI 函数外,其余的代码看起来应该很熟悉,该函数用于检索临时目录的路径,C:\Users\<username>\AppData\Local\Temp
。之后,文件名 TMPFILE
会附加到路径中,并传递给 CreateFileW
函数。
// 要创建的文件名
#define TMPFILE L"MaldevAcad.tmp"
BOOL ApiHammering(DWORD dwStress) {
WCHAR szPath [MAX_PATH * 2],
szTmpPath [MAX_PATH];
HANDLE hRFile = INVALID_HANDLE_VALUE,
hWFile = INVALID_HANDLE_VALUE;
DWORD dwNumberOfBytesRead = NULL,
dwNumberOfBytesWritten = NULL;
PBYTE pRandBuffer = NULL;
SIZE_T sBufferSize = 0xFFFFF; // 1048575 字节
INT Random = 0;
// 获取临时文件夹路径
if (!GetTempPathW(MAX_PATH, szTmpPath)) {
printf("[!] GetTempPathW 失败,错误:%d \n", GetLastError());
return FALSE;
}
// 构建文件路径
wsprintfW(szPath, L"%s%s", szTmpPath, TMPFILE);
for (SIZE_T i = 0; i < dwStress; i++){
// 以写入模式创建文件
if ((hWFile = CreateFileW(szPath, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL)) == INVALID_HANDLE_VALUE) {
printf("[!] CreateFileW 失败,错误:%d \n", GetLastError());
return FALSE;
}
// 分配缓冲区并用随机值填充它
pRandBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sBufferSize);
srand(time(NULL));
Random = rand() % 0xFF;
memset(pRandBuffer, Random, sBufferSize);
// 将随机数据写入文件
if (!WriteFile(hWFile, pRandBuffer, sBufferSize, &dwNumberOfBytesWritten, NULL) || dwNumberOfBytesWritten != sBufferSize) {
printf("[!] WriteFile 失败,错误:%d \n", GetLastError());
printf("[i] 已写入 %d 字节,总计 %d \n", dwNumberOfBytesWritten, sBufferSize);
return FALSE;
}
// 清除缓冲区并关闭文件句柄
RtlZeroMemory(pRandBuffer, sBufferSize);
CloseHandle(hWFile);
// 以读取模式打开文件,并标记为关闭时删除
if ((hRFile = CreateFileW(szPath, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL)) == INVALID_HANDLE_VALUE) {
printf("[!] CreateFileW 失败,错误:%d \n", GetLastError());
return FALSE;
}
// 读取之前写入的随机数据
if (!ReadFile(hRFile, pRandBuffer, sBufferSize, &dwNumberOfBytesRead, NULL) || dwNumberOfBytesRead != sBufferSize) {
printf("[!] ReadFile 失败,错误: %d \n", GetLastError());
printf("[i] 已读入 %d 字节,总计 %d \n", dwNumberOfBytesRead, sBufferSize);
return FALSE;
}
// 清除缓冲区并释放它
RtlZeroMemory(pRandBuffer, sBufferSize);
HeapFree(GetProcessHeap(), NULL, pRandBuffer);
// 关闭文件句柄 - 删除文件
CloseHandle(hRFile);
}
return TRUE;
}
通过 API Hammering 延迟执行
要使用 API Hammering 延迟执行,计算 ApiHammering
函数执行一定数量循环所需的时间。为此,使用 GetTickCount64
WinAPI 来测量 ApiHammering
调用前后的时间。在此示例中,循环次数为 1000。
int main() {
DWORD T0 = NULL,
T1 = NULL;
T0 = GetTickCount64();
if (!ApiHammering(1000)) {
return -1;
}
T1 = GetTickCount64();
printf(">>> ApiHammering(1000) Took : %d MilliSeconds To Complete \n", (DWORD)(T1 - T0));
printf("[#] 按 <Enter> 键退出 ... ");
getchar();
return 0;
}
输出显示,在当前机器上执行 1000 个循环大约需要 5.1 秒。该数字将根据目标系统的硬件规格而略有不同。
![image](https://maldevacademy.s3.amazonaws.com/images/Intermediate/api-hammering-115849002-8f48543a-45d1-46bf-b740-5362f2ae7dc2.png)
秒转换为循环次数
以下 SECTOSTRESS
宏可用于将秒数 i
转换为循环次数。由于 1000 次循环耗时 5.157 秒,因此每秒将花费 1000 / 5.157 = 194。应将宏的输出用作 ApiHammering
函数的参数。
#define SECTOSTRESS(i) ((int)i * 194)
通过 API 猛击代码延迟执行
以下代码片段展示了使用前面提到的技术的主函数。
int main() {
DWORD T0 = NULL,
T1 = NULL;
T0 = GetTickCount64();
// 延迟执行 5 秒钟的循环数
if (!ApiHammering(SECTOSTRESS(5))) {
return -1;
}
T1 = GetTickCount64();
printf(">>> ApiHammering 延迟执行:%d \n", (DWORD)(T1 - T0));
printf("[#] 按 <Enter> 退出 ... ");
getchar();
return 0;
}
演示
下图是上述代码的输出。ApiHammering
能够将执行延迟 5016 毫秒,这与传递给 SECTOSTRESS
宏的值大致相同。
![image](https://maldevacademy.s3.amazonaws.com/images/Intermediate/api-hammering-215850112-05e21d3e-12a5-45c8-8d0f-31e466a2eae7.png)
线程中的 API 攻击
ApiHammering
函数可在后台线程中执行,直至主线程执行完毕。这可通过使用 CreateThread
WinAPI 完成。应将 -1
值传递给 ApiHammering
函数,这将使其无限次循环处理 process。
以下所示主函数创建一个新线程,并使用 -1
值调用 ApiHammering
函数。
int main() {
DWORD dwThreadId = NULL; // 线程 ID
if (!CreateThread(NULL, NULL, ApiHammering, -1, NULL, &dwThreadId)) {
printf("[!] CreateThread With Error : %d \n", GetLastError());
return -1;
}
printf("[+] 线程 %d 已创建,将在后台运行 ApiHammering\n", dwThreadId);
/*
注入代码可置于此处
*/
printf("[#] 按 <Enter> 键退出 ... ");
getchar();
return 0;
}