原文链接:https://www.x86matthew.com/view_post?id=ntdll_pipe
我最近使用了一台安装了 AV 软件的计算机,该软件将用户模式挂钩注入到ntdll.dll中的各种函数中。我不了解现代 AV 软件的运行方式,所以我决定看看这是多么容易克服。
最明显的方法是使用CreateFile和ReadFile从磁盘读取ntdll.dll,但这会触发 AV 启发式引擎。 我的下一个想法是使用受信任的 Microsoft 可执行文件为我完成这项工作 – 一个候选者是cmd.exe。 我使用CreateProcess创建了一个隐藏的带有stdin的cmd.exe进程
重定向到我的程序中的自定义命名管道。我还为ntdll.dll输出内容创建了一个单独的命名管道。使用WriteFile将类型 %windir%\\system32\\ntdll.dll > \\.\pipe\ntdll_output_pipe发送到自定义标准输入管道,然后将ntdll.dll的内容写入我的输出管道,我读取并存储在缓冲区中. 这种简单的方法没有触发任何 AV 警告。
这可以通过删除标准输入重定向并使用初始参数中的 type 命令启动 cmd.exe 来稍微简化( cmd.exe / c type % windir%\\system32\\ntdll.dll > \\.\pipe\ntdll_output_pipe),但这看起来更可疑。
我已经清理了代码,以便可以轻松地使用它来读取任何命令的输出内容。
完整代码如下:
// NtdllPipe.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <stdio.h>
#include <windows.h>
struct BackgroundConsoleInstanceStruct
{
char szInstanceName[128];
HANDLE hConsoleProcess;
HANDLE hConsoleInputPipe;
};
struct CommandOutput_StoreDataParamStruct
{
BYTE* pOutputPtr;
DWORD dwMaxOutputSize;
DWORD dwTotalSize;
};
DWORD BackgroundConsole_Create(const char* pInstanceName, BackgroundConsoleInstanceStruct* pBackgroundConsoleInstance)
{
PROCESS_INFORMATION ProcessInfo;
STARTUPINFO StartupInfo;
char szConsoleInputPipeName[512];
char szLaunchCmd[1024];
BackgroundConsoleInstanceStruct BackgroundConsoleInstance;
HANDLE hConsoleInputPipe;
// create console input pipe
memset(szConsoleInputPipeName, 0, sizeof(szConsoleInputPipeName));
_snprintf(szConsoleInputPipeName, sizeof(szConsoleInputPipeName) - 1, "\\\\.\\pipe\\BackgroundConsoleIn_%s", pInstanceName);
hConsoleInputPipe = CreateNamedPipe(szConsoleInputPipeName, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL);
if (hConsoleInputPipe == INVALID_HANDLE_VALUE)
{
// error
return 1;
}
// initialise startupinfo
memset(&StartupInfo, 0, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow = SW_HIDE;
// create launch cmd
memset(szLaunchCmd, 0, sizeof(szLaunchCmd));
_snprintf(szLaunchCmd, sizeof(szLaunchCmd) - 1, "cmd /c cmd < %s", szConsoleInputPipeName);
// launch cmd.exe
if (CreateProcess(NULL, szLaunchCmd, NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, &StartupInfo, &ProcessInfo) == 0)
{
// error
CloseHandle(hConsoleInputPipe);
return 1;
}
// close thread handle
CloseHandle(ProcessInfo.hThread);
// wait for cmd.exe to connect to input pipe
if (ConnectNamedPipe(hConsoleInputPipe, NULL) == 0)
{
// error
CloseHandle(hConsoleInputPipe);
CloseHandle(ProcessInfo.hProcess);
return 1;
}
// store background console entry data
memset((void*)&BackgroundConsoleInstance, 0, sizeof(BackgroundConsoleInstance));
strncpy(BackgroundConsoleInstance.szInstanceName, pInstanceName, sizeof(BackgroundConsoleInstance.szInstanceName) - 1);
BackgroundConsoleInstance.hConsoleProcess = ProcessInfo.hProcess;
BackgroundConsoleInstance.hConsoleInputPipe = hConsoleInputPipe;
memcpy((void*)pBackgroundConsoleInstance, (void*)&BackgroundConsoleInstance, sizeof(BackgroundConsoleInstance));
return 0;
}
DWORD BackgroundConsole_Close(BackgroundConsoleInstanceStruct* pBackgroundConsoleInstance)
{
// close console input pipe
CloseHandle(pBackgroundConsoleInstance->hConsoleInputPipe);
// wait for console process to end
WaitForSingleObject(pBackgroundConsoleInstance->hConsoleProcess, INFINITE);
CloseHandle(pBackgroundConsoleInstance->hConsoleProcess);
return 0;
}
DWORD BackgroundConsole_Exec(BackgroundConsoleInstanceStruct* pBackgroundConsoleInstance, const char* pCommand, DWORD(*pCommandOutput)(BYTE* pBufferData, DWORD dwBufferLength, BYTE* pParam), BYTE* pCommandOutputParam)
{
char szWriteCommand[2048];
char szCommandOutputPipeName[512];
HANDLE hCommandOutputPipe = NULL;
BYTE bReadBuffer[1024];
DWORD dwBytesRead = 0;
// create output pipe
memset(szCommandOutputPipeName, 0, sizeof(szCommandOutputPipeName));
_snprintf(szCommandOutputPipeName, sizeof(szCommandOutputPipeName) - 1, "\\\\.\\pipe\\BackgroundConsoleOut_%s", pBackgroundConsoleInstance->szInstanceName);
hCommandOutputPipe = CreateNamedPipe(szCommandOutputPipeName, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL);
if (hCommandOutputPipe == INVALID_HANDLE_VALUE)
{
// error
return 1;
}
// write command to console
memset(szWriteCommand, 0, sizeof(szWriteCommand));
_snprintf(szWriteCommand, sizeof(szWriteCommand) - 1, "%s > %s\n", pCommand, szCommandOutputPipeName);
if (WriteFile(pBackgroundConsoleInstance->hConsoleInputPipe, szWriteCommand, strlen(szWriteCommand), NULL, NULL) == 0)
{
// error
CloseHandle(hCommandOutputPipe);
return 1;
}
// wait for target to connect to output pipe
if (ConnectNamedPipe(hCommandOutputPipe, NULL) == 0)
{
// error
CloseHandle(hCommandOutputPipe);
return 1;
}
// get data from output pipe
for (;;)
{
// read data from stdout pipe (ensure the buffer is null terminated in case this is string data)
memset(bReadBuffer, 0, sizeof(bReadBuffer));
if (ReadFile(hCommandOutputPipe, bReadBuffer, sizeof(bReadBuffer) - 1, &dwBytesRead, NULL) == 0)
{
// failed - check error code
if (GetLastError() == ERROR_BROKEN_PIPE)
{
// pipe closed
break;
}
else
{
// error
CloseHandle(hCommandOutputPipe);
return 1;
}
}
// send current buffer to output function
if (pCommandOutput(bReadBuffer, dwBytesRead, pCommandOutputParam) != 0)
{
// error
CloseHandle(hCommandOutputPipe);
return 1;
}
}
// close handle
CloseHandle(hCommandOutputPipe);
return 0;
}
DWORD CommandOutput_StoreData(BYTE* pBufferData, DWORD dwBufferLength, BYTE* pParam)
{
CommandOutput_StoreDataParamStruct* pCommandOutput_StoreDataParam = NULL;
// get param
pCommandOutput_StoreDataParam = (CommandOutput_StoreDataParamStruct*)pParam;
// check if an output buffer was specified
if (pCommandOutput_StoreDataParam->pOutputPtr != NULL)
{
// validate length
if (dwBufferLength > (pCommandOutput_StoreDataParam->dwMaxOutputSize - pCommandOutput_StoreDataParam->dwTotalSize))
{
return 1;
}
// copy data
memcpy((void*)(pCommandOutput_StoreDataParam->pOutputPtr + pCommandOutput_StoreDataParam->dwTotalSize), pBufferData, dwBufferLength);
}
// increase output size
pCommandOutput_StoreDataParam->dwTotalSize += dwBufferLength;
return 0;
}
// www.x86matthew.com
int main()
{
BackgroundConsoleInstanceStruct BackgroundConsoleInstance;
CommandOutput_StoreDataParamStruct CommandOutput_StoreDataParam;
BYTE* pNtdllCopy = NULL;
DWORD dwAllocSize = 0;
printf("Creating hidden cmd.exe process...\n");
// create background console
if (BackgroundConsole_Create("mytest", &BackgroundConsoleInstance) != 0)
{
return 1;
}
printf("Retrieving ntdll file size...\n");
// call the function with a blank output buffer to retrieve the file size
memset((void*)&CommandOutput_StoreDataParam, 0, sizeof(CommandOutput_StoreDataParam));
CommandOutput_StoreDataParam.pOutputPtr = NULL;
CommandOutput_StoreDataParam.dwMaxOutputSize = 0;
CommandOutput_StoreDataParam.dwTotalSize = 0;
if (BackgroundConsole_Exec(&BackgroundConsoleInstance, "type %windir%\\system32\\ntdll.dll", CommandOutput_StoreData, (BYTE*)&CommandOutput_StoreDataParam) != 0)
{
return 1;
}
printf("ntdll.dll file size: %u bytes - allocating memory...\n", CommandOutput_StoreDataParam.dwTotalSize);
// allocate memory
dwAllocSize = CommandOutput_StoreDataParam.dwTotalSize;
pNtdllCopy = (BYTE*)malloc(dwAllocSize);
if (pNtdllCopy == NULL)
{
return 1;
}
printf("Reading ntdll.dll data from disk...\n");
// call the function again to read the file contents
memset((void*)&CommandOutput_StoreDataParam, 0, sizeof(CommandOutput_StoreDataParam));
CommandOutput_StoreDataParam.pOutputPtr = pNtdllCopy;
CommandOutput_StoreDataParam.dwMaxOutputSize = dwAllocSize;
CommandOutput_StoreDataParam.dwTotalSize = 0;
if (BackgroundConsole_Exec(&BackgroundConsoleInstance, "type %windir%\\system32\\ntdll.dll", CommandOutput_StoreData, (BYTE*)&CommandOutput_StoreDataParam) != 0)
{
return 1;
}
printf("Read %u bytes successfully\n", CommandOutput_StoreDataParam.dwTotalSize);
// (pNtdllCopy now contains a copy of ntdll)
// clean up
free(pNtdllCopy);
BackgroundConsole_Close(&BackgroundConsoleInstance);
return 0;
}