簡體   English   中英

.NET Memory 映射文件和任務計划程序,從任務運行時客戶端無法訪問服務器創建的 MMF

[英].NET Memory Mapped Files and Task Scheduler, client can't access server-created MMF when running from Task

我正在測試使用 Memory 映射文件。 我有一個寫入命名 MMF 的服務器和一個從同名 MMF 讀取並使用命名信號量來防止同時訪問的客戶端。

當我以交互方式運行客戶端和服務器時,我可以從服務器 -> 客戶端傳遞數據。

當我從計划任務運行服務器並將其配置為僅在用戶登錄時運行(並且我是它正在運行並已登錄的用戶)並以交互方式運行客戶端時,它也可以工作

當我從計划任務運行服務器並將其設置為無論用戶是否登錄或我的實際所需上下文 (SYSTEM) 都運行時,我的客戶端不再可以看到 MMF 已創建(即使我的日志/錯誤捕獲確認這是)。

我似乎錯過了計划任務設置中的一些環境更改,這些更改阻止了我的 MMF 被讀取。 任何人都可以提供任何幫助嗎?

編輯:示例代碼來說明問題。

服務器:

try {
    $mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateOrOpen('LinkMon', 10MB)

    ## Create a mutex that's used to temporaily block access to the Memory Mapped File
    $MmfSemaphore = [System.Threading.Semaphore]::new(1, 1, 'LinkMonSemaphore')
} catch {
    $_
}


while (1) {

    if ([datetime]::Now.Second % 2 -eq 0) {
        $s = Get-Process | Select -First 10
    } else {
        $s = Get-Service | Select -First 10
    }

    $j = $s | ConvertTo-Json
    $b = [System.Text.Encoding]::Ascii.GetBytes($j)

    try {
        $MmfSemaphore.WaitOne()
        $MmfStream = $mmf.CreateViewStream()
        $MmfBw = [System.IO.BinaryWriter]::new($MmfStream)

        ## [System.Text.Encoding]::Ascii.GetString(([System.Text.Encoding]::Ascii.GetBytes((("$($b.Length)").PadLeft(10, '0')))))

        $MmfBw.Write(([System.Text.Encoding]::Ascii.GetBytes((("$($b.Length)").PadLeft(10, '0')))))
        $MmfBw.Write($b)
        $MmfStream.Position = 0  

        $MmfSemaphore.Release()
    } catch {
        $_
    }

    Start-Sleep -Seconds 1
}


$MmfSemaphore.Dispose()
$MmfBw.Dispose()
$MmfStream.Dispose()
$mmf.Dispose()

客戶:

try {
    $mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::OpenExisting('LinkMon')

    $MmfSemaphore = [System.Threading.Semaphore]::OpenExisting('LinkMonSemaphore')
} catch {
    $_
}

while (1) {
    try {
        $MmfSemaphore.WaitOne()

        $MmfStream = $mmf.CreateViewStream()
        $MmfBr = [System.IO.BinaryReader]::new($MmfStream)

        $DataLength = ([int]([System.Text.Encoding]::ASCII.GetString($MmfBr.ReadBytes(10))))
        $Payload = $MmfBr.ReadBytes($DataLength)
        $MmfStream.Position = 0  


        $MmfSemaphore.Release()

        $o = [System.Text.Encoding]::Ascii.GetString($Payload) | ConvertFrom-Json
    } catch {
        $_
    }

    $o | ft
    Start-Sleep -Milliseconds 500
}

$MmfSemaphore.Dispose()
$MmfBr.Dispose()
$MmfStream.Dispose()
$mmf.Dispose()

編輯2:

基於查看一些 C++ 和 C# 實現似乎有類似問題,我嘗試修改 MMF 的安全性,但無濟於事。 這是新代碼,但情況仍然存在:當我在兩個不同的進程中交互地運行客戶端和服務器時,它們可以來回發送數據。 當我在計划任務中作為系統運行服務器時,我無法讀取根據我的調試日志成功創建的 MMF。 當我正常或在提升的上下文中運行 PowerShell 時客戶端收到的錯誤是:使用“1”參數調用“OpenExisting”的異常:“不存在給定名稱的句柄。”

服務器:

$code = @"
using System.IO.MemoryMappedFiles;
using System.Security.AccessControl;
using System.Security.Principal;

namespace mmf
{
    public static class Security
    {
        public static MemoryMappedFileSecurity GetMmfSec() 
        {
            var security = new MemoryMappedFileSecurity();
            security.AddAccessRule(new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MemoryMappedFileRights.FullControl, AccessControlType.Allow));
            return security;
        }
    }
}
"@
Add-Type -Language CSharp $code

$acl = [mmf.Security]::GetMmfSec()

