I'm writing a Windows service that needs to know whether there are any users currently logged-on in the machine.
So far I've tried Win32_LogonSession (WMI), and LsaEnumerateLogonSessions/LsaGetLogonSessionData (secur32.dll).
Both work, and seem to return the same data, but they are too slow to update when a user log off:
When the system starts, they return "0 interactive users". (OK)
When I log on, they return "1 interactive user". (OK)
But then when I log off, the number of users is kept at 1. After a new log-on, the number is 2, and so on.
Thus Win32_LogonSession nor LsaEnumerateLogonSessions are good enough. The service needs to know within 5 minutes after the last interactive user leaves.
Not even SysInternals' LogonSessions.exe gives up-to-date answers.
Also, the answer cannot be "monitor logon and logoff events and have a counter variable", because the service can be started at any time.
I ended up with the following approach: count the number of interactive sessions which have at least one process running.
1) Get the logon session id for each interactive session.
LsaEnumerateLogonSessions (secur32.dll)
LsaGetLogonSessionData (secur32.dll)
sessionData.LogonType = SECURITY_LOGON_TYPE.Interactive or sessionData.LogonType = SECURITY_LOGON_TYPE.RemoteInteractive
sessionData.LoginID <- Keep this value in a LUID set.
LsaFreeReturnBuffer (secur32.dll)
2) Get the logon session id for each running process.
[First we need to enable the SeDebugPrivilege to the current application.]
GetCurrentProcess (kernel32.dll)
OpenProcessToken TOKEN_ADJUST_PRIVILEGES (advapi32.dll)
LookupPrivilegeValue SE_DEBUG_NAME (advapi32.dll)
AdjustTokenPrivileges (advapi32.dll)
CloseHandle (kernel32.dll)
[Then retrieve the data we want.]
EnumProcesses (psapi.dll)
OpenProcess PROCESS_QUERY_INFORMATION (kernel32.dll)
OpenProcessToken TOKEN_QUERY (advapi32.dll)
GetTokenInformation TOKEN_INFORMATION_CLASS.TokenStatistics (advapi32.dll)
accessTokenStatistics.AuthenticationId <- Keep this value in a LUID set.
CloseHandle (kernel32.dll)
3) Sets intersection cardinality
interactiveSessionsCount = | { sessionData.LoginID } ∩ { accessTokenStatistics.AuthenticationId } |
Obs: sessionData.LoginID and accessTokenStatistics.AuthenticationId are both of type LUID.
WTSEnumerateSessionsA + WTSQuerySessionInformationA work better with detecting active/interfactive sessions.
Related
My program, when started up with the system, is unable to access a networked location:
fn main() {
ensure_network("\\\\SERVER\\".to_string());
}
fn ensure_network(network_dir: String) {
let timer = std::time::Instant::now();
let mut prev_counter = 0;
loop {
if std::fs::read_dir(&network_dir).is_ok() {
break;
}
if timer.elapsed().as_secs() > prev_counter + 60 {
println!("Still Failing.");
prev_counter = timer.elapsed().as_secs();
}
std::hint::spin_loop();
}
println!("Network access obtained (Time elapsed: {})",
timer.elapsed().as_secs_f32());
}
Edit (Restating problem after much research into the issue):
This program starts up with the PC using Task Scheduler. It is set to "Run only when user is logged on" and to "Run with highest privileges." However, most of the time the program fails to find the connection and gives the error, "The user name or password is incorrect. (os error 1326)."
The program succeeds when run manually with administrator privilege.
On occasion the program will succeed on startup, but this is rare.
The program will succeed if any other application is started as administrator after the program enters its loop.
On Task Scheduler you can delay the execution of the task.
It's okay if you execute it after login, but when Active Directory or anyway a Domain system is between you and the login, the connection to the shared storage may take a while, and the program may try to execute before this happens. Try to put on a 10-20 seconds delay on it and see if this solve your problem.
If it doesn't work, again supposing that you have a domain in the middle, you may need to explicit give user and passwd to access the network where the directory you're looking for.
The purpose is to create a new process in session 0 from a process in non 0 console session.
I know some methods that fulfill the purpose, but I want to know why the method described below does not, despite msdn says it should work.
unsigned FindProcessInSession(unsigned SessionId,const wchar_t*ProcessName)
{
PWTS_PROCESS_INFOW pinfo;DWORD Count,Result=0;
if(WTSEnumerateProcessesW(WTS_CURRENT_SERVER_HANDLE,0,1,&pinfo,&Count)){
for(unsigned i=0;i<Count;++i)if(pinfo[i].SessionId==SessionId&&_wcsicmp(pinfo[i].pProcessName,ProcessName)==0){
Result=pinfo[i].ProcessId;break;
}
WTSFreeMemory(pinfo);
}
return Result;
}
int main()
{
HANDLE hProcess=OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,0,FindProcessInSession(0,L"smss.exe")),ProcessToken,NewToken;
if(hProcess&&OpenProcessToken(hProcess,TOKEN_DUPLICATE,&ProcessToken)&&DuplicateTokenEx(ProcessToken,MAXIMUM_ALLOWED,0,SecurityImpersonation,TokenImpersonation,&NewToken)){
static STARTUPINFOW si={sizeof(STARTUPINFOW)};PROCESS_INFORMATION pi;DWORD SessionId,l;
printf("GetTokenInformation %d\n",GetTokenInformation(NewToken,TokenSessionId,&SessionId,sizeof SessionId,&l));
printf("SessionId %d\n",SessionId);
printf("CreateProcessWithTokenW %d\n",CreateProcessWithTokenW(NewToken,0,L"c:\\windows\\system32\\cmd.exe",0,0,0,0,&si,&pi));
}
}
During testing on my pc,GetTokenInformation successfully output SessionId=0 and CreateProcessWithTokenW created a new process in SYSTEM username, with all privileges from smss.exe. but the new process still ran in whatever session the calling process was in.
msdn on CreateProcessWithTokenW notes
Terminal Services: The process is run in the session specified in the token. By default, this is the same session that called LogonUser. To change the session, use the SetTokenInformation function.
Apparantly the secondary logon service CreateProcessWithTokenW relies on not does not follow the documentation
CreateProcessWithTokenW get SessionId of caller and set this SessionId in token (as side effect - token was modified after CreateProcessWithTokenW. you can call GetTokenInformation again after CreateProcessWithTokenW and view that now already not 0 here. so - CreateProcessWithTokenW can not be used for run process in another session. need use CreateProcessAsUserW. more research here
My process (server) creates a child process (client) by CreateProcess and I am doing IPC between these processes. I begin with anonymous pipe, but soon I find that it does not support overlapped operations as explained here.
So, named-pipe is my second choice. My confusion is: if I create a named-pipe, is it possible to limit the access of this pipe only to my child process created by previously call to CreateProcess? Thus, even if another process obtains the Pipe's Name, it still cannot read or write to the pipe.
My IPC usage only limits to local machine and single platform (Windows).
BTW, I can change both codes for these processes.
You could explicitly assign an ACL to the new pipe by using the lpSecurityAttributes parameter. This would allow you to ensure that, if another user is logged on, they can't connect to the pipe.
However, if you create both ends of the pipe in the parent process there is very little scope for malfeasance, so in general explicitly setting an ACL is not necessary. Once you have opened the client end of the pipe, no other process can connect to the pipe anyway (you would have to create a second instance if you wanted them to do so) so there is only a very brief interval during which another process could interfere; and even if that happened, you wouldn't be able to connect the client end, so you would know something had gone wrong.
In other words, the scope for attack is limited to denial of service, and since the attacking process would need to be running on the same machine, it can achieve a much more effective denial of service simply by tanking the CPU.
Note that:
You should use the FILE_FLAG_FIRST_PIPE_INSTANCE flag when creating the pipe, to ensure that you know if there is a name collision.
You should also use PIPE_REJECT_REMOTE_CLIENTS for obvious reasons.
The default permissions on a named pipe do not allow other non-administrative users to create a new instance, so a man-in-the-middle style attack is not a risk in this case.
A malicious process running as the same user, or as an administrative user, could potentially man-in-the-middle your connection (regardless of whether you set an ACL or not) but since any such malicious process could also inject malicious code directly into the parent and/or child there is little point in worrying about it. The attacker is already on the wrong side of the air-tight hatchway; locking the windows won't do you any good.
If your process is running with elevated privilege, you probably should set an ACL on the pipe. The default ACL would potentially allow non-elevated processes running as the same user context to man-in-the-middle the connection. You can resolve this by setting an ACL that grants full access only to Administrators. The risk is still minimal, but in this particular case a defense-in-depth measure is probably appropriate.
An anonymous pipe is implemented as a named pipe with a unique name, so you haven't actually lost anything by using a named pipe. An attacker could in principle man-in-the-middle an anonymous pipe just as easily as a named one. (Edit: according to RbMm, this is no longer true.)
Asynchronous (overlapped) operations of course full supported by anonymous pipes. supported asynchronous operations or no - depending only from are FILE_SYNCHRONOUS_IO_[NO]NALERT used in call ZwCreateNamedPipeFile and ZwOpenFile, but not from which name (or empty) have pipe. CreatePipe create pipe pair with FILE_SYNCHRONOUS_IO_NONALERT option - only because this handles returned from this api can not be used in asynchronous operation. unfortunately CreatePipe have no parameters to change this behavior, but we can yourself do this task
begin from vista we can create anonymous (unnamed) and asynchronous pipe pair, but for this you need use ndll api. next code is almost similar CreatePipe internal code, except i create asynchronous pipe pair.
NTSTATUS CreatePipeAnonymousPair(PHANDLE phServerPipe, PHANDLE phClientPipe)
{
HANDLE hFile;
IO_STATUS_BLOCK iosb;
static UNICODE_STRING NamedPipe = RTL_CONSTANT_STRING(L"\\Device\\NamedPipe\\");
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &NamedPipe, OBJ_CASE_INSENSITIVE };
NTSTATUS status;
if (0 <= (status = ZwOpenFile(&hFile, SYNCHRONIZE, &oa, &iosb, FILE_SHARE_VALID_FLAGS, 0)))
{
oa.RootDirectory = hFile;
static LARGE_INTEGER timeout = { 0, MINLONG };
static UNICODE_STRING empty = {};
oa.ObjectName = ∅
if (0 <= (status = ZwCreateNamedPipeFile(phServerPipe,
FILE_READ_ATTRIBUTES|FILE_READ_DATA|
FILE_WRITE_ATTRIBUTES|FILE_WRITE_DATA|
FILE_CREATE_PIPE_INSTANCE,
&oa, &iosb, FILE_SHARE_READ|FILE_SHARE_WRITE,
FILE_CREATE, 0, FILE_PIPE_BYTE_STREAM_TYPE, FILE_PIPE_BYTE_STREAM_MODE,
FILE_PIPE_QUEUE_OPERATION, 1, 0, 0, &timeout)))
{
oa.RootDirectory = *phServerPipe;
oa.Attributes = OBJ_CASE_INSENSITIVE|OBJ_INHERIT;
if (0 > (status = ZwOpenFile(phClientPipe, FILE_READ_ATTRIBUTES|FILE_READ_DATA|
FILE_WRITE_ATTRIBUTES|FILE_WRITE_DATA, &oa, &iosb, FILE_SHARE_VALID_FLAGS, 0)))
{
ZwClose(oa.RootDirectory);
*phServerPipe = 0;
}
}
ZwClose(hFile);
}
return status;
}
note that hClientPipe created as Inherited - so can pass it to child process. also when you will be use hServerPipe in ConnectNamedPipe you got FALSE with GetLastError() == ERROR_PIPE_CONNECTED (because client is already connected)/ or if you will be use FSCTL_PIPE_LISTEN - you got STATUS_PIPE_CONNECTED - this is really not error but ok code
We're running a Windows Service which is responsible for monitoring a set of processes. The service is basically just responsible for (a) checking if the defined jobs are running, and (b) starting the jobs if they are not.
The service is created via the following command (sc: https://technet.microsoft.com/en-us/library/bb490995.aspx):
sc create "My Service" binPath= C:\heyoo\myservice.exe type= own start= auto error= normal
sc start "SCF Service"
One of the jobs the service is responsible for creating is 'Camera.exe'. Camera.exe retrieves a video feed from the connected cameras (FireWire 1394), and does some processing on them.
A week ago, the service was rewritten from using ShellExecute to use CreateProcess so it is able to better monitor the defined jobs (as it gets the HANDLE to the process).
ShellExecute call (old method):
bool Execute()
{
int result = (int)ShellExecute(NULL, "open", "C:\\bin\\Camera.exe", NULL, NULL, SW_SHOWDEFAULT);
return result > 32;
}
CreateProcess call (new method):
// Called with Execute("C:\\bin\\Camera.exe", "");
bool Execute(std::string prog, std::string args)
{
std::string cmd = std::string(prog) + " " + args;
char *path = new char[cmd.length()+1];
strcpy(path, cmd.c_str());
STARTUPINFO si = {0};
si.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
DWORD creationFlags = REALTIME_PRIORITY_CLASS;
BOOL result = CreateProcess(NULL, path, NULL, NULL, FALSE, creationFlags, NULL, NULL, &si, &pi);
delete[] path;
if (result) {
SetProcInfo(pi);
}
return result;
}
With the new CreateProcess method, we noticed that (A) the network systematically fails after a certain interval, and (B) the images retrieved from the cameras contain invalid timestamps (correct timestamps are crucial to us).
A frequently takes down the entire network connection, and requires a reboot to get back online. B causes the processing of images to fail, as we are highly dependent on valid timestamps.
The problems (A & B) does only arise when Service.exe is run as a service. When running Service.exe or Camera.exe from the command line, none of the problems occurr.
Today I removed the CreateProcess calls from the service (went back to ShellExecute), and the problems disappeared again. What am I doing wrong with the API call?
DWORD creationFlags = REALTIME_PRIORITY_CLASS;
This is the most obvious difference. When you call ShellExecute, the process will be created with normal priority. The documentation for real time priority says:
Process that has the highest possible priority. The threads of a real-time priority class process preempt the threads of all other processes, including operating system processes performing important tasks. For example, a real-time process that executes for more than a very brief interval can cause disk caches not to flush or cause the mouse to be unresponsive.
You really don't want to do this!
Pass 0 as the creation flags. The documentation says:
If none of the priority class flags is specified, the priority class defaults to NORMAL_PRIORITY_CLASS unless the priority class of the creating process is IDLE_PRIORITY_CLASS or BELOW_NORMAL_PRIORITY_CLASS. In this case, the child process receives the default priority class of the calling process.
For what it is worth, you can obtain a process handle by using ShellExecuteEx rather than ShellExecute. In fact you should always prefer ShellExecuteEx to ShellExecute since the latter cannot report errors properly. Even so, you are creating a new process, and so CreateProcess is the right function for that task.
I tried to execute the bellow perl script and locked the user session...
$n=15;
while($n>0)
{
print "$n,";
$n--;
sleep(1);
}
It worked as usual without any extra code..., There was no output when i locked the session, as i locked the session before the next second.
The output seen when I unlocked the session:
C:\Pradeep>perl test.pl
15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,
C:\Pradeep>
When i run the script bellow which I use to connect to a server using Win32::GuiTest functions like
SetForegroundWindow($_);
SendKeys("Password01");
etc...
it connected without any issues and the server login was successful.
But, when i lock my session in the middle of my execution and unlocked the session, the execution of script was completed, but the server login was not done.
use Win32::GuiTest qw(FindWindowLike GetWindowText SetForegroundWindow SendKeys);
system('"start %windir%\system32\mstsc.exe"');
$Win32::GuiTest::debug = 0;
$max_Sleep_time=3;
$Cur_Sleep_time=0;
do
{
sleep(1);
#windows = FindWindowLike(0, "Remote Desktop Connection");
$number_of_windows_opend = scalar(#windows);
$Cur_Sleep_time++;
}while ($number_of_windows_opend==0&&$Cur_Sleep_time!=$max_Sleep_time);
for (#windows) {
SetForegroundWindow($_);
SendKeys("server_name");
SendKeys("{ENTER}");
sleep(10);
#windows_seq = FindWindowLike(0, "Windows Security");
for (#windows_seq) {
SetForegroundWindow($_);
SendKeys("Password01");
SendKeys("{ENTER}");
}
#windows={};
exit;
}
According to me I used the active windows for doing my functionality. So it is not working.
is there any other way i can successfully do the above functionality if the user session is locked in the middle of the execution process. or do i have to make changes in my code?
Instead of using send keys use WMSetText(); function. It takes the window/control HWND and text as input and sets the text to the specified object.
Note: Using WMSetText(); you can just set the text, you can't send keys like {ENTER},{F1} etc...
You've already been told the answer several times:
http://perlmonks.org/?node_id=1073507
http://perlmonks.org/?node_id=1073302
http://perlmonks.org/?node_id=1073530
This is explained in the documentation of Win32::GuiTest. For obvious security reasons you can't send keys to applications when the screen is locked, you can't send keys to appications which aren't active.