压在透明的玻璃上c-国产精品国产一级A片精品免费-国产精品视频网-成人黄网站18秘 免费看|www.tcsft.com

CDecryptPwd(一)——Navicat

0x00 前言

本篇是CDecryptPwd系列的第一篇,筆者將介紹Navicat加密過程、其使用的加密算法以及如何使用C語言來實現其加密過程,文章最后是筆者自己編寫的工具(解密Navicat保存在注冊表中的數據庫密碼)。

0x01 環境

  • 平臺:Windows 10(64 bit)
  • 編譯環境:Visual Studio Community 2019
  • Navicat Premium 版本 12.1(64 bit)

0x02 Blowfish

Navicat使用加密算法是Blowfish Cipher(河豚加密),所以在介紹Navicat加密過程之前筆者要先介紹一下Blowfish Cipher。

0x02.0 Introduction

Blowfish是一種對稱密鑰分組密碼,Bruce Schneier在1993年設計并公布。它沒有專利,任何人均可自由使用。

0x02.1 Detail

  • 密鑰長度:32-448位
  • 分組長度:64位
  • 16輪循環的Feistel結構

0x02.2 Feistel Structure

在介紹Blowfish之前先來搞清楚Feistel Structure(下圖來自Wikipedia):

  1. 將原數據分成左右兩部分
  2. 原數據的右側不變,直接變成下次循環的左側
  3. 將原數據的右側與子密鑰傳遞給輪函數F
  4. 輪函數返回值與原數據左側進行異或運算,變成下次循環的右側

上述4個步驟是一輪循環的工作過程,Feistel Structure是進行了16輪循環方才完成一次加密。需要說明一點,在最后一輪循環中左右數據不對調。解密過程是加密過程的反向,具體可參照上圖,不再贅述。

0x02.3 Source Code

筆者分析的源碼是Paul Kocher版本,并沒有使用Bruce Schneier版本。Schneier版本中的子密鑰來源是BLOWFISH.DAT文件,而Kocher版本是直接在源文件中定義子密鑰,分析起來較為直觀。如需其他版本可到此網站下載

0x02.3-1?BLOWFISH_CTX

blowfish.h頭文件中有如下定義:

typedef struct {
  unsigned long P[16 + 2];
  unsigned long S[4][256];
} BLOWFISH_CTX;

為結構體定義別名,結構體中的兩數組即P-Box與S-Box。

0x02.3-2?ORIG_P[16 + 2]ORIG_S[4][256]

ORIG_PORIG_S取自圓周率的小數位,每4個字節賦值給其中的一個元素。

0x02.3-3?void Blowfish_Init(BLOWFISH_CTX *ctx, unsigned char *key, int keyLen)

該函數用來初始化S-Box與P-Box。傳遞參數中的key即密鑰,keyLen是密鑰長度。

  1. BLOWFISH_CTX *ctx中S-Box進行初始化,直接將ORIG_S中的每個元素逐一賦值給S-Box
  2. BLOWFISH_CTX *ctx中P-Box進行初始化,具體過程如下:
    a.?data=0x00000000;
    b. 如果參數中的字符數組key長度不足4,則循環使用key中字符(當使用到key中最后一個字符時,下一個字符是key中第一個字符)與data << 8進行或運算
    上述過程總結起來就是將參數中的字符數組key轉換為ASCII碼形式(e.g.:key[3]="abc"——>>0x61626361并存儲于data中)
    c. 將ORIG_P中的每個元素與data作異或運算后逐一賦值給P-Box
  3. datal=0x00000000;
    datar=0x00000000;
  4. 將上面經過變換后的ctxdataldatar傳遞給Blowfish_Encrypt
  5. 將加密后的dataldatar賦值給P-Box中的元素
  6. 重復9次步驟4-5
  7. 與步驟4類似,不過這次傳遞的是上面過程中已經加密后的dataldatar
  8. 將加密后的dataldatar賦值給S-Box中的元素
  9. 重復512次步驟7-8

    步驟5、8中提到的賦值過程是這樣的(以步驟5來舉例):
    第一次?P[0]=datalP[1]=datar
    第二次?P[2]=datalP[3]=datar
    ……

0x02.3-4?void Blowfish_Encrypt(BLOWFISH_CTX *ctx, unsigned long *xl, unsigned long *xr)

