Windows process with Untrusted Integrity level - windows

I couldn't find much information about Untrusted integrity level in Windows, and have some questions about it:
Is there a place where an untrusted integrity level process can create named objects? (mutexes, events, etc..)
Should untrusted integrity level process be able to open an existing named object, that was given a security descriptor in it's creation time with ACE with SYSTEM_MANDATORY_LABEL_NO_WRITE_UP to MandatoryLevelUntrusted? When I try it, it fails with 0xc0000022(access denied), while with MandatoryLevelLow it works great.
How do usually untrusted integrity processes communicate with their broker process? (like how does a google chrome tab communicates with the google chrome broker?)

Is there a place where an untrusted integrity level process can create
named objects? (mutexes, events, etc..)
by default - no. code with untrusted token (thread or process) can create object only in directory with Untrusted Mandatory Level - no one standard folders have this kind of label. some have Low Mandatory Level but untrusted - no.
but you can easy create this folder yourself. with Untrusted Mandatory Level and NULL DACL - untrusted code can create objects in this folder.
NTSTATUS CreateUntrustedFolder(PHANDLE phObject, PCUNICODE_STRING ObjectName)
{
ULONG cb = MAX_SID_SIZE;
PSID UntrustedSid = (PSID)alloca(MAX_SID_SIZE);
if (CreateWellKnownSid(WinUntrustedLabelSid, 0, UntrustedSid, &cb))
{
PACL Sacl = (PACL)alloca(cb += sizeof(ACL) + sizeof(ACE_HEADER) + sizeof(ACCESS_MASK));
InitializeAcl(Sacl, cb, ACL_REVISION);
if (AddMandatoryAce(Sacl, ACL_REVISION, 0, 0, UntrustedSid))
{
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
SetSecurityDescriptorSacl(&sd, TRUE, Sacl, FALSE);
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, (PUNICODE_STRING)ObjectName, OBJ_CASE_INSENSITIVE|OBJ_OPENIF, &sd };
return ZwCreateDirectoryObject(phObject, DIRECTORY_ALL_ACCESS, &oa);
}
}
return STATUS_UNSUCCESSFUL;
}
about untrusted code creation - if start process at begin with token marked as untrusted integrity level - process fail to start. this when ntdll.dll try load kernel32.dll - it try open section \KnownDlls\kernel32.dll with SECTION_MAP_WRITE as well , but this object have Low Mandatory Level with SYSTEM_MANDATORY_LABEL_NO_WRITE_UP - as result untrusted code fail open this section with write access.
as result you need initially create process say with Low Mandatory Level and then set untrusted level
ULONG SetProcessUntrusted(HANDLE hProcess)
{
TOKEN_MANDATORY_LABEL tml = { { (PSID)alloca(MAX_SID_SIZE), SE_GROUP_INTEGRITY } };
ULONG cb = MAX_SID_SIZE;
HANDLE hToken;
if (!CreateWellKnownSid(WinUntrustedLabelSid, 0, tml.Label.Sid, &cb) ||
!OpenProcessToken(hProcess, TOKEN_ADJUST_DEFAULT, &hToken))
{
return GetLastError();
}
ULONG dwError = NOERROR;
if (!SetTokenInformation(hToken, TokenIntegrityLevel, &tml, sizeof(tml)))
{
dwError = GetLastError();
}
CloseHandle(hToken);
return dwError;
}
Should untrusted integrity level process be able to open an existing
named object
this depend from object label(level and mask) , code intergrity level and required access. if code intergrity level >= object label level - we can open object (if dacl let do this). otherwise need look for object label mask and required access. for example object have Low Mandatory Level with SYSTEM_MANDATORY_LABEL_NO_WRITE_UP and code Untrusted Mandatory Level - this code can open object with read and execute access, but fail open it for write access

Related

Getting process information of every process

