Skip to content

Commit

Permalink
Added support for alternate Windows 10 format
Browse files Browse the repository at this point in the history
  • Loading branch information
ash47 committed Dec 8, 2018
1 parent bfa1ce0 commit f07b1dd
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 2 deletions.
68 changes: 67 additions & 1 deletion EnterpriseWifiPasswordRecover/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static void Stage1()
{
// The location where the magic is stored in the registry
string keyName = @"Software\Microsoft\Wlansvc\UserData\Profiles\";
string keyName2 = @"Software\Microsoft\Wlansvc\Profiles\";

// Will contain a list of places to search in the registry for profiles
List<RegistryKey> possibleKeys = new List<RegistryKey>();
Expand All @@ -56,6 +57,7 @@ public static void Stage1()

// Try to open the store for wireless keys
possibleKeys.Add(possibleKey.OpenSubKey(keyName));
possibleKeys.Add(possibleKey.OpenSubKey(keyName2));
}
catch
{
Expand All @@ -72,6 +74,7 @@ public static void Stage1()
try
{
possibleKeys.Add(Registry.LocalMachine.OpenSubKey(keyName));
possibleKeys.Add(Registry.LocalMachine.OpenSubKey(keyName2));
}
catch
{
Expand All @@ -82,6 +85,7 @@ public static void Stage1()
try
{
possibleKeys.Add(Registry.CurrentUser.OpenSubKey(keyName));
possibleKeys.Add(Registry.CurrentUser.OpenSubKey(keyName2));
}
catch
{
Expand All @@ -93,6 +97,7 @@ public static void Stage1()
{
// Grab the registry location
RegistryKey key = possibleKeys[i];

if (key == null) continue;
try
{
Expand All @@ -104,6 +109,8 @@ public static void Stage1()
string subKey = subKeys[j];
RegistryKey subKey2 = key.OpenSubKey(subKey);

Console.WriteLine(subKey);

// Is there MSMUserData? This is where the gold is stored
byte[] theData = (byte[])subKey2.GetValue("MSMUserData", null);

Expand Down Expand Up @@ -317,6 +324,7 @@ public static UsernameStore DecryptUsername(byte[] toDecrypt, UsernameStore theS
{
// Used to find the start of the username / password blob
byte[] searchForUsername = { 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 };
byte[] searchForUsername2 = { 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00 };
byte[] nullArray = { 0x00 };

// Ensure we have a store for the username
Expand Down Expand Up @@ -353,7 +361,62 @@ public static UsernameStore DecryptUsername(byte[] toDecrypt, UsernameStore theS
}
else
{
Console.WriteLine("Failed to find username field!");
// Failed to find the blob, maybe it's not encrypted?

usernameFieldStart = SigScan(toDecrypt, searchForUsername2);

Console.WriteLine(usernameFieldStart);

if(usernameFieldStart != -1)
{
// Looks good?

// There will be some null bytes, skip until the end of those
usernameFieldStart += searchForUsername2.Length;
usernameFieldStart = SigScan(toDecrypt, nullArray, usernameFieldStart, true);

// Find where the username field ends
int usernameFieldEnd = SigScan(toDecrypt, nullArray, usernameFieldStart + 1);

byte[] usernameField = Slice(toDecrypt, usernameFieldStart, usernameFieldEnd);
theStore.username = Encoding.ASCII.GetString(usernameField);

// Keep searching, the password is probably here too!
int passowrdFieldStart = SigScan(toDecrypt, nullArray, usernameFieldEnd + 1, true);

if(passowrdFieldStart != -1)
{
int passwordFieldEnd = SigScan(toDecrypt, nullArray, passowrdFieldStart + 1);

if(passwordFieldEnd != -1)
{
byte[] passwordField = Slice(toDecrypt, passowrdFieldStart, passwordFieldEnd);
theStore.password = Encoding.ASCII.GetString(passwordField);

// Maybe there is a domain too?
int domainFieldStart = SigScan(toDecrypt, nullArray, passwordFieldEnd + 1, true);

if (domainFieldStart != -1)
{
int domainFieldEnd = SigScan(toDecrypt, nullArray, domainFieldStart + 1);

if(domainFieldEnd != -1)
{
byte[] domainField = Slice(toDecrypt, domainFieldStart, domainFieldEnd);

if(domainField[0] != 0x01)
{
theStore.domain = Encoding.ASCII.GetString(domainField);
}
}
}
}
}
}
else
{
Console.WriteLine("Failed to find username field!");
}
}

return theStore;
Expand Down Expand Up @@ -401,6 +464,9 @@ public static UsernameStore DecryptPassword(byte[] toDecrypt, UsernameStore theS
}
else
{
// Ok, we failed to find an encrypted blob, maybe it's not encrypted?
//searchForPassword = { 0x03, 0x00, 0x00, 0x00, 0xD0, 0x8C, 0x9D, 0xDF, 0x01 };

Console.WriteLine("Failed to find an encrypted password blob :/");
}

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ This is a tool that recovers WPA2 Enterprise Wifi Credentials from a machine.
- `C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces\{interfaceID}\{profileID}.xml`
- The following registry entry is created to store the enterprise credentials
- `HKEY_CURRENT_USER\Software\Microsoft\Wlansvc\UserData\Profiles\{profileID}\`
- Sometimes it is stored in: `HKEY_LOCAL_MACHINE\Software\Microsoft\Wlansvc\UserData\Profiles\{profileID}\`
- Sometimes it is stored in: `HKEY_LOCAL_MACHINE\Software\Microsoft\Wlansvc\Profiles\{profileID}\`
- The "MSMUserData" binary value contains the encrypted credentials
- In Windows 10, the first layer can be decrypted using the following C# code, run in the context of the system user:
- `ProtectedData.Unprotect(<data>, null, DataProtectionScope.LocalMachine);`
- This will decrypt and the result will be a blog of binary data, looking at the data, it's easy to spot the domain and username in clear text.
- The username comes directly after the following bytes `{ 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }`, and terminates after a null byte.
- The username comes directly after the following bytes `{ 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }`, and terminates after a null byte if it is encrypted.
- If the blob isn't encrypted with the user's credentials, then the username will be after `{ 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00 }` in plain text, then a series of null bytes, followed by a clear text password, then a series of null bytes and possibly a domain, if you reach `0x01` then there is no domain.
- The domain comes after a bunch of the username, there will be a series of null bytes. If you reach a `0xE6` character, then there is no domain present in the credentials.
- The password is then encrypted again using the same C# call, however, it is encrypted using the context of the user who first connected to the wireless network (or the user who first logged in after the connection was made)
- The encrypted password section starts with (and includes) the following bytes `{ 0x01, 0x00, 0x00, 0x00, 0xD0, 0x8C, 0x9D, 0xDF, 0x01 }`
Expand Down

0 comments on commit f07b1dd

Please sign in to comment.