Goole CTF 比賽 逆向組 Moom.exe 的逆向過程簡單分析

CTF比賽逆向組程序 Moon.exe 的分析過程

 

一、    觀察

剛拿到這個程序的時候,雙擊運行發現黑框框一閃而過,什麼都沒看到,於是就用命令行去執行它,結果只輸出“gl3wInit()”, 如圖1所示:

圖1

用IDA查看程序裏面的流程,發現sub_4032C0()這個函數動態加載很多關於OpenGL的函數,總共有1236個,如圖2所示:

圖2

二、    程序正常運行

中間掙扎了很久,一直無法讓程序正常運行起來,最後想到OpenGL繪圖可能跟顯卡有關,而我的電腦默認使用集成顯卡,於是我就使用獨立顯卡運行這個程序,程序正常運行了,如圖3所示:

圖3

這是一個OpenGL繪出的一個圖形界面,可以直接輸入,而輸入的字符長度是固定32個字符,隨意輸入32個字符之後,程序輸出一個結果---“Nope”,如圖4所示:


圖4

三、    查找輸入和輸出之間的關係

於是用x64dbg加載該程序,並輸入32個password字符,然後找到password的處理過程,如圖5所示:

圖5

然後再找到一個比較函數----memcmp(),這個比較函數比較兩個512字節的字符串,其中一個字符串是固定的,只有另外一個字符串會發生變化,這個會變化的字符串和用戶輸入的32個字節存在某種聯繫,這種聯繫也就是密碼和另外一個固定的512個字節的聯繫。如圖6所示:

圖6

輸入了32個“8”之後,產生了一組對應的512字節的數據,如圖7所示:

圖7

四、    分析

從這些數據中暫時還看不出它們和用戶輸入的32個字符之間的關係,經過多次調試之後,發現這些數據是經過OpenGL的一段Shader腳本語言產生的,繼而去了解Shader腳本語言,找到了關鍵的函數glGetShaderSource(),該函數是一個加載Shader腳本的函數。在IDA中找到調用這個函數的位置,如圖8所示:

圖8

這個函數是在0x4017B0這個位置被調用的,然後就在x64dbg中定位到這個位置,如圖9所示:


圖9

在這個位置上下一個斷點,重新加載這個程序,讓它直接運行到這個位置之後斷下,就可以得到這個函數的實際參數,再對照glGetShaderSource()這個函數的參數,知道他的第四個參數就是Shader 腳本語言的源程序,如圖10所示:

圖10

五、    查找Shader腳本

而在程序的執行過程中,我們發現這個函數被執行了三次,而且每一次得到的Shader腳本都不一樣,Shader腳本代碼的地址就保存在RSI 寄存器中,前兩次得到的Shader代碼比較短,第三次的Shader代碼比較長,最長的這部分代碼也是最主要的代碼,前面的代碼是次要的。如圖11、圖12、圖13所示:

圖11

圖12

 

圖13

六、    數據變換過程

Shader代碼在附錄中。這部分代碼的功能就是把用戶輸入的32個字符數據轉變成64個DWORD類型的數據,是一個變相的hash過程,具體的實現過程可以參考附錄中的Shader代碼。至於最後爲什麼是比較兩個512字節的數據,而不是比較兩個64個DWORD類型的數據,這個過程是在程序中實現的,程序中使用一個循環,把每一個DWORD類型的數據表示成十六進制的形式,然後再把這個DWORD類型的十六進制形式的數據的每一位都拆開,最後把每一位都轉換成爲一個對應的ASCII字符。例如:一個DWORD類型的數據0x30c7ead9,最後會變成“30c7ead9”這樣的一個字符串。也就是說,原來的一個DWORD類型的數據,最後變成了八個字節的ASCII字符串,所以64個DWORD類型的數據,最後就變成了一個64*8=512字節的字符串,所以最後比較的是兩個512字節的字符串。中間的轉化過程的代碼如圖14所示:

圖14

在IDA中的位置如圖15所示:

