What is the Best way to recover a MyBB pass
#1
The hash I'm dealing with is md5(md5($salt).$pass) and a salt (a random 8 character string) which from my understanding is option 6 on hashcat and option 2811 on Plus and Lite.

On hashcat I try:
hashcat-cli64.exe --hash-mode 6 --attack-mode 3 --output-file C:\recovered.txt --custom-charset1 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 !”#$%&'()*+,-./:;⇔?@[\]^_`{|}~ --threads 4 C:\hashes.txt ?1?1?1?1?1?1?1?1?1?1?1?1?1?1?1

and it seems to hang up when it gets to 5 characters.


on Plus I try:
cudaHashcat-plus64.exe --hash-type 2811 --attack-mode 7 --custom-charset1 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 !”#$%&'()*+,-./:;⇔?@[\]^_`{|}~ C:\hashes.txt ?1?1?1?1?1 C:\wordlist.dic

with a 1GB word list (no duplicates in it) and it says it will take 980 years.. lol and it's not dropping very much per hour (sometimes a few years at best but then goes back up at times too).


I'm running a Quadro 600 and an i7 920 @ 2.67 GHz (12GBs DDR3).

So is there a better way I could go about this?
I know the original phrase is strong and could be between 8 and 18 characters and the hash is 32 characters long.
Thank you for support.

edit-
Oddly enough, this is for a MyBB 1.6.8 password and I see this site uses the same software.. so perhaps admin could give some insight to this? Smile
#2
i'm confused as to why the topic states SSHA but in the body you state md5(md5(salt).pass). in fact i'm not sure the topic is at all relevant to your questions.

you do not need that super-long custom charset string. you can either use ?a, or if you really want to, define a custom charset with ?l?u?d?s

nvidia sucks for password cracking, quadro cards are even worse than gtx. however, the specific attack you're running would take years no matter what gpu you have because the keyspace is way too large. use a smaller dictionary and a smaller mask.

the best way to recover the hash is to formulate a logical attack plan, then execute against it. brute force is not practical, especially if you are sure the plaintext is between 8 and 18 chars in length, but using markov chains will help increase probability if that's the way you want to go. hybrid attacks are practical if you keep them reasonable. dict + rules will likely be your best bet.
#3
thank you for explaining this epixoip. I was under the impression that SHA1 = md5(md5($salt).$pass)? the subject's a typo. Thank you for letting me know that I'm wasting my time using the Quadro.

So something simply like this in addition to using something like passwordpro rules?
hashcat-cli64.exe --hash-mode 6 --attack-mode 1 --output-file C:\recovered.txt --rules-file C:\rules\passwordspro.rule C:\hashes.txt C:\wordlist.dic --pw-min 8 --pw-max 8
and then step up the min max until a solution is found...

For Markov chains I assume (I just downloaded it) I use the statsprocessor
(since this http://hashcat.net/wiki/doku.php?id=markov_attack is tbd)
http://hashcat.net/wiki/doku.php?id=statsprocessor
so with statsproc do I use something like:
--pw-min 8 --pw-max 8 hashcat.hcstat ?l?l?l?l?l?l?l?l to generate a list and then try it
then maybe
--pw-min 8 --pw-max 8 hashcat.hcstat ?u?u?u?u?u?u?u?u
then maybe
--pw-min 8 --pw-max 8 hashcat.hcstat ?a?a?a?a?a?a?a?a
Then rinse and repeat in +1 integers for min max until 18?
Therefore generating 30 different wordlists?
Is that how the statsproc works?
Then use each wordlist one at a time with haschat until solution found?

I'm amazed at how some make it look so easy.. as it seems it takes as much and if not more time than app reverse engineering. So is that what you mean by a plan of attack (if this even qualifies as one lol)? am I way off the beaten path or am I headed in the right direction?
Thank you
#4
sha1 is just that -- sha1. it's a raw cryptographic algorithm, like md5 is.

and actually mybb is md5(md5(salt).md5(pass)), not md5(md5(salt).pass). it is mode 2811 on all versions of hashcat. not sure where you got mode 6 from.

you may not necessarily be wasting your time with your quadro, it's probably still faster than your CPU.

--pw-min, --pw-max, and --rules-file do nothing when using combinator attack (attack mode 1.) --pw-min and --pw-max only work in mask attack mode. --rules only works in straight mode. combinator attack also requires two dictionary files.

not sure it's possible to use statsprocessor with cpu hashcat on Windows since hashcat cannot read from stdin and Windows doesn't support things like named pipes. you really wouldn't want to generate wordlists with statsprocessor, they would be massive. best use your quadro for that. and note that oclHashcat uses markov mode by default for all masks.

we make it look easy because frankly, it is easy. some people just make it way more complicated than it need be. and yes, i think you're way off the beaten path here, but i'll throw you a bone and show you a very simple attack plan to help you get started.

start with dict attack:
Code:
cudaHashcat -m 2811 -o recovered.txt hashes.txt wordlist.dic

then do dict + rules:
Code:
cudaHashcat -m 2811 -o recovered.txt hashes.txt wordlist.dic -r rules/best64.rule
cudaHashcat -m 2811 -o recovered.txt hashes.txt wordlist.dic -r rules/d3ad0ne.rule

then do some hybrid attacks:
Code:
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 6 wordlist.dic ?d?d
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 6 wordlist.dic ?d?d?d
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 6 wordlist.dic ?d?d?d?d

do some mask attacks:
Code:
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 3 -i ?l?l?l?l?l?l?l?l?l?l
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 3 ?l?l?l?l?l?l?d?d
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 3 ?l?l?l?l?d?d?d?d
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 3 -i ?d?d?d?d?d?d?d?d?d?d
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 3 ?u?l?l?l?l?l?l?d
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 3 ?u?l?l?l?l?l?d?d
cudaHashcat -m 2811 -o recovered.txt hashes.txt -a 3 -i ?a?a?a?a?a?a

i think you get the idea.
#5
Nice introduction, we should setup a basic attack command wiki page.
#6
Awesome, so I've been doing what you recommended epixoip:
Code:
cudaHashcat -m 2811 -o recovered.txt hashes.txt wordlist.dic
- this went by quick no results

Code:
cudaHashcat -m 2811 -o recovered.txt hashes.txt wordlist.dic -r rules/best64.rule
- this went by quick no results

Code:
cudaHashcat -m 2811 -o recovered.txt hashes.txt wordlist.dic -r rules/d3ad0ne.rule
- this took well over 48 hours with 6.43 Trillion progress. No results.

I'm continuing down the list.

About MyBB 1.6.8 .. I'm going to paste the password related PHP source code for you:
(skip to bottom for summary)
in functions.php
PHP Code:
Starting at line 1319
// Loop through each of parent forums to ensure we have a password for them too
    
$parents explode(','$forum_cache[$fid]['parentlist']);
    
rsort($parents);
    if(!empty(
$parents))
    {
        foreach(
$parents as $parent_id)
        {
            if(
$parent_id == $fid || $parent_id == $pid)
            {
                continue;
            }
            
            if(
$forum_cache[$parent_id]['password'] != "")
            {
                
check_forum_password($parent_id$fid);
            }
        }
    }
    
    
$password $forum_cache[$fid]['password'];
    if(
$password)
    {
        if(
$mybb->input['pwverify'] && $pid == 0)
        {
            if(
$password == $mybb->input['pwverify'])
            {
                
my_setcookie("forumpass[$fid]"md5($mybb->user['uid'].$mybb->input['pwverify']), nulltrue);
                
$showform false;
            }
            else
            {
                eval(
"\$pwnote = \"".$templates->get("forumdisplay_password_wrongpass")."\";");
                
$showform true;
            }
        }
        else
        {
            if(!
$mybb->cookies['forumpass'][$fid] || ($mybb->cookies['forumpass'][$fid] && md5($mybb->user['uid'].$password) != $mybb->cookies['forumpass'][$fid]))
            {
                
$showform true;
            }
            else
            {
                
$showform false;
            }
        }
    }
    else
    {
        
$showform false;
    }

    if(
$showform)
    {
        if(
$pid)
        {
            
header("Location: ".$mybb->settings['bburl']."/".get_forum_link($fid));
        }
        else
        {
            
$_SERVER['REQUEST_URI'] = htmlspecialchars_uni($_SERVER['REQUEST_URI']);
            eval(
"\$pwform = \"".$templates->get("forumdisplay_password")."\";");
            
output_page($pwform);
        }
        exit;
    }
}

then starting again at line 3071
/**
 * Get a list of the unviewable forums for the current user
 *
 * @param boolean Set to true to only fetch those forums for which users can actually read a thread in.
 * @return string Comma separated values list of the forum IDs which the user cannot view
 */
function get_unviewable_forums($only_readable_threads=false)
{
    global 
$forum_cache$permissioncache$mybb$unviewableforums$unviewable$templates$forumpass;

    
$pid intval($pid);

    if(!
$permissions)
    {
        
$permissions $mybb->usergroup;
    }

    if(!
is_array($forum_cache))
    {
        
cache_forums();
    }

    if(!
is_array($permissioncache))
    {
        
$permissioncache forum_permissions();
    }

    
$password_forums = array();
    foreach(
$forum_cache as $fid => $forum)
    {
        if(
$permissioncache[$forum['fid']])
        {
            
$perms $permissioncache[$forum['fid']];
        }
        else
        {
            
$perms $mybb->usergroup;
        }

        
$pwverified 1;

        if(
$forum['password'] != "")
        {
            if(
$mybb->cookies['forumpass'][$forum['fid']] != md5($mybb->user['uid'].$forum['password']))
            {
                
$pwverified 0;
            }
            
            
$password_forums[$forum['fid']] = $forum['password'];
        }
        else
        {
            
// Check parents for passwords
            
$parents explode(","$forum['parentlist']);
            foreach(
$parents as $parent)
            {
                if(isset(
$password_forums[$parent]) && $mybb->cookies['forumpass'][$parent] != md5($mybb->user['uid'].$password_forums[$parent]))
                {
                    
$pwverified 0;
                }
            }
        }

        if(
$perms['canview'] == || $pwverified == || ($only_readable_threads == true && $perms['canviewthreads'] == 0))
        {
            if(
$unviewableforums)
            {
                
$unviewableforums .= ",";
            }

            
$unviewableforums .= "'".$forum['fid']."'";
        }
    }

    return 
$unviewableforums;


in functions_archive.php
PHP Code:
starting at line 238
    
// Loop through each of parent forums to ensure we have a password for them too
    
$parents explode(','$forum_cache[$fid]['parentlist']);
    
rsort($parents);
    if(!empty(
$parents))
    {
        foreach(
$parents as $parent_id)
        {
            if(
$parent_id == $fid || $parent_id == $pid)
            {
                continue;
            }
            
            if(
$forum_cache[$parent_id]['password'] != "")
            {
                
check_forum_password_archive($parent_id$fid);
            }
        }
    }
    
    
$password $forum_cache[$fid]['password'];
    if(
$password)
    {
        if(!
$mybb->cookies['forumpass'][$fid] || ($mybb->cookies['forumpass'][$fid] && md5($mybb->user['uid'].$password) != $mybb->cookies['forumpass'][$fid]))
        {
            
archive_error_no_permission();
        }
    }
}
?>

in functions_user.php
PHP Code:
starting at line 175
/**
 * Salts a password based on a supplied salt.
 *
 * @param string The md5()'ed password.
 * @param string The salt.
 * @return string The password hash.
 */
function salt_password($password$salt)
{
    return 
md5(md5($salt).$password);
}

/**
 * Generates a random salt
 *
 * @return string The salt.
 */
function generate_salt()
{
    return 
random_str(8);
}

/**
 * Generates a 50 character random login key.
 *
 * @return string The login key.
 */
function generate_loginkey()
{
    return 
random_str(50);
}

/**
 * Updates a user's salt in the database (does not update a password).
 *
 * @param int The uid of the user to update.
 * @return string The new salt.
 */
function update_salt($uid)
{
    global 
$db;
    
    
$salt generate_salt();
    
$sql_array = array(
        
"salt" => $salt
    
);
    
$db->update_query("users"$sql_array"uid='{$uid}'"1);
    
    return 
$salt;
}

/**
 * Generates a new login key for a user.
 *
 * @param int The uid of the user to update.
 * @return string The new login key.
 */
function update_loginkey($uid)
{
    global 
$db;
    
    
$loginkey generate_loginkey();
    
$sql_array = array(
        
"loginkey" => $loginkey
    
);
    
$db->update_query("users"$sql_array"uid='{$uid}'"1);
    
    return 
$loginkey;



the above repeats for same files in inc sub directory.


SO to highlight what's important:
PHP Code:
/**
 * Updates a user's password.
 *
 * @param int The user's id.
 * @param string The md5()'ed password.
 * @param string (Optional) The salt of the user.
 * @return array The new password.
 */
function update_password($uid$password$salt="")
{
    global 
$db$plugins;

    
$newpassword = array();

    
// If no salt was specified, check in database first, if still doesn't exist, create one
    
if(!$salt)
    {
        
$query $db->simple_select("users""salt""uid='$uid'", array('limit' => 1));
        
$user $db->fetch_array($query);
        if(
$user['salt'])
        {
            
$salt $user['salt'];
        }
        else
        {
            
$salt generate_salt();
        }
        
$newpassword['salt'] = $salt;
    }

    
// Create new password based on salt
    
$saltedpw salt_password($password$salt);

    
// Generate new login key
    
$loginkey generate_loginkey();

    
// Update password and login key in database
    
$newpassword['password'] = $saltedpw;
    
$newpassword['loginkey'] = $loginkey;
    
$db->update_query("users"$newpassword"uid='$uid'"1);

    
$plugins->run_hooks("password_changed");

    return 
$newpassword;
}

/**
 * Salts a password based on a supplied salt.
 *
 * @param string The md5()'ed password.
 * @param string The salt.
 * @return string The password hash.
 */
function salt_password($password$salt)
{
    return 
md5(md5($salt).$password);
}

/**
 * Generates a random salt
 *
 * @return string The salt.
 */
function generate_salt()
{
    return 
random_str(8);
}

/**
 * Generates a 50 character random login key.
 *
 * @return string The login key.
 */
function generate_loginkey()
{
    return 
random_str(50);
}

/**
 * Updates a user's salt in the database (does not update a password).
 *
 * @param int The uid of the user to update.
 * @return string The new salt.
 */
function update_salt($uid)
{
    global 
$db;
    
    
$salt generate_salt();
    
$sql_array = array(
        
"salt" => $salt
    
);
    
$db->update_query("users"$sql_array"uid='{$uid}'"1);
    
    return 
$salt;


Summary
So this is what happens:
md5(md5($salt).$password)

As far as I can tell things have definitely changed from the MyBB 1.2 days... I think the above proves that. That's why I was choosing option 6.
But am I wasting my time with 2811 on plus then? If 2811 = md5(md5(salt).md5(pass)) then maybe I am?
#7
Quote:So this is what happens:
md5(md5($salt).$password)

The comment above says $password is the md5'ed password, not the plaintext-password. This makes much more sense, since it means its still 2811 and nothing changed silently.
#8
I've no success so far, it's been a while too.
Is there a way to use a login key to help unhash this?
#9
there is no word like "unhash" and thus no such process.