天天看點

解碼base64_CTF逆向中的常見算法解析base64

CTF中的逆向題,大多是考察對代碼的讀寫能力以及加密混淆算法的識别能力。能夠在一大堆代碼裡快速識别出經典的加密混淆算法,可以大大提高我們的解題效率

計劃從本期開始解析CTF逆向中的常見算法,包括算法的原理、代碼、代碼特征以及常見應用,先從最常見的base64開始

0x1 base64的原理

對于一串字元,算法要求每三個字元為一組,生成四個字元。如果最後一組字元數不足三個,則用等号補充。具體轉換規則如下表所示,三個字元共24位,每6位作為一組計算索引值,再結合base64表查找相應的索引,完成三字元到四字元的轉換

解碼base64_CTF逆向中的常見算法解析base64

base64表如下:

解碼base64_CTF逆向中的常見算法解析base64

如下表所示,字元不足三個時,最後一個字元低位補0,計算索引,再添加等号

解碼base64_CTF逆向中的常見算法解析base64

0x2 base64 程式設計實作:

char *__fastcall base64_encode(char *a1){  int v1; // eax  int v2; // eax  int v3; // eax  int v4; // eax  int v5; // eax  int v6; // eax  int v8; // [rsp+1Ch] [rbp-54h]  int v9; // [rsp+20h] [rbp-50h]  int v10; // [rsp+24h] [rbp-4Ch]  int v11; // [rsp+24h] [rbp-4Ch]  int v12; // [rsp+24h] [rbp-4Ch]  int v13; // [rsp+28h] [rbp-48h]  int v14; // [rsp+2Ch] [rbp-44h]  char src[56]; // [rsp+30h] [rbp-40h]  unsigned __int64 v16; // [rsp+68h] [rbp-8h]  v16 = __readfsqword(0x28u);  v1 = strlen(a1);                              // a1為輸入的字元串  v14 = v1 % 3;                                 // v14為輸入字元串長度除3以後的餘數  v13 = v1 / 3;                                 // v13為3個一組的字元組合數量  memset(src, 0, 0x30uLL);  v10 = 0;  v8 = 0;  v9 = 0;  while ( v8 < v13 )  {    v2 = v10;    v11 = v10 + 1;    src[v2] = base64_table[a1[v9] >> 2];        // 第一個:a1[0]左移2位,取前6位作為索引值,查找對應字元    v3 = v11++;    src[v3] = base64_table[(16 * (a1[v9] & 3)) | (a1[v9 + 1] >> 4)];// 第二個:a1[0]取後2位與a1[1]的前4位拼接    src[v11] = base64_table[(4 * (a1[v9 + 1] & 0xF)) | (a1[v9 + 2] >> 6)];// 第三個:a1[1]取後4位與a1[2]的前2位拼接,查找對應字元    v4 = v11 + 1;    v10 = v11 + 2;    src[v4] = base64_table[a1[v9 + 2] & 0x3F];  // 第四個:a1[2]取後6位作為索引,查找對應字元    v9 += 3;    ++v8;  }  if ( v14 == 1 )                               // 餘數為1,則需要添加兩個等号  {    src[v10] = base64_table[a1[v9] >> 2];    src[v10 + 1] = base64_table[16 * (a1[v9] & 3)];    strcat(src, "==");  }  else if ( v14 == 2 )                          // 餘數為2,則需要添加1個等号  {    v5 = v10;    v12 = v10 + 1;    src[v5] = base64_table[a1[v9] >> 2];    v6 = v12++;    src[v6] = base64_table[(16 * (a1[v9] & 3)) | (a1[v9 + 1] >> 4)];    src[v12] = base64_table[4 * (a1[v9 + 1] & 0xF)];    src[v12 + 1] = '=';  }  strcpy(a1, src);  return a1;}
           

0x3 base64的代碼特征:

1.base64算法要用到base64表,可以在程式中找到連續的字元串ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

2.根據base64加密原理,代碼中必然存在根據餘數個數判斷是否添加等号的代碼

3.((a[0]&3)<<4)|(a[1]>>4)和(16*(a[0]&3))|(a[1]/16)是等價操作,都表示取a[0]後2位與a[1]前4位拼接,是base64中的常見操作

0x4 base64的擴充應用

  1. base64變表

顧名思義,就是更改base64的索引表。如之前索引0對應A,變成索引0對應B。可使用如下代碼完成轉換:

import base64base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'#初始表diy_base = 'TSRQPONMLKJIHGFEDCBAUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'#新表s = 'd2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD=='#待解密字元串ss = ''for i in range(len(s)):    ss += base[diy_base.find(s[i])]ss += '=='print ssa = base64.b64decode(ss)print a
           

對于base64變表的逆向題,找到新的base表是關鍵。如果新的base表是寫在靜态程式中,可以通過IDA字元串查找(shift+f12)來擷取或者使用以下指令來發現

strings 可執行程式名 | grep -x '.\{30,\}' | head
           

如果base表是在程式加載時才出現,可以分析程式加載時base表的生成代碼,靜态分析出新的base64表或者采用動态調試的方法提取base64表

調試步驟:

  • 在linux虛拟機的同一目錄下放置二進制程式leve3和IDA的debug服務端linux_server64,運作linux_server64
解碼base64_CTF逆向中的常見算法解析base64
  • IDA中選擇Remote linux debuger,在程式中設定斷點後,在Debugger--Process options配置調試程式的位置
解碼base64_CTF逆向中的常見算法解析base64
  • 運作後,即可以看到新的base64表
解碼base64_CTF逆向中的常見算法解析base64

2.base64隐寫

base64算法解碼過程:首先把所有的等号去掉,然後查表将字元轉為二進制的索引值,最後每8位一組計算ascii碼還原字元,不足8位則丢棄

仍然以下表為例,被紅框标記的部分由于不足8位被丢棄了,說明其不會影響解碼結果,也就是說放啥都行。是以此處可以隐寫資訊

解碼base64_CTF逆向中的常見算法解析base64

網上找的base64隐寫解密腳本:

def get_base64_diff_value(s1, s2):    base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'    res = 0for i in xrange(len(s2)):if s1[i] != s2[i]:return abs(base64chars.index(s1[i]) - base64chars.index(s2[i]))return resdef solve_stego():with open('stego.txt', 'rb') as f:        file_lines = f.readlines()        bin_str = ''for line in file_lines:            steg_line = line.replace('\n', '')            norm_line = line.replace('\n', '').decode('base64').encode('base64').replace('\n', '')            diff = get_base64_diff_value(steg_line, norm_line)print diff            pads_num = steg_line.count('=')if diff:                bin_str += bin(diff)[2:].zfill(pads_num * 2)else:                bin_str += '0' * pads_num * 2print goflag(bin_str)def goflag(bin_str):    res_str = ''for i in xrange(0, len(bin_str), 8):        res_str += chr(int(bin_str[i:i + 8], 2))return res_strif __name__ == '__main__':    solve_stego()
           

參考連結:

https://blog.csdn.net/xnightmare/article/details/103774379