圖15

七、    分析Shader代碼並編寫逆向程序

分析它最後的Shader代碼,可以得知,它是一個類似於hash的的一個算法,把32個字符串password映射到64個DWORD值的hash表上面,雖然每一個哈希值都與前面的每一個password有關,但是它們的關係比較特殊,如圖16所示:

圖16

這個循環的過程就是利用32個password產生一個結果h,而這個結果h雖然是一個uint類型的變量,但是在這個循環的過程中,它的結果只會落在0到0xFF之間,所以我們可以使用枚舉的方法來暴力破解它。此外,這段Shader代碼的中間還有一個細節,就是每一個password主要影響兩個相鄰的hash值,如圖17所示:

圖17

根據Shader代碼的這兩個主要的特性,我們就可以使用枚舉的方法得到原始的密碼,具體的代碼請參考附錄。最後我們就得到了原始的密碼爲:

CTF{OpenGLMoonMoonG0esT0TheMoon}

 

 

 

 

 

 

 

 

 

 

 

 

 

八、    總結

這個程序使用的手段有點特別。

首先,它的數據轉換過程不在moon.exe程序中,而是使用Shader腳本語言實現數據的轉換,並且轉換的過程在GPU中執行,不在CPU中執行,使得逆向的過程中無法看到數據的轉換過程,給逆向帶來較大的困難。

其次,這個程序使用了OpenGl的一些庫函數來實現繪圖,使得我們在得到Shader腳本程序的過程中,要學習一些額外的Shader語言的相關知識,才能找Shader腳本的加載函數GlGetShaderSource(),最後纔得到了Shader腳本代碼。

       最後,這個程序使用集成顯卡運行不起來,想要看到運行效果,必須使用獨立顯卡才能看到,這個問題剛開始的時候困擾了我很久,再加上我本來不玩遊戲,對這方面的知識瞭解的不太多,使得它成爲我逆向這個程序的第一塊屏障。

       總之,通過逆向這個程序,中間收穫很多,學習到不少新知識,比如關於OpenGL的、Shader腳本語言的,還有一些逆向的思維等。能得到最終的結果,我要感謝我的“導師”--李博士,還有我的隊友--王同學,在李博士的指導下和在於隊友的探討過程中,我才克服了重重困難,逆向出最終的結果。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

附  錄

1.    Shader腳本的源代碼

#version 430

layout(local_size_x=8,local_size_y=8)in;

layout(std430,binding=0) buffer

 

shaderExchangeProtocol

{

       uintstate[64];

       uinthash[64];

       uintpassword[32];

};

 

vec3 calc(uint p)

{

       floatr=radians(p);

       floatc=cos(r);

       floats=sin(r);

       mat3m=mat3(c,-s,0.0,      s,c,0.0,     0.0,0.0,1.0);

       vec3pt=vec3(1024.0,0.0,0.0);

       vec3res=m*pt;

       res+=vec3(2048.0,2048.0,0.0);

       returnres;

}

 

uint extend(uint e)

{

       uinti;

       uintr=e^0x5f208c26;

       for(i=15;i<31;i+=3)

       {

              uintf=e<<i;

              r^=f;

       }

       returnr;

}

uint hash_alpha(uint p)

{

       vec3res=calc(p);

       returnextend(uint(res[0]));

}

uint hash_beta(uint p)

{

       vec3res=calc(p);

       returnextend(uint(res[1]));

}

void main()

{

       uintidx=gl_GlobalInvocationID.x+gl_GlobalInvocationID.y*8;

       uintfinal;

       if(state[idx]!=1)

       {

              return;

       }

       if((idx&1)==0)

       {

              final=hash_alpha(password[idx/2]);

       }

       else

       {

              final=hash_beta(password[idx/2]);

       }

       uinti;

       for(i=0;i<32;i+=6)

       {

              final^=idx<<i;

       }

       uinth=0x5a;

       for(i=0;i<32;i++)

       {

              uintp=password[i];

              uintr=(i*3)&7;

              p=(p<<r)|(p>>(8-r));

              p&=0xff;

              h^=p;

       }

       final^=(h|(h<<8)|(h<<16)|(h<<24));

       hash[idx]=final;

       state[idx]=2;

       memoryBarrierShared();

}

 

 

 

 

 

 