## Create the initial Memory Mapped File and semaphore to synchronize writes to it.
try {
    $mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateOrOpen('Global\Restricted\LinkMon', 10MB, [System.IO.MemoryMappedFiles.MemoryMappedFileAccess]::ReadWrite, [System.IO.MemoryMappedFiles.MemoryMappedFileOptions]::None, $acl, [System.IO.HandleInheritability]::Inheritable)
    $sd = $mmf.GetAccessControl()

    $sd | ConvertTo-Json | Out-File C:\temp\server_access.txt

    ## Create a sempahore that's used to temporaily block access to the Memory Mapped File
    $MmfSemaphore = [System.Threading.Semaphore]::new(1, 1, 'LinkMonSemaphore')
} catch {
    $_
}


while (1) {

    if ([datetime]::Now.Second % 2 -eq 0) {
        $s = Get-Process | Select -First 10
    } else {
        $s = Get-Service | Select -First 10
    }
    $s | ft

    $j = $s | ConvertTo-Json
    $b = [System.Text.Encoding]::Ascii.GetBytes($j)

    try {
        $MmfSemaphore.WaitOne()
        $MmfStream = $mmf.CreateViewStream()
        $MmfBw = [System.IO.BinaryWriter]::new($MmfStream)

        ## [System.Text.Encoding]::Ascii.GetString(([System.Text.Encoding]::Ascii.GetBytes((("$($b.Length)").PadLeft(10, '0')))))

        $MmfBw.Write(([System.Text.Encoding]::Ascii.GetBytes((("$($b.Length)").PadLeft(10, '0')))))
        $MmfBw.Write($b)
        $MmfStream.Position = 0  #Reset the position of the pointer back to the top of the file so we're overwriting the old info

        $MmfSemaphore.Release()
    } catch {
        $_
    }

    Start-Sleep -Milliseconds 500
}

trap {
    $MmfSemaphore.Dispose()
    $MmfBw.Dispose()
    $MmfStream.Dispose()
    $mmf.Dispose()
}

客戶:

try {
    $mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::OpenExisting('Global\Restricted\LinkMon', [System.IO.MemoryMappedFiles.MemoryMappedFileRights]::ReadWrite, [System.IO.HandleInheritability]::Inheritable)

    $MmfSemaphore = [System.Threading.Semaphore]::OpenExisting('LinkMonSemaphore')
} catch {
    $_
    break
}

while (1) {
    try {
        $MmfSemaphore.WaitOne()

        $MmfStream = $mmf.CreateViewStream()
        $MmfBr = [System.IO.BinaryReader]::new($MmfStream)

        $DataLength = ([int]([System.Text.Encoding]::ASCII.GetString($MmfBr.ReadBytes(10))))
        $Payload = $MmfBr.ReadBytes($DataLength)
        $MmfStream.Position = 0  


        $MmfSemaphore.Release()

        $o = [System.Text.Encoding]::Ascii.GetString($Payload) | ConvertFrom-Json
    } catch {
        $_
    }

    $o | ft
    Start-Sleep -Milliseconds 500
}

$MmfSemaphore.Dispose()
$MmfBr.Dispose()
$MmfStream.Dispose()
$mmf.Dispose()

編輯3:

因此,在我注意到 Object 管理器中的 memory 'Section' object 移動后,我經歷了一系列迭代。 我想看看該位置是否重要,或者它是否在不同情況下在 object 上標記了不同的 ACL(它確實移動並最終得到不同的 ACL)。 這是我已經完成的迭代矩陣,如果這有助於任何人幫助我解決這個問題:

在此處輸入圖像描述

編輯4:

我還開始向 MMF 添加Everyone, Full Control 並嘗試迭代無濟於事。

我開始關注流程完整性。 雖然這似乎是一項卓有成效的努力,但當服務器從 powershell_ise 以 SYSTEM 交互方式運行時(使用 psexec 將 powershell_ise 實例生成為 SYSTEM),我仍然可以讓我的客戶端(非提升)從 MMF 讀取數據。 但是,當從計划任務以 SYSTEM 身份運行時,該進程仍以“系統”完整性級別運行,但我仍然無法訪問該文件。

所以……我明白了。 夠瘋狂的,它是我所看到的所有不同事物的組合。 當服務器不是從我的登錄用戶(通過 psexec)而是通過本地策略 > 啟動腳本或作為系統運行的計划任務作為系統執行時:

  1. ...它通過 Windows 完整性隔離來隔離
  2. ...它在 \BaseNamedObjects Kernel 命名空間而不是 \Sessions\1\BaseNamedObjects 作為交互式用戶執行時動態分配(因此您需要更新名稱定位)
  3. ...它被標記了一個新的非常有限的 ACL
  4. 這發生在所有 kernel 級別的對象上——所以我必須在我的 MemoryMappedFile 和我的信號量上考慮這些注意事項

