简体   繁体   English

.NET Memory 映射文件和任务计划程序,从任务运行时客户端无法访问服务器创建的 MMF

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

I'm testing out working with Memory Mapped Files.我正在测试使用 Memory 映射文件。 I have a server that writes to a named MMF and a client that reads from the same-named MMF and using a named Semaphore for simultaneous access prevention.我有一个写入命名 MMF 的服务器和一个从同名 MMF 读取并使用命名信号量来防止同时访问的客户端。

When I run both the client and the server interactively, I can pass data from server -> client.当我以交互方式运行客户端和服务器时,我可以从服务器 -> 客户端传递数据。

When I run the server from a Scheduled Task and configure it to only run when the user's logged in (and I'm the user who it's running as and is logged in), and run the client interactively, it also works当我从计划任务运行服务器并将其配置为仅在用户登录时运行(并且我是它正在运行并已登录的用户)并以交互方式运行客户端时,它也可以工作

When I run the server from a Scheduled Task and set it to run whether or not the user's logged in OR my actual desired context (SYSTEM), my client no longer can see that the MMF is created (even though my log/error trapping confirms that it is).当我从计划任务运行服务器并将其设置为无论用户是否登录或我的实际所需上下文 (SYSTEM) 都运行时,我的客户端不再可以看到 MMF 已创建(即使我的日志/错误捕获确认这是)。

I seem to be missing some environment change in the Scheduled Task settings that's preventing my MMF from being read.我似乎错过了计划任务设置中的一些环境更改,这些更改阻止了我的 MMF 被读取。 Can anyone provide any assistance?任何人都可以提供任何帮助吗?

Edit: Sample code to illustrate the problem.编辑:示例代码来说明问题。

Server:服务器:

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()

Client:客户:

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()

Edit2:编辑2:

Based on looking at some C++ and C# implementations that appeared to have similar problems, I tried modifying the security of the MMF but to no avail.基于查看一些 C++ 和 C# 实现似乎有类似问题,我尝试修改 MMF 的安全性,但无济于事。 Here is the new code and the situation still stands: When I run the client and server interactively in two different processes, they can send data back and forth.这是新代码,但情况仍然存在:当我在两个不同的进程中交互地运行客户端和服务器时,它们可以来回发送数据。 When I run the server in a Scheduled Task as System, I cannot read the MMF that according to my debug log, was successfully created.当我在计划任务中作为系统运行服务器时,我无法读取根据我的调试日志成功创建的 MMF。 The error the client receives when I run PowerShell normally or in elevated context is: Exception calling "OpenExisting" with "1" argument(s): "No handle of the given name exists."当我正常或在提升的上下文中运行 PowerShell 时客户端收到的错误是:使用“1”参数调用“OpenExisting”的异常:“不存在给定名称的句柄。”

Server:服务器:

$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()
}

Client:客户:

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()

EDIT3:编辑3:

So I went through a bunch of iterations after I noticed that the memory 'Section' object in the Object Manager was moving around.因此,在我注意到 Object 管理器中的 memory 'Section' object 移动后,我经历了一系列迭代。 I wanted to see if the location appeared to matter or if it stamped a different ACL on the object in different cases (and it did move around and end up with different ACLs).我想看看该位置是否重要,或者它是否在不同情况下在 object 上标记了不同的 ACL(它确实移动并最终得到不同的 ACL)。 Here is the matrix of iterations I've gone though if this helps anyone help me troubleshoot this:这是我已经完成的迭代矩阵,如果这有助于任何人帮助我解决这个问题:

在此处输入图像描述

EDIT4:编辑4:

I also started adding Everyone, Full Control to the MMF and tried the iterations to no avail.我还开始向 MMF 添加Everyone, Full Control 并尝试迭代无济于事。

I started looking at Process Integrity.我开始关注流程完整性。 And while that seemed like a fruitful endeavor, when the server is running as SYSTEM from powershell_ise interactively (use psexec to spawn an instance of powershell_ise as SYSTEM), I can still have my client (non-elevated) read data from the MMF.虽然这似乎是一项卓有成效的努力,但当服务器从 powershell_ise 以 SYSTEM 交互方式运行时(使用 psexec 将 powershell_ise 实例生成为 SYSTEM),我仍然可以让我的客户端(非提升)从 MMF 读取数据。 However, when running as SYSTEM from a Scheduled Task, the process is still running with 'System' Integrity level but I still can't access the file.但是,当从计划任务以 SYSTEM 身份运行时,该进程仍以“系统”完整性级别运行,但我仍然无法访问该文件。

So... I got it.所以……我明白了。 Crazy enough, it was a combination of all the different things I was looking at.够疯狂的,它是我所看到的所有不同事物的组合。 When the server is executed as System not from my logged on user (via psexec) but via either Local Policy > Startup Script or Scheduled Task running as System:当服务器不是从我的登录用户(通过 psexec)而是通过本地策略 > 启动脚本或作为系统运行的计划任务作为系统执行时:

  1. ...it gets isolated with Windows Integrity isolations ...它通过 Windows 完整性隔离来隔离
  2. ...it gets dynamically allocated in the \BaseNamedObjects Kernel namespace instead of the \Sessions\1\BaseNamedObjects as it does when executed as an interactive user (so you need to update your name targeting) ...它在 \BaseNamedObjects Kernel 命名空间而不是 \Sessions\1\BaseNamedObjects 作为交互式用户执行时动态分配(因此您需要更新名称定位)
  3. ...it gets stamped with a new and very limited ACL ...它被标记了一个新的非常有限的 ACL
  4. This happens to ALL kernel level objects -- so I had to account for these considerations on my MemoryMappedFile AND my Semaphore这发生在所有 kernel 级别的对象上——所以我必须在我的 MemoryMappedFile 和我的信号量上考虑这些注意事项

So the following modifications were made to address the aforementioned points:因此,针对上述问题进行了以下修改:

  1. At runtime, the server adjusts the SACL to allow access to by Low Integrity Processes在运行时,服务器调整 SACL 以允许低完整性进程访问
  2. While the server targets, just the name, 'LinkMon', the client had to target, 'Global\LinkMon' to properly locate the new location of the MMF memory section虽然服务器的目标只是名称“LinkMon”,但客户端必须定位“Global\LinkMon”才能正确定位 MMF memory 部分的新位置
  3. The server created the MMF and Semaphore with Constructors that set explicit ACLs to allow for access (ultimately Everyone, Full Control) -- I haven't played seeing how limited I can make this to still allow access.服务器使用构造函数创建了 MMF 和信号量,这些构造函数设置了显式的 ACL 以允许访问(最终是每个人,完全控制)——我还没有玩过,我还没有看到我可以让它有多么有限,仍然允许访问。 I'll probably try limiting this further to something like Authenticated Users\Read, but I need a break.我可能会尝试将其进一步限制为 Authenticated Users\Read 之类的内容,但我需要休息一下。

And finally, here are the final test client and server (remember server running as System from a Scheduled Task) that allows the server to write data to the MMF and a standard user running client to read from the MMF:最后,这是最终的测试客户端和服务器(记住服务器作为计划任务中的系统运行),它允许服务器将数据写入 MMF 和运行客户端的标准用户从 MMF 读取:

Server (heavy inline comments):服务器(大量内联注释):

## 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. 

Client (not so heavily documented but much is the same or in reverse of the server):客户端(文档不多,但与服务器相同或相反):

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