[UNCTF2020]BetterCpu WriteUp

這題不同前一題虛擬機ezvm一樣,指令很多而且複雜,需要通過寫文檔和腳本來化簡過程。

 直接丟進IDA7.2(如果使用IDA7.0則虛擬機的emulator部分會分析出錯)查看。

   進入main函數後按F5反編譯,再進入ezvm::ezvm()里看構造函數的初始化,因為出題人已經給了符號表,所以捋清整個控制流其實是不難的(比賽時沒做出來,問就是沒載入符號表和不會寫腳本)。&v1 + 4的地址就是局部變數enc_flag的地址,故unk_409020就是加密後的flag。

怎麼載入符號表?就是進入IDA7.2時會彈出一個關於DWARF debug informa的對話框,選Yes就行了 。 

 然後進入ezvm::run(),裡面一大段switch顯然就是虛擬機的emulator部分,而且顯然是個棧式虛擬機(什麼是堆棧虛擬機和暫存器虛擬機?)如果還不清楚,後面寫腳本生成的偽彙編程式碼就會看的更清楚。

需要注意的是,這個虛擬機棧的sp並不是指向棧的第一個元素,而是指向第一個元素的前面一個元素。

下面是虛擬機機器碼的文檔(其實也可以不用寫)。

BetterCpu Virtual Machine Instructment

9bytes
0xEu
cmp rflag,1
je $+idata

9bytes
0xFu
cmp rflag,1
je $-idata

0x10u
push reg

0x11u
pop reg

0x12u
pop mem[reg]

0x13u
push mem[reg]

9bytes
0x20u
mov reg,idata

0x21u
mov reg,getchar()

0x22u
putchar(reg)

0x30u
sadd

0x31u
ssub

0x32u
sxor

0x33u
slhs

0x34u
srhs

0x40u
scmp

0x80u
return

其中sdd,ssub,sxor,scmp是我自己編的指令方便寫腳本,讀者可以對照反編譯結構自行理解。

下面就可以寫腳本將機器碼翻譯成彙編程式碼了。

codes =[
  0x20, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 
  0x12, 0x21, 0x10, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x13, 0x30, 0x11, 0x12, 0x20, 0x01, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x30, 0x12, 0x20, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x20, 0x2C, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x0F, 
  0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x12, 0x20, 
  0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x30, 
  0x11, 0x13, 0x20, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x10, 0x30, 0x20, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x10, 0x32, 0x20, 0x42, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x10, 0x31, 0x20, 0x00, 0x01, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x13, 0x30, 0x11, 0x12, 0x20, 0x01, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x30, 0x12, 
  0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 
  0x20, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 
  0x40, 0x0F, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 
  0x12, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x13, 0x30, 0x11, 0x13, 0x20, 0x50, 0x01, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x13, 0x30, 0x11, 0x13, 0x40, 0x0E, 0x98, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x30, 0x12, 0x20, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x20, 
  0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 
  0x0F, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 
  0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x20, 
  0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x20, 
  0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x20, 
  0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x20, 
  0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x20, 
  0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x20, 
  0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x20, 
  0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x20, 
  0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x80, 
  0x20, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x20, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]

fp = open('./codes.asm','wb')
def getQword(i):
    return (codes[i+1] + (codes[i+2] << 8) + (codes[i+3] << 16) + (codes[i+4] << 32) + (codes[i+5] << 8) +
    (codes[i+6] << 40) + (codes[i+7] << 48)  + (codes[i+8] << 56))

i = 0
while i < len(codes):
    if codes[i] == 0xE:
        fp.write(b"cmp rflag,1\nje $ + "+ hex(getQword(i)).encode() + b"\n")
        i+=9
    elif codes[i] == 0xF:
        fp.write(b"cmp rflag,1\nje $ - "+ hex(getQword(i)).encode() + b"\n")
        i+=9
    elif codes[i] == 0x10:
        fp.write(b"push reg\n")
        i+=1
    elif codes[i] == 0x11:
        fp.write(b"pop reg\n")
        i+=1
    elif codes[i] == 0x12:
        fp.write(b"pop mem[reg]\n")
        i+=1
    elif codes[i] == 0x13:
        fp.write(b"push mem[reg]\n")
        i+=1
    elif codes[i] == 0x20:
        fp.write(b"mov reg," + hex(getQword(i)).encode()+ b"\n")
        i+=9
    elif codes[i] == 0x21:
        fp.write(b"mov reg,getchar()\n")
        i+=1
    elif codes[i] == 0x22:
        fp.write(b"putchar(reg)\n")
        i+=1
    elif codes[i] == 0x30:
        fp.write(b"sadd\n")
        i+=1
    elif codes[i] == 0x31:
        fp.write(b"ssub\n")
        i+=1
    elif codes[i] == 0x32:
        fp.write(b"sxor\n")
        i+=1
    elif codes[i] == 0x33:
        fp.write(b"slhs\n")
        i+=1
    elif codes[i] == 0x34:
        fp.write(b"srhs\n")
        i+=1
    elif codes[i] == 0x40:
        fp.write(b"scmp\n")
        i+=1
    elif codes[i] == 0x80:
        fp.write(b"end\n")
        break

