天天看點

RE-Base64編碼分析Base64編碼原理IDA中算法分析

base64加解密c++實作

  • Base64編碼原理
    • 1、加密原理
    • 2、c++實作加解密過程
  • IDA中算法分析
    • std::string的使用總結
    • base64加密分析
    • 小插曲-base64隐寫
    • 源碼
    • 實戰中的魔改例子
      • 1、通過異或還原表
      • 2、通過移位産生表

Base64編碼原理

目前單字元最大為3Byte(特殊字元),漢字(2Byte)、字母(1Byte)…

而base64則是通過位運算加索引表将3個字元的内容編碼為4個字元,也就是編碼目前所有的單字元是完全夠用的,base64能實作不可見字元和圖檔的傳遞,非常的便捷實用。

1、加密原理

将3個8bit的轉換為4個6bit的資料,再通過索引表轉換為4個可見字元,如下:

RE-Base64編碼分析Base64編碼原理IDA中算法分析

base64的正常索引表為:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

上述即明文為:"son"時 先将三個字元依次轉化為 83 111 110

對應二進制則為 01010011 01101111 01101110

将則24位拼在一起之後4等分(6個一組):

010100 110110 111101 101110

之後轉化為10進制,作為下标進行索引,如上圖所示。

對一個長為len的明文,重複進行len//3次,如果len為三的倍數那麼密文長度為(4/3)len,如果len的長度不為3的倍數,則需要額外的補充’\0’直到分組為3的倍數,補充幾次‘/0’則在密文尾部添加幾個‘=’,最終明文長度仍為4的倍數

RE-Base64編碼分析Base64編碼原理IDA中算法分析

2、c++實作加解密過程

加密函數主體:

核心為通過移位運算和&運算,将24bit劃分為4組6bit接着轉為10進制,進行索引,同時對明文不滿足3的倍數進行填充。

string base64_encode(char*m,int len){ //轉入的明文m 和 明文長度len
	string cipher;
	char tmp_3[3];//3個一組 臨時存放 
	char enbase_4[4];//3->4  
	int i=0;

//了解base64的原理後直到是3->4是以通過一個循環來實作過程
	
	
	while(len){
		
		tmp_3[i]= *(m++);//将明文内容3個一組引入到tmp臨時空間中去
		i++;

		
		if(i==3){ //如果得到3個字元,則進行位運算


			enbase_4[0]=(tmp_3[0]&(0xfc))>>2; //0xfc == 0x11111100 
			
			enbase_4[1]=((tmp_3[0]&(0x03))<<4)+((tmp_3[1]&0xf0)>>4);
			
			enbase_4[2]=((tmp_3[1]&0x0f)<<2)+((tmp_3[2]&0xc0)>>6);
			
			enbase_4[3]=tmp_3[2]&0x3f;//通過位運算實作3個8bit一組 -> 4個6bit一組 
			
			
			for(int j=0;j<4;j++){
			cipher+=base_table[enbase_4[j]]; //索引 
		}
		
		i=0;
			
		}
		
		len--;
	} 
	//上述循環将len//3 組資料轉為base64 ,最優情況 i=0&&len==0;其餘則是i=1 || i=2 
	
	if(i)
	{
		for(int j=i;j<3;j++)
			tmp_3[j]='\0'; //補充到三位  每一個位元組補'\0' 即 0b00000000
			 
		enbase_4[0]=(tmp_3[0]&(0xfc)) >>2; 
		
		enbase_4[1]=((tmp_3[0]&(0x03))<<4) + ((tmp_3[1]&0xf0)>>4);
		
		enbase_4[2]=((tmp_3[1]&0x0f)<<2) + ((tmp_3[2]&0xc0)>>6); 
		
		
		// 三次即可,如果i=2的話 最多隻有三組6為二進制的值不全為0,且最後一組的後兩位一定為0
		
		 for(int j=0;j<i+1; j++) //注意為j<i+1  1個8bit 分為2個6bit 
		 {
		 	cipher+=base_table[enbase_4[j]];
		 }
		 
		 while(i!=3){
		 	cipher+='='; //缺幾個位元組 就補幾個'=' 
		 	i++; 
		 }
		 
		
	}
	return cipher;
}
           

解密函數主體:

解密則是加密的逆過程,實作4->3 ,并且是找出密文在base64表中的下标,将所有找出的下标8個一組轉ASCII,得到最初的明文,特殊是對于尾部有‘=’的處理