2.    最終的512字節的數據:

0000000001064A30  33 30 63 3765 61 64 39 37 31 30 37 37 37 35 39  30c7ead971077759

0000000001064A40  36 39 62 6534 62 61 30 30 63 66 35 35 37 38 66  69be4ba00cf5578f

0000000001064A50  31 30 34 3861 62 31 33 37 35 31 31 33 36 33 31  1048ab1375113631

0000000001064A60 64 62 62 36 38 37 31 64 62 65 33 35 31 36 3262  dbb6871dbe35162b

0000000001064A70  31 63 36 3265 39 38 32 65 62 36 61 37 35 31 32  1c62e982eb6a7512

0000000001064A80  66 33 32 3734 37 34 33 66 62 32 65 35 35 63 38  f3274743fb2e55c8

0000000001064A90  31 38 39 3132 37 37 39 65 66 37 61 33 34 31 36  18912779ef7a3416

0000000001064AA0  39 61 38 3338 36 36 36 66 66 33 39 39 34 62 62  9a838666ff3994bb

0000000001064AB0  34 64 33 6336 65 31 34 62 61 32 64 37 33 32 66  4d3c6e14ba2d732f

0000000001064AC0  31 34 34 3134 66 32 63 31 63 62 35 64 33 38 34  14414f2c1cb5d384

0000000001064AD0  34 39 33 3561 65 62 62 62 65 33 66 62 32 30 36  4935aebbbe3fb206

0000000001064AE0  33 34 33 6130 30 34 65 31 38 61 30 39 32 64 61  343a004e18a092da

0000000001064AF0  62 61 30 3265 33 63 30 39 36 39 38 37 31 35 34  ba02e3c096987154

0000000001064B00  38 65 64 3263 33 37 32 65 62 36 38 64 31 61 66  8ed2c372eb68d1af

0000000001064B10  34 31 31 3532 63 62 33 62 36 31 66 33 30 30 65  41152cb3b61f300e

0000000001064B20  33 63 31 6138 32 34 36 31 30 38 30 31 30 64 32  3c1a8246108010d2

0000000001064B30  38 32 65 3136 64 66 38 61 65 37 62 66 66 36 63  82e16df8ae7bff6c

0000000001064B40  62 36 33 3134 64 34 61 64 33 38 62 35 66 39 37  b6314d4ad38b5f97

0000000001064B50  37 39 65 6632 33 32 30 38 65 66 65 33 65 31 62  79ef23208efe3e1b

0000000001064B60  36 39 39 3730 30 34 32 39 65 61 65 31 66 61 39  699700429eae1fa9

0000000001064B70  33 63 30 3336 65 35 64 63 62 65 38 37 64 33 32  3c036e5dcbe87d32

0000000001064B80  62 65 31 6563 66 61 63 32 34 35 32 64 64 66 64  be1ecfac2452ddfd

0000000001064B90  63 37 30 3461 30 30 65 61 32 34 66 62 63 32 31  c704a00ea24fbc21

0000000001064BA0  36 31 62 3738 32 34 61 39 36 38 65 39 64 61 31  61b7824a968e9da1

0000000001064BB0  64 62 37 3536 37 31 32 62 65 33 65 37 62 33 64  db756712be3e7b3d

0000000001064BC0  33 34 32 3063 38 66 33 33 63 33 37 64 62 61 34  3420c8f33c37dba4

0000000001064BD0  32 30 37 3261 39 34 31 64 37 39 39 62 61 32 65  2072a941d799ba2e

0000000001064BE0  65 62 62 6638 36 31 39 31 63 62 35 39 61 61 34  ebbf86191cb59aa4

