[re]複雜VM逆向:2020網鼎杯玄武組re baby_vm wp

[re]複雜VM逆向:2020網鼎杯玄武組re baby_vm wp


可以明顯感覺到玄武組的題目難於其他三個組。這麼複雜的一道題也好意思叫baby???

題目分析

拿到題目發現是一個64位windows程序,結合題目名稱,懷疑是虛擬機逆向。
在這裏插入圖片描述
直接逆向分析:
在這裏插入圖片描述
沒有加殼,把所有函數的符號都去了,但字符串沒有混淆,可以根據“Tell Me Your Flag:”字符串找到函數的主邏輯:
在這裏插入圖片描述
向下分析可以的到flag的格式:
在這裏插入圖片描述
flag長度42,uuid格式,並且由0-9,a-f組成,也就是小寫16進制數。如:

flag{12345678-0123-5678-0123-567890123456}

接着將輸入的函數去掉flag{}和-之後轉換成16進制數,然後傳給chkflag函數:
在這裏插入圖片描述
在這裏插入圖片描述
注意中間的那些ifdebug函數出現了很多次,是用來檢測調試的,如果調試的過程中經過了這些函數,就會退出,所以可以直接patch掉,但太多,也可以直接就斷點避開這些函數。

然後查看chkflag函數:
在這裏插入圖片描述
這個函數的邏輯是:

  1. 先對輸入(flag去掉頭尾和-之後轉換成16進制數之後)進行md5
  2. 然後對flag進行某種變換(之後在分析)
  3. 對變換結果進行md5
  4. 分別取輸入(flag去掉頭尾和-之後轉換成16進制數之後)、輸入的md5、輸入的變換的md5的依次4個字節轉換成整數
  5. 將上述三個整數進入VM虛擬機進行計算,一共四輪
  6. 每一輪的計算結果(4字節)和輸入的md5的4字節一起存入一個數組,然後和一個全局變量校驗
  7. 返回校驗結果,如果相同則通過,輸出flag正確

關於md5是怎麼看出來的,首先點進這個函數能看到一堆跟hash有關的函數:
在這裏插入圖片描述
百度一下就知道這是windows計算哈希用的一些東西,然後根據輸入和輸出的結果,首先,我們選擇一個轉換成16進制數之後是可顯示的ascii嘛的輸入作爲輸入,如:flag{78797878-7878-7878-7878-787878787878}

轉換成16進制之後是:
在這裏插入圖片描述
然後對比md5計算的結果:

927B94B9FE8EE835AB2A400FF4219644

可以搜到:
在這裏插入圖片描述
注意上述步驟中的第六步,他VM計算的結果和我們輸入flag的md5一起存在了result的數組裏,每隔四個字節一個,那麼我們是不是就知道了flag的md5了呢?沒錯確實我們知道了flag的md5,但並沒啥用,查不出來,因爲這個md5的輸入並不是可見字符串,所以市面上的彩虹表都搜不出來。。。。

這是結果:
在這裏插入圖片描述
這裏每隔四個字節都是flag的md5的四個字節(轉換成整數後),取出來flag的md5就是:

9036d8c5ea6281976ca9321969262204

根本搜不出來(24小時都是騙人的,我當天就沒做出來,到現在寫wp都快一週了):
在這裏插入圖片描述
老老實實的來分析一下VM吧

虛擬機分析

在這裏插入圖片描述
首先將上文提到的三個數放在一個數組中,然後和opcode一起作爲輸入初始化虛擬機:
在這裏插入圖片描述
整個虛擬機的虛擬處理器和內存結構如下上面所示,每一個寄存器是4個字節,也就是32位的。初始化完成之後將虛擬機結構體返回,在內存中看一下:
在這裏插入圖片描述
值得注意的是寄存器0是用來存儲當前執行到opcode第幾個的:
在這裏插入圖片描述
然後就是對opcode進行分析,找到各個操作碼對應的操作。技巧在之前VM題目中已經提到過了,其實就是在執行一個opcode對應的函數之前和之後,記錄並對比寄存器和棧的狀態,大部分操作數都可以分析得出。可能有一些比如位移和異或不太容易看出,也可以直接進入到函數中,查看關鍵操作,比如這道題目裏的0x11操作數,輸入輸出如下:

78787878,07 -> f0f0f0f0 

我最開始還以爲是每個字節分別異或0x07,結果正好是0xf0f0f0f0,但後來我發現不對,進入這個函數之後發現了IDA的ROR4宏,也就是循環右移:
在這裏插入圖片描述
所以這個0x11操作數就是循環右移x位。也就是說,簡單的操作數對比輸入輸出,複雜的操作數進去看關鍵操作指令,剩下的就是熟練度,熟練度越高,分析出虛擬機行爲越快。