string base64_decode(char*c,int len){
	string m;
	char tmp_4[4];
	char debase_3[3];//實作4B->3B  
	int i=0;
	int index=0; //表示目前在密文中的下标 
	
	while(len--&&c[index]!='='){ //讀完密文或讀到‘=’退出循環
		tmp_4[i]=base_table.find(c[index])&0x3f; //通過find函數 找到下标對應的值
		 
		i++;index++;
		
		if(i==4){
			
			debase_3[0]=((tmp_4[0]&0x3f)<<2) + ((tmp_4[1]&0x30)>>4);
			debase_3[1]=((tmp_4[1]&0xf)<<4) + ((tmp_4[2]&0x3c)>>2);
			debase_3[2]=((tmp_4[2]&0x3)<<6) + (tmp_4[3]&0x3f);//注意位運算的優先級 
			
			for(int j =0;j<3;j++)
			     m+=(char)debase_3[j];
			
			i=0;
		}
	}
	
	if(i){ // 如果i不為0 則i表示 '=' 在4個字元中的下标 
		for(int j=i;j<4;j++)
		    tmp_4[j]='\0';//把所有等号的位置全補上0
		    
		debase_3[0]=((tmp_4[0]&0x3f)<<2) + ((tmp_4[1]&0x30)>>4);
		
		debase_3[1]=((tmp_4[1]&0xf)<<4) + ((tmp_4[2]&0x3c)>>2); //最多轉成兩個字元
		
		for(int j=0;j<i-1;j++)  //j<i-1 比如 i=3時出現'=' 那麼證明一個等号 故明文有兩位 
		   m+=debase_3[j];
		
	}
	return m;	
}
           

IDA中算法分析

補全主函數後生成EXE檔案,用IDA64對EXE檔案進行靜态分析。

RE-Base64編碼分析Base64編碼原理IDA中算法分析

用c++實作base64編碼的時候用到了string庫,分析看起來比較複雜,去了解了一下std::string庫的常見使用。

std::string的使用總結

std::string s1;
std::string s3(s2);
std::string s2(“this is a string”);
begin 得到指向字元串開頭的Iterator
end 得到指向字元串結尾的Iterator
rbegin 得到指向反向字元串開頭的Iterator
rend 得到指向反向字元串結尾的Iterator
size 得到字元串的大小
length() 和size函數功能相同
max_size 字元串可能的最大大小
capacity 在不重新配置設定記憶體的情況下,字元串可能的大小
empty 判斷是否為空
operator[] 取第幾個元素,相當于數組
c_str 取得C風格的const char* 字元串
data 取得字元串内容位址
operator= 指派操作符
reserve 預留白間
swap 交換函數
insert 插入字元
append 追加字元
push_back 追加字元
erase 删除字元串
clear 清空字元容器中所有内容
resize 重新配置設定空間
assign 和指派操作符一樣
replace 替代
copy 字元串到空間
find 查找,傳回基于0的索引号
rfind 反向查找
find_first_of 查找包含子串中的任何字元,傳回第一個位置
find_first_not_of 查找不包含子串中的任何字元,傳回第一個位置
find_last_of 查找包含子串中的任何字元,傳回最後一個位置
find_last_not_of 查找不包含子串中的任何字元,傳回最後一個位置
substr(n1,len) 得到字元串從n1開始的長度為len的子串
比較字元串(支援所有的關系運算符)
compare 比較字元串
operator+ 字元串連結
operator+= += 操作符
operator== 判斷是否相等
operator!= 判斷是否不等于
operator< 判斷是否小于
operator>> 從輸入流中讀入字元串
operator<< 字元串寫入輸出流
getline 從輸入流中讀入一
————————————————
版權聲明:本文為CSDN部落客「哀醬」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。
原文連結:https://blog.csdn.net/u010821666/article/details/77934510
           

通過一定的C++基礎,std::string 大緻能辨識,string本身是打包好的一個類,是以有着構造和析構函數這種指派。

base64加密分析

程式流程是對輸入進行了base64加密,之後和一個字元串解密,一般題目中隻要識别出編碼方式便能簡單破解,為了深入學習base64編碼原理,下面主要分析base64的加密算法。

RE-Base64編碼分析Base64編碼原理IDA中算法分析