0000000001064BF0  39 61 38 3065 62 65 30 62 36 31 61 37 39 37 34  9a80ebe0b61a7974

0000000001064C00  31 38 38 3863 62 36 32 33 34 31 32 35 39 66 36  1888cb62341259f6

0000000001064C10  32 38 34 3861 61 64 34 34 64 66 32 62 38 30 39  2848aad44df2b809

0000000001064C20  33 38 33 6530 39 34 33 37 39 32 38 39 38 30 66  383e09437928980f

 

3.    處理這512字節數據的一個Python腳本:

def GetDataFromString(str):

       result= str[-19: -2]

       returnresult

       pass

 

def main():

      

       result= "{"

       data= ""

       count= 0;

       try:

              f= open("Password.txt")            

              forline in f:

                     data= GetDataFromString(line)

                     var= "0x" + data[0:8] + ", "

                     result+= var

                     var= "0x" + data[8:16] + ", "

                     result+= var

                     count+= 2;

              result= result[:-2] + "}"

              print("get%d datas."%count)

              print(result)

              f.close()

              f= open("result.txt", "w")

              f.write(result)

              f.close()

       except:

              print("%sconvert to dword error\n"%var)

main()

 

4.    把腳本處理過後得到的64個DWORD類型的數據轉換爲原始的32個字符

 

#include<math.h>

#include<stdio.h>

 

typedef unsigned int uint;

#define M_PI 3.14159265358979323846

 

uint state[64];

uint hash[64];

uint password[32];  // 要求的密碼

 

// 正確的 hash 結果

uint CorrectHash[64] = {

       0x30C7EAD9,0x71077759, 0x69BE4BA0, 0x0CF5578F,

       0x1048AB13,0x75113631, 0xDBB6871D, 0xBE35162B,

       0x1C62E982,0xEB6A7512, 0xF3274743, 0xFB2E55C8,

       0x18912779,0xEF7A3416, 0x9A838666, 0xFF3994BB,

       0x4D3C6E14,0xBA2D732F, 0x14414F2C, 0x1CB5D384,

       0x4935AEBB,0xBE3FB206, 0x343A004E, 0x18A092DA,

       0xBA02E3C0,0x96987154, 0x8ED2C372, 0xEB68D1AF,

       0x41152CB3,0xB61F300E, 0x3C1A8246, 0x108010D2,

       0x82E16DF8,0xAE7BFF6C, 0xB6314D4A, 0xD38B5F97,

       0x79EF2320,0x8EFE3E1B, 0x69970042, 0x9EAE1FA9,

       0x3C036E5D,0xCBE87D32, 0xBE1ECFAC, 0x2452DDFD,

       0xC704A00E,0xA24FBC21, 0x61B7824A, 0x968E9DA1,

       0xDB756712,0xBE3E7B3D, 0x3420C8F3, 0x3C37DBA4,

       0x2072A941,0xD799BA2E, 0xEBBF8619, 0x1CB59AA4,

       0x9A80EBE0,0xB61A7974, 0x1888CB62, 0x341259F6,

       0x2848AAD4,0x4DF2B809, 0x383E0943, 0x7928980F,

};

 

struct vec3

{

       floatxyz[3];

       vec3(floata, float b, float c)

       {

              xyz[0]= a;

              xyz[1]= b;

              xyz[2]= c;

       }

 

       //重載 += 運算符

       voidoperator += (const vec3& res)

       {

              size_ti = 0;

              for(i = 0; i < 3; i++)

              {

                     xyz[i]+= res.xyz[i];

              }

       }

};

 

struct mat3

{

       floatv[3][3];

       mat3(floata0, float a1, float a2, float b0, float b1, float b2, float c0, float c1, floatc2)

       {

              //這裏的保存順序要注意,是列優先

              v[0][0]= a0;

              v[0][1]= b0;

              v[0][2]= c0;

 

              v[1][0]= a1;

              v[1][1]= b1;

              v[1][2]= c1;

 

              v[2][0]= a2;

              v[2][1]= b2;

              v[2][2]= c2;

 

       }

 

