4

I am trying to set an executable to run as a local low-privileged user (different from the current logged-on user) by default. The Windows command "runas /savecred /user:username appname.exe" can do it, but it requires the current logged-on user to manually type in password of the different user at the first time. Ideally, I want the installer of the program set the credential, so no interaction would be needed for the current logged-on user. (I don't want to make the program as a Windows Service for other good reasons.)

When I ran the "runas" command, I observed that a new "interactive logon" Windows Credential got created in the Credential Manager, where "Internet or network address" was set as "$computer_name\$username (Interactive Logon)", "User name" was set as "$computer_name\$username", "password" was displayed as "********", and "Persistence" was set as "Enterprise".

Based on that observation, I found a Win32 API, CredWrite, and but I'm having trouble to make it work. I got error code 87 (ERROR_INVALID_PARAMETER - The parameter is incorrect.) when CredWrite was called.

#include <windows.h>
#include <wincred.h>
#include <tchar.h>

void main ()
{
    char* password = "randompassword";
    DWORD blobsize= 1 + strlen(password);

    CREDENTIAL cred = {0};
    cred.Flags = CRED_FLAGS_USERNAME_TARGET;
    cred.Type = CRED_TYPE_DOMAIN_PASSWORD;
    cred.TargetName = L"computername\\username";
    cred.CredentialBlobSize = blobsize;
    cred.CredentialBlob = (LPBYTE) password;
    cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
    cred.UserName = L"computername\\username";

    if (!CredWrite(&cred, 0))
    {
        std::cerr << GetLastError() << std::endl;
    }
}

The references that I have been looking at are listed at the bottom. I was able to create a CRED_TYPE_GENERIC type credential but it was not recognized by RUNAS. I'm really puzzled on how to set fields like TargetName and Type in order to programmatically create an interactive logon credential as RUNAS does. The official MSDN documentation isn't clear and there is no example.

CredWrite : https://msdn.microsoft.com/en-us/library/windows/desktop/aa375187(v=vs.85).aspx CREDENTIAL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx The sample code for creating a CRED_TYPE_GENERIC type credential: How do I store and retrieve credentials from the Windows Vault credential manager?

Community
  • 1
  • 1
Batistuta
  • 127
  • 1
  • 9
  • see this url http://stackoverflow.com/questions/3729406/create-local-user-account – Proxytype Sep 27 '16 at 17:47
  • Thanks for reply, Proxytype.I had no problem to create the local low-privileged account with NetUserAdd. – Batistuta Sep 27 '16 at 17:55
  • looks like: Certain fields cannot be changed in an existing credential. This error is returned if a field does not match the value in a protected field of the existing credential. -- you cannot change the parameter after the user created, it's mean you need to set it when you create the user.. https://msdn.microsoft.com/en-us/library/windows/desktop/aa375187(v=vs.85).aspx – Proxytype Sep 27 '16 at 17:59
  • I was confused by that line ("Certain fields cannot be changed in an existing credential.") in the documentation for the error code ERROR_INVALID_PARAMETER. I think I'm creating a new credential, because there is no credential of that user shown in the Credential Manager, though the user account got created earlier. – Batistuta Sep 27 '16 at 18:03
  • after you create the user some of the parameters are look for writing... you need ring0 code for release the write protection and this is malicious :) – Proxytype Sep 27 '16 at 18:05
  • Some parameters are locked for write after the user account gets created earlier in the same process of the installer? Is it documented some where? – Batistuta Sep 27 '16 at 18:15
  • in what i see in your code it's make sense... you cannot just change password for user without handle the operation system security... i dont think you can find information about that so quickly... try here: https://en.wikipedia.org/wiki/Protection_ring – Proxytype Sep 27 '16 at 18:19
  • I don't want to change the password. I just want to mimic what "runas /savecred /user" does. RUNAS prompts the user to type in password, if it matches the password of the specified user, a new credential gets created. Otherwise, it does nothing. Similarly, when a password gets passed into CredWrite, it should be verified first by the OS. That's just my theory though. – Batistuta Sep 27 '16 at 18:23
  • at the end the password is different from the native credential... you can try this: http://stackoverflow.com/questions/10038871/c-sharp-pinvoking-credwrite-issue-on-windows-xp-pro – Proxytype Sep 27 '16 at 18:28

