Why can't I restore files even when I have backup/restore privileges enabled?
Backup and restore privileges allow some, but not all, types of access checks to be bypassed. Typically those are sufficient to allow a backup utility to restore data to even sensitive folders. However, there are some exceptions to that. For example, at the time of this writing, much of the contents under %programfiles%\WindowsApps out of the box won't grant sufficient privileges for restore operations that need to modify data on the disk. If I enable auditing on my Windows 10 1607 system, I may see entries such as this when trying to restore data even when running from a Local System service:
Access Reasons:
- READ_CONTROL: Unknown or unchecked
- WRITE_DAC: Unknown or unchecked
- WRITE_OWNER: Unknown or unchecked
- SYNCHRONIZE: Unknown or unchecked
- ACCESS_SYS_SEC: Unknown or unchecked
- ReadData (or ListDirectory): Unknown or unchecked
- WriteData (or AddFile): Denied by Process Trust Label ACE
- AppendData (or AddSubdirectory or CreatePipeInstance): Denied by Process Trust Label ACE
- WriteEA: Denied by Process Trust Label ACE
- ReadAttributes: Unknown or unchecked
- WriteAttributes: Unknown or unchecked
Access Mask: 0x11E0197
Privileges Used for Access Check:
- SeBackupPrivilege
- SeRestorePrivilege
This is because of the SYSTEM_PROCESS_TRUST_LABEL_ACE present in an object's SACL; while its meaning isn't documented, you can reason that the SYSTEM_PROCESS_TRUST_LABEL_ACE, which you can see on the object through the use of common security APIs, corresponds to the message "Denied by Process Trust Label ACE." You can see that by running code such as (which could easily be modified to check for said ACE if one wanted to do so):
#include <Windows.h>
#include <sddl.h>
#include <AclAPI.h>
#include <winternl.h>
#include <sstream>
typedef NTSTATUS ( __stdcall * PZwQueryDirectoryFile )( HANDLE FileHandle ,
HANDLE Event ,
void * ApcRoutine ,
void * ApcContext ,
PIO_STATUS_BLOCK IoStatusBlock ,
void * FileInformation ,
ULONG Length ,
ULONG FileInformationClass ,
BOOLEAN ReturnSingleEntry ,
PUNICODE_STRING FileName ,
BOOLEAN RestartScan
); typedef struct _FILE_DIRECTORY_INFORMATION {
ULONG NextEntryOffset ;
ULONG FileIndex ;
LARGE_INTEGER CreationTime ;
LARGE_INTEGER LastAccessTime ;
LARGE_INTEGER LastWriteTime ;
LARGE_INTEGER ChangeTime ;
LARGE_INTEGER EndOfFile ;
LARGE_INTEGER AllocationSize ;
ULONG FileAttributes ;
ULONG FileNameLength ;
WCHAR FileName [ 1 ]; } FILE_DIRECTORY_INFORMATION , * PFILE_DIRECTORY_INFORMATION ; PZwQueryDirectoryFile NtQueryDirectoryFile ; enum FileInfoClass : ULONG {
FileDirInformation = 1 }; void EnablePrivileges () {
HANDLE myToken = NULL ;
LUID_AND_ATTRIBUTES attr [ 3 ] = {};
for ( int i = 0 ; i < _countof ( attr ); i ++) attr [ i ]. Attributes = SE_PRIVILEGE_ENABLED ;
LookupPrivilegeValueW ( NULL , SE_BACKUP_NAME , & attr [ 0 ]. Luid );
LookupPrivilegeValueW ( NULL , SE_RESTORE_NAME , & attr [ 1 ]. Luid );
LookupPrivilegeValueW ( NULL , SE_SECURITY_NAME , & attr [ 2 ]. Luid );
DWORD bufferSize = sizeof ( LUID_AND_ATTRIBUTES ) * _countof ( attr ) + sizeof ( DWORD );
PTOKEN_PRIVILEGES tp = ( PTOKEN_PRIVILEGES ) malloc ( bufferSize );
if (! tp ) DebugBreak ();
tp -> PrivilegeCount = _countof ( attr );
memcpy ( tp -> Privileges , attr , sizeof ( attr ));
if (! OpenProcessToken ( GetCurrentProcess (), TOKEN_ADJUST_PRIVILEGES , & myToken )) DebugBreak ();
if (! AdjustTokenPrivileges ( myToken , FALSE , tp , NULL , NULL , NULL )) DebugBreak ();
if ( GetLastError () == ERROR_NOT_ALL_ASSIGNED ) wprintf ( L"Not all privileges were assigned...\n" );
if ( tp ) {
free ( tp );
tp = NULL ;
}
if ( myToken ) CloseHandle ( myToken ); }
void OutputSYSTEM_PROCESS_TRUST_LABEL_ACE_TYPESACLEntries ( LPCWSTR fileName ) {
PSECURITY_DESCRIPTOR psd = NULL ;
PACL dacl = NULL ;
PACL sacl = NULL ;
HANDLE hFile = CreateFile ( fileName , ACCESS_SYSTEM_SECURITY | READ_CONTROL , FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL , OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
if ( hFile == INVALID_HANDLE_VALUE ) DebugBreak ();
int err = GetSecurityInfo ( hFile , SE_OBJECT_TYPE :: SE_FILE_OBJECT , SACL_SECURITY_INFORMATION | PROCESS_TRUST_LABEL_SECURITY_INFORMATION , NULL , NULL , & dacl , & sacl , & psd );
if ( err != 0 ) {
wprintf ( L"Failed to get security info:\t%d\n" , err );
CloseHandle ( hFile );
return ;
}
else
{
ACL_SIZE_INFORMATION sizeInfo = {};
if ( sacl ) {
if ( GetAclInformation ( sacl , & sizeInfo , sizeof ( sizeInfo ), AclSizeInformation )) {
for ( ULONG i = 0 ; i < sizeInfo . AceCount ; i ++) {
PACE_HEADER ah = NULL ;
if ( GetAce ( sacl , i , ( void **)& ah ) && ah ) {
DWORD type = ah -> AceType ;
DWORD flags = ah -> AceFlags ;
wprintf ( L"System ACE %d is of type %d with flags %x\n" , i , type , flags );
if ( type == SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE ) {
SYSTEM_PROCESS_TRUST_LABEL_ACE * ace = ( SYSTEM_PROCESS_TRUST_LABEL_ACE *) ah ;
PSID aceSid = ( PSID )(& ace -> SidStart );
LPWSTR ssid = NULL ;
wprintf ( L"%s:\tProcess Trust Level Ace with access mask %x for SID " , fileName , ace -> Mask );
if ( aceSid == NULL ) {
wprintf ( L"(NULL)\n" );
}
else if ( ConvertSidToStringSidW ( aceSid , & ssid )) {
wprintf ( L"%s\n" , ssid );
LocalFree ( ssid );
}
else {
wprintf ( L"(could not convert)\n" );
}
ace -> Mask = 0 ;
}
}
}
}
}
else wprintf ( L"Null SACL\n" );
}
CloseHandle ( hFile ); } void ProcessDirectory ( LPCWSTR directoryName , int currentRecurseLevel , int maxRecurseLevel ); void ProcessFdi ( LPCWSTR directoryName , PFILE_DIRECTORY_INFORMATION p , int currentRecurseLevel , int maxRecurseLevel ) {
//Skip . and ..
DWORD one = CompareStringOrdinal ( L"." , - 1 , p -> FileName , p -> FileNameLength / 2 , TRUE );
if ( one == CSTR_EQUAL || CompareStringOrdinal ( L".." , - 1 , p -> FileName , p -> FileNameLength / 2 , TRUE ) == CSTR_EQUAL ) return ;
std :: wstringstream wss ;
wss << directoryName ;
if (*(--( wss . str (). end ())). _Ptr != '\\' ) wss << L"\\" ;
wss . write ( p -> FileName , p -> FileNameLength / 2 );
if ( p -> FileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
OutputSYSTEM_PROCESS_TRUST_LABEL_ACE_TYPESACLEntries ( wss . str (). data ());
ProcessDirectory ( wss . str (). data (), currentRecurseLevel + 1 , maxRecurseLevel );
}
else {
OutputSYSTEM_PROCESS_TRUST_LABEL_ACE_TYPESACLEntries ( wss . str (). data ());
} } void ProcessDirectory ( LPCWSTR directoryName , int currentRecurseLevel , int maxRecurseLevel ) {
if ( currentRecurseLevel > maxRecurseLevel ) return ;
HANDLE hf = CreateFile ( directoryName , GENERIC_READ , FILE_SHARE_READ | FILE_SHARE_WRITE , NULL , OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
if ( hf == INVALID_HANDLE_VALUE ) {
wprintf ( L"Failed to process %s:\t%d\n" , directoryName , GetLastError ());
return ;
}
DWORD fniSize = 200000 ; //arbitrarily large for laziness and since we're not going to check status values
void * FileNamesInfo = malloc ( fniSize );
if (! FileNamesInfo ) {
CloseHandle ( hf );
DebugBreak ();
}
IO_STATUS_BLOCK iosb = {};
HRESULT status = HRESULT_FROM_NT ( NtQueryDirectoryFile ( hf , NULL , NULL , NULL , & iosb , FileNamesInfo , fniSize , FileDirInformation , FALSE , NULL , FALSE ));
if ( FAILED ( status )) {
wprintf ( L"Failed to query %s:\t%x\n" , directoryName , status );
CloseHandle ( hf );
free ( FileNamesInfo );
return ;
}
PFILE_DIRECTORY_INFORMATION fdi = ( PFILE_DIRECTORY_INFORMATION ) FileNamesInfo ;
ProcessFdi ( directoryName , fdi , currentRecurseLevel , maxRecurseLevel );
while ( fdi -> NextEntryOffset != 0 ) {
DWORD offset = fdi -> NextEntryOffset ;
fdi = ( PFILE_DIRECTORY_INFORMATION )(( __int3264 ) fdi + offset );
ProcessFdi ( directoryName , fdi , currentRecurseLevel , maxRecurseLevel );
}
free ( FileNamesInfo );
CloseHandle ( hf ); } int main () {
EnablePrivileges ();
NtQueryDirectoryFile = ( PZwQueryDirectoryFile ) GetProcAddress ( GetModuleHandle ( L"ntdll.dll" ), "NtQueryDirectoryFile" );
if (! NtQueryDirectoryFile ) DebugBreak ();
ProcessDirectory ( L"C:\\Program Files\\WindowsApps\\" , 0 , 3 );
return 0 ; }