My Windows application uses
NetUserAdd to create a local user account
LookupAccountName to get user sid
NetUserGetInfo to get level-2 user info
NetUserDel to delete the local user account
In real environment, the application works fine, because nobody would add the user and delete it right away. However, my test does that, so it sporadically fails at NetUserGetInfo or NetUserDel in an unexpected way. There is no error code returned. The application just terminates silently in Windows 7 and Windows 10.
I could imagine that when NetUserAdd returns, the user account might not have been fully constructed yet in the background. Is there a recommended way to check whether a user account is in a stable state before calling NetUserDel?
LookupAccountName always succeeds, so it is not a good guard. NetUserGetInfo itself sporadically fails unexpectedly. I failed to find other API in MSDN documentation (very few examples there).
Instead of using those unstable win32 API, calling DOS command "net user" via system() can circumvent the issue.
Related
I am having a problem with calling the function MsiOpenDatabase (https://msdn.microsoft.com/en-us/library/aa370338(v=vs.85).aspx) from inside a program when I choose to "run as administrator". When I run it under an admin account but without explicitly starting the executable as elevated it all works just fine. This indicates that the path to MSI file etc should be correct.
So, when running elevated the MsiOpenDatabase() I get an error code of 110 (0x6e).
I have tried to call MsiGetLastErrorRecord as explained here (https://msdn.microsoft.com/en-us/library/aa370124(v=vs.85).aspx) but nothing happens when I try to print the code in a message box. It simply doesn't get there.
I do not have Visual studio for debuggning on the target machine, so debugging is a bit of a pain.
Target machine is Windows 7 x64. Application is 32-bit.
But just the pure fact that it works un-elevated but fails when run as an administrator...it feels like there should be some kind of answer to this which can be derived from this fact perhaps?
Thankful for any help!
EDIT:
I finally solved it!
Apparently I had to go to the network share where the MSI file is located (which I am trying to call MsiOpenDatabase on) and right cklick on a file there and choose "run as administrator" because then and only then did I get a UAC dialog box asking for credentials (I mean I was able to open Windows Explorer as admin and navigate to the network share without problem so I never thought that it would be what would give me these peoblems). After haing done that I was able to run my application and it did no longer fail on any MsiOpenDatabase call.
But, why must I do this procedure to get access to run file on a network share since I already had access (execute rights) with the same user but when not elevated? How come Windows needs to ask the same user for credentials if it is already running elevated on the very same account that already has access to the network share? Seems strange to me, but I suppose I am missing some crucial part?
SAMPLE CODE
LPCTSTR szPersist = MSIDBOPEN_READONLY;
MSIHANDLE handleDB;
UINT result = MsiOpenDatabase(strPath, szPersist, &handleDB); // strPath is something like _T("\\server\MSI\Setup.msi");
result variable has value 110 when this error occurrs as explained above and keep the part in the update section in mind. I find it strange, but perhaps someone knows UAC better than me and why I have to provide credentials again by going to a file on the netowrk share and choose to run as admin to get it working (since I have already provided credentials as non-admin with the same account earlier at that very same network share location)?
This is standard UAC behavior since Windows Vista and is not related to MSI at all. Do a google search for "uac network drives".
You should be closing your MSI handles though as I commented above. Use PMSIHANDLE instead of MSIHANDLE.
I have been dabbling with working nicely with UAC for a while and I found about a few things:
With UAC enabled, a program in the Startup folder, that requires to be run as admin (say by an embedded manifest), cannot be run according to this Stack Overflow thread.
Another method of running a program at startup is by creating a key containing the path to that application in: HKLM or HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run or HKLM or HKCU\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Run in 64 bit machines.
Yet another method is using the task scheduler setting the Run with highest privileges option. This is the only method that bypasses the problem stated in point 1.
Coming from a Linux background, I had no clue about all these admin rights related problems. If someone can list out scenarios which absolutely need administrator privileges, it would be of great help!
I'm asking this because when I'm developing some application, I keep encountering several problems during implementation mostly because my application required admin rights when it shouldn't.
If I know, at design time, all possible scenarios that require admin rights, I could possibly design a common service for all my applications that takes care of all the administrator tasks (I think services are the Windows way of doing things like this).
There really isn't a list of scenarios or API function calls that require elevation. Your best option will probably be to focus on what API calls require elevation. The reason for this is that it may be required only if certain values are passed to the function. For instance CreateFile can create a file in your home directory without elevation but requires it for creating a file in C:\Windows. If the directory is provided through user input the only way you can know if elevation is required is to check the error code when the call fails. If elevation is required the function will set the error status to ERROR_ACCESS_DENIED and return a value indicating failure.
Ok, so we have a C++ app that runs fine in Windows XP.
It has the following code in the initialization
// Register all OLE server (factories) as running. This enables the
// OLE libraries to create objects from other applications.
COleObjectFactory::RegisterAll();
Now like i said, it works fine in Windows XP, but as far I understand the program tries to register its COM interface Which is fine in XP, but this may be a problem in Windows Vista and Windows 7 because of the UAC. Especially if its is run as a standard user (with no elevated privileges).
If i understand it correctly this is needed for the program to run properly, but it cant execute this code without elevated privileges. If it will run every time the app runs (this usually runs when CWinApp::init() is run)
Before you say just use admin privileges, the user will not have them, there is no way to change that
So, now my questions are:
1) am i correct in my assumptions?
2) if I am correct, what is the best way around this? Can i just remove this? Do i need to set up some other thing? (we changed some VB modules to use a XML file instead of stuff in the registry
PS: the modules compiles into DLLs
PPS: UAC MUST be on
Take note that:
The documentation for these functions makes no mention of any privilege requirements; and
nobody online seems to be having trouble with these functions in limited privilege environments; and
it's 2012, I think someone would have noticed if these functions didn't work under UAC by now.
So (with nothing to suggest otherwise) I'd say It Just Works.
Notwithstanding the above, I looked at the implementation of COleObjectFactory::RegisterAll() and COleObjectFactory::UpdateRegistryAll().
RegisterAll
Ultimately calling RegisterAll ends up in olefact.cpp:135 where CoRegisterClassObject is called. From MSDN:
Registers an EXE class object with OLE so other applications can connect to it.
I believe this registration will be limited to the current user's session and the lifetime of the application. The Remarks section touches on privileges (As of Windows Server 2003...) but doesn't provide anything concrete.
There's an object known as the Running Object Table (ROT) that can be retrieved via GetRunningObjectTable. The documentation has this snippet:
Each workstation has a local ROT that maintains a table of the objects that have been registered as running on that computer.
The COM Elevation Moniker has some more information about the ROT and privileges (it suggests processes of various privilege levels work fine with it). The links on the left-hand side might help, too.
Overall it seems there's nothing to suggest that CoRegisterClassObject requires administrator permissions.
UpdateRegistryAll
This function ends up in olefact.cpp:375 where it opens HKEY_CLASSES_ROOT. At this point the documentation gets a bit better:
Registry functions such as RegOpenKeyEx or RegQueryValueEx allow you to specify the HKEY_CLASSES_ROOT key. When you call these functions from a process running in the interactive user account, the system merges the default settings in HKEY_LOCAL_MACHINE\Software\Classes with the interactive user's settings at HKEY_CURRENT_USER\Software\Classes.
Further on:
If you write keys to a key under HKEY_CLASSES_ROOT, the system stores the information under HKEY_LOCAL_MACHINE\Software\Classes
The documentation doesn't define what happens when you try to write to HKEY_CLASSES_ROOT under limited privileges (i.e. a standard user can't write to HKLM), but I believe that you'll end up writing to HKCU instead.
And finally, note:
Windows Server 2003 and Windows XP/2000: Applications can register dependent COM objects to either the per-machine or per-user COM configuration store (HKEY_LOCAL_MACHINE\Software\Classes or HKEY_CURRENT_USER\Software\Classes).
So if it falls through to HKCU, you should be fine.
Caveat Implementor: Don't rely on implementation details.
I've written some sample code which when I call from the windows command prompt under the context of a normal user account, dump's all the user's saved credentials using CredEnumerate(). However, I really want to be able to do this from SYSTEM user context so I've tested my program from a SYSTEM cmd prompt.
When I running my program as SYSTEM, I run LogonUser like so:
bLoggedOn = LogonUser(userName.c_str(), domain.c_str(), password.c_str(), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &userToken_);
Then I run ImpersonateLoggedOnUser() on the token to give me the security context of the local user. After this I do:
bOk = CredEnumerate(NULL, 0, &count, &pCredentials);
And I'd expect this to return the credentials in the same way as if I'd not gone in from system and impersonated. Can anyone spot anything that I've missed to truly put myself in the user's context?
I guess I aught to answer this question myself since I've now spent ages working out how to do this and I'm not sure it's widely known. CredEnumerate/CredRead will never provide password information for domain passwords no matter what process context you're in or what token you have despite what it seems to hint at on MSDN. The only way to get access to the saved credential information is to do so using the undocumented function LSAICryptUnprotectData() which is in lsasrv.dll. This can decrypt the files you find in %APPDATA%\Microsoft\Credentials and can provide an identical data structure to CredEnumerate except with the password filled in. The only catch is that this must be done in the process context of lsass.exe (The windows security subsystem), no setting of privilledges etc is enough to give a normal process the rights to do this. If you're a hacker you can do this by performing a CreateRemoteThread() to inject a thread into lsass.exe or if you're trying to do this in a legitimate way, i.e you're extending the Windows operating system in some way for a third party application, like I was, you can do this by creating a Windows authentication package which lsass will load. This AP can then use a named pipe or some such method to allow interaction with the rest of your code.
I was trying to get a Uuid via NtAllocateUuids or simply calling UuidCreateSequential,
but Windows wasn't able to get an Ethernet or token-ring hardware address for my laptop.
And so, when the system is booting, windows sets the UuidSeed to a random number instead of a given MAC.
--> uniqueness is guaranteed only until the system is next restarted.
I was trying to manual set the UuidSeed with NtSetUuidSeed but i was getting a STATUS_ACCESS_DENIED error.
"Windows NT/2000 Native API Reference" has following remarks:
--The token of the calling thread must have an AuthenticationId of SYSTEM_LUID
Is there any way to achieve this from a process, running as Administrator?
Something like ImpersonateLoggedOnUser() could work but afaik this is also only accessible as LocalSystem :/
Thx ;)
You can create a program and schedule to run it using the AT command. The scheduling service run as the local system account. On Vista things are different and I'm not sure if that is possible any more. You could write your own service of course.
ImpersonateLoggedOnUser worked fine with a token handle returned from:
HANDLE GetLSAToken() //duplicate a system token from the "System" process
or
BOOL CreatePureSystemToken(HANDLE &hToken) //create a new system token
http://www.codeproject.com/KB/system/RunUser.aspx
CoreCode.cpp