用户
搜索
  • TA的每日心情
    开心
    2018-4-29 15:23
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]初来乍到

    i春秋-脚本小子

    Rank: 2

    3

    主题

    6

    帖子

    81

    魔法币
    收听
    0
    粉丝
    0
    注册时间
    2018-4-29
    发表于 2020-1-1 11:31:51 12162
    本帖最后由 ERFZE 于 2020-1-1 11:35 编辑

    0x00 前言

    本篇是CDecryptPwd系列的第一篇,笔者将介绍Navicat加密过程、其使用的加密算法以及如何使用C语言来实现其加密过程,文章最后是笔者自己编写的工具(解密Navicat保存在注册表中的数据库密码)。

    0x01 环境

    • 平台:Windows 10(64 bit)

    • 编译环境:Visual Studio Community 2019

    • Navicat Premium 版本 12.1(64 bit)

    0x02 Blowfish

    河豚加密部分见Blowfish(河豚)Cipher

    0x03 CBC模式

    如果对于分组密码模式已经有所了解的读者可直接跳过此节

    Blowfish Cipher与DES、AES一样,都是分组密码,Blowfish Cipher每次只能处理64位(即分组长度为8字节)的数据,可是通常我们在加密时输入的明文长度都会大于8字节,这时如何去处理每个明文分组就成为一个应当考虑的问题。分组密码的模式是这个问题的解决方案,常见模式有5种:

    • ECB(Electronic Codebook, 电子密码本)模式
    • CBC(Cipher Block Chaining, 密码分组链接)模式
    • CFB(Cipher Feedback, 密码反馈)模式
    • OFB(Output Feedback, 输出反馈)模式
    • CTR(Counter, 计数器)模式

    Navicat并没有使用上述的任何一种加密模式,但其采用的加密模式与CBC相似,故简单介绍CBC模式(下图来自Wikipedia),如果对于其他4种加密模式感兴趣,可自行百度。

    Plaintext是明文按照分组密码的分组长度分割而成,初始化向量IV、密钥Key在加密时自行决定,Block Cipher Encryption是使用的加密算法,最终将每个密文分组Ciphertext连接起来即密文。

    0x04 Navicat 加密过程

    0x04.1 新建连接

    首先,打开Navicat,新建一个MySQL连接:

    连接名为Test,用户名默认root,密码123456:

    Navicat将主机(Host),端口(Port),用户名(UserName)与加密后的密码(Pwd)保存到注册表中,不同的数据库连接对应的注册表路径不同,具体路径如下:

    Win+R之后键入regedit打开注册表,按照上述路径去查找,可以看到刚刚我们建立的MySQL连接的相关键值对:

    0x04.2 Navicat如何加密数据库密码

    0x04.2-1 Key

    Navicat使用SHA-1生成160位长度的密钥:

    存放于无符号字符数组中:

    unsigned char Key[20] = {
        0x42, 0xCE, 0xB2, 0x71, 0xA5, 0xE4, 0x58, 0xB7,
        0x4A, 0xEA, 0x93, 0x94, 0x79, 0x22, 0x35, 0x43,
        0x91, 0x87, 0x33, 0x40
    };
    0x04.2-2 IV

    Navicat在加解密过程中用到的IV是通过Blowfish Cipher加密8字节长度的0xFFFFFFFFFFFFFFFF得到,代码细节如下:

        unsigned long l,r;
        unsigned char IV[BLOCK_SIZE] = "";
        int i;
        BLOWFISH_CTX ctx;
    
         //Initialize Initial Vector
        l=0xFFFFFFFF;
        r=0xFFFFFFFF;
        Blowfish_Init(&ctx,Key,20);
        Blowfish_Encrypt(&ctx,&l,&r);
        for(i=3; i>=0; --i)
        {
            IV[i]=(unsigned char)(l & 0xFF);
            l >>=8;
            IV[i+4]=(unsigned char)(r & 0xFF);
            r >>=8;
        }

    Blowfish_Encrypt(&ctx,&l,&r)之后的for循环是要将8字节长度密文逐字节赋值给IV数组中的每个元素,IV数组中每个元素值具体如下:

    unsigned char IV[8] = {
        0xD9, 0xC7, 0xC3, 0xC8, 0x87, 0x0D, 0x64, 0xBD
    };
    0x04.2-3 Mode

    0x03部分中已经提到Navicat采用的分组密码模式并非5种主要加密模式之一,其采用的分组密码模式工作过程如下所示:

    • 每个PlaintextBlock长度为8字节;在Blowfish_Cipher环节不需要提供密钥Key,密钥Key在调用Blowfish_Init()已经提供,Blowfish Cipher在加解密过程使用已经初始化的ctx进行。

    • 只有剩余分组大小不足8字节时,才会进行上图中的最后一步。否则,一切照旧。
    0x04.2-4 密文存储

    按照分组密码模式,最终的密文应与明文长度一致,可注册表中保存的是"15057D7BA390"。这是因为Navicat在向注册表中写入的并非密文,而是十六进制表示的密文ASCII码。

    0x05 Navicat Part of CDecryptPwd

    源码见附件,下面介绍的是各环节的核心部分。

    0x05.1 blowfish.c & blowfish.h

    这两个文件直接使用的是Paul Kocher版本源码。

    0x05.2 NavicatPartHeader.h

    #include <stdio.h>
    #include <string.h>
    #include "blowfish.h"
    #include <Windows.h>
    #include <tchar.h>
    #include <locale.h>
    
    #define BLOCK_SIZE 8
    #define KEYLENGTH 256
    
    void XorText(unsigned char left[],unsigned char right[],unsigned char result[],unsigned long r_length);
    unsigned long CharConvertLong(unsigned char Text[],short Len);
    void LongConvertChar(unsigned long num,unsigned char Text[],short Len);
    void Navicat_Encrypt(unsigned char PlainText[],unsigned char CipherText[]);
    void Navicat_Decrypt(unsigned char CipherText[],unsigned char PlainText[]);

    该文件包含了NavicatPart.cNavicatPartMain.c文件中要使用到的头文件;定义了两个全局符号常量BLOCK_SIZEKEYLENGTH,分别是分组长度与最大键值长度;以及NavicatPart.c中的函数原型声明。

    0x05.3 XorText() & CharConvertLong() & LongConvertChar() of NavicatPart.c

    1. void XorText(unsigned char left[],unsigned char right[],unsigned char result[],unsigned long r_length)接受4个参数:左操作字符串、右操作字符串、结果字符串、右操作字符串长度,功能是左操作字符串与右操作字符串的异或运算。之所以使用右操作字符串长度控制何时结束,是因为考虑到模式的剩余分组可能会小于分组长度(8字节)。
    void XorText(unsigned char left[],unsigned char right[],unsigned char result[],unsigned long r_length)
    {
        int i;
        for(i=0;i<r_length;++i)
            result[i]=left[i]^right[i];
    }
    1. unsigned long CharConvertLong(unsigned char Text[],short Len)接受2个参数:转换字符串、其长度,功能是用十六进制ASCII码表示字符串,即该函数返回值。(e.g.:unsigned char Test[5] = {0xA4,0x37,0x98,0x56,'\0'}---->>unsigned long T = CharConvertLong(Test, 4)= 0xA4379856)
    unsigned long CharConvertLong(unsigned char Text[],short Len)
    {
        unsigned long result=0;
        short i;
    
        for(i=0;i<Len;++i)
        {
            result <<=8;
            result |=Text[i];
        }
        return result;
    }
    1. void LongConvertChar(unsigned long num,unsigned char Text[],short Len)接受3个参数:转换数、存储字符串、长度,功能与上一函数相反,是将数值的十六进制表示逐字节分割后赋给存储字符串中每个元素。(e.g.:unsigned long T = 0xA4379856---->>unsigned char Test[5] = LongConvertChar(T,Test,4) = {0xA4,0x37,0x98,0x56,'\0'};)
    void LongConvertChar(unsigned long num,unsigned char Text[],short Len)
    {
        short i;
        for(i=Len-1;i>=0;--i)
        {
            Text[i]=(unsigned char)(num & 0xFF);
            num >>=8;
        }
    }

    0x05.4 Navicat_Encrypt() of NavicatPart.c

    void Navicat_Encrypt(unsigned char PlainText[],unsigned char CipherText[])
    {
        unsigned long l,r,TextLength,block,remain,l_temp,r_temp;
        unsigned char IV[BLOCK_SIZE] = "";
        unsigned char c_temp[BLOCK_SIZE + 1] = "";
        int i;
        BLOWFISH_CTX ctx;
    
        //Initialize Initial Vector
        l=0xFFFFFFFF;
        r=0xFFFFFFFF;
        Blowfish_Init(&ctx,Key,20);
        Blowfish_Encrypt(&ctx,&l,&r);
        for(i=3; i>=0; --i)
        {
            IV[i]=(unsigned char)(l & 0xFF);
            l >>=8;
            IV[i+4]=(unsigned char)(r & 0xFF);
            r >>=8;
        }
    
        //Encrypt PlainText
        TextLength=strlen(PlainText);
        block=TextLength/BLOCK_SIZE;
        remain=TextLength%BLOCK_SIZE;
        for(i=0;i<block;++i)
        {
            memcpy(c_temp, PlainText + i * BLOCK_SIZE, BLOCK_SIZE);
            c_temp[BLOCK_SIZE] = '\0';
            XorText(IV,c_temp,c_temp,BLOCK_SIZE);
            l_temp=CharConvertLong(c_temp,4);
            r_temp=CharConvertLong(c_temp+4,4);
            Blowfish_Encrypt(&ctx,&l_temp,&r_temp);
            LongConvertChar(l_temp,c_temp,4);
            LongConvertChar(r_temp,c_temp+4,4);
            memcpy(CipherText + i * BLOCK_SIZE, c_temp, BLOCK_SIZE);
            XorText(IV,c_temp,IV,BLOCK_SIZE);
        }
    
        if(remain)
        {
            l_temp=CharConvertLong(IV,4);
            r_temp=CharConvertLong(IV+4,4);
            Blowfish_Encrypt(&ctx,&l_temp,&r_temp);
            LongConvertChar(l_temp,IV,4);
            LongConvertChar(r_temp,IV+4,4);
            memcpy(c_temp, PlainText + i * BLOCK_SIZE, remain);
            c_temp[remain] = '\0';
            XorText(IV,c_temp,c_temp,remain);
            memcpy(CipherText + i * BLOCK_SIZE, c_temp, remain);
        }
    }

    该函数在main中并未使用。

    1. 初始化IV上面已经介绍,不再赘述,需提示一点:这一部分不能作为函数独立,然后在Navicat_Encrypt()中调用该函数,因为下面的Blowfish Cipher加密要使用其初始化的ctx。

    2. 第二个for循环部分完成0x04.2-3图中给出的前(n-1)步的过程。

    3. if部分是处理剩余分组大小不足8字节的情况,即第n步。

    4. CharConverLong()LongConverChar()的存在是因Paul Kocher版本源只能处理无符号长整型。

    0x05.5 Navicat_Decrypt() of NavicatPart.c

    void Navicat_Decrypt(unsigned char CipherText[],unsigned char PlainText[])
    {
        unsigned long l,r,TextLength,block,remain,l_temp,r_temp;
        unsigned char IV[BLOCK_SIZE] = "";
        unsigned char c_temp1[BLOCK_SIZE+1] = "";
        unsigned char c_temp2[BLOCK_SIZE+1] = "";
        int i;
        BLOWFISH_CTX ctx;
    
        //Initialize Initial Vector
        l=0xFFFFFFFF;
        r=0xFFFFFFFF;
        Blowfish_Init(&ctx,Key,20);
        Blowfish_Encrypt(&ctx,&l,&r);
        for(i=3; i>=0; --i)
        {
            IV[i]=(unsigned char)(l & 0xFF);
            l >>=8;
            IV[i+4]=(unsigned char)(r & 0xFF);
            r >>=8;
        }
    
        //Decrypt CipherText
        TextLength=strlen(CipherText);
        block=TextLength/BLOCK_SIZE;
        remain=TextLength%BLOCK_SIZE;
        for(i=0;i<block;++i)
        {
            memcpy(c_temp1, CipherText + i * BLOCK_SIZE, BLOCK_SIZE);
            c_temp1[BLOCK_SIZE] = '\0';
            memcpy(c_temp2, CipherText + i * BLOCK_SIZE, BLOCK_SIZE);
            c_temp2[BLOCK_SIZE] = '\0';
            l_temp=CharConvertLong(c_temp1,4);
            r_temp=CharConvertLong(c_temp1+4,4);
            Blowfish_Decrypt(&ctx,&l_temp,&r_temp);
            LongConvertChar(l_temp,c_temp1,4);
            LongConvertChar(r_temp,c_temp1+4,4);
            XorText(IV,c_temp1,c_temp1,BLOCK_SIZE);
            memcpy(PlainText+i*BLOCK_SIZE, c_temp1, BLOCK_SIZE);
            XorText(IV,c_temp2,IV,BLOCK_SIZE);
        }
    
        if(remain)
        {
            l_temp=CharConvertLong(IV,4);
            r_temp=CharConvertLong(IV+4,4);
            Blowfish_Encrypt(&ctx,&l_temp,&r_temp);
            LongConvertChar(l_temp,IV,4);
            LongConvertChar(r_temp,IV+4,4);
            memcpy(c_temp1, CipherText + i * BLOCK_SIZE, remain);
            c_temp1[remain] = '\0';
            XorText(IV,c_temp1, c_temp1,remain);
            memcpy(PlainText + i * BLOCK_SIZE, c_temp1, remain);
        }
    }

    解密过程可参照下图理解:

    除了多一步密文分组的拷贝,其余都是加密过程的逆过程,不再赘述。

    0x05.6 main() of NavicatPartMain.c

    • 主程序功能:
      1. 遍历注册表,路径前缀计算机\HKEY_CURRENT_USER\Software\PremiumSoft不变,变化的是与其拼接的字符串,可根据Navicat写入注册表时创建路径的规律来进行拼接:

    DataNavicatPremium子项均与存储数据库相关信息无关,不包含Servers,而存储数据库相关信息的NavicatNavicatPG子项均包含Servers,所以可进行这一判断来决定是否要用RegEnumKeyEx()遍历Servers下的子项。

    1. 使用RegEnumValue()遍历Servers子项中的键值对,主要是Host、UserName、Pwd、Port四项。在读取Pwd值之后传递给Navicat_Decrypt()进行解密。需要说明一点:在读取Port之前,读取类型要从REG_SZ转为REG_DWORD,否则读出的值无意义。
      • 运行效果图:

    0x06 参考

    0x07 2019-12-10更新

    对主函数中部分代码注释:

    if (_tcscmp(ValueName, _T("Pwd")) == 0)
    {
        WideCharToMultiByte(CP_ACP, 0, KeyData, -1, CipherText, MAX_PATH, NULL, NULL);
        for (j = 0; j < strlen(CipherText); ++j)
        {
            memcpy(StrTmp, CipherText + j * 2, 2);
            StrTmp[2] = '\0';
            CipherTextCP[j] = (BYTE)strtol(StrTmp, &ChTmp, 16);
         }
         CipherTextCP[j] = '\0';
    
         Navicat_Decrypt(CipherTextCP, PlainText);
         wprintf(_T("%Ts :"), ValueName);
         printf("%s\n", PlainText);
    }
    1. WideCharToMultiByte()函数用于将Wide String转换为String,具体语法见Microsoft Docs
    2. 下面的for循环是将十六进制的字符形式转为十六进制的数值形式之后存储到待解密字符串中。示例如下:
    CipherText=="15057D7BA390";    //这是存储在pwd中十六进制的字符形式
    CipherTextCP=={
    0x15,0x05,0x7D,0x7B,0xA3,0x90
    }                            //这是转换后的十六进制的数值形式,代表密文

    for循环中的memcpy()StrTmp[2]='\0'只是为后面的strtol()函数服务。



    Source Code.rar (12.35 KB, 下载次数: 4)
    J0o1ey 版主 培训/授权项目Q547006660 秦 春秋文阁 春秋游侠 核心白帽 i春秋签约作者 幽默灌水王 积极活跃奖 白帽高手
    沙发
    发表于 2020-1-3 19:24:40
    感谢分享
    有培训需求或是技术交流需求的朋友可以联系我~QQ547006660交流群820783253,团队首页www.gcowsec.ocm
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册