[英].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)而是通过本地策略 > 启动脚本或作为系统运行的计划任务作为系统执行时:
因此,针对上述问题进行了以下修改:
最后,这是最终的测试客户端和服务器(记住服务器作为计划任务中的系统运行),它允许服务器将数据写入 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.