I am trying to create a program that any normal user can run on windows and generate a process list of all processes, including the executable location. I have used CreateToolhelp32Snapshot() to get all process names, pid, ppid. But having issues getting the image path. Everything I do results in pretty much Access Denied.
I have tried ZwQueryInformationProcess, GetProcessImageFileName, etc. and also using OpenProcess to get the handle to each process. I can get the handle by using PROCESS_QUERY_LIMITED_INFORMATION, but any other option doesn't work. I am lost and have been at this for a few days. Can anyone point me in the right direction?
This is the code that works for non-admin user on Windows. Use the szExeFile member of PROCESSENTRY32 to get the path:
HANDLE hProcessSnap = NULL;
HANDLE hProcess = NULL;
PROCESSENTRY32 pe32;
DWORD dwPriorityClass = 0;
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
return;
}
// Set the size of the structure before using it.
pe32.dwSize = sizeof(PROCESSENTRY32);
// Retrieve information about the first process,
// and exit if unsuccessful
if (!Process32First(hProcessSnap, &pe32))
{
CloseHandle(hProcessSnap); // clean the snapshot object
return;
}
// Now walk the snapshot of processes, and
// display information about each process in turn
do
{
// do something with the pe32 struct.
// pe32.szExeFile -> path of the file
} while (Process32Next(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);

Using IRPs for I/O on device object returned by IoGetDeviceObjectPointer()

Can one use IoCallDriver() with an IRP created by IoBuildAsynchronousFsdRequest() on a device object returned by IoGetDeviceObjectPointer()? What I have currently fails with blue screen (BSOD) 0x7E (unhandled exception), which when caught shows an Access Violation (0xc0000005). Same code worked when the device was stacked (using the device object returned by IoAttachDeviceToDeviceStack()).
So what I have is about the following:
status = IoGetDeviceObjectPointer(&device_name, FILE_ALL_ACCESS, &FileObject, &windows_device);
if (!NT_SUCCESS(status)) {
return -1;
}
offset.QuadPart = 0;
newIrp = IoBuildAsynchronousFsdRequest(io, windows_device, buffer, 4096, &offset, &io_stat);
if (newIrp == NULL) {
return -1;
}
IoSetCompletionRoutine(newIrp, DrbdIoCompletion, bio, TRUE, TRUE, TRUE);
status = ObReferenceObjectByPointer(newIrp->Tail.Overlay.Thread, THREAD_ALL_ACCESS, NULL, KernelMode);
if (!NT_SUCCESS(status)) {
return -1;
}
status = IoCallDriver(bio->bi_bdev->windows_device, newIrp);
if (!NT_SUCCESS(status)) {
return -1;
}
return 0;
device_name is \Device\HarddiskVolume7 which exists according to WinObj.exe .
buffer has enough space and is read/writable. offset and io_stat are on stack (also tried with heap, didn't help). When catching the exception (SEH exception) it doesn't blue screen but shows an access violation as reason for the exception. io is IRP_MJ_READ.
Do I miss something obvious? Is it in general better to use IRPs than the ZwCreateFile / ZwReadFile / ZwWriteFile API (which would be an option, but isn't that slower?)? I also tried a ZwCreateFile to have an extra reference, but this also didn't help.
Thanks for any insights.
you make in this code how minimum 2 critical errors.
can I ask - from which file you try read (or write) data ? from
FileObject you say ? but how file system driver, which will handle
this request know this ? you not pass any file object to newIrp.
look for IoBuildAsynchronousFsdRequest - it have no file object
parameter (and impossible get file object from device object - only
visa versa - because on device can be multiple files open). so it
and can not be filled by this api in newIrp. you must setup it
yourself:
PIO_STACK_LOCATION irpSp = IoGetNextIrpStackLocation( newIrp );
irpSp->FileObject = FileObject;
I guess bug was exactly when file system try access FileObject
from irp which is 0 in your case. also read docs for
IRP_MJ_READ - IrpSp->FileObject -
Pointer to the file object that is associated with DeviceObject
you pass I guess local variables io_stat (and offset) to
IoBuildAsynchronousFsdRequest. as result io_stat must be valid
until newIrp is completed - I/O subsystem write final result to it
when operation completed. but you not wait in function until request
will be completed (in case STATUS_PENDING returned) but just exit
from function. as result later I/O subsystem, if operation completed
asynchronous, write data to arbitrary address &io_stat (it became
arbitrary just after you exit from function). so you need or check
for STATUS_PENDING returned and wait in this case (have actually
synchronous io request). but more logical use
IoBuildSynchronousFsdRequest in this case. or allocate io_stat
not from stack, but say in your object which correspond to file. in
this case you can not have more than single io request with this
object at time. or if you want exactly asynchronous I/O - you can do
next trick - newIrp->UserIosb = &newIrp->IoStatus. as result you
iosb always will be valid for newIrp. and actual operation status
you check/use in DrbdIoCompletion
also can you explain (not for me - for self) next code line ?:
status = ObReferenceObjectByPointer(newIrp->Tail.Overlay.Thread, THREAD_ALL_ACCESS, NULL, KernelMode);
who and where dereference thread and what sense in this ?
Can one use ...
we can use all, but with condition - we understand what we doing and deep understand system internally.
Is it in general better to use IRPs than the ZwCreateFile / ZwReadFile
/ ZwWriteFile API
for performance - yes, better. but this require more code and more complex code compare api calls. and require more knowledge. also if you know that previous mode is kernel mode - you can use NtCreateFile, NtWriteFile, NtReadFile - this of course will be bit slow (need every time reference file object by handle) but more faster compare Zw version
Just wanted to add that the ObReferenceObjectByPointer is needed
because the IRP references the current thread which may exit before
the request is completed. It is dereferenced in the Completion
Routine. Also as a hint the completion routine must return
STATUS_MORE_PROCESSING_REQUIRED if it frees the IRP (took me several
days to figure that out).
here you make again several mistakes. how i understand you in completion routine do next:
IoFreeIrp(Irp);
return StopCompletion;
but call simply call IoFreeIrp here is error - resource leak. i advice you check (DbgPrint) Irp->MdlAddress at this point. if you read data from file system object and request completed asynchronous - file system always allocate Mdl for access user buffer in arbitrary context. now question - who free this Mdl ? IoFreeIrp - simply free Irp memory - nothing more. you do this yourself ? doubt. but Irp is complex object, which internally hold many resources. as result need not only free it memory but call "destructor" for it. this "destructor" is IofCompleteRequest. when you return StopCompletion (=STATUS_MORE_PROCESSING_REQUIRED) you break this destructor at very begin. but you must latter again call IofCompleteRequest for continue Irp (and it resources) correct destroy.
about referencing Tail.Overlay.Thread - what you doing - have no sense:
It is dereferenced in the Completion Routine.
but IofCompleteRequest access Tail.Overlay.Thread after it
call your completion routine (and if you not return
StopCompletion). as result your reference/dereference thread lost
sense - because you deference it too early, before system
actually access it.
also if you return StopCompletion and not more call
IofCompleteRequest for this Irp - system not access
Tail.Overlay.Thread at all. and you not need reference it in this
case.
and exist else one reason, why reference thread is senseless. system
access Tail.Overlay.Thread only for insert Apc to him - for call
final part (IopCompleteRequest) of Irp destruction in original
thread context. really this need only for user mode Irp's requests,
where buffers and iosb located in user mode and valid only in
context of process (original thread ). but if thread is terminated -
call of KeInsertQueueApc fail - system not let insert apc to
died thread. as result IopCompleteRequest will be not called and
resources not freed.
so you or dereference Tail.Overlay.Thread too early or you not need do this at all. and reference for died thread anyway not help. in all case what you doing is error.
you can try do next here:
PETHREAD Thread = Irp->Tail.Overlay.Thread;
IofCompleteRequest(Irp, IO_NO_INCREMENT);// here Thread will be referenced
ObfDereferenceObject(Thread);
return StopCompletion;
A second call to IofCompleteRequest causes the I/O manager to resume calling the IRP's completion. here io manager and access Tail.Overlay.Thread insert Apc to him. and finally you call ObfDereferenceObject(Thread); already after system access it and return StopCompletion for break first call to IofCompleteRequest. look like correct but.. if thread already terminated, how i explain in 3 this will be error, because KeInsertQueueApc fail. for extended test - call IofCallDriver from separate thread and just exit from it. and in completion run next code:
PETHREAD Thread = Irp->Tail.Overlay.Thread;
if (PsIsThreadTerminating(Thread))
{
DbgPrint("ThreadTerminating\n");
if (PKAPC Apc = (PKAPC)ExAllocatePool(NonPagedPool, sizeof(KAPC)))
{
KeInitializeApc(Apc, Thread, 0, KernelRoutine, 0, 0, KernelMode, 0);
if (!KeInsertQueueApc(Apc, 0, 0, IO_NO_INCREMENT))
{
DbgPrint("!KeInsertQueueApc\n");
ExFreePool(Apc);
}
}
}
PMDL MdlAddress = Irp->MdlAddress;
IofCompleteRequest(Irp, IO_NO_INCREMENT);
ObfDereferenceObject(Thread);
if (MdlAddress == Irp->MdlAddress)
{
// IopCompleteRequest not called due KeInsertQueueApc fail
DbgPrint("!!!!!!!!!!!\n");
IoFreeMdl(MdlAddress);
IoFreeIrp(Irp);
}
return StopCompletion;
//---------------
VOID KernelRoutine (PKAPC Apc,PKNORMAL_ROUTINE *,PVOID *,PVOID *,PVOID *)
{
DbgPrint("KernelRoutine(%p)\n", Apc);
ExFreePool(Apc);
}
and you must got next debug output:
ThreadTerminating
!KeInsertQueueApc
!!!!!!!!!!!
and KernelRoutine will be not called (like and IopCompleteRequest) - no print from it.
so what is correct solution ? this of course not documented anywhere, but based on deep internal understand. you not need reference original thread. you need do next:
Irp->Tail.Overlay.Thread = KeGetCurrentThread();
return ContinueCompletion;
you can safe change Tail.Overlay.Thread - if you have no any pointers valid only in original process context. this is true for kernel mode requests - all your buffers in kernel mode and valid in any context. and of course you not need break Irp destruction but continue it. for correct free mdl and all irp resources. and finally system call IoFreeIrp for you.
and again for iosb pointer. how i say pass local variable address, if you exit from function before irp completed (and this iosb accessed) is error. if you break Irp destruction, iosb will be not accessed of course, but in this case much better pass 0 pointer as iosb. (if you latter something change and iosb pointer will be accessed - will be the worst error - arbitrary memory corrupted - with unpredictable effect. and research crash of this will be very-very hard). but if you completion routine - you not need separate iosb at all - you have irp in completion and can direct access it internal iosb - for what you need else one ? so the best solution will be do next:
Irp->UserIosb = &Irp->IoStatus;
full correct example how read file asynchronous:
NTSTATUS DemoCompletion (PDEVICE_OBJECT /*DeviceObject*/, PIRP Irp, BIO* bio)
{
DbgPrint("DemoCompletion(p=%x mdl=%p)\n", Irp->PendingReturned, Irp->MdlAddress);
bio->CheckResult(Irp->IoStatus.Status, Irp->IoStatus.Information);
bio->Release();
Irp->Tail.Overlay.Thread = KeGetCurrentThread();
return ContinueCompletion;
}
VOID DoTest (PVOID buf)
{
PFILE_OBJECT FileObject;
NTSTATUS status;
UNICODE_STRING ObjectName = RTL_CONSTANT_STRING(L"\\Device\\HarddiskVolume2");
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE };
if (0 <= (status = GetDeviceObjectPointer(&oa, &FileObject)))
{
status = STATUS_INSUFFICIENT_RESOURCES;
if (BIO* bio = new BIO(FileObject))
{
if (buf = bio->AllocBuffer(PAGE_SIZE))
{
LARGE_INTEGER ByteOffset = {};
PDEVICE_OBJECT DeviceObject = IoGetRelatedDeviceObject(FileObject);
if (PIRP Irp = IoBuildAsynchronousFsdRequest(IRP_MJ_READ, DeviceObject, buf, PAGE_SIZE, &ByteOffset, 0))
{
Irp->UserIosb = &Irp->IoStatus;
Irp->Tail.Overlay.Thread = 0;
PIO_STACK_LOCATION IrpSp = IoGetNextIrpStackLocation(Irp);
IrpSp->FileObject = FileObject;
bio->AddRef();
IrpSp->CompletionRoutine = (PIO_COMPLETION_ROUTINE)DemoCompletion;
IrpSp->Context = bio;
IrpSp->Control = SL_INVOKE_ON_CANCEL|SL_INVOKE_ON_ERROR|SL_INVOKE_ON_SUCCESS;
status = IofCallDriver(DeviceObject, Irp);
}
}
bio->Release();
}
ObfDereferenceObject(FileObject);
}
DbgPrint("DoTest=%x\n", status);
}
struct BIO
{
PVOID Buffer;
PFILE_OBJECT FileObject;
LONG dwRef;
void AddRef()
{
InterlockedIncrement(&dwRef);
}
void Release()
{
if (!InterlockedDecrement(&dwRef))
{
delete this;
}
}
void* operator new(size_t cb)
{
return ExAllocatePool(PagedPool, cb);
}
void operator delete(void* p)
{
ExFreePool(p);
}
BIO(PFILE_OBJECT FileObject) : FileObject(FileObject), Buffer(0), dwRef(1)
{
DbgPrint("%s<%p>(%p)\n", __FUNCTION__, this, FileObject);
ObfReferenceObject(FileObject);
}
~BIO()
{
if (Buffer)
{
ExFreePool(Buffer);
}
ObfDereferenceObject(FileObject);
DbgPrint("%s<%p>(%p)\n", __FUNCTION__, this, FileObject);
}
PVOID AllocBuffer(ULONG NumberOfBytes)
{
return Buffer = ExAllocatePool(PagedPool, NumberOfBytes);
}
void CheckResult(NTSTATUS status, ULONG_PTR Information)
{
DbgPrint("CheckResult:status = %x, info = %p\n", status, Information);
if (0 <= status)
{
if (ULONG_PTR cb = min(16, Information))
{
char buf[64], *sz = buf;
PBYTE pb = (PBYTE)Buffer;
do sz += sprintf(sz, "%02x ", *pb++); while (--cb); sz[-1]= '\n';
DbgPrint(buf);
}
}
}
};
NTSTATUS GetDeviceObjectPointer(POBJECT_ATTRIBUTES poa, PFILE_OBJECT *FileObject )
{
HANDLE hFile;
IO_STATUS_BLOCK iosb;
NTSTATUS status = IoCreateFile(&hFile, FILE_READ_DATA, poa, &iosb, 0, 0,
FILE_SHARE_VALID_FLAGS, FILE_OPEN, FILE_NO_INTERMEDIATE_BUFFERING, 0, 0, CreateFileTypeNone, 0, 0);
if (0 <= (status))
{
status = ObReferenceObjectByHandle(hFile, 0, *IoFileObjectType, KernelMode, (void**)FileObject, 0);
NtClose(hFile);
}
return status;
}
and output:
BIO::BIO<FFFFC000024D4870>(FFFFE00001BAAB70)
DoTest=103
DemoCompletion(p=1 mdl=FFFFE0000200EE70)
CheckResult:status = 0, info = 0000000000001000
eb 52 90 4e 54 46 53 20 20 20 20 00 02 08 00 00
BIO::~BIO<FFFFC000024D4870>(FFFFE00001BAAB70)
the eb 52 90 4e 54 46 53 read ok

Difference between access token taken from process than from user/pass

I am trying to understand the difference between 2 supposly identical access tokens.
One way to get access token is using OpenProcessToken(). The function returns an access token that can be used to create other processes or to impersonate.
Another way (and there are more, but lets focus these two) is to use LogonUser() function that also returns an access token that can be used to create other processes or to impersonate.
The difference is that the access token from (1) is valid as long as the process exists, while the access token in (2) is not attached to any process.
So, (1) what exactly do I get from OpenProcessToken()? Not an access token?
(2) Can I "change" the access token from OpenProcessToken() to be "detached" from the process? (i.e. still valid after the process dies)
access token from (1) is valid as long as the process exists
This is not true. The token is not "attached" to any process. Of course this isn't documented, but you can parse the pdb symbols and observe that struct _TOKEN doesn't have any links to the _EPROCESS object.
As with any kernel object, the token has a reference count, and it will be destroyed when the reference count reaches zero - but until then, it remains valid. Every _EPROCESS has a reference to the token, but not the other way around. Once you've opened the process token, it will remain valid as long as you keep it open, even if the process that you obtained it from terminates.
This is easy to test: open a token from some process (with the necessary permissions) and then close/terminate the process. You will find that you can still use the token with CreateProcessAsUserW to start a new process.
So no, there is no difference between the two tokens; in both cases you have a handle to a kernel object.
Here is demonstration code showing that the token from OpenProcessToken remains valid even after the process exits. We create an instance of notepad, open its token, wait until the process exists, and then attempt (successfully!) to use the token to start a new instance of notepad.
void demo()
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
// create new process in suspended state
// path to notepad hardcoded for for simplicity
if (CreateProcessW(L"c:\\windows\\notepad.exe", 0, 0, 0, 0, CREATE_SUSPENDED, 0, 0,&si, &pi))
{
// open process token with TOKEN_QUERY only
HANDLE hToken;
NTSTATUS status = NtOpenProcessToken(pi.hProcess, TOKEN_QUERY, &hToken);
// resume process
ResumeThread(pi.hThread);
NtClose(pi.hThread);
// close notepad
WaitForInputIdle(pi.hProcess, INFINITE);
PostThreadMessage(pi.dwThreadId, WM_QUIT, 0, 0);
// wait notepad exit
ZwWaitForSingleObject(pi.hProcess, FALSE, 0);
NtClose(pi.hProcess);
// try use "invalid" token from died process
if (0 <= status)
{
// we need TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY access for CreateProcessAsUserW
// we can at begin (in NtOpenProcessToken open it with the necessary access, this is only for demo
if (0 <= ZwDuplicateObject(NtCurrentProcess(),
hToken, NtCurrentProcess(), &hToken,
TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY, 0, DUPLICATE_CLOSE_SOURCE))
{
if (CreateProcessAsUserW(hToken, L"c:\\windows\\notepad.exe", 0, 0, 0, 0, 0, 0, 0, &si, &pi))
{
NtClose(pi.hThread);
NtClose(pi.hProcess);
}
else
{
GetLastError();
}
NtClose(hToken);
}
}
}
}

Delete open file in Windows (creating an anonymous file)?

Under Linux, my program would do something like this:
Process 1 opens a file (e.g. by mapping it into memory). Let's call this file#1
Process 2 unlinks the file, and creates a new file with the same name. Let's call this file#2.
Process 1 continues to work with file#1. When it is closed, it is deleted (since it has no links). Process 1 continues to work with the content in file#1, and does not see content from file#2.
When both processes have exited, file#2 remains on disk.
I want to achieve the same semantics in Windows. After reading this question, I think FILE_SHARE_DELETE does basically this. Is opening the file with FILE_SHARE_DELETE enough, or do I need to consider something more?
The above execution flow is just an example, I know there are other ways of solving that exact problem in Windows, but I want to understand how to make such code portable between Windows and Linux.
Edit: to clarify: The use cases would be to reuse a filename for different unrelated files, but let existing processes keep their data (think transactional update of a config file for example), and to make a file anonymous (unnamed), but continue to use it like an anonymous memory map. Again I know both are possible on Windows through other means, but I am trying to find a way that is portable across platforms.
You can achieve this by using a combination of CreateFile, CreateFileMapping and MapViewOfFile calls. MapViewOfFile will give you a memory-mapped buffer of the file backed by the file on disk.
Following code when executed from different processes, will write the process id of last closing process in the file at c:\temp\temp.txt
int main()
{
TCHAR szMsg[256];
HANDLE hMapFile;
LPCTSTR pBuf;
HANDLE hFile = CreateFileW(
L"C:\\Temp\\temp.txt",
GENERIC_WRITE|GENERIC_READ,
FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
hMapFile = CreateFileMapping(
hFile, // Handle of file opened with rw access
NULL, // default security
PAGE_READWRITE, // read/write access
0, // maximum object size (high-order DWORD)
BUF_SIZE, // maximum object size (low-order DWORD)
szName); // name of mapping object
if (hMapFile == NULL)
{
printf( "Could not create file mapping object (%d).\n", GetLastError());
return 1;
}
pBuf = (LPTSTR) MapViewOfFile(hMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
BUF_SIZE);
if (pBuf == NULL)
{
printf("Could not map view of file (%d).\n", GetLastError());
CloseHandle(hMapFile);
return 1;
}
wsprintfW(szMsg, L"This is process id %d", GetCurrentProcessId());
CopyMemory((PVOID)pBuf, szMsg, (wcslen(szMsg) * sizeof(TCHAR)));
MessageBoxW(NULL, szMsg, L"Check", MB_OK);
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);
CloseHandle(hFile);
return 0;
}
Make sure you open the file with GENERIC_READ|GENERIC_WRITE access and allow FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE access to subsequent opens.
Also note the use of CREATE_ALWAYS in CreateFile which will delete the old file and open a new one every-time CreateFile is called. This is the 'unlink' effect you talk about.
Code inspired from Creating Named Shared Memory at msdn.

CreateProcessAsUser from service with proper PEB and ACL

I have read tons of SO questions on this matter, but I didn't find a real definitive guide for doing this the right way.
My goal is to enumerate [disconnected and active] user console sessions and start a process in each one of them. Every user session process requires at least these rights in its DACL :
Token access rights :
TOKEN_QUERY (for GetTokenInformation())
TOKEN_QUERY_SOURCE (for GetTokenInformation())
Process access rights :
PROCESS_QUERY_INFORMATION (for OpenProcessToken())
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ (for GetModuleFileNameEx())
PROCESS_VM_OPERATION (used with GetTokenInformation() to get other processes' username later with LookupAccountSid())
But as you can read here (at the bottom) : "Windows Vista introduces protected processes to enhance support for Digital Rights Management. The system restricts access to protected processes and the threads of protected processes."
So I thought maybe only with PROCESS_QUERY_LIMITED_INFORMATION I can get some information about other processes. I tried QueryFullProcessImageName() for elevated processes starting from Vista (see Giori's answer) but it doesn't work anymore as it seems.
Solution : CreateProcessAs_LOCAL_SYSTEM using a duplicated token of the Windows service.
Problem : The spawned processes should have the respective logged on user's environment variables set to be able to locate network printers and mapped drives among other things. But if I use the service's token I inherit its PEB and I can't even translate the mapped drives to their UNC paths.
So I started looking for ways to "elevate" the process and bypassing the UAC prompt, I tried :
Enabling some privileges like SE_DEBUG_PRIVILEGE in the token using AdjustTokenPrivileges() (does not work if the token does not have those privileges, verification can be done first using LookUpPrivilegeValue())
using the token from winlogon.exe. (does not work)
Changing the DACL (source code) (didn't work)
The steps I'm following are :
Enumerate sessions using WTSEnumerateSessions()
Get the token (two choices) :
SYSTEM token : OpenProcessToken(GetCurrentProcess(),TokenAccessLevels.MaximumAllowed, out hProcessToken)
User token : WTSQueryUserToken(sessionId, out hUserToken)
Duplicate the token using DuplicateTokenEx()
LookUpPrivilegeValue() / AdjustTokenPrivileges() (useless ?)
CreateEnvironmentBlock()
CreateProccessAsUser(), flags : NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, Startup info's desktop : "WinSta0\Default"
Change process DACL (see link above, useless ?)
Dispose/Clean : destroy PEB created, close opened handles and free memory.
My question : how to grant the process created using CreateProccessAsUser() from a Windows Service running under LOCAL_SYSTEM account enough privileges/rights to get information on other processes (from other sessions; of other users and different integrity levels) without losing the user's environment variables ?
You're confused about a number of things.
Every user session process requires at least these rights in its DACL
The process DACL controls access to that process, it does not determine what access that process has. The security token for the process determines access rights.
Windows Vista introduces protected processes to enhance support for Digital Rights Management.
It seems clear that you haven't gotten far enough to worry about protected processes yet. Get it to work for ordinary processes first!
The spawned processes should have the respective logged on user's environment variables set to be able to locate network printers and mapped drives among other things.
Network printers and mapped drives have nothing to do with environment variables. I think what you're trying to do is to put the new process into the user's logon session, that's what controls network drive mappings and the like.
how to grant the process created using CreateProccessAsUser() [...] enough privileges/rights to get information on other processes (from other sessions; of other users and different integrity levels) without losing the user's environment variables ?
Don't. This would violate the integrity of the security model.
Instead, enumerate and query processes from the system service, and pass only whatever information is necessary to the user session processes, using shared memory (look up "file mapping object" in MSDN) or another suitable IPC mechanism.
I know that this has been asked a while ago. Since I happened to have been doing the same, below is the working pseudo-code.
First, how to run a process in a user session from a service:
//IMPORTANT: All error checks are omitted for brevity!
// Each of the lines of code below MUST be
// checked for possible errors!!!
//INFO: The following pseudo-code is intended to run
// from the Windows local service.
DWORD dwSessionID; //Session ID to run your user process in
//Get token for the session ID
HANDLE hToken;
WTSQueryUserToken(dwSessionID, &hToken);
//Duplicate this token
HANDLE hToken2;
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken2);
PSID gpSidMIL_High = NULL;
if(you_want_to_change_integrity_level_for_user_process)
{
if(!Windows_XP)
{
//For example, create "high" mandaroty integrity level SID
::ConvertStringSidToSid(L"S-1-16-12288", &gpSidMIL_High);
TOKEN_MANDATORY_LABEL tml = {0};
tml.Label.Attributes = SE_GROUP_INTEGRITY;
tml.Label.Sid = gpSidMIL_High;
SetTokenInformation(hToken2, TokenIntegrityLevel, &tml,
sizeof(TOKEN_MANDATORY_LABEL) + ::GetSidLengthRequired(1));
}
}
//Copy environment strings
LPVOID pEnvBlock = NULL;
CreateEnvironmentBlock(&pEnvBlock, hToken2, FALSE);
//Initialize the STARTUPINFO structure.
// Specify that the process runs in the interactive desktop.
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = _T("winsta0\\default");
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
//Create non-const buffer
TCHAR pBuffCmdLine[MAX_PATH];
pBuffCmdLine[0] = 0;
//Copy process path & parameters to the non-constant buffer
StringCchCopy(pBuffCmdLine, MAX_PATH, L"\"C:\\Program Files (x86)\\Company\\Brand\\process.exe\" -parameter");
//Impersonate the user
ImpersonateLoggedOnUser(hToken2);
//Launch the process in the user session.
bResult = CreateProcessAsUser(
hToken2, // client's access token
L"C:\\Program Files (x86)\\Company\\Brand\\process.exe", // file to execute
pBuffCmdLine[0] != 0 ? pBuffCmdLine : NULL, // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, // creation flags
pEnvBlock, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
//Get last error
nOSError = GetLastError();
//Revert to self
RevertToSelf();
//At this point you may want to wait for the user process to start, etc.
//using its handle in `pi.hProcess`
...
//Otherwise, close handles
if(pi.hProcess)
CloseHandle(pi.hProcess);
if(pi.hThread)
CloseHandle(pi.hThread);
//Clean-up
if(pEnvBlock)
DestroyEnvironmentBlock(pEnvBlock);
CloseHandle(hToken2);
CloseHandle(hToken);
if(gpSidMIL_High)
::LocalFree(gpSidMIL_High);
If you need to run your process in all sessions with a logged in interactive user, you can run the method I gave above for the sessions that you can obtain from the following enumeration:
//Enumerate all sessions
WTS_SESSION_INFO* pWSI = NULL;
DWORD nCntWSI = 0;
if(WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, NULL, 1, &pWSI, &nCntWSI))
{
//Go through all sessions
for(DWORD i = 0; i < nCntWSI; i++)
{
//To select logged in interactive user session,
//try to get its name. If you get something, then
//this session has a user logged in to...
LPTSTR pUserName = NULL;
DWORD dwcbSzUserName = 0;
if(WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,
pWSI[i].SessionId,
WTSUserName, &pUserName, &dwcbSzUserName) &&
pUserName &&
dwcbSzUserName >= sizeof(TCHAR) &&
pUserName[0] != 0)
{
//Use my method above to run your user process
// in this session.
DWORD dwSessionID = pWSI[i].SessionId;
}
//Free mem
if(pUserName)
WTSFreeMemory(pUserName);
}
//Free mem
WTSFreeMemory(pWSI);
}

Resources