生成的偽彙編程式碼如下:

mov reg,0x57
putchar(reg)
mov reg,0x65
putchar(reg)
mov reg,0x31
putchar(reg)
mov reg,0x63
putchar(reg)
mov reg,0x30
putchar(reg)
mov reg,0x6d
putchar(reg)
mov reg,0x65
putchar(reg)
mov reg,0xa
putchar(reg)
mov reg,0x50
putchar(reg)
mov reg,0x6c
putchar(reg)
mov reg,0x65
putchar(reg)
mov reg,0x61
putchar(reg)
mov reg,0x73
putchar(reg)
mov reg,0x65
putchar(reg)
mov reg,0x20
putchar(reg)
mov reg,0x49
putchar(reg)
mov reg,0x6e
putchar(reg)
mov reg,0x50
putchar(reg)
mov reg,0x75
putchar(reg)
mov reg,0x74
putchar(reg)
mov reg,0x3a
putchar(reg)
mov reg,0xa
putchar(reg)
;print Welc0me Input


mov reg,0x0
push reg
pop mem[reg]


;將UserInput字元串放入memory+0x100處
loop_1:
mov reg,getchar()
push reg
mov reg,0x100
push reg
mov reg,0x0
push mem[reg]
;stack men[reg] 0x100 UserInput
sadd
pop reg
pop mem[reg]
mov reg,0x1
push reg
mov reg,0x0
push mem[reg]
sadd
pop mem[reg]
mov reg,0x0
push mem[reg]
mov reg,0x2c
push reg
scmp
cmp rflag,1 ;循環夠0x2C次
je loop_1



mov reg,0x0
push reg
pop mem[reg]



loop_2:
mov reg,0x100
push reg
mov reg,0x0
push mem[reg]
sadd
pop reg
push mem[reg];pointer to UserInput
mov reg,0x66
push reg
sadd
mov reg,0x36
push reg
sxor
mov reg,0x42
push reg
ssub
mov reg,0x100
push reg
mov reg,0x0
push mem[reg]
sadd
pop reg
pop mem[reg]
;enc_flag[i] == (UserInput[i] + 0x66)^0x36-0x42
mov reg,0x1
push reg
mov reg,0x0
push mem[reg]
sadd
pop mem[reg]
mov reg,0x0
push mem[reg]
mov reg,0x2c
push reg
scmp
cmp rflag,1
je loop_2



mov reg,0x0
push reg
pop mem[reg]
mov reg,0x100
push reg
mov reg,0x0
push mem[reg]
sadd
pop reg
push mem[reg]
mov reg,0x150
push reg
mov reg,0x0
push mem[reg]
sadd
pop reg
push mem[reg]
scmp
cmp rflag,1
je $ + 0x98
mov reg,0x1
push reg
mov reg,0x0
push mem[reg]
sadd
pop mem[reg]
mov reg,0x0
push mem[reg]
mov reg,0x2c
push reg
scmp
cmp rflag,1
je $ - 0x63


;print Success
mov reg,0x53
putchar(reg)
mov reg,0x75
putchar(reg)
mov reg,0x63
putchar(reg)
mov reg,0x63
putchar(reg)
mov reg,0x65
putchar(reg)
mov reg,0x73
putchar(reg)
mov reg,0x73
putchar(reg)
mov reg,0x21
putchar(reg)
mov reg,0xa
putchar(reg)
end

加密過程很簡單,注釋已經寫好在偽彙編程式碼里了,直接寫腳本得flag:

#include<iostream>
#include<string>
using namespace std;

int enc_flag[]= {171,160,189,170,184,149,74,86,87,77,177,72,67,177,183,173,177,92,187,170,170,187,172,177,74,182,175,160,177,71,66,92,121,173,177,91,107,177,108,104,107,94,147,4,0};
string flag;
int main(void){
  for(int i = 0;i < 0x2c; ++i){
    flag += ((enc_flag[i]+0x42)^0x36)-0x66;
  }
  cout << flag << endl;
  return 0;
}

flag: unctf{THIS_VM_is_Better_Than_YLB’s_E5_2650}