第一部分,對明文字元串進行3個一組分組,每一組進行分割和base索引。

從上圖也可以看出base64算法明顯的特征:分組進行位運算實作3變4,另外就是base64索引表。

源代碼将base64的表寫成了static,存放在全局變量區,内部檔案可見,外部檔案不可見,是以要獲得表需要進行動态調試,跟進v4便可得到表,不太清楚為什麼base_table顯示UNKNOW。

RE-Base64編碼分析Base64編碼原理IDA中算法分析
RE-Base64編碼分析Base64編碼原理IDA中算法分析

當然如果把static string base_table換成其他内容則變成了一道base64換表的題,并且需要動态調試找到索引表。

RE-Base64編碼分析Base64編碼原理IDA中算法分析

接下來則是對明文不是3的倍數的處理:

即如果2個字元則補一個’='且密文經過換表的内容為2+1=3

二進制位 xxxxxx xxxxxx xxxx00 000000 也就是隻要前3部分的索引值,剩餘一位補‘=’。

同理如果1個字元則要補兩個‘=’那麼分割的二進制位 xxxxxx xx0000 000000 000000 也就是隻有前兩部分的值為特殊值,剩餘兩位補‘=’。

小插曲-base64隐寫

通過上述補位也可以看出,補充的0有部分是參與base64表的索引的,則也為我們提供了一個思路,可不可以不用0來補,用其他相用的資料是否可能起到更好的效果,這就與MISC中的base64隐寫有了一定的聯系,即一個等号時,第三部分低二位的值用明文來填充,兩個等号時第二部分的低4位用明文來填充。

即:

xxxxxx xxxxxx xxxxab 000000

xxxxxx xxabcd 000000 000000

a,b,c,d…代表某一二進制資料。可見base64隐寫雖然可能對明文的最後一個字母有所影響,但是不影響解密内容,畢竟隻是改寫了填充部分。

附上解密代碼:

import base64
a=("")
mode=("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")		#base64 mode碼表
c=("")
f = open("base64.txt","r")					#讀取檔案
line = f.readline()                         #一行一行讀取 下标指針自動向後移動
while line:
    if("=" in line):
        a=line.replace('=','')[-2:]		#去掉‘=’留下最後一個字元 檔案讀取最後一位為‘/n'
        print(a,end="")					#這裡用來檢測是否出錯了
        num=(line.count('='))*2         #等号個數來決定截取二進制字元的個數  一個'='截取兩個 二個'='截取4個
        c=c+(bin(mode.index(a[0])).replace("0b",'')[-(num):].zfill(num))		#這裡是關鍵,對其進行查表對号,再進行二進制轉換篩選
        line = f.readline()              #.zfill()是在左邊填0 補成num位
    else:
        line = f.readline()      #沒有等号就跳過
        continue
f.close()
for i in range (0,len(c),8):
    print(chr(int(c[i:i+8],2)),end='')	#輸出    8位二進制轉為 10進制 之後轉ascii碼

           

根據腳本和上述解釋,了解起來感覺更加輕松。

源碼

最終附上自己寫的源碼,可能在健壯性和效率等有些方面不足或存在錯誤,還請各位師傅們指正。

#include<iostream>
#include<cstring>
using namespace std;
string base64_encode(char*m,int len);
string base64_decode(char*c,int len);
static string base_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int main(){
	char m[100];
	string ss;
	cout<<"pleas tell me your secret:";
	cin>>m;
	ss=base64_encode(m,strlen(m)); 
	string secret="ZmxhZ3tUaGlzX2lzX215X2Jhc2V9";
	if(secret==ss)
	cout<<"you get it"<<endl;
	else 
	cout<<"try a again!"<<endl; 
	return 0;
}

