三個白帽條條大路通羅馬系列2之二進位制題分析
0x00 前言
該題目是一道演算法題,演算法涉及二次HASH加密及驗證。本人透過分析,認為只有用暴力列舉的方法才能得到認證碼。經過長時間跟蹤,透過研究加密演算法,分析出了透過列舉的方式猜解得到認證碼的方法。本文將先對該題進行二進位制逆向分析,然後進行演算法破解分析。
0x01 逆向
直接將exe拖入IDA中,然後查詢字串Your code 和Wrong code
找到的每處所在的函式都按F2設定一個斷點:
選擇local win32 debugger,然後點選開始按鈕進行除錯:
這時候程式中斷在我們設定的斷點處:
上圖中,如果介面顯示的是彙編程式碼,直接按一下F5就切換到了偽C程式碼裡了。
上面的程式碼具體是幹什麼的,我們暫時不去關心,這時按F9讓程式繼續執行,然後輸入認證碼,隨便輸入一串字元就好,輸長一點,回車之後到達了我們之前下斷的wrong code這裡,IDA反編譯的偽C程式碼如下:
#!c
int __cdecl sub_431970(int a1, int a2)
{
memset(&v4, 0xCCu, 0x108u); //這裡memset是我肉眼識別出函式功能後,做的標記,
v6 = strlen(secret[0]);// secret[0] 一個記憶體中固定的字串
v5 = a2;
for ( i = 0; i < 0x10; i += 4 )
{
v7 = 0;
for ( j = i; j < i + 4; ++j )
{
for ( k = 0; ; ++k )
{
v2 = strlen(secret[0]);
if ( k >= v2 )
break;
if ( secret[0][k] == *(_BYTE *)(j + a1) ) //a1 由b@@[email protected]@[email protected]@@X轉換得到
{
v7 <<= 6;
v7 += k;
break;
}
}
}
*(_BYTE *)v5 = v7 >> 16;
*(_BYTE *)(v5 + 1) = BYTE1(v7);
*(_BYTE *)(v5 + 2) = v7;
v5 += 3;
}
return sub_42FDC0();
}
透過閱讀上面的程式碼,以及跟蹤分析,首先發現字串a1是把我們輸入的認證碼依次替換掉b@@[email protected]@[email protected]@@X
中的@
得到的。另外字串secret[0]是固定的值為:R9Ly6NoJvsIPnWhETYtHe4Sdl+MbGujaZpk102wKCr7/ODg5zXAFqQfxBicV3m8U
透過檢視記憶體值,便一目瞭然:
透過分析演算法發現他的HASH加密演算法如下:
把a1字串每4個一組,如果字元a1[i]是字串secret[0]裡的字元,那麼
#!c
V7=0
Hash= v7<<6+k //k是a1[i]在字串secret[0]裡的下標。
如果a1[i+1]依然滿足條件
那麼hash= hash<<6+k;
依次進行,經過4個字元後 V7從0開始 再hash後面四個字元。
把最終得到的4組hash值以16進位制形式存入記憶體中,每組HASH得到一個3位元組的資料,4組完成後共得到12位元組資料。
這裡隨便輸入的認證碼,本次HASH運算完,得到的12位元組資料如下:
然後第一次HASH結束。
在函式尾部下斷點,F9 一次,再F8後來到了 程式最開始的函式中,也就是第一次我們斷下的函式里:
繼續按F8,進入了下面的函式:
#!c
int __cdecl sub_431E10(int a1)
{
memset(&v2, 0xCCu, 0x114u);
v9 = a1;
v8 = strlen(a1);
strlen(secret[0]) = strlen(secret[0]);
if ( v8 != 12 ) 這裡檢查我們上面得到的HASH後的內容是否是12位元組
{
sub_43019E("Wrong code!\n");
sub_4302C0(1u);
}
for ( i = 0; i < v8; ++i )
{
v5 = 0;
for ( j = 0; j < strlen(secret[0]); ++j )
{
if ( secret[0][j] == *(_BYTE *)(i + v9) ) //這裡檢查上面HASH後的每一個位元組對應的字元是否在
//字串secret[0]裡
{
v5 = 1;
break;
}
}
if ( !v5 )
{
sub_43019E("Wrong code!\n"); //如果不是 就掛了。
sub_4302C0(1u);
}
}
v3 = malloc(16);
memset(v3, 0, 16);
sub_430045(v9, v3);
sub_42F3F2(v3);
return sub_42FDC0();
}
如果上面認證全部透過,按F9後,會進入HASH加密的函式:
這個時候需要進行加密的就是上面得到的第一次HASH加密後的12位元組資料。
對這12位元組加密,每4個一組,共3組,每組得到3位元組資料,這樣第二次HASH後就得到了9位元組的資料。按F9後到達這裡:
#!c
int __cdecl sub_431CA0(int a1)
{
memset(&v2, 0xCCu, 0xF0u);
v6 = a1;
v5 = strlen(a1);
if ( v5 != 9 ) //首先判斷第二次HASH後的位元組長度是否為9,不是就錯了。
{
sub_43019E("Wrong code!\n");
sub_4302C0(1u);
}
for ( i = 0; i < v5; ++i )
{
if ( (signed int)*(_BYTE *)(i + v6) < 48 || (signed int)*(_BYTE *)(i + v6) > 57 ) //檢查第二次HASH後的//每個位元組是否都為數字 //字元
{
sub_43019E("Wrong code!\n");
sub_4302C0(1u);
}
}
for ( i = 0; i < v5; ++i )
{
for ( j = i + 1; j < v5; ++j )
{
if ( *(_BYTE *)(i + v6) == *(_BYTE *)(j + v6) ) //檢查第二次HASH後的每個位元組是否彼此重複。
{
sub_43019E("Wrong code!\n");
sub_4302C0(1u);
}
}
}
sub_42F1D1(v6); //如果上面都透過了 ,就到達了勝利的彼岸了!
return sub_42FDC0();
}
以上就是整個二進位制演算法的逆向過程,這裡我們搞清楚了他的加密演算法,下面就是嘗試破解認證碼了。
0x02 演算法破解
針對第一部分HASH演算法,我計劃是從R9Ly6NoJvsIPnWhETYtHe4Sdl+MbGujaZpk102wKCr7/ODg5zXAFqQfxBicV3m8U
裡任意選取兩個(根據需要也可能是1個)字元分別填充到b@@t [email protected] @[email protected] n@@X
,並分組進行HASH加密,再驗證HASH結果是否滿足條件,如果滿足條件,那麼就儲存一下選取的字元以及HASH後的密文。
具體演算法如下:
#!c
char m2[17]="b@@[email protected]@[email protected]@@X",*p,*q;
int in1=0,in2=0,in3=0,in4=0;
q=m2;
get(q,0,1,2); //分組取hash 引數分別為字串 序號 m2裡需要填充的字元座標
q+=4;
get(q,1,1,0);
q+=4;
get(q,2,0,2);
q+=4;
get(q,3,1,2); //共4組,到這裡infos就得到了所有滿足第一次HASH結果的可能的值
針對第二次HASH加密,我們把get()函式獲得的所有滿足條件的密文字串進行組合,然後對每組字串再次嘗試hash加密,把得到的結果進行檢查是否每個字元都是0-9內並且互不重複,把滿足條件的結果記錄下來。
最後完整破解認證碼的程式碼如下:
#!c
#include <stdio.h>
#include <string.h>
#include <windows.h>
char m1[70]="R9Ly6NoJvsIPnWhETYtHe4Sdl+MbGujaZpk102wKCr7/ODg5zXAFqQfxBicV3m8U";
char sz[11]="0123456789";
struct info
{
char *hash; //存放第一次hash後的值
char *mw; //存放hash前明文
};
info infos[4][1000];
long hash(char *s)
{
int j=0,v7=0,k=0;
v7 = 0;
for ( j = 0; j < 4; ++j )
{
for ( k = 0; ; ++k )
{
if ( k >= strlen(m1))
break;
if ( m1[k] == s[j] )
{
v7 <<= 6;
v7 += k;
break;
}
}
}
return v7;
}
int check(char a,char *s) //檢查字元a是否在字串s裡
{
int i=0;
for (i=0;i<strlen(s);i++)
{
if (s[i]==a)
{
return 1;
}
}
return 0;
}
int check2(char *s) //第二次hash 每次hash後檢查一下 是否在0-9間,同時檢查一下是否有重複數字
{
int v7=hash(s);
char *v5;
v5=(char *)malloc(4);
v5[0] = char(v7 >> 16);
v5[1] = char(v7 >> 8);
v5[2] = char(v7);
v5[3]='\0';
if ( !check(v5[0],sz) || !check(v5[1],sz) || !check(v5[2],sz) )
{
return 0;
}
else if(v5[0]==v5[1]||v5[0]==v5[2]||v5[1]==v5[2])
{
return 0;
}
else
return 1;
}
void get(char *s,int index,int in1,int in2) //暴力列舉 獲得滿足第一次hash結果的 hash密文 和明文
{
char *p,*q,char ms[3];
char *v5;
int i0=0,i=0,j=0,v7=0,k=0,num=0;
p=q=s;
for (i0=0;i0<64;i0++)
{
p[in1]=m1[i0];
for ( i = 0; i < 64; i ++ )
{
if (in2!=0)
{
p[in2]=m1[i];
}
else
i=65;
v7=hash(p);
v5=(char *)malloc(4);
v5[0] = char(v7 >> 16);
v5[1] = char(v7 >> 8);
v5[2] = char(v7);
v5[3]='\0';
if ( check(v5[0],m1) && check(v5[1],m1) && check(v5[2],m1) )
{
infos[index][num].hash=(char *)malloc(4);
strcpy(infos[index][num].hash,v5);
ms[0]=p[in1];
if (in2==0)
{
ms[1]='\0';
}
else
{
ms[1]=p[in2];
ms[2]='\0';
}
infos[index][num].mw=(char *)malloc(3);
strcpy(infos[index][num].mw,ms);
num++;
delete v5;
}
}
if (i<64)
{
break;
}
}
}
void main()
{
char m2[17]="b@@[email protected]@[email protected]@@X",*p,*q;
int in1=0,in2=0,in3=0,in4=0;
q=m2;
get(q,0,1,2); //分段hash 引數分別為字串 序號 m2裡需要填充的字元座標
q+=4;
get(q,1,1,0);
q+=4;
get(q,2,0,2);
q+=4;
get(q,3,1,2); //到這裡infos就得到了所有滿足第一次HASH結果的可能的值
char temp1[15],temp2[15],temp3[15],temp4[15];
for(in1=0;;in1++) //暴力列舉 所有組合,查詢滿足第二次條件的值
{
if (!infos[0][in1].hash)
{
break;
}
strcpy(temp1,infos[0][in1].hash);
for (in2=0;;in2++)
{
strcpy(temp2,temp1);
if (!infos[1][in2].hash)
{
break;
}
strcat(temp2,infos[1][in2].hash);
p=temp2;
if (!check2(p))
{
continue;
}
for (in3=0;;in3++)
{
strcpy(temp3,temp2);
if (!infos[2][in3].hash)
{
break;
}
strcat(temp3,infos[2][in3].hash);
p=temp3+4;
if (!check2(p))
{
continue;
}
for (in4=0;;in4++)
{
strcpy(temp4,temp3);
if (!infos[3][in4].hash)
{
break;
}
strcat(temp4,infos[3][in4].hash);
p=temp4+8;
if (!p||!check2(p))
{
continue;
}
else
{
p=temp4;
char result[10];
result[0]='\0';
for (int i=0;i<3;i++)
{
int v7=hash(p);
char *v5=(char *)malloc(4);
v5[0] = char(v7 >> 16);
v5[1] = char(v7 >> 8);
v5[2] = char(v7);
v5[3]='\0';
strcat(result,v5);
p+=4;
}
for (i=0;i<strlen(result);i++)
{
char tt=result[i];
result[i]='a';
if (check(tt,result))
{
break;
}
result[i]=tt;
}
if (i==strlen(result))
{
puts("got it!!!");
printf("%s%s%s%s\n",infos[0][in1].mw,infos[1][in2].mw,infos[2][in3].mw,infos[3][in4].mw);
}
}
}
}
}
}
}
0x03 驗證
破解程式碼執行後結果如下:
得到了兩個結果:
分別輸入兩個結果,顯示如下:
第一個結果,輸入後沒任何反應,也不提示錯誤。第二個輸入後成功得到flag。
分析認為這個加密演算法有2個解,但是最終能解密FLAG的只有第二個才可以。
0x04 總結
本題主要是透過兩次HASH運算 並分別進行驗證的方式加密;兩次都可以根據已知限制條件,分別採用暴力列舉的方式進行暴力破解。雖然破解程式碼量有點大,但是破解速度並沒有收到影響,執行破解程式碼後能馬上得到結果!
文中有分析不當或不周全的地方,希望大家批評指正!
相關文章
- 三個白帽-條條大路通羅馬系列2-Writeup2020-08-19
- 《調教命令列06》條條大道通羅馬,羅馬羅馬你在哪(學習技巧)2020-04-01命令列
- 羅馬數字轉換成十進位制2020-11-11
- ORACLE中用一條SQL實現其它進位制到十進位制的轉換2007-03-12OracleSQL
- 白條抵庫2008-06-03
- 10進位制 VS 2進位制2018-07-10
- WebSocket系列之二進位制資料設計與傳輸2018-03-31Web
- SQL---2進位制問題2009-08-12SQL
- 幾期『三個白帽』小競賽的writeup2020-08-19
- JavaScript 進位制轉換(2進位制、8進位制、10進位制、16進位制之間的轉換)2009-07-02JavaScript
- 三個白帽之來自星星的你(一)writeup2020-08-19
- 使用 Haskell 將十進位制數字轉成羅馬數字2018-07-01Haskell
- C語言十進位制,八進位制,十六進位制輸出分析2015-03-30C語言
- mysql 日誌之二進位制日誌2015-07-13MySql
- C# 2進位制、8進位制、10進位制、16進位制...各種進位制間的輕鬆轉換2010-01-22C#
- 【新特性速遞】進度條,進度條,進度條2020-06-12
- java之二進位制與資料型別2021-09-09Java資料型別
- 2進位制_8進位制_16進位制之間快速轉換的技巧.txt2013-03-23
- 三個白帽挑戰之我是李雷雷我在尋找韓梅梅系列3——writeup2020-08-19
- 2^k進位制數2020-09-29
- 進位制詳解:二進位制、八進位制和十六進位制2021-07-07
- 演算法學習之二進位制的妙用2019-02-07演算法
- 計算機簡史第三章 機電時代之二進位制2024-06-09計算機
- C語言中printf打出2進位制與16進位制數2018-07-31C語言
- 【進位制轉換】二進位制、十六進位制、十進位制、八進位制對應關係2024-08-05
- 對2個hex(16進位制)字串進行異或操作2018-02-02字串
- Android花樣loading進度條(三)-配文字環形進度條2021-09-09Android
- 計算機基礎進位制轉換(二進位制、八進位制、十進位制、十六進位制)2018-09-07計算機
- 二進位制,八進位制,十進位制,十六進位制的相互轉換2020-02-01
- JavaScript 二進位制、八進位制與十六進位制2019-12-20JavaScript
- java中二進位制、八進位制、十進位制、十六進位制的轉換2018-10-12Java
- 二進位制,八進位制,十進位制,十六進位制之間的轉換2018-07-09
- Python 進位制互相轉換(二進位制、十進位制和十六進位制)2012-12-03Python
- JAVA 二進位制,八進位制,十六進位制,十進位制間進行相互轉換2018-11-12Java
- Qt進位制轉換(十進位制轉十六進位制)2020-12-07QT
- Java基礎系列-二進位制操作2019-02-22Java
- 【C進階】22、條件編譯分析2021-12-25編譯
- 1474 十進位制轉m進位制+1475 m進位制轉十進位制2014-10-11