Registry: Delete key for each local user within system - c++

I got a certain registry key (created by our software) which needs to be removed on each local user account at some point.
Thus, I try to load the users hive and then use SHDeleteKey (as the key is not empty) to get the job done.
However, SHDeleteKey always returns LSTATUS 2 (ERROR_FILE_NOT_FOUND).
The Registry key for each user is placed under
HKCU\Software\XYZ
First, I set the required privileges within my code, which seems to work (return val is TRUE):
(...)
HANDLE th;
LUID rsto;
LUID bckp;
TOKEN_PRIVILEGES tp;
TOKEN_PRIVILEGES tp2;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &th);
LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &rsto);
LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &bckp);
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[0].Luid = rsto;
tp2.PrivilegeCount = 1;
tp2.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp2.Privileges[0].Luid = bckp;
BOOL p = AdjustTokenPrivileges(th, 0, &tp, 1024, 0, 0);
BOOL p2 = AdjustTokenPrivileges(th, 0, &tp2, 1024, 0, 0);
(...)
Then I use RegloadKey to load the users hive.
std::string connection contains the path to the respective ntuser.dat file. username is the local user account name.
So the hive should be loaded under HKEY_USERS\username:
(...)
DWORD result = RegLoadKey(HKEY_USERS, username.c_str(), connection.c_str());
return result == ERROR_SUCCESS;
(...)
Now, I try to delete:
(...)
k = username + "\\Software\\XYZ";
result = SHDeleteKey(HKEY_USERS, k.c_str());
And now result has value of 2.
But the key exists.
What am I doing wrong?
Thank you in advance...
UPDATED INFO:
I realized the problem needs to be somewhere on RegLoadKey.
When I load the hive via command line (REG.exe load "HKU\username" ...), I can see the node "username" under HKEY_USERS within regedit.exe. All child nodes are loaded under that node.
When I pause my program after RegLoadKey, the node "username" is also shown under HKEY_USERS, but the node is visualized as empty, so no child nodes are available.
How can this happen? This problem is driving me nuts.

Looked at my code again today and I just saw that I loaded "ntuser.BAT" instead of "ntuser.DAT" (both files exist).
I'm really sorry that I wasted your time. :-/

Related

winapi - GetServiceKeyName returns empty service name

I'm trying to get service name from display name using winapi function GetServiceKeyName in C++ with the following code:
SC_HANDLE hSCManager = OpenSCManager(NULL,
NULL, // service control manager database
SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE |SC_MANAGER_QUERY_LOCK_STATUS | STANDARD_RIGHTS_READ);
LPTSTR lpServiceName;
LPCTSTR lpDisplayName = L"My service";
DWORD dwSizeNeeded = sizeof(lpDisplayName);
GetServiceKeyName(hSCManager, lpDisplayName, lpServiceName, &dwSizeNeeded);
After finish, dwSizeNeeded have 15 as value and lpServiceName have "". Where I'm wrong calling to this function? Need any special right here? The inconvenience here is that this app doesn't have admin rights so I cannot (I guess) set SC_MANAGER_ALL_ACCESS. Of course, My service is up and running in system, so I have not bad display name.
Two lines needs to be modified:
...
TCHAR lpServiceName[512];
DWORD dwSizeNeeded = sizeof lpServcieName / sizeof lpServiceName[0]; // Number of elements in buffer
...
You should also see what GetServiceKeyName returns as that is the signal if it has succeeded or not. Checking the string is only valid if GetServiceKeyName returns TRUE.

Query cached ACL of a directory for a disconnected Windows domain user via Authz API

I would like to explore the effective rights of a local directory for the currently logged on domain user. This link has given me a good starting point and works as expected.
Unfortunately, querying the ACL fails when I disconnect from the domain controller and log on with my cached domain user profile. When being confronted with this environment, the AuthzInitializeContextFromSid function in the example code returns FALSE and sets 1355 (ERROR_NO_SUCH_DOMAIN) as the last error code:
...
PSID pSid = NULL;
BOOL bResult = FALSE;
LUID unusedId = { 0 };
AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzClientContext = NULL;
pSid = ConvertNameToBinarySid(lpszUserName);
if (pSid != NULL)
{
bResult = AuthzInitializeContextFromSid(0,
pSid,
hManager,
NULL,
unusedId,
NULL,
&hAuthzClientContext);
if (bResult)
{
GetAccess(hAuthzClientContext, psd);
AuthzFreeContext(hAuthzClientContext);
}
else
{
//prints 1355 as last error code
wprintf_s(_T("AuthzInitializeContextFromSid failed with %d\n"), GetLastError());
}
...
}
Is it possible to query the (cached) ACL nonetheless? Surely they must be accessible some way or another, even if the domain controller is unreachable.

