3

我正在编写一个程序来帮助我为我正在撰写的关于密码安全性的研究报告收集统计数据。当我尝试暴力破解 MD5 散列密码以显着提高性能时,我决定让应用程序在多个线程上运行。应用程序在单个线程上运行良好,但在 2 个线程运行时,在 TryPass 函数中的“使用 (MD5 md5Hash = MD5.Create())”处抛出 StackOverFlowException。

    // Microsoft's GetMd5Hash function.
    static string GetMd5Hash(MD5 md5Hash, string input)
    {
        // Convert the input string to a byte array and compute the hash. 
        byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));

        // Create a new Stringbuilder to collect the bytes 
        // and create a string.
        StringBuilder sBuilder = new StringBuilder();

        // Loop through each byte of the hashed data  
        // and format each one as a hexadecimal string. 
        for (int i = 0; i < data.Length; i++)
        {
            sBuilder.Append(data[i].ToString("x2"));
        }

        // Return the hexadecimal string. 
        return sBuilder.ToString();
    }

    static bool TryPass(string attempt, string password)
    {
        using (MD5 md5Hash = MD5.Create())
        {
            if (GetMd5Hash(md5Hash, attempt) == password)
                return true;
            else
                return false;
        }
    }



    static bool BruteForce(BruteOptions bruteOptions)
    {
        if (bruteOptions.prefix.Length == 1 && TryPass(bruteOptions.prefix, bruteOptions.password)) // If it's the first in a series, try it.
            return true;

        for (int i = 0; i < bruteOptions.chars.Length; i++)
        {
            if (TryPass(bruteOptions.prefix + bruteOptions.chars[i], bruteOptions.password))
            {
                Console.WriteLine("The password is: " + bruteOptions.prefix + bruteOptions.chars[i]);
                return true;
            }

            if (bruteOptions.prefix.Length + 1 < bruteOptions.maxLength)
                if (BruteForce(bruteOptions))
                    return true;

            //Console.WriteLine(prefix + chars[i]);
        }

        return false;
    }



    public struct BruteOptions
    {
        public string password, prefix;
        public char[] chars;
        public int maxLength;
    }

    static void OptionBruteForce()
    {
        Console.WriteLine("-----------------------");
        Console.WriteLine("----- Brute-Force -----");
        Console.WriteLine("-----------------------");

        BruteOptions bruteOptions = new BruteOptions();
        bruteOptions.password = ReadString("Enter the MD5 password hash to brute-force: ");
        bruteOptions.chars = ReadString("Enter the characters to use: ").ToCharArray();
        bruteOptions.maxLength = ReadIntegerRange("Max length of password: ", 1, 16);
        bruteOptions.prefix = "";

        Stopwatch myStopWatch = Stopwatch.StartNew();

        int NUM_THREADS = bruteOptions.chars.Length;
        Thread[] workers = new Thread[NUM_THREADS]; // Run a thread for each char.
        var countdownEvent = new CountdownEvent(NUM_THREADS);
        bool result = false;

        // Start workers.
        for (int i = 0; i < NUM_THREADS; i++)
        {
            int index = i;
            BruteOptions newBruteOptions = bruteOptions;
            newBruteOptions.prefix = bruteOptions.chars[index].ToString();

            workers[index] = new Thread(delegate()
            {
                // Also check single char.
                if (BruteForce(bruteOptions))
                {
                    result = true;

                    // End all other threads.
                    for (int ii = 0; ii < NUM_THREADS; ii++)
                    {
                        if (workers[ii].ThreadState == System.Threading.ThreadState.Running && index != ii) // Ensures we don't prematurely abort this thread.
                        {
                            workers[ii].Abort();
                            countdownEvent.Signal(); // Signal so we can zero it and continue on the UI thread.
                        }
                    }
                }

                // Signal the CountdownEvent.
                countdownEvent.Signal();
            });

            workers[index].Start();
        }

        // Wait for workers.
        countdownEvent.Wait();

        if (!result)
            Console.WriteLine("No Match.");

        Console.WriteLine("Took " + myStopWatch.ElapsedMilliseconds + " Milliseconds");
    }

这就是所有相关的代码。任何有关为什么会发生这种情况的见解将不胜感激!我完全被难住了。我试图在初始化每个线程时指定更大的堆栈大小,但无济于事。

提前致谢!

4

3 回答 3

2

static bool BruteForce(BruteOptions bruteOptions)是无限递归的:如果长度允许,它会调用自己,使用相同的参数:

if (bruteOptions.prefix.Length + 1 < bruteOptions.maxLength)
    if (BruteForce(bruteOptions))

bruteOptions保持与函数入口处相同。

作为解决方案,您可以使用以下代码:

if (bruteOptions.prefix.Length + 1 < bruteOptions.maxLength)
{
    BruteOptions newOptions = bruteOptions;
    newOptions.prefix += bruteOptions.chars[i];
    if (BruteForce(newOptions))
        return true;
}

另外,您传递bruteOptions,而不是传递newBruteOptions给您在 main 函数中使用的委托。因此,实际上没有使用您的多线程。您的所有线程都测试相同的密码。将其更改为newBruteOptions.

此外,不要假设任何关于线程执行顺序的事情。您假设在找到正确密码的那一刻所有工作人员都已被填满,这可能是错误的。然后你会NullReferenceException在这一行得到一个:

if (workers[ii].ThreadState == System.Threading.ThreadState.Running && index != ii) // Ensures we don't prematurely abort this thread.
于 2013-06-13T04:47:54.910 回答
0

我猜你的单线程代码看起来有点不同。

我会失去递归。

于 2013-06-13T04:28:47.333 回答
0

只是为了好玩,这是一个没有递归的替代实现,并且更好地利用了作为 .Net 一部分的语言结构和框架

class Program
{
    private static string StringFromIndexPermutation(char[] characters, int[] indexes)
    {
        var buffer = new char[indexes.Length];
        for (var i = 0; i < buffer.Length; ++i)
        {
            buffer[i] = characters[indexes[i]];
        }

        return new string(buffer);
    }

    /// <summary>
    /// Increments a set of "digits" in a base "numberBase" number with the MSD at position 0
    /// </summary>
    /// <param name="buffer">The buffer of integers representing the numeric string</param>
    /// <param name="numberBase">The base to treat the digits of the number as</param>
    /// <returns>false if the number in the buffer has just overflowed, true otherwise</returns>
    private static bool Increment(int[] buffer, int numberBase)
    {
        for (var i = buffer.Length - 1; i >= 0; --i)
        {
            if ((buffer[i] + 1) < numberBase)
            {
                ++buffer[i];
                return true;
            }

            buffer[i] = 0;
        }

        return false;
    }

    /// <summary>
    /// Calculate all the permutations of some set of characters in a string from length 1 to maxLength
    /// </summary>
    /// <param name="characters">The characters to permute</param>
    /// <param name="maxLength">The maximum length of the permuted string</param>
    /// <returns>The set of all permutations</returns>
    public static IEnumerable<string> Permute(char[] characters, int maxLength)
    {
        for (var i = 0; i < maxLength; ++i)
        {
            var permutation = new int[i + 1];
            yield return StringFromIndexPermutation(characters, permutation);

            while (Increment(permutation, characters.Length))
            {
                yield return StringFromIndexPermutation(characters, permutation);
            }
        }
    }

    static string ReadString(string message)
    {
        Console.Write(message);
        return Console.ReadLine();
    }

    private static int ReadIntegerRange(string message, int min, int max)
    {
        Console.Write(message + "({0} - {1})", min, max);

        while(true)
        {
            var test = Console.ReadLine();
            int value;

            if (int.TryParse(test, out value))
            {
                return value;
            }
        }

        return -1;
    }

    static void OptionBruteForce()
    {
        Console.WriteLine("-----------------------");
        Console.WriteLine("----- Brute-Force -----");
        Console.WriteLine("-----------------------");

        var password = ReadString("Enter the MD5 password hash to brute-force: ");
        var chars = ReadString("Enter the characters to use: ").Distinct().ToArray();
        var maxLength = ReadIntegerRange("Max length of password: ", 1, 16);

        var myStopWatch = Stopwatch.StartNew();

        var result = false;
        string match = null;
        var cancellationTokenSource = new CancellationTokenSource();
        var passwordBytes = Encoding.Default.GetBytes(password);
        var originalMd5 = MD5.Create();
        var passwordHash = originalMd5.ComputeHash(passwordBytes);
        var hashAlgGetter = new ConcurrentDictionary<Thread, MD5>();

        try
        {
            Parallel.ForEach(Permute(chars, maxLength), new ParallelOptions
            {
                CancellationToken = cancellationTokenSource.Token
            }, test =>
            {
                var md5 = hashAlgGetter.GetOrAdd(Thread.CurrentThread, t => MD5.Create());
                var data = Encoding.Default.GetBytes(test);
                var hash = md5.ComputeHash(data);

                if (hash.SequenceEqual(passwordHash))
                {
                    myStopWatch.Stop();
                    match = test;
                    result = true;
                    cancellationTokenSource.Cancel();
                }
            });
        }
        catch (OperationCanceledException)
        {
        }

        if (!result)
        {
            Console.WriteLine("No Match.");
        }
        else
        {
            Console.WriteLine("Password is: {0}", match);
        }

        Console.WriteLine("Took " + myStopWatch.ElapsedMilliseconds + " Milliseconds");
    }

    static void Main()
    {
        OptionBruteForce();
        Console.ReadLine();
    }
}
于 2013-06-13T05:57:05.720 回答