string base64_encode(char*m,int len){
	string cipher;
	char tmp_3[3];//3個一組 臨時存放 
	char enbase_4[4];//3->4  
	int i=0;
	
	
	while(len){
		
		//tmp_3[i] = m[i]; 錯誤的索引 i被改變了
		tmp_3[i]= *(m++);
		i++;

		
		if(i==3){
			enbase_4[0]=(tmp_3[0]&(0xfc))>>2; //0xfc == 0x11111100 
			
			enbase_4[1]=((tmp_3[0]&(0x03))<<4)+((tmp_3[1]&0xf0)>>4);
			
			enbase_4[2]=((tmp_3[1]&0x0f)<<2)+((tmp_3[2]&0xc0)>>6);
			
			enbase_4[3]=tmp_3[2]&0x3f;//通過位運算實作3個8bit一組 -> 4個6bit一組 
			
			for(int j=0;j<4;j++){
			cipher+=base_table[enbase_4[j]]; //索引 
		}
		
		i=0;
			
		}
		
		len--;
	} 
	//上述循環将len//3 組資料轉為base64 ,最優情況 i=0&&len==0;其餘則是i=1 || i=2 
	
	if(i)
	{
		for(int j=i;j<3;j++)
			tmp_3[j]='\0'; //補充到三位  每一個位元組補'\0' 即 0b00000000
			 
		enbase_4[0]=(tmp_3[0]&(0xfc)) >>2; 
		enbase_4[1]=((tmp_3[0]&(0x03))<<4) + ((tmp_3[1]&0xf0)>>4);
		enbase_4[2]=((tmp_3[1]&0x0f)<<2) + ((tmp_3[2]&0xc0)>>6); 
		
		// 三次即可,如果i=2的話 最多隻有三組6為二進制的值不全為0,且最後一組的後兩位一定為0
		
		 for(int j=0;j<i+1; j++) //注意為j<i+1  1個8bit 分為2個6bit 
		 {
		 	cipher+=base_table[enbase_4[j]];
		 }
		 
		 while(i!=3){
		 	cipher+='='; //缺幾個位元組 就補幾個'=' 
		 	i++; 
		 }
		 
		
	}
	return cipher;
}

string base64_decode(char*c,int len){
	string m;
	char tmp_4[4];
	char debase_3[3];//實作4B->3B  
	int i=0;
	int index=0; //表示目前在密文中的下标 
	
	while(len--&&c[index]!='='){
		tmp_4[i]=base_table.find(c[index])&0x3f; //通過find函數 找到下标對應的值
		 
		i++;index++;
		
		if(i==4){
			
			debase_3[0]=((tmp_4[0]&0x3f)<<2) + ((tmp_4[1]&0x30)>>4);
			debase_3[1]=((tmp_4[1]&0xf)<<4) + ((tmp_4[2]&0x3c)>>2);
			debase_3[2]=((tmp_4[2]&0x3)<<6) + (tmp_4[3]&0x3f);//注意位運算的優先級 
			
			for(int j =0;j<3;j++)
			     m+=(char)debase_3[j];
			
			i=0;
		}
	}
	
	if(i){ // 如果i不為0 則表示 '=' 在4個字元中的下标 
		for(int j=i;j<4;j++)
		    tmp_4[j]='\0';
		    
		debase_3[0]=((tmp_4[0]&0x3f)<<2) + ((tmp_4[1]&0x30)>>4);
		
		debase_3[1]=((tmp_4[1]&0xf)<<4) + ((tmp_4[2]&0x3c)>>2); //最多轉成兩個字元
		
		for(int j=0;j<i-1;j++)  //j<i-1 比如 i=3時出現'=' 那麼證明一個等号 故明文有兩位 
		   m+=debase_3[j];
		
	}
	return m;	
}
           

實戰中的魔改例子

1、通過異或還原表

RE-Base64編碼分析Base64編碼原理IDA中算法分析

具體步驟不在過多叙述,主要看到在循環體的索引後進行了一步異或,跟進檢視表的内容。

RE-Base64編碼分析Base64編碼原理IDA中算法分析

确實,異或使base64表面目全飛。

dump出,接着跟0x76異或一次,發現是原始的base64表,正常base64解密即可。

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

2、通過移位産生表

附上執行個體:

RE-Base64編碼分析Base64編碼原理IDA中算法分析

對每一個轉換後的6bit數 + 61 也就是表的ASCII碼範圍是61 + i (i 從 0 到 63),直接python寫個腳本生成表,通過索引還原,再解密即可。

import base64
table=''
for i in range(64):
    table+=chr(i+61)
c='@BdxRTbRBbjIVf`PEyqe^\^\|cc|[email protected][Myy]=='
base='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
cc=''
for i in range(len(c)-2):
    cc+=base[table.find(c[i])]
cc+='=='
cipher=base64.b64decode(cc)
           

參考連結:

base64加密原理c++.

std::string使用總結.

為什麼要使用Base64?.