       //重載矩陣乘法

       vec3operator*(const vec3& res)

       {

              size_ti = 0;

              vec3tmp = vec3(0, 0, 0);

 

              //矩陣(3*3)*矩陣(3*1)

              for(i = 0; i < 3; i++)

              {

                     tmp.xyz[i]=

                            v[i][0]* res.xyz[0] +

                            v[i][1]* res.xyz[1] +

                            v[i][2]* res.xyz[2];

              }

              returntmp;

       }

 

};

 

float radians(uint p)

{

       returnM_PI / 180.0 * p;

}

 

vec3 calc(uint p)

{

       floatr = radians(p);

       floatc = cos(r);

       floats = sin(r);

       mat3m = mat3(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);

       vec3pt = vec3(1024.0, 0.0, 0.0);

       vec3res = m*pt;

       res+= vec3(2048.0, 2048.0, 0.0);

       returnres;

}

 

uint extend(uint e)

{

       uinti;

       uintr = e ^ 0x5f208c26;

       for(i = 15; i<31; i += 3)

       {

              uintf = e << i;

              r^= f;

       }

       returnr;

}

uint hash_alpha(uint p)

{

       vec3res = calc(p);

       returnextend(uint(res.xyz[0]));

}

uint hash_beta(uint p)

{

       vec3res = calc(p);

       returnextend(uint(res.xyz[1]));

}

 

void core(int idx)

{

       uintfinalRes;

       if(state[idx] != 1)

       {

              return;

       }

       if((idx & 1) == 0)

       {

              finalRes= hash_alpha(password[idx / 2]);

       }

       else

       {

              finalRes= hash_beta(password[idx / 2]);

       }

       uinti;

       for(i = 0; i<32; i += 6)

       {

              finalRes^= idx << i;

       }

 

       //這裏的 h 的範圍 是從 0x0 到 0xFF

       //於是枚舉所有的 h 的值, 從而忽略其他 password 對 h 值的影響

       //uinth = 0x5a;

       //for(i = 0; i<32; i++)

       //{

       //     uint p = password[i];

       //     uint r = (i * 3) & 7;

       //     p = (p << r) | (p >> (8 - r));

       //     p &= 0xff;

       //     h ^= p;

       //}

 

       uinth;

       uinttmpres = finalRes;

       for(i = 0x0; i <= 0xFF; i++)

       {

              h= i;

              tmpres= finalRes;

              tmpres^= (h | (h << 8) | (h << 16) | (h << 24));

 

              if(tmpres == CorrectHash[idx])

              {

                     //得到正確地 hash 值

                     hash[idx]= tmpres;

                     state[idx]= 2;

                     break;

              }

       }

      

}

 

void printPass()

{

       printf("Passwordis :\n");

       for(size_t i = 0; i < 32; i++)

       {

              printf("%c",password[i]);

       }

       printf("\n");

}

 

void printHash()

{

       printf("Hashis :\n");

       for(size_t i = 0; i < 64; i++)

       {

              printf("0x%08X", hash[i]);

              if((i + 1) % 4 == 0) printf("\n");

       }

       printf("\n");

}

 

int main()

{

       size_ti = 0;

 

       //初始化

       for(i = 0; i < 64; i++)

       {

              state[i]= 1;

              hash[i]= 0;

       }

 

       for(int k = 0; k < 32; k++)

       {

              for(i = 0; i <= 256; i++)

              {

                     password[k]= i;

                     core(k* 2);           // 一個 password 對應兩個 hash 值

                     core(k* 2 + 1);

                     if(state[k * 2] == 2) // 只要有一個hash值是對的,另外一個也是對的

                     {

                            break;

                     }

              }

       }

 

       printPass();

       printf("\n");

 

       printHash();

       printf("\n");

 

       return0;

}

 

5.    最終結果:

CTF{OpenGLMoonMoonG0esT0TheMoon}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章