Windows Unprivileged - Processes
Note that absolutely none of this is authoritative or directly based on relevant documentation. It’s mostly what I found and figured out and guessed and (in some cases) made up. Some of it may be wrong or dangerous or lead to disaster or confusion. I am not taking responsibility here for anything, not even spelling. Read and digest at your own peril!
Like services (see Services) processes have security descriptors that contain a DACL.
Processes can be intefered with by users who are not their owners (although this does not work if the processes are owned by the Administrators group). It is sometimes the case that one wants one user (or members of a group) to be able to terminate processes run by other users (for example services that cannot be stopped because of a problem).
If user legrand starts a program, user benoit cannot typically interfere with it:
PS C:\Users\benoit> query session
SESSIONNAME USERNAME ID STATE TYPE DEVICE
services 0 Disc
>console benoit 1 Active
legrand 3 Disc
rdp-tcp 65536 Listen
PS C:\Users\benoit> Get-Process winver
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
186 13 2308 15432 8836 3 winver
PS C:\Users\benoit> Get-Process winver|Stop-Process
Stop-Process : Cannot stop process "winver (8836)" because of the following error: Access is denied
At line:1 char:20
+ Get-Process winver|Stop-Process
+ ~~~~~~~~~~~~
+ CategoryInfo : CloseError: (System.Diagnostics.Process (winver):Process) [Stop-Process], ProcessCommandException
+ FullyQualifiedErrorId : CouldNotStopProcess,Microsoft.PowerShell.Commands.StopProcessCommand
PS C:\Users\benoit> taskkill /im winver.exe
ERROR: The process "winver.exe" with PID 8836 could not be terminated.
Reason: Access is denied.
PS C:\Users\benoit>
Note that user legrand is logged into session 3 and is running the program winver. User benoit tries to terminate winver but cannot, neither using PowerShell’s Stop-Process cmdlet nor using the taskkill program.
But the DACL (discretionary access control list) of the process (running program) can be modified by those who have access to it and by the user who starts it.
This is what the DACL of the process 8836 looks like:
PS C:\> Get-Process winver
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
185 13 2352 15472 0.11 8836 3 winver
PS C:\> & 'C:\Program Files\ABTokenTools\AclEdit.exe'
AclEdit type pathObject [sddl] [D|E]
0 SE_UNKNOWN_OBJECT_TYPE
1 SE_FILE_OBJECT
2 SE_SERVICE
3 SE_PRINTER
4 SE_REGISTRY_KEY
5 SE_LMSHARE
6 SE_KERNEL_OBJECT
7 SE_WINDOW_OBJECT
8 SE_DS_OBJECT
9 SE_DS_OBJECT_ALL
10 SE_PROVIDER_DEFINED_OBJECT
11 SE_WMIGUID_OBJECT
12 SE_REGISTRY_WOW64_32KEY
13 SE_REGISTRY_WOW64_64KEY
Currently supports setting DACLs and owners. Setting an owner might require the appropriate privilege.
Disable or enable inheritance with AclEdit type pathObject sddl D|E.
File, service, printer, registry, and share objects take UNC paths. DS_OBJECT takes X.500 format.
Registry paths start with "CLASSES_ROOT", "CURRENT_USER", "MACHINE", and "USERS". "MACHINE\SOFTWARE" is a key.
"6 pid" will display ACL of process with id pid
"6 \KernelObjects\Session#" will display ACL of session number #.
"7 WinSta0 or 7 Default" will display permissions of the current session's window station 0 or default desktop.
PS C:\> & 'C:\Program Files\ABTokenTools\AclEdit.exe' 6 8836
O:S-1-5-21-344341352-2539047333-2300305637-1005D:(A;;0x1fffff;;;S-1-5-21-344341352-2539047333-2300305637-1005)(A;;0x1fffff;;;SY)(A;;0x121411;;;S-1-5-5-0-36021173)
PS C:\>
Looking the the SDDL output, we see the following:
O:S-1-5-21-344341352-2539047333-2300305637-1005 | The owner of the process is user legrand |
D: | beginning of DACL |
(A;;0x1fffff;;;S-1-5-21-344341352-2539047333-2300305637-1005) | User legrand has full access |
(A;;0x1fffff;;;SY) | LocalSystem has full access |
(A;;0x121411;;;S-1-5-5-0-36021173) | The user’s session has some access |
We need the process to start with a different DACL.
To see how this works, we first have to look at how process creation works.
This is the necessary includes and some variables and a function for error reporting. I always include the variables and function in every program. The includes are necessary for process creation and modifying a process security descriptor.
Also note that this program, since it uses wchar.h requires the C runtime library which either needs to be installed separately on the target computer or statically linked:
Properties -> Configuration Properties -> C/C++ -> Code Generation -> Runtime Library: Multi-threaded (/MT)
#include <Windows.h>
#include <AclAPI.h>
#include <sddl.h>
#include <securitybaseapi.h>
#include <wchar.h>
BOOL debug = TRUE; // Error() below will only do something if debug is TRUE.
// Windows APIs give results in various ways, some say true if they work, some give a status number, but GetLastError() will always get the last error.
BOOL ok = TRUE;
DWORD error = 0;
LSTATUS status = 0;
void Error(LPCWSTR sz)
{
if (!debug) { return; }
if (!ok || status) { error = GetLastError(); }
fwprintf(stderr, L"%s\tOK: [%d]\tSTATUS: [%d], Error: [%d]\n", sz, ok, status, error);
error = 0;
status = 0;
ok = TRUE;
}
Then creating a process is not so difficult:
int main()
{
// need a startup info struct and a process info struct
STARTUPINFO si;
PROCESS_INFORMATION pi;
// they need to be initialised with nothing
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// create new process to run winver.exe
// image path, command line, process attributes, thread attributes, inherit handles, creation flags, environment, directory, startup info address, process info address
ok = CreateProcess(L"C:\\Windows\\System32\\winver.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
Error(L"CreateProcess");
// close handles to process and thread in pi struct
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
This simply starts winver.exe literally without any special settings.
User benoit was not able to terminate this process started and owned by user legrand.
Now let’s create a process with a custom DACL. In this case we make the DACL quite ridiculous, we simply allow everything to everyone. (It would be more prudent to read the existing DACL and add to it.)
The process is created suspended so the DACL can be updated before the process is released into the wild.
int main()
{
// need a startup info struct and a process info struct
STARTUPINFO si;
PROCESS_INFORMATION pi;
// they need to be initialised with nothing
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// create new process to run winver.exe
// image path, command line, process attributes, thread attributes, inherit handles, creation flags, environment, directory, startup info address, process info address
ok = CreateProcess(L"C:\\Windows\\System32\\winver.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
Error(L"CreateProcess");
// can now set process acl, will simply allow everyone to do everything with it
PSECURITY_DESCRIPTOR pSD = NULL;
LPWSTR sddl = L"D:(A;;0x1fffff;;;WD)"; // "WD" (world) is everyone.
ok = ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &pSD, NULL);
Error(L"ConvertStringSecurityDescriptorToSecurityDescriptor");
BOOL tfDaclPresent = FALSE;
BOOL tfDaclDefaulted = FALSE;
PACL pDacl = NULL;
ok = GetSecurityDescriptorDacl(pSD, &tfDaclPresent, &pDacl, &tfDaclDefaulted);
Error(L"GetSecurityDescriptorDacl");
status = SetSecurityInfo(pi.hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pDacl, NULL);
Error(L"SetSecurityInfo");
// release the process into the wild
ResumeThread(pi.hThread);
// close handles to process and thread in pi struct
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
This results in a process winver.exe with the following (very simple) DACL:
PS C:\> Get-Process winver
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
181 13 2348 15472 0.09 8160 3 winver
PS C:\> & 'C:\Program Files\ABTokenTools\AclEdit.exe' 6 8160
O:S-1-5-21-344341352-2539047333-2300305637-1005D:(A;;0x1fffff;;;WD)
PS C:\>
O:S-1-5-21-344341352-2539047333-2300305637-1005 | The owner is still user legrand |
D: | DACL starts |
(A;;0x1fffff;;;WD) | Everyone (WD) has full access |
And now user benoit can terminate the process, since user benoit is a man of the world:
PS C:\Users\benoit> Get-Process winver
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
182 13 2320 15472 0.09 8160 3 winver
PS C:\Users\benoit> Get-Process winver|Stop-Process
Confirm
Are you sure you want to perform the Stop-Process operation on the following item: winver(8160)?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
PS C:\Users\benoit> Get-Process winver
Get-Process : Cannot find a process with the name "winver". Verify the process name and call the cmdlet again.
At line:1 char:1
+ Get-Process winver
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (winver:String) [Get-Process], ProcessCommandException
+ FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand
PS C:\Users\benoit>
Now, this way of dealing with processes is not particularly simple. For example, you need whomever starts the process to cooperate and configure the DACL correctly. An easier way to deal with allowing a user to stop processes of a certain name via JEA.
Next: Processes JEA