Writing Files from Low-Integrity Processes

Internet Explorer 7 introduced Protected Mode, which uses Windows' Integrity Controls feature to help prevent the contamination of the system with data that originates from the Internet. As a part of this feature, Internet Explorer now maintains two stores for the Temporary Internet Files and two Cookie Jars to store the user's cookies. For each, there are Low Integrity stores used by Protected Mode Internet Explorer instances, and Medium Integrity stores that are shared by Internet Explorer instances (and other applications using WinINET) running at Medium or High integrity levels.

As a side-effect of these changes, it became significantly harder for normal applications (running at Medium integrity) to examine the data storage for Internet content that was downloaded by a Protected Mode Internet Explorer instance. In some cases, we added new APIs, like IEGetProtectedModeCookie to allow a Medium Integrity process to read a Low Integrity cookie. However, most APIs, like FindFirstUrlCacheEntry, will return only data from the stores matching the current process' Integrity Level.

I recently wished to write a program which would list out the contents of both of the Internet Explorer Temporary Internet Files stores. I wrote a program which simply writes metadata about each cache entry to a file. The program starts at Medium Integrity and writes metadata of each entry in the Medium Integrity cache to CacheData.txt. The program then respawns itself running at Low Integrity and that instance writes metadata of each entry in the Low Integrity cache to CacheData_Low.txt.

I used the CSCreateLowIntegrityProcess example from the All-In-One Code Framework library to respawn my process at Low Integrity, but quickly found a problem—the Low Integrity process could not create a new file because there are only a few locations on disk where Low Integrity processes can write. While writing to one of the reserved sandboxed locations is fine for some needs, I really wanted to keep both text files in the same folder.

The solution is fairly simple:

  1. Have the initial (Medium Integrity) process create the CacheData_Low.txt file
  2. Have the initial process update the Mandatory Label on this file with a marker indicating it may be updated by a low-integrity process
  3. Pass the name of this file from the initial process to the Low Rights process

As it turns out, updating the Mandatory Label for a file from .NET is non-trivial, because the needed APIs must be P/Invoked. Additionally, you must ensure that you take the TakeOwnership right for the file in order to have permission to update the Mandatory Label. The code I used is as follows:

using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;

 // ...
 /// <summary>
/// Marks an existing file as writable from Low Integrity
/// </summary>
/// <param name="sFileName">The file to mark as writable</param>
/// <returns>TRUE if successful, FALSE if Label could not be updated. Exception on other failures.</returns>
internal static bool MarkFileLowILWritable(string sFileName)
{
    bool bResult = false;
    IntPtr pSD = IntPtr.Zero;
    IntPtr pSacl = IntPtr.Zero;
    IntPtr lpbSaclPresent = IntPtr.Zero;
    IntPtr lpbSaclDefaulted = IntPtr.Zero;
    uint securityDescriptorSize = 0;

    // Ensure that we have the TakeOwnership right that allows modification of the Mandatory Label
    FileSecurity fSecurity = File.GetAccessControl(sFileName);
    fSecurity.AddAccessRule(new FileSystemAccessRule(WindowsIdentity.GetCurrent().Name, FileSystemRights.TakeOwnership, AccessControlType.Allow));
    File.SetAccessControl(sFileName, fSecurity);

    // Set the Low-Writable Mandatory label
    if (ConvertStringSecurityDescriptorToSecurityDescriptorW("S:(ML;;NW;;;LW)", 1, ref pSD, ref securityDescriptorSize))
    {
        if (GetSecurityDescriptorSacl(pSD, out lpbSaclPresent, out pSacl, out lpbSaclDefaulted))
        {
            uint result = SetNamedSecurityInfoW(sFileName, SE_OBJECT_TYPE.SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, pSacl);
            bResult = (result == 0);
        }
        LocalFree(pSD);
    }

    return bResult;
}

The P/Invoke declarations and constants are as follows:

 public const int LABEL_SECURITY_INFORMATION = 0x00000010;

public enum SE_OBJECT_TYPE
{
    SE_UNKNOWN_OBJECT_TYPE = 0,
    SE_FILE_OBJECT,
    SE_SERVICE,
    SE_PRINTER,
    SE_REGISTRY_KEY,
    SE_LMSHARE,
    SE_KERNEL_OBJECT,
    SE_WINDOW_OBJECT,
    SE_DS_OBJECT,
    SE_DS_OBJECT_ALL,
    SE_PROVIDER_DEFINED_OBJECT,
    SE_WMIGUID_OBJECT,
    SE_REGISTRY_WOW64_32KEY,
}

[DllImport("advapi32.dll", EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean ConvertStringSecurityDescriptorToSecurityDescriptorW(
    [MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
    UInt32 sDRevision,
    ref IntPtr securityDescriptor,
    ref UInt32 securityDescriptorSize);

[DllImport("advapi32.dll", EntryPoint = "GetSecurityDescriptorSacl")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean GetSecurityDescriptorSacl(
    IntPtr pSecurityDescriptor,
    out IntPtr lpbSaclPresent,
    out IntPtr pSacl,
    out IntPtr lpbSaclDefaulted);

[DllImport("advapi32.dll", EntryPoint = "SetNamedSecurityInfoW")]
public static extern UInt32 SetNamedSecurityInfoW(
    [MarshalAs(UnmanagedType.LPWStr)] String pObjectName,
    SE_OBJECT_TYPE objectType,
    Int32 securityInfo,
    IntPtr psidOwner,
    IntPtr psidGroup,
    IntPtr pDacl,
    IntPtr pSacl);

[DllImport("kernel32.dll", EntryPoint = "LocalFree")]
public static extern UInt32 LocalFree(IntPtr hMem);

Until next time,

-Eric