Get local administrators users' names using C++

I'm wondering if I can get witch users belongs to my local administrator group and list them. Is there any way to do that using C++? Any WinAPI way, maybe?
Thanks a lot.
You can use NetUserGetLocalGroups and NetUserGetInfo to retrive your information and check the value of usri1_priv in the USER_INFO_1 structure.
I think something like this should get you started (taken from MSDN):
BOOL IsUserAdmin(VOID)
/*++
Routine Description: This routine returns TRUE if the caller's
process is a member of the Administrators local group. Caller is NOT
expected to be impersonating anyone and is expected to be able to
open its own process and process token.
Arguments: None.
Return Value:
TRUE - Caller has Administrators local group.
FALSE - Caller does not have Administrators local group. --
*/
{
BOOL b;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
PSID AdministratorsGroup;
b = AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&AdministratorsGroup);
if(b)
{
if (!CheckTokenMembership( NULL, AdministratorsGroup, &b))
{
b = FALSE;
}
FreeSid(AdministratorsGroup);
}
return(b);
}
You can also consult this page (it's old, but it should work)

Detecting user name from process ID

Ho can i get user account name, that ran the process with specified id. Is there any api function for this?
I am using windows,c++.
There is not an API function that does this directly, however you can combine a few API calls to do this. Of course your program will need to satisfy any ACLs that are applied to the process that you are interested in examining.
First, given the process ID, you'll need to open a handle to the process. You can use OpenProcess for that, requesting the PROCESS_QUERY_INFORMATION access right.
Once you have that handle, you can call OpenProcessToken, requesting the TOKEN_QUERY access right.
Finally, you can then call GetTokenInformation, requesting the TokenUser information class, which will give you the user account of the token. This information is provided to you in the form of a SID. To convert the SID to the actual name of the account, you can call LookupAccountSid.
Don't forget to call CloseHandle on both the process handle and the token handle once you're finished with them.
And this is function using Aaron Klotz algorithm:
public static string GetProcessOwnerByID(int processId)
{
IntPtr processHandle = IntPtr.Zero;
IntPtr tokenHandle = IntPtr.Zero;
try
{
processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, false, processId);
if (processHandle == IntPtr.Zero)
return "NO ACCESS";
OpenProcessToken(processHandle, TOKEN_QUERY, out tokenHandle);
using (WindowsIdentity wi = new WindowsIdentity(tokenHandle))
{
string user = wi.Name;
return user.Contains(#"\") ? user.Substring(user.IndexOf(#"\") + 1) : user;
}
}
finally
{
if (tokenHandle != IntPtr.Zero) CloseHandle(tokenHandle);
if (processHandle != IntPtr.Zero) CloseHandle(processHandle);
}
}
Whole function (with constants) can be found on GitHub gist

Programmatically allow write access for a Registry key

I need to programmatically modify the Access Descriptors on a known Registry key during product installation. The way I want it to work is:
The installer is run in Administrative mode.
A Registry key is created.
A function (the one I need) queries the ACL from the key.
If this function finds that the group 'Users' already has write access, nothing should be done.
If not, it should add a new permission allowing write access to the 'Users' group.
The permissions are saved for the Registry key.
This question is similar to Setting Registry key write permissions using .NET, however, I need a C++/Win32 implementation.
Thanks in advance
For getting and setting the ACL of the key you need to use RegGetKeySecurity and RegSetKeySecurity. Then you need to iterate through the ACEs, examining any that apply to the "Users" group SID. Then you'll either modify/remove the existing one and/or add a new one. Be advised that working with ACLs in plain old Win32 C is a pain.
The smallest code to grant access consists of 3 API calls. It gives full access to the given hkey for all authenticated users and administrators.
This snippet does not contain proper error handling and reporting. Do not copy/paste it into the production code.
PSECURITY_DESCRIPTOR sd = nullptr;
ULONG sd_size = 0;
TCHAR* rights = TEXT( "D:" ) // Discretionary ACL
TEXT( "(A;OICI;GA;;;AU)" ) // Allow full control to all authenticated users
TEXT( "(A;OICI;GA;;;BA)" ); // Allow full control to administrators
ConvertStringSecurityDescriptorToSecurityDescriptor( rights, SDDL_REVISION_1, &sd, &sd_size );
RegSetKeySecurity( hkey, DACL_SECURITY_INFORMATION, sd );
LocalFree( sd );
Detecting if "Users" have write access to the key might be more difficult than expected. I ended up with writing a test value to the registry and checking the result of that write.
Just to expand on Mikhail Vorotilov's answer, and also drawing inspiration from the example code at
https://learn.microsoft.com/en-us/windows/win32/secbp/creating-a-dacl
bool RegistryGrantAll(HKEY hKey)
{
bool bResult = false;
PSECURITY_DESCRIPTOR sd = nullptr;
const TCHAR* szSD =
TEXT("D:") // Discretionary ACL
TEXT("(D;OICI;KA;;;BG)") // Deny access to built-in guests
TEXT("(D;OICI;KA;;;AN)") // Deny access to anonymous logon
TEXT("(A;OICI;KRKW;;;AU)") // Allow KEY_READ and KEY_WRITE to authenticated users ("AU")
TEXT("(A;OICI;KA;;;BA)"); // Allow KEY_ALL_ACCESS to administrators ("BA" = Built-in Administrators)
if (ConvertStringSecurityDescriptorToSecurityDescriptor((LPCTSTR)szSD, SDDL_REVISION_1, &sd, 0))
{
auto result = RegSetKeySecurity(hKey, DACL_SECURITY_INFORMATION, sd);
if (ERROR_SUCCESS == result)
bResult = true;
else
SetLastError(result);
// Free the memory allocated for the SECURITY_DESCRIPTOR.
LocalFree(sd);
}
return bResult;
}
If the function returns false then call GetLastError() for more information on the failure cause.
Code compiles on VS2019 and appears to work.
I have not added code to check that hKey is a valid registry handle.
Edit: I've edited this a few times following testing. Sorry about all the edits. What I ended up with was far closer to Mikhail's answer than I started with.
Links to further info:
https://learn.microsoft.com/en-us/windows/win32/secbp/creating-a-dacl
https://learn.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertstringsecuritydescriptortosecuritydescriptorw
https://learn.microsoft.com/en-us/windows/win32/secauthz/ace-strings
Sup, hope OP is still interested in the answer. Here is the working code adding ACEs to ACLs, it may be used to add ACEs to registry or filesystem DACLs. I haven't tried it with anything else yet. As you may notice, no nasty RegGetKeySecurity or manual ACL composing needed. There's even no need to RegOpenKeyEx. For more info, check this MS doc.
UPD Of course it will need admin rights for execution.
// sk - alloced string / path to needed key
// common look: MACHINE\\Software\\... where MACHINE == HKEY_LOCAL_MACHINE
// google for more address abbrevations
PSECURITY_DESCRIPTOR pSD = 0;
EXPLICIT_ACCESS ea;
PACL pOldDACL = 0, pNewDACL = 0;
if (ERROR_SUCCESS == GetNamedSecurityInfo(sk, SE_REGISTRY_KEY, DACL_SECURITY_INFORMATION, 0, 0, &pOldDACL, 0, &pSD)) {
memset(&ea, 0, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = KEY_ALL_ACCESS;
ea.grfAccessMode = GRANT_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
ea.Trustee.ptstrName = <USERNAME HERE>; //DOMAIN\\USERNAME
if (ERROR_SUCCESS == SetEntriesInAcl(1, &ea, pOldDACL, &pNewDACL)) {
if (ERROR_SUCCESS == SetNamedSecurityInfo(sk, SE_REGISTRY_KEY, DACL_SECURITY_INFORMATION, 0, 0, pNewDACL, 0)) {
if (pSD != 0) LocalFree((HLOCAL)pSD);
if (pNewDACL != 0) LocalFree((HLOCAL)pNewDACL);
SAFE_FREE(sk);
// WE'RE GOOD!
return ... ;
} else {
if (pSD) LocalFree((HLOCAL)pSD);
if (pNewDACL) LocalFree((HLOCAL)pNewDACL);
SAFE_FREE(sk);
// SetNamedSecurityInfo failed
return ... ;
}
} else {
if (pSD) LocalFree((HLOCAL)pSD);
if (pNewDACL) LocalFree((HLOCAL)pNewDACL);
SAFE_FREE(sk);
// SetEntriesInAcl failed
return ... ;
}
} else {
if (pSD) LocalFree((HLOCAL)pSD);
SAFE_FREE(sk);
// GetNamedSecurityInfo failed
return ... ;
}