該函數是Blowfish的加密部分。傳遞參數中的ctx應該已經初始化過S-Box與P-Box,xl指向原數據的左半部分,xr指向原數據的右半部分。

  1. 左側數據與P-Box中第i個元素作異或運算(i=n-1,其中n是輪數)
  2. 將左側數據與ctx傳遞給輪函數F
  3. 右側數據與輪函數F返回值作異或運算
  4. 交換運算后的左右兩側數據
  5. 重復16次步驟1-5
  6. 將最后一輪運算互換的左右兩側數據換回來
  7. 右側數據與P-Box中第16個元素作異或運算
  8. 左側數據與P-Box中第17個元素作異或運算

上述過程即一次完整的加密過程,可參照下圖來理解(來自Wikipedia,其中輪函數F的工作過程見0x02.3-6):

0x02.3-5?void Blowfish_Decrypt(BLOWFISH_CTX *ctx, unsigned long *xl, unsigned long *xr)

解密過程是加密過程的逆過程,如有困惑,可參照源碼理解,不再贅述。

0x02.3-6?static unsigned long F(BLOWFISH_CTX *ctx, unsigned long x)

輪函數工作過程:

  1. 將參數x逐字節分割,賦值給a,b,c,d四個變量(e.g.:x=0x21564378,則a=0x21,b=0x56,c=0x43,d=0x78)
  2. y = ((ctx->S[0][a] + ctx->S[1][b]) ^ ctx->S[2][c]) + ctx->S[3][d]
  3. 返回y的值

0x02.4 Demo

此Demo來自Paul Kocher版本根目錄下的blowfish_test.c

#include <stdio.h>
#include "blowfish.h"

void main(void) {
  unsigned long L = 1, R = 2;
  BLOWFISH_CTX ctx;

  Blowfish_Init (&ctx, (unsigned char*)"TESTKEY", 7);
  Blowfish_Encrypt(&ctx, &L, &R);
  printf("%08lX %08lXn", L, R);

  if (L == 0xDF333FD2L && R == 0x30A71BB4L)
      printf("Test encryption OK.n");
  else
      printf("Test encryption failed.n");
  Blowfish_Decrypt(&ctx, &L, &R);
  if (L == 1 && R == 2)
        printf("Test decryption OK.n");
  else
      printf("Test decryption failed.n");
}

需要說明的一點是Paul Kocher這個版本并沒有考慮到小端序的情況,它均按大端序來處理,所以如果在Linux平臺運行此Demo會像下圖所示:

可以看到加密結果并非源碼中給出的結果,而在Windows平臺下運行,正常顯示:

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

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];
}

unsigned long CharConvertLong(unsigned char Text[],short Len)接受2個參數:轉換字符串、其長度,功能是用十六進制ASCII碼表示字符串,即該函數返回值。(e.g.:unsigned char Test[5] = {0xA4,0x37,0x98,0x56,''}——>>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;
}

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,''};)

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] = '';
        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] = '';
        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] = '';
        memcpy(c_temp2, CipherText + i * BLOCK_SIZE, BLOCK_SIZE);
        c_temp2[BLOCK_SIZE] = '';
        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] = '';
        XorText(IV,c_temp1, c_temp1,remain);
        memcpy(PlainText + i * BLOCK_SIZE, c_temp1, remain);
    }
}

解密過程可參照下圖理解:

除了多一步密文分組的拷貝,其余都是加密過程的逆過程,不再贅述。

0x05.6 main() of?NavicatPartMain.c

主程序功能:

  • 遍歷注冊表,路徑前綴計算機HKEY_CURRENT_USERSoftwarePremiumSoft不變,變化的是與其拼接的字符串,可根據Navicat寫入注冊表時創建路徑的規律來進行拼接:

DataNavicatPremium子項均與存儲數據庫相關信息無關,不包含Servers,而存儲數據庫相關信息的NavicatNavicatPG子項均包含Servers,所以可進行這一判斷來決定是否要用RegEnumKeyEx()遍歷Servers下的子項。

  • 使用RegEnumValue()遍歷Servers子項中的鍵值對,主要是Host、UserName、Pwd、Port四項。在讀取Pwd值之后傳遞給Navicat_Decrypt()進行解密。需要說明一點:在讀取Port之前,讀取類型要從REG_SZ轉為REG_DWORD,否則讀出的值無意義。

運行效果圖:

0x06 參考

轉載自安全客:https://www.anquanke.com/post/id/193511

上一篇:代碼審計新手入門——xdcms_v1.0

下一篇:認識 JavaAgent——獲取目標進程已加載的所有類