gsass1 / NTop

💻 htop-like system-monitor for Windows with Vi-keybindings.
GNU General Public License v3.0
1.23k stars 69 forks source link

Username is showing up as SYSTEM on Remote Desktop Server #34

Open greatquux opened 4 years ago

greatquux commented 4 years ago

For some reason, all usernames (except Administrator where I'm running ntop) show up as SYSTEM when I try to run it on a remote desktop server. The processes are there, just USER is showing as SYSTEM. Any idea why or how to fix?

image

greatquux commented 4 years ago

BTW this is ntop 0.3.4, the latest release binary. I haven't compiled the latest code, but I don't think any of the recent commits seem to relate to this.

gsass1 commented 4 years ago

It seems that either GetTokenInformation or OpenProcessToken fails probably due to missing permissions. Could you try running as administrator? For now it only says "SYSTEM" because that's just the default user name I set.

greatquux commented 4 years ago

I am running it as a local machine (and domain) Administrator, and UAC is completely disabled (EnableLUA set to 0). That's why in my screenshot I included Task Manager also to show I can see the usernames in there.

dd86k commented 4 years ago

Unfortunately I have no TS setup and there seems to be no error code shown nor any kind of logging/tracing going on. Process.UserName is set to SYSTEM (_tcsncpy_s(Process.UserName, UNLEN, _T("SYSTEM"), UNLEN);) by default before attempting to open the process (OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, Entry.th32ProcessID);).

And my knowledge of Active Directory is rather limited. The doc for LookupAccountSid declares that the first parameter (lpSystemName), if NULL, looks on the local system first, then on trusted domain controllers (relative to the local computer), and if the machine is on an untrusted domain, that the parameter should be set.

But then I have not much of an idea for a trusted computer (joined?) on a domain. I feel like NULL is the same as "." and the name of the computer, but that could be tested out with a snapshot build could be done to show the error code (e.g. e00000000) to get a better clue.

List of possible failures:

One thing I noticed is that the handle is never closed after the OpenProcess call. Microsoft recommends closing it when done.

My best guess is probably that the processes are protected as noted in https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights#protected-processes due to the handle being NULL (Io counters are unset as well).

Next question would be, is it a local Administrator account or one from the domain, if it's joined a domain?

greatquux commented 4 years ago

The server is joined to a domain, and it's a domain Administrator account (that is a domain admin and local machine admin) that I'm logging into. Task Manager (and also Process Explorer) must be doing something different to get the user names as they show up there. I'll see if I am able to do a build myself and show any error codes.

greatquux commented 4 years ago