因此,針對上述問題進行了以下修改:

  1. 在運行時,服務器調整 SACL 以允許低完整性進程訪問
  2. 雖然服務器的目標只是名稱“LinkMon”,但客戶端必須定位“Global\LinkMon”才能正確定位 MMF memory 部分的新位置
  3. 服務器使用構造函數創建了 MMF 和信號量,這些構造函數設置了顯式的 ACL 以允許訪問(最終是每個人,完全控制)——我還沒有玩過,我還沒有看到我可以讓它有多么有限,仍然允許訪問。 我可能會嘗試將其進一步限制為 Authenticated Users\Read 之類的內容,但我需要休息一下。

最后,這是最終的測試客戶端和服務器(記住服務器作為計划任務中的系統運行),它允許服務器將數據寫入 MMF 和運行客戶端的標准用戶從 MMF 讀取:

服務器(大量內聯注釋):

## Credits for Standing on the Shoulders of Giants:  ##
## https://stackoverflow.com/questions/41369232/createmutex-fails-after-impersonation
## https://stackoverflow.com/questions/3282365/opening-a-named-pipe-in-low-integrity-level/14424623#14424623
## https://stackoverflow.com/questions/34887587/gaining-access-to-a-memorymappedfile-from-low-integrity-process
## https://blog.didierstevens.com/2010/09/07/integrity-levels-and-dll-injection/
## https://stackoverflow.com/questions/9912534/how-to-create-a-new-process-with-a-lower-integrity-level-il
## https://docs.microsoft.com/en-us/windows/win32/sysinfo/kernel-objects
## https://docs.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces
## https://www.tiraniddo.dev/2019/02/a-brief-history-of-basenamedobjects-on.html


Add-Type -Language CSharp @"
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace Native
{
    public static class NativeMethods
    {
        public const string LOW_INTEGRITY_SSL_SACL = "S:(ML;;NW;;;LW)";

        public static int ERROR_SUCCESS = 0x0;

        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 ConvertStringSecurityDescriptorToSecurityDescriptor(
            [MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
            UInt32 sDRevision,
            ref IntPtr securityDescriptor,
            ref UInt32 securityDescriptorSize);

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

        [DllImport("Advapi32.dll", EntryPoint = "SetSecurityInfo")]
        public static extern int SetSecurityInfo(SafeHandle hFileMappingObject,
                                                    SE_OBJECT_TYPE objectType,
                                                    Int32 securityInfo,
                                                    IntPtr psidOwner,
                                                    IntPtr psidGroup,
                                                    IntPtr pDacl,
                                                    IntPtr pSacl);
        [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);
    }

    public class InterProcessSecurity
    {

        public static void SetLowIntegrityLevel(SafeHandle hObject)
        {
            IntPtr pSD = IntPtr.Zero;
            IntPtr pSacl;
            IntPtr lpbSaclPresent;
            IntPtr lpbSaclDefaulted;
            uint securityDescriptorSize = 0;

            if (NativeMethods.ConvertStringSecurityDescriptorToSecurityDescriptor(NativeMethods.LOW_INTEGRITY_SSL_SACL, 1, ref pSD, ref securityDescriptorSize))
            {
                if (NativeMethods.GetSecurityDescriptorSacl(pSD, out lpbSaclPresent, out pSacl, out lpbSaclDefaulted))
                {
                    var err = NativeMethods.SetSecurityInfo(hObject,
                                                  NativeMethods.SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
                                                  NativeMethods.LABEL_SECURITY_INFORMATION,
                                                  IntPtr.Zero,
                                                  IntPtr.Zero,
                                                  IntPtr.Zero,
                                                  pSacl);
                    if (err != NativeMethods.ERROR_SUCCESS)
                    {
                        throw new Win32Exception(err);
                    }
                }
                NativeMethods.LocalFree(pSD);
            }
        }
    }
}
"@



Add-Type -Language CSharp @"
using System.IO.MemoryMappedFiles;
using System.Security.AccessControl;
using System.Security.Principal;

namespace ipc
{
    public static class Security
    {
        public static MemoryMappedFileSecurity GetMmfSec() 
        {
            var security = new MemoryMappedFileSecurity();
            security.AddAccessRule(new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MemoryMappedFileRights.FullControl, AccessControlType.Allow));
            security.AddAccessRule(new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), MemoryMappedFileRights.FullControl, AccessControlType.Allow));
            security.AddAccessRule(new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>(new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null), MemoryMappedFileRights.FullControl, AccessControlType.Allow));
            return security;
        }

        public static SemaphoreSecurity GetSemaphoreSec()
        {
            var security = new SemaphoreSecurity();
            security.AddAccessRule(new System.Security.AccessControl.SemaphoreAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), SemaphoreRights.FullControl, AccessControlType.Allow));
            security.AddAccessRule(new System.Security.AccessControl.SemaphoreAccessRule(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), SemaphoreRights.FullControl, AccessControlType.Allow));
            security.AddAccessRule(new System.Security.AccessControl.SemaphoreAccessRule(new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null), SemaphoreRights.FullControl, AccessControlType.Allow));
            return security;
        }
    }
}
"@