2 Answers2

3

There is an undocumented Flag of the CREDENTIAL structure that you pass to CredWrite.

If you call the API with flag = 8196, you will get the result you desire

char* password1 = "randompassword";
DWORD blobsize = strlen(password1); // Do NOT add 1 here.

CREDENTIAL cred = { 0 };
cred.Flags = 8196;
cred.Type = 2;
cred.TargetName = L"computername\\username";
cred.CredentialBlobSize = blobsize;
cred.CredentialBlob = (LPBYTE)password1;
cred.Persist = 3;
cred.UserName = L"computername\\username";

if (!CredWrite(&cred, 0))
{
    std::cerr << GetLastError() << std::endl;
}
4x6hw
  • 149
  • 9
1

The ERROR_INVALID_PARAMETER error (error code 87) was caused by the wrong value of CredentialBlobSize. "If the Type member is CRED_TYPE_DOMAIN_PASSWORD, this member contains the plaintext Unicode password for UserName. The CredentialBlob and CredentialBlobSize members do not include a trailing zero character." (CREDENTIAL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx)

With this fix a Windows Credential (not Generic or Certificate-based Credential) can be created without error.

#include <windows.h>
#include <wincred.h>
#include <tchar.h>

void main ()
{
    char* password = "randompassword";
    DWORD blobsize= strlen(password); // Do NOT add 1 here.

    CREDENTIAL cred = {0};
    cred.Flags = CRED_FLAGS_USERNAME_TARGET;
    cred.Type = CRED_TYPE_DOMAIN_PASSWORD;
    cred.TargetName = L"computername\\username";
    cred.CredentialBlobSize = blobsize;
    cred.CredentialBlob = (LPBYTE) password;
    cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
    cred.UserName = L"computername\\username";

    if (!CredWrite(&cred, 0))
    {
        std::cerr << GetLastError() << std::endl;
    }
} 

However, there is still a subtle difference. The "Internet or network address" of the one that I created was "$computer_name\$username (Windows Identity)" which cannot be recognized by RUNAS. The one created by RUNAS was "$computer_name\$username (Interactive Logon)". How does that bit get set?

I can use CredRead to read the credential that I created, but fail to do that for the one created by RUNAS.

CREDENTIAL cred = { 0 };
PCREDENTIAL pcred = &cred;
// Works for Windows Identity. TargetName is case-insensitive.
CredRead(L"$computer_name\$username", CRED_TYPE_DOMAIN_PASSWORD, 0, &pcred); 
// Fails for Interactive Logon with error code 87 or 1168. I tried all possible CRED_TYPE_*.
CredRead(L"$computer_name\$username", CRED_TYPE_*, 0, &pcred); 

Similar threads: 1. Runas Command, Bypass Credentials (https://community.spiceworks.com/topic/485852-runas-command-bypass-credentials) 2 .runas /savecred… don't accept cmdkey /add (credentials) (runas /savecred... don't accept cmdkey /add (credentials)) 3. runas with the /savecred switch does not accept a credential stored by the cmdkey command (https://social.technet.microsoft.com/Forums/windowsserver/en-US/c0e398cc-224f-4390-bc69-0ee544f9b5cb/runas-with-the-savecred-switch-does-not-accept-a-credential-stored-by-the-cmdkey-command?forum=winserversecurity)

CMDKEY command allowed me to create a credential closest to the one created by RUNAS. Both look the same in the result returned by "cmdkey /list". However, the one created by CMDKEY is not honored by RUNAS.

cmdkey /add:Domain:interactive=$computer_name\$username1 /user:$computer_name\$username1 /pass:$password

cmdkey /list

# created by CMDKEY
Target: Domain:interactive=$computer_name\$username1
Type: Domain Password
User: $computer_name\$username1

# created by RUNAS
Target: Domain:interactive=$computer_name\$username2
Type: Domain Password
User: $computer_name\$username2
Community
  • 1
  • 1
Batistuta
  • 127
  • 1
  • 9