將操作數導出:

D0 00 00 00 00 02 01 0D  01 02 04 01 10 00 00 00 
02 01 01 00 00 00 00 02  03 01 01 00 00 00 02 02 
09 01 02 0D 01 02 05 01  08 00 00 00 02 06 14 05 
06 11 04 05 01 B9 79 37  9E 02 05 0C 04 05 0D 04 
03 AD 00 00 00 0C 04 01  02 04 0E 01 03 07 06 19 
00 00 00 D0 01 00 00 00  D0 02 00 00 00 02 02 02 
03 0D 04 02 05 10 05 02  10 05 03 0D 05 0D 04 02 
05 0F 05 02 0F 05 03 0D  05 0D 04 02 05 0F 05 02 
0D 05 0D 04 02 05 0F 05  03 0D 05 0D 02 02 05 0F 
05 03 02 06 0C 05 06 02  06 0C 05 06 02 06 0C 05 
06 02 06 0C 05 06 0D 05  02 01 13 01 FF 00 08 00 
00 00 01 01 03 00 00 00  02 02 08 01 02 E0 08 00 
00 00 01 00 09 00 00 00  02 01 06 00 00 00 02 03 
12 02 03 E0 09 00 00 00  02 04 00 00 00 00 00 00 
90 36 D8 C5 CC 02 79 1F  EA 62 81 97 15 3D AE 2F 
6C A9 32 19 91 FE EB CE  69 26 22 04 42 AF F6 AF 

然後根據操作數的順序,來一個一個的分析,遇到相同的就跳過,分析完畢之後沒出現的操作數也不用分析了。我分析的虛擬機操作數含義如下:

opcode:
00 xx 00 00 00 0x: 把棧偏移xx mov 給0x寄存器
E0 xx 00 00 00 0x: 把第x個寄存器mov到棧偏移xx處
01 xx xx xx xx:push xxxxxxxx 立即數
0d 0x : push xreg
02 0x : pop reg0x
03 AD 00 00 00 0C 04 01: call AD
04 : 返回
06 19 00 00 00 : jmp 19
07 : falg=~flag
08 0x 0y : regx+=regy
09 0x 0y : regx-=regy
0c 0x 0y : regx^=regy
0f 0x 0y : regx&=regy
10 0x 0y : regx|=regy
11 0x 0y : regx 循環右移regy
12 0x 0y : regx 循環左移regy
13 0x : 0x寄存器取反
14 0x 0y : regx 取餘 regy.
D0 0x 00 00 00 : 緩衝區 第x部分(輸入)拿到棧上
ff : 退出

比較麻煩的就是0x03 call操作數和0x04 ret操作數,這兩個操作數居然還會保存寄存器狀態和回退寄存器狀態,很強。

然後整理一下,整個虛擬機的操作,翻譯成python就是:

temp=input1
temp2=input2
temp3=input3
for i in range(16):
    x=(15-i)%8
    temp=((temp>>x)&0xffffffff|(temp<<(32-x))&0xffffffff)&0xffffffff
    temp=temp^0x9e3779b9
    temp=((temp<<6)&0xffffffff|(temp>>(32-6))&0xffffffff)&0xffffffff

newtemp6=(temp3&temp2)^(temp & temp2)^(temp & temp3)^(temp & temp3& temp2)^(temp | temp3| temp2)
newtemp6=newtemp6^0xffffffff

input1 input2 input3分別是輸入的flag轉換成16進制之後的4個字節、flag的md5轉換成16進制的4個字節和flag變換後md5後的4個字節。

可以看到對輸入的這三個內容進行了一些列的邏輯運算,然後得到的結果(newtemp6)就是要返回去對比的。

需要跟上文提到的result每隔4字節的四個字節對比:
在這裏插入圖片描述

解題

雖然VM部分分析完畢,但並不能直接就做出來,因爲咋一看去,這個VM是三個輸入,和一個輸出,我們無法通過一個輸出去反推三個輸入,而且三個輸入之間也沒有線性關係(一個輸入是flag,另外兩個都是md5)。

但仔細分析其實並不是,因爲輸入的另一個內容我們已經知道了,那就是flag的md5。我們不知道的其實只有兩個內容,flag變換後的md5和flag。

那麼我們這時候分析一下那個變換:
在這裏插入圖片描述
可以看到這個變換就是把輸入的16字節的flag進行逐字節的累加,然後對0x64取餘,然後根據取餘結果生成100字節的輸出。接下來就是對輸出進行md5了,那麼也就是說最後生成的內容是一個對0x64取餘的操作,那麼一共也就0x64種可能啊。所以,整理一下思路:

三個輸入,一個結果

一個結果和一個輸入已知,一個輸入有100中可能,一個輸入未知並且是要求的輸入

所以此題可解,我們爆破100種情況,然後分別反推這個輸入。並且我們知道輸入的內容的md5,進而可以從100種結果之中篩選出正確的結果。

還有一個難點就是,逆推這個邏輯關係:

newtemp6=(temp3&temp2)^(temp & temp2)^(temp & temp3)^(temp & temp3& temp2)^(temp | temp3| temp2)

已知newtemp6,temp2,temp3來求temp。這裏我直接通過查表來進行復原,將所有可能打印出來,這裏我忘記打印帶取反結果的了,懶得改了,後文寫代碼的時候直接結果輸入之前取反:

a=[1,0]
for temp in a:
    for temp2 in a:
        for temp3 in a:
            print temp2,temp3,(temp3&temp2)^(temp & temp2)^(temp & temp3)^(temp & temp3& temp2)^(temp | temp3| temp2),temp

在這裏插入圖片描述
然後生成一個對應的表:

vmRetable={
        "111":"1",
        "100":"1",
        "010":"1",
        "001":"1",
        "110":"0",
        "101":"0",
        "011":"0",
        "000":"0"
}

對查到的結果拼接在一起之後就是前半段計算的temp:

for i in range(16):
    x=(15-i)%8
    temp=((temp>>x)&0xffffffff|(temp<<(32-x))&0xffffffff)&0xffffffff
    temp=temp^0x9e3779b9
    temp=((temp<<6)&0xffffffff|(temp>>(32-6))&0xffffffff)&0xffffffff

然後再逆這段即可。完整代碼如下:

import hashlib
import binascii

result=[0xC5D83690, 0x1F7902CC, 0x978162EA, 0x2FAE3D15, 0x1932A96C,0xCEEBFE91, 0x04222669, 0xAFF6AF42] #結果數組
flagmd5='9036d8c5ea6281976ca9321969262204'  #flag的md5
vmRetable={
        "111":"1",
        "100":"1",
        "010":"1",
        "001":"1",
        "110":"0",
        "101":"0",
        "011":"0",
        "000":"0"
}

def transform(input):   #變換函數
    output=""
    for k in range(100):
        a=0
        input^=0xC3
        output+=chr(input&0xff)
        for j in range(8):
            a^=((input&0xff)>>j)&1
        input=(a|2*input)&0xff
    m = hashlib.md5()
    m.update(output)
    output=m.hexdigest()
    return output

def getinputbit(a,b,c):   #查表復原最後胡的邏輯運算
    return vmRetable[a+b+c]

def movere(temp):         #復原邏輯運算前的循環位移
    i=15
    while i>=0:
        x=(15-i)%8
        temp=((temp>>6)&0xffffffff|(temp<<(32-6))&0xffffffff)&0xffffffff
        temp=temp^0x9e3779b9
        temp=((temp<<x)&0xffffffff|(temp>>(32-x))&0xffffffff)&0xffffffff
        i-=1
    return temp    
def big2small(data):   #大小端轉換
    return binascii.hexlify(binascii.unhexlify(data)[::-1])


for i in range(0x64):   #循環生成變換內容
    transoutput=transform(i)
    flag=""
    for i in range(4):
        md5bin=bin(int(result[2*i]))[2:].rjust(32,'0')
        transbin=bin(int(big2small(transoutput[8*i:8*i+8]),16))[2:].rjust(32,'0')
        resultbin=bin(result[2*i+1]^0xffffffff)[2:].rjust(32,'0')
        flagbin=""  #先將三個輸入轉換成二進制
        for j in range(32):  #對二進制內容查表復原flag的位移後二進制形式
            flagbin+=getinputbit(md5bin[j],transbin[j],resultbin[j])
        flagtemp=movere(int(flagbin,2))   #復原flag的一部分
        flagpart=""
        for j in range(4):  #將flag從整型轉換成字符串型(好md5)
            flagpart+=chr((flagtemp>>(j*8))&0xff)
        flag+=flagpart

    m = hashlib.md5()
    m.update(flag)
    flagmd5_=m.hexdigest()  #對flag求md5

    flagstr="flag{"
    if(flagmd5_==flagmd5):  #如果和真flag的md5相同則計算正確,輸出
        for i in range(len(flag)):
            if(i==4 or i==6 or i==8 or i==10):
                flagstr+='-'
            flagstr+=hex(ord(flag[i]))[2:].rjust(2,'0')
        flagstr+='}'
        print flagstr

成功:
在這裏插入圖片描述
flag:

flag{9e573902-0e31-4837-a337-32a475ca007c}

在這裏插入圖片描述

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