## Get the ACLs to set in the constructors for the MMF and Semaphore to pass to the constructor on kernel level object creation
$mmfacl = [ipc.Security]::GetMmfSec()
$semaphacl = [ipc.Security]::GetSemaphoreSec()

$semaphCreated = $false

## Create the initial Memory Mapped File and semaphore to synchronize writes to the MMF--much of this only matters when the interactive user isn't executing the server (e.g. from Service, Scheduled Task, Startup Script, etc.)
try {
    $mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateOrOpen('LinkMon', 10MB, [System.IO.MemoryMappedFiles.MemoryMappedFileAccess]::ReadWrite, [System.IO.MemoryMappedFiles.MemoryMappedFileOptions]::None, $mmfacl, [System.IO.HandleInheritability]::Inheritable)

    ## Set the server process integrity level to low
    [Native.InterProcessSecurity]::SetLowIntegrityLevel($mmf.SafeMemoryMappedFileHandle)

    ## Create a sempahore that's used to temporaily block access to the Memory Mapped File
    $MmfSemaphore = [System.Threading.Semaphore]::new(1, 1, 'LinkMonSemaphore', [ref]$semaphCreated, $semaphacl)
} catch {
    $_
    break
}

## Infinite loop to constantly send data to the MMF, test MMF contention via the Semaphore between the client and server, and see passing data between them as well
while (1) {

    ## Alternate every two seconds to send random data to the MMF to test catching it with the client
    if ([datetime]::Now.Second % 2 -eq 0) {
        $s = Get-Process | Select -First 10
    } else {
        $s = Get-Service | Select -First 10
    }
    $s | ft

    ## Convert the object to text (serialize) and get the bytes to send to the MMF -- Despite it's name, it's a buffer more-so than a file
    $j = $s | ConvertTo-Json
    $b = [System.Text.Encoding]::Ascii.GetBytes($j)

    ## Get a kernel lock via the Semaphore, and write our binary data to the MMF
    try {
        $MmfSemaphore.WaitOne()
        $MmfStream = $mmf.CreateViewStream()
        $MmfBw = [System.IO.BinaryWriter]::new($MmfStream)

        ## Write the length of the data in the first 10 bytes so the client knows how far to read into the file and then write the data
        ## Otherwise, you'll be reading the entire size of the statically assinged MMF each time
        $MmfBw.Write(([System.Text.Encoding]::Ascii.GetBytes((("$($b.Length)").PadLeft(10, '0')))))
        $MmfBw.Write($b)
        $MmfStream.Position = 0  #Reset the position of the pointer back to the top of the file so we're overwriting the old info

        ## Release the kernel level lock
        
    } catch {
        $_
    } finally {
        $MmfSemaphore.Release()
    }

    Start-Sleep -Milliseconds 500
}


$MmfSemaphore.Dispose()
$MmfBw.Dispose()
$MmfStream.Dispose()
$mmf.Dispose()


## TODO:
## One of the significant problems I've run into with the Semaphore and PowerShell if that if you close PowerShell (either the client or server) while the lock is acquired, the lock remains.
## Since there is no 'using' construct analogous to that in Csharp that auto-implements IDisposable, all Semaphore locks should have their releases in a Try/Finally block. 

客戶端(文檔不多,但與服務器相同或相反):

try {
    ## Notice the targeted name here is not the same:  the server targets, 'LinkMon', but since it's running as System from a Scheduled Task, it's created in the Global BaseNamedObject Kernel Namespace so it must target that new location
    ## HUGE thanks to the Sysinternals team--I used WinObj.exe a lot to see the different conditions throughout all of this
    $mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::OpenExisting('Global\LinkMon')

    $MmfSemaphore = [System.Threading.Semaphore]::OpenExisting('Global\LinkMonSemaphore')
} catch {
    $_
    break
}

while (1) {
    try {
        $MmfSemaphore.WaitOne()

        $MmfStream = $mmf.CreateViewStream()
        $MmfBr = [System.IO.BinaryReader]::new($MmfStream)

        $DataLength = ([int]([System.Text.Encoding]::ASCII.GetString($MmfBr.ReadBytes(10))))
        $Payload = $MmfBr.ReadBytes($DataLength)
        $MmfStream.Position = 0  

        $o = [System.Text.Encoding]::Ascii.GetString($Payload)
    } catch {
        $_
    } finally {
        $MmfSemaphore.Release()
    }

    $o | ft
    Start-Sleep -Milliseconds 500
}

$MmfSemaphore.Dispose()
$MmfBr.Dispose()
$MmfStream.Dispose()
$mmf.Dispose()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM