New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Keepass Windows User Account #1437

Open
DieKart opened this Issue Nov 9, 2017 · 13 comments

Comments

Projects
None yet
5 participants
@DieKart

DieKart commented Nov 9, 2017

Hashcat can currently crack KeePass .kdb and .kdbx files with/without keyfile but there is not support for Windows User Account. This option is readily available to users of KeePass but not supported by Hashcat. Could Hashcat please add support for this feature.
Harmj0y has written an article on this feature providing some insight of what it http://www.harmj0y.net/blog/redteaming/a-case-study-in-attacking-keepass/
Maybe you guys can help us out here: @Fist0urs, @magnumripper, @HarmJ0y.

@philsmd

This comment has been minimized.

Show comment
Hide comment
@philsmd

philsmd Nov 9, 2017

Member

I did some very quick research on this (I hope that everything mentioned below is correct, feel free to independently verify it and correct me):
This feature is also called (also internally in the keepass sources etc) as WUA = Windows User Account.

The main setting file of keepass (.xml) stores this setting as
<UserAccount>true</UserAccount>

Some information about WUA is also mentioned here https://keepass.info/help/base/keys.html#winuser and there you can find links to https://sourceforge.net/p/keepass/wiki/Recover%20Windows%20User%20Account%20Credentials/

The concept of this protected data is also known as the Data Protection application programming interface (DPAPI).

Windows saves the WUA Master Key(s) here:

WUA Master Key: %APPDATA%\Microsoft\Protect\<SID>\  (the long file, not the "Preferred" file)

where %APPDATA% is just

%APPDATA%: C:\Users\<username>\AppData\Roaming\

The keepass specific ProtectedData (DPAPI) blob is stored in this file:

%APPDATA%\KeePass\ProtectedUserKey.bin

If we look at the keepass source code, we can find a KeePassLib/Keys/KcpUserAccount.cs file that is responsible to write and read the ProtectedUserKey.bin.
To generate the data (if the file did not exist yet), it uses the following steps:

de135b5f18a34670b2572429698898e6

My conclusion is that what we need to do to add support for it is to have the 64-bytes of data from ProtectedData.Unprotect () somewhere within the hash (or command line option). These 64 bytes need to be appended to the sha256 ($pass) + keyfile (optional)
It seems the order is

  1. sha256 ($pass)
  2. 32 bytes for the keyfile settings (optional)
  3. 64 bytes unprotected-data for the WUA settings (optional)

btw: there seems to be also a new jtr issue with the same request of implementing this WUA feature: see magnumripper/JohnTheRipper#2863


update: I was interested enough to give this seemingly minor modification that needs to be done a try and here are some more info and code:

I generateted a new Keepass Database (with newest Keepass 2.37, but the version shouldn't matter to much for now, it just should be at least 2.x I think).
After selecting the "User Account" option when creating a new database, the %APPDATA%\KeePass\ folder containing the ProtectedUserKey.bin was created.
The content of the file in hex was in my case (this of course depends on the computer/user account, it's always different for a new account, it remains the same if you "just" change your windows password):

01000000d08c9ddf0115d1118c7a00c04fc297eb010000000fdf04152eb77d41ae7573c264a446ba00000000020000000000106600000001000020000000d742af90ddc4428fd29e4d1a9d2611d47df223bad79a84413477978293d03c4e000000000e80000000020000200000005cb8caeb15d68fcda00637526d4584750776126af1d3847d5bb8c5e9da3839bd50000000171fa403753833691a362044609babb936d4be81dfa463e1fc292681a71db38e7d21394b13865aa40e6f91d3f73b468cb0fc968baacfaf7fb8267906c32a2a517296618cbf07c8f8db88c65c7fb52dba400000003bc90d6acde4fe764103d4d03bb087266b2bdb182d1e59442505ac60450fcdf1a78305d42f63df18ed8ea7a504c805becb38f6f0024bafb3a90a29e9b154b4b2

I've used this code to convert it to the unprotected version:

#include <windows.h>
#include <wincrypt.h>
#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

int main ()
{
  DATA_BLOB DataIn;
  DataIn.pbData = (BYTE *) "\x01\x00\x00\x00\xd0\x8c\x9d\xdf\x01\x15\xd1\x11\x8c\x7a\x00\xc0\x4f\xc2\x97\xeb\x01\x00\x00\x00\x0f\xdf\x04\x15\x2e\xb7\x7d\x41\xae\x75\x73\xc2\x64\xa4\x46\xba\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x10\x66\x00\x00\x00\x01\x00\x00\x20\x00\x00\x00\xd7\x42\xaf\x90\xdd\xc4\x42\x8f\xd2\x9e\x4d\x1a\x9d\x26\x11\xd4\x7d\xf2\x23\xba\xd7\x9a\x84\x41\x34\x77\x97\x82\x93\xd0\x3c\x4e\x00\x00\x00\x00\x0e\x80\x00\x00\x00\x02\x00\x00\x20\x00\x00\x00\x5c\xb8\xca\xeb\x15\xd6\x8f\xcd\xa0\x06\x37\x52\x6d\x45\x84\x75\x07\x76\x12\x6a\xf1\xd3\x84\x7d\x5b\xb8\xc5\xe9\xda\x38\x39\xbd\x50\x00\x00\x00\x17\x1f\xa4\x03\x75\x38\x33\x69\x1a\x36\x20\x44\x60\x9b\xab\xb9\x36\xd4\xbe\x81\xdf\xa4\x63\xe1\xfc\x29\x26\x81\xa7\x1d\xb3\x8e\x7d\x21\x39\x4b\x13\x86\x5a\xa4\x0e\x6f\x91\xd3\xf7\x3b\x46\x8c\xb0\xfc\x96\x8b\xaa\xcf\xaf\x7f\xb8\x26\x79\x06\xc3\x2a\x2a\x51\x72\x96\x61\x8c\xbf\x07\xc8\xf8\xdb\x88\xc6\x5c\x7f\xb5\x2d\xba\x40\x00\x00\x00\x3b\xc9\x0d\x6a\xcd\xe4\xfe\x76\x41\x03\xd4\xd0\x3b\xb0\x87\x26\x6b\x2b\xdb\x18\x2d\x1e\x59\x44\x25\x05\xac\x60\x45\x0f\xcd\xf1\xa7\x83\x05\xd4\x2f\x63\xdf\x18\xed\x8e\xa7\xa5\x04\xc8\x05\xbe\xcb\x38\xf6\xf0\x02\x4b\xaf\xb3\xa9\x0a\x29\xe9\xb1\x54\xb4\xb2"  "\x00";
  DataIn.cbData = 294;

  DATA_BLOB entropy;
  entropy.pbData = (BYTE *) "\xde\x13\x5b\x5f\x18\xa3\x46\x70\xb2\x57\x24\x29\x69\x88\x98\xe6"  "\x00";
  entropy.cbData = 16;

  DATA_BLOB DataOut;
  LPWSTR pDescrOut = NULL;

 if (CryptUnprotectData (&DataIn, &pDescrOut, &entropy, NULL, NULL, 0, &DataOut))
  {
    // printf ("The description of the data was: %S\n", pDescrOut);

    printf ("The decrypted data length is: %i\n", DataOut.cbData);

    printf ("The decrypted data is: ");

    unsigned int i = 0;

    for (i = 0; i < DataOut.cbData; i++)
    {
      printf ("%02x", DataOut.pbData[i]);
    }

    printf ("\n");
  }
  else
  {
    fprintf (stderr, "An error while decrypting the data. \n");
  }

  return 0;
}

I compiled it like this:

x86_64-w64-mingw32-g++ -m64 protected_data_unprotect.cpp -o protected_data_unprotect.exe -lcrypt32

Note: this program needs to be run on the correct windows computer where the DPAPI master key file etc is used to unprotect the content. Also note that DataIn.pbData could easily be read from a file (I just didn't bother too much to add the code for opening/reading the file for now). Furthermore, entropy.pbData seems to be constant/static and doesn't need to be changed. The user only needs to modify DataIn.pbData (or someone could add the code to read these bytes from the ProtectedUserKey.bin file).

Running the protected_data_unprotect.exe program yielded this for me (this of course also depends on your data and windows account, it's different from you, but the length should be always 64):

The decrypted data length is: 64
The decrypted data is: 591674cc5fb167aa5f9e55c0a96cd66fadeb02366215d731364b19603a36b3b779c6e3b9dbf3027fd34c074b4c15c4a6efaf172933afd46dcaf2e6f9d911fcfc

This seems to be all what we need, i.e. the additional 64 bytes (unprotected version of the bytes from ProtectedUserKey.bin).

I tested to crack my database with keepass2john + hashcat.

The current patch (that was successfully applied to this version fddb66e) is:
https://gist.github.com/philsmd/36a38c1527293da3fb4848c3ef1d0aa9

there are a couple of todos for now:

  1. the protected_data_len needs to be read from the host code (it somehow needs to depend on the hash)
  2. the data after this if statement: if (protected_data_len > 0) of course also needs to be dynamic and supplied by the user (and past by the host code)

for now this is just a POC and those TODOs need to be implemented by choosing a new hash format and implementing the modified parser (and passing the data to the GPU)

I have these 2 hashes for you to test (I used keepass2john to convert the databases, both Databases used the same ProtectedUserKey.bin and therefore my patch with the currently hard-coded 64-bytes work for both):

  1. with "Windows User Account" setting but without keyfile (password is "hashcat", without quotes):
$keepass$*2*60000*0*a4f18d89fe227b96fd435aa4da26d856b1a3f7951a85fd6350730bb84e8b6113*fbce34b2a9f882d27f02a16afd6e02133ca3681903715766e92e4738a2ca2341*10fd77ccb982b386ba91b52063d5bd22*657adcda06deb085367c6705d68f072156082ec8aefddfedce8501b44e1f9c5b*e7a2bc6acbd0c20945fffce5ff70c8f41e77eb3c5987cad431db69d0bc24bd9b
  1. with "Windows User Account" setting and with keyfile (password is "hashcat!", without quotes):
$keepass$*2*60000*0*5e96fa939c3ca427c6afca68c865aebb3c98f5ea1c7e1e87e94c2042b7ed6376*2e1769b64f582de6252bd901975a19e59056275e4c79f3f14883f1e3fb90fa27*d2a38c754f6eff1fcdb667a967cf5fd5*25c76ba25e65e367597c98418c49cf66a10204c528f43a316b34fca5de896684*fe57adc05093db6c4d63c7923738e893c1e36d2dcfe04a95449e954180bce0e9*1*64*74eb49f8ec2e021d17b6b6a032cdb19e6f11c1acff670c232bfd53be9c07c94f

both crack perfectly fine.

This means it's actually very easy to add support for it.

The only bummers for now are:

  1. keepass2john needs to be modified to support reading the ProtectedUserKey.bin file and generate a different hash with these additional 64 bytes
  2. the unprotected data from ProtectedUserKey.bin needs, as said, be extracted on the actual machine it was first generated (and also with the correct user account that was used while the database was generated). This of course implies that in case of a hard disk failure you have to get back access to all this information (the WUA master key and the ProtectedUserKey.bin file). Just the access to the file ProtectedUserKey.bin doesn't mean that you can somehow unprotect it. You also need to have the window's WUA master key.
  3. It doesn't seem that we can figure out from just the .kdbx file whether WUA was used or not. This means that you either have the original %APPDATA%/KeePass/KeePass.config.xml where this information could be recovered (because the default/last options are stored for convenience), or you need to test all combinations: pass, pass+keyfile, pass+WUA, pass+keyfile+WUA

Thx

Member

philsmd commented Nov 9, 2017

I did some very quick research on this (I hope that everything mentioned below is correct, feel free to independently verify it and correct me):
This feature is also called (also internally in the keepass sources etc) as WUA = Windows User Account.

The main setting file of keepass (.xml) stores this setting as
<UserAccount>true</UserAccount>

Some information about WUA is also mentioned here https://keepass.info/help/base/keys.html#winuser and there you can find links to https://sourceforge.net/p/keepass/wiki/Recover%20Windows%20User%20Account%20Credentials/

The concept of this protected data is also known as the Data Protection application programming interface (DPAPI).

Windows saves the WUA Master Key(s) here:

WUA Master Key: %APPDATA%\Microsoft\Protect\<SID>\  (the long file, not the "Preferred" file)

where %APPDATA% is just

%APPDATA%: C:\Users\<username>\AppData\Roaming\

The keepass specific ProtectedData (DPAPI) blob is stored in this file:

%APPDATA%\KeePass\ProtectedUserKey.bin

If we look at the keepass source code, we can find a KeePassLib/Keys/KcpUserAccount.cs file that is responsible to write and read the ProtectedUserKey.bin.
To generate the data (if the file did not exist yet), it uses the following steps:

de135b5f18a34670b2572429698898e6

My conclusion is that what we need to do to add support for it is to have the 64-bytes of data from ProtectedData.Unprotect () somewhere within the hash (or command line option). These 64 bytes need to be appended to the sha256 ($pass) + keyfile (optional)
It seems the order is

  1. sha256 ($pass)
  2. 32 bytes for the keyfile settings (optional)
  3. 64 bytes unprotected-data for the WUA settings (optional)

btw: there seems to be also a new jtr issue with the same request of implementing this WUA feature: see magnumripper/JohnTheRipper#2863


update: I was interested enough to give this seemingly minor modification that needs to be done a try and here are some more info and code:

I generateted a new Keepass Database (with newest Keepass 2.37, but the version shouldn't matter to much for now, it just should be at least 2.x I think).
After selecting the "User Account" option when creating a new database, the %APPDATA%\KeePass\ folder containing the ProtectedUserKey.bin was created.
The content of the file in hex was in my case (this of course depends on the computer/user account, it's always different for a new account, it remains the same if you "just" change your windows password):

01000000d08c9ddf0115d1118c7a00c04fc297eb010000000fdf04152eb77d41ae7573c264a446ba00000000020000000000106600000001000020000000d742af90ddc4428fd29e4d1a9d2611d47df223bad79a84413477978293d03c4e000000000e80000000020000200000005cb8caeb15d68fcda00637526d4584750776126af1d3847d5bb8c5e9da3839bd50000000171fa403753833691a362044609babb936d4be81dfa463e1fc292681a71db38e7d21394b13865aa40e6f91d3f73b468cb0fc968baacfaf7fb8267906c32a2a517296618cbf07c8f8db88c65c7fb52dba400000003bc90d6acde4fe764103d4d03bb087266b2bdb182d1e59442505ac60450fcdf1a78305d42f63df18ed8ea7a504c805becb38f6f0024bafb3a90a29e9b154b4b2

I've used this code to convert it to the unprotected version:

#include <windows.h>
#include <wincrypt.h>
#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

int main ()
{
  DATA_BLOB DataIn;
  DataIn.pbData = (BYTE *) "\x01\x00\x00\x00\xd0\x8c\x9d\xdf\x01\x15\xd1\x11\x8c\x7a\x00\xc0\x4f\xc2\x97\xeb\x01\x00\x00\x00\x0f\xdf\x04\x15\x2e\xb7\x7d\x41\xae\x75\x73\xc2\x64\xa4\x46\xba\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x10\x66\x00\x00\x00\x01\x00\x00\x20\x00\x00\x00\xd7\x42\xaf\x90\xdd\xc4\x42\x8f\xd2\x9e\x4d\x1a\x9d\x26\x11\xd4\x7d\xf2\x23\xba\xd7\x9a\x84\x41\x34\x77\x97\x82\x93\xd0\x3c\x4e\x00\x00\x00\x00\x0e\x80\x00\x00\x00\x02\x00\x00\x20\x00\x00\x00\x5c\xb8\xca\xeb\x15\xd6\x8f\xcd\xa0\x06\x37\x52\x6d\x45\x84\x75\x07\x76\x12\x6a\xf1\xd3\x84\x7d\x5b\xb8\xc5\xe9\xda\x38\x39\xbd\x50\x00\x00\x00\x17\x1f\xa4\x03\x75\x38\x33\x69\x1a\x36\x20\x44\x60\x9b\xab\xb9\x36\xd4\xbe\x81\xdf\xa4\x63\xe1\xfc\x29\x26\x81\xa7\x1d\xb3\x8e\x7d\x21\x39\x4b\x13\x86\x5a\xa4\x0e\x6f\x91\xd3\xf7\x3b\x46\x8c\xb0\xfc\x96\x8b\xaa\xcf\xaf\x7f\xb8\x26\x79\x06\xc3\x2a\x2a\x51\x72\x96\x61\x8c\xbf\x07\xc8\xf8\xdb\x88\xc6\x5c\x7f\xb5\x2d\xba\x40\x00\x00\x00\x3b\xc9\x0d\x6a\xcd\xe4\xfe\x76\x41\x03\xd4\xd0\x3b\xb0\x87\x26\x6b\x2b\xdb\x18\x2d\x1e\x59\x44\x25\x05\xac\x60\x45\x0f\xcd\xf1\xa7\x83\x05\xd4\x2f\x63\xdf\x18\xed\x8e\xa7\xa5\x04\xc8\x05\xbe\xcb\x38\xf6\xf0\x02\x4b\xaf\xb3\xa9\x0a\x29\xe9\xb1\x54\xb4\xb2"  "\x00";
  DataIn.cbData = 294;

  DATA_BLOB entropy;
  entropy.pbData = (BYTE *) "\xde\x13\x5b\x5f\x18\xa3\x46\x70\xb2\x57\x24\x29\x69\x88\x98\xe6"  "\x00";
  entropy.cbData = 16;

  DATA_BLOB DataOut;
  LPWSTR pDescrOut = NULL;

 if (CryptUnprotectData (&DataIn, &pDescrOut, &entropy, NULL, NULL, 0, &DataOut))
  {
    // printf ("The description of the data was: %S\n", pDescrOut);

    printf ("The decrypted data length is: %i\n", DataOut.cbData);

    printf ("The decrypted data is: ");

    unsigned int i = 0;

    for (i = 0; i < DataOut.cbData; i++)
    {
      printf ("%02x", DataOut.pbData[i]);
    }

    printf ("\n");
  }
  else
  {
    fprintf (stderr, "An error while decrypting the data. \n");
  }

  return 0;
}

I compiled it like this:

x86_64-w64-mingw32-g++ -m64 protected_data_unprotect.cpp -o protected_data_unprotect.exe -lcrypt32

Note: this program needs to be run on the correct windows computer where the DPAPI master key file etc is used to unprotect the content. Also note that DataIn.pbData could easily be read from a file (I just didn't bother too much to add the code for opening/reading the file for now). Furthermore, entropy.pbData seems to be constant/static and doesn't need to be changed. The user only needs to modify DataIn.pbData (or someone could add the code to read these bytes from the ProtectedUserKey.bin file).

Running the protected_data_unprotect.exe program yielded this for me (this of course also depends on your data and windows account, it's different from you, but the length should be always 64):

The decrypted data length is: 64
The decrypted data is: 591674cc5fb167aa5f9e55c0a96cd66fadeb02366215d731364b19603a36b3b779c6e3b9dbf3027fd34c074b4c15c4a6efaf172933afd46dcaf2e6f9d911fcfc

This seems to be all what we need, i.e. the additional 64 bytes (unprotected version of the bytes from ProtectedUserKey.bin).

I tested to crack my database with keepass2john + hashcat.

The current patch (that was successfully applied to this version fddb66e) is:
https://gist.github.com/philsmd/36a38c1527293da3fb4848c3ef1d0aa9

there are a couple of todos for now:

  1. the protected_data_len needs to be read from the host code (it somehow needs to depend on the hash)
  2. the data after this if statement: if (protected_data_len > 0) of course also needs to be dynamic and supplied by the user (and past by the host code)

for now this is just a POC and those TODOs need to be implemented by choosing a new hash format and implementing the modified parser (and passing the data to the GPU)

I have these 2 hashes for you to test (I used keepass2john to convert the databases, both Databases used the same ProtectedUserKey.bin and therefore my patch with the currently hard-coded 64-bytes work for both):

  1. with "Windows User Account" setting but without keyfile (password is "hashcat", without quotes):
$keepass$*2*60000*0*a4f18d89fe227b96fd435aa4da26d856b1a3f7951a85fd6350730bb84e8b6113*fbce34b2a9f882d27f02a16afd6e02133ca3681903715766e92e4738a2ca2341*10fd77ccb982b386ba91b52063d5bd22*657adcda06deb085367c6705d68f072156082ec8aefddfedce8501b44e1f9c5b*e7a2bc6acbd0c20945fffce5ff70c8f41e77eb3c5987cad431db69d0bc24bd9b
  1. with "Windows User Account" setting and with keyfile (password is "hashcat!", without quotes):
$keepass$*2*60000*0*5e96fa939c3ca427c6afca68c865aebb3c98f5ea1c7e1e87e94c2042b7ed6376*2e1769b64f582de6252bd901975a19e59056275e4c79f3f14883f1e3fb90fa27*d2a38c754f6eff1fcdb667a967cf5fd5*25c76ba25e65e367597c98418c49cf66a10204c528f43a316b34fca5de896684*fe57adc05093db6c4d63c7923738e893c1e36d2dcfe04a95449e954180bce0e9*1*64*74eb49f8ec2e021d17b6b6a032cdb19e6f11c1acff670c232bfd53be9c07c94f

both crack perfectly fine.

This means it's actually very easy to add support for it.

The only bummers for now are:

  1. keepass2john needs to be modified to support reading the ProtectedUserKey.bin file and generate a different hash with these additional 64 bytes
  2. the unprotected data from ProtectedUserKey.bin needs, as said, be extracted on the actual machine it was first generated (and also with the correct user account that was used while the database was generated). This of course implies that in case of a hard disk failure you have to get back access to all this information (the WUA master key and the ProtectedUserKey.bin file). Just the access to the file ProtectedUserKey.bin doesn't mean that you can somehow unprotect it. You also need to have the window's WUA master key.
  3. It doesn't seem that we can figure out from just the .kdbx file whether WUA was used or not. This means that you either have the original %APPDATA%/KeePass/KeePass.config.xml where this information could be recovered (because the default/last options are stored for convenience), or you need to test all combinations: pass, pass+keyfile, pass+WUA, pass+keyfile+WUA

Thx

@philsmd

This comment has been minimized.

Show comment
Hide comment
@philsmd

philsmd Nov 10, 2017

Member

Here is a slightly modified version of the extraction c++ file that uses the %APPDATA% environment variable and reads the file ProtectedUserKey.bin directly (using stat/fopen/fread/fclose):

#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
#include <sys/stat.h>

#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

int main ()
{
  char *appdata = getenv ("APPDATA");

  char file_name[2049];

  snprintf (file_name, 2049, "%s\\Keepass\\ProtectedUserKey.bin", appdata);

  struct stat st;

  if (stat (file_name, &st) != 0)
  {
    fprintf (stderr, "ERROR: Could not determine the file size of the file '%s'. Does the file exist?\n", file_name);

    exit (1);
  }

  size_t file_size = st.st_size;

  FILE *fh = fopen (file_name, "rb");

  if (fh == NULL)
  {
    fprintf (stderr, "ERROR: Could not open the file '%s'\n", file_name);

    exit (1);
  }

  BYTE file_content[file_size + 1];

  if (fread (file_content, 1, file_size, fh) != file_size)
  {
    fprintf (stderr, "ERROR: Could not read %i bytes from file '%s'\n", file_size, file_name);

    fclose (fh);

    exit (1);
  }

  fclose (fh);

  DATA_BLOB DataIn;
  DataIn.pbData = file_content;
  DataIn.cbData = file_size;

  DATA_BLOB entropy;
  entropy.pbData = (BYTE *) "\xde\x13\x5b\x5f\x18\xa3\x46\x70\xb2\x57\x24\x29\x69\x88\x98\xe6";
  entropy.cbData = 16;

  DATA_BLOB DataOut;
  LPWSTR pDescrOut = NULL;

 if (CryptUnprotectData (&DataIn, &pDescrOut, &entropy, NULL, NULL, 0, &DataOut))
  {
    // printf ("The description of the data was: %S\n", pDescrOut);

    printf ("The decrypted data length is: %i\n", DataOut.cbData);

    printf ("The decrypted data is: ");

    unsigned int i = 0;

    for (i = 0; i < DataOut.cbData; i++)
    {
      printf ("%02x", DataOut.pbData[i]);
    }

    printf ("\n");
  }
  else
  {
    fprintf (stderr, "An error occurred while decrypting the data. \n");
  }

  return 0;
}
Member

philsmd commented Nov 10, 2017

Here is a slightly modified version of the extraction c++ file that uses the %APPDATA% environment variable and reads the file ProtectedUserKey.bin directly (using stat/fopen/fread/fclose):

#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
#include <sys/stat.h>

#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

int main ()
{
  char *appdata = getenv ("APPDATA");

  char file_name[2049];

  snprintf (file_name, 2049, "%s\\Keepass\\ProtectedUserKey.bin", appdata);

  struct stat st;

  if (stat (file_name, &st) != 0)
  {
    fprintf (stderr, "ERROR: Could not determine the file size of the file '%s'. Does the file exist?\n", file_name);

    exit (1);
  }

  size_t file_size = st.st_size;

  FILE *fh = fopen (file_name, "rb");

  if (fh == NULL)
  {
    fprintf (stderr, "ERROR: Could not open the file '%s'\n", file_name);

    exit (1);
  }

  BYTE file_content[file_size + 1];

  if (fread (file_content, 1, file_size, fh) != file_size)
  {
    fprintf (stderr, "ERROR: Could not read %i bytes from file '%s'\n", file_size, file_name);

    fclose (fh);

    exit (1);
  }

  fclose (fh);

  DATA_BLOB DataIn;
  DataIn.pbData = file_content;
  DataIn.cbData = file_size;

  DATA_BLOB entropy;
  entropy.pbData = (BYTE *) "\xde\x13\x5b\x5f\x18\xa3\x46\x70\xb2\x57\x24\x29\x69\x88\x98\xe6";
  entropy.cbData = 16;

  DATA_BLOB DataOut;
  LPWSTR pDescrOut = NULL;

 if (CryptUnprotectData (&DataIn, &pDescrOut, &entropy, NULL, NULL, 0, &DataOut))
  {
    // printf ("The description of the data was: %S\n", pDescrOut);

    printf ("The decrypted data length is: %i\n", DataOut.cbData);

    printf ("The decrypted data is: ");

    unsigned int i = 0;

    for (i = 0; i < DataOut.cbData; i++)
    {
      printf ("%02x", DataOut.pbData[i]);
    }

    printf ("\n");
  }
  else
  {
    fprintf (stderr, "An error occurred while decrypting the data. \n");
  }

  return 0;
}
@Fist0urs

This comment has been minimized.

Show comment
Hide comment
@Fist0urs

Fist0urs Nov 11, 2017

Contributor

Hi there, I have quickly read what you did and nice job @philsmd!
I've implemented the stuff in both jtr and hashcat for DPAPI and there is also a DPAPImk2john in the run/ in order to extract stuff related to it.
Unfortunately I really don't have time to take a deeper look at it and implement stuff :/, but feel free to email me if you have any question about DPAPI internal mechanisms (also look at #1238), I'll answer it asap.
Cheers!

Contributor

Fist0urs commented Nov 11, 2017

Hi there, I have quickly read what you did and nice job @philsmd!
I've implemented the stuff in both jtr and hashcat for DPAPI and there is also a DPAPImk2john in the run/ in order to extract stuff related to it.
Unfortunately I really don't have time to take a deeper look at it and implement stuff :/, but feel free to email me if you have any question about DPAPI internal mechanisms (also look at #1238), I'll answer it asap.
Cheers!

@DieKart

This comment has been minimized.

Show comment
Hide comment
@DieKart

DieKart Nov 12, 2017

@philsmd solution worked! Great job.
I tested with a couple test databases all crack without issue.

DieKart commented Nov 12, 2017

@philsmd solution worked! Great job.
I tested with a couple test databases all crack without issue.

@philsmd

This comment has been minimized.

Show comment
Hide comment
@philsmd

philsmd Nov 21, 2017

Member

Very good.

I think that to make any progress here we need to ask @magnumripper / @kholia (and also CC: @Fist0urs ) what we should do about the hash format changes that are needed and what they plan to do to support this in jtr etc.
Of course we could just invent a new hash format with 64 bytes added (64 bytes longer than the normal format), but I think it is a good idea to see if keepass2john will be changed in the future to support this and also to generate the new hash format etc.

The algorithm and kernel/code modification should be clear from the examples above. The extraction of the 64 bytes should also be no problem with the code provided above. It's now just a matter of when/if this will be supported also by keepass2john and if so are there several different types of hashes or just one hash format that contains the 64 bytes (or not if wua is not used etc).
I think hashcat could adapt its parser to the output of keepass2john quite easily (if it changes in the future).

This jtr issue might be good to follow too since it might be re-opened in the future (we will see): magnumripper/JohnTheRipper#2555

Member

philsmd commented Nov 21, 2017

Very good.

I think that to make any progress here we need to ask @magnumripper / @kholia (and also CC: @Fist0urs ) what we should do about the hash format changes that are needed and what they plan to do to support this in jtr etc.
Of course we could just invent a new hash format with 64 bytes added (64 bytes longer than the normal format), but I think it is a good idea to see if keepass2john will be changed in the future to support this and also to generate the new hash format etc.

The algorithm and kernel/code modification should be clear from the examples above. The extraction of the 64 bytes should also be no problem with the code provided above. It's now just a matter of when/if this will be supported also by keepass2john and if so are there several different types of hashes or just one hash format that contains the 64 bytes (or not if wua is not used etc).
I think hashcat could adapt its parser to the output of keepass2john quite easily (if it changes in the future).

This jtr issue might be good to follow too since it might be re-opened in the future (we will see): magnumripper/JohnTheRipper#2555

@kholia

This comment has been minimized.

Show comment
Hide comment
@kholia

kholia Nov 21, 2017

@philsmd I haven't looked closely at this stuff recently. Can we treat this 64 bytes of "CryptUnprotectData" data like a keyfile? If a keyfile is being used, can we simply append this 64 bytes of data to that keyfile data? If these hacks are not possible at all, we can bump the version field to 3, and come up with a new hash format.

I have opened the corresponding JtR issue now. Patches for keepass2john.c are highly welcome. Thanks!

kholia commented Nov 21, 2017

@philsmd I haven't looked closely at this stuff recently. Can we treat this 64 bytes of "CryptUnprotectData" data like a keyfile? If a keyfile is being used, can we simply append this 64 bytes of data to that keyfile data? If these hacks are not possible at all, we can bump the version field to 3, and come up with a new hash format.

I have opened the corresponding JtR issue now. Patches for keepass2john.c are highly welcome. Thanks!

@philsmd

This comment has been minimized.

Show comment
Hide comment
@philsmd

philsmd Nov 21, 2017

Member

yes we could just add the 64 bytes.
Unfortunately there are many variants possible:

  1. pass
  2. pass+keyfile
  3. pass+wua
  4. pass+keyfile+wua

so maybe we should add an additional field at the end (always or just when wua is being used).

In theory we could differentiate if the additional data is for keyfile or for wua just depending on the length (keyfile are always 32 bytes, while wua always uses 64 bytes).
We could also think about always printing all fields or not ... or somehow add an identifier such that it is clear if the data is from the keyfile or from the unprotected data file.

An additional question is, does keepass2john plan to add both support for:

  1. adding the 64 bytes via a command line option (--wua 64-hex-bytes-here)
  2. using something like the source code above to automatically unprotect the data (if the user is using the machine were the protected data was generated).

I think both cases would somehow make sense because without case number 2 the user might not know how to extract the data (if he didn't read the steps/examples above).
An additional problem would be that you only would be able to compile this code (case 2) on a windows machine or with some cross-compiler (mingw on linux) and of course it will only run on windows systems. Therefore case 2 is just for .exe files I think.

Member

philsmd commented Nov 21, 2017

yes we could just add the 64 bytes.
Unfortunately there are many variants possible:

  1. pass
  2. pass+keyfile
  3. pass+wua
  4. pass+keyfile+wua

so maybe we should add an additional field at the end (always or just when wua is being used).

In theory we could differentiate if the additional data is for keyfile or for wua just depending on the length (keyfile are always 32 bytes, while wua always uses 64 bytes).
We could also think about always printing all fields or not ... or somehow add an identifier such that it is clear if the data is from the keyfile or from the unprotected data file.

An additional question is, does keepass2john plan to add both support for:

  1. adding the 64 bytes via a command line option (--wua 64-hex-bytes-here)
  2. using something like the source code above to automatically unprotect the data (if the user is using the machine were the protected data was generated).

I think both cases would somehow make sense because without case number 2 the user might not know how to extract the data (if he didn't read the steps/examples above).
An additional problem would be that you only would be able to compile this code (case 2) on a windows machine or with some cross-compiler (mingw on linux) and of course it will only run on windows systems. Therefore case 2 is just for .exe files I think.

@kholia

This comment has been minimized.

Show comment
Hide comment
@kholia

kholia Nov 21, 2017

Yes, I think support for both cases (manual specification of "wua" bytes, automatic extraction of those bytes) would be needed.

Yes, we already build JtR Jumbo with MinGW for Windows users. So adding this automatic extraction code (within some ifdef) to keepass2john.c should be doable without much problem.

kholia commented Nov 21, 2017

Yes, I think support for both cases (manual specification of "wua" bytes, automatic extraction of those bytes) would be needed.

Yes, we already build JtR Jumbo with MinGW for Windows users. So adding this automatic extraction code (within some ifdef) to keepass2john.c should be doable without much problem.

@philsmd

This comment has been minimized.

Show comment
Hide comment
@philsmd

philsmd Nov 21, 2017

Member

That's great.

I just realized I didn't answer your question about the additional 64 bytes and if they can be seen as a longer keyfile. Yes this is absolutely true! We could think about it like this if we consider the cases above:

  1. pass => 0 bytes
  2. pass+keyfile => 32 bytes
  3. pass+wua => 64 bytes
  4. pass+keyfile+wua => 32+64 = 96 bytes

So in theory for the crackers it doesn't matter if the additional data comes from a keyfile of from wua. The crackers just need to support 0 to 96 bytes (at the time of writing this, maybe this will be increased in the future, but I don't think anytime soon), possible values are 0, 32, 64 or 96 bytes for the additional data.

Member

philsmd commented Nov 21, 2017

That's great.

I just realized I didn't answer your question about the additional 64 bytes and if they can be seen as a longer keyfile. Yes this is absolutely true! We could think about it like this if we consider the cases above:

  1. pass => 0 bytes
  2. pass+keyfile => 32 bytes
  3. pass+wua => 64 bytes
  4. pass+keyfile+wua => 32+64 = 96 bytes

So in theory for the crackers it doesn't matter if the additional data comes from a keyfile of from wua. The crackers just need to support 0 to 96 bytes (at the time of writing this, maybe this will be increased in the future, but I don't think anytime soon), possible values are 0, 32, 64 or 96 bytes for the additional data.

@kholia

This comment has been minimized.

Show comment
Hide comment
@kholia

kholia Nov 21, 2017

Awesome! This hack should really simplify the changes required I think.

kholia commented Nov 21, 2017

Awesome! This hack should really simplify the changes required I think.

@hycday

This comment has been minimized.

Show comment
Hide comment
@hycday

hycday Sep 2, 2018

hi,

awesome topic!

considering:

  • I fall under the "pass+wua" category
  • I know my password
  • I know my Windows User Account login+password
  • I reinstalled my Windows on same computer and used the same user account

can i manage to reopen my database following any abovementioned method ?

or is any of the prerequesites to still be under the initial Windows installation where the database was created ?

hycday commented Sep 2, 2018

hi,

awesome topic!

considering:

  • I fall under the "pass+wua" category
  • I know my password
  • I know my Windows User Account login+password
  • I reinstalled my Windows on same computer and used the same user account

can i manage to reopen my database following any abovementioned method ?

or is any of the prerequesites to still be under the initial Windows installation where the database was created ?

@Fist0urs

This comment has been minimized.

Show comment
Hide comment
@Fist0urs

Fist0urs Sep 3, 2018

Contributor

Hi @hycday,

Windows offers a way to "migrate" between profiles that use DPAPI. Unfortunately at some point you will always have to rely on the masterkeys (usually in C:\Users<username>\AppData\Roaming\Microsoft\Protect<SID>, ) so if you've erased/overwritten it it will be quite complicated not to say impossible...

Anw take a look at this blogpost from @HarmJ0y if you want to have a better understanding.

Contributor

Fist0urs commented Sep 3, 2018

Hi @hycday,

Windows offers a way to "migrate" between profiles that use DPAPI. Unfortunately at some point you will always have to rely on the masterkeys (usually in C:\Users<username>\AppData\Roaming\Microsoft\Protect<SID>, ) so if you've erased/overwritten it it will be quite complicated not to say impossible...

Anw take a look at this blogpost from @HarmJ0y if you want to have a better understanding.

@hycday

This comment has been minimized.

Show comment
Hide comment
@hycday

hycday Sep 3, 2018

@Fist0urs thanks for your answer !
I managed to find an old backup, and find the folder SID with the "Preferred" file and 12 WUA Master Key files.
However, I couldnt retrieve the ProtectedUserKey.bin file (DPAPI blob) located in the C:\Users<username>\AppData\Roaming\KeePass\ directory of the old WUA.
Is that mandatory ?

hycday commented Sep 3, 2018

@Fist0urs thanks for your answer !
I managed to find an old backup, and find the folder SID with the "Preferred" file and 12 WUA Master Key files.
However, I couldnt retrieve the ProtectedUserKey.bin file (DPAPI blob) located in the C:\Users<username>\AppData\Roaming\KeePass\ directory of the old WUA.
Is that mandatory ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment