• Rainbow Table 分析(转)


    来源:NCPH

    ===================
    Rainbow Table
    ===================

    Rainbow Table 是由Philippe Oechsilin在Making a Faster Cryptanalytic Time-Memory Trade-Off中
    提出的一种改进型的PreComputering Table. 主要目的是为了提高成功率, 并且减少存储空间.

    rainbowcrack-1.2-src是Zhu Shuanglei对此的一个实现, 针对他的实现写下了这些说明Rainbow Table的笔记.

    1. Rainbow Table的组织和生成(rtgen说明)

    Rainbow Table 是由很多16bytes的RainbowChain组成的.

    RainbowChain的结构如下:
    struct RainbowChain
    {
    +000  uint64 nIndexS;
    +008  uint64 nIndexE;
    };

    rainbowTable的数量是由字符空间决定的, 事先计算好再由argv[7]传入.
    int nRainbowChainCount   = atoi(argv[7]);

    nIndexS由函数cwc.GenerateRandomIndex()随机生成( 这样生成是有目的的, 将在后面解释 )
    与此同时这个index也被放入了CChainWalkContext.m_nIndex中, m_nindex启到中间记录作用
    nIndexE经过nRainbowChainLen次计算而来的.

    整个过程如下:

    1) 将随机生成m_nindex(开始即nIndexS)通过cwc.IndexToPlain() 见[1]得到由字符空间定义表示字符
    整个转换过程和二进制转16进制差不多, 只不过变成了明文字符长度进制(m_nPlainCharsetLen)

    2) 对步骤1得到字符进行预先设定的HASH函数 - cwc.PlainToHash()

    3) 对生成的HASH进行Reduce, 在此Reduce函数为cwc.HashToIndex(nPos) 见[2]
    最后得到的m_nindex还是必须规定在字符空间的范围内( .'. % m_nPlainSpaceTotal).

    将上面三步重复nRainBowChainLen次后, 将m_nindex放入到nIndexE中. nRainBowChainLen就是Chain长度.
    并且所有的RainbowChain均是重复上述步骤.

    也可以表示成如下过程:

    PLAIN      HASH       Reduce
    K1 -------> C -------> H  --------> K2 ----->....

    index       plain       hash      reduced hash
    (new index)  <----- 减少存储空间的关键

    由于在整个过程中会产生新的index, 虽然并不记录, 但还是可以推算得到的, 因为所有函数都是单向的.
    所以严格按照排列生成 nIndexS就变得没有必要了. 这些新的index很可能就已经包括在内了, 当然这些
    新的index会有一定reduce范围, 这是由m_nReduceOffset造成的. 成功率的概念也是由于这个原因, 很可能
    整个Random的过程并没有覆盖到每一个排列, 增加多张同一个ReduceOffset表的目的也是为提高覆盖率, 但是
    还是会有miss率, 哪怕是0.01%甚至更小.

    根据Philippe Oechsilin的Paper中将 K1 -> K2 的过程定义为fn, 而fn不同原因主要在于Reduce函数不同
    (造成这中不同的原因在于nPos的增加 见[2])

    2. Rainbow Table的排序(rtsort的说明)

    rtsort 使用了快速排序 和 外部排序
    排序的对象就是 RainbowChain.nIndexE, 这一点需要十分注意.

    外部排序只有在内存容量很低的时候, 才会采用.

    外部排序的过程:

    1) 根据内存的大小从rainbow table文件中读取相应的大小的内容.

    2) 使用快速排序将相应的chain进行排序, 存放到temp文件中.

    3) 相应的信息存放到CSortedSegment结构的链表中

    4) 重复上述1-3步, 直到读取完rainbow table的所有内容.

    5) 将链表中的所有的项进行归并.(并非使用二路归并, 而是一起归并)
    归并从所有的经过排序的项中取最小的一个, 排序的对象是 RainbowChain.nIndexE , 后8个字节

    +++---
    此时还作了一些优化:

    GetNextChain会预先在文件中读取m_nFileChainCount放到RainbowChain.m_chain[]的数组中.
    但大小超过1024, 也只读取1024.
    if (m_nChainCount == m_nNextChainIndex) 是一个判断 可能进行下一次读取的条件
    返回m_nNextChainIndex指向的m_chain[m_nNextChainIndex]的地址(RainbowChain *)

    RemoveTopChain 主要作用就是 更新m_nNextChainIndex, 但全部读完后, 返回true, 让MergeSortedSegment删去该链项(list.erase)
    ---+++

    6) 将归并后的项放入原来的文件中.

    PrepareSortedSegment函数        过程  1 - 4
    MergeSortedSegment  函数        过程  5 , 6

    3. Rainbow Table的使用(rcrack的说明)

    更应该说rcrack的过程就是查表的过程.

    1)  针对每个rt文件进行搜索
    for (i = 0; i < vPathName.size() && hs.AnyhashLeft(); i++)
    {
    SearchRainbowTable(vPathName[i], hs);
    printf("\n");
    }

    2)  在SearchRainbowTable中根据内存大小, 分成一块块的进行Search.
    调用SearchTableChunk函数.

    3)  以下为rcrack的关键函数

    SearchTableChunk(pChain, nRainbowChainLen, nRainbowChainCountRead, hs);

    pChain - 存放在内存中Rainbow Table的Chain表, 可能Rainbow Table中的项要比内存大得多,
    所以用CMemoryPool实现根据内存大小分配空间. pChain使用CMemoryPool.Alloc分配的.

    nRainbowChainLen - RainbowChain的长度

    nRainbowChainCountRead - 读入pChain中RainbowChain的数量, 读入文件大小/16 (当然必须是16的倍数)

    hs    -  存放将要检验的HASH, 并且还要存放Crack的结果, 是否发现原始HASH, 是否发现, 明文等等.
    //++
    vector<string> m_vHash;
    vector<bool>   m_vFound;
    vector<string> m_vPlain;
    vector<string> m_vBinary;
    //--

    整个查表过程如下
    a. 首先作一些准备工作
    HASH的设置,转换之类的工作
    RequestWalk的目的是为某个需要破解的HASH, 生成一个存放用于匹配pChain[i].nIndexE的数组.
    RequestWalk会在第一次时创建, 会根据Chain的长度和相应Pos的位置计算好所要匹配的项. (见[4-1])
    只需按Pos的位置定义, 所以只需再开始时计算一次, 以后该HASH的计算均可使用.

    b. 计算过程和比对过程
    第一次将HASH使用nPos位置的Reduce函数(Rn-1 n为ChainLen), 与pChain[i].nIndexE中的所有项相比较.
    若找到了相匹配的值, 则使用CheckAlarm函数来进行检验. CheckAlarm根据猜测的所在位置, 从nIndexS
    开始推, 重复f函数的步骤, 到达最后nPos位置的时候, 不会再使用f函数中Reduce函数, 而只推到HASH值.(见[5])
    能够推到的那个HASH的那个index就表示为数字的明文.
    注意当然也可能有匹配值有多个的情况, 因为Reduce函数的收敛性的问题,
    原始的HASH和reduce函数的解空间只有缩减的份, 因为Reduce函数只取HASH开头的8bytes作运算.
    所以就需要在一个匹配域中进行查找, 如果CheckAlarm函数验证不通过的, 则说明这个nPos不是所要的位置.
    这样的一种情况就被称为False Alarm.

    如果第一次匹配不成功, 则认为这个HASH是前一个index经过HASH函数推出来的.
    Rn-2, 得到一个新的index, 然后一步一步的推到最后, 即Chain链的结束(当然这里只有一次f n-1)
    和上面比较过程相同, 相同的话就可以确定猜测的位置.不同的话, 继续相同的步骤, 只是将Pos的位置向前移.
    直到Pos为0 为止.

    最后将结果放到HASHSET中(hs), 不管是找到了明文, 还是没有发现.
    将明文的信息存放到hs中的工作是由CheckAlarm完成的.(见[5])

    4. 遗留问题

    参数的设定和优化还是有很多不理解的地方. 有一篇关于此的论文无法找到.
    chainlen的确定, 还有一些不理解.
    仅大致知道受M = m × l × m0 和 T = t × l × t0 限制(即根据内存大小, 得出最佳计算时间以及成功率)
    还需要仔细仔细地研究一下, 未完待续...

    ==============
    Appendix
    ==============

    函数注释:

    [1]

    
    void CChainWalkContext::IndexToPlain()
    {
    int i;
    for (i = m_nPlainLenMax - 1; i >= m_nPlainLenMin - 1; i--)
    {
    if (m_nIndex >= m_nPlainSpaceUpToX[i])
    {
    m_nPlainLen = i + 1;
    break;
    }
    }
    //  根据 m_nIndex的大小来判断m_nPlainLen的大小
    //  m_nIndex 就是开始随机生成的, 和之后中间步骤
    //  m_nPlainSpaceUpToX 用来计算Pxx的
    //  当i 时 P 应该有的大小.
    //  P的概率统计的东东.
    
    uint64 nIndexOfX = m_nIndex - m_nPlainSpaceUpToX[m_nPlainLen - 1]
    
    //此段密码长度应该有的偏移大小.
    
    /*
    // Slow version
    for (i = m_nPlainLen - 1; i >= 0; i--)
    {
    m_Plain[i] = m_PlainCharset[nIndexOfX % m_nPlainCharsetLen];
    nIndexOfX /= m_nPlainCharsetLen;
    }
    */
    
    //  事实上完全可以用上面的那个慢速版本来完成
    //  为了避免64位的除法运行
    //  当数据还是32位时, 就截成32位来算
    
    // Fast version
    for (i = m_nPlainLen - 1; i >= 0; i--)
    {
    #ifdef _WIN32
    if (nIndexOfX < 0x100000000I64)
    break;
    #else
    if (nIndexOfX < 0x100000000llu)
    break;
    #endif
    // m_Plain 的方式和16 进制差不多, 不过是明文字符长度进制
    // 最先算出来的是最后一位.
    m_Plain[i] = m_PlainCharset[nIndexOfX % m_nPlainCharsetLen]; //根据明文字符的内容, 来取关于的大小
    nIndexOfX /= m_nPlainCharsetLen;
    }
    
    // 算完了 64位, 为什么还要计算32位呢?(见上)
    unsigned int nIndexOfX32 = (unsigned int)nIndexOfX;
    for (; i >= 0; i--)
    {
    //m_Plain[i] = m_PlainCharset[nIndexOfX32 % m_nPlainCharsetLen];
    //nIndexOfX32 /= m_nPlainCharsetLen;
    
    unsigned int nPlainCharsetLen = m_nPlainCharsetLen;
    unsigned int nTemp;
    #ifdef _WIN32
    __asm
    {
    mov eax, nIndexOfX32
    xor edx, edx
    div nPlainCharsetLen
    mov nIndexOfX32, eax
    mov nTemp, edx
    }
    #else
    __asm__ __volatile__ ( "mov %2, %%eax;"
    "xor %%edx, %%edx;"
    "divl %3;"
    "mov %%eax, %0;"
    "mov %%edx, %1;"
    : "=m"(nIndexOfX32), "=m"(nTemp)
    : "m"(nIndexOfX32), "m"(nPlainCharsetLen)
    : "%eax", "%edx"
    );
    #endif
    m_Plain[i] = m_PlainCharset[nTemp];
    }
    }
    

    [2]

    
    void CChainWalkContext::HashToIndex(int nPos)
    {
    m_nIndex = (*(uint64*)m_Hash + m_nReduceOffset + nPos) % m_nPlainSpaceTotal;
    // nPos 的目的就是要有所变化,每次有加1
    // 这就是每个Reduce 函数不同的原因了
    // m_nReduceOffset与RaombowTableIndex 相关 见[3]
    // m_nReduceOffset = 65536 * nRainbowTableIndex;
    // m_Hash是取HASH值的前8  个字节, 因为HASH可能会超过8 bytes
    }
    

    [3]

    
    rtgen lm alpha 1 7 3 2100 8000000 all
    
    bool CChainWalkContext::SetRainbowTableIndex(int nRainbowTableIndex)  <--------- argv[5] 即 3
    {
    if (nRainbowTableIndex < 0)
    return false;
    m_nRainbowTableIndex = nRainbowTableIndex;  // 将所要计算的表分成几张表, 而最后all(argv[8]仅仅是生成表的名字罢了)
    // 的重复则是为了增加成功率.
    m_nReduceOffset = 65536 * nRainbowTableIndex;
    
    return true;
    }
    

    [4]

    
    void CCrackEngine::SearchTableChunk(RainbowChain* pChain, int nRainbowChainLen, int nRainbowChainCount, CHashSet& hs)
    {
    vector<string> vHash;
    hs.GetLeftHashWithLen(vHash, CChainWalkContext::GetHashLen());
    printf("searching for %d hash%s...\n", vHash.size(),vHash.size() > 1 ? "es" : "");
    
    int nChainWalkStep = 0;
    int nFalseAlarm = 0;
    int nChainWalkStepDueToFalseAlarm = 0;
    
    int nHashIndex;
    for (nHashIndex = 0; nHashIndex < vHash.size(); nHashIndex++)// 针对每个HASH 进行验证
    {
    unsigned char TargetHash[MAX_HASH_LEN];
    int nHashLen;
    ParseHash(vHash[nHashIndex], TargetHash, nHashLen);// string -> binary
    if (nHashLen != CChainWalkContext::GetHashLen())
    printf("debug: nHashLen mismatch\n");
    
    // Rqeuest ChainWalk
    bool fNewlyGenerated;
    uint64* pStartPosIndexE = m_cws.RequestWalk(TargetHash,    // 一些结构的准备
    nHashLen,
    
    CChainWalkContext::GetHashRoutineName(),
    CChainWalkContext::GetPlainCharsetName(),
    
    CChainWalkContext::GetPlainLenMin(),
    CChainWalkContext::GetPlainLenMax(),
    
    CChainWalkContext::GetRainbowTableIndex(),
    nRainbowChainLen,
    fNewlyGenerated);
    //printf("debug: using %s walk for %s\n", fNewlyGenerated ? "newly generated" : "existing",
    //          vHash[nHashIndex].c_str());
    
    // Walk
    int nPos;
    for (nPos = nRainbowChainLen - 2; nPos >= 0; nPos--)
    {
    

    [4-1]

    
    if (fNewlyGenerated) // 是否是新建的.   RequestWalk中返回相应的信息
    {
    CChainWalkContext cwc;
    cwc.SetHash(TargetHash);
    cwc.HashToIndex(nPos);    // 这个就是R n-1 , 第二次 R n-2
    int i;
    for (i = nPos + 1; i <= nRainbowChainLen - 2; i++)
    {
    cwc.IndexToPlain();   //  三步为
    cwc.PlainToHash();    //  f n-1.
    cwc.HashToIndex(i);   //
    }
    
    pStartPosIndexE[nPos] = cwc.GetIndex();  // 得到的值将和pChain[i].nIndexE的所有项进行比较
    nChainWalkStep += nRainbowChainLen - 2 - nPos;  // 第几步了
    }
    uint64 nIndexEOfCurPos = pStartPosIndexE[nPos];
    
    // Search matching nIndexE
    int nMatchingIndexE = BinarySearch(pChain, nRainbowChainCount, nIndexEOfCurPos); // 二分查找
    if (nMatchingIndexE != -1)  //找到了
    {
    int nMatchingIndexEFrom, nMatchingIndexETo;
    GetChainIndexRangeWithSameEndpoint(pChain, nRainbowChainCount,
    nMatchingIndexE,
    nMatchingIndexEFrom,
    
    nMatchingIndexETo);
    
    // 找到相同的区域, 因为完全有可能相同.
    // 因为函数的收敛性的问题, 原始的HASH和reduce函数的解空间只有缩减的份
    int i;
    for (i = nMatchingIndexEFrom; i <= nMatchingIndexETo; i++)
    {
    // 原来相关的明文存放的是在CheckAlarm 函数中操作的
    // 找到一个确实的, 就放入然后退出, 该HASH 的encryptanalysis
    if (CheckAlarm(pChain + i, nPos, TargetHash, hs))  // 再进行判断一次. 再正向过程一次.
    {
    //printf("debug: discarding walk for %s\n", vHash[nHashIndex].c_str());
    m_cws.DiscardWalk(pStartPosIndexE);
    goto NEXT_HASH;
    }
    else // 如果不是则说明是一次误报, false alarm.
    {
    nChainWalkStepDueToFalseAlarm += nPos + 1;
    nFalseAlarm++;
    }
    }
    }
    }
    NEXT_HASH:;
    }
    
    //printf("debug: chain walk step: %d\n", nChainWalkStep);
    //printf("debug: false alarm: %d\n", nFalseAlarm);
    //printf("debug: chain walk step due to false alarm: %d\n", nChainWalkStepDueToFalseAlarm);
    
    m_nTotalChainWalkStep += nChainWalkStep;
    m_nTotalFalseAlarm += nFalseAlarm;
    m_nTotalChainWalkStepDueToFalseAlarm += nChainWalkStepDueToFalseAlarm;
    }
    

    [5]

    
    bool CCrackEngine::CheckAlarm(RainbowChain* pChain, int nGuessedPos, unsigned char* pHash, CHashSet& hs)
    {
    CChainWalkContext cwc;
    cwc.SetIndex(pChain->nIndexS);
    int nPos;
    for (nPos = 0; nPos < nGuessedPos; nPos++) //根据猜测的位置从头nIndexS推到相应的位置
    {
    cwc.IndexToPlain();
    cwc.PlainToHash();
    cwc.HashToIndex(nPos);
    }
    cwc.IndexToPlain();     +-
    cwc.PlainToHash();       \ 只作了一个HASH, 并没有什么Reduce函数(cwc.HashToIndex(nPos) ), 就是为了验证
    
    if (cwc.CheckHash(pHash))     // 验证函数, 比较pHash和生成的函数
    {
    printf("plaintext of %s is %s\n", cwc.GetHash().c_str(), cwc.GetPlain().c_str());
    hs.SetPlain(cwc.GetHash(), cwc.GetPlain(), cwc.GetBinary());  // 结果的放入
    return true;
    }
    
    return false;
    

    [6]

    
    RequestWalk的目的是为某个需要破解的HASH, 生成一个存放用于匹配pChain[i].nIndexE的数组.
    uint64* CChainWalkSet::RequestWalk(unsigned char* pHash, int nHashLen,
    string sHashRoutineName,
    string sPlainCharsetName, int nPlainLenMin,
    int nPlainLenMax,
    int nRainbowTableIndex,
    int nRainbowChainLen,
    bool& fNewlyGenerated)
    {
    if (   m_sHashRoutineName   != sHashRoutineName        // 如果相应的参数有所变化, 则所有的东东全部重新设置.
    || m_sPlainCharsetName  != sPlainCharsetName
    || m_nPlainLenMin       != nPlainLenMin
    || m_nPlainLenMax       != nPlainLenMax
    || m_nRainbowTableIndex != nRainbowTableIndex
    || m_nRainbowChainLen   != nRainbowChainLen)
    {
    DiscardAll();                                  //  <-----------  Here
    
    m_sHashRoutineName   = sHashRoutineName;
    m_sPlainCharsetName  = sPlainCharsetName;
    m_nPlainLenMin       = nPlainLenMin;
    m_nPlainLenMax       = nPlainLenMax;
    m_nRainbowTableIndex = nRainbowTableIndex;
    m_nRainbowChainLen   = nRainbowChainLen;
    
    ChainWalk cw;
    memcpy(cw.Hash, pHash, nHashLen);
    cw.pIndexE = new uint64[nRainbowChainLen - 1];
    m_lChainWalk.push_back(cw);
    
    fNewlyGenerated = true;
    return cw.pIndexE;
    }
    
    list<ChainWalk>::iterator it;
    for (it = m_lChainWalk.begin(); it != m_lChainWalk.end(); it++)
    {
    if (memcmp(it->Hash, pHash, nHashLen) == 0) // 判断这个HASH 是否是新,
    {
    fNewlyGenerated = false;
    return it->pIndexE;                 // 如是, 则返回该 8字节数组的指针
    }
    }
    
    ChainWalk cw;                                     // ChainWalk 有两个结构 一个放hash值, 另一个放后面8 字节数组的指针
    memcpy(cw.Hash, pHash, nHashLen);
    cw.pIndexE = new uint64[nRainbowChainLen - 1];    // 一个存放用于匹配pChain[i].mIndexE的数组.
    m_lChainWalk.push_back(cw);                       // 如没有整个HASH, 新建一个, 加入到ChainWalk结构的List
    
    fNewlyGenerated = true;
    return cw.pIndexE;                           //返回的是指向一个放8字节的数组指针
    }
    
  • 相关阅读:
    mongodb.conf配置文件
    mongodb创建普通角色和普通用户
    add unique index 注意事项
    error: src refspec master does not match any
    innodb_data_file_path配置变更引发mysql重启失败
    time_zone参数配置
    主键有无检测
    gdb在线修改mysql变量
    mybatis连接数据库
    mongo登录
  • 原文地址:https://www.cnblogs.com/rainbowzc/p/1676720.html
Copyright © 2020-2023  润新知