It looks like LookupAccountSid is getting error 122, ERROR_INSUFFICIENT_BUFFER. That's the only error code I ever see, I just put a little GetLastError !=0 check after LookupAccountSid and printed it, but I'm not advanced enough in Win32 C programming to get much farther. :( I found a forum thread https://social.msdn.microsoft.com/Forums/en-US/b0c6d946-92a7-4f52-8809-4b8f72010219/lookupaccountsid-fails-with-strange-errors-in-a-thread-that-is-impersonating-a-pipe-client?forum=windowssecurity that seems to indicate you can just reallocate the buffer to make it a bit larger and try again?

dd86k commented 4 years ago

ERROR_INSUFFICIENT_BUFFER directly from LookupAccountSid? That's surprising, since the account username is typically smaller than 256 (UNLEN) characters and the domain name is typically smaller than 260 (MAX_PATH) characters.

My main guess remains that OpenProcess returns NULL because it either has no permission or is simply incapable of processing the query for TS user processes since no other process information is filled out in your screenshot (e.g. used memory, which is processed before username, but only if it has a handle from OpenProcess).

It's possible that Task Manager (and other clones alike) use GetSecurityInfo (https://social.msdn.microsoft.com/Forums/en-US/b0c6d946-92a7-4f52-8809-4b8f72010219/lookupaccountsid-fails-with-strange-errors-in-a-thread-that-is-impersonating-a-pipe-client?forum=windowssecurity)... But then I'm not seeing the function being imported in taskmgr.exe.

Unfortunately my knowledge of Win32 is pretty limited too. I know no foss task managers

gsass1 commented 4 years ago

This is how ReactOS' taskmgr does it btw: https://doxygen.reactos.org/d7/dd5/perfdata_8c_source.html

greatquux commented 4 years ago

Weirdly, I'm getting error 122 from LookupAccountSid even on my Windows 10 computer which works and displays the user names and memory correctly, and also on the remote desktop server which doesn't. But in each case, I do see a non-NULL value for Process.Handle. And all this is taking place right after the call to LookupAccountSid that is if(OpenProcessToken()) block, which is inside if(Process.Handle) so it should have a value.

image

My print statements in that screenshot come right after the call to LookupAccountSid and before the FIXME comment: image

Where do you think I should put a ConPrintf to check on a variable?

EDIT: GitHub doesn't like when I send screenshots via email (Evolution).

dd86k commented 4 years ago

Interesting. Although seeing your snippet: I just remembered that the error code is only set when an error occurs, otherwise I think a successful call doesn't set the errorlevel for the thread (from the first GetTokenInformation). So it's normal you also see 122 on Windows with the provided snippet.

A better thing to print would be the result of LookupAccountSID (if it returns 0 then it failed).

@gsass1 That's interesting. Would you have a quick idea why the other fields (e.g. UsedMem, CPU time) are unset? I could try checking but I doubt I'd have anymore clue.

greatquux commented 4 years ago

I saved the result from LookupAccountSID in a BOOL and it always came out 1, I put an if() around it and will only print it if's not true but I haven't gotten anything out of it. Interestingly, I tried it on another RD server that is not on a domain, just workgroup with local accounts, and even there we see that it can't get CPU, memory, or username values properly. Task Manager is included to show that it has them:

image

dd86k commented 4 years ago

Looking at the ReactOS code: Curious that ReactOS does OpenProcessToken(hProcess, TOKEN_QUERY, &hProcessToken) betwen instead of TOKEN_READ as used in ntop. Want to try with TOKEN_QUERY instead? (Note: TOKEN_READ = TOKEN_QUERY | TOKEN_READ)

Otherwise curious if TokenUserStruct->User.Sid is set to NULL by the later GetTokenInformation.

greatquux commented 4 years ago

Ugh. Still a mystery. Changing to TOKEN_QUERY had no effect. User.Sid is not NULL (and it seems to have valid values when I print it out). If I change the default SYSTEM to something like "default" in most cases it does come back as "default" so it's definitely not writing anything in there.

dd86k commented 4 years ago

Checked around a bit (Well-known SIDs) and there values that have special meaning (e.g. S-1-1-0 for 'null' and S-1-5-32-555 for DOMAIN_ALIAS_RID_REMOTE_DESKTOP_USERS (thanks Microsoft)). Since it's non-null, do you mind printing/checking a few SID values as examples? From there I'll have a better clue.

The ReactOS code is nice but it does not even support logging via Active Directory (see LDAP/ActiveDirectory at Missing ReactOS Functionality) so I doubt their Task Manager would be able to translate anything. Curses.

Sorry if I'm taking too long. Tonight I'll try getting 2008 R2 up and running and try something out.

greatquux commented 4 years ago

I think I have finally figured things out. Once I got SIDs to print properly, and found I needed to use printf to properly redirect output to a file, and remembered my C pointer lessons from 25 years ago, I finally saw that the process loop was skipping over anything it couldn't get the info for on my RD server. The OpenProcess() call right after setting the default ExeName and UserName was failing but not because the Handle was NULL, because it did not have sufficient rights. On my workstation this happens to csrss.exe and the Idle process only, but on the RD server it was for everyone else.
If I run ntop.exe as SYSTEM, I can see all the usernames properly (and presumably the other stats). It looks like you need to give ntop.exe the SeDebugPrivilege privilege (or at least do it with a command line option) so that it can read stats on everything. It took a while to figure it all out, but as always, it was good to learn!

dd86k commented 4 years ago

That's particular. Maybe I should add a command-line option for SeDebugPrivilege? (Or maybe PROCESS_ALL_ACCESS)

greatquux commented 4 years ago

Maybe, but definitely experiment first on your computer to see if you are able to access the information about the csrss.exe process first. Right now I am having trouble even giving myself the SE_DEBUG_NAME privilege (even though I'm a local machine admin with UAC turned off) and I